| /* |
| * Samsung MIPI DSIM Bridge |
| * |
| * Copyright 2018-2019 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. |
| */ |
| |
| #include <asm/unaligned.h> |
| #include <linux/clk.h> |
| #include <linux/completion.h> |
| #include <linux/delay.h> |
| #include <linux/gcd.h> |
| #include <linux/log2.h> |
| #include <linux/module.h> |
| #include <linux/of_graph.h> |
| #include <drm/bridge/sec_mipi_dsim.h> |
| #include <drm/drmP.h> |
| #include <drm/drm_atomic_helper.h> |
| #include <drm/drm_bridge.h> |
| #include <drm/drm_connector.h> |
| #include <drm/drm_crtc_helper.h> |
| #include <drm/drm_encoder.h> |
| #include <drm/drm_fourcc.h> |
| #include <drm/drm_mipi_dsi.h> |
| #include <drm/drm_panel.h> |
| #include <video/videomode.h> |
| #include <video/mipi_display.h> |
| |
| /* 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_CONTINUOUS_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 ESCMODE_SET_STOPSTATE_CNT(X) REG_PUT(x, 31, 21) |
| #define ESCMODE_FORCESTOPSTATE BIT(20) |
| #define ESCMODE_FORCEBTA BIT(16) |
| #define ESCMODE_CMDLPDT BIT(7) |
| #define ESCMODE_TXLPDT BIT(6) |
| #define ESCMODE_TXTRIGGERRST BIT(5) |
| |
| #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_RXTE BIT(17) |
| #define INTSRC_RXACK BIT(16) |
| #define INTSRC_MASK (INTSRC_PLLSTABLE | \ |
| INTSRC_SWRSTRELEASE | \ |
| INTSRC_SFRPLFIFOEMPTY | \ |
| INTSRC_SFRPHFIFOEMPTY | \ |
| INTSRC_FRAMEDONE | \ |
| INTSRC_LPDRTOUT | \ |
| INTSRC_TATOUT | \ |
| INTSRC_RXDATDONE | \ |
| INTSRC_RXTE | \ |
| INTSRC_RXACK) |
| |
| #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 INTMSK_MSKRXTE BIT(17) |
| #define INTMSK_MSKRXACK BIT(16) |
| |
| #define PKTHDR_SET_DATA1(x) REG_PUT(x, 23, 16) |
| #define PKTHDR_GET_DATA1(x) REG_GET(x, 23, 16) |
| #define PKTHDR_SET_DATA0(x) REG_PUT(x, 15, 8) |
| #define PKTHDR_GET_DATA0(x) REG_GET(x, 15, 8) |
| #define PKTHDR_GET_WC(x) REG_GET(x, 23, 8) |
| #define PKTHDR_SET_DI(x) REG_PUT(x, 7, 0) |
| #define PKTHDR_GET_DI(x) REG_GET(x, 7, 0) |
| #define PKTHDR_SET_DT(x) REG_PUT(x, 5, 0) |
| #define PKTHDR_GET_DT(x) REG_GET(x, 5, 0) |
| #define PKTHDR_SET_VC(x) REG_PUT(x, 7, 6) |
| #define PKTHDR_GET_VC(x) REG_GET(x, 7, 6) |
| |
| #define FIFOCTRL_FULLRX BIT(25) |
| #define FIFOCTRL_EMPTYRX BIT(24) |
| #define FIFOCTRL_FULLHSFR BIT(23) |
| #define FIFOCTRL_EMPTYHSFR BIT(22) |
| #define FIFOCTRL_FULLLSFR BIT(21) |
| #define FIFOCTRL_EMPTYLSFR BIT(20) |
| #define FIFOCTRL_FULLHMAIN BIT(11) |
| #define FIFOCTRL_EMPTYHMAIN BIT(10) |
| #define FIFOCTRL_FULLLMAIN BIT(9) |
| #define FIFOCTRL_EMPTYLMAIN BIT(8) |
| #define FIFOCTRL_NINITRX BIT(4) |
| #define FIFOCTRL_NINITSFR BIT(3) |
| #define FIFOCTRL_NINITI80 BIT(2) |
| #define FIFOCTRL_NINITSUB BIT(1) |
| #define FIFOCTRL_NINITMAIN BIT(0) |
| |
| #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 PLLCTRL_SET_P(x) REG_PUT(x, 18, 13) |
| #define PLLCTRL_SET_M(x) REG_PUT(x, 12, 3) |
| #define PLLCTRL_SET_S(x) REG_PUT(x, 2, 0) |
| |
| #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 27000 |
| |
| #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 |
| |
| #define MIPI_FIFO_TIMEOUT msecs_to_jiffies(250) |
| |
| #define MIPI_HFP_PKT_OVERHEAD 6 |
| #define MIPI_HBP_PKT_OVERHEAD 6 |
| #define MIPI_HSA_PKT_OVERHEAD 6 |
| |
| #define to_sec_mipi_dsim(dsi) container_of(dsi, struct sec_mipi_dsim, dsi_host) |
| #define conn_to_sec_mipi_dsim(conn) \ |
| container_of(conn, struct sec_mipi_dsim, connector) |
| |
| /* used for CEA standard modes */ |
| struct dsim_hblank_par { |
| char *name; /* drm display mode name */ |
| int vrefresh; |
| int hfp_wc; |
| int hbp_wc; |
| int hsa_wc; |
| int lanes; |
| }; |
| |
| struct dsim_pll_pms { |
| uint32_t bit_clk; /* kHz */ |
| uint32_t p; |
| uint32_t m; |
| uint32_t s; |
| uint32_t k; |
| }; |
| |
| struct sec_mipi_dsim { |
| struct mipi_dsi_host dsi_host; |
| struct drm_connector connector; |
| struct drm_encoder *encoder; |
| struct drm_bridge *bridge; |
| struct drm_bridge *next; |
| struct drm_panel *panel; |
| struct device *dev; |
| |
| void __iomem *base; |
| int irq; |
| |
| struct clk *clk_cfg; |
| struct clk *clk_pllref; |
| struct clk *pclk; /* pixel clock */ |
| |
| /* kHz clocks */ |
| uint32_t pix_clk; |
| uint32_t bit_clk; |
| uint32_t pref_clk; /* phy ref clock rate in KHz */ |
| |
| unsigned int lanes; |
| unsigned int channel; /* virtual channel */ |
| enum mipi_dsi_pixel_format format; |
| unsigned long mode_flags; |
| const struct dsim_hblank_par *hpar; |
| unsigned int pms; |
| unsigned int p; |
| unsigned int m; |
| unsigned int s; |
| unsigned long long lp_data_rate; |
| unsigned long long hs_data_rate; |
| struct videomode vmode; |
| |
| struct completion pll_stable; |
| struct completion ph_tx_done; |
| struct completion pl_tx_done; |
| struct completion rx_done; |
| const struct sec_mipi_dsim_plat_data *pdata; |
| }; |
| |
| #define DSIM_HBLANK_PARAM(nm, vf, hfp, hbp, hsa, num) \ |
| .name = (nm), \ |
| .vrefresh = (vf), \ |
| .hfp_wc = (hfp), \ |
| .hbp_wc = (hbp), \ |
| .hsa_wc = (hsa), \ |
| .lanes = (num) |
| |
| #define DSIM_PLL_PMS(c, pp, mm, ss) \ |
| .bit_clk = (c), \ |
| .p = (pp), \ |
| .m = (mm), \ |
| .s = (ss) |
| |
| static const struct dsim_hblank_par hblank_4lanes[] = { |
| /* { 88, 148, 44 } */ |
| { DSIM_HBLANK_PARAM("1920x1080", 60, 60, 105, 27, 4), }, |
| /* { 528, 148, 44 } */ |
| { DSIM_HBLANK_PARAM("1920x1080", 50, 390, 105, 27, 4), }, |
| /* { 88, 148, 44 } */ |
| { DSIM_HBLANK_PARAM("1920x1080", 30, 60, 105, 27, 4), }, |
| /* { 110, 220, 40 } */ |
| { DSIM_HBLANK_PARAM("1280x720" , 60, 78, 159, 24, 4), }, |
| /* { 440, 220, 40 } */ |
| { DSIM_HBLANK_PARAM("1280x720" , 50, 324, 159, 24, 4), }, |
| /* { 16, 60, 62 } */ |
| { DSIM_HBLANK_PARAM("720x480" , 60, 6, 39, 40, 4), }, |
| /* { 12, 68, 64 } */ |
| { DSIM_HBLANK_PARAM("720x576" , 50, 3, 45, 42, 4), }, |
| /* { 16, 48, 96 } */ |
| { DSIM_HBLANK_PARAM("640x480" , 60, 6, 30, 66, 4), }, |
| }; |
| |
| static const struct dsim_hblank_par hblank_2lanes[] = { |
| /* { 88, 148, 44 } */ |
| { DSIM_HBLANK_PARAM("1920x1080", 30, 114, 210, 60, 2), }, |
| /* { 110, 220, 40 } */ |
| { DSIM_HBLANK_PARAM("1280x720" , 60, 159, 320, 40, 2), }, |
| /* { 440, 220, 40 } */ |
| { DSIM_HBLANK_PARAM("1280x720" , 50, 654, 320, 40, 2), }, |
| /* { 16, 60, 62 } */ |
| { DSIM_HBLANK_PARAM("720x480" , 60, 16, 66, 88, 2), }, |
| /* { 12, 68, 64 } */ |
| { DSIM_HBLANK_PARAM("720x576" , 50, 12, 96, 72, 2), }, |
| /* { 16, 48, 96 } */ |
| { DSIM_HBLANK_PARAM("640x480" , 60, 18, 66, 138, 2), }, |
| }; |
| |
| static const struct dsim_hblank_par *sec_mipi_dsim_get_hblank_par(const char *name, |
| int vrefresh, |
| int lanes) |
| { |
| int i, size; |
| const struct dsim_hblank_par *hpar, *hblank; |
| |
| if (unlikely(!name)) |
| return NULL; |
| |
| switch (lanes) { |
| case 2: |
| hblank = hblank_2lanes; |
| size = ARRAY_SIZE(hblank_2lanes); |
| break; |
| case 4: |
| hblank = hblank_4lanes; |
| size = ARRAY_SIZE(hblank_4lanes); |
| break; |
| default: |
| pr_err("No hblank data for mode %s with %d lanes\n", |
| name, lanes); |
| return NULL; |
| } |
| |
| for (i = 0; i < size; i++) { |
| hpar = &hblank[i]; |
| |
| if (!strcmp(name, hpar->name)) { |
| if (vrefresh != hpar->vrefresh) |
| continue; |
| |
| /* found */ |
| return hpar; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| static int sec_mipi_dsim_set_pref_rate(struct sec_mipi_dsim *dsim) |
| { |
| int ret; |
| uint32_t rate; |
| struct device *dev = dsim->dev; |
| const struct sec_mipi_dsim_plat_data *pdata = dsim->pdata; |
| const struct sec_mipi_dsim_pll *dpll = pdata->dphy_pll; |
| const struct sec_mipi_dsim_range *fin_range = &dpll->fin; |
| |
| ret = of_property_read_u32(dev->of_node, "pref-rate", &rate); |
| if (ret < 0) { |
| dev_dbg(dev, "no valid rate assigned for pref clock\n"); |
| dsim->pref_clk = PHY_REF_CLK; |
| } else { |
| if (unlikely(rate < fin_range->min || rate > fin_range->max)) { |
| dev_warn(dev, "pref-rate get is invalid: %uKHz\n", |
| rate); |
| dsim->pref_clk = PHY_REF_CLK; |
| } else |
| dsim->pref_clk = rate; |
| } |
| |
| set_rate: |
| ret = clk_set_rate(dsim->clk_pllref, |
| ((unsigned long)dsim->pref_clk) * 1000); |
| if (ret) { |
| dev_err(dev, "failed to set pll ref clock rate\n"); |
| return ret; |
| } |
| |
| rate = clk_get_rate(dsim->clk_pllref) / 1000; |
| if (unlikely(!rate)) { |
| dev_err(dev, "failed to get pll ref clock rate\n"); |
| return -EINVAL; |
| } |
| |
| if (rate != dsim->pref_clk) { |
| if (unlikely(dsim->pref_clk == PHY_REF_CLK)) { |
| /* set default rate failed */ |
| dev_err(dev, "no valid pll ref clock rate\n"); |
| return -EINVAL; |
| } |
| |
| dev_warn(dev, "invalid assigned rate for pref: %uKHz\n", |
| dsim->pref_clk); |
| dev_warn(dev, "use default pref rate instead: %uKHz\n", |
| PHY_REF_CLK); |
| |
| dsim->pref_clk = PHY_REF_CLK; |
| goto set_rate; |
| } |
| |
| return 0; |
| } |
| |
| static void sec_mipi_dsim_irq_init(struct sec_mipi_dsim *dsim); |
| |
| /* For now, dsim only support one device attached */ |
| static int sec_mipi_dsim_host_attach(struct mipi_dsi_host *host, |
| struct mipi_dsi_device *dsi) |
| { |
| struct sec_mipi_dsim *dsim = to_sec_mipi_dsim(host); |
| const struct sec_mipi_dsim_plat_data *pdata = dsim->pdata; |
| struct device *dev = dsim->dev; |
| struct drm_panel *panel; |
| |
| if (!dsi->lanes || dsi->lanes > pdata->max_data_lanes) { |
| dev_err(dev, "invalid data lanes number\n"); |
| return -EINVAL; |
| } |
| |
| if (dsim->channel) |
| return -EINVAL; |
| |
| if (!(dsi->mode_flags & MIPI_DSI_MODE_VIDEO) || |
| !((dsi->mode_flags & MIPI_DSI_MODE_VIDEO_BURST) || |
| (dsi->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE))) { |
| dev_err(dev, "unsupported dsi mode\n"); |
| return -EINVAL; |
| } |
| |
| if (dsi->format != MIPI_DSI_FMT_RGB888 && |
| dsi->format != MIPI_DSI_FMT_RGB565 && |
| dsi->format != MIPI_DSI_FMT_RGB666 && |
| dsi->format != MIPI_DSI_FMT_RGB666_PACKED) { |
| dev_err(dev, "unsupported pixel format: %#x\n", dsi->format); |
| return -EINVAL; |
| } |
| |
| if (!dsim->next) { |
| /* 'dsi' must be panel device */ |
| panel = of_drm_find_panel(dsi->dev.of_node); |
| |
| if (!panel) { |
| dev_err(dev, "refuse unknown dsi device attach\n"); |
| WARN_ON(!panel); |
| return -ENODEV; |
| } |
| |
| /* Don't support multiple panels */ |
| if (dsim->panel && panel && dsim->panel != panel) { |
| dev_err(dev, "don't support multiple panels\n"); |
| return -EBUSY; |
| } |
| |
| dsim->panel = panel; |
| } |
| |
| /* TODO: DSIM 3 lanes has some display issue, so |
| * avoid 3 lanes enable, and force data lanes to |
| * be 2. |
| */ |
| if (dsi->lanes == 3) |
| dsi->lanes = 2; |
| |
| dsim->lanes = dsi->lanes; |
| dsim->channel = dsi->channel; |
| dsim->format = dsi->format; |
| dsim->mode_flags = dsi->mode_flags; |
| |
| /* TODO: support later */ |
| #if 0 |
| if (dsim->connector.dev) |
| drm_helper_hpd_irq_event(dsim->connector.dev); |
| #endif |
| |
| return 0; |
| } |
| |
| static int sec_mipi_dsim_host_detach(struct mipi_dsi_host *host, |
| struct mipi_dsi_device *dsi) |
| { |
| struct sec_mipi_dsim *dsim = to_sec_mipi_dsim(host); |
| |
| if (WARN_ON(!dsim->next && !dsim->panel)) |
| return -ENODEV; |
| |
| /* clear the saved dsi parameters */ |
| dsim->lanes = 0; |
| dsim->channel = 0; |
| dsim->format = 0; |
| dsim->mode_flags = 0; |
| |
| return 0; |
| } |
| |
| static void sec_mipi_dsim_config_cmd_lpm(struct sec_mipi_dsim *dsim, |
| bool enable) |
| { |
| uint32_t escmode; |
| |
| escmode = dsim_read(dsim, DSIM_ESCMODE); |
| |
| if (enable) |
| escmode |= ESCMODE_CMDLPDT; |
| else |
| escmode &= ~ESCMODE_CMDLPDT; |
| |
| dsim_write(dsim, escmode, DSIM_ESCMODE); |
| } |
| |
| static void sec_mipi_dsim_write_pl_to_sfr_fifo(struct sec_mipi_dsim *dsim, |
| const void *payload, |
| size_t length) |
| { |
| uint32_t pl_data; |
| |
| if (!length) |
| return; |
| |
| while (length >= 4) { |
| pl_data = get_unaligned_le32(payload); |
| dsim_write(dsim, pl_data, DSIM_PAYLOAD); |
| payload += 4; |
| length -= 4; |
| } |
| |
| pl_data = 0; |
| switch (length) { |
| case 3: |
| pl_data |= ((u8 *)payload)[2] << 16; |
| case 2: |
| pl_data |= ((u8 *)payload)[1] << 8; |
| case 1: |
| pl_data |= ((u8 *)payload)[0]; |
| dsim_write(dsim, pl_data, DSIM_PAYLOAD); |
| break; |
| } |
| } |
| |
| static void sec_mipi_dsim_write_ph_to_sfr_fifo(struct sec_mipi_dsim *dsim, |
| void *header, |
| bool use_lpm) |
| { |
| uint32_t pkthdr; |
| |
| pkthdr = PKTHDR_SET_DATA1(((u8 *)header)[2]) | /* WC MSB */ |
| PKTHDR_SET_DATA0(((u8 *)header)[1]) | /* WC LSB */ |
| PKTHDR_SET_DI(((u8 *)header)[0]); /* Data ID */ |
| |
| dsim_write(dsim, pkthdr, DSIM_PKTHDR); |
| } |
| |
| static int sec_mipi_dsim_read_pl_from_sfr_fifo(struct sec_mipi_dsim *dsim, |
| void *payload, |
| size_t length) |
| { |
| uint8_t data_type; |
| uint16_t word_count = 0; |
| uint32_t fifoctrl, ph, pl; |
| |
| fifoctrl = dsim_read(dsim, DSIM_FIFOCTRL); |
| |
| if (WARN_ON(fifoctrl & FIFOCTRL_EMPTYRX)) |
| return -EINVAL; |
| |
| ph = dsim_read(dsim, DSIM_RXFIFO); |
| data_type = PKTHDR_GET_DT(ph); |
| switch (data_type) { |
| case MIPI_DSI_RX_ACKNOWLEDGE_AND_ERROR_REPORT: |
| dev_err(dsim->dev, "peripheral report error: (0-7)%x, (8-15)%x\n", |
| PKTHDR_GET_DATA0(ph), PKTHDR_GET_DATA1(ph)); |
| return -EPROTO; |
| case MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_2BYTE: |
| case MIPI_DSI_RX_GENERIC_SHORT_READ_RESPONSE_2BYTE: |
| if (!WARN_ON(length < 2)) { |
| ((u8 *)payload)[1] = PKTHDR_GET_DATA1(ph); |
| word_count++; |
| } |
| /* fall through */ |
| case MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_1BYTE: |
| case MIPI_DSI_RX_GENERIC_SHORT_READ_RESPONSE_1BYTE: |
| ((u8 *)payload)[0] = PKTHDR_GET_DATA0(ph); |
| word_count++; |
| length = word_count; |
| break; |
| case MIPI_DSI_RX_DCS_LONG_READ_RESPONSE: |
| case MIPI_DSI_RX_GENERIC_LONG_READ_RESPONSE: |
| word_count = PKTHDR_GET_WC(ph); |
| if (word_count > length) { |
| dev_err(dsim->dev, "invalid receive buffer length\n"); |
| return -EINVAL; |
| } |
| |
| length = word_count; |
| |
| while (word_count >= 4) { |
| pl = dsim_read(dsim, DSIM_RXFIFO); |
| ((u8 *)payload)[0] = pl & 0xff; |
| ((u8 *)payload)[1] = (pl >> 8) & 0xff; |
| ((u8 *)payload)[2] = (pl >> 16) & 0xff; |
| ((u8 *)payload)[3] = (pl >> 24) & 0xff; |
| payload += 4; |
| word_count -= 4; |
| } |
| |
| if (word_count > 0) { |
| pl = dsim_read(dsim, DSIM_RXFIFO); |
| |
| switch (word_count) { |
| case 3: |
| ((u8 *)payload)[2] = (pl >> 16) & 0xff; |
| case 2: |
| ((u8 *)payload)[1] = (pl >> 8) & 0xff; |
| case 1: |
| ((u8 *)payload)[0] = pl & 0xff; |
| break; |
| } |
| } |
| |
| break; |
| default: |
| return -EINVAL; |
| } |
| |
| return length; |
| } |
| |
| static ssize_t sec_mipi_dsim_host_transfer(struct mipi_dsi_host *host, |
| const struct mipi_dsi_msg *msg) |
| { |
| int ret; |
| bool use_lpm; |
| struct mipi_dsi_packet packet; |
| struct sec_mipi_dsim *dsim = to_sec_mipi_dsim(host); |
| |
| if ((msg->rx_buf && !msg->rx_len) || (msg->rx_len && !msg->rx_buf)) |
| return -EINVAL; |
| |
| ret = mipi_dsi_create_packet(&packet, msg); |
| if (ret) { |
| dev_err(dsim->dev, "failed to create dsi packet: %d\n", ret); |
| return ret; |
| } |
| |
| /* need to read data from peripheral */ |
| if (unlikely(msg->rx_buf)) |
| reinit_completion(&dsim->rx_done); |
| |
| /* config LPM for CMD TX */ |
| use_lpm = msg->flags & MIPI_DSI_MSG_USE_LPM ? true : false; |
| sec_mipi_dsim_config_cmd_lpm(dsim, use_lpm); |
| |
| if (packet.payload_length) { /* Long Packet case */ |
| reinit_completion(&dsim->pl_tx_done); |
| |
| /* write packet payload */ |
| sec_mipi_dsim_write_pl_to_sfr_fifo(dsim, |
| packet.payload, |
| packet.payload_length); |
| |
| /* write packet header */ |
| sec_mipi_dsim_write_ph_to_sfr_fifo(dsim, |
| packet.header, |
| use_lpm); |
| |
| ret = wait_for_completion_timeout(&dsim->ph_tx_done, |
| MIPI_FIFO_TIMEOUT); |
| if (!ret) { |
| dev_err(dsim->dev, "wait payload tx done time out\n"); |
| return -EBUSY; |
| } |
| } else { |
| reinit_completion(&dsim->ph_tx_done); |
| |
| /* write packet header */ |
| sec_mipi_dsim_write_ph_to_sfr_fifo(dsim, |
| packet.header, |
| use_lpm); |
| |
| ret = wait_for_completion_timeout(&dsim->ph_tx_done, |
| MIPI_FIFO_TIMEOUT); |
| if (!ret) { |
| dev_err(dsim->dev, "wait pkthdr tx done time out\n"); |
| return -EBUSY; |
| } |
| } |
| |
| /* read packet payload */ |
| if (unlikely(msg->rx_buf)) { |
| ret = wait_for_completion_timeout(&dsim->rx_done, |
| MIPI_FIFO_TIMEOUT); |
| if (!ret) { |
| dev_err(dsim->dev, "wait rx done time out\n"); |
| return -EBUSY; |
| } |
| |
| ret = sec_mipi_dsim_read_pl_from_sfr_fifo(dsim, |
| msg->rx_buf, |
| msg->rx_len); |
| if (ret < 0) |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static const struct mipi_dsi_host_ops sec_mipi_dsim_host_ops = { |
| .attach = sec_mipi_dsim_host_attach, |
| .detach = sec_mipi_dsim_host_detach, |
| .transfer = sec_mipi_dsim_host_transfer, |
| }; |
| |
| static int sec_mipi_dsim_bridge_attach(struct drm_bridge *bridge) |
| { |
| int ret; |
| struct sec_mipi_dsim *dsim = bridge->driver_private; |
| struct device *dev = dsim->dev; |
| struct device_node *np = dev->of_node; |
| struct device_node *endpoint, *remote; |
| struct drm_bridge *next = NULL; |
| struct drm_encoder *encoder = dsim->encoder; |
| |
| /* TODO: All bridges and planes should have already been added */ |
| |
| /* A panel has been found, ignor other dsi devices */ |
| if (dsim->panel) |
| return 0; |
| |
| /* find next bridge */ |
| endpoint = of_graph_get_next_endpoint(np, NULL); |
| /* At least one endpoint should be existed */ |
| if (!endpoint) |
| return -ENODEV; |
| |
| while(endpoint && !next) { |
| remote = of_graph_get_remote_port_parent(endpoint); |
| |
| if (!remote || !of_device_is_available(remote)) { |
| of_node_put(remote); |
| endpoint = of_graph_get_next_endpoint(np, endpoint); |
| continue; |
| } |
| |
| next = of_drm_find_bridge(remote); |
| if (next) { |
| /* Found */ |
| of_node_put(endpoint); |
| break; |
| } |
| |
| endpoint = of_graph_get_next_endpoint(np, endpoint); |
| } |
| |
| /* No valid dsi device attached */ |
| if (!next) |
| return -ENODEV; |
| |
| /* duplicate bridges or next bridge exists */ |
| WARN_ON(bridge == next || bridge->next || dsim->next); |
| |
| dsim->next = next; |
| next->encoder = encoder; |
| ret = drm_bridge_attach(encoder, next, bridge); |
| if (ret) { |
| dev_err(dev, "Unable to attach bridge %s: %d\n", |
| remote->name, ret); |
| dsim->next = NULL; |
| return ret; |
| } |
| |
| /* bridge chains */ |
| bridge->next = next; |
| |
| return 0; |
| } |
| |
| 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 = wait_for_completion_timeout(&dsim->pll_stable, HZ / 10); |
| if (!ret) { |
| dev_err(dsim->dev, "wait for pll stable time out\n"); |
| return -EBUSY; |
| } |
| |
| /* 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)) { |
| dev_err(dsim->dev, "clock is not in stop state\n"); |
| return -EBUSY; |
| } |
| |
| stop = STATUS_GET_STOPSTATEDAT(status); |
| if ((stop & data_lanes_en) != data_lanes_en) { |
| dev_err(dsim->dev, |
| "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, wc; |
| uint32_t mdresol = 0, mvporch = 0, mhporch = 0, msync = 0; |
| struct videomode *vmode = &dsim->vmode; |
| |
| mdresol |= MDRESOL_SET_MAINVRESOL(vmode->vactive) | |
| MDRESOL_SET_MAINHRESOL(vmode->hactive); |
| dsim_write(dsim, mdresol, DSIM_MDRESOL); |
| |
| mvporch |= MVPORCH_SET_MAINVBP(vmode->vback_porch) | |
| MVPORCH_SET_STABLEVFP(vmode->vfront_porch) | |
| 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->hpar) { |
| wc = DIV_ROUND_UP(vmode->hfront_porch * (bpp >> 3), |
| dsim->lanes); |
| hfp_wc = wc > MIPI_HFP_PKT_OVERHEAD ? |
| wc - MIPI_HFP_PKT_OVERHEAD : vmode->hfront_porch; |
| wc = DIV_ROUND_UP(vmode->hback_porch * (bpp >> 3), |
| dsim->lanes); |
| hbp_wc = wc > MIPI_HBP_PKT_OVERHEAD ? |
| wc - MIPI_HBP_PKT_OVERHEAD : vmode->hback_porch; |
| } else { |
| hfp_wc = dsim->hpar->hfp_wc; |
| hbp_wc = dsim->hpar->hbp_wc; |
| } |
| |
| mhporch |= MHPORCH_SET_MAINHFP(hfp_wc) | |
| MHPORCH_SET_MAINHBP(hbp_wc); |
| |
| dsim_write(dsim, mhporch, DSIM_MHPORCH); |
| |
| /* calculate hsa word counts */ |
| if (!dsim->hpar) { |
| wc = DIV_ROUND_UP(vmode->hsync_len * (bpp >> 3), |
| dsim->lanes); |
| hsa_wc = wc > MIPI_HSA_PKT_OVERHEAD ? |
| wc - MIPI_HSA_PKT_OVERHEAD : vmode->hsync_len; |
| } else |
| hsa_wc = dsim->hpar->hsa_wc; |
| |
| msync |= MSYNC_SET_MAINVSA(vmode->vsync_len) | |
| MSYNC_SET_MAINHSA(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_NON_CONTINUOUS_CLOCK_LANE; |
| 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) |
| { |
| struct sec_mipi_dsim_dphy_timing key = { 0 }; |
| const struct sec_mipi_dsim_dphy_timing *match = NULL; |
| const struct sec_mipi_dsim_plat_data *pdata = dsim->pdata; |
| uint32_t phytiming = 0, phytiming1 = 0, phytiming2 = 0, timeout = 0; |
| uint32_t hactive, vactive; |
| struct videomode *vmode = &dsim->vmode; |
| struct drm_display_mode mode; |
| |
| key.bit_clk = DIV_ROUND_CLOSEST_ULL(dsim->bit_clk, 1000); |
| |
| /* '1280x720@60Hz' mode with 2 data lanes |
| * requires special fine tuning for DPHY |
| * TIMING config according to the tests. |
| */ |
| if (dsim->lanes == 2) { |
| hactive = vmode->hactive; |
| vactive = vmode->vactive; |
| |
| if (hactive == 1280 && vactive == 720) { |
| memset(&mode, 0x0, sizeof(mode)); |
| drm_display_mode_from_videomode(vmode, &mode); |
| |
| if (drm_mode_vrefresh(&mode) == 60) |
| key.bit_clk >>= 1; |
| } |
| } |
| |
| match = bsearch(&key, pdata->dphy_timing, pdata->num_dphy_timing, |
| sizeof(struct sec_mipi_dsim_dphy_timing), |
| pdata->dphy_timing_cmp); |
| if (WARN_ON(!match)) |
| return; |
| |
| phytiming |= PHYTIMING_SET_M_TLPXCTL(match->lpx) | |
| PHYTIMING_SET_M_THSEXITCTL(match->hs_exit); |
| dsim_write(dsim, phytiming, DSIM_PHYTIMING); |
| |
| phytiming1 |= PHYTIMING1_SET_M_TCLKPRPRCTL(match->clk_prepare) | |
| PHYTIMING1_SET_M_TCLKZEROCTL(match->clk_zero) | |
| PHYTIMING1_SET_M_TCLKPOSTCTL(match->clk_post) | |
| PHYTIMING1_SET_M_TCLKTRAILCTL(match->clk_trail); |
| dsim_write(dsim, phytiming1, DSIM_PHYTIMING1); |
| |
| phytiming2 |= PHYTIMING2_SET_M_THSPRPRCTL(match->hs_prepare) | |
| PHYTIMING2_SET_M_THSZEROCTL(match->hs_zero) | |
| PHYTIMING2_SET_M_THSTRAILCTL(match->hs_trail); |
| dsim_write(dsim, phytiming2, DSIM_PHYTIMING2); |
| |
| timeout |= TIMEOUT_SET_BTAOUT(0xff) | |
| TIMEOUT_SET_LPDRTOUT(0xff); |
| dsim_write(dsim, timeout, DSIM_TIMEOUT); |
| } |
| |
| static void sec_mipi_dsim_init_fifo_pointers(struct sec_mipi_dsim *dsim) |
| { |
| uint32_t fifoctrl, fifo_ptrs; |
| |
| fifoctrl = dsim_read(dsim, DSIM_FIFOCTRL); |
| |
| fifo_ptrs = FIFOCTRL_NINITRX | |
| FIFOCTRL_NINITSFR | |
| FIFOCTRL_NINITI80 | |
| FIFOCTRL_NINITSUB | |
| FIFOCTRL_NINITMAIN; |
| |
| fifoctrl &= ~fifo_ptrs; |
| dsim_write(dsim, fifoctrl, DSIM_FIFOCTRL); |
| udelay(500); |
| |
| fifoctrl |= fifo_ptrs; |
| dsim_write(dsim, fifoctrl, DSIM_FIFOCTRL); |
| udelay(500); |
| } |
| |
| static void sec_mipi_dsim_config_clkctrl(struct sec_mipi_dsim *dsim) |
| { |
| uint32_t clkctrl = 0, data_lanes_en; |
| uint32_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(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); |
| } |
| |
| struct dsim_pll_pms *sec_mipi_dsim_calc_pmsk(struct sec_mipi_dsim *dsim) |
| { |
| uint32_t p, m, s; |
| uint32_t best_p, best_m, best_s; |
| uint32_t fin, fout; |
| uint32_t s_pow_2, raw_s; |
| uint64_t mfin, pfvco, pfout, psfout; |
| uint32_t delta, best_delta = ~0U; |
| struct dsim_pll_pms *pll_pms; |
| struct device *dev = dsim->dev; |
| const struct sec_mipi_dsim_plat_data *pdata = dsim->pdata; |
| struct sec_mipi_dsim_pll dpll = *pdata->dphy_pll; |
| struct sec_mipi_dsim_range *prange = &dpll.p; |
| struct sec_mipi_dsim_range *mrange = &dpll.m; |
| struct sec_mipi_dsim_range *srange = &dpll.s; |
| struct sec_mipi_dsim_range *krange = &dpll.k; |
| struct sec_mipi_dsim_range *fvco_range = &dpll.fvco; |
| struct sec_mipi_dsim_range *fpref_range = &dpll.fpref; |
| struct sec_mipi_dsim_range pr_new = *prange; |
| struct sec_mipi_dsim_range sr_new = *srange; |
| |
| pll_pms = devm_kzalloc(dev, sizeof(*pll_pms), GFP_KERNEL); |
| if (!pll_pms) { |
| dev_err(dev, "Unable to allocate 'pll_pms'\n"); |
| return ERR_PTR(-ENOMEM); |
| } |
| |
| fout = dsim->bit_clk; |
| fin = dsim->pref_clk; |
| |
| /* TODO: ignore 'k' for PMS calculation, |
| * only use 'p', 'm' and 's' to generate |
| * the requested PLL output clock. |
| */ |
| krange->min = 0; |
| krange->max = 0; |
| |
| /* narrow 'p' range via 'Fpref' limitation: |
| * Fpref : [2MHz ~ 30MHz] (Fpref = Fin / p) |
| */ |
| prange->min = max(prange->min, DIV_ROUND_UP(fin, fpref_range->max)); |
| prange->max = min(prange->max, fin / fpref_range->min); |
| |
| /* narrow 'm' range via 'Fvco' limitation: |
| * Fvco: [1050MHz ~ 2100MHz] (Fvco = ((m + k / 65536) * Fin) / p) |
| * So, m = Fvco * p / Fin and Fvco > Fin; |
| */ |
| pfvco = fvco_range->min * prange->min; |
| mrange->min = max_t(uint32_t, mrange->min, |
| DIV_ROUND_UP_ULL(pfvco, fin)); |
| pfvco = fvco_range->max * prange->max; |
| mrange->max = min_t(uint32_t, mrange->max, |
| DIV_ROUND_UP_ULL(pfvco, fin)); |
| |
| dev_dbg(dev, "p: min = %u, max = %u, " |
| "m: min = %u, max = %u, " |
| "s: min = %u, max = %u\n", |
| prange->min, prange->max, mrange->min, |
| mrange->max, srange->min, srange->max); |
| |
| /* first determine 'm', then can determine 'p', last determine 's' */ |
| for (m = mrange->min; m <= mrange->max; m++) { |
| /* p = m * Fin / Fvco */ |
| mfin = m * fin; |
| pr_new.min = max_t(uint32_t, prange->min, |
| DIV_ROUND_UP_ULL(mfin, fvco_range->max)); |
| pr_new.max = min_t(uint32_t, prange->max, |
| (mfin / fvco_range->min)); |
| |
| if (pr_new.max < pr_new.min || pr_new.min < prange->min) |
| continue; |
| |
| for (p = pr_new.min; p <= pr_new.max; p++) { |
| /* s = order_pow_of_two((m * Fin) / (p * Fout)) */ |
| pfout = p * fout; |
| raw_s = DIV_ROUND_CLOSEST_ULL(mfin, pfout); |
| |
| s_pow_2 = rounddown_pow_of_two(raw_s); |
| sr_new.min = max_t(uint32_t, srange->min, |
| order_base_2(s_pow_2)); |
| |
| s_pow_2 = roundup_pow_of_two(DIV_ROUND_CLOSEST_ULL(mfin, pfout)); |
| sr_new.max = min_t(uint32_t, srange->max, |
| order_base_2(s_pow_2)); |
| |
| if (sr_new.max < sr_new.min || sr_new.min < srange->min) |
| continue; |
| |
| for (s = sr_new.min; s <= sr_new.max; s++) { |
| /* fout = m * Fin / (p * 2^s) */ |
| psfout = pfout * (1 << s); |
| delta = abs(psfout - mfin); |
| if (delta < best_delta) { |
| best_p = p; |
| best_m = m; |
| best_s = s; |
| best_delta = delta; |
| } |
| } |
| } |
| } |
| |
| if (best_delta == ~0U) |
| return ERR_PTR(-EINVAL); |
| |
| pll_pms->p = best_p; |
| pll_pms->m = best_m; |
| pll_pms->s = best_s; |
| |
| dev_dbg(dev, "fout = %u, fin = %u, m = %u, " |
| "p = %u, s = %u, best_delta = %u\n", |
| fout, fin, pll_pms->m, pll_pms->p, pll_pms->s, best_delta); |
| |
| return pll_pms; |
| } |
| |
| int sec_mipi_dsim_check_pll_out(void *driver_private, |
| const struct drm_display_mode *mode) |
| { |
| int bpp; |
| uint32_t pix_clk, bit_clk; |
| struct sec_mipi_dsim *dsim = driver_private; |
| const struct sec_mipi_dsim_plat_data *pdata = dsim->pdata; |
| const struct dsim_hblank_par *hpar; |
| const struct dsim_pll_pms *pmsk; |
| |
| bpp = mipi_dsi_pixel_format_to_bpp(dsim->format); |
| if (bpp < 0) |
| return -EINVAL; |
| |
| pix_clk = mode->clock; |
| bit_clk = DIV_ROUND_UP(pix_clk * bpp, dsim->lanes); |
| |
| if (bit_clk * 1000 > pdata->max_data_rate) { |
| dev_err(dsim->dev, |
| "reuest bit clk freq exceeds lane's maximum value\n"); |
| return -EINVAL; |
| } |
| |
| dsim->pix_clk = pix_clk; |
| dsim->bit_clk = bit_clk; |
| dsim->hpar = NULL; |
| |
| pmsk = sec_mipi_dsim_calc_pmsk(dsim); |
| if (IS_ERR(pmsk)) { |
| dev_err(dsim->dev, |
| "failed to get pmsk for: fin = %u, fout = %u\n", |
| dsim->pref_clk, dsim->bit_clk); |
| return -EINVAL; |
| } |
| |
| dsim->pms = PLLCTRL_SET_P(pmsk->p) | |
| PLLCTRL_SET_M(pmsk->m) | |
| PLLCTRL_SET_S(pmsk->s); |
| |
| if (dsim->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) { |
| hpar = sec_mipi_dsim_get_hblank_par(mode->name, |
| mode->vrefresh, |
| dsim->lanes); |
| dsim->hpar = hpar; |
| if (!hpar) |
| dev_dbg(dsim->dev, "no pre-exist hpar can be used\n"); |
| } |
| |
| return 0; |
| } |
| EXPORT_SYMBOL(sec_mipi_dsim_check_pll_out); |
| |
| static void sec_mipi_dsim_bridge_enable(struct drm_bridge *bridge) |
| { |
| int ret; |
| struct sec_mipi_dsim *dsim = bridge->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); |
| |
| /* config dsim dpi */ |
| sec_mipi_dsim_config_dpi(dsim); |
| |
| /* config dsim pll */ |
| ret = sec_mipi_dsim_config_pll(dsim); |
| if (ret) { |
| dev_err(dsim->dev, "dsim pll config failed: %d\n", ret); |
| return; |
| } |
| |
| /* config dphy timings */ |
| sec_mipi_dsim_config_dphy(dsim); |
| |
| /* initialize FIFO pointers */ |
| sec_mipi_dsim_init_fifo_pointers(dsim); |
| |
| /* prepare panel if exists */ |
| if (dsim->panel) { |
| ret = drm_panel_prepare(dsim->panel); |
| if (unlikely(ret)) { |
| dev_err(dsim->dev, "panel prepare failed: %d\n", ret); |
| return; |
| } |
| } |
| |
| /* config esc clock, byte clock and etc */ |
| sec_mipi_dsim_config_clkctrl(dsim); |
| |
| /* enable panel if exists */ |
| if (dsim->panel) { |
| ret = drm_panel_enable(dsim->panel); |
| if (unlikely(ret)) { |
| dev_err(dsim->dev, "panel enable failed: %d\n", ret); |
| goto panel_unprepare; |
| } |
| } |
| |
| /* enable data transfer of dsim */ |
| sec_mipi_dsim_set_standby(dsim, true); |
| |
| return; |
| |
| panel_unprepare: |
| ret = drm_panel_unprepare(dsim->panel); |
| if (unlikely(ret)) |
| dev_err(dsim->dev, "panel unprepare failed: %d\n", ret); |
| } |
| |
| 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); |
| } |
| |
| static void sec_mipi_dsim_bridge_disable(struct drm_bridge *bridge) |
| { |
| int ret; |
| struct sec_mipi_dsim *dsim = bridge->driver_private; |
| |
| /* disable panel if exists */ |
| if (dsim->panel) { |
| ret = drm_panel_disable(dsim->panel); |
| if (unlikely(ret)) |
| dev_err(dsim->dev, "panel disable failed: %d\n", ret); |
| } |
| |
| /* disable data transfer of dsim */ |
| sec_mipi_dsim_set_standby(dsim, false); |
| |
| /* disable esc clock & byte clock */ |
| sec_mipi_dsim_disable_clkctrl(dsim); |
| |
| /* disable dsim pll */ |
| sec_mipi_dsim_disable_pll(dsim); |
| |
| /* unprepare panel if exists */ |
| if (dsim->panel) { |
| ret = drm_panel_unprepare(dsim->panel); |
| if (unlikely(ret)) |
| dev_err(dsim->dev, "panel unprepare failed: %d\n", ret); |
| } |
| } |
| |
| static bool sec_mipi_dsim_bridge_mode_fixup(struct drm_bridge *bridge, |
| const struct drm_display_mode *mode, |
| struct drm_display_mode *adjusted_mode) |
| { |
| int private_flags; |
| struct sec_mipi_dsim *dsim = bridge->driver_private; |
| |
| /* Since mipi dsi cannot do color conversion, |
| * so the pixel format output by mipi dsi should |
| * be the same with the pixel format recieved by |
| * mipi dsi. And the pixel format information needs |
| * to be passed to CRTC to be checked with the CRTC |
| * attached plane fb pixel format. |
| */ |
| switch (dsim->format) { |
| case MIPI_DSI_FMT_RGB888: |
| private_flags = MEDIA_BUS_FMT_RGB888_1X24; |
| break; |
| case MIPI_DSI_FMT_RGB666: |
| private_flags = MEDIA_BUS_FMT_RGB666_1X24_CPADHI; |
| break; |
| case MIPI_DSI_FMT_RGB666_PACKED: |
| private_flags = MEDIA_BUS_FMT_RGB666_1X18; |
| break; |
| case MIPI_DSI_FMT_RGB565: |
| private_flags = MEDIA_BUS_FMT_RGB565_1X16; |
| break; |
| default: |
| return false; |
| } |
| |
| adjusted_mode->private_flags = private_flags; |
| |
| /* the 'bus_flags' in connector's display_info is useless |
| * for mipi dsim, since dsim only sends packets with no |
| * polarities information in the packets. But the dsim |
| * host has some polarities requirements for the CRTC: |
| * dsim only can accpet active high Vsync, Hsync and DE |
| * signals. |
| */ |
| if (adjusted_mode->flags & DRM_MODE_FLAG_NHSYNC) { |
| adjusted_mode->flags &= ~DRM_MODE_FLAG_NHSYNC; |
| adjusted_mode->flags |= DRM_MODE_FLAG_PHSYNC; |
| } |
| |
| if (adjusted_mode->flags & DRM_MODE_FLAG_NVSYNC) { |
| adjusted_mode->flags &= ~DRM_MODE_FLAG_NVSYNC; |
| adjusted_mode->flags |= DRM_MODE_FLAG_PVSYNC; |
| } |
| |
| return true; |
| } |
| |
| static void sec_mipi_dsim_bridge_mode_set(struct drm_bridge *bridge, |
| struct drm_display_mode *mode, |
| struct drm_display_mode *adjusted_mode) |
| { |
| struct sec_mipi_dsim *dsim = bridge->driver_private; |
| |
| /* This hook is called when the display pipe is completely |
| * off. And since the pm runtime is implemented, the dsim |
| * hardware cannot be accessed at this moment. So move all |
| * the mode_set config to ->enable() hook. |
| * And this hook is called only when 'mode_changed' is true, |
| * so it is called not every time atomic commit. |
| */ |
| |
| /* workaround for CEA standard mode "1280x720@60" |
| * display on 4 data lanes with Non-burst with sync |
| * pulse DSI mode, since use the standard horizontal |
| * timings cannot display correctly. And this code |
| * cannot be put into the dsim Bridge's mode_fixup, |
| * since the DSI device lane number change always |
| * happens after that. |
| */ |
| if (!strcmp(mode->name, "1280x720") && |
| mode->vrefresh == 60 && |
| dsim->lanes == 4 && |
| dsim->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) { |
| adjusted_mode->hsync_start += 2; |
| adjusted_mode->hsync_end += 2; |
| adjusted_mode->htotal += 2; |
| } |
| |
| drm_display_mode_to_videomode(adjusted_mode, &dsim->vmode); |
| } |
| |
| static const struct drm_bridge_funcs sec_mipi_dsim_bridge_funcs = { |
| .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, |
| .mode_fixup = sec_mipi_dsim_bridge_mode_fixup, |
| }; |
| |
| void sec_mipi_dsim_suspend(struct device *dev) |
| { |
| struct sec_mipi_dsim *dsim = dev_get_drvdata(dev); |
| |
| /* TODO: add dsim reset */ |
| |
| clk_disable_unprepare(dsim->clk_cfg); |
| |
| clk_disable_unprepare(dsim->clk_pllref); |
| } |
| EXPORT_SYMBOL(sec_mipi_dsim_suspend); |
| |
| void sec_mipi_dsim_resume(struct device *dev) |
| { |
| struct sec_mipi_dsim *dsim = dev_get_drvdata(dev); |
| |
| clk_prepare_enable(dsim->clk_pllref); |
| |
| clk_prepare_enable(dsim->clk_cfg); |
| |
| sec_mipi_dsim_irq_init(dsim); |
| |
| /* TODO: add dsim de-reset */ |
| } |
| EXPORT_SYMBOL(sec_mipi_dsim_resume); |
| |
| static void __maybe_unused sec_mipi_dsim_irq_mask(struct sec_mipi_dsim *dsim, |
| int irq_idx) |
| { |
| uint32_t intmsk; |
| |
| intmsk = dsim_read(dsim, DSIM_INTMSK); |
| |
| switch (irq_idx) { |
| case PLLSTABLE: |
| intmsk |= INTMSK_MSKPLLSTABLE; |
| break; |
| case SWRSTRELEASE: |
| intmsk |= INTMSK_MSKSWRELEASE; |
| break; |
| case SFRPLFIFOEMPTY: |
| intmsk |= INTMSK_MSKSFRPLFIFOEMPTY; |
| break; |
| case SFRPHFIFOEMPTY: |
| intmsk |= INTMSK_MSKSFRPHFIFOEMPTY; |
| break; |
| case FRAMEDONE: |
| intmsk |= INTMSK_MSKFRAMEDONE; |
| break; |
| case LPDRTOUT: |
| intmsk |= INTMSK_MSKLPDRTOUT; |
| break; |
| case TATOUT: |
| intmsk |= INTMSK_MSKTATOUT; |
| break; |
| case RXDATDONE: |
| intmsk |= INTMSK_MSKRXDATDONE; |
| break; |
| case RXTE: |
| intmsk |= INTMSK_MSKRXTE; |
| break; |
| case RXACK: |
| intmsk |= INTMSK_MSKRXACK; |
| break; |
| default: |
| /* unsupported irq */ |
| return; |
| } |
| |
| writel(intmsk, dsim->base + DSIM_INTMSK); |
| } |
| |
| static void sec_mipi_dsim_irq_unmask(struct sec_mipi_dsim *dsim, |
| int irq_idx) |
| { |
| uint32_t intmsk; |
| |
| intmsk = dsim_read(dsim, DSIM_INTMSK); |
| |
| switch (irq_idx) { |
| case PLLSTABLE: |
| intmsk &= ~INTMSK_MSKPLLSTABLE; |
| break; |
| case SWRSTRELEASE: |
| intmsk &= ~INTMSK_MSKSWRELEASE; |
| break; |
| case SFRPLFIFOEMPTY: |
| intmsk &= ~INTMSK_MSKSFRPLFIFOEMPTY; |
| break; |
| case SFRPHFIFOEMPTY: |
| intmsk &= ~INTMSK_MSKSFRPHFIFOEMPTY; |
| break; |
| case FRAMEDONE: |
| intmsk &= ~INTMSK_MSKFRAMEDONE; |
| break; |
| case LPDRTOUT: |
| intmsk &= ~INTMSK_MSKLPDRTOUT; |
| break; |
| case TATOUT: |
| intmsk &= ~INTMSK_MSKTATOUT; |
| break; |
| case RXDATDONE: |
| intmsk &= ~INTMSK_MSKRXDATDONE; |
| break; |
| case RXTE: |
| intmsk &= ~INTMSK_MSKRXTE; |
| break; |
| case RXACK: |
| intmsk &= ~INTMSK_MSKRXACK; |
| break; |
| default: |
| /* unsupported irq */ |
| return; |
| } |
| |
| dsim_write(dsim, intmsk, DSIM_INTMSK); |
| } |
| |
| /* write 1 clear irq */ |
| static void sec_mipi_dsim_irq_clear(struct sec_mipi_dsim *dsim, |
| int irq_idx) |
| { |
| uint32_t intsrc = 0; |
| |
| switch (irq_idx) { |
| case PLLSTABLE: |
| intsrc |= INTSRC_PLLSTABLE; |
| break; |
| case SWRSTRELEASE: |
| intsrc |= INTSRC_SWRSTRELEASE; |
| break; |
| case SFRPLFIFOEMPTY: |
| intsrc |= INTSRC_SFRPLFIFOEMPTY; |
| break; |
| case SFRPHFIFOEMPTY: |
| intsrc |= INTSRC_SFRPHFIFOEMPTY; |
| break; |
| case FRAMEDONE: |
| intsrc |= INTSRC_FRAMEDONE; |
| break; |
| case LPDRTOUT: |
| intsrc |= INTSRC_LPDRTOUT; |
| break; |
| case TATOUT: |
| intsrc |= INTSRC_TATOUT; |
| break; |
| case RXDATDONE: |
| intsrc |= INTSRC_RXDATDONE; |
| break; |
| case RXTE: |
| intsrc |= INTSRC_RXTE; |
| break; |
| case RXACK: |
| intsrc |= INTSRC_RXACK; |
| break; |
| default: |
| /* unsupported irq */ |
| return; |
| } |
| |
| dsim_write(dsim, intsrc, DSIM_INTSRC); |
| } |
| |
| static void sec_mipi_dsim_irq_init(struct sec_mipi_dsim *dsim) |
| { |
| sec_mipi_dsim_irq_unmask(dsim, PLLSTABLE); |
| sec_mipi_dsim_irq_unmask(dsim, SWRSTRELEASE); |
| |
| if (dsim->panel) { |
| sec_mipi_dsim_irq_unmask(dsim, SFRPLFIFOEMPTY); |
| sec_mipi_dsim_irq_unmask(dsim, SFRPHFIFOEMPTY); |
| sec_mipi_dsim_irq_unmask(dsim, LPDRTOUT); |
| sec_mipi_dsim_irq_unmask(dsim, TATOUT); |
| sec_mipi_dsim_irq_unmask(dsim, RXDATDONE); |
| sec_mipi_dsim_irq_unmask(dsim, RXTE); |
| sec_mipi_dsim_irq_unmask(dsim, RXACK); |
| } |
| } |
| |
| static irqreturn_t sec_mipi_dsim_irq_handler(int irq, void *data) |
| { |
| uint32_t intsrc, status; |
| struct sec_mipi_dsim *dsim = data; |
| |
| intsrc = dsim_read(dsim, DSIM_INTSRC); |
| status = dsim_read(dsim, DSIM_STATUS); |
| |
| if (WARN_ON(!intsrc)) { |
| dev_err(dsim->dev, "interrupt is not from dsim\n"); |
| return IRQ_NONE; |
| } |
| |
| if (WARN_ON(!(intsrc & INTSRC_MASK))) { |
| dev_warn(dsim->dev, "unenable irq happens: %#x\n", intsrc); |
| /* just clear irqs */ |
| dsim_write(dsim, intsrc, DSIM_INTSRC); |
| return IRQ_NONE; |
| } |
| |
| if (intsrc & INTSRC_PLLSTABLE) { |
| WARN_ON(!(status & STATUS_PLLSTABLE)); |
| sec_mipi_dsim_irq_clear(dsim, PLLSTABLE); |
| complete(&dsim->pll_stable); |
| } |
| |
| if (intsrc & INTSRC_SWRSTRELEASE) |
| sec_mipi_dsim_irq_clear(dsim, SWRSTRELEASE); |
| |
| if (intsrc & INTSRC_SFRPLFIFOEMPTY) { |
| sec_mipi_dsim_irq_clear(dsim, SFRPLFIFOEMPTY); |
| complete(&dsim->pl_tx_done); |
| } |
| |
| if (intsrc & INTSRC_SFRPHFIFOEMPTY) { |
| sec_mipi_dsim_irq_clear(dsim, SFRPHFIFOEMPTY); |
| complete(&dsim->ph_tx_done); |
| } |
| |
| if (WARN_ON(intsrc & INTSRC_LPDRTOUT)) { |
| sec_mipi_dsim_irq_clear(dsim, LPDRTOUT); |
| dev_warn(dsim->dev, "LP RX timeout\n"); |
| } |
| |
| if (WARN_ON(intsrc & INTSRC_TATOUT)) { |
| sec_mipi_dsim_irq_clear(dsim, TATOUT); |
| dev_warn(dsim->dev, "Turns around Acknowledge timeout\n"); |
| } |
| |
| if (intsrc & INTSRC_RXDATDONE) { |
| sec_mipi_dsim_irq_clear(dsim, RXDATDONE); |
| complete(&dsim->rx_done); |
| } |
| |
| if (intsrc & INTSRC_RXTE) { |
| sec_mipi_dsim_irq_clear(dsim, RXTE); |
| dev_dbg(dsim->dev, "TE Rx trigger received\n"); |
| } |
| |
| if (intsrc & INTSRC_RXACK) { |
| sec_mipi_dsim_irq_clear(dsim, RXACK); |
| dev_dbg(dsim->dev, "ACK Rx trigger received\n"); |
| } |
| |
| return IRQ_HANDLED; |
| } |
| |
| static int sec_mipi_dsim_connector_get_modes(struct drm_connector *connector) |
| { |
| struct sec_mipi_dsim *dsim = conn_to_sec_mipi_dsim(connector); |
| |
| if (WARN_ON(!dsim->panel)) |
| return -ENODEV; |
| |
| return drm_panel_get_modes(dsim->panel); |
| } |
| |
| static const struct drm_connector_helper_funcs |
| sec_mipi_dsim_connector_helper_funcs = { |
| .get_modes = sec_mipi_dsim_connector_get_modes, |
| }; |
| |
| static enum drm_connector_status |
| sec_mipi_dsim_connector_detect(struct drm_connector *connector, |
| bool force) |
| { |
| /* TODO: add support later */ |
| |
| return connector_status_connected; |
| } |
| |
| static const struct drm_connector_funcs sec_mipi_dsim_connector_funcs = { |
| .detect = sec_mipi_dsim_connector_detect, |
| .fill_modes = drm_helper_probe_single_connector_modes, |
| .destroy = drm_connector_cleanup, |
| .reset = drm_atomic_helper_connector_reset, |
| .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, |
| .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, |
| }; |
| |
| int sec_mipi_dsim_bind(struct device *dev, struct device *master, void *data, |
| struct drm_encoder *encoder, struct resource *res, |
| int irq, const struct sec_mipi_dsim_plat_data *pdata) |
| { |
| int ret, version; |
| struct drm_device *drm_dev = data; |
| struct drm_bridge *bridge; |
| struct drm_connector *connector; |
| struct sec_mipi_dsim *dsim; |
| |
| dev_dbg(dev, "sec-dsim bridge bind begin\n"); |
| |
| dsim = devm_kzalloc(dev, sizeof(*dsim), GFP_KERNEL); |
| if (!dsim) { |
| dev_err(dev, "Unable to allocate 'dsim'\n"); |
| return -ENOMEM; |
| } |
| |
| dsim->dev = dev; |
| dsim->irq = irq; |
| dsim->pdata = pdata; |
| dsim->encoder = encoder; |
| |
| dsim->dsi_host.ops = &sec_mipi_dsim_host_ops; |
| dsim->dsi_host.dev = dev; |
| |
| dsim->base = devm_ioremap_resource(dev, res); |
| if (IS_ERR(dsim->base)) |
| return PTR_ERR(dsim->base); |
| |
| dsim->clk_pllref = devm_clk_get(dev, "pll-ref"); |
| if (IS_ERR(dsim->clk_pllref)) { |
| ret = PTR_ERR(dsim->clk_pllref); |
| dev_err(dev, "Unable to get phy pll reference clock: %d\n", ret); |
| return ret; |
| } |
| |
| dsim->clk_cfg = devm_clk_get(dev, "cfg"); |
| if (IS_ERR(dsim->clk_cfg)) { |
| ret = PTR_ERR(dsim->clk_cfg); |
| dev_err(dev, "Unable to get configuration clock: %d\n", ret); |
| return ret; |
| } |
| |
| clk_prepare_enable(dsim->clk_cfg); |
| version = dsim_read(dsim, DSIM_VERSION); |
| WARN_ON(version != pdata->version); |
| clk_disable_unprepare(dsim->clk_cfg); |
| |
| dev_info(dev, "version number is %#x\n", version); |
| |
| /* set suitable rate for phy ref clock */ |
| ret = sec_mipi_dsim_set_pref_rate(dsim); |
| if (ret) { |
| dev_err(dev, "failed to set pll ref clock rate\n"); |
| return ret; |
| } |
| |
| ret = devm_request_irq(dev, dsim->irq, |
| sec_mipi_dsim_irq_handler, |
| 0, dev_name(dev), dsim); |
| if (ret) { |
| dev_err(dev, "failed to request dsim irq: %d\n", ret); |
| return ret; |
| } |
| |
| init_completion(&dsim->pll_stable); |
| init_completion(&dsim->ph_tx_done); |
| init_completion(&dsim->pl_tx_done); |
| init_completion(&dsim->rx_done); |
| |
| /* Initialize and attach sec dsim bridge */ |
| bridge = devm_kzalloc(dev, sizeof(*bridge), GFP_KERNEL); |
| if (!bridge) { |
| dev_err(dev, "Unable to allocate 'bridge'\n"); |
| return -ENOMEM; |
| } |
| |
| /* mipi dsi host needs to be registered before bridge attach, since: |
| * 1. Have Panel |
| * The 'mipi_dsi_host_register' will allocate a mipi_dsi_device |
| * if the dsi host node has a panel child node in DTB. And dsi |
| * host ->attach() will be called in panel's probe(). |
| * |
| * 2. Have Bridge |
| * The dsi host ->attach() will be called through the below |
| * 'drm_bridge_attach()' which will attach next bridge in a |
| * chain. |
| */ |
| ret = mipi_dsi_host_register(&dsim->dsi_host); |
| if (ret) { |
| dev_err(dev, "Unable to register mipi dsi host: %d\n", ret); |
| return ret; |
| } |
| |
| dsim->bridge = bridge; |
| bridge->driver_private = dsim; |
| bridge->funcs = &sec_mipi_dsim_bridge_funcs; |
| bridge->of_node = dev->of_node; |
| bridge->encoder = encoder; |
| encoder->bridge = bridge; |
| |
| dev_set_drvdata(dev, dsim); |
| |
| /* attach sec dsim bridge and its next bridge if exists */ |
| ret = drm_bridge_attach(encoder, bridge, NULL); |
| if (ret) { |
| dev_err(dev, "Failed to attach bridge: %s\n", dev_name(dev)); |
| mipi_dsi_host_unregister(&dsim->dsi_host); |
| return ret; |
| } |
| |
| if (dsim->panel) { |
| /* A panel has been attached */ |
| connector = &dsim->connector; |
| |
| drm_connector_helper_add(connector, |
| &sec_mipi_dsim_connector_helper_funcs); |
| ret = drm_connector_init(drm_dev, connector, |
| &sec_mipi_dsim_connector_funcs, |
| DRM_MODE_CONNECTOR_DSI); |
| if (ret) |
| goto host_unregister; |
| |
| /* TODO */ |
| connector->dpms = DRM_MODE_DPMS_OFF; |
| |
| ret = drm_mode_connector_attach_encoder(connector, encoder); |
| if (ret) |
| goto cleanup_connector; |
| |
| ret = drm_panel_attach(dsim->panel, connector); |
| if (ret) |
| goto cleanup_connector; |
| } |
| |
| dev_dbg(dev, "sec-dsim bridge bind end\n"); |
| |
| return 0; |
| |
| cleanup_connector: |
| drm_connector_cleanup(connector); |
| host_unregister: |
| mipi_dsi_host_unregister(&dsim->dsi_host); |
| return ret; |
| } |
| EXPORT_SYMBOL(sec_mipi_dsim_bind); |
| |
| void sec_mipi_dsim_unbind(struct device *dev, struct device *master, void *data) |
| { |
| struct sec_mipi_dsim *dsim = dev_get_drvdata(dev); |
| |
| if (dsim->panel) { |
| drm_panel_detach(dsim->panel); |
| drm_connector_cleanup(&dsim->connector); |
| dsim->panel = NULL; |
| } |
| |
| mipi_dsi_host_unregister(&dsim->dsi_host); |
| } |
| EXPORT_SYMBOL(sec_mipi_dsim_unbind); |
| |
| MODULE_DESCRIPTION("Samsung MIPI DSI Host Controller bridge driver"); |
| MODULE_AUTHOR("Fancy Fang <chen.fang@nxp.com>"); |
| MODULE_LICENSE("GPL"); |