/* * dsi_debug.c: dsi debug interface. * * Copyright (c) 2013-2019, 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 #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0) #include #else #include #endif /* HACK! This needs to come from DT */ #include #include #include #include #include #include #include #include #include "dc_reg.h" #include "dc_priv.h" #include "dsi_regs.h" #include "dsi.h" #ifdef CONFIG_DEBUG_FS #define MAX_PANEL_REG_READ_SIZE 300 static int dbg_dsi_show(struct seq_file *s, void *unused) { struct tegra_dc_dsi_data *dsi; struct tegra_dc *dc; unsigned long i = 0, j = 0; u32 col = 0; /* * on T210 and earlier, max->instances = 2 which means the last two * array elements are unused (as desired) on those platforms */ u32 base[] = {tegra_dc_get_dsi_base(), tegra_dc_get_dsib_base(), TEGRA_DSIC_BASE, TEGRA_DSID_BASE}; dc = ((struct tegra_dc_dsi_data *) s->private)->dc; dsi = (struct tegra_dc_dsi_data *) dc->out_data; /* for compatibility with fake OR, check * s->private->dc->out_data->enabled instead of * s->private->enabled */ if (!dsi->enabled) { seq_puts(s, "DSI controller suspended\n"); return 0; } tegra_dc_io_start(dsi->dc); tegra_dsi_clk_enable(dsi); /* mem dd dump */ for (i = 0; i < dsi->max_instances; i++) { for (col = 0, j = 0; j < 0x64; j++) { if (col == 0) seq_printf(s, "%08lX:", base[i] + 4*j); seq_printf(s, "%c%08lX", col == 2 ? '-' : ' ', tegra_dsi_controller_readl(dsi, j, i)); if (col == 3) { seq_puts(s, "\n"); col = 0; } else col++; } seq_puts(s, "\n"); } #define DUMP_REG(a) seq_printf(s, "%-45s | %#05x | %#010lx |\n", \ #a, a, tegra_dsi_readl(dsi, a)); DUMP_REG(DSI_INCR_SYNCPT_CNTRL); DUMP_REG(DSI_INCR_SYNCPT_ERROR); DUMP_REG(DSI_CTXSW); DUMP_REG(DSI_POWER_CONTROL); DUMP_REG(DSI_INT_ENABLE); DUMP_REG(DSI_HOST_DSI_CONTROL); DUMP_REG(DSI_CONTROL); DUMP_REG(DSI_SOL_DELAY); DUMP_REG(DSI_MAX_THRESHOLD); DUMP_REG(DSI_TRIGGER); DUMP_REG(DSI_TX_CRC); DUMP_REG(DSI_STATUS); DUMP_REG(DSI_INIT_SEQ_CONTROL); DUMP_REG(DSI_INIT_SEQ_DATA_0); DUMP_REG(DSI_INIT_SEQ_DATA_1); DUMP_REG(DSI_INIT_SEQ_DATA_2); DUMP_REG(DSI_INIT_SEQ_DATA_3); DUMP_REG(DSI_INIT_SEQ_DATA_4); DUMP_REG(DSI_INIT_SEQ_DATA_5); DUMP_REG(DSI_INIT_SEQ_DATA_6); DUMP_REG(DSI_INIT_SEQ_DATA_7); DUMP_REG(DSI_PKT_SEQ_0_LO); DUMP_REG(DSI_PKT_SEQ_0_HI); DUMP_REG(DSI_PKT_SEQ_1_LO); DUMP_REG(DSI_PKT_SEQ_1_HI); DUMP_REG(DSI_PKT_SEQ_2_LO); DUMP_REG(DSI_PKT_SEQ_2_HI); DUMP_REG(DSI_PKT_SEQ_3_LO); DUMP_REG(DSI_PKT_SEQ_3_HI); DUMP_REG(DSI_PKT_SEQ_4_LO); DUMP_REG(DSI_PKT_SEQ_4_HI); DUMP_REG(DSI_PKT_SEQ_5_LO); DUMP_REG(DSI_PKT_SEQ_5_HI); DUMP_REG(DSI_DCS_CMDS); DUMP_REG(DSI_PKT_LEN_0_1); DUMP_REG(DSI_PKT_LEN_2_3); DUMP_REG(DSI_PKT_LEN_4_5); DUMP_REG(DSI_PKT_LEN_6_7); DUMP_REG(DSI_PHY_TIMING_0); DUMP_REG(DSI_PHY_TIMING_1); DUMP_REG(DSI_PHY_TIMING_2); DUMP_REG(DSI_BTA_TIMING); DUMP_REG(DSI_TIMEOUT_0); DUMP_REG(DSI_TIMEOUT_1); DUMP_REG(DSI_TO_TALLY); DUMP_REG(DSI_PAD_CONTROL); DUMP_REG(DSI_PAD_CONTROL_CD); DUMP_REG(DSI_PAD_CD_STATUS); DUMP_REG(DSI_VID_MODE_CONTROL); DUMP_REG(DSI_PAD_CONTROL_0_VS1); DUMP_REG(DSI_PAD_CONTROL_CD_VS1); DUMP_REG(DSI_PAD_CD_STATUS_VS1); DUMP_REG(DSI_PAD_CONTROL_1_VS1); DUMP_REG(DSI_PAD_CONTROL_2_VS1); DUMP_REG(DSI_PAD_CONTROL_3_VS1); DUMP_REG(DSI_PAD_CONTROL_4_VS1); DUMP_REG(DSI_GANGED_MODE_CONTROL); DUMP_REG(DSI_GANGED_MODE_START); DUMP_REG(DSI_GANGED_MODE_SIZE); #undef DUMP_REG tegra_dsi_clk_disable(dsi); tegra_dc_io_end(dsi->dc); return 0; } static int dbg_dsi_open(struct inode *inode, struct file *file) { return single_open(file, dbg_dsi_show, inode->i_private); } static const struct file_operations dbg_fops = { .open = dbg_dsi_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; static u32 max_ret_payload_size; static u32 panel_reg_addr; static int read_panel_get(struct seq_file *s, void *unused) { struct tegra_dc_dsi_data *dsi = s->private; struct tegra_dc *dc = dsi->dc; int err = 0; u8 buf[MAX_PANEL_REG_READ_SIZE] = {0}; int j = 0 , b = 0 , k; u32 payload_size = 0; if (!dsi->enabled) { dev_info(&dc->ndev->dev, " controller suspended\n"); return -EINVAL; } seq_printf(s, "max ret payload size:0x%x\npanel reg addr:0x%x\n", max_ret_payload_size, panel_reg_addr); if ((max_ret_payload_size > MAX_PANEL_REG_READ_SIZE) || (max_ret_payload_size == 0)) { seq_printf(s, "max reg payload size should be a positive value below 0x%x\n", MAX_PANEL_REG_READ_SIZE); return err; } err = tegra_dsi_read_data(dsi->dc, dsi, max_ret_payload_size, panel_reg_addr, buf); for (b = 0; b < max_ret_payload_size; b += 4) { seq_printf(s, "\n Read data[%d] ", j++); for (k = b+4; k > b; k--) seq_printf(s, " %x ", buf[k-1]); } seq_puts(s, "\n"); switch (buf[0]) { case DSI_ESCAPE_CMD: seq_printf(s, "escape cmd[0x%x]\n", buf[0]); break; case DSI_ACK_NO_ERR: seq_printf(s, "Panel ack, no err[0x%x]\n", buf[0]); goto fail; break; default: seq_puts(s, "Invalid read response\n"); break; } switch (buf[4] & 0xff) { case GEN_LONG_RD_RES: /* Fall through */ case DCS_LONG_RD_RES: payload_size = (buf[5] | (buf[6] << 8)) & 0xFFFF; seq_printf(s, "Long read response Packet\n" "payload_size[0x%x]\n", payload_size); break; case GEN_1_BYTE_SHORT_RD_RES: /* Fall through */ case DCS_1_BYTE_SHORT_RD_RES: payload_size = 1; seq_printf(s, "Short read response Packet\n" "payload_size[0x%x]\n", payload_size); break; case GEN_2_BYTE_SHORT_RD_RES: /* Fall through */ case DCS_2_BYTE_SHORT_RD_RES: payload_size = 2; seq_printf(s, "Short read response Packet\n" "payload_size[0x%x]\n", payload_size); break; case ACK_ERR_RES: payload_size = 2; seq_printf(s, "Acknowledge error report response\n" "Packet payload_size[0x%x]\n", payload_size); break; default: seq_puts(s, "Invalid response packet\n"); break; } fail: return err; } static ssize_t read_panel_set(struct file *file, const char *buf, size_t count, loff_t *off) { struct seq_file *s = file->private_data; struct tegra_dc_dsi_data *dsi = s->private; struct tegra_dc *dc = dsi->dc; if (sscanf(buf, "%x %x", &max_ret_payload_size, &panel_reg_addr) != 2) return -EINVAL; dev_info(&dc->ndev->dev, "max ret payload size:0x%x\npanel reg addr:0x%x\n", max_ret_payload_size, panel_reg_addr); return count; } static int read_panel_open(struct inode *inode, struct file *file) { return single_open(file, read_panel_get, inode->i_private); } static const struct file_operations read_panel_fops = { .open = read_panel_open, .read = seq_read, .write = read_panel_set, .llseek = seq_lseek, .release = single_release, }; static int panel_sanity_check(struct seq_file *s, void *unused) { struct tegra_dc_dsi_data *dsi = s->private; struct tegra_dc *dc = dsi->dc; struct sanity_status *san = NULL; int err = 0; san = devm_kzalloc(&dc->ndev->dev, sizeof(*san), GFP_KERNEL); if (!san) { dev_info(&dc->ndev->dev, "No memory available\n"); return err; } tegra_dsi_enable_read_debug(dsi); err = tegra_dsi_panel_sanity_check(dc, dsi, san); tegra_dsi_disable_read_debug(dsi); if (err < 0) seq_puts(s, "Sanity check failed\n"); else seq_puts(s, "Sanity check successful\n"); return err; } static int sanity_panel_open(struct inode *inode, struct file *file) { return single_open(file, panel_sanity_check, inode->i_private); } static const struct file_operations sanity_panel_fops = { .open = sanity_panel_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; static u32 command_value; static u32 data_id; static u32 command_value1; static int send_host_cmd_v_blank_dcs(struct seq_file *s, void *unused) { struct tegra_dc_dsi_data *dsi = s->private; int err; struct tegra_dsi_cmd user_command[] = { DSI_CMD_SHORT(data_id, command_value, command_value1), DSI_DLY_MS(20), }; if (!dsi->enabled) { seq_puts(s, "DSI controller suspended\n"); return 0; } seq_printf(s, "data_id taken :0x%x\n", data_id); seq_printf(s, "command value taken :0x%x\n", command_value); seq_printf(s, "second command value taken :0x%x\n", command_value1); err = tegra_dsi_start_host_cmd_v_blank_dcs(dsi, user_command); return err; } static ssize_t host_cmd_v_blank_dcs_get_cmd(struct file *file, const char *buf, size_t count, loff_t *off) { struct seq_file *s = file->private_data; struct tegra_dc_dsi_data *dsi = s->private; struct tegra_dc *dc = dsi->dc; char *pbuf; if (!dsi->enabled) { dev_info(&dc->ndev->dev, "DSI controller suspended\n"); return count; } pbuf = vmalloc(count); if (!pbuf) return -ENOMEM; if (copy_from_user(pbuf, buf, count)) { vfree(pbuf); return -EFAULT; } if (sscanf(pbuf, "%x %x %x", &data_id, &command_value, &command_value1) != 3) { vfree(pbuf); return -EINVAL; } dev_info(&dc->ndev->dev, "data id taken :0x%x\n", data_id); dev_info(&dc->ndev->dev, "command value taken :0x%x\n", command_value); dev_info(&dc->ndev->dev, "second command value taken :0x%x\n", command_value1); vfree(pbuf); return count; } static int host_cmd_v_blank_dcs_open(struct inode *inode, struct file *file) { return single_open(file, send_host_cmd_v_blank_dcs, inode->i_private); } static const struct file_operations host_cmd_v_blank_dcs_fops = { .open = host_cmd_v_blank_dcs_open, .read = seq_read, .write = host_cmd_v_blank_dcs_get_cmd, .llseek = seq_lseek, .release = single_release, }; static int remove_host_cmd_dcs(struct seq_file *s, void *unused) { struct tegra_dc_dsi_data *dsi = s->private; tegra_dsi_stop_host_cmd_v_blank_dcs(dsi); seq_puts(s, "host_cmd_v_blank_dcs stopped\n"); return 0; } static int rm_host_cmd_dcs_open(struct inode *inode, struct file *file) { return single_open(file, remove_host_cmd_dcs, inode->i_private); } static const struct file_operations remove_host_cmd_dcs_fops = { .open = rm_host_cmd_dcs_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; static int send_write_data_cmd(struct seq_file *s, void *unused) { struct tegra_dc_dsi_data *dsi = s->private; struct tegra_dc *dc = dsi->dc; int err; u8 del = 100; struct tegra_dsi_cmd user_command[] = { DSI_CMD_SHORT(data_id, command_value, command_value1), DSI_DLY_MS(20), }; seq_printf(s, "data_id taken :0x%x\n", data_id); seq_printf(s, "command value taken :0x%x\n", command_value); seq_printf(s, "second command value taken :0x%x\n", command_value1); err = tegra_dsi_write_data(dc, dsi, user_command, del); return err; } static ssize_t write_data_get_cmd(struct file *file, const char *buf, size_t count, loff_t *off) { struct seq_file *s = file->private_data; struct tegra_dc_dsi_data *dsi = s->private; struct tegra_dc *dc = dsi->dc; if (sscanf(buf, "%x %x %x", &data_id, &command_value, &command_value1) != 3) return -EINVAL; dev_info(&dc->ndev->dev, "data_id taken :0x%x\n", data_id); dev_info(&dc->ndev->dev, "command value taken :0x%x\n", command_value); dev_info(&dc->ndev->dev, "second command value taken :0x%x\n", command_value1); return count; } static int write_data_open(struct inode *inode, struct file *file) { return single_open(file, send_write_data_cmd, inode->i_private); } static const struct file_operations write_data_fops = { .open = write_data_open, .read = seq_read, .write = write_data_get_cmd, .llseek = seq_lseek, .release = single_release, }; /* long packet write * [data_id(cmd_type)] [word count] [data_0] [data_1] [data_2] ... */ static u32 long_data_id; static u32 data_count; static u8 *data_buf; static int send_write_long_data_cmd(struct seq_file *s, void *unused) { struct tegra_dc_dsi_data *dsi = s->private; struct tegra_dc *dc = dsi->dc; int err, i; u8 del = 100; struct tegra_dsi_cmd user_command[] = { DSI_CMD_LONG_SIZE(long_data_id, data_buf, data_count), DSI_DLY_MS(20), }; if (data_buf == NULL) return -EINVAL; seq_printf(s, "data_id taken : 0x%x\n", long_data_id); seq_printf(s, "WC value taken : 0x%x\n", data_count); for (i = 0; i < data_count; i++) seq_printf(s, "data %d taken : 0x%x\n", i, data_buf[i]); err = tegra_dsi_write_data(dc, dsi, user_command, del); mdelay(20); kfree(data_buf); data_buf = NULL; return err; } static ssize_t write_long_data_get_cmd(struct file *file, const char __user *buf, size_t count, loff_t *off) { struct seq_file *s = file->private_data; struct tegra_dc_dsi_data *dsi = s->private; struct tegra_dc *dc = dsi->dc; char *token, *buffer, *orig_buffer; u32 i, j, value; if ((count <= 0) || (count > sizeof(long))) return -EINVAL; orig_buffer = kzalloc(count + 1, GFP_KERNEL); if (!orig_buffer) { dev_err(&dc->ndev->dev, "Not enough memory for buffer\n"); return -ENOMEM; } if (copy_from_user(orig_buffer, buf, count)) { dev_err(&dc->ndev->dev, "Copy from user failed\n"); goto fail; } buffer = orig_buffer; i = sscanf(buffer, "%x %x", &long_data_id, &data_count); if (i != 2 || !data_count) { dev_err(&dc->ndev->dev, "Invalid data id or word count\n"); goto fail; } dev_info(&dc->ndev->dev, "data_id taken : 0x%x\n", long_data_id); dev_info(&dc->ndev->dev, "WC taken : 0x%x\n", data_count); /* data buffer could be already allocated if this is called * twice without sending data, so free it first and re-alloc */ kfree(data_buf); data_buf = kzalloc(data_count, GFP_KERNEL); if (!data_buf) { dev_err(&dc->ndev->dev, "Not enough memory for data buffer\n"); goto fail; } /* skip first two params which are id and count */ i = 0; while (i < 2) { token = strsep(&buffer, " "); if (*token == '\0') continue; i++; } /* parse space separated list of data packets */ i = 0; while ((token = strsep(&buffer, " ")) != NULL) { if (*token == '\0') continue; if (i >= data_count) break; j = sscanf(token, "%x", &value); if (j != 1) break; data_buf[i] = (u8)value; dev_info(&dc->ndev->dev, "data %d taken:0x%x\n", i, data_buf[i]); i++; } if (i < data_count) { dev_err(&dc->ndev->dev, "Number of data is less than word count\n"); goto fail; } else if (token != NULL) { dev_err(&dc->ndev->dev, "Number of data is greater than word count\n"); goto fail; } kfree(orig_buffer); return count; fail: kfree(orig_buffer); kfree(data_buf); data_buf = NULL; return -EINVAL; } static int write_long_data_open(struct inode *inode, struct file *file) { return single_open(file, send_write_long_data_cmd, inode->i_private); } static const struct file_operations write_long_data_fops = { .open = write_long_data_open, .read = seq_read, .write = write_long_data_get_cmd, .llseek = seq_lseek, .release = single_release, }; static int dsi_crc_show(struct seq_file *s, void *unused) { struct tegra_dc_dsi_data *dsi; struct tegra_dc *dc; unsigned long crc = 0; int i; dc = ((struct tegra_dc_dsi_data *) s->private)->dc; dsi = (struct tegra_dc_dsi_data *) dc->out_data; /* for compatibility with fake OR, use * s->private->dc->out_data->enabled instead of * s->private->enabled */ if (!dsi->enabled) { seq_puts(s, "dsi not enabled, aborting\n"); return 0; } mutex_lock(&dc->lock); /*TODO: is this necessary?*/ for (i = 0; i < dsi->max_instances; i++) { if (dsi->base[i] == NULL) { pr_err("dsi->base[%d] = NULL, force CRC = 0\n", i); crc = 0; } else { crc = tegra_dsi_controller_readl(dsi, DSI_TX_CRC, i); } seq_printf(s, "DSI_DSI_TX_CRC[%d] = 0x%08lx\n", i + dsi->info.dsi_instance, crc); } mutex_unlock(&dc->lock); /*TODO: is this necessary?*/ return 0; } static ssize_t dsi_crc_write(struct file *file, const char *buf, size_t count, loff_t *off) { struct seq_file *s = file->private_data; struct tegra_dc_dsi_data *dsi; struct tegra_dc *dc; u32 dsi_ctrl, data; int i; dc = ((struct tegra_dc_dsi_data *) s->private)->dc; dsi = (struct tegra_dc_dsi_data *) dc->out_data; if (!dsi->enabled) { seq_puts(s, "dsi not enabled, aborting\n"); return -EINVAL; } if (sscanf(buf, "%x", &data) != 1) { seq_puts(s, "parameter not found, aborting\n"); return -EINVAL; } data &= 1; /* only keep bit0 */ mutex_lock(&dc->lock); /* TODO: is this necessary? */ for (i = 0; i < dsi->max_instances; i++) { if (dsi->base[i] == NULL) { pr_err("dsi->base[%d] = NULL, exit\n", i); break; } dsi_ctrl = tegra_dsi_controller_readl(dsi, DSI_CONTROL, i); dsi_ctrl &= ~DSI_CONTROL_DBG_ENABLE_MASK; dsi_ctrl |= DSI_CONTROL_DBG_ENABLE(data); tegra_dsi_controller_writel(dsi, dsi_ctrl, DSI_CONTROL, i); } mutex_unlock(&dc->lock); /* TODO: is this necessary? */ return count; } static int dsi_crc_open(struct inode *inode, struct file *file) { return single_open(file, dsi_crc_show, inode->i_private); } static const struct file_operations crc_fops = { .open = dsi_crc_open, .read = seq_read, .write = dsi_crc_write, .llseek = seq_lseek, .release = single_release, }; static int show_dsc_status(void *data, u64 *value) { struct tegra_dc_dsi_data *dsi = (struct tegra_dc_dsi_data *)data; struct tegra_dc *dc = dsi->dc; if (dc) { if (dc->out->type != TEGRA_DC_OUT_DSI) { pr_err("Invalid DC out type %d\n", dc->out->type); return 0; } *value = dc->out->dsc_en; } return 0; } static int set_dsc_en_dis(void *data, u64 value) { struct tegra_dc_dsi_data *dsi = (struct tegra_dc_dsi_data *)data; struct tegra_dc *dc = dsi->dc; value = !!value; if (dc) { if (dc->out->type != TEGRA_DC_OUT_DSI) { pr_err("Invalid DC out type %d\n", dc->out->type); } else { dc->out->dsc_en = value; dev_info(&dc->ndev->dev, "%s Link compression\n", (value ? "Enabling" : "Disabling")); } } return 0; } DEFINE_SIMPLE_ATTRIBUTE(dsi_dsc_control_override_fops, show_dsc_status, set_dsc_en_dis, "%llu\n"); static int show_dsc_comp_rate(void *data, u64 *value) { struct tegra_dc_dsi_data *dsi = (struct tegra_dc_dsi_data *)data; struct tegra_dc *dc = dsi->dc; if (dc) { if (dc->out->type != TEGRA_DC_OUT_DSI) { pr_err("Invalid DC out type %d\n", dc->out->type); return 0; } *value = dc->out->dsc_bpp; } return 0; } static int set_dsc_comp_rate(void *data, u64 value) { struct tegra_dc_dsi_data *dsi = (struct tegra_dc_dsi_data *)data; struct tegra_dc *dc = dsi->dc; /* Only 8,12 and 16 bpp compression rates are supported */ switch(value) { case DSC_COMP_RATE_8_BPP: case DSC_COMP_RATE_12_BPP: case DSC_COMP_RATE_16_BPP: break; default: dev_err(&dc->ndev->dev, "Invalid compression rate\n"); return 0; } if (dc) { if (dc->out->type != TEGRA_DC_OUT_DSI) { pr_err("Invalid DC out type %d\n", dc->out->type); } else { dc->out->dsc_bpp = value; dev_info(&dc->ndev->dev, "%lld bpp comp rate set\n", value); } } return 0; } DEFINE_SIMPLE_ATTRIBUTE(dsi_dsc_comp_rate_override_fops, show_dsc_comp_rate, set_dsc_comp_rate, "%llu\n"); static int show_dsc_num_comp_pkts(void *data, u64 *value) { struct tegra_dc_dsi_data *dsi = (struct tegra_dc_dsi_data *)data; struct tegra_dc *dc = dsi->dc; if (dc) { if (dc->out->type != TEGRA_DC_OUT_DSI) { pr_err("Invalid DC out type %d\n", dc->out->type); return 0; } *value = dc->out->num_of_slices; } return 0; } static int set_dsc_num_comp_pkts(void *data, u64 value) { struct tegra_dc_dsi_data *dsi = (struct tegra_dc_dsi_data *)data; struct tegra_dc *dc = dsi->dc; /* * Only 1,2 and 4 num of compressed packets per row are supported * by T210 DSI controller. T18x DSI controller additionally supports * 3 compressed packets per row. */ switch(value) { case DSC_ONE_COMP_PKTS_PER_ROW: case DSC_TWO_COMP_PKTS_PER_ROW: case DSC_FOUR_COMP_PKTS_PER_ROW: break; case DSC_THREE_COMP_PKTS_PER_ROW: if (tegra_dc_is_nvdisplay()) break; dev_err(&dc->ndev->dev, "Invalid num of comp pkts per row\n"); return 0; default: dev_err(&dc->ndev->dev, "Invalid num of comp pkts per row\n"); return 0; } if (dc) { if (dc->out->type != TEGRA_DC_OUT_DSI) { pr_err("Invalid DC out type %d\n", dc->out->type); } else { dc->out->num_of_slices = value; dev_info(&dc->ndev->dev, "num comp pkts set to %lld\n", value); } } return 0; } DEFINE_SIMPLE_ATTRIBUTE(dsi_dsc_num_comp_pkts_override_fops, show_dsc_num_comp_pkts, set_dsc_num_comp_pkts, "%llu\n"); static int dbg_hotplug_show(struct seq_file *m, void *unused) { struct tegra_dc_dsi_data *dsi = m->private; struct tegra_dc *dc = dsi->dc; if (WARN_ON(!dsi || !dc || !dc->out)) return -EINVAL; seq_printf(m, "dsi hpd state: %d\n", dc->out->hotplug_state); return 0; } static int dbg_hotplug_open(struct inode *inode, struct file *file) { return single_open(file, dbg_hotplug_show, inode->i_private); } /* * sw control for hpd. * 0 is normal state, hw drives hpd. * -1 is force deassert, sw drives hpd. * 1 is force assert, sw drives hpd. * before releasing to hw, sw must ensure hpd state is normal i.e. 0 */ static ssize_t dbg_hotplug_write(struct file *file, const char __user *addr, size_t len, loff_t *pos) { struct seq_file *m = file->private_data; /* single_open() initialized */ struct tegra_dc_dsi_data *dsi = m->private; struct tegra_dc *dc = dsi->dc; int ret; int hotplug_state; long new_state; if (WARN_ON(!dsi || !dc || !dc->out)) return -EINVAL; ret = kstrtol_from_user(addr, len, 10, &new_state); if (ret < 0) return ret; mutex_lock(&dc->lock); rmb(); hotplug_state = dc->out->hotplug_state; if (tegra_platform_is_sim()) goto success; if (hotplug_state == TEGRA_HPD_STATE_NORMAL && new_state != TEGRA_HPD_STATE_NORMAL && dc->hotplug_supported) { /* to be done later once GPIO handling is added */ } else if (hotplug_state != TEGRA_HPD_STATE_NORMAL && new_state == TEGRA_HPD_STATE_NORMAL && dc->hotplug_supported) { /* to be done later once GPIO handling is added */ } success: dc->out->hotplug_state = new_state; wmb(); mutex_unlock(&dc->lock); tegra_dsi_pending_hpd(dsi); return len; } static const struct file_operations dbg_hotplug_fops = { .open = dbg_hotplug_open, .read = seq_read, .write = dbg_hotplug_write, .llseek = seq_lseek, .release = single_release, }; static struct dentry *dsidir; void tegra_dc_dsi_debug_create(struct tegra_dc_dsi_data *dsi) { struct dentry *retval; struct dentry *dscdir; dsidir = debugfs_create_dir("tegra_dsi", NULL); if (!dsidir) return; retval = debugfs_create_file("regs", 0444, dsidir, dsi, &dbg_fops); if (!retval) goto free_out; retval = debugfs_create_file("read_panel", 0644, dsidir, dsi, &read_panel_fops); if (!retval) goto free_out; retval = debugfs_create_file("panel_sanity", 0444, dsidir, dsi, &sanity_panel_fops); if (!retval) goto free_out; retval = debugfs_create_file("host_cmd_v_blank_dcs", 0644, dsidir, dsi, &host_cmd_v_blank_dcs_fops); if (!retval) goto free_out; retval = debugfs_create_file("remove_host_cmd_dcs", 0644, dsidir, dsi, &remove_host_cmd_dcs_fops); if (!retval) goto free_out; retval = debugfs_create_file("write_data", 0644, dsidir, dsi, &write_data_fops); if (!retval) goto free_out; retval = debugfs_create_file("write_long_data", 0644, dsidir, dsi, &write_long_data_fops); if (!retval) goto free_out; retval = debugfs_create_file("crc", 0444, dsidir, dsi, &crc_fops); if (!retval) goto free_out; dscdir = debugfs_create_dir("link_compression", dsidir); if (!dscdir) goto free_out; if (is_hotplug_supported(dsi)) { retval = debugfs_create_file("hotplug", S_IRUGO, dsidir, dsi, &dbg_hotplug_fops); if (!retval) goto free_out; } retval = debugfs_create_file("dsc_enable", 0644, dscdir, (void *)dsi, &dsi_dsc_control_override_fops); if (!retval) goto free_out; retval = debugfs_create_file("comp_rate", 0644, dscdir, (void *)dsi, &dsi_dsc_comp_rate_override_fops); if (!retval) goto free_out; retval = debugfs_create_file("num_comp_pkts", 0644, dscdir, (void *)dsi, &dsi_dsc_num_comp_pkts_override_fops); if (!retval) goto free_out; return; free_out: debugfs_remove_recursive(dsidir); dsidir = NULL; return; } EXPORT_SYMBOL(tegra_dc_dsi_debug_create); #else void tegra_dc_dsi_debug_create(struct tegra_dc_dsi_data *dsi) {} EXPORT_SYMBOL(tegra_dc_dsi_debug_create); #endif