/*
* drivers/video/tegra/dc/dsi.c
*
- * Copyright (c) 2011, NVIDIA Corporation.
+ * Copyright (c) 2011-2012, 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
*
*/
+#include <linux/kernel.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/fb.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
-#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/workqueue.h>
#include <linux/debugfs.h>
#include <linux/seq_file.h>
+#include <linux/nvhost.h>
#include <mach/clk.h>
#include <mach/dc.h>
#include <mach/fb.h>
#include <mach/csi.h>
+#include <mach/iomap.h>
#include <linux/nvhost.h>
#include "dc_reg.h"
#include "dsi_regs.h"
#include "dsi.h"
+#define APB_MISC_GP_MIPI_PAD_CTRL_0 (TEGRA_APB_MISC_BASE + 0x820)
+#define DSIB_MODE_ENABLE 0x2
+
#define DSI_USE_SYNC_POINTS 1
#define S_TO_MS(x) (1000 * (x))
struct dsi_phy_timing_inclk phy_timing;
+ bool ulpm;
+ bool enabled;
+ bool host_suspended;
+
u8 driven_mode;
u8 controller_index;
u32 current_dsi_clk_khz;
u32 dsi_control_val;
-
- bool ulpm;
- bool enabled;
};
const u32 dsi_pkt_seq_reg[NUMOF_PKT_SEQ] = {
DSI_PKT_LEN_6_7,
};
+static int tegra_dsi_host_suspend(struct tegra_dc *dc);
+static int tegra_dsi_host_resume(struct tegra_dc *dc);
+
inline unsigned long tegra_dsi_readl(struct tegra_dc_dsi_data *dsi, u32 reg)
{
unsigned long ret;
- BUG_ON(!nvhost_module_powered(nvhost_get_host(dsi->dc->ndev)->dev));
+ BUG_ON(!nvhost_module_powered_ext(nvhost_get_parent(dsi->dc->ndev)));
ret = readl(dsi->base + reg * 4);
trace_printk("readl %p=%#08lx\n", dsi->base + reg * 4, ret);
return ret;
inline void tegra_dsi_writel(struct tegra_dc_dsi_data *dsi, u32 val, u32 reg)
{
- BUG_ON(!nvhost_module_powered(nvhost_get_host(dsi->dc->ndev)->dev));
+ BUG_ON(!nvhost_module_powered_ext(nvhost_get_parent(dsi->dc->ndev)));
trace_printk("writel %p=%#08x\n", dsi->base + reg * 4, val);
writel(val, dsi->base + reg * 4);
}
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);
{ }
#endif
+static inline void tegra_dsi_clk_enable(struct tegra_dc_dsi_data *dsi)
+{
+ if (!tegra_is_clk_enabled(dsi->dsi_clk)) {
+ clk_enable(dsi->dsi_clk);
+ clk_enable(dsi->dsi_fixed_clk);
+ }
+}
+
+static inline void tegra_dsi_clk_disable(struct tegra_dc_dsi_data *dsi)
+{
+ if (tegra_is_clk_enabled(dsi->dsi_clk)) {
+ clk_disable(dsi->dsi_clk);
+ clk_disable(dsi->dsi_fixed_clk);
+ }
+}
+
static int tegra_dsi_syncpt(struct tegra_dc_dsi_data *dsi)
{
u32 val;
ret = 0;
- dsi->syncpt_val = nvhost_syncpt_read(
- &nvhost_get_host(dsi->dc->ndev)->syncpt,
- dsi->syncpt_id);
+ dsi->syncpt_val = nvhost_syncpt_read_ext(dsi->dc->ndev, dsi->syncpt_id);
val = DSI_INCR_SYNCPT_COND(OP_DONE) |
DSI_INCR_SYNCPT_INDX(dsi->syncpt_id);
tegra_dsi_writel(dsi, val, DSI_INCR_SYNCPT);
/* TODO: Use interrupt rather than polling */
- ret = nvhost_syncpt_wait(&nvhost_get_host(dsi->dc->ndev)->syncpt,
- dsi->syncpt_id, dsi->syncpt_val + 1);
+ ret = nvhost_syncpt_wait_timeout_ext(dsi->dc->ndev, dsi->syncpt_id,
+ dsi->syncpt_val + 1, MAX_SCHEDULE_TIMEOUT, NULL);
if (ret < 0) {
dev_err(&dsi->dc->ndev->dev, "DSI sync point failure\n");
goto fail;
u32 dsi_to_pixel_clk_ratio;
u32 temp;
u32 temp1;
- u32 mipi_clk_adj_kHz;
+ u32 mipi_clk_adj_kHz = 0;
u32 sol_delay;
struct tegra_dc_mode *dc_modes = &dc->mode;
}
}
+static void tegra_dsi_soft_reset(struct tegra_dc_dsi_data *dsi)
+{
+ u32 trigger;
+
+ tegra_dsi_writel(dsi,
+ DSI_POWER_CONTROL_LEG_DSI_ENABLE(TEGRA_DSI_DISABLE),
+ DSI_POWER_CONTROL);
+ /* stabilization delay */
+ udelay(300);
+
+ tegra_dsi_writel(dsi,
+ DSI_POWER_CONTROL_LEG_DSI_ENABLE(TEGRA_DSI_ENABLE),
+ DSI_POWER_CONTROL);
+ /* stabilization delay */
+ udelay(300);
+
+ /* dsi HW does not clear host trigger bit automatically
+ * on dsi interface disable if host fifo is empty or in mid
+ * of host transmission
+ */
+ trigger = tegra_dsi_readl(dsi, DSI_TRIGGER);
+ if (trigger)
+ tegra_dsi_writel(dsi, 0x0, DSI_TRIGGER);
+}
+
static void tegra_dsi_stop_dc_stream(struct tegra_dc *dc,
struct tegra_dc_dsi_data *dsi)
{
long timeout;
u32 frame_period = DIV_ROUND_UP(S_TO_MS(1), dsi->info.refresh_rate);
- /* stop dc */
- tegra_dsi_stop_dc_stream(dc, dsi);
+ INIT_COMPLETION(dc->frame_end_complete);
- /* enable frame end interrupt */
- val = tegra_dc_readl(dc, DC_CMD_INT_MASK);
- val |= FRAME_END_INT;
- tegra_dc_writel(dc, val, DC_CMD_INT_MASK);
+ /* unmask frame end interrupt */
+ val = tegra_dc_unmask_interrupt(dc, FRAME_END_INT);
+
+ tegra_dsi_stop_dc_stream(dc, dsi);
/* wait for frame_end completion.
* timeout is 2 frame duration to accomodate for
&dc->frame_end_complete,
msecs_to_jiffies(2 * frame_period));
- /* disable frame end interrupt */
- val = tegra_dc_readl(dc, DC_CMD_INT_MASK);
- val &= ~FRAME_END_INT;
- tegra_dc_writel(dc, val, DC_CMD_INT_MASK);
+ /* give 2 line time to dsi HW to catch up
+ * with pixels sent by dc
+ */
+ udelay(50);
+
+ tegra_dsi_soft_reset(dsi);
+
+ /* reinstate interrupt mask */
+ tegra_dc_restore_interrupt(dc, val); /* potentially a race? */
if (timeout == 0)
dev_warn(&dc->ndev->dev,
/* Enable DSI clock */
tegra_dc_setup_clk(dc, dsi->dsi_clk);
- if (!dsi->clk_ref) {
- dsi->clk_ref = true;
- clk_enable(dsi->dsi_clk);
- clk_enable(dsi->dsi_fixed_clk);
- tegra_periph_reset_deassert(dsi->dsi_clk);
- }
+ tegra_dsi_clk_enable(dsi);
+ tegra_periph_reset_deassert(dsi->dsi_clk);
+
dsi->current_dsi_clk_khz = clk_get_rate(dsi->dsi_clk) / 1000;
dsi->current_bit_clk_ns = 1000*1000 / (dsi->current_dsi_clk_khz * 2);
}
dsi->status.vtype = DSI_VIDEO_TYPE_NOT_INIT;
}
-static void tegra_dsi_set_control_reg_hs(struct tegra_dc_dsi_data *dsi)
+static void tegra_dsi_set_control_reg_hs(struct tegra_dc_dsi_data *dsi,
+ u8 driven_mode)
{
u32 dsi_control;
u32 host_dsi_control;
max_threshold = 0;
dcs_cmd = 0;
- if (dsi->driven_mode == TEGRA_DSI_DRIVEN_BY_HOST) {
+ if (driven_mode == TEGRA_DSI_DRIVEN_BY_HOST) {
dsi_control |= DSI_CTRL_HOST_DRIVEN;
host_dsi_control |= HOST_DSI_CTRL_HOST_DRIVEN;
max_threshold =
max_threshold =
DSI_MAX_THRESHOLD_MAX_THRESHOLD(DSI_VIDEO_FIFO_DEPTH);
dsi->status.driven = DSI_DRIVEN_MODE_DC;
- }
- if (dsi->info.video_data_type == TEGRA_DSI_VIDEO_TYPE_COMMAND_MODE) {
- dsi_control |= DSI_CTRL_CMD_MODE;
- dcs_cmd = DSI_DCS_CMDS_LT5_DCS_CMD(DSI_WRITE_MEMORY_START)|
- DSI_DCS_CMDS_LT3_DCS_CMD(DSI_WRITE_MEMORY_CONTINUE);
- dsi->status.vtype = DSI_VIDEO_TYPE_CMD_MODE;
-
- } else {
- dsi_control |= DSI_CTRL_VIDEO_MODE;
- dsi->status.vtype = DSI_VIDEO_TYPE_VIDEO_MODE;
+ if (dsi->info.video_data_type ==
+ TEGRA_DSI_VIDEO_TYPE_COMMAND_MODE) {
+ dsi_control |= DSI_CTRL_CMD_MODE;
+ dcs_cmd = DSI_DCS_CMDS_LT5_DCS_CMD(
+ DSI_WRITE_MEMORY_START)|
+ DSI_DCS_CMDS_LT3_DCS_CMD(
+ DSI_WRITE_MEMORY_CONTINUE);
+ dsi->status.vtype = DSI_VIDEO_TYPE_CMD_MODE;
+ } else {
+ dsi_control |= DSI_CTRL_VIDEO_MODE;
+ dsi->status.vtype = DSI_VIDEO_TYPE_VIDEO_MODE;
+ }
}
tegra_dsi_writel(dsi, max_threshold, DSI_MAX_THRESHOLD);
tegra_vi_csi_writel(val, CSI_CIL_PAD_CONFIG);
}
+static void tegra_dsi_panelB_enable(void)
+{
+ unsigned int val;
+
+ val = readl(IO_ADDRESS(APB_MISC_GP_MIPI_PAD_CTRL_0));
+ val |= DSIB_MODE_ENABLE;
+ writel(val, (IO_ADDRESS(APB_MISC_GP_MIPI_PAD_CTRL_0)));
+}
+
static int tegra_dsi_init_hw(struct tegra_dc *dc,
struct tegra_dc_dsi_data *dsi)
{
tegra_dsi_set_dsi_clk(dc, dsi, dsi->target_lp_clk_khz);
if (dsi->info.dsi_instance) {
- /* TODO:Set the misc register*/
+ tegra_dsi_panelB_enable();
}
/* TODO: only need to change the timing for bta */
dsi->status.lphs = DSI_LPHS_IN_LP_MODE;
dsi->status.lp_op = lp_op;
+ dsi->driven_mode = TEGRA_DSI_DRIVEN_BY_HOST;
success:
err = 0;
fail:
}
static int tegra_dsi_set_to_hs_mode(struct tegra_dc *dc,
- struct tegra_dc_dsi_data *dsi)
+ struct tegra_dc_dsi_data *dsi,
+ u8 driven_mode)
{
int err;
goto fail;
}
- if (dsi->status.lphs == DSI_LPHS_IN_HS_MODE)
+ if (dsi->status.lphs == DSI_LPHS_IN_HS_MODE &&
+ dsi->driven_mode == driven_mode)
goto success;
+ dsi->driven_mode = driven_mode;
+
if (dsi->status.dc_stream == DSI_DC_STREAM_ENABLE)
tegra_dsi_stop_dc_stream_at_frame_end(dc, dsi);
tegra_dsi_set_phy_timing(dsi, DSI_LPHS_IN_HS_MODE);
- if (dsi->driven_mode == TEGRA_DSI_DRIVEN_BY_DC) {
+ if (driven_mode == TEGRA_DSI_DRIVEN_BY_DC) {
tegra_dsi_set_pkt_seq(dc, dsi);
tegra_dsi_set_pkt_length(dc, dsi);
tegra_dsi_set_sol_delay(dc, dsi);
tegra_dsi_set_dc_clk(dc, dsi);
}
- tegra_dsi_set_control_reg_hs(dsi);
+ tegra_dsi_set_control_reg_hs(dsi, driven_mode);
if (dsi->status.clk_out == DSI_PHYCLK_OUT_DIS ||
dsi->info.enable_hs_clock_on_lp_cmd_mode)
return (err < 0 ? true : false);
}
-static void tegra_dsi_soft_reset(struct tegra_dc_dsi_data *dsi)
-{
- u32 trigger;
- u32 status;
-
- tegra_dsi_writel(dsi,
- DSI_POWER_CONTROL_LEG_DSI_ENABLE(TEGRA_DSI_DISABLE),
- DSI_POWER_CONTROL);
- /* stabilization delay */
- udelay(300);
-
- tegra_dsi_writel(dsi,
- DSI_POWER_CONTROL_LEG_DSI_ENABLE(TEGRA_DSI_ENABLE),
- DSI_POWER_CONTROL);
- /* stabilization delay */
- udelay(300);
-
- /* dsi HW does not clear host trigger bit automatically
- * on dsi interface disable if host fifo is empty
- */
- trigger = tegra_dsi_readl(dsi, DSI_TRIGGER);
- status = tegra_dsi_readl(dsi, DSI_STATUS);
- if (trigger & DSI_TRIGGER_HOST_TRIGGER(0x1) &&
- status & DSI_STATUS_IDLE(0x1)) {
- trigger &= ~(DSI_TRIGGER_HOST_TRIGGER(0x1));
- tegra_dsi_writel(dsi, trigger, DSI_TRIGGER);
- }
-}
-
static void tegra_dsi_reset_read_count(struct tegra_dc_dsi_data *dsi)
{
u32 val;
struct tegra_dc *dc,
u8 lp_op)
{
- struct dsi_status *init_status;
+ struct dsi_status *init_status = NULL;
int err;
+ if (dsi->status.init != DSI_MODULE_INIT ||
+ dsi->status.lphs == DSI_LPHS_NOT_INIT) {
+ err = -EPERM;
+ goto fail;
+ }
+
init_status = kzalloc(sizeof(*init_status), GFP_KERNEL);
if (!init_status)
return ERR_PTR(-ENOMEM);
*init_status = dsi->status;
- if (dsi->status.lphs == DSI_LPHS_IN_HS_MODE) {
- if (dsi->status.driven == DSI_DRIVEN_MODE_DC) {
- if (dsi->status.dc_stream == DSI_DC_STREAM_ENABLE)
- tegra_dsi_stop_dc_stream_at_frame_end(dc, dsi);
- dsi->driven_mode = TEGRA_DSI_DRIVEN_BY_HOST;
- if (dsi->info.hs_cmd_mode_supported) {
- err = tegra_dsi_set_to_hs_mode(dc, dsi);
- if (err < 0) {
- dev_err(&dc->ndev->dev,
- "Switch to HS host mode failed\n");
- goto fail;
- }
- }
- }
- if (!dsi->info.hs_cmd_mode_supported) {
- err =
- tegra_dsi_set_to_lp_mode(dc, dsi, lp_op);
- if (err < 0) {
- dev_err(&dc->ndev->dev,
- "DSI failed to go to LP mode\n");
- goto fail;
- }
- }
- } else if (dsi->status.lphs == DSI_LPHS_IN_LP_MODE) {
- if (dsi->status.lp_op != lp_op) {
- err = tegra_dsi_set_to_lp_mode(dc, dsi, lp_op);
- if (err < 0) {
- dev_err(&dc->ndev->dev,
- "DSI failed to go to LP mode\n");
- goto fail;
- }
+ if (dsi->info.hs_cmd_mode_supported) {
+ err = tegra_dsi_set_to_hs_mode(dc, dsi,
+ TEGRA_DSI_DRIVEN_BY_HOST);
+ if (err < 0) {
+ dev_err(&dc->ndev->dev,
+ "Switch to HS host mode failed\n");
+ goto fail;
}
+
+ goto success;
}
+ if (dsi->status.lp_op != lp_op) {
+ err = tegra_dsi_set_to_lp_mode(dc, dsi, lp_op);
+ if (err < 0) {
+ dev_err(&dc->ndev->dev,
+ "DSI failed to go to LP mode\n");
+ goto fail;
+ }
+ }
+success:
return init_status;
fail:
kfree(init_status);
{
int err = 0;
struct dsi_status *init_status;
+ bool restart_dc_stream = false;
if (dsi->status.init != DSI_MODULE_INIT ||
dsi->ulpm) {
goto fail;
}
+ if (dsi->status.dc_stream == DSI_DC_STREAM_ENABLE) {
+ restart_dc_stream = true;
+ tegra_dsi_stop_dc_stream_at_frame_end(dc, dsi);
+ }
+
if (tegra_dsi_host_busy(dsi)) {
tegra_dsi_soft_reset(dsi);
-
- /* WAR to stop host write in middle */
- tegra_dsi_writel(dsi, TEGRA_DSI_DISABLE, DSI_TRIGGER);
-
if (tegra_dsi_host_busy(dsi)) {
err = -EBUSY;
dev_err(&dc->ndev->dev, "DSI host busy\n");
goto fail;
}
+ if (restart_dc_stream)
+ init_status->dc_stream = DSI_DC_STREAM_ENABLE;
+
return init_status;
fail:
return ERR_PTR(err);
struct tegra_dc_dsi_data *dsi,
struct dsi_status *init_status)
{
- bool switch_back_to_dc_mode = false;
- bool switch_back_to_hs_mode = false;
- bool restart_dc_stream;
int err = 0;
- switch_back_to_dc_mode = (dsi->status.driven ==
- DSI_DRIVEN_MODE_HOST &&
- init_status->driven ==
- DSI_DRIVEN_MODE_DC);
- switch_back_to_hs_mode = (dsi->status.lphs ==
- DSI_LPHS_IN_LP_MODE &&
- init_status->lphs ==
- DSI_LPHS_IN_HS_MODE);
- restart_dc_stream = (dsi->status.dc_stream ==
- DSI_DC_STREAM_DISABLE &&
- init_status->dc_stream ==
- DSI_DC_STREAM_ENABLE);
-
- if (dsi->status.lphs == DSI_LPHS_IN_LP_MODE &&
- init_status->lphs == DSI_LPHS_IN_LP_MODE) {
- if (dsi->status.lp_op != init_status->lp_op) {
- err =
- tegra_dsi_set_to_lp_mode(dc, dsi, init_status->lp_op);
- if (err < 0) {
- dev_err(&dc->ndev->dev,
- "Failed to config LP mode\n");
- goto fail;
- }
+ if (init_status->lphs == DSI_LPHS_IN_LP_MODE) {
+ err = tegra_dsi_set_to_lp_mode(dc, dsi, init_status->lp_op);
+ if (err < 0) {
+ dev_err(&dc->ndev->dev,
+ "Failed to config LP mode\n");
+ goto fail;
}
goto success;
}
- if (switch_back_to_dc_mode)
- dsi->driven_mode = TEGRA_DSI_DRIVEN_BY_DC;
- if (switch_back_to_dc_mode || switch_back_to_hs_mode) {
- err = tegra_dsi_set_to_hs_mode(dc, dsi);
+ if (init_status->lphs == DSI_LPHS_IN_HS_MODE) {
+ u8 driven = (init_status->driven == DSI_DRIVEN_MODE_DC) ?
+ TEGRA_DSI_DRIVEN_BY_DC : TEGRA_DSI_DRIVEN_BY_HOST;
+ err = tegra_dsi_set_to_hs_mode(dc, dsi, driven);
if (err < 0) {
dev_err(&dc->ndev->dev, "Failed to config HS mode\n");
goto fail;
}
}
- if (restart_dc_stream)
- tegra_dsi_start_dc_stream(dc, dsi);
+ if (init_status->dc_stream == DSI_DC_STREAM_ENABLE)
+ tegra_dsi_start_dc_stream(dc, dsi);
success:
fail:
kfree(init_status);
u8 *pdata, u8 data_id, u16 data_len)
{
u8 virtual_channel;
- u8 *pval;
u32 val;
int err;
pdata += 4;
} else {
val = 0;
- pval = (u8 *) &val;
- do
- *pval++ = *pdata++;
- while (--data_len);
+ memcpy(&val, pdata, data_len);
+ pdata += data_len;
+ data_len = 0;
}
tegra_dsi_writel(dsi, val, DSI_WR_DATA);
}
return err;
}
-int tegra_dsi_write_data(struct tegra_dc *dc,
+static void tegra_dc_dsi_hold_host(struct tegra_dc *dc)
+{
+ struct tegra_dc_dsi_data *dsi = tegra_dc_get_outdata(dc);
+
+ if (dc->out->flags & TEGRA_DC_OUT_ONE_SHOT_LP_MODE) {
+ /*
+ * The reference count should never be more than 1.
+ */
+ BUG_ON(tegra_is_clk_enabled(dc->clk) > 1);
+
+ if (dsi->host_suspended)
+ tegra_dsi_host_resume(dc);
+
+ /*
+ * Take an extra refrence to count for the clk_disable in
+ * tegra_dc_release_host.
+ */
+ clk_enable(dc->clk);
+ }
+}
+
+static void tegra_dc_dsi_release_host(struct tegra_dc *dc)
+{
+ if (dc->out->flags & TEGRA_DC_OUT_ONE_SHOT_LP_MODE)
+ clk_disable(dc->clk);
+}
+
+static void tegra_dc_dsi_idle(struct tegra_dc *dc)
+{
+ if (dc->out->flags & TEGRA_DC_OUT_ONE_SHOT_LP_MODE)
+ tegra_dsi_host_suspend(dc);
+}
+
+static int tegra_dsi_write_data_nosync(struct tegra_dc *dc,
struct tegra_dc_dsi_data *dsi,
u8 *pdata, u8 data_id, u16 data_len)
{
int err = 0;
struct dsi_status *init_status;
- tegra_dc_io_start(dc);
-
init_status = tegra_dsi_prepare_host_transmission(
dc, dsi, DSI_LP_OP_WRITE);
if (IS_ERR_OR_NULL(init_status)) {
err = tegra_dsi_restore_state(dc, dsi, init_status);
if (err < 0)
dev_err(&dc->ndev->dev, "Failed to restore prev state\n");
+
+ return err;
+}
+
+int tegra_dsi_write_data(struct tegra_dc *dc,
+ struct tegra_dc_dsi_data *dsi,
+ u8 *pdata, u8 data_id, u16 data_len)
+{
+ int err;
+
+ tegra_dc_io_start(dc);
+ tegra_dc_dsi_hold_host(dc);
+
+ err = tegra_dsi_write_data_nosync(dc, dsi, pdata, data_id, data_len);
+
+ tegra_dc_dsi_release_host(dc);
tegra_dc_io_end(dc);
+
return err;
}
+
EXPORT_SYMBOL(tegra_dsi_write_data);
static int tegra_dsi_send_panel_cmd(struct tegra_dc *dc,
if (cur_cmd->cmd_type == TEGRA_DSI_DELAY_MS)
mdelay(cur_cmd->sp_len_dly.delay_ms);
else {
- err = tegra_dsi_write_data(dc, dsi,
+ err = tegra_dsi_write_data_nosync(dc, dsi,
cur_cmd->pdata,
cur_cmd->data_id,
cur_cmd->sp_len_dly.data_len);
return err;
}
-static u8 get_8bit_ecc(u32 header)
+static u8 tegra_dsi_ecc(u32 header)
{
char ecc_parity[24] = {
0x07, 0x0b, 0x0d, 0x0e, 0x13, 0x15, 0x16, 0x19,
return ecc_byte;
}
-/* This function is written to send DCS short write (1 parameter) only.
- * This means the cmd will contain only 1 byte of index and 1 byte of value.
- * The data type ID is fixed at 0x15 and the ECC is calculated based on the
- * data in pdata.
- * The command will be sent by hardware every frame.
- * pdata should contain both the index + value for each cmd.
- * data_len will be the total number of bytes in pdata.
- */
-int tegra_dsi_send_panel_short_cmd(struct tegra_dc *dc, u8 *pdata, u8 data_len)
+static u16 tegra_dsi_cs(char *pdata, u16 data_len)
{
- u8 ecc8bits = 0, data_len_orig = 0;
- u32 val = 0, pkthdr = 0;
- int err = 0, count = 0;
- struct tegra_dc_dsi_data *dsi = tegra_dc_get_outdata(dc);
+ u16 byte_cnt;
+ u8 bit_cnt;
+ char curr_byte;
+ u16 crc = 0xFFFF;
+ u16 poly = 0x8408;
- data_len_orig = data_len;
- if (pdata != NULL) {
- while (data_len) {
- if (data_len >= 2) {
- pkthdr = (CMD_SHORTW |
- (((u16 *)pdata)[0]) << 8 | 0x00 << 24);
- ecc8bits = get_8bit_ecc(pkthdr);
- val = (pkthdr | (ecc8bits << 24));
- data_len -= 2;
- pdata += 2;
- count++;
+ if (data_len > 0) {
+ for (byte_cnt = 0; byte_cnt < data_len; byte_cnt++) {
+ curr_byte = pdata[byte_cnt];
+ for (bit_cnt = 0; bit_cnt < 8; bit_cnt++) {
+ if (((crc & 0x0001 ) ^
+ (curr_byte & 0x0001)) > 0)
+ crc = ((crc >> 1) & 0x7FFF) ^ poly;
+ else
+ crc = (crc >> 1) & 0x7FFF;
+
+ curr_byte = (curr_byte >> 1 ) & 0x7F;
}
- switch (count) {
- case 1:
- tegra_dsi_writel(dsi, val, DSI_INIT_SEQ_DATA_0);
- break;
- case 2:
- tegra_dsi_writel(dsi, val, DSI_INIT_SEQ_DATA_1);
- break;
- case 3:
- tegra_dsi_writel(dsi, val, DSI_INIT_SEQ_DATA_2);
- break;
- case 4:
- tegra_dsi_writel(dsi, val, DSI_INIT_SEQ_DATA_3);
- break;
- case 5:
- tegra_dsi_writel(dsi, val, DSI_INIT_SEQ_DATA_4);
- break;
- case 6:
- tegra_dsi_writel(dsi, val, DSI_INIT_SEQ_DATA_5);
- break;
- case 7:
- tegra_dsi_writel(dsi, val, DSI_INIT_SEQ_DATA_6);
- break;
- case 8:
- tegra_dsi_writel(dsi, val, DSI_INIT_SEQ_DATA_7);
- break;
- default:
- err = 1;
- break;
+ }
+ }
+ return crc;
+}
+
+static int tegra_dsi_dcs_pkt_seq_ctrl_init(struct tegra_dc_dsi_data *dsi,
+ struct tegra_dsi_cmd *cmd)
+{
+ u8 virtual_channel;
+ u32 val;
+ u16 data_len = cmd->sp_len_dly.data_len;
+ u8 seq_ctrl_reg = 0;
+
+ virtual_channel = dsi->info.virtual_channel <<
+ DSI_VIR_CHANNEL_BIT_POSITION;
+
+ val = (virtual_channel | cmd->data_id) << 0 |
+ data_len << 8;
+
+ val |= tegra_dsi_ecc(val) << 24;
+
+ tegra_dsi_writel(dsi, val, DSI_INIT_SEQ_DATA_0 + seq_ctrl_reg++);
+
+ /* if pdata != NULL, pkt type is long pkt */
+ if (cmd->pdata != NULL) {
+ u8 *pdata;
+ u8 *pdata_mem;
+ /* allocate memory for pdata + 2 bytes checksum */
+ pdata_mem = kzalloc(sizeof(u8) * data_len + 2, GFP_KERNEL);
+ if (!pdata_mem) {
+ dev_err(&dsi->dc->ndev->dev, "dsi: memory err\n");
+ tegra_dsi_soft_reset(dsi);
+ return -ENOMEM;
+ }
+
+ memcpy(pdata_mem, cmd->pdata, data_len);
+ pdata = pdata_mem;
+ *((u16 *)(pdata + data_len)) = tegra_dsi_cs(pdata, data_len);
+
+ /* data_len = length of pdata + 2 byte checksum */
+ data_len += 2;
+
+ while (data_len) {
+ if (data_len >= 4) {
+ val = ((u32 *) pdata)[0];
+ data_len -= 4;
+ pdata += 4;
+ } else {
+ val = 0;
+ memcpy(&val, pdata, data_len);
+ pdata += data_len;
+ data_len = 0;
}
+ tegra_dsi_writel(dsi, val, DSI_INIT_SEQ_DATA_0 +
+ seq_ctrl_reg++);
}
+ kfree(pdata_mem);
+ }
+
+ return 0;
+}
+
+int tegra_dsi_start_host_cmd_v_blank_dcs(struct tegra_dc_dsi_data * dsi,
+ struct tegra_dsi_cmd *cmd)
+{
+#define PKT_HEADER_LEN_BYTE 4
+#define CHECKSUM_LEN_BYTE 2
+
+ int err = 0;
+ u32 val;
+ u16 tot_pkt_len = PKT_HEADER_LEN_BYTE;
+ struct tegra_dc *dc = dsi->dc;
+
+ if (cmd->cmd_type != TEGRA_DSI_PACKET_CMD)
+ return -EINVAL;
+
+ mutex_lock(&dsi->lock);
+ tegra_dc_io_start(dc);
+ tegra_dc_dsi_hold_host(dc);
+
+
+ err = tegra_dsi_dcs_pkt_seq_ctrl_init(dsi, cmd);
+ if (err < 0) {
+ dev_err(&dsi->dc->ndev->dev,
+ "dsi: dcs pkt seq ctrl init failed\n");
+ goto fail;
}
- val = DSI_INIT_SEQ_CONTROL_DSI_FRAME_INIT_BYTE_COUNT(data_len_orig * 2)
- | DSI_INIT_SEQ_CONTROL_DSI_SEND_INIT_SEQUENCE(1);
+ if (cmd->pdata) {
+ u16 data_len = cmd->sp_len_dly.data_len;
+ tot_pkt_len += data_len + CHECKSUM_LEN_BYTE;
+ }
+
+ val = DSI_INIT_SEQ_CONTROL_DSI_FRAME_INIT_BYTE_COUNT(tot_pkt_len) |
+ DSI_INIT_SEQ_CONTROL_DSI_SEND_INIT_SEQUENCE(
+ TEGRA_DSI_ENABLE);
tegra_dsi_writel(dsi, val, DSI_INIT_SEQ_CONTROL);
+fail:
+ tegra_dc_dsi_release_host(dc);
+ tegra_dc_io_end(dc);
+ mutex_unlock(&dsi->lock);
return err;
+
+#undef PKT_HEADER_LEN_BYTE
+#undef CHECKSUM_LEN_BYTE
}
-EXPORT_SYMBOL(tegra_dsi_send_panel_short_cmd);
+EXPORT_SYMBOL(tegra_dsi_start_host_cmd_v_blank_dcs);
+
+void tegra_dsi_stop_host_cmd_v_blank_dcs(struct tegra_dc_dsi_data * dsi)
+{
+ struct tegra_dc *dc = dsi->dc;
+ u32 cnt;
+
+ mutex_lock(&dsi->lock);
+ tegra_dc_io_start(dc);
+ tegra_dc_dsi_hold_host(dc);
+
+ tegra_dsi_writel(dsi, TEGRA_DSI_DISABLE, DSI_INIT_SEQ_CONTROL);
+
+ /* clear seq data registers */
+ for (cnt = 0; cnt < 8; cnt++)
+ tegra_dsi_writel(dsi, 0, DSI_INIT_SEQ_DATA_0 + cnt);
+
+ tegra_dc_dsi_release_host(dc);
+ tegra_dc_io_end(dc);
+
+ mutex_unlock(&dsi->lock);
+}
+EXPORT_SYMBOL(tegra_dsi_stop_host_cmd_v_blank_dcs);
static int tegra_dsi_bta(struct tegra_dc_dsi_data *dsi)
{
tegra_dsi_writel(dsi, val, DSI_HOST_DSI_CONTROL);
#if DSI_USE_SYNC_POINTS
- /* FIXME: Workaround for nvhost_syncpt_read */
- dsi->syncpt_val = nvhost_syncpt_update_min(
- &nvhost_get_host(dsi->dc->ndev)->syncpt,
+ dsi->syncpt_val = nvhost_syncpt_read_ext(dsi->dc->ndev,
dsi->syncpt_id);
val = DSI_INCR_SYNCPT_COND(OP_DONE) |
tegra_dsi_writel(dsi, val, DSI_INCR_SYNCPT);
/* TODO: Use interrupt rather than polling */
- err = nvhost_syncpt_wait(&nvhost_get_host(dsi->dc->ndev)->syncpt,
- dsi->syncpt_id, dsi->syncpt_val + 1);
+ err = nvhost_syncpt_wait_timeout_ext(dsi->dc->ndev, dsi->syncpt_id,
+ dsi->syncpt_val + 1, MAX_SCHEDULE_TIMEOUT, NULL);
if (err < 0)
dev_err(&dsi->dc->ndev->dev,
"DSI sync point failure\n");
int err = 0;
struct dsi_status *init_status;
+ mutex_lock(&dsi->lock);
tegra_dc_io_start(dc);
init_status = tegra_dsi_prepare_host_transmission(
if (err < 0)
dev_err(&dc->ndev->dev, "Failed to restore prev state\n");
tegra_dc_io_end(dc);
+ mutex_unlock(&dsi->lock);
return err;
}
EXPORT_SYMBOL(tegra_dsi_read_data);
}
+static void tegra_dsi_send_dc_frames(struct tegra_dc *dc,
+ struct tegra_dc_dsi_data *dsi,
+ int no_of_frames)
+{
+ int err;
+ u32 frame_period = DIV_ROUND_UP(S_TO_MS(1), dsi->info.refresh_rate);
+ u8 lp_op = dsi->status.lp_op;
+ bool switch_to_lp = (dsi->status.lphs == DSI_LPHS_IN_LP_MODE);
+
+ if (dsi->status.lphs != DSI_LPHS_IN_HS_MODE) {
+ err = tegra_dsi_set_to_hs_mode(dc, dsi,
+ TEGRA_DSI_DRIVEN_BY_DC);
+ if (err < 0) {
+ dev_err(&dc->ndev->dev,
+ "Switch to HS host mode failed\n");
+ return;
+ }
+ }
+
+ /*
+ * Some panels need DC frames be sent under certain
+ * conditions. We are working on the right fix for this
+ * requirement, while using this current fix.
+ */
+ tegra_dsi_start_dc_stream(dc, dsi);
+
+ /*
+ * Send frames in Continuous or One-shot mode.
+ */
+ if (dc->out->flags & TEGRA_DC_OUT_ONE_SHOT_MODE) {
+ while (no_of_frames--) {
+ tegra_dc_writel(dc, GENERAL_ACT_REQ | NC_HOST_TRIG,
+ DC_CMD_STATE_CONTROL);
+ mdelay(frame_period);
+ }
+ } else
+ mdelay(no_of_frames * frame_period);
+
+ tegra_dsi_stop_dc_stream_at_frame_end(dc, dsi);
+
+ if (switch_to_lp) {
+ err = tegra_dsi_set_to_lp_mode(dc, dsi, lp_op);
+ if (err < 0)
+ dev_err(&dc->ndev->dev,
+ "DSI failed to go to LP mode\n");
+ }
+}
+
static void tegra_dc_dsi_enable(struct tegra_dc *dc)
{
struct tegra_dc_dsi_data *dsi = tegra_dc_get_outdata(dc);
int err;
u32 val;
- tegra_dc_io_start(dc);
mutex_lock(&dsi->lock);
+ tegra_dc_io_start(dc);
+ tegra_dc_dsi_hold_host(dc);
/* Stop DC stream before configuring DSI registers
* to avoid visible glitches on panel during transition
}
if (dsi->info.panel_reset) {
+ /*
+ * Certain panels need dc frames be sent before
+ * waking panel.
+ */
+ if (dsi->info.panel_send_dc_frames)
+ tegra_dsi_send_dc_frames(dc, dsi, 2);
+
err = tegra_dsi_send_panel_cmd(dc, dsi,
dsi->info.dsi_init_cmd,
dsi->info.n_init_cmd);
}
}
+ /*
+ * Certain panels need dc frames be sent before
+ * waking panel.
+ */
+ if (dsi->info.panel_send_dc_frames)
+ tegra_dsi_send_dc_frames(dc, dsi, 2);
+
err = tegra_dsi_set_to_lp_mode(dc, dsi, DSI_LP_OP_WRITE);
if (err < 0) {
dev_err(&dc->ndev->dev,
goto fail;
}
- err = tegra_dsi_set_to_hs_mode(dc, dsi);
+ err = tegra_dsi_set_to_hs_mode(dc, dsi,
+ TEGRA_DSI_DRIVEN_BY_DC);
if (err < 0) {
dev_err(&dc->ndev->dev,
"dsi: not able to set to hs mode\n");
if (dsi->status.driven == DSI_DRIVEN_MODE_DC)
tegra_dsi_start_dc_stream(dc, dsi);
fail:
- mutex_unlock(&dsi->lock);
+ tegra_dc_dsi_release_host(dc);
tegra_dc_io_end(dc);
+ mutex_unlock(&dsi->lock);
}
static void _tegra_dc_dsi_init(struct tegra_dc *dc)
struct tegra_dsi_out *p_dsi)
{
struct tegra_dsi_cmd *p_init_cmd;
- struct tegra_dsi_cmd *p_early_suspend_cmd;
- struct tegra_dsi_cmd *p_late_resume_cmd;
+ struct tegra_dsi_cmd *p_early_suspend_cmd = NULL;
+ struct tegra_dsi_cmd *p_late_resume_cmd = NULL;
struct tegra_dsi_cmd *p_suspend_cmd;
int err;
kfree(dsi);
}
-static int tegra_dsi_deep_sleep(struct tegra_dc *dc,
- struct tegra_dc_dsi_data *dsi)
+static void tegra_dsi_config_phy_clk(struct tegra_dc_dsi_data *dsi,
+ u32 settings)
{
- int err = 0;
- int val;
struct clk *parent_clk = NULL;
struct clk *base_clk = NULL;
+ /* Disable dsi fast and slow clock */
+ parent_clk = clk_get_parent(dsi->dsi_clk);
+ base_clk = clk_get_parent(parent_clk);
+ if (dsi->info.dsi_instance)
+ tegra_clk_cfg_ex(base_clk,
+ TEGRA_CLK_PLLD_CSI_OUT_ENB,
+ settings);
+ else
+ tegra_clk_cfg_ex(base_clk,
+ TEGRA_CLK_PLLD_DSI_OUT_ENB,
+ settings);
+}
+
+static int tegra_dsi_deep_sleep(struct tegra_dc *dc,
+ struct tegra_dc_dsi_data *dsi, u32 suspend_aggr)
+{
+ int val = 0;
+ int err = 0;
+
if (!dsi->enabled) {
err = -EPERM;
goto fail;
}
- err = tegra_dsi_set_to_lp_mode(dc, dsi, DSI_LP_OP_WRITE);
- if (err < 0) {
- dev_err(&dc->ndev->dev,
- "DSI failed to go to LP mode\n");
- goto fail;
- }
+ switch (suspend_aggr) {
+ case DSI_SUSPEND_FULL:
+ /* Suspend DSI panel */
+ err = tegra_dsi_set_to_lp_mode(dc, dsi, DSI_LP_OP_WRITE);
+ if (err < 0) {
+ dev_err(&dc->ndev->dev,
+ "DSI failed to go to LP mode\n");
+ goto fail;
+ }
- /* Suspend panel */
- err = tegra_dsi_send_panel_cmd(dc, dsi,
- dsi->info.dsi_suspend_cmd,
- dsi->info.n_suspend_cmd);
- if (err < 0) {
- dev_err(&dc->ndev->dev,
- "dsi: Error sending suspend cmd\n");
- goto fail;
- }
+ err = tegra_dsi_send_panel_cmd(dc, dsi,
+ dsi->info.dsi_suspend_cmd,
+ dsi->info.n_suspend_cmd);
+ /*
+ * Certain panels need dc frames be sent after
+ * putting panel to sleep.
+ */
+ if (dsi->info.panel_send_dc_frames)
+ tegra_dsi_send_dc_frames(dc, dsi, 2);
- if (!dsi->ulpm) {
- err = tegra_dsi_enter_ulpm(dsi);
if (err < 0) {
dev_err(&dc->ndev->dev,
- "DSI failed to enter ulpm\n");
+ "dsi: Error sending suspend cmd\n");
goto fail;
}
+ case DSI_HOST_SUSPEND_LV2:
+ /* Set DSI to ULPM and suspend pads. DSI will be set to the
+ * lowest power state in this level. */
+ if (!dsi->ulpm) {
+ err = tegra_dsi_enter_ulpm(dsi);
+ if (err < 0) {
+ dev_err(&dc->ndev->dev,
+ "DSI failed to enter ulpm\n");
+ goto fail;
+ }
+ }
+
+ val = DSI_PAD_CONTROL_PAD_PDIO(0x3) |
+ DSI_PAD_CONTROL_PAD_PDIO_CLK(0x1) |
+ DSI_PAD_CONTROL_PAD_PULLDN_ENAB(TEGRA_DSI_ENABLE);
+ tegra_dsi_writel(dsi, val, DSI_PAD_CONTROL);
+
+ /* Suspend core-logic */
+ val = DSI_POWER_CONTROL_LEG_DSI_ENABLE(TEGRA_DSI_DISABLE);
+ tegra_dsi_writel(dsi, val, DSI_POWER_CONTROL);
+ case DSI_HOST_SUSPEND_LV1:
+ /* Disable dsi fast and slow clock */
+ tegra_dsi_config_phy_clk(dsi, TEGRA_DSI_DISABLE);
+ case DSI_HOST_SUSPEND_LV0:
+ /* Disable dsi source clock */
+ tegra_dsi_clk_disable(dsi);
+ break;
+ case DSI_NO_SUSPEND:
+ break;
+ default:
+ dev_err(&dc->ndev->dev, "DSI suspend aggressiveness"
+ "is not supported.\n");
}
- /*
- * Suspend pad
- * It is ok to overwrite previous value of DSI_PAD_CONTROL reg
- * because it will be restored properly in resume sequence
- */
- val = DSI_PAD_CONTROL_PAD_PDIO(0x3) |
- DSI_PAD_CONTROL_PAD_PDIO_CLK(0x1) |
- DSI_PAD_CONTROL_PAD_PULLDN_ENAB(TEGRA_DSI_ENABLE);
- tegra_dsi_writel(dsi, val, DSI_PAD_CONTROL);
+ dsi->enabled = false;
- /* Suspend core-logic */
- val = DSI_POWER_CONTROL_LEG_DSI_ENABLE(TEGRA_DSI_DISABLE);
- tegra_dsi_writel(dsi, val, DSI_POWER_CONTROL);
+ return 0;
+fail:
+ return err;
+}
- /* Disable dsi fast and slow clock */
- parent_clk = clk_get_parent(dsi->dsi_clk);
- base_clk = clk_get_parent(parent_clk);
- if (dsi->info.dsi_instance)
- tegra_clk_cfg_ex(base_clk,
- TEGRA_CLK_PLLD_CSI_OUT_ENB,
- 0);
- else
- tegra_clk_cfg_ex(base_clk,
- TEGRA_CLK_PLLD_DSI_OUT_ENB,
- 0);
+static int tegra_dsi_host_suspend(struct tegra_dc *dc)
+{
+ int err = 0;
+ struct tegra_dc_dsi_data *dsi = tegra_dc_get_outdata(dc);
- /* Disable dsi source clock */
- clk_disable(dsi->dsi_clk);
- clk_disable(dsi->dsi_fixed_clk);
+ if (dsi->host_suspended)
+ return 0;
- dsi->clk_ref = false;
- dsi->enabled = false;
+ dsi->host_suspended = true;
- return 0;
+ tegra_dsi_stop_dc_stream(dc, dsi);
+
+ err = tegra_dsi_deep_sleep(dc, dsi, dsi->info.suspend_aggr);
+ if (err < 0)
+ dev_err(&dc->ndev->dev,
+ "DSI failed to enter deep sleep\n");
+
+ tegra_dc_clk_disable(dc);
+
+ return err;
+}
+
+static int tegra_dsi_host_resume(struct tegra_dc *dc)
+{
+ int val = 0;
+ int err = 0;
+ struct tegra_dc_dsi_data *dsi = tegra_dc_get_outdata(dc);
+
+ if (!dsi->host_suspended)
+ return 0;
+
+ tegra_dc_clk_enable(dc);
+ switch (dsi->info.suspend_aggr) {
+ case DSI_HOST_SUSPEND_LV0:
+ tegra_dsi_clk_enable(dsi);
+ break;
+ case DSI_HOST_SUSPEND_LV1:
+ tegra_dsi_config_phy_clk(dsi, TEGRA_DSI_ENABLE);
+ tegra_dsi_clk_enable(dsi);
+ break;
+ case DSI_HOST_SUSPEND_LV2:
+ tegra_dsi_config_phy_clk(dsi, TEGRA_DSI_ENABLE);
+ tegra_dsi_clk_enable(dsi);
+
+ tegra_dsi_writel(dsi,
+ DSI_POWER_CONTROL_LEG_DSI_ENABLE(TEGRA_DSI_ENABLE),
+ DSI_POWER_CONTROL);
+
+ if (dsi->ulpm) {
+ err = tegra_dsi_enter_ulpm(dsi);
+ if (err < 0) {
+ dev_err(&dc->ndev->dev,
+ "DSI failed to enter ulpm\n");
+ goto fail;
+ }
+
+ val = tegra_dsi_readl(dsi, DSI_PAD_CONTROL);
+ val &= ~(DSI_PAD_CONTROL_PAD_PDIO(0x3) |
+ DSI_PAD_CONTROL_PAD_PDIO_CLK(0x1) |
+ DSI_PAD_CONTROL_PAD_PULLDN_ENAB(0x1));
+ tegra_dsi_writel(dsi, val, DSI_PAD_CONTROL);
+
+ if (tegra_dsi_exit_ulpm(dsi) < 0) {
+ dev_err(&dc->ndev->dev,
+ "DSI failed to exit ulpm\n");
+ goto fail;
+ }
+ }
+ break;
+ case DSI_NO_SUSPEND:
+ break;
+ default:
+ dev_err(&dc->ndev->dev, "DSI suspend aggressivenes"
+ "is not supported.\n");
+ }
+
+ tegra_dsi_start_dc_stream(dc, dsi);
+ dsi->enabled = true;
+ dsi->host_suspended = false;
fail:
return err;
}
int err;
struct tegra_dc_dsi_data *dsi = tegra_dc_get_outdata(dc);
- tegra_dc_io_start(dc);
mutex_lock(&dsi->lock);
+ tegra_dc_io_start(dc);
if (dsi->status.dc_stream == DSI_DC_STREAM_ENABLE)
tegra_dsi_stop_dc_stream_at_frame_end(dc, dsi);
if (dsi->info.power_saving_suspend) {
- if (tegra_dsi_deep_sleep(dc, dsi) < 0) {
+ if (tegra_dsi_deep_sleep(dc, dsi, DSI_SUSPEND_FULL) < 0) {
dev_err(&dc->ndev->dev,
"DSI failed to enter deep sleep\n");
goto fail;
}
}
}
-
fail:
mutex_unlock(&dsi->lock);
tegra_dc_io_end(dc);
}
}
- if (tegra_dsi_deep_sleep(dc, dsi) < 0) {
+ if (tegra_dsi_deep_sleep(dc, dsi, DSI_SUSPEND_FULL) < 0) {
dev_err(&dc->ndev->dev,
"DSI failed to enter deep sleep\n");
goto fail;
.destroy = tegra_dc_dsi_destroy,
.enable = tegra_dc_dsi_enable,
.disable = tegra_dc_dsi_disable,
+ .hold = tegra_dc_dsi_hold_host,
+ .release = tegra_dc_dsi_release_host,
+ .idle = tegra_dc_dsi_idle,
#ifdef CONFIG_PM
.suspend = tegra_dc_dsi_suspend,
.resume = tegra_dc_dsi_resume,