summaryrefslogtreecommitdiff
path: root/drivers/gpu/drm/bridge/sec-dsim.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/gpu/drm/bridge/sec-dsim.c')
-rw-r--r--drivers/gpu/drm/bridge/sec-dsim.c2013
1 files changed, 2013 insertions, 0 deletions
diff --git a/drivers/gpu/drm/bridge/sec-dsim.c b/drivers/gpu/drm/bridge/sec-dsim.c
new file mode 100644
index 000000000000..8eb9d1863ef4
--- /dev/null
+++ b/drivers/gpu/drm/bridge/sec-dsim.c
@@ -0,0 +1,2013 @@
+/*
+ * 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 <linux/pm_runtime.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_probe_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 12000
+
+#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;
+
+ /* detached panel should be NULL */
+ dsim->panel = NULL;
+
+ 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;
+ /* fall through */
+ case 2:
+ pl_data |= ((u8 *)payload)[1] << 8;
+ /* fall through */
+ 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;
+ /* fall through */
+ case 2:
+ ((u8 *)payload)[1] = (pl >> 8) & 0xff;
+ /* fall through */
+ 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;
+ bool attach_bridge = false;
+ 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 = NULL;
+ struct drm_bridge *next = ERR_PTR(-ENODEV);
+ 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) {
+ /* check the endpoint can attach bridge or not */
+ attach_bridge = of_property_read_bool(endpoint, "attach-bridge");
+ if (!attach_bridge) {
+ endpoint = of_graph_get_next_endpoint(np, endpoint);
+ continue;
+ }
+
+ 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 workable bridge exists */
+ if (IS_ERR(next))
+ return PTR_ERR(next);
+
+ /* For the panel driver loading is after dsim bridge,
+ * defer bridge binding to wait for panel driver ready.
+ * The disadvantage of probe defer is endless probing
+ * in some cases.
+ */
+ if (!next)
+ return -EPROBE_DEFER;
+
+ /* 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 = 0, best_m = 0, best_s = 0;
+ 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 = (uint64_t)fvco_range->min * prange->min;
+ mrange->min = max_t(uint32_t, mrange->min,
+ DIV_ROUND_UP_ULL(pfvco, fin));
+ pfvco = (uint64_t)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 = (uint64_t)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 = (uint64_t)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) {
+ devm_kfree(dev, pll_pms);
+ 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);
+
+ /* free 'dsim_pll_pms' structure data which is
+ * allocated in 'sec_mipi_dsim_calc_pmsk()'.
+ */
+ devm_kfree(dsim->dev, (void *)pmsk);
+
+ 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;
+ }
+
+ /* 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;
+ }
+
+ return true;
+}
+
+static void sec_mipi_dsim_bridge_mode_set(struct drm_bridge *bridge,
+ const struct drm_display_mode *mode,
+ const 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.
+ */
+
+ 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;
+ struct device_node *node = NULL;
+
+ 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;
+ }
+
+ dev_set_drvdata(dev, dsim);
+
+ pm_runtime_get_sync(dev);
+ version = dsim_read(dsim, DSIM_VERSION);
+ WARN_ON(version != pdata->version);
+ pm_runtime_put_sync(dev);
+
+ 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;
+
+ /* 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));
+
+ /* no bridge exists, so defer probe to wait
+ * panel driver loading
+ */
+ if (ret != -EPROBE_DEFER) {
+ for_each_available_child_of_node(dev->of_node, node) {
+ /* skip nodes without reg property */
+ if (!of_find_property(node, "reg", NULL))
+ continue;
+
+ /* error codes only ENODEV or EPROBE_DEFER */
+ dsim->panel = of_drm_find_panel(node);
+ if (!IS_ERR(dsim->panel))
+ goto panel;
+
+ ret = PTR_ERR(dsim->panel);
+ }
+ }
+
+ mipi_dsi_host_unregister(&dsim->dsi_host);
+ return ret;
+ }
+
+panel:
+ 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_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);
+ }
+
+ 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");