2946 lines
78 KiB
C
2946 lines
78 KiB
C
/* Copyright (c) 2012-2015, The Linux Foundation. All rights reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 and
|
|
* only version 2 as published by the Free Software Foundation.
|
|
*
|
|
* 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/module.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/io.h>
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/iopoll.h>
|
|
#include <linux/kthread.h>
|
|
|
|
#include <linux/msm-bus.h>
|
|
|
|
#include "mdss.h"
|
|
#include "mdss_dsi.h"
|
|
#include "mdss_panel.h"
|
|
#include "mdss_debug.h"
|
|
#include "mdss_smmu.h"
|
|
#include "mdss_dsi_phy.h"
|
|
|
|
#define VSYNC_PERIOD 17
|
|
#define DMA_TX_TIMEOUT 200
|
|
#define DMA_TPG_FIFO_LEN 64
|
|
|
|
struct mdss_dsi_ctrl_pdata *ctrl_list[DSI_CTRL_MAX];
|
|
|
|
struct mdss_hw mdss_dsi0_hw = {
|
|
.hw_ndx = MDSS_HW_DSI0,
|
|
.ptr = NULL,
|
|
.irq_handler = mdss_dsi_isr,
|
|
};
|
|
|
|
struct mdss_hw mdss_dsi1_hw = {
|
|
.hw_ndx = MDSS_HW_DSI1,
|
|
.ptr = NULL,
|
|
.irq_handler = mdss_dsi_isr,
|
|
};
|
|
|
|
|
|
#define DSI_EVENT_Q_MAX 4
|
|
|
|
#define DSI_BTA_EVENT_TIMEOUT (HZ / 10)
|
|
|
|
/* Mutex common for both the controllers */
|
|
static struct mutex dsi_mtx;
|
|
|
|
/* event */
|
|
struct dsi_event_q {
|
|
struct mdss_dsi_ctrl_pdata *ctrl;
|
|
u32 arg;
|
|
u32 todo;
|
|
};
|
|
|
|
struct mdss_dsi_event {
|
|
int inited;
|
|
wait_queue_head_t event_q;
|
|
u32 event_pndx;
|
|
u32 event_gndx;
|
|
struct dsi_event_q todo_list[DSI_EVENT_Q_MAX];
|
|
spinlock_t event_lock;
|
|
};
|
|
|
|
static struct mdss_dsi_event dsi_event;
|
|
|
|
static int dsi_event_thread(void *data);
|
|
|
|
void mdss_dsi_ctrl_init(struct device *ctrl_dev,
|
|
struct mdss_dsi_ctrl_pdata *ctrl)
|
|
{
|
|
if (ctrl->panel_data.panel_info.pdest == DISPLAY_1) {
|
|
mdss_dsi0_hw.ptr = (void *)(ctrl);
|
|
ctrl->dsi_hw = &mdss_dsi0_hw;
|
|
ctrl->ndx = DSI_CTRL_0;
|
|
} else {
|
|
mdss_dsi1_hw.ptr = (void *)(ctrl);
|
|
ctrl->dsi_hw = &mdss_dsi1_hw;
|
|
ctrl->ndx = DSI_CTRL_1;
|
|
}
|
|
|
|
if (!(ctrl->dsi_irq_line))
|
|
ctrl->dsi_hw->irq_info = mdss_intr_line();
|
|
|
|
ctrl->panel_mode = ctrl->panel_data.panel_info.mipi.mode;
|
|
|
|
ctrl_list[ctrl->ndx] = ctrl; /* keep it */
|
|
|
|
if (ctrl->mdss_util->register_irq(ctrl->dsi_hw))
|
|
pr_err("%s: mdss_register_irq failed.\n", __func__);
|
|
|
|
pr_debug("%s: ndx=%d base=%p\n", __func__, ctrl->ndx, ctrl->ctrl_base);
|
|
|
|
init_completion(&ctrl->dma_comp);
|
|
init_completion(&ctrl->mdp_comp);
|
|
init_completion(&ctrl->video_comp);
|
|
init_completion(&ctrl->dynamic_comp);
|
|
init_completion(&ctrl->bta_comp);
|
|
spin_lock_init(&ctrl->irq_lock);
|
|
spin_lock_init(&ctrl->mdp_lock);
|
|
mutex_init(&ctrl->mutex);
|
|
mutex_init(&ctrl->cmd_mutex);
|
|
mutex_init(&ctrl->clk_lane_mutex);
|
|
mutex_init(&ctrl->cmdlist_mutex);
|
|
mdss_dsi_buf_alloc(ctrl_dev, &ctrl->tx_buf, SZ_4K);
|
|
mdss_dsi_buf_alloc(ctrl_dev, &ctrl->rx_buf, SZ_4K);
|
|
mdss_dsi_buf_alloc(ctrl_dev, &ctrl->status_buf, SZ_4K);
|
|
ctrl->cmdlist_commit = mdss_dsi_cmdlist_commit;
|
|
ctrl->err_cont.err_time_delta = 100;
|
|
ctrl->err_cont.max_err_index = MAX_ERR_INDEX;
|
|
|
|
if (dsi_event.inited == 0) {
|
|
kthread_run(dsi_event_thread, (void *)&dsi_event,
|
|
"mdss_dsi_event");
|
|
mutex_init(&dsi_mtx);
|
|
dsi_event.inited = 1;
|
|
}
|
|
}
|
|
|
|
static void mdss_dsi_set_reg(struct mdss_dsi_ctrl_pdata *ctrl, int off,
|
|
u32 mask, u32 val)
|
|
{
|
|
u32 data;
|
|
|
|
off &= ~0x03;
|
|
val &= mask; /* set bits indicated at mask only */
|
|
data = MIPI_INP(ctrl->ctrl_base + off);
|
|
data &= ~mask;
|
|
data |= val;
|
|
pr_debug("%s: ndx=%d off=%x data=%x\n", __func__,
|
|
ctrl->ndx, off, data);
|
|
MIPI_OUTP(ctrl->ctrl_base + off, data);
|
|
}
|
|
|
|
void mdss_dsi_clk_req(struct mdss_dsi_ctrl_pdata *ctrl,
|
|
struct dsi_panel_clk_ctrl *clk_ctrl)
|
|
{
|
|
enum dsi_clk_req_client client = clk_ctrl->client;
|
|
int enable = clk_ctrl->state;
|
|
void *clk_handle = ctrl->mdp_clk_handle;
|
|
|
|
if (clk_ctrl->client == DSI_CLK_REQ_DSI_CLIENT)
|
|
clk_handle = ctrl->dsi_clk_handle;
|
|
|
|
MDSS_XLOG(ctrl->ndx, enable, ctrl->mdp_busy, current->pid,
|
|
client);
|
|
if (enable == 0) {
|
|
/* need wait before disable */
|
|
mutex_lock(&ctrl->cmd_mutex);
|
|
mdss_dsi_cmd_mdp_busy(ctrl);
|
|
mutex_unlock(&ctrl->cmd_mutex);
|
|
}
|
|
|
|
MDSS_XLOG(ctrl->ndx, enable, ctrl->mdp_busy, current->pid,
|
|
client);
|
|
mdss_dsi_clk_ctrl(ctrl, clk_handle,
|
|
MDSS_DSI_ALL_CLKS, enable);
|
|
}
|
|
|
|
void mdss_dsi_pll_relock(struct mdss_dsi_ctrl_pdata *ctrl)
|
|
{
|
|
int rc;
|
|
|
|
/*
|
|
* todo: this code does not work very well with dual
|
|
* dsi use cases. Need to fix this eventually.
|
|
*/
|
|
|
|
rc = mdss_dsi_clk_force_toggle(ctrl->dsi_clk_handle, MDSS_DSI_LINK_CLK);
|
|
if (rc)
|
|
pr_err("clock toggle failed, rc = %d\n", rc);
|
|
}
|
|
|
|
void mdss_dsi_enable_irq(struct mdss_dsi_ctrl_pdata *ctrl, u32 term)
|
|
{
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&ctrl->irq_lock, flags);
|
|
if (ctrl->dsi_irq_mask & term) {
|
|
spin_unlock_irqrestore(&ctrl->irq_lock, flags);
|
|
return;
|
|
}
|
|
if (ctrl->dsi_irq_mask == 0) {
|
|
MDSS_XLOG(ctrl->ndx, term);
|
|
ctrl->mdss_util->enable_irq(ctrl->dsi_hw);
|
|
pr_debug("%s: IRQ Enable, ndx=%d mask=%x term=%x\n", __func__,
|
|
ctrl->ndx, (int)ctrl->dsi_irq_mask, (int)term);
|
|
}
|
|
ctrl->dsi_irq_mask |= term;
|
|
spin_unlock_irqrestore(&ctrl->irq_lock, flags);
|
|
}
|
|
|
|
void mdss_dsi_disable_irq(struct mdss_dsi_ctrl_pdata *ctrl, u32 term)
|
|
{
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&ctrl->irq_lock, flags);
|
|
if (!(ctrl->dsi_irq_mask & term)) {
|
|
spin_unlock_irqrestore(&ctrl->irq_lock, flags);
|
|
return;
|
|
}
|
|
ctrl->dsi_irq_mask &= ~term;
|
|
if (ctrl->dsi_irq_mask == 0) {
|
|
MDSS_XLOG(ctrl->ndx, term);
|
|
ctrl->mdss_util->disable_irq(ctrl->dsi_hw);
|
|
pr_debug("%s: IRQ Disable, ndx=%d mask=%x term=%x\n", __func__,
|
|
ctrl->ndx, (int)ctrl->dsi_irq_mask, (int)term);
|
|
}
|
|
spin_unlock_irqrestore(&ctrl->irq_lock, flags);
|
|
}
|
|
|
|
/*
|
|
* mdss_dsi_disale_irq_nosync() should be called
|
|
* from interrupt context
|
|
*/
|
|
void mdss_dsi_disable_irq_nosync(struct mdss_dsi_ctrl_pdata *ctrl, u32 term)
|
|
{
|
|
spin_lock(&ctrl->irq_lock);
|
|
if (!(ctrl->dsi_irq_mask & term)) {
|
|
spin_unlock(&ctrl->irq_lock);
|
|
return;
|
|
}
|
|
ctrl->dsi_irq_mask &= ~term;
|
|
if (ctrl->dsi_irq_mask == 0) {
|
|
MDSS_XLOG(ctrl->ndx, term);
|
|
ctrl->mdss_util->disable_irq_nosync(ctrl->dsi_hw);
|
|
pr_debug("%s: IRQ Disable, ndx=%d mask=%x term=%x\n", __func__,
|
|
ctrl->ndx, (int)ctrl->dsi_irq_mask, (int)term);
|
|
}
|
|
spin_unlock(&ctrl->irq_lock);
|
|
}
|
|
|
|
void mdss_dsi_video_test_pattern(struct mdss_dsi_ctrl_pdata *ctrl)
|
|
{
|
|
int i;
|
|
|
|
MIPI_OUTP((ctrl->ctrl_base) + 0x015c, 0x021);
|
|
MIPI_OUTP((ctrl->ctrl_base) + 0x0164, 0xff0000); /* red */
|
|
i = 0;
|
|
while (i++ < 50) {
|
|
MIPI_OUTP((ctrl->ctrl_base) + 0x0180, 0x1);
|
|
/* Add sleep to get ~50 fps frame rate*/
|
|
msleep(20);
|
|
}
|
|
MIPI_OUTP((ctrl->ctrl_base) + 0x015c, 0x0);
|
|
}
|
|
|
|
void mdss_dsi_cmd_test_pattern(struct mdss_dsi_ctrl_pdata *ctrl)
|
|
{
|
|
int i;
|
|
|
|
MIPI_OUTP((ctrl->ctrl_base) + 0x015c, 0x201);
|
|
MIPI_OUTP((ctrl->ctrl_base) + 0x016c, 0xff0000); /* red */
|
|
i = 0;
|
|
while (i++ < 50) {
|
|
MIPI_OUTP((ctrl->ctrl_base) + 0x0184, 0x1);
|
|
/* Add sleep to get ~50 fps frame rate*/
|
|
msleep(20);
|
|
}
|
|
MIPI_OUTP((ctrl->ctrl_base) + 0x015c, 0x0);
|
|
}
|
|
|
|
void mdss_dsi_read_hw_revision(struct mdss_dsi_ctrl_pdata *ctrl)
|
|
{
|
|
/* clock must be on */
|
|
ctrl->shared_data->hw_rev = MIPI_INP(ctrl->ctrl_base);
|
|
}
|
|
|
|
void mdss_dsi_get_hw_revision(struct mdss_dsi_ctrl_pdata *ctrl)
|
|
{
|
|
if (ctrl->shared_data->hw_rev)
|
|
return;
|
|
|
|
mdss_dsi_clk_ctrl(ctrl, ctrl->dsi_clk_handle, MDSS_DSI_CORE_CLK,
|
|
MDSS_DSI_CLK_ON);
|
|
ctrl->shared_data->hw_rev = MIPI_INP(ctrl->ctrl_base);
|
|
mdss_dsi_clk_ctrl(ctrl, ctrl->dsi_clk_handle, MDSS_DSI_CORE_CLK,
|
|
MDSS_DSI_CLK_OFF);
|
|
|
|
pr_debug("%s: ndx=%d hw_rev=%x\n", __func__,
|
|
ctrl->ndx, ctrl->shared_data->hw_rev);
|
|
}
|
|
|
|
void mdss_dsi_read_phy_revision(struct mdss_dsi_ctrl_pdata *ctrl)
|
|
{
|
|
u32 reg_val;
|
|
|
|
if (ctrl->shared_data->phy_rev > DSI_PHY_REV_UNKNOWN)
|
|
return;
|
|
|
|
reg_val = MIPI_INP(ctrl->phy_io.base);
|
|
|
|
if (reg_val == DSI_PHY_REV_20)
|
|
ctrl->shared_data->phy_rev = DSI_PHY_REV_20;
|
|
else if (reg_val == DSI_PHY_REV_10)
|
|
ctrl->shared_data->phy_rev = DSI_PHY_REV_10;
|
|
else
|
|
ctrl->shared_data->phy_rev = DSI_PHY_REV_UNKNOWN;
|
|
}
|
|
|
|
void mdss_dsi_host_init(struct mdss_panel_data *pdata)
|
|
{
|
|
u32 dsi_ctrl, intr_ctrl;
|
|
u32 data;
|
|
struct mdss_dsi_ctrl_pdata *ctrl_pdata = NULL;
|
|
struct mipi_panel_info *pinfo = NULL;
|
|
|
|
if (pdata == NULL) {
|
|
pr_err("%s: Invalid input data\n", __func__);
|
|
return;
|
|
}
|
|
|
|
ctrl_pdata = container_of(pdata, struct mdss_dsi_ctrl_pdata,
|
|
panel_data);
|
|
|
|
pinfo = &pdata->panel_info.mipi;
|
|
|
|
if (pinfo->mode == DSI_VIDEO_MODE) {
|
|
data = 0;
|
|
if (pinfo->last_line_interleave_en)
|
|
data |= BIT(31);
|
|
if (pinfo->pulse_mode_hsa_he)
|
|
data |= BIT(28);
|
|
if (pinfo->hfp_power_stop)
|
|
data |= BIT(24);
|
|
if (pinfo->hbp_power_stop)
|
|
data |= BIT(20);
|
|
if (pinfo->hsa_power_stop)
|
|
data |= BIT(16);
|
|
if (pinfo->eof_bllp_power_stop)
|
|
data |= BIT(15);
|
|
if (pinfo->bllp_power_stop)
|
|
data |= BIT(12);
|
|
data |= ((pinfo->traffic_mode & 0x03) << 8);
|
|
data |= ((pinfo->dst_format & 0x03) << 4); /* 2 bits */
|
|
data |= (pinfo->vc & 0x03);
|
|
MIPI_OUTP((ctrl_pdata->ctrl_base) + 0x0010, data);
|
|
|
|
data = 0;
|
|
data |= ((pinfo->rgb_swap & 0x07) << 12);
|
|
if (pinfo->b_sel)
|
|
data |= BIT(8);
|
|
if (pinfo->g_sel)
|
|
data |= BIT(4);
|
|
if (pinfo->r_sel)
|
|
data |= BIT(0);
|
|
MIPI_OUTP((ctrl_pdata->ctrl_base) + 0x0020, data);
|
|
} else if (pinfo->mode == DSI_CMD_MODE) {
|
|
data = 0;
|
|
data |= ((pinfo->interleave_max & 0x0f) << 20);
|
|
data |= ((pinfo->rgb_swap & 0x07) << 16);
|
|
if (pinfo->b_sel)
|
|
data |= BIT(12);
|
|
if (pinfo->g_sel)
|
|
data |= BIT(8);
|
|
if (pinfo->r_sel)
|
|
data |= BIT(4);
|
|
data |= (pinfo->dst_format & 0x0f); /* 4 bits */
|
|
MIPI_OUTP((ctrl_pdata->ctrl_base) + 0x0040, data);
|
|
|
|
/* DSI_COMMAND_MODE_MDP_DCS_CMD_CTRL */
|
|
data = pinfo->wr_mem_continue & 0x0ff;
|
|
data <<= 8;
|
|
data |= (pinfo->wr_mem_start & 0x0ff);
|
|
if (pinfo->insert_dcs_cmd)
|
|
data |= BIT(16);
|
|
MIPI_OUTP((ctrl_pdata->ctrl_base) + 0x0044, data);
|
|
} else
|
|
pr_err("%s: Unknown DSI mode=%d\n", __func__, pinfo->mode);
|
|
|
|
dsi_ctrl = BIT(8) | BIT(2); /* clock enable & cmd mode */
|
|
intr_ctrl = 0;
|
|
intr_ctrl = (DSI_INTR_CMD_DMA_DONE_MASK | DSI_INTR_CMD_MDP_DONE_MASK);
|
|
|
|
if (pinfo->crc_check)
|
|
dsi_ctrl |= BIT(24);
|
|
if (pinfo->ecc_check)
|
|
dsi_ctrl |= BIT(20);
|
|
if (pinfo->data_lane3)
|
|
dsi_ctrl |= BIT(7);
|
|
if (pinfo->data_lane2)
|
|
dsi_ctrl |= BIT(6);
|
|
if (pinfo->data_lane1)
|
|
dsi_ctrl |= BIT(5);
|
|
if (pinfo->data_lane0)
|
|
dsi_ctrl |= BIT(4);
|
|
|
|
|
|
data = 0;
|
|
if (pinfo->te_sel)
|
|
data |= BIT(31);
|
|
data |= pinfo->mdp_trigger << 4;/* cmd mdp trigger */
|
|
data |= pinfo->dma_trigger; /* cmd dma trigger */
|
|
data |= (pinfo->stream & 0x01) << 8;
|
|
MIPI_OUTP((ctrl_pdata->ctrl_base) + 0x0084,
|
|
data); /* DSI_TRIG_CTRL */
|
|
|
|
/* DSI_LAN_SWAP_CTRL */
|
|
MIPI_OUTP((ctrl_pdata->ctrl_base) + 0x00b0, ctrl_pdata->dlane_swap);
|
|
|
|
/* clock out ctrl */
|
|
data = pinfo->t_clk_post & 0x3f; /* 6 bits */
|
|
data <<= 8;
|
|
data |= pinfo->t_clk_pre & 0x3f; /* 6 bits */
|
|
/* DSI_CLKOUT_TIMING_CTRL */
|
|
MIPI_OUTP((ctrl_pdata->ctrl_base) + 0xc4, data);
|
|
|
|
data = 0;
|
|
if (pinfo->rx_eot_ignore)
|
|
data |= BIT(4);
|
|
if (pinfo->tx_eot_append)
|
|
data |= BIT(0);
|
|
MIPI_OUTP((ctrl_pdata->ctrl_base) + 0x00cc,
|
|
data); /* DSI_EOT_PACKET_CTRL */
|
|
/*
|
|
* DSI_HS_TIMER_CTRL -> timer resolution = 8 esc clk
|
|
* HS TX timeout - 16136 (0x3f08) esc clk
|
|
*/
|
|
MIPI_OUTP((ctrl_pdata->ctrl_base) + 0x00bc, 0x3fd08);
|
|
|
|
|
|
/* allow only ack-err-status to generate interrupt */
|
|
/* DSI_ERR_INT_MASK0 */
|
|
MIPI_OUTP((ctrl_pdata->ctrl_base) + 0x010c, 0x03f03fc0);
|
|
|
|
intr_ctrl |= DSI_INTR_ERROR_MASK;
|
|
MIPI_OUTP((ctrl_pdata->ctrl_base) + 0x0110,
|
|
intr_ctrl); /* DSI_INTL_CTRL */
|
|
|
|
/* turn esc, byte, dsi, pclk, sclk, hclk on */
|
|
MIPI_OUTP((ctrl_pdata->ctrl_base) + 0x11c,
|
|
0x23f); /* DSI_CLK_CTRL */
|
|
|
|
/* Reset DSI_LANE_CTRL */
|
|
if (!ctrl_pdata->mmss_clamp)
|
|
MIPI_OUTP((ctrl_pdata->ctrl_base) + 0x00ac, 0x0);
|
|
|
|
dsi_ctrl |= BIT(0); /* enable dsi */
|
|
MIPI_OUTP((ctrl_pdata->ctrl_base) + 0x0004, dsi_ctrl);
|
|
|
|
/* enable contention detection for receiving */
|
|
mdss_dsi_lp_cd_rx(ctrl_pdata);
|
|
|
|
wmb();
|
|
}
|
|
|
|
void mdss_dsi_set_tx_power_mode(int mode, struct mdss_panel_data *pdata)
|
|
{
|
|
struct mdss_dsi_ctrl_pdata *ctrl_pdata = NULL;
|
|
u32 data;
|
|
|
|
if (pdata == NULL) {
|
|
pr_err("%s: Invalid input data\n", __func__);
|
|
return;
|
|
}
|
|
|
|
ctrl_pdata = container_of(pdata, struct mdss_dsi_ctrl_pdata,
|
|
panel_data);
|
|
|
|
data = MIPI_INP((ctrl_pdata->ctrl_base) + 0x3c);
|
|
|
|
if (mode == 0)
|
|
data &= ~BIT(26);
|
|
else
|
|
data |= BIT(26);
|
|
|
|
MIPI_OUTP((ctrl_pdata->ctrl_base) + 0x3c, data);
|
|
}
|
|
|
|
void mdss_dsi_sw_reset(struct mdss_dsi_ctrl_pdata *ctrl, bool restore)
|
|
{
|
|
u32 data0;
|
|
unsigned long flag;
|
|
|
|
if (!ctrl) {
|
|
pr_err("%s: Invalid input data\n", __func__);
|
|
return;
|
|
}
|
|
|
|
data0 = MIPI_INP(ctrl->ctrl_base + 0x0004);
|
|
MIPI_OUTP(ctrl->ctrl_base + 0x0004, (data0 & ~BIT(0)));
|
|
/*
|
|
* dsi controller need to be disabled before
|
|
* clocks turned on
|
|
*/
|
|
wmb(); /* make sure dsi contoller is disabled */
|
|
|
|
/* turn esc, byte, dsi, pclk, sclk, hclk on */
|
|
MIPI_OUTP(ctrl->ctrl_base + 0x11c, 0x23f); /* DSI_CLK_CTRL */
|
|
wmb(); /* make sure clocks enabled */
|
|
|
|
/* dsi controller can only be reset while clocks are running */
|
|
MIPI_OUTP(ctrl->ctrl_base + 0x118, 0x01);
|
|
wmb(); /* make sure reset happen */
|
|
MIPI_OUTP(ctrl->ctrl_base + 0x118, 0x00);
|
|
wmb(); /* controller out of reset */
|
|
|
|
if (restore) {
|
|
MIPI_OUTP(ctrl->ctrl_base + 0x0004, data0);
|
|
wmb(); /* make sure dsi controller enabled again */
|
|
}
|
|
|
|
/* It is safe to clear mdp_busy as reset is happening */
|
|
spin_lock_irqsave(&ctrl->mdp_lock, flag);
|
|
ctrl->mdp_busy = false;
|
|
complete_all(&ctrl->mdp_comp);
|
|
spin_unlock_irqrestore(&ctrl->mdp_lock, flag);
|
|
}
|
|
|
|
static void mdss_dsi_cfg_lane_ctrl(struct mdss_dsi_ctrl_pdata *ctrl,
|
|
u32 bits, int set)
|
|
{
|
|
u32 data;
|
|
|
|
data = MIPI_INP(ctrl->ctrl_base + 0x00ac);
|
|
if (set)
|
|
data |= bits;
|
|
else
|
|
data &= ~bits;
|
|
MIPI_OUTP(ctrl->ctrl_base + 0x0ac, data);
|
|
}
|
|
|
|
|
|
static inline bool mdss_dsi_poll_clk_lane(struct mdss_dsi_ctrl_pdata *ctrl)
|
|
{
|
|
u32 clk = 0;
|
|
|
|
if (readl_poll_timeout(((ctrl->ctrl_base) + 0x00a8),
|
|
clk,
|
|
(clk & 0x0010),
|
|
10, 1000)) {
|
|
pr_err("%s: ndx=%d clk lane NOT stopped, clk=%x\n",
|
|
__func__, ctrl->ndx, clk);
|
|
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static void mdss_dsi_wait_clk_lane_to_stop(struct mdss_dsi_ctrl_pdata *ctrl)
|
|
{
|
|
if (mdss_dsi_poll_clk_lane(ctrl)) /* stopped */
|
|
return;
|
|
|
|
/* clk stuck at hs, start recovery process */
|
|
|
|
/* force clk lane tx stop -- bit 20 */
|
|
mdss_dsi_cfg_lane_ctrl(ctrl, BIT(20), 1);
|
|
|
|
if (mdss_dsi_poll_clk_lane(ctrl) == false)
|
|
pr_err("%s: clk lane recovery failed\n", __func__);
|
|
|
|
/* clear clk lane tx stop -- bit 20 */
|
|
mdss_dsi_cfg_lane_ctrl(ctrl, BIT(20), 0);
|
|
}
|
|
|
|
static void mdss_dsi_stop_hs_clk_lane(struct mdss_dsi_ctrl_pdata *ctrl);
|
|
|
|
/*
|
|
* mdss_dsi_start_hs_clk_lane:
|
|
* this function is work around solution for 8994 dsi clk lane
|
|
* may stuck at HS problem
|
|
*/
|
|
static void mdss_dsi_start_hs_clk_lane(struct mdss_dsi_ctrl_pdata *ctrl)
|
|
{
|
|
|
|
/* make sure clk lane is stopped */
|
|
mdss_dsi_stop_hs_clk_lane(ctrl);
|
|
|
|
mutex_lock(&ctrl->clk_lane_mutex);
|
|
mdss_dsi_clk_ctrl(ctrl, ctrl->dsi_clk_handle, MDSS_DSI_ALL_CLKS,
|
|
MDSS_DSI_CLK_ON);
|
|
if (ctrl->clk_lane_cnt) {
|
|
pr_err("%s: ndx=%d do-wait, cnt=%d\n",
|
|
__func__, ctrl->ndx, ctrl->clk_lane_cnt);
|
|
mdss_dsi_wait_clk_lane_to_stop(ctrl);
|
|
}
|
|
|
|
/* force clk lane hs for next dma or mdp stream */
|
|
mdss_dsi_cfg_lane_ctrl(ctrl, BIT(28), 1);
|
|
ctrl->clk_lane_cnt++;
|
|
pr_debug("%s: ndx=%d, set_hs, cnt=%d\n", __func__,
|
|
ctrl->ndx, ctrl->clk_lane_cnt);
|
|
mdss_dsi_clk_ctrl(ctrl, ctrl->dsi_clk_handle, MDSS_DSI_ALL_CLKS,
|
|
MDSS_DSI_CLK_OFF);
|
|
mutex_unlock(&ctrl->clk_lane_mutex);
|
|
}
|
|
|
|
/*
|
|
* mdss_dsi_stop_hs_clk_lane:
|
|
* this function is work around solution for 8994 dsi clk lane
|
|
* may stuck at HS problem
|
|
*/
|
|
static void mdss_dsi_stop_hs_clk_lane(struct mdss_dsi_ctrl_pdata *ctrl)
|
|
{
|
|
u32 fifo = 0;
|
|
u32 lane = 0;
|
|
|
|
mutex_lock(&ctrl->clk_lane_mutex);
|
|
if (ctrl->clk_lane_cnt == 0) /* stopped already */
|
|
goto release;
|
|
|
|
mdss_dsi_clk_ctrl(ctrl, ctrl->dsi_clk_handle, MDSS_DSI_ALL_CLKS,
|
|
MDSS_DSI_CLK_ON);
|
|
/* fifo */
|
|
if (readl_poll_timeout(((ctrl->ctrl_base) + 0x000c),
|
|
fifo,
|
|
((fifo & 0x11110000) == 0x11110000),
|
|
10, 1000)) {
|
|
pr_err("%s: fifo NOT empty, fifo=%x\n",
|
|
__func__, fifo);
|
|
goto end;
|
|
}
|
|
|
|
/* data lane status */
|
|
if (readl_poll_timeout(((ctrl->ctrl_base) + 0x00a8),
|
|
lane,
|
|
((lane & 0x000f) == 0x000f),
|
|
100, 2000)) {
|
|
pr_err("%s: datalane NOT stopped, lane=%x\n",
|
|
__func__, lane);
|
|
}
|
|
end:
|
|
/* stop force clk lane hs */
|
|
mdss_dsi_cfg_lane_ctrl(ctrl, BIT(28), 0);
|
|
|
|
mdss_dsi_wait_clk_lane_to_stop(ctrl);
|
|
|
|
ctrl->clk_lane_cnt = 0;
|
|
release:
|
|
pr_debug("%s: ndx=%d, cnt=%d\n", __func__,
|
|
ctrl->ndx, ctrl->clk_lane_cnt);
|
|
|
|
mdss_dsi_clk_ctrl(ctrl, ctrl->dsi_clk_handle, MDSS_DSI_ALL_CLKS,
|
|
MDSS_DSI_CLK_OFF);
|
|
mutex_unlock(&ctrl->clk_lane_mutex);
|
|
}
|
|
|
|
static void mdss_dsi_cmd_start_hs_clk_lane(struct mdss_dsi_ctrl_pdata *ctrl)
|
|
{
|
|
struct mdss_dsi_ctrl_pdata *mctrl = NULL;
|
|
|
|
if (mdss_dsi_sync_wait_enable(ctrl)) {
|
|
if (!mdss_dsi_sync_wait_trigger(ctrl))
|
|
return;
|
|
mctrl = mdss_dsi_get_other_ctrl(ctrl);
|
|
|
|
if (mctrl)
|
|
mdss_dsi_start_hs_clk_lane(mctrl);
|
|
}
|
|
|
|
mdss_dsi_start_hs_clk_lane(ctrl);
|
|
}
|
|
|
|
static void mdss_dsi_cmd_stop_hs_clk_lane(struct mdss_dsi_ctrl_pdata *ctrl)
|
|
{
|
|
struct mdss_dsi_ctrl_pdata *mctrl = NULL;
|
|
|
|
if (mdss_dsi_sync_wait_enable(ctrl)) {
|
|
if (!mdss_dsi_sync_wait_trigger(ctrl))
|
|
return;
|
|
mctrl = mdss_dsi_get_other_ctrl(ctrl);
|
|
|
|
if (mctrl)
|
|
mdss_dsi_stop_hs_clk_lane(mctrl);
|
|
}
|
|
|
|
mdss_dsi_stop_hs_clk_lane(ctrl);
|
|
}
|
|
|
|
static void mdss_dsi_ctl_phy_reset(struct mdss_dsi_ctrl_pdata *ctrl, u32 event)
|
|
{
|
|
u32 data0, data1, mask = 0, data_lane_en = 0;
|
|
struct mdss_dsi_ctrl_pdata *ctrl0, *ctrl1;
|
|
u32 ln0, ln1, ln_ctrl0, ln_ctrl1, i;
|
|
/*
|
|
* Add 2 ms delay suggested by HW team.
|
|
* Check clk lane stop state after every 200 us
|
|
*/
|
|
u32 loop = 10, u_dly = 200;
|
|
pr_debug("%s: MDSS DSI CTRL and PHY reset. ctrl-num = %d\n",
|
|
__func__, ctrl->ndx);
|
|
if (event == DSI_EV_DLNx_FIFO_OVERFLOW) {
|
|
mask = BIT(20); /* clock lane only for overflow recovery */
|
|
} else if (event == DSI_EV_LP_RX_TIMEOUT) {
|
|
data_lane_en = (MIPI_INP(ctrl->ctrl_base + 0x0004) &
|
|
DSI_DATA_LANES_ENABLED) >> 4;
|
|
/* clock and data lanes for LP_RX_TO recovery */
|
|
mask = BIT(20) | (data_lane_en << 16);
|
|
}
|
|
|
|
if (mdss_dsi_is_hw_config_split(ctrl->shared_data)) {
|
|
pr_debug("%s: Split display enabled\n", __func__);
|
|
ctrl0 = mdss_dsi_get_ctrl_by_index(DSI_CTRL_0);
|
|
ctrl1 = mdss_dsi_get_ctrl_by_index(DSI_CTRL_1);
|
|
|
|
if (ctrl0->recovery)
|
|
ctrl0->recovery->fxn(ctrl0->recovery->data,
|
|
MDP_INTF_DSI_VIDEO_FIFO_OVERFLOW);
|
|
/*
|
|
* Disable PHY contention detection and receive.
|
|
* Configure the strength ctrl 1 register.
|
|
*/
|
|
MIPI_OUTP((ctrl0->phy_io.base) + 0x0188, 0);
|
|
MIPI_OUTP((ctrl1->phy_io.base) + 0x0188, 0);
|
|
|
|
data0 = MIPI_INP(ctrl0->ctrl_base + 0x0004);
|
|
data1 = MIPI_INP(ctrl1->ctrl_base + 0x0004);
|
|
/* Disable DSI video mode */
|
|
MIPI_OUTP(ctrl0->ctrl_base + 0x004, (data0 & ~BIT(1)));
|
|
MIPI_OUTP(ctrl1->ctrl_base + 0x004, (data1 & ~BIT(1)));
|
|
/* Disable DSI controller */
|
|
MIPI_OUTP(ctrl0->ctrl_base + 0x004,
|
|
(data0 & ~(BIT(0) | BIT(1))));
|
|
MIPI_OUTP(ctrl1->ctrl_base + 0x004,
|
|
(data1 & ~(BIT(0) | BIT(1))));
|
|
/* "Force On" all dynamic clocks */
|
|
MIPI_OUTP(ctrl0->ctrl_base + 0x11c, 0x100a00);
|
|
MIPI_OUTP(ctrl1->ctrl_base + 0x11c, 0x100a00);
|
|
|
|
/* DSI_SW_RESET */
|
|
MIPI_OUTP(ctrl0->ctrl_base + 0x118, 0x1);
|
|
MIPI_OUTP(ctrl1->ctrl_base + 0x118, 0x1);
|
|
wmb();
|
|
MIPI_OUTP(ctrl0->ctrl_base + 0x118, 0x0);
|
|
MIPI_OUTP(ctrl1->ctrl_base + 0x118, 0x0);
|
|
wmb();
|
|
|
|
/* Remove "Force On" all dynamic clocks */
|
|
MIPI_OUTP(ctrl0->ctrl_base + 0x11c, 0x00); /* DSI_CLK_CTRL */
|
|
MIPI_OUTP(ctrl1->ctrl_base + 0x11c, 0x00); /* DSI_CLK_CTRL */
|
|
|
|
/* Enable DSI controller */
|
|
MIPI_OUTP(ctrl0->ctrl_base + 0x004, (data0 & ~BIT(1)));
|
|
MIPI_OUTP(ctrl1->ctrl_base + 0x004, (data1 & ~BIT(1)));
|
|
|
|
/*
|
|
* Toggle Clk lane Force TX stop so that
|
|
* clk lane status is no more in stop state
|
|
*/
|
|
ln0 = MIPI_INP(ctrl0->ctrl_base + 0x00a8);
|
|
ln1 = MIPI_INP(ctrl1->ctrl_base + 0x00a8);
|
|
pr_debug("%s: lane status, ctrl0 = 0x%x, ctrl1 = 0x%x\n",
|
|
__func__, ln0, ln1);
|
|
ln_ctrl0 = MIPI_INP(ctrl0->ctrl_base + 0x00ac);
|
|
ln_ctrl1 = MIPI_INP(ctrl1->ctrl_base + 0x00ac);
|
|
MIPI_OUTP(ctrl0->ctrl_base + 0x0ac, ln_ctrl0 | mask);
|
|
MIPI_OUTP(ctrl1->ctrl_base + 0x0ac, ln_ctrl1 | mask);
|
|
ln_ctrl0 = MIPI_INP(ctrl0->ctrl_base + 0x00ac);
|
|
ln_ctrl1 = MIPI_INP(ctrl1->ctrl_base + 0x00ac);
|
|
for (i = 0; i < loop; i++) {
|
|
ln0 = MIPI_INP(ctrl0->ctrl_base + 0x00a8);
|
|
ln1 = MIPI_INP(ctrl1->ctrl_base + 0x00a8);
|
|
if ((ln0 == 0x1f1f) && (ln1 == 0x1f1f))
|
|
break;
|
|
else
|
|
/* Check clk lane stopState for every 200us */
|
|
udelay(u_dly);
|
|
}
|
|
if (i == loop) {
|
|
MDSS_XLOG(ctrl0->ndx, ln0, 0x1f1f);
|
|
MDSS_XLOG(ctrl1->ndx, ln1, 0x1f1f);
|
|
pr_err("Clock lane still in stop state");
|
|
MDSS_XLOG_TOUT_HANDLER("mdp", "dsi0_ctrl", "dsi0_phy",
|
|
"dsi1_ctrl", "dsi1_phy", "panic");
|
|
}
|
|
pr_debug("%s: lane ctrl, ctrl0 = 0x%x, ctrl1 = 0x%x\n",
|
|
__func__, ln0, ln1);
|
|
MIPI_OUTP(ctrl0->ctrl_base + 0x0ac, ln_ctrl0 & ~mask);
|
|
MIPI_OUTP(ctrl1->ctrl_base + 0x0ac, ln_ctrl1 & ~mask);
|
|
|
|
/* Enable Video mode for DSI controller */
|
|
MIPI_OUTP(ctrl0->ctrl_base + 0x004, data0);
|
|
MIPI_OUTP(ctrl1->ctrl_base + 0x004, data1);
|
|
|
|
/*
|
|
* Enable PHY contention detection and receive.
|
|
* Configure the strength ctrl 1 register.
|
|
*/
|
|
MIPI_OUTP((ctrl0->phy_io.base) + 0x0188, 0x6);
|
|
MIPI_OUTP((ctrl1->phy_io.base) + 0x0188, 0x6);
|
|
/*
|
|
* Add sufficient delay to make sure
|
|
* pixel transmission as started
|
|
*/
|
|
udelay(200);
|
|
} else {
|
|
if (ctrl->recovery)
|
|
ctrl->recovery->fxn(ctrl->recovery->data,
|
|
MDP_INTF_DSI_VIDEO_FIFO_OVERFLOW);
|
|
/* Disable PHY contention detection and receive */
|
|
MIPI_OUTP((ctrl->phy_io.base) + 0x0188, 0);
|
|
|
|
data0 = MIPI_INP(ctrl->ctrl_base + 0x0004);
|
|
/* Disable DSI video mode */
|
|
MIPI_OUTP(ctrl->ctrl_base + 0x004, (data0 & ~BIT(1)));
|
|
/* Disable DSI controller */
|
|
MIPI_OUTP(ctrl->ctrl_base + 0x004,
|
|
(data0 & ~(BIT(0) | BIT(1))));
|
|
/* "Force On" all dynamic clocks */
|
|
MIPI_OUTP(ctrl->ctrl_base + 0x11c, 0x100a00);
|
|
|
|
/* DSI_SW_RESET */
|
|
MIPI_OUTP(ctrl->ctrl_base + 0x118, 0x1);
|
|
wmb();
|
|
MIPI_OUTP(ctrl->ctrl_base + 0x118, 0x0);
|
|
wmb();
|
|
|
|
/* Remove "Force On" all dynamic clocks */
|
|
MIPI_OUTP(ctrl->ctrl_base + 0x11c, 0x00);
|
|
/* Enable DSI controller */
|
|
MIPI_OUTP(ctrl->ctrl_base + 0x004, (data0 & ~BIT(1)));
|
|
|
|
/*
|
|
* Toggle Clk lane Force TX stop so that
|
|
* clk lane status is no more in stop state
|
|
*/
|
|
ln0 = MIPI_INP(ctrl->ctrl_base + 0x00a8);
|
|
pr_debug("%s: lane status, ctrl = 0x%x\n",
|
|
__func__, ln0);
|
|
ln_ctrl0 = MIPI_INP(ctrl->ctrl_base + 0x00ac);
|
|
MIPI_OUTP(ctrl->ctrl_base + 0x0ac, ln_ctrl0 | mask);
|
|
ln_ctrl0 = MIPI_INP(ctrl->ctrl_base + 0x00ac);
|
|
for (i = 0; i < loop; i++) {
|
|
ln0 = MIPI_INP(ctrl->ctrl_base + 0x00a8);
|
|
if (ln0 == 0x1f1f)
|
|
break;
|
|
else
|
|
/* Check clk lane stopState for every 200us */
|
|
udelay(u_dly);
|
|
}
|
|
if (i == loop) {
|
|
MDSS_XLOG(ctrl->ndx, ln0, 0x1f1f);
|
|
pr_err("Clock lane still in stop state");
|
|
MDSS_XLOG_TOUT_HANDLER("mdp", "dsi0_ctrl", "dsi0_phy",
|
|
"dsi1_ctrl", "dsi1_phy", "panic");
|
|
}
|
|
pr_debug("%s: lane status = 0x%x\n",
|
|
__func__, ln0);
|
|
MIPI_OUTP(ctrl->ctrl_base + 0x0ac, ln_ctrl0 & ~mask);
|
|
|
|
/* Enable Video mode for DSI controller */
|
|
MIPI_OUTP(ctrl->ctrl_base + 0x004, data0);
|
|
/* Enable PHY contention detection and receiver */
|
|
MIPI_OUTP((ctrl->phy_io.base) + 0x0188, 0x6);
|
|
/*
|
|
* Add sufficient delay to make sure
|
|
* pixel transmission as started
|
|
*/
|
|
udelay(200);
|
|
}
|
|
pr_debug("Recovery done\n");
|
|
}
|
|
|
|
void mdss_dsi_err_intr_ctrl(struct mdss_dsi_ctrl_pdata *ctrl, u32 mask,
|
|
int enable)
|
|
{
|
|
u32 intr;
|
|
|
|
intr = MIPI_INP(ctrl->ctrl_base + 0x0110);
|
|
intr &= DSI_INTR_TOTAL_MASK;
|
|
|
|
if (enable)
|
|
intr |= mask;
|
|
else
|
|
intr &= ~mask;
|
|
|
|
pr_debug("%s: intr=%x enable=%d\n", __func__, intr, enable);
|
|
|
|
MIPI_OUTP(ctrl->ctrl_base + 0x0110, intr); /* DSI_INTL_CTRL */
|
|
}
|
|
|
|
void mdss_dsi_controller_cfg(int enable,
|
|
struct mdss_panel_data *pdata)
|
|
{
|
|
|
|
u32 dsi_ctrl;
|
|
u32 status;
|
|
u32 sleep_us = 1000;
|
|
u32 timeout_us = 16000;
|
|
struct mdss_dsi_ctrl_pdata *ctrl_pdata = NULL;
|
|
|
|
if (pdata == NULL) {
|
|
pr_err("%s: Invalid input data\n", __func__);
|
|
return;
|
|
}
|
|
|
|
ctrl_pdata = container_of(pdata, struct mdss_dsi_ctrl_pdata,
|
|
panel_data);
|
|
|
|
/* Check for CMD_MODE_DMA_BUSY */
|
|
if (readl_poll_timeout(((ctrl_pdata->ctrl_base) + 0x0008),
|
|
status,
|
|
((status & 0x02) == 0),
|
|
sleep_us, timeout_us))
|
|
pr_info("%s: DSI status=%x failed\n", __func__, status);
|
|
|
|
/* Check for x_HS_FIFO_EMPTY */
|
|
if (readl_poll_timeout(((ctrl_pdata->ctrl_base) + 0x000c),
|
|
status,
|
|
((status & 0x11111000) == 0x11111000),
|
|
sleep_us, timeout_us))
|
|
pr_info("%s: FIFO status=%x failed\n", __func__, status);
|
|
|
|
/* Check for VIDEO_MODE_ENGINE_BUSY */
|
|
if (readl_poll_timeout(((ctrl_pdata->ctrl_base) + 0x0008),
|
|
status,
|
|
((status & 0x08) == 0),
|
|
sleep_us, timeout_us)) {
|
|
pr_debug("%s: DSI status=%x\n", __func__, status);
|
|
pr_debug("%s: Doing sw reset\n", __func__);
|
|
mdss_dsi_sw_reset(ctrl_pdata, false);
|
|
}
|
|
|
|
dsi_ctrl = MIPI_INP((ctrl_pdata->ctrl_base) + 0x0004);
|
|
if (enable)
|
|
dsi_ctrl |= 0x01;
|
|
else
|
|
dsi_ctrl &= ~0x01;
|
|
|
|
MIPI_OUTP((ctrl_pdata->ctrl_base) + 0x0004, dsi_ctrl);
|
|
wmb();
|
|
}
|
|
|
|
void mdss_dsi_restore_intr_mask(struct mdss_dsi_ctrl_pdata *ctrl)
|
|
{
|
|
u32 mask;
|
|
|
|
mask = MIPI_INP((ctrl->ctrl_base) + 0x0110);
|
|
mask &= DSI_INTR_TOTAL_MASK;
|
|
mask |= (DSI_INTR_CMD_DMA_DONE_MASK | DSI_INTR_ERROR_MASK |
|
|
DSI_INTR_BTA_DONE_MASK);
|
|
MIPI_OUTP((ctrl->ctrl_base) + 0x0110, mask);
|
|
}
|
|
|
|
void mdss_dsi_op_mode_config(int mode,
|
|
struct mdss_panel_data *pdata)
|
|
{
|
|
u32 dsi_ctrl, intr_ctrl, dma_ctrl;
|
|
struct mdss_dsi_ctrl_pdata *ctrl_pdata = NULL;
|
|
|
|
if (pdata == NULL) {
|
|
pr_err("%s: Invalid input data\n", __func__);
|
|
return;
|
|
}
|
|
|
|
ctrl_pdata = container_of(pdata, struct mdss_dsi_ctrl_pdata,
|
|
panel_data);
|
|
|
|
dsi_ctrl = MIPI_INP((ctrl_pdata->ctrl_base) + 0x0004);
|
|
/*If Video enabled, Keep Video and Cmd mode ON */
|
|
if (dsi_ctrl & 0x02)
|
|
dsi_ctrl &= ~0x05;
|
|
else
|
|
dsi_ctrl &= ~0x07;
|
|
|
|
if (mode == DSI_VIDEO_MODE) {
|
|
dsi_ctrl |= 0x03;
|
|
intr_ctrl = DSI_INTR_CMD_DMA_DONE_MASK | DSI_INTR_BTA_DONE_MASK
|
|
| DSI_INTR_ERROR_MASK;
|
|
} else { /* command mode */
|
|
dsi_ctrl |= 0x05;
|
|
if (pdata->panel_info.type == MIPI_VIDEO_PANEL)
|
|
dsi_ctrl |= 0x02;
|
|
|
|
intr_ctrl = DSI_INTR_CMD_DMA_DONE_MASK | DSI_INTR_ERROR_MASK |
|
|
DSI_INTR_CMD_MDP_DONE_MASK | DSI_INTR_BTA_DONE_MASK;
|
|
}
|
|
|
|
dma_ctrl = BIT(28) | BIT(26); /* embedded mode & LP mode */
|
|
if (mdss_dsi_sync_wait_enable(ctrl_pdata))
|
|
dma_ctrl |= BIT(31);
|
|
|
|
pr_debug("%s: configuring ctrl%d\n", __func__, ctrl_pdata->ndx);
|
|
MIPI_OUTP((ctrl_pdata->ctrl_base) + 0x0110, intr_ctrl);
|
|
MIPI_OUTP((ctrl_pdata->ctrl_base) + 0x0004, dsi_ctrl);
|
|
MIPI_OUTP((ctrl_pdata->ctrl_base) + 0x003c, dma_ctrl);
|
|
wmb();
|
|
}
|
|
|
|
void mdss_dsi_cmd_bta_sw_trigger(struct mdss_panel_data *pdata)
|
|
{
|
|
u32 status;
|
|
int timeout_us = 10000;
|
|
struct mdss_dsi_ctrl_pdata *ctrl_pdata = NULL;
|
|
|
|
if (pdata == NULL) {
|
|
pr_err("%s: Invalid input data\n", __func__);
|
|
return;
|
|
}
|
|
|
|
ctrl_pdata = container_of(pdata, struct mdss_dsi_ctrl_pdata,
|
|
panel_data);
|
|
|
|
MIPI_OUTP((ctrl_pdata->ctrl_base) + 0x098, 0x01); /* trigger */
|
|
wmb();
|
|
|
|
/* Check for CMD_MODE_DMA_BUSY */
|
|
if (readl_poll_timeout(((ctrl_pdata->ctrl_base) + 0x0008),
|
|
status, ((status & 0x0010) == 0),
|
|
0, timeout_us))
|
|
pr_info("%s: DSI status=%x failed\n", __func__, status);
|
|
|
|
mdss_dsi_ack_err_status(ctrl_pdata);
|
|
|
|
pr_debug("%s: BTA done, status = %d\n", __func__, status);
|
|
}
|
|
|
|
static int mdss_dsi_read_status(struct mdss_dsi_ctrl_pdata *ctrl)
|
|
{
|
|
struct dcs_cmd_req cmdreq;
|
|
|
|
memset(&cmdreq, 0, sizeof(cmdreq));
|
|
cmdreq.cmds = ctrl->status_cmds.cmds;
|
|
cmdreq.cmds_cnt = ctrl->status_cmds.cmd_cnt;
|
|
cmdreq.flags = CMD_REQ_COMMIT | CMD_CLK_CTRL | CMD_REQ_RX;
|
|
cmdreq.rlen = ctrl->status_cmds_rlen;
|
|
cmdreq.cb = NULL;
|
|
cmdreq.rbuf = ctrl->status_buf.data;
|
|
|
|
if (ctrl->status_cmds.link_state == DSI_LP_MODE)
|
|
cmdreq.flags |= CMD_REQ_LP_MODE;
|
|
else if (ctrl->status_cmds.link_state == DSI_HS_MODE)
|
|
cmdreq.flags |= CMD_REQ_HS_MODE;
|
|
|
|
return mdss_dsi_cmdlist_put(ctrl, &cmdreq);
|
|
}
|
|
|
|
|
|
/**
|
|
* mdss_dsi_reg_status_check() - Check dsi panel status through reg read
|
|
* @ctrl_pdata: pointer to the dsi controller structure
|
|
*
|
|
* This function can be used to check the panel status through reading the
|
|
* status register from the panel.
|
|
*
|
|
* Return: positive value if the panel is in good state, negative value or
|
|
* zero otherwise.
|
|
*/
|
|
int mdss_dsi_reg_status_check(struct mdss_dsi_ctrl_pdata *ctrl_pdata)
|
|
{
|
|
int ret = 0;
|
|
struct mdss_dsi_ctrl_pdata *sctrl_pdata = NULL;
|
|
|
|
if (ctrl_pdata == NULL) {
|
|
pr_err("%s: Invalid input data\n", __func__);
|
|
return 0;
|
|
}
|
|
|
|
pr_debug("%s: Checking Register status\n", __func__);
|
|
|
|
mdss_dsi_clk_ctrl(ctrl_pdata, ctrl_pdata->dsi_clk_handle,
|
|
MDSS_DSI_ALL_CLKS, MDSS_DSI_CLK_ON);
|
|
|
|
sctrl_pdata = mdss_dsi_get_other_ctrl(ctrl_pdata);
|
|
if (!mdss_dsi_sync_wait_enable(ctrl_pdata)) {
|
|
ret = mdss_dsi_read_status(ctrl_pdata);
|
|
} else {
|
|
/*
|
|
* Read commands to check ESD status are usually sent at
|
|
* the same time to both the controllers. However, if
|
|
* sync_wait is enabled, we need to ensure that the
|
|
* dcs commands are first sent to the non-trigger
|
|
* controller so that when the commands are triggered,
|
|
* both controllers receive it at the same time.
|
|
*/
|
|
if (mdss_dsi_sync_wait_trigger(ctrl_pdata)) {
|
|
if (sctrl_pdata)
|
|
ret = mdss_dsi_read_status(sctrl_pdata);
|
|
ret = mdss_dsi_read_status(ctrl_pdata);
|
|
} else {
|
|
ret = mdss_dsi_read_status(ctrl_pdata);
|
|
if (sctrl_pdata)
|
|
ret = mdss_dsi_read_status(sctrl_pdata);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* mdss_dsi_read_status returns the number of bytes returned
|
|
* by the panel. Success value is greater than zero and failure
|
|
* case returns zero.
|
|
*/
|
|
if (ret > 0) {
|
|
if (!mdss_dsi_sync_wait_enable(ctrl_pdata) ||
|
|
mdss_dsi_sync_wait_trigger(ctrl_pdata))
|
|
ret = ctrl_pdata->check_read_status(ctrl_pdata);
|
|
else if (sctrl_pdata)
|
|
ret = ctrl_pdata->check_read_status(sctrl_pdata);
|
|
} else {
|
|
pr_err("%s: Read status register returned error\n", __func__);
|
|
}
|
|
|
|
mdss_dsi_clk_ctrl(ctrl_pdata, ctrl_pdata->dsi_clk_handle,
|
|
MDSS_DSI_ALL_CLKS, MDSS_DSI_CLK_OFF);
|
|
pr_debug("%s: Read register done with ret: %d\n", __func__, ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
void mdss_dsi_dsc_config(struct mdss_dsi_ctrl_pdata *ctrl, struct dsc_desc *dsc)
|
|
{
|
|
u32 data, offset;
|
|
|
|
if (dsc->pkt_per_line <= 0) {
|
|
pr_err("%s: Error: pkt_per_line cannot be negative or 0\n",
|
|
__func__);
|
|
return;
|
|
}
|
|
|
|
if (ctrl->panel_mode == DSI_VIDEO_MODE) {
|
|
MIPI_OUTP((ctrl->ctrl_base) +
|
|
MDSS_DSI_VIDEO_COMPRESSION_MODE_CTRL2, 0);
|
|
data = dsc->bytes_per_pkt << 16;
|
|
data |= (0x0b << 8); /* dtype of compressed image */
|
|
offset = MDSS_DSI_VIDEO_COMPRESSION_MODE_CTRL;
|
|
} else {
|
|
/* strem 0 */
|
|
MIPI_OUTP((ctrl->ctrl_base) +
|
|
MDSS_DSI_COMMAND_COMPRESSION_MODE_CTRL3, 0);
|
|
|
|
MIPI_OUTP((ctrl->ctrl_base) +
|
|
MDSS_DSI_COMMAND_COMPRESSION_MODE_CTRL2,
|
|
dsc->bytes_in_slice);
|
|
|
|
data = DTYPE_DCS_LWRITE << 8;
|
|
offset = MDSS_DSI_COMMAND_COMPRESSION_MODE_CTRL;
|
|
}
|
|
|
|
/*
|
|
* pkt_per_line:
|
|
* 0 == 1 pkt
|
|
* 1 == 2 pkt
|
|
* 2 == 4 pkt
|
|
* 3 pkt is not support
|
|
*/
|
|
if (dsc->pkt_per_line == 4)
|
|
data |= (dsc->pkt_per_line - 2) << 6;
|
|
else
|
|
data |= (dsc->pkt_per_line - 1) << 6;
|
|
data |= dsc->eol_byte_num << 4;
|
|
data |= 1; /* enable */
|
|
MIPI_OUTP((ctrl->ctrl_base) + offset, data);
|
|
}
|
|
|
|
static void mdss_dsi_mode_setup(struct mdss_panel_data *pdata)
|
|
{
|
|
struct mdss_dsi_ctrl_pdata *ctrl_pdata = NULL;
|
|
struct mdss_panel_info *pinfo;
|
|
struct mipi_panel_info *mipi;
|
|
struct dsc_desc *dsc = NULL;
|
|
u32 data = 0;
|
|
u32 hbp, hfp, vbp, vfp, hspw, vspw, width, height;
|
|
u32 ystride, bpp, dst_bpp, byte_num;
|
|
u32 stream_ctrl, stream_total;
|
|
u32 dummy_xres = 0, dummy_yres = 0;
|
|
u32 hsync_period, vsync_period;
|
|
|
|
ctrl_pdata = container_of(pdata, struct mdss_dsi_ctrl_pdata,
|
|
panel_data);
|
|
|
|
pinfo = &pdata->panel_info;
|
|
if (pinfo->compression_mode == COMPRESSION_DSC)
|
|
dsc = &pinfo->dsc;
|
|
|
|
dst_bpp = pdata->panel_info.fbc.enabled ?
|
|
(pdata->panel_info.fbc.target_bpp) : (pinfo->bpp);
|
|
|
|
hbp = pdata->panel_info.lcdc.h_back_porch;
|
|
hfp = pdata->panel_info.lcdc.h_front_porch;
|
|
vbp = pdata->panel_info.lcdc.v_back_porch;
|
|
vfp = pdata->panel_info.lcdc.v_front_porch;
|
|
hspw = pdata->panel_info.lcdc.h_pulse_width;
|
|
vspw = pdata->panel_info.lcdc.v_pulse_width;
|
|
width = mult_frac(pdata->panel_info.xres, dst_bpp,
|
|
pdata->panel_info.bpp);
|
|
height = pdata->panel_info.yres;
|
|
pr_debug("%s: fbc=%d width=%d height=%d dst_bpp=%d\n", __func__,
|
|
pdata->panel_info.fbc.enabled, width, height, dst_bpp);
|
|
|
|
if (dsc) /* compressed */
|
|
width = dsc->pclk_per_line;
|
|
|
|
if (pdata->panel_info.type == MIPI_VIDEO_PANEL) {
|
|
dummy_xres = mult_frac((pdata->panel_info.lcdc.border_left +
|
|
pdata->panel_info.lcdc.border_right),
|
|
dst_bpp, pdata->panel_info.bpp);
|
|
dummy_yres = pdata->panel_info.lcdc.border_top +
|
|
pdata->panel_info.lcdc.border_bottom;
|
|
}
|
|
|
|
mipi = &pdata->panel_info.mipi;
|
|
if (pdata->panel_info.type == MIPI_VIDEO_PANEL) {
|
|
vsync_period = vspw + vbp + height + dummy_yres + vfp;
|
|
hsync_period = hspw + hbp + width + dummy_xres + hfp;
|
|
|
|
if (ctrl_pdata->timing_db_mode)
|
|
MIPI_OUTP((ctrl_pdata->ctrl_base) + 0x1e8, 0x1);
|
|
MIPI_OUTP((ctrl_pdata->ctrl_base) + 0x24,
|
|
((hspw + hbp + width + dummy_xres) << 16 |
|
|
(hspw + hbp)));
|
|
MIPI_OUTP((ctrl_pdata->ctrl_base) + 0x28,
|
|
((vspw + vbp + height + dummy_yres) << 16 |
|
|
(vspw + vbp)));
|
|
MIPI_OUTP((ctrl_pdata->ctrl_base) + 0x2C,
|
|
((vsync_period - 1) << 16)
|
|
| (hsync_period - 1));
|
|
|
|
MIPI_OUTP((ctrl_pdata->ctrl_base) + 0x30, (hspw << 16));
|
|
MIPI_OUTP((ctrl_pdata->ctrl_base) + 0x34, 0);
|
|
MIPI_OUTP((ctrl_pdata->ctrl_base) + 0x38, (vspw << 16));
|
|
if (ctrl_pdata->timing_db_mode)
|
|
MIPI_OUTP((ctrl_pdata->ctrl_base) + 0x1e4, 0x1);
|
|
} else { /* command mode */
|
|
if (mipi->dst_format == DSI_CMD_DST_FORMAT_RGB888)
|
|
bpp = 3;
|
|
else if (mipi->dst_format == DSI_CMD_DST_FORMAT_RGB666)
|
|
bpp = 3;
|
|
else if (mipi->dst_format == DSI_CMD_DST_FORMAT_RGB565)
|
|
bpp = 2;
|
|
else
|
|
bpp = 3; /* Default format set to RGB888 */
|
|
|
|
ystride = width * bpp + 1;
|
|
|
|
if (dsc) {
|
|
byte_num = dsc->bytes_per_pkt;
|
|
if (pinfo->mipi.insert_dcs_cmd)
|
|
byte_num++;
|
|
|
|
stream_ctrl = (byte_num << 16) |
|
|
(mipi->vc << 8) | DTYPE_DCS_LWRITE;
|
|
stream_total = dsc->pic_height << 16 |
|
|
dsc->pclk_per_line;
|
|
} else if (pinfo->partial_update_enabled &&
|
|
mdss_dsi_is_panel_on(pdata) && pinfo->roi.w &&
|
|
pinfo->roi.h) {
|
|
stream_ctrl = (((pinfo->roi.w * bpp) + 1) << 16) |
|
|
(mipi->vc << 8) | DTYPE_DCS_LWRITE;
|
|
stream_total = pinfo->roi.h << 16 | pinfo->roi.w;
|
|
} else {
|
|
stream_ctrl = (ystride << 16) | (mipi->vc << 8) |
|
|
DTYPE_DCS_LWRITE;
|
|
stream_total = height << 16 | width;
|
|
}
|
|
|
|
/* DSI_COMMAND_MODE_NULL_INSERTION_CTRL */
|
|
if ((ctrl_pdata->shared_data->hw_rev >= MDSS_DSI_HW_REV_104)
|
|
&& ctrl_pdata->null_insert_enabled) {
|
|
data = (mipi->vc << 1); /* Virtual channel ID */
|
|
data |= 0 << 16; /* Word count of the NULL packet */
|
|
data |= 0x1; /* Enable Null insertion */
|
|
MIPI_OUTP((ctrl_pdata->ctrl_base) + 0x2b4, data);
|
|
}
|
|
|
|
/* Enable frame transfer in burst mode */
|
|
if (ctrl_pdata->shared_data->hw_rev >= MDSS_DSI_HW_REV_103) {
|
|
data = MIPI_INP(ctrl_pdata->ctrl_base + 0x1b8);
|
|
data = data | BIT(16);
|
|
MIPI_OUTP((ctrl_pdata->ctrl_base + 0x1b8), data);
|
|
ctrl_pdata->burst_mode_enabled = 1;
|
|
}
|
|
|
|
/* DSI_COMMAND_MODE_MDP_STREAM_CTRL */
|
|
MIPI_OUTP((ctrl_pdata->ctrl_base) + 0x60, stream_ctrl);
|
|
MIPI_OUTP((ctrl_pdata->ctrl_base) + 0x58, stream_ctrl);
|
|
|
|
/* DSI_COMMAND_MODE_MDP_STREAM_TOTAL */
|
|
MIPI_OUTP((ctrl_pdata->ctrl_base) + 0x64, stream_total);
|
|
MIPI_OUTP((ctrl_pdata->ctrl_base) + 0x5C, stream_total);
|
|
}
|
|
|
|
if (dsc) /* compressed */
|
|
mdss_dsi_dsc_config(ctrl_pdata, dsc);
|
|
}
|
|
|
|
void mdss_dsi_ctrl_setup(struct mdss_dsi_ctrl_pdata *ctrl)
|
|
{
|
|
struct mdss_panel_data *pdata = &ctrl->panel_data;
|
|
|
|
pr_debug("%s: called for ctrl%d\n", __func__, ctrl->ndx);
|
|
|
|
mdss_dsi_mode_setup(pdata);
|
|
mdss_dsi_host_init(pdata);
|
|
mdss_dsi_op_mode_config(pdata->panel_info.mipi.mode, pdata);
|
|
}
|
|
|
|
/**
|
|
* mdss_dsi_bta_status_check() - Check dsi panel status through bta check
|
|
* @ctrl_pdata: pointer to the dsi controller structure
|
|
*
|
|
* This function can be used to check status of the panel using bta check
|
|
* for the panel.
|
|
*
|
|
* Return: positive value if the panel is in good state, negative value or
|
|
* zero otherwise.
|
|
*/
|
|
int mdss_dsi_bta_status_check(struct mdss_dsi_ctrl_pdata *ctrl_pdata)
|
|
{
|
|
int ret = 0;
|
|
unsigned long flag;
|
|
|
|
if (ctrl_pdata == NULL) {
|
|
pr_err("%s: Invalid input data\n", __func__);
|
|
|
|
/*
|
|
* This should not return error otherwise
|
|
* BTA status thread will treat it as dead panel scenario
|
|
* and request for blank/unblank
|
|
*/
|
|
return 0;
|
|
}
|
|
|
|
mutex_lock(&ctrl_pdata->cmd_mutex);
|
|
|
|
pr_debug("%s: Checking BTA status\n", __func__);
|
|
|
|
mdss_dsi_clk_ctrl(ctrl_pdata, ctrl_pdata->dsi_clk_handle,
|
|
MDSS_DSI_ALL_CLKS, MDSS_DSI_CLK_ON);
|
|
spin_lock_irqsave(&ctrl_pdata->mdp_lock, flag);
|
|
reinit_completion(&ctrl_pdata->bta_comp);
|
|
mdss_dsi_enable_irq(ctrl_pdata, DSI_BTA_TERM);
|
|
spin_unlock_irqrestore(&ctrl_pdata->mdp_lock, flag);
|
|
MIPI_OUTP(ctrl_pdata->ctrl_base + 0x098, 0x01); /* trigger */
|
|
wmb();
|
|
|
|
ret = wait_for_completion_killable_timeout(&ctrl_pdata->bta_comp,
|
|
DSI_BTA_EVENT_TIMEOUT);
|
|
if (ret <= 0) {
|
|
mdss_dsi_disable_irq(ctrl_pdata, DSI_BTA_TERM);
|
|
pr_err("%s: DSI BTA error: %i\n", __func__, ret);
|
|
}
|
|
|
|
mdss_dsi_clk_ctrl(ctrl_pdata, ctrl_pdata->dsi_clk_handle,
|
|
MDSS_DSI_ALL_CLKS, MDSS_DSI_CLK_OFF);
|
|
pr_debug("%s: BTA done with ret: %d\n", __func__, ret);
|
|
|
|
mutex_unlock(&ctrl_pdata->cmd_mutex);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int mdss_dsi_cmd_reg_tx(u32 data,
|
|
unsigned char *ctrl_base)
|
|
{
|
|
int i;
|
|
char *bp;
|
|
|
|
bp = (char *)&data;
|
|
pr_debug("%s: ", __func__);
|
|
for (i = 0; i < 4; i++)
|
|
pr_debug("%x ", *bp++);
|
|
|
|
pr_debug("\n");
|
|
|
|
MIPI_OUTP(ctrl_base + 0x0084, 0x04);/* sw trigger */
|
|
MIPI_OUTP(ctrl_base + 0x0004, 0x135);
|
|
|
|
wmb();
|
|
|
|
MIPI_OUTP(ctrl_base + 0x03c, data);
|
|
wmb();
|
|
MIPI_OUTP(ctrl_base + 0x090, 0x01); /* trigger */
|
|
wmb();
|
|
|
|
udelay(300);
|
|
|
|
return 4;
|
|
}
|
|
|
|
static int mdss_dsi_wait4video_eng_busy(struct mdss_dsi_ctrl_pdata *ctrl);
|
|
|
|
static int mdss_dsi_cmd_dma_tx(struct mdss_dsi_ctrl_pdata *ctrl,
|
|
struct dsi_buf *tp);
|
|
|
|
static int mdss_dsi_cmd_dma_rx(struct mdss_dsi_ctrl_pdata *ctrl,
|
|
struct dsi_buf *rp, int rlen);
|
|
|
|
static int mdss_dsi_cmd_dma_tpg_tx(struct mdss_dsi_ctrl_pdata *ctrl,
|
|
struct dsi_buf *tp)
|
|
{
|
|
int len, i, ret = 0, data = 0;
|
|
u32 *bp;
|
|
struct mdss_dsi_ctrl_pdata *mctrl = NULL;
|
|
|
|
if (tp->len > DMA_TPG_FIFO_LEN) {
|
|
pr_debug("command length more than FIFO length\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
mdss_dsi_get_hw_revision(ctrl);
|
|
|
|
if (ctrl->shared_data->hw_rev < MDSS_DSI_HW_REV_103) {
|
|
pr_err("CMD DMA TPG not supported for this DSI version\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
bp = (u32 *)tp->data;
|
|
len = ALIGN(tp->len, 4);
|
|
|
|
reinit_completion(&ctrl->dma_comp);
|
|
|
|
if (mdss_dsi_sync_wait_trigger(ctrl))
|
|
mctrl = mdss_dsi_get_other_ctrl(ctrl);
|
|
|
|
data = BIT(16) | BIT(17); /* select CMD_DMA_PATTERN_SEL to 3 */
|
|
data |= BIT(2); /* select CMD_DMA_FIFO_MODE to 1 */
|
|
data |= BIT(1); /* enable CMD_DMA_TPG */
|
|
|
|
MIPI_OUTP(ctrl->ctrl_base + 0x15c, data);
|
|
if (mctrl)
|
|
MIPI_OUTP(mctrl->ctrl_base + 0x15c, data);
|
|
|
|
/*
|
|
* The DMA command parameters need to be programmed to the DMA_INIT_VAL
|
|
* register in the proper order. The 'len' value will be a multiple
|
|
* of 4, the padding bytes to make sure of this will be taken care of in
|
|
* mdss_dsi_cmd_dma_add API.
|
|
*/
|
|
for (i = 0; i < len; i += 4) {
|
|
MIPI_OUTP(ctrl->ctrl_base + 0x17c, *bp);
|
|
if (mctrl)
|
|
MIPI_OUTP(mctrl->ctrl_base + 0x17c, *bp);
|
|
wmb(); /* make sure write happens before writing next command */
|
|
bp++;
|
|
}
|
|
|
|
/*
|
|
* The number of writes to the DMA_INIT_VAL register should be an even
|
|
* number of dwords (32 bits). In case 'len' is not a multiple of 8,
|
|
* we need to do make an extra write to the register with 0x00 to
|
|
* satisfy this condition.
|
|
*/
|
|
if ((len % 8) != 0) {
|
|
MIPI_OUTP(ctrl->ctrl_base + 0x17c, 0x00);
|
|
if (mctrl)
|
|
MIPI_OUTP(mctrl->ctrl_base + 0x17c, 0x00);
|
|
}
|
|
|
|
if (mctrl) {
|
|
MIPI_OUTP(mctrl->ctrl_base + 0x04c, len);
|
|
MIPI_OUTP(mctrl->ctrl_base + 0x090, 0x01); /* trigger */
|
|
}
|
|
MIPI_OUTP(ctrl->ctrl_base + 0x04c, len);
|
|
wmb(); /* make sure DMA length is programmed */
|
|
|
|
MIPI_OUTP(ctrl->ctrl_base + 0x090, 0x01); /* trigger */
|
|
wmb(); /* make sure DMA trigger happens */
|
|
|
|
ret = wait_for_completion_timeout(&ctrl->dma_comp,
|
|
msecs_to_jiffies(DMA_TX_TIMEOUT));
|
|
if (ret == 0)
|
|
ret = -ETIMEDOUT;
|
|
else
|
|
ret = tp->len;
|
|
|
|
/* Reset the DMA TPG FIFO */
|
|
MIPI_OUTP(ctrl->ctrl_base + 0x1ec, 0x1);
|
|
wmb(); /* make sure FIFO reset happens */
|
|
MIPI_OUTP(ctrl->ctrl_base + 0x1ec, 0x0);
|
|
wmb(); /* make sure FIFO reset happens */
|
|
/* Disable CMD_DMA_TPG */
|
|
MIPI_OUTP(ctrl->ctrl_base + 0x15c, 0x0);
|
|
|
|
if (mctrl) {
|
|
/* Reset the DMA TPG FIFO */
|
|
MIPI_OUTP(mctrl->ctrl_base + 0x1ec, 0x1);
|
|
wmb(); /* make sure FIFO reset happens */
|
|
MIPI_OUTP(mctrl->ctrl_base + 0x1ec, 0x0);
|
|
wmb(); /* make sure FIFO reset happens */
|
|
/* Disable CMD_DMA_TPG */
|
|
MIPI_OUTP(mctrl->ctrl_base + 0x15c, 0x0);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int mdss_dsi_cmds2buf_tx(struct mdss_dsi_ctrl_pdata *ctrl,
|
|
struct dsi_cmd_desc *cmds, int cnt, int use_dma_tpg)
|
|
{
|
|
struct dsi_buf *tp;
|
|
struct dsi_cmd_desc *cm;
|
|
struct dsi_ctrl_hdr *dchdr;
|
|
int len, wait, tot = 0;
|
|
|
|
tp = &ctrl->tx_buf;
|
|
mdss_dsi_buf_init(tp);
|
|
cm = cmds;
|
|
len = 0;
|
|
while (cnt--) {
|
|
dchdr = &cm->dchdr;
|
|
mdss_dsi_buf_reserve(tp, len);
|
|
len = mdss_dsi_cmd_dma_add(tp, cm);
|
|
if (!len) {
|
|
pr_err("%s: failed to add cmd = 0x%x\n",
|
|
__func__, cm->payload[0]);
|
|
return 0;
|
|
}
|
|
tot += len;
|
|
if (dchdr->last) {
|
|
tp->data = tp->start; /* begin of buf */
|
|
|
|
wait = mdss_dsi_wait4video_eng_busy(ctrl);
|
|
|
|
mdss_dsi_enable_irq(ctrl, DSI_CMD_TERM);
|
|
if (use_dma_tpg)
|
|
len = mdss_dsi_cmd_dma_tpg_tx(ctrl, tp);
|
|
else
|
|
len = mdss_dsi_cmd_dma_tx(ctrl, tp);
|
|
if (IS_ERR_VALUE(len)) {
|
|
mdss_dsi_disable_irq(ctrl, DSI_CMD_TERM);
|
|
pr_err("%s: failed to call cmd_dma_tx for cmd = 0x%x\n",
|
|
__func__, cm->payload[0]);
|
|
return 0;
|
|
}
|
|
pr_debug("%s: cmd_dma_tx for cmd = 0x%x, len = %d\n",
|
|
__func__, cm->payload[0], len);
|
|
|
|
if (!wait || dchdr->wait > VSYNC_PERIOD)
|
|
usleep_range(dchdr->wait * 1000, dchdr->wait * 1000);
|
|
|
|
mdss_dsi_buf_init(tp);
|
|
len = 0;
|
|
}
|
|
cm++;
|
|
}
|
|
return tot;
|
|
}
|
|
|
|
/**
|
|
* __mdss_dsi_cmd_mode_config() - Enable/disable command mode engine
|
|
* @ctrl: pointer to the dsi controller structure
|
|
* @enable: true to enable command mode, false to disable command mode
|
|
*
|
|
* This function can be used to temporarily enable the command mode
|
|
* engine (even for video mode panels) so as to transfer any dma commands to
|
|
* the panel. It can also be used to disable the command mode engine
|
|
* when no longer needed.
|
|
*
|
|
* Return: true, if there was a mode switch to command mode for video mode
|
|
* panels.
|
|
*/
|
|
static inline bool __mdss_dsi_cmd_mode_config(
|
|
struct mdss_dsi_ctrl_pdata *ctrl, bool enable)
|
|
{
|
|
bool mode_changed = false;
|
|
u32 dsi_ctrl;
|
|
|
|
dsi_ctrl = MIPI_INP((ctrl->ctrl_base) + 0x0004);
|
|
/* if currently in video mode, enable command mode */
|
|
if (enable) {
|
|
if ((dsi_ctrl) & BIT(1)) {
|
|
MIPI_OUTP((ctrl->ctrl_base) + 0x0004,
|
|
dsi_ctrl | BIT(2));
|
|
mode_changed = true;
|
|
}
|
|
} else {
|
|
MIPI_OUTP((ctrl->ctrl_base) + 0x0004, dsi_ctrl & ~BIT(2));
|
|
}
|
|
|
|
return mode_changed;
|
|
}
|
|
|
|
/*
|
|
* mdss_dsi_cmds_tx:
|
|
* thread context only
|
|
*/
|
|
int mdss_dsi_cmds_tx(struct mdss_dsi_ctrl_pdata *ctrl,
|
|
struct dsi_cmd_desc *cmds, int cnt, int use_dma_tpg)
|
|
{
|
|
int len = 0;
|
|
struct mdss_dsi_ctrl_pdata *mctrl = NULL;
|
|
|
|
/*
|
|
* Turn on cmd mode in order to transmit the commands.
|
|
* For video mode, do not send cmds more than one pixel line,
|
|
* since it only transmit it during BLLP.
|
|
*/
|
|
|
|
if (mdss_dsi_sync_wait_enable(ctrl)) {
|
|
if (mdss_dsi_sync_wait_trigger(ctrl)) {
|
|
mctrl = mdss_dsi_get_other_ctrl(ctrl);
|
|
if (!mctrl) {
|
|
pr_warn("%s: sync_wait, NULL at other control\n",
|
|
__func__);
|
|
goto do_send;
|
|
}
|
|
|
|
mctrl->cmd_cfg_restore =
|
|
__mdss_dsi_cmd_mode_config(mctrl, 1);
|
|
} else if (!ctrl->do_unicast) {
|
|
/* broadcast cmds, let cmd_trigger do it */
|
|
return 0;
|
|
|
|
}
|
|
}
|
|
|
|
pr_debug("%s: ctrl=%d do_unicast=%d\n", __func__,
|
|
ctrl->ndx, ctrl->do_unicast);
|
|
|
|
do_send:
|
|
ctrl->cmd_cfg_restore = __mdss_dsi_cmd_mode_config(ctrl, 1);
|
|
|
|
len = mdss_dsi_cmds2buf_tx(ctrl, cmds, cnt, use_dma_tpg);
|
|
if (!len)
|
|
pr_err("%s: failed to call\n", __func__);
|
|
|
|
if (!ctrl->do_unicast) {
|
|
if (mctrl && mctrl->cmd_cfg_restore) {
|
|
__mdss_dsi_cmd_mode_config(mctrl, 0);
|
|
mctrl->cmd_cfg_restore = false;
|
|
}
|
|
|
|
if (ctrl->cmd_cfg_restore) {
|
|
__mdss_dsi_cmd_mode_config(ctrl, 0);
|
|
ctrl->cmd_cfg_restore = false;
|
|
}
|
|
}
|
|
|
|
return len;
|
|
}
|
|
|
|
/* MIPI_DSI_MRPS, Maximum Return Packet Size */
|
|
static char max_pktsize[2] = {0x00, 0x00}; /* LSB tx first, 10 bytes */
|
|
|
|
static struct dsi_cmd_desc pkt_size_cmd = {
|
|
{DTYPE_MAX_PKTSIZE, 1, 0, 0, 0, sizeof(max_pktsize)},
|
|
max_pktsize,
|
|
};
|
|
|
|
/*
|
|
* mdss_dsi_cmds_rx() - dcs read from panel
|
|
* @ctrl: dsi controller
|
|
* @cmds: read command descriptor
|
|
* @len: number of bytes to read back
|
|
*
|
|
* controller have 4 registers can hold 16 bytes of rxed data
|
|
* dcs packet: 4 bytes header + payload + 2 bytes crc
|
|
* 1st read: 4 bytes header + 10 bytes payload + 2 crc
|
|
* 2nd read: 14 bytes payload + 2 crc
|
|
* 3rd read: 14 bytes payload + 2 crc
|
|
*
|
|
*/
|
|
int mdss_dsi_cmds_rx(struct mdss_dsi_ctrl_pdata *ctrl,
|
|
struct dsi_cmd_desc *cmds, int rlen, int use_dma_tpg)
|
|
{
|
|
int data_byte, rx_byte, dlen, end;
|
|
int short_response, diff, pkt_size, ret = 0;
|
|
struct dsi_buf *tp, *rp;
|
|
char cmd;
|
|
struct mdss_dsi_ctrl_pdata *mctrl = NULL;
|
|
|
|
|
|
if (ctrl->panel_data.panel_info.panel_ack_disabled) {
|
|
pr_err("%s: ACK from Client not supported\n", __func__);
|
|
return rlen;
|
|
}
|
|
|
|
if (rlen == 0) {
|
|
pr_debug("%s: Minimum MRPS value should be 1\n", __func__);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Turn on cmd mode in order to transmit the commands.
|
|
* For video mode, do not send cmds more than one pixel line,
|
|
* since it only transmit it during BLLP.
|
|
*/
|
|
if (mdss_dsi_sync_wait_enable(ctrl)) {
|
|
if (mdss_dsi_sync_wait_trigger(ctrl)) {
|
|
mctrl = mdss_dsi_get_other_ctrl(ctrl);
|
|
if (!mctrl) {
|
|
pr_warn("%s: sync_wait, NULL at other control\n",
|
|
__func__);
|
|
goto do_send;
|
|
}
|
|
|
|
mctrl->cmd_cfg_restore =
|
|
__mdss_dsi_cmd_mode_config(mctrl, 1);
|
|
} else {
|
|
/* skip cmds, let cmd_trigger do it */
|
|
return 0;
|
|
|
|
}
|
|
}
|
|
|
|
do_send:
|
|
ctrl->cmd_cfg_restore = __mdss_dsi_cmd_mode_config(ctrl, 1);
|
|
|
|
if (rlen <= 2) {
|
|
short_response = 1;
|
|
pkt_size = rlen;
|
|
rx_byte = 4;
|
|
} else {
|
|
short_response = 0;
|
|
data_byte = 10; /* first read */
|
|
if (rlen < data_byte)
|
|
pkt_size = rlen;
|
|
else
|
|
pkt_size = data_byte;
|
|
rx_byte = data_byte + 6; /* 4 header + 2 crc */
|
|
}
|
|
|
|
|
|
tp = &ctrl->tx_buf;
|
|
rp = &ctrl->rx_buf;
|
|
|
|
end = 0;
|
|
mdss_dsi_buf_init(rp);
|
|
while (!end) {
|
|
pr_debug("%s: rlen=%d pkt_size=%d rx_byte=%d\n",
|
|
__func__, rlen, pkt_size, rx_byte);
|
|
/*
|
|
* Skip max_pkt_size dcs cmd if
|
|
* its already been configured
|
|
* for the requested pkt_size
|
|
*/
|
|
if (pkt_size == ctrl->cur_max_pkt_size)
|
|
goto skip_max_pkt_size;
|
|
|
|
max_pktsize[0] = pkt_size;
|
|
mdss_dsi_buf_init(tp);
|
|
ret = mdss_dsi_cmd_dma_add(tp, &pkt_size_cmd);
|
|
if (!ret) {
|
|
pr_err("%s: failed to add max_pkt_size\n",
|
|
__func__);
|
|
rp->len = 0;
|
|
rp->read_cnt = 0;
|
|
goto end;
|
|
}
|
|
|
|
mdss_dsi_wait4video_eng_busy(ctrl);
|
|
|
|
mdss_dsi_enable_irq(ctrl, DSI_CMD_TERM);
|
|
if (use_dma_tpg)
|
|
ret = mdss_dsi_cmd_dma_tpg_tx(ctrl, tp);
|
|
else
|
|
ret = mdss_dsi_cmd_dma_tx(ctrl, tp);
|
|
if (IS_ERR_VALUE(ret)) {
|
|
mdss_dsi_disable_irq(ctrl, DSI_CMD_TERM);
|
|
pr_err("%s: failed to tx max_pkt_size\n",
|
|
__func__);
|
|
rp->len = 0;
|
|
rp->read_cnt = 0;
|
|
goto end;
|
|
}
|
|
ctrl->cur_max_pkt_size = pkt_size;
|
|
pr_debug("%s: max_pkt_size=%d sent\n",
|
|
__func__, pkt_size);
|
|
|
|
skip_max_pkt_size:
|
|
mdss_dsi_buf_init(tp);
|
|
ret = mdss_dsi_cmd_dma_add(tp, cmds);
|
|
if (!ret) {
|
|
pr_err("%s: failed to add cmd = 0x%x\n",
|
|
__func__, cmds->payload[0]);
|
|
rp->len = 0;
|
|
rp->read_cnt = 0;
|
|
goto end;
|
|
}
|
|
|
|
if (ctrl->shared_data->hw_rev >= MDSS_DSI_HW_REV_101) {
|
|
/* clear the RDBK_DATA registers */
|
|
MIPI_OUTP(ctrl->ctrl_base + 0x01d4, 0x1);
|
|
wmb(); /* make sure the RDBK registers are cleared */
|
|
MIPI_OUTP(ctrl->ctrl_base + 0x01d4, 0x0);
|
|
wmb(); /* make sure the RDBK registers are cleared */
|
|
}
|
|
|
|
mdss_dsi_wait4video_eng_busy(ctrl); /* video mode only */
|
|
mdss_dsi_enable_irq(ctrl, DSI_CMD_TERM);
|
|
/* transmit read comamnd to client */
|
|
if (use_dma_tpg)
|
|
ret = mdss_dsi_cmd_dma_tpg_tx(ctrl, tp);
|
|
else
|
|
ret = mdss_dsi_cmd_dma_tx(ctrl, tp);
|
|
if (IS_ERR_VALUE(ret)) {
|
|
mdss_dsi_disable_irq(ctrl, DSI_CMD_TERM);
|
|
pr_err("%s: failed to tx cmd = 0x%x\n",
|
|
__func__, cmds->payload[0]);
|
|
rp->len = 0;
|
|
rp->read_cnt = 0;
|
|
goto end;
|
|
}
|
|
|
|
/*
|
|
* once cmd_dma_done interrupt received,
|
|
* return data from client is ready and stored
|
|
* at RDBK_DATA register already
|
|
* since rx fifo is 16 bytes, dcs header is kept at first loop,
|
|
* after that dcs header lost during shift into registers
|
|
*/
|
|
dlen = mdss_dsi_cmd_dma_rx(ctrl, rp, rx_byte);
|
|
|
|
if (!dlen)
|
|
goto end;
|
|
|
|
if (short_response)
|
|
break;
|
|
|
|
if (rlen <= data_byte) {
|
|
diff = data_byte - rlen;
|
|
end = 1;
|
|
} else {
|
|
diff = 0;
|
|
rlen -= data_byte;
|
|
}
|
|
|
|
dlen -= 2; /* 2 crc */
|
|
dlen -= diff;
|
|
rp->data += dlen; /* next start position */
|
|
rp->len += dlen;
|
|
if (!end) {
|
|
data_byte = 14; /* NOT first read */
|
|
if (rlen < data_byte)
|
|
pkt_size += rlen;
|
|
else
|
|
pkt_size += data_byte;
|
|
}
|
|
pr_debug("%s: rp data=%x len=%d dlen=%d diff=%d\n",
|
|
__func__, (int) (unsigned long) rp->data,
|
|
rp->len, dlen, diff);
|
|
}
|
|
|
|
/*
|
|
* For single Long read, if the requested rlen < 10,
|
|
* we need to shift the start position of rx
|
|
* data buffer to skip the bytes which are not
|
|
* updated.
|
|
*/
|
|
if (rp->read_cnt < 16 && !short_response)
|
|
rp->data = rp->start + (16 - rp->read_cnt);
|
|
else
|
|
rp->data = rp->start;
|
|
cmd = rp->data[0];
|
|
switch (cmd) {
|
|
case DTYPE_ACK_ERR_RESP:
|
|
pr_debug("%s: rx ACK_ERR_PACLAGE\n", __func__);
|
|
rp->len = 0;
|
|
rp->read_cnt = 0;
|
|
case DTYPE_GEN_READ1_RESP:
|
|
case DTYPE_DCS_READ1_RESP:
|
|
mdss_dsi_short_read1_resp(rp);
|
|
break;
|
|
case DTYPE_GEN_READ2_RESP:
|
|
case DTYPE_DCS_READ2_RESP:
|
|
mdss_dsi_short_read2_resp(rp);
|
|
break;
|
|
case DTYPE_GEN_LREAD_RESP:
|
|
case DTYPE_DCS_LREAD_RESP:
|
|
mdss_dsi_long_read_resp(rp);
|
|
break;
|
|
default:
|
|
pr_warning("%s:Invalid response cmd\n", __func__);
|
|
rp->len = 0;
|
|
rp->read_cnt = 0;
|
|
}
|
|
end:
|
|
|
|
if (mctrl && mctrl->cmd_cfg_restore) {
|
|
__mdss_dsi_cmd_mode_config(mctrl, 0);
|
|
mctrl->cmd_cfg_restore = false;
|
|
}
|
|
|
|
if (ctrl->cmd_cfg_restore) {
|
|
__mdss_dsi_cmd_mode_config(ctrl, 0);
|
|
ctrl->cmd_cfg_restore = false;
|
|
}
|
|
|
|
if (rp->len && (rp->len != rp->read_cnt))
|
|
pr_err("Bytes read: %d requested:%d mismatch\n",
|
|
rp->read_cnt, rp->len);
|
|
|
|
return rp->read_cnt;
|
|
}
|
|
|
|
static int mdss_dsi_cmd_dma_tx(struct mdss_dsi_ctrl_pdata *ctrl,
|
|
struct dsi_buf *tp)
|
|
{
|
|
int len, ret = 0;
|
|
int domain = MDSS_IOMMU_DOMAIN_UNSECURE;
|
|
char *bp;
|
|
struct mdss_dsi_ctrl_pdata *mctrl = NULL;
|
|
int ignored = 0; /* overflow ignored */
|
|
|
|
bp = tp->data;
|
|
|
|
len = ALIGN(tp->len, 4);
|
|
ctrl->dma_size = ALIGN(tp->len, SZ_4K);
|
|
|
|
ctrl->mdss_util->iommu_lock();
|
|
if (ctrl->mdss_util->iommu_attached()) {
|
|
ret = mdss_smmu_dsi_map_buffer(tp->dmap, domain, ctrl->dma_size,
|
|
&(ctrl->dma_addr), tp->start, DMA_TO_DEVICE);
|
|
if (IS_ERR_VALUE(ret)) {
|
|
pr_err("unable to map dma memory to iommu(%d)\n", ret);
|
|
ctrl->mdss_util->iommu_unlock();
|
|
return -ENOMEM;
|
|
}
|
|
ctrl->dmap_iommu_map = true;
|
|
} else {
|
|
ctrl->dma_addr = tp->dmap;
|
|
}
|
|
|
|
reinit_completion(&ctrl->dma_comp);
|
|
|
|
if (ctrl->panel_mode == DSI_VIDEO_MODE)
|
|
ignored = 1;
|
|
|
|
if (mdss_dsi_sync_wait_trigger(ctrl)) {
|
|
/* broadcast same cmd to other panel */
|
|
mctrl = mdss_dsi_get_other_ctrl(ctrl);
|
|
if (mctrl && mctrl->dma_addr == 0) {
|
|
if (ignored) {
|
|
/* mask out overflow isr */
|
|
mdss_dsi_set_reg(mctrl, 0x10c,
|
|
0x0f0000, 0x0f0000);
|
|
}
|
|
MIPI_OUTP(mctrl->ctrl_base + 0x048, ctrl->dma_addr);
|
|
MIPI_OUTP(mctrl->ctrl_base + 0x04c, len);
|
|
MIPI_OUTP(mctrl->ctrl_base + 0x090, 0x01); /* trigger */
|
|
}
|
|
}
|
|
|
|
if (ignored) {
|
|
/* mask out overflow isr */
|
|
mdss_dsi_set_reg(ctrl, 0x10c, 0x0f0000, 0x0f0000);
|
|
}
|
|
|
|
/* send cmd to its panel */
|
|
MIPI_OUTP((ctrl->ctrl_base) + 0x048, ctrl->dma_addr);
|
|
MIPI_OUTP((ctrl->ctrl_base) + 0x04c, len);
|
|
wmb();
|
|
|
|
MIPI_OUTP((ctrl->ctrl_base) + 0x090, 0x01);
|
|
wmb();
|
|
|
|
if (ctrl->do_unicast) {
|
|
/* let cmd_trigger to kickoff later */
|
|
pr_debug("%s: SKIP, ndx=%d do_unicast=%d\n", __func__,
|
|
ctrl->ndx, ctrl->do_unicast);
|
|
ret = tp->len;
|
|
goto end;
|
|
}
|
|
|
|
ret = wait_for_completion_timeout(&ctrl->dma_comp,
|
|
msecs_to_jiffies(DMA_TX_TIMEOUT));
|
|
if (ret == 0) {
|
|
u32 reg_val, status;
|
|
|
|
reg_val = MIPI_INP(ctrl->ctrl_base + 0x0110);/* DSI_INTR_CTRL */
|
|
status = reg_val & DSI_INTR_CMD_DMA_DONE;
|
|
if (status) {
|
|
reg_val &= DSI_INTR_MASK_ALL;
|
|
/* clear CMD DMA isr only */
|
|
reg_val |= DSI_INTR_CMD_DMA_DONE;
|
|
MIPI_OUTP(ctrl->ctrl_base + 0x0110, reg_val);
|
|
mdss_dsi_disable_irq_nosync(ctrl, DSI_CMD_TERM);
|
|
complete(&ctrl->dma_comp);
|
|
|
|
pr_warn("%s: dma tx done but irq not triggered\n",
|
|
__func__);
|
|
} else {
|
|
ret = -ETIMEDOUT;
|
|
}
|
|
}
|
|
|
|
if (!IS_ERR_VALUE(ret))
|
|
ret = tp->len;
|
|
|
|
if (mctrl && mctrl->dma_addr) {
|
|
if (ignored) {
|
|
/* clear pending overflow status */
|
|
mdss_dsi_set_reg(mctrl, 0xc, 0xffffffff, 0x44440000);
|
|
/* restore overflow isr */
|
|
mdss_dsi_set_reg(mctrl, 0x10c, 0x0f0000, 0);
|
|
}
|
|
if (mctrl->dmap_iommu_map) {
|
|
mdss_smmu_dsi_unmap_buffer(mctrl->dma_addr, domain,
|
|
mctrl->dma_size, DMA_TO_DEVICE);
|
|
mctrl->dmap_iommu_map = false;
|
|
}
|
|
mctrl->dma_addr = 0;
|
|
mctrl->dma_size = 0;
|
|
}
|
|
|
|
if (ctrl->dmap_iommu_map) {
|
|
mdss_smmu_dsi_unmap_buffer(ctrl->dma_addr, domain,
|
|
ctrl->dma_size, DMA_TO_DEVICE);
|
|
ctrl->dmap_iommu_map = false;
|
|
}
|
|
|
|
if (ignored) {
|
|
/* clear pending overflow status */
|
|
mdss_dsi_set_reg(ctrl, 0xc, 0xffffffff, 0x44440000);
|
|
/* restore overflow isr */
|
|
mdss_dsi_set_reg(ctrl, 0x10c, 0x0f0000, 0);
|
|
}
|
|
ctrl->dma_addr = 0;
|
|
ctrl->dma_size = 0;
|
|
end:
|
|
ctrl->mdss_util->iommu_unlock();
|
|
return ret;
|
|
}
|
|
|
|
static int mdss_dsi_cmd_dma_rx(struct mdss_dsi_ctrl_pdata *ctrl,
|
|
struct dsi_buf *rp, int rx_byte)
|
|
|
|
{
|
|
u32 *lp, *temp, data;
|
|
int i, j = 0, off, cnt;
|
|
bool ack_error = false;
|
|
char reg[16];
|
|
int repeated_bytes = 0;
|
|
|
|
lp = (u32 *)rp->data;
|
|
temp = (u32 *)reg;
|
|
cnt = rx_byte;
|
|
cnt += 3;
|
|
cnt >>= 2;
|
|
|
|
if (cnt > 4)
|
|
cnt = 4; /* 4 x 32 bits registers only */
|
|
|
|
if (ctrl->shared_data->hw_rev >= MDSS_DSI_HW_REV_101) {
|
|
rp->read_cnt = (MIPI_INP((ctrl->ctrl_base) + 0x01d4) >> 16);
|
|
pr_debug("%s: bytes read:%d\n", __func__, rp->read_cnt);
|
|
|
|
ack_error = (rx_byte == 4) ? (rp->read_cnt == 8) :
|
|
((rp->read_cnt - 4) == (max_pktsize[0] + 6));
|
|
|
|
if (ack_error)
|
|
rp->read_cnt -= 4; /* 4 byte read err report */
|
|
if (!rp->read_cnt) {
|
|
pr_err("%s: Errors detected, no data rxed\n", __func__);
|
|
return 0;
|
|
}
|
|
} else if (rx_byte == 4) {
|
|
rp->read_cnt = 4;
|
|
} else {
|
|
rp->read_cnt = (max_pktsize[0] + 6);
|
|
}
|
|
|
|
/*
|
|
* In case of multiple reads from the panel, after the first read, there
|
|
* is possibility that there are some bytes in the payload repeating in
|
|
* the RDBK_DATA registers. Since we read all the parameters from the
|
|
* panel right from the first byte for every pass. We need to skip the
|
|
* repeating bytes and then append the new parameters to the rx buffer.
|
|
*/
|
|
if (rp->read_cnt > 16) {
|
|
int bytes_shifted, data_lost = 0, rem_header_bytes = 0;
|
|
/* Any data more than 16 bytes will be shifted out */
|
|
bytes_shifted = rp->read_cnt - rx_byte;
|
|
if (bytes_shifted >= 4)
|
|
data_lost = bytes_shifted - 4; /* remove dcs header */
|
|
else
|
|
rem_header_bytes = 4 - bytes_shifted; /* rem header */
|
|
/*
|
|
* (rp->len - 4) -> current rx buffer data length.
|
|
* If data_lost > 0, then ((rp->len - 4) - data_lost) will be
|
|
* the number of repeating bytes.
|
|
* If data_lost == 0, then ((rp->len - 4) + rem_header_bytes)
|
|
* will be the number of bytes repeating in between rx buffer
|
|
* and the current RDBK_DATA registers. We need to skip the
|
|
* repeating bytes.
|
|
*/
|
|
repeated_bytes = (rp->len - 4) - data_lost + rem_header_bytes;
|
|
}
|
|
|
|
off = 0x06c; /* DSI_RDBK_DATA0 */
|
|
off += ((cnt - 1) * 4);
|
|
|
|
for (i = 0; i < cnt; i++) {
|
|
data = (u32)MIPI_INP((ctrl->ctrl_base) + off);
|
|
/* to network byte order */
|
|
if (!repeated_bytes)
|
|
*lp++ = ntohl(data);
|
|
else
|
|
*temp++ = ntohl(data);
|
|
pr_debug("%s: data = 0x%x and ntohl(data) = 0x%x\n",
|
|
__func__, data, ntohl(data));
|
|
off -= 4;
|
|
}
|
|
|
|
/* Skip duplicates and append other data to the rx buffer */
|
|
if (repeated_bytes) {
|
|
for (i = repeated_bytes; i < 16; i++)
|
|
rp->data[j++] = reg[i];
|
|
}
|
|
|
|
return rx_byte;
|
|
}
|
|
|
|
static int mdss_dsi_bus_bandwidth_vote(struct dsi_shared_data *sdata, bool on)
|
|
{
|
|
int rc = 0;
|
|
bool changed = false;
|
|
|
|
if (on) {
|
|
if (sdata->bus_refcount == 0)
|
|
changed = true;
|
|
sdata->bus_refcount++;
|
|
} else {
|
|
if (sdata->bus_refcount != 0) {
|
|
sdata->bus_refcount--;
|
|
if (sdata->bus_refcount == 0)
|
|
changed = true;
|
|
} else {
|
|
pr_warn("%s: bus bw votes are not balanced\n",
|
|
__func__);
|
|
}
|
|
}
|
|
|
|
if (changed) {
|
|
rc = msm_bus_scale_client_update_request(sdata->bus_handle,
|
|
on ? 1 : 0);
|
|
if (rc)
|
|
pr_err("%s: Bus bandwidth vote failed\n", __func__);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
int mdss_dsi_en_wait4dynamic_done(struct mdss_dsi_ctrl_pdata *ctrl)
|
|
{
|
|
unsigned long flag;
|
|
u32 data;
|
|
int rc = 0;
|
|
struct mdss_dsi_ctrl_pdata *sctrl_pdata;
|
|
|
|
/* DSI_INTL_CTRL */
|
|
data = MIPI_INP((ctrl->ctrl_base) + 0x0110);
|
|
data &= DSI_INTR_TOTAL_MASK;
|
|
data |= DSI_INTR_DYNAMIC_REFRESH_MASK;
|
|
MIPI_OUTP((ctrl->ctrl_base) + 0x0110, data);
|
|
|
|
spin_lock_irqsave(&ctrl->mdp_lock, flag);
|
|
reinit_completion(&ctrl->dynamic_comp);
|
|
mdss_dsi_enable_irq(ctrl, DSI_DYNAMIC_TERM);
|
|
spin_unlock_irqrestore(&ctrl->mdp_lock, flag);
|
|
|
|
/*
|
|
* Ensure that registers are updated before triggering
|
|
* dynamic refresh
|
|
*/
|
|
wmb();
|
|
|
|
MIPI_OUTP((ctrl->ctrl_base) + DSI_DYNAMIC_REFRESH_CTRL,
|
|
(BIT(13) | BIT(8) | BIT(0)));
|
|
|
|
sctrl_pdata = mdss_dsi_get_ctrl_clk_slave();
|
|
|
|
if (sctrl_pdata)
|
|
MIPI_OUTP((sctrl_pdata->ctrl_base) + DSI_DYNAMIC_REFRESH_CTRL,
|
|
(BIT(13) | BIT(8) | BIT(0)));
|
|
|
|
if (!wait_for_completion_timeout(&ctrl->dynamic_comp,
|
|
msecs_to_jiffies(VSYNC_PERIOD * 4))) {
|
|
pr_err("Dynamic interrupt timedout\n");
|
|
rc = -EINVAL;
|
|
}
|
|
|
|
data = MIPI_INP((ctrl->ctrl_base) + 0x0110);
|
|
data &= DSI_INTR_TOTAL_MASK;
|
|
data &= ~DSI_INTR_DYNAMIC_REFRESH_MASK;
|
|
MIPI_OUTP((ctrl->ctrl_base) + 0x0110, data);
|
|
|
|
return rc;
|
|
}
|
|
|
|
void mdss_dsi_wait4video_done(struct mdss_dsi_ctrl_pdata *ctrl)
|
|
{
|
|
unsigned long flag;
|
|
u32 data;
|
|
|
|
/* DSI_INTL_CTRL */
|
|
data = MIPI_INP((ctrl->ctrl_base) + 0x0110);
|
|
data &= DSI_INTR_TOTAL_MASK;
|
|
data |= DSI_INTR_VIDEO_DONE_MASK;
|
|
|
|
MIPI_OUTP((ctrl->ctrl_base) + 0x0110, data);
|
|
|
|
spin_lock_irqsave(&ctrl->mdp_lock, flag);
|
|
reinit_completion(&ctrl->video_comp);
|
|
mdss_dsi_enable_irq(ctrl, DSI_VIDEO_TERM);
|
|
spin_unlock_irqrestore(&ctrl->mdp_lock, flag);
|
|
|
|
wait_for_completion_timeout(&ctrl->video_comp,
|
|
msecs_to_jiffies(VSYNC_PERIOD * 4));
|
|
|
|
data = MIPI_INP((ctrl->ctrl_base) + 0x0110);
|
|
data &= DSI_INTR_TOTAL_MASK;
|
|
data &= ~DSI_INTR_VIDEO_DONE_MASK;
|
|
MIPI_OUTP((ctrl->ctrl_base) + 0x0110, data);
|
|
}
|
|
|
|
static int mdss_dsi_wait4video_eng_busy(struct mdss_dsi_ctrl_pdata *ctrl)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (ctrl->panel_mode == DSI_CMD_MODE)
|
|
return ret;
|
|
|
|
if (ctrl->ctrl_state & CTRL_STATE_MDP_ACTIVE) {
|
|
mdss_dsi_wait4video_done(ctrl);
|
|
/* delay 4 ms to skip BLLP */
|
|
usleep_range(4000, 4000);
|
|
ret = 1;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void mdss_dsi_cmd_mdp_start(struct mdss_dsi_ctrl_pdata *ctrl)
|
|
{
|
|
unsigned long flag;
|
|
|
|
spin_lock_irqsave(&ctrl->mdp_lock, flag);
|
|
mdss_dsi_enable_irq(ctrl, DSI_MDP_TERM);
|
|
ctrl->mdp_busy = true;
|
|
reinit_completion(&ctrl->mdp_comp);
|
|
MDSS_XLOG(ctrl->ndx, ctrl->mdp_busy, current->pid);
|
|
spin_unlock_irqrestore(&ctrl->mdp_lock, flag);
|
|
}
|
|
|
|
static int mdss_dsi_mdp_busy_tout_check(struct mdss_dsi_ctrl_pdata *ctrl)
|
|
{
|
|
unsigned long flag;
|
|
u32 isr;
|
|
bool stop_hs_clk = false;
|
|
int tout = 1;
|
|
|
|
/*
|
|
* two possible scenario:
|
|
* 1) DSI_INTR_CMD_MDP_DONE set but isr not fired
|
|
* 2) DSI_INTR_CMD_MDP_DONE set and cleared (isr fired)
|
|
* but event_thread not wakeup
|
|
*/
|
|
mdss_dsi_clk_ctrl(ctrl, ctrl->dsi_clk_handle, MDSS_DSI_ALL_CLKS,
|
|
MDSS_DSI_CLK_ON);
|
|
spin_lock_irqsave(&ctrl->mdp_lock, flag);
|
|
|
|
isr = MIPI_INP(ctrl->ctrl_base + 0x0110);
|
|
if (isr & DSI_INTR_CMD_MDP_DONE) {
|
|
pr_warn("INTR_CMD_MDP_DONE set but isr not fired\n");
|
|
isr &= DSI_INTR_MASK_ALL;
|
|
isr |= DSI_INTR_CMD_MDP_DONE; /* clear this isr only */
|
|
MIPI_OUTP(ctrl->ctrl_base + 0x0110, isr);
|
|
mdss_dsi_disable_irq_nosync(ctrl, DSI_MDP_TERM);
|
|
ctrl->mdp_busy = false;
|
|
if (ctrl->shared_data->cmd_clk_ln_recovery_en &&
|
|
ctrl->panel_mode == DSI_CMD_MODE) {
|
|
/* has hs_lane_recovery do the work */
|
|
stop_hs_clk = true;
|
|
}
|
|
tout = 0; /* recovered */
|
|
}
|
|
|
|
spin_unlock_irqrestore(&ctrl->mdp_lock, flag);
|
|
|
|
if (stop_hs_clk)
|
|
mdss_dsi_stop_hs_clk_lane(ctrl);
|
|
|
|
complete_all(&ctrl->mdp_comp);
|
|
|
|
mdss_dsi_clk_ctrl(ctrl, ctrl->dsi_clk_handle, MDSS_DSI_ALL_CLKS,
|
|
MDSS_DSI_CLK_OFF);
|
|
|
|
return tout;
|
|
}
|
|
|
|
void mdss_dsi_cmd_mdp_busy(struct mdss_dsi_ctrl_pdata *ctrl)
|
|
{
|
|
unsigned long flags;
|
|
int need_wait = 0;
|
|
int rc;
|
|
|
|
pr_debug("%s: start pid=%d\n",
|
|
__func__, current->pid);
|
|
|
|
MDSS_XLOG(ctrl->ndx, ctrl->mdp_busy, current->pid, XLOG_FUNC_ENTRY);
|
|
spin_lock_irqsave(&ctrl->mdp_lock, flags);
|
|
if (ctrl->mdp_busy == true)
|
|
need_wait++;
|
|
spin_unlock_irqrestore(&ctrl->mdp_lock, flags);
|
|
|
|
if (need_wait) {
|
|
/* wait until DMA finishes the current job */
|
|
pr_debug("%s: pending pid=%d\n",
|
|
__func__, current->pid);
|
|
rc = wait_for_completion_timeout(&ctrl->mdp_comp,
|
|
msecs_to_jiffies(DMA_TX_TIMEOUT));
|
|
spin_lock_irqsave(&ctrl->mdp_lock, flags);
|
|
if (!ctrl->mdp_busy)
|
|
rc = 1;
|
|
spin_unlock_irqrestore(&ctrl->mdp_lock, flags);
|
|
if (!rc) {
|
|
if (mdss_dsi_mdp_busy_tout_check(ctrl)) {
|
|
pr_err("%s: timeout error\n", __func__);
|
|
MDSS_XLOG_TOUT_HANDLER("mdp", "dsi0_ctrl",
|
|
"dsi0_phy", "dsi1_ctrl", "dsi1_phy",
|
|
"vbif", "vbif_nrt", "dbg_bus",
|
|
"vbif_dbg_bus", "panic");
|
|
}
|
|
}
|
|
}
|
|
pr_debug("%s: done pid=%d\n", __func__, current->pid);
|
|
MDSS_XLOG(ctrl->ndx, ctrl->mdp_busy, current->pid, XLOG_FUNC_EXIT);
|
|
}
|
|
|
|
int mdss_dsi_cmdlist_tx(struct mdss_dsi_ctrl_pdata *ctrl,
|
|
struct dcs_cmd_req *req)
|
|
{
|
|
int len;
|
|
|
|
if (mdss_dsi_sync_wait_enable(ctrl)) {
|
|
ctrl->do_unicast = false;
|
|
if (!ctrl->cmd_sync_wait_trigger &&
|
|
req->flags & CMD_REQ_UNICAST)
|
|
ctrl->do_unicast = true;
|
|
}
|
|
|
|
len = mdss_dsi_cmds_tx(ctrl, req->cmds, req->cmds_cnt,
|
|
(req->flags & CMD_REQ_DMA_TPG));
|
|
|
|
if (req->cb)
|
|
req->cb(len);
|
|
|
|
return len;
|
|
}
|
|
|
|
int mdss_dsi_cmdlist_rx(struct mdss_dsi_ctrl_pdata *ctrl,
|
|
struct dcs_cmd_req *req)
|
|
{
|
|
struct dsi_buf *rp;
|
|
int len = 0;
|
|
|
|
if (req->rbuf) {
|
|
rp = &ctrl->rx_buf;
|
|
len = mdss_dsi_cmds_rx(ctrl, req->cmds, req->rlen,
|
|
(req->flags & CMD_REQ_DMA_TPG));
|
|
memcpy(req->rbuf, rp->data, rp->len);
|
|
ctrl->rx_len = len;
|
|
} else {
|
|
pr_err("%s: No rx buffer provided\n", __func__);
|
|
}
|
|
|
|
if (req->cb)
|
|
req->cb(len);
|
|
|
|
return len;
|
|
}
|
|
|
|
int mdss_dsi_cmdlist_commit(struct mdss_dsi_ctrl_pdata *ctrl, int from_mdp)
|
|
{
|
|
struct dcs_cmd_req *req;
|
|
struct mdss_panel_info *pinfo;
|
|
struct mdss_rect *roi = NULL;
|
|
bool use_iommu = false;
|
|
int ret = -EINVAL;
|
|
int rc = 0;
|
|
bool hs_req = false;
|
|
bool cmd_mutex_acquired = false;
|
|
|
|
if (mdss_get_sd_client_cnt())
|
|
return -EPERM;
|
|
|
|
if (from_mdp) { /* from mdp kickoff */
|
|
if (!ctrl->burst_mode_enabled) {
|
|
mutex_lock(&ctrl->cmd_mutex);
|
|
cmd_mutex_acquired = true;
|
|
}
|
|
pinfo = &ctrl->panel_data.panel_info;
|
|
if (pinfo->partial_update_enabled)
|
|
roi = &pinfo->roi;
|
|
}
|
|
|
|
req = mdss_dsi_cmdlist_get(ctrl);
|
|
if (req && from_mdp && ctrl->burst_mode_enabled) {
|
|
mutex_lock(&ctrl->cmd_mutex);
|
|
cmd_mutex_acquired = true;
|
|
}
|
|
|
|
MDSS_XLOG(ctrl->ndx, from_mdp, ctrl->mdp_busy, current->pid,
|
|
XLOG_FUNC_ENTRY);
|
|
|
|
if (req && (req->flags & CMD_REQ_HS_MODE))
|
|
hs_req = true;
|
|
|
|
if (!ctrl->burst_mode_enabled ||
|
|
(from_mdp && ctrl->shared_data->cmd_clk_ln_recovery_en)) {
|
|
/* make sure dsi_cmd_mdp is idle */
|
|
mdss_dsi_cmd_mdp_busy(ctrl);
|
|
}
|
|
|
|
mdss_dsi_get_hw_revision(ctrl);
|
|
|
|
/* For DSI versions less than 1.3.0, CMD DMA TPG is not supported */
|
|
if (req && (ctrl->shared_data->hw_rev < MDSS_DSI_HW_REV_103))
|
|
req->flags &= ~CMD_REQ_DMA_TPG;
|
|
|
|
pr_debug("%s: ctrl=%d from_mdp=%d pid=%d\n", __func__,
|
|
ctrl->ndx, from_mdp, current->pid);
|
|
|
|
if (from_mdp) { /* from mdp kickoff */
|
|
/*
|
|
* when partial update enabled, the roi of pinfo
|
|
* is updated before mdp kickoff. Either width or
|
|
* height of roi is non zero, then really kickoff
|
|
* will followed.
|
|
*/
|
|
if (!roi || (roi->w != 0 || roi->h != 0)) {
|
|
if (ctrl->shared_data->cmd_clk_ln_recovery_en &&
|
|
ctrl->panel_mode == DSI_CMD_MODE)
|
|
mdss_dsi_start_hs_clk_lane(ctrl);
|
|
}
|
|
} else { /* from dcs send */
|
|
if (ctrl->shared_data->cmd_clk_ln_recovery_en &&
|
|
ctrl->panel_mode == DSI_CMD_MODE && hs_req)
|
|
mdss_dsi_cmd_start_hs_clk_lane(ctrl);
|
|
}
|
|
|
|
if (!req)
|
|
goto need_lock;
|
|
|
|
MDSS_XLOG(ctrl->ndx, req->flags, req->cmds_cnt, from_mdp, current->pid);
|
|
|
|
pr_debug("%s: from_mdp=%d pid=%d\n", __func__, from_mdp, current->pid);
|
|
|
|
if (!(req->flags & CMD_REQ_DMA_TPG)) {
|
|
/*
|
|
* mdss interrupt is generated in mdp core clock domain
|
|
* mdp clock need to be enabled to receive dsi interrupt
|
|
* also, axi bus bandwidth need since dsi controller will
|
|
* fetch dcs commands from axi bus
|
|
*/
|
|
rc = mdss_dsi_bus_bandwidth_vote(ctrl->shared_data, true);
|
|
if (rc) {
|
|
pr_err("%s: Bus bw vote failed\n", __func__);
|
|
if (from_mdp)
|
|
mutex_unlock(&ctrl->cmd_mutex);
|
|
return rc;
|
|
}
|
|
|
|
if (ctrl->mdss_util->iommu_ctrl) {
|
|
rc = ctrl->mdss_util->iommu_ctrl(1);
|
|
if (IS_ERR_VALUE(rc)) {
|
|
pr_err("IOMMU attach failed\n");
|
|
mutex_unlock(&ctrl->cmd_mutex);
|
|
return rc;
|
|
}
|
|
use_iommu = true;
|
|
}
|
|
}
|
|
|
|
mdss_dsi_clk_ctrl(ctrl, ctrl->dsi_clk_handle, MDSS_DSI_ALL_CLKS,
|
|
MDSS_DSI_CLK_ON);
|
|
|
|
if (req->flags & CMD_REQ_HS_MODE)
|
|
mdss_dsi_set_tx_power_mode(0, &ctrl->panel_data);
|
|
|
|
if (req->flags & CMD_REQ_RX)
|
|
ret = mdss_dsi_cmdlist_rx(ctrl, req);
|
|
else
|
|
ret = mdss_dsi_cmdlist_tx(ctrl, req);
|
|
|
|
if (req->flags & CMD_REQ_HS_MODE)
|
|
mdss_dsi_set_tx_power_mode(1, &ctrl->panel_data);
|
|
|
|
if (!(req->flags & CMD_REQ_DMA_TPG)) {
|
|
if (use_iommu)
|
|
ctrl->mdss_util->iommu_ctrl(0);
|
|
|
|
(void)mdss_dsi_bus_bandwidth_vote(ctrl->shared_data, false);
|
|
mdss_dsi_clk_ctrl(ctrl, ctrl->dsi_clk_handle, MDSS_DSI_ALL_CLKS,
|
|
MDSS_DSI_CLK_OFF);
|
|
}
|
|
|
|
need_lock:
|
|
|
|
MDSS_XLOG(ctrl->ndx, from_mdp, ctrl->mdp_busy, current->pid,
|
|
XLOG_FUNC_EXIT);
|
|
|
|
if (from_mdp) { /* from mdp kickoff */
|
|
/*
|
|
* when partial update enabled, the roi of pinfo
|
|
* is updated before mdp kickoff. Either width or
|
|
* height of roi is 0, then it is false kickoff so
|
|
* no mdp_busy flag set needed.
|
|
* when partial update disabled, mdp_busy flag
|
|
* alway set.
|
|
*/
|
|
if (!roi || (roi->w != 0 || roi->h != 0))
|
|
mdss_dsi_cmd_mdp_start(ctrl);
|
|
if (cmd_mutex_acquired)
|
|
mutex_unlock(&ctrl->cmd_mutex);
|
|
} else { /* from dcs send */
|
|
if (ctrl->shared_data->cmd_clk_ln_recovery_en &&
|
|
ctrl->panel_mode == DSI_CMD_MODE &&
|
|
(req && (req->flags & CMD_REQ_HS_MODE)))
|
|
mdss_dsi_cmd_stop_hs_clk_lane(ctrl);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void dsi_send_events(struct mdss_dsi_ctrl_pdata *ctrl,
|
|
u32 events, u32 arg)
|
|
{
|
|
struct dsi_event_q *evq;
|
|
|
|
if (!dsi_event.inited)
|
|
return;
|
|
|
|
pr_debug("%s: ev=%x\n", __func__, events);
|
|
|
|
spin_lock(&dsi_event.event_lock);
|
|
evq = &dsi_event.todo_list[dsi_event.event_pndx++];
|
|
evq->todo = events;
|
|
evq->arg = arg;
|
|
evq->ctrl = ctrl;
|
|
dsi_event.event_pndx %= DSI_EVENT_Q_MAX;
|
|
wake_up(&dsi_event.event_q);
|
|
spin_unlock(&dsi_event.event_lock);
|
|
}
|
|
|
|
static int dsi_event_thread(void *data)
|
|
{
|
|
struct mdss_dsi_event *ev;
|
|
struct dsi_event_q *evq;
|
|
struct mdss_dsi_ctrl_pdata *ctrl;
|
|
unsigned long flag;
|
|
struct sched_param param;
|
|
u32 todo = 0, ln_status, force_clk_ln_hs;
|
|
u32 arg;
|
|
int ret;
|
|
|
|
param.sched_priority = 16;
|
|
ret = sched_setscheduler_nocheck(current, SCHED_FIFO, ¶m);
|
|
if (ret)
|
|
pr_err("%s: set priority failed\n", __func__);
|
|
|
|
ev = (struct mdss_dsi_event *)data;
|
|
/* event */
|
|
init_waitqueue_head(&ev->event_q);
|
|
spin_lock_init(&ev->event_lock);
|
|
|
|
while (1) {
|
|
wait_event(ev->event_q, (ev->event_pndx != ev->event_gndx));
|
|
spin_lock_irqsave(&ev->event_lock, flag);
|
|
evq = &ev->todo_list[ev->event_gndx++];
|
|
todo = evq->todo;
|
|
ctrl = evq->ctrl;
|
|
arg = evq->arg;
|
|
evq->todo = 0;
|
|
ev->event_gndx %= DSI_EVENT_Q_MAX;
|
|
spin_unlock_irqrestore(&ev->event_lock, flag);
|
|
|
|
pr_debug("%s: ev=%x\n", __func__, todo);
|
|
|
|
if (todo & DSI_EV_PLL_UNLOCKED)
|
|
mdss_dsi_pll_relock(ctrl);
|
|
|
|
if (todo & DSI_EV_MDP_FIFO_UNDERFLOW) {
|
|
mutex_lock(&ctrl->mutex);
|
|
if (ctrl->recovery) {
|
|
pr_debug("%s: Handling underflow event\n",
|
|
__func__);
|
|
mdss_dsi_clk_ctrl(ctrl, ctrl->dsi_clk_handle,
|
|
MDSS_DSI_ALL_CLKS,
|
|
MDSS_DSI_CLK_ON);
|
|
mdss_dsi_sw_reset(ctrl, true);
|
|
ctrl->recovery->fxn(ctrl->recovery->data,
|
|
MDP_INTF_DSI_CMD_FIFO_UNDERFLOW);
|
|
mdss_dsi_clk_ctrl(ctrl, ctrl->dsi_clk_handle,
|
|
MDSS_DSI_ALL_CLKS,
|
|
MDSS_DSI_CLK_OFF);
|
|
} else {
|
|
MDSS_XLOG_TOUT_HANDLER("mdp", "dsi0_ctrl",
|
|
"dsi0_phy", "dsi1_ctrl", "dsi1_phy", "vbif",
|
|
"vbif_nrt", "dbg_bus", "vbif_dbg_bus", "panic");
|
|
}
|
|
mutex_unlock(&ctrl->mutex);
|
|
}
|
|
|
|
if (todo & DSI_EV_DSI_FIFO_EMPTY) {
|
|
mdss_dsi_clk_ctrl(ctrl, ctrl->dsi_clk_handle,
|
|
MDSS_DSI_CORE_CLK,
|
|
MDSS_DSI_CLK_ON);
|
|
mdss_dsi_sw_reset(ctrl, true);
|
|
mdss_dsi_clk_ctrl(ctrl, ctrl->dsi_clk_handle,
|
|
MDSS_DSI_CORE_CLK,
|
|
MDSS_DSI_CLK_OFF);
|
|
}
|
|
|
|
if (todo & DSI_EV_DLNx_FIFO_OVERFLOW) {
|
|
mutex_lock(&dsi_mtx);
|
|
/*
|
|
* For targets other than msm8994,
|
|
* run the overflow recovery sequence only when
|
|
* data lanes are in stop state and
|
|
* clock lane is not in Stop State.
|
|
*/
|
|
ln_status = MIPI_INP(ctrl->ctrl_base + 0x00a8);
|
|
force_clk_ln_hs = (MIPI_INP(ctrl->ctrl_base + 0x00ac)
|
|
& BIT(28));
|
|
pr_debug("%s: lane_status: 0x%x\n",
|
|
__func__, ln_status);
|
|
if (ctrl->recovery
|
|
&& (ctrl->shared_data->hw_rev
|
|
!= MDSS_DSI_HW_REV_103)
|
|
&& !(force_clk_ln_hs)
|
|
&& (ln_status
|
|
& DSI_DATA_LANES_STOP_STATE)
|
|
&& !(ln_status
|
|
& DSI_CLK_LANE_STOP_STATE)) {
|
|
pr_debug("%s: Handling overflow event.\n",
|
|
__func__);
|
|
mdss_dsi_clk_ctrl(ctrl, ctrl->dsi_clk_handle,
|
|
MDSS_DSI_ALL_CLKS,
|
|
MDSS_DSI_CLK_ON);
|
|
mdss_dsi_ctl_phy_reset(ctrl,
|
|
DSI_EV_DLNx_FIFO_OVERFLOW);
|
|
mdss_dsi_err_intr_ctrl(ctrl,
|
|
DSI_INTR_ERROR_MASK, 1);
|
|
mdss_dsi_clk_ctrl(ctrl, ctrl->dsi_clk_handle,
|
|
MDSS_DSI_ALL_CLKS,
|
|
MDSS_DSI_CLK_OFF);
|
|
} else if (ctrl->recovery
|
|
&& (ctrl->shared_data->hw_rev
|
|
== MDSS_DSI_HW_REV_103)) {
|
|
pr_debug("%s: Handle overflow->Rev_103\n",
|
|
__func__);
|
|
mdss_dsi_clk_ctrl(ctrl, ctrl->dsi_clk_handle,
|
|
MDSS_DSI_ALL_CLKS,
|
|
MDSS_DSI_CLK_ON);
|
|
mdss_dsi_ctl_phy_reset(ctrl,
|
|
DSI_EV_DLNx_FIFO_OVERFLOW);
|
|
mdss_dsi_err_intr_ctrl(ctrl,
|
|
DSI_INTR_ERROR_MASK, 1);
|
|
mdss_dsi_clk_ctrl(ctrl, ctrl->dsi_clk_handle,
|
|
MDSS_DSI_ALL_CLKS,
|
|
MDSS_DSI_CLK_OFF);
|
|
}
|
|
mutex_unlock(&dsi_mtx);
|
|
}
|
|
|
|
if (todo & DSI_EV_MDP_BUSY_RELEASE) {
|
|
pr_debug("%s: Handling MDP_BUSY_RELEASE event\n",
|
|
__func__);
|
|
spin_lock_irqsave(&ctrl->mdp_lock, flag);
|
|
ctrl->mdp_busy = false;
|
|
mdss_dsi_disable_irq_nosync(ctrl, DSI_MDP_TERM);
|
|
complete(&ctrl->mdp_comp);
|
|
spin_unlock_irqrestore(&ctrl->mdp_lock, flag);
|
|
|
|
/* enable dsi error interrupt */
|
|
mdss_dsi_clk_ctrl(ctrl, ctrl->dsi_clk_handle,
|
|
MDSS_DSI_ALL_CLKS, MDSS_DSI_CLK_ON);
|
|
mdss_dsi_err_intr_ctrl(ctrl, DSI_INTR_ERROR_MASK, 1);
|
|
mdss_dsi_clk_ctrl(ctrl, ctrl->dsi_clk_handle,
|
|
MDSS_DSI_ALL_CLKS, MDSS_DSI_CLK_OFF);
|
|
}
|
|
|
|
if (todo & DSI_EV_STOP_HS_CLK_LANE)
|
|
mdss_dsi_stop_hs_clk_lane(ctrl);
|
|
|
|
if (todo & DSI_EV_LP_RX_TIMEOUT) {
|
|
mdss_dsi_clk_ctrl(ctrl, ctrl->dsi_clk_handle,
|
|
MDSS_DSI_ALL_CLKS, MDSS_DSI_CLK_ON);
|
|
mdss_dsi_ctl_phy_reset(ctrl, DSI_EV_LP_RX_TIMEOUT);
|
|
mdss_dsi_clk_ctrl(ctrl, ctrl->dsi_clk_handle,
|
|
MDSS_DSI_ALL_CLKS, MDSS_DSI_CLK_OFF);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void mdss_dsi_ack_err_status(struct mdss_dsi_ctrl_pdata *ctrl)
|
|
{
|
|
u32 status;
|
|
unsigned char *base;
|
|
|
|
base = ctrl->ctrl_base;
|
|
|
|
status = MIPI_INP(base + 0x0068);/* DSI_ACK_ERR_STATUS */
|
|
|
|
if (status) {
|
|
MIPI_OUTP(base + 0x0068, status);
|
|
/* Writing of an extra 0 needed to clear error bits */
|
|
MIPI_OUTP(base + 0x0068, 0);
|
|
/*
|
|
* After bta done, h/w may have a fake overflow and
|
|
* that overflow may further cause ack_err about 3 ms
|
|
* later which is another false alarm. Here the
|
|
* warning message is ignored.
|
|
*/
|
|
if (ctrl->panel_data.panel_info.esd_check_enabled &&
|
|
(ctrl->status_mode == ESD_BTA) && (status & 0x1008000))
|
|
return;
|
|
|
|
pr_err("%s: status=%x\n", __func__, status);
|
|
}
|
|
}
|
|
|
|
void mdss_dsi_timeout_status(struct mdss_dsi_ctrl_pdata *ctrl)
|
|
{
|
|
u32 status;
|
|
unsigned char *base;
|
|
|
|
base = ctrl->ctrl_base;
|
|
|
|
status = MIPI_INP(base + 0x00c0);/* DSI_TIMEOUT_STATUS */
|
|
|
|
if (status & 0x0111) {
|
|
MIPI_OUTP(base + 0x00c0, status);
|
|
if (status & 0x0110)
|
|
dsi_send_events(ctrl, DSI_EV_LP_RX_TIMEOUT, 0);
|
|
pr_err("%s: status=%x\n", __func__, status);
|
|
}
|
|
}
|
|
|
|
void mdss_dsi_dln0_phy_err(struct mdss_dsi_ctrl_pdata *ctrl, bool print_en)
|
|
{
|
|
u32 status;
|
|
unsigned char *base;
|
|
|
|
base = ctrl->ctrl_base;
|
|
|
|
status = MIPI_INP(base + 0x00b4);/* DSI_DLN0_PHY_ERR */
|
|
|
|
if (status & 0x011111) {
|
|
MIPI_OUTP(base + 0x00b4, status);
|
|
if (print_en)
|
|
pr_err("%s: status=%x\n", __func__, status);
|
|
ctrl->err_cont.phy_err_cnt++;
|
|
}
|
|
}
|
|
|
|
void mdss_dsi_fifo_status(struct mdss_dsi_ctrl_pdata *ctrl)
|
|
{
|
|
u32 status;
|
|
unsigned char *base;
|
|
|
|
base = ctrl->ctrl_base;
|
|
|
|
status = MIPI_INP(base + 0x000c);/* DSI_FIFO_STATUS */
|
|
|
|
/* fifo underflow, overflow and empty*/
|
|
if (status & 0xcccc4489) {
|
|
MIPI_OUTP(base + 0x000c, status);
|
|
pr_err("%s: status=%x\n", __func__, status);
|
|
if (status & 0x44440000) {/* DLNx_HS_FIFO_OVERFLOW */
|
|
dsi_send_events(ctrl, DSI_EV_DLNx_FIFO_OVERFLOW, 0);
|
|
/* Ignore FIFO EMPTY when overflow happens */
|
|
status = status & 0xeeeeffff;
|
|
}
|
|
if (status & 0x0080) /* CMD_DMA_FIFO_UNDERFLOW */
|
|
dsi_send_events(ctrl, DSI_EV_MDP_FIFO_UNDERFLOW, 0);
|
|
if (status & 0x11110000) /* DLN_FIFO_EMPTY */
|
|
dsi_send_events(ctrl, DSI_EV_DSI_FIFO_EMPTY, 0);
|
|
ctrl->err_cont.fifo_err_cnt++;
|
|
}
|
|
}
|
|
|
|
void mdss_dsi_status(struct mdss_dsi_ctrl_pdata *ctrl)
|
|
{
|
|
u32 status;
|
|
unsigned char *base;
|
|
|
|
base = ctrl->ctrl_base;
|
|
|
|
status = MIPI_INP(base + 0x0008);/* DSI_STATUS */
|
|
|
|
if (status & 0x80000000) { /* INTERLEAVE_OP_CONTENTION */
|
|
MIPI_OUTP(base + 0x0008, status);
|
|
pr_err("%s: status=%x\n", __func__, status);
|
|
}
|
|
}
|
|
|
|
void mdss_dsi_clk_status(struct mdss_dsi_ctrl_pdata *ctrl)
|
|
{
|
|
u32 status;
|
|
unsigned char *base;
|
|
|
|
base = ctrl->ctrl_base;
|
|
status = MIPI_INP(base + 0x0120);/* DSI_CLK_STATUS */
|
|
|
|
if (status & 0x10000) { /* DSI_CLK_PLL_UNLOCKED */
|
|
MIPI_OUTP(base + 0x0120, status);
|
|
dsi_send_events(ctrl, DSI_EV_PLL_UNLOCKED, 0);
|
|
pr_err("%s: status=%x\n", __func__, status);
|
|
}
|
|
}
|
|
|
|
static void __dsi_error_counter(struct dsi_err_container *err_container)
|
|
{
|
|
s64 prev_time, curr_time;
|
|
int prev_index;
|
|
|
|
err_container->err_cnt++;
|
|
|
|
err_container->index = (err_container->index + 1) %
|
|
err_container->max_err_index;
|
|
curr_time = ktime_to_ms(ktime_get());
|
|
err_container->err_time[err_container->index] = curr_time;
|
|
|
|
prev_index = (err_container->index + 1) % err_container->max_err_index;
|
|
prev_time = err_container->err_time[prev_index];
|
|
|
|
if (prev_time &&
|
|
((curr_time - prev_time) < err_container->err_time_delta))
|
|
MDSS_XLOG_TOUT_HANDLER_WQ("mdp", "dsi0_ctrl", "dsi0_phy",
|
|
"dsi1_ctrl", "dsi1_phy", "panic");
|
|
}
|
|
|
|
void mdss_dsi_error(struct mdss_dsi_ctrl_pdata *ctrl)
|
|
{
|
|
u32 intr;
|
|
|
|
/* disable dsi error interrupt */
|
|
mdss_dsi_err_intr_ctrl(ctrl, DSI_INTR_ERROR_MASK, 0);
|
|
|
|
/* DSI_ERR_INT_MASK0 */
|
|
mdss_dsi_clk_status(ctrl); /* Mask0, 0x10000000 */
|
|
mdss_dsi_fifo_status(ctrl); /* mask0, 0x133d00 */
|
|
mdss_dsi_ack_err_status(ctrl); /* mask0, 0x01f */
|
|
mdss_dsi_timeout_status(ctrl); /* mask0, 0x0e0 */
|
|
mdss_dsi_status(ctrl); /* mask0, 0xc0100 */
|
|
mdss_dsi_dln0_phy_err(ctrl, true); /* mask0, 0x3e00000 */
|
|
|
|
/* clear dsi error interrupt */
|
|
intr = MIPI_INP(ctrl->ctrl_base + 0x0110);
|
|
intr &= DSI_INTR_TOTAL_MASK;
|
|
intr |= DSI_INTR_ERROR;
|
|
MIPI_OUTP(ctrl->ctrl_base + 0x0110, intr);
|
|
|
|
__dsi_error_counter(&ctrl->err_cont);
|
|
dsi_send_events(ctrl, DSI_EV_MDP_BUSY_RELEASE, 0);
|
|
}
|
|
|
|
irqreturn_t mdss_dsi_isr(int irq, void *ptr)
|
|
{
|
|
u32 isr;
|
|
u32 intr;
|
|
struct mdss_dsi_ctrl_pdata *ctrl =
|
|
(struct mdss_dsi_ctrl_pdata *)ptr;
|
|
|
|
if (!ctrl->ctrl_base) {
|
|
pr_err("%s:%d DSI base adr no Initialized",
|
|
__func__, __LINE__);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
isr = MIPI_INP(ctrl->ctrl_base + 0x0110);/* DSI_INTR_CTRL */
|
|
MIPI_OUTP(ctrl->ctrl_base + 0x0110, (isr & ~DSI_INTR_ERROR));
|
|
|
|
pr_debug("%s: ndx=%d isr=%x\n", __func__, ctrl->ndx, isr);
|
|
|
|
if (isr & DSI_INTR_BTA_DONE) {
|
|
spin_lock(&ctrl->mdp_lock);
|
|
mdss_dsi_disable_irq_nosync(ctrl, DSI_BTA_TERM);
|
|
complete(&ctrl->bta_comp);
|
|
/*
|
|
* When bta done happens, the panel should be in good
|
|
* state. However, bta could cause the fake overflow
|
|
* error for video mode. The similar issue happens when
|
|
* sending dcs cmd. This overflow further causes
|
|
* flicking because of phy reset which is unncessary,
|
|
* so here overflow error is ignored, and errors are
|
|
* cleared.
|
|
*/
|
|
if (ctrl->panel_data.panel_info.esd_check_enabled &&
|
|
(ctrl->status_mode == ESD_BTA) &&
|
|
(ctrl->panel_mode == DSI_VIDEO_MODE)) {
|
|
isr &= ~DSI_INTR_ERROR;
|
|
/* clear only overflow */
|
|
mdss_dsi_set_reg(ctrl, 0x0c, 0x44440000, 0x44440000);
|
|
}
|
|
spin_unlock(&ctrl->mdp_lock);
|
|
}
|
|
|
|
if (isr & DSI_INTR_ERROR) {
|
|
MDSS_XLOG(ctrl->ndx, ctrl->mdp_busy, isr, 0x97);
|
|
mdss_dsi_error(ctrl);
|
|
}
|
|
|
|
if (isr & DSI_INTR_VIDEO_DONE) {
|
|
spin_lock(&ctrl->mdp_lock);
|
|
mdss_dsi_disable_irq_nosync(ctrl, DSI_VIDEO_TERM);
|
|
complete(&ctrl->video_comp);
|
|
spin_unlock(&ctrl->mdp_lock);
|
|
}
|
|
|
|
if (isr & DSI_INTR_CMD_DMA_DONE) {
|
|
MDSS_XLOG(ctrl->ndx, ctrl->mdp_busy, isr, 0x98);
|
|
spin_lock(&ctrl->mdp_lock);
|
|
mdss_dsi_disable_irq_nosync(ctrl, DSI_CMD_TERM);
|
|
complete(&ctrl->dma_comp);
|
|
spin_unlock(&ctrl->mdp_lock);
|
|
}
|
|
|
|
if (isr & DSI_INTR_CMD_MDP_DONE) {
|
|
MDSS_XLOG(ctrl->ndx, ctrl->mdp_busy, isr, 0x99);
|
|
spin_lock(&ctrl->mdp_lock);
|
|
mdss_dsi_disable_irq_nosync(ctrl, DSI_MDP_TERM);
|
|
if (ctrl->shared_data->cmd_clk_ln_recovery_en &&
|
|
ctrl->panel_mode == DSI_CMD_MODE) {
|
|
/* stop force clk lane hs */
|
|
mdss_dsi_cfg_lane_ctrl(ctrl, BIT(28), 0);
|
|
dsi_send_events(ctrl, DSI_EV_STOP_HS_CLK_LANE,
|
|
DSI_MDP_TERM);
|
|
}
|
|
ctrl->mdp_busy = false;
|
|
complete_all(&ctrl->mdp_comp);
|
|
spin_unlock(&ctrl->mdp_lock);
|
|
}
|
|
|
|
if (isr & DSI_INTR_DYNAMIC_REFRESH_DONE) {
|
|
spin_lock(&ctrl->mdp_lock);
|
|
mdss_dsi_disable_irq_nosync(ctrl, DSI_DYNAMIC_TERM);
|
|
|
|
/* clear dfps interrupt */
|
|
intr = MIPI_INP(ctrl->ctrl_base + 0x0110);
|
|
intr |= DSI_INTR_DYNAMIC_REFRESH_DONE;
|
|
MIPI_OUTP(ctrl->ctrl_base + 0x0110, intr);
|
|
|
|
complete(&ctrl->dynamic_comp);
|
|
spin_unlock(&ctrl->mdp_lock);
|
|
}
|
|
|
|
return IRQ_HANDLED;
|
|
}
|