| /* |
| * Copyright 2018 NXP |
| * |
| * SPDX-License-Identifier: GPL-2.0+ |
| */ |
| |
| #include <common.h> |
| #include <dm.h> |
| #include <asm/io.h> |
| #include <linux/errno.h> |
| #include <asm/arch/clock.h> |
| #include <asm/arch/imx-regs.h> |
| #include <asm/arch/sys_proto.h> |
| #include <div64.h> |
| #include <sec_mipi_dsim.h> |
| #include <imx_mipi_dsi_bridge.h> |
| |
| #define MIPI_FIFO_TIMEOUT 250000 /* 250ms */ |
| |
| #define DRIVER_NAME "imx_sec_mipi_dsim" |
| |
| /* dsim registers */ |
| #define DSIM_VERSION 0x00 |
| #define DSIM_STATUS 0x04 |
| #define DSIM_RGB_STATUS 0x08 |
| #define DSIM_SWRST 0x0c |
| #define DSIM_CLKCTRL 0x10 |
| #define DSIM_TIMEOUT 0x14 |
| #define DSIM_CONFIG 0x18 |
| #define DSIM_ESCMODE 0x1c |
| #define DSIM_MDRESOL 0x20 |
| #define DSIM_MVPORCH 0x24 |
| #define DSIM_MHPORCH 0x28 |
| #define DSIM_MSYNC 0x2c |
| #define DSIM_SDRESOL 0x30 |
| #define DSIM_INTSRC 0x34 |
| #define DSIM_INTMSK 0x38 |
| |
| /* packet */ |
| #define DSIM_PKTHDR 0x3c |
| #define DSIM_PAYLOAD 0x40 |
| #define DSIM_RXFIFO 0x44 |
| #define DSIM_FIFOTHLD 0x48 |
| #define DSIM_FIFOCTRL 0x4c |
| #define DSIM_MEMACCHR 0x50 |
| #define DSIM_MULTI_PKT 0x78 |
| |
| /* pll control */ |
| #define DSIM_PLLCTRL_1G 0x90 |
| #define DSIM_PLLCTRL 0x94 |
| #define DSIM_PLLCTRL1 0x98 |
| #define DSIM_PLLCTRL2 0x9c |
| #define DSIM_PLLTMR 0xa0 |
| |
| /* dphy */ |
| #define DSIM_PHYTIMING 0xb4 |
| #define DSIM_PHYTIMING1 0xb8 |
| #define DSIM_PHYTIMING2 0xbc |
| |
| /* reg bit manipulation */ |
| #define REG_MASK(e, s) (((1 << ((e) - (s) + 1)) - 1) << (s)) |
| #define REG_PUT(x, e, s) (((x) << (s)) & REG_MASK(e, s)) |
| #define REG_GET(x, e, s) (((x) & REG_MASK(e, s)) >> (s)) |
| |
| /* register bit fields */ |
| #define STATUS_PLLSTABLE BIT(31) |
| #define STATUS_SWRSTRLS BIT(20) |
| #define STATUS_TXREADYHSCLK BIT(10) |
| #define STATUS_ULPSCLK BIT(9) |
| #define STATUS_STOPSTATECLK BIT(8) |
| #define STATUS_GET_ULPSDAT(x) REG_GET(x, 7, 4) |
| #define STATUS_GET_STOPSTATEDAT(x) REG_GET(x, 3, 0) |
| |
| #define RGB_STATUS_CMDMODE_INSEL BIT(31) |
| #define RGB_STATUS_GET_RGBSTATE(x) REG_GET(x, 12, 0) |
| |
| #define CLKCTRL_TXREQUESTHSCLK BIT(31) |
| #define CLKCTRL_DPHY_SEL_1G BIT(29) |
| #define CLKCTRL_DPHY_SEL_1P5G (0x0 << 29) |
| #define CLKCTRL_ESCCLKEN BIT(28) |
| #define CLKCTRL_PLLBYPASS BIT(29) |
| #define CLKCTRL_BYTECLKSRC_DPHY_PLL REG_PUT(0, 26, 25) |
| #define CLKCTRL_BYTECLKEN BIT(24) |
| #define CLKCTRL_SET_LANEESCCLKEN(x) REG_PUT(x, 23, 19) |
| #define CLKCTRL_SET_ESCPRESCALER(x) REG_PUT(x, 15, 0) |
| |
| #define TIMEOUT_SET_BTAOUT(x) REG_PUT(x, 23, 16) |
| #define TIMEOUT_SET_LPDRTOUT(x) REG_PUT(x, 15, 0) |
| |
| #define CONFIG_NON_CONTINOUS_CLOCK_LANE BIT(31) |
| #define CONFIG_CLKLANE_STOP_START BIT(30) |
| #define CONFIG_MFLUSH_VS BIT(29) |
| #define CONFIG_EOT_R03 BIT(28) |
| #define CONFIG_SYNCINFORM BIT(27) |
| #define CONFIG_BURSTMODE BIT(26) |
| #define CONFIG_VIDEOMODE BIT(25) |
| #define CONFIG_AUTOMODE BIT(24) |
| #define CONFIG_HSEDISABLEMODE BIT(23) |
| #define CONFIG_HFPDISABLEMODE BIT(22) |
| #define CONFIG_HBPDISABLEMODE BIT(21) |
| #define CONFIG_HSADISABLEMODE BIT(20) |
| #define CONFIG_SET_MAINVC(x) REG_PUT(x, 19, 18) |
| #define CONFIG_SET_SUBVC(x) REG_PUT(x, 17, 16) |
| #define CONFIG_SET_MAINPIXFORMAT(x) REG_PUT(x, 14, 12) |
| #define CONFIG_SET_SUBPIXFORMAT(x) REG_PUT(x, 10, 8) |
| #define CONFIG_SET_NUMOFDATLANE(x) REG_PUT(x, 6, 5) |
| #define CONFIG_SET_LANEEN(x) REG_PUT(x, 4, 0) |
| |
| #define MDRESOL_MAINSTANDBY BIT(31) |
| #define MDRESOL_SET_MAINVRESOL(x) REG_PUT(x, 27, 16) |
| #define MDRESOL_SET_MAINHRESOL(x) REG_PUT(x, 11, 0) |
| |
| #define MVPORCH_SET_CMDALLOW(x) REG_PUT(x, 31, 28) |
| #define MVPORCH_SET_STABLEVFP(x) REG_PUT(x, 26, 16) |
| #define MVPORCH_SET_MAINVBP(x) REG_PUT(x, 10, 0) |
| |
| #define MHPORCH_SET_MAINHFP(x) REG_PUT(x, 31, 16) |
| #define MHPORCH_SET_MAINHBP(x) REG_PUT(x, 15, 0) |
| |
| #define MSYNC_SET_MAINVSA(x) REG_PUT(x, 31, 22) |
| #define MSYNC_SET_MAINHSA(x) REG_PUT(x, 15, 0) |
| |
| #define INTSRC_PLLSTABLE BIT(31) |
| #define INTSRC_SWRSTRELEASE BIT(30) |
| #define INTSRC_SFRPLFIFOEMPTY BIT(29) |
| #define INTSRC_SFRPHFIFOEMPTY BIT(28) |
| #define INTSRC_FRAMEDONE BIT(24) |
| #define INTSRC_LPDRTOUT BIT(21) |
| #define INTSRC_TATOUT BIT(20) |
| #define INTSRC_RXDATDONE BIT(18) |
| #define INTSRC_MASK (INTSRC_PLLSTABLE | \ |
| INTSRC_SWRSTRELEASE | \ |
| INTSRC_SFRPLFIFOEMPTY | \ |
| INTSRC_SFRPHFIFOEMPTY | \ |
| INTSRC_FRAMEDONE | \ |
| INTSRC_LPDRTOUT | \ |
| INTSRC_TATOUT | \ |
| INTSRC_RXDATDONE) |
| |
| #define INTMSK_MSKPLLSTABLE BIT(31) |
| #define INTMSK_MSKSWRELEASE BIT(30) |
| #define INTMSK_MSKSFRPLFIFOEMPTY BIT(29) |
| #define INTMSK_MSKSFRPHFIFOEMPTY BIT(28) |
| #define INTMSK_MSKFRAMEDONE BIT(24) |
| #define INTMSK_MSKLPDRTOUT BIT(21) |
| #define INTMSK_MSKTATOUT BIT(20) |
| #define INTMSK_MSKRXDATDONE BIT(18) |
| |
| #define PLLCTRL_DPDNSWAP_CLK BIT(25) |
| #define PLLCTRL_DPDNSWAP_DAT BIT(24) |
| #define PLLCTRL_PLLEN BIT(23) |
| #define PLLCTRL_SET_PMS(x) REG_PUT(x, 19, 1) |
| |
| #define PHYTIMING_SET_M_TLPXCTL(x) REG_PUT(x, 15, 8) |
| #define PHYTIMING_SET_M_THSEXITCTL(x) REG_PUT(x, 7, 0) |
| |
| #define PHYTIMING1_SET_M_TCLKPRPRCTL(x) REG_PUT(x, 31, 24) |
| #define PHYTIMING1_SET_M_TCLKZEROCTL(x) REG_PUT(x, 23, 16) |
| #define PHYTIMING1_SET_M_TCLKPOSTCTL(x) REG_PUT(x, 15, 8) |
| #define PHYTIMING1_SET_M_TCLKTRAILCTL(x) REG_PUT(x, 7, 0) |
| |
| #define PHYTIMING2_SET_M_THSPRPRCTL(x) REG_PUT(x, 23, 16) |
| #define PHYTIMING2_SET_M_THSZEROCTL(x) REG_PUT(x, 15, 8) |
| #define PHYTIMING2_SET_M_THSTRAILCTL(x) REG_PUT(x, 7, 0) |
| |
| #define dsim_read(dsim, reg) readl(dsim->base + reg) |
| #define dsim_write(dsim, val, reg) writel(val, dsim->base + reg) |
| |
| /* fixed phy ref clk rate */ |
| #define PHY_REF_CLK 27000000 |
| |
| #define MAX_MAIN_HRESOL 2047 |
| #define MAX_MAIN_VRESOL 2047 |
| #define MAX_SUB_HRESOL 1024 |
| #define MAX_SUB_VRESOL 1024 |
| |
| /* in KHZ */ |
| #define MAX_ESC_CLK_FREQ 20000 |
| |
| /* dsim all irqs index */ |
| #define PLLSTABLE 1 |
| #define SWRSTRELEASE 2 |
| #define SFRPLFIFOEMPTY 3 |
| #define SFRPHFIFOEMPTY 4 |
| #define SYNCOVERRIDE 5 |
| #define BUSTURNOVER 6 |
| #define FRAMEDONE 7 |
| #define LPDRTOUT 8 |
| #define TATOUT 9 |
| #define RXDATDONE 10 |
| #define RXTE 11 |
| #define RXACK 12 |
| #define ERRRXECC 13 |
| #define ERRRXCRC 14 |
| #define ERRESC3 15 |
| #define ERRESC2 16 |
| #define ERRESC1 17 |
| #define ERRESC0 18 |
| #define ERRSYNC3 19 |
| #define ERRSYNC2 20 |
| #define ERRSYNC1 21 |
| #define ERRSYNC0 22 |
| #define ERRCONTROL3 23 |
| #define ERRCONTROL2 24 |
| #define ERRCONTROL1 25 |
| #define ERRCONTROL0 26 |
| |
| /* Dispmix Control & GPR Registers */ |
| #define DISPLAY_MIX_SFT_RSTN_CSR 0x00 |
| #define MIPI_DSI_I_PRESETn_SFT_EN BIT(5) |
| #define DISPLAY_MIX_CLK_EN_CSR 0x04 |
| #define MIPI_DSI_PCLK_SFT_EN BIT(8) |
| #define MIPI_DSI_CLKREF_SFT_EN BIT(9) |
| #define GPR_MIPI_RESET_DIV 0x08 |
| /* Clock & Data lanes reset: Active Low */ |
| #define GPR_MIPI_S_RESETN BIT(16) |
| #define GPR_MIPI_M_RESETN BIT(17) |
| |
| #define PS2KHZ(ps) (1000000000UL / (ps)) |
| |
| /* DSIM PLL configuration from spec: |
| * |
| * Fout(DDR) = (M * Fin) / (P * 2^S), so Fout / Fin = M / (P * 2^S) |
| * Fin_pll = Fin / P (6 ~ 12 MHz) |
| * S: [2:0], M: [12:3], P: [18:13], so |
| * TODO: 'S' is in [0 ~ 3], 'M' is in, 'P' is in [1 ~ 33] |
| * |
| */ |
| |
| struct sec_mipi_dsim { |
| void __iomem *base; |
| void __iomem *disp_mix_gpr_base; |
| |
| /* kHz clocks */ |
| uint64_t pix_clk; |
| uint64_t bit_clk; |
| |
| unsigned int lanes; |
| unsigned int channel; /* virtual channel */ |
| enum mipi_dsi_pixel_format format; |
| unsigned long mode_flags; |
| unsigned int pms; |
| unsigned int p; |
| unsigned int m; |
| unsigned int s; |
| struct fb_videomode vmode; |
| |
| const struct sec_mipi_dsim_plat_data *pdata; |
| |
| struct mipi_dsi_client_dev *dsi_panel_dev; |
| struct mipi_dsi_client_driver *dsi_panel_drv; |
| }; |
| |
| static void disp_mix_dsim_soft_reset_release(struct sec_mipi_dsim *dsim, bool release) |
| { |
| if (release) |
| /* release dsi blk reset */ |
| setbits_le32(dsim->disp_mix_gpr_base + DISPLAY_MIX_SFT_RSTN_CSR, MIPI_DSI_I_PRESETn_SFT_EN); |
| |
| else |
| clrbits_le32(dsim->disp_mix_gpr_base + DISPLAY_MIX_SFT_RSTN_CSR, MIPI_DSI_I_PRESETn_SFT_EN); |
| } |
| |
| static void disp_mix_dsim_clks_enable(struct sec_mipi_dsim *dsim, bool enable) |
| { |
| if (enable) |
| setbits_le32(dsim->disp_mix_gpr_base + DISPLAY_MIX_CLK_EN_CSR, MIPI_DSI_PCLK_SFT_EN | MIPI_DSI_CLKREF_SFT_EN); |
| else |
| clrbits_le32(dsim->disp_mix_gpr_base + DISPLAY_MIX_CLK_EN_CSR, MIPI_DSI_PCLK_SFT_EN | MIPI_DSI_CLKREF_SFT_EN); |
| } |
| |
| static void disp_mix_dsim_lanes_reset(struct sec_mipi_dsim *dsim, bool reset) |
| { |
| if (!reset) |
| /* release lanes reset */ |
| setbits_le32(dsim->disp_mix_gpr_base + GPR_MIPI_RESET_DIV, GPR_MIPI_S_RESETN | GPR_MIPI_M_RESETN); |
| else |
| /* reset lanes */ |
| clrbits_le32(dsim->disp_mix_gpr_base + GPR_MIPI_RESET_DIV, GPR_MIPI_S_RESETN | GPR_MIPI_M_RESETN); |
| } |
| |
| static void sec_mipi_dsim_wr_tx_header(struct sec_mipi_dsim *dsim, |
| u8 di, u8 data0, u8 data1) |
| { |
| unsigned int reg; |
| |
| reg = (data1 << 16) | (data0 << 8) | ((di & 0x3f) << 0); |
| |
| dsim_write(dsim, reg, DSIM_PKTHDR); |
| } |
| |
| static void sec_mipi_dsim_wr_tx_data(struct sec_mipi_dsim *dsim, |
| unsigned int tx_data) |
| { |
| dsim_write(dsim, tx_data, DSIM_PAYLOAD); |
| } |
| |
| static void sec_mipi_dsim_long_data_wr(struct sec_mipi_dsim *dsim, |
| 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; |
| debug("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; |
| debug("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]; |
| } |
| |
| sec_mipi_dsim_wr_tx_data(dsim, 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; |
| |
| debug("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]); |
| |
| sec_mipi_dsim_wr_tx_data(dsim, payload); |
| } |
| } |
| } |
| |
| static int sec_mipi_dsim_wait_for_pkt_done(struct sec_mipi_dsim *dsim, unsigned long timeout) |
| { |
| uint32_t intsrc; |
| |
| do { |
| intsrc = dsim_read(dsim, DSIM_INTSRC); |
| if (intsrc & INTSRC_SFRPLFIFOEMPTY) { |
| dsim_write(dsim, INTSRC_SFRPLFIFOEMPTY, DSIM_INTSRC); |
| return 0; |
| } |
| |
| udelay(1); |
| } while (--timeout); |
| |
| return -ETIMEDOUT; |
| } |
| |
| static int sec_mipi_dsim_pkt_write(struct sec_mipi_dsim *dsim, |
| u8 data_type, const u8 *buf, int len) |
| { |
| int ret = 0; |
| const unsigned char *data = (const unsigned char*)buf; |
| |
| if (len == 0) |
| /* handle generic short write command */ |
| sec_mipi_dsim_wr_tx_header(dsim, data_type, data[0], data[1]); |
| else { |
| /* handle generic long write command */ |
| sec_mipi_dsim_long_data_wr(dsim, data, len); |
| sec_mipi_dsim_wr_tx_header(dsim, data_type, len & 0xff, (len & 0xff00) >> 8); |
| |
| ret = sec_mipi_dsim_wait_for_pkt_done(dsim, MIPI_FIFO_TIMEOUT); |
| if (ret) { |
| printf("wait tx done timeout!\n"); |
| return -ETIMEDOUT; |
| } |
| } |
| mdelay(10); |
| |
| return 0; |
| } |
| |
| static int sec_mipi_dsim_wait_pll_stable(struct sec_mipi_dsim *dsim) |
| { |
| uint32_t status; |
| ulong start; |
| |
| start = get_timer(0); /* Get current timestamp */ |
| |
| do { |
| status = dsim_read(dsim, DSIM_STATUS); |
| if (status & STATUS_PLLSTABLE) |
| return 0; |
| } while (get_timer(0) < (start + 100)); /* Wait 100ms */ |
| |
| return -ETIMEDOUT; |
| } |
| |
| static int sec_mipi_dsim_config_pll(struct sec_mipi_dsim *dsim) |
| { |
| int ret; |
| uint32_t pllctrl = 0, status, data_lanes_en, stop; |
| |
| dsim_write(dsim, 0x8000, DSIM_PLLTMR); |
| |
| /* TODO: config dp/dn swap if requires */ |
| |
| pllctrl |= PLLCTRL_SET_PMS(dsim->pms) | PLLCTRL_PLLEN; |
| dsim_write(dsim, pllctrl, DSIM_PLLCTRL); |
| |
| ret = sec_mipi_dsim_wait_pll_stable(dsim); |
| if (ret) { |
| printf("wait for pll stable time out\n"); |
| return ret; |
| } |
| |
| /* wait for clk & data lanes to go to stop state */ |
| mdelay(1); |
| |
| data_lanes_en = (0x1 << dsim->lanes) - 1; |
| status = dsim_read(dsim, DSIM_STATUS); |
| if (!(status & STATUS_STOPSTATECLK)) { |
| printf("clock is not in stop state\n"); |
| return -EBUSY; |
| } |
| |
| stop = STATUS_GET_STOPSTATEDAT(status); |
| if ((stop & data_lanes_en) != data_lanes_en) { |
| printf("one or more data lanes is not in stop state\n"); |
| return -EBUSY; |
| } |
| |
| return 0; |
| } |
| |
| static void sec_mipi_dsim_set_main_mode(struct sec_mipi_dsim *dsim) |
| { |
| uint32_t bpp, hfp_wc, hbp_wc, hsa_wc; |
| uint32_t mdresol = 0, mvporch = 0, mhporch = 0, msync = 0; |
| struct fb_videomode *vmode = &dsim->vmode; |
| |
| mdresol |= MDRESOL_SET_MAINVRESOL(vmode->yres) | |
| MDRESOL_SET_MAINHRESOL(vmode->xres); |
| dsim_write(dsim, mdresol, DSIM_MDRESOL); |
| |
| mvporch |= MVPORCH_SET_MAINVBP(vmode->upper_margin) | |
| MVPORCH_SET_STABLEVFP(vmode->lower_margin) | |
| MVPORCH_SET_CMDALLOW(0x0); |
| dsim_write(dsim, mvporch, DSIM_MVPORCH); |
| |
| bpp = mipi_dsi_pixel_format_to_bpp(dsim->format); |
| |
| /* calculate hfp & hbp word counts */ |
| if (dsim->dsi_panel_drv) { |
| /* Panel driver is registered, will work with panel */ |
| hfp_wc = vmode->right_margin * (bpp >> 3); |
| hbp_wc = vmode->left_margin * (bpp >> 3); |
| } else { |
| hfp_wc = vmode->right_margin * (bpp >> 3) / dsim->lanes - 6; |
| hbp_wc = vmode->left_margin * (bpp >> 3) / dsim->lanes - 6; |
| } |
| |
| mhporch |= MHPORCH_SET_MAINHFP(hfp_wc) | |
| MHPORCH_SET_MAINHBP(hbp_wc); |
| |
| dsim_write(dsim, mhporch, DSIM_MHPORCH); |
| |
| /* calculate hsa word counts */ |
| if (dsim->dsi_panel_drv) { |
| hsa_wc = vmode->hsync_len * (bpp >> 3); |
| } else { |
| hsa_wc = vmode->hsync_len * (bpp >> 3) / dsim->lanes - 6; |
| } |
| |
| msync |= MSYNC_SET_MAINVSA(vmode->vsync_len) | |
| MSYNC_SET_MAINHSA(hsa_wc); |
| |
| debug("hfp_wc %u hbp_wc %u hsa_wc %u\n", hfp_wc, hbp_wc, hsa_wc); |
| |
| dsim_write(dsim, msync, DSIM_MSYNC); |
| } |
| |
| static void sec_mipi_dsim_config_dpi(struct sec_mipi_dsim *dsim) |
| { |
| uint32_t config = 0, rgb_status = 0, data_lanes_en; |
| |
| if (dsim->mode_flags & MIPI_DSI_MODE_VIDEO) |
| rgb_status &= ~RGB_STATUS_CMDMODE_INSEL; |
| else |
| rgb_status |= RGB_STATUS_CMDMODE_INSEL; |
| |
| dsim_write(dsim, rgb_status, DSIM_RGB_STATUS); |
| |
| if (dsim->mode_flags & MIPI_DSI_CLOCK_NON_CONTINUOUS) |
| config |= CONFIG_CLKLANE_STOP_START; |
| |
| if (dsim->mode_flags & MIPI_DSI_MODE_VSYNC_FLUSH) |
| config |= CONFIG_MFLUSH_VS; |
| |
| /* disable EoT packets in HS mode */ |
| if (dsim->mode_flags & MIPI_DSI_MODE_EOT_PACKET) |
| config |= CONFIG_EOT_R03; |
| |
| if (dsim->mode_flags & MIPI_DSI_MODE_VIDEO) { |
| config |= CONFIG_VIDEOMODE; |
| |
| if (dsim->mode_flags & MIPI_DSI_MODE_VIDEO_BURST) |
| config |= CONFIG_BURSTMODE; |
| |
| else if (dsim->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) |
| config |= CONFIG_SYNCINFORM; |
| |
| if (dsim->mode_flags & MIPI_DSI_MODE_VIDEO_AUTO_VERT) |
| config |= CONFIG_AUTOMODE; |
| |
| if (dsim->mode_flags & MIPI_DSI_MODE_VIDEO_HSE) |
| config |= CONFIG_HSEDISABLEMODE; |
| |
| if (dsim->mode_flags & MIPI_DSI_MODE_VIDEO_HFP) |
| config |= CONFIG_HFPDISABLEMODE; |
| |
| if (dsim->mode_flags & MIPI_DSI_MODE_VIDEO_HBP) |
| config |= CONFIG_HBPDISABLEMODE; |
| |
| if (dsim->mode_flags & MIPI_DSI_MODE_VIDEO_HSA) |
| config |= CONFIG_HSADISABLEMODE; |
| } |
| |
| config |= CONFIG_SET_MAINVC(dsim->channel); |
| |
| if (dsim->mode_flags & MIPI_DSI_MODE_VIDEO) { |
| switch (dsim->format) { |
| case MIPI_DSI_FMT_RGB565: |
| config |= CONFIG_SET_MAINPIXFORMAT(0x4); |
| break; |
| case MIPI_DSI_FMT_RGB666_PACKED: |
| config |= CONFIG_SET_MAINPIXFORMAT(0x5); |
| break; |
| case MIPI_DSI_FMT_RGB666: |
| config |= CONFIG_SET_MAINPIXFORMAT(0x6); |
| break; |
| case MIPI_DSI_FMT_RGB888: |
| config |= CONFIG_SET_MAINPIXFORMAT(0x7); |
| break; |
| default: |
| config |= CONFIG_SET_MAINPIXFORMAT(0x7); |
| break; |
| } |
| } |
| |
| /* config data lanes number and enable lanes */ |
| data_lanes_en = (0x1 << dsim->lanes) - 1; |
| config |= CONFIG_SET_NUMOFDATLANE(dsim->lanes - 1); |
| config |= CONFIG_SET_LANEEN(0x1 | data_lanes_en << 1); |
| |
| dsim_write(dsim, config, DSIM_CONFIG); |
| } |
| |
| static void sec_mipi_dsim_config_dphy(struct sec_mipi_dsim *dsim) |
| { |
| uint32_t phytiming = 0, phytiming1 = 0, phytiming2 = 0, timeout = 0; |
| |
| /* TODO: add a PHY timing table arranged by the pll Fout */ |
| |
| phytiming |= PHYTIMING_SET_M_TLPXCTL(6) | |
| PHYTIMING_SET_M_THSEXITCTL(11); |
| dsim_write(dsim, phytiming, DSIM_PHYTIMING); |
| |
| phytiming1 |= PHYTIMING1_SET_M_TCLKPRPRCTL(7) | |
| PHYTIMING1_SET_M_TCLKZEROCTL(38) | |
| PHYTIMING1_SET_M_TCLKPOSTCTL(13) | |
| PHYTIMING1_SET_M_TCLKTRAILCTL(8); |
| dsim_write(dsim, phytiming1, DSIM_PHYTIMING1); |
| |
| phytiming2 |= PHYTIMING2_SET_M_THSPRPRCTL(8) | |
| PHYTIMING2_SET_M_THSZEROCTL(13) | |
| PHYTIMING2_SET_M_THSTRAILCTL(11); |
| dsim_write(dsim, phytiming2, DSIM_PHYTIMING2); |
| |
| timeout |= TIMEOUT_SET_BTAOUT(0xf) | |
| TIMEOUT_SET_LPDRTOUT(0xf); |
| dsim_write(dsim, 0xf000f, DSIM_TIMEOUT); |
| } |
| |
| static void sec_mipi_dsim_config_clkctrl(struct sec_mipi_dsim *dsim) |
| { |
| uint32_t clkctrl = 0, data_lanes_en; |
| uint64_t byte_clk, esc_prescaler; |
| |
| clkctrl |= CLKCTRL_TXREQUESTHSCLK; |
| |
| /* using 1.5Gbps PHY */ |
| clkctrl |= CLKCTRL_DPHY_SEL_1P5G; |
| |
| clkctrl |= CLKCTRL_ESCCLKEN; |
| |
| clkctrl &= ~CLKCTRL_PLLBYPASS; |
| |
| clkctrl |= CLKCTRL_BYTECLKSRC_DPHY_PLL; |
| |
| clkctrl |= CLKCTRL_BYTECLKEN; |
| |
| data_lanes_en = (0x1 << dsim->lanes) - 1; |
| clkctrl |= CLKCTRL_SET_LANEESCCLKEN(0x1 | data_lanes_en << 1); |
| |
| /* calculate esc prescaler from byte clock: |
| * EscClk = ByteClk / EscPrescaler; |
| */ |
| byte_clk = dsim->bit_clk >> 3; |
| esc_prescaler = DIV_ROUND_UP_ULL(byte_clk, MAX_ESC_CLK_FREQ); |
| |
| clkctrl |= CLKCTRL_SET_ESCPRESCALER(esc_prescaler); |
| |
| dsim_write(dsim, clkctrl, DSIM_CLKCTRL); |
| } |
| |
| static void sec_mipi_dsim_set_standby(struct sec_mipi_dsim *dsim, |
| bool standby) |
| { |
| uint32_t mdresol = 0; |
| |
| mdresol = dsim_read(dsim, DSIM_MDRESOL); |
| |
| if (standby) |
| mdresol |= MDRESOL_MAINSTANDBY; |
| else |
| mdresol &= ~MDRESOL_MAINSTANDBY; |
| |
| dsim_write(dsim, mdresol, DSIM_MDRESOL); |
| } |
| |
| static void sec_mipi_dsim_disable_clkctrl(struct sec_mipi_dsim *dsim) |
| { |
| uint32_t clkctrl; |
| |
| clkctrl = dsim_read(dsim, DSIM_CLKCTRL); |
| |
| clkctrl &= ~CLKCTRL_TXREQUESTHSCLK; |
| |
| clkctrl &= ~CLKCTRL_ESCCLKEN; |
| |
| clkctrl &= ~CLKCTRL_BYTECLKEN; |
| |
| dsim_write(dsim, clkctrl, DSIM_CLKCTRL); |
| } |
| |
| static void sec_mipi_dsim_disable_pll(struct sec_mipi_dsim *dsim) |
| { |
| uint32_t pllctrl; |
| |
| pllctrl = dsim_read(dsim, DSIM_PLLCTRL); |
| |
| pllctrl &= ~PLLCTRL_PLLEN; |
| |
| dsim_write(dsim, pllctrl, DSIM_PLLCTRL); |
| } |
| |
| /* For now, dsim only support one device attached */ |
| static int sec_mipi_dsim_bridge_attach(struct mipi_dsi_bridge_driver *bridge_driver, |
| struct mipi_dsi_client_dev *dsi_dev) |
| { |
| struct sec_mipi_dsim *dsim_host = (struct sec_mipi_dsim *)bridge_driver->driver_private; |
| |
| if (!dsi_dev->lanes || dsi_dev->lanes > dsim_host->pdata->max_data_lanes) { |
| printf("invalid data lanes number\n"); |
| return -EINVAL; |
| } |
| |
| if (!(dsi_dev->mode_flags & MIPI_DSI_MODE_VIDEO) || |
| !((dsi_dev->mode_flags & MIPI_DSI_MODE_VIDEO_BURST) || |
| (dsi_dev->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE))) { |
| printf("unsupported dsi mode\n"); |
| return -EINVAL; |
| } |
| |
| if (dsi_dev->format != MIPI_DSI_FMT_RGB888 && |
| dsi_dev->format != MIPI_DSI_FMT_RGB565 && |
| dsi_dev->format != MIPI_DSI_FMT_RGB666 && |
| dsi_dev->format != MIPI_DSI_FMT_RGB666_PACKED) { |
| printf("unsupported pixel format: %#x\n", dsi_dev->format); |
| return -EINVAL; |
| } |
| |
| if (!dsi_dev->name) { |
| printf("panel_device name is NULL.\n"); |
| return -EFAULT; |
| } |
| |
| if (dsim_host->dsi_panel_drv) { |
| if (strcmp(dsi_dev->name, dsim_host->dsi_panel_drv->name)) { |
| printf("The panel device name %s is not for LCD driver %s\n", |
| dsi_dev->name, dsim_host->dsi_panel_drv->name); |
| return -EFAULT; |
| } |
| } |
| |
| dsim_host->dsi_panel_dev = dsi_dev; |
| |
| dsim_host->lanes = dsi_dev->lanes; |
| dsim_host->channel = dsi_dev->channel; |
| dsim_host->format = dsi_dev->format; |
| dsim_host->mode_flags = dsi_dev->mode_flags; |
| |
| return 0; |
| } |
| |
| static int sec_mipi_dsim_bridge_enable(struct mipi_dsi_bridge_driver *bridge_driver) |
| { |
| int ret; |
| struct sec_mipi_dsim *dsim_host = (struct sec_mipi_dsim *)bridge_driver->driver_private; |
| |
| /* At this moment, the dsim bridge's preceding encoder has |
| * already been enabled. So the dsim can be configed here |
| */ |
| |
| /* config main display mode */ |
| sec_mipi_dsim_set_main_mode(dsim_host); |
| |
| /* config dsim dpi */ |
| sec_mipi_dsim_config_dpi(dsim_host); |
| |
| /* config dsim pll */ |
| ret = sec_mipi_dsim_config_pll(dsim_host); |
| if (ret) { |
| printf("dsim pll config failed: %d\n", ret); |
| return -EPERM; |
| } |
| |
| /* config dphy timings */ |
| sec_mipi_dsim_config_dphy(dsim_host); |
| |
| /* config esc clock, byte clock and etc */ |
| sec_mipi_dsim_config_clkctrl(dsim_host); |
| |
| /* enable data transfer of dsim */ |
| sec_mipi_dsim_set_standby(dsim_host, true); |
| |
| /* Call panel driver's setup */ |
| if (dsim_host->dsi_panel_drv && dsim_host->dsi_panel_drv->dsi_client_setup) { |
| ret = dsim_host->dsi_panel_drv->dsi_client_setup(dsim_host->dsi_panel_dev); |
| if (ret < 0) { |
| printf("failed to init mipi lcd.\n"); |
| return ret; |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int sec_mipi_dsim_bridge_disable(struct mipi_dsi_bridge_driver *bridge_driver) |
| { |
| uint32_t intsrc; |
| struct sec_mipi_dsim *dsim_host = (struct sec_mipi_dsim *)bridge_driver->driver_private; |
| |
| /* disable data transfer of dsim */ |
| sec_mipi_dsim_set_standby(dsim_host, false); |
| |
| /* disable esc clock & byte clock */ |
| sec_mipi_dsim_disable_clkctrl(dsim_host); |
| |
| /* disable dsim pll */ |
| sec_mipi_dsim_disable_pll(dsim_host); |
| |
| /* Clear all intsrc */ |
| intsrc = dsim_read(dsim_host, DSIM_INTSRC); |
| dsim_write(dsim_host, intsrc, DSIM_INTSRC); |
| |
| return 0; |
| } |
| |
| static int sec_mipi_dsim_bridge_mode_set(struct mipi_dsi_bridge_driver *bridge_driver, |
| struct fb_videomode *fbmode) |
| { |
| int bpp; |
| uint64_t pix_clk, bit_clk; |
| struct sec_mipi_dsim *dsim_host = (struct sec_mipi_dsim *)bridge_driver->driver_private; |
| |
| dsim_host->vmode = *fbmode; |
| |
| bpp = mipi_dsi_pixel_format_to_bpp(dsim_host->format); |
| if (bpp < 0) |
| return -EINVAL; |
| |
| pix_clk = PS2KHZ(fbmode->pixclock) * 1000; |
| bit_clk = DIV_ROUND_UP_ULL(pix_clk * bpp, dsim_host->lanes); |
| |
| if (bit_clk > dsim_host->pdata->max_data_rate) { |
| printf("request bit clk freq exceeds lane's maximum value\n"); |
| return -EINVAL; |
| } |
| |
| dsim_host->pix_clk = DIV_ROUND_UP_ULL(pix_clk, 1000); |
| dsim_host->bit_clk = DIV_ROUND_UP_ULL(bit_clk, 1000); |
| |
| if (dsim_host->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) { |
| /* TODO: add PMS calculate and check |
| * Only support '1080p@60Hz' for now, |
| * add other modes support later |
| */ |
| dsim_host->pms = 0x4210; |
| } |
| |
| debug("%s: bitclk %llu pixclk %llu\n", __func__, dsim_host->bit_clk, dsim_host->pix_clk); |
| |
| return 0; |
| } |
| |
| /* Add a LCD panel driver, will search the panel device to bind with them */ |
| int sec_mipi_dsim_bridge_add_client_driver(struct mipi_dsi_bridge_driver *bridge_driver, |
| struct mipi_dsi_client_driver *panel_drv) |
| { |
| struct sec_mipi_dsim *dsim_host = (struct sec_mipi_dsim *)bridge_driver->driver_private; |
| |
| if (!panel_drv) { |
| printf("mipi_dsi_northwest_panel_driver is NULL.\n"); |
| return -EFAULT; |
| } |
| |
| if (!panel_drv->name) { |
| printf("mipi_dsi_northwest_panel_driver name is NULL.\n"); |
| return -EFAULT; |
| } |
| |
| if (dsim_host->dsi_panel_dev) { |
| if (strcmp(panel_drv->name, dsim_host->dsi_panel_dev->name)) { |
| printf("The panel driver name %s is not for LCD device %s\n", |
| panel_drv->name, dsim_host->dsi_panel_dev->name); |
| return -EFAULT; |
| } |
| } |
| |
| dsim_host->dsi_panel_drv = panel_drv; |
| |
| return 0; |
| } |
| |
| static int sec_mipi_dsim_bridge_pkt_write(struct mipi_dsi_bridge_driver *bridge_driver, |
| u8 data_type, const u8 *buf, int len) |
| { |
| struct sec_mipi_dsim *dsim_host = (struct sec_mipi_dsim *)bridge_driver->driver_private; |
| |
| #ifdef DEBUG |
| int i = 0; |
| printf("sec_mipi_dsim_bridge_pkt_write, data_type %u, len %d buf: \n", data_type, len); |
| |
| if (len == 0) |
| len = 2; |
| |
| for (i; i < len; i++) { |
| printf("0x%.2x ", buf[i]); |
| } |
| printf("\n"); |
| #endif |
| |
| return sec_mipi_dsim_pkt_write(dsim_host, data_type, buf, len); |
| } |
| |
| struct mipi_dsi_bridge_driver imx_sec_dsim_driver = { |
| .attach = sec_mipi_dsim_bridge_attach, |
| .enable = sec_mipi_dsim_bridge_enable, |
| .disable = sec_mipi_dsim_bridge_disable, |
| .mode_set = sec_mipi_dsim_bridge_mode_set, |
| .pkt_write = sec_mipi_dsim_bridge_pkt_write, |
| .add_client_driver = sec_mipi_dsim_bridge_add_client_driver, |
| .name = DRIVER_NAME, |
| }; |
| |
| int sec_mipi_dsim_setup(const struct sec_mipi_dsim_plat_data *plat_data) |
| { |
| struct sec_mipi_dsim *dsim_host; |
| |
| if (!plat_data) { |
| printf("Invalid platform data \n"); |
| return -EINVAL; |
| } |
| |
| dsim_host = (struct sec_mipi_dsim *)malloc(sizeof(struct sec_mipi_dsim)); |
| if (!dsim_host) { |
| printf("failed to allocate sec_mipi_dsim object.\n"); |
| return -ENOMEM; |
| } |
| |
| dsim_host->base = (void __iomem *)plat_data->reg_base; |
| dsim_host->disp_mix_gpr_base = (void __iomem *)plat_data->gpr_base; |
| dsim_host->pdata = plat_data; |
| dsim_host->dsi_panel_drv = NULL; |
| dsim_host->dsi_panel_dev = NULL; |
| |
| /* Pull dsim out of reset */ |
| disp_mix_dsim_soft_reset_release(dsim_host, true); |
| disp_mix_dsim_clks_enable(dsim_host, true); |
| disp_mix_dsim_lanes_reset(dsim_host, false); |
| |
| imx_sec_dsim_driver.driver_private = dsim_host; |
| return imx_mipi_dsi_bridge_register_driver(&imx_sec_dsim_driver); |
| } |