#include "dphdcp.h"
#include "dp_lt.h"
+#if defined(CONFIG_MODS) && (defined(CONFIG_ARCH_TEGRA_21x_SOC) || \
+ defined(CONFIG_ARCH_TEGRA_18x_SOC))
+#include <linux/ctype.h>
+#include <asm/uaccess.h>
+#endif
+
+#if defined(CONFIG_ARCH_TEGRA_21x_SOC) || defined(CONFIG_ARCH_TEGRA_18x_SOC)
+#include "hda_dc.h"
+#endif
+
#ifdef CONFIG_TEGRA_DC_FAKE_PANEL_SUPPORT
#include "fake_panel.h"
#endif /*CONFIG_TEGRA_DC_FAKE_PANEL_SUPPORT*/
+#include <linux/tegra_prod.h>
+
+#define PROD_NODE "/host1x/sor1"
+
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 inline void tegra_dp_reset(struct tegra_dc_dp_data *dp);
disable_irq(irq);
}
+#define is_hotplug_supported(dp) \
+({ \
+ !tegra_platform_is_fpga() && \
+ !tegra_platform_is_linsim() && \
+ tegra_dc_is_ext_dp_panel(dp->dc) && \
+ dp->dc->out->type != TEGRA_DC_OUT_FAKE_DP; \
+})
+
+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 unsigned long
tegra_dc_dpaux_poll_register(struct tegra_dc_dp_data *dp,
u32 reg, u32 mask, u32 exp_val,
}
}
- while ((timeout_retries > 0) && (defer_retries > 0)) {
+ while (1) {
if ((timeout_retries != DP_AUX_TIMEOUT_MAX_TRIES) ||
(defer_retries != DP_AUX_DEFER_MAX_TRIES))
usleep_range(DP_DPCP_RETRY_SLEEP_NS,
}
}
- while ((timeout_retries > 0) && (defer_retries > 0)) {
+ while (1) {
if ((timeout_retries != DP_AUX_TIMEOUT_MAX_TRIES) ||
(defer_retries != DP_AUX_DEFER_MAX_TRIES))
usleep_range(DP_DPCP_RETRY_SLEEP_NS,
return ret;
}
-/* I2C read over DPAUX cannot handle more than 16B per transaction due to
- * DPAUX transaction limitation.
- * This requires breaking each read into multiple i2c write/read transaction */
-static int tegra_dc_i2c_read(struct tegra_dc_dp_data *dp, u32 i2c_addr,
- u32 addr, u8 *data, u32 *size, u32 *aux_stat)
+/* TODO: Handle update status scenario and size > 16 bytes*/
+static int tegra_dc_dp_i2c_write(struct tegra_dc_dp_data *dp, u32 i2c_addr,
+ u8 *data, u32 *size, u32 *aux_stat)
{
- u32 finished = 0;
- u32 cur_size;
- int ret = 0;
- u32 len;
- u8 iaddr = (u8)addr;
+ int ret = 0;
+
+ if (*size == 0) {
+ dev_err(&dp->dc->ndev->dev,
+ "dp: i2c write size can't be 0\n");
+ return -EINVAL;
+ }
+
+ if (dp->dc->out->type == TEGRA_DC_OUT_FAKE_DP)
+ return ret;
+
+ mutex_lock(&dp->dpaux_lock);
+
+ ret = tegra_dc_dpaux_write_chunk_locked(dp,
+ DPAUX_DP_AUXCTL_CMD_MOTWR,
+ i2c_addr, data, size, aux_stat);
+
+ mutex_unlock(&dp->dpaux_lock);
+
+ return ret;
+}
+
+static int tegra_dc_dp_i2c_read(struct tegra_dc_dp_data *dp, u32 i2c_addr,
+ u8 *data, u32 *size, u32 *aux_stat)
+{
+ u32 finished = 0;
+ u32 cur_size;
+ int ret = 0;
if (*size == 0) {
dev_err(&dp->dc->ndev->dev,
return ret;
mutex_lock(&dp->dpaux_lock);
+
do {
cur_size = *size - finished;
+
if (cur_size > DP_AUX_MAX_BYTES)
cur_size = DP_AUX_MAX_BYTES;
- len = 1;
- ret = tegra_dc_dpaux_write_chunk_locked(dp,
- DPAUX_DP_AUXCTL_CMD_I2CWR,
- i2c_addr, &iaddr, &len, aux_stat);
- if (!ret) {
- ret = tegra_dc_dpaux_read_chunk_locked(dp,
- DPAUX_DP_AUXCTL_CMD_I2CRD,
- i2c_addr, data, &cur_size, aux_stat);
- }
+ ret = tegra_dc_dpaux_read_chunk_locked(dp,
+ DPAUX_DP_AUXCTL_CMD_MOTRD,
+ i2c_addr, data, &cur_size, aux_stat);
if (ret)
break;
- iaddr += cur_size;
data += cur_size;
finished += cur_size;
} while (*size > finished);
+
+ cur_size = 0;
+ tegra_dc_dpaux_read_chunk_locked(dp,
+ DPAUX_DP_AUXCTL_CMD_I2CRD,
+ i2c_addr, data, &cur_size, aux_stat);
+
mutex_unlock(&dp->dpaux_lock);
*size = finished;
+
return ret;
}
u32 aux_stat;
int status = 0;
u32 len = 0;
- u32 start_addr = 0;
struct tegra_dc_dp_data *dp = tegra_dc_get_outdata(dc);
for (i = 0; i < num; ++i) {
pmsg = &msgs[i];
- if (!pmsg->flags && pmsg->addr == 0x50) { /* write */
- /*
- * Ignore writes to I2C addr 0x30 for now.
- * This means we can't access 256-byte chunks.
- */
- start_addr = pmsg->buf[0];
- } else if (pmsg->flags & I2C_M_RD) { /* Read */
+ if (!pmsg->flags) {
len = pmsg->len;
- status = tegra_dc_i2c_read(dp, pmsg->addr, start_addr,
- pmsg->buf, &len, &aux_stat);
+
+ status = tegra_dc_dp_i2c_write(dp, 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_dp_i2c_read(dp, pmsg->addr,
+ pmsg->buf, &len, &aux_stat);
if (status) {
dev_err(&dp->dc->ndev->dev,
"dp: Failed for I2C read"
return status;
}
} else {
- /* No other functionalities are supported for now */
dev_err(&dp->dc->ndev->dev,
- "dp: i2x_xfer: Unknown flag 0x%x\n",
+ "dp: i2x_xfer: Invalid i2c flag 0x%x\n",
pmsg->flags);
return -EINVAL;
}
}
+
return i;
}
return ret;
}
-static inline int tegra_dp_dpcd_write_field(struct tegra_dc_dp_data *dp,
+int tegra_dp_dpcd_write_field(struct tegra_dc_dp_data *dp,
u32 cmd, u8 mask, u8 data)
{
u8 dpcd_data;
dc->out->hotplug_state = new_state;
- tegra_hpd_set_pending_evt(&dp->hpd_data);
+ tegra_dp_pending_hpd(dp);
return len;
}
.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_dp_link_config *cfg = NULL;
+ u32 bits_per_pixel = 0, previous_bpp = 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;
+ previous_bpp = cfg->bits_per_pixel;
+
+ /* disable the dc and output controllers */
+ if (dp->dc->enabled)
+ tegra_dc_disable(dp->dc);
+
+ 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);
+ bits_per_pixel = previous_bpp;
+ }
+
+ dp->dc->out->depth = bits_per_pixel;
+ cfg->bits_per_pixel = bits_per_pixel;
+
+ ret = tegra_dc_dp_calc_config(dp, dp->mode, cfg);
+ if (!ret) {
+ dev_info(&dp->dc->ndev->dev, "Unable to set %ubpp properly. Restoring to %ubpp\n",
+ cfg->bits_per_pixel, previous_bpp);
+ dp->dc->out->depth = previous_bpp;
+ cfg->bits_per_pixel = previous_bpp;
+ }
+
+ /* enable the dc and output controllers */
+ if (!dp->dc->enabled)
+ tegra_dc_enable(dp->dc);
+
+ 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,
+};
+
+#if defined(CONFIG_MODS) && (defined(CONFIG_ARCH_TEGRA_21x_SOC) || \
+ defined(CONFIG_ARCH_TEGRA_18x_SOC))
+static u16 dp_i2c_addr;
+static u32 dp_i2c_num_bytes;
+
+static int dpaux_i2c_data_show(struct seq_file *s, void *unused)
+{
+ struct tegra_dc_dp_data *dp = s->private;
+ u32 size = dp_i2c_num_bytes;
+ u32 i, j, aux_stat;
+ u8 row_size = 16;
+ u8 *data;
+
+ data = kzalloc(size, GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ tegra_dc_dp_i2c_read(dp, dp_i2c_addr, data, &size, &aux_stat);
+ 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");
+ }
+
+ kfree(data);
+ return 0;
+}
+
+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;
+ u32 i = 0, size = 0;
+ u32 aux_stat;
+ u8 *data;
+ char tmp[3];
+ char *buf;
+ int ret = count;
+
+ buf = kzalloc(count, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ data = kzalloc(count, GFP_KERNEL);
+ if (!data) {
+ ret = -ENOMEM;
+ goto free_mem;
+ }
+
+ if (copy_from_user(buf, user_buf, count)) {
+ ret = -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 (isspace(buf[i])) {
+ i += 1;
+ continue;
+ }
+
+ tmp[0] = buf[i]; tmp[1] = buf[i + 1]; tmp[2] = '\0';
+ if (kstrtou8(tmp, 16, data + size)) {
+ ret = -EINVAL;
+ goto free_mem;
+ }
+
+ size += 1;
+ i += 2;
+ }
+
+ tegra_dc_dp_i2c_write(dp, dp_i2c_addr, data, &size, &aux_stat);
+
+free_mem:
+ kfree(buf);
+ 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 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 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", S_IRUGO | S_IWUGO, dpaux_i2c_dir,
+ &dp_i2c_addr);
+ if (!retval)
+ goto free_out;
+ retval = debugfs_create_u32("num_bytes", S_IRUGO | S_IWUGO,
+ dpaux_i2c_dir, &dp_i2c_num_bytes);
+ if (!retval)
+ goto free_out;
+ retval = debugfs_create_file("data", S_IRUGO, 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;
+}
+#endif
+
static struct dentry *dpdir;
static void tegra_dc_dp_debug_create(struct tegra_dc_dp_data *dp)
&link_speed_fops);
if (!retval)
goto free_out;
- retval = debugfs_create_file("hotplug", S_IRUGO, dpdir, dp,
- &dbg_hotplug_fops);
+ retval = debugfs_create_file("bitsperpixel", S_IRUGO, dpdir, dp,
+ &bits_per_pixel_fops);
+ if (!retval)
+ goto free_out;
+ retval = debugfs_create_file("test_settings", S_IRUGO, dpdir, dp,
+ &test_settings_fops);
+ if (!retval)
+ goto free_out;
+
+ /* hotplug not allowed for eDP */
+ if (is_hotplug_supported(dp)) {
+ retval = debugfs_create_file("hotplug", S_IRUGO, dpdir, dp,
+ &dbg_hotplug_fops);
+ if (!retval)
+ goto free_out;
+ }
+
+#if defined(CONFIG_MODS) && (defined(CONFIG_ARCH_TEGRA_21x_SOC) || \
+ defined(CONFIG_ARCH_TEGRA_18x_SOC))
+ retval = tegra_dpaux_i2c_dir_create(dp, dpdir);
if (!retval)
goto free_out;
+#endif
return;
free_out:
do {
ret = tegra_dc_dp_dpcd_write(dp, NV_DPCD_SET_POWER, state);
- } while ((retry++ < DP_POWER_ON_MAX_TRIES) && ret);
+ } while ((state != NV_DPCD_SET_POWER_VAL_D3_PWRDWN) &&
+ (retry++ < DP_POWER_ON_MAX_TRIES) && ret);
return ret;
}
unsigned long rate;
cfg->is_valid = false;
-
rate = tegra_dc_pclk_round_rate(dp->sor->dc, dp->sor->dc->mode.pclk);
if (!link_rate || !cfg->lane_count || !rate ||
static int tegra_dp_init_max_link_cfg(struct tegra_dc_dp_data *dp,
struct tegra_dc_dp_link_config *cfg)
{
-
-#ifdef CONFIG_TEGRA_DC_FAKE_PANEL_SUPPORT
if (dp->dc->out->type == TEGRA_DC_OUT_FAKE_DP)
tegra_dc_init_fake_panel_link_cfg(cfg);
- else
-#endif
- {
+ else {
u8 dpcd_data;
int ret;
- CHECK_RET(tegra_dc_dp_dpcd_read(dp, NV_DPCD_MAX_LANE_COUNT,
- &dpcd_data));
+ if (dp->sink_cap_valid)
+ dpcd_data = dp->sink_cap[NV_DPCD_MAX_LANE_COUNT];
+ else
+ CHECK_RET(tegra_dc_dp_dpcd_read(dp,
+ NV_DPCD_MAX_LANE_COUNT, &dpcd_data));
cfg->max_lane_count = dpcd_data & NV_DPCD_MAX_LANE_COUNT_MASK;
- cfg->tps3_supported =
- (dpcd_data & NV_DPCD_MAX_LANE_COUNT_TPS3_SUPPORTED_YES) ?
- true : false;
+ if (cfg->max_lane_count >= 4)
+ cfg->max_lane_count = 4;
+ else if (cfg->max_lane_count >= 2)
+ cfg->max_lane_count = 2;
+ else
+ cfg->max_lane_count = 1;
+
+ if (dp->pdata && dp->pdata->max_n_lanes &&
+ dp->pdata->max_n_lanes < cfg->max_lane_count)
+ cfg->max_lane_count = dp->pdata->max_n_lanes;
+
+ cfg->tps3_supported =
+ (dpcd_data &
+ NV_DPCD_MAX_LANE_COUNT_TPS3_SUPPORTED_YES) ?
+ true : false;
cfg->support_enhanced_framing =
(dpcd_data & NV_DPCD_MAX_LANE_COUNT_ENHANCED_FRAMING_YES) ?
true : false;
- CHECK_RET(tegra_dc_dp_dpcd_read(dp, NV_DPCD_MAX_DOWNSPREAD,
- &dpcd_data));
+ if (dp->sink_cap_valid)
+ dpcd_data = dp->sink_cap[NV_DPCD_MAX_DOWNSPREAD];
+ else
+ CHECK_RET(tegra_dc_dp_dpcd_read(dp,
+ NV_DPCD_MAX_DOWNSPREAD, &dpcd_data));
cfg->downspread =
- (dpcd_data & NV_DPCD_MAX_DOWNSPREAD_VAL_0_5_PCT) ?
- true : false;
-
+ (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;
+ NV_DPCD_MAX_DOWNSPREAD_NO_AUX_HANDSHAKE_LT_T) ?
+ true : false;
CHECK_RET(tegra_dc_dp_dpcd_read(dp,
NV_DPCD_TRAINING_AUX_RD_INTERVAL, &dpcd_data));
cfg->aux_rd_interval = dpcd_data;
- CHECK_RET(tegra_dc_dp_dpcd_read(dp, NV_DPCD_MAX_LINK_BANDWIDTH,
- &cfg->max_link_bw));
+ if (dp->sink_cap_valid)
+ cfg->max_link_bw =
+ dp->sink_cap[NV_DPCD_MAX_LINK_BANDWIDTH];
+ else
+ CHECK_RET(tegra_dc_dp_dpcd_read(dp,
+ NV_DPCD_MAX_LINK_BANDWIDTH,
+ &cfg->max_link_bw));
+
+ if (cfg->max_link_bw >= SOR_LINK_SPEED_G5_4)
+ cfg->max_link_bw = SOR_LINK_SPEED_G5_4;
+ else if (cfg->max_link_bw >= SOR_LINK_SPEED_G2_7)
+ cfg->max_link_bw = SOR_LINK_SPEED_G2_7;
+ else
+ cfg->max_link_bw = SOR_LINK_SPEED_G1_62;
+
+ if (dp->pdata && dp->pdata->max_link_bw &&
+ dp->pdata->max_link_bw < cfg->max_link_bw)
+ cfg->max_link_bw = dp->pdata->max_link_bw;
CHECK_RET(tegra_dc_dp_dpcd_read(dp, NV_DPCD_EDP_CONFIG_CAP,
&dpcd_data));
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;
-
+ (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;
+ NV_DPCD_EDP_CONFIG_CAP_DISPLAY_CONTROL_CAP_YES) ?
+ true : false;
}
cfg->bits_per_pixel = dp->dc->out->depth ? : 24;
cfg->lane_count = cfg->max_lane_count;
- cfg->link_bw = (dp->pdata && dp->pdata->link_bw) ?
- dp->pdata->link_bw : cfg->max_link_bw;
+ cfg->link_bw = cfg->max_link_bw;
cfg->enhanced_framing = cfg->support_enhanced_framing;
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);
-}
-
static int tegra_dp_set_enhanced_framing(struct tegra_dc_dp_data *dp,
bool enable)
{
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);
-
- CHECK_RET(tegra_dp_dpcd_write_field(dp, NV_DPCD_LANE_COUNT_SET,
- NV_DPCD_LANE_COUNT_SET_MASK,
- lane_cnt));
-
- return 0;
-}
-
-static void tegra_dp_link_cal(struct tegra_dc_dp_data *dp)
-{
- struct tegra_dc_sor_data *sor = dp->sor;
- struct tegra_dc_dp_link_config *cfg = &dp->link_cfg;
- u32 load_adj;
-
- switch (cfg->link_bw) {
- case SOR_LINK_SPEED_G1_62:
- load_adj = 0x3;
- break;
- case SOR_LINK_SPEED_G2_7:
- load_adj = 0x4;
- break;
- case SOR_LINK_SPEED_G5_4:
- load_adj = 0x6;
- break;
- default:
- BUG();
- }
-
- tegra_sor_write_field(sor, NV_SOR_PLL1,
- NV_SOR_PLL1_LOADADJ_DEFAULT_MASK,
- load_adj << NV_SOR_PLL1_LOADADJ_SHIFT);
-}
-
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);
u32 aux_stat = tegra_dpaux_readl(dp, DPAUX_DP_AUXSTAT);
bool link_stable = !!true;
- u8 dpcd_200h_205h[6];
+ u8 dpcd_200h_205h[DPCD_LINK_SINK_STATUS_REGS] = {0, 0, 0, 0, 0, 0};
u32 n_lanes = dp->lt_data.n_lanes;
tegra_dc_io_start(dp->dc);
* HW failed to automatically read DPCD
* offsets 0x200-0x205. Initiate SW transaction.
*/
- for (cnt = 0; cnt < 6; cnt++) {
- tegra_dc_dp_dpcd_read(dp, 0x200 + cnt,
+ 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 {
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);
#undef LANE0_1_CR_CE_SL_MASK
#undef LANE0_CR_CE_SL_MASK
#undef INTERLANE_ALIGN_MASK
+#undef DPCD_LINK_SINK_STATUS_REGS
+
}
static irqreturn_t tegra_dp_irq(int irq, void *ptr)
tegra_dc_io_end(dc);
if (status & (DPAUX_INTR_AUX_PLUG_EVENT_PENDING |
- DPAUX_INTR_AUX_UNPLUG_EVENT_PENDING)) {
- 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");
- else
+ complete_all(&dp->hpd_plug);
+ } else {
dev_info(&dp->dc->ndev->dev,
"dp: unplug event received\n");
- tegra_hpd_set_pending_evt(&dp->hpd_data);
+ INIT_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");
return 0;
}
+static int tegra_dp_prods_init(struct tegra_dc_dp_data *dp)
+{
+ struct device_node *np_prod = of_find_node_by_path(PROD_NODE);
+
+ if (!np_prod) {
+ dev_warn(&dp->dc->ndev->dev,
+ "dp: find prod node failed\n");
+ return -EINVAL;
+ }
+
+ dp->prod_list =
+ tegra_prod_init((const struct device_node *)np_prod);
+ if (IS_ERR(dp->prod_list)) {
+ dev_warn(&dp->dc->ndev->dev,
+ "dp: prod list init failed with error %ld\n",
+ PTR_ERR(dp->prod_list));
+ of_node_put(np_prod);
+ return -EINVAL;
+ }
+
+ of_node_put(np_prod);
+ return 0;
+}
+
static int tegra_dc_dp_init(struct tegra_dc *dc)
{
struct tegra_dc_dp_data *dp;
tegra_dphdcp_debugfs_init(dp->dphdcp);
#endif
+ tegra_dp_prods_init(dp);
+
init_completion(&dp->aux_tx);
+ init_completion(&dp->hpd_plug);
mutex_init(&dp->dpaux_lock);
tegra_dc_set_outdata(dc, 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
+
tegra_dc_dp_debug_create(dp);
of_node_put(np_dp);
tegra_sor_tpg(dp->sor, tp, n_lanes);
}
-static void tegra_dp_tu_config(struct tegra_dc_dp_data *dp,
- const struct tegra_dc_dp_link_config *cfg)
+static void tegra_dp_read_sink_cap(struct tegra_dc_dp_data *dp)
{
- 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));
+ 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_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));
+ tegra_dc_io_start(dc);
- 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);
+ dp->sink_cap_valid = false;
- 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);
+ err = tegra_dc_dpaux_read(dp, DPAUX_DP_AUXCTL_CMD_AUXRD,
+ start_offset, dp->sink_cap, &sink_cap_rd_size,
+ &aux_stat);
+ if (!err)
+ dp->sink_cap_valid = true;
- 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);
+ tegra_dc_io_end(dc);
}
static void tegra_dp_hpd_op_edid_ready(void *drv_data)
*/
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_io_start(dc);
+ tegra_dc_dp_dpcd_read(dp, NV_DPCD_SINK_COUNT,
+ &dp->sink_cnt_cp_ready);
+ tegra_dc_io_end(dc);
}
static void tegra_dp_hpd_op_edid_recheck(void *drv_data)
tegra_dp_int_dis(dp, DPAUX_INTR_EN_AUX_IRQ_EVENT);
}
+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_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
+}
+
static void tegra_dc_dp_enable(struct tegra_dc *dc)
{
struct tegra_dc_dp_data *dp = tegra_dc_get_outdata(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 power on panel (0x%x)\n", ret);
- tegra_dc_io_end(dc);
+ "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_dp_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);
+ }
+
tegra_dp_dpcd_init(dp);
tegra_dc_sor_enable_dp(dp->sor);
- tegra_dp_set_enhanced_framing(dp, cfg->enhanced_framing);
-
if (cfg->alt_scramber_reset_cap)
tegra_dc_dp_set_assr(dp, true);
else
tegra_dc_dp_dpcd_write(dp, NV_DPCD_MAIN_LINK_CHANNEL_CODING_SET,
NV_DPCD_MAIN_LINK_CHANNEL_CODING_SET_ANSI_8B10B);
- tegra_sor_writel(sor, NV_SOR_LVDS, 0);
-
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_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);
+ /*
+ * 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_update_link_config(dp);
+ tegra_dp_set_enhanced_framing(dp, cfg->enhanced_framing);
tegra_dp_tpg(dp, TRAINING_PATTERN_DISABLE, cfg->lane_count);
/* Host is ready. Start link training. */
dp->enabled = true;
- if (likely(dc->out->type != TEGRA_DC_OUT_FAKE_DP)) {
+#if defined(CONFIG_ARCH_TEGRA_21x_SOC) || defined(CONFIG_ARCH_TEGRA_18x_SOC)
+ if (tegra_dc_is_ext_dp_panel(dc))
+ tegra_hda_set_data(dp, SINK_DP);
+#endif
+
+ if (likely(dc->out->type != TEGRA_DC_OUT_FAKE_DP) &&
+ !no_lt_at_unblank) {
tegra_dp_lt_set_pending_evt(&dp->lt_data);
ret = tegra_dp_lt_wait_for_completion(&dp->lt_data,
LT_TIMEOUT_MS);
tegra_dc_sor_attach(dp->sor);
}
- tegra_dphdcp_set_plug(dp->dphdcp, true);
+ if (tegra_dc_is_ext_dp_panel(dc) &&
+ dc->out->type != TEGRA_DC_OUT_FAKE_DP)
+ tegra_dphdcp_set_plug(dp->dphdcp, true);
+ dc->connected = true;
tegra_dc_io_end(dc);
return;
clk_put(dp->dpaux_clk);
clk_put(dp->parent_clk);
#endif
+
devm_iounmap(&dc->ndev->dev, dp->aux_base);
devm_release_mem_region(&dc->ndev->dev,
dp->res->start,
if (!np_dp || !of_device_is_available(np_dp))
release_resource(dp->res);
devm_kfree(&dc->ndev->dev, dp);
+ if (!IS_ERR(dp->prod_list))
+ tegra_prod_release(&dp->prod_list);
of_node_put(np_dp);
}
dp->enabled = false;
tegra_dc_io_start(dc);
- tegra_dphdcp_set_plug(dp->dphdcp, false);
+
+ if (tegra_dc_is_ext_dp_panel(dc) &&
+ dc->out->type != TEGRA_DC_OUT_FAKE_DP)
+ tegra_dphdcp_set_plug(dp->dphdcp, false);
cancel_delayed_work_sync(&dp->irq_evt_dwork);
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);
tegra_dc_sor_disable(dp->sor, false);
tegra_dp_clk_disable(dp);
tegra_dc_io_end(dc);
+
+#if defined(CONFIG_ARCH_TEGRA_21x_SOC) || defined(CONFIG_ARCH_TEGRA_18x_SOC)
+ if (tegra_dc_is_ext_dp_panel(dc))
+ tegra_hda_reset_data();
+#endif
}
void tegra_dc_dp_pre_disable_link(struct tegra_dc_dp_data *dp)
return false;
if (dc->out->type == TEGRA_DC_OUT_FAKE_DP ||
- tegra_platform_is_linsim() ||
- !tegra_dc_is_ext_dp_panel(dc))
+ tegra_platform_is_linsim())
return true;
tegra_dpaux_clk_enable(dp);
if (tegra_platform_is_linsim())
return true;
- tegra_hpd_set_pending_evt(&dp->hpd_data);
+ tegra_dp_pending_hpd(dp);
return tegra_dc_hpd(dc);
}
if (dp->dc->out->type != TEGRA_DC_OUT_FAKE_DP)
tegra_dp_enable_irq(dp->irq);
- tegra_hpd_set_pending_evt(&dp->hpd_data);
+ tegra_dp_pending_hpd(dp);
dp->suspended = false;
}
return false;
if (mode->pixclock && tegra_dc_get_out_max_pixclock(dc) &&
- mode->pixclock > tegra_dc_get_out_max_pixclock(dc))
+ mode->pixclock < tegra_dc_get_out_max_pixclock(dc))
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
+ && mode->refresh == 60) ? true : false;
+
return true;
}
static void tegra_dp_hpd_op_init(void *drv_data)
{
- struct tegra_dc_dp_data *dp __maybe_unused = drv_data;
+ struct tegra_dc_dp_data *dp = drv_data;
#ifdef CONFIG_SWITCH
- dp->hpd_data.hpd_switch_name = "dp";
- dp->hpd_data.audio_switch_name = "dp_audio";
+ if (tegra_dc_is_ext_dp_panel(dp->dc)) {
+ dp->hpd_data.hpd_switch_name = "dp";
+ dp->hpd_data.audio_switch_name = "dp_audio";
+ }
#endif
}
+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 struct tegra_hpd_ops hpd_ops = {
.edid_read = tegra_dp_hpd_op_edid_read,
.edid_ready = tegra_dp_hpd_op_edid_ready,
.get_mode_filter = tegra_dp_op_get_mode_filter,
.get_hpd_state = tegra_dp_hpd_op_get_hpd_state,
.init = tegra_dp_hpd_op_init,
+ .edid_read_prepare = tegra_dp_hpd_op_edid_read_prepare,
};
struct tegra_dc_out_ops tegra_dc_dp_ops = {