aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/video/samsung_duallcd/s5p-dsim.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/video/samsung_duallcd/s5p-dsim.c')
-rw-r--r--drivers/video/samsung_duallcd/s5p-dsim.c1597
1 files changed, 1597 insertions, 0 deletions
diff --git a/drivers/video/samsung_duallcd/s5p-dsim.c b/drivers/video/samsung_duallcd/s5p-dsim.c
new file mode 100644
index 0000000..05f4658
--- /dev/null
+++ b/drivers/video/samsung_duallcd/s5p-dsim.c
@@ -0,0 +1,1597 @@
+/* linux/drivers/video/samsung/s5p-dsim.c
+ *
+ * Samsung MIPI-DSIM driver.
+ *
+ * InKi Dae, <inki.dae@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Modified by Samsung Electronics (UK) on May 2010
+ *
+*/
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/clk.h>
+#include <linux/mutex.h>
+#include <linux/wait.h>
+#include <linux/fs.h>
+#include <linux/mm.h>
+#include <linux/fb.h>
+#include <linux/ctype.h>
+#include <linux/platform_device.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/memory.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/kthread.h>
+#include <linux/workqueue.h>
+#include <linux/regulator/consumer.h>
+#include <linux/gpio.h>
+#include <plat/clock.h>
+#include <plat/regs-dsim.h>
+#include <plat/gpio-cfg.h>
+#include <mach/map.h>
+#include <mach/dsim.h>
+#include <mach/mipi_ddi.h>
+#include <mach/irqs.h>
+
+#include "s5p-dsim.h"
+#include "s5p_dsim_lowlevel.h"
+#include "s3cfb.h"
+
+#ifdef CONFIG_HAS_WAKELOCK
+#include <linux/wakelock.h>
+#include <linux/earlysuspend.h>
+#include <linux/suspend.h>
+#endif
+
+struct mipi_lcd_info {
+ struct list_head list;
+ struct mipi_lcd_driver *mipi_drv;
+};
+
+static DEFINE_MUTEX(dsim_rd_wr_mutex);
+static DECLARE_COMPLETION(dsim_rd_comp);
+static DECLARE_COMPLETION(dsim_wr_comp);
+
+#define MIPI_RESP_ERR 0x02
+#define MIPI_RESP_EOTP 0x08
+#define MIPI_RESP_GENERIC_RD_1 0x11
+#define MIPI_RESP_GENERIC_RD_2 0x12
+#define MIPI_RESP_GENERIC_RD_LONG 0x1A
+#define MIPI_RESP_DCS_RD_LONG 0x1C
+#define MIPI_RESP_DCS_RD_1 0x21
+#define MIPI_RESP_DCS_RD_2 0x22
+
+#define MIPI_CMD_GENERIC_WR_0 0x03
+#define MIPI_CMD_GENERIC_WR_1 0x13
+#define MIPI_CMD_GENERIC_WR_2 0x23
+#define MIPI_CMD_GENERIC_WR_LONG 0x29
+
+#define MIPI_CMD_DSI_WR_0 0x05
+#define MIPI_CMD_DSI_WR_1 0x15
+#define MIPI_CMD_DSI_WR_LONG 0x39
+
+#define MIPI_CMD_GENERIC_RD_0 0x04
+#define MIPI_CMD_GENERIC_RD_1 0x14
+#define MIPI_CMD_GENERIC_RD_2 0x24
+
+#define MIPI_CMD_DSI_RD_0 0x06
+
+#define MIPI_CMD_DSI_SET_PKT_SZ 0x37
+
+#define DSIM_TIMEOUT msecs_to_jiffies(250)
+#define DSIM_RX_FIFO_READ_DONE 0x30800002
+#define DSIM_MAX_RX_FIFO 20
+
+#define S5P_DSIM_INT_SFR_FIFO_EMPTY 29
+#define S5P_DSIM_INT_BTA 25
+#define S5P_DSIM_INT_MSK_FRAME_DONE 24
+#define S5P_DSIM_INT_RX_TIMEOUT 21
+#define S5P_DSIM_INT_BTA_TIMEOUT 20
+#define S5P_DSIM_INT_RX_DONE 18
+#define S5P_DSIM_INT_RX_TE 17
+#define S5P_DSIM_INT_RX_ACK 16
+#define S5P_DSIM_INT_RX_ECC_ERR 15
+#define S5P_DSIM_IMT_RX_CRC_ERR 14
+
+static LIST_HEAD(lcd_info_list);
+static DEFINE_MUTEX(mipi_lock);
+static struct dsim_global *g_dsim;
+
+static struct s5p_platform_dsim *to_dsim_plat(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+
+ return (struct s5p_platform_dsim *)pdev->dev.platform_data;
+}
+
+static void s5p_dsim_frame_done_interrupt_enable(struct dsim_global *dsim, u8 enable)
+{
+ u32 intmsk;
+ u8 state = !enable;
+
+ if (!dsim->mipi_ddi_pd->resume_complete)
+ return;
+
+ intmsk = readl(dsim->reg_base + S5P_DSIM_INTMSK);
+
+ if (state == 0) /* enable Frame Done interrupts */
+ intmsk &= ~(0x01 << S5P_DSIM_INT_MSK_FRAME_DONE);
+ else /* disable Frame Done interrupts */
+ intmsk |= (0x01 << S5P_DSIM_INT_MSK_FRAME_DONE);
+
+ writel(intmsk, dsim->reg_base + S5P_DSIM_INTMSK);
+}
+
+void set_dsim_lcd_enabled(u8 enable)
+{
+ struct dsim_global *dsim = g_dsim;
+
+ dsim->dsim_lcd_info->lcd_enabled = enable;
+ if (dsim->dsim_info->hs_toggle)
+ s5p_dsim_frame_done_interrupt_enable(dsim, enable);
+}
+
+void set_dsim_hs_clk_toggle_count(u8 count)
+{
+ struct dsim_global *dsim = g_dsim;
+
+ dsim->dsim_toggle_per_frame_count = count;
+ if (dsim->dsim_lcd_info->lcd_enabled)
+ s5p_dsim_frame_done_interrupt_enable(dsim, count ? 1 : 0);
+}
+
+static void dsim_work_q_handler(struct work_struct *work)
+{
+ struct dsim_global *dsim =
+ container_of(work, struct dsim_global, dsim_work.work);
+
+ s5p_dsim_frame_done_interrupt_enable(dsim, 1);
+}
+
+static void dsim_check_hs_toggle_work_q_handler(struct work_struct *work)
+{
+ struct dsim_global *dsim =
+ container_of(work, struct dsim_global, check_hs_toggle_work.work);
+
+ if (dsim->dsim_info->hs_toggle) {
+ dev_info(dsim->dev, "check_hs_toggle\n");
+ schedule_delayed_work(&dsim->check_hs_toggle_work, msecs_to_jiffies(120000));
+ }
+}
+
+unsigned char s5p_dsim_wr_data(void *ptr,
+ unsigned int data_id, unsigned int data0, unsigned int data1)
+{
+ struct dsim_global *dsim = ptr;
+ unsigned int dsim_base = dsim->reg_base;
+
+ if (dsim->state == DSIM_STATE_ULPS) {
+ dev_err(dsim->dev, "DSIM state: ULPS\n");
+ return DSIM_FALSE;
+ }
+
+ if (dsim->mipi_ddi_pd->resume_complete == 0) {
+ dev_err(dsim->dev, "DSIM Status: SUSPEND\n");
+ return DSIM_FALSE;
+ }
+
+ mutex_lock(&dsim_rd_wr_mutex);
+
+ switch (data_id) {
+ /* short packet types of packet types for command. */
+ case GEN_SHORT_WR_NO_PARA:
+ case GEN_SHORT_WR_1_PARA:
+ case GEN_SHORT_WR_2_PARA:
+ case DCS_WR_NO_PARA:
+ case DCS_WR_1_PARA:
+ case SET_MAX_RTN_PKT_SIZE:
+ s5p_dsim_wr_tx_header(dsim_base, (unsigned char) data_id,
+ (unsigned char) data0, (unsigned char) data1);
+ mutex_unlock(&dsim_rd_wr_mutex);
+ return DSIM_TRUE; /* response should be implemented */
+ /* general command */
+ case CMD_OFF:
+ case CMD_ON:
+ case SHUT_DOWN:
+ case TURN_ON:
+ s5p_dsim_wr_tx_header(dsim_base, (unsigned char) data_id,
+ (unsigned char) data0, (unsigned char) data1);
+ mutex_unlock(&dsim_rd_wr_mutex);
+ return DSIM_TRUE; /* response should be implemented. */
+ /* packet types for video data */
+ case VSYNC_START:
+ case VSYNC_END:
+ case HSYNC_START:
+ case HSYNC_END:
+ case EOT_PKT:
+ mutex_unlock(&dsim_rd_wr_mutex);
+ return DSIM_TRUE;
+
+ /* short and response packet types for command */
+ case GEN_RD_1_PARA:
+ case GEN_RD_2_PARA:
+ case GEN_RD_NO_PARA:
+ case DCS_RD_NO_PARA:
+ s5p_dsim_clear_interrupt(dsim_base, 0xffffffff);
+ s5p_dsim_wr_tx_header(dsim_base, (unsigned char) data_id,
+ (unsigned char) data0, (unsigned char) data1);
+ mutex_unlock(&dsim_rd_wr_mutex);
+ return DSIM_FALSE; /* response should be implemented. */
+
+ /* long packet type and null packet */
+ case NULL_PKT:
+ case BLANKING_PKT:
+ mutex_unlock(&dsim_rd_wr_mutex);
+ return DSIM_TRUE;
+ case GEN_LONG_WR:
+ case DCS_LONG_WR:
+ {
+ u32 uCnt = 0;
+ u32* pWordPtr = (u32 *)data0;
+ INIT_COMPLETION(dsim_wr_comp);
+
+ do {
+ s5p_dsim_wr_tx_data(dsim_base, pWordPtr[uCnt]);
+ } while (((data1-1) / 4) > uCnt++);
+
+ /* put data into header fifo */
+ s5p_dsim_wr_tx_header(dsim_base, (unsigned char) data_id,
+ (unsigned char) (((unsigned short) data1) & 0xff),
+ (unsigned char) ((((unsigned short) data1) & 0xff00) >> 8));
+
+ if (!wait_for_completion_interruptible_timeout(&dsim_wr_comp, DSIM_TIMEOUT)) {
+ dev_err(dsim->dev, "[DSIM:ERROR] %s Timeout\n", __func__);
+ mutex_unlock(&dsim_rd_wr_mutex);
+ return DSIM_FALSE;
+ }
+ mutex_unlock(&dsim_rd_wr_mutex);
+ return DSIM_TRUE;
+ }
+ /* packet typo for video data */
+ case RGB565_PACKED:
+ case RGB666_PACKED:
+ case RGB666_LOOSLY:
+ case RGB888_PACKED:
+ mutex_unlock(&dsim_rd_wr_mutex);
+ return DSIM_TRUE; /* response should be implemented. */
+ default:
+ dev_warn(dsim->dev, "data id %x is not supported current DSI spec\n", data_id);
+ mutex_unlock(&dsim_rd_wr_mutex);
+ return DSIM_FALSE;
+ }
+}
+
+int s5p_dsim_rd_data(void *ptr, u8 addr, u16 count, u8 *buf)
+{
+ u32 i, temp;
+ u8 response = 0;
+ u16 rxsize;
+ u32 txhd;
+ u32 rxhd;
+ int j;
+ struct dsim_global *dsim = ptr;
+ unsigned int reg_base = dsim->reg_base;
+
+ if (dsim->mipi_ddi_pd->resume_complete == 0) {
+ dev_err(dsim->dev, "DSIM Status: SUSPEND\n");
+ return DSIM_FALSE;
+ }
+
+ mutex_lock(&dsim_rd_wr_mutex);
+ INIT_COMPLETION(dsim_rd_comp);
+
+ switch (count) {
+ case 1:
+#if defined(CONFIG_FB_S5P_S6E63M0)
+ response = MIPI_RESP_DCS_RD_1;
+#else
+ response = MIPI_RESP_GENERIC_RD_1;
+#endif /* CONFIG_FB_S5P_S6E63M0 */
+ break;
+ case 2:
+#if defined(CONFIG_FB_S5P_S6E63M0)
+ response = MIPI_RESP_DCS_RD_2;
+#else
+ response = MIPI_RESP_GENERIC_RD_2;
+#endif /* CONFIG_FB_S5P_S6E63M0 */
+ break;
+ default:
+ response = MIPI_RESP_GENERIC_RD_LONG;
+ break;
+ }
+
+ /* set return packet size */
+ txhd = MIPI_CMD_DSI_SET_PKT_SZ | count << 8;
+
+ writel(txhd, reg_base + S5P_DSIM_PKTHDR);
+
+ /* set address to read */
+#if defined(CONFIG_FB_S5P_S6E63M0)
+ txhd = MIPI_CMD_DSI_RD_0 | addr << 8;
+#else
+ txhd = MIPI_CMD_GENERIC_RD_1 | addr << 8;
+#endif /* CONFIG_FB_S5P_S6E63M0 */
+
+ writel(txhd, reg_base + S5P_DSIM_PKTHDR);
+
+ if (!wait_for_completion_interruptible_timeout(&dsim_rd_comp, DSIM_TIMEOUT)) {
+ dev_err(dsim->dev, "ERROR:%s timout\n", __func__);
+ mutex_unlock(&dsim_rd_wr_mutex);
+ return 0;
+ }
+
+ rxhd = readl(reg_base + S5P_DSIM_RXFIFO);
+ dev_info(dsim->dev, "rxhd : %x\n", rxhd);
+ if ((u8)(rxhd & 0xff) != response) {
+ dev_err(dsim->dev, "[DSIM:ERROR]:%s wrong response rxhd : %x, response:%x\n"
+ , __func__, rxhd, response);
+ goto clear_rx_fifo;
+ }
+ /* for short packet */
+ if (count <= 2) {
+ for (i = 0; i < count; i++)
+ buf[i] = (rxhd >> (8+(i*8))) & 0xff;
+ rxsize = count;
+ } else {
+ /* for long packet */
+ rxsize = (u16)((rxhd & 0x00ffff00) >> 8);
+ dev_info(dsim->dev, "rcv size : %d\n", rxsize);
+ if (rxsize != count) {
+ dev_err(dsim->dev, "[DSIM:ERROR]:%s received data size mismatch received : %d, requested : %d\n",
+ __func__, rxsize, count);
+ goto clear_rx_fifo;
+ }
+
+ for (i = 0; i < rxsize>>2; i++) {
+ temp = readl(reg_base + S5P_DSIM_RXFIFO);
+ dev_info(dsim->dev, "pkt : %08x\n", temp);
+ for (j = 0; j < 4; j++) {
+ buf[(i*4)+j] = (u8)(temp>>(j*8))&0xff;
+ /* printk("Value : %02x\n",(temp>>(j*8))&0xff); */
+ }
+ }
+ if (rxsize % 4) {
+ temp = readl(reg_base + S5P_DSIM_RXFIFO);
+ dev_info(dsim->dev, "pkt-l : %08x\n", temp);
+ for (j = 0; j < rxsize%4; j++) {
+ buf[(i*4)+j] = (u8)(temp>>(j*8))&0xff;
+ /* printk("Value : %02x\n",(temp>>(j*8))&0xff); */
+ }
+ }
+ }
+
+ temp = readl(reg_base + S5P_DSIM_RXFIFO);
+
+#if !defined(CONFIG_FB_S5P_S6E63M0)
+ if (temp != DSIM_RX_FIFO_READ_DONE) {
+ dev_warn(dsim->dev, "[DSIM:WARN]:%s Can't found RX FIFO READ DONE FLAG : %x\n", __func__, temp);
+ goto clear_rx_fifo;
+ }
+#endif
+
+ mutex_unlock(&dsim_rd_wr_mutex);
+ return rxsize;
+
+clear_rx_fifo:
+ i = 0;
+ while (1) {
+ temp = readl(reg_base+S5P_DSIM_RXFIFO);
+ if ((temp == DSIM_RX_FIFO_READ_DONE) || (i > DSIM_MAX_RX_FIFO))
+ break;
+ dev_info(dsim->dev, "[DSIM:INFO] : %s clear rx fifo : %08x\n", __func__, temp);
+ i++;
+ }
+ dev_info(dsim->dev, "[DSIM:INFO] : %s done count : %d, temp : %08x\n", __func__, i, temp);
+
+ mutex_unlock(&dsim_rd_wr_mutex);
+ return 0;
+
+}
+
+static irqreturn_t s5p_dsim_isr(int irq, void *dev_id)
+{
+ int i;
+ unsigned int intsrc = 0;
+ unsigned int intmsk = 0;
+ struct dsim_global *dsim = NULL;
+
+ dsim = (struct dsim_global *)dev_id;
+ if (!dsim) {
+ printk(KERN_ERR "%s:error:wrong parameter\n", __func__);
+ return IRQ_HANDLED;
+ }
+
+ intsrc = readl(dsim->reg_base + S5P_DSIM_INTSRC);
+ intmsk = readl(dsim->reg_base + S5P_DSIM_INTMSK);
+
+ intmsk = ~(intmsk) & intsrc;
+
+ for (i = 0; i < 32; i++) {
+ if (intmsk & (0x01<<i)) {
+ switch (i) {
+ case S5P_DSIM_INT_BTA:
+ /* printk("S5P_DSIM_INT_BTA\n"); */
+ break;
+ case S5P_DSIM_INT_RX_TIMEOUT:
+ /* printk("S5P_DSIM_INT_RX_TIMEOUT\n"); */
+ break;
+ case S5P_DSIM_INT_BTA_TIMEOUT:
+ /* printk("S5P_DSIM_INT_BTA_TIMEOUT\n"); */
+ break;
+ case S5P_DSIM_INT_RX_DONE:
+ complete_all(&dsim_rd_comp);
+ /* printk("S5P_DSIM_INT_RX_DONE\n"); */
+ break;
+ case S5P_DSIM_INT_RX_TE:
+ /* printk("S5P_DSIM_INT_RX_TE\n"); */
+ break;
+ case S5P_DSIM_INT_RX_ACK:
+ /* printk("S5P_DSIM_INT_RX_ACK\n"); */
+ break;
+ case S5P_DSIM_INT_RX_ECC_ERR:
+ /* printk("S5P_DSIM_INT_RX_ECC_ERR\n"); */
+ break;
+ case S5P_DSIM_IMT_RX_CRC_ERR:
+ /* printk("S5P_DSIM_IMT_RX_CRC_ERR\n"); */
+ break;
+ case S5P_DSIM_INT_SFR_FIFO_EMPTY:
+ /* printk("S5P_DSIM_INT_SFR_FIFO_EMPTY\n"); */
+ complete_all(&dsim_wr_comp);
+ break;
+ case S5P_DSIM_INT_MSK_FRAME_DONE:
+ /* printk("S5P_DSIM_INT_MSK_FRAME_DONE\n"); */
+ if (dsim->dsim_lcd_info->lcd_enabled && dsim->mipi_ddi_pd->resume_complete) {
+ if (completion_done(&dsim_wr_comp) && completion_done(&dsim_rd_comp)) {
+ if (s3cfb_vsync_status_check()) {
+ s5p_dsim_toggle_hs_clock(dsim->reg_base);
+ if (!dsim->dsim_toggle_per_frame_count) {
+ s5p_dsim_frame_done_interrupt_enable(dsim, 0);
+ if (likely(dsim->dsim_info->hs_toggle - 1))
+ schedule_delayed_work(&dsim->dsim_work, dsim->dsim_info->hs_toggle);
+ }
+ if (dsim->dsim_toggle_per_frame_count)
+ dsim->dsim_toggle_per_frame_count--;
+ }
+ }
+ }
+ break;
+ }
+ }
+ }
+ /* clear irq */
+ writel(intsrc, dsim->reg_base + S5P_DSIM_INTSRC);
+ return IRQ_HANDLED;
+}
+
+static void s5p_dsim_init_header_fifo(struct dsim_global *dsim)
+{
+ unsigned int cnt;
+
+ for (cnt = 0; cnt < DSIM_HEADER_FIFO_SZ; cnt++)
+ dsim->header_fifo_index[cnt] = -1;
+ return;
+}
+
+static unsigned char s5p_dsim_pll_on(unsigned int dsim_base, unsigned char enable)
+{
+ if (enable) {
+ int sw_timeout = 1000;
+ s5p_dsim_clear_interrupt(dsim_base, DSIM_PLL_STABLE);
+ s5p_dsim_enable_pll(dsim_base, 1);
+ while (1) {
+ sw_timeout--;
+ if (s5p_dsim_is_pll_stable(dsim_base))
+ return DSIM_TRUE;
+ if (sw_timeout == 0)
+ return DSIM_FALSE;
+ }
+ } else
+ s5p_dsim_enable_pll(dsim_base, 0);
+
+ return DSIM_TRUE;
+}
+
+static unsigned long s5p_dsim_change_pll(struct dsim_global *dsim, unsigned char pre_divider,
+ unsigned short main_divider, unsigned char scaler)
+{
+ unsigned long dfin_pll, dfvco, dpll_out;
+ unsigned char freq_band;
+ unsigned char temp0 = 0, temp1 = 0;
+ unsigned int dsim_base = dsim->reg_base;
+
+ dfin_pll = (MIPI_FIN / pre_divider);
+
+ if (dfin_pll < 6 * 1000 * 1000 || dfin_pll > 12 * 1000 * 1000) {
+ dev_warn(dsim->dev, "warning!!\n");
+ dev_warn(dsim->dev, "fin_pll range is 6MHz ~ 12MHz\n");
+ dev_warn(dsim->dev, "fin_pll of mipi dphy pll is %luMHz\n", (dfin_pll / 1000000));
+
+ s5p_dsim_enable_afc(dsim_base, 0, 0);
+ } else {
+ if (dfin_pll < 7 * 1000000)
+ s5p_dsim_enable_afc(dsim_base, 1, 0x1);
+ else if (dfin_pll < 8 * 1000000)
+ s5p_dsim_enable_afc(dsim_base, 1, 0x0);
+ else if (dfin_pll < 9 * 1000000)
+ s5p_dsim_enable_afc(dsim_base, 1, 0x3);
+ else if (dfin_pll < 10 * 1000000)
+ s5p_dsim_enable_afc(dsim_base, 1, 0x2);
+ else if (dfin_pll < 11 * 1000000)
+ s5p_dsim_enable_afc(dsim_base, 1, 0x5);
+ else
+ s5p_dsim_enable_afc(dsim_base, 1, 0x4);
+ }
+
+ dfvco = dfin_pll * main_divider;
+ dev_dbg(dsim->dev, "dfvco = %lu, dfin_pll = %lu, main_divider = %d\n",
+ dfvco, dfin_pll, main_divider);
+ if (dfvco < 500000000 || dfvco > 1000000000) {
+ dev_warn(dsim->dev, "Caution!!\n");
+ dev_warn(dsim->dev, "fvco range is 500MHz ~ 1000MHz\n");
+ dev_warn(dsim->dev, "fvco of mipi dphy pll is %luMHz\n", (dfvco / 1000000));
+ }
+
+ dpll_out = dfvco / (1 << scaler);
+ dev_dbg(dsim->dev, "dpll_out = %lu, dfvco = %lu, scaler = %d\n",
+ dpll_out, dfvco, scaler);
+ if (dpll_out < 100 * 1000000)
+ freq_band = 0x0;
+ else if (dpll_out < 120 * 1000000)
+ freq_band = 0x1;
+ else if (dpll_out < 170 * 1000000)
+ freq_band = 0x2;
+ else if (dpll_out < 220 * 1000000)
+ freq_band = 0x3;
+ else if (dpll_out < 270 * 1000000)
+ freq_band = 0x4;
+ else if (dpll_out < 320 * 1000000)
+ freq_band = 0x5;
+ else if (dpll_out < 390 * 1000000)
+ freq_band = 0x6;
+ else if (dpll_out < 450 * 1000000)
+ freq_band = 0x7;
+ else if (dpll_out < 510 * 1000000)
+ freq_band = 0x8;
+ else if (dpll_out < 560 * 1000000)
+ freq_band = 0x9;
+ else if (dpll_out < 640 * 1000000)
+ freq_band = 0xa;
+ else if (dpll_out < 690 * 1000000)
+ freq_band = 0xb;
+ else if (dpll_out < 770 * 1000000)
+ freq_band = 0xc;
+ else if (dpll_out < 870 * 1000000)
+ freq_band = 0xd;
+ else if (dpll_out < 950 * 1000000)
+ freq_band = 0xe;
+ else
+ freq_band = 0xf;
+
+ dev_dbg(dsim->dev, "freq_band = %d\n", freq_band);
+
+ s5p_dsim_pll_freq(dsim_base, pre_divider, main_divider, scaler);
+
+ s5p_dsim_hs_zero_ctrl(dsim_base, temp0);
+ s5p_dsim_prep_ctrl(dsim_base, temp1);
+
+ /* Freq Band */
+ s5p_dsim_pll_freq_band(dsim_base, freq_band);
+
+ /* Stable time */
+ s5p_dsim_pll_stable_time(dsim_base, dsim->dsim_info->pll_stable_time);
+
+ /* Enable PLL */
+ dev_dbg(dsim->dev, "FOUT of mipi dphy pll is %luMHz\n", (dpll_out / 1000000));
+
+ return dpll_out;
+}
+
+static void s5p_dsim_set_clock(struct dsim_global *dsim,
+ unsigned char byte_clk_sel, unsigned char enable)
+{
+ unsigned int esc_div;
+ unsigned long esc_clk_error_rate;
+ unsigned int dsim_base = dsim->reg_base;
+
+ if (enable) {
+ dsim->e_clk_src = byte_clk_sel;
+
+ /* Escape mode clock and byte clock source */
+ s5p_dsim_set_byte_clock_src(dsim_base, byte_clk_sel);
+
+ /* DPHY, DSIM Link : D-PHY clock out */
+ if (byte_clk_sel == DSIM_PLL_OUT_DIV8) {
+ dsim->hs_clk = s5p_dsim_change_pll(dsim, dsim->dsim_info->p,
+ dsim->dsim_info->m, dsim->dsim_info->s);
+ dsim->byte_clk = dsim->hs_clk / 8;
+ s5p_dsim_enable_pll_bypass(dsim_base, 0);
+ s5p_dsim_pll_on(dsim_base, 1);
+ usleep_range(1000, 1000);
+ /* DPHY : D-PHY clock out, DSIM link : external clock out */
+ } else if (byte_clk_sel == DSIM_EXT_CLK_DIV8)
+ dev_warn(dsim->dev, "this project is not supported external clock source for MIPI DSIM\n");
+ else if (byte_clk_sel == DSIM_EXT_CLK_BYPASS)
+ dev_warn(dsim->dev, "this project is not supported external clock source for MIPI DSIM\n");
+
+ /* escape clock divider */
+ esc_div = dsim->byte_clk / (dsim->dsim_info->esc_clk);
+ dev_dbg(dsim->dev, "esc_div = %d, byte_clk = %lu, esc_clk = %lu\n",
+ esc_div, dsim->byte_clk, dsim->dsim_info->esc_clk);
+ if ((dsim->byte_clk / esc_div) >= 20000000 ||
+ (dsim->byte_clk / esc_div) > dsim->dsim_info->esc_clk)
+ esc_div += 1;
+
+ dsim->escape_clk = dsim->byte_clk / esc_div;
+ dev_dbg(dsim->dev, "escape_clk = %lu, byte_clk = %lu, esc_div = %d\n",
+ dsim->escape_clk, dsim->byte_clk, esc_div);
+
+ /*
+ * enable escclk on lane
+ */
+ s5p_dsim_enable_byte_clock(dsim_base, DSIM_TRUE);
+
+ /* enable byte clk and escape clock */
+ s5p_dsim_set_esc_clk_prs(dsim_base, 1, esc_div);
+ /* escape clock on lane */
+ s5p_dsim_enable_esc_clk_on_lane(dsim_base, (DSIM_LANE_CLOCK | dsim->data_lane), 1);
+
+ dev_dbg(dsim->dev, "byte clock is %luMHz\n", (dsim->byte_clk / 1000000));
+ dev_dbg(dsim->dev, "escape clock that user's need is %lu\n", (dsim->dsim_info->esc_clk / 1000000));
+ dev_dbg(dsim->dev, "escape clock divider is %x\n", esc_div);
+ dev_dbg(dsim->dev, "escape clock is %luMHz\n", ((dsim->byte_clk / esc_div) / 1000000));
+
+ if ((dsim->byte_clk / esc_div) > dsim->escape_clk) {
+ esc_clk_error_rate = dsim->escape_clk / (dsim->byte_clk / esc_div);
+ dev_warn(dsim->dev, "error rate is %lu over\n", (esc_clk_error_rate / 100));
+ } else if ((dsim->byte_clk / esc_div) < (dsim->escape_clk)) {
+ esc_clk_error_rate = (dsim->byte_clk / esc_div) / dsim->escape_clk;
+ dev_warn(dsim->dev, "error rate is %lu under\n", (esc_clk_error_rate / 100));
+ }
+ } else {
+ s5p_dsim_enable_esc_clk_on_lane(dsim_base, (DSIM_LANE_CLOCK | dsim->data_lane), 0);
+ s5p_dsim_set_esc_clk_prs(dsim_base, 0, 0);
+
+ s5p_dsim_enable_byte_clock(dsim_base, DSIM_FALSE);
+
+ if (byte_clk_sel == DSIM_PLL_OUT_DIV8)
+ s5p_dsim_pll_on(dsim_base, 0);
+ }
+}
+
+static int s5p_dsim_late_resume_init_dsim(struct dsim_global *dsim)
+{
+ unsigned int dsim_base = dsim->reg_base;
+
+ if (dsim->pd->init_d_phy)
+ dsim->pd->init_d_phy(dsim->reg_base);
+
+ dsim->state = DSIM_STATE_RESET;
+
+ switch (dsim->dsim_info->e_no_data_lane) {
+ case DSIM_DATA_LANE_1:
+ dsim->data_lane = DSIM_LANE_DATA0;
+ break;
+ case DSIM_DATA_LANE_2:
+ dsim->data_lane = DSIM_LANE_DATA0 | DSIM_LANE_DATA1;
+ break;
+ case DSIM_DATA_LANE_3:
+ dsim->data_lane = DSIM_LANE_DATA0 | DSIM_LANE_DATA1 |
+ DSIM_LANE_DATA2;
+ break;
+ case DSIM_DATA_LANE_4:
+ dsim->data_lane = DSIM_LANE_DATA0 | DSIM_LANE_DATA1 |
+ DSIM_LANE_DATA2 | DSIM_LANE_DATA3;
+ break;
+ default:
+ dev_info(dsim->dev, "data lane is invalid\n");
+ return -1;
+ };
+
+ s5p_dsim_init_header_fifo(dsim);
+ s5p_dsim_sw_reset(dsim_base);
+ s5p_dsim_dp_dn_swap(dsim_base, dsim->dsim_info->e_lane_swap);
+
+ /* enable only frame done interrupt */
+ /* s5p_dsim_clear_interrupt(dsim_base, AllDsimIntr); */
+ /* s5p_dsim_set_interrupt_mask(dsim->reg_base, AllDsimIntr, 1); */
+
+ return 0;
+}
+
+#if 0
+static int s5p_dsim_init_dsim(struct dsim_global *dsim)
+{
+ unsigned int dsim_base = dsim->reg_base;
+
+ if (dsim->pd->init_d_phy)
+ dsim->pd->init_d_phy(dsim->reg_base);
+
+ dsim->state = DSIM_STATE_RESET;
+
+ switch (dsim->dsim_info->e_no_data_lane) {
+ case DSIM_DATA_LANE_1:
+ dsim->data_lane = DSIM_LANE_DATA0;
+ break;
+ case DSIM_DATA_LANE_2:
+ dsim->data_lane = DSIM_LANE_DATA0 | DSIM_LANE_DATA1;
+ break;
+ case DSIM_DATA_LANE_3:
+ dsim->data_lane = DSIM_LANE_DATA0 | DSIM_LANE_DATA1 |
+ DSIM_LANE_DATA2;
+ break;
+ case DSIM_DATA_LANE_4:
+ dsim->data_lane = DSIM_LANE_DATA0 | DSIM_LANE_DATA1 |
+ DSIM_LANE_DATA2 | DSIM_LANE_DATA3;
+ break;
+ default:
+ dev_info(dsim->dev, "data lane is invalid\n");
+ return -1;
+ };
+
+ s5p_dsim_init_header_fifo(dsim);
+ s5p_dsim_dp_dn_swap(dsim_base, dsim->dsim_info->e_lane_swap);
+
+ /* enable only frame done interrupt */
+ /* s5p_dsim_clear_interrupt(dsim_base, AllDsimIntr); */
+ /* s5p_dsim_set_interrupt_mask(dsim->reg_base, AllDsimIntr, 1); */
+
+ return 0;
+}
+#endif
+
+static void s5p_dsim_set_display_mode(struct dsim_global *dsim,
+ struct dsim_lcd_config *main_lcd, struct dsim_lcd_config *sub_lcd)
+{
+ struct s3cfb_lcd *main_lcd_panel_info = NULL, *sub_lcd_panel_info = NULL;
+ struct s3cfb_lcd_timing *main_timing = NULL;
+ unsigned int dsim_base = dsim->reg_base;
+
+ if (main_lcd != NULL) {
+ if (main_lcd->lcd_panel_info != NULL) {
+ main_lcd_panel_info =
+ (struct s3cfb_lcd *) main_lcd->lcd_panel_info;
+
+ s5p_dsim_set_main_disp_resol(dsim_base,
+ main_lcd_panel_info->height,
+ main_lcd_panel_info->width);
+ } else
+ dev_warn(dsim->dev, "lcd panel info of main lcd is NULL\n");
+ } else {
+ dev_err(dsim->dev, "main lcd is NULL\n");
+ return;
+ }
+
+ /* in case of VIDEO MODE (RGB INTERFACE) */
+ if (dsim->dsim_lcd_info->e_interface == (u32)DSIM_VIDEO) {
+
+ main_timing = &main_lcd_panel_info->timing;
+ if (main_timing == NULL) {
+ dev_err(dsim->dev, "main_timing is NULL\n");
+ return;
+ }
+
+ s5p_dsim_set_main_disp_vporch(dsim_base,
+ main_timing->cmd_allow_len,
+ main_timing->stable_vfp, (u16) main_timing->v_bp);
+ s5p_dsim_set_main_disp_hporch(dsim_base,
+ main_timing->h_fp, (u16) main_timing->h_bp);
+ s5p_dsim_set_main_disp_sync_area(dsim_base,
+ main_timing->v_sw, (u16) main_timing->h_sw);
+
+ /* in case of COMMAND MODE (CPU or I80 INTERFACE) */
+ } else {
+ if (sub_lcd != NULL) {
+ if (sub_lcd->lcd_panel_info != NULL) {
+ sub_lcd_panel_info =
+ (struct s3cfb_lcd *)
+ sub_lcd->lcd_panel_info;
+
+ s5p_dsim_set_sub_disp_resol(dsim_base,
+ sub_lcd_panel_info->height,
+ sub_lcd_panel_info->width);
+ } else
+ dev_warn(dsim->dev, "lcd panel info of sub lcd is NULL\n");
+ }
+ }
+
+ s5p_dsim_display_config(dsim_base, dsim->dsim_lcd_info, NULL);
+}
+
+static int s5p_dsim_init_link(struct dsim_global *dsim)
+{
+ unsigned int time_out = 100;
+ unsigned int dsim_base = dsim->reg_base;
+
+ switch (dsim->state) {
+ case DSIM_STATE_RESET:
+ case DSIM_STATE_INIT:
+ s5p_dsim_init_fifo_pointer(dsim_base, 0x0);
+ usleep_range(10000, 10000);
+ s5p_dsim_init_fifo_pointer(dsim_base, 0x1f);
+
+ /* dsi configuration */
+ s5p_dsim_init_config(dsim_base, dsim->dsim_lcd_info, NULL, dsim->dsim_info);
+ s5p_dsim_enable_lane(dsim_base, DSIM_LANE_CLOCK, 1);
+ s5p_dsim_enable_lane(dsim_base, dsim->data_lane, 1);
+
+ /* set clock configuration */
+ s5p_dsim_set_clock(dsim, dsim->dsim_info->e_byte_clk, 1);
+ usleep_range(5000, 5000);
+ /* check clock and data lane state is stop state */
+ while (!(s5p_dsim_is_lane_state(dsim_base, DSIM_LANE_CLOCK) == DSIM_LANE_STATE_STOP) &&
+ !(s5p_dsim_is_lane_state(dsim_base, dsim->data_lane) == DSIM_LANE_STATE_STOP)) {
+ time_out--;
+ if (time_out == 0) {
+ dev_info(dsim->dev, "DSI Master state is not stop state!!!\n");
+ dev_info(dsim->dev, "Please check initialization process\n");
+
+ return DSIM_FALSE;
+ }
+ }
+
+ if (time_out != 0) {
+ /* dev_info(dsim->dev, "initialization of DSI Master is successful\n"); */
+ /* dev_info(dsim->dev, "DSI Master state is stop state\n"); */
+ }
+
+ dsim->state = DSIM_STATE_STOP;
+
+ /* BTA sequence counters */
+ s5p_dsim_set_stop_state_counter(dsim_base, dsim->dsim_info->stop_holding_cnt);
+ s5p_dsim_set_bta_timeout(dsim_base, dsim->dsim_info->bta_timeout);
+ s5p_dsim_set_lpdr_timeout(dsim_base, dsim->dsim_info->rx_timeout);
+
+ /* default LPDT by both cpu and lcd controller */
+ s5p_dsim_set_data_mode(dsim_base, DSIM_TRANSFER_BOTH, DSIM_STATE_STOP);
+
+ return DSIM_TRUE;
+ default:
+ dev_info(dsim->dev, "DSI Master is already init\n");
+
+ return DSIM_FALSE;
+ }
+}
+
+static unsigned char s5p_dsim_set_hs_enable(struct dsim_global *dsim)
+{
+ u8 ret = DSIM_FALSE;
+ unsigned int dsim_base = dsim->reg_base;
+
+ if (dsim->state == DSIM_STATE_STOP) {
+ if (dsim->e_clk_src != DSIM_EXT_CLK_BYPASS) {
+ dsim->state = DSIM_STATE_HSCLKEN;
+#if defined(CONFIG_FB_S5P_S6E63M0)
+ s5p_dsim_set_data_mode(dsim_base,
+ DSIM_TRANSFER_BYLCDC, DSIM_STATE_HSCLKEN);
+#else
+ s5p_dsim_set_data_mode(dsim_base,
+ DSIM_TRANSFER_BOTH, DSIM_STATE_HSCLKEN);
+#endif
+ s5p_dsim_enable_hs_clock(dsim_base, 1);
+
+ ret = DSIM_TRUE;
+ } else
+ dev_warn(dsim->dev, "clock source is external bypass\n");
+ } else
+ dev_warn(dsim->dev, "DSIM is not stop state\n");
+
+ return ret;
+}
+
+#if 0
+static unsigned char s5p_dsim_set_stopstate(struct dsim_global *dsim)
+{
+ u8 ret = DSIM_FALSE;
+ unsigned int dsim_base = dsim->reg_base;
+
+ if (dsim->state == DSIM_STATE_HSCLKEN) {
+ if (dsim->e_clk_src != DSIM_EXT_CLK_BYPASS) {
+ dsim->state = DSIM_STATE_STOP;
+ s5p_dsim_enable_hs_clock(dsim_base, 0);
+ ret = DSIM_TRUE;
+ } else
+ dev_warn(dsim->dev, "clock source is external bypass\n");
+ } else if (dsim->state == DSIM_STATE_ULPS) {
+ /* will be update for exiting ulps */
+ ret = DSIM_TRUE;
+ } else if (dsim->state == DSIM_STATE_STOP) {
+ dev_warn(dsim->dev, "DSIM is already stop state\n");
+ ret = DSIM_TRUE;
+ } else
+ dev_warn(dsim->dev, "DSIM is not stop state\n");
+
+ return ret;
+}
+#endif
+
+static unsigned char s5p_dsim_set_data_transfer_mode(struct dsim_global *dsim,
+ unsigned char data_path, unsigned char hs_enable)
+{
+ u8 ret = DSIM_FALSE;
+ unsigned int dsim_base = dsim->reg_base;
+
+ if (hs_enable) {
+ if (dsim->state == DSIM_STATE_HSCLKEN) {
+ s5p_dsim_set_data_mode(dsim_base, data_path, DSIM_STATE_HSCLKEN);
+ ret = DSIM_TRUE;
+ } else {
+ dev_err(dsim->dev, "HS Clock lane is not enabled\n");
+ ret = DSIM_FALSE;
+ }
+ } else {
+ if (dsim->state == DSIM_STATE_INIT || dsim->state == DSIM_STATE_ULPS) {
+ dev_err(dsim->dev, "DSI Master is not STOP or HSDT state\n");
+ ret = DSIM_FALSE;
+ } else {
+ s5p_dsim_set_data_mode(dsim_base, data_path, DSIM_STATE_STOP);
+ ret = DSIM_TRUE;
+ }
+ }
+
+ return ret;
+}
+
+int s5p_dsim_register_lcd_driver(struct mipi_lcd_driver *lcd_drv)
+{
+ struct mipi_lcd_info *lcd_info = NULL;
+ struct dsim_global *dsim = g_dsim;
+
+ lcd_info = kmalloc(sizeof(struct mipi_lcd_info), GFP_KERNEL);
+ if (lcd_info == NULL)
+ return -ENOMEM;
+
+ lcd_info->mipi_drv = kmalloc(sizeof(struct mipi_lcd_driver), GFP_KERNEL);
+ if (lcd_info->mipi_drv == NULL) {
+ kfree(lcd_info);
+ return -ENOMEM;
+ }
+
+ memcpy(lcd_info->mipi_drv, lcd_drv, sizeof(struct mipi_lcd_driver));
+
+ mutex_lock(&mipi_lock);
+ list_add_tail(&lcd_info->list, &lcd_info_list);
+ mutex_unlock(&mipi_lock);
+
+ dev_dbg(dsim->dev, "registered lcd panel driver(%s) to mipi-dsi driver\n", lcd_drv->name);
+
+ return 0;
+}
+
+static struct mipi_lcd_driver *scan_mipi_driver(struct dsim_global *dsim, const char *name)
+{
+ struct mipi_lcd_info *lcd_info;
+ struct mipi_lcd_driver *mipi_drv = NULL;
+
+ mutex_lock(&mipi_lock);
+
+ dev_dbg(dsim->dev, "find lcd panel driver(%s)\n", name);
+
+ list_for_each_entry(lcd_info, &lcd_info_list, list) {
+ mipi_drv = lcd_info->mipi_drv;
+
+ if ((strcmp(mipi_drv->name, name)) == 0) {
+ mutex_unlock(&mipi_lock);
+ dev_dbg(dsim->dev, "found!!!(%s)\n", mipi_drv->name);
+ return mipi_drv;
+ }
+ }
+
+ dev_warn(dsim->dev, "failed to find lcd panel driver(%s)\n", name);
+
+ mutex_unlock(&mipi_lock);
+
+ return NULL;
+}
+
+static void s5p_dsim_interrupt_mask_set(struct dsim_global *dsim)
+{
+ u32 int_stat;
+
+ int_stat = readl(dsim->reg_base + S5P_DSIM_INTMSK);
+
+ int_stat &= ~((0x01<<S5P_DSIM_INT_BTA) | (0x01<<S5P_DSIM_INT_RX_TIMEOUT) |
+ (0x01<<S5P_DSIM_INT_BTA_TIMEOUT) | (0x01 << S5P_DSIM_INT_RX_DONE) |
+ (0x01<<S5P_DSIM_INT_RX_TE) | (0x01<<S5P_DSIM_INT_RX_ACK) |
+ (0x01<<S5P_DSIM_INT_RX_ECC_ERR) | (0x01<<S5P_DSIM_IMT_RX_CRC_ERR) |
+ (0x01<<S5P_DSIM_INT_SFR_FIFO_EMPTY));
+
+ writel(int_stat, dsim->reg_base + S5P_DSIM_INTMSK);
+}
+
+int s5p_dsim_fifo_clear(void)
+{
+ int dsim_count = 0, ret;
+ struct dsim_global *dsim = g_dsim;
+
+ writel(SwRstRelease, dsim->reg_base + S5P_DSIM_INTSRC);
+
+ writel(DSIM_FUNCRST, dsim->reg_base + S5P_DSIM_SWRST);
+
+ do {
+ if (++dsim_count > 90000) {
+ dev_err(dsim->dev, "dsim fifo clear fail re_try dsim resume\n");
+ ret = 0;
+ break;
+ }
+
+ if (readl(dsim->reg_base + S5P_DSIM_INTSRC) & SwRstRelease) {
+ s5p_dsim_interrupt_mask_set(dsim);
+ ret = 1;
+ break;
+ }
+ } while (1);
+
+ return ret;
+}
+
+#if defined(CONFIG_S5P_DSIM_SWITCHABLE_DUAL_LCD)
+int s5p_dsim_get_panel_sel_value(void)
+{
+ /*Temporary provided*/
+ struct dsim_global *dsim = g_dsim;
+ unsigned int lcd_sel_pin = dsim->mipi_ddi_pd->lcd_sel_pin;
+ int sel;
+
+ if (!lcd_sel_pin) {
+ printk(KERN_ERR "lcd_sel_pin is NULL\n");
+ return -EINVAL;
+ }
+ sel = gpio_get_value(lcd_sel_pin);
+ dsim->panel_select = sel;
+
+ return sel;
+ /*Temporary provided*/
+}
+
+int s5p_dsim_get_lcd_sel_value(void)
+{
+ struct dsim_global *dsim = g_dsim;
+ unsigned int lcd_sel_pin = dsim->mipi_ddi_pd->lcd_sel_pin;
+ int sel;
+
+ if (!lcd_sel_pin) {
+ printk(KERN_ERR "lcd_sel_pin is NULL\n");
+ return -EINVAL;
+ }
+ sel = gpio_get_value(lcd_sel_pin);
+ dsim->panel_select = sel;
+
+ return sel;
+}
+
+int s5p_dsim_set_lcd_sel_value(unsigned int lcd_sel)
+{
+ struct dsim_global *dsim = g_dsim;
+ unsigned int lcd_sel_pin = dsim->mipi_ddi_pd->lcd_sel_pin;
+ int prev_lcd_sel = s5p_dsim_get_lcd_sel_value();
+
+ if (!lcd_sel_pin) {
+ printk(KERN_ERR "lcd_sel_pin is NULL\n");
+ return -EINVAL;
+ }
+
+ gpio_set_value(lcd_sel_pin, lcd_sel);
+ return prev_lcd_sel;
+}
+
+int s5p_dsim_toggle_lcd(void)
+{
+ struct dsim_global *dsim = g_dsim;
+ int prev_lcd_sel = s5p_dsim_get_lcd_sel_value();
+ int new_lcd_sel = prev_lcd_sel ^ 1;
+
+ if (prev_lcd_sel < 0) {
+ printk(KERN_ERR "previous lcd sel value is invalid\n");
+ return -EINVAL;
+ }
+
+ prev_lcd_sel = s5p_dsim_set_lcd_sel_value(new_lcd_sel);
+ if (dsim->mipi_ddi_pd->resume_complete) {
+ if (s5p_dsim_fifo_clear() == 0) {
+ s5p_dsim_early_suspend();
+ mdelay(10);
+ s5p_dsim_late_resume();
+ if (s5p_dsim_fifo_clear() == 0)
+ printk(KERN_ERR "dsim resume fail!!\n");
+ }
+ mdelay(10);
+ }
+ return 0;
+}
+
+int s5p_dsim_select_lcd(unsigned int lcd_sel)
+{
+ struct dsim_global *dsim = g_dsim;
+ int prev_lcd_sel;
+
+ if ((lcd_sel != DDI_MAIN_LCD) &&
+ (lcd_sel != DDI_SUB_LCD)) {
+ printk(KERN_ERR "lcd_sel(%d) is invalid value\n", lcd_sel);
+ return -EINVAL;
+ }
+ prev_lcd_sel = s5p_dsim_set_lcd_sel_value(lcd_sel);
+ if (dsim->mipi_ddi_pd->resume_complete) {
+ if (s5p_dsim_fifo_clear() == 0) {
+ s5p_dsim_early_suspend();
+ mdelay(10);
+ s5p_dsim_late_resume();
+ if (s5p_dsim_fifo_clear() == 0)
+ printk(KERN_ERR "dsim resume fail!!\n");
+ }
+ mdelay(10);
+ }
+ return 0;
+}
+#endif /* CONFIG_S5P_DSIM_SWITCHABLE_DUAL_LCD */
+
+#ifdef CONFIG_HAS_EARLYSUSPEND
+void s5p_dsim_early_suspend(void)
+{
+ u32 int_stat = 0;
+ pm_message_t state;
+ struct dsim_global *dsim = g_dsim;
+
+ dev_info(dsim->dev, "+%s\n", __func__);
+
+ if (dsim->mipi_ddi_pd->resume_complete == 0)
+ return;
+
+ dsim->mipi_ddi_pd->resume_complete = 0;
+ dsim->dsim_lcd_info->lcd_enabled = 0;
+
+ /* int_stat = readl(dsim->reg_base + S5P_DSIM_INTMSK); */
+
+ int_stat |= ((0x01<<S5P_DSIM_INT_BTA) | (0x01<<S5P_DSIM_INT_RX_TIMEOUT) |
+ (0x01<<S5P_DSIM_INT_BTA_TIMEOUT) | (0x01 << S5P_DSIM_INT_RX_DONE) |
+ (0x01<<S5P_DSIM_INT_RX_TE) | (0x01<<S5P_DSIM_INT_RX_ACK) |
+ (0x01<<S5P_DSIM_INT_RX_ECC_ERR) | (0x01<<S5P_DSIM_IMT_RX_CRC_ERR) |
+ (0x01<<S5P_DSIM_INT_SFR_FIFO_EMPTY) | (0x01 << S5P_DSIM_INT_MSK_FRAME_DONE));
+
+ writel(int_stat, dsim->reg_base + S5P_DSIM_INTMSK);
+
+ /* disable_irq(dsim->irq); */
+ state.event = PM_EVENT_SUSPEND;
+
+ if (dsim->mipi_drv->suspend)
+ dsim->mipi_drv->suspend(dsim->dev, state);
+
+ if (dsim->mipi_ddi_pd->lcd_power_on)
+ dsim->mipi_ddi_pd->lcd_power_on(dsim->dev, 0);
+
+ s5p_dsim_enable_hs_clock(dsim->reg_base, 0);
+ s5p_dsim_set_clock(dsim, dsim->dsim_info->e_byte_clk, 0);
+
+#if defined(CONFIG_CPU_EXYNOS4210)
+ writel(0x1, dsim->reg_base + S5P_DSIM_SWRST);
+#endif
+
+ if (dsim->pd->exit_d_phy)
+ dsim->pd->exit_d_phy(dsim->reg_base);
+
+ clk_disable(dsim->clock);
+
+ if (dsim->pd->mipi_power)
+ dsim->pd->mipi_power(0);
+
+ dev_info(dsim->dev, "-%s\n", __func__);
+
+ return;
+}
+
+void s5p_dsim_late_resume(void)
+{
+ struct dsim_global *dsim = g_dsim;
+
+ dev_info(dsim->dev, "+%s\n", __func__);
+
+ /* MIPI SIGNAL ON */
+ if (dsim->pd->mipi_power)
+ dsim->pd->mipi_power(1);
+
+ clk_enable(dsim->clock);
+ usleep_range(10000, 10000);
+
+ if (dsim->mipi_ddi_pd->lcd_power_on)
+ dsim->mipi_ddi_pd->lcd_power_on(dsim->dev, 1);
+ usleep_range(25000, 25000);
+
+ if (dsim->mipi_ddi_pd->lcd_reset)
+ dsim->mipi_ddi_pd->lcd_reset();
+ usleep_range(5000, 5000);
+
+ s5p_dsim_late_resume_init_dsim(dsim);
+ s5p_dsim_init_link(dsim);
+ usleep_range(10000, 10000);
+ s5p_dsim_set_hs_enable(dsim);
+#if defined(CONFIG_FB_S5P_S6E63M0)
+ s5p_dsim_set_data_transfer_mode(dsim, DSIM_TRANSFER_BYCPU, 0);
+#else
+ s5p_dsim_set_data_transfer_mode(dsim, DSIM_TRANSFER_BYCPU, 1);
+#endif
+ s5p_dsim_set_display_mode(dsim, dsim->dsim_lcd_info, NULL);
+ s5p_dsim_set_data_transfer_mode(dsim, DSIM_TRANSFER_BYLCDC, 1);
+ /* s5p_dsim_set_interrupt_mask(dsim->reg_base, AllDsimIntr, 0); */
+
+ dsim->mipi_ddi_pd->resume_complete = 1;
+
+ dev_info(dsim->dev, "-%s\n", __func__);
+
+ return;
+}
+
+#else
+#ifdef CONFIG_PM
+static int s5p_dsim_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ struct dsim_global *dsim = platform_get_drvdata(pdev);
+
+ dev_info(&pdev->dev, "%s\n", __func__);
+
+ dsim->mipi_ddi_pd->resume_complete = 0;
+
+ if (dsim->mipi_drv->suspend)
+ dsim->mipi_drv->suspend(&pdev->dev, state);
+ else
+ dev_warn(&pdev->dev, "suspend func is null\n");
+
+ clk_disable(dsim->clock);
+
+ if (dsim->pd->mipi_power)
+ dsim->pd->mipi_power(0);
+ else
+ dev_warn(&pdev->dev, "mipi_power func is null\n");
+
+ return 0;
+}
+
+static int s5p_dsim_resume(struct platform_device *pdev)
+{
+ u32 int_stat;
+
+ struct dsim_global *dsim = platform_get_drvdata(pdev);
+
+ dev_info(&pdev->dev, "%s\n", __func__);
+
+ if (dsim->pd->mipi_power)
+ dsim->pd->mipi_power(1);
+ else
+ dev_warn(&pdev->dev, "mipi_power func is null\n");
+
+ usleep_range(10000, 10000);
+
+ clk_enable(dsim->clock);
+
+ if (dsim->mipi_drv->resume)
+ dsim->mipi_drv->resume(&pdev->dev);
+ else
+ dev_warn(&pdev->dev, "resume func is null\n");
+
+ s5p_dsim_init_dsim(dsim);
+ s5p_dsim_init_link(dsim);
+
+ s5p_dsim_set_hs_enable(dsim);
+ s5p_dsim_set_data_transfer_mode(dsim, DSIM_TRANSFER_BYCPU, 1);
+
+ msleep(120);
+
+ /* initialize lcd panel */
+ if (dsim->mipi_drv->init)
+ dsim->mipi_drv->init(&pdev->dev);
+ else
+ dev_warn(&pdev->dev, "init func is null\n");
+
+ s5p_dsim_set_display_mode(dsim, dsim->dsim_lcd_info, NULL);
+
+ s5p_dsim_set_data_transfer_mode(dsim, DSIM_TRANSFER_BYLCDC, 1);
+
+ int_stat = readl(dsim->reg_base + S5P_DSIM_INTMSK);
+
+ int_stat &= ~((0x01<<S5P_DSIM_INT_BTA) | (0x01<<S5P_DSIM_INT_RX_TIMEOUT) |
+ (0x01<<S5P_DSIM_INT_BTA_TIMEOUT) | (0x01 << S5P_DSIM_INT_RX_DONE) |
+ (0x01<<S5P_DSIM_INT_RX_TE) | (0x01<<S5P_DSIM_INT_RX_ACK) |
+ (0x01<<S5P_DSIM_INT_RX_ECC_ERR) | (0x01<<S5P_DSIM_IMT_RX_CRC_ERR) |
+ (0x01<<S5P_DSIM_INT_SFR_FIFO_EMPTY));
+
+ writel(int_stat, dsim->reg_base + S5P_DSIM_INTMSK);
+
+ dsim->mipi_ddi_pd->resume_complete = 1;
+
+ return 0;
+}
+#else
+#define s5p_dsim_suspend NULL
+#define s5p_dsim_resume NULL
+#endif
+#endif
+
+u32 read_dsim_register(u32 num)
+{
+ struct dsim_global *dsim = g_dsim;
+
+ return readl(dsim->reg_base + (num*4));
+}
+
+static ssize_t hs_toggle_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ char temp[3];
+ struct dsim_global *dsim = container_of(dev, struct dsim_global, panel);
+
+ sprintf(temp, "%d\n", jiffies_to_msecs(dsim->dsim_info->hs_toggle));
+ strcpy(buf, temp);
+
+ return strlen(buf);
+}
+
+static int hs_toggle_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t size)
+{
+ int value;
+ int rc;
+ struct dsim_global *dsim = container_of(dev, struct dsim_global, panel);
+
+ rc = strict_strtoul(buf, (unsigned int)0, (unsigned long *)&value);
+ if (rc < 0)
+ return rc;
+ else {
+ dev_info(dev, "%s - %d, %d\n", __func__, jiffies_to_msecs(dsim->dsim_info->hs_toggle), value);
+
+ if (value == 1) {
+ dsim->dsim_info->hs_toggle = msecs_to_jiffies(3000);
+ s5p_dsim_frame_done_interrupt_enable(dsim, 1);
+ schedule_delayed_work(&dsim->check_hs_toggle_work, msecs_to_jiffies(120000));
+ } else {
+ dsim->dsim_info->hs_toggle = 0;
+ s5p_dsim_frame_done_interrupt_enable(dsim, 0);
+ cancel_delayed_work(&dsim->check_hs_toggle_work);
+ }
+ }
+ return size;
+}
+
+static DEVICE_ATTR(hs_toggle, 0644, hs_toggle_show, hs_toggle_store);
+
+static ssize_t dsim_dump_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ int reg_val, i;
+ char temp[50];
+ struct dsim_global *dsim = dev_get_drvdata(dev);
+
+ for (i = 0; i < 25; i++) {
+ reg_val = readl(dsim->reg_base + i*4);
+ sprintf(temp, "[DSIM]0x11C8_00%02X = 0x%08X\n", (i*4), reg_val);
+ strcat(buf, temp);
+ }
+
+ return strlen(buf);
+}
+static DEVICE_ATTR(dsim_dump, 0444, dsim_dump_show, NULL);
+
+static struct dsim_ops s5p_dsim_ops = {
+ .cmd_write = s5p_dsim_wr_data,
+ .cmd_read = s5p_dsim_rd_data,
+ .suspend = s5p_dsim_early_suspend,
+ .resume = s5p_dsim_late_resume,
+};
+
+static int s5p_dsim_probe(struct platform_device *pdev)
+{
+ struct resource *res;
+ int ret = -1;
+ u32 int_stat;
+ struct dsim_global *dsim;
+
+ dsim = kzalloc(sizeof(struct dsim_global), GFP_KERNEL);
+ if (!dsim) {
+ pr_err("failed to allocate for dsim_global\n");
+ ret = -ENOMEM;
+ goto err_alloc;
+ }
+
+ g_dsim = dsim;
+
+ dsim->pd = to_dsim_plat(&pdev->dev);
+ if (!dsim->pd) {
+ dev_err(&pdev->dev, "platform data is NULL\n");
+ return -EINVAL;
+ }
+
+ dsim->dev = &pdev->dev;
+
+ /* set dsim config data, dsim lcd config data and lcd panel data. */
+ dsim->dsim_info = dsim->pd->dsim_info;
+ dsim->dsim_lcd_info = dsim->pd->dsim_lcd_info;
+ dsim->lcd_panel_info = (struct s3cfb_lcd *) dsim->dsim_lcd_info->lcd_panel_info;
+ dsim->mipi_ddi_pd = (struct mipi_ddi_platform_data *) dsim->dsim_lcd_info->mipi_ddi_pd;
+ dsim->mipi_ddi_pd->te_irq = dsim->pd->te_irq;
+
+ if (dsim->pd->mipi_power)
+ dsim->pd->mipi_power(1);
+
+ strcpy(dsim->pd->lcd_panel_name, dsim->lcd_panel_info->name);
+
+ /* clock */
+ dsim->clock = clk_get(&pdev->dev, dsim->pd->clk_name);
+ if (IS_ERR(dsim->clock)) {
+ dev_err(&pdev->dev, "failed to get dsim clock source\n");
+ return -EINVAL;
+ }
+
+ clk_enable(dsim->clock);
+
+ /* io memory */
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(&pdev->dev, "failed to get io memory region\n");
+ ret = -EINVAL;
+ goto err_clk_disable;
+ }
+
+ /* request mem region */
+ res = request_mem_region(res->start,
+ res->end - res->start + 1, pdev->name);
+ if (!res) {
+ dev_err(&pdev->dev, "failed to request io memory region\n");
+ ret = -EINVAL;
+ goto err_clk_disable;
+ }
+
+ /* ioremap for register block */
+ dsim->reg_base = (unsigned int)ioremap(res->start,
+ res->end - res->start + 1);
+ if (!dsim->reg_base) {
+ dev_err(&pdev->dev, "failed to remap io region\n");
+ ret = -EINVAL;
+ goto err_clk_disable;
+ }
+
+ res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+ if (!res) {
+ dev_err(&pdev->dev, "failed to request dsim irq resource\n");
+ ret = -EINVAL;
+ goto err_clk_disable;
+ }
+ /* dsim->irq = res->start; */
+
+ /* clear interrupt */
+ /* int_stat = readl(dsim->reg_base + S5P_DSIM_INTSRC); */
+ int_stat = 0xffffffff;
+ writel(int_stat, dsim->reg_base + S5P_DSIM_INTSRC);
+
+ /* enable interrupts */
+ int_stat = readl(dsim->reg_base + S5P_DSIM_INTMSK);
+
+ int_stat &= ~((0x01<<S5P_DSIM_INT_BTA) | (0x01<<S5P_DSIM_INT_RX_TIMEOUT) |
+ (0x01<<S5P_DSIM_INT_BTA_TIMEOUT) | (0x01 << S5P_DSIM_INT_RX_DONE) |
+ (0x01<<S5P_DSIM_INT_RX_TE) | (0x01<<S5P_DSIM_INT_RX_ACK) |
+ (0x01<<S5P_DSIM_INT_RX_ECC_ERR) | (0x01<<S5P_DSIM_IMT_RX_CRC_ERR) |
+ (0x01<<S5P_DSIM_INT_SFR_FIFO_EMPTY));
+
+ writel(int_stat, dsim->reg_base + S5P_DSIM_INTMSK);
+
+ init_completion(&dsim_rd_comp);
+ init_completion(&dsim_wr_comp);
+ mutex_init(&dsim_rd_wr_mutex);
+
+ dsim->mipi_ddi_pd->resume_complete = 1;
+ dsim->dsim_lcd_info->lcd_enabled = 1;
+
+ ret = request_irq(res->start, (void *)s5p_dsim_isr, IRQF_DISABLED, pdev->name, dsim);
+ if (ret != 0) {
+ dev_err(&pdev->dev, "failed to request dsim irq\n");
+ ret = -EINVAL;
+ goto err_clk_disable;
+ }
+
+ INIT_DELAYED_WORK(&dsim->dsim_work, dsim_work_q_handler);
+ INIT_DELAYED_WORK(&dsim->check_hs_toggle_work, dsim_check_hs_toggle_work_q_handler);
+
+ dsim->ops = &s5p_dsim_ops;
+
+ platform_set_drvdata(pdev, dsim);
+
+ dsim->panel.parent = &pdev->dev;
+ dev_set_name(&dsim->panel, dsim->pd->lcd_panel_name);
+ ret = device_register(&dsim->panel);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "faild device register\n");
+ ret = -EINVAL;
+ goto mipi_drv_err;
+ }
+
+ /* find lcd panel driver registered to mipi-dsi driver. */
+ dsim->mipi_drv = scan_mipi_driver(dsim, dsim->pd->lcd_panel_name);
+ if (dsim->mipi_drv == NULL) {
+ dev_err(&pdev->dev, "mipi_drv is NULL\n");
+ ret = -EINVAL;
+ goto mipi_drv_err;
+ }
+
+ ret = dsim->mipi_drv->probe(&dsim->panel);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "faild probe\n");
+ ret = -EINVAL;
+ goto mipi_drv_err;
+ }
+
+ dsim->state = DSIM_STATE_HSCLKEN;
+
+ ret = device_create_file(&(pdev->dev), &dev_attr_dsim_dump);
+ if (ret < 0)
+ dev_err(&pdev->dev, "failed to add sysfs entries, %d\n", __LINE__);
+
+ if (!dsim->dsim_info->hs_toggle) {
+ ret = device_create_file(&dsim->panel, &dev_attr_hs_toggle);
+ if (ret < 0)
+ dev_err(&pdev->dev, "failed to add sysfs entries, %d\n", __LINE__);
+ }
+
+ if (dsim->dsim_info->hs_toggle)
+ s5p_dsim_frame_done_interrupt_enable(dsim, 1);
+
+ dev_info(&pdev->dev, "mipi-dsi driver has been probed\n");
+
+#if 0
+#ifdef CONFIG_HAS_WAKELOCK
+#ifdef CONFIG_HAS_EARLYSUSPEND
+ dsim->early_suspend.suspend = s5p_dsim_early_suspend;
+ dsim->early_suspend.resume = s5p_dsim_late_resume;
+ dsim->early_suspend.level = EARLY_SUSPEND_LEVEL_DISABLE_FB - 1;
+ register_early_suspend(&dsim->early_suspend);
+#endif
+#endif
+#endif
+ return 0;
+
+mipi_drv_err:
+ free_irq(res->start, &dsim);
+ dsim->pd->mipi_power(0);
+
+err_clk_disable:
+ clk_disable(dsim->clock);
+err_alloc:
+ return ret;
+}
+
+static int s5p_dsim_remove(struct platform_device *pdev)
+{
+ return 0;
+}
+
+static struct platform_driver s5p_dsim_driver = {
+ .probe = s5p_dsim_probe,
+ .remove = s5p_dsim_remove,
+#ifndef CONFIG_HAS_EARLYSUSPEND
+ .suspend = s5p_dsim_suspend,
+ .resume = s5p_dsim_resume,
+#endif
+ .driver = {
+ .name = "s5p-dsim",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int s5p_dsim_register(void)
+{
+ return platform_driver_register(&s5p_dsim_driver);
+}
+
+static void s5p_dsim_unregister(void)
+{
+ platform_driver_unregister(&s5p_dsim_driver);
+}
+
+module_init(s5p_dsim_register);
+module_exit(s5p_dsim_unregister);
+
+MODULE_AUTHOR("InKi Dae <inki.dae@samsung.com>");
+MODULE_DESCRIPTION("Samusung MIPI-DSIM driver");
+MODULE_LICENSE("GPL");