summaryrefslogtreecommitdiff
path: root/drivers/gpu/drm/imx/nwl_dsi-imx.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/gpu/drm/imx/nwl_dsi-imx.c')
-rw-r--r--drivers/gpu/drm/imx/nwl_dsi-imx.c1052
1 files changed, 1052 insertions, 0 deletions
diff --git a/drivers/gpu/drm/imx/nwl_dsi-imx.c b/drivers/gpu/drm/imx/nwl_dsi-imx.c
new file mode 100644
index 000000000000..55d623af5f74
--- /dev/null
+++ b/drivers/gpu/drm/imx/nwl_dsi-imx.c
@@ -0,0 +1,1052 @@
+/*
+ * i.MX drm driver - Northwest Logic MIPI DSI display driver
+ *
+ * Copyright (C) 2017 NXP
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <drm/bridge/nwl_dsi.h>
+#include <drm/drmP.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_of.h>
+#include <linux/busfreq-imx.h>
+#include <linux/clk.h>
+#include <linux/component.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/mfd/syscon.h>
+#include <linux/mfd/syscon/imx8mq-iomuxc-gpr.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_graph.h>
+#include <linux/of_platform.h>
+#include <linux/phy/phy-mixel-mipi-dsi.h>
+#include <linux/phy/phy.h>
+#include <linux/regmap.h>
+#include <soc/imx8/sc/sci.h>
+#include <video/videomode.h>
+
+#include "imx-drm.h"
+
+#define DRIVER_NAME "nwl_dsi-imx"
+
+/* 8MQ SRC specific registers */
+#define SRC_MIPIPHY_RCR 0x28
+#define RESET_BYTE_N BIT(1)
+#define RESET_N BIT(2)
+#define DPI_RESET_N BIT(3)
+#define ESC_RESET_N BIT(4)
+#define PCLK_RESET_N BIT(5)
+
+#define DC_ID(x) SC_R_DC_ ## x
+#define MIPI_ID(x) SC_R_MIPI_ ## x
+#define SYNC_CTRL(x) SC_C_SYNC_CTRL ## x
+#define PXL_VLD(x) SC_C_PXL_LINK_MST ## x ## _VLD
+#define PXL_ADDR(x) SC_C_PXL_LINK_MST ## x ## _ADDR
+
+/* Possible clocks */
+#define CLK_PIXEL "pixel"
+#define CLK_CORE "core"
+#define CLK_BYPASS "bypass"
+#define CLK_PHYREF "phy_ref"
+
+/* Possible valid PHY reference clock rates*/
+u32 phyref_rates[] = {
+ 24000000,
+ 25000000,
+ 27000000,
+};
+
+struct imx_mipi_dsi {
+ struct drm_encoder encoder;
+ struct drm_bridge bridge;
+ struct drm_bridge *next_bridge;
+ struct device *dev;
+ struct phy *phy;
+
+ /* Optional external regs */
+ struct regmap *csr;
+ struct regmap *reset;
+ struct regmap *mux_sel;
+
+ /* Optional clocks */
+ struct clk_config *clk_config;
+ size_t clk_num;
+
+ u32 tx_ulps_reg;
+ u32 pxl2dpi_reg;
+
+ unsigned long bit_clk;
+ unsigned long pix_clk;
+ u32 phyref_rate;
+ u32 instance;
+ u32 sync_pol;
+ u32 power_on_delay;
+ bool no_clk_reset;
+ bool enabled;
+ bool suspended;
+};
+
+struct clk_config {
+ const char *id;
+ struct clk *clk;
+ bool present;
+ bool enabled;
+ u32 rate;
+};
+
+enum imx_ext_regs {
+ IMX_REG_CSR = BIT(1),
+ IMX_REG_SRC = BIT(2),
+ IMX_REG_GPR = BIT(3),
+};
+
+struct devtype {
+ int (*poweron)(struct imx_mipi_dsi *);
+ void (*poweroff)(struct imx_mipi_dsi *);
+ u32 ext_regs; /* required external registers */
+ u32 tx_ulps_reg;
+ u32 pxl2dpi_reg;
+ u8 max_instances;
+ struct clk_config clk_config[4];
+};
+
+static int imx8qm_dsi_poweron(struct imx_mipi_dsi *dsi);
+static void imx8qm_dsi_poweroff(struct imx_mipi_dsi *dsi);
+static struct devtype imx8qm_dev = {
+ .poweron = &imx8qm_dsi_poweron,
+ .poweroff = &imx8qm_dsi_poweroff,
+ .clk_config = {
+ { .id = CLK_CORE, .present = false },
+ { .id = CLK_PIXEL, .present = true },
+ { .id = CLK_BYPASS, .present = true },
+ { .id = CLK_PHYREF, .present = true },
+ },
+ .ext_regs = IMX_REG_CSR,
+ .tx_ulps_reg = 0x00,
+ .pxl2dpi_reg = 0x04,
+ .max_instances = 2,
+};
+
+static int imx8qxp_dsi_poweron(struct imx_mipi_dsi *dsi);
+static void imx8qxp_dsi_poweroff(struct imx_mipi_dsi *dsi);
+static struct devtype imx8qxp_dev = {
+ .poweron = &imx8qxp_dsi_poweron,
+ .poweroff = &imx8qxp_dsi_poweroff,
+ .clk_config = {
+ { .id = CLK_CORE, .present = false },
+ { .id = CLK_PIXEL, .present = true },
+ { .id = CLK_BYPASS, .present = true },
+ { .id = CLK_PHYREF, .present = true },
+ },
+ .ext_regs = IMX_REG_CSR,
+ .tx_ulps_reg = 0x30,
+ .pxl2dpi_reg = 0x40,
+ .max_instances = 2,
+};
+
+static int imx8mq_dsi_poweron(struct imx_mipi_dsi *dsi);
+static void imx8mq_dsi_poweroff(struct imx_mipi_dsi *dsi);
+static struct devtype imx8mq_dev = {
+ .poweron = &imx8mq_dsi_poweron,
+ .poweroff = &imx8mq_dsi_poweroff,
+ .clk_config = {
+ { .id = CLK_CORE, .present = true },
+ { .id = CLK_PIXEL, .present = false },
+ { .id = CLK_BYPASS, .present = false },
+ { .id = CLK_PHYREF, .present = true },
+ },
+ .ext_regs = IMX_REG_SRC | IMX_REG_GPR,
+ .max_instances = 1,
+};
+
+static const struct of_device_id imx_nwl_dsi_dt_ids[] = {
+ { .compatible = "fsl,imx8qm-mipi-dsi", .data = &imx8qm_dev, },
+ { .compatible = "fsl,imx8qxp-mipi-dsi", .data = &imx8qxp_dev, },
+ { .compatible = "fsl,imx8mq-mipi-dsi_drm", .data = &imx8mq_dev, },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, imx_nwl_dsi_dt_ids);
+
+static inline struct imx_mipi_dsi *encoder_to_dsi(struct drm_encoder *encoder)
+{
+ return container_of(encoder, struct imx_mipi_dsi, encoder);
+}
+
+static void imx_nwl_dsi_set_clocks(struct imx_mipi_dsi *dsi, bool enable)
+{
+ struct device *dev = dsi->dev;
+ const char *id;
+ struct clk *clk;
+ unsigned long new_rate, cur_rate;
+ bool enabled;
+ size_t i;
+
+ for (i = 0; i < dsi->clk_num; i++) {
+ if (!dsi->clk_config[i].present)
+ continue;
+ id = dsi->clk_config[i].id;
+ clk = dsi->clk_config[i].clk;
+ new_rate = dsi->clk_config[i].rate;
+ cur_rate = clk_get_rate(clk);
+ enabled = dsi->clk_config[i].enabled;
+
+ /* BYPASS clk must have the same rate as PHY_REF clk */
+ if (!strcmp(id, CLK_BYPASS) || !strcmp(id, CLK_PHYREF))
+ new_rate = dsi->phyref_rate;
+
+ if (enable) {
+ if (enabled && new_rate != cur_rate)
+ clk_disable_unprepare(clk);
+ else if (enabled && new_rate == cur_rate)
+ continue;
+ if (new_rate > 0)
+ clk_set_rate(clk, new_rate);
+ clk_prepare_enable(clk);
+ dsi->clk_config[i].enabled = true;
+ cur_rate = clk_get_rate(clk);
+ DRM_DEV_DEBUG_DRIVER(dev,
+ "Enabled %s clk (rate: req=%lu act=%lu)\n",
+ id, new_rate, cur_rate);
+ } else if (enabled) {
+ clk_disable_unprepare(clk);
+ dsi->clk_config[i].enabled = false;
+ DRM_DEV_DEBUG_DRIVER(dev, "Disabled %s clk\n", id);
+ }
+ }
+}
+
+/*
+ * v2 is true for QXP
+ * On QM, we have 2 DPUs, each one with a MIPI-DSI link
+ * On QXP, we have 1 DPU with two MIPI-DSI links
+ * Because of this, we will have different initialization
+ * paths for MIPI0 and MIPI1 on QM vs QXP
+ */
+static int imx8q_dsi_poweron(struct imx_mipi_dsi *dsi, bool v2)
+{
+ struct device *dev = dsi->dev;
+ int ret = 0;
+ sc_err_t sci_err = 0;
+ sc_ipc_t ipc_handle = 0;
+ u32 inst = dsi->instance;
+ u32 mu_id;
+ sc_rsrc_t mipi_id, dc_id;
+ sc_ctrl_t mipi_ctrl;
+
+ sci_err = sc_ipc_getMuID(&mu_id);
+ if (sci_err != SC_ERR_NONE) {
+ DRM_DEV_ERROR(dev, "Failed to get MU ID (%d)\n", sci_err);
+ return -ENODEV;
+ }
+ sci_err = sc_ipc_open(&ipc_handle, mu_id);
+ if (sci_err != SC_ERR_NONE) {
+ DRM_DEV_ERROR(dev, "Failed to open IPC (%d)\n", sci_err);
+ return -ENODEV;
+ }
+
+ mipi_id = inst?MIPI_ID(1):MIPI_ID(0);
+ dc_id = (!v2 && inst)?DC_ID(1):DC_ID(0);
+ DRM_DEV_DEBUG_DRIVER(dev, "MIPI ID: %d DC ID: %d\n",
+ mipi_id,
+ dc_id);
+
+ if (v2) {
+ sci_err = sc_misc_set_control(ipc_handle,
+ mipi_id, SC_C_MODE, 0);
+ if (sci_err != SC_ERR_NONE)
+ DRM_DEV_ERROR(dev,
+ "Failed to set SC_C_MODE (%d)\n",
+ sci_err);
+ sci_err = sc_misc_set_control(ipc_handle,
+ mipi_id, SC_C_DUAL_MODE, 0);
+ if (sci_err != SC_ERR_NONE)
+ DRM_DEV_ERROR(dev,
+ "Failed to set SC_C_DUAL_MODE (%d)\n",
+ sci_err);
+ sci_err = sc_misc_set_control(ipc_handle,
+ mipi_id, SC_C_PXL_LINK_SEL, 0);
+ if (sci_err != SC_ERR_NONE)
+ DRM_DEV_ERROR(dev,
+ "Failed to set SC_C_PXL_LINK_SEL (%d)\n",
+ sci_err);
+ }
+
+ /* Initialize Pixel Link */
+ mipi_ctrl = (v2 && inst)?PXL_ADDR(2):PXL_ADDR(1);
+ sci_err = sc_misc_set_control(ipc_handle,
+ dc_id,
+ mipi_ctrl,
+ 0);
+ if (sci_err != SC_ERR_NONE) {
+ DRM_DEV_ERROR(dev,
+ "Failed to set SC_C_PXL_LINK_MST%d_ADDR (%d)\n",
+ inst,
+ sci_err);
+ ret = -ENODEV;
+ goto err_ipc;
+ }
+
+ mipi_ctrl = (v2 && inst)?PXL_VLD(2):PXL_VLD(1);
+ sci_err = sc_misc_set_control(ipc_handle,
+ dc_id,
+ mipi_ctrl,
+ 1);
+ if (sci_err != SC_ERR_NONE) {
+ DRM_DEV_ERROR(dev,
+ "Failed to set SC_C_PXL_LINK_MST%d_VLD (%d)\n",
+ inst + 1,
+ sci_err);
+ ret = -ENODEV;
+ goto err_ipc;
+ }
+
+ mipi_ctrl = (v2 && inst)?SYNC_CTRL(1):SYNC_CTRL(0);
+ sci_err = sc_misc_set_control(ipc_handle,
+ dc_id,
+ mipi_ctrl,
+ 1);
+ if (sci_err != SC_ERR_NONE) {
+ DRM_DEV_ERROR(dev,
+ "Failed to set SC_C_SYNC_CTRL%d (%d)\n",
+ inst,
+ sci_err);
+ ret = -ENODEV;
+ goto err_ipc;
+ }
+
+ /* Assert DPI and MIPI bits */
+ sci_err = sc_misc_set_control(ipc_handle,
+ mipi_id,
+ SC_C_DPI_RESET,
+ 1);
+ if (sci_err != SC_ERR_NONE) {
+ DRM_DEV_ERROR(dev,
+ "Failed to assert DPI reset (%d)\n",
+ sci_err);
+ ret = -ENODEV;
+ goto err_ipc;
+ }
+
+ sci_err = sc_misc_set_control(ipc_handle,
+ mipi_id,
+ SC_C_MIPI_RESET,
+ 1);
+ if (sci_err != SC_ERR_NONE) {
+ DRM_DEV_ERROR(dev,
+ "Failed to assert MIPI reset (%d)\n",
+ sci_err);
+ ret = -ENODEV;
+ goto err_ipc;
+ }
+
+ regmap_write(dsi->csr,
+ dsi->tx_ulps_reg,
+ 0);
+ regmap_write(dsi->csr,
+ dsi->pxl2dpi_reg,
+ DPI_24_BIT);
+
+ sc_ipc_close(ipc_handle);
+ return ret;
+
+err_ipc:
+ sc_ipc_close(ipc_handle);
+ return ret;
+}
+
+static void imx8q_dsi_poweroff(struct imx_mipi_dsi *dsi, bool v2)
+{
+ struct device *dev = dsi->dev;
+ sc_err_t sci_err = 0;
+ sc_ipc_t ipc_handle = 0;
+ u32 mu_id;
+ u32 inst = dsi->instance;
+ sc_rsrc_t mipi_id, dc_id;
+ sc_ctrl_t mipi_ctrl;
+
+ mipi_id = inst?MIPI_ID(1):MIPI_ID(0);
+ dc_id = (!v2 && inst)?DC_ID(1):DC_ID(0);
+
+ /* Deassert DPI and MIPI bits */
+ if (sc_ipc_getMuID(&mu_id) != SC_ERR_NONE ||
+ sc_ipc_open(&ipc_handle, mu_id) != SC_ERR_NONE)
+ return;
+
+ sci_err = sc_misc_set_control(ipc_handle,
+ mipi_id, SC_C_DPI_RESET, 0);
+ if (sci_err != SC_ERR_NONE)
+ DRM_DEV_ERROR(dev,
+ "Failed to deassert DPI reset (%d)\n",
+ sci_err);
+
+ sci_err = sc_misc_set_control(ipc_handle,
+ mipi_id, SC_C_MIPI_RESET, 0);
+ if (sci_err != SC_ERR_NONE)
+ DRM_DEV_ERROR(dev,
+ "Failed to deassert MIPI reset (%d)\n",
+ sci_err);
+
+ mipi_ctrl = (v2 && inst)?SYNC_CTRL(1):SYNC_CTRL(0);
+ sci_err = sc_misc_set_control(ipc_handle,
+ dc_id, mipi_ctrl, 0);
+ if (sci_err != SC_ERR_NONE)
+ DRM_DEV_ERROR(dev,
+ "Failed to reset SC_C_SYNC_CTRL0 (%d)\n",
+ sci_err);
+
+ mipi_ctrl = (v2 && inst)?PXL_VLD(2):PXL_VLD(1);
+ sci_err = sc_misc_set_control(ipc_handle,
+ dc_id, mipi_ctrl, 0);
+ if (sci_err != SC_ERR_NONE)
+ DRM_DEV_ERROR(dev,
+ "Failed to reset SC_C_SYNC_CTRL0 (%d)\n",
+ sci_err);
+
+ sc_ipc_close(ipc_handle);
+}
+
+static int imx8qm_dsi_poweron(struct imx_mipi_dsi *dsi)
+{
+ return imx8q_dsi_poweron(dsi, false);
+}
+
+static void imx8qm_dsi_poweroff(struct imx_mipi_dsi *dsi)
+{
+ return imx8q_dsi_poweroff(dsi, false);
+}
+
+static int imx8qxp_dsi_poweron(struct imx_mipi_dsi *dsi)
+{
+ return imx8q_dsi_poweron(dsi, true);
+}
+
+static void imx8qxp_dsi_poweroff(struct imx_mipi_dsi *dsi)
+{
+ return imx8q_dsi_poweroff(dsi, true);
+}
+
+static int imx8mq_dsi_poweron(struct imx_mipi_dsi *dsi)
+{
+ regmap_update_bits(dsi->reset, SRC_MIPIPHY_RCR,
+ PCLK_RESET_N, PCLK_RESET_N);
+ regmap_update_bits(dsi->reset, SRC_MIPIPHY_RCR,
+ ESC_RESET_N, ESC_RESET_N);
+ regmap_update_bits(dsi->reset, SRC_MIPIPHY_RCR,
+ RESET_BYTE_N, RESET_BYTE_N);
+ regmap_update_bits(dsi->reset, SRC_MIPIPHY_RCR,
+ DPI_RESET_N, DPI_RESET_N);
+
+ return 0;
+}
+
+static void imx8mq_dsi_poweroff(struct imx_mipi_dsi *dsi)
+{
+ regmap_update_bits(dsi->reset, SRC_MIPIPHY_RCR,
+ PCLK_RESET_N, 0);
+ regmap_update_bits(dsi->reset, SRC_MIPIPHY_RCR,
+ ESC_RESET_N, 0);
+ regmap_update_bits(dsi->reset, SRC_MIPIPHY_RCR,
+ RESET_BYTE_N, 0);
+ regmap_update_bits(dsi->reset, SRC_MIPIPHY_RCR,
+ DPI_RESET_N, 0);
+}
+
+static void imx_nwl_dsi_enable(struct imx_mipi_dsi *dsi)
+{
+ struct device *dev = dsi->dev;
+ const struct of_device_id *of_id = of_match_device(imx_nwl_dsi_dt_ids,
+ dev);
+ const struct devtype *devtype = of_id->data;
+ unsigned long bit_clk, min_sleep, max_sleep;
+ int ret;
+
+ if (dsi->enabled)
+ return;
+
+ DRM_DEV_DEBUG_DRIVER(dev, "id = %s\n", (dsi->instance)?"DSI1":"DSI0");
+
+ /*
+ * TODO: we are doing this here, because the ADV7535 which is a drm
+ * bridge, may change the DSI parameters in mode_set. One of the
+ * changed parameter is DSI lanes, which affects the PHY settings.
+ * This is why, we need run this function again, here, in order
+ * to correctly set-up the PHY. Since we can't do anything here, we
+ * will ignore it's status.
+ * In the future, maybe it will be best to move the PHY handling
+ * into the DSI host driver.
+ */
+ bit_clk = nwl_dsi_get_bit_clock(dsi->next_bridge, dsi->pix_clk);
+ if (bit_clk != dsi->bit_clk) {
+ mixel_phy_mipi_set_phy_speed(dsi->phy,
+ bit_clk,
+ dsi->phyref_rate,
+ false);
+ dsi->bit_clk = bit_clk;
+ }
+
+ /*
+ * On some systems we need to wait some time before enabling the
+ * phy_ref clock, in order to allow the parent PLL to become stable
+ */
+ if (dsi->power_on_delay > 20) {
+ msleep(dsi->power_on_delay);
+ } else if (dsi->power_on_delay > 0) {
+ max_sleep = dsi->power_on_delay * 1000;
+ min_sleep = 1000;
+ if (max_sleep > 6000)
+ min_sleep = max_sleep - 5000;
+ usleep_range(min_sleep, max_sleep);
+ }
+
+ request_bus_freq(BUS_FREQ_HIGH);
+
+ imx_nwl_dsi_set_clocks(dsi, true);
+
+ ret = devtype->poweron(dsi);
+ if (ret < 0) {
+ DRM_DEV_ERROR(dev, "Failed to power on DSI (%d)\n", ret);
+ return;
+ }
+
+ dsi->enabled = true;
+}
+
+static void imx_nwl_dsi_disable(struct imx_mipi_dsi *dsi)
+{
+ struct device *dev = dsi->dev;
+ const struct of_device_id *of_id = of_match_device(imx_nwl_dsi_dt_ids,
+ dev);
+ const struct devtype *devtype = of_id->data;
+
+ if (!dsi->enabled)
+ return;
+
+ DRM_DEV_DEBUG_DRIVER(dev, "id = %s\n", (dsi->instance)?"DSI1":"DSI0");
+
+ if (!dsi->no_clk_reset)
+ devtype->poweroff(dsi);
+
+ imx_nwl_dsi_set_clocks(dsi, false);
+
+ release_bus_freq(BUS_FREQ_HIGH);
+
+ dsi->enabled = false;
+}
+
+static void imx_nwl_update_sync_polarity(unsigned int *flags, u32 sync_pol)
+{
+ /* Make sure all flags are set-up accordingly */
+ if (sync_pol) {
+ *flags |= DRM_MODE_FLAG_PHSYNC;
+ *flags |= DRM_MODE_FLAG_PVSYNC;
+ *flags &= ~DRM_MODE_FLAG_NHSYNC;
+ *flags &= ~DRM_MODE_FLAG_NVSYNC;
+ } else {
+ *flags &= ~DRM_MODE_FLAG_PHSYNC;
+ *flags &= ~DRM_MODE_FLAG_PVSYNC;
+ *flags |= DRM_MODE_FLAG_NHSYNC;
+ *flags |= DRM_MODE_FLAG_NVSYNC;
+ }
+}
+
+/*
+ * This function will try the required phy speed for current mode
+ * If the phy speed can be achieved, the phy will save the speed
+ * configuration
+ */
+static int imx_nwl_try_phy_speed(struct imx_mipi_dsi *dsi,
+ struct drm_display_mode *mode)
+{
+ struct device *dev = dsi->dev;
+ unsigned long pixclock;
+ unsigned long bit_clk;
+ size_t i, num_rates = ARRAY_SIZE(phyref_rates);
+ int ret = 0;
+
+ pixclock = mode->clock * 1000;
+ /*
+ * DSI host should know the required bit clock, since it has info
+ * about bits-per-pixel and number of lanes from DSI device
+ */
+ bit_clk = nwl_dsi_get_bit_clock(dsi->next_bridge, pixclock);
+
+ /* If bit_clk is the same with current, we're good */
+ if (bit_clk == dsi->bit_clk)
+ return 0;
+
+ for (i = 0; i < num_rates; i++) {
+ dsi->phyref_rate = phyref_rates[i];
+ DRM_DEV_DEBUG_DRIVER(dev, "Trying PHY ref rate: %u\n",
+ dsi->phyref_rate);
+ ret = mixel_phy_mipi_set_phy_speed(dsi->phy,
+ bit_clk,
+ dsi->phyref_rate,
+ false);
+ /* Pick the first non-failing rate */
+ if (!ret)
+ break;
+ }
+ if (ret < 0) {
+ DRM_DEV_ERROR(dev,
+ "Cannot setup PHY for mode: %ux%u @%d kHz\n",
+ mode->hdisplay,
+ mode->vdisplay,
+ mode->clock);
+ DRM_DEV_ERROR(dev, "PHY_REF clk: %u, bit clk: %lu\n",
+ dsi->phyref_rate, bit_clk);
+ } else {
+ dsi->bit_clk = bit_clk;
+ dsi->pix_clk = pixclock;
+ }
+
+ return ret;
+}
+
+static void imx_nwl_dsi_encoder_enable(struct drm_encoder *encoder)
+{
+ struct imx_mipi_dsi *dsi = encoder_to_dsi(encoder);
+
+ pm_runtime_get_sync(dsi->dev);
+ imx_nwl_dsi_enable(dsi);
+}
+
+static void imx_nwl_dsi_encoder_disable(struct drm_encoder *encoder)
+{
+ struct imx_mipi_dsi *dsi = encoder_to_dsi(encoder);
+
+ imx_nwl_dsi_disable(dsi);
+ pm_runtime_put_sync(dsi->dev);
+}
+
+static int imx_nwl_dsi_encoder_atomic_check(struct drm_encoder *encoder,
+ struct drm_crtc_state *crtc_state,
+ struct drm_connector_state *conn_state)
+{
+ struct imx_crtc_state *imx_crtc_state = to_imx_crtc_state(crtc_state);
+ struct imx_mipi_dsi *dsi = encoder_to_dsi(encoder);
+ unsigned int *flags = &crtc_state->adjusted_mode.flags;
+
+ imx_crtc_state->bus_format = MEDIA_BUS_FMT_RGB101010_1X30;
+ imx_nwl_update_sync_polarity(flags, dsi->sync_pol);
+
+ /* Try to see if the phy can satisfy the current mode */
+ return imx_nwl_try_phy_speed(dsi, &crtc_state->adjusted_mode);
+}
+
+static const struct drm_encoder_helper_funcs
+imx_nwl_dsi_encoder_helper_funcs = {
+ .enable = imx_nwl_dsi_encoder_enable,
+ .disable = imx_nwl_dsi_encoder_disable,
+ .atomic_check = imx_nwl_dsi_encoder_atomic_check,
+};
+
+static void imx_nwl_dsi_encoder_destroy(struct drm_encoder *encoder)
+{
+ drm_encoder_cleanup(encoder);
+}
+
+static const struct drm_encoder_funcs imx_nwl_dsi_encoder_funcs = {
+ .destroy = imx_nwl_dsi_encoder_destroy,
+};
+
+
+static void imx_nwl_dsi_bridge_enable(struct drm_bridge *bridge)
+{
+ struct imx_mipi_dsi *dsi = bridge->driver_private;
+
+ imx_nwl_dsi_enable(dsi);
+ pm_runtime_get_sync(dsi->dev);
+}
+
+static void imx_nwl_dsi_bridge_disable(struct drm_bridge *bridge)
+{
+ struct imx_mipi_dsi *dsi = bridge->driver_private;
+
+ imx_nwl_dsi_disable(dsi);
+ pm_runtime_put_sync(dsi->dev);
+}
+
+static bool imx_nwl_dsi_bridge_mode_fixup(struct drm_bridge *bridge,
+ const struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+ struct imx_mipi_dsi *dsi = bridge->driver_private;
+ unsigned int *flags = &adjusted_mode->flags;
+
+ imx_nwl_update_sync_polarity(flags, dsi->sync_pol);
+
+ return (imx_nwl_try_phy_speed(dsi, adjusted_mode) == 0);
+}
+
+static int imx_nwl_dsi_bridge_attach(struct drm_bridge *bridge)
+{
+ struct imx_mipi_dsi *dsi = bridge->driver_private;
+ struct drm_encoder *encoder = bridge->encoder;
+ int ret = 0;
+
+ DRM_DEV_DEBUG_DRIVER(dsi->dev, "id = %s\n",
+ (dsi->instance)?"DSI1":"DSI0");
+ if (!encoder) {
+ DRM_DEV_ERROR(dsi->dev, "Parent encoder object not found\n");
+ return -ENODEV;
+ }
+
+ /* Attach the next bridge in chain */
+ nwl_dsi_add_bridge(encoder, dsi->next_bridge);
+ ret = drm_bridge_attach(encoder->dev, dsi->next_bridge);
+ if (ret) {
+ DRM_DEV_ERROR(dsi->dev, "Failed to attach bridge! (%d)\n",
+ ret);
+ nwl_dsi_del_bridge(encoder, dsi->next_bridge);
+ }
+
+ return ret;
+}
+
+static void imx_nwl_dsi_bridge_detach(struct drm_bridge *bridge)
+{
+ struct imx_mipi_dsi *dsi = bridge->driver_private;
+
+ DRM_DEV_DEBUG_DRIVER(dsi->dev, "id = %s\n",
+ (dsi->instance)?"DSI1":"DSI0");
+ drm_bridge_detach(dsi->next_bridge);
+ nwl_dsi_del_bridge(dsi->next_bridge->encoder, dsi->next_bridge);
+}
+
+static const struct drm_bridge_funcs imx_nwl_dsi_bridge_funcs = {
+ .enable = imx_nwl_dsi_bridge_enable,
+ .disable = imx_nwl_dsi_bridge_disable,
+ .mode_fixup = imx_nwl_dsi_bridge_mode_fixup,
+ .attach = imx_nwl_dsi_bridge_attach,
+ .detach = imx_nwl_dsi_bridge_detach,
+};
+
+static int imx_nwl_dsi_parse_of(struct device *dev, bool as_bridge)
+{
+ struct device_node *np = dev->of_node;
+ const struct of_device_id *of_id = of_match_device(imx_nwl_dsi_dt_ids,
+ dev);
+ const struct devtype *devtype = of_id->data;
+ struct imx_mipi_dsi *dsi = dev_get_drvdata(dev);
+ struct clk *clk;
+ const char *clk_id;
+ size_t i, clk_config_sz;
+ int id;
+ u32 mux_val;
+ int ret = 0;
+
+ id = of_alias_get_id(np, "mipi_dsi");
+ if (id < 0) {
+ dev_err(dev, "No mipi_dsi alias found!");
+ return id;
+ }
+ if (id > devtype->max_instances - 1) {
+ dev_err(dev, "Too many instances! (cur: %d, max: %d)\n",
+ id, devtype->max_instances);
+ return -ENODEV;
+ }
+ dsi->instance = id;
+
+ dsi->phy = devm_phy_get(dev, "dphy");
+ if (IS_ERR(dsi->phy)) {
+ ret = PTR_ERR(dsi->phy);
+ dev_err(dev, "Could not get PHY (%d)\n", ret);
+ return ret;
+ }
+
+ /* Look for optional clocks */
+ dsi->clk_num = ARRAY_SIZE(devtype->clk_config);
+ dsi->clk_config = devm_kcalloc(dev,
+ dsi->clk_num,
+ sizeof(struct clk_config),
+ GFP_KERNEL);
+ clk_config_sz = dsi->clk_num * sizeof(struct clk_config);
+ memcpy(dsi->clk_config, devtype->clk_config, clk_config_sz);
+
+ for (i = 0; i < dsi->clk_num; i++) {
+ if (!dsi->clk_config[i].present)
+ continue;
+
+ clk_id = dsi->clk_config[i].id;
+ clk = devm_clk_get(dev, clk_id);
+ if (IS_ERR(clk)) {
+ ret = PTR_ERR(clk);
+ dev_err(dev, "Failed to get %s clock (%d)\n",
+ clk_id, ret);
+ return ret;
+ }
+ dev_dbg(dev, "Setup clk %s (rate: %lu)\n",
+ clk_id, clk_get_rate(clk));
+ dsi->clk_config[i].clk = clk;
+ }
+
+ dsi->tx_ulps_reg = devtype->tx_ulps_reg;
+ dsi->pxl2dpi_reg = devtype->pxl2dpi_reg;
+
+ of_property_read_u32(np, "sync-pol", &dsi->sync_pol);
+ of_property_read_u32(np, "pwr-delay", &dsi->power_on_delay);
+
+ /* Look for optional regmaps */
+ dsi->csr = syscon_regmap_lookup_by_phandle(np, "csr");
+ if (IS_ERR(dsi->csr) && (devtype->ext_regs & IMX_REG_CSR)) {
+ ret = PTR_ERR(dsi->csr);
+ dev_err(dev, "Failed to get CSR regmap (%d)\n", ret);
+ return ret;
+ }
+ dsi->reset = syscon_regmap_lookup_by_phandle(np, "src");
+ if (IS_ERR(dsi->reset) && (devtype->ext_regs & IMX_REG_SRC)) {
+ ret = PTR_ERR(dsi->reset);
+ dev_err(dev, "Failed to get SRC regmap (%d)\n", ret);
+ return ret;
+ }
+ dsi->mux_sel = syscon_regmap_lookup_by_phandle(np, "mux-sel");
+ if (IS_ERR(dsi->mux_sel) && (devtype->ext_regs & IMX_REG_GPR)) {
+ ret = PTR_ERR(dsi->mux_sel);
+ dev_err(dev, "Failed to get GPR regmap (%d)\n", ret);
+ return ret;
+ }
+ if (IS_ERR(dsi->mux_sel))
+ return 0;
+
+ mux_val = IMX8MQ_GPR13_MIPI_MUX_SEL;
+ if (as_bridge)
+ mux_val = 0;
+ dev_info(dev, "Using %s as input source\n",
+ (mux_val)?"DCSS":"LCDIF");
+ regmap_update_bits(dsi->mux_sel,
+ IOMUXC_GPR13,
+ IMX8MQ_GPR13_MIPI_MUX_SEL,
+ mux_val);
+
+ dsi->no_clk_reset = of_property_read_bool(np, "no_clk_reset");
+
+ return 0;
+}
+
+static int imx_nwl_dsi_bind(struct device *dev,
+ struct device *master,
+ void *data)
+{
+ struct drm_device *drm = data;
+ struct drm_bridge *next_bridge = NULL;
+ struct imx_mipi_dsi *dsi = dev_get_drvdata(dev);
+ int ret = 0;
+
+ ret = imx_nwl_dsi_parse_of(dev, false);
+ if (ret)
+ return ret;
+
+ DRM_DEV_DEBUG_DRIVER(dev, "id = %s\n", (dsi->instance)?"DSI1":"DSI0");
+
+ /* Re-validate the bridge */
+ if (dsi->next_bridge)
+ next_bridge = of_drm_find_bridge(dsi->next_bridge->of_node);
+ dsi->next_bridge = next_bridge;
+
+ if (!dsi->next_bridge) {
+ dev_warn(dev, "No bridge found, skipping encoder creation\n");
+ return ret;
+ }
+
+ ret = imx_drm_encoder_parse_of(drm, &dsi->encoder, dev->of_node);
+ if (ret)
+ return ret;
+
+ drm_encoder_helper_add(&dsi->encoder,
+ &imx_nwl_dsi_encoder_helper_funcs);
+ ret = drm_encoder_init(drm,
+ &dsi->encoder,
+ &imx_nwl_dsi_encoder_funcs,
+ DRM_MODE_ENCODER_DSI,
+ NULL);
+ if (ret) {
+ DRM_DEV_ERROR(dev, "failed to init DSI encoder (%d)\n", ret);
+ return ret;
+ }
+
+ dsi->next_bridge->encoder = &dsi->encoder;
+ dsi->encoder.bridge = dsi->next_bridge;
+ ret = drm_bridge_attach(dsi->encoder.dev, dsi->next_bridge);
+ if (ret)
+ drm_encoder_cleanup(&dsi->encoder);
+
+ return ret;
+}
+
+static void imx_nwl_dsi_unbind(struct device *dev,
+ struct device *master,
+ void *data)
+{
+ struct imx_mipi_dsi *dsi = dev_get_drvdata(dev);
+ struct drm_bridge *next_bridge = NULL;
+
+ DRM_DEV_DEBUG_DRIVER(dev, "id = %s\n", (dsi->instance)?"DSI1":"DSI0");
+
+ /*
+ * At this point, our next bridge in chain might be already removed,
+ * so update it's status.
+ */
+ if (dsi->next_bridge)
+ next_bridge = of_drm_find_bridge(dsi->next_bridge->of_node);
+ if (next_bridge)
+ drm_bridge_detach(next_bridge);
+ dsi->next_bridge = next_bridge;
+
+ if (dsi->enabled)
+ imx_nwl_dsi_encoder_disable(&dsi->encoder);
+
+ if (dsi->encoder.dev)
+ drm_encoder_cleanup(&dsi->encoder);
+
+ pm_runtime_disable(dev);
+}
+
+static const struct component_ops imx_nwl_dsi_component_ops = {
+ .bind = imx_nwl_dsi_bind,
+ .unbind = imx_nwl_dsi_unbind,
+};
+
+static int imx_nwl_dsi_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct device_node *remote_node, *endpoint;
+ int remote_ports = 0;
+ struct imx_mipi_dsi *dsi;
+ int ret = 0;
+
+ if (!np)
+ return -ENODEV;
+
+ dsi = devm_kzalloc(dev, sizeof(*dsi), GFP_KERNEL);
+ if (!dsi)
+ return -ENOMEM;
+
+ /* Search for next bridge (usually the DSI HOST bridge) */
+ endpoint = of_graph_get_next_endpoint(np, NULL);
+ while (endpoint && !dsi->next_bridge) {
+ remote_node = of_graph_get_remote_port_parent(endpoint);
+ if (!remote_node) {
+ dev_err(dev, "No endpoint found!\n");
+ return -ENODEV;
+ }
+
+ dsi->next_bridge = of_drm_find_bridge(remote_node);
+ of_node_put(remote_node);
+ endpoint = of_graph_get_next_endpoint(np, endpoint);
+ if (!of_device_is_available(remote_node))
+ continue;
+ remote_ports++;
+ };
+
+ /*
+ * Normally, we should have two remote ports: one is our input source,
+ * while the second is the NWL host bridge. This bridge can be disabled
+ * if the connector fails to find a physical device. In this case, we
+ * should continue and do nothing, so that DRM master can bind all the
+ * components.
+ */
+ if (!dsi->next_bridge && remote_ports == 2) {
+ dev_warn(dev, "Waiting for DSI host bridge\n");
+ return -EPROBE_DEFER;
+ }
+
+ dsi->dev = dev;
+ dev_set_drvdata(dev, dsi);
+
+ pm_runtime_enable(dev);
+
+ if (of_property_read_bool(dev->of_node, "as_bridge")) {
+ ret = imx_nwl_dsi_parse_of(dev, true);
+ if (ret)
+ return ret;
+ /* Create our bridge */
+ dsi->bridge.driver_private = dsi;
+ dsi->bridge.funcs = &imx_nwl_dsi_bridge_funcs;
+ dsi->bridge.of_node = np;
+
+ ret = drm_bridge_add(&dsi->bridge);
+ if (ret) {
+ dev_err(dev, "Failed to add imx-nwl-dsi bridge (%d)\n",
+ ret);
+ return ret;
+ }
+ dev_info(dev, "Added drm bridge!");
+ return 0;
+ }
+
+ return component_add(&pdev->dev, &imx_nwl_dsi_component_ops);
+}
+
+static int imx_nwl_dsi_remove(struct platform_device *pdev)
+{
+ component_del(&pdev->dev, &imx_nwl_dsi_component_ops);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int imx_nwl_suspend(struct device *dev)
+{
+ struct imx_mipi_dsi *dsi = dev_get_drvdata(dev);
+
+ if (!dsi->enabled)
+ return 0;
+
+ if (dsi->next_bridge)
+ drm_bridge_disable(dsi->next_bridge);
+ imx_nwl_dsi_disable(dsi);
+ dsi->suspended = true;
+
+ return 0;
+}
+
+static int imx_nwl_resume(struct device *dev)
+{
+ struct imx_mipi_dsi *dsi = dev_get_drvdata(dev);
+
+ if (!dsi->suspended)
+ return 0;
+
+ imx_nwl_dsi_enable(dsi);
+ if (dsi->next_bridge)
+ drm_bridge_enable(dsi->next_bridge);
+ dsi->suspended = false;
+
+ return 0;
+}
+
+#endif
+
+static const struct dev_pm_ops imx_nwl_pm_ops = {
+ SET_RUNTIME_PM_OPS(imx_nwl_suspend, imx_nwl_resume, NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(imx_nwl_suspend, imx_nwl_resume)
+};
+
+static struct platform_driver imx_nwl_dsi_driver = {
+ .probe = imx_nwl_dsi_probe,
+ .remove = imx_nwl_dsi_remove,
+ .driver = {
+ .of_match_table = imx_nwl_dsi_dt_ids,
+ .name = DRIVER_NAME,
+ .pm = &imx_nwl_pm_ops,
+ },
+};
+
+module_platform_driver(imx_nwl_dsi_driver);
+
+MODULE_AUTHOR("NXP Semiconductor");
+MODULE_DESCRIPTION("i.MX Northwest Logic MIPI-DSI driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" DRIVER_NAME);