diff mbox series

[v3,RESEND,9/9] drm/bridge: imx: Add i.MX93 MIPI DSI support

Message ID 20230821034008.3876938-10-victor.liu@nxp.com
State Accepted
Commit ce62f8ea7e3f8a8e788c3b5ea0195f1224575b66
Headers show
Series [v3,RESEND,1/9] drm/bridge: synopsys: dw-mipi-dsi: Add dw_mipi_dsi_get_bridge() helper | expand

Commit Message

Liu Ying Aug. 21, 2023, 3:40 a.m. UTC
Freescale i.MX93 SoC embeds a Synopsys Designware MIPI DSI host
controller and a Synopsys Designware MIPI DPHY.  Some configurations
and extensions to them are controlled by i.MX93 media blk-ctrl.

Add a DRM bridge for i.MX93 MIPI DSI by using existing DW MIPI DSI
bridge helpers and implementing i.MX93 MIPI DSI specific extensions.

Signed-off-by: Liu Ying <victor.liu@nxp.com>
---
v2->v3:
* Select GENERIC_PHY to fix Kconfig warning for GENERIC_PHY_MIPI_DPHY
  dependency.

v1->v2:
* Use dev_err_probe() to replace DRM_DEV_ERROR().  (Sam and Alexander)
* Use dev_*() to replace DRM_*().  (Sam)
* Fix build for arm architecture.
  (Reported-by: kernel test robot <lkp@intel.com>)
* Improve error messages for imx93_dsi_phy_init().

 drivers/gpu/drm/bridge/imx/Kconfig          |  11 +
 drivers/gpu/drm/bridge/imx/Makefile         |   1 +
 drivers/gpu/drm/bridge/imx/imx93-mipi-dsi.c | 917 ++++++++++++++++++++
 3 files changed, 929 insertions(+)
 create mode 100644 drivers/gpu/drm/bridge/imx/imx93-mipi-dsi.c

Comments

Robert Foss Oct. 16, 2023, 9:36 a.m. UTC | #1
On Mon, Aug 21, 2023 at 5:38 AM Liu Ying <victor.liu@nxp.com> wrote:
>
> Freescale i.MX93 SoC embeds a Synopsys Designware MIPI DSI host
> controller and a Synopsys Designware MIPI DPHY.  Some configurations
> and extensions to them are controlled by i.MX93 media blk-ctrl.
>
> Add a DRM bridge for i.MX93 MIPI DSI by using existing DW MIPI DSI
> bridge helpers and implementing i.MX93 MIPI DSI specific extensions.
>
> Signed-off-by: Liu Ying <victor.liu@nxp.com>
> ---
> v2->v3:
> * Select GENERIC_PHY to fix Kconfig warning for GENERIC_PHY_MIPI_DPHY
>   dependency.
>
> v1->v2:
> * Use dev_err_probe() to replace DRM_DEV_ERROR().  (Sam and Alexander)
> * Use dev_*() to replace DRM_*().  (Sam)
> * Fix build for arm architecture.
>   (Reported-by: kernel test robot <lkp@intel.com>)
> * Improve error messages for imx93_dsi_phy_init().
>
>  drivers/gpu/drm/bridge/imx/Kconfig          |  11 +
>  drivers/gpu/drm/bridge/imx/Makefile         |   1 +
>  drivers/gpu/drm/bridge/imx/imx93-mipi-dsi.c | 917 ++++++++++++++++++++
>  3 files changed, 929 insertions(+)
>  create mode 100644 drivers/gpu/drm/bridge/imx/imx93-mipi-dsi.c
>
> diff --git a/drivers/gpu/drm/bridge/imx/Kconfig b/drivers/gpu/drm/bridge/imx/Kconfig
> index 9fae28db6aa7..5a4f3d58501e 100644
> --- a/drivers/gpu/drm/bridge/imx/Kconfig
> +++ b/drivers/gpu/drm/bridge/imx/Kconfig
> @@ -49,4 +49,15 @@ config DRM_IMX8QXP_PIXEL_LINK_TO_DPI
>           Choose this to enable pixel link to display pixel interface(PXL2DPI)
>           found in Freescale i.MX8qxp processor.
>
> +config DRM_IMX93_MIPI_DSI
> +       tristate "Freescale i.MX93 specific extensions for Synopsys DW MIPI DSI"
> +       depends on OF
> +       depends on COMMON_CLK
> +       select DRM_DW_MIPI_DSI
> +       select GENERIC_PHY
> +       select GENERIC_PHY_MIPI_DPHY
> +       help
> +         Choose this to enable MIPI DSI controller found in Freescale i.MX93
> +         processor.
> +
>  endif # ARCH_MXC || COMPILE_TEST
> diff --git a/drivers/gpu/drm/bridge/imx/Makefile b/drivers/gpu/drm/bridge/imx/Makefile
> index 8e2ebf3399a1..2b0c2e44aa1b 100644
> --- a/drivers/gpu/drm/bridge/imx/Makefile
> +++ b/drivers/gpu/drm/bridge/imx/Makefile
> @@ -4,3 +4,4 @@ obj-$(CONFIG_DRM_IMX8QXP_LDB) += imx8qxp-ldb.o
>  obj-$(CONFIG_DRM_IMX8QXP_PIXEL_COMBINER) += imx8qxp-pixel-combiner.o
>  obj-$(CONFIG_DRM_IMX8QXP_PIXEL_LINK) += imx8qxp-pixel-link.o
>  obj-$(CONFIG_DRM_IMX8QXP_PIXEL_LINK_TO_DPI) += imx8qxp-pxl2dpi.o
> +obj-$(CONFIG_DRM_IMX93_MIPI_DSI) += imx93-mipi-dsi.o
> diff --git a/drivers/gpu/drm/bridge/imx/imx93-mipi-dsi.c b/drivers/gpu/drm/bridge/imx/imx93-mipi-dsi.c
> new file mode 100644
> index 000000000000..3ff30ce80c5b
> --- /dev/null
> +++ b/drivers/gpu/drm/bridge/imx/imx93-mipi-dsi.c
> @@ -0,0 +1,917 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +
> +/*
> + * Copyright 2022,2023 NXP
> + */
> +
> +#include <linux/bitfield.h>
> +#include <linux/bits.h>
> +#include <linux/clk.h>
> +#include <linux/delay.h>
> +#include <linux/math.h>
> +#include <linux/media-bus-format.h>
> +#include <linux/mfd/syscon.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/phy/phy.h>
> +#include <linux/phy/phy-mipi-dphy.h>
> +#include <linux/platform_device.h>
> +#include <linux/regmap.h>
> +
> +#include <drm/bridge/dw_mipi_dsi.h>
> +#include <drm/drm_bridge.h>
> +#include <drm/drm_mipi_dsi.h>
> +#include <drm/drm_modes.h>
> +
> +/* DPHY PLL configuration registers */
> +#define DSI_REG                                0x4c
> +#define  CFGCLKFREQRANGE_MASK          GENMASK(5, 0)
> +#define  CFGCLKFREQRANGE(x)            FIELD_PREP(CFGCLKFREQRANGE_MASK, (x))
> +#define  CLKSEL_MASK                   GENMASK(7, 6)
> +#define  CLKSEL_STOP                   FIELD_PREP(CLKSEL_MASK, 0)
> +#define  CLKSEL_GEN                    FIELD_PREP(CLKSEL_MASK, 1)
> +#define  CLKSEL_EXT                    FIELD_PREP(CLKSEL_MASK, 2)
> +#define  HSFREQRANGE_MASK              GENMASK(14, 8)
> +#define  HSFREQRANGE(x)                        FIELD_PREP(HSFREQRANGE_MASK, (x))
> +#define  UPDATE_PLL                    BIT(17)
> +#define  SHADOW_CLR                    BIT(18)
> +#define  CLK_EXT                       BIT(19)
> +
> +#define DSI_WRITE_REG0                 0x50
> +#define  M_MASK                                GENMASK(9, 0)
> +#define  M(x)                          FIELD_PREP(M_MASK, ((x) - 2))
> +#define  N_MASK                                GENMASK(13, 10)
> +#define  N(x)                          FIELD_PREP(N_MASK, ((x) - 1))
> +#define  VCO_CTRL_MASK                 GENMASK(19, 14)
> +#define  VCO_CTRL(x)                   FIELD_PREP(VCO_CTRL_MASK, (x))
> +#define  PROP_CTRL_MASK                        GENMASK(25, 20)
> +#define  PROP_CTRL(x)                  FIELD_PREP(PROP_CTRL_MASK, (x))
> +#define  INT_CTRL_MASK                 GENMASK(31, 26)
> +#define  INT_CTRL(x)                   FIELD_PREP(INT_CTRL_MASK, (x))
> +
> +#define DSI_WRITE_REG1                 0x54
> +#define  GMP_CTRL_MASK                 GENMASK(1, 0)
> +#define  GMP_CTRL(x)                   FIELD_PREP(GMP_CTRL_MASK, (x))
> +#define  CPBIAS_CTRL_MASK              GENMASK(8, 2)
> +#define  CPBIAS_CTRL(x)                        FIELD_PREP(CPBIAS_CTRL_MASK, (x))
> +#define  PLL_SHADOW_CTRL               BIT(9)
> +
> +/* display mux control register */
> +#define DISPLAY_MUX                    0x60
> +#define  MIPI_DSI_RGB666_MAP_CFG       GENMASK(7, 6)
> +#define  RGB666_CONFIG1                        FIELD_PREP(MIPI_DSI_RGB666_MAP_CFG, 0)
> +#define  RGB666_CONFIG2                        FIELD_PREP(MIPI_DSI_RGB666_MAP_CFG, 1)
> +#define  MIPI_DSI_RGB565_MAP_CFG       GENMASK(5, 4)
> +#define  RGB565_CONFIG1                        FIELD_PREP(MIPI_DSI_RGB565_MAP_CFG, 0)
> +#define  RGB565_CONFIG2                        FIELD_PREP(MIPI_DSI_RGB565_MAP_CFG, 1)
> +#define  RGB565_CONFIG3                        FIELD_PREP(MIPI_DSI_RGB565_MAP_CFG, 2)
> +#define  LCDIF_CROSS_LINE_PATTERN      GENMASK(3, 0)
> +#define  RGB888_TO_RGB888              FIELD_PREP(LCDIF_CROSS_LINE_PATTERN, 0)
> +#define  RGB888_TO_RGB666              FIELD_PREP(LCDIF_CROSS_LINE_PATTERN, 6)
> +#define  RGB565_TO_RGB565              FIELD_PREP(LCDIF_CROSS_LINE_PATTERN, 7)
> +
> +#define MHZ(x)                         ((x) * 1000000UL)
> +
> +#define REF_CLK_RATE_MAX               MHZ(64)
> +#define REF_CLK_RATE_MIN               MHZ(2)
> +#define FOUT_MAX                       MHZ(1250)
> +#define FOUT_MIN                       MHZ(40)
> +#define FVCO_DIV_FACTOR                        MHZ(80)
> +
> +#define MBPS(x)                                ((x) * 1000000UL)
> +
> +#define DATA_RATE_MAX_SPEED            MBPS(2500)
> +#define DATA_RATE_MIN_SPEED            MBPS(80)
> +
> +#define M_MAX                          625UL
> +#define M_MIN                          64UL
> +
> +#define N_MAX                          16U
> +#define N_MIN                          1U
> +
> +struct imx93_dsi {
> +       struct device *dev;
> +       struct regmap *regmap;
> +       struct clk *clk_pixel;
> +       struct clk *clk_ref;
> +       struct clk *clk_cfg;
> +       struct dw_mipi_dsi *dmd;
> +       struct dw_mipi_dsi_plat_data pdata;
> +       union phy_configure_opts phy_cfg;
> +       unsigned long ref_clk_rate;
> +       u32 format;
> +};
> +
> +struct dphy_pll_cfg {
> +       u32 m;  /* PLL Feedback Multiplication Ratio */
> +       u32 n;  /* PLL Input Frequency Division Ratio */
> +};
> +
> +struct dphy_pll_vco_prop {
> +       unsigned long max_fout;
> +       u8 vco_cntl;
> +       u8 prop_cntl;
> +};
> +
> +struct dphy_pll_hsfreqrange {
> +       unsigned long max_mbps;
> +       u8 hsfreqrange;
> +};
> +
> +/* DPHY Databook Table 3-13 Charge-pump Programmability */
> +static const struct dphy_pll_vco_prop vco_prop_map[] = {
> +       {   55, 0x3f, 0x0d },
> +       {   82, 0x37, 0x0d },
> +       {  110, 0x2f, 0x0d },
> +       {  165, 0x27, 0x0d },
> +       {  220, 0x1f, 0x0d },
> +       {  330, 0x17, 0x0d },
> +       {  440, 0x0f, 0x0d },
> +       {  660, 0x07, 0x0d },
> +       { 1149, 0x03, 0x0d },
> +       { 1152, 0x01, 0x0d },
> +       { 1250, 0x01, 0x0e },
> +};
> +
> +/* DPHY Databook Table 5-7 Frequency Ranges and Defaults */
> +static const struct dphy_pll_hsfreqrange hsfreqrange_map[] = {
> +       {   89, 0x00 },
> +       {   99, 0x10 },
> +       {  109, 0x20 },
> +       {  119, 0x30 },
> +       {  129, 0x01 },
> +       {  139, 0x11 },
> +       {  149, 0x21 },
> +       {  159, 0x31 },
> +       {  169, 0x02 },
> +       {  179, 0x12 },
> +       {  189, 0x22 },
> +       {  204, 0x32 },
> +       {  219, 0x03 },
> +       {  234, 0x13 },
> +       {  249, 0x23 },
> +       {  274, 0x33 },
> +       {  299, 0x04 },
> +       {  324, 0x14 },
> +       {  349, 0x25 },
> +       {  399, 0x35 },
> +       {  449, 0x05 },
> +       {  499, 0x16 },
> +       {  549, 0x26 },
> +       {  599, 0x37 },
> +       {  649, 0x07 },
> +       {  699, 0x18 },
> +       {  749, 0x28 },
> +       {  799, 0x39 },
> +       {  849, 0x09 },
> +       {  899, 0x19 },
> +       {  949, 0x29 },
> +       {  999, 0x3a },
> +       { 1049, 0x0a },
> +       { 1099, 0x1a },
> +       { 1149, 0x2a },
> +       { 1199, 0x3b },
> +       { 1249, 0x0b },
> +       { 1299, 0x1b },
> +       { 1349, 0x2b },
> +       { 1399, 0x3c },
> +       { 1449, 0x0c },
> +       { 1499, 0x1c },
> +       { 1549, 0x2c },
> +       { 1599, 0x3d },
> +       { 1649, 0x0d },
> +       { 1699, 0x1d },
> +       { 1749, 0x2e },
> +       { 1799, 0x3e },
> +       { 1849, 0x0e },
> +       { 1899, 0x1e },
> +       { 1949, 0x2f },
> +       { 1999, 0x3f },
> +       { 2049, 0x0f },
> +       { 2099, 0x40 },
> +       { 2149, 0x41 },
> +       { 2199, 0x42 },
> +       { 2249, 0x43 },
> +       { 2299, 0x44 },
> +       { 2349, 0x45 },
> +       { 2399, 0x46 },
> +       { 2449, 0x47 },
> +       { 2499, 0x48 },
> +       { 2500, 0x49 },
> +};
> +
> +static void dphy_pll_write(struct imx93_dsi *dsi, unsigned int reg, u32 value)
> +{
> +       int ret;
> +
> +       ret = regmap_write(dsi->regmap, reg, value);
> +       if (ret < 0)
> +               dev_err(dsi->dev, "failed to write 0x%08x to pll reg 0x%x: %d\n",
> +                       value, reg, ret);
> +}
> +
> +static inline unsigned long data_rate_to_fout(unsigned long data_rate)
> +{
> +       /* Fout is half of data rate */
> +       return data_rate / 2;
> +}
> +
> +static int
> +dphy_pll_get_configure_from_opts(struct imx93_dsi *dsi,
> +                                struct phy_configure_opts_mipi_dphy *dphy_opts,
> +                                struct dphy_pll_cfg *cfg)
> +{
> +       struct device *dev = dsi->dev;
> +       unsigned long fin = dsi->ref_clk_rate;
> +       unsigned long fout;
> +       unsigned long best_fout = 0;
> +       unsigned int fvco_div;
> +       unsigned int min_n, max_n, n, best_n;
> +       unsigned long m, best_m;
> +       unsigned long min_delta = ULONG_MAX;
> +       unsigned long delta;
> +       u64 tmp;
> +
> +       if (dphy_opts->hs_clk_rate < DATA_RATE_MIN_SPEED ||
> +           dphy_opts->hs_clk_rate > DATA_RATE_MAX_SPEED) {
> +               dev_dbg(dev, "invalid data rate per lane: %lu\n",
> +                       dphy_opts->hs_clk_rate);
> +               return -EINVAL;
> +       }
> +
> +       fout = data_rate_to_fout(dphy_opts->hs_clk_rate);
> +
> +       /* DPHY Databook 3.3.6.1 Output Frequency */
> +       /* Fout = Fvco / Fvco_div = (Fin * M) / (Fvco_div * N) */
> +       /* Fvco_div could be 1/2/4/8 according to Fout range. */
> +       fvco_div = 8UL / min(DIV_ROUND_UP(fout, FVCO_DIV_FACTOR), 8UL);
> +
> +       /* limitation: 2MHz <= Fin / N <= 8MHz */
> +       min_n = DIV_ROUND_UP_ULL((u64)fin, MHZ(8));
> +       max_n = DIV_ROUND_DOWN_ULL((u64)fin, MHZ(2));
> +
> +       /* clamp possible N(s) */
> +       min_n = clamp(min_n, N_MIN, N_MAX);
> +       max_n = clamp(max_n, N_MIN, N_MAX);
> +
> +       dev_dbg(dev, "Fout = %lu, Fvco_div = %u, n_range = [%u, %u]\n",
> +               fout, fvco_div, min_n, max_n);
> +
> +       for (n = min_n; n <= max_n; n++) {
> +               /* M = (Fout * N * Fvco_div) / Fin */
> +               m = DIV_ROUND_CLOSEST(fout * n * fvco_div, fin);
> +
> +               /* check M range */
> +               if (m < M_MIN || m > M_MAX)
> +                       continue;
> +
> +               /* calculate temporary Fout */
> +               tmp = m * fin;
> +               do_div(tmp, n * fvco_div);
> +               if (tmp < FOUT_MIN || tmp > FOUT_MAX)
> +                       continue;
> +
> +               delta = abs(fout - tmp);
> +               if (delta < min_delta) {
> +                       best_n = n;
> +                       best_m = m;
> +                       min_delta = delta;
> +                       best_fout = tmp;
> +               }
> +       }
> +
> +       if (best_fout) {
> +               cfg->m = best_m;
> +               cfg->n = best_n;
> +               dev_dbg(dev, "best Fout = %lu, m = %u, n = %u\n",
> +                       best_fout, cfg->m, cfg->n);
> +       } else {
> +               dev_dbg(dev, "failed to find best Fout\n");
> +               return -EINVAL;
> +       }
> +
> +       return 0;
> +}
> +
> +static void dphy_pll_clear_shadow(struct imx93_dsi *dsi)
> +{
> +       /* Reference DPHY Databook Figure 3-3 Initialization Timing Diagram. */
> +       /* Select clock generation first. */
> +       dphy_pll_write(dsi, DSI_REG, CLKSEL_GEN);
> +
> +       /* Clear shadow after clock selection is done a while. */
> +       fsleep(1);
> +       dphy_pll_write(dsi, DSI_REG, CLKSEL_GEN | SHADOW_CLR);
> +
> +       /* A minimum pulse of 5ns on shadow_clear signal. */
> +       fsleep(1);
> +       dphy_pll_write(dsi, DSI_REG, CLKSEL_GEN);
> +}
> +
> +static unsigned long dphy_pll_get_cfgclkrange(struct imx93_dsi *dsi)
> +{
> +       /*
> +        * DPHY Databook Table 4-4 System Control Signals mentions an equation
> +        * for cfgclkfreqrange[5:0].
> +        */
> +       return (clk_get_rate(dsi->clk_cfg) / MHZ(1) - 17) * 4;
> +}
> +
> +static u8
> +dphy_pll_get_hsfreqrange(struct phy_configure_opts_mipi_dphy *dphy_opts)
> +{
> +       unsigned long mbps = dphy_opts->hs_clk_rate / MHZ(1);
> +       int i;
> +
> +       for (i = 0; i < ARRAY_SIZE(hsfreqrange_map); i++)
> +               if (mbps <= hsfreqrange_map[i].max_mbps)
> +                       return hsfreqrange_map[i].hsfreqrange;
> +
> +       return 0;
> +}
> +
> +static u8 dphy_pll_get_vco(struct phy_configure_opts_mipi_dphy *dphy_opts)
> +{
> +       unsigned long fout = data_rate_to_fout(dphy_opts->hs_clk_rate) / MHZ(1);
> +       int i;
> +
> +       for (i = 0; i < ARRAY_SIZE(vco_prop_map); i++)
> +               if (fout <= vco_prop_map[i].max_fout)
> +                       return vco_prop_map[i].vco_cntl;
> +
> +       return 0;
> +}
> +
> +static u8 dphy_pll_get_prop(struct phy_configure_opts_mipi_dphy *dphy_opts)
> +{
> +       unsigned long fout = data_rate_to_fout(dphy_opts->hs_clk_rate) / MHZ(1);
> +       int i;
> +
> +       for (i = 0; i < ARRAY_SIZE(vco_prop_map); i++)
> +               if (fout <= vco_prop_map[i].max_fout)
> +                       return vco_prop_map[i].prop_cntl;
> +
> +       return 0;
> +}
> +
> +static int dphy_pll_update(struct imx93_dsi *dsi)
> +{
> +       int ret;
> +
> +       ret = regmap_update_bits(dsi->regmap, DSI_REG, UPDATE_PLL, UPDATE_PLL);
> +       if (ret < 0) {
> +               dev_err(dsi->dev, "failed to set UPDATE_PLL: %d\n", ret);
> +               return ret;
> +       }
> +
> +       /*
> +        * The updatepll signal should be asserted for a minimum of four clkin
> +        * cycles, according to DPHY Databook Figure 3-3 Initialization Timing
> +        * Diagram.
> +        */
> +       fsleep(10);
> +
> +       ret = regmap_update_bits(dsi->regmap, DSI_REG, UPDATE_PLL, 0);
> +       if (ret < 0) {
> +               dev_err(dsi->dev, "failed to clear UPDATE_PLL: %d\n", ret);
> +               return ret;
> +       }
> +
> +       return 0;
> +}
> +
> +static int dphy_pll_configure(struct imx93_dsi *dsi, union phy_configure_opts *opts)
> +{
> +       struct dphy_pll_cfg cfg = { 0 };
> +       u32 val;
> +       int ret;
> +
> +       ret = dphy_pll_get_configure_from_opts(dsi, &opts->mipi_dphy, &cfg);
> +       if (ret) {
> +               dev_err(dsi->dev, "failed to get phy pll cfg %d\n", ret);
> +               return ret;
> +       }
> +
> +       dphy_pll_clear_shadow(dsi);
> +
> +       /* DSI_REG */
> +       val = CLKSEL_GEN |
> +             CFGCLKFREQRANGE(dphy_pll_get_cfgclkrange(dsi)) |
> +             HSFREQRANGE(dphy_pll_get_hsfreqrange(&opts->mipi_dphy));
> +       dphy_pll_write(dsi, DSI_REG, val);
> +
> +       /* DSI_WRITE_REG0 */
> +       val = M(cfg.m) | N(cfg.n) | INT_CTRL(0) |
> +             VCO_CTRL(dphy_pll_get_vco(&opts->mipi_dphy)) |
> +             PROP_CTRL(dphy_pll_get_prop(&opts->mipi_dphy));
> +       dphy_pll_write(dsi, DSI_WRITE_REG0, val);
> +
> +       /* DSI_WRITE_REG1 */
> +       dphy_pll_write(dsi, DSI_WRITE_REG1, GMP_CTRL(1) | CPBIAS_CTRL(0x10));
> +
> +       ret = clk_prepare_enable(dsi->clk_ref);
> +       if (ret < 0) {
> +               dev_err(dsi->dev, "failed to enable ref clock: %d\n", ret);
> +               return ret;
> +       }
> +
> +       /*
> +        * At least 10 refclk cycles are required before updatePLL assertion,
> +        * according to DPHY Databook Figure 3-3 Initialization Timing Diagram.
> +        */
> +       fsleep(10);
> +
> +       ret = dphy_pll_update(dsi);
> +       if (ret < 0) {
> +               clk_disable_unprepare(dsi->clk_ref);
> +               return ret;
> +       }
> +
> +       return 0;
> +}
> +
> +static void dphy_pll_clear_reg(struct imx93_dsi *dsi)
> +{
> +       dphy_pll_write(dsi, DSI_REG, 0);
> +       dphy_pll_write(dsi, DSI_WRITE_REG0, 0);
> +       dphy_pll_write(dsi, DSI_WRITE_REG1, 0);
> +}
> +
> +static int dphy_pll_init(struct imx93_dsi *dsi)
> +{
> +       int ret;
> +
> +       ret = clk_prepare_enable(dsi->clk_cfg);
> +       if (ret < 0) {
> +               dev_err(dsi->dev, "failed to enable config clock: %d\n", ret);
> +               return ret;
> +       }
> +
> +       dphy_pll_clear_reg(dsi);
> +
> +       return 0;
> +}
> +
> +static void dphy_pll_uninit(struct imx93_dsi *dsi)
> +{
> +       dphy_pll_clear_reg(dsi);
> +       clk_disable_unprepare(dsi->clk_cfg);
> +}
> +
> +static void dphy_pll_power_off(struct imx93_dsi *dsi)
> +{
> +       dphy_pll_clear_reg(dsi);
> +       clk_disable_unprepare(dsi->clk_ref);
> +}
> +
> +static int imx93_dsi_get_phy_configure_opts(struct imx93_dsi *dsi,
> +                                           const struct drm_display_mode *mode,
> +                                           union phy_configure_opts *phy_cfg,
> +                                           u32 lanes, u32 format)
> +{
> +       struct device *dev = dsi->dev;
> +       int bpp;
> +       int ret;
> +
> +       bpp = mipi_dsi_pixel_format_to_bpp(format);
> +       if (bpp < 0) {
> +               dev_dbg(dev, "failed to get bpp for pixel format %d\n", format);
> +               return -EINVAL;
> +       }
> +
> +       ret = phy_mipi_dphy_get_default_config(mode->clock * MSEC_PER_SEC, bpp,
> +                                              lanes, &phy_cfg->mipi_dphy);
> +       if (ret < 0) {
> +               dev_dbg(dev, "failed to get default phy cfg %d\n", ret);
> +               return ret;
> +       }
> +
> +       return 0;
> +}
> +
> +static enum drm_mode_status
> +imx93_dsi_validate_mode(struct imx93_dsi *dsi, const struct drm_display_mode *mode)
> +{
> +       struct drm_bridge *bridge = dw_mipi_dsi_get_bridge(dsi->dmd);
> +
> +       /* Get the last bridge */
> +       while (drm_bridge_get_next_bridge(bridge))
> +               bridge = drm_bridge_get_next_bridge(bridge);
> +
> +       if ((bridge->ops & DRM_BRIDGE_OP_DETECT) &&
> +           (bridge->ops & DRM_BRIDGE_OP_EDID)) {
> +               unsigned long pixel_clock_rate = mode->clock * 1000;
> +               unsigned long rounded_rate;
> +
> +               /* Allow +/-0.5% pixel clock rate deviation */
> +               rounded_rate = clk_round_rate(dsi->clk_pixel, pixel_clock_rate);
> +               if (rounded_rate < pixel_clock_rate * 995 / 1000 ||
> +                   rounded_rate > pixel_clock_rate * 1005 / 1000) {
> +                       dev_dbg(dsi->dev, "failed to round clock for mode " DRM_MODE_FMT "\n",
> +                               DRM_MODE_ARG(mode));
> +                       return MODE_NOCLOCK;
> +               }
> +       }
> +
> +       return MODE_OK;
> +}
> +
> +static enum drm_mode_status
> +imx93_dsi_validate_phy(struct imx93_dsi *dsi, const struct drm_display_mode *mode,
> +                      unsigned long mode_flags, u32 lanes, u32 format)
> +{
> +       union phy_configure_opts phy_cfg;
> +       struct dphy_pll_cfg cfg = { 0 };
> +       struct device *dev = dsi->dev;
> +       int ret;
> +
> +       ret = imx93_dsi_get_phy_configure_opts(dsi, mode, &phy_cfg, lanes,
> +                                              format);
> +       if (ret < 0) {
> +               dev_dbg(dev, "failed to get phy cfg opts %d\n", ret);
> +               return MODE_ERROR;
> +       }
> +
> +       ret = dphy_pll_get_configure_from_opts(dsi, &phy_cfg.mipi_dphy, &cfg);
> +       if (ret < 0) {
> +               dev_dbg(dev, "failed to get phy pll cfg %d\n", ret);
> +               return MODE_NOCLOCK;
> +       }
> +
> +       return MODE_OK;
> +}
> +
> +static enum drm_mode_status
> +imx93_dsi_mode_valid(void *priv_data, const struct drm_display_mode *mode,
> +                    unsigned long mode_flags, u32 lanes, u32 format)
> +{
> +       struct imx93_dsi *dsi = priv_data;
> +       struct device *dev = dsi->dev;
> +       enum drm_mode_status ret;
> +
> +       ret = imx93_dsi_validate_mode(dsi, mode);
> +       if (ret != MODE_OK) {
> +               dev_dbg(dev, "failed to validate mode " DRM_MODE_FMT "\n",
> +                       DRM_MODE_ARG(mode));
> +               return ret;
> +       }
> +
> +       ret = imx93_dsi_validate_phy(dsi, mode, mode_flags, lanes, format);
> +       if (ret != MODE_OK) {
> +               dev_dbg(dev, "failed to validate phy for mode " DRM_MODE_FMT "\n",
> +                       DRM_MODE_ARG(mode));
> +               return ret;
> +       }
> +
> +       return MODE_OK;
> +}
> +
> +static bool imx93_dsi_mode_fixup(void *priv_data,
> +                                const struct drm_display_mode *mode,
> +                                struct drm_display_mode *adjusted_mode)
> +{
> +       struct imx93_dsi *dsi = priv_data;
> +       unsigned long pixel_clock_rate;
> +       unsigned long rounded_rate;
> +
> +       pixel_clock_rate = mode->clock * 1000;
> +       rounded_rate = clk_round_rate(dsi->clk_pixel, pixel_clock_rate);
> +
> +       memcpy(adjusted_mode, mode, sizeof(*mode));
> +       adjusted_mode->clock = rounded_rate / 1000;
> +
> +       dev_dbg(dsi->dev, "adj clock %d for mode " DRM_MODE_FMT "\n",
> +               adjusted_mode->clock, DRM_MODE_ARG(mode));
> +
> +       return true;
> +}
> +
> +static u32 *imx93_dsi_get_input_bus_fmts(void *priv_data,
> +                                        struct drm_bridge *bridge,
> +                                        struct drm_bridge_state *bridge_state,
> +                                        struct drm_crtc_state *crtc_state,
> +                                        struct drm_connector_state *conn_state,
> +                                        u32 output_fmt,
> +                                        unsigned int *num_input_fmts)
> +{
> +       u32 *input_fmts, input_fmt;
> +
> +       *num_input_fmts = 0;
> +
> +       switch (output_fmt) {
> +       case MEDIA_BUS_FMT_RGB888_1X24:
> +       case MEDIA_BUS_FMT_RGB666_1X18:
> +       case MEDIA_BUS_FMT_FIXED:
> +               input_fmt = MEDIA_BUS_FMT_RGB888_1X24;
> +               break;
> +       case MEDIA_BUS_FMT_RGB565_1X16:
> +               input_fmt = output_fmt;
> +               break;
> +       default:
> +               return NULL;
> +       }
> +
> +       input_fmts = kmalloc(sizeof(*input_fmts), GFP_KERNEL);
> +       if (!input_fmts)
> +               return NULL;
> +       input_fmts[0] = input_fmt;
> +       *num_input_fmts = 1;
> +
> +       return input_fmts;
> +}
> +
> +static int imx93_dsi_phy_init(void *priv_data)
> +{
> +       struct imx93_dsi *dsi = priv_data;
> +       unsigned int fmt = 0;
> +       int ret;
> +
> +       switch (dsi->format) {
> +       case MIPI_DSI_FMT_RGB888:
> +               fmt = RGB888_TO_RGB888;
> +               break;
> +       case MIPI_DSI_FMT_RGB666:
> +               fmt = RGB888_TO_RGB666;
> +               regmap_update_bits(dsi->regmap, DISPLAY_MUX,
> +                                  MIPI_DSI_RGB666_MAP_CFG, RGB666_CONFIG2);
> +               break;
> +       case MIPI_DSI_FMT_RGB666_PACKED:
> +               fmt = RGB888_TO_RGB666;
> +               regmap_update_bits(dsi->regmap, DISPLAY_MUX,
> +                                  MIPI_DSI_RGB666_MAP_CFG, RGB666_CONFIG1);
> +               break;
> +       case MIPI_DSI_FMT_RGB565:
> +               fmt = RGB565_TO_RGB565;
> +               regmap_update_bits(dsi->regmap, DISPLAY_MUX,
> +                                  MIPI_DSI_RGB565_MAP_CFG, RGB565_CONFIG1);
> +               break;
> +       }
> +
> +       regmap_update_bits(dsi->regmap, DISPLAY_MUX, LCDIF_CROSS_LINE_PATTERN, fmt);
> +
> +       ret = dphy_pll_init(dsi);
> +       if (ret < 0) {
> +               dev_err(dsi->dev, "failed to init phy pll: %d\n", ret);
> +               return ret;
> +       }
> +
> +       ret = dphy_pll_configure(dsi, &dsi->phy_cfg);
> +       if (ret < 0) {
> +               dev_err(dsi->dev, "failed to configure phy pll: %d\n", ret);
> +               dphy_pll_uninit(dsi);
> +               return ret;
> +       }
> +
> +       return 0;
> +}
> +
> +static void imx93_dsi_phy_power_off(void *priv_data)
> +{
> +       struct imx93_dsi *dsi = priv_data;
> +
> +       dphy_pll_power_off(dsi);
> +       dphy_pll_uninit(dsi);
> +}
> +
> +static int
> +imx93_dsi_get_lane_mbps(void *priv_data, const struct drm_display_mode *mode,
> +                       unsigned long mode_flags, u32 lanes, u32 format,
> +                       unsigned int *lane_mbps)
> +{
> +       struct imx93_dsi *dsi = priv_data;
> +       union phy_configure_opts phy_cfg;
> +       struct device *dev = dsi->dev;
> +       int ret;
> +
> +       ret = imx93_dsi_get_phy_configure_opts(dsi, mode, &phy_cfg, lanes,
> +                                              format);
> +       if (ret < 0) {
> +               dev_dbg(dev, "failed to get phy cfg opts %d\n", ret);
> +               return ret;
> +       }
> +
> +       *lane_mbps = DIV_ROUND_UP(phy_cfg.mipi_dphy.hs_clk_rate, USEC_PER_SEC);
> +
> +       memcpy(&dsi->phy_cfg, &phy_cfg, sizeof(phy_cfg));
> +
> +       dev_dbg(dev, "get lane_mbps %u for mode " DRM_MODE_FMT "\n",
> +               *lane_mbps, DRM_MODE_ARG(mode));
> +
> +       return 0;
> +}
> +
> +/* High-Speed Transition Times */
> +struct hstt {
> +       unsigned int maxfreq;
> +       struct dw_mipi_dsi_dphy_timing timing;
> +};
> +
> +#define HSTT(_maxfreq, _c_lp2hs, _c_hs2lp, _d_lp2hs, _d_hs2lp) \
> +{                                                              \
> +       .maxfreq = (_maxfreq),                                  \
> +       .timing = {                                             \
> +               .clk_lp2hs = (_c_lp2hs),                        \
> +               .clk_hs2lp = (_c_hs2lp),                        \
> +               .data_lp2hs = (_d_lp2hs),                       \
> +               .data_hs2lp = (_d_hs2lp),                       \
> +       }                                                       \
> +}
> +
> +/* DPHY Databook Table A-4 High-Speed Transition Times */
> +static const struct hstt hstt_table[] = {
> +       HSTT(80,    21,  17,  15, 10),
> +       HSTT(90,    23,  17,  16, 10),
> +       HSTT(100,   22,  17,  16, 10),
> +       HSTT(110,   25,  18,  17, 11),
> +       HSTT(120,   26,  20,  18, 11),
> +       HSTT(130,   27,  19,  19, 11),
> +       HSTT(140,   27,  19,  19, 11),
> +       HSTT(150,   28,  20,  20, 12),
> +       HSTT(160,   30,  21,  22, 13),
> +       HSTT(170,   30,  21,  23, 13),
> +       HSTT(180,   31,  21,  23, 13),
> +       HSTT(190,   32,  22,  24, 13),
> +       HSTT(205,   35,  22,  25, 13),
> +       HSTT(220,   37,  26,  27, 15),
> +       HSTT(235,   38,  28,  27, 16),
> +       HSTT(250,   41,  29,  30, 17),
> +       HSTT(275,   43,  29,  32, 18),
> +       HSTT(300,   45,  32,  35, 19),
> +       HSTT(325,   48,  33,  36, 18),
> +       HSTT(350,   51,  35,  40, 20),
> +       HSTT(400,   59,  37,  44, 21),
> +       HSTT(450,   65,  40,  49, 23),
> +       HSTT(500,   71,  41,  54, 24),
> +       HSTT(550,   77,  44,  57, 26),
> +       HSTT(600,   82,  46,  64, 27),
> +       HSTT(650,   87,  48,  67, 28),
> +       HSTT(700,   94,  52,  71, 29),
> +       HSTT(750,   99,  52,  75, 31),
> +       HSTT(800,  105,  55,  82, 32),
> +       HSTT(850,  110,  58,  85, 32),
> +       HSTT(900,  115,  58,  88, 35),
> +       HSTT(950,  120,  62,  93, 36),
> +       HSTT(1000, 128,  63,  99, 38),
> +       HSTT(1050, 132,  65, 102, 38),
> +       HSTT(1100, 138,  67, 106, 39),
> +       HSTT(1150, 146,  69, 112, 42),
> +       HSTT(1200, 151,  71, 117, 43),
> +       HSTT(1250, 153,  74, 120, 45),
> +       HSTT(1300, 160,  73, 124, 46),
> +       HSTT(1350, 165,  76, 130, 47),
> +       HSTT(1400, 172,  78, 134, 49),
> +       HSTT(1450, 177,  80, 138, 49),
> +       HSTT(1500, 183,  81, 143, 52),
> +       HSTT(1550, 191,  84, 147, 52),
> +       HSTT(1600, 194,  85, 152, 52),
> +       HSTT(1650, 201,  86, 155, 53),
> +       HSTT(1700, 208,  88, 161, 53),
> +       HSTT(1750, 212,  89, 165, 53),
> +       HSTT(1800, 220,  90, 171, 54),
> +       HSTT(1850, 223,  92, 175, 54),
> +       HSTT(1900, 231,  91, 180, 55),
> +       HSTT(1950, 236,  95, 185, 56),
> +       HSTT(2000, 243,  97, 190, 56),
> +       HSTT(2050, 248,  99, 194, 58),
> +       HSTT(2100, 252, 100, 199, 59),
> +       HSTT(2150, 259, 102, 204, 61),
> +       HSTT(2200, 266, 105, 210, 62),
> +       HSTT(2250, 269, 109, 213, 63),
> +       HSTT(2300, 272, 109, 217, 65),
> +       HSTT(2350, 281, 112, 225, 66),
> +       HSTT(2400, 283, 115, 226, 66),
> +       HSTT(2450, 282, 115, 226, 67),
> +       HSTT(2500, 281, 118, 227, 67),
> +};
> +
> +static int imx93_dsi_phy_get_timing(void *priv_data, unsigned int lane_mbps,
> +                                   struct dw_mipi_dsi_dphy_timing *timing)
> +{
> +       struct imx93_dsi *dsi = priv_data;
> +       struct device *dev = dsi->dev;
> +       int i;
> +
> +       for (i = 0; i < ARRAY_SIZE(hstt_table); i++)
> +               if (lane_mbps <= hstt_table[i].maxfreq)
> +                       break;
> +
> +       if (i == ARRAY_SIZE(hstt_table)) {
> +               dev_err(dev, "failed to get phy timing for lane_mbps %u\n",
> +                       lane_mbps);
> +               return -EINVAL;
> +       }
> +
> +       *timing = hstt_table[i].timing;
> +
> +       dev_dbg(dev, "get phy timing for %u <= %u (lane_mbps)\n",
> +               lane_mbps, hstt_table[i].maxfreq);
> +
> +       return 0;
> +}
> +
> +static const struct dw_mipi_dsi_phy_ops imx93_dsi_phy_ops = {
> +       .init = imx93_dsi_phy_init,
> +       .power_off = imx93_dsi_phy_power_off,
> +       .get_lane_mbps = imx93_dsi_get_lane_mbps,
> +       .get_timing = imx93_dsi_phy_get_timing,
> +};
> +
> +static int imx93_dsi_host_attach(void *priv_data, struct mipi_dsi_device *device)
> +{
> +       struct imx93_dsi *dsi = priv_data;
> +
> +       dsi->format = device->format;
> +
> +       return 0;
> +}
> +
> +static const struct dw_mipi_dsi_host_ops imx93_dsi_host_ops = {
> +       .attach = imx93_dsi_host_attach,
> +};
> +
> +static int imx93_dsi_probe(struct platform_device *pdev)
> +{
> +       struct device *dev = &pdev->dev;
> +       struct device_node *np = dev->of_node;
> +       struct imx93_dsi *dsi;
> +       int ret;
> +
> +       dsi = devm_kzalloc(dev, sizeof(*dsi), GFP_KERNEL);
> +       if (!dsi)
> +               return -ENOMEM;
> +
> +       dsi->regmap = syscon_regmap_lookup_by_phandle(np, "fsl,media-blk-ctrl");
> +       if (IS_ERR(dsi->regmap)) {
> +               ret = PTR_ERR(dsi->regmap);
> +               dev_err(dev, "failed to get block ctrl regmap: %d\n", ret);
> +               return ret;
> +       }
> +
> +       dsi->clk_pixel = devm_clk_get(dev, "pix");
> +       if (IS_ERR(dsi->clk_pixel))
> +               return dev_err_probe(dev, PTR_ERR(dsi->clk_pixel),
> +                                    "failed to get pixel clock\n");
> +
> +       dsi->clk_cfg = devm_clk_get(dev, "phy_cfg");
> +       if (IS_ERR(dsi->clk_cfg))
> +               return dev_err_probe(dev, PTR_ERR(dsi->clk_cfg),
> +                                    "failed to get phy cfg clock\n");
> +
> +       dsi->clk_ref = devm_clk_get(dev, "phy_ref");
> +       if (IS_ERR(dsi->clk_ref))
> +               return dev_err_probe(dev, PTR_ERR(dsi->clk_ref),
> +                                    "failed to get phy ref clock\n");
> +
> +       dsi->ref_clk_rate = clk_get_rate(dsi->clk_ref);
> +       if (dsi->ref_clk_rate < REF_CLK_RATE_MIN ||
> +           dsi->ref_clk_rate > REF_CLK_RATE_MAX) {
> +               dev_err(dev, "invalid phy ref clock rate %lu\n",
> +                       dsi->ref_clk_rate);
> +               return -EINVAL;
> +       }
> +       dev_dbg(dev, "phy ref clock rate: %lu\n", dsi->ref_clk_rate);
> +
> +       dsi->dev = dev;
> +       dsi->pdata.max_data_lanes = 4;
> +       dsi->pdata.mode_valid = imx93_dsi_mode_valid;
> +       dsi->pdata.mode_fixup = imx93_dsi_mode_fixup;
> +       dsi->pdata.get_input_bus_fmts = imx93_dsi_get_input_bus_fmts;
> +       dsi->pdata.phy_ops = &imx93_dsi_phy_ops;
> +       dsi->pdata.host_ops = &imx93_dsi_host_ops;
> +       dsi->pdata.priv_data = dsi;
> +       platform_set_drvdata(pdev, dsi);
> +
> +       dsi->dmd = dw_mipi_dsi_probe(pdev, &dsi->pdata);
> +       if (IS_ERR(dsi->dmd))
> +               return dev_err_probe(dev, PTR_ERR(dsi->dmd),
> +                                    "failed to probe dw_mipi_dsi\n");
> +
> +       return 0;
> +}
> +
> +static void imx93_dsi_remove(struct platform_device *pdev)
> +{
> +       struct imx93_dsi *dsi = platform_get_drvdata(pdev);
> +
> +       dw_mipi_dsi_remove(dsi->dmd);
> +}
> +
> +static const struct of_device_id imx93_dsi_dt_ids[] = {
> +       { .compatible = "fsl,imx93-mipi-dsi", },
> +       { /* sentinel */ }
> +};
> +MODULE_DEVICE_TABLE(of, imx93_dsi_dt_ids);
> +
> +static struct platform_driver imx93_dsi_driver = {
> +       .probe  = imx93_dsi_probe,
> +       .remove_new = imx93_dsi_remove,
> +       .driver = {
> +               .of_match_table = imx93_dsi_dt_ids,
> +               .name = "imx93_mipi_dsi",
> +       },
> +};
> +module_platform_driver(imx93_dsi_driver);
> +
> +MODULE_DESCRIPTION("Freescale i.MX93 MIPI DSI driver");
> +MODULE_AUTHOR("Liu Ying <victor.liu@nxp.com>");
> +MODULE_LICENSE("GPL");
> --
> 2.37.1
>


Reviewed-by: Robert Foss <rfoss@kernel.org>
diff mbox series

Patch

diff --git a/drivers/gpu/drm/bridge/imx/Kconfig b/drivers/gpu/drm/bridge/imx/Kconfig
index 9fae28db6aa7..5a4f3d58501e 100644
--- a/drivers/gpu/drm/bridge/imx/Kconfig
+++ b/drivers/gpu/drm/bridge/imx/Kconfig
@@ -49,4 +49,15 @@  config DRM_IMX8QXP_PIXEL_LINK_TO_DPI
 	  Choose this to enable pixel link to display pixel interface(PXL2DPI)
 	  found in Freescale i.MX8qxp processor.
 
+config DRM_IMX93_MIPI_DSI
+	tristate "Freescale i.MX93 specific extensions for Synopsys DW MIPI DSI"
+	depends on OF
+	depends on COMMON_CLK
+	select DRM_DW_MIPI_DSI
+	select GENERIC_PHY
+	select GENERIC_PHY_MIPI_DPHY
+	help
+	  Choose this to enable MIPI DSI controller found in Freescale i.MX93
+	  processor.
+
 endif # ARCH_MXC || COMPILE_TEST
diff --git a/drivers/gpu/drm/bridge/imx/Makefile b/drivers/gpu/drm/bridge/imx/Makefile
index 8e2ebf3399a1..2b0c2e44aa1b 100644
--- a/drivers/gpu/drm/bridge/imx/Makefile
+++ b/drivers/gpu/drm/bridge/imx/Makefile
@@ -4,3 +4,4 @@  obj-$(CONFIG_DRM_IMX8QXP_LDB) += imx8qxp-ldb.o
 obj-$(CONFIG_DRM_IMX8QXP_PIXEL_COMBINER) += imx8qxp-pixel-combiner.o
 obj-$(CONFIG_DRM_IMX8QXP_PIXEL_LINK) += imx8qxp-pixel-link.o
 obj-$(CONFIG_DRM_IMX8QXP_PIXEL_LINK_TO_DPI) += imx8qxp-pxl2dpi.o
+obj-$(CONFIG_DRM_IMX93_MIPI_DSI) += imx93-mipi-dsi.o
diff --git a/drivers/gpu/drm/bridge/imx/imx93-mipi-dsi.c b/drivers/gpu/drm/bridge/imx/imx93-mipi-dsi.c
new file mode 100644
index 000000000000..3ff30ce80c5b
--- /dev/null
+++ b/drivers/gpu/drm/bridge/imx/imx93-mipi-dsi.c
@@ -0,0 +1,917 @@ 
+// SPDX-License-Identifier: GPL-2.0+
+
+/*
+ * Copyright 2022,2023 NXP
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/math.h>
+#include <linux/media-bus-format.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/phy/phy.h>
+#include <linux/phy/phy-mipi-dphy.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#include <drm/bridge/dw_mipi_dsi.h>
+#include <drm/drm_bridge.h>
+#include <drm/drm_mipi_dsi.h>
+#include <drm/drm_modes.h>
+
+/* DPHY PLL configuration registers */
+#define DSI_REG				0x4c
+#define  CFGCLKFREQRANGE_MASK		GENMASK(5, 0)
+#define  CFGCLKFREQRANGE(x)		FIELD_PREP(CFGCLKFREQRANGE_MASK, (x))
+#define  CLKSEL_MASK			GENMASK(7, 6)
+#define  CLKSEL_STOP			FIELD_PREP(CLKSEL_MASK, 0)
+#define  CLKSEL_GEN			FIELD_PREP(CLKSEL_MASK, 1)
+#define  CLKSEL_EXT			FIELD_PREP(CLKSEL_MASK, 2)
+#define  HSFREQRANGE_MASK		GENMASK(14, 8)
+#define  HSFREQRANGE(x)			FIELD_PREP(HSFREQRANGE_MASK, (x))
+#define  UPDATE_PLL			BIT(17)
+#define  SHADOW_CLR			BIT(18)
+#define  CLK_EXT			BIT(19)
+
+#define DSI_WRITE_REG0			0x50
+#define  M_MASK				GENMASK(9, 0)
+#define  M(x)				FIELD_PREP(M_MASK, ((x) - 2))
+#define  N_MASK				GENMASK(13, 10)
+#define  N(x)				FIELD_PREP(N_MASK, ((x) - 1))
+#define  VCO_CTRL_MASK			GENMASK(19, 14)
+#define  VCO_CTRL(x)			FIELD_PREP(VCO_CTRL_MASK, (x))
+#define  PROP_CTRL_MASK			GENMASK(25, 20)
+#define  PROP_CTRL(x)			FIELD_PREP(PROP_CTRL_MASK, (x))
+#define  INT_CTRL_MASK			GENMASK(31, 26)
+#define  INT_CTRL(x)			FIELD_PREP(INT_CTRL_MASK, (x))
+
+#define DSI_WRITE_REG1			0x54
+#define  GMP_CTRL_MASK			GENMASK(1, 0)
+#define  GMP_CTRL(x)			FIELD_PREP(GMP_CTRL_MASK, (x))
+#define  CPBIAS_CTRL_MASK		GENMASK(8, 2)
+#define  CPBIAS_CTRL(x)			FIELD_PREP(CPBIAS_CTRL_MASK, (x))
+#define  PLL_SHADOW_CTRL		BIT(9)
+
+/* display mux control register */
+#define DISPLAY_MUX			0x60
+#define  MIPI_DSI_RGB666_MAP_CFG	GENMASK(7, 6)
+#define  RGB666_CONFIG1			FIELD_PREP(MIPI_DSI_RGB666_MAP_CFG, 0)
+#define  RGB666_CONFIG2			FIELD_PREP(MIPI_DSI_RGB666_MAP_CFG, 1)
+#define  MIPI_DSI_RGB565_MAP_CFG	GENMASK(5, 4)
+#define  RGB565_CONFIG1			FIELD_PREP(MIPI_DSI_RGB565_MAP_CFG, 0)
+#define  RGB565_CONFIG2			FIELD_PREP(MIPI_DSI_RGB565_MAP_CFG, 1)
+#define  RGB565_CONFIG3			FIELD_PREP(MIPI_DSI_RGB565_MAP_CFG, 2)
+#define  LCDIF_CROSS_LINE_PATTERN	GENMASK(3, 0)
+#define  RGB888_TO_RGB888		FIELD_PREP(LCDIF_CROSS_LINE_PATTERN, 0)
+#define  RGB888_TO_RGB666		FIELD_PREP(LCDIF_CROSS_LINE_PATTERN, 6)
+#define  RGB565_TO_RGB565		FIELD_PREP(LCDIF_CROSS_LINE_PATTERN, 7)
+
+#define MHZ(x)				((x) * 1000000UL)
+
+#define REF_CLK_RATE_MAX		MHZ(64)
+#define REF_CLK_RATE_MIN		MHZ(2)
+#define FOUT_MAX			MHZ(1250)
+#define FOUT_MIN			MHZ(40)
+#define FVCO_DIV_FACTOR			MHZ(80)
+
+#define MBPS(x)				((x) * 1000000UL)
+
+#define DATA_RATE_MAX_SPEED		MBPS(2500)
+#define DATA_RATE_MIN_SPEED		MBPS(80)
+
+#define M_MAX				625UL
+#define M_MIN				64UL
+
+#define N_MAX				16U
+#define N_MIN				1U
+
+struct imx93_dsi {
+	struct device *dev;
+	struct regmap *regmap;
+	struct clk *clk_pixel;
+	struct clk *clk_ref;
+	struct clk *clk_cfg;
+	struct dw_mipi_dsi *dmd;
+	struct dw_mipi_dsi_plat_data pdata;
+	union phy_configure_opts phy_cfg;
+	unsigned long ref_clk_rate;
+	u32 format;
+};
+
+struct dphy_pll_cfg {
+	u32 m;	/* PLL Feedback Multiplication Ratio */
+	u32 n;	/* PLL Input Frequency Division Ratio */
+};
+
+struct dphy_pll_vco_prop {
+	unsigned long max_fout;
+	u8 vco_cntl;
+	u8 prop_cntl;
+};
+
+struct dphy_pll_hsfreqrange {
+	unsigned long max_mbps;
+	u8 hsfreqrange;
+};
+
+/* DPHY Databook Table 3-13 Charge-pump Programmability */
+static const struct dphy_pll_vco_prop vco_prop_map[] = {
+	{   55, 0x3f, 0x0d },
+	{   82, 0x37, 0x0d },
+	{  110, 0x2f, 0x0d },
+	{  165, 0x27, 0x0d },
+	{  220, 0x1f, 0x0d },
+	{  330, 0x17, 0x0d },
+	{  440, 0x0f, 0x0d },
+	{  660, 0x07, 0x0d },
+	{ 1149, 0x03, 0x0d },
+	{ 1152, 0x01, 0x0d },
+	{ 1250, 0x01, 0x0e },
+};
+
+/* DPHY Databook Table 5-7 Frequency Ranges and Defaults */
+static const struct dphy_pll_hsfreqrange hsfreqrange_map[] = {
+	{   89, 0x00 },
+	{   99, 0x10 },
+	{  109, 0x20 },
+	{  119, 0x30 },
+	{  129, 0x01 },
+	{  139, 0x11 },
+	{  149, 0x21 },
+	{  159, 0x31 },
+	{  169, 0x02 },
+	{  179, 0x12 },
+	{  189, 0x22 },
+	{  204, 0x32 },
+	{  219, 0x03 },
+	{  234, 0x13 },
+	{  249, 0x23 },
+	{  274, 0x33 },
+	{  299, 0x04 },
+	{  324, 0x14 },
+	{  349, 0x25 },
+	{  399, 0x35 },
+	{  449, 0x05 },
+	{  499, 0x16 },
+	{  549, 0x26 },
+	{  599, 0x37 },
+	{  649, 0x07 },
+	{  699, 0x18 },
+	{  749, 0x28 },
+	{  799, 0x39 },
+	{  849, 0x09 },
+	{  899, 0x19 },
+	{  949, 0x29 },
+	{  999, 0x3a },
+	{ 1049, 0x0a },
+	{ 1099, 0x1a },
+	{ 1149, 0x2a },
+	{ 1199, 0x3b },
+	{ 1249, 0x0b },
+	{ 1299, 0x1b },
+	{ 1349, 0x2b },
+	{ 1399, 0x3c },
+	{ 1449, 0x0c },
+	{ 1499, 0x1c },
+	{ 1549, 0x2c },
+	{ 1599, 0x3d },
+	{ 1649, 0x0d },
+	{ 1699, 0x1d },
+	{ 1749, 0x2e },
+	{ 1799, 0x3e },
+	{ 1849, 0x0e },
+	{ 1899, 0x1e },
+	{ 1949, 0x2f },
+	{ 1999, 0x3f },
+	{ 2049, 0x0f },
+	{ 2099, 0x40 },
+	{ 2149, 0x41 },
+	{ 2199, 0x42 },
+	{ 2249, 0x43 },
+	{ 2299, 0x44 },
+	{ 2349, 0x45 },
+	{ 2399, 0x46 },
+	{ 2449, 0x47 },
+	{ 2499, 0x48 },
+	{ 2500, 0x49 },
+};
+
+static void dphy_pll_write(struct imx93_dsi *dsi, unsigned int reg, u32 value)
+{
+	int ret;
+
+	ret = regmap_write(dsi->regmap, reg, value);
+	if (ret < 0)
+		dev_err(dsi->dev, "failed to write 0x%08x to pll reg 0x%x: %d\n",
+			value, reg, ret);
+}
+
+static inline unsigned long data_rate_to_fout(unsigned long data_rate)
+{
+	/* Fout is half of data rate */
+	return data_rate / 2;
+}
+
+static int
+dphy_pll_get_configure_from_opts(struct imx93_dsi *dsi,
+				 struct phy_configure_opts_mipi_dphy *dphy_opts,
+				 struct dphy_pll_cfg *cfg)
+{
+	struct device *dev = dsi->dev;
+	unsigned long fin = dsi->ref_clk_rate;
+	unsigned long fout;
+	unsigned long best_fout = 0;
+	unsigned int fvco_div;
+	unsigned int min_n, max_n, n, best_n;
+	unsigned long m, best_m;
+	unsigned long min_delta = ULONG_MAX;
+	unsigned long delta;
+	u64 tmp;
+
+	if (dphy_opts->hs_clk_rate < DATA_RATE_MIN_SPEED ||
+	    dphy_opts->hs_clk_rate > DATA_RATE_MAX_SPEED) {
+		dev_dbg(dev, "invalid data rate per lane: %lu\n",
+			dphy_opts->hs_clk_rate);
+		return -EINVAL;
+	}
+
+	fout = data_rate_to_fout(dphy_opts->hs_clk_rate);
+
+	/* DPHY Databook 3.3.6.1 Output Frequency */
+	/* Fout = Fvco / Fvco_div = (Fin * M) / (Fvco_div * N) */
+	/* Fvco_div could be 1/2/4/8 according to Fout range. */
+	fvco_div = 8UL / min(DIV_ROUND_UP(fout, FVCO_DIV_FACTOR), 8UL);
+
+	/* limitation: 2MHz <= Fin / N <= 8MHz */
+	min_n = DIV_ROUND_UP_ULL((u64)fin, MHZ(8));
+	max_n = DIV_ROUND_DOWN_ULL((u64)fin, MHZ(2));
+
+	/* clamp possible N(s) */
+	min_n = clamp(min_n, N_MIN, N_MAX);
+	max_n = clamp(max_n, N_MIN, N_MAX);
+
+	dev_dbg(dev, "Fout = %lu, Fvco_div = %u, n_range = [%u, %u]\n",
+		fout, fvco_div, min_n, max_n);
+
+	for (n = min_n; n <= max_n; n++) {
+		/* M = (Fout * N * Fvco_div) / Fin */
+		m = DIV_ROUND_CLOSEST(fout * n * fvco_div, fin);
+
+		/* check M range */
+		if (m < M_MIN || m > M_MAX)
+			continue;
+
+		/* calculate temporary Fout */
+		tmp = m * fin;
+		do_div(tmp, n * fvco_div);
+		if (tmp < FOUT_MIN || tmp > FOUT_MAX)
+			continue;
+
+		delta = abs(fout - tmp);
+		if (delta < min_delta) {
+			best_n = n;
+			best_m = m;
+			min_delta = delta;
+			best_fout = tmp;
+		}
+	}
+
+	if (best_fout) {
+		cfg->m = best_m;
+		cfg->n = best_n;
+		dev_dbg(dev, "best Fout = %lu, m = %u, n = %u\n",
+			best_fout, cfg->m, cfg->n);
+	} else {
+		dev_dbg(dev, "failed to find best Fout\n");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static void dphy_pll_clear_shadow(struct imx93_dsi *dsi)
+{
+	/* Reference DPHY Databook Figure 3-3 Initialization Timing Diagram. */
+	/* Select clock generation first. */
+	dphy_pll_write(dsi, DSI_REG, CLKSEL_GEN);
+
+	/* Clear shadow after clock selection is done a while. */
+	fsleep(1);
+	dphy_pll_write(dsi, DSI_REG, CLKSEL_GEN | SHADOW_CLR);
+
+	/* A minimum pulse of 5ns on shadow_clear signal. */
+	fsleep(1);
+	dphy_pll_write(dsi, DSI_REG, CLKSEL_GEN);
+}
+
+static unsigned long dphy_pll_get_cfgclkrange(struct imx93_dsi *dsi)
+{
+	/*
+	 * DPHY Databook Table 4-4 System Control Signals mentions an equation
+	 * for cfgclkfreqrange[5:0].
+	 */
+	return (clk_get_rate(dsi->clk_cfg) / MHZ(1) - 17) * 4;
+}
+
+static u8
+dphy_pll_get_hsfreqrange(struct phy_configure_opts_mipi_dphy *dphy_opts)
+{
+	unsigned long mbps = dphy_opts->hs_clk_rate / MHZ(1);
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(hsfreqrange_map); i++)
+		if (mbps <= hsfreqrange_map[i].max_mbps)
+			return hsfreqrange_map[i].hsfreqrange;
+
+	return 0;
+}
+
+static u8 dphy_pll_get_vco(struct phy_configure_opts_mipi_dphy *dphy_opts)
+{
+	unsigned long fout = data_rate_to_fout(dphy_opts->hs_clk_rate) / MHZ(1);
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(vco_prop_map); i++)
+		if (fout <= vco_prop_map[i].max_fout)
+			return vco_prop_map[i].vco_cntl;
+
+	return 0;
+}
+
+static u8 dphy_pll_get_prop(struct phy_configure_opts_mipi_dphy *dphy_opts)
+{
+	unsigned long fout = data_rate_to_fout(dphy_opts->hs_clk_rate) / MHZ(1);
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(vco_prop_map); i++)
+		if (fout <= vco_prop_map[i].max_fout)
+			return vco_prop_map[i].prop_cntl;
+
+	return 0;
+}
+
+static int dphy_pll_update(struct imx93_dsi *dsi)
+{
+	int ret;
+
+	ret = regmap_update_bits(dsi->regmap, DSI_REG, UPDATE_PLL, UPDATE_PLL);
+	if (ret < 0) {
+		dev_err(dsi->dev, "failed to set UPDATE_PLL: %d\n", ret);
+		return ret;
+	}
+
+	/*
+	 * The updatepll signal should be asserted for a minimum of four clkin
+	 * cycles, according to DPHY Databook Figure 3-3 Initialization Timing
+	 * Diagram.
+	 */
+	fsleep(10);
+
+	ret = regmap_update_bits(dsi->regmap, DSI_REG, UPDATE_PLL, 0);
+	if (ret < 0) {
+		dev_err(dsi->dev, "failed to clear UPDATE_PLL: %d\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int dphy_pll_configure(struct imx93_dsi *dsi, union phy_configure_opts *opts)
+{
+	struct dphy_pll_cfg cfg = { 0 };
+	u32 val;
+	int ret;
+
+	ret = dphy_pll_get_configure_from_opts(dsi, &opts->mipi_dphy, &cfg);
+	if (ret) {
+		dev_err(dsi->dev, "failed to get phy pll cfg %d\n", ret);
+		return ret;
+	}
+
+	dphy_pll_clear_shadow(dsi);
+
+	/* DSI_REG */
+	val = CLKSEL_GEN |
+	      CFGCLKFREQRANGE(dphy_pll_get_cfgclkrange(dsi)) |
+	      HSFREQRANGE(dphy_pll_get_hsfreqrange(&opts->mipi_dphy));
+	dphy_pll_write(dsi, DSI_REG, val);
+
+	/* DSI_WRITE_REG0 */
+	val = M(cfg.m) | N(cfg.n) | INT_CTRL(0) |
+	      VCO_CTRL(dphy_pll_get_vco(&opts->mipi_dphy)) |
+	      PROP_CTRL(dphy_pll_get_prop(&opts->mipi_dphy));
+	dphy_pll_write(dsi, DSI_WRITE_REG0, val);
+
+	/* DSI_WRITE_REG1 */
+	dphy_pll_write(dsi, DSI_WRITE_REG1, GMP_CTRL(1) | CPBIAS_CTRL(0x10));
+
+	ret = clk_prepare_enable(dsi->clk_ref);
+	if (ret < 0) {
+		dev_err(dsi->dev, "failed to enable ref clock: %d\n", ret);
+		return ret;
+	}
+
+	/*
+	 * At least 10 refclk cycles are required before updatePLL assertion,
+	 * according to DPHY Databook Figure 3-3 Initialization Timing Diagram.
+	 */
+	fsleep(10);
+
+	ret = dphy_pll_update(dsi);
+	if (ret < 0) {
+		clk_disable_unprepare(dsi->clk_ref);
+		return ret;
+	}
+
+	return 0;
+}
+
+static void dphy_pll_clear_reg(struct imx93_dsi *dsi)
+{
+	dphy_pll_write(dsi, DSI_REG, 0);
+	dphy_pll_write(dsi, DSI_WRITE_REG0, 0);
+	dphy_pll_write(dsi, DSI_WRITE_REG1, 0);
+}
+
+static int dphy_pll_init(struct imx93_dsi *dsi)
+{
+	int ret;
+
+	ret = clk_prepare_enable(dsi->clk_cfg);
+	if (ret < 0) {
+		dev_err(dsi->dev, "failed to enable config clock: %d\n", ret);
+		return ret;
+	}
+
+	dphy_pll_clear_reg(dsi);
+
+	return 0;
+}
+
+static void dphy_pll_uninit(struct imx93_dsi *dsi)
+{
+	dphy_pll_clear_reg(dsi);
+	clk_disable_unprepare(dsi->clk_cfg);
+}
+
+static void dphy_pll_power_off(struct imx93_dsi *dsi)
+{
+	dphy_pll_clear_reg(dsi);
+	clk_disable_unprepare(dsi->clk_ref);
+}
+
+static int imx93_dsi_get_phy_configure_opts(struct imx93_dsi *dsi,
+					    const struct drm_display_mode *mode,
+					    union phy_configure_opts *phy_cfg,
+					    u32 lanes, u32 format)
+{
+	struct device *dev = dsi->dev;
+	int bpp;
+	int ret;
+
+	bpp = mipi_dsi_pixel_format_to_bpp(format);
+	if (bpp < 0) {
+		dev_dbg(dev, "failed to get bpp for pixel format %d\n", format);
+		return -EINVAL;
+	}
+
+	ret = phy_mipi_dphy_get_default_config(mode->clock * MSEC_PER_SEC, bpp,
+					       lanes, &phy_cfg->mipi_dphy);
+	if (ret < 0) {
+		dev_dbg(dev, "failed to get default phy cfg %d\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static enum drm_mode_status
+imx93_dsi_validate_mode(struct imx93_dsi *dsi, const struct drm_display_mode *mode)
+{
+	struct drm_bridge *bridge = dw_mipi_dsi_get_bridge(dsi->dmd);
+
+	/* Get the last bridge */
+	while (drm_bridge_get_next_bridge(bridge))
+		bridge = drm_bridge_get_next_bridge(bridge);
+
+	if ((bridge->ops & DRM_BRIDGE_OP_DETECT) &&
+	    (bridge->ops & DRM_BRIDGE_OP_EDID)) {
+		unsigned long pixel_clock_rate = mode->clock * 1000;
+		unsigned long rounded_rate;
+
+		/* Allow +/-0.5% pixel clock rate deviation */
+		rounded_rate = clk_round_rate(dsi->clk_pixel, pixel_clock_rate);
+		if (rounded_rate < pixel_clock_rate * 995 / 1000 ||
+		    rounded_rate > pixel_clock_rate * 1005 / 1000) {
+			dev_dbg(dsi->dev, "failed to round clock for mode " DRM_MODE_FMT "\n",
+				DRM_MODE_ARG(mode));
+			return MODE_NOCLOCK;
+		}
+	}
+
+	return MODE_OK;
+}
+
+static enum drm_mode_status
+imx93_dsi_validate_phy(struct imx93_dsi *dsi, const struct drm_display_mode *mode,
+		       unsigned long mode_flags, u32 lanes, u32 format)
+{
+	union phy_configure_opts phy_cfg;
+	struct dphy_pll_cfg cfg = { 0 };
+	struct device *dev = dsi->dev;
+	int ret;
+
+	ret = imx93_dsi_get_phy_configure_opts(dsi, mode, &phy_cfg, lanes,
+					       format);
+	if (ret < 0) {
+		dev_dbg(dev, "failed to get phy cfg opts %d\n", ret);
+		return MODE_ERROR;
+	}
+
+	ret = dphy_pll_get_configure_from_opts(dsi, &phy_cfg.mipi_dphy, &cfg);
+	if (ret < 0) {
+		dev_dbg(dev, "failed to get phy pll cfg %d\n", ret);
+		return MODE_NOCLOCK;
+	}
+
+	return MODE_OK;
+}
+
+static enum drm_mode_status
+imx93_dsi_mode_valid(void *priv_data, const struct drm_display_mode *mode,
+		     unsigned long mode_flags, u32 lanes, u32 format)
+{
+	struct imx93_dsi *dsi = priv_data;
+	struct device *dev = dsi->dev;
+	enum drm_mode_status ret;
+
+	ret = imx93_dsi_validate_mode(dsi, mode);
+	if (ret != MODE_OK) {
+		dev_dbg(dev, "failed to validate mode " DRM_MODE_FMT "\n",
+			DRM_MODE_ARG(mode));
+		return ret;
+	}
+
+	ret = imx93_dsi_validate_phy(dsi, mode, mode_flags, lanes, format);
+	if (ret != MODE_OK) {
+		dev_dbg(dev, "failed to validate phy for mode " DRM_MODE_FMT "\n",
+			DRM_MODE_ARG(mode));
+		return ret;
+	}
+
+	return MODE_OK;
+}
+
+static bool imx93_dsi_mode_fixup(void *priv_data,
+				 const struct drm_display_mode *mode,
+				 struct drm_display_mode *adjusted_mode)
+{
+	struct imx93_dsi *dsi = priv_data;
+	unsigned long pixel_clock_rate;
+	unsigned long rounded_rate;
+
+	pixel_clock_rate = mode->clock * 1000;
+	rounded_rate = clk_round_rate(dsi->clk_pixel, pixel_clock_rate);
+
+	memcpy(adjusted_mode, mode, sizeof(*mode));
+	adjusted_mode->clock = rounded_rate / 1000;
+
+	dev_dbg(dsi->dev, "adj clock %d for mode " DRM_MODE_FMT "\n",
+		adjusted_mode->clock, DRM_MODE_ARG(mode));
+
+	return true;
+}
+
+static u32 *imx93_dsi_get_input_bus_fmts(void *priv_data,
+					 struct drm_bridge *bridge,
+					 struct drm_bridge_state *bridge_state,
+					 struct drm_crtc_state *crtc_state,
+					 struct drm_connector_state *conn_state,
+					 u32 output_fmt,
+					 unsigned int *num_input_fmts)
+{
+	u32 *input_fmts, input_fmt;
+
+	*num_input_fmts = 0;
+
+	switch (output_fmt) {
+	case MEDIA_BUS_FMT_RGB888_1X24:
+	case MEDIA_BUS_FMT_RGB666_1X18:
+	case MEDIA_BUS_FMT_FIXED:
+		input_fmt = MEDIA_BUS_FMT_RGB888_1X24;
+		break;
+	case MEDIA_BUS_FMT_RGB565_1X16:
+		input_fmt = output_fmt;
+		break;
+	default:
+		return NULL;
+	}
+
+	input_fmts = kmalloc(sizeof(*input_fmts), GFP_KERNEL);
+	if (!input_fmts)
+		return NULL;
+	input_fmts[0] = input_fmt;
+	*num_input_fmts = 1;
+
+	return input_fmts;
+}
+
+static int imx93_dsi_phy_init(void *priv_data)
+{
+	struct imx93_dsi *dsi = priv_data;
+	unsigned int fmt = 0;
+	int ret;
+
+	switch (dsi->format) {
+	case MIPI_DSI_FMT_RGB888:
+		fmt = RGB888_TO_RGB888;
+		break;
+	case MIPI_DSI_FMT_RGB666:
+		fmt = RGB888_TO_RGB666;
+		regmap_update_bits(dsi->regmap, DISPLAY_MUX,
+				   MIPI_DSI_RGB666_MAP_CFG, RGB666_CONFIG2);
+		break;
+	case MIPI_DSI_FMT_RGB666_PACKED:
+		fmt = RGB888_TO_RGB666;
+		regmap_update_bits(dsi->regmap, DISPLAY_MUX,
+				   MIPI_DSI_RGB666_MAP_CFG, RGB666_CONFIG1);
+		break;
+	case MIPI_DSI_FMT_RGB565:
+		fmt = RGB565_TO_RGB565;
+		regmap_update_bits(dsi->regmap, DISPLAY_MUX,
+				   MIPI_DSI_RGB565_MAP_CFG, RGB565_CONFIG1);
+		break;
+	}
+
+	regmap_update_bits(dsi->regmap, DISPLAY_MUX, LCDIF_CROSS_LINE_PATTERN, fmt);
+
+	ret = dphy_pll_init(dsi);
+	if (ret < 0) {
+		dev_err(dsi->dev, "failed to init phy pll: %d\n", ret);
+		return ret;
+	}
+
+	ret = dphy_pll_configure(dsi, &dsi->phy_cfg);
+	if (ret < 0) {
+		dev_err(dsi->dev, "failed to configure phy pll: %d\n", ret);
+		dphy_pll_uninit(dsi);
+		return ret;
+	}
+
+	return 0;
+}
+
+static void imx93_dsi_phy_power_off(void *priv_data)
+{
+	struct imx93_dsi *dsi = priv_data;
+
+	dphy_pll_power_off(dsi);
+	dphy_pll_uninit(dsi);
+}
+
+static int
+imx93_dsi_get_lane_mbps(void *priv_data, const struct drm_display_mode *mode,
+			unsigned long mode_flags, u32 lanes, u32 format,
+			unsigned int *lane_mbps)
+{
+	struct imx93_dsi *dsi = priv_data;
+	union phy_configure_opts phy_cfg;
+	struct device *dev = dsi->dev;
+	int ret;
+
+	ret = imx93_dsi_get_phy_configure_opts(dsi, mode, &phy_cfg, lanes,
+					       format);
+	if (ret < 0) {
+		dev_dbg(dev, "failed to get phy cfg opts %d\n", ret);
+		return ret;
+	}
+
+	*lane_mbps = DIV_ROUND_UP(phy_cfg.mipi_dphy.hs_clk_rate, USEC_PER_SEC);
+
+	memcpy(&dsi->phy_cfg, &phy_cfg, sizeof(phy_cfg));
+
+	dev_dbg(dev, "get lane_mbps %u for mode " DRM_MODE_FMT "\n",
+		*lane_mbps, DRM_MODE_ARG(mode));
+
+	return 0;
+}
+
+/* High-Speed Transition Times */
+struct hstt {
+	unsigned int maxfreq;
+	struct dw_mipi_dsi_dphy_timing timing;
+};
+
+#define HSTT(_maxfreq, _c_lp2hs, _c_hs2lp, _d_lp2hs, _d_hs2lp)	\
+{								\
+	.maxfreq = (_maxfreq),					\
+	.timing = {						\
+		.clk_lp2hs = (_c_lp2hs),			\
+		.clk_hs2lp = (_c_hs2lp),			\
+		.data_lp2hs = (_d_lp2hs),			\
+		.data_hs2lp = (_d_hs2lp),			\
+	}							\
+}
+
+/* DPHY Databook Table A-4 High-Speed Transition Times */
+static const struct hstt hstt_table[] = {
+	HSTT(80,    21,  17,  15, 10),
+	HSTT(90,    23,  17,  16, 10),
+	HSTT(100,   22,  17,  16, 10),
+	HSTT(110,   25,  18,  17, 11),
+	HSTT(120,   26,  20,  18, 11),
+	HSTT(130,   27,  19,  19, 11),
+	HSTT(140,   27,  19,  19, 11),
+	HSTT(150,   28,  20,  20, 12),
+	HSTT(160,   30,  21,  22, 13),
+	HSTT(170,   30,  21,  23, 13),
+	HSTT(180,   31,  21,  23, 13),
+	HSTT(190,   32,  22,  24, 13),
+	HSTT(205,   35,  22,  25, 13),
+	HSTT(220,   37,  26,  27, 15),
+	HSTT(235,   38,  28,  27, 16),
+	HSTT(250,   41,  29,  30, 17),
+	HSTT(275,   43,  29,  32, 18),
+	HSTT(300,   45,  32,  35, 19),
+	HSTT(325,   48,  33,  36, 18),
+	HSTT(350,   51,  35,  40, 20),
+	HSTT(400,   59,  37,  44, 21),
+	HSTT(450,   65,  40,  49, 23),
+	HSTT(500,   71,  41,  54, 24),
+	HSTT(550,   77,  44,  57, 26),
+	HSTT(600,   82,  46,  64, 27),
+	HSTT(650,   87,  48,  67, 28),
+	HSTT(700,   94,  52,  71, 29),
+	HSTT(750,   99,  52,  75, 31),
+	HSTT(800,  105,  55,  82, 32),
+	HSTT(850,  110,  58,  85, 32),
+	HSTT(900,  115,  58,  88, 35),
+	HSTT(950,  120,  62,  93, 36),
+	HSTT(1000, 128,  63,  99, 38),
+	HSTT(1050, 132,  65, 102, 38),
+	HSTT(1100, 138,  67, 106, 39),
+	HSTT(1150, 146,  69, 112, 42),
+	HSTT(1200, 151,  71, 117, 43),
+	HSTT(1250, 153,  74, 120, 45),
+	HSTT(1300, 160,  73, 124, 46),
+	HSTT(1350, 165,  76, 130, 47),
+	HSTT(1400, 172,  78, 134, 49),
+	HSTT(1450, 177,  80, 138, 49),
+	HSTT(1500, 183,  81, 143, 52),
+	HSTT(1550, 191,  84, 147, 52),
+	HSTT(1600, 194,  85, 152, 52),
+	HSTT(1650, 201,  86, 155, 53),
+	HSTT(1700, 208,  88, 161, 53),
+	HSTT(1750, 212,  89, 165, 53),
+	HSTT(1800, 220,  90, 171, 54),
+	HSTT(1850, 223,  92, 175, 54),
+	HSTT(1900, 231,  91, 180, 55),
+	HSTT(1950, 236,  95, 185, 56),
+	HSTT(2000, 243,  97, 190, 56),
+	HSTT(2050, 248,  99, 194, 58),
+	HSTT(2100, 252, 100, 199, 59),
+	HSTT(2150, 259, 102, 204, 61),
+	HSTT(2200, 266, 105, 210, 62),
+	HSTT(2250, 269, 109, 213, 63),
+	HSTT(2300, 272, 109, 217, 65),
+	HSTT(2350, 281, 112, 225, 66),
+	HSTT(2400, 283, 115, 226, 66),
+	HSTT(2450, 282, 115, 226, 67),
+	HSTT(2500, 281, 118, 227, 67),
+};
+
+static int imx93_dsi_phy_get_timing(void *priv_data, unsigned int lane_mbps,
+				    struct dw_mipi_dsi_dphy_timing *timing)
+{
+	struct imx93_dsi *dsi = priv_data;
+	struct device *dev = dsi->dev;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(hstt_table); i++)
+		if (lane_mbps <= hstt_table[i].maxfreq)
+			break;
+
+	if (i == ARRAY_SIZE(hstt_table)) {
+		dev_err(dev, "failed to get phy timing for lane_mbps %u\n",
+			lane_mbps);
+		return -EINVAL;
+	}
+
+	*timing = hstt_table[i].timing;
+
+	dev_dbg(dev, "get phy timing for %u <= %u (lane_mbps)\n",
+		lane_mbps, hstt_table[i].maxfreq);
+
+	return 0;
+}
+
+static const struct dw_mipi_dsi_phy_ops imx93_dsi_phy_ops = {
+	.init = imx93_dsi_phy_init,
+	.power_off = imx93_dsi_phy_power_off,
+	.get_lane_mbps = imx93_dsi_get_lane_mbps,
+	.get_timing = imx93_dsi_phy_get_timing,
+};
+
+static int imx93_dsi_host_attach(void *priv_data, struct mipi_dsi_device *device)
+{
+	struct imx93_dsi *dsi = priv_data;
+
+	dsi->format = device->format;
+
+	return 0;
+}
+
+static const struct dw_mipi_dsi_host_ops imx93_dsi_host_ops = {
+	.attach = imx93_dsi_host_attach,
+};
+
+static int imx93_dsi_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct device_node *np = dev->of_node;
+	struct imx93_dsi *dsi;
+	int ret;
+
+	dsi = devm_kzalloc(dev, sizeof(*dsi), GFP_KERNEL);
+	if (!dsi)
+		return -ENOMEM;
+
+	dsi->regmap = syscon_regmap_lookup_by_phandle(np, "fsl,media-blk-ctrl");
+	if (IS_ERR(dsi->regmap)) {
+		ret = PTR_ERR(dsi->regmap);
+		dev_err(dev, "failed to get block ctrl regmap: %d\n", ret);
+		return ret;
+	}
+
+	dsi->clk_pixel = devm_clk_get(dev, "pix");
+	if (IS_ERR(dsi->clk_pixel))
+		return dev_err_probe(dev, PTR_ERR(dsi->clk_pixel),
+				     "failed to get pixel clock\n");
+
+	dsi->clk_cfg = devm_clk_get(dev, "phy_cfg");
+	if (IS_ERR(dsi->clk_cfg))
+		return dev_err_probe(dev, PTR_ERR(dsi->clk_cfg),
+				     "failed to get phy cfg clock\n");
+
+	dsi->clk_ref = devm_clk_get(dev, "phy_ref");
+	if (IS_ERR(dsi->clk_ref))
+		return dev_err_probe(dev, PTR_ERR(dsi->clk_ref),
+				     "failed to get phy ref clock\n");
+
+	dsi->ref_clk_rate = clk_get_rate(dsi->clk_ref);
+	if (dsi->ref_clk_rate < REF_CLK_RATE_MIN ||
+	    dsi->ref_clk_rate > REF_CLK_RATE_MAX) {
+		dev_err(dev, "invalid phy ref clock rate %lu\n",
+			dsi->ref_clk_rate);
+		return -EINVAL;
+	}
+	dev_dbg(dev, "phy ref clock rate: %lu\n", dsi->ref_clk_rate);
+
+	dsi->dev = dev;
+	dsi->pdata.max_data_lanes = 4;
+	dsi->pdata.mode_valid = imx93_dsi_mode_valid;
+	dsi->pdata.mode_fixup = imx93_dsi_mode_fixup;
+	dsi->pdata.get_input_bus_fmts = imx93_dsi_get_input_bus_fmts;
+	dsi->pdata.phy_ops = &imx93_dsi_phy_ops;
+	dsi->pdata.host_ops = &imx93_dsi_host_ops;
+	dsi->pdata.priv_data = dsi;
+	platform_set_drvdata(pdev, dsi);
+
+	dsi->dmd = dw_mipi_dsi_probe(pdev, &dsi->pdata);
+	if (IS_ERR(dsi->dmd))
+		return dev_err_probe(dev, PTR_ERR(dsi->dmd),
+				     "failed to probe dw_mipi_dsi\n");
+
+	return 0;
+}
+
+static void imx93_dsi_remove(struct platform_device *pdev)
+{
+	struct imx93_dsi *dsi = platform_get_drvdata(pdev);
+
+	dw_mipi_dsi_remove(dsi->dmd);
+}
+
+static const struct of_device_id imx93_dsi_dt_ids[] = {
+	{ .compatible = "fsl,imx93-mipi-dsi", },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, imx93_dsi_dt_ids);
+
+static struct platform_driver imx93_dsi_driver = {
+	.probe	= imx93_dsi_probe,
+	.remove_new = imx93_dsi_remove,
+	.driver	= {
+		.of_match_table = imx93_dsi_dt_ids,
+		.name = "imx93_mipi_dsi",
+	},
+};
+module_platform_driver(imx93_dsi_driver);
+
+MODULE_DESCRIPTION("Freescale i.MX93 MIPI DSI driver");
+MODULE_AUTHOR("Liu Ying <victor.liu@nxp.com>");
+MODULE_LICENSE("GPL");