| /* |
| * drivers/video/tegra/dc/dsi_debug.c |
| * |
| * Copyright (c) 2013-2014 NVIDIA CORPORATION, All rights reserved. |
| * |
| * This software is licensed under the terms of the GNU General Public |
| * License version 2, as published by the Free Software Foundation, and |
| * may be copied, distributed, and modified under those terms. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| */ |
| |
| #include <linux/kernel.h> |
| #include <linux/clk.h> |
| #include <linux/moduleparam.h> |
| #include <linux/export.h> |
| #include <linux/debugfs.h> |
| #include <linux/seq_file.h> |
| #include <linux/delay.h> |
| #include "dc_reg.h" |
| #include "dc_priv.h" |
| #include "dev.h" |
| #include "dsi_regs.h" |
| #include "dsi.h" |
| /* HACK! This needs to come from DT */ |
| #include "../../../../arch/arm/mach-tegra/iomap.h" |
| |
| #ifdef CONFIG_DEBUG_FS |
| |
| static int dbg_dsi_show(struct seq_file *s, void *unused) |
| { |
| struct tegra_dc_dsi_data *dsi = s->private; |
| unsigned long i = 0, j = 0; |
| u32 col = 0; |
| u32 base[MAX_DSI_INSTANCE] = {TEGRA_DSI_BASE, TEGRA_DSIB_BASE}; |
| |
| 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[300] = {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 == 0) { |
| seq_puts(s, "echo was not successful\n"); |
| return err; |
| } |
| err = tegra_dsi_read_data(dsi->dc, dsi, |
| max_ret_payload_size, |
| panel_reg_addr, buf); |
| |
| seq_printf(s, " Read data[%d] ", b); |
| |
| for (b = 1; b < (max_ret_payload_size+1); b++) { |
| j = (b*4)-1; |
| for (k = j; k > (j-4); k--) |
| if ((k%4) == 0 && b != max_ret_payload_size) { |
| seq_printf(s, " %x ", buf[k]); |
| seq_printf(s, "\n Read data[%d] ", b); |
| } |
| else |
| seq_printf(s, " %x ", buf[k]); |
| } |
| 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; |
| |
| if (!dsi->enabled) { |
| dev_info(&dc->ndev->dev, "DSI controller suspended\n"); |
| return count; |
| } |
| |
| 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 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), |
| }; |
| |
| 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_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, |
| }; |
| |
| static struct dentry *dsidir; |
| |
| void tegra_dc_dsi_debug_create(struct tegra_dc_dsi_data *dsi) |
| { |
| struct dentry *retval; |
| |
| dsidir = debugfs_create_dir("tegra_dsi", NULL); |
| if (!dsidir) |
| return; |
| retval = debugfs_create_file("regs", S_IRUGO, dsidir, dsi, |
| &dbg_fops); |
| if (!retval) |
| goto free_out; |
| retval = debugfs_create_file("read_panel", S_IRUGO|S_IWUSR, dsidir, |
| dsi, &read_panel_fops); |
| if (!retval) |
| goto free_out; |
| retval = debugfs_create_file("panel_sanity", S_IRUGO, dsidir, |
| dsi, &sanity_panel_fops); |
| if (!retval) |
| goto free_out; |
| retval = debugfs_create_file("host_cmd_v_blank_dcs", S_IRUGO|S_IWUSR, |
| dsidir, dsi, &host_cmd_v_blank_dcs_fops); |
| if (!retval) |
| goto free_out; |
| retval = debugfs_create_file("remove_host_cmd_dcs", S_IRUGO|S_IWUSR, |
| dsidir, dsi, &remove_host_cmd_dcs_fops); |
| if (!retval) |
| goto free_out; |
| retval = debugfs_create_file("write_data", S_IRUGO|S_IWUSR, |
| dsidir, dsi, &write_data_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 |