| /* |
| * 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 struct regulator *mipi_phy_reg; |
| static DECLARE_COMPLETION(dsi_rx_done); |
| static DECLARE_COMPLETION(dsi_tx_done); |
| |
| static void mipi_dsi_dphy_power_down(void); |
| 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 void mipi_dsi_dphy_power_on(struct platform_device *pdev) |
| { |
| int ret; |
| |
| regulator_set_voltage(mipi_phy_reg, 1000000, 1000000); |
| |
| ret = regulator_enable(mipi_phy_reg); |
| if (ret){ |
| dev_err(&pdev->dev, "failed to enable mipi phy regulatore\n"); |
| BUG_ON(1); |
| } |
| } |
| |
| static void mipi_dsi_dphy_power_down(void) |
| { |
| regulator_disable(mipi_phy_reg); |
| } |
| |
| 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_phy_reg = devm_regulator_get(&pdev->dev, "mipi-phy"); |
| if (IS_ERR(mipi_phy_reg)) { |
| dev_err(&pdev->dev, "mipi phy power supply not found\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); |
| mipi_dsi_dphy_power_down(); |
| } |
| |
| 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_dphy_power_down(); |
| 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_dphy_power_on(pdev); |
| 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"); |