/* * dp_debug.c: dp debug interface. * * Copyright (c) 2015-2018 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 "dc_reg.h" #include "dc_priv.h" #include "dp.h" #include "dp_lt.h" #include "sor_regs.h" #include "sor.h" #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0) #include #else #include #endif #ifdef CONFIG_DEBUG_FS struct tegra_dp_test_settings default_dp_test_settings = { DRIVE_CURRENT_L0, PRE_EMPHASIS_L0, 4, NV_DPCD_MAX_LINK_BANDWIDTH_VAL_1_62_GBPS, TEGRA_DC_DP_TRAINING_PATTERN_DISABLE, 0, "rbr", "none", false, false, }; static inline int parse_bitrate_setting(char *bitrate_name, struct tegra_dp_test_settings *test_settings) { u8 bitrate; if (!strcmp(bitrate_name, "rbr")) bitrate = NV_DPCD_MAX_LINK_BANDWIDTH_VAL_1_62_GBPS; else if (!strcmp(bitrate_name, "hbr")) bitrate = NV_DPCD_MAX_LINK_BANDWIDTH_VAL_2_70_GBPS; else if (!strcmp(bitrate_name, "hbr2")) bitrate = NV_DPCD_MAX_LINK_BANDWIDTH_VAL_5_40_GBPS; else if (!strcmp(bitrate_name, "hbr3")) bitrate = NV_DPCD_MAX_LINK_BANDWIDTH_VAL_8_10_GBPS; else return -EINVAL; test_settings->bitrate = bitrate; test_settings->bitrate_name = bitrate_name; return 0; } static inline int parse_patt_setting(char *patt, struct tegra_dp_test_settings *test_settings) { u8 tpg; if (!strcmp(patt, "none")) tpg = TEGRA_DC_DP_TRAINING_PATTERN_DISABLE; else if (!strcmp(patt, "t1")) tpg = TEGRA_DC_DP_TRAINING_PATTERN_1; else if (!strcmp(patt, "t2")) tpg = TEGRA_DC_DP_TRAINING_PATTERN_2; else if (!strcmp(patt, "t3")) tpg = TEGRA_DC_DP_TRAINING_PATTERN_3; else if (!strcmp(patt, "d102")) tpg = TEGRA_DC_DP_TRAINING_PATTERN_D102; else if (!strcmp(patt, "sblerrrate")) tpg = TEGRA_DC_DP_TRAINING_PATTERN_SBLERRRATE; else if (!strcmp(patt, "prbs7")) tpg = TEGRA_DC_DP_TRAINING_PATTERN_PRBS7; else if (!strcmp(patt, "pltpat") || !strcmp(patt, "pctpat")) tpg = TEGRA_DC_DP_TRAINING_PATTERN_CSTM; else if (!strcmp(patt, "hbr2compliance")) tpg = TEGRA_DC_DP_TRAINING_PATTERN_HBR2_COMPLIANCE; else if (!strcmp(patt, "cp2520_pat1")) tpg = TEGRA_DC_DP_TRAINING_PATTERN_CP2520_PAT1; else if (!strcmp(patt, "cp2520_pat3")) tpg = TEGRA_DC_DP_TRAINING_PATTERN_CP2520_PAT3; else if (!strcmp(patt, "t4")) tpg = TEGRA_DC_DP_TRAINING_PATTERN_4; else return -EINVAL; test_settings->tpg = tpg; test_settings->patt = patt; return 0; } static int parse_test_settings(const char __user *user_buf, size_t count, struct tegra_dp_test_settings *test_settings) { #define IN_RANGE(val, low, high) ((val) >= (low) && (val) <= (high)) char *buf, *token, *settings[8]; int i = 0, j = 0; u8 u8_val; buf = kzalloc(count + 1, GFP_KERNEL); if (!buf) { pr_err("dp_debug: Not enough memory for buffer\n"); return -ENOMEM; } if (copy_from_user(buf, user_buf, count)) { pr_err("dp_debug: Copy from user failed\n"); goto fail; } while ((token = strsep(&buf, ",\n")) && *token != '\0') settings[i++] = token; for (; j < i; j++) { char *name = strsep(&settings[j], "="); char *val = strsep(&settings[j], "="); if (!name || !val) goto parse_fail; if (!strcmp(name, "br")) { if (parse_bitrate_setting(val, test_settings)) goto parse_fail; continue; } if (!strcmp(name, "patt")) { if (parse_patt_setting(val, test_settings)) goto parse_fail; continue; } if (kstrtou8(val, 10, &u8_val)) goto parse_fail; if (!strcmp(name, "sw") && IN_RANGE(u8_val, DRIVE_CURRENT_L0, DRIVE_CURRENT_L3)) test_settings->drive_strength = u8_val; else if (!strcmp(name, "pre") && IN_RANGE(u8_val, PRE_EMPHASIS_L0, PRE_EMPHASIS_L3)) test_settings->preemphasis = u8_val; else if (!strcmp(name, "lanes") && (u8_val == 1 || u8_val == 2 || u8_val == 4)) test_settings->lanes = u8_val; else if (!strcmp(name, "ssc")) pr_info("dp_debug: SSC is fixed, ignoring request\n"); else if (!strcmp(name, "tx_pu_disable")) test_settings->disable_tx_pu = u8_val; else if (!strcmp(name, "panel")) test_settings->panel_type = u8_val; else goto parse_fail; } kfree(buf); return 0; parse_fail: pr_err("dp_debug: Invalid arg or value given\n"); fail: kfree(buf); return -EINVAL; } static int test_settings_show(struct seq_file *s, void *unused) { struct tegra_dc_dp_data *dp = s->private; struct tegra_dp_test_settings *test_settings = &dp->test_settings; seq_printf(s, "\tDrive strength: %d\n", test_settings->drive_strength); seq_printf(s, "\tPreemphasis level: %d\n", test_settings->preemphasis); seq_printf(s, "\tLanes: %d\n", test_settings->lanes); seq_printf(s, "\tBitrate: %s\n", test_settings->bitrate_name); seq_printf(s, "\tTest pattern: %s\n", test_settings->patt); seq_printf(s, "\tSSC %sabled\n", test_settings->disable_ssc ? "dis" : "en"); seq_printf(s, "\tTX_PU %sabled\n", test_settings->disable_tx_pu ? "dis" : "en"); seq_printf(s, "\tPanel type : %s\n", test_settings->panel_type ? "Internal" : "External"); return 0; } static ssize_t test_settings_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 *dc = dp->dc; struct tegra_dc_sor_data *sor = dp->sor; struct tegra_dc_dp_link_config *cfg = &dp->link_cfg; struct tegra_dp_test_settings *test_settings = &dp->test_settings; int dc_out_type = dc->out->type; u32 vs_reg, pe_reg; u32 max_tx_pu; if (parse_test_settings(buf, count, test_settings)) return -EINVAL; /* Switch DC out type as TEGRA_DC_OUT_FAKE_DP so that SOR will be * enabled as if it was interacting with a fake panel. */ dc->out->type = TEGRA_DC_OUT_FAKE_DP; if (!dc->enabled) { /* Set default mode to VGA. tegra_dc_enable needs some mode * to be set. */ tegra_dc_set_default_videomode(dc); tegra_dc_enable(dc); } /* detach SOR and precharge lanes */ tegra_dc_sor_detach(sor); tegra_sor_precharge_lanes(sor); tegra_dc_io_start(dc); /* Set panel type */ if (test_settings->panel_type) tegra_sor_write_field(sor, NV_SOR_DP_SPARE(sor->portnum), NV_SOR_DP_SPARE_PANEL_MASK, NV_SOR_DP_SPARE_PANEL_INTERNAL); else tegra_sor_write_field(sor, NV_SOR_DP_SPARE(sor->portnum), NV_SOR_DP_SPARE_PANEL_MASK, NV_SOR_DP_SPARE_PANEL_EXTERNAL); /* set lane count and bitrate */ cfg->lane_count = test_settings->lanes; cfg->link_bw = test_settings->bitrate; tegra_dp_update_link_config(dp); /* set drive strength, preemphasis, and postcursor */ vs_reg = dp->pdata->lt_data[DP_VS].data[POST_CURSOR2_L0] [test_settings->drive_strength][test_settings->preemphasis]; pe_reg = dp->pdata->lt_data[DP_PE].data[POST_CURSOR2_L0] [test_settings->drive_strength][test_settings->preemphasis]; tegra_sor_writel(sor, NV_SOR_DC(sor->portnum), vs_reg | (vs_reg << 8) | (vs_reg << 16) | (vs_reg << 24)); tegra_sor_writel(sor, NV_SOR_PR(sor->portnum), pe_reg | (pe_reg << 8) | (pe_reg << 16) | (pe_reg << 24)); tegra_sor_writel(sor, NV_SOR_POSTCURSOR(sor->portnum), 0); /* set TX_PU */ if (test_settings->disable_tx_pu) { tegra_sor_write_field(sor, nv_sor_dp_padctl(sor->portnum), NV_SOR_DP_PADCTL_TX_PU_ENABLE | NV_SOR_DP_PADCTL_TX_PU_VALUE_DEFAULT_MASK, NV_SOR_DP_PADCTL_TX_PU_DISABLE); } else { max_tx_pu = dp->pdata->lt_data[DP_TX_PU].data[POST_CURSOR2_L0] [test_settings->drive_strength][test_settings->preemphasis]; tegra_sor_write_field(sor, nv_sor_dp_padctl(sor->portnum), NV_SOR_DP_PADCTL_TX_PU_VALUE_DEFAULT_MASK, (max_tx_pu << NV_SOR_DP_PADCTL_TX_PU_VALUE_SHIFT | NV_SOR_DP_PADCTL_TX_PU_ENABLE)); } /* send training pattern */ if (test_settings->tpg == TEGRA_DC_DP_TRAINING_PATTERN_CSTM) { if (!strcmp(test_settings->patt, "pltpat")) { tegra_sor_writel(sor, NV_SOR_DP_LQ_CSTM_0, 0x3e0f83e0); tegra_sor_writel(sor, NV_SOR_DP_LQ_CSTM_1, 0x0f83e0f8); tegra_sor_writel(sor, NV_SOR_DP_LQ_CSTM_2, 0x0000f83e); } else { tegra_sor_writel(sor, NV_SOR_DP_LQ_CSTM_0, 0xfe0f83e0); tegra_sor_writel(sor, NV_SOR_DP_LQ_CSTM_1, 0xaccccccc); tegra_sor_writel(sor, NV_SOR_DP_LQ_CSTM_2, 0x0000aa8a); } } else { tegra_sor_writel(sor, NV_SOR_DP_LQ_CSTM_0, 0); tegra_sor_writel(sor, NV_SOR_DP_LQ_CSTM_1, 0); tegra_sor_writel(sor, NV_SOR_DP_LQ_CSTM_2, 0); } if (test_settings->tpg == TEGRA_DC_DP_TRAINING_PATTERN_HBR2_COMPLIANCE) tegra_sor_writel(sor, NV_SOR_DP_TPG_CONFIG, 0x00FC); else tegra_sor_writel(sor, NV_SOR_DP_TPG_CONFIG, 0); if (test_settings->tpg == TEGRA_DC_DP_TRAINING_PATTERN_CP2520_PAT1 || test_settings->tpg == TEGRA_DC_DP_TRAINING_PATTERN_CP2520_PAT3 || test_settings->tpg == TEGRA_DC_DP_TRAINING_PATTERN_4) tegra_dp_set_enhanced_framing(dp, false); else tegra_dp_set_enhanced_framing(dp, cfg->enhanced_framing); tegra_sor_tpg(sor, test_settings->tpg, test_settings->lanes); /* restore DC out type */ dc->out->type = dc_out_type; tegra_dc_io_end(dc); return count; } static int test_settings_open(struct inode *inode, struct file *file) { return single_open(file, test_settings_show, inode->i_private); } const struct file_operations test_settings_fops = { .open = test_settings_open, .read = seq_read, .write = test_settings_set, .llseek = seq_lseek, .release = single_release, }; EXPORT_SYMBOL(test_settings_fops); #endif