| /* |
| * Copyright (C) 2011-2015 Freescale Semiconductor, Inc. All Rights Reserved. |
| */ |
| |
| /* |
| * The code contained herein is licensed under the GNU General Public |
| * License. You may obtain a copy of the GNU General Public License |
| * Version 2 or later at the following locations: |
| * |
| * http://www.opensource.org/licenses/gpl-license.html |
| * http://www.gnu.org/copyleft/gpl.html |
| */ |
| #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/bitops.h> |
| #include <linux/ipu.h> |
| #include <linux/mfd/syscon.h> |
| #include <linux/mfd/syscon/imx6q-iomuxc-gpr.h> |
| #include <linux/mipi_dsi.h> |
| #include <linux/module.h> |
| #include <linux/mxcfb.h> |
| #include <linux/backlight.h> |
| #include <linux/of_device.h> |
| #include <linux/of_gpio.h> |
| #include <linux/regulator/consumer.h> |
| #include <linux/spinlock.h> |
| #include <linux/delay.h> |
| #include <video/mipi_display.h> |
| |
| #include "mipi_dsi.h" |
| |
| #define DISPDRV_MIPI "mipi_dsi" |
| #define ROUND_UP(x) ((x)+1) |
| #define NS2PS_RATIO (1000) |
| #define NUMBER_OF_CHUNKS (0x8) |
| #define NULL_PKT_SIZE (0x8) |
| #define PHY_BTA_MAXTIME (0xd00) |
| #define PHY_LP2HS_MAXTIME (0x40) |
| #define PHY_HS2LP_MAXTIME (0x40) |
| #define PHY_STOP_WAIT_TIME (0x20) |
| #define DSI_CLKMGR_CFG_CLK_DIV (0x107) |
| #define DSI_GEN_PLD_DATA_BUF_ENTRY (0x10) |
| #define MIPI_MUX_CTRL(v) (((v) & 0x3) << 4) |
| #define MIPI_LCD_SLEEP_MODE_DELAY (120) |
| #define MIPI_DSI_REG_RW_TIMEOUT (20) |
| #define MIPI_DSI_PHY_TIMEOUT (10) |
| |
| 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 |
| { |
| "", {NULL, NULL} |
| } |
| }; |
| |
| struct _mipi_dsi_phy_pll_clk { |
| u32 max_phy_clk; |
| u32 config; |
| }; |
| |
| /* configure data for DPHY PLL 27M reference clk out */ |
| static const struct _mipi_dsi_phy_pll_clk mipi_dsi_phy_pll_clk_table[] = { |
| {1000, 0x74}, /* 950-1000MHz */ |
| {950, 0x54}, /* 900-950Mhz */ |
| {900, 0x34}, /* 850-900Mhz */ |
| {850, 0x14}, /* 800-850MHz */ |
| {800, 0x32}, /* 750-800MHz */ |
| {750, 0x12}, /* 700-750Mhz */ |
| {700, 0x30}, /* 650-700Mhz */ |
| {650, 0x10}, /* 600-650MHz */ |
| {600, 0x2e}, /* 550-600MHz */ |
| {550, 0x0e}, /* 500-550Mhz */ |
| {500, 0x2c}, /* 450-500Mhz */ |
| {450, 0x0c}, /* 400-450MHz */ |
| {400, 0x4a}, /* 360-400MHz */ |
| {360, 0x2a}, /* 330-360Mhz */ |
| {330, 0x48}, /* 300-330Mhz */ |
| {300, 0x28}, /* 270-300MHz */ |
| {270, 0x08}, /* 250-270MHz */ |
| {250, 0x46}, /* 240-250Mhz */ |
| {240, 0x26}, /* 210-240Mhz */ |
| {210, 0x06}, /* 200-210MHz */ |
| {200, 0x44}, /* 180-200MHz */ |
| {180, 0x24}, /* 160-180MHz */ |
| {160, 0x04}, /* 150-160MHz */ |
| }; |
| |
| static int valid_mode(int pixel_fmt) |
| { |
| return ((pixel_fmt == IPU_PIX_FMT_RGB24) || |
| (pixel_fmt == IPU_PIX_FMT_BGR24) || |
| (pixel_fmt == IPU_PIX_FMT_RGB666) || |
| (pixel_fmt == IPU_PIX_FMT_RGB565) || |
| (pixel_fmt == IPU_PIX_FMT_BGR666) || |
| (pixel_fmt == IPU_PIX_FMT_RGB332)); |
| } |
| |
| static inline void mipi_dsi_read_register(struct mipi_dsi_info *mipi_dsi, |
| u32 reg, u32 *val) |
| { |
| *val = ioread32(mipi_dsi->mmio_base + reg); |
| dev_dbg(&mipi_dsi->pdev->dev, "read_reg:0x%02x, val:0x%08x.\n", |
| reg, *val); |
| } |
| |
| static inline void mipi_dsi_write_register(struct mipi_dsi_info *mipi_dsi, |
| u32 reg, u32 val) |
| { |
| iowrite32(val, mipi_dsi->mmio_base + reg); |
| dev_dbg(&mipi_dsi->pdev->dev, "\t\twrite_reg:0x%02x, val:0x%08x.\n", |
| reg, val); |
| } |
| |
| static int mipi_dsi_pkt_write(struct mipi_dsi_info *mipi_dsi, |
| u8 data_type, const u32 *buf, int len) |
| { |
| u32 val; |
| u32 status = 0; |
| int write_len = len; |
| uint32_t timeout = 0; |
| |
| if (len) { |
| /* generic long write command */ |
| while (len / DSI_GEN_PLD_DATA_BUF_SIZE) { |
| mipi_dsi_write_register(mipi_dsi, |
| MIPI_DSI_GEN_PLD_DATA, *buf); |
| buf++; |
| len -= DSI_GEN_PLD_DATA_BUF_SIZE; |
| mipi_dsi_read_register(mipi_dsi, |
| MIPI_DSI_CMD_PKT_STATUS, &status); |
| while ((status & DSI_CMD_PKT_STATUS_GEN_PLD_W_FULL) == |
| DSI_CMD_PKT_STATUS_GEN_PLD_W_FULL) { |
| msleep(1); |
| timeout++; |
| if (timeout == MIPI_DSI_REG_RW_TIMEOUT) |
| return -EIO; |
| mipi_dsi_read_register(mipi_dsi, |
| MIPI_DSI_CMD_PKT_STATUS, &status); |
| } |
| } |
| /* write the remainder bytes */ |
| if (len > 0) { |
| while ((status & DSI_CMD_PKT_STATUS_GEN_PLD_W_FULL) == |
| DSI_CMD_PKT_STATUS_GEN_PLD_W_FULL) { |
| msleep(1); |
| timeout++; |
| if (timeout == MIPI_DSI_REG_RW_TIMEOUT) |
| return -EIO; |
| mipi_dsi_read_register(mipi_dsi, |
| MIPI_DSI_CMD_PKT_STATUS, &status); |
| } |
| mipi_dsi_write_register(mipi_dsi, |
| MIPI_DSI_GEN_PLD_DATA, *buf); |
| } |
| |
| val = data_type | ((write_len & DSI_GEN_HDR_DATA_MASK) |
| << DSI_GEN_HDR_DATA_SHIFT); |
| } else { |
| /* generic short write command */ |
| val = data_type | ((*buf & DSI_GEN_HDR_DATA_MASK) |
| << DSI_GEN_HDR_DATA_SHIFT); |
| } |
| |
| mipi_dsi_read_register(mipi_dsi, MIPI_DSI_CMD_PKT_STATUS, &status); |
| while ((status & DSI_CMD_PKT_STATUS_GEN_CMD_FULL) == |
| DSI_CMD_PKT_STATUS_GEN_CMD_FULL) { |
| msleep(1); |
| timeout++; |
| if (timeout == MIPI_DSI_REG_RW_TIMEOUT) |
| return -EIO; |
| mipi_dsi_read_register(mipi_dsi, MIPI_DSI_CMD_PKT_STATUS, |
| &status); |
| } |
| mipi_dsi_write_register(mipi_dsi, MIPI_DSI_GEN_HDR, val); |
| |
| mipi_dsi_read_register(mipi_dsi, MIPI_DSI_CMD_PKT_STATUS, &status); |
| while (!((status & DSI_CMD_PKT_STATUS_GEN_CMD_EMPTY) == |
| DSI_CMD_PKT_STATUS_GEN_CMD_EMPTY) || |
| !((status & DSI_CMD_PKT_STATUS_GEN_PLD_W_EMPTY) == |
| DSI_CMD_PKT_STATUS_GEN_PLD_W_EMPTY)) { |
| msleep(1); |
| timeout++; |
| if (timeout == MIPI_DSI_REG_RW_TIMEOUT) |
| return -EIO; |
| mipi_dsi_read_register(mipi_dsi, MIPI_DSI_CMD_PKT_STATUS, |
| &status); |
| } |
| |
| return 0; |
| } |
| |
| static int mipi_dsi_pkt_read(struct mipi_dsi_info *mipi_dsi, |
| u8 data_type, u32 *buf, int len) |
| { |
| u32 val; |
| int read_len = 0; |
| uint32_t timeout = 0; |
| |
| if (!len) { |
| mipi_dbg("%s, len = 0 invalid error!\n", __func__); |
| return -EINVAL; |
| } |
| |
| val = data_type | ((*buf & DSI_GEN_HDR_DATA_MASK) |
| << DSI_GEN_HDR_DATA_SHIFT); |
| memset(buf, 0, len); |
| mipi_dsi_write_register(mipi_dsi, MIPI_DSI_GEN_HDR, val); |
| |
| /* wait for cmd to sent out */ |
| mipi_dsi_read_register(mipi_dsi, MIPI_DSI_CMD_PKT_STATUS, &val); |
| while ((val & DSI_CMD_PKT_STATUS_GEN_RD_CMD_BUSY) != |
| DSI_CMD_PKT_STATUS_GEN_RD_CMD_BUSY) { |
| msleep(1); |
| timeout++; |
| if (timeout == MIPI_DSI_REG_RW_TIMEOUT) |
| return -EIO; |
| mipi_dsi_read_register(mipi_dsi, MIPI_DSI_CMD_PKT_STATUS, |
| &val); |
| } |
| /* wait for entire response stroed in FIFO */ |
| while ((val & DSI_CMD_PKT_STATUS_GEN_RD_CMD_BUSY) == |
| DSI_CMD_PKT_STATUS_GEN_RD_CMD_BUSY) { |
| msleep(1); |
| timeout++; |
| if (timeout == MIPI_DSI_REG_RW_TIMEOUT) |
| return -EIO; |
| mipi_dsi_read_register(mipi_dsi, MIPI_DSI_CMD_PKT_STATUS, |
| &val); |
| } |
| |
| mipi_dsi_read_register(mipi_dsi, MIPI_DSI_CMD_PKT_STATUS, &val); |
| while (!(val & DSI_CMD_PKT_STATUS_GEN_PLD_R_EMPTY)) { |
| mipi_dsi_read_register(mipi_dsi, MIPI_DSI_GEN_PLD_DATA, buf); |
| read_len += DSI_GEN_PLD_DATA_BUF_SIZE; |
| buf++; |
| mipi_dsi_read_register(mipi_dsi, MIPI_DSI_CMD_PKT_STATUS, |
| &val); |
| if (read_len == (DSI_GEN_PLD_DATA_BUF_ENTRY * |
| DSI_GEN_PLD_DATA_BUF_SIZE)) |
| break; |
| } |
| |
| if ((len <= read_len) && |
| ((len + DSI_GEN_PLD_DATA_BUF_SIZE) >= read_len)) |
| return 0; |
| else { |
| dev_err(&mipi_dsi->pdev->dev, |
| "actually read_len:%d != len:%d.\n", read_len, len); |
| return -ERANGE; |
| } |
| } |
| |
| static 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_dphy_init(struct mipi_dsi_info *mipi_dsi, |
| u32 cmd, u32 data) |
| { |
| u32 val; |
| u32 timeout = 0; |
| |
| mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PHY_IF_CTRL, |
| DSI_PHY_IF_CTRL_RESET); |
| mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PWR_UP, DSI_PWRUP_POWERUP); |
| |
| mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PHY_TST_CTRL0, 0); |
| mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PHY_TST_CTRL1, |
| (0x10000 | cmd)); |
| mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PHY_TST_CTRL0, 2); |
| mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PHY_TST_CTRL0, 0); |
| mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PHY_TST_CTRL1, (0 | data)); |
| mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PHY_TST_CTRL0, 2); |
| mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PHY_TST_CTRL0, 0); |
| val = DSI_PHY_RSTZ_EN_CLK | DSI_PHY_RSTZ_DISABLE_RST | |
| DSI_PHY_RSTZ_DISABLE_SHUTDOWN; |
| mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PHY_RSTZ, val); |
| |
| mipi_dsi_read_register(mipi_dsi, MIPI_DSI_PHY_STATUS, &val); |
| while ((val & DSI_PHY_STATUS_LOCK) != DSI_PHY_STATUS_LOCK) { |
| msleep(1); |
| timeout++; |
| if (timeout == MIPI_DSI_PHY_TIMEOUT) { |
| dev_err(&mipi_dsi->pdev->dev, |
| "Error: phy lock timeout!\n"); |
| break; |
| } |
| mipi_dsi_read_register(mipi_dsi, MIPI_DSI_PHY_STATUS, &val); |
| } |
| timeout = 0; |
| while ((val & DSI_PHY_STATUS_STOPSTATE_CLK_LANE) != |
| DSI_PHY_STATUS_STOPSTATE_CLK_LANE) { |
| msleep(1); |
| timeout++; |
| if (timeout == MIPI_DSI_PHY_TIMEOUT) { |
| dev_err(&mipi_dsi->pdev->dev, |
| "Error: phy lock lane timeout!\n"); |
| break; |
| } |
| mipi_dsi_read_register(mipi_dsi, MIPI_DSI_PHY_STATUS, &val); |
| } |
| } |
| |
| static void mipi_dsi_enable_controller(struct mipi_dsi_info *mipi_dsi, |
| bool init) |
| { |
| u32 val = 0; |
| u32 lane_byte_clk_period; |
| struct fb_videomode *mode = mipi_dsi->mode; |
| struct mipi_lcd_config *lcd_config = mipi_dsi->lcd_config; |
| |
| if (init) { |
| mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PWR_UP, |
| DSI_PWRUP_RESET); |
| mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PHY_RSTZ, |
| DSI_PHY_RSTZ_RST); |
| mipi_dsi_write_register(mipi_dsi, MIPI_DSI_CLKMGR_CFG, |
| DSI_CLKMGR_CFG_CLK_DIV); |
| |
| if (!(mode->sync & FB_SYNC_VERT_HIGH_ACT)) |
| val = DSI_DPI_CFG_VSYNC_ACT_LOW; |
| if (!(mode->sync & FB_SYNC_HOR_HIGH_ACT)) |
| val |= DSI_DPI_CFG_HSYNC_ACT_LOW; |
| if ((mode->sync & FB_SYNC_OE_LOW_ACT)) |
| val |= DSI_DPI_CFG_DATAEN_ACT_LOW; |
| if (MIPI_RGB666_LOOSELY == lcd_config->dpi_fmt) |
| val |= DSI_DPI_CFG_EN18LOOSELY; |
| val |= (lcd_config->dpi_fmt & DSI_DPI_CFG_COLORCODE_MASK) |
| << DSI_DPI_CFG_COLORCODE_SHIFT; |
| val |= (lcd_config->virtual_ch & DSI_DPI_CFG_VID_MASK) |
| << DSI_DPI_CFG_VID_SHIFT; |
| mipi_dsi_write_register(mipi_dsi, MIPI_DSI_DPI_CFG, val); |
| |
| val = DSI_PCKHDL_CFG_EN_BTA | |
| DSI_PCKHDL_CFG_EN_ECC_RX | |
| DSI_PCKHDL_CFG_EN_CRC_RX; |
| |
| mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PCKHDL_CFG, val); |
| |
| val = (mode->xres & DSI_VID_PKT_CFG_VID_PKT_SZ_MASK) |
| << DSI_VID_PKT_CFG_VID_PKT_SZ_SHIFT; |
| val |= (NUMBER_OF_CHUNKS & DSI_VID_PKT_CFG_NUM_CHUNKS_MASK) |
| << DSI_VID_PKT_CFG_NUM_CHUNKS_SHIFT; |
| val |= (NULL_PKT_SIZE & DSI_VID_PKT_CFG_NULL_PKT_SZ_MASK) |
| << DSI_VID_PKT_CFG_NULL_PKT_SZ_SHIFT; |
| mipi_dsi_write_register(mipi_dsi, MIPI_DSI_VID_PKT_CFG, val); |
| |
| /* enable LP mode when TX DCS cmd and enable DSI command mode */ |
| mipi_dsi_write_register(mipi_dsi, MIPI_DSI_CMD_MODE_CFG, |
| MIPI_DSI_CMD_MODE_CFG_EN_LOWPOWER); |
| |
| /* mipi lane byte clk period in ns unit */ |
| lane_byte_clk_period = NS2PS_RATIO / |
| (lcd_config->max_phy_clk / BITS_PER_BYTE); |
| val = ROUND_UP(mode->hsync_len * mode->pixclock / |
| NS2PS_RATIO / lane_byte_clk_period) |
| << DSI_TME_LINE_CFG_HSA_TIME_SHIFT; |
| val |= ROUND_UP(mode->left_margin * mode->pixclock / |
| NS2PS_RATIO / lane_byte_clk_period) |
| << DSI_TME_LINE_CFG_HBP_TIME_SHIFT; |
| val |= ROUND_UP((mode->left_margin + mode->right_margin + |
| mode->hsync_len + mode->xres) * mode->pixclock |
| / NS2PS_RATIO / lane_byte_clk_period) |
| << DSI_TME_LINE_CFG_HLINE_TIME_SHIFT; |
| mipi_dsi_write_register(mipi_dsi, MIPI_DSI_TMR_LINE_CFG, val); |
| |
| val = ((mode->vsync_len & DSI_VTIMING_CFG_VSA_LINES_MASK) |
| << DSI_VTIMING_CFG_VSA_LINES_SHIFT); |
| val |= ((mode->upper_margin & DSI_VTIMING_CFG_VBP_LINES_MASK) |
| << DSI_VTIMING_CFG_VBP_LINES_SHIFT); |
| val |= ((mode->lower_margin & DSI_VTIMING_CFG_VFP_LINES_MASK) |
| << DSI_VTIMING_CFG_VFP_LINES_SHIFT); |
| val |= ((mode->yres & DSI_VTIMING_CFG_V_ACT_LINES_MASK) |
| << DSI_VTIMING_CFG_V_ACT_LINES_SHIFT); |
| mipi_dsi_write_register(mipi_dsi, MIPI_DSI_VTIMING_CFG, val); |
| |
| val = ((PHY_BTA_MAXTIME & DSI_PHY_TMR_CFG_BTA_TIME_MASK) |
| << DSI_PHY_TMR_CFG_BTA_TIME_SHIFT); |
| val |= ((PHY_LP2HS_MAXTIME & DSI_PHY_TMR_CFG_LP2HS_TIME_MASK) |
| << DSI_PHY_TMR_CFG_LP2HS_TIME_SHIFT); |
| val |= ((PHY_HS2LP_MAXTIME & DSI_PHY_TMR_CFG_HS2LP_TIME_MASK) |
| << DSI_PHY_TMR_CFG_HS2LP_TIME_SHIFT); |
| mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PHY_TMR_CFG, val); |
| |
| val = (((lcd_config->data_lane_num - 1) & |
| DSI_PHY_IF_CFG_N_LANES_MASK) |
| << DSI_PHY_IF_CFG_N_LANES_SHIFT); |
| val |= ((PHY_STOP_WAIT_TIME & DSI_PHY_IF_CFG_WAIT_TIME_MASK) |
| << DSI_PHY_IF_CFG_WAIT_TIME_SHIFT); |
| mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PHY_IF_CFG, val); |
| |
| mipi_dsi_read_register(mipi_dsi, MIPI_DSI_ERROR_ST0, &val); |
| mipi_dsi_read_register(mipi_dsi, MIPI_DSI_ERROR_ST1, &val); |
| mipi_dsi_write_register(mipi_dsi, MIPI_DSI_ERROR_MSK0, 0); |
| mipi_dsi_write_register(mipi_dsi, MIPI_DSI_ERROR_MSK1, 0); |
| |
| mipi_dsi_dphy_init(mipi_dsi, DSI_PHY_CLK_INIT_COMMAND, |
| mipi_dsi->dphy_pll_config); |
| } else { |
| mipi_dsi_dphy_init(mipi_dsi, DSI_PHY_CLK_INIT_COMMAND, |
| mipi_dsi->dphy_pll_config); |
| } |
| } |
| |
| static void mipi_dsi_disable_controller(struct mipi_dsi_info *mipi_dsi) |
| { |
| mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PHY_IF_CTRL, |
| DSI_PHY_IF_CTRL_RESET); |
| mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PWR_UP, DSI_PWRUP_RESET); |
| mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PHY_RSTZ, DSI_PHY_RSTZ_RST); |
| } |
| |
| static irqreturn_t mipi_dsi_irq_handler(int irq, void *data) |
| { |
| u32 mask0; |
| u32 mask1; |
| u32 status0; |
| u32 status1; |
| struct mipi_dsi_info *mipi_dsi; |
| |
| mipi_dsi = (struct mipi_dsi_info *)data; |
| mipi_dsi_read_register(mipi_dsi, MIPI_DSI_ERROR_ST0, &status0); |
| mipi_dsi_read_register(mipi_dsi, MIPI_DSI_ERROR_ST1, &status1); |
| mipi_dsi_read_register(mipi_dsi, MIPI_DSI_ERROR_MSK0, &mask0); |
| mipi_dsi_read_register(mipi_dsi, MIPI_DSI_ERROR_MSK1, &mask1); |
| |
| if ((status0 & (~mask0)) || (status1 & (~mask1))) { |
| dev_err(&mipi_dsi->pdev->dev, |
| "mipi_dsi IRQ status0:0x%x, status1:0x%x!\n", |
| status0, status1); |
| } |
| |
| return IRQ_HANDLED; |
| } |
| |
| static inline void mipi_dsi_set_mode(struct mipi_dsi_info *mipi_dsi, |
| bool cmd_mode) |
| { |
| u32 val; |
| |
| if (cmd_mode) { |
| mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PWR_UP, |
| DSI_PWRUP_RESET); |
| mipi_dsi_read_register(mipi_dsi, MIPI_DSI_CMD_MODE_CFG, &val); |
| val |= MIPI_DSI_CMD_MODE_CFG_EN_CMD_MODE; |
| mipi_dsi_write_register(mipi_dsi, MIPI_DSI_CMD_MODE_CFG, val); |
| mipi_dsi_write_register(mipi_dsi, MIPI_DSI_VID_MODE_CFG, 0); |
| mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PWR_UP, |
| DSI_PWRUP_POWERUP); |
| } else { |
| mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PWR_UP, |
| DSI_PWRUP_RESET); |
| /* Disable Command mode when tranfering video data */ |
| mipi_dsi_read_register(mipi_dsi, MIPI_DSI_CMD_MODE_CFG, &val); |
| val &= ~MIPI_DSI_CMD_MODE_CFG_EN_CMD_MODE; |
| mipi_dsi_write_register(mipi_dsi, MIPI_DSI_CMD_MODE_CFG, val); |
| val = DSI_VID_MODE_CFG_EN | DSI_VID_MODE_CFG_EN_BURSTMODE | |
| DSI_VID_MODE_CFG_EN_LP_MODE; |
| mipi_dsi_write_register(mipi_dsi, MIPI_DSI_VID_MODE_CFG, val); |
| mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PWR_UP, |
| DSI_PWRUP_POWERUP); |
| mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PHY_IF_CTRL, |
| DSI_PHY_IF_CTRL_TX_REQ_CLK_HS); |
| } |
| } |
| |
| static int mipi_dsi_power_on(struct mxc_dispdrv_handle *disp) |
| { |
| int err; |
| struct mipi_dsi_info *mipi_dsi = mxc_dispdrv_getdata(disp); |
| |
| if (!mipi_dsi->dsi_power_on) { |
| clk_prepare_enable(mipi_dsi->dphy_clk); |
| clk_prepare_enable(mipi_dsi->cfg_clk); |
| mipi_dsi_enable_controller(mipi_dsi, false); |
| mipi_dsi_set_mode(mipi_dsi, false); |
| /* host send pclk/hsync/vsync for two frames before sleep-out */ |
| msleep((1000/mipi_dsi->mode->refresh + 1) << 1); |
| mipi_dsi_set_mode(mipi_dsi, true); |
| err = mipi_dsi_dcs_cmd(mipi_dsi, MIPI_DCS_EXIT_SLEEP_MODE, |
| NULL, 0); |
| if (err) { |
| dev_err(&mipi_dsi->pdev->dev, |
| "MIPI DSI DCS Command sleep-in error!\n"); |
| } |
| msleep(MIPI_LCD_SLEEP_MODE_DELAY); |
| mipi_dsi_set_mode(mipi_dsi, false); |
| mipi_dsi->dsi_power_on = 1; |
| } |
| |
| return 0; |
| } |
| |
| void mipi_dsi_power_off(struct mxc_dispdrv_handle *disp) |
| { |
| int err; |
| struct mipi_dsi_info *mipi_dsi = mxc_dispdrv_getdata(disp); |
| |
| if (mipi_dsi->dsi_power_on) { |
| mipi_dsi_set_mode(mipi_dsi, true); |
| 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"); |
| } |
| /* To allow time for the supply voltages |
| * and clock circuits to stabilize. |
| */ |
| msleep(5); |
| /* video stream timing on */ |
| mipi_dsi_set_mode(mipi_dsi, false); |
| msleep(MIPI_LCD_SLEEP_MODE_DELAY); |
| |
| mipi_dsi_set_mode(mipi_dsi, true); |
| mipi_dsi_disable_controller(mipi_dsi); |
| mipi_dsi->dsi_power_on = 0; |
| clk_disable_unprepare(mipi_dsi->dphy_clk); |
| clk_disable_unprepare(mipi_dsi->cfg_clk); |
| } |
| } |
| |
| static int mipi_dsi_lcd_init(struct mipi_dsi_info *mipi_dsi, |
| struct mxc_dispdrv_setting *setting) |
| { |
| int err; |
| int size; |
| int i; |
| 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; |
| } |
| /* get the videomode in the order: cmdline->platform data->driver */ |
| 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); |
| /* Note: only support fb mode from driver */ |
| mipi_dsi->mode = mipi_lcd_modedb + i; |
| break; |
| } |
| } |
| if ((err < 0) || (size == i)) { |
| dev_err(dev, "failed to add videomode.\n"); |
| return err; |
| } |
| |
| for (i = 0; i < ARRAY_SIZE(mipi_dsi_phy_pll_clk_table); i++) { |
| if (mipi_dsi_phy_pll_clk_table[i].max_phy_clk < |
| mipi_dsi->lcd_config->max_phy_clk) |
| break; |
| } |
| if ((i == ARRAY_SIZE(mipi_dsi_phy_pll_clk_table)) || |
| (mipi_dsi->lcd_config->max_phy_clk > |
| mipi_dsi_phy_pll_clk_table[0].max_phy_clk)) { |
| dev_err(dev, "failed to find data in" |
| "mipi_dsi_phy_pll_clk_table.\n"); |
| return -EINVAL; |
| } |
| mipi_dsi->dphy_pll_config = mipi_dsi_phy_pll_clk_table[--i].config; |
| dev_dbg(dev, "dphy_pll_config:0x%x.\n", mipi_dsi->dphy_pll_config); |
| |
| return 0; |
| } |
| |
| static int mipi_dsi_enable(struct mxc_dispdrv_handle *disp, |
| struct fb_info *fbi) |
| { |
| int err; |
| struct mipi_dsi_info *mipi_dsi = mxc_dispdrv_getdata(disp); |
| |
| if (!mipi_dsi->lcd_inited) { |
| err = clk_prepare_enable(mipi_dsi->dphy_clk); |
| err |= clk_prepare_enable(mipi_dsi->cfg_clk); |
| if (err) |
| dev_err(&mipi_dsi->pdev->dev, |
| "clk enable error:%d!\n", err); |
| mipi_dsi_enable_controller(mipi_dsi, true); |
| err = mipi_dsi->lcd_callback->mipi_lcd_setup( |
| mipi_dsi); |
| if (err < 0) { |
| dev_err(&mipi_dsi->pdev->dev, |
| "failed to init mipi lcd."); |
| clk_disable_unprepare(mipi_dsi->dphy_clk); |
| clk_disable_unprepare(mipi_dsi->cfg_clk); |
| return err; |
| } |
| mipi_dsi_set_mode(mipi_dsi, false); |
| mipi_dsi->dsi_power_on = 1; |
| mipi_dsi->lcd_inited = 1; |
| } |
| mipi_dsi_power_on(mipi_dsi->disp_mipi); |
| |
| 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); |
| } |
| |
| 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; |
| int ret = 0; |
| |
| if (!valid_mode(setting->if_fmt)) { |
| dev_warn(dev, "Input pixel format not valid" |
| "use default RGB24\n"); |
| setting->if_fmt = IPU_PIX_FMT_RGB24; |
| } |
| |
| ret = ipu_di_to_crtc(dev, mipi_dsi->dev_id, |
| mipi_dsi->disp_id, &setting->crtc); |
| if (ret < 0) |
| return ret; |
| |
| ret = mipi_dsi_lcd_init(mipi_dsi, setting); |
| if (ret) { |
| dev_err(dev, "failed to init mipi dsi lcd\n"); |
| return ret; |
| } |
| |
| dev_dbg(dev, "MIPI DSI dispdrv inited!\n"); |
| 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 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 int device_reset(struct device *dev) |
| { |
| struct device_node *np = dev->of_node; |
| enum of_gpio_flags flags; |
| unsigned long gpio_flags; |
| unsigned int gpio; |
| bool initially_in_reset; |
| bool active_low; |
| s32 delay_us; |
| int ret; |
| |
| gpio = of_get_named_gpio_flags(np, "reset-gpios", 0, &flags); |
| if (gpio == -EPROBE_DEFER) { |
| return gpio; |
| } else if (!gpio_is_valid(gpio)) { |
| dev_err(dev, "invalid reset gpio: %d\n", gpio); |
| return gpio; |
| } |
| |
| active_low = flags & OF_GPIO_ACTIVE_LOW; |
| |
| ret = of_property_read_u32(np, "reset-delay-us", &delay_us); |
| if (ret < 0 || delay_us < 0) { |
| dev_err(dev, "invalid reset delay\n"); |
| return -EINVAL; |
| } |
| |
| initially_in_reset = of_property_read_bool(np, "initially-in-reset"); |
| if (active_low ^ initially_in_reset) |
| gpio_flags = GPIOF_OUT_INIT_HIGH; |
| else |
| gpio_flags = GPIOF_OUT_INIT_LOW; |
| |
| ret = devm_gpio_request_one(dev, gpio, gpio_flags, NULL); |
| if (ret < 0) { |
| dev_err(dev, "failed to request gpio %d: %d\n", gpio, ret); |
| return ret; |
| } |
| |
| gpio_set_value_cansleep(gpio, active_low ? 0 : 1); |
| udelay(delay_us); |
| gpio_set_value_cansleep(gpio, active_low ? 1 : 0); |
| |
| return 0; |
| } |
| |
| static int imx6q_mipi_dsi_get_mux(int dev_id, int disp_id) |
| { |
| if (dev_id > 1 || disp_id > 1) |
| return -EINVAL; |
| |
| return (dev_id << 5) | (disp_id << 4); |
| } |
| |
| static struct mipi_dsi_bus_mux imx6q_mipi_dsi_mux[] = { |
| { |
| .reg = IOMUXC_GPR3, |
| .mask = IMX6Q_GPR3_MIPI_MUX_CTL_MASK, |
| .get_mux = imx6q_mipi_dsi_get_mux, |
| }, |
| }; |
| |
| static int imx6dl_mipi_dsi_get_mux(int dev_id, int disp_id) |
| { |
| if (dev_id > 1 || disp_id > 1) |
| return -EINVAL; |
| |
| /* MIPI DSI source is LCDIF */ |
| if (dev_id) |
| disp_id = 0; |
| |
| return (dev_id << 5) | (disp_id << 4); |
| } |
| |
| static struct mipi_dsi_bus_mux imx6dl_mipi_dsi_mux[] = { |
| { |
| .reg = IOMUXC_GPR3, |
| .mask = IMX6Q_GPR3_MIPI_MUX_CTL_MASK, |
| .get_mux = imx6dl_mipi_dsi_get_mux, |
| }, |
| }; |
| |
| static const struct of_device_id imx_mipi_dsi_dt_ids[] = { |
| { .compatible = "fsl,imx6q-mipi-dsi", .data = imx6q_mipi_dsi_mux, }, |
| { .compatible = "fsl,imx6dl-mipi-dsi", .data = imx6dl_mipi_dsi_mux, }, |
| { } |
| }; |
| MODULE_DEVICE_TABLE(of, imx_mipi_dsi_dt_ids); |
| |
| /** |
| * 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; |
| const struct of_device_id *of_id = |
| of_match_device(of_match_ptr(imx_mipi_dsi_dt_ids), |
| &pdev->dev); |
| struct mipi_dsi_info *mipi_dsi; |
| struct resource *res; |
| u32 dev_id, disp_id; |
| const char *lcd_panel; |
| int mux; |
| int ret = 0; |
| |
| if (!np) { |
| dev_err(&pdev->dev, "failed to find device tree node\n"); |
| return -ENODEV; |
| } |
| |
| mipi_dsi = devm_kzalloc(&pdev->dev, sizeof(*mipi_dsi), GFP_KERNEL); |
| if (!mipi_dsi) |
| return -ENOMEM; |
| |
| ret = of_property_read_string(np, "lcd_panel", &lcd_panel); |
| if (ret) { |
| dev_err(&pdev->dev, "failed to read of property lcd_panel\n"); |
| return ret; |
| } |
| |
| ret = of_property_read_u32(np, "dev_id", &dev_id); |
| if (ret) { |
| dev_err(&pdev->dev, "failed to read of property dev_id\n"); |
| return ret; |
| } |
| ret = of_property_read_u32(np, "disp_id", &disp_id); |
| if (ret) { |
| dev_err(&pdev->dev, "failed to read of property disp_id\n"); |
| return ret; |
| } |
| mipi_dsi->dev_id = dev_id; |
| mipi_dsi->disp_id = disp_id; |
| |
| res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
| if (!res) { |
| dev_err(&pdev->dev, "failed to get platform resource 0\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 -EBUSY; |
| |
| mipi_dsi->irq = platform_get_irq(pdev, 0); |
| if (mipi_dsi->irq < 0) { |
| dev_err(&pdev->dev, "failed get device irq\n"); |
| return -ENODEV; |
| } |
| |
| ret = devm_request_irq(&pdev->dev, mipi_dsi->irq, |
| mipi_dsi_irq_handler, |
| 0, "mipi_dsi", mipi_dsi); |
| if (ret) { |
| dev_err(&pdev->dev, "failed to request 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); |
| } |
| |
| 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; |
| } |
| } else { |
| mipi_dsi->disp_power_on = NULL; |
| } |
| |
| ret = device_reset(&pdev->dev); |
| if (ret) { |
| if (ret != -EPROBE_DEFER) |
| dev_err(&pdev->dev, "failed to reset: %d\n", ret); |
| goto dev_reset_fail; |
| } |
| |
| if (of_id) |
| mipi_dsi->bus_mux = of_id->data; |
| |
| mipi_dsi->regmap = syscon_regmap_lookup_by_phandle(np, "gpr"); |
| if (IS_ERR(mipi_dsi->regmap)) { |
| dev_err(&pdev->dev, "failed to get parent regmap\n"); |
| ret = PTR_ERR(mipi_dsi->regmap); |
| goto get_parent_regmap_fail; |
| } |
| |
| mux = mipi_dsi->bus_mux->get_mux(dev_id, disp_id); |
| if (mux >= 0) |
| regmap_update_bits(mipi_dsi->regmap, mipi_dsi->bus_mux->reg, |
| mipi_dsi->bus_mux->mask, (unsigned int)mux); |
| else |
| dev_warn(&pdev->dev, "invalid dev_id or disp_id muxing\n"); |
| |
| 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->pdev = pdev; |
| 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; |
| |
| 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: |
| get_parent_regmap_fail: |
| dev_reset_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 struct platform_driver mipi_dsi_driver = { |
| .driver = { |
| .of_match_table = imx_mipi_dsi_dt_ids, |
| .name = "mxc_mipi_dsi", |
| }, |
| .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 -ENODEV; |
| } |
| 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"); |