/* * drivers/video/tegra/dc/dp.c * * Copyright (c) 2011-2013, 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 #include #include #include #include #include #include #include #include #include "dp.h" #include "sor.h" #include "sor_regs.h" #include "dpaux_regs.h" #include "dc_priv.h" #include "edid.h" static void tegra_dp_lt_config(struct tegra_dc_dp_data *dp, u32 pe[4], u32 vs[4], u32 pc[4]); static int tegra_dp_link_config(struct tegra_dc_dp_data *dp, const struct tegra_dc_dp_link_config *cfg); static inline u32 tegra_dpaux_readl(struct tegra_dc_dp_data *dp, u32 reg) { return readl(dp->aux_base + reg * 4); } static inline void tegra_dpaux_writel(struct tegra_dc_dp_data *dp, u32 reg, u32 val) { writel(val, dp->aux_base + reg * 4); } static inline void tegra_dp_int_en(struct tegra_dc_dp_data *dp, u32 intr) { u32 val; /* clear pending interrupt */ tegra_dpaux_writel(dp, DPAUX_INTR_AUX, intr); val = tegra_dpaux_readl(dp, DPAUX_INTR_EN_AUX); val |= intr; tegra_dpaux_writel(dp, DPAUX_INTR_EN_AUX, val); } static inline void tegra_dp_int_dis(struct tegra_dc_dp_data *dp, u32 intr) { u32 val; val = tegra_dpaux_readl(dp, DPAUX_INTR_EN_AUX); val &= intr; tegra_dpaux_writel(dp, DPAUX_INTR_EN_AUX, val); } 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 u32 tegra_dc_dpaux_poll_register(struct tegra_dc_dp_data *dp, u32 reg, u32 mask, u32 exp_val, u32 poll_interval_us, u32 timeout_ms) { unsigned long timeout_jf = jiffies + msecs_to_jiffies(timeout_ms); u32 reg_val = 0; do { usleep_range(poll_interval_us, poll_interval_us << 1); reg_val = tegra_dpaux_readl(dp, reg); } while (((reg_val & mask) != exp_val) && time_after(timeout_jf, jiffies)); if ((reg_val & mask) == exp_val) return 0; /* success */ dev_dbg(&dp->dc->ndev->dev, "sor_poll_register 0x%x: timeout\n", reg); return jiffies - timeout_jf + 1; } static inline int tegra_dpaux_wait_transaction(struct tegra_dc_dp_data *dp) { /* According to DP spec, each aux transaction needs to finish within 40ms. */ if (tegra_dc_dpaux_poll_register(dp, DPAUX_DP_AUXCTL, DPAUX_DP_AUXCTL_TRANSACTREQ_MASK, DPAUX_DP_AUXCTL_TRANSACTREQ_DONE, 100, DP_AUX_TIMEOUT_MS) != 0) { dev_err(&dp->dc->ndev->dev, "dp: DPAUX transaction timeout\n"); return -EFAULT; } return 0; } static int tegra_dc_dpaux_write_chunk(struct tegra_dc_dp_data *dp, u32 cmd, u32 addr, u8 *data, u32 *size, u32 *aux_stat) { int i; u32 reg_val; u32 timeout_retries = DP_AUX_TIMEOUT_MAX_TRIES; u32 defer_retries = DP_AUX_DEFER_MAX_TRIES; if (*size >= DP_AUX_MAX_BYTES) return -EINVAL; /* only write one chunk of data */ /* Make sure the command is write command */ switch (cmd) { case DPAUX_DP_AUXCTL_CMD_I2CWR: case DPAUX_DP_AUXCTL_CMD_MOTWR: case DPAUX_DP_AUXCTL_CMD_AUXWR: break; default: dev_err(&dp->dc->ndev->dev, "dp: aux write cmd 0x%x is invalid\n", cmd); return -EINVAL; } *aux_stat = tegra_dpaux_readl(dp, DPAUX_DP_AUXSTAT); if (!(*aux_stat & DPAUX_DP_AUXSTAT_HPD_STATUS_PLUGGED)) { dev_err(&dp->dc->ndev->dev, "dp: HPD is not detected\n"); return -EFAULT; } tegra_dpaux_writel(dp, DPAUX_DP_AUXADDR, addr); for (i = 0; i < DP_AUX_MAX_BYTES/4; ++i) { tegra_dpaux_writel(dp, DPAUX_DP_AUXDATA_WRITE_W(i), (u32)*data); data += 4; } reg_val = tegra_dpaux_readl(dp, DPAUX_DP_AUXCTL); reg_val &= ~DPAUX_DP_AUXCTL_CMD_MASK; reg_val |= cmd; reg_val &= ~DPAUX_DP_AUXCTL_CMDLEN_FIELD; reg_val |= (*size << DPAUX_DP_AUXCTL_CMDLEN_SHIFT); while ((timeout_retries > 0) && (defer_retries > 0)) { if ((timeout_retries != DP_AUX_TIMEOUT_MAX_TRIES) || (defer_retries != DP_AUX_DEFER_MAX_TRIES)) usleep_range(DP_DPCP_RETRY_SLEEP_NS, DP_DPCP_RETRY_SLEEP_NS << 1); reg_val |= DPAUX_DP_AUXCTL_TRANSACTREQ_PENDING; tegra_dpaux_writel(dp, DPAUX_DP_AUXCTL, reg_val); if (tegra_dpaux_wait_transaction(dp)) dev_err(&dp->dc->ndev->dev, "dp: aux write transaction timeout\n"); *aux_stat = tegra_dpaux_readl(dp, DPAUX_DP_AUXSTAT); if ((*aux_stat & DPAUX_DP_AUXSTAT_TIMEOUT_ERROR_PENDING) || (*aux_stat & DPAUX_DP_AUXSTAT_RX_ERROR_PENDING) || (*aux_stat & DPAUX_DP_AUXSTAT_SINKSTAT_ERROR_PENDING) || (*aux_stat & DPAUX_DP_AUXSTAT_NO_STOP_ERROR_PENDING)) { if (timeout_retries-- > 0) { dev_dbg(&dp->dc->ndev->dev, "dp: aux write retry (0x%x) -- %d\n", *aux_stat, timeout_retries); /* clear the error bits */ tegra_dpaux_writel(dp, DPAUX_DP_AUXSTAT, *aux_stat); continue; } else { dev_err(&dp->dc->ndev->dev, "dp: aux write got error (0x%x)\n", *aux_stat); return -EFAULT; } } if ((*aux_stat & DPAUX_DP_AUXSTAT_REPLYTYPE_I2CDEFER) || (*aux_stat & DPAUX_DP_AUXSTAT_REPLYTYPE_DEFER)) { if (defer_retries-- > 0) { dev_dbg(&dp->dc->ndev->dev, "dp: aux write defer (0x%x) -- %d\n", *aux_stat, defer_retries); /* clear the error bits */ tegra_dpaux_writel(dp, DPAUX_DP_AUXSTAT, *aux_stat); continue; } else { dev_err(&dp->dc->ndev->dev, "dp: aux write defer exceeds max retries " "(0x%x)\n", *aux_stat); return -EFAULT; } } if ((*aux_stat & DPAUX_DP_AUXSTAT_REPLYTYPE_MASK) == DPAUX_DP_AUXSTAT_REPLYTYPE_ACK) { *size = ((*aux_stat) & DPAUX_DP_AUXSTAT_REPLY_M_MASK); return 0; } else { dev_err(&dp->dc->ndev->dev, "dp: aux write failed (0x%x)\n", *aux_stat); return -EFAULT; } } /* Should never come to here */ return -EFAULT; } static int tegra_dc_dpaux_write(struct tegra_dc_dp_data *dp, u32 cmd, u32 addr, u8 *data, u32 *size, u32 *aux_stat) { u32 cur_size = 0; u32 finished = 0; int ret = 0; do { cur_size = *size - finished; if (cur_size >= DP_AUX_MAX_BYTES) cur_size = DP_AUX_MAX_BYTES - 1; ret = tegra_dc_dpaux_write_chunk(dp, cmd, addr, data, &cur_size, aux_stat); finished += cur_size; addr += cur_size; data += cur_size; if (ret) break; } while (*size >= finished); *size = finished; return ret; } static int tegra_dc_dpaux_read_chunk(struct tegra_dc_dp_data *dp, u32 cmd, u32 addr, u8 *data, u32 *size, u32 *aux_stat) { u32 reg_val; u32 timeout_retries = DP_AUX_TIMEOUT_MAX_TRIES; u32 defer_retries = DP_AUX_DEFER_MAX_TRIES; if (*size >= DP_AUX_MAX_BYTES) return -EINVAL; /* only read one chunk */ /* Check to make sure the command is read command */ switch (cmd) { case DPAUX_DP_AUXCTL_CMD_I2CRD: case DPAUX_DP_AUXCTL_CMD_I2CREQWSTAT: case DPAUX_DP_AUXCTL_CMD_MOTRD: case DPAUX_DP_AUXCTL_CMD_AUXRD: break; default: dev_err(&dp->dc->ndev->dev, "dp: aux read cmd 0x%x is invalid\n", cmd); return -EINVAL; } *aux_stat = tegra_dpaux_readl(dp, DPAUX_DP_AUXSTAT); if (!(*aux_stat & DPAUX_DP_AUXSTAT_HPD_STATUS_PLUGGED)) { dev_err(&dp->dc->ndev->dev, "dp: HPD is not detected\n"); return -EFAULT; } tegra_dpaux_writel(dp, DPAUX_DP_AUXADDR, addr); reg_val = tegra_dpaux_readl(dp, DPAUX_DP_AUXCTL); reg_val &= ~DPAUX_DP_AUXCTL_CMD_MASK; reg_val |= cmd; reg_val &= ~DPAUX_DP_AUXCTL_CMDLEN_FIELD; reg_val |= (*size << DPAUX_DP_AUXCTL_CMDLEN_SHIFT); while ((timeout_retries > 0) && (defer_retries > 0)) { if ((timeout_retries != DP_AUX_TIMEOUT_MAX_TRIES) || (defer_retries != DP_AUX_DEFER_MAX_TRIES)) usleep_range(DP_DPCP_RETRY_SLEEP_NS, DP_DPCP_RETRY_SLEEP_NS << 1); reg_val |= DPAUX_DP_AUXCTL_TRANSACTREQ_PENDING; tegra_dpaux_writel(dp, DPAUX_DP_AUXCTL, reg_val); if (tegra_dpaux_wait_transaction(dp)) dev_err(&dp->dc->ndev->dev, "dp: aux read transaction timeout\n"); *aux_stat = tegra_dpaux_readl(dp, DPAUX_DP_AUXSTAT); if ((*aux_stat & DPAUX_DP_AUXSTAT_TIMEOUT_ERROR_PENDING) || (*aux_stat & DPAUX_DP_AUXSTAT_RX_ERROR_PENDING) || (*aux_stat & DPAUX_DP_AUXSTAT_SINKSTAT_ERROR_PENDING) || (*aux_stat & DPAUX_DP_AUXSTAT_NO_STOP_ERROR_PENDING)) { if (timeout_retries-- > 0) { dev_dbg(&dp->dc->ndev->dev, "dp: aux read retry (0x%x) -- %d\n", *aux_stat, timeout_retries); /* clear the error bits */ tegra_dpaux_writel(dp, DPAUX_DP_AUXSTAT, *aux_stat); continue; /* retry */ } else { dev_err(&dp->dc->ndev->dev, "dp: aux read got error (0x%x)\n", *aux_stat); return -EFAULT; } } if ((*aux_stat & DPAUX_DP_AUXSTAT_REPLYTYPE_I2CDEFER) || (*aux_stat & DPAUX_DP_AUXSTAT_REPLYTYPE_DEFER)) { if (defer_retries-- > 0) { dev_dbg(&dp->dc->ndev->dev, "dp: aux read defer (0x%x) -- %d\n", *aux_stat, defer_retries); /* clear the error bits */ tegra_dpaux_writel(dp, DPAUX_DP_AUXSTAT, *aux_stat); continue; } else { dev_err(&dp->dc->ndev->dev, "dp: aux read defer exceeds max retries " "(0x%x)\n", *aux_stat); return -EFAULT; } } if ((*aux_stat & DPAUX_DP_AUXSTAT_REPLYTYPE_MASK) == DPAUX_DP_AUXSTAT_REPLYTYPE_ACK) { int i; u32 temp_data[4]; for (i = 0; i < DP_AUX_MAX_BYTES/4; ++i) temp_data[i] = tegra_dpaux_readl(dp, DPAUX_DP_AUXDATA_READ_W(i)); *size = ((*aux_stat) & DPAUX_DP_AUXSTAT_REPLY_M_MASK); memcpy(data, temp_data, *size); return 0; } else { dev_err(&dp->dc->ndev->dev, "dp: aux read failed (0x%x\n", *aux_stat); return -EFAULT; } } /* Should never come to here */ return -EFAULT; } static int tegra_dc_dpaux_read(struct tegra_dc_dp_data *dp, u32 cmd, u32 addr, u8 *data, u32 *size, u32 *aux_stat) { u32 finished = 0; u32 cur_size; int ret = 0; do { cur_size = *size - finished; if (cur_size >= DP_AUX_MAX_BYTES) cur_size = DP_AUX_MAX_BYTES - 1; ret = tegra_dc_dpaux_read_chunk(dp, cmd, addr, data, &cur_size, aux_stat); /* cur_size should be the real size returned */ addr += cur_size; data += cur_size; finished += cur_size; if (ret) break; } while (*size >= finished); *size = finished; 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) { u32 finished = 0; u32 cur_size; int ret = 0; u32 len; u8 iaddr = (u8)addr; if (*size == 0) { dev_err(&dp->dc->ndev->dev, "dp: i2c read size can't be 0\n"); return -EINVAL; } do { cur_size = *size - finished; /* DPCD write size is actually size-1. */ if (cur_size >= DP_AUX_MAX_BYTES) cur_size = DP_AUX_MAX_BYTES - 1; else cur_size -= 1; len = 0; CHECK_RET(tegra_dc_dpaux_write_chunk(dp, DPAUX_DP_AUXCTL_CMD_MOTWR, i2c_addr, &iaddr, &len, aux_stat)); CHECK_RET(tegra_dc_dpaux_read_chunk(dp, DPAUX_DP_AUXCTL_CMD_I2CRD, i2c_addr, data, &cur_size, aux_stat)); iaddr += cur_size; data += cur_size; finished += cur_size; } while (*size > finished); return ret; } static int tegra_dc_dp_dpcd_read(struct tegra_dc_dp_data *dp, u32 cmd, u8 *data_ptr) { u32 size = 0; u32 status = 0; int ret; ret = tegra_dc_dpaux_read_chunk(dp, DPAUX_DP_AUXCTL_CMD_AUXRD, cmd, data_ptr, &size, &status); 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; u32 start_addr; struct tegra_dc_dp_data *dp = tegra_dc_get_outdata(dc); for (i = 0; i < num; ++i) { pmsg = &msgs[i]; if (!pmsg->flags) { /* write */ /* Ignore the write-for-read command now as it is already handled in the read operations */ } else if (pmsg->flags & I2C_M_RD) { /* Read */ len = pmsg->len; start_addr = 0; status = tegra_dc_i2c_read(dp, pmsg->addr, start_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 { /* No other functionalities are supported for now */ dev_err(&dp->dc->ndev->dev, "dp: i2x_xfer: Unknown flag 0x%x\n", pmsg->flags); return -EINVAL; } } return i; } static int tegra_dc_dp_dpcd_write(struct tegra_dc_dp_data *dp, u32 cmd, u8 data) { u32 size = 0; u32 status = 0; int ret; ret = tegra_dc_dpaux_write_chunk(dp, DPAUX_DP_AUXCTL_CMD_AUXWR, cmd, &data, &size, &status); 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; } static inline u64 tegra_div64(u64 dividend, u32 divisor) { do_div(dividend, divisor); return dividend; } #ifdef CONFIG_DEBUG_FS static int dbg_dp_show(struct seq_file *s, void *unused) { struct tegra_dc_dp_data *dp = s->private; #define DUMP_REG(a) seq_printf(s, "%-32s %03x %08x\n", \ #a, a, tegra_dpaux_readl(dp, a)) tegra_dc_io_start(dp->dc); clk_prepare_enable(dp->clk); 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); clk_disable_unprepare(dp->clk); tegra_dc_io_end(dp->dc); return 0; } static int dbg_dp_open(struct inode *inode, struct file *file) { return single_open(file, dbg_dp_show, inode->i_private); } static const struct file_operations dbg_fops = { .open = dbg_dp_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; static struct dentry *dpdir; static void tegra_dc_dp_debug_create(struct tegra_dc_dp_data *dp) { struct dentry *retval; dpdir = debugfs_create_dir("tegra_dp", NULL); if (!dpdir) return; retval = debugfs_create_file("regs", S_IRUGO, dpdir, dp, &dbg_fops); if (!retval) goto free_out; return; free_out: debugfs_remove_recursive(dpdir); dpdir = NULL; return; } #else static inline void tegra_dc_dp_debug_create(struct tegra_dc_dp_data *dp) { } #endif static void tegra_dc_dpaux_enable(struct tegra_dc_dp_data *dp) { /* clear interrupt */ tegra_dpaux_writel(dp, DPAUX_INTR_AUX, 0xffffffff); /* do not enable interrupt for now. Enable them when Isr in place */ tegra_dpaux_writel(dp, DPAUX_INTR_EN_AUX, 0x0); tegra_dpaux_writel(dp, DPAUX_HYBRID_PADCTL, DPAUX_HYBRID_PADCTL_AUX_DRVZ_OHM_50 | DPAUX_HYBRID_PADCTL_AUX_CMH_V0_70 | 0x18 << DPAUX_HYBRID_PADCTL_AUX_DRVI_SHIFT | DPAUX_HYBRID_PADCTL_AUX_INPUT_RCV_ENABLE); tegra_dpaux_writel(dp, DPAUX_HYBRID_SPARE, DPAUX_HYBRID_SPARE_PAD_PWR_POWERUP); } static void tegra_dc_dp_dump_link_cfg(struct tegra_dc_dp_data *dp, const struct tegra_dc_dp_link_config *cfg) { BUG_ON(!cfg); dev_info(&dp->dc->ndev->dev, "DP config: cfg_name "\ "cfg_value\n"); dev_info(&dp->dc->ndev->dev, " Lane Count %d\n", cfg->max_lane_count); dev_info(&dp->dc->ndev->dev, " SupportEnhancedFraming %s\n", cfg->support_enhanced_framing ? "Y" : "N"); dev_info(&dp->dc->ndev->dev, " Bandwidth %d\n", cfg->max_link_bw); dev_info(&dp->dc->ndev->dev, " bpp %d\n", cfg->bits_per_pixel); dev_info(&dp->dc->ndev->dev, " EnhancedFraming %s\n", cfg->enhanced_framing ? "Y" : "N"); dev_info(&dp->dc->ndev->dev, " Scramble_enabled %s\n", cfg->scramble_ena ? "Y" : "N"); dev_info(&dp->dc->ndev->dev, " LinkBW %d\n", cfg->link_bw); dev_info(&dp->dc->ndev->dev, " lane_count %d\n", cfg->lane_count); dev_info(&dp->dc->ndev->dev, " activespolarity %d\n", cfg->activepolarity); dev_info(&dp->dc->ndev->dev, " active_count %d\n", cfg->active_count); dev_info(&dp->dc->ndev->dev, " tu_size %d\n", cfg->tu_size); dev_info(&dp->dc->ndev->dev, " active_frac %d\n", cfg->active_frac); dev_info(&dp->dc->ndev->dev, " watermark %d\n", cfg->watermark); dev_info(&dp->dc->ndev->dev, " hblank_sym %d\n", cfg->hblank_sym); dev_info(&dp->dc->ndev->dev, " vblank_sym %d\n", cfg->vblank_sym); }; static bool _tegra_dp_lower_link_config(struct tegra_dc_dp_data *dp, struct tegra_dc_dp_link_config *cfg) { if (cfg->link_bw == SOR_LINK_SPEED_G1_62) { if (cfg->max_link_bw > SOR_LINK_SPEED_G1_62) cfg->link_bw = SOR_LINK_SPEED_G2_7; cfg->lane_count /= 2; } else if (cfg->link_bw == SOR_LINK_SPEED_G2_7) cfg->link_bw = SOR_LINK_SPEED_G1_62; else if (cfg->link_bw == SOR_LINK_SPEED_G5_4) { if (cfg->lane_count == 1) { cfg->link_bw = SOR_LINK_SPEED_G2_7; cfg->lane_count = cfg->max_lane_count; } else cfg->lane_count /= 2; } else { dev_err(&dp->dc->ndev->dev, "dp: Error link rate %d\n", cfg->link_bw); return false; } return (cfg->lane_count > 0); } /* Calcuate if given cfg can meet the mode request. */ /* Return true if mode is possible, false otherwise. */ static 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; int pclk; if (!link_rate || !cfg->lane_count || !mode->pclk || !cfg->bits_per_pixel) return false; pclk = clk_get_rate(dp->dc->clk); if ((u64)pclk * cfg->bits_per_pixel >= (u64)link_rate * 8 * cfg->lane_count) return false; num_linkclk_line = (u32)tegra_div64( (u64)link_rate * mode->h_active, pclk); ratio_f = (u64)pclk * 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, pclk) - 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, pclk) - (36 / cfg->lane_count) - 4; if (cfg->vblank_sym < 0) cfg->vblank_sym = 0; cfg->is_valid = true; tegra_dc_dp_dump_link_cfg(dp, cfg); return true; } static int tegra_dc_dp_init_max_link_cfg(struct tegra_dc_dp_data *dp, struct tegra_dc_dp_link_config *cfg) { u8 dpcd_data; int ret; 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; 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)); cfg->downspread = (dpcd_data & NV_DPCD_MAX_DOWNSPREAD_VAL_0_5_PCT) ? true : false; CHECK_RET(tegra_dc_dp_dpcd_read(dp, 0x0e, &cfg->aux_rd_interval)); CHECK_RET(tegra_dc_dp_dpcd_read(dp, NV_DPCD_MAX_LINK_BANDWIDTH, &cfg->max_link_bw)); cfg->bits_per_pixel = dp->dc->pdata->default_out->depth; /* * Set to a high value for link training and attach. * Will be re-programmed when dp is enabled. */ cfg->drive_current = 0x40404040; cfg->preemphasis = 0x0f0f0f0f; cfg->postcursor = 0; 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; cfg->lane_count = cfg->max_lane_count; cfg->link_bw = cfg->max_link_bw; cfg->enhanced_framing = cfg->support_enhanced_framing; tegra_dc_dp_calc_config(dp, dp->mode, 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; CHECK_RET(tegra_dc_dp_dpcd_write(dp, NV_DPCD_EDP_CONFIG_SET, dpcd_data)); /* 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); } static int tegra_dp_set_lane_count(struct tegra_dc_dp_data *dp, const struct tegra_dc_dp_link_config *cfg) { u8 dpcd_data; int ret; /* check if panel support enhanched_framing */ dpcd_data = cfg->lane_count; if (cfg->enhanced_framing) dpcd_data |= NV_DPCD_LANE_COUNT_SET_ENHANCEDFRAMING_T; CHECK_RET(tegra_dc_dp_dpcd_write(dp, NV_DPCD_LANE_COUNT_SET, dpcd_data)); tegra_dc_sor_set_lane_count(dp->sor, cfg->lane_count); /* Also power down lanes that will not be used */ return 0; } static int tegra_dc_dp_set_lane_config(struct tegra_dc_dp_data *dp, u32 lane_count, const u8 *edc, const u8 *c2, u8 training_pattern) { u32 lane; u8 pre_emphasis; u8 drive_current; u8 post_cursor2; u8 temp_edc[5]; u8 temp_c2[2]; u32 size; u32 status; int ret; temp_c2[0] = temp_c2[1] = 0; memset(temp_edc, 0, sizeof(temp_edc)); for (lane = 0; lane < lane_count; ++lane) { if (lane & 1) { /* Lane 1, 3 */ pre_emphasis = (edc[lane/2] & NV_DPCD_ADJUST_REQ_LANEXPLUS1_PE_MASK) >> NV_DPCD_ADJUST_REQ_LANEXPLUS1_PE_SHIFT; drive_current = (edc[lane/2] & NV_DPCD_ADJUST_REQ_LANEXPLUS1_DC_MASK) >> NV_DPCD_ADJUST_REQ_LANEXPLUS1_DC_SHIFT; } else { /* Lane 0, 2 */ pre_emphasis = (edc[lane/2] & NV_DPCD_ADJUST_REQ_LANEX_PE_MASK) >> NV_DPCD_ADJUST_REQ_LANEX_PE_SHIFT; drive_current = (edc[lane/2] & NV_DPCD_ADJUST_REQ_LANEX_DC_MASK) >> NV_DPCD_ADJUST_REQ_LANEX_DC_SHIFT; } post_cursor2 = (*c2 >> NV_DPCD_ADJUST_REQ_POST_CURSOR2_LANE_SHIFT(lane)) & NV_DPCD_ADJUST_REQ_POST_CURSOR2_LANE_MASK; temp_edc[lane+1] = drive_current << NV_DPCD_TRAINING_LANEX_SET_DC_SHIFT; if (drive_current == drive_current_Level3) temp_edc[lane+1] |= NV_DPCD_TRAINING_LANEX_SET_DC_MAX_REACHED_T; temp_edc[lane+1] |= (pre_emphasis << NV_DPCD_TRAINING_LANEX_SET_PE_SHIFT); if (pre_emphasis == preemphasis_Level3) temp_edc[lane+1] |= NV_DPCD_TRAINING_LANEX_SET_PE_MAX_REACHED_T; if (lane & 1) { /* lane 1 and 3 */ temp_c2[lane/2] |= post_cursor2 << NV_DPCD_LANEXPLUS1_SET2_PC2_SHIFT; if (post_cursor2 == post_cursor2_Level3) temp_c2[lane/2] |= NV_DPCD_LANEXPLUS1_SET2_PC2_MAX_REACHED_T; } else { /* lane 0 and 2 */ temp_c2[lane/2] |= post_cursor2 << NV_DPCD_LANEX_SET2_PC2_SHIFT; if (post_cursor2 == post_cursor2_Level3) temp_c2[lane/2] |= NV_DPCD_LANEX_SET2_PC2_MAX_REACHED_T; } tegra_dc_sor_set_dp_lanedata(dp->sor, lane_count, post_cursor2, pre_emphasis, drive_current); } usleep_range(10, 30); /* Now program the sink */ if (training_pattern != training_pattern_none) { tegra_dc_dp_dpcd_read(dp, NV_DPCD_TRAINING_PATTERN_SET, temp_edc); temp_edc[0] &= NV_DPCD_TRAINING_PATTERN_SET_TPS_MASK; temp_edc[0] |= (training_pattern & NV_DPCD_TRAINING_PATTERN_SET_TPS_MASK); temp_edc[0] |= NV_DPCD_TRAINING_PATTERN_SET_SC_DISABLED_T; size = 4; ret = tegra_dc_dpaux_write(dp, DPAUX_DP_AUXCTL_CMD_AUXWR, NV_DPCD_TRAINING_PATTERN_SET, temp_edc, &size, &status); if (ret) { dev_err(&dp->dc->ndev->dev, "Failed to set NV_DPCD_TRAINING_PATTERN_SET\n"); return ret; } } else { /* No training pattern, only set LANE config */ size = 3; ret = tegra_dc_dpaux_write(dp, DPAUX_DP_AUXCTL_CMD_AUXWR, NV_DPCD_TRAINING_LANE0_SET, temp_edc+1, &size, &status); if (ret) { dev_err(&dp->dc->ndev->dev, "Failed to set NV_DPCD_TRAINING_LANE0_SET\n"); return ret; } } size = 1; ret = tegra_dc_dpaux_write(dp, DPAUX_DP_AUXCTL_CMD_AUXWR, NV_DPCD_TRAINING_LANE0_1_SET2, temp_c2, &size, &status); if (ret) dev_err(&dp->dc->ndev->dev, "Failed to set NV_DPCD_TRAINING_LANE0_1_SET2\n"); return ret; } static int tegra_dc_dpcd_read_lane_request(struct tegra_dc_dp_data *dp, u32 lane_count, u8 *edc, u8 *c2) { u32 size; int ret; u32 status; /* read the new preemphasis & drive current values */ size = lane_count/2; ret = tegra_dc_dpaux_read(dp, DPAUX_DP_AUXCTL_CMD_AUXRD, NV_DPCD_LANE0_1_ADJUST_REQ, edc, &size, &status); if (ret) { dev_err(&dp->dc->ndev->dev, "Failed to read NV_DPCD_LANE0_1_ADJUST_REQ\n"); return ret; } return tegra_dc_dp_dpcd_read(dp, NV_DPCD_ADJUST_REQ_POST_CURSOR2, c2); } static int tegra_dc_dp_lt_clock_recovery(struct tegra_dc_dp_data *dp, const struct tegra_dc_dp_link_config *cfg) { int ret; u8 edc_data[2] = { 0, 0 }; u8 c2_data = 0; u32 lane; u8 temp_edc[4]; u8 data; u32 cr_done; bool sl_changed; u32 sl_max_count; u32 sl_retry_count; u8 mask; u32 retry_count; /* Set pattern on the source side */ tegra_dc_sor_set_dp_linkctl(dp->sor, true, training_pattern_1, cfg); /* Now the sink side */ ret = tegra_dc_dp_set_lane_config(dp, cfg->lane_count, edc_data, &c2_data, training_pattern_1); if (ret) dev_dbg(&dp->dc->ndev->dev, "Failed to set the sink link for clock recovery\n"); sl_retry_count = 0; retry_count = 0; do { usleep_range(100, 200); temp_edc[0] = edc_data[0]; temp_edc[1] = edc_data[1]; /* read lane registers for all the lanes */ for (lane = 0, cr_done = 0; lane < cfg->lane_count; ++lane) { ret = tegra_dc_dp_dpcd_read(dp, (lane/2) ? NV_DPCD_LANE2_3_STATUS : NV_DPCD_LANE0_1_STATUS, &data); if (ret) return ret; mask = (lane & 1) ? NV_DPCD_STATUS_LANEXPLUS1_CR_DONE_YES : NV_DPCD_STATUS_LANEX_CR_DONE_YES; if (data & mask) cr_done++; else /* no need to check the rest of the lanes */ break; } if (cr_done == cfg->lane_count) break; /* Success -- Done with clock recovery */ /* Check if the swing-levels changed or reached maxium */ sl_max_count = 0; sl_changed = false; for (lane = 0; lane < cfg->lane_count; ++lane) { mask = (lane & 1) ? NV_DPCD_ADJUST_REQ_LANEXPLUS1_DC_MASK : NV_DPCD_ADJUST_REQ_LANEX_DC_MASK; if ((edc_data[lane/2] & mask) != (temp_edc[lane/2] & mask)) { sl_changed = true; sl_retry_count = 0; break; } if ((edc_data[lane/2] & mask) == drive_current_Level3) sl_max_count++; } if (sl_max_count == cfg->lane_count) { dev_err(&dp->dc->ndev->dev, "Reached MAX_SWING_LEVEL yet CR_LOCK failed\n"); return -EFAULT; } if (!sl_changed && (sl_retry_count++ > DP_CLOCK_RECOVERY_MAX_TRIES)) { dev_err(&dp->dc->ndev->dev, "Exceeded max combination\n"); return -EFAULT; } /* Read the config on the sink side */ ret = tegra_dc_dpcd_read_lane_request(dp, cfg->lane_count, edc_data, &c2_data); /* Write the data */ ret = tegra_dc_dp_set_lane_config(dp, cfg->lane_count, edc_data, &c2_data, training_pattern_none); if (ret) { dev_err(&dp->dc->ndev->dev, "Failed to update lane configuration"); return ret; } } while (++retry_count < DP_CLOCK_RECOVERY_TOT_TRIES); if (retry_count == DP_CLOCK_RECOVERY_TOT_TRIES) { dev_err(&dp->dc->ndev->dev, "Exceeded max retry times\n"); return -EFAULT; } return ret; } static int tegra_dc_dp_lt_channel_equalization(struct tegra_dc_dp_data *dp, const struct tegra_dc_dp_link_config *cfg) { int ret; u8 data; u8 edc_data[2] = { 0, 0 }; u8 c2_data = 0; u8 updated_status; u32 tries; u8 lane; u32 ce_done; u8 mask; /* Set pattern on the source side */ tegra_dc_sor_set_dp_linkctl(dp->sor, true, training_pattern_2, cfg); /* Now the sink side */ CHECK_RET(tegra_dc_dp_dpcd_read(dp, NV_DPCD_TRAINING_PATTERN_SET, &data)); data &= ~NV_DPCD_TRAINING_PATTERN_SET_TPS_MASK; data |= training_pattern_2; CHECK_RET(tegra_dc_dp_dpcd_write(dp, NV_DPCD_TRAINING_PATTERN_SET, data)); usleep_range(400, 800); for (tries = 0; tries <= DP_CLOCK_RECOVERY_MAX_TRIES; ++tries) { ret = tegra_dc_dpcd_read_lane_request(dp, cfg->lane_count, edc_data, &c2_data); if (ret) return ret; /* Write the data */ ret = tegra_dc_dp_set_lane_config(dp, cfg->lane_count, edc_data, &c2_data, training_pattern_none); if (ret) { dev_err(&dp->dc->ndev->dev, "Failed to update lane configuration"); return ret; } usleep_range(400, 800); CHECK_RET(tegra_dc_dp_dpcd_read(dp, NV_DPCD_LANE_ALIGN_STATUS_UPDATED, &updated_status)); for (lane = 0, ce_done = 0; lane < cfg->lane_count; ++lane) { CHECK_RET(tegra_dc_dp_dpcd_read(dp, (lane/2) ? NV_DPCD_LANE2_3_STATUS : NV_DPCD_LANE0_1_STATUS, &data)); mask = (lane & 1) ? NV_DPCD_STATUS_LANEXPLUS1_CR_DONE_YES | NV_DPCD_STATUS_LANEXPLUS1_CHN_EQ_DONE_YES | NV_DPCD_STATUS_LANEXPLUS1_SYMBOL_LOCKED_YES : NV_DPCD_STATUS_LANEX_CR_DONE_YES | NV_DPCD_STATUS_LANEX_CHN_EQ_DONE_YES | NV_DPCD_STATUS_LANEX_SYMBOL_LOCKED_YES; if (((data & mask) == mask) && (updated_status & NV_DPCD_LANE_ALIGN_STATUS_UPDATED_DONE_YES)) ce_done++; else /* no need to check the rest of the lanes */ break; } if ((data & NV_DPCD_STATUS_LANEX_CR_DONE_YES) == 0) { dev_err(&dp->dc->ndev->dev, "Clock recovery has not been locked\n"); return -EFAULT; } if (ce_done == cfg->lane_count) { /* All lanes done with training */ dev_dbg(&dp->dc->ndev->dev, "Channel Equalization passed\n"); return 0; } } /* Reaches max tries */ dev_err(&dp->dc->ndev->dev, "Channel Equalization exceeded max combinations\n"); return -EFAULT; } static __maybe_unused int tegra_dc_dp_link_training(struct tegra_dc_dp_data *dp, const struct tegra_dc_dp_link_config *cfg) { int ret = tegra_dc_dp_lt_clock_recovery(dp, cfg); if (ret) dev_dbg(&dp->dc->ndev->dev, "DP: failed link training clock recovery with " "lane %d bw %d\n", cfg->lane_count, cfg->link_bw); else { ret = tegra_dc_dp_lt_channel_equalization(dp, cfg); if (ret) dev_dbg(&dp->dc->ndev->dev, "DP: failed link training channel equal with " "lane %d bw %d\n", cfg->lane_count, cfg->link_bw); } /* TODO: read back the link status for debugging purpose */ return ret; } static bool tegra_dc_dp_link_trained(struct tegra_dc_dp_data *dp, const struct tegra_dc_dp_link_config *cfg) { u32 lane; u8 mask; u8 data; int ret; for (lane = 0; lane < cfg->lane_count; ++lane) { CHECK_RET(tegra_dc_dp_dpcd_read(dp, (lane/2) ? NV_DPCD_LANE2_3_STATUS : NV_DPCD_LANE0_1_STATUS, &data)); mask = (lane & 1) ? NV_DPCD_STATUS_LANEXPLUS1_CR_DONE_YES | NV_DPCD_STATUS_LANEXPLUS1_CHN_EQ_DONE_YES | NV_DPCD_STATUS_LANEXPLUS1_SYMBOL_LOCKED_YES : NV_DPCD_STATUS_LANEX_CR_DONE_YES | NV_DPCD_STATUS_LANEX_CHN_EQ_DONE_YES | NV_DPCD_STATUS_LANEX_SYMBOL_LOCKED_YES; if ((data & mask) != mask) return false; } return true; } static bool tegra_dp_channel_eq_status(struct tegra_dc_dp_data *dp) { u32 cnt; u32 n_lanes = dp->link_cfg.lane_count; u8 data_ptr; bool ce_done = true; for (cnt = 0; cnt < n_lanes / 2; cnt++) { tegra_dc_dp_dpcd_read(dp, (NV_DPCD_LANE0_1_STATUS + cnt), &data_ptr); if (n_lanes == 1) { ce_done = (data_ptr & (0x1 << NV_DPCD_STATUS_LANEX_CHN_EQ_DONE_SHIFT)) && (data_ptr & (0x1 << NV_DPCD_STATUS_LANEX_SYMBOL_LOCKED_SHFIT)); break; } else if (!(data_ptr & (0x1 << NV_DPCD_STATUS_LANEX_CHN_EQ_DONE_SHIFT)) || !(data_ptr & (0x1 << NV_DPCD_STATUS_LANEX_SYMBOL_LOCKED_SHFIT)) || !(data_ptr & (0x1 << NV_DPCD_STATUS_LANEXPLUS1_CHN_EQ_DONE_SHIFT)) || !(data_ptr & (0x1 << NV_DPCD_STATUS_LANEXPLUS1_SYMBOL_LOCKED_SHIFT))) { ce_done = false; break; } } if (ce_done) { tegra_dc_dp_dpcd_read(dp, NV_DPCD_LANE_ALIGN_STATUS_UPDATED, &data_ptr); if (!(data_ptr & NV_DPCD_LANE_ALIGN_STATUS_UPDATED_DONE_YES)) ce_done = false; } return ce_done; } static bool tegra_dp_clock_recovery_status(struct tegra_dc_dp_data *dp) { u32 cnt; u32 n_lanes = dp->link_cfg.lane_count; u8 data_ptr; for (cnt = 0; cnt < n_lanes / 2; cnt++) { tegra_dc_dp_dpcd_read(dp, (NV_DPCD_LANE0_1_STATUS + cnt), &data_ptr); if (n_lanes == 1) return (data_ptr & 0x1) ? true : false; else if (!(data_ptr & 0x1) || !(data_ptr & (0x1 << NV_DPCD_STATUS_LANEXPLUS1_CR_DONE_SHIFT))) return false; } return true; } static void tegra_dp_lt_adjust(struct tegra_dc_dp_data *dp, u32 pe[4], u32 vs[4], u32 pc[4], bool pc_supported) { size_t cnt; u8 data_ptr; u32 n_lanes = dp->link_cfg.lane_count; for (cnt = 0; cnt < n_lanes / 2; cnt++) { tegra_dc_dp_dpcd_read(dp, (NV_DPCD_LANE0_1_ADJUST_REQ + cnt), &data_ptr); pe[2 * cnt] = (data_ptr & NV_DPCD_ADJUST_REQ_LANEX_PE_MASK) >> NV_DPCD_ADJUST_REQ_LANEX_PE_SHIFT; vs[2 * cnt] = (data_ptr & NV_DPCD_ADJUST_REQ_LANEX_DC_MASK) >> NV_DPCD_ADJUST_REQ_LANEX_DC_SHIFT; pe[1 + 2 * cnt] = (data_ptr & NV_DPCD_ADJUST_REQ_LANEXPLUS1_PE_MASK) >> NV_DPCD_ADJUST_REQ_LANEXPLUS1_PE_SHIFT; vs[1 + 2 * cnt] = (data_ptr & NV_DPCD_ADJUST_REQ_LANEXPLUS1_DC_MASK) >> NV_DPCD_ADJUST_REQ_LANEXPLUS1_DC_SHIFT; } if (pc_supported) { tegra_dc_dp_dpcd_read(dp, NV_DPCD_ADJUST_REQ_POST_CURSOR2, &data_ptr); for (cnt = 0; cnt < n_lanes; cnt++) { pc[cnt] = (data_ptr >> NV_DPCD_ADJUST_REQ_POST_CURSOR2_LANE_SHIFT(cnt)) & NV_DPCD_ADJUST_REQ_POST_CURSOR2_LANE_MASK; } } } static inline u32 tegra_dp_wait_aux_training(struct tegra_dc_dp_data *dp, bool is_clk_recovery) { if (!dp->link_cfg.aux_rd_interval) is_clk_recovery ? usleep_range(150, 200) : usleep_range(450, 500); else msleep(dp->link_cfg.aux_rd_interval * 4); return dp->link_cfg.aux_rd_interval; } static void tegra_dp_tpg(struct tegra_dc_dp_data *dp, u32 tp, u32 n_lanes) { tegra_dc_sor_set_dp_linkctl(dp->sor, true, tp, &dp->link_cfg); if (tp == training_pattern_disabled) tegra_dc_dp_dpcd_write(dp, NV_DPCD_TRAINING_PATTERN_SET, (tp | NV_DPCD_TRAINING_PATTERN_SET_SC_DISABLED_F)); else tegra_dc_dp_dpcd_write(dp, NV_DPCD_TRAINING_PATTERN_SET, (tp | NV_DPCD_TRAINING_PATTERN_SET_SC_DISABLED_T)); } static inline void tegra_dp_save_link_config(struct tegra_dc_dp_data *dp, struct tegra_dc_dp_link_config *old_cfg) { *old_cfg = dp->link_cfg; } static inline void tegra_dp_restore_link_config(struct tegra_dc_dp_data *dp, struct tegra_dc_dp_link_config *old_cfg) { dp->link_cfg = *old_cfg; tegra_dp_link_config(dp, old_cfg); } static bool tegra_dp_lower_link_config(struct tegra_dc_dp_data *dp, struct tegra_dc_dp_link_config *cfg) { struct tegra_dc_dp_link_config tmp_cfg; tegra_dp_save_link_config(dp, &tmp_cfg); cfg->is_valid = false; if (!_tegra_dp_lower_link_config(dp, cfg)) goto fail; if (!tegra_dc_dp_calc_config(dp, dp->mode, cfg)) goto fail; cfg->is_valid = true; tegra_dp_link_config(dp, cfg); return true; fail: tegra_dp_restore_link_config(dp, &tmp_cfg); return false; } static int _tegra_dp_channel_eq(struct tegra_dc_dp_data *dp, u32 pe[4], u32 vs[4], u32 pc[4], bool pc_supported, u32 n_lanes) { bool cr_done = true; bool ce_done = true; u32 retry_cnt = 1; retry: tegra_dp_wait_aux_training(dp, false); cr_done = tegra_dp_clock_recovery_status(dp); ce_done = tegra_dp_channel_eq_status(dp); if (!cr_done) { dev_err(&dp->dc->ndev->dev, "dp: CR failed in channel EQ sequence!\n"); goto fail; } if (ce_done) return 0; if (++retry_cnt > 5) goto fail; tegra_dp_lt_adjust(dp, pe, vs, pc, pc_supported); tegra_dp_lt_config(dp, pe, vs, pc); goto retry; fail: if (tegra_dp_lower_link_config(dp, &dp->link_cfg)) return -EAGAIN; return -EBUSY; } static int tegra_dp_channel_eq(struct tegra_dc_dp_data *dp, u32 pe[4], u32 vs[4], u32 pc[4]) { u32 n_lanes = dp->link_cfg.lane_count; bool pc_supported = dp->link_cfg.tps3_supported; int err; u32 tp_src = training_pattern_2; if (pc_supported) tp_src = training_pattern_3; tegra_dp_tpg(dp, tp_src, n_lanes); err = _tegra_dp_channel_eq(dp, pe, vs, pc, pc_supported, n_lanes); tegra_dp_tpg(dp, training_pattern_disabled, n_lanes); return err; } static void tegra_dp_set_tx_pu(struct tegra_dc_dp_data *dp, u32 pe[4], u32 vs[4], u32 pc[4]) { tegra_sor_write_field(dp->sor, NV_SOR_DP_PADCTL(dp->sor->portnum), NV_SOR_DP_PADCTL_TX_PU_ENABLE, NV_SOR_DP_PADCTL_TX_PU_DISABLE); } static void tegra_dp_lt_config(struct tegra_dc_dp_data *dp, u32 pe[4], u32 vs[4], u32 pc[4]) { struct tegra_dc_sor_data *sor = dp->sor; u32 n_lanes = dp->link_cfg.lane_count; bool pc_supported = dp->link_cfg.tps3_supported; u32 cnt; u32 val; for (cnt = 0; cnt < n_lanes; cnt++) { u32 mask = 0; u32 pe_reg, vs_reg, pc_reg; u32 shift = 0; switch (cnt) { case 0: mask = NV_SOR_PR_LANE2_DP_LANE0_MASK; shift = NV_SOR_PR_LANE2_DP_LANE0_SHIFT; break; case 1: mask = NV_SOR_PR_LANE1_DP_LANE1_MASK; shift = NV_SOR_PR_LANE1_DP_LANE1_SHIFT; break; case 2: mask = NV_SOR_PR_LANE0_DP_LANE2_MASK; shift = NV_SOR_PR_LANE0_DP_LANE2_SHIFT; break; case 3: mask = NV_SOR_PR_LANE3_DP_LANE3_MASK; shift = NV_SOR_PR_LANE3_DP_LANE3_SHIFT; break; default: dev_err(&dp->dc->ndev->dev, "dp: incorrect lane cnt\n"); } pe_reg = tegra_dp_pe_regs[pc[cnt]][vs[cnt]][pe[cnt]]; vs_reg = tegra_dp_vs_regs[pc[cnt]][vs[cnt]][pe[cnt]]; pc_reg = tegra_dp_pc_regs[pc[cnt]][vs[cnt]][pe[cnt]]; tegra_sor_write_field(sor, NV_SOR_PR(sor->portnum), mask, (pe_reg << shift)); tegra_sor_write_field(sor, NV_SOR_DC(sor->portnum), mask, (vs_reg << shift)); if (pc_supported) { tegra_sor_write_field( sor, NV_SOR_POSTCURSOR(sor->portnum), mask, (pc_reg << shift)); } } tegra_dp_set_tx_pu(dp, pe, vs, pc); usleep_range(15, 20); for (cnt = 0; cnt < n_lanes; cnt++) { u32 max_vs_flag = tegra_dp_is_max_vs(pe[cnt], vs[cnt]); u32 max_pe_flag = tegra_dp_is_max_pe(pe[cnt], vs[cnt]); val = (vs[cnt] << NV_DPCD_TRAINING_LANEX_SET_DC_SHIFT) | (max_vs_flag ? NV_DPCD_TRAINING_LANEX_SET_DC_MAX_REACHED_T : NV_DPCD_TRAINING_LANEX_SET_DC_MAX_REACHED_F) | (pe[cnt] << NV_DPCD_TRAINING_LANEX_SET_PE_SHIFT) | (max_pe_flag ? NV_DPCD_TRAINING_LANEX_SET_PE_MAX_REACHED_T : NV_DPCD_TRAINING_LANEX_SET_PE_MAX_REACHED_F); tegra_dc_dp_dpcd_write(dp, (NV_DPCD_TRAINING_LANE0_SET + cnt), val); } if (pc_supported) { for (cnt = 0; cnt < n_lanes / 2; cnt++) { u32 max_pc_flag0 = tegra_dp_is_max_pc(pc[cnt]); u32 max_pc_flag1 = tegra_dp_is_max_pc(pc[cnt + 1]); val = (pc[cnt] << NV_DPCD_LANEX_SET2_PC2_SHIFT) | (max_pc_flag0 ? NV_DPCD_LANEX_SET2_PC2_MAX_REACHED_T : NV_DPCD_LANEX_SET2_PC2_MAX_REACHED_F) | (pc[cnt + 1] << NV_DPCD_LANEXPLUS1_SET2_PC2_SHIFT) | (max_pc_flag1 ? NV_DPCD_LANEXPLUS1_SET2_PC2_MAX_REACHED_T : NV_DPCD_LANEXPLUS1_SET2_PC2_MAX_REACHED_F); tegra_dc_dp_dpcd_write(dp, (NV_DPCD_TRAINING_LANE0_1_SET2 + cnt), val); } } } static int _tegra_dp_clk_recovery(struct tegra_dc_dp_data *dp, u32 pe[4], u32 vs[4], u32 pc[4], bool pc_supported, u32 n_lanes) { bool cr_done = true; u32 vs_temp[4]; u32 retry_cnt = 1; retry: tegra_dp_lt_config(dp, pe, vs, pc); tegra_dp_wait_aux_training(dp, true); cr_done = tegra_dp_clock_recovery_status(dp); if (cr_done) return 0; memcpy(vs_temp, vs, sizeof(vs_temp)); tegra_dp_lt_adjust(dp, pe, vs, pc, pc_supported); if (!memcmp(vs_temp, vs, sizeof(vs_temp))) { if (retry_cnt++ >= 5) return -EBUSY; goto retry; } return _tegra_dp_clk_recovery(dp, pe, vs, pc, pc_supported, n_lanes); } static int tegra_dp_clk_recovery(struct tegra_dc_dp_data *dp, u32 pe[4], u32 vs[4], u32 pc[4]) { u32 n_lanes = dp->link_cfg.lane_count; bool pc_supported = dp->link_cfg.tps3_supported; int err; tegra_dp_tpg(dp, training_pattern_1, n_lanes); err = _tegra_dp_clk_recovery(dp, pe, vs, pc, pc_supported, n_lanes); if (err < 0) tegra_dp_tpg(dp, training_pattern_disabled, n_lanes); return err; } static int tegra_dp_full_lt(struct tegra_dc_dp_data *dp) { struct tegra_dc_sor_data *sor = dp->sor; int err; u32 pe[4] = { preemphasis_disabled, preemphasis_disabled, preemphasis_disabled, preemphasis_disabled }; u32 vs[4] = { drive_current_Level0, drive_current_Level0, drive_current_Level0, drive_current_Level0 }; u32 pc[4] = { post_cursor2_Level0, post_cursor2_Level0, post_cursor2_Level0, post_cursor2_Level0 }; tegra_sor_precharge_lanes(sor); retry_cr: memset(pe, preemphasis_disabled, sizeof(pe)); memset(vs, drive_current_Level0, sizeof(vs)); memset(pc, post_cursor2_Level0, sizeof(pc)); err = tegra_dp_clk_recovery(dp, pe, vs, pc); if (err < 0) { if (tegra_dp_lower_link_config(dp, &dp->link_cfg)) goto retry_cr; dev_err(&dp->dc->ndev->dev, "dp: clk recovery failed\n"); goto fail; } err = tegra_dp_channel_eq(dp, pe, vs, pc); if (err < 0) { if (err == -EAGAIN) goto retry_cr; dev_err(&dp->dc->ndev->dev, "dp: channel equalization failed\n"); goto fail; } tegra_dc_dp_dump_link_cfg(dp, &dp->link_cfg); return 0; fail: return err; } static int tegra_dc_dp_fast_link_training(struct tegra_dc_dp_data *dp, const struct tegra_dc_dp_link_config *cfg) { struct tegra_dc_sor_data *sor = dp->sor; u8 link_bw; u8 lane_count; u32 data; u32 size; u32 status; int j; u32 mask = 0xffff >> ((4 - cfg->lane_count) * 4); BUG_ON(!cfg || !cfg->is_valid); tegra_dc_sor_set_lane_parm(sor, cfg); tegra_dc_dp_dpcd_write(dp, NV_DPCD_MAIN_LINK_CHANNEL_CODING_SET, NV_DPCD_MAIN_LINK_CHANNEL_CODING_SET_ANSI_8B10B); /* Send TP1 */ tegra_dc_sor_set_dp_linkctl(sor, true, training_pattern_1, cfg); tegra_dc_dp_dpcd_write(dp, NV_DPCD_TRAINING_PATTERN_SET, NV_DPCD_TRAINING_PATTERN_SET_TPS_TP1); for (j = 0; j < cfg->lane_count; ++j) tegra_dc_dp_dpcd_write(dp, NV_DPCD_TRAINING_LANE0_SET + j, 0x24); usleep_range(500, 1000); size = 2; tegra_dc_dpaux_read(dp, DPAUX_DP_AUXCTL_CMD_AUXRD, NV_DPCD_LANE0_1_STATUS, (u8 *)&data, &size, &status); status = mask & 0x1111; if ((data & status) != status) { dev_err(&dp->dc->ndev->dev, "dp: Link training error for TP1 (0x%x)\n", data); return -EFAULT; } /* enable ASSR */ tegra_dc_dp_set_assr(dp, cfg->scramble_ena); tegra_dc_sor_set_dp_linkctl(sor, true, training_pattern_3, cfg); tegra_dc_dp_dpcd_write(dp, NV_DPCD_TRAINING_PATTERN_SET, cfg->link_bw == 20 ? 0x23 : 0x22); for (j = 0; j < cfg->lane_count; ++j) tegra_dc_dp_dpcd_write(dp, NV_DPCD_TRAINING_LANE0_SET + j, 0x24); usleep_range(500, 1000); size = 4; tegra_dc_dpaux_read(dp, DPAUX_DP_AUXCTL_CMD_AUXRD, NV_DPCD_LANE0_1_STATUS, (u8 *)&data, &size, &status); if ((data & mask) != (0x7777 & mask)) { dev_info(&dp->dc->ndev->dev, "dp: Link training error for TP2/3 (0x%x)\n", data); return -EFAULT; } tegra_dc_sor_set_dp_linkctl(sor, true, training_pattern_disabled, cfg); tegra_dc_dp_dpcd_write(dp, NV_DPCD_TRAINING_PATTERN_SET, 0); if (!tegra_dc_dp_link_trained(dp, cfg)) { tegra_dc_sor_read_link_config(dp->sor, &link_bw, &lane_count); dev_err(&dp->dc->ndev->dev, "Fast link trainging failed, link bw %d, lane # %d\n", link_bw, lane_count); return -EFAULT; } else dev_dbg(&dp->dc->ndev->dev, "Fast link trainging succeeded, link bw %d, lane %d\n", cfg->link_bw, cfg->lane_count); return 0; } static int tegra_dp_link_config(struct tegra_dc_dp_data *dp, const struct tegra_dc_dp_link_config *cfg) { u8 dpcd_data; u8 link_bw; u8 lane_count; u32 retry; int ret; if (cfg->lane_count == 0) { /* TODO: shutdown the link */ return 0; } /* Set power state if it is not in normal level */ CHECK_RET(tegra_dc_dp_dpcd_read(dp, NV_DPCD_SET_POWER, &dpcd_data)); if (dpcd_data == NV_DPCD_SET_POWER_VAL_D3_PWRDWN) { dpcd_data = NV_DPCD_SET_POWER_VAL_D0_NORMAL; retry = 3; /* DP spec requires 3 retries */ do { ret = tegra_dc_dp_dpcd_write(dp, NV_DPCD_SET_POWER, dpcd_data); } while ((--retry > 0) && ret); if (ret) { dev_err(&dp->dc->ndev->dev, "dp: Failed to set DP panel power\n"); return ret; } } /* Enable ASSR if possible */ if (cfg->alt_scramber_reset_cap) CHECK_RET(tegra_dc_dp_set_assr(dp, true)); ret = tegra_dp_set_link_bandwidth(dp, cfg->link_bw); if (ret) { dev_err(&dp->dc->ndev->dev, "dp: Failed to set link bandwidth\n"); return ret; } ret = tegra_dp_set_lane_count(dp, cfg); if (ret) { dev_err(&dp->dc->ndev->dev, "dp: Failed to set lane count\n"); return ret; } tegra_dc_sor_set_dp_linkctl(dp->sor, true, training_pattern_none, cfg); /* Now do the fast link training for eDP */ ret = tegra_dc_dp_fast_link_training(dp, cfg); if (ret) { dev_err(&dp->dc->ndev->dev, "dp: fast link training failed\n"); /* Try full link training then */ if (tegra_dp_full_lt(dp)) { dev_err(&dp->dc->ndev->dev, "dp: full link training failed\n"); return ret; } } else { /* set to a known-good drive setting if fast link succeeded */ tegra_dc_sor_set_voltage_swing(dp->sor, dp->dc->out->dp->drive_current, dp->dc->out->dp->preemphasis); } /* Everything goes well, double check the link config */ /* TODO: record edc/c2 data for debugging */ tegra_dc_sor_read_link_config(dp->sor, &link_bw, &lane_count); if ((cfg->link_bw == link_bw) && (cfg->lane_count == lane_count)) return 0; else return -EFAULT; } static int tegra_dc_dp_explore_link_cfg(struct tegra_dc_dp_data *dp, struct tegra_dc_dp_link_config *cfg, struct tegra_dc_mode *mode) { struct tegra_dc_dp_link_config temp_cfg; if (!mode->pclk || !mode->h_active || !mode->v_active) { dev_err(&dp->dc->ndev->dev, "dp: error mode configuration"); return -EINVAL; } if (!cfg->max_link_bw || !cfg->max_lane_count) { dev_err(&dp->dc->ndev->dev, "dp: error link configuration"); return -EINVAL; } cfg->is_valid = false; memcpy(&temp_cfg, cfg, sizeof(temp_cfg)); temp_cfg.link_bw = temp_cfg.max_link_bw; temp_cfg.lane_count = temp_cfg.max_lane_count; while (tegra_dc_dp_calc_config(dp, mode, &temp_cfg) && tegra_dp_link_config(dp, &temp_cfg)) { /* current link cfg is doable */ memcpy(cfg, &temp_cfg, sizeof(temp_cfg)); /* try to lower the config */ if (!_tegra_dp_lower_link_config(dp, &temp_cfg)) break; } return cfg->is_valid ? 0 : -EFAULT; } static void tegra_dc_dp_lt_worker(struct work_struct *work) { struct tegra_dc_dp_data *dp = container_of(work, struct tegra_dc_dp_data, lt_work); tegra_dc_disable(dp->dc); if (!dp->link_cfg.is_valid || tegra_dp_link_config(dp, &dp->link_cfg)) { /* If current config is not valid or cannot be trained, needs to re-explore the possilbe config */ if (tegra_dc_dp_init_max_link_cfg(dp, &dp->link_cfg)) dev_err(&dp->dc->ndev->dev, "dp: failed to init link configuration\n"); else if (tegra_dc_dp_explore_link_cfg(dp, &dp->link_cfg, dp->mode)) dev_err(&dp->dc->ndev->dev, "dp irq: cannot get working config\n"); } tegra_dc_enable(dp->dc); } static irqreturn_t tegra_dp_irq(int irq, void *ptr) { struct tegra_dc_dp_data *dp = ptr; struct tegra_dc *dc = dp->dc; u32 status; u8 data; u8 clear_data = 0; tegra_dc_io_start(dc); /* clear pending bits */ status = tegra_dpaux_readl(dp, DPAUX_INTR_AUX); tegra_dpaux_writel(dp, DPAUX_INTR_AUX, status); if (status & DPAUX_INTR_AUX_PLUG_EVENT_PENDING) complete_all(&dp->hpd_plug); if (status & DPAUX_INTR_AUX_IRQ_EVENT_PENDING) { if (tegra_dc_dp_dpcd_read(dp, NV_DPCD_DEVICE_SERVICE_IRQ_VECTOR, &data)) dev_err(&dc->ndev->dev, "dp: failed to read IRQ_VECTOR\n"); dev_dbg(&dc->ndev->dev, "dp irq: Handle HPD with DPCD_IRQ_VECTOR 0x%x\n", data); /* For eDP only answer auto_test_request */ if (data & NV_DPCD_DEVICE_SERVICE_IRQ_VECTOR_AUTO_TEST_YES && dp->link_cfg.is_valid) { /* Schedule to do the link training */ schedule_work(&dp->lt_work); /* Now clear auto_test bit */ clear_data |= NV_DPCD_DEVICE_SERVICE_IRQ_VECTOR_AUTO_TEST_YES; } if (clear_data) tegra_dc_dp_dpcd_write(dp, NV_DPCD_DEVICE_SERVICE_IRQ_VECTOR, clear_data); } tegra_dc_io_end(dc); return IRQ_HANDLED; } static int tegra_dc_dp_init(struct tegra_dc *dc) { struct tegra_dc_dp_data *dp; struct resource *res; struct resource *base_res; void __iomem *base; struct clk *clk; int err; u32 irq; dp = kzalloc(sizeof(*dp), GFP_KERNEL); if (!dp) return -ENOMEM; irq = platform_get_irq_byname(dc->ndev, "dp"); if (irq <= 0) { dev_err(&dc->ndev->dev, "dp: no irq\n"); err = -ENOENT; goto err_free_dp; } res = platform_get_resource_byname(dc->ndev, IORESOURCE_MEM, "dpaux"); if (!res) { dev_err(&dc->ndev->dev, "dp: no mem resources for dpaux\n"); err = -EFAULT; goto err_free_dp; } base_res = request_mem_region(res->start, resource_size(res), dc->ndev->name); if (!base_res) { dev_err(&dc->ndev->dev, "dp: request_mem_region failed\n"); err = -EFAULT; goto err_free_dp; } base = ioremap(res->start, resource_size(res)); if (!base) { dev_err(&dc->ndev->dev, "dp: registers can't be mapped\n"); err = -EFAULT; goto err_release_resource_reg; } clk = clk_get(&dc->ndev->dev, "dpaux"); if (IS_ERR_OR_NULL(clk)) { dev_err(&dc->ndev->dev, "dp: dc clock %s.dpaux unavailable\n", dev_name(&dc->ndev->dev)); err = -EFAULT; goto err_iounmap_reg; } 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_get_clk; } tegra_dp_disable_irq(irq); dp->dc = dc; dp->aux_base = base; dp->aux_base_res = base_res; dp->clk = clk; dp->mode = &dc->mode; dp->sor = tegra_dc_sor_init(dc, &dp->link_cfg); dp->irq = irq; if (IS_ERR_OR_NULL(dp->sor)) { err = PTR_ERR(dp->sor); dp->sor = NULL; goto err_get_clk; } dp->dp_edid = tegra_edid_create(dc, tegra_dc_dp_i2c_xfer); if (IS_ERR_OR_NULL(dp->dp_edid)) { dev_err(&dc->ndev->dev, "dp: failed to create edid obj\n"); err = PTR_ERR(dp->dp_edid); goto err_edid_destroy; } tegra_dc_set_edid(dc, dp->dp_edid); INIT_WORK(&dp->lt_work, tegra_dc_dp_lt_worker); init_completion(&dp->hpd_plug); tegra_dc_set_outdata(dc, dp); tegra_dc_dp_debug_create(dp); return 0; err_edid_destroy: tegra_edid_destroy(dp->dp_edid); err_get_clk: clk_put(clk); err_iounmap_reg: iounmap(base); err_release_resource_reg: release_resource(base_res); err_free_dp: kfree(dp); 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 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(dp, DPAUX_HPD_CONFIG, val); tegra_dpaux_writel(dp, 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_hpd_plug(struct tegra_dc_dp_data *dp) { #define TEGRA_DP_HPD_PLUG_TIMEOUT_MS 10000 u32 val; int err = 0; might_sleep(); val = tegra_dpaux_readl(dp, DPAUX_DP_AUXSTAT); if (likely(val & DPAUX_DP_AUXSTAT_HPD_STATUS_PLUGGED)) return 0; INIT_COMPLETION(dp->hpd_plug); tegra_dp_int_en(dp, DPAUX_INTR_EN_AUX_PLUG_EVENT_EN); if (!wait_for_completion_timeout(&dp->hpd_plug, msecs_to_jiffies(TEGRA_DP_HPD_PLUG_TIMEOUT_MS))) { err = -ENODEV; goto fail; } fail: tegra_dp_int_dis(dp, DPAUX_INTR_EN_AUX_PLUG_EVENT_DIS); return err; #undef TEGRA_DP_HPD_PLUG_TIMEOUT_MS } static bool tegra_dc_dp_sink_out_of_sync(struct tegra_dc_dp_data *dp, int delay_ms) { u8 dpcd_data; bool out_of_sync; msleep(delay_ms); tegra_dc_dp_dpcd_read(dp, NV_DPCD_SINK_STATUS, &dpcd_data); out_of_sync = ((dpcd_data & NV_DPCD_SINK_STATUS_PORT0_IN_SYNC) != NV_DPCD_SINK_STATUS_PORT0_IN_SYNC); if (out_of_sync) dev_err(&dp->dc->ndev->dev, "SINK receive port 0 is out of synchronization\n"); else dev_info(&dp->dc->ndev->dev, "SINK is in synchronization\n"); return out_of_sync; } static void tegra_dc_dp_check_sink(struct tegra_dc_dp_data *dp) { #define FRAME_IN_MS 17 /* refresh rate = 60/s */ u8 max_retry = 3; int delay_frame; /* DP TCON may skip some main stream frames, thus we need to wait some delay before reading the DPCD SINK STATUS register, starting from 3 */ delay_frame = 3; while (tegra_dc_dp_sink_out_of_sync(dp, FRAME_IN_MS * delay_frame) && max_retry--) { tegra_dc_detach(dp->sor); tegra_dc_dp_explore_link_cfg(dp, &dp->link_cfg, dp->mode); tegra_dc_sor_set_power_state(dp->sor, 1); tegra_dc_sor_attach(dp->sor); /* Increase delay_frame for next try in case the sink is skipping more frames */ delay_frame += 2; } } static void tegra_dc_dp_enable(struct tegra_dc *dc) { struct tegra_dc_dp_data *dp = tegra_dc_get_outdata(dc); u8 data; u32 retry; int ret; if (!__clk_get_enable_count(dp->clk)) clk_prepare_enable(dp->clk); tegra_dc_io_start(dc); tegra_dc_dpaux_enable(dp); msleep(DP_LCDVCC_TO_HPD_DELAY_MS); tegra_dp_enable_irq(dp->irq); tegra_dp_hpd_config(dp); if (tegra_dp_hpd_plug(dp) < 0) { dev_err(&dc->ndev->dev, "dp: hpd plug failed\n"); goto error_enable; } /* Power on panel */ if (tegra_dc_dp_init_max_link_cfg(dp, &dp->link_cfg)) { dev_err(&dc->ndev->dev, "dp: failed to init link configuration\n"); goto error_enable; } tegra_dc_sor_enable_dp(dp->sor); tegra_dc_sor_set_panel_power(dp->sor, true); /* Write power on to DPCD */ data = NV_DPCD_SET_POWER_VAL_D0_NORMAL; retry = 0; do { ret = tegra_dc_dp_dpcd_write(dp, NV_DPCD_SET_POWER, data); } while ((retry++ < DP_POWER_ON_MAX_TRIES) && ret); if (ret) { dev_err(&dp->dc->ndev->dev, "dp: failed to power on panel (0x%x)\n", ret); goto error_enable; } /* Confirm DP is plugging status */ if (!(tegra_dpaux_readl(dp, DPAUX_DP_AUXSTAT) & DPAUX_DP_AUXSTAT_HPD_STATUS_PLUGGED)) { dev_err(&dp->dc->ndev->dev, "dp: could not detect HPD\n"); goto error_enable; } /* 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"); tegra_dc_dp_explore_link_cfg(dp, &dp->link_cfg, dp->mode); tegra_dc_sor_set_power_state(dp->sor, 1); tegra_dc_sor_attach(dp->sor); tegra_dc_dp_check_sink(dp); /* * Power down the unused lanes to save power * (about hundreds milli-watts, varies from boards). */ tegra_dc_sor_power_down_unused_lanes(dp->sor); dp->enabled = true; error_enable: tegra_dc_io_end(dc); return; } static void tegra_dc_dp_destroy(struct tegra_dc *dc) { struct tegra_dc_dp_data *dp = tegra_dc_get_outdata(dc); if (dp->sor) tegra_dc_sor_destroy(dp->sor); if (dp->dp_edid) tegra_edid_destroy(dp->dp_edid); clk_put(dp->clk); iounmap(dp->aux_base); release_resource(dp->aux_base_res); kfree(dp); } static void tegra_dc_dp_disable(struct tegra_dc *dc) { struct tegra_dc_dp_data *dp = tegra_dc_get_outdata(dc); if (!dp->enabled) return; dp->enabled = false; tegra_dc_io_start(dc); tegra_dc_detach(dp->sor); tegra_dp_disable_irq(dp->irq); tegra_dpaux_writel(dp, DPAUX_HYBRID_SPARE, DPAUX_HYBRID_SPARE_PAD_PWR_POWERDOWN); /* Power down SOR */ tegra_dc_sor_disable(dp->sor, false); clk_disable_unprepare(dp->clk); tegra_dc_io_end(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 *parent_clk = NULL; struct clk *base_clk = NULL; int ret; if (clk == dc->clk) { parent_clk = clk_get_sys(NULL, dc->out->parent_clk ? : "pll_d_out0"); BUG_ON(!parent_clk); if (clk_get_parent(clk) != parent_clk) clk_set_parent(clk, parent_clk); base_clk = clk_get_parent(parent_clk); BUG_ON(!base_clk); clk_prepare_enable(base_clk); ret = clk_set_rate(base_clk, dp->dc->mode.pclk * 2); if (ret) dev_err(&dp->dc->ndev->dev, "Set rate %d for %s failed: %d\n", dp->dc->mode.pclk, __clk_get_name(base_clk), ret); clk_disable_unprepare(base_clk); } tegra_dc_sor_setup_clk(dp->sor, clk, false); parent_clk = __clk_lookup("pll_dp"); clk_set_rate(parent_clk, 270000000); if (!__clk_get_enable_count(parent_clk)) clk_prepare_enable(parent_clk); return tegra_dc_pclk_round_rate(dc, dp->sor->dc->mode.pclk); } static bool tegra_dc_dp_early_enable(struct tegra_dc *dc) { struct tegra_dc_dp_data *dp = tegra_dc_get_outdata(dc); struct fb_monspecs specs; u32 reg_val; int ret = true; /* Power on panel */ if (dc->out->enable) dc->out->enable(&dc->ndev->dev); clk_prepare_enable(dp->clk); tegra_dc_dpaux_enable(dp); tegra_dp_hpd_config(dp); tegra_unpowergate_partition(TEGRA_POWERGATE_SOR); msleep(80); if (tegra_dp_hpd_plug(dp) < 0) { dev_err(&dc->ndev->dev, "dp: hpd plug failed\n"); ret = false; goto out; } reg_val = tegra_dpaux_readl(dp, DPAUX_DP_AUXSTAT); if (!(reg_val & DPAUX_DP_AUXSTAT_HPD_STATUS_PLUGGED)) { dev_err(&dc->ndev->dev, "dp: Failed to detect HPD\n"); ret = false; goto out; } if (tegra_edid_get_monspecs(dp->dp_edid, &specs)) { dev_err(&dc->ndev->dev, "dp: Failed to get EDID data\n"); ret = false; goto out; } tegra_dc_set_fb_mode(dc, specs.modedb, false); fb_destroy_modedb(specs.modedb); out: tegra_powergate_partition(TEGRA_POWERGATE_SOR); msleep(50); clk_disable_unprepare(dp->clk); return ret; } 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, .setup_clk = tegra_dc_dp_setup_clk, .early_enable = tegra_dc_dp_early_enable, };