Add PLL initialization for the i.MX95 SoC, which differs from the i.MX93. Unlike the i.MX93 PHY, the i.MX95 PHY uses more syscon interfaces to configure the PHY. Retain common code where possible, which are frequency table look ups and matching tables.
Signed-off-by: Marek Vasut <[email protected]> --- Cc: Abel Vesa <[email protected]> Cc: Conor Dooley <[email protected]> Cc: Fabio Estevam <[email protected]> Cc: Krzysztof Kozlowski <[email protected]> Cc: Laurent Pinchart <[email protected]> Cc: Liu Ying <[email protected]> Cc: Lucas Stach <[email protected]> Cc: Peng Fan <[email protected]> Cc: Pengutronix Kernel Team <[email protected]> Cc: Rob Herring <[email protected]> Cc: Shawn Guo <[email protected]> Cc: Thomas Zimmermann <[email protected]> Cc: [email protected] Cc: [email protected] Cc: [email protected] Cc: [email protected] Cc: [email protected] --- drivers/gpu/drm/bridge/imx/imx93-mipi-dsi.c | 612 ++++++++++++++++++-- 1 file changed, 575 insertions(+), 37 deletions(-) diff --git a/drivers/gpu/drm/bridge/imx/imx93-mipi-dsi.c b/drivers/gpu/drm/bridge/imx/imx93-mipi-dsi.c index 8f7a0d46601a4..d0fda2fca2b43 100644 --- a/drivers/gpu/drm/bridge/imx/imx93-mipi-dsi.c +++ b/drivers/gpu/drm/bridge/imx/imx93-mipi-dsi.c @@ -13,6 +13,7 @@ #include <linux/mfd/syscon.h> #include <linux/module.h> #include <linux/of.h> +#include <linux/of_graph.h> #include <linux/phy/phy.h> #include <linux/phy/phy-mipi-dphy.h> #include <linux/platform_device.h> @@ -23,7 +24,7 @@ #include <drm/drm_mipi_dsi.h> #include <drm/drm_modes.h> -/* DPHY PLL configuration registers */ +/* i.MX93 DPHY PLL configuration registers */ #define DSI_REG 0x4c #define CFGCLKFREQRANGE_MASK GENMASK(5, 0) #define CFGCLKFREQRANGE(x) FIELD_PREP(CFGCLKFREQRANGE_MASK, (x)) @@ -70,6 +71,123 @@ #define RGB888_TO_RGB666 FIELD_PREP(LCDIF_CROSS_LINE_PATTERN, 6) #define RGB565_TO_RGB565 FIELD_PREP(LCDIF_CROSS_LINE_PATTERN, 7) +/* i.MX95 DPHY PLL configuration registers */ +#define DSI_DPI_CFG_POL 0x14 +#define COLORM_ACTIVE_LOW BIT(4) +#define SHUTD_ACTIVE_LOW BIT(3) +#define HSYNC_ACTIVE_LOW BIT(2) +#define VSYNC_ACTIVE_LOW BIT(1) +#define DATAEN_ACTIVE_LOW BIT(0) + +#define DSI_PHY_TST_CTRL0 0xb4 +#define PHY_TESTCLK BIT(1) +#define PHY_UNTESTCLK 0 +#define PHY_TESTCLR BIT(0) +#define PHY_UNTESTCLR 0 + +#define DSI_PHY_TST_CTRL1 0xb8 +#define PHY_TESTEN BIT(16) +#define PHY_UNTESTEN 0 +#define PHY_TESTDOUT_MASK GENMASK(15, 8) +#define PHY_TESTDOUT(n) FIELD_PREP(PHY_TESTDOUT_MASK, (n)) +#define PHY_TESTDIN(n) FIELD_PREP(GENMASK(7, 0), (n)) + +/* master CSR */ +#define DSI_CLOCK_GATING_CONTROL 0x0 +#define DISPLAY_ASYNC_FIFO(n) BIT(0 + (n)) +#define DPHY_PLL_CLKIN BIT(2) +#define DPHY_PLL_CLKOUT BIT(3) +#define DPHY_PLL_CLKEXT BIT(4) + +#define DSI_PIXEL_LINK_CONTROL 0x4 +#define PIXEL_LINK_SEL BIT(0) + +#define DSI_CLOCK_SETTING 0x8 +#define DPHY_REF_CLOCK_DIV(n) FIELD_PREP(GENMASK(3, 0), (n)) +#define DPHY_BYPASS_CLOCK BIT(6) +#define DPHY_REF_CLOCK_SOURCE BIT(7) +#define DPHY_PLL_DIV(n) FIELD_PREP(GENMASK(11, 8), (n)) +#define PL_SLV_DSI_INGRESS_CLK_SEL(n) BIT(16 + (n)) + +/* stream CSR */ +#define DSI_HOST_CONFIGURATION 0x0 +#define PIXEL_LINK_FORMAT_MASK GENMASK(2, 0) +#define SHUTDOWN BIT(4) +#define COLORMODE BIT(5) + +#define DSI_PARITY_ERROR_STATUS 0x4 +#define DSI_PARITY_ERROR_THRESHOLD 0x8 + +/* DPHY CSR */ +#define PHY_MODE_CONTROL 0x0 +#define PHY_ENABLE_EXT(n) FIELD_PREP(GENMASK(7, 4), (n)) +#define PLL_GP_CLK_EN BIT(3) +#define PLL_CLKSEL_MASK GENMASK(2, 1) +#define PLL_CLKSEL_STOP FIELD_PREP(PLL_CLKSEL_MASK, 0) +#define PLL_CLKSEL_GEN FIELD_PREP(PLL_CLKSEL_MASK, 1) +#define PLL_CLKSEL_EXT FIELD_PREP(PLL_CLKSEL_MASK, 2) +#define TX_RXZ_DSI_MODE BIT(0) + +#define PHY_FREQ_CONTROL 0x4 +#define PHY_HSFREQRANGE(n) FIELD_PREP(GENMASK(22, 16), (n)) +#define PHY_CFGCLKFREQRANGE(n) FIELD_PREP(GENMASK(7, 0), (n)) + +#define PHY_TEST_MODE_CONTROL 0x8 +#define EXT_SIGNAL_ALL GENMASK(19, 14) +#define TURNDISABLE_0 BIT(13) +#define ENABLECLK_EXT BIT(12) + +#define PHY_TEST_MODE_STATUS 0xc +#define PHY_LOCK BIT(11) + +/* DPHY test control register */ +#define DIG_RDWR_TX_PLL_1 0x15e +#define PLL_CPBIAS_CNTRL_RW_MASK GENMASK(6, 0) +#define PLL_CPBIAS_CNTRL_RW(n) FIELD_PREP(PLL_CPBIAS_CNTRL_RW_MASK, (n)) +#define DIG_RDWR_TX_PLL_5 0x162 +#define PLL_INT_CNTRL_RW(n) FIELD_PREP(GENMASK(7, 2), (n)) +#define PLL_GMP_CNTRL_RW(n) FIELD_PREP(GENMASK(1, 0), (n)) +#define DIG_RDWR_TX_PLL_9 0x166 +#define PLL_LOCK_SEL_RW BIT(2) +#define DIG_RDWR_TX_PLL_13 0x16a +#define DIG_RDWR_TX_PLL_17 0x16e +#define PLL_PWRON_OVR_RW BIT(7) +#define PLL_PWRON_OVR_EN_RW BIT(6) +#define PLL_PROP_CNTRL_RW_MASK GENMASK(5, 0) +#define PLL_PROP_CNTRL_RW(n) FIELD_PREP(PLL_PROP_CNTRL_RW_MASK, (n)) +#define DIG_RDWR_TX_PLL_22 0x173 +#define DIG_RDWR_TX_PLL_23 0x174 +#define DIG_RDWR_TX_PLL_24 0x175 +#define PLL_TH2_RW(n) FIELD_PREP(GENMASK(7, 0), (n)) +#define DIG_RDWR_TX_PLL_25 0x176 +#define PLL_TH3_RW(n) FIELD_PREP(GENMASK(7, 0), (n)) +#define DIG_RDWR_TX_PLL_27 0x178 +#define PLL_N_OVR_EN_RW BIT(7) +#define PLL_N_OVR_RW_MASK GENMASK(6, 3) +#define PLL_N_OVR_RW(n) FIELD_PREP(PLL_N_OVR_RW_MASK, ((n) - 1)) +#define DIG_RDWR_TX_PLL_28 0x179 +#define PLL_M_LOW(n) (((n) - 2) & 0xff) +#define DIG_RDWR_TX_PLL_29 0x17a +#define PLL_M_HIGH(n) ((((n) - 2) >> 8) & 0xff) +#define DIG_RDWR_TX_PLL_30 0x17b +#define PLL_VCO_CNTRL_OVR_EN_RW BIT(7) +#define PLL_VCO_CNTRL_OVR_RW_MASK GENMASK(6, 1) +#define PLL_VCO_CNTRL_OVR_RW(n) FIELD_PREP(PLL_VCO_CNTRL_OVR_RW_MASK, (n)) +#define PLL_M_OVR_EN_RW BIT(0) +#define DIG_RDWR_TX_PLL_31 0x17c +#define PLL_CLKOUTEN_RIGHT_RW BIT(1) +#define PLL_CLKOUTEN_LEFT_RW BIT(0) +#define DIG_RDWR_TX_CB_0 0x1aa +#define DIG_RDWR_TX_CB_1 0x1ab +#define DIG_RDWR_TX_TX_SLEW_0 0x26b +#define DIG_RDWR_TX_TX_SLEW_5 0x270 +#define DIG_RDWR_TX_TX_SLEW_6 0x271 +#define DIG_RDWR_TX_TX_SLEW_7 0x272 +#define DIG_RDWR_TX_CLK_TERMLOWCAP 0x402 + +#define IMX95_DSI_ENDPOINT_PL0 0 +#define IMX95_DSI_ENDPOINT_PL1 1 + #define MHZ(x) ((x) * 1000000UL) #define REF_CLK_RATE_MAX MHZ(64) @@ -89,9 +207,24 @@ #define N_MAX 16U #define N_MIN 1U +#define PIXEL_LINK_STREAMS 2 + +enum dsi_pixel_link_format { + RGB_24BIT, + RGB_30BIT, + RGB_18BIT, + RGB_16BIT, + YCBCR_20BIT_422, + YCBCR_16BIT_422, +}; + struct imx93_dsi { struct device *dev; + void __iomem *base; struct regmap *regmap; + struct regmap *mst; + struct regmap *str; + struct regmap *phy; struct clk *clk_pixel; struct clk *clk_ref; struct clk *clk_cfg; @@ -99,6 +232,10 @@ struct imx93_dsi { struct dw_mipi_dsi_plat_data pdata; union phy_configure_opts phy_cfg; unsigned long ref_clk_rate; + unsigned int lanes; + unsigned int lane_mbps; + bool use_pl0; + u32 fixed_format; u32 format; }; @@ -200,7 +337,7 @@ static const struct dphy_pll_hsfreqrange hsfreqrange_map[] = { { 2500, 0x49 }, }; -static void dphy_pll_write(struct imx93_dsi *dsi, unsigned int reg, u32 value) +static void imx93_dsi_pll_write(struct imx93_dsi *dsi, unsigned int reg, u32 value) { int ret; @@ -293,19 +430,19 @@ dphy_pll_get_configure_from_opts(struct imx93_dsi *dsi, return 0; } -static void dphy_pll_clear_shadow(struct imx93_dsi *dsi) +static void imx93_dsi_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); + imx93_dsi_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); + imx93_dsi_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); + imx93_dsi_pll_write(dsi, DSI_REG, CLKSEL_GEN); } static unsigned long dphy_pll_get_cfgclkrange(struct imx93_dsi *dsi) @@ -354,7 +491,7 @@ static u8 dphy_pll_get_prop(struct phy_configure_opts_mipi_dphy *dphy_opts) return 0; } -static int dphy_pll_update(struct imx93_dsi *dsi) +static int imx93_dsi_pll_update(struct imx93_dsi *dsi) { int ret; @@ -380,7 +517,7 @@ static int dphy_pll_update(struct imx93_dsi *dsi) return 0; } -static int dphy_pll_configure(struct imx93_dsi *dsi, union phy_configure_opts *opts) +static int imx93_dsi_pll_configure(struct imx93_dsi *dsi, union phy_configure_opts *opts) { struct dphy_pll_cfg cfg = { 0 }; u32 val; @@ -392,22 +529,22 @@ static int dphy_pll_configure(struct imx93_dsi *dsi, union phy_configure_opts *o return ret; } - dphy_pll_clear_shadow(dsi); + imx93_dsi_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); + imx93_dsi_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); + imx93_dsi_pll_write(dsi, DSI_WRITE_REG0, val); /* DSI_WRITE_REG1 */ - dphy_pll_write(dsi, DSI_WRITE_REG1, GMP_CTRL(1) | CPBIAS_CTRL(0x10)); + imx93_dsi_pll_write(dsi, DSI_WRITE_REG1, GMP_CTRL(1) | CPBIAS_CTRL(0x10)); ret = clk_prepare_enable(dsi->clk_ref); if (ret < 0) { @@ -421,7 +558,7 @@ static int dphy_pll_configure(struct imx93_dsi *dsi, union phy_configure_opts *o */ fsleep(10); - ret = dphy_pll_update(dsi); + ret = imx93_dsi_pll_update(dsi); if (ret < 0) { clk_disable_unprepare(dsi->clk_ref); return ret; @@ -430,14 +567,14 @@ static int dphy_pll_configure(struct imx93_dsi *dsi, union phy_configure_opts *o return 0; } -static void dphy_pll_clear_reg(struct imx93_dsi *dsi) +static void imx93_dsi_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); + imx93_dsi_pll_write(dsi, DSI_REG, 0); + imx93_dsi_pll_write(dsi, DSI_WRITE_REG0, 0); + imx93_dsi_pll_write(dsi, DSI_WRITE_REG1, 0); } -static int dphy_pll_init(struct imx93_dsi *dsi) +static int imx93_dsi_pll_init(struct imx93_dsi *dsi) { int ret; @@ -447,20 +584,20 @@ static int dphy_pll_init(struct imx93_dsi *dsi) return ret; } - dphy_pll_clear_reg(dsi); + imx93_dsi_pll_clear_reg(dsi); return 0; } -static void dphy_pll_uninit(struct imx93_dsi *dsi) +static void imx93_dsi_pll_uninit(struct imx93_dsi *dsi) { - dphy_pll_clear_reg(dsi); + imx93_dsi_pll_clear_reg(dsi); clk_disable_unprepare(dsi->clk_cfg); } -static void dphy_pll_power_off(struct imx93_dsi *dsi) +static void imx93_pll_power_off(struct imx93_dsi *dsi) { - dphy_pll_clear_reg(dsi); + imx93_dsi_pll_clear_reg(dsi); clk_disable_unprepare(dsi->clk_ref); } @@ -581,6 +718,10 @@ static bool imx93_dsi_mode_fixup(void *priv_data, dev_dbg(dsi->dev, "adj clock %d for mode " DRM_MODE_FMT "\n", adjusted_mode->clock, DRM_MODE_ARG(mode)); + /* pixel link always generates active low HSYNC and VSYNC */ + adjusted_mode->flags &= ~(DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC); + adjusted_mode->flags |= DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC; + return true; } @@ -592,6 +733,7 @@ static u32 *imx93_dsi_get_input_bus_fmts(void *priv_data, u32 output_fmt, unsigned int *num_input_fmts) { + struct imx93_dsi *dsi = priv_data; u32 *input_fmts, input_fmt; *num_input_fmts = 0; @@ -599,12 +741,15 @@ static u32 *imx93_dsi_get_input_bus_fmts(void *priv_data, 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; + case MEDIA_BUS_FMT_FIXED: + input_fmt = dsi->fixed_format; + break; + default: return NULL; } @@ -647,16 +792,16 @@ static int imx93_dsi_phy_init(void *priv_data) regmap_update_bits(dsi->regmap, DISPLAY_MUX, LCDIF_CROSS_LINE_PATTERN, fmt); - ret = dphy_pll_init(dsi); + ret = imx93_dsi_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); + ret = imx93_dsi_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); + imx93_dsi_pll_uninit(dsi); return ret; } @@ -667,8 +812,342 @@ 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); + imx93_pll_power_off(dsi); + imx93_dsi_pll_uninit(dsi); +} + +static void +imx95_dsi_phy_csr_write(struct imx93_dsi *dsi, unsigned int reg, u32 value) +{ + int ret; + + ret = regmap_write(dsi->phy, reg, value); + if (ret < 0) + dev_err(dsi->dev, "failed to write 0x%08x to phy reg 0x%x: %d\n", + value, reg, ret); +} + +static int imx95_dsi_select_input(struct imx93_dsi *dsi) +{ + struct device *dev = dsi->dev; + struct device_node *remote0, *remote1 = NULL; + struct device_node *remote_pi0, *remote_pi1 = NULL; + struct device_node *remote_ldb_ch0, *remote_ldb_ch1 = NULL; + u32 port; + int ret; + + /* pixel link0 */ + remote0 = of_graph_get_remote_node(dev->of_node, 0, + IMX95_DSI_ENDPOINT_PL0); + /* pixel interleaver channel0 */ + remote_pi0 = of_graph_get_remote_node(remote0, + IMX95_DSI_ENDPOINT_PL0, 0); + /* ldb channel0 */ + port = IMX95_DSI_ENDPOINT_PL0 + PIXEL_LINK_STREAMS; + remote_ldb_ch0 = of_graph_get_remote_node(remote0, port, 1); + if (remote_pi0 && !remote_ldb_ch0) { + dsi->use_pl0 = true; + } else { + /* pixel link1 */ + remote1 = of_graph_get_remote_node(dev->of_node, 0, + IMX95_DSI_ENDPOINT_PL1); + /* pixel interleaver channel1 */ + remote_pi1 = of_graph_get_remote_node(remote1, + IMX95_DSI_ENDPOINT_PL1, 0); + /* ldb channel1 */ + port = IMX95_DSI_ENDPOINT_PL1 + PIXEL_LINK_STREAMS; + remote_ldb_ch1 = of_graph_get_remote_node(remote1, port, 1); + if (!remote_pi1 || remote_ldb_ch1) { + dev_err(dev, "No valid input endpoint found\n"); + ret = -EINVAL; + goto out; + } + + dsi->use_pl0 = false; + } + + dev_info(dev, "Using Pixel Link%d as input source\n", + dsi->use_pl0 ? 0 : 1); + + return 0; + +out: + of_node_put(remote_ldb_ch1); + of_node_put(remote_pi1); + of_node_put(remote1); + of_node_put(remote_ldb_ch0); + of_node_put(remote_pi0); + of_node_put(remote0); + + return ret; +} + +static inline void imx95_dsi_write(struct imx93_dsi *dsi, u32 reg, u32 val) +{ + writel(val, dsi->base + reg); +} + +static inline u32 imx95_dsi_read(struct imx93_dsi *dsi, u32 reg) +{ + return readl(dsi->base + reg); +} + +static void imx95_dsi_phy_write_testcode(struct imx93_dsi *dsi, u16 test_code) +{ + /* + * Each control register address is composed by a 4-bit word(testcode + * MSBs) and an 8-bit word(testcode LSBs). + */ + + /* 1. For writing the 4-bit testcode MSBs: */ + /* a. Ensure that TESTCLK and TESTEN are set to low. */ + imx95_dsi_write(dsi, DSI_PHY_TST_CTRL0, PHY_UNTESTCLK); + imx95_dsi_write(dsi, DSI_PHY_TST_CTRL1, PHY_UNTESTEN); + /* b. Set TESTEN to high. */ + imx95_dsi_write(dsi, DSI_PHY_TST_CTRL1, PHY_TESTEN); + /* c. Set TESTCLK to high. */ + imx95_dsi_write(dsi, DSI_PHY_TST_CTRL0, PHY_TESTCLK); + /* d. Place 0x00 in TESTDIN. */ + imx95_dsi_write(dsi, DSI_PHY_TST_CTRL1, PHY_TESTEN | PHY_TESTDIN(0)); + /* + * e. Set TESTCLK to low(with the falling edge on TESTCLK, the TESTDIN + * signal content is latched internally). + */ + imx95_dsi_write(dsi, DSI_PHY_TST_CTRL0, PHY_UNTESTCLK); + /* f. Set TESTEN to low. */ + imx95_dsi_write(dsi, DSI_PHY_TST_CTRL1, PHY_UNTESTEN); + /* g. Place the MSB 8-bit word of testcode in TESTDIN. */ + imx95_dsi_write(dsi, DSI_PHY_TST_CTRL1, PHY_TESTDIN(test_code >> 8)); + /* h. Set TESTCLK to high. */ + imx95_dsi_write(dsi, DSI_PHY_TST_CTRL0, PHY_TESTCLK); + + /* 2. For writing the 8-bit testcode LSBs: */ + /* a. Set TESTCLK to low. */ + imx95_dsi_write(dsi, DSI_PHY_TST_CTRL0, PHY_UNTESTCLK); + /* b. Set TESTEN to high. */ + imx95_dsi_write(dsi, DSI_PHY_TST_CTRL1, PHY_TESTEN); + /* c. Set TESTCLK to high. */ + imx95_dsi_write(dsi, DSI_PHY_TST_CTRL0, PHY_TESTCLK); + /* d. Place the LSB 8-bit word of testcode in TESTDIN. */ + imx95_dsi_write(dsi, DSI_PHY_TST_CTRL1, + PHY_TESTEN | PHY_TESTDIN(test_code & 0xff)); + /* + * e. Set TESTCLK to low(with the falling edge on TESTCLK, the TESTDIN + * signal content is latched internally). + */ + imx95_dsi_write(dsi, DSI_PHY_TST_CTRL0, PHY_UNTESTCLK); + /* f. Set TESTEN to low. */ + imx95_dsi_write(dsi, DSI_PHY_TST_CTRL1, + PHY_UNTESTEN | PHY_TESTDIN(test_code & 0xff)); +} + +static void +imx95_dsi_phy_tst_ctrl_write(struct imx93_dsi *dsi, u16 test_code, u8 test_data) +{ + imx95_dsi_phy_write_testcode(dsi, test_code); + + /* For writing the data: */ + /* a. Place the 8-bit word in TESTDIN. */ + imx95_dsi_write(dsi, DSI_PHY_TST_CTRL1, PHY_TESTDIN(test_data)); + /* b. Set TESTCLK to high. */ + imx95_dsi_write(dsi, DSI_PHY_TST_CTRL0, PHY_TESTCLK); +} + +static u8 imx95_dsi_phy_tst_ctrl_read(struct imx93_dsi *dsi, u16 test_code) +{ + u32 val; + + imx95_dsi_phy_write_testcode(dsi, test_code); + + /* For reading the data: */ + val = imx95_dsi_read(dsi, DSI_PHY_TST_CTRL1); + return FIELD_GET(PHY_TESTDOUT_MASK, val); +} + +static void +imx95_dsi_phy_tst_ctrl_update(struct imx93_dsi *dsi, u16 test_code, u8 mask, u8 val) +{ + u8 tmp; + + tmp = imx95_dsi_phy_tst_ctrl_read(dsi, test_code); + tmp &= ~mask; + imx95_dsi_phy_tst_ctrl_write(dsi, test_code, tmp | val); +} + +static int imx95_dsi_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; + } + + ret = clk_prepare_enable(dsi->clk_ref); + if (ret < 0) { + dev_err(dsi->dev, "failed to enable ref clock: %d\n", ret); + return ret; + } + + val = PHY_CFGCLKFREQRANGE(dphy_pll_get_cfgclkrange(dsi)) | + PHY_HSFREQRANGE(dphy_pll_get_hsfreqrange(&opts->mipi_dphy)); + imx95_dsi_phy_csr_write(dsi, PHY_FREQ_CONTROL, val); + + /* set pll_mpll_prog_rw (bits1:0) to 2'b11 */ + imx95_dsi_phy_tst_ctrl_write(dsi, DIG_RDWR_TX_PLL_13, 0x03); + /* set cb_sel_vref_lprx_rw (bits 1:0) to 2'b10 */ + imx95_dsi_phy_tst_ctrl_write(dsi, DIG_RDWR_TX_CB_1, 0x06); + /* set cb_sel_vrefcd_lprx_rw (bits 6:5) to 2'b10 */ + imx95_dsi_phy_tst_ctrl_write(dsi, DIG_RDWR_TX_CB_0, 0x53); + /* + * set txclk_term_lowcap_lp00_en_ovr_en and + * txclk_term_lowcap_lp00_en_ovr(bits 1:0) to 2'b10 + */ + imx95_dsi_phy_tst_ctrl_write(dsi, DIG_RDWR_TX_CLK_TERMLOWCAP, 0x02); + /* bit [5:4] of TX control register with address 0x272 to 2'b00 */ + imx95_dsi_phy_tst_ctrl_write(dsi, DIG_RDWR_TX_TX_SLEW_7, 0x00); + /* sr_osc_freq_target */ + imx95_dsi_phy_tst_ctrl_write(dsi, DIG_RDWR_TX_TX_SLEW_6, 0x07); + imx95_dsi_phy_tst_ctrl_write(dsi, DIG_RDWR_TX_TX_SLEW_5, 0xd0); + /* enable slew rate calibration */ + imx95_dsi_phy_tst_ctrl_write(dsi, DIG_RDWR_TX_TX_SLEW_7, 0x10); + + ret = clk_prepare_enable(dsi->clk_cfg); + if (ret < 0) { + dev_err(dsi->dev, "failed to enable cfg clock: %d\n", ret); + return ret; + } + + /* pll m */ + imx95_dsi_phy_tst_ctrl_write(dsi, DIG_RDWR_TX_PLL_28, + PLL_M_LOW(cfg.m)); + imx95_dsi_phy_tst_ctrl_write(dsi, DIG_RDWR_TX_PLL_29, + PLL_M_HIGH(cfg.m)); + imx95_dsi_phy_tst_ctrl_update(dsi, DIG_RDWR_TX_PLL_30, + PLL_M_OVR_EN_RW, PLL_M_OVR_EN_RW); + + /* pll n */ + imx95_dsi_phy_tst_ctrl_update(dsi, DIG_RDWR_TX_PLL_27, + PLL_N_OVR_RW_MASK | PLL_N_OVR_EN_RW, + PLL_N_OVR_RW(cfg.n) | PLL_N_OVR_EN_RW); + + /* vco ctrl */ + val = dphy_pll_get_vco(&opts->mipi_dphy); + imx95_dsi_phy_tst_ctrl_update(dsi, DIG_RDWR_TX_PLL_30, + PLL_VCO_CNTRL_OVR_RW_MASK | + PLL_VCO_CNTRL_OVR_EN_RW, + PLL_VCO_CNTRL_OVR_RW(val) | + PLL_VCO_CNTRL_OVR_EN_RW); + + /* cpbias ctrl */ + imx95_dsi_phy_tst_ctrl_update(dsi, DIG_RDWR_TX_PLL_1, + PLL_CPBIAS_CNTRL_RW_MASK, + PLL_CPBIAS_CNTRL_RW(0x10)); + + /* int ctrl & gmp ctrl */ + imx95_dsi_phy_tst_ctrl_write(dsi, DIG_RDWR_TX_PLL_5, + PLL_INT_CNTRL_RW(0x0) | + PLL_GMP_CNTRL_RW(0x1)); + + /* prop ctrl */ + val = dphy_pll_get_prop(&opts->mipi_dphy); + imx95_dsi_phy_tst_ctrl_update(dsi, DIG_RDWR_TX_PLL_17, + PLL_PROP_CNTRL_RW_MASK, + PLL_PROP_CNTRL_RW(val)); + + /* pll lock configurations */ + imx95_dsi_phy_tst_ctrl_write(dsi, DIG_RDWR_TX_PLL_22, 0x2); /* TH1 */ + imx95_dsi_phy_tst_ctrl_write(dsi, DIG_RDWR_TX_PLL_23, 0x0); /* TH1 */ + imx95_dsi_phy_tst_ctrl_write(dsi, DIG_RDWR_TX_PLL_24, + PLL_TH2_RW(0x60)); + imx95_dsi_phy_tst_ctrl_write(dsi, DIG_RDWR_TX_PLL_25, + PLL_TH3_RW(0x3)); + imx95_dsi_phy_tst_ctrl_update(dsi, DIG_RDWR_TX_PLL_9, + PLL_LOCK_SEL_RW, PLL_LOCK_SEL_RW); + + /* pll power on */ + imx95_dsi_phy_tst_ctrl_update(dsi, DIG_RDWR_TX_PLL_17, + PLL_PWRON_OVR_RW | PLL_PWRON_OVR_EN_RW, + PLL_PWRON_OVR_RW | PLL_PWRON_OVR_EN_RW); + + /* pll clkouten right/left */ + imx95_dsi_phy_tst_ctrl_write(dsi, DIG_RDWR_TX_PLL_31, + PLL_CLKOUTEN_RIGHT_RW | + PLL_CLKOUTEN_LEFT_RW); + + return 0; +} + +static int imx95_dsi_phy_init(void *priv_data) +{ + struct imx93_dsi *dsi = priv_data; + u64 lane_mask; + int bpp, ret; + + regmap_write(dsi->mst, DSI_PIXEL_LINK_CONTROL, dsi->use_pl0 ? 0 : PIXEL_LINK_SEL); + + bpp = mipi_dsi_pixel_format_to_bpp(dsi->format); + if (bpp < 0) { + dev_err(dsi->dev, "failed to get dsi format bpp\n"); + return bpp; + } + + imx95_dsi_write(dsi, DSI_DPI_CFG_POL, VSYNC_ACTIVE_LOW | HSYNC_ACTIVE_LOW); + + regmap_write(dsi->mst, DSI_CLOCK_SETTING, 0); + + switch (bpp) { + case 24: + regmap_write(dsi->str, DSI_HOST_CONFIGURATION, RGB_24BIT); + break; + case 18: + regmap_write(dsi->str, DSI_HOST_CONFIGURATION, RGB_18BIT); + break; + case 16: + regmap_write(dsi->str, DSI_HOST_CONFIGURATION, RGB_16BIT); + break; + default: + dev_err(dsi->dev, "invalid dsi format bpp %d\n", bpp); + return -EINVAL; + } + + lane_mask = int_pow(2, dsi->lanes) - 1; + imx95_dsi_phy_csr_write(dsi, PHY_MODE_CONTROL, + PHY_ENABLE_EXT(lane_mask) | PLL_CLKSEL_GEN | + TX_RXZ_DSI_MODE); + + ret = imx95_dsi_pll_configure(dsi, &dsi->phy_cfg); + if (ret < 0) { + dev_err(dsi->dev, "failed to configure phy pll: %d\n", ret); + return ret; + } + + imx95_dsi_phy_csr_write(dsi, PHY_TEST_MODE_CONTROL, TURNDISABLE_0); + + regmap_write(dsi->mst, DSI_CLOCK_GATING_CONTROL, + DISPLAY_ASYNC_FIFO(dsi->use_pl0)); + + return 0; +} + +static void imx95_dsi_phy_power_off(void *priv_data) +{ + struct imx93_dsi *dsi = priv_data; + + /* set 1 to disable */ + regmap_write(dsi->mst, DSI_CLOCK_GATING_CONTROL, + DPHY_PLL_CLKEXT | DPHY_PLL_CLKOUT | DPHY_PLL_CLKIN | + DISPLAY_ASYNC_FIFO(0) | DISPLAY_ASYNC_FIFO(1)); + + imx95_dsi_phy_csr_write(dsi, PHY_MODE_CONTROL, TX_RXZ_DSI_MODE); + + clk_disable_unprepare(dsi->clk_cfg); + clk_disable_unprepare(dsi->clk_ref); + + regmap_write(dsi->mst, DSI_PIXEL_LINK_CONTROL, 0); } static int @@ -814,10 +1293,18 @@ static const struct dw_mipi_dsi_phy_ops imx93_dsi_phy_ops = { .get_timing = imx93_dsi_phy_get_timing, }; +static const struct dw_mipi_dsi_phy_ops imx95_dsi_phy_ops = { + .init = imx95_dsi_phy_init, + .power_off = imx95_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->lanes = device->lanes; dsi->format = device->format; return 0; @@ -827,17 +1314,12 @@ 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) +static int imx93_dsi_parse_dt(struct imx93_dsi *dsi) { - struct device *dev = &pdev->dev; + struct device *dev = dsi->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); @@ -845,6 +1327,46 @@ static int imx93_dsi_probe(struct platform_device *pdev) return ret; } + return 0; +} + +static int imx95_dsi_parse_dt(struct platform_device *pdev, struct imx93_dsi *dsi) +{ + struct device *dev = dsi->dev; + struct device_node *np = dev->of_node; + + dsi->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(dsi->base)) + return PTR_ERR(dsi->base); + + dsi->mst = syscon_regmap_lookup_by_phandle(np, "fsl,disp-master-csr"); + if (IS_ERR(dsi->mst)) + return dev_err_probe(dev, PTR_ERR(dsi->mst), + "failed to get master CSR\n"); + + dsi->str = syscon_regmap_lookup_by_phandle(np, "fsl,disp-stream-csr"); + if (IS_ERR(dsi->str)) + return dev_err_probe(dev, PTR_ERR(dsi->str), + "failed to get stream CSR\n"); + + dsi->phy = syscon_regmap_lookup_by_phandle(np, "fsl,mipi-combo-phy-csr"); + if (IS_ERR(dsi->phy)) + return dev_err_probe(dev, PTR_ERR(dsi->phy), + "failed to get phy CSR\n"); + + return imx95_dsi_select_input(dsi); +} + +static int imx93_dsi_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct imx93_dsi *dsi; + int ret; + + dsi = devm_kzalloc(dev, sizeof(*dsi), GFP_KERNEL); + if (!dsi) + return -ENOMEM; + dsi->clk_pixel = devm_clk_get(dev, "pix"); if (IS_ERR(dsi->clk_pixel)) return dev_err_probe(dev, PTR_ERR(dsi->clk_pixel), @@ -870,11 +1392,26 @@ static int imx93_dsi_probe(struct platform_device *pdev) dev_dbg(dev, "phy ref clock rate: %lu\n", dsi->ref_clk_rate); dsi->dev = dev; + + if (of_device_is_compatible(dev->of_node, "fsl,imx93-mipi-dsi")) + ret = imx93_dsi_parse_dt(dsi); + else + ret = imx95_dsi_parse_dt(pdev, dsi); + if (ret) + return ret; + + dsi->pdata.base = dsi->base; 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; + if (of_device_is_compatible(dev->of_node, "fsl,imx93-mipi-dsi")) { + dsi->pdata.phy_ops = &imx93_dsi_phy_ops; + dsi->fixed_format = MEDIA_BUS_FMT_RGB888_1X24; + } else { + dsi->pdata.phy_ops = &imx95_dsi_phy_ops; + dsi->fixed_format = MEDIA_BUS_FMT_RGB888_1X36_CPADLO; + } dsi->pdata.host_ops = &imx93_dsi_host_ops; dsi->pdata.priv_data = dsi; platform_set_drvdata(pdev, dsi); @@ -896,6 +1433,7 @@ static void imx93_dsi_remove(struct platform_device *pdev) static const struct of_device_id imx93_dsi_dt_ids[] = { { .compatible = "fsl,imx93-mipi-dsi", }, + { .compatible = "fsl,imx95-mipi-dsi", }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, imx93_dsi_dt_ids); -- 2.51.0
