988 lines
24 KiB
C
988 lines
24 KiB
C
/*
|
|
* 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 <linux/version.h>
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0)
|
|
#include <linux/uaccess.h>
|
|
#else
|
|
#include <asm/uaccess.h>
|
|
#endif
|
|
/* HACK! This needs to come from DT */
|
|
#include <iomap.h>
|
|
#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 "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
|