diff options
Diffstat (limited to 'drivers/video/fbdev/mxc/mipi_dsi_samsung.c')
-rw-r--r-- | drivers/video/fbdev/mxc/mipi_dsi_samsung.c | 923 |
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"); |