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

3612 lines
91 KiB
C
Raw Normal View History

2022-02-16 09:13:02 -06:00
/*
* dp.c: tegra dp driver.
*
* 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/err.h>
#include <linux/gpio.h>
#include <linux/wait.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/seq_file.h>
#include <linux/debugfs.h>
#include <linux/version.h>
#include <soc/tegra/chip-id.h>
#include <linux/clk/tegra.h>
#include <linux/moduleparam.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/ctype.h>
#include <linux/extcon/extcon-disp.h>
#include <linux/extcon.h>
#ifdef CONFIG_SWITCH
#include <linux/switch.h>
#endif
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0)
#include <linux/uaccess.h>
#include <linux/miscdevice.h>
#else
#include <asm/uaccess.h>
#endif
#include "dc.h"
#include "dp.h"
#include "sor.h"
#include "sor_regs.h"
#include "dpaux_regs.h"
#include "dpaux.h"
#include "dc_priv.h"
#include "edid.h"
#include "hdcp/dphdcp.h"
#include "dp_lt.h"
#include "dp_auto.h"
#include "hda_dc.h"
#include "fake_panel.h"
#include <linux/tegra_prod.h>
#include "bridge/hdmi2fpd_ds90uh949.h"
static bool tegra_dp_debug = true;
module_param(tegra_dp_debug, bool, 0644);
MODULE_PARM_DESC(tegra_dp_debug, "Enable to print all link configs");
/*
* WAR for DPR-120 firmware v1.9[r6] limitation for CTS 400.3.2.*
* The analyzer issues IRQ_EVENT while we are still link training.
* Not expected but analyzer limitation.
* Ongoing link training confuses the analyzer leading to false failure.
* The WAR eludes link training during unblank. This keeps the purpose
* of CTS intact within analyzer limitation.
*/
static bool no_lt_at_unblank = false;
module_param(no_lt_at_unblank, bool, 0644);
MODULE_PARM_DESC(no_lt_at_unblank, "DP enabled but link not trained");
static struct tegra_hpd_ops hpd_ops;
static int dp_instance;
static void tegra_dc_dp_debugfs_create(struct tegra_dc_dp_data *dp);
static void tegra_dc_dp_debugfs_remove(struct tegra_dc_dp_data *dp);
static int tegra_dp_init_max_link_cfg(struct tegra_dc_dp_data *dp,
struct tegra_dc_dp_link_config *cfg);
static inline void tegra_dp_default_int(struct tegra_dc_dp_data *dp,
bool enable);
__maybe_unused
static void tegra_dp_hpd_config(struct tegra_dc_dp_data *dp);
static inline void tegra_dp_clk_enable(struct tegra_dc_dp_data *dp)
{
tegra_disp_clk_prepare_enable(dp->parent_clk);
}
static inline void tegra_dp_clk_disable(struct tegra_dc_dp_data *dp)
{
tegra_disp_clk_disable_unprepare(dp->parent_clk);
}
static inline void tegra_dp_enable_irq(u32 irq)
{
enable_irq(irq);
}
static inline void tegra_dp_disable_irq(u32 irq)
{
disable_irq(irq);
}
static inline void tegra_dp_pending_hpd(struct tegra_dc_dp_data *dp)
{
if (!is_hotplug_supported(dp))
return;
tegra_hpd_set_pending_evt(&dp->hpd_data);
}
static inline void tegra_dp_hpd_suspend(struct tegra_dc_dp_data *dp)
{
if (!is_hotplug_supported(dp))
return;
tegra_hpd_suspend(&dp->hpd_data);
}
int tegra_dc_dp_dpcd_read(struct tegra_dc_dp_data *dp, u32 cmd,
u8 *data_ptr)
{
u32 size = 1;
u32 status = 0;
int ret = 0;
if (dp->dc->out->type == TEGRA_DC_OUT_FAKE_DP)
return ret;
mutex_lock(&dp->dpaux->lock);
tegra_dpaux_get(dp->dpaux);
ret = tegra_dc_dpaux_read_chunk_locked(dp->dpaux,
DPAUX_DP_AUXCTL_CMD_AUXRD, cmd, data_ptr,
&size, &status);
tegra_dpaux_put(dp->dpaux);
mutex_unlock(&dp->dpaux->lock);
if (ret)
dev_err(&dp->dc->ndev->dev,
"dp: Failed to read DPCD data. CMD 0x%x, Status 0x%x\n",
cmd, status);
return ret;
}
static int tegra_dc_dp_i2c_xfer(struct tegra_dc *dc, struct i2c_msg *msgs,
int num)
{
struct i2c_msg *pmsg;
int i;
u32 aux_stat;
int status = 0;
u32 len = 0;
struct tegra_dc_dp_data *dp = tegra_dc_get_outdata(dc);
/* No physical panel and/or emulator is attached in simulation. */
if (tegra_platform_is_sim())
return -EINVAL;
for (i = 0; i < num; ++i) {
pmsg = &msgs[i];
if (!pmsg->flags) {
len = pmsg->len;
status = tegra_dc_dpaux_i2c_write(dp->dpaux,
DPAUX_DP_AUXCTL_CMD_MOTWR,
pmsg->addr, pmsg->buf, &len, &aux_stat);
if (status) {
dev_err(&dp->dc->ndev->dev,
"dp: Failed for I2C write"
" addr:%d, size:%d, stat:0x%x\n",
pmsg->addr, len, aux_stat);
return status;
}
} else if (pmsg->flags & I2C_M_RD) {
len = pmsg->len;
status = tegra_dc_dpaux_i2c_read(dp->dpaux, pmsg->addr,
pmsg->buf, &len, &aux_stat);
if (status) {
dev_err(&dp->dc->ndev->dev,
"dp: Failed for I2C read"
" addr:%d, size:%d, stat:0x%x\n",
pmsg->addr, len, aux_stat);
return status;
}
} else {
dev_err(&dp->dc->ndev->dev,
"dp: i2x_xfer: Invalid i2c flag 0x%x\n",
pmsg->flags);
return -EINVAL;
}
}
return i;
}
static i2c_transfer_func_t tegra_dp_hpd_op_edid_read(void *drv_data)
{
struct tegra_dc_dp_data *dp = drv_data;
return (dp->edid_src == EDID_SRC_DT) ?
tegra_dc_edid_blob : tegra_dc_dp_i2c_xfer;
}
int tegra_dc_dp_dpcd_write(struct tegra_dc_dp_data *dp, u32 cmd,
u8 data)
{
u32 size = 1;
u32 status = 0;
int ret;
if (dp->dc->out->type == TEGRA_DC_OUT_FAKE_DP)
return 0;
mutex_lock(&dp->dpaux->lock);
tegra_dpaux_get(dp->dpaux);
ret = tegra_dc_dpaux_write_chunk_locked(dp->dpaux,
DPAUX_DP_AUXCTL_CMD_AUXWR, cmd, &data, &size, &status);
tegra_dpaux_put(dp->dpaux);
mutex_unlock(&dp->dpaux->lock);
if (ret)
dev_err(&dp->dc->ndev->dev,
"dp: Failed to write DPCD data. CMD 0x%x, Status 0x%x\n",
cmd, status);
return ret;
}
int tegra_dp_dpcd_write_field(struct tegra_dc_dp_data *dp,
u32 cmd, u8 mask, u8 data)
{
u8 dpcd_data;
int ret;
if (dp->dc->out->type == TEGRA_DC_OUT_FAKE_DP)
return 0;
might_sleep();
ret = tegra_dc_dp_dpcd_read(dp, cmd, &dpcd_data);
if (ret)
return ret;
dpcd_data &= ~mask;
dpcd_data |= data;
ret = tegra_dc_dp_dpcd_write(dp, cmd, dpcd_data);
if (ret)
return ret;
return 0;
}
static inline u64 tegra_div64(u64 dividend, u32 divisor)
{
do_div(dividend, divisor);
return dividend;
}
static inline bool tegra_dp_is_audio_supported(struct tegra_dc_dp_data *dp)
{
if (tegra_edid_audio_supported(dp->hpd_data.edid)
&& tegra_dc_is_ext_panel(dp->dc) &&
dp->dc->out->type != TEGRA_DC_OUT_FAKE_DP)
return true;
else
return false;
}
#ifdef CONFIG_DEBUG_FS
static int dbg_dp_dpaux_show(struct seq_file *s, void *unused)
{
#define DUMP_REG(a) seq_printf(s, "%-32s %03x %08x\n", \
#a, a, tegra_dpaux_readl(dpaux, a))
struct tegra_dc_dp_data *dp = s->private;
struct tegra_dc_dpaux_data *dpaux = dp->dpaux;
tegra_dpaux_get(dpaux);
DUMP_REG(DPAUX_INTR_EN_AUX);
DUMP_REG(DPAUX_INTR_AUX);
DUMP_REG(DPAUX_DP_AUXADDR);
DUMP_REG(DPAUX_DP_AUXCTL);
DUMP_REG(DPAUX_DP_AUXSTAT);
DUMP_REG(DPAUX_HPD_CONFIG);
DUMP_REG(DPAUX_HPD_IRQ_CONFIG);
DUMP_REG(DPAUX_DP_AUX_CONFIG);
DUMP_REG(DPAUX_HYBRID_PADCTL);
DUMP_REG(DPAUX_HYBRID_SPARE);
tegra_dpaux_put(dpaux);
return 0;
}
static int dbg_dp_dpaux_open(struct inode *inode, struct file *file)
{
return single_open(file, dbg_dp_dpaux_show, inode->i_private);
}
static const struct file_operations dbg_fops = {
.open = dbg_dp_dpaux_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static int lane_count_show(struct seq_file *s, void *unused)
{
struct tegra_dc_dp_data *dp = s->private;
struct tegra_dc_dp_link_config *cfg = &dp->link_cfg;
seq_puts(s, "\n");
seq_printf(s,
"DP Lane_Count: \t%d\n",
cfg->lane_count);
return 0;
}
static ssize_t lane_count_set(struct file *file, const char __user *buf,
size_t count, loff_t *off)
{
struct seq_file *s = file->private_data;
struct tegra_dc_dp_data *dp = s->private;
struct tegra_dc_dp_link_config *cfg = &dp->link_cfg;
long lane_count = 0;
int ret = 0;
ret = kstrtol_from_user(buf, count, 10, &lane_count);
if (ret < 0)
return ret;
if (cfg->lane_count == lane_count)
return count;
dp->test_max_lanes = lane_count;
cfg->is_valid = false;
tegra_dp_init_max_link_cfg(dp, cfg);
return count;
}
static int lane_count_open(struct inode *inode, struct file *file)
{
return single_open(file, lane_count_show, inode->i_private);
}
static const struct file_operations lane_count_fops = {
.open = lane_count_open,
.read = seq_read,
.write = lane_count_set,
.llseek = seq_lseek,
.release = single_release,
};
static int link_speed_show(struct seq_file *s, void *unused)
{
struct tegra_dc_dp_data *dp = s->private;
struct tegra_dc_dp_link_config *cfg = &dp->link_cfg;
seq_puts(s, "\n");
seq_printf(s,
"DP Link Speed: \t%d\n",
cfg->link_bw);
return 0;
}
static ssize_t link_speed_set(struct file *file, const char __user *buf,
size_t count, loff_t *off)
{
struct seq_file *s = file->private_data;
struct tegra_dc_dp_data *dp = s->private;
struct tegra_dc_dp_link_config *cfg = &dp->link_cfg;
long link_speed = 0;
int ret = 0;
ret = kstrtol_from_user(buf, count, 10, &link_speed);
if (ret < 0)
return ret;
if (cfg->link_bw == link_speed)
return count;
dp->test_max_link_bw = link_speed;
cfg->is_valid = false;
tegra_dp_init_max_link_cfg(dp, cfg);
return count;
}
static int link_speed_open(struct inode *inode, struct file *file)
{
return single_open(file, link_speed_show, inode->i_private);
}
static const struct file_operations link_speed_fops = {
.open = link_speed_open,
.read = seq_read,
.write = link_speed_set,
.llseek = seq_lseek,
.release = single_release,
};
static int dbg_hotplug_show(struct seq_file *m, void *unused)
{
struct tegra_dc_dp_data *dp = m->private;
struct tegra_dc *dc = dp->dc;
if (WARN_ON(!dp || !dc || !dc->out))
return -EINVAL;
seq_printf(m, "dp hpd state: %d\n", dc->out->hotplug_state);
return 0;
}
static int dbg_hotplug_open(struct inode *inode, struct file *file)
{
return single_open(file, dbg_hotplug_show, inode->i_private);
}
/*
* sw control for hpd.
* 0 is normal state, hw drives hpd.
* -1 is force deassert, sw drives hpd.
* 1 is force assert, sw drives hpd.
* before releasing to hw, sw must ensure hpd state is normal i.e. 0
*/
static ssize_t dbg_hotplug_write(struct file *file, const char __user *addr,
size_t len, loff_t *pos)
{
struct seq_file *m = file->private_data; /* single_open() initialized */
struct tegra_dc_dp_data *dp = m->private;
struct tegra_dc_dpaux_data *dpaux = dp->dpaux;
struct tegra_dc *dc = dp->dc;
int ret;
long new_state;
if (WARN_ON(!dp || !dc || !dpaux || !dc->out))
return -EINVAL;
ret = kstrtol_from_user(addr, len, 10, &new_state);
if (ret < 0)
return ret;
if (dc->out->hotplug_state == TEGRA_HPD_STATE_NORMAL
&& new_state != TEGRA_HPD_STATE_NORMAL
&& dc->hotplug_supported) {
/* SW controlled hotplug. Ignore hpd HW interrupts. */
tegra_dpaux_int_toggle(dpaux, DPAUX_INTR_EN_AUX_PLUG_EVENT |
DPAUX_INTR_EN_AUX_UNPLUG_EVENT |
DPAUX_INTR_EN_AUX_PLUG_EVENT,
false);
} else if (dc->out->hotplug_state != TEGRA_HPD_STATE_NORMAL
&& new_state == TEGRA_HPD_STATE_NORMAL
&& dc->hotplug_supported) {
/* Enable hpd HW interrupts */
tegra_dpaux_int_toggle(dpaux, DPAUX_INTR_EN_AUX_PLUG_EVENT |
DPAUX_INTR_EN_AUX_UNPLUG_EVENT |
DPAUX_INTR_EN_AUX_PLUG_EVENT,
true);
}
dc->out->hotplug_state = new_state;
reinit_completion(&dc->hpd_complete);
tegra_dp_pending_hpd(dp);
wait_for_completion(&dc->hpd_complete);
return len;
}
static const struct file_operations dbg_hotplug_fops = {
.open = dbg_hotplug_open,
.read = seq_read,
.write = dbg_hotplug_write,
.llseek = seq_lseek,
.release = single_release,
};
static int bits_per_pixel_show(struct seq_file *s, void *unused)
{
struct tegra_dc_dp_data *dp = s->private;
struct tegra_dc_dp_link_config *cfg = NULL;
if (WARN_ON(!dp || !dp->dc || !dp->dc->out))
return -EINVAL;
cfg = &dp->link_cfg;
if (WARN_ON(!cfg))
return -EINVAL;
seq_puts(s, "\n");
seq_printf(s, "DP Bits Per Pixel: %u\n", cfg->bits_per_pixel);
return 0;
}
static ssize_t bits_per_pixel_set(struct file *file, const char __user *buf,
size_t count, loff_t *off)
{
struct seq_file *s = file->private_data;
struct tegra_dc_dp_data *dp = s->private;
struct tegra_dc_dpaux_data *dpaux = dp->dpaux;
struct tegra_dc_dp_link_config *cfg = NULL;
u32 bits_per_pixel = 0;
int ret = 0;
if (WARN_ON(!dp || !dp->dc || !dp->dc->out))
return -EINVAL;
ret = kstrtouint_from_user(buf, count, 10, &bits_per_pixel);
if (ret < 0)
return ret;
cfg = &dp->link_cfg;
if (WARN_ON(!cfg))
return -EINVAL;
if (cfg->bits_per_pixel == bits_per_pixel)
return count;
if ((bits_per_pixel == 18) || (bits_per_pixel == 24))
dev_info(&dp->dc->ndev->dev, "Setting the bits per pixel from %u to %u\n",
cfg->bits_per_pixel, bits_per_pixel);
else {
dev_info(&dp->dc->ndev->dev, "%ubpp is not supported. Restoring to %ubpp\n",
bits_per_pixel, cfg->bits_per_pixel);
return count;
}
tegra_dpaux_int_toggle(dpaux, DPAUX_INTR_EN_AUX_PLUG_EVENT |
DPAUX_INTR_EN_AUX_UNPLUG_EVENT |
DPAUX_INTR_EN_AUX_PLUG_EVENT,
false);
dp->dc->out->hotplug_state = TEGRA_HPD_STATE_FORCE_DEASSERT;
tegra_dp_pending_hpd(dp);
/* wait till HPD state machine has reached disable state */
msleep(HPD_DROP_TIMEOUT_MS + 500);
dp->dc->out->depth = bits_per_pixel;
tegra_dpaux_int_toggle(dpaux, DPAUX_INTR_EN_AUX_PLUG_EVENT |
DPAUX_INTR_EN_AUX_UNPLUG_EVENT |
DPAUX_INTR_EN_AUX_PLUG_EVENT,
true);
dp->dc->out->hotplug_state = TEGRA_HPD_STATE_NORMAL;
tegra_dp_pending_hpd(dp);
if (tegra_dp_is_audio_supported(dp)) {
disp_state_extcon_aux_report(dp->sor->ctrl_num,
EXTCON_DISP_AUX_STATE_DISABLED);
pr_info("Extcon AUX%d(DP): disable\n", dp->sor->ctrl_num);
usleep_range(1000, 1020);
disp_state_extcon_aux_report(dp->sor->ctrl_num,
EXTCON_DISP_AUX_STATE_ENABLED);
pr_info("Extcon AUX%d(DP): enable\n", dp->sor->ctrl_num);
}
#ifdef CONFIG_SWITCH
if (tegra_edid_audio_supported(dp->hpd_data.edid) &&
tegra_dc_is_ext_panel(dp->dc) &&
dp->dc->out->type != TEGRA_DC_OUT_FAKE_DP) {
switch_set_state(&dp->audio_switch, 0);
msleep(1);
pr_info("audio_switch toggle 0\n");
switch_set_state(&dp->audio_switch, 1);
pr_info("audio_switch toggle 1\n");
}
#endif
return count;
}
static int bits_per_pixel_open(struct inode *inode, struct file *file)
{
return single_open(file, bits_per_pixel_show, inode->i_private);
}
static const struct file_operations bits_per_pixel_fops = {
.open = bits_per_pixel_open,
.read = seq_read,
.write = bits_per_pixel_set,
.llseek = seq_lseek,
.release = single_release,
};
static inline void dpaux_print_data(struct seq_file *s, u8 *data, u32 size)
{
u8 row_size = 16;
u32 i, j;
for (i = 0; i < size; i += row_size) {
for (j = i; j < i + row_size && j < size; j++)
seq_printf(s, "%02x ", data[j]);
seq_puts(s, "\n");
}
}
static int dpaux_i2c_data_show(struct seq_file *s, void *unused)
{
struct tegra_dc_dp_data *dp = s->private;
u32 addr = dp->dpaux_i2c_dbg_addr;
u32 size = dp->dpaux_i2c_dbg_num_bytes;
u32 aux_stat;
u8 *data;
int ret;
data = kzalloc(size, GFP_KERNEL);
if (!data)
return -ENOMEM;
ret = tegra_dc_dpaux_i2c_read(dp->dpaux, addr, data, &size, &aux_stat);
if (ret) {
seq_printf(s, "Error reading %d bytes from I2C reg %x",
dp->dpaux_i2c_dbg_num_bytes,
dp->dpaux_i2c_dbg_addr);
goto free_mem;
}
dpaux_print_data(s, data, size);
free_mem:
kfree(data);
return ret;
}
static int dpaux_dpcd_data_show(struct seq_file *s, void *unused)
{
struct tegra_dc_dp_data *dp = s->private;
u32 addr = dp->dpaux_dpcd_dbg_addr;
u32 size = dp->dpaux_dpcd_dbg_num_bytes;
u32 i;
u8 *data;
int ret;
data = kzalloc(size, GFP_KERNEL);
if (!data)
return -ENOMEM;
tegra_dpaux_get(dp->dpaux);
for (i = 0; i < size; i++) {
ret = tegra_dc_dp_dpcd_read(dp, addr+i, data+i);
if (ret) {
seq_printf(s, "Reading %d bytes from reg %x; "
"Error at DPCD reg offset %x\n",
dp->dpaux_dpcd_dbg_num_bytes,
dp->dpaux_dpcd_dbg_addr,
addr+i);
goto free_mem;
}
}
dpaux_print_data(s, data, size);
free_mem:
tegra_dpaux_put(dp->dpaux);
kfree(data);
return ret;
}
static inline int dpaux_parse_input(const char __user *user_buf,
u8 *data, size_t count)
{
int size = 0;
u32 i = 0;
char tmp[3];
char *buf;
buf = kzalloc(count, GFP_KERNEL);
if (!buf)
return -ENOMEM;
if (copy_from_user(buf, user_buf, count)) {
size = -EINVAL;
goto free_mem;
}
/*
* Assumes each line of input is of the form: XX XX XX XX ...,
* where X represents one hex digit. You can have an arbitrary
* amount of whitespace between each XX.
*/
while (i + 1 < count) {
if (buf[i] == ' ') {
i += 1;
continue;
}
tmp[0] = buf[i]; tmp[1] = buf[i + 1]; tmp[2] = '\0';
if (kstrtou8(tmp, 16, data + size)) {
size = -EINVAL;
goto free_mem;
}
size += 1;
i += 2;
}
free_mem:
kfree(buf);
return size;
}
static ssize_t dpaux_i2c_data_set(struct file *file,
const char __user *user_buf, size_t count, loff_t *off)
{
struct seq_file *s = file->private_data;
struct tegra_dc_dp_data *dp = s->private;
int size = 0;
u32 aux_stat;
u32 addr = dp->dpaux_i2c_dbg_addr;
u8 *data;
int ret = count;
data = kzalloc(count, GFP_KERNEL);
if (!data)
return -ENOMEM;
size = dpaux_parse_input(user_buf, data, count);
if (size <= 0) {
ret = -EINVAL;
goto free_mem;
}
ret = tegra_dc_dpaux_i2c_write(dp->dpaux, DPAUX_DP_AUXCTL_CMD_I2CWR,
addr, data, &size, &aux_stat);
if (!ret)
ret = count;
else
ret = -EIO;
free_mem:
kfree(data);
return ret;
}
static ssize_t dpaux_dpcd_data_set(struct file *file,
const char __user *user_buf, size_t count, loff_t *off)
{
struct seq_file *s = file->private_data;
struct tegra_dc_dp_data *dp = s->private;
int size = 0;
u32 aux_stat;
u32 addr = dp->dpaux_dpcd_dbg_addr;
u8 *data;
int ret = count;
data = kzalloc(count, GFP_KERNEL);
if (!data)
return -ENOMEM;
size = dpaux_parse_input(user_buf, data, count);
if (size <= 0) {
ret = -EINVAL;
goto free_mem;
}
ret = tegra_dc_dpaux_write(dp->dpaux, DPAUX_DP_AUXCTL_CMD_AUXWR,
addr, data, &size, &aux_stat);
if (!ret)
ret = count;
else
ret = -EIO;
free_mem:
kfree(data);
return ret;
}
static int dpaux_i2c_data_open(struct inode *inode, struct file *file)
{
return single_open(file, dpaux_i2c_data_show, inode->i_private);
}
static int dpaux_dpcd_data_open(struct inode *inode, struct file *file)
{
return single_open(file, dpaux_dpcd_data_show, inode->i_private);
}
static const struct file_operations dpaux_i2c_data_fops = {
.open = dpaux_i2c_data_open,
.read = seq_read,
.write = dpaux_i2c_data_set,
.llseek = seq_lseek,
.release = single_release,
};
static const struct file_operations dpaux_dpcd_data_fops = {
.open = dpaux_dpcd_data_open,
.read = seq_read,
.write = dpaux_dpcd_data_set,
.llseek = seq_lseek,
.release = single_release,
};
static struct dentry *tegra_dpaux_i2c_dir_create(struct tegra_dc_dp_data *dp,
struct dentry *parent)
{
struct dentry *dpaux_i2c_dir;
struct dentry *retval = NULL;
dpaux_i2c_dir = debugfs_create_dir("dpaux_i2c", parent);
if (!dpaux_i2c_dir)
return retval;
retval = debugfs_create_u16("addr", 0644, dpaux_i2c_dir,
&dp->dpaux_i2c_dbg_addr);
if (!retval)
goto free_out;
retval = debugfs_create_u32("num_bytes", 0644,
dpaux_i2c_dir, &dp->dpaux_i2c_dbg_num_bytes);
if (!retval)
goto free_out;
retval = debugfs_create_file("data", 0444, dpaux_i2c_dir, dp,
&dpaux_i2c_data_fops);
if (!retval)
goto free_out;
return retval;
free_out:
debugfs_remove_recursive(dpaux_i2c_dir);
return retval;
}
static struct dentry *tegra_dpaux_dpcd_dir_create(struct tegra_dc_dp_data *dp,
struct dentry *parent)
{
struct dentry *dpaux_dir;
struct dentry *retval = NULL;
dpaux_dir = debugfs_create_dir("dpaux_dpcd", parent);
if (!dpaux_dir)
return retval;
retval = debugfs_create_u16("addr", 0644, dpaux_dir,
&dp->dpaux_dpcd_dbg_addr);
if (!retval)
goto free_out;
retval = debugfs_create_u32("num_bytes", 0644,
dpaux_dir, &dp->dpaux_dpcd_dbg_num_bytes);
if (!retval)
goto free_out;
retval = debugfs_create_file("data", 0444, dpaux_dir, dp,
&dpaux_dpcd_data_fops);
if (!retval)
goto free_out;
return retval;
free_out:
debugfs_remove_recursive(dpaux_dir);
return retval;
}
static void tegra_dc_dp_debugfs_create(struct tegra_dc_dp_data *dp)
{
struct dentry *retval;
char debug_dirname[CHAR_BUF_SIZE_MAX];
snprintf(debug_dirname, sizeof(debug_dirname),
"tegra_dp%d", dp->dc->ndev->id);
dp->debugdir = debugfs_create_dir(debug_dirname, NULL);
if (!dp->debugdir) {
dev_err(&dp->dc->ndev->dev, "could not create %s debugfs\n",
debug_dirname);
return;
}
retval = debugfs_create_file("dpaux_regs", 0444, dp->debugdir, dp,
&dbg_fops);
if (!retval)
goto free_out;
retval = debugfs_create_file("lanes", 0444, dp->debugdir, dp,
&lane_count_fops);
if (!retval)
goto free_out;
retval = debugfs_create_file("linkspeed", 0444, dp->debugdir, dp,
&link_speed_fops);
if (!retval)
goto free_out;
retval = debugfs_create_file("bitsperpixel", 0444, dp->debugdir, dp,
&bits_per_pixel_fops);
if (!retval)
goto free_out;
retval = debugfs_create_file("test_settings", 0444, dp->debugdir, dp,
&test_settings_fops);
if (!retval)
goto free_out;
retval = tegra_dpaux_i2c_dir_create(dp, dp->debugdir);
if (!retval)
goto free_out;
retval = tegra_dpaux_dpcd_dir_create(dp, dp->debugdir);
if (!retval)
goto free_out;
/* hotplug not allowed for eDP */
if (is_hotplug_supported(dp)) {
retval = debugfs_create_file("hotplug", 0444, dp->debugdir,
dp, &dbg_hotplug_fops);
if (!retval)
goto free_out;
}
return;
free_out:
dev_err(&dp->dc->ndev->dev, "could not create %s debugfs\n",
debug_dirname);
tegra_dc_dp_debugfs_remove(dp);
}
static void tegra_dc_dp_debugfs_remove(struct tegra_dc_dp_data *dp)
{
debugfs_remove_recursive(dp->debugdir);
dp->debugdir = NULL;
}
#else
static void tegra_dc_dp_debugfs_create(struct tegra_dc_dp_data *dp)
{ }
static void tegra_dc_dp_debugfs_remove(struct tegra_dc_dp_data *dp)
{ }
#endif
static void tegra_dp_dpaux_enable(struct tegra_dc_dp_data *dp)
{
struct tegra_dc_dpaux_data *dpaux = dp->dpaux;
tegra_dpaux_config_pad_mode(dpaux, TEGRA_DPAUX_PAD_MODE_AUX);
if (dp->dc->out->type != TEGRA_DC_OUT_FAKE_DP) {
tegra_dp_enable_irq(dp->irq);
tegra_dp_hpd_config(dp);
tegra_dp_default_int(dp, true);
}
}
static void tegra_dp_dpaux_disable(struct tegra_dc_dp_data *dp)
{
struct tegra_dc_dpaux_data *dpaux = dp->dpaux;
tegra_dpaux_pad_power(dpaux, false);
tegra_dpaux_put(dpaux);
if (dp->sor->safe_clk)
tegra_sor_safe_clk_disable(dp->sor);
}
static int tegra_dp_panel_power_state(struct tegra_dc_dp_data *dp, u8 state)
{
u32 retry = 0;
int ret;
do {
ret = tegra_dc_dp_dpcd_write(dp, NV_DPCD_SET_POWER, state);
} while ((state != NV_DPCD_SET_POWER_VAL_D3_PWRDWN) &&
(retry++ < DP_POWER_ON_MAX_TRIES) && ret);
return ret;
}
/* Calcuate if given cfg can meet the mode request. */
/* Return true if mode is possible, false otherwise. */
bool tegra_dc_dp_calc_config(struct tegra_dc_dp_data *dp,
const struct tegra_dc_mode *mode,
struct tegra_dc_dp_link_config *cfg)
{
const u32 link_rate = 27 * cfg->link_bw * 1000 * 1000;
const u64 f = 100000; /* precision factor */
u32 num_linkclk_line; /* Number of link clocks per line */
u64 ratio_f; /* Ratio of incoming to outgoing data rate */
u64 frac_f;
u64 activesym_f; /* Activesym per TU */
u64 activecount_f;
u32 activecount;
u32 activepolarity;
u64 approx_value_f;
u32 activefrac = 0;
u64 accumulated_error_f = 0;
u32 lowest_neg_activecount = 0;
u32 lowest_neg_activepolarity = 0;
u32 lowest_neg_tusize = 64;
u32 num_symbols_per_line;
u64 lowest_neg_activefrac = 0;
u64 lowest_neg_error_f = 64 * f;
u64 watermark_f;
int i;
bool neg;
unsigned long rate;
cfg->is_valid = false;
/* The pclk rate is fixed at 27 MHz on FPGA. */
if (tegra_dc_is_t19x() && tegra_platform_is_fpga())
rate = 27000000;
else
rate = tegra_dc_clk_get_rate(dp->dc);
if (!link_rate || !cfg->lane_count || !rate ||
!cfg->bits_per_pixel)
return false;
if ((u64)rate * cfg->bits_per_pixel >=
(u64)link_rate * 8 * cfg->lane_count) {
dev_dbg(&dp->dc->ndev->dev,
"Requested rate calc > link_rate calc\n");
return false;
}
num_linkclk_line = (u32)tegra_div64(
(u64)link_rate * mode->h_active, rate);
ratio_f = (u64)rate * cfg->bits_per_pixel * f;
ratio_f /= 8;
ratio_f = tegra_div64(ratio_f, link_rate * cfg->lane_count);
for (i = 64; i >= 32; --i) {
activesym_f = ratio_f * i;
activecount_f = tegra_div64(activesym_f, (u32)f) * f;
frac_f = activesym_f - activecount_f;
activecount = (u32)tegra_div64(activecount_f, (u32)f);
if (frac_f < (f / 2)) /* fraction < 0.5 */
activepolarity = 0;
else {
activepolarity = 1;
frac_f = f - frac_f;
}
if (frac_f != 0) {
frac_f = tegra_div64((f * f), frac_f); /* 1/fraction */
if (frac_f > (15 * f))
activefrac = activepolarity ? 1 : 15;
else
activefrac = activepolarity ?
(u32)tegra_div64(frac_f, (u32)f) + 1 :
(u32)tegra_div64(frac_f, (u32)f);
}
if (activefrac == 1)
activepolarity = 0;
if (activepolarity == 1)
approx_value_f = activefrac ? tegra_div64(
activecount_f + (activefrac * f - f) * f,
(activefrac * f)) :
activecount_f + f;
else
approx_value_f = activefrac ?
activecount_f + tegra_div64(f, activefrac) :
activecount_f;
if (activesym_f < approx_value_f) {
accumulated_error_f = num_linkclk_line *
tegra_div64(approx_value_f - activesym_f, i);
neg = true;
} else {
accumulated_error_f = num_linkclk_line *
tegra_div64(activesym_f - approx_value_f, i);
neg = false;
}
if ((neg && (lowest_neg_error_f > accumulated_error_f)) ||
(accumulated_error_f == 0)) {
lowest_neg_error_f = accumulated_error_f;
lowest_neg_tusize = i;
lowest_neg_activecount = activecount;
lowest_neg_activepolarity = activepolarity;
lowest_neg_activefrac = activefrac;
if (accumulated_error_f == 0)
break;
}
}
if (lowest_neg_activefrac == 0) {
cfg->activepolarity = 0;
cfg->active_count = lowest_neg_activepolarity ?
lowest_neg_activecount : lowest_neg_activecount - 1;
cfg->tu_size = lowest_neg_tusize;
cfg->active_frac = 1;
} else {
cfg->activepolarity = lowest_neg_activepolarity;
cfg->active_count = (u32)lowest_neg_activecount;
cfg->tu_size = lowest_neg_tusize;
cfg->active_frac = (u32)lowest_neg_activefrac;
}
dev_dbg(&dp->dc->ndev->dev,
"dp: sor configuration: polarity: %d active count: %d "
"tu size: %d, active frac: %d\n",
cfg->activepolarity, cfg->active_count, cfg->tu_size,
cfg->active_frac);
watermark_f = tegra_div64(ratio_f * cfg->tu_size * (f - ratio_f), f);
cfg->watermark = (u32)tegra_div64(watermark_f + lowest_neg_error_f,
f) + cfg->bits_per_pixel / 4 - 1;
num_symbols_per_line = (mode->h_active * cfg->bits_per_pixel) /
(8 * cfg->lane_count);
if (cfg->watermark > 30) {
dev_dbg(&dp->dc->ndev->dev,
"dp: sor setting: unable to get a good tusize, "
"force watermark to 30.\n");
cfg->watermark = 30;
return false;
} else if (cfg->watermark > num_symbols_per_line) {
dev_dbg(&dp->dc->ndev->dev,
"dp: sor setting: force watermark to the number "
"of symbols in the line.\n");
cfg->watermark = num_symbols_per_line;
return false;
}
/* Refer to dev_disp.ref for more information. */
/* # symbols/hblank = ((SetRasterBlankEnd.X + SetRasterSize.Width - */
/* SetRasterBlankStart.X - 7) * link_clk / pclk) */
/* - 3 * enhanced_framing - Y */
/* where Y = (# lanes == 4) 3 : (# lanes == 2) ? 6 : 12 */
cfg->hblank_sym = (int)tegra_div64((u64)(mode->h_back_porch +
mode->h_front_porch + mode->h_sync_width - 7)
* link_rate, rate)
- 3 * cfg->enhanced_framing - (12 / cfg->lane_count);
if (cfg->hblank_sym < 0)
cfg->hblank_sym = 0;
/* Refer to dev_disp.ref for more information. */
/* # symbols/vblank = ((SetRasterBlankStart.X - */
/* SetRasterBlankEen.X - 25) * link_clk / pclk) */
/* - Y - 1; */
/* where Y = (# lanes == 4) 12 : (# lanes == 2) ? 21 : 39 */
cfg->vblank_sym = (int)tegra_div64((u64)(mode->h_active - 25)
* link_rate, rate) - (36 / cfg->lane_count) - 4;
if (cfg->vblank_sym < 0)
cfg->vblank_sym = 0;
cfg->is_valid = true;
return true;
}
int tegra_dc_dp_read_ext_dpcd_caps(struct tegra_dc_dp_data *dp,
struct tegra_dc_dp_ext_dpcd_caps *ext_caps)
{
u8 dpcd_data = 0;
int ret = 0;
ext_caps->valid = false;
ret = tegra_dc_dp_dpcd_read(dp, NV_DPCD_TRAINING_AUX_RD_INTERVAL,
&dpcd_data);
if (ret || !(dpcd_data >> NV_DPCD_EXT_RECEIVER_CAP_FIELD_PRESENT_SHIFT))
return ret;
ret = tegra_dc_dp_dpcd_read(dp, NV_DPCD_REV_EXT_CAP,
&ext_caps->revision);
if (ret)
return ret;
ret = tegra_dc_dp_dpcd_read(dp, NV_DPCD_MAX_LINK_BANDWIDTH_EXT_CAP,
&ext_caps->max_link_bw);
if (ret)
return ret;
ext_caps->valid = true;
return ret;
}
int tegra_dc_dp_get_max_link_bw(struct tegra_dc_dp_data *dp)
{
struct tegra_dc_dp_link_config *cfg = &dp->link_cfg;
u8 max_link_bw = 0;
/*
* The max link bw supported is a product of everything below:
* 1) The max link bw supported by the DPRX.
* 2) If the DPRX advertises an additional max link bw value as part of
* the Extended Receiver Capability field, this value should override
* the value in #1.
* 3) The max link bw supported by the DPTX.
* 4) If test configuration parameters are set on the DPTX side, these
* parameters need to be accounted for as well.
*/
/* Constraint #1 */
if (dp->sink_cap_valid) {
max_link_bw = dp->sink_cap[NV_DPCD_MAX_LINK_BANDWIDTH];
} else if (tegra_dc_dp_dpcd_read(dp, NV_DPCD_MAX_LINK_BANDWIDTH,
&max_link_bw)) {
dev_err(&dp->dc->ndev->dev,
"dp: NV_DPCD_MAX_LINK_BANDWIDTH read failed\n");
return 0;
}
/* Constraint #2 */
if (tegra_dc_is_ext_panel(dp->dc) && !cfg->ext_dpcd_caps.valid) {
/* DPCD caps are already read in hpd worker, use them if they
* are valid. Also, use cached values for internal panels as
* they don't change during runtime */
if (tegra_dc_dp_read_ext_dpcd_caps(dp, &cfg->ext_dpcd_caps)) {
dev_err(&dp->dc->ndev->dev,
"dp: Failed to read ext DPCD caps\n");
return 0;
}
}
if (cfg->ext_dpcd_caps.valid)
max_link_bw = cfg->ext_dpcd_caps.max_link_bw;
/* Constraint #3 */
if (dp->pdata && dp->pdata->link_bw > 0)
max_link_bw = min(max_link_bw, (u8)dp->pdata->link_bw);
/* Constraint #4 */
if (dp->test_max_link_bw > 0)
max_link_bw = min(max_link_bw, (u8)dp->test_max_link_bw);
if (max_link_bw >= NV_DPCD_MAX_LINK_BANDWIDTH_VAL_8_10_GBPS)
max_link_bw = NV_DPCD_MAX_LINK_BANDWIDTH_VAL_8_10_GBPS;
else if (max_link_bw >= NV_DPCD_MAX_LINK_BANDWIDTH_VAL_5_40_GBPS)
max_link_bw = NV_DPCD_MAX_LINK_BANDWIDTH_VAL_5_40_GBPS;
else if (max_link_bw >= NV_DPCD_MAX_LINK_BANDWIDTH_VAL_2_70_GBPS)
max_link_bw = NV_DPCD_MAX_LINK_BANDWIDTH_VAL_2_70_GBPS;
else
max_link_bw = NV_DPCD_MAX_LINK_BANDWIDTH_VAL_1_62_GBPS;
return max_link_bw;
}
int tegra_dc_dp_get_max_lane_count(struct tegra_dc_dp_data *dp, u8 *dpcd_data)
{
u8 max_lane_count = 0;
/*
* The max lane count supported is a product of everything below:
* 1) The max lane count supported by the DPRX.
* 2) The # of connected lanes reported by the extcon provider (Type-C).
* 3) The max lane count supported by the DPTX.
* 4) If test configuration parameters are set on the DPTX side, these
* parameters need to be accounted for as well.
*/
/* Constraint #1 */
if (dp->sink_cap_valid) {
max_lane_count = dp->sink_cap[NV_DPCD_MAX_LANE_COUNT];
} else if (tegra_dc_dp_dpcd_read(dp, NV_DPCD_MAX_LANE_COUNT,
&max_lane_count)) {
dev_err(&dp->dc->ndev->dev,
"dp: NV_DPCD_MAX_LANE_COUNT read failed\n");
return 0;
}
*dpcd_data = max_lane_count;
max_lane_count = max_lane_count & NV_DPCD_MAX_LANE_COUNT_MASK;
/* Constraint #2 */
max_lane_count = min(max_lane_count, dp->typec_lane_count);
/* Constraint #3 */
if (dp->pdata && dp->pdata->lanes > 0)
max_lane_count = min(max_lane_count, (u8)dp->pdata->lanes);
/* Constraint #4 */
if (dp->test_max_lanes > 0)
max_lane_count = min(max_lane_count, (u8)dp->test_max_lanes);
if (max_lane_count >= 4)
max_lane_count = 4;
else if (max_lane_count >= 2)
max_lane_count = 2;
else
max_lane_count = 1;
return max_lane_count;
}
static inline u32 tegra_dp_get_bpp(struct tegra_dc_dp_data *dp, u32 vmode)
{
int yuv_flag = vmode & FB_VMODE_YUV_MASK;
if (yuv_flag == (FB_VMODE_Y422 | FB_VMODE_Y24)) {
return 16;
} else if (yuv_flag & (FB_VMODE_Y422 | FB_VMODE_Y420)) {
/* YUV 422 non 8bpc and YUV 420 modes are not supported in hw */
dev_err(&dp->dc->ndev->dev, "%s: Unsupported mode with vmode: 0x%x for DP\n",
__func__, vmode);
return 0;
} else if (yuv_flag & FB_VMODE_Y24) {
return 24;
} else if (yuv_flag & FB_VMODE_Y30) {
return 30;
} else if (yuv_flag & FB_VMODE_Y36) {
return 36;
} else {
dev_info(&dp->dc->ndev->dev, "%s: vmode=0x%x did not specify bpp\n",
__func__, vmode);
return dp->dc->out->depth ? dp->dc->out->depth : 24;
}
}
static int tegra_dc_init_default_panel_link_cfg(struct tegra_dc_dp_data *dp)
{
struct tegra_dc_dp_link_config *cfg = &dp->link_cfg;
if (!cfg->is_valid) {
if (dp->test_max_lanes > 0)
cfg->max_lane_count = dp->test_max_lanes;
else
cfg->max_lane_count = dp->pdata->lanes;
if (dp->test_max_link_bw > 0)
cfg->max_link_bw = dp->test_max_link_bw;
else
cfg->max_link_bw = dp->pdata->link_bw;
cfg->tps = TEGRA_DC_DP_TRAINING_PATTERN_2;
cfg->support_enhanced_framing = true;
cfg->downspread = true;
cfg->support_fast_lt = true;
cfg->aux_rd_interval = 0;
cfg->alt_scramber_reset_cap = true;
cfg->only_enhanced_framing = true;
cfg->edp_cap = true;
cfg->scramble_ena = 0;
cfg->lt_data_valid = 0;
}
return 0;
}
void tegra_dp_set_max_link_bw(struct tegra_dc_sor_data *sor,
struct tegra_dc_dp_link_config *cfg)
{
unsigned int key; /* Index into the link speed table */
for (key = sor->num_link_speeds - 1; key; key--)
if (cfg->max_link_bw >= sor->link_speeds[key].link_rate)
break;
cfg->max_link_bw = sor->link_speeds[key].link_rate;
}
static int tegra_dp_init_sink_link_cfg(struct tegra_dc_dp_data *dp,
struct tegra_dc_dp_link_config *cfg)
{
u8 dpcd_data = 0;
int ret = 0;
cfg->max_lane_count = tegra_dc_dp_get_max_lane_count(dp, &dpcd_data);
if (cfg->max_lane_count == 0) {
dev_err(&dp->dc->ndev->dev,
"dp: Invalid max lane count: %u\n", cfg->max_lane_count);
return -EINVAL;
}
if (dpcd_data & NV_DPCD_MAX_LANE_COUNT_TPS3_SUPPORTED_YES)
cfg->tps = TEGRA_DC_DP_TRAINING_PATTERN_3;
else
cfg->tps = TEGRA_DC_DP_TRAINING_PATTERN_2;
cfg->support_enhanced_framing =
(dpcd_data & NV_DPCD_MAX_LANE_COUNT_ENHANCED_FRAMING_YES) ?
true : false;
if (dp->sink_cap_valid) {
dpcd_data = dp->sink_cap[NV_DPCD_MAX_DOWNSPREAD];
} else {
ret = tegra_dc_dp_dpcd_read(dp, NV_DPCD_MAX_DOWNSPREAD,
&dpcd_data);
if (ret)
return ret;
}
/*
* The check for TPS4 should be after the check for TPS3. That helps
* assign a higher priority to TPS4.
*/
if (tegra_dc_is_t19x() &&
(dpcd_data & NV_DPCD_MAX_DOWNSPREAD_TPS4_SUPPORTED_YES))
cfg->tps = TEGRA_DC_DP_TRAINING_PATTERN_4;
cfg->downspread =
(dpcd_data & NV_DPCD_MAX_DOWNSPREAD_VAL_0_5_PCT) ?
true : false;
cfg->support_fast_lt = (dpcd_data &
NV_DPCD_MAX_DOWNSPREAD_NO_AUX_HANDSHAKE_LT_T) ?
true : false;
ret = tegra_dc_dp_dpcd_read(dp, NV_DPCD_TRAINING_AUX_RD_INTERVAL,
&dpcd_data);
if (ret)
return ret;
cfg->aux_rd_interval = dpcd_data &
NV_DPCD_TRAINING_AUX_RD_INTERVAL_MASK;
cfg->max_link_bw = tegra_dc_dp_get_max_link_bw(dp);
if (cfg->max_link_bw == 0) {
dev_err(&dp->dc->ndev->dev,
"dp: Invalid max link bw: %u\n", cfg->max_link_bw);
return -EINVAL;
}
tegra_dp_set_max_link_bw(dp->sor, cfg);
ret = tegra_dc_dp_dpcd_read(dp, NV_DPCD_EDP_CONFIG_CAP, &dpcd_data);
if (ret)
return ret;
cfg->alt_scramber_reset_cap =
(dpcd_data & NV_DPCD_EDP_CONFIG_CAP_ASC_RESET_YES) ?
true : false;
cfg->only_enhanced_framing = (dpcd_data &
NV_DPCD_EDP_CONFIG_CAP_FRAMING_CHANGE_YES) ?
true : false;
cfg->edp_cap = (dpcd_data &
NV_DPCD_EDP_CONFIG_CAP_DISPLAY_CONTROL_CAP_YES) ?
true : false;
ret = tegra_dc_dp_dpcd_read(dp, NV_DPCD_FEATURE_ENUM_LIST, &dpcd_data);
if (ret)
return ret;
cfg->support_vsc_ext_colorimetry = (dpcd_data &
NV_DPCD_FEATURE_ENUM_LIST_VSC_EXT_COLORIMETRY) ?
true : false;
return 0;
}
static int tegra_dp_init_max_link_cfg(struct tegra_dc_dp_data *dp,
struct tegra_dc_dp_link_config *cfg)
{
int ret = 0;
if (dp->dc->out->type == TEGRA_DC_OUT_FAKE_DP)
tegra_dc_init_default_panel_link_cfg(dp);
else
ret = tegra_dp_init_sink_link_cfg(dp, cfg);
if (ret)
return ret;
cfg->bits_per_pixel = tegra_dp_get_bpp(dp, dp->dc->mode.vmode);
cfg->lane_count = cfg->max_lane_count;
cfg->link_bw = cfg->max_link_bw;
cfg->enhanced_framing = cfg->only_enhanced_framing ?
cfg->support_enhanced_framing :
(dp->pdata->enhanced_framing_disable ?
false : cfg->support_enhanced_framing);
tegra_dc_dp_calc_config(dp, dp->mode, cfg);
dp->max_link_cfg = *cfg;
return 0;
}
static int tegra_dc_dp_set_assr(struct tegra_dc_dp_data *dp, bool ena)
{
int ret;
u8 dpcd_data = ena ?
NV_DPCD_EDP_CONFIG_SET_ASC_RESET_ENABLE :
NV_DPCD_EDP_CONFIG_SET_ASC_RESET_DISABLE;
ret = tegra_dc_dp_dpcd_write(dp, NV_DPCD_EDP_CONFIG_SET, dpcd_data);
if (ret)
return ret;
/* Also reset the scrambler to 0xfffe */
tegra_dc_sor_set_internal_panel(dp->sor, ena);
return 0;
}
static int tegra_dp_set_link_bandwidth(struct tegra_dc_dp_data *dp, u8 link_bw)
{
tegra_dc_sor_set_link_bandwidth(dp->sor, link_bw);
/* Sink side */
return tegra_dc_dp_dpcd_write(dp, NV_DPCD_LINK_BANDWIDTH_SET, link_bw);
}
int tegra_dp_set_enhanced_framing(struct tegra_dc_dp_data *dp, bool enable)
{
int ret;
tegra_sor_write_field(dp->sor,
NV_SOR_DP_LINKCTL(dp->sor->portnum),
NV_SOR_DP_LINKCTL_ENHANCEDFRAME_ENABLE,
(enable ? NV_SOR_DP_LINKCTL_ENHANCEDFRAME_ENABLE :
NV_SOR_DP_LINKCTL_ENHANCEDFRAME_DISABLE));
ret = tegra_dp_dpcd_write_field(dp, NV_DPCD_LANE_COUNT_SET,
NV_DPCD_LANE_COUNT_SET_ENHANCEDFRAMING_T,
(enable ? NV_DPCD_LANE_COUNT_SET_ENHANCEDFRAMING_T :
NV_DPCD_LANE_COUNT_SET_ENHANCEDFRAMING_F));
if (ret)
return ret;
return 0;
}
static int tegra_dp_set_lane_count(struct tegra_dc_dp_data *dp, u8 lane_cnt)
{
int ret;
tegra_sor_power_lanes(dp->sor, lane_cnt, true);
ret = tegra_dp_dpcd_write_field(dp, NV_DPCD_LANE_COUNT_SET,
NV_DPCD_LANE_COUNT_SET_MASK,
lane_cnt);
if (ret)
return ret;
return 0;
}
/*
* Get the index of a certain link speed in the link speed table that has a
* given link rate
*/
static inline unsigned int tegra_dp_link_speed_get(struct tegra_dc_dp_data *dp,
u8 link_rate)
{
unsigned int key;
for (key = 0; key < dp->sor->num_link_speeds; key++)
if (dp->sor->link_speeds[key].link_rate == link_rate)
break;
return key;
}
static void tegra_dp_config_common_prods(struct tegra_dc_dp_data *dp)
{
if (!IS_ERR_OR_NULL(dp->prod_list)) {
int err = 0;
err = tegra_prod_set_by_name(&dp->sor->base, "prod_c_dp",
dp->prod_list);
if (err)
dev_warn(&dp->dc->ndev->dev,
"dp: prod set failed\n");
}
}
static void tegra_dp_link_cal(struct tegra_dc_dp_data *dp)
{
struct tegra_dc_dp_link_config *cfg = &dp->link_cfg;
int err = 0;
char *prop = NULL;
unsigned int key; /* Index into the link speed table */
if (IS_ERR_OR_NULL(dp->prod_list))
return;
key = tegra_dp_link_speed_get(dp, cfg->link_bw);
if (WARN_ON(key == dp->sor->num_link_speeds))
return;
prop = dp->sor->link_speeds[key].prod_prop;
err = tegra_prod_set_by_name(&dp->sor->base, prop, dp->prod_list);
if (err == -ENODEV) {
dev_info(&dp->dc->ndev->dev,
"DP: no %s prod settings node in device tree\n", prop);
} else if (err) {
dev_warn(&dp->dc->ndev->dev, "DP : Prod set failed\n");
}
}
static void tegra_dp_irq_evt_worker(struct work_struct *work)
{
#define LANE0_1_CR_CE_SL_MASK (0x7 | (0x7 << 4))
#define LANE0_CR_CE_SL_MASK (0x7)
#define INTERLANE_ALIGN_MASK (0x1)
#define DPCD_LINK_SINK_STATUS_REGS 6
struct tegra_dc_dp_data *dp = container_of(to_delayed_work(work),
struct tegra_dc_dp_data,
irq_evt_dwork);
struct tegra_dc *dc = dp->dc;
struct tegra_dc_dpaux_data *dpaux = dp->dpaux;
u32 aux_stat;
bool link_stable = !!true;
u8 dpcd_200h_205h[DPCD_LINK_SINK_STATUS_REGS] = {0, 0, 0, 0, 0, 0};
u32 n_lanes = dp->lt_data.n_lanes;
tegra_dpaux_get(dpaux);
aux_stat = tegra_dpaux_readl(dpaux, DPAUX_DP_AUXSTAT);
if (aux_stat & DPAUX_DP_AUXSTAT_SINKSTAT_ERROR_PENDING) {
int cnt;
/*
* HW failed to automatically read DPCD
* offsets 0x200-0x205. Initiate SW transaction.
*/
for (cnt = 0; cnt < DPCD_LINK_SINK_STATUS_REGS; cnt++) {
tegra_dc_dp_dpcd_read(dp, NV_DPCD_SINK_COUNT + cnt,
&dpcd_200h_205h[cnt]);
}
} else {
u32 aux_sinkstat_lo = tegra_dpaux_readl(dpaux,
DPAUX_DP_AUX_SINKSTATLO);
u32 aux_sinkstat_hi = tegra_dpaux_readl(dpaux,
DPAUX_DP_AUX_SINKSTATHI);
dpcd_200h_205h[0] = aux_sinkstat_lo & 0xff;
dpcd_200h_205h[1] = (aux_sinkstat_lo >> 8) & 0xff;
dpcd_200h_205h[2] = (aux_sinkstat_lo >> 16) & 0xff;
dpcd_200h_205h[3] = (aux_sinkstat_lo >> 24) & 0xff;
dpcd_200h_205h[4] = aux_sinkstat_hi & 0xff;
dpcd_200h_205h[5] = (aux_sinkstat_hi >> 8) & 0xff;
}
switch (n_lanes) {
case 4:
link_stable &= !!((dpcd_200h_205h[3] &
LANE0_1_CR_CE_SL_MASK) ==
LANE0_1_CR_CE_SL_MASK);
/* fall through */
case 2:
link_stable &= !!((dpcd_200h_205h[2] &
LANE0_1_CR_CE_SL_MASK) ==
LANE0_1_CR_CE_SL_MASK);
/* fall through */
case 1:
link_stable &= !!((dpcd_200h_205h[2] &
LANE0_CR_CE_SL_MASK) ==
LANE0_CR_CE_SL_MASK);
/* fall through */
default:
link_stable &= !!(dpcd_200h_205h[4] &
INTERLANE_ALIGN_MASK);
}
if (dpcd_200h_205h[1] &
NV_DPCD_DEVICE_SERVICE_IRQ_VECTOR_AUTO_TEST_YES) {
enum auto_test_requests test_rq;
test_rq = tegra_dp_auto_get_test_rq(dp);
tegra_dp_auto_is_test_supported(test_rq) ?
tegra_dp_auto_ack_test_rq(dp) :
tegra_dp_auto_nack_test_rq(dp);
if (test_rq == TEST_LINK_TRAINING) {
dp->lt_data.force_trigger = true;
link_stable = false;
}
}
if (!link_stable) {
int ret = 0;
ret = tegra_dc_reserve_common_channel(dc);
if (ret) {
dev_err(&dc->ndev->dev,
"%s: DC %d reserve failed during DP IRQ\n",
__func__, dc->ctrl_num);
goto done;
}
mutex_lock(&dc->lock);
tegra_dp_lt_set_pending_evt(&dp->lt_data);
ret = tegra_dp_lt_wait_for_completion(&dp->lt_data,
STATE_DONE_PASS, LT_TIMEOUT_MS);
mutex_unlock(&dc->lock);
tegra_dc_release_common_channel(dc);
if (!ret)
dev_err(&dc->ndev->dev,
"dp: link training after IRQ failed\n");
} else {
dev_info(&dc->ndev->dev,
"dp: link stable, ignore irq event\n");
}
done:
tegra_dpaux_put(dpaux);
#undef LANE0_1_CR_CE_SL_MASK
#undef LANE0_CR_CE_SL_MASK
#undef INTERLANE_ALIGN_MASK
#undef DPCD_LINK_SINK_STATUS_REGS
}
static inline struct tegra_dc_extcon_cable
*tegra_dp_get_typec_ecable(struct tegra_dc *dc)
{
if (!dc || !dc->out || !dc->out->dp_out)
return NULL;
return &dc->out->dp_out->typec_ecable;
}
static void tegra_dp_wait_for_typec_connect(struct tegra_dc_dp_data *dp)
{
#if KERNEL_VERSION(4, 9, 0) <= LINUX_VERSION_CODE
struct tegra_dc_extcon_cable *typec_ecable;
struct tegra_dc *dc;
union extcon_property_value lane_count = {0};
int ret;
bool ecable_connected;
if (!dp || !dp->dc) {
pr_err("%s: all arguments must be non-NULL!\n", __func__);
return;
}
dc = dp->dc;
if (dc->out->type == TEGRA_DC_OUT_FAKE_DP)
return;
typec_ecable = tegra_dp_get_typec_ecable(dc);
if (!typec_ecable || !typec_ecable->edev)
return;
mutex_lock(&typec_ecable->lock);
ecable_connected = typec_ecable->connected;
if (!ecable_connected)
reinit_completion(&typec_ecable->comp);
mutex_unlock(&typec_ecable->lock);
if (!ecable_connected) {
/*
* See the comment above these fields in dp.h for why this is
* required.
*/
if (unlikely(!dp->typec_notified_once &&
dp->typec_timed_out_once))
return;
if (!wait_for_completion_timeout(&typec_ecable->comp,
msecs_to_jiffies(1000))) {
dev_info(&dc->ndev->dev,
"dp: typec extcon wait timeout\n");
dp->typec_timed_out_once = true;
goto typec_lane_count_err;
}
}
ret = extcon_get_property(typec_ecable->edev, EXTCON_DISP_DP,
EXTCON_PROP_DISP_DP_LANE, &lane_count);
if (ret) {
dev_err(&dc->ndev->dev,
"dp: extcon get lane prop error - ret=%d\n", ret);
goto typec_lane_count_err;
}
if (lane_count.intval > 0)
dp->typec_lane_count = lane_count.intval;
return;
typec_lane_count_err:
dp->typec_lane_count = 4;
#endif
}
static int tegra_dp_typec_ecable_notifier(struct notifier_block *nb,
unsigned long event, void *data)
{
struct tegra_dc_extcon_cable *typec_ecable =
container_of(nb, struct tegra_dc_extcon_cable, nb);
struct tegra_dc_dp_data *dp =
(struct tegra_dc_dp_data *)typec_ecable->drv_data;
/* See the comment above this field in dp.h for why this is required. */
dp->typec_notified_once = true;
mutex_lock(&typec_ecable->lock);
typec_ecable->connected = !!event;
if (event) /* connected */
complete(&typec_ecable->comp);
mutex_unlock(&typec_ecable->lock);
return NOTIFY_DONE;
}
static int tegra_dp_register_typec_ecable(struct tegra_dc_dp_data *dp)
{
struct tegra_dc_extcon_cable *typec_ecable;
int ret;
#if KERNEL_VERSION(4, 9, 0) <= LINUX_VERSION_CODE
union extcon_property_value lane_count = {0};
int init_cable_state;
#endif
if (!dp || !dp->dc) {
pr_err("%s: all arguments must be non-NULL!\n", __func__);
return -EINVAL;
}
/* Assume that all TX lanes are dedicated for DP by default. */
dp->typec_lane_count = 4;
dp->typec_notified_once = false;
dp->typec_timed_out_once = false;
typec_ecable = tegra_dp_get_typec_ecable(dp->dc);
if (!typec_ecable || !typec_ecable->edev)
return 0;
mutex_init(&typec_ecable->lock);
init_completion(&typec_ecable->comp);
typec_ecable->drv_data = dp;
typec_ecable->connected = false;
typec_ecable->nb.notifier_call = tegra_dp_typec_ecable_notifier;
ret = extcon_register_notifier(typec_ecable->edev, EXTCON_DISP_DP,
&typec_ecable->nb);
if (ret < 0) {
dev_err(&dp->dc->ndev->dev,
"dp: typec extcon notifier registration failed\n");
return ret;
}
#if KERNEL_VERSION(4, 9, 0) <= LINUX_VERSION_CODE
/*
* Query the initial Type-C cable state here in case ucsi_ccg updated it
* before we were able to register the extcon notifier.
*/
mutex_lock(&typec_ecable->lock);
init_cable_state = extcon_get_state(typec_ecable->edev, EXTCON_DISP_DP);
if (init_cable_state < 0) {
dev_err(&dp->dc->ndev->dev,
"dp: failed to get initial cable state\n");
} else if (init_cable_state) { /* connected */
ret = extcon_get_property(typec_ecable->edev, EXTCON_DISP_DP,
EXTCON_PROP_DISP_DP_LANE, &lane_count);
if (ret) {
dev_err(&dp->dc->ndev->dev,
"dp: failed to get initial lane count\n");
} else if (lane_count.intval > 0) {
typec_ecable->connected = true;
dp->typec_lane_count = lane_count.intval;
}
}
mutex_unlock(&typec_ecable->lock);
#endif
return 0;
}
static void tegra_dp_unregister_typec_ecable(struct tegra_dc *dc)
{
struct tegra_dc_extcon_cable *typec_ecable;
int ret;
if (!dc) {
pr_err("%s: all arguments must be non-NULL!\n", __func__);
return;
}
typec_ecable = tegra_dp_get_typec_ecable(dc);
if (!typec_ecable || !typec_ecable->edev)
return;
ret = extcon_unregister_notifier(typec_ecable->edev, EXTCON_DISP_DP,
&typec_ecable->nb);
if (ret < 0)
dev_err(&dc->ndev->dev,
"dp: typec extcon notifier unregistration failed\n");
typec_ecable->drv_data = NULL;
typec_ecable->connected = false;
}
static irqreturn_t tegra_dp_irq(int irq, void *ptr)
{
struct tegra_dc_dp_data *dp = ptr;
struct tegra_dc_dpaux_data *dpaux = dp->dpaux;
struct tegra_dc *dc = dp->dc;
u32 status;
if (!dpaux) {
dev_err(&dc->ndev->dev,
"%s: must be non-NULL\n", __func__);
return IRQ_HANDLED;
}
if (dp->dc->out->type == TEGRA_DC_OUT_FAKE_DP)
return IRQ_HANDLED;
if (dp->suspended) {
dev_info(&dc->ndev->dev,
"dp: irq received while suspended, ignoring\n");
return IRQ_HANDLED;
}
tegra_dpaux_get(dpaux);
/* clear pending bits */
status = tegra_dpaux_readl(dpaux, DPAUX_INTR_AUX);
tegra_dpaux_writel(dpaux, DPAUX_INTR_AUX, status);
tegra_dpaux_put(dpaux);
if (status & (DPAUX_INTR_AUX_PLUG_EVENT_PENDING |
DPAUX_INTR_AUX_UNPLUG_EVENT_PENDING)) {
if (status & DPAUX_INTR_AUX_PLUG_EVENT_PENDING) {
dev_info(&dp->dc->ndev->dev,
"dp: plug event received\n");
complete_all(&dp->hpd_plug);
} else {
dev_info(&dp->dc->ndev->dev,
"dp: unplug event received\n");
reinit_completion(&dp->hpd_plug);
}
tegra_dp_pending_hpd(dp);
} else if (status & DPAUX_INTR_AUX_IRQ_EVENT_PENDING) {
dev_info(&dp->dc->ndev->dev, "dp: irq event received%s\n",
dp->enabled ? "" : ", ignoring");
if (dp->enabled) {
cancel_delayed_work(&dp->irq_evt_dwork);
schedule_delayed_work(&dp->irq_evt_dwork,
msecs_to_jiffies(
HPD_IRQ_EVENT_TIMEOUT_MS));
}
}
return IRQ_HANDLED;
}
static void tegra_dp_dpaux_init(struct tegra_dc_dp_data *dp)
{
WARN_ON(!dp || !dp->dc || !dp->dpaux);
if (dp->sor->safe_clk)
tegra_sor_safe_clk_enable(dp->sor);
tegra_dpaux_get(dp->dpaux);
/* do not enable interrupt for now. */
tegra_dpaux_writel(dp->dpaux, DPAUX_INTR_EN_AUX, 0x0);
/* clear interrupt */
tegra_dpaux_writel(dp->dpaux, DPAUX_INTR_AUX, 0xffffffff);
}
static int tegra_dc_dp_hotplug_init(struct tegra_dc *dc)
{
struct tegra_dc_dp_data *dp = tegra_dc_get_outdata(dc);
/*
* dp interrupts are received by dpaux.
* Initialize dpaux to receive hotplug events.
*/
tegra_dp_dpaux_init(dp);
tegra_dp_dpaux_enable(dp);
return 0;
}
static void _tegra_dc_dp_init(struct tegra_dc *dc)
{
struct tegra_dc_dp_data *dp = tegra_dc_get_outdata(dc);
if (dp->pdata->edp2lvds_bridge_enable)
dp->out_ops = &tegra_edp2lvds_ops;
else
dp->out_ops = NULL;
if (dp->out_ops && dp->out_ops->init)
dp->out_ops->init(dp);
if (dp->out_ops && dp->out_ops->enable)
dp->out_ops->enable(dp);
}
static int tegra_dc_dp_init(struct tegra_dc *dc)
{
u32 irq;
int err;
struct clk *parent_clk;
struct device_node *sor_np, *panel_np;
struct tegra_dc_dp_data *dp;
sor_np = tegra_dc_get_conn_np(dc);
if (!sor_np) {
dev_err(&dc->ndev->dev, "%s: error getting connector np\n",
__func__);
return -ENODEV;
}
panel_np = tegra_dc_get_panel_np(dc);
if (!panel_np) {
dev_err(&dc->ndev->dev, "%s: error getting panel np\n",
__func__);
return -ENODEV;
}
dp = devm_kzalloc(&dc->ndev->dev, sizeof(*dp), GFP_KERNEL);
if (!dp) {
err = -ENOMEM;
goto err_dp_alloc;
}
dp->hpd_switch_name = devm_kzalloc(&dc->ndev->dev,
CHAR_BUF_SIZE_MAX, GFP_KERNEL);
if (!dp->hpd_switch_name) {
err = -ENOMEM;
goto err_free_dp;
}
dp->audio_switch_name = devm_kzalloc(&dc->ndev->dev,
CHAR_BUF_SIZE_MAX, GFP_KERNEL);
if (!dp->audio_switch_name) {
err = -ENOMEM;
goto err_hpd_switch;
}
if ((
((dc->pdata->flags & TEGRA_DC_FLAG_ENABLED) &&
(dc->pdata->flags & TEGRA_DC_FLAG_SET_EARLY_MODE))
|| (tegra_fb_is_console_enabled(dc->pdata) &&
tegra_dc_is_ext_panel(dc))
) &&
dc->out->type != TEGRA_DC_OUT_FAKE_DP
) {
dp->early_enable = true;
} else {
dp->early_enable = false;
}
dp->edid_src = EDID_SRC_PANEL;
if (of_property_read_bool(panel_np, "nvidia,edid"))
dp->edid_src = EDID_SRC_DT;
/*
* If the new output type is fakeDP and an DPAUX instance from a
* previous output type exists, re-use it.
*/
if (dc->out->type == TEGRA_DC_OUT_FAKE_DP && dc->out_data
&& !tegra_dc_is_nvdisplay()) {
struct tegra_dc_dp_data *dp_copy =
(struct tegra_dc_dp_data *)dc->out_data;
if (dp_copy->dpaux)
dp->dpaux = dp_copy->dpaux;
}
if (!dp->dpaux)
dp->dpaux = tegra_dpaux_init_data(dc, sor_np);
if (IS_ERR_OR_NULL(dp->dpaux)) {
err = PTR_ERR(dp->dpaux);
dev_err(&dc->ndev->dev, "dpaux registers can't be mapped\n");
dp->dpaux = NULL;
goto err_audio_switch;
}
irq = tegra_dpaux_get_irq(dp->dpaux);
if (!irq) {
dev_err(&dc->ndev->dev, "%s: error getting irq\n", __func__);
err = -ENOENT;
goto err_audio_switch;
}
parent_clk = tegra_disp_of_clk_get_by_name(sor_np, "pll_dp");
if (IS_ERR_OR_NULL(parent_clk)) {
dev_err(&dc->ndev->dev, "dp: clock pll_dp unavailable\n");
err = -EFAULT;
goto err_audio_switch;
}
if (request_threaded_irq(irq, NULL, tegra_dp_irq,
IRQF_ONESHOT, "tegra_dp", dp)) {
dev_err(&dc->ndev->dev,
"dp: request_irq %u failed\n", irq);
err = -EBUSY;
goto err_audio_switch;
}
if (dc->out->type != TEGRA_DC_OUT_FAKE_DP)
tegra_dp_disable_irq(irq);
dp->dc = dc;
dp->parent_clk = parent_clk;
dp->mode = &dc->mode;
err = tegra_dp_register_typec_ecable(dp);
if (err) {
dev_err(&dc->ndev->dev, "dp: typec ecable register failed\n");
goto err_audio_switch;
}
if (dp_instance) {
snprintf(dp->hpd_switch_name, CHAR_BUF_SIZE_MAX,
"dp%d", dp_instance);
snprintf(dp->audio_switch_name, CHAR_BUF_SIZE_MAX,
"dp%d_audio", dp_instance);
} else {
snprintf(dp->hpd_switch_name, CHAR_BUF_SIZE_MAX, "dp");
snprintf(dp->audio_switch_name, CHAR_BUF_SIZE_MAX, "dp_audio");
}
#ifdef CONFIG_SWITCH
dp->hpd_data.hpd_switch.name = dp->hpd_switch_name;
dp->audio_switch.name = dp->audio_switch_name;
#endif
/*
* If the new output type is fakeDP and an SOR instance
* from a previous output type exists, re-use it.
*/
if (dc->out->type == TEGRA_DC_OUT_FAKE_DP &&
!tegra_dc_is_nvdisplay() && dc->out_data &&
((struct tegra_dc_dp_data *)dc->out_data)->sor) {
dp->sor = ((struct tegra_dc_dp_data *)dc->out_data)->sor;
} else {
dp->sor = tegra_dc_sor_init(dc, &dp->link_cfg);
if (dc->initialized)
dp->sor->clk_type = TEGRA_SOR_MACRO_CLK;
}
dp->irq = irq;
dp->pdata = dc->pdata->default_out->dp_out;
dp->suspended = false;
if (IS_ERR_OR_NULL(dp->sor)) {
err = PTR_ERR(dp->sor);
dp->sor = NULL;
dev_err(&dc->ndev->dev, "%s: error getting sor,%d\n",
__func__, err);
goto err_audio_switch;
}
#ifdef CONFIG_DPHDCP
if (dp->sor->hdcp_support) {
dp->dphdcp = tegra_dphdcp_create(dp, dc->ndev->id,
dc->out->ddc_bus);
if (IS_ERR_OR_NULL(dp->dphdcp)) {
err = PTR_ERR(dp->dphdcp);
dev_err(&dc->ndev->dev,
"dp hdcp creation failed with err %d\n", err);
} else {
/* create a /d entry to change the max retries */
tegra_dphdcp_debugfs_init(dp->dphdcp);
}
}
#endif
if (!tegra_platform_is_sim()) {
dp->prod_list = devm_tegra_prod_get_from_node(
&dc->ndev->dev, sor_np);
if (IS_ERR(dp->prod_list)) {
dev_warn(&dc->ndev->dev, "%s: error getting prod-list\n",
__func__);
dp->prod_list = NULL;
}
}
init_completion(&dp->hpd_plug);
tegra_dc_set_outdata(dc, dp);
_tegra_dc_dp_init(dc);
if (dp->pdata->hdmi2fpd_bridge_enable) {
hdmi2fpd_init(dc);
hdmi2fpd_enable(dc);
}
/*
* Adding default link configuration at init. Since
* we check for max link bandwidth during modeset,
* this addresses usecases where modeset happens
* before unblank without preset default configuration
*/
tegra_dc_init_default_panel_link_cfg(dp);
/*
* We don't really need hpd driver for eDP.
* Nevertheless, go ahead and init hpd driver.
* eDP uses some of its fields to interact with panel.
*/
tegra_hpd_init(&dp->hpd_data, dc, dp, &hpd_ops);
tegra_dp_lt_init(&dp->lt_data, dp);
INIT_DELAYED_WORK(&dp->irq_evt_dwork, tegra_dp_irq_evt_worker);
#ifdef CONFIG_DEBUG_FS
dp->test_settings = default_dp_test_settings;
#endif
#ifdef CONFIG_SWITCH
if (tegra_dc_is_ext_panel(dc)) {
err = switch_dev_register(&dp->hpd_data.hpd_switch);
if (err)
dev_err(&dc->ndev->dev,
"%s: failed to register hpd switch, err=%d\n",
__func__, err);
}
if (tegra_dc_is_ext_panel(dc) &&
dc->out->type != TEGRA_DC_OUT_FAKE_DP) {
err = switch_dev_register(&dp->audio_switch);
if (err)
dev_err(&dc->ndev->dev,
"%s: failed to register audio switch, err=%d\n",
__func__, err);
}
#endif
#ifdef CONFIG_TEGRA_HDA_DC
if (tegra_dc_is_ext_panel(dc) && dp->sor->audio_support)
tegra_hda_init(dc, dp);
#endif
if (!(dc->mode.pclk) && IS_ENABLED(CONFIG_FRAMEBUFFER_CONSOLE)) {
if (dc->out->fbcon_default_mode)
tegra_dc_set_fb_mode(dc,
dc->out->fbcon_default_mode, false);
else
tegra_dc_set_fb_mode(dc, &tegra_dc_vga_mode, false);
}
tegra_dc_dp_debugfs_create(dp);
dp_instance++;
return 0;
err_audio_switch:
devm_kfree(&dc->ndev->dev, dp->audio_switch_name);
err_hpd_switch:
devm_kfree(&dc->ndev->dev, dp->hpd_switch_name);
err_free_dp:
devm_kfree(&dc->ndev->dev, dp);
err_dp_alloc:
return err;
}
static void tegra_dp_hpd_config(struct tegra_dc_dp_data *dp)
{
#define TEGRA_DP_HPD_UNPLUG_MIN_US 2000
#define TEGRA_DP_HPD_PLUG_MIN_US 250
#define TEGRA_DP_HPD_IRQ_MIN_US 250
struct tegra_dc_dpaux_data *dpaux = dp->dpaux;
u32 val;
val = TEGRA_DP_HPD_PLUG_MIN_US |
(TEGRA_DP_HPD_UNPLUG_MIN_US <<
DPAUX_HPD_CONFIG_UNPLUG_MIN_TIME_SHIFT);
tegra_dpaux_writel(dpaux, DPAUX_HPD_CONFIG, val);
tegra_dpaux_writel(dpaux, DPAUX_HPD_IRQ_CONFIG,
TEGRA_DP_HPD_IRQ_MIN_US);
#undef TEGRA_DP_HPD_IRQ_MIN_US
#undef TEGRA_DP_HPD_PLUG_MIN_US
#undef TEGRA_DP_HPD_UNPLUG_MIN_US
}
static int tegra_dp_dpcd_init(struct tegra_dc_dp_data *dp)
{
struct tegra_dc_dp_link_config *cfg = &dp->link_cfg;
int ret;
u32 size_ieee_oui = 3, auxstat;
u8 data_ieee_oui_be[3] = {(NV_IEEE_OUI >> 16) & 0xff,
(NV_IEEE_OUI >> 8) & 0xff,
NV_IEEE_OUI & 0xff};
/* Check DP version */
if (tegra_dc_dp_dpcd_read(dp, NV_DPCD_REV, &dp->revision))
dev_err(&dp->dc->ndev->dev,
"dp: failed to read the revision number from sink\n");
ret = tegra_dp_init_max_link_cfg(dp, cfg);
if (ret) {
dev_err(&dp->dc->ndev->dev, "dp: failed to init link cfg\n");
return ret;
}
tegra_dc_dpaux_write(dp->dpaux, DPAUX_DP_AUXCTL_CMD_AUXWR,
NV_DPCD_SOURCE_IEEE_OUI, data_ieee_oui_be, &size_ieee_oui,
&auxstat);
return 0;
}
void tegra_dp_tpg(struct tegra_dc_dp_data *dp, u32 tp, u32 n_lanes)
{
tegra_sor_tpg(dp->sor, tp, n_lanes);
tegra_dc_dp_dpcd_write(dp, NV_DPCD_TRAINING_PATTERN_SET,
dp->sor->training_patterns[tp].dpcd_val);
}
static void tegra_dp_tu_config(struct tegra_dc_dp_data *dp,
const struct tegra_dc_dp_link_config *cfg)
{
struct tegra_dc_sor_data *sor = dp->sor;
u32 reg_val;
tegra_sor_write_field(sor, NV_SOR_DP_LINKCTL(sor->portnum),
NV_SOR_DP_LINKCTL_TUSIZE_MASK,
(cfg->tu_size << NV_SOR_DP_LINKCTL_TUSIZE_SHIFT));
tegra_sor_write_field(sor, NV_SOR_DP_CONFIG(sor->portnum),
NV_SOR_DP_CONFIG_WATERMARK_MASK,
cfg->watermark);
tegra_sor_write_field(sor, NV_SOR_DP_CONFIG(sor->portnum),
NV_SOR_DP_CONFIG_ACTIVESYM_COUNT_MASK,
(cfg->active_count <<
NV_SOR_DP_CONFIG_ACTIVESYM_COUNT_SHIFT));
tegra_sor_write_field(sor, NV_SOR_DP_CONFIG(sor->portnum),
NV_SOR_DP_CONFIG_ACTIVESYM_FRAC_MASK,
(cfg->active_frac <<
NV_SOR_DP_CONFIG_ACTIVESYM_FRAC_SHIFT));
reg_val = cfg->activepolarity ?
NV_SOR_DP_CONFIG_ACTIVESYM_POLARITY_POSITIVE :
NV_SOR_DP_CONFIG_ACTIVESYM_POLARITY_NEGATIVE;
tegra_sor_write_field(sor, NV_SOR_DP_CONFIG(sor->portnum),
NV_SOR_DP_CONFIG_ACTIVESYM_POLARITY_POSITIVE,
reg_val);
tegra_sor_write_field(sor, NV_SOR_DP_CONFIG(sor->portnum),
NV_SOR_DP_CONFIG_ACTIVESYM_CNTL_ENABLE,
NV_SOR_DP_CONFIG_ACTIVESYM_CNTL_ENABLE);
tegra_sor_write_field(sor, NV_SOR_DP_CONFIG(sor->portnum),
NV_SOR_DP_CONFIG_RD_RESET_VAL_NEGATIVE,
NV_SOR_DP_CONFIG_RD_RESET_VAL_NEGATIVE);
}
void tegra_dp_update_link_config(struct tegra_dc_dp_data *dp)
{
struct tegra_dc_dp_link_config *cfg = &dp->link_cfg;
tegra_dp_set_link_bandwidth(dp, cfg->link_bw);
tegra_dp_set_lane_count(dp, cfg->lane_count);
tegra_dp_link_cal(dp);
tegra_dp_tu_config(dp, cfg);
}
static void tegra_dp_read_sink_cap(struct tegra_dc_dp_data *dp)
{
struct tegra_dc *dc = dp->dc;
u32 sink_cap_rd_size = DP_DPCD_SINK_CAP_SIZE;
u32 aux_stat = 0;
u8 start_offset = 0;
int err;
tegra_dc_io_start(dc);
dp->sink_cap_valid = false;
err = tegra_dc_dpaux_read(dp->dpaux, DPAUX_DP_AUXCTL_CMD_AUXRD,
start_offset, dp->sink_cap, &sink_cap_rd_size,
&aux_stat);
if (!err)
dp->sink_cap_valid = true;
tegra_dc_io_end(dc);
}
static void tegra_dp_hpd_op_edid_ready(void *drv_data)
{
struct tegra_dc_dp_data *dp = drv_data;
struct tegra_dc *dc = dp->dc;
/*
* we have a new panel connected.
* Forget old LT config data.
*/
tegra_dp_lt_invalidate(&dp->lt_data);
/* in mm */
dc->out->h_size = dc->out->h_size ? : dp->hpd_data.mon_spec.max_x * 10;
dc->out->v_size = dc->out->v_size ? : dp->hpd_data.mon_spec.max_y * 10;
/*
* EDID specifies either the acutal screen sizes or
* the aspect ratios. The panel file can choose to
* trust the value as the actual sizes by leaving
* width/height to 0s
*/
dc->out->width = dc->out->width ? : dc->out->h_size;
dc->out->height = dc->out->height ? : dc->out->v_size;
tegra_dp_read_sink_cap(dp);
tegra_dc_dp_read_ext_dpcd_caps(dp, &dp->link_cfg.ext_dpcd_caps);
tegra_dc_io_start(dc);
tegra_dc_dp_dpcd_read(dp, NV_DPCD_SINK_COUNT,
&dp->sink_cnt_cp_ready);
if (tegra_dp_auto_is_rq(dp)) {
enum auto_test_requests test_rq;
test_rq = tegra_dp_auto_get_test_rq(dp);
tegra_dp_auto_is_test_supported(test_rq) ?
tegra_dp_auto_ack_test_rq(dp) :
tegra_dp_auto_nack_test_rq(dp);
if (test_rq == TEST_EDID_READ)
tegra_dp_auto_set_edid_checksum(dp);
}
/*
* For Type-C, wait until ucsi_ccg notifies us that an extcon CONNECT
* event has occurred. This ensures that we're in DP configuration, and
* that the EXTCON_PROP_DISP_DP_LANE property has been populated by
* ucsi_ccg before we proceed with our subsequent mode filtering.
*/
tegra_dp_wait_for_typec_connect(dp);
/* Early enables DC with first mode from the monitor specs */
if (dp->early_enable) {
struct tegra_hpd_data *data = &dp->hpd_data;
struct fb_videomode *target_videomode;
struct fb_var_screeninfo var;
/* This function is called only when EDID is read
* successfully. target_videomode should never set
* to default VGA mode unless unexpected issue
* happens and first mode was a null pointer.
*/
target_videomode = (data->mon_spec.modedb) ?
data->mon_spec.modedb : &tegra_dc_vga_mode;
memset(&var, 0x0, sizeof(var));
fb_videomode_to_var(&var, target_videomode);
var.bits_per_pixel = dc->pdata->fb->bits_per_pixel;
tegra_fb_set_var(dc, &var);
if (!dp->dc->enabled)
tegra_dc_enable(dp->dc);
dp->early_enable = false;
if (tegra_fb_is_console_enabled(dc->pdata)) {
tegra_fb_update_monspecs(dc->fb,
&dp->hpd_data.mon_spec,
tegra_dc_dp_ops.mode_filter);
}
}
tegra_dc_io_end(dc);
}
static void tegra_dp_hpd_op_edid_recheck(void *drv_data)
{
struct tegra_dc_dp_data __maybe_unused *dp = drv_data;
/*
* If we ever encounter panel which sends unplug event
* to indicate synchronization loss, this is the placeholder.
* As per specification, panel is expected to send irq_event to
* indicate synchronization loss to host.
*/
}
static inline void tegra_dp_default_int(struct tegra_dc_dp_data *dp,
bool enable)
{
struct tegra_dc_dpaux_data *dpaux = dp->dpaux;
if (dp->dc->out->type == TEGRA_DC_OUT_FAKE_DP)
return;
if (enable)
tegra_dpaux_int_toggle(dpaux, DPAUX_INTR_EN_AUX_IRQ_EVENT |
DPAUX_INTR_EN_AUX_PLUG_EVENT |
DPAUX_INTR_EN_AUX_UNPLUG_EVENT,
true);
else
tegra_dpaux_int_toggle(dpaux, DPAUX_INTR_EN_AUX_IRQ_EVENT,
false);
}
static int tegra_edp_edid_read(struct tegra_dc_dp_data *dp)
{
struct tegra_hpd_data *data = &dp->hpd_data;
BUG_ON(!data);
memset(&data->mon_spec, 0, sizeof(data->mon_spec));
return tegra_edid_get_monspecs(data->edid, &data->mon_spec);
}
static void tegra_edp_mode_set(struct tegra_dc_dp_data *dp)
{
struct fb_videomode *best_edp_fbmode = dp->hpd_data.mon_spec.modedb;
if (best_edp_fbmode)
tegra_dc_set_fb_mode(dp->dc, best_edp_fbmode, false);
else
tegra_dc_set_default_videomode(dp->dc);
}
static int tegra_edp_wait_plug_hpd(struct tegra_dc_dp_data *dp)
{
#define TEGRA_DP_HPD_PLUG_TIMEOUT_MS 1000
u32 val;
int err = 0;
might_sleep();
if (!tegra_platform_is_silicon()) {
msleep(TEGRA_DP_HPD_PLUG_TIMEOUT_MS);
return 0;
}
val = tegra_dpaux_readl(dp->dpaux, DPAUX_DP_AUXSTAT);
if (likely(val & DPAUX_DP_AUXSTAT_HPD_STATUS_PLUGGED))
err = 0;
else if (!wait_for_completion_timeout(&dp->hpd_plug,
msecs_to_jiffies(TEGRA_DP_HPD_PLUG_TIMEOUT_MS)))
err = -ENODEV;
return err;
#undef TEGRA_DP_HPD_PLUG_TIMEOUT_MS
}
#define VSC_PKT_ID (0x07)
#define VSC_REV (0x05)
#define VSC_N_VALID_DATA_BYTES (0x13)
static void tegra_dp_vsc_col_ext_header(struct tegra_dc_dp_data *dp)
{
u32 val = (VSC_N_VALID_DATA_BYTES << 24) |
(VSC_REV << 16) | (VSC_PKT_ID << 8);
tegra_sor_writel(dp->sor, NV_SOR_DP_GENERIC_INFOFRAME_HEADER, val);
}
#undef VSC_N_VALID_DATA_BYTES
#undef VSC_REV
#undef VSC_PKT_ID
static void tegra_dp_vsc_col_ext_payload(struct tegra_dc_dp_data *dp,
u8 vsc_pix_encoding, u8 colorimetry,
u8 dynamic_range, u8 bpc,
u8 content_type)
{
u8 db16 = 0;
u8 db17 = 0;
u8 db18 = 0;
struct tegra_dc_sor_data *sor = dp->sor;
db16 = (vsc_pix_encoding << 4) | colorimetry;
db17 = (dynamic_range << 7) | bpc;
db18 = content_type;
tegra_sor_writel(sor, NV_SOR_DP_GENERIC_INFOFRAME_SUBPACK(0), 0);
tegra_sor_writel(sor, NV_SOR_DP_GENERIC_INFOFRAME_SUBPACK(1), 0);
tegra_sor_writel(sor, NV_SOR_DP_GENERIC_INFOFRAME_SUBPACK(2), 0);
tegra_sor_writel(sor, NV_SOR_DP_GENERIC_INFOFRAME_SUBPACK(3), 0);
tegra_sor_writel(sor, NV_SOR_DP_GENERIC_INFOFRAME_SUBPACK(4),
(db18 << 16) | (db17 << 8) | db16);
tegra_sor_writel(sor, NV_SOR_DP_GENERIC_INFOFRAME_SUBPACK(5), 0);
tegra_sor_writel(sor, NV_SOR_DP_GENERIC_INFOFRAME_SUBPACK(6), 0);
}
static int tegra_dp_vsc_col_ext_enable(struct tegra_dc_dp_data *dp)
{
struct tegra_dc_sor_data *sor = dp->sor;
unsigned long ret;
u32 nv_sor_dp_misc1_override_reg = nv_sor_dp_misc1_override();
ret = tegra_dc_sor_poll_register(sor, nv_sor_dp_misc1_override_reg,
NV_SOR_DP_MISC1_OVERRIDE_CNTL_TRIGGER,
NV_SOR_DP_MISC1_OVERRIDE_CNTL_DONE,
100, TEGRA_SOR_TIMEOUT_MS);
if (!ret) {
tegra_sor_writel(sor, nv_sor_dp_misc1_bit6(),
NV_SOR_DP_MISC1_BIT6_0_SET);
tegra_sor_writel(sor, nv_sor_dp_misc1_override_reg,
NV_SOR_DP_MISC1_OVERRIDE_CNTL_TRIGGER |
NV_SOR_DP_MISC1_OVERRIDE_ENABLE);
tegra_sor_write_field(sor, NV_SOR_DP_AUDIO_CTRL,
NV_SOR_DP_AUDIO_CTRL_GENERIC_INFOFRAME_ENABLE |
NV_SOR_DP_AUDIO_CTRL_NEW_SETTINGS_TRIGGER,
NV_SOR_DP_AUDIO_CTRL_GENERIC_INFOFRAME_ENABLE |
NV_SOR_DP_AUDIO_CTRL_NEW_SETTINGS_TRIGGER);
}
return !ret ? 0 : -ETIMEDOUT;
}
static int tegra_dp_vsc_col_ext_disable(struct tegra_dc_dp_data *dp)
{
struct tegra_dc_sor_data *sor = dp->sor;
unsigned long ret;
u32 nv_sor_dp_misc1_override_reg = nv_sor_dp_misc1_override();
ret = tegra_dc_sor_poll_register(sor, nv_sor_dp_misc1_override_reg,
NV_SOR_DP_MISC1_OVERRIDE_CNTL_TRIGGER,
NV_SOR_DP_MISC1_OVERRIDE_CNTL_DONE,
100, TEGRA_SOR_TIMEOUT_MS);
if (!ret) {
tegra_sor_writel(sor, nv_sor_dp_misc1_override_reg,
NV_SOR_DP_MISC1_OVERRIDE_CNTL_TRIGGER |
NV_SOR_DP_MISC1_OVERRIDE_DISABLE);
tegra_sor_write_field(sor, NV_SOR_DP_AUDIO_CTRL,
NV_SOR_DP_AUDIO_CTRL_GENERIC_INFOFRAME_ENABLE |
NV_SOR_DP_AUDIO_CTRL_NEW_SETTINGS_TRIGGER,
NV_SOR_DP_AUDIO_CTRL_GENERIC_INFOFRAME_DISABLE |
NV_SOR_DP_AUDIO_CTRL_NEW_SETTINGS_TRIGGER);
}
return !ret ? 0 : -ETIMEDOUT;
}
static inline u8 tegra_dp_vsc_get_bpc(struct tegra_dc_dp_data *dp)
{
int yuv_flag = dp->dc->mode.vmode & FB_VMODE_YUV_MASK;
u8 bpc = VSC_8BPC;
if (yuv_flag & FB_VMODE_Y24) {
bpc = VSC_8BPC;
} else if (yuv_flag & FB_VMODE_Y30) {
bpc = VSC_10BPC;
} else if (yuv_flag & FB_VMODE_Y36) {
bpc = VSC_12BPC;
} else if (yuv_flag & FB_VMODE_Y48) {
bpc = VSC_16BPC;
} else {
switch (dp->dc->out->depth) {
case 18:
bpc = VSC_6BPC;
break;
case 30:
bpc = VSC_10BPC;
break;
case 36:
bpc = VSC_12BPC;
break;
case 48:
bpc = VSC_16BPC;
break;
case 24:
default:
bpc = VSC_8BPC;
break;
}
};
return bpc;
}
static inline u8 tegra_dp_vsc_get_pixel_encoding(struct tegra_dc_dp_data *dp)
{
int yuv_flag = dp->dc->mode.vmode & FB_VMODE_YUV_MASK;
if (yuv_flag & FB_VMODE_Y422)
return VSC_YUV422;
else if (yuv_flag & FB_VMODE_Y444)
return VSC_YUV444;
else if (IS_RGB(yuv_flag))
return VSC_RGB;
return VSC_RGB;
}
static inline u8 tegra_dp_vsc_get_dynamic_range(struct tegra_dc_dp_data *dp)
{
if ((dp->dc->mode.vmode & FB_VMODE_BYPASS) ||
!(dp->dc->mode.vmode & FB_VMODE_LIMITED_RANGE))
return VSC_VESA_RANGE;
return VSC_CEA_RANGE;
}
static inline u8 tegra_dp_vsc_get_colorimetry(struct tegra_dc_dp_data *dp)
{
u32 vmode_flag = dp->dc->mode.vmode;
u8 colorimetry = VSC_RGB_SRGB;
if (vmode_flag & FB_VMODE_EC_ENABLE) {
u32 ec = vmode_flag & FB_VMODE_EC_MASK;
switch (ec) {
case FB_VMODE_EC_ADOBE_RGB:
colorimetry = VSC_RGB_ADOBERGB;
break;
case FB_VMODE_EC_ADOBE_YCC601:
colorimetry = VSC_YUV_ADOBEYCC601;
break;
case FB_VMODE_EC_SYCC601:
colorimetry = VSC_YUV_SYCC601;
break;
case FB_VMODE_EC_XVYCC601:
colorimetry = VSC_YUV_XVYCC709;
break;
case FB_VMODE_EC_XVYCC709:
colorimetry = VSC_YUV_XVYCC709;
break;
default:
colorimetry = VSC_RGB_SRGB;
break;
}
}
return colorimetry;
}
static void tegra_dp_vsc_col_ext(struct tegra_dc_dp_data *dp)
{
struct tegra_dc_dp_link_config *cfg = &dp->link_cfg;
u8 vsc_pix_encoding = 0, colorimetry = 0, dynamic_range = 0,
bpc = 0, content_type = 0;
u32 vmode_flag = dp->dc->mode.vmode;
u32 ec = vmode_flag & FB_VMODE_EC_MASK;
if (!tegra_dc_is_nvdisplay() || !cfg->support_vsc_ext_colorimetry)
return;
if (!(vmode_flag & FB_VMODE_Y420) &&
!(ec & (FB_VMODE_EC_BT2020_CYCC | FB_VMODE_EC_BT2020_YCC_RGB)))
return;
tegra_dp_vsc_col_ext_disable(dp);
vsc_pix_encoding = tegra_dp_vsc_get_pixel_encoding(dp);
colorimetry = tegra_dp_vsc_get_colorimetry(dp);
dynamic_range = tegra_dp_vsc_get_dynamic_range(dp);
bpc = tegra_dp_vsc_get_bpc(dp);
content_type = VSC_CONTENT_TYPE_DEFAULT;
tegra_dp_vsc_col_ext_header(dp);
tegra_dp_vsc_col_ext_payload(dp, vsc_pix_encoding,
colorimetry, dynamic_range,
bpc, content_type);
tegra_dp_vsc_col_ext_enable(dp);
}
static inline void tegra_dp_set_sor_clk_src(struct tegra_dc_dp_data *dp,
struct clk *src)
{
struct tegra_dc_sor_data *sor = dp->sor;
/*
* Disable and re-enable the sor_clk while switching the source to avoid
* any momentary glitches. This shouldn't really matter since the SOR
* wouldn't be actively sending any data at this point in time, but
* we're doing this to be safe.
*/
tegra_disp_clk_disable_unprepare(sor->sor_clk);
clk_set_parent(sor->sor_clk, src);
tegra_disp_clk_prepare_enable(sor->sor_clk);
}
static void tegra_dp_prepare_pad(struct tegra_dc_dp_data *dp)
{
struct tegra_dc *dc = dp->dc;
struct tegra_dc_sor_data *sor = dp->sor;
if (!dc->initialized) {
tegra_sor_reset(sor);
/*
* Enable PLLDP and the PLLD* pixel clock reference.
* Select the SOR safe clock before powering up the pads.
*
* For nvdisplay, the above steps are performed as part of
* out_ops->setup_clk(), which is invoked during the head enable
* sequence prior to this point.
*/
if (!tegra_dc_is_nvdisplay()) {
tegra_dp_clk_enable(dp);
tegra_dc_setup_clk(dc, dc->clk);
tegra_sor_config_safe_clk(sor);
}
}
tegra_sor_clk_enable(sor);
if (!dc->initialized) {
tegra_sor_write_field(sor, NV_SOR_CLK_CNTRL,
NV_SOR_CLK_CNTRL_DP_CLK_SEL_MASK,
NV_SOR_CLK_CNTRL_DP_CLK_SEL_DIFF_DPCLK);
tegra_dc_sor_set_link_bandwidth(sor, dp->link_cfg.link_bw);
/* Program common and linkspeed-specific PROD settings. */
tegra_dp_config_common_prods(dp);
tegra_dp_link_cal(dp);
}
}
static void tegra_dc_dp_enable(struct tegra_dc *dc)
{
struct tegra_dc_dp_data *dp = tegra_dc_get_outdata(dc);
struct tegra_dc_dp_link_config *cfg = &dp->link_cfg;
struct tegra_dc_sor_data *sor = dp->sor;
int ret;
if (dp->enabled)
return;
tegra_dc_io_start(dc);
if (tegra_platform_is_fpga())
tegra_sor_program_fpga_clk_mux(sor);
/* Change for seamless */
if (!dc->initialized) {
ret = tegra_dp_panel_power_state(dp,
NV_DPCD_SET_POWER_VAL_D0_NORMAL);
if (ret < 0) {
dev_err(&dp->dc->ndev->dev,
"dp: failed to exit panel power save mode (0x%x)\n",
ret);
tegra_dc_io_end(dp->dc);
return;
}
}
/* For eDP, driver gets to decide the best mode. */
if (!tegra_dc_is_ext_panel(dc) &&
dc->out->type != TEGRA_DC_OUT_FAKE_DP) {
int err;
/*
* Hotplug for internal panels is not supported.
* Wait till the panel asserts hpd
*/
err = tegra_edp_wait_plug_hpd(dp);
if (err < 0) {
tegra_dc_io_end(dc);
dc->connected = false;
dev_err(&dc->ndev->dev,
"edp: plug hpd wait timeout\n");
return;
}
err = tegra_edp_edid_read(dp);
if (err < 0)
dev_warn(&dc->ndev->dev, "edp: edid read failed\n");
else
tegra_dp_hpd_op_edid_ready(dp);
tegra_edp_mode_set(dp);
tegra_dc_setup_clk(dc, dc->clk);
}
if (tegra_dp_dpcd_init(dp)) {
dev_err(&dp->dc->ndev->dev, "dp: failed dpcd init\n");
return;
}
tegra_dp_prepare_pad(dp);
tegra_dc_sor_enable_dp(dp->sor);
if (cfg->alt_scramber_reset_cap)
tegra_dc_dp_set_assr(dp, true);
else
tegra_dc_sor_set_internal_panel(dp->sor, false);
tegra_dc_dp_dpcd_write(dp, NV_DPCD_MAIN_LINK_CHANNEL_CODING_SET,
NV_DPCD_MAIN_LINK_CHANNEL_CODING_SET_ANSI_8B10B);
if (!dc->initialized) {
tegra_sor_write_field(sor, NV_SOR_DP_CONFIG(sor->portnum),
NV_SOR_DP_CONFIG_IDLE_BEFORE_ATTACH_ENABLE,
NV_SOR_DP_CONFIG_IDLE_BEFORE_ATTACH_ENABLE);
tegra_dc_dp_dpcd_write(dp, NV_DPCD_DOWNSPREAD_CTRL,
NV_DPCD_DOWNSPREAD_CTRL_SPREAD_AMP_LT_0_5);
tegra_dc_dp_dpcd_write(dp, NV_DPCD_LINK_BANDWIDTH_SET,
cfg->link_bw);
/*
* enhanced framing enable field shares DPCD offset
* with lane count set field. Make sure lane count is set
* before enhanced framing enable. CTS waits on first
* write to this offset to check for lane count set.
*/
tegra_dp_dpcd_write_field(dp, NV_DPCD_LANE_COUNT_SET,
NV_DPCD_LANE_COUNT_SET_MASK,
cfg->lane_count);
tegra_dp_set_enhanced_framing(dp, cfg->enhanced_framing);
tegra_dp_tu_config(dp, cfg);
}
tegra_sor_port_enable(sor, true);
tegra_sor_config_xbar(dp->sor);
/* Select the macro feedback clock. */
if (!dc->initialized) {
if (tegra_dc_is_nvdisplay()) {
tegra_sor_clk_switch_setup(sor, true);
tegra_dp_set_sor_clk_src(dp, sor->pad_clk);
} else {
tegra_sor_config_dp_clk_t21x(sor);
}
}
/* Host is ready. Start link training. */
dp->enabled = true;
#ifdef CONFIG_TEGRA_HDA_DC
if (tegra_dc_is_ext_panel(dc) && sor->audio_support)
tegra_hda_enable(dp->hda_handle);
#endif
tegra_dp_vsc_col_ext(dp);
if (likely(dc->out->type != TEGRA_DC_OUT_FAKE_DP) &&
!no_lt_at_unblank) {
if (!dc->initialized) {
tegra_dp_lt_set_pending_evt(&dp->lt_data);
ret = tegra_dp_lt_wait_for_completion(&dp->lt_data,
STATE_DONE_PASS, LT_TIMEOUT_MS);
if (!ret)
dev_err(&dp->dc->ndev->dev,
"dp: link training failed\n");
} else {
/* Perform SOR attach here */
tegra_dc_sor_attach(dp->sor);
}
} else {
/* Just enable host. */
tegra_dp_tpg(dp, TEGRA_DC_DP_TRAINING_PATTERN_DISABLE,
dp->link_cfg.lane_count);
tegra_dc_sor_attach(dp->sor);
}
#ifdef CONFIG_DPHDCP
if (tegra_dc_is_ext_panel(dc) && dp->dphdcp &&
dc->out->type != TEGRA_DC_OUT_FAKE_DP) {
tegra_dphdcp_set_plug(dp->dphdcp, true);
}
#endif
dc->connected = true;
tegra_dc_io_end(dc);
if (tegra_dp_is_audio_supported(dp)) {
disp_state_extcon_aux_report(dp->sor->ctrl_num,
EXTCON_DISP_AUX_STATE_ENABLED);
pr_info("Extcon AUX%d(DP): enable\n", dp->sor->ctrl_num);
}
#ifdef CONFIG_SWITCH
if (tegra_edid_audio_supported(dp->hpd_data.edid)
&& tegra_dc_is_ext_panel(dc) &&
dc->out->type != TEGRA_DC_OUT_FAKE_DP) {
pr_info("dp_audio switch 1\n");
switch_set_state(&dp->audio_switch, 1);
}
#endif
}
void tegra_dc_dp_enable_link(struct tegra_dc_dp_data *dp)
{
if (!dp->enabled)
tegra_dc_dp_enable(dp->dc);
else
tegra_dc_sor_attach(dp->sor);
}
static void tegra_dc_dp_destroy(struct tegra_dc *dc)
{
struct tegra_dc_dp_data *dp = NULL;
if (!dc->current_topology.valid)
return;
dp = tegra_dc_get_outdata(dc);
if (dp->dc->out->type != TEGRA_DC_OUT_FAKE_DP) {
tegra_dp_disable_irq(dp->irq);
tegra_dp_default_int(dp, false);
}
if (dp->pdata->hdmi2fpd_bridge_enable)
hdmi2fpd_destroy(dc);
#ifdef CONFIG_TEGRA_HDA_DC
if (tegra_dc_is_ext_panel(dc) && dp->sor->audio_support)
tegra_hda_destroy(dp->hda_handle);
#endif
if (dp->dphdcp)
tegra_dphdcp_destroy(dp->dphdcp);
tegra_dp_dpaux_disable(dp);
if (dp->dpaux)
tegra_dpaux_destroy_data(dp->dpaux);
if (dp->sor)
tegra_dc_sor_destroy(dp->sor);
tegra_hpd_shutdown(&dp->hpd_data);
clk_put(dp->parent_clk);
dp->prod_list = NULL;
tegra_dp_unregister_typec_ecable(dc);
tegra_dc_out_destroy(dc);
tegra_dc_dp_debugfs_remove(dp);
#ifdef CONFIG_SWITCH
if (tegra_dc_is_ext_panel(dc) &&
dc->out->type != TEGRA_DC_OUT_FAKE_DP) {
switch_dev_unregister(&dp->audio_switch);
}
#endif
devm_kfree(&dc->ndev->dev, dp->hpd_switch_name);
devm_kfree(&dc->ndev->dev, dp->audio_switch_name);
free_irq(dp->irq, dp);
devm_kfree(&dc->ndev->dev, dp);
dc->current_topology.valid = false;
}
static void tegra_dc_dp_disable(struct tegra_dc *dc)
{
struct tegra_dc_dp_data *dp = tegra_dc_get_outdata(dc);
int ret;
if (!dp->enabled)
return;
dp->enabled = false;
tegra_dc_io_start(dc);
#ifdef CONFIG_DPHDCP
if (tegra_dc_is_ext_panel(dc) && dp->dphdcp &&
dc->out->type != TEGRA_DC_OUT_FAKE_DP)
tegra_dphdcp_set_plug(dp->dphdcp, false);
#endif
if (dc->out->type != TEGRA_DC_OUT_FAKE_DP) {
cancel_delayed_work_sync(&dp->irq_evt_dwork);
tegra_dp_lt_force_disable(&dp->lt_data);
ret = tegra_dp_lt_wait_for_completion(&dp->lt_data,
STATE_DONE_FAIL, LT_TIMEOUT_MS);
WARN_ON(!ret);
}
if (tegra_dc_hpd(dc)) {
ret = tegra_dp_panel_power_state(dp,
NV_DPCD_SET_POWER_VAL_D3_PWRDWN);
if (ret < 0)
dev_info(&dp->dc->ndev->dev,
"dp: failed to enter panel power save mode\n");
}
tegra_dc_sor_detach(dp->sor);
if (tegra_dc_is_nvdisplay()) {
tegra_sor_clk_switch_setup(dp->sor, false);
tegra_dp_set_sor_clk_src(dp, dp->sor->safe_clk);
}
tegra_dc_sor_disable(dp->sor);
tegra_dp_clk_disable(dp);
tegra_dc_io_end(dc);
#ifdef CONFIG_TEGRA_HDA_DC
if (tegra_dc_is_ext_panel(dc) && dp->sor->audio_support)
tegra_hda_disable(dp->hda_handle);
#endif
if (tegra_dp_is_audio_supported(dp)) {
disp_state_extcon_aux_report(dp->sor->ctrl_num,
EXTCON_DISP_AUX_STATE_DISABLED);
pr_info("Extcon AUX%d(DP) disable\n", dp->sor->ctrl_num);
}
#ifdef CONFIG_SWITCH
if (tegra_edid_audio_supported(dp->hpd_data.edid)
&& tegra_dc_is_ext_panel(dc) &&
dc->out->type != TEGRA_DC_OUT_FAKE_DP) {
pr_info("dp_audio switch 0\n");
switch_set_state(&dp->audio_switch, 0);
}
#endif
}
void tegra_dc_dp_pre_disable_link(struct tegra_dc_dp_data *dp)
{
tegra_dc_sor_pre_detach(dp->sor);
}
void tegra_dc_dp_disable_link(struct tegra_dc_dp_data *dp, bool powerdown)
{
tegra_dc_sor_detach(dp->sor);
if (powerdown)
tegra_dc_dp_disable(dp->dc);
}
static long tegra_dc_dp_setup_clk(struct tegra_dc *dc, struct clk *clk)
{
struct tegra_dc_dp_data *dp = tegra_dc_get_outdata(dc);
struct clk *dc_parent_clk;
struct tegra_dc_sor_data *sor = NULL;
if (!tegra_platform_is_silicon())
return tegra_dc_pclk_round_rate(dc, dc->mode.pclk);
if (clk == dc->clk) {
if (tegra_dc_is_nvdisplay()) {
dc_parent_clk = tegra_disp_clk_get(&dc->ndev->dev,
dc->out->parent_clk);
if (IS_ERR_OR_NULL(dc_parent_clk)) {
dev_err(&dc->ndev->dev,
"dp: failed to get clock %s\n",
dc->out->parent_clk);
return -EINVAL;
}
} else {
if (dc->out->type == TEGRA_DC_OUT_FAKE_DP)
dc_parent_clk = clk_get_sys(NULL,
"pll_d2_out0");
else
dc_parent_clk = clk_get_sys(NULL,
dc->out->parent_clk);
}
clk_set_parent(dc->clk, dc_parent_clk);
}
/* set pll_d2 to pclk rate */
tegra_sor_setup_clk(dp->sor, clk, false);
if (tegra_dc_is_nvdisplay()) {
sor = dp->sor;
/* enable pll_dp */
tegra_dp_clk_enable(dp);
tegra_sor_safe_clk_enable(sor);
/* Change for seamless */
if (!dc->initialized)
clk_set_parent(sor->sor_clk, sor->safe_clk);
tegra_disp_clk_prepare_enable(sor->pad_clk);
tegra_disp_clk_prepare_enable(sor->sor_clk);
}
return tegra_dc_pclk_round_rate(dc, dc->mode.pclk);
}
static bool tegra_dc_dp_hpd_state(struct tegra_dc *dc)
{
struct tegra_dc_dp_data *dp = tegra_dc_get_outdata(dc);
struct tegra_dc_dpaux_data *dpaux = dp->dpaux;
u32 val;
if (dp->suspended)
return false;
if (WARN_ON(!dc || !dc->out))
return false;
if (dc->out->type == TEGRA_DC_OUT_FAKE_DP || tegra_platform_is_vdk())
return true;
tegra_dpaux_get(dpaux);
val = tegra_dpaux_readl(dpaux, DPAUX_DP_AUXSTAT);
tegra_dpaux_put(dpaux);
return !!(val & DPAUX_DP_AUXSTAT_HPD_STATUS_PLUGGED);
}
/* used by tegra_dc_probe() to detect connection(HPD) status at boot */
static bool tegra_dc_dp_detect(struct tegra_dc *dc)
{
struct tegra_dc_dp_data *dp = tegra_dc_get_outdata(dc);
if ((tegra_platform_is_sim() || tegra_platform_is_fpga()) &&
(dc->out->hotplug_state == TEGRA_HPD_STATE_NORMAL)) {
complete(&dc->hpd_complete);
return true;
}
if (dc->out->type == TEGRA_DC_OUT_FAKE_DP && !dc->vedid &&
dp->edid_src != EDID_SRC_DT) {
complete(&dc->hpd_complete);
return false;
}
if (tegra_fb_is_console_enabled(dc->pdata) &&
!tegra_dc_is_ext_panel(dc) &&
dc->out->type != TEGRA_DC_OUT_FAKE_DP) {
if (dp->hpd_data.mon_spec.modedb_len > 0) {
tegra_fb_update_monspecs(dc->fb, &dp->hpd_data.mon_spec,
tegra_dc_dp_ops.mode_filter);
tegra_fb_update_fix(dc->fb, &dp->hpd_data.mon_spec);
}
}
tegra_dp_pending_hpd(dp);
return tegra_dc_hpd(dc);
}
static void tegra_dc_dp_suspend(struct tegra_dc *dc)
{
struct tegra_dc_dp_data *dp = tegra_dc_get_outdata(dc);
if (dp->pdata->hdmi2fpd_bridge_enable)
hdmi2fpd_suspend(dc);
if (dp->suspended)
return;
dp->suspended = true;
tegra_dp_lt_invalidate(&dp->lt_data);
if (dp->dc->out->type != TEGRA_DC_OUT_FAKE_DP) {
tegra_dp_disable_irq(dp->irq);
tegra_dp_default_int(dp, false);
}
tegra_dp_hpd_suspend(dp);
tegra_dp_dpaux_disable(dp);
}
static void tegra_dc_dp_resume(struct tegra_dc *dc)
{
struct tegra_dc_dp_data *dp = tegra_dc_get_outdata(dc);
if (!dp->suspended)
return;
if (dp->pdata->hdmi2fpd_bridge_enable)
hdmi2fpd_resume(dc);
/* Get ready to receive any hpd event */
tegra_dc_dp_hotplug_init(dc);
dp->suspended = false;
if (tegra_platform_is_sim() &&
(dc->out->hotplug_state == TEGRA_HPD_STATE_NORMAL))
return;
if (is_hotplug_supported(dp))
reinit_completion(&dc->hpd_complete);
dp->hpd_data.hpd_resuming = true;
tegra_dp_pending_hpd(dp);
if (is_hotplug_supported(dp))
wait_for_completion(&dc->hpd_complete);
}
static void tegra_dc_dp_modeset_notifier(struct tegra_dc *dc)
{
struct tegra_dc_dp_data *dp = tegra_dc_get_outdata(dc);
struct tegra_dc_dpaux_data *dpaux = dp->dpaux;
/* In case of seamless display, kernel carries forward BL config */
if (dc->initialized)
return;
tegra_dc_io_start(dc);
tegra_dpaux_clk_en(dpaux);
tegra_dc_sor_modeset_notifier(dp->sor, false);
if (!(tegra_platform_is_vdk()))
tegra_dc_dp_calc_config(dp, dp->mode, &dp->link_cfg);
tegra_dpaux_clk_dis(dpaux);
tegra_dc_io_end(dc);
}
static bool tegra_dp_check_dc_constraint(const struct fb_videomode *mode)
{
return (mode->hsync_len >= 1) && (mode->vsync_len >= 1) &&
(mode->lower_margin + mode->vsync_len +
mode->upper_margin > 1) &&
(mode->xres >= 16) && (mode->yres >= 16);
}
static bool tegra_dp_mode_filter(const struct tegra_dc *dc,
struct fb_videomode *mode)
{
struct tegra_dc_dp_data *dp = dc->out_data;
u8 link_rate = 0, lane_count = 0;
unsigned int key; /* Index into the link speed table */
int capability = 1;
struct tegra_vrr *vrr;
if (!tegra_dc_hpd((struct tegra_dc *)dc))
return false;
if (!mode->pixclock)
return false;
if (tegra_dc_is_nvdisplay()) {
if (mode->xres > 8192)
return false;
} else {
if (mode->xres > 4096)
return false;
}
/* Check if the mode's pixel clock is more than the max rate*/
if (!tegra_dc_valid_pixclock(dc, mode))
return false;
/*
* Workaround for modes that fail the constraint:
* V_FRONT_PORCH >= V_REF_TO_SYNC + 1
*
* This constraint does not apply to nvdisplay.
*/
if (!tegra_dc_is_nvdisplay() && mode->lower_margin == 1) {
mode->lower_margin++;
mode->upper_margin--;
mode->vmode |= FB_VMODE_ADJUSTED;
}
if (tegra_dc_is_t21x()) {
/* No support for YUV modes on T21x hardware. */
if (mode->vmode & (YUV_MASK))
return false;
}
if (mode->vmode & FB_VMODE_INTERLACED)
return false;
if ((mode->vmode & FB_VMODE_Y420_ONLY) ||
(mode->vmode & FB_VMODE_Y420))
return false;
if ((mode->vmode & FB_VMODE_Y422) &&
!(mode->vmode & FB_VMODE_Y24))
return false;
if (!tegra_dp_check_dc_constraint(mode))
return false;
/*
* CTS mandates that if edid is corrupted
* use fail-safe mode i.e. VGA 640x480@60
*/
if (dc->edid->errors)
return (mode->xres == 640 && mode->yres == 480)
? true : false;
if (dc->out->vrr) {
vrr = dc->out->vrr;
/* FIXME Bug: 1740464 */
if (tegra_dc_is_vrr_authentication_enabled())
capability = vrr->capability;
if (capability) {
mode->upper_margin += 2;
if (mode->lower_margin >= 4)
mode->lower_margin -= 2;
}
}
/* Note: The multiplier when multiplied to 270MHz gives us the link
* bandwidth. In other words, one is derived from the other, and the
* spec ends up using the two terms interchangeably
*/
if (dc->out->type == TEGRA_DC_OUT_FAKE_DP) {
link_rate = dp->link_cfg.max_link_bw;
lane_count = dp->link_cfg.max_lane_count;
} else {
u8 dpcd_data = 0;
link_rate = tegra_dc_dp_get_max_link_bw(dp);
lane_count = tegra_dc_dp_get_max_lane_count(dp, &dpcd_data);
}
if (link_rate == 0 || lane_count == 0) {
dev_err(&dc->ndev->dev,
"dp: Invalid link rate (%u) or lane count (%u)\n",
link_rate, lane_count);
return false;
}
if (dc->out->dp_out != NULL) {
u32 bits_per_pixel;
u32 max_link_bw_rate;
u64 total_max_link_bw;
u64 mode_bw;
bits_per_pixel = tegra_dp_get_bpp(dp, mode->vmode);
key = tegra_dp_link_speed_get(dp, link_rate);
if (WARN_ON(key == dp->sor->num_link_speeds)) {
dev_info(&dc->ndev->dev, "invalid link bw\n");
return false;
}
max_link_bw_rate = dp->sor->link_speeds[key].max_link_bw;
/* max link bandwidth = lane_freq * lanes * 8 / 10 */
total_max_link_bw = (u64)max_link_bw_rate
* 1000 * 1000 * 8 / 10 * lane_count;
mode_bw = (u64)PICOS2KHZ(mode->pixclock) * 1000
* bits_per_pixel;
if (total_max_link_bw < mode_bw) {
dev_info(&dc->ndev->dev,
"mode bw=%llu > link bw=%llu\n",
mode_bw, total_max_link_bw);
return false;
}
}
return true;
}
static void tegra_dp_init_hpd_timer_data(struct tegra_dc_dp_data *dp)
{
struct tegra_hpd_timer_data *timer_data = &dp->hpd_data.timer_data;
timer_data->plug_stabilize_delay_us = 100000; /* spec-recommended */
/*
* For DP, PLUG and UNPLUG interrupts are generated by the AUX logic
* only after the minimum DPAUX_HPD_CONFIG_0.PLUG_MIN_TIME and
* DPAUX_HPD_CONFIG_0.UNPLUG_MIN_TIME detection thresholds have been
* satisfied. DPAUX_HPD_CONFIG_0.UNPLUG_MIN_TIME is always set at 2ms
* per the spec. If HPD has dropped LOW for 2ms, this should always be
* considered as a legitimate UNPLUG event - no additional stabilization
* delay is needed.
*/
timer_data->unplug_stabilize_delay_us = 0;
timer_data->reassert_delay_us = 100000; /* spec-recommended */
timer_data->check_edid_delay_us = 400;
/*
* DP sinks can generate pairs of UNPLUG + PLUG events that occur within
* (2ms, 100ms). To address these cases, reset the HPD state machine if
* HPD drops LOW and then comes back up within reassert_delay_us.
*/
timer_data->reset_on_reassert = true;
/*
* For DP, a PLUG interrupt is only generated when HPD transitions from
* LOW to HIGH, and stays HIGH for at least
* DPAUX_HPD_CONFIG_0.PLUG_MIN_TIME. If the state machine is currently
* enabled, and receives a PLUG event, this indicates SW was not able to
* schedule the HPD worker for the previous UNPLUG before it got
* canceled. In this scenario, we should still treat the current PLUG
* event as a legitimate event, and reset.
*/
timer_data->reset_on_plug_bounce = true;
}
static void tegra_dp_hpd_op_init(void *drv_data)
{
struct tegra_dc_dp_data *dp = drv_data;
tegra_dp_init_hpd_timer_data(dp);
}
static bool (*tegra_dp_op_get_mode_filter(void *drv_data))
(const struct tegra_dc *dc, struct fb_videomode *mode) {
return tegra_dp_mode_filter;
}
static bool tegra_dp_hpd_op_get_hpd_state(void *drv_data)
{
struct tegra_dc_dp_data *dp = drv_data;
return tegra_dc_hpd(dp->dc);
}
static bool tegra_dp_hpd_op_edid_read_prepare(void *drv_data)
{
struct tegra_dc_dp_data *dp = drv_data;
int ret;
tegra_dc_io_start(dp->dc);
ret = tegra_dp_panel_power_state(dp, NV_DPCD_SET_POWER_VAL_D0_NORMAL);
if (ret < 0) {
dev_err(&dp->dc->ndev->dev,
"dp: failed to exit panel power save mode (0x%x)\n", ret);
tegra_dc_io_end(dp->dc);
return false;
}
tegra_dc_io_end(dp->dc);
return true;
}
static void tegra_dc_dp_sor_sleep(struct tegra_dc *dc)
{
struct tegra_dc_dp_data *dp = tegra_dc_get_outdata(dc);
if (dp->sor->sor_state == SOR_ATTACHED)
tegra_dc_sor_sleep(dp->sor);
}
static u32 tegra_dc_dp_sor_crc_check(struct tegra_dc *dc)
{
struct tegra_dc_dp_data *dp = tegra_dc_get_outdata(dc);
return tegra_dc_sor_debugfs_get_crc(dp->sor, NULL);
}
static void tegra_dc_dp_sor_crc_toggle(struct tegra_dc *dc,
u32 val)
{
struct tegra_dc_dp_data *dp = tegra_dc_get_outdata(dc);
tegra_dc_sor_toggle_crc(dp->sor, val);
}
static int tegra_dc_dp_sor_crc_en_dis(struct tegra_dc *dc,
struct tegra_dc_ext_crc_or_params *params,
bool en)
{
struct tegra_dc_dp_data *dp = tegra_dc_get_outdata(dc);
if (params->out_type != TEGRA_DC_EXT_DP)
return -EINVAL;
tegra_dc_sor_crc_en_dis(dp->sor, params->sor_params, en);
return 0;
}
static int tegra_dc_dp_sor_crc_en(struct tegra_dc *dc,
struct tegra_dc_ext_crc_or_params *params)
{
return tegra_dc_dp_sor_crc_en_dis(dc, params, true);
}
static int tegra_dc_dp_sor_crc_dis(struct tegra_dc *dc,
struct tegra_dc_ext_crc_or_params *params)
{
return tegra_dc_dp_sor_crc_en_dis(dc, params, false);
}
static int tegra_dc_dp_sor_crc_get(struct tegra_dc *dc, u32 *crc)
{
struct tegra_dc_dp_data *dp = tegra_dc_get_outdata(dc);
return tegra_dc_sor_crc_get(dp->sor, crc);
}
static struct tegra_hpd_ops hpd_ops = {
.init = tegra_dp_hpd_op_init,
.edid_read = tegra_dp_hpd_op_edid_read,
.edid_ready = tegra_dp_hpd_op_edid_ready,
.edid_recheck = tegra_dp_hpd_op_edid_recheck,
.get_mode_filter = tegra_dp_op_get_mode_filter,
.get_hpd_state = tegra_dp_hpd_op_get_hpd_state,
.edid_read_prepare = tegra_dp_hpd_op_edid_read_prepare,
};
static int tegra_dc_dp_get_sor_ctrl_num(struct tegra_dc *dc)
{
struct tegra_dc_dp_data *dp = tegra_dc_get_outdata(dc);
return (!dp) ? -ENODEV : tegra_sor_get_ctrl_num(dp->sor);
}
struct tegra_dc_out_ops tegra_dc_dp_ops = {
.init = tegra_dc_dp_init,
.destroy = tegra_dc_dp_destroy,
.enable = tegra_dc_dp_enable,
.disable = tegra_dc_dp_disable,
.detect = tegra_dc_dp_detect,
.setup_clk = tegra_dc_dp_setup_clk,
.modeset_notifier = tegra_dc_dp_modeset_notifier,
.mode_filter = tegra_dp_mode_filter,
.hpd_state = tegra_dc_dp_hpd_state,
.suspend = tegra_dc_dp_suspend,
.resume = tegra_dc_dp_resume,
.hotplug_init = tegra_dc_dp_hotplug_init,
.shutdown_interface = tegra_dc_dp_sor_sleep,
.get_crc = tegra_dc_dp_sor_crc_check,
.toggle_crc = tegra_dc_dp_sor_crc_toggle,
.get_connector_instance = tegra_dc_dp_get_sor_ctrl_num,
.crc_en = tegra_dc_dp_sor_crc_en,
.crc_dis = tegra_dc_dp_sor_crc_dis,
.crc_get = tegra_dc_dp_sor_crc_get,
};