summaryrefslogtreecommitdiff
path: root/drivers/video/fbdev/mxc/mipi_dsi_samsung.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/video/fbdev/mxc/mipi_dsi_samsung.c')
-rw-r--r--drivers/video/fbdev/mxc/mipi_dsi_samsung.c923
1 files changed, 923 insertions, 0 deletions
diff --git a/drivers/video/fbdev/mxc/mipi_dsi_samsung.c b/drivers/video/fbdev/mxc/mipi_dsi_samsung.c
new file mode 100644
index 000000000000..e5caebb5e355
--- /dev/null
+++ b/drivers/video/fbdev/mxc/mipi_dsi_samsung.c
@@ -0,0 +1,923 @@
+/*
+ * Copyright (C) 2015 Freescale Semiconductor, Inc. All Rights Reserved.
+ * Copyright 2017 NXP.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+
+ * 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.
+
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <linux/types.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/err.h>
+#include <linux/clk.h>
+#include <linux/console.h>
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/bitops.h>
+#include <linux/mipi_dsi_samsung.h>
+#include <linux/module.h>
+#include <linux/mxcfb.h>
+#include <linux/pm_runtime.h>
+#include <linux/busfreq-imx.h>
+#include <linux/backlight.h>
+#include <linux/of_device.h>
+#include <linux/of_address.h>
+#include <linux/regulator/consumer.h>
+#include <linux/reset.h>
+#include <linux/spinlock.h>
+#include <linux/delay.h>
+#include <video/mipi_display.h>
+#include <linux/mfd/syscon.h>
+
+#include "mipi_dsi.h"
+
+#define DISPDRV_MIPI "mipi_dsi_samsung"
+#define ROUND_UP(x) ((x)+1)
+#define NS2PS_RATIO (1000)
+#define MIPI_LCD_SLEEP_MODE_DELAY (120)
+#define MIPI_FIFO_TIMEOUT msecs_to_jiffies(250)
+
+static struct mipi_dsi_match_lcd mipi_dsi_lcd_db[] = {
+#ifdef CONFIG_FB_MXC_TRULY_WVGA_SYNC_PANEL
+ {
+ "TRULY-WVGA",
+ {mipid_hx8369_get_lcd_videomode, mipid_hx8369_lcd_setup}
+ },
+#endif
+#ifdef CONFIG_FB_MXC_TRULY_PANEL_TFT3P5079E
+ {
+ "TRULY-WVGA-TFT3P5079E",
+ {mipid_otm8018b_get_lcd_videomode, mipid_otm8018b_lcd_setup}
+ },
+#endif
+#ifdef CONFIG_FB_MXC_TRULY_PANEL_TFT3P5581E
+ {
+ "TRULY-WVGA-TFT3P5581E",
+ {mipid_hx8363_get_lcd_videomode, mipid_hx8363_lcd_setup}
+ },
+#endif
+ {
+ "", {NULL, NULL}
+ }
+};
+
+enum mipi_dsi_mode {
+ DSI_COMMAND_MODE,
+ DSI_VIDEO_MODE
+};
+
+enum mipi_dsi_trans_mode {
+ DSI_LP_MODE,
+ DSI_HS_MODE
+};
+
+static DECLARE_COMPLETION(dsi_rx_done);
+static DECLARE_COMPLETION(dsi_tx_done);
+
+static void mipi_dsi_set_mode(struct mipi_dsi_info *mipi_dsi,
+ enum mipi_dsi_trans_mode mode);
+
+static int mipi_dsi_lcd_init(struct mipi_dsi_info *mipi_dsi,
+ struct mxc_dispdrv_setting *setting)
+{
+ int i, size, err;
+ struct fb_videomode *mipi_lcd_modedb;
+ struct fb_videomode mode;
+ struct device *dev = &mipi_dsi->pdev->dev;
+
+ for (i = 0; i < ARRAY_SIZE(mipi_dsi_lcd_db); i++) {
+ if (!strcmp(mipi_dsi->lcd_panel,
+ mipi_dsi_lcd_db[i].lcd_panel)) {
+ mipi_dsi->lcd_callback =
+ &mipi_dsi_lcd_db[i].lcd_callback;
+ break;
+ }
+ }
+ if (i == ARRAY_SIZE(mipi_dsi_lcd_db)) {
+ dev_err(dev, "failed to find supported lcd panel.\n");
+ return -EINVAL;
+ }
+
+ /* set default bpp to 32 if not set*/
+ if (!setting->default_bpp)
+ setting->default_bpp = 32;
+
+ mipi_dsi->lcd_callback->get_mipi_lcd_videomode(&mipi_lcd_modedb, &size,
+ &mipi_dsi->lcd_config);
+
+ err = fb_find_mode(&setting->fbi->var, setting->fbi,
+ setting->dft_mode_str,
+ mipi_lcd_modedb, size, NULL,
+ setting->default_bpp);
+ if (err != 1)
+ fb_videomode_to_var(&setting->fbi->var, mipi_lcd_modedb);
+
+ INIT_LIST_HEAD(&setting->fbi->modelist);
+ for (i = 0; i < size; i++) {
+ fb_var_to_videomode(&mode, &setting->fbi->var);
+ if (fb_mode_is_equal(&mode, mipi_lcd_modedb + i)) {
+ err = fb_add_videomode(mipi_lcd_modedb + i,
+ &setting->fbi->modelist);
+ mipi_dsi->mode = mipi_lcd_modedb + i;
+ break;
+ }
+ }
+
+ if ((err < 0) || (size == i)) {
+ dev_err(dev, "failed to add videomode.\n");
+ return err;
+ }
+
+ return 0;
+}
+
+static void mipi_dsi_wr_tx_header(struct mipi_dsi_info *mipi_dsi,
+ u8 di, u8 data0, u8 data1)
+{
+ unsigned int reg;
+
+ reg = (data1 << 16) | (data0 << 8) | ((di & 0x3f) << 0);
+
+ writel(reg, mipi_dsi->mmio_base + MIPI_DSI_PKTHDR);
+}
+
+static void mipi_dsi_wr_tx_data(struct mipi_dsi_info *mipi_dsi,
+ unsigned int tx_data)
+{
+ writel(tx_data, mipi_dsi->mmio_base + MIPI_DSI_PAYLOAD);
+}
+
+static void mipi_dsi_long_data_wr(struct mipi_dsi_info *mipi_dsi,
+ const unsigned char *data0, unsigned int data_size)
+{
+ unsigned int data_cnt = 0, payload = 0;
+
+ /* in case that data count is more then 4 */
+ for (data_cnt = 0; data_cnt < data_size; data_cnt += 4) {
+ /*
+ * after sending 4bytes per one time,
+ * send remainder data less then 4.
+ */
+ if ((data_size - data_cnt) < 4) {
+ if ((data_size - data_cnt) == 3) {
+ payload = data0[data_cnt] |
+ data0[data_cnt + 1] << 8 |
+ data0[data_cnt + 2] << 16;
+ dev_dbg(&mipi_dsi->pdev->dev, "count = 3 payload = %x, %x %x %x\n",
+ payload, data0[data_cnt],
+ data0[data_cnt + 1],
+ data0[data_cnt + 2]);
+ } else if ((data_size - data_cnt) == 2) {
+ payload = data0[data_cnt] |
+ data0[data_cnt + 1] << 8;
+ dev_dbg(&mipi_dsi->pdev->dev,
+ "count = 2 payload = %x, %x %x\n", payload,
+ data0[data_cnt],
+ data0[data_cnt + 1]);
+ } else if ((data_size - data_cnt) == 1) {
+ payload = data0[data_cnt];
+ }
+
+ mipi_dsi_wr_tx_data(mipi_dsi, payload);
+ /* send 4bytes per one time. */
+ } else {
+ payload = data0[data_cnt] |
+ data0[data_cnt + 1] << 8 |
+ data0[data_cnt + 2] << 16 |
+ data0[data_cnt + 3] << 24;
+
+ dev_dbg(&mipi_dsi->pdev->dev,
+ "count = 4 payload = %x, %x %x %x %x\n",
+ payload, *(u8 *)(data0 + data_cnt),
+ data0[data_cnt + 1],
+ data0[data_cnt + 2],
+ data0[data_cnt + 3]);
+
+ mipi_dsi_wr_tx_data(mipi_dsi, payload);
+ }
+ }
+}
+
+static int mipi_dsi_pkt_write(struct mipi_dsi_info *mipi_dsi,
+ u8 data_type, const u32 *buf, int len)
+{
+ int ret = 0;
+ struct platform_device *pdev = mipi_dsi->pdev;
+ const unsigned char *data = (const unsigned char*)buf;
+
+ if (len == 0)
+ /* handle generic short write command */
+ mipi_dsi_wr_tx_header(mipi_dsi, data_type, data[0], data[1]);
+ else {
+ reinit_completion(&dsi_tx_done);
+
+ /* handle generic long write command */
+ mipi_dsi_long_data_wr(mipi_dsi, data, len);
+ mipi_dsi_wr_tx_header(mipi_dsi, data_type, len & 0xff, (len & 0xff00) >> 8);
+
+ ret = wait_for_completion_timeout(&dsi_tx_done, MIPI_FIFO_TIMEOUT);
+ if (!ret) {
+ dev_err(&pdev->dev, "wait tx done timeout!\n");
+ return -ETIMEDOUT;
+ }
+ }
+ mdelay(10);
+
+ return 0;
+}
+
+static void mipi_dsi_rd_tx_header(struct mipi_dsi_info *mipi_dsi,
+ u8 data_type, u8 data0)
+{
+ unsigned int reg = (data_type << 0) | (data0 << 8);
+
+ writel(reg, mipi_dsi->mmio_base + MIPI_DSI_PKTHDR);
+}
+
+static unsigned int mipi_dsi_rd_rx_fifo(struct mipi_dsi_info *mipi_dsi)
+{
+ return readl(mipi_dsi->mmio_base + MIPI_DSI_RXFIFO);
+}
+
+static int mipi_dsi_pkt_read(struct mipi_dsi_info *mipi_dsi,
+ u8 data_type, u32 *buf, int len)
+{
+ int ret;
+ struct platform_device *pdev = mipi_dsi->pdev;
+
+ if (len <= 4) {
+ reinit_completion(&dsi_rx_done);
+
+ mipi_dsi_rd_tx_header(mipi_dsi, data_type, buf[0]);
+
+ ret = wait_for_completion_timeout(&dsi_rx_done, MIPI_FIFO_TIMEOUT);
+ if (!ret) {
+ dev_err(&pdev->dev, "wait rx done timeout!\n");
+ return -ETIMEDOUT;
+ }
+
+ buf[0] = mipi_dsi_rd_rx_fifo(mipi_dsi);
+ buf[0] = buf[0] >> 8;
+ }
+ else {
+ /* TODO: add support later */
+ }
+
+ return 0;
+}
+
+int mipi_dsi_dcs_cmd(struct mipi_dsi_info *mipi_dsi,
+ u8 cmd, const u32 *param, int num)
+{
+ int err = 0;
+ u32 buf[DSI_CMD_BUF_MAXSIZE];
+
+ switch (cmd) {
+ case MIPI_DCS_EXIT_SLEEP_MODE:
+ case MIPI_DCS_ENTER_SLEEP_MODE:
+ case MIPI_DCS_SET_DISPLAY_ON:
+ case MIPI_DCS_SET_DISPLAY_OFF:
+ buf[0] = cmd;
+ err = mipi_dsi_pkt_write(mipi_dsi,
+ MIPI_DSI_DCS_SHORT_WRITE, buf, 0);
+ break;
+
+ default:
+ dev_err(&mipi_dsi->pdev->dev,
+ "MIPI DSI DCS Command:0x%x Not supported!\n", cmd);
+ break;
+ }
+
+ return err;
+}
+
+static void mipi_dsi_set_main_standby(struct mipi_dsi_info *mipi_dsi,
+ unsigned int enable)
+{
+ unsigned int reg;
+
+ reg = readl(mipi_dsi->mmio_base + MIPI_DSI_MDRESOL);
+
+ reg &= ~MIPI_DSI_MAIN_STANDBY(1);
+
+ if (enable)
+ reg |= MIPI_DSI_MAIN_STANDBY(1);
+
+ writel(reg, mipi_dsi->mmio_base + MIPI_DSI_MDRESOL);
+}
+
+static void mipi_dsi_power_off(struct mxc_dispdrv_handle *disp)
+{
+ int err;
+ struct mipi_dsi_info *mipi_dsi = mxc_dispdrv_getdata(disp);
+
+ err = mipi_dsi_dcs_cmd(mipi_dsi, MIPI_DCS_ENTER_SLEEP_MODE,
+ NULL, 0);
+ if (err) {
+ dev_err(&mipi_dsi->pdev->dev,
+ "MIPI DSI DCS Command display on error!\n");
+ }
+ msleep(MIPI_LCD_SLEEP_MODE_DELAY);
+
+ mipi_dsi_set_main_standby(mipi_dsi, 0);
+
+ clk_disable_unprepare(mipi_dsi->dphy_clk);
+ clk_disable_unprepare(mipi_dsi->cfg_clk);
+}
+
+static int mipi_dsi_lane_stop_state(struct mipi_dsi_info *mipi_dsi)
+{
+ unsigned int reg;
+
+ reg = readl(mipi_dsi->mmio_base + MIPI_DSI_STATUS);
+
+ if (((reg & MIPI_DSI_STOP_STATE_DAT(0x3)) == 0x3) &&
+ ((reg & MIPI_DSI_STOP_STATE_CLK(0x1)) ||
+ (reg & MIPI_DSI_TX_READY_HS_CLK(0x1))))
+ return 1;
+
+ return 0;
+}
+
+static void mipi_dsi_init_interrupt(struct mipi_dsi_info *mipi_dsi)
+{
+ unsigned int intsrc, intmsk;
+
+ intsrc = (INTSRC_SFR_PL_FIFO_EMPTY | INTSRC_RX_DATA_DONE);
+ writel(intsrc, mipi_dsi->mmio_base + MIPI_DSI_INTSRC);
+
+ intmsk = ~(INTMSK_SFR_PL_FIFO_EMPTY | INTMSK_RX_DATA_DONE);
+ writel(intmsk, mipi_dsi->mmio_base + MIPI_DSI_INTMSK);
+}
+
+static int mipi_dsi_master_init(struct mipi_dsi_info *mipi_dsi,
+ bool init)
+{
+ unsigned int time_out = 100;
+ unsigned int reg, byte_clk, esc_div;
+ struct fb_videomode *mode = mipi_dsi->mode;
+ struct device *dev = &mipi_dsi->pdev->dev;
+
+ /* configure DPHY PLL clock */
+ writel(MIPI_DSI_TX_REQUEST_HSCLK(0) |
+ MIPI_DSI_DPHY_SEL(0) |
+ MIPI_DSI_PLL_BYPASS(0) |
+ MIPI_DSI_BYTE_CLK_SRC(0),
+ mipi_dsi->mmio_base + MIPI_DSI_CLKCTRL);
+ if (!strcmp(mipi_dsi->lcd_panel, "TRULY-WVGA-TFT3P5581E"))
+ writel(MIPI_DSI_PLL_EN(1) | MIPI_DSI_PMS(0x3141),
+ mipi_dsi->mmio_base + MIPI_DSI_PLLCTRL);
+ else
+ writel(MIPI_DSI_PLL_EN(1) | MIPI_DSI_PMS(0x4190),
+ mipi_dsi->mmio_base + MIPI_DSI_PLLCTRL);
+
+ /* set PLLTMR: stable time */
+ writel(33024, mipi_dsi->mmio_base + MIPI_DSI_PLLTMR);
+ udelay(300);
+
+ /* configure byte clock */
+ reg = readl(mipi_dsi->mmio_base + MIPI_DSI_CLKCTRL);
+ reg |= MIPI_DSI_BYTE_CLK_EN(1);
+ byte_clk = 1500000000 / 8;
+ esc_div = DIV_ROUND_UP(byte_clk, 20 * 1000000);
+ reg |= (esc_div & 0xffff);
+ /* enable escape clock for clock lane and data lane0 and lane1 */
+ reg |= MIPI_DSI_LANE_ESC_CLK_EN(0x7);
+ reg |= MIPI_DSI_ESC_CLK_EN(1);
+ writel(reg, mipi_dsi->mmio_base + MIPI_DSI_CLKCTRL);
+
+ /* set main display resolution */
+ writel(MIPI_DSI_MAIN_HRESOL(mode->xres) |
+ MIPI_DSI_MAIN_VRESOL(mode->yres) |
+ MIPI_DSI_MAIN_STANDBY(0),
+ mipi_dsi->mmio_base + MIPI_DSI_MDRESOL);
+
+ /* set config register */
+ writel(MIPI_DSI_MFLUSH_VS(1) |
+ MIPI_DSI_SYNC_IN_FORM(0) |
+ MIPI_DSI_BURST_MODE(1) |
+ MIPI_DSI_VIDEO_MODE(1) |
+ MIPI_DSI_AUTO_MODE(0) |
+ MIPI_DSI_HSE_DISABLE_MODE(0) |
+ MIPI_DSI_HFP_DISABLE_MODE(0) |
+ MIPI_DSI_HBP_DISABLE_MODE(0) |
+ MIPI_DSI_HSA_DISABLE_MODE(0) |
+ MIPI_DSI_MAIN_VC(0) |
+ MIPI_DSI_SUB_VC(1) |
+ MIPI_DSI_MAIN_PIX_FORMAT(0x7) |
+ MIPI_DSI_SUB_PIX_FORMAT(0x7) |
+ MIPI_DSI_NUM_OF_DATALANE(0x1) |
+ MIPI_DSI_LANE_EN(0x7), /* enable data lane 0 and 1 */
+ mipi_dsi->mmio_base + MIPI_DSI_CONFIG);
+
+ /* set main display vporch */
+ writel(MIPI_DSI_CMDALLOW(0xf) |
+ MIPI_DSI_STABLE_VFP(mode->lower_margin) |
+ MIPI_DSI_MAIN_VBP(mode->upper_margin),
+ mipi_dsi->mmio_base + MIPI_DSI_MVPORCH);
+ /* set main display hporch */
+ writel(MIPI_DSI_MAIN_HFP(mode->right_margin) |
+ MIPI_DSI_MAIN_HBP(mode->left_margin),
+ mipi_dsi->mmio_base + MIPI_DSI_MHPORCH);
+ /* set main display sync */
+ writel(MIPI_DSI_MAIN_VSA(mode->vsync_len) |
+ MIPI_DSI_MAIN_HSA(mode->hsync_len),
+ mipi_dsi->mmio_base + MIPI_DSI_MSYNC);
+
+ /* configure d-phy timings */
+ if (!strcmp(mipi_dsi->lcd_panel, "TRULY-WVGA-TFT3P5581E")) {
+ writel(MIPI_DSI_M_TLPXCTL(2) | MIPI_DSI_M_THSEXITCTL(4),
+ mipi_dsi->mmio_base + MIPI_DSI_PHYTIMING);
+ writel(MIPI_DSI_M_TCLKPRPRCTL(5) |
+ MIPI_DSI_M_TCLKZEROCTL(14) |
+ MIPI_DSI_M_TCLKPOSTCTL(8) |
+ MIPI_DSI_M_TCLKTRAILCTL(3),
+ mipi_dsi->mmio_base + MIPI_DSI_PHYTIMING1);
+ writel(MIPI_DSI_M_THSPRPRCTL(3) |
+ MIPI_DSI_M_THSZEROCTL(3) |
+ MIPI_DSI_M_THSTRAILCTL(3),
+ mipi_dsi->mmio_base + MIPI_DSI_PHYTIMING2);
+ } else {
+ writel(MIPI_DSI_M_TLPXCTL(11) | MIPI_DSI_M_THSEXITCTL(18),
+ mipi_dsi->mmio_base + MIPI_DSI_PHYTIMING);
+ writel(MIPI_DSI_M_TCLKPRPRCTL(13) |
+ MIPI_DSI_M_TCLKZEROCTL(65) |
+ MIPI_DSI_M_TCLKPOSTCTL(17) |
+ MIPI_DSI_M_TCLKTRAILCTL(13),
+ mipi_dsi->mmio_base + MIPI_DSI_PHYTIMING1);
+ writel(MIPI_DSI_M_THSPRPRCTL(16) |
+ MIPI_DSI_M_THSZEROCTL(24) |
+ MIPI_DSI_M_THSTRAILCTL(16),
+ mipi_dsi->mmio_base + MIPI_DSI_PHYTIMING2);
+ }
+
+ writel(0xf000f, mipi_dsi->mmio_base + MIPI_DSI_TIMEOUT);
+
+ /* Init FIFO */
+ writel(0x0, mipi_dsi->mmio_base + MIPI_DSI_FIFOCTRL);
+ udelay(300);
+ writel(0x1f, mipi_dsi->mmio_base + MIPI_DSI_FIFOCTRL);
+
+ /* check clock and data lanes are in stop state
+ * which means dphy is in low power mode
+ */
+ while (!mipi_dsi_lane_stop_state(mipi_dsi)) {
+ time_out--;
+ if (time_out == 0) {
+ dev_err(dev, "MIPI DSI is not stop state.\n");
+ return -EINVAL;
+ }
+ }
+
+ /* transfer commands always in lp mode */
+ writel(MIPI_DSI_CMD_LPDT, mipi_dsi->mmio_base + MIPI_DSI_ESCMODE);
+
+ mipi_dsi_init_interrupt(mipi_dsi);
+
+ return 0;
+}
+
+static int mipi_dsi_disp_init(struct mxc_dispdrv_handle *disp,
+ struct mxc_dispdrv_setting *setting)
+{
+ struct mipi_dsi_info *mipi_dsi = mxc_dispdrv_getdata(disp);
+ struct device *dev = &mipi_dsi->pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct reset_control *reset = NULL;
+ int ret = 0;
+
+ reset = of_reset_control_get(np, NULL);
+ if (IS_ERR(reset))
+ return PTR_ERR(reset);
+
+ ret = mipi_dsi_lcd_init(mipi_dsi, setting);
+ if (ret) {
+ dev_err(dev, "failed to init mipi dsi lcd\n");
+ goto out;
+ }
+
+ dev_info(dev, "MIPI DSI dispdrv inited!\n");
+
+out:
+ reset_control_put(reset);
+ return ret;
+}
+
+static void mipi_dsi_disp_deinit(struct mxc_dispdrv_handle *disp)
+{
+ struct mipi_dsi_info *mipi_dsi;
+
+ mipi_dsi = mxc_dispdrv_getdata(disp);
+
+ mipi_dsi_power_off(mipi_dsi->disp_mipi);
+ if (mipi_dsi->bl)
+ backlight_device_unregister(mipi_dsi->bl);
+}
+
+static void mipi_dsi_set_mode(struct mipi_dsi_info *mipi_dsi,
+ enum mipi_dsi_trans_mode mode)
+{
+ unsigned int dsi_clkctrl;
+
+ dsi_clkctrl = readl(mipi_dsi->mmio_base + MIPI_DSI_CLKCTRL);
+
+ switch (mode) {
+ case DSI_LP_MODE:
+ dsi_clkctrl &= ~MIPI_DSI_TX_REQUEST_HSCLK(1);
+ break;
+ case DSI_HS_MODE:
+ dsi_clkctrl |= MIPI_DSI_TX_REQUEST_HSCLK(1);
+ break;
+ default:
+ dev_err(&mipi_dsi->pdev->dev,
+ "invalid dsi mode\n");
+ return;
+ }
+
+ writel(dsi_clkctrl, mipi_dsi->mmio_base + MIPI_DSI_CLKCTRL);
+ mdelay(1);
+}
+
+static int mipi_dsi_enable(struct mxc_dispdrv_handle *disp,
+ struct fb_info *fbi)
+{
+ int ret;
+ struct mipi_dsi_info *mipi_dsi = mxc_dispdrv_getdata(disp);
+
+ if (fbi->state == FBINFO_STATE_SUSPENDED) {
+ if (mipi_dsi->disp_power_on) {
+ ret = regulator_enable(mipi_dsi->disp_power_on);
+ if (ret) {
+ dev_err(&mipi_dsi->pdev->dev, "failed to enable display "
+ "power regulator, err = %d\n", ret);
+ return ret;
+ }
+ }
+ }
+
+ if (!mipi_dsi->dsi_power_on)
+ pm_runtime_get_sync(&mipi_dsi->pdev->dev);
+
+ ret = clk_prepare_enable(mipi_dsi->dphy_clk);
+ ret |= clk_prepare_enable(mipi_dsi->cfg_clk);
+ if (ret) {
+ dev_err(&mipi_dsi->pdev->dev,
+ "clk enable error:%d!\n", ret);
+ return -EINVAL;
+ }
+
+ if (!mipi_dsi->lcd_inited) {
+ ret = mipi_dsi_master_init(mipi_dsi, true);
+ if (ret)
+ return -EINVAL;
+
+ msleep(20);
+ ret = device_reset(&mipi_dsi->pdev->dev);
+ if (ret) {
+ dev_err(&mipi_dsi->pdev->dev, "failed to reset device: %d\n", ret);
+ return -EINVAL;
+ }
+ msleep(120);
+
+ /* the panel should be config under LP mode */
+ ret = mipi_dsi->lcd_callback->mipi_lcd_setup(mipi_dsi);
+ if (ret < 0) {
+ dev_err(&mipi_dsi->pdev->dev,
+ "failed to init mipi lcd.\n");
+ return ret ;
+ }
+ mipi_dsi->lcd_inited = 1;
+
+ /* change to HS mode for panel display */
+ mipi_dsi_set_mode(mipi_dsi, DSI_HS_MODE);
+ } else {
+ ret = mipi_dsi_dcs_cmd(mipi_dsi, MIPI_DCS_EXIT_SLEEP_MODE,
+ NULL, 0);
+ if (ret) {
+ dev_err(&mipi_dsi->pdev->dev,
+ "MIPI DSI DCS Command sleep-in error!\n");
+ }
+ msleep(MIPI_LCD_SLEEP_MODE_DELAY);
+ }
+
+ mipi_dsi_set_main_standby(mipi_dsi, 1);
+
+ return 0;
+}
+
+static void mipi_dsi_disable(struct mxc_dispdrv_handle *disp,
+ struct fb_info *fbi)
+{
+ struct mipi_dsi_info *mipi_dsi = mxc_dispdrv_getdata(disp);
+
+ mipi_dsi_power_off(mipi_dsi->disp_mipi);
+
+ if (fbi->state == FBINFO_STATE_SUSPENDED) {
+ if (mipi_dsi->dsi_power_on) {
+ pm_runtime_put_noidle(&mipi_dsi->pdev->dev);
+ pm_runtime_put_sync_suspend(&mipi_dsi->pdev->dev);
+ pm_runtime_get_noresume(&mipi_dsi->pdev->dev);
+ }
+
+ if (mipi_dsi->disp_power_on)
+ regulator_disable(mipi_dsi->disp_power_on);
+
+ mipi_dsi->lcd_inited = 0;
+ }
+}
+
+static int mipi_dsi_setup(struct mxc_dispdrv_handle *disp,
+ struct fb_info *fbi)
+{
+ struct mipi_dsi_info *mipi_dsi = mxc_dispdrv_getdata(disp);
+ int xres_virtual = fbi->var.xres_virtual;
+ int yres_virtual = fbi->var.yres_virtual;
+ int xoffset = fbi->var.xoffset;
+ int yoffset = fbi->var.yoffset;
+ int pixclock = fbi->var.pixclock;
+
+ if (!mipi_dsi->mode)
+ return 0;
+
+ /* set the mode back to var in case userspace changes it */
+ fb_videomode_to_var(&fbi->var, mipi_dsi->mode);
+
+ /* restore some var entries cached */
+ fbi->var.xres_virtual = xres_virtual;
+ fbi->var.yres_virtual = yres_virtual;
+ fbi->var.xoffset = xoffset;
+ fbi->var.yoffset = yoffset;
+ fbi->var.pixclock = pixclock;
+
+ return 0;
+}
+
+static struct mxc_dispdrv_driver mipi_dsi_drv = {
+ .name = DISPDRV_MIPI,
+ .init = mipi_dsi_disp_init,
+ .deinit = mipi_dsi_disp_deinit,
+ .enable = mipi_dsi_enable,
+ .disable = mipi_dsi_disable,
+ .setup = mipi_dsi_setup,
+};
+
+static const struct of_device_id imx_mipi_dsi_dt_ids[] = {
+ { .compatible = "fsl,imx7d-mipi-dsi", .data = NULL, },
+ { }
+};
+MODULE_DEVICE_TABLE(of, imx_mipi_dsi_dt_ids);
+
+static irqreturn_t mipi_dsi_irq_handler(int irq, void *data)
+{
+ unsigned int intsrc, intclr;
+ struct mipi_dsi_info *mipi_dsi = data;
+ struct platform_device *pdev = mipi_dsi->pdev;
+
+ intclr = 0;
+ intsrc = readl(mipi_dsi->mmio_base + MIPI_DSI_INTSRC);
+
+ dev_dbg(&pdev->dev, "intsrc = 0x%x\n", intsrc);
+
+ if (intsrc & INTSRC_SFR_PL_FIFO_EMPTY) {
+ dev_dbg(&pdev->dev, "playload tx finished\n");
+ intclr |= INTSRC_SFR_PL_FIFO_EMPTY;
+ complete(&dsi_tx_done);
+ }
+
+ if(intsrc & INTSRC_RX_DATA_DONE) {
+ dev_dbg(&pdev->dev, "rx data finished\n");
+ intclr |= INTSRC_RX_DATA_DONE;
+ complete(&dsi_rx_done);
+ }
+
+ /* clear the interrupts */
+ if (intclr)
+ writel(intclr, mipi_dsi->mmio_base + MIPI_DSI_INTSRC);
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * This function is called by the driver framework to initialize the MIPI DSI
+ * device.
+ *
+ * @param pdev The device structure for the MIPI DSI passed in by the
+ * driver framework.
+ *
+ * @return Returns 0 on success or negative error code on error
+ */
+static int mipi_dsi_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct mipi_dsi_info *mipi_dsi;
+ struct resource *res;
+ const char *lcd_panel;
+ int ret = 0;
+
+ mipi_dsi = devm_kzalloc(&pdev->dev, sizeof(*mipi_dsi), GFP_KERNEL);
+ if (!mipi_dsi)
+ return -ENOMEM;
+ mipi_dsi->pdev = pdev;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(&pdev->dev, "failed to get platform resource mem\n");
+ return -ENODEV;
+ }
+
+ if (!devm_request_mem_region(&pdev->dev, res->start,
+ resource_size(res), pdev->name))
+ return -EBUSY;
+
+ mipi_dsi->mmio_base = devm_ioremap(&pdev->dev, res->start,
+ resource_size(res));
+ if (!mipi_dsi->mmio_base)
+ return -ENOMEM;
+
+ mipi_dsi->irq = platform_get_irq(pdev, 0);
+ if (mipi_dsi->irq < 0) {
+ dev_err(&pdev->dev, "failed to get device irq\n");
+ return -EINVAL;
+ }
+
+ ret = devm_request_irq(&pdev->dev, mipi_dsi->irq,
+ mipi_dsi_irq_handler,
+ 0, "mipi_dsi_samsung", mipi_dsi);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to request mipi dsi irq\n");
+ return ret;
+ }
+
+ mipi_dsi->dphy_clk = devm_clk_get(&pdev->dev, "mipi_pllref_clk");
+ if (IS_ERR(mipi_dsi->dphy_clk)) {
+ dev_err(&pdev->dev, "failed to get dphy pll_ref_clk\n");
+ return PTR_ERR(mipi_dsi->dphy_clk);
+ }
+
+ mipi_dsi->cfg_clk = devm_clk_get(&pdev->dev, "mipi_cfg_clk");
+ if (IS_ERR(mipi_dsi->cfg_clk)) {
+ dev_err(&pdev->dev, "failed to get cfg_clk\n");
+ return PTR_ERR(mipi_dsi->cfg_clk);
+ }
+
+ ret = of_property_read_string(np, "lcd_panel", &lcd_panel);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to read lcd_panel property\n");
+ return ret;
+ }
+
+ mipi_dsi->disp_power_on = devm_regulator_get(&pdev->dev,
+ "disp-power-on");
+ if (!IS_ERR(mipi_dsi->disp_power_on)) {
+ ret = regulator_enable(mipi_dsi->disp_power_on);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to enable display "
+ "power regulator, err = %d\n", ret);
+ return ret;
+ }
+ }
+
+ mipi_dsi->lcd_panel = kstrdup(lcd_panel, GFP_KERNEL);
+ if (!mipi_dsi->lcd_panel) {
+ dev_err(&pdev->dev, "failed to allocate lcd panel name\n");
+ ret = -ENOMEM;
+ goto kstrdup_fail;
+ }
+
+ mipi_dsi->disp_mipi = mxc_dispdrv_register(&mipi_dsi_drv);
+ if (IS_ERR(mipi_dsi->disp_mipi)) {
+ dev_err(&pdev->dev, "mxc_dispdrv_register error\n");
+ ret = PTR_ERR(mipi_dsi->disp_mipi);
+ goto dispdrv_reg_fail;
+ }
+
+ mipi_dsi->mipi_dsi_pkt_read = mipi_dsi_pkt_read;
+ mipi_dsi->mipi_dsi_pkt_write = mipi_dsi_pkt_write;
+ mipi_dsi->mipi_dsi_dcs_cmd = mipi_dsi_dcs_cmd;
+
+ pm_runtime_enable(&pdev->dev);
+
+ mxc_dispdrv_setdata(mipi_dsi->disp_mipi, mipi_dsi);
+ dev_set_drvdata(&pdev->dev, mipi_dsi);
+
+ dev_info(&pdev->dev, "i.MX MIPI DSI driver probed\n");
+ return ret;
+
+dispdrv_reg_fail:
+ kfree(mipi_dsi->lcd_panel);
+kstrdup_fail:
+ if (mipi_dsi->disp_power_on)
+ regulator_disable(mipi_dsi->disp_power_on);
+
+ return ret;
+}
+
+static void mipi_dsi_shutdown(struct platform_device *pdev)
+{
+ struct mipi_dsi_info *mipi_dsi = dev_get_drvdata(&pdev->dev);
+
+ mipi_dsi_power_off(mipi_dsi->disp_mipi);
+}
+
+static int mipi_dsi_remove(struct platform_device *pdev)
+{
+ struct mipi_dsi_info *mipi_dsi = dev_get_drvdata(&pdev->dev);
+
+ mxc_dispdrv_puthandle(mipi_dsi->disp_mipi);
+ mxc_dispdrv_unregister(mipi_dsi->disp_mipi);
+
+ if (mipi_dsi->disp_power_on)
+ regulator_disable(mipi_dsi->disp_power_on);
+
+ kfree(mipi_dsi->lcd_panel);
+ dev_set_drvdata(&pdev->dev, NULL);
+
+ return 0;
+}
+
+static int mipi_dsi_runtime_suspend(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct mipi_dsi_info *mipi_dsi = dev_get_drvdata(&pdev->dev);
+
+ if (mipi_dsi->dsi_power_on) {
+ release_bus_freq(BUS_FREQ_HIGH);
+ dev_dbg(dev, "mipi dsi busfreq high release.\n");
+
+ mipi_dsi->dsi_power_on = 0;
+ }
+
+ return 0;
+}
+
+static int mipi_dsi_runtime_resume(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct mipi_dsi_info *mipi_dsi = dev_get_drvdata(&pdev->dev);
+
+ if (!mipi_dsi->dsi_power_on) {
+ request_bus_freq(BUS_FREQ_HIGH);
+ dev_dbg(dev, "mipi dsi busfreq high request.\n");
+
+ mipi_dsi->dsi_power_on = 1;
+ }
+
+ return 0;
+}
+
+static const struct dev_pm_ops mipi_dsi_pm_ops = {
+ .runtime_suspend = mipi_dsi_runtime_suspend,
+ .runtime_resume = mipi_dsi_runtime_resume,
+ .runtime_idle = NULL,
+};
+
+static struct platform_driver mipi_dsi_driver = {
+ .driver = {
+ .of_match_table = imx_mipi_dsi_dt_ids,
+ .name = "mxc_mipi_dsi_samsung",
+ .pm = &mipi_dsi_pm_ops,
+ },
+ .probe = mipi_dsi_probe,
+ .remove = mipi_dsi_remove,
+ .shutdown = mipi_dsi_shutdown,
+};
+
+static int __init mipi_dsi_init(void)
+{
+ int err;
+
+ err = platform_driver_register(&mipi_dsi_driver);
+ if (err) {
+ pr_err("mipi_dsi_driver register failed\n");
+ return err;
+ }
+
+ pr_debug("MIPI DSI driver module loaded: %s\n", mipi_dsi_driver.driver.name);
+
+ return 0;
+}
+
+static void __exit mipi_dsi_cleanup(void)
+{
+ platform_driver_unregister(&mipi_dsi_driver);
+}
+
+module_init(mipi_dsi_init);
+module_exit(mipi_dsi_cleanup);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("i.MX MIPI DSI driver");
+MODULE_LICENSE("GPL");