[PATCH v3 RESEND 9/9] drm/bridge: imx: Add i.MX93 MIPI DSI support
Robert Foss
rfoss at kernel.org
Mon Oct 16 09:36:45 UTC 2023
On Mon, Aug 21, 2023 at 5:38 AM Liu Ying <victor.liu at 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 at 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 at 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 at nxp.com>");
> +MODULE_LICENSE("GPL");
> --
> 2.37.1
>
Reviewed-by: Robert Foss <rfoss at kernel.org>
More information about the dri-devel
mailing list