Re: [EXT] [PATCH v3 2/2] drm/bridge: Add NWL MIPI DSI host controller support
From: Robert Chiras
Date: Thu Aug 22 2019 - 09:46:17 EST
Hi Guido,
I added my signed-off, plus some comments inline.
On Jo, 2019-08-22 at 12:44 +0200, Guido GÃnther wrote:
> This adds initial support for the NWL MIPI DSI Host controller found
> on
> i.MX8 SoCs.
>
> It adds support for the i.MX8MQ but the same IP can be found on
> e.g. the i.MX8QXP.
>
> It has been tested on the Librem 5 devkit using mxsfb.
>
> Signed-off-by: Guido GÃnther <agx@xxxxxxxxxxx>
Signed-off-by: Robert Chiras <robert.chiras@xxxxxxx>
> Co-developed-by: Robert Chiras <robert.chiras@xxxxxxx>
> ---
> Âdrivers/gpu/drm/bridge/KconfigÂÂÂÂÂÂÂÂÂÂÂ|ÂÂÂ2 +
> Âdrivers/gpu/drm/bridge/MakefileÂÂÂÂÂÂÂÂÂÂ|ÂÂÂ1 +
> Âdrivers/gpu/drm/bridge/nwl-dsi/KconfigÂÂÂ|ÂÂ16 +
> Âdrivers/gpu/drm/bridge/nwl-dsi/MakefileÂÂ|ÂÂÂ4 +
> Âdrivers/gpu/drm/bridge/nwl-dsi/nwl-drv.c | 501 ++++++++++++++++
> Âdrivers/gpu/drm/bridge/nwl-dsi/nwl-drv.h |ÂÂ65 +++
> Âdrivers/gpu/drm/bridge/nwl-dsi/nwl-dsi.c | 700
> +++++++++++++++++++++++
> Âdrivers/gpu/drm/bridge/nwl-dsi/nwl-dsi.h | 112 ++++
> Â8 files changed, 1401 insertions(+)
> Âcreate mode 100644 drivers/gpu/drm/bridge/nwl-dsi/Kconfig
> Âcreate mode 100644 drivers/gpu/drm/bridge/nwl-dsi/Makefile
> Âcreate mode 100644 drivers/gpu/drm/bridge/nwl-dsi/nwl-drv.c
> Âcreate mode 100644 drivers/gpu/drm/bridge/nwl-dsi/nwl-drv.h
> Âcreate mode 100644 drivers/gpu/drm/bridge/nwl-dsi/nwl-dsi.c
> Âcreate mode 100644 drivers/gpu/drm/bridge/nwl-dsi/nwl-dsi.h
>
> diff --git a/drivers/gpu/drm/bridge/Kconfig
> b/drivers/gpu/drm/bridge/Kconfig
> index 1cc9f502c1f2..7980b5c2156f 100644
> --- a/drivers/gpu/drm/bridge/Kconfig
> +++ b/drivers/gpu/drm/bridge/Kconfig
> @@ -154,6 +154,8 @@ source "drivers/gpu/drm/bridge/analogix/Kconfig"
>
> Âsource "drivers/gpu/drm/bridge/adv7511/Kconfig"
>
> +source "drivers/gpu/drm/bridge/nwl-dsi/Kconfig"
> +
> Âsource "drivers/gpu/drm/bridge/synopsys/Kconfig"
>
> Âendmenu
> diff --git a/drivers/gpu/drm/bridge/Makefile
> b/drivers/gpu/drm/bridge/Makefile
> index 4934fcf5a6f8..d9f6c0f77592 100644
> --- a/drivers/gpu/drm/bridge/Makefile
> +++ b/drivers/gpu/drm/bridge/Makefile
> @@ -16,4 +16,5 @@ obj-$(CONFIG_DRM_ANALOGIX_DP) += analogix/
> Âobj-$(CONFIG_DRM_I2C_ADV7511) += adv7511/
> Âobj-$(CONFIG_DRM_TI_SN65DSI86) += ti-sn65dsi86.o
> Âobj-$(CONFIG_DRM_TI_TFP410) += ti-tfp410.o
> +obj-$(CONFIG_DRM_NWL_MIPI_DSI) += nwl-dsi/
> Âobj-y += synopsys/
> diff --git a/drivers/gpu/drm/bridge/nwl-dsi/Kconfig
> b/drivers/gpu/drm/bridge/nwl-dsi/Kconfig
> new file mode 100644
> index 000000000000..3b157a9f2229
> --- /dev/null
> +++ b/drivers/gpu/drm/bridge/nwl-dsi/Kconfig
> @@ -0,0 +1,16 @@
> +config DRM_NWL_MIPI_DSI
> +ÂÂÂÂÂÂÂtristate "Support for Northwest Logic MIPI DSI Host
> controller"
> +ÂÂÂÂÂÂÂdepends on DRM
> +ÂÂÂÂÂÂÂdepends on COMMON_CLK
> +ÂÂÂÂÂÂÂdepends on OF && HAS_IOMEM
> +ÂÂÂÂÂÂÂselect DRM_KMS_HELPER
> +ÂÂÂÂÂÂÂselect DRM_MIPI_DSI
> +ÂÂÂÂÂÂÂselect DRM_PANEL_BRIDGE
> +ÂÂÂÂÂÂÂselect GENERIC_PHY_MIPI_DPHY
> +ÂÂÂÂÂÂÂselect MFD_SYSCON
> +ÂÂÂÂÂÂÂselect MULTIPLEXER
> +ÂÂÂÂÂÂÂselect REGMAP_MMIO
> +ÂÂÂÂÂÂÂhelp
> +ÂÂÂÂÂÂÂÂÂThis enables the Northwest Logic MIPI DSI Host controller
> as
> +ÂÂÂÂÂÂÂÂÂfor example found on NXP's i.MX8 Processors.
> +
> diff --git a/drivers/gpu/drm/bridge/nwl-dsi/Makefile
> b/drivers/gpu/drm/bridge/nwl-dsi/Makefile
> new file mode 100644
> index 000000000000..804baf2f1916
> --- /dev/null
> +++ b/drivers/gpu/drm/bridge/nwl-dsi/Makefile
> @@ -0,0 +1,4 @@
> +# SPDX-License-Identifier: GPL-2.0
> +nwl-mipi-dsi-y := nwl-drv.o nwl-dsi.o
> +obj-$(CONFIG_DRM_NWL_MIPI_DSI) += nwl-mipi-dsi.o
> +header-test-y += nwl-drv.h nwl-dsi.h
> diff --git a/drivers/gpu/drm/bridge/nwl-dsi/nwl-drv.c
> b/drivers/gpu/drm/bridge/nwl-dsi/nwl-drv.c
> new file mode 100644
> index 000000000000..e457438738c0
> --- /dev/null
> +++ b/drivers/gpu/drm/bridge/nwl-dsi/nwl-drv.c
> @@ -0,0 +1,501 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * i.MX8 NWL MIPI DSI host driver
> + *
> + * Copyright (C) 2017 NXP
> + * Copyright (C) 2019 Purism SPC
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/irq.h>
> +#include <linux/mfd/syscon.h>
> +#include <linux/module.h>
> +#include <linux/mux/consumer.h>
> +#include <linux/of.h>
> +#include <linux/of_platform.h>
> +#include <linux/phy/phy.h>
> +#include <linux/reset.h>
> +#include <linux/regmap.h>
> +#include <linux/sys_soc.h>
> +
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_of.h>
> +#include <drm/drm_print.h>
> +#include <drm/drm_probe_helper.h>
> +
> +#include "nwl-drv.h"
> +#include "nwl-dsi.h"
> +
> +#define DRV_NAME "nwl-dsi"
> +
> +/* Possible platform specific clocks */
> +#define NWL_DSI_CLK_COREÂÂÂÂÂÂÂ"core"
> +
> +static const struct regmap_config nwl_dsi_regmap_config = {
> +ÂÂÂÂÂÂÂ.reg_bits = 16,
> +ÂÂÂÂÂÂÂ.val_bits = 32,
> +ÂÂÂÂÂÂÂ.reg_stride = 4,
> +ÂÂÂÂÂÂÂ.max_register = NWL_DSI_IRQ_MASK2,
> +ÂÂÂÂÂÂÂ.name = DRV_NAME,
> +};
> +
> +struct nwl_dsi_platform_data {
> +ÂÂÂÂÂÂÂint (*poweron)(struct nwl_dsi *dsi);
> +ÂÂÂÂÂÂÂint (*poweroff)(struct nwl_dsi *dsi);
> +ÂÂÂÂÂÂÂint (*select_input)(struct nwl_dsi *dsi);
> +ÂÂÂÂÂÂÂint (*deselect_input)(struct nwl_dsi *dsi);
> +ÂÂÂÂÂÂÂstruct nwl_dsi_plat_clk_config
> clk_config[NWL_DSI_MAX_PLATFORM_CLOCKS];
> +};
> +
> +static inline struct nwl_dsi *bridge_to_dsi(struct drm_bridge
> *bridge)
> +{
> +ÂÂÂÂÂÂÂreturn container_of(bridge, struct nwl_dsi, bridge);
> +}
> +
> +static int nwl_dsi_set_platform_clocks(struct nwl_dsi *dsi, bool
> enable)
> +{
> +ÂÂÂÂÂÂÂstruct device *dev = dsi->dev;
> +ÂÂÂÂÂÂÂconst char *id;
> +ÂÂÂÂÂÂÂstruct clk *clk;
> +ÂÂÂÂÂÂÂsize_t i;
> +ÂÂÂÂÂÂÂunsigned long rate;
> +ÂÂÂÂÂÂÂint ret, result = 0;
> +
> +ÂÂÂÂÂÂÂDRM_DEV_DEBUG_DRIVER(dev, "%s platform clocks\n",
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂenable ? "enabling" : "disabling");
> +ÂÂÂÂÂÂÂfor (i = 0; i < ARRAY_SIZE(dsi->pdata->clk_config); i++) {
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂif (!dsi->clk_config[i].present)
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂcontinue;
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂid = dsi->clk_config[i].id;
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂclk = dsi->clk_config[i].clk;
> +
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂif (enable) {
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂret = clk_prepare_enable(clk);
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂif (ret < 0) {
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂDRM_DEV_ERROR(dev,
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ"Failed to enable %s
> clk: %d\n",
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂid, ret);
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂresult = result ?: ret;
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ}
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂrate = clk_get_rate(clk);
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂDRM_DEV_DEBUG_DRIVER(dev, "Enabled %s clk
> @%lu Hz\n",
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂid, rate);
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ} else {
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂclk_disable_unprepare(clk);
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂDRM_DEV_DEBUG_DRIVER(dev, "Disabled %s
> clk\n", id);
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ}
> +ÂÂÂÂÂÂÂ}
> +
> +ÂÂÂÂÂÂÂreturn result;
> +}
> +
> +static int nwl_dsi_plat_enable(struct nwl_dsi *dsi)
> +{
> +ÂÂÂÂÂÂÂstruct device *dev = dsi->dev;
> +ÂÂÂÂÂÂÂint ret;
> +
> +ÂÂÂÂÂÂÂif (dsi->pdata->select_input)
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂdsi->pdata->select_input(dsi);
> +
> +ÂÂÂÂÂÂÂret = nwl_dsi_set_platform_clocks(dsi, true);
> +ÂÂÂÂÂÂÂif (ret < 0)
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂreturn ret;
> +
> +ÂÂÂÂÂÂÂret = dsi->pdata->poweron(dsi);
> +ÂÂÂÂÂÂÂif (ret < 0)
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂDRM_DEV_ERROR(dev, "Failed to power on DSI: %d\n",
> ret);
> +ÂÂÂÂÂÂÂreturn ret;
> +}
> +
> +static void nwl_dsi_plat_disable(struct nwl_dsi *dsi)
> +{
> +ÂÂÂÂÂÂÂdsi->pdata->poweroff(dsi);
> +ÂÂÂÂÂÂÂnwl_dsi_set_platform_clocks(dsi, false);
> +ÂÂÂÂÂÂÂif (dsi->pdata->deselect_input)
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂdsi->pdata->deselect_input(dsi);
> +}
> +
> +static void nwl_dsi_bridge_disable(struct drm_bridge *bridge)
> +{
> +ÂÂÂÂÂÂÂstruct nwl_dsi *dsi = bridge_to_dsi(bridge);
> +
> +ÂÂÂÂÂÂÂnwl_dsi_disable(dsi);
> +ÂÂÂÂÂÂÂnwl_dsi_plat_disable(dsi);
> +ÂÂÂÂÂÂÂpm_runtime_put(dsi->dev);
> +}
> +
> +static int nwl_dsi_get_dphy_params(struct nwl_dsi *dsi,
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂconst struct drm_display_mode
> *mode,
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂunion phy_configure_opts
> *phy_opts)
> +{
> +ÂÂÂÂÂÂÂunsigned long rate;
> +ÂÂÂÂÂÂÂint ret;
> +
> +ÂÂÂÂÂÂÂif (dsi->lanes < 1 || dsi->lanes > 4)
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂreturn -EINVAL;
> +
> +ÂÂÂÂÂÂÂ/*
> +ÂÂÂÂÂÂÂÂ* So far the DPHY spec minimal timings work for both mixel
> +ÂÂÂÂÂÂÂÂ* dphy and nwl dsi host
> +ÂÂÂÂÂÂÂÂ*/
> +ÂÂÂÂÂÂÂret = phy_mipi_dphy_get_default_config(
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂmode->crtc_clock * 1000,
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂmipi_dsi_pixel_format_to_bpp(dsi->format), dsi-
> >lanes,
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ&phy_opts->mipi_dphy);
> +ÂÂÂÂÂÂÂif (ret < 0)
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂreturn ret;
> +
> +ÂÂÂÂÂÂÂrate = clk_get_rate(dsi->tx_esc_clk);
> +ÂÂÂÂÂÂÂDRM_DEV_DEBUG_DRIVER(dsi->dev, "LP clk is @%lu Hz\n", rate);
> +ÂÂÂÂÂÂÂphy_opts->mipi_dphy.lp_clk_rate = rate;
> +
> +ÂÂÂÂÂÂÂreturn 0;
> +}
> +
> +static bool nwl_dsi_bridge_mode_fixup(struct drm_bridge *bridge,
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂconst struct drm_display_mode
> *mode,
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂstruct drm_display_mode
> *adjusted_mode)
> +{
> +ÂÂÂÂÂÂÂ/* At least LCDIF + NWL needs active high sync */
> +ÂÂÂÂÂÂÂadjusted_mode->flags |= (DRM_MODE_FLAG_PHSYNC |
> DRM_MODE_FLAG_PVSYNC);
> +ÂÂÂÂÂÂÂadjusted_mode->flags &= ~(DRM_MODE_FLAG_NHSYNC |
> DRM_MODE_FLAG_NVSYNC);
> +
> +ÂÂÂÂÂÂÂreturn true;
> +}
> +
> +static enum drm_mode_status
> +nwl_dsi_bridge_mode_valid(struct drm_bridge *bridge,
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂconst struct drm_display_mode *mode)
> +{
> +ÂÂÂÂÂÂÂstruct nwl_dsi *dsi = bridge_to_dsi(bridge);
> +ÂÂÂÂÂÂÂint bpp = mipi_dsi_pixel_format_to_bpp(dsi->format);
> +
> +ÂÂÂÂÂÂÂif (mode->clock * bpp > 15000000)
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂreturn MODE_CLOCK_HIGH;
> +
> +ÂÂÂÂÂÂÂif (mode->clock * bpp < 80000)
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂreturn MODE_CLOCK_LOW;
These limits (80MBPS min and 1500MBPS max) are per lane, so you should
involve the numbers of lanes here, too. According to this formula, you
limit the maximum DSIÂthroughput to 1.5Gbps, while the maximum is 6Gbps
(1.5 * 4 lanes)
> +
> +ÂÂÂÂÂÂÂreturn MODE_OK;
> +}
> +
> +static void
> +nwl_dsi_bridge_mode_set(struct drm_bridge *bridge,
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂconst struct drm_display_mode *mode,
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂconst struct drm_display_mode *adjusted_mode)
> +{
> +ÂÂÂÂÂÂÂstruct nwl_dsi *dsi = bridge_to_dsi(bridge);
> +ÂÂÂÂÂÂÂstruct device *dev = dsi->dev;
> +ÂÂÂÂÂÂÂunion phy_configure_opts new_cfg;
> +ÂÂÂÂÂÂÂunsigned long phy_ref_rate;
> +ÂÂÂÂÂÂÂint ret;
> +
> +ÂÂÂÂÂÂÂret = nwl_dsi_get_dphy_params(dsi, adjusted_mode, &new_cfg);
> +ÂÂÂÂÂÂÂif (ret < 0)
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂreturn;
> +
> +ÂÂÂÂÂÂÂ/*
> +ÂÂÂÂÂÂÂÂ* If hs clock is unchanged, we're all good - all parameters
> are
> +ÂÂÂÂÂÂÂÂ* derived from it atm.
> +ÂÂÂÂÂÂÂÂ*/
> +ÂÂÂÂÂÂÂif (new_cfg.mipi_dphy.hs_clk_rate == dsi-
> >phy_cfg.mipi_dphy.hs_clk_rate)
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂreturn;
> +
> +ÂÂÂÂÂÂÂphy_ref_rate = clk_get_rate(dsi->phy_ref_clk);
> +ÂÂÂÂÂÂÂDRM_DEV_DEBUG_DRIVER(dev, "PHY at ref rate: %lu\n",
> phy_ref_rate);
> +ÂÂÂÂÂÂÂ/* Save the new desired phy config */
> +ÂÂÂÂÂÂÂmemcpy(&dsi->phy_cfg, &new_cfg, sizeof(new_cfg));
> +
> +ÂÂÂÂÂÂÂmemcpy(&dsi->mode, adjusted_mode, sizeof(dsi->mode));
> +ÂÂÂÂÂÂÂdrm_mode_debug_printmodeline(adjusted_mode);
> +}
> +
> +static void nwl_dsi_bridge_pre_enable(struct drm_bridge *bridge)
> +{
> +ÂÂÂÂÂÂÂstruct nwl_dsi *dsi = bridge_to_dsi(bridge);
> +
> +ÂÂÂÂÂÂÂpm_runtime_get_sync(dsi->dev);
> +ÂÂÂÂÂÂÂnwl_dsi_plat_enable(dsi);
> +ÂÂÂÂÂÂÂnwl_dsi_enable(dsi);
> +}
> +
> +static int nwl_dsi_bridge_attach(struct drm_bridge *bridge)
> +{
> +ÂÂÂÂÂÂÂstruct nwl_dsi *dsi = bridge->driver_private;
> +
> +ÂÂÂÂÂÂÂreturn drm_bridge_attach(bridge->encoder, dsi->panel_bridge,
> bridge);
> +}
> +
> +static const struct drm_bridge_funcs nwl_dsi_bridge_funcs = {
> +ÂÂÂÂÂÂÂ.pre_enable = nwl_dsi_bridge_pre_enable,
> +ÂÂÂÂÂÂÂ.disableÂÂÂÂ= nwl_dsi_bridge_disable,
> +ÂÂÂÂÂÂÂ.mode_fixup = nwl_dsi_bridge_mode_fixup,
> +ÂÂÂÂÂÂÂ.mode_setÂÂÂ= nwl_dsi_bridge_mode_set,
> +ÂÂÂÂÂÂÂ.mode_valid = nwl_dsi_bridge_mode_valid,
> +ÂÂÂÂÂÂÂ.attachÂÂÂÂÂ= nwl_dsi_bridge_attach,
> +};
> +
> +static int nwl_dsi_parse_dt(struct nwl_dsi *dsi)
> +{
> +ÂÂÂÂÂÂÂstruct platform_device *pdev = to_platform_device(dsi->dev);
> +ÂÂÂÂÂÂÂstruct clk *clk;
> +ÂÂÂÂÂÂÂconst char *clk_id;
> +ÂÂÂÂÂÂÂvoid __iomem *base;
> +ÂÂÂÂÂÂÂint i, ret;
> +
> +ÂÂÂÂÂÂÂdsi->phy = devm_phy_get(dsi->dev, "dphy");
> +ÂÂÂÂÂÂÂif (IS_ERR(dsi->phy)) {
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂret = PTR_ERR(dsi->phy);
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂif (ret != -EPROBE_DEFER)
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂDRM_DEV_ERROR(dsi->dev, "Could not get PHY:
> %d\n", ret);
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂreturn ret;
> +ÂÂÂÂÂÂÂ}
> +
> +ÂÂÂÂÂÂÂ/* Platform dependent clocks */
> +ÂÂÂÂÂÂÂmemcpy(dsi->clk_config, dsi->pdata->clk_config,
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂsizeof(dsi->pdata->clk_config));
> +
> +ÂÂÂÂÂÂÂfor (i = 0; i < ARRAY_SIZE(dsi->pdata->clk_config); i++) {
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂif (!dsi->clk_config[i].present)
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂcontinue;
> +
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂclk_id = dsi->clk_config[i].id;
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂclk = devm_clk_get(dsi->dev, clk_id);
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂif (IS_ERR(clk)) {
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂret = PTR_ERR(clk);
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂDRM_DEV_ERROR(dsi->dev, "Failed to get %s
> clock: %d\n",
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂclk_id, ret);
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂreturn ret;
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ}
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂDRM_DEV_DEBUG_DRIVER(dsi->dev, "Setup clk %s (rate:
> %lu)\n",
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂclk_id, clk_get_rate(clk));
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂdsi->clk_config[i].clk = clk;
> +ÂÂÂÂÂÂÂ}
> +
> +ÂÂÂÂÂÂÂ/* DSI clocks */
> +ÂÂÂÂÂÂÂclk = devm_clk_get(dsi->dev, "phy_ref");
> +ÂÂÂÂÂÂÂif (IS_ERR(clk)) {
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂret = PTR_ERR(clk);
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂDRM_DEV_ERROR(dsi->dev, "Failed to get phy_ref clock:
> %d\n",
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂret);
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂreturn ret;
> +ÂÂÂÂÂÂÂ}
> +ÂÂÂÂÂÂÂdsi->phy_ref_clk = clk;
> +
> +ÂÂÂÂÂÂÂclk = devm_clk_get(dsi->dev, "rx_esc");
> +ÂÂÂÂÂÂÂif (IS_ERR(clk)) {
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂret = PTR_ERR(clk);
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂDRM_DEV_ERROR(dsi->dev, "Failed to get rx_esc clock:
> %d\n",
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂret);
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂreturn ret;
> +ÂÂÂÂÂÂÂ}
> +ÂÂÂÂÂÂÂdsi->rx_esc_clk = clk;
> +
> +ÂÂÂÂÂÂÂclk = devm_clk_get(dsi->dev, "tx_esc");
> +ÂÂÂÂÂÂÂif (IS_ERR(clk)) {
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂret = PTR_ERR(clk);
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂDRM_DEV_ERROR(dsi->dev, "Failed to get tx_esc clock:
> %d\n",
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂret);
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂreturn ret;
> +ÂÂÂÂÂÂÂ}
> +ÂÂÂÂÂÂÂdsi->tx_esc_clk = clk;
> +
> +ÂÂÂÂÂÂÂdsi->mux = devm_mux_control_get(dsi->dev, NULL);
> +ÂÂÂÂÂÂÂif (IS_ERR(dsi->mux)) {
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂret = PTR_ERR(dsi->mux);
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂif (ret != -EPROBE_DEFER)
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂDRM_DEV_ERROR(dsi->dev, "Failed to get mux:
> %d\n", ret);
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂreturn ret;
> +ÂÂÂÂÂÂÂ}
> +
> +ÂÂÂÂÂÂÂbase = devm_platform_ioremap_resource(pdev, 0);
> +ÂÂÂÂÂÂÂif (IS_ERR(base))
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂreturn PTR_ERR(base);
> +
> +ÂÂÂÂÂÂÂdsi->regmap =
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂdevm_regmap_init_mmio(dsi->dev, base,
> &nwl_dsi_regmap_config);
> +ÂÂÂÂÂÂÂif (IS_ERR(dsi->regmap)) {
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂret = PTR_ERR(dsi->regmap);
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂDRM_DEV_ERROR(dsi->dev, "Failed to create NWL DSI
> regmap: %d\n",
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂret);
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂreturn ret;
> +ÂÂÂÂÂÂÂ}
> +
> +ÂÂÂÂÂÂÂdsi->irq = platform_get_irq(pdev, 0);
> +ÂÂÂÂÂÂÂif (dsi->irq < 0) {
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂDRM_DEV_ERROR(dsi->dev, "Failed to get device IRQ:
> %d\n",
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂdsi->irq);
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂreturn dsi->irq;
> +ÂÂÂÂÂÂÂ}
> +
> +ÂÂÂÂÂÂÂdsi->rstc = devm_reset_control_array_get(dsi->dev, false,
> true);
> +ÂÂÂÂÂÂÂif (IS_ERR(dsi->rstc)) {
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂDRM_DEV_ERROR(dsi->dev, "Failed to get resets:
> %ld\n",
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂPTR_ERR(dsi->rstc));
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂreturn PTR_ERR(dsi->rstc);
> +ÂÂÂÂÂÂÂ}
> +
> +ÂÂÂÂÂÂÂreturn 0;
> +}
> +
> +static int imx8mq_dsi_select_input(struct nwl_dsi *dsi)
> +{
> +ÂÂÂÂÂÂÂstruct device_node *remote;
> +ÂÂÂÂÂÂÂu32 use_dcss = 1;
> +ÂÂÂÂÂÂÂint ret;
> +
> +ÂÂÂÂÂÂÂremote = of_graph_get_remote_node(dsi->dev->of_node, 0, 0);
> +ÂÂÂÂÂÂÂif (strcmp(remote->name, "lcdif") == 0)
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂuse_dcss = 0;
> +
> +ÂÂÂÂÂÂÂDRM_DEV_INFO(dsi->dev, "Using %s as input source\n",
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ(use_dcss) ? "DCSS" : "LCDIF");
> +
> +ÂÂÂÂÂÂÂret = mux_control_try_select(dsi->mux, use_dcss);
> +ÂÂÂÂÂÂÂif (ret < 0)
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂDRM_DEV_ERROR(dsi->dev, "Failed to select input:
> %d\n", ret);
> +
> +ÂÂÂÂÂÂÂof_node_put(remote);
> +ÂÂÂÂÂÂÂreturn ret;
> +}
> +
> +
> +static int imx8mq_dsi_deselect_input(struct nwl_dsi *dsi)
> +{
> +ÂÂÂÂÂÂÂint ret;
> +
> +ÂÂÂÂÂÂÂret = mux_control_deselect(dsi->mux);
> +ÂÂÂÂÂÂÂif (ret < 0)
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂDRM_DEV_ERROR(dsi->dev, "Failed to deselect input:
> %d\n", ret);
> +
> +ÂÂÂÂÂÂÂreturn ret;
> +}
> +
> +
> +static int imx8mq_dsi_poweron(struct nwl_dsi *dsi)
> +{
> +ÂÂÂÂÂÂÂint ret = 0;
> +
> +ÂÂÂÂÂÂÂ/* otherwise the display stays blank */
> +ÂÂÂÂÂÂÂusleep_range(200, 300);
> +
> +ÂÂÂÂÂÂÂif (dsi->rstc)
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂret = reset_control_deassert(dsi->rstc);
> +
> +ÂÂÂÂÂÂÂreturn ret;
> +}
> +
> +static int imx8mq_dsi_poweroff(struct nwl_dsi *dsi)
> +{
> +ÂÂÂÂÂÂÂint ret = 0;
> +
> +ÂÂÂÂÂÂÂif (dsi->quirks & SRC_RESET_QUIRK)
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂreturn 0;
> +
> +ÂÂÂÂÂÂÂif (dsi->rstc)
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂret = reset_control_assert(dsi->rstc);
> +ÂÂÂÂÂÂÂreturn ret;
> +}
> +
> +static const struct drm_bridge_timings nwl_dsi_timings = {
> +ÂÂÂÂÂÂÂ.input_bus_flags = DRM_BUS_FLAG_DE_LOW,
> +};
> +
> +static const struct nwl_dsi_platform_data imx8mq_dev = {
> +ÂÂÂÂÂÂÂ.poweron = &imx8mq_dsi_poweron,
> +ÂÂÂÂÂÂÂ.poweroff = &imx8mq_dsi_poweroff,
> +ÂÂÂÂÂÂÂ.select_input = &imx8mq_dsi_select_input,
> +ÂÂÂÂÂÂÂ.deselect_input = &imx8mq_dsi_deselect_input,
> +ÂÂÂÂÂÂÂ.clk_config = {
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ{ .id = NWL_DSI_CLK_CORE, .present = true },
> +ÂÂÂÂÂÂÂ},
> +};
> +
> +static const struct of_device_id nwl_dsi_dt_ids[] = {
> +ÂÂÂÂÂÂÂ{ .compatible = "fsl,imx8mq-nwl-dsi", .data = &imx8mq_dev, },
> +ÂÂÂÂÂÂÂ{ /* sentinel */ }
> +};
> +MODULE_DEVICE_TABLE(of, nwl_dsi_dt_ids);
> +
> +static const struct soc_device_attribute nwl_dsi_quirks_match[] = {
> +ÂÂÂÂÂÂÂ{ .soc_id = "i.MX8MQ", .revision = "2.0",
> +ÂÂÂÂÂÂÂÂÂ.data = (void *)(E11418_HS_MODE_QUIRK | SRC_RESET_QUIRK) },
> +ÂÂÂÂÂÂÂ{ /* sentinel. */ },
> +};
> +
> +static int nwl_dsi_probe(struct platform_device *pdev)
> +{
> +ÂÂÂÂÂÂÂstruct device *dev = &pdev->dev;
> +ÂÂÂÂÂÂÂconst struct of_device_id *of_id =
> of_match_device(nwl_dsi_dt_ids, dev);
> +ÂÂÂÂÂÂÂconst struct nwl_dsi_platform_data *pdata = of_id->data;
> +ÂÂÂÂÂÂÂconst struct soc_device_attribute *attr;
> +ÂÂÂÂÂÂÂstruct nwl_dsi *dsi;
> +ÂÂÂÂÂÂÂint ret;
> +
> +ÂÂÂÂÂÂÂdsi = devm_kzalloc(dev, sizeof(*dsi), GFP_KERNEL);
> +ÂÂÂÂÂÂÂif (!dsi)
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂreturn -ENOMEM;
> +
> +ÂÂÂÂÂÂÂdsi->dev = dev;
> +ÂÂÂÂÂÂÂdsi->pdata = pdata;
> +
> +ÂÂÂÂÂÂÂret = nwl_dsi_parse_dt(dsi);
> +ÂÂÂÂÂÂÂif (ret)
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂreturn ret;
> +
> +ÂÂÂÂÂÂÂret = devm_request_irq(dev, dsi->irq, nwl_dsi_irq_handler, 0,
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂdev_name(dev), dsi);
> +ÂÂÂÂÂÂÂif (ret < 0) {
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂDRM_DEV_ERROR(dev, "Failed to request IRQ %d: %d\n",
> dsi->irq,
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂret);
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂreturn ret;
> +ÂÂÂÂÂÂÂ}
> +
> +ÂÂÂÂÂÂÂdsi->dsi_host.ops = &nwl_dsi_host_ops;
> +ÂÂÂÂÂÂÂdsi->dsi_host.dev = dev;
> +ÂÂÂÂÂÂÂret = mipi_dsi_host_register(&dsi->dsi_host);
> +ÂÂÂÂÂÂÂif (ret) {
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂDRM_DEV_ERROR(dev, "Failed to register MIPI host:
> %d\n", ret);
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂreturn ret;
> +ÂÂÂÂÂÂÂ}
> +
> +ÂÂÂÂÂÂÂattr = soc_device_match(nwl_dsi_quirks_match);
> +ÂÂÂÂÂÂÂif (attr)
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂdsi->quirks = (uintptr_t)attr->data;
> +
> +ÂÂÂÂÂÂÂdsi->bridge.driver_private = dsi;
> +ÂÂÂÂÂÂÂdsi->bridge.funcs = &nwl_dsi_bridge_funcs;
> +ÂÂÂÂÂÂÂdsi->bridge.of_node = dev->of_node;
> +ÂÂÂÂÂÂÂdsi->bridge.timings = &nwl_dsi_timings;
> +
> +ÂÂÂÂÂÂÂdrm_bridge_add(&dsi->bridge);
> +
> +ÂÂÂÂÂÂÂdev_set_drvdata(dev, dsi);
> +ÂÂÂÂÂÂÂpm_runtime_enable(dev);
> +ÂÂÂÂÂÂÂreturn 0;
> +}
> +
> +static int nwl_dsi_remove(struct platform_device *pdev)
> +{
> +ÂÂÂÂÂÂÂstruct nwl_dsi *dsi = platform_get_drvdata(pdev);
> +
> +ÂÂÂÂÂÂÂmipi_dsi_host_unregister(&dsi->dsi_host);
> +ÂÂÂÂÂÂÂpm_runtime_disable(&pdev->dev);
> +ÂÂÂÂÂÂÂreturn 0;
> +}
> +
> +static struct platform_driver nwl_dsi_driver = {
> +ÂÂÂÂÂÂÂ.probeÂÂÂÂÂÂÂÂÂÂ= nwl_dsi_probe,
> +ÂÂÂÂÂÂÂ.removeÂÂÂÂÂÂÂÂÂ= nwl_dsi_remove,
> +ÂÂÂÂÂÂÂ.driverÂÂÂÂÂÂÂÂÂ= {
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ.of_match_table = nwl_dsi_dt_ids,
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ.nameÂÂÂ= DRV_NAME,
> +ÂÂÂÂÂÂÂ},
> +};
> +
> +module_platform_driver(nwl_dsi_driver);
> +
> +MODULE_AUTHOR("NXP Semiconductor");
> +MODULE_AUTHOR("Purism SPC");
> +MODULE_DESCRIPTION("Northwest Logic MIPI-DSI driver");
> +MODULE_LICENSE("GPL"); /* GPLv2 or later */
> diff --git a/drivers/gpu/drm/bridge/nwl-dsi/nwl-drv.h
> b/drivers/gpu/drm/bridge/nwl-dsi/nwl-drv.h
> new file mode 100644
> index 000000000000..1e72a9221401
> --- /dev/null
> +++ b/drivers/gpu/drm/bridge/nwl-dsi/nwl-drv.h
> @@ -0,0 +1,65 @@
> +/* SPDX-License-Identifier: GPL-2.0+ */
> +/*
> + * NWL MIPI DSI host driver
> + *
> + * Copyright (C) 2017 NXP
> + * Copyright (C) 2019 Purism SPC
> + */
> +
> +#ifndef __NWL_DRV_H__
> +#define __NWL_DRV_H__
> +
> +#include <linux/mux/consumer.h>
> +#include <linux/phy/phy.h>
> +
> +#include <drm/drm_bridge.h>
> +#include <drm/drm_mipi_dsi.h>
> +
> +struct nwl_dsi_platform_data;
> +
> +/* i.MX8 NWL quirks */
> +/* i.MX8MQ errata E11418 */
> +#define E11418_HS_MODE_QUIRKÂÂÂÂÂÂÂBIT(0)
> +/* Skip DSI bits in SRC on disable to avoid blank display on enable
> */
> +#define SRC_RESET_QUIRKÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂBIT(1)
> +
> +#define NWL_DSI_MAX_PLATFORM_CLOCKS 1
> +struct nwl_dsi_plat_clk_config {
> +ÂÂÂÂÂÂÂconst char *id;
> +ÂÂÂÂÂÂÂstruct clk *clk;
> +ÂÂÂÂÂÂÂbool present;
> +};
> +
> +struct nwl_dsi {
> +ÂÂÂÂÂÂÂstruct drm_bridge bridge;
> +ÂÂÂÂÂÂÂstruct mipi_dsi_host dsi_host;
> +ÂÂÂÂÂÂÂstruct drm_bridge *panel_bridge;
> +ÂÂÂÂÂÂÂstruct device *dev;
> +ÂÂÂÂÂÂÂstruct phy *phy;
> +ÂÂÂÂÂÂÂunion phy_configure_opts phy_cfg;
> +ÂÂÂÂÂÂÂunsigned int quirks;
> +
> +ÂÂÂÂÂÂÂstruct regmap *regmap;
> +ÂÂÂÂÂÂÂint irq;
> +ÂÂÂÂÂÂÂstruct reset_control *rstc;
> +ÂÂÂÂÂÂÂstruct mux_control *mux;
> +
> +ÂÂÂÂÂÂÂ/* DSI clocks */
> +ÂÂÂÂÂÂÂstruct clk *phy_ref_clk;
> +ÂÂÂÂÂÂÂstruct clk *rx_esc_clk;
> +ÂÂÂÂÂÂÂstruct clk *tx_esc_clk;
> +ÂÂÂÂÂÂÂ/* Platform dependent clocks */
> +ÂÂÂÂÂÂÂstruct nwl_dsi_plat_clk_config
> clk_config[NWL_DSI_MAX_PLATFORM_CLOCKS];
> +
> +ÂÂÂÂÂÂÂ/* dsi lanes */
> +ÂÂÂÂÂÂÂu32 lanes;
> +ÂÂÂÂÂÂÂenum mipi_dsi_pixel_format format;
> +ÂÂÂÂÂÂÂstruct drm_display_mode mode;
> +ÂÂÂÂÂÂÂunsigned long dsi_mode_flags;
> +
> +ÂÂÂÂÂÂÂstruct nwl_dsi_transfer *xfer;
> +
> +ÂÂÂÂÂÂÂconst struct nwl_dsi_platform_data *pdata;
> +};
> +
> +#endif /* __NWL_DRV_H__ */
> diff --git a/drivers/gpu/drm/bridge/nwl-dsi/nwl-dsi.c
> b/drivers/gpu/drm/bridge/nwl-dsi/nwl-dsi.c
> new file mode 100644
> index 000000000000..fd030af55bb4
> --- /dev/null
> +++ b/drivers/gpu/drm/bridge/nwl-dsi/nwl-dsi.c
> @@ -0,0 +1,700 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * NWL MIPI DSI host driver
> + *
> + * Copyright (C) 2017 NXP
> + * Copyright (C) 2019 Purism SPC
> + */
> +
> +#include <linux/bitfield.h>
> +#include <linux/clk.h>
> +#include <linux/irq.h>
> +#include <linux/regmap.h>
> +#include <linux/time64.h>
> +
> +#include <video/mipi_display.h>
> +#include <video/videomode.h>
> +
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_crtc_helper.h>
> +#include <drm/drm_of.h>
> +#include <drm/drm_panel.h>
> +#include <drm/drm_print.h>
> +
> +#include "nwl-drv.h"
> +#include "nwl-dsi.h"
> +
> +#define NWL_DSI_MIPI_FIFO_TIMEOUT msecs_to_jiffies(500)
> +
> +/*
> + * PKT_CONTROL format:
> + * [15: 0] - word count
> + * [17:16] - virtual channel
> + * [23:18] - data type
> + * [24]ÂÂÂÂÂÂÂÂÂÂÂ- LP or HS select (0 - LP, 1 - HS)
> + * [25]ÂÂÂÂÂÂÂÂÂÂÂ- perform BTA after packet is sent
> + * [26]ÂÂÂÂÂÂÂÂÂÂÂ- perform BTA only, no packet tx
> + */
> +#define NWL_DSI_WC(x)ÂÂÂÂÂÂÂÂÂÂFIELD_PREP(GENMASK(15, 0), (x))
> +#define NWL_DSI_TX_VC(x)ÂÂÂÂÂÂÂFIELD_PREP(GENMASK(17, 16), (x))
> +#define NWL_DSI_TX_DT(x)ÂÂÂÂÂÂÂFIELD_PREP(GENMASK(23, 18), (x))
> +#define NWL_DSI_HS_SEL(x)ÂÂÂÂÂÂFIELD_PREP(GENMASK(24, 24), (x))
> +#define NWL_DSI_BTA_TX(x)ÂÂÂÂÂÂFIELD_PREP(GENMASK(25, 25), (x))
> +#define NWL_DSI_BTA_NO_TX(x)ÂÂÂFIELD_PREP(GENMASK(26, 26), (x))
> +
> +/*
> + * RX_PKT_HEADER format:
> + * [15: 0] - word count
> + * [21:16] - data type
> + * [23:22] - virtual channel
> + */
> +#define NWL_DSI_RX_DT(x)ÂÂÂÂÂÂÂFIELD_GET(GENMASK(21, 16), (x))
> +#define NWL_DSI_RX_VC(x)ÂÂÂÂÂÂÂFIELD_GET(GENMASK(23, 22), (x))
> +
> +/* DSI Video mode */
> +#define NWL_DSI_VM_BURST_MODE_WITH_SYNC_PULSESÂÂÂÂÂÂÂÂÂ0
> +#define NWL_DSI_VM_NON_BURST_MODE_WITH_SYNC_EVENTSÂÂÂÂÂBIT(0)
> +#define NWL_DSI_VM_BURST_MODEÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂBIT(1)
> +
> +/* * DPI color coding */
> +#define NWL_DSI_DPI_16_BIT_565_PACKEDÂÂ0
> +#define NWL_DSI_DPI_16_BIT_565_ALIGNED 1
> +#define NWL_DSI_DPI_16_BIT_565_SHIFTED 2
> +#define NWL_DSI_DPI_18_BIT_PACKEDÂÂÂÂÂÂ3
> +#define NWL_DSI_DPI_18_BIT_ALIGNEDÂÂÂÂÂ4
> +#define NWL_DSI_DPI_24_BITÂÂÂÂÂÂÂÂÂÂÂÂÂ5
> +
> +/* * DPI Pixel format */
> +#define NWL_DSI_PIXEL_FORMAT_16ÂÂ0
> +#define NWL_DSI_PIXEL_FORMAT_18ÂÂBIT(0)
> +#define NWL_DSI_PIXEL_FORMAT_18L BIT(1)
> +#define NWL_DSI_PIXEL_FORMAT_24ÂÂ(BIT(0) | BIT(1))
> +
> +enum transfer_direction {
> +ÂÂÂÂÂÂÂDSI_PACKET_SEND,
> +ÂÂÂÂÂÂÂDSI_PACKET_RECEIVE,
> +};
> +
> +struct nwl_dsi_transfer {
> +ÂÂÂÂÂÂÂconst struct mipi_dsi_msg *msg;
> +ÂÂÂÂÂÂÂstruct mipi_dsi_packet packet;
> +ÂÂÂÂÂÂÂstruct completion completed;
> +
> +ÂÂÂÂÂÂÂint status; /* status of transmission */
> +ÂÂÂÂÂÂÂenum transfer_direction direction;
> +ÂÂÂÂÂÂÂbool need_bta;
> +ÂÂÂÂÂÂÂu8 cmd;
> +ÂÂÂÂÂÂÂu16 rx_word_count;
> +ÂÂÂÂÂÂÂsize_t tx_len; /* in bytes */
> +ÂÂÂÂÂÂÂsize_t rx_len; /* in bytes */
> +};
> +
> +static int nwl_dsi_write(struct nwl_dsi *dsi, unsigned int reg, u32
> val)
> +{
> +ÂÂÂÂÂÂÂint ret;
> +
> +ÂÂÂÂÂÂÂret = regmap_write(dsi->regmap, reg, val);
> +ÂÂÂÂÂÂÂif (ret < 0)
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂDRM_DEV_ERROR(dsi->dev,
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ"Failed to write NWL DSI reg 0x%x:
> %d\n", reg,
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂret);
> +ÂÂÂÂÂÂÂreturn ret;
> +}
> +
> +static u32 nwl_dsi_read(struct nwl_dsi *dsi, u32 reg)
> +{
> +ÂÂÂÂÂÂÂunsigned int val;
> +ÂÂÂÂÂÂÂint ret;
> +
> +ÂÂÂÂÂÂÂret = regmap_read(dsi->regmap, reg, &val);
> +ÂÂÂÂÂÂÂif (ret < 0)
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂDRM_DEV_ERROR(dsi->dev, "Failed to read NWL DSI reg
> 0x%x: %d\n",
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂreg, ret);
> +
> +ÂÂÂÂÂÂÂreturn val;
> +}
> +
> +static u32 nwl_dsi_get_dpi_pixel_format(enum mipi_dsi_pixel_format
> format)
> +{
> +ÂÂÂÂÂÂÂswitch (format) {
> +ÂÂÂÂÂÂÂcase MIPI_DSI_FMT_RGB565:
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂreturn NWL_DSI_PIXEL_FORMAT_16;
> +ÂÂÂÂÂÂÂcase MIPI_DSI_FMT_RGB666:
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂreturn NWL_DSI_PIXEL_FORMAT_18L;
> +ÂÂÂÂÂÂÂcase MIPI_DSI_FMT_RGB666_PACKED:
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂreturn NWL_DSI_PIXEL_FORMAT_18;
> +ÂÂÂÂÂÂÂcase MIPI_DSI_FMT_RGB888:
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂreturn NWL_DSI_PIXEL_FORMAT_24;
> +ÂÂÂÂÂÂÂdefault:
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂreturn -EINVAL;
> +ÂÂÂÂÂÂÂ}
> +}
> +
> +#define PSEC_PER_SEC 1000000000000LL
> +/*
> + * ps2bc - Picoseconds to byte clock cycles
> + */
> +static u32 ps2bc(struct nwl_dsi *dsi, unsigned long long ps)
> +{
> +ÂÂÂÂÂÂÂint bpp = mipi_dsi_pixel_format_to_bpp(dsi->format);
> +
> +ÂÂÂÂÂÂÂreturn DIV_ROUND_UP(ps * dsi->mode.clock * 1000 * bpp,
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂdsi->lanes * 8 * PSEC_PER_SEC);
> +}
> +
> +/*
> + * ui2bc - UI time periods to byte clock cycles
> + */
> +static u32 ui2bc(struct nwl_dsi *dsi, unsigned long long ui)
> +{
> +ÂÂÂÂÂÂÂint bpp = mipi_dsi_pixel_format_to_bpp(dsi->format);
> +
> +ÂÂÂÂÂÂÂreturn DIV_ROUND_UP(ui * dsi->lanes, dsi->mode.clock * 1000 *
> bpp);
> +}
> +
> +/*
> + * us2bc - micro seconds to lp clock cycles
> + */
> +static u32 us2lp(u32 lp_clk_rate, unsigned long us)
> +{
> +ÂÂÂÂÂÂÂreturn DIV_ROUND_UP(us * lp_clk_rate, USEC_PER_SEC);
> +}
> +
> +static int nwl_dsi_config_host(struct nwl_dsi *dsi)
> +{
> +ÂÂÂÂÂÂÂu32 cycles;
> +ÂÂÂÂÂÂÂstruct phy_configure_opts_mipi_dphy *cfg = &dsi-
> >phy_cfg.mipi_dphy;
> +
> +ÂÂÂÂÂÂÂif (dsi->lanes < 1 || dsi->lanes > 4)
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂreturn -EINVAL;
> +
> +ÂÂÂÂÂÂÂDRM_DEV_DEBUG_DRIVER(dsi->dev, "DSI Lanes %d\n", dsi->lanes);
> +ÂÂÂÂÂÂÂnwl_dsi_write(dsi, NWL_DSI_CFG_NUM_LANES, dsi->lanes - 1);
> +
> +ÂÂÂÂÂÂÂif (dsi->dsi_mode_flags & MIPI_DSI_CLOCK_NON_CONTINUOUS) {
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂnwl_dsi_write(dsi, NWL_DSI_CFG_NONCONTINUOUS_CLK,
> 0x01);
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂnwl_dsi_write(dsi, NWL_DSI_CFG_AUTOINSERT_EOTP,
> 0x01);
> +ÂÂÂÂÂÂÂ} else {
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂnwl_dsi_write(dsi, NWL_DSI_CFG_NONCONTINUOUS_CLK,
> 0x00);
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂnwl_dsi_write(dsi, NWL_DSI_CFG_AUTOINSERT_EOTP,
> 0x00);
> +ÂÂÂÂÂÂÂ}
> +
> +ÂÂÂÂÂÂÂ/* values in byte clock cycles */
> +ÂÂÂÂÂÂÂcycles = ui2bc(dsi, cfg->clk_pre);
> +ÂÂÂÂÂÂÂDRM_DEV_DEBUG_DRIVER(dsi->dev, "cfg_t_pre: 0x%x\n", cycles);
> +ÂÂÂÂÂÂÂnwl_dsi_write(dsi, NWL_DSI_CFG_T_PRE, cycles);
> +ÂÂÂÂÂÂÂcycles = ps2bc(dsi, cfg->lpx + cfg->clk_prepare + cfg-
> >clk_zero);
> +ÂÂÂÂÂÂÂDRM_DEV_DEBUG_DRIVER(dsi->dev, "cfg_tx_gap (pre): 0x%x\n",
> cycles);
> +ÂÂÂÂÂÂÂcycles += ui2bc(dsi, cfg->clk_pre);
> +ÂÂÂÂÂÂÂDRM_DEV_DEBUG_DRIVER(dsi->dev, "cfg_tx_gap: 0x%x\n", cycles);
> +ÂÂÂÂÂÂÂnwl_dsi_write(dsi, NWL_DSI_CFG_T_POST, cycles);
> +ÂÂÂÂÂÂÂcycles = ps2bc(dsi, cfg->hs_exit);
> +ÂÂÂÂÂÂÂDRM_DEV_DEBUG_DRIVER(dsi->dev, "cfg_tx_gap: 0x%x\n", cycles);
> +ÂÂÂÂÂÂÂnwl_dsi_write(dsi, NWL_DSI_CFG_TX_GAP, cycles);
> +
> +ÂÂÂÂÂÂÂnwl_dsi_write(dsi, NWL_DSI_CFG_EXTRA_CMDS_AFTER_EOTP, 0x01);
> +ÂÂÂÂÂÂÂnwl_dsi_write(dsi, NWL_DSI_CFG_HTX_TO_COUNT, 0x00);
> +ÂÂÂÂÂÂÂnwl_dsi_write(dsi, NWL_DSI_CFG_LRX_H_TO_COUNT, 0x00);
> +ÂÂÂÂÂÂÂnwl_dsi_write(dsi, NWL_DSI_CFG_BTA_H_TO_COUNT, 0x00);
> +ÂÂÂÂÂÂÂ/* In LP clock cycles */
> +ÂÂÂÂÂÂÂcycles = us2lp(cfg->lp_clk_rate, cfg->wakeup);
> +ÂÂÂÂÂÂÂDRM_DEV_DEBUG_DRIVER(dsi->dev, "cfg_twakeup: 0x%x\n",
> cycles);
> +ÂÂÂÂÂÂÂnwl_dsi_write(dsi, NWL_DSI_CFG_TWAKEUP, cycles);
> +
> +ÂÂÂÂÂÂÂreturn 0;
> +}
> +
> +static int nwl_dsi_config_dpi(struct nwl_dsi *dsi)
> +{
> +ÂÂÂÂÂÂÂu32 color_format, mode;
> +ÂÂÂÂÂÂÂbool burst_mode;
> +ÂÂÂÂÂÂÂint hfront_porch, hback_porch, vfront_porch, vback_porch;
> +ÂÂÂÂÂÂÂint hsync_len, vsync_len;
> +
> +ÂÂÂÂÂÂÂhfront_porch = dsi->mode.hsync_start - dsi->mode.hdisplay;
> +ÂÂÂÂÂÂÂhsync_len = dsi->mode.hsync_end - dsi->mode.hsync_start;
> +ÂÂÂÂÂÂÂhback_porch = dsi->mode.htotal - dsi->mode.hsync_end;
> +
> +ÂÂÂÂÂÂÂvfront_porch = dsi->mode.vsync_start - dsi->mode.vdisplay;
> +ÂÂÂÂÂÂÂvsync_len = dsi->mode.vsync_end - dsi->mode.vsync_start;
> +ÂÂÂÂÂÂÂvback_porch = dsi->mode.vtotal - dsi->mode.vsync_end;
> +
> +ÂÂÂÂÂÂÂDRM_DEV_DEBUG_DRIVER(dsi->dev, "hfront_porch = %d\n",
> hfront_porch);
> +ÂÂÂÂÂÂÂDRM_DEV_DEBUG_DRIVER(dsi->dev, "hback_porch = %d\n",
> hback_porch);
> +ÂÂÂÂÂÂÂDRM_DEV_DEBUG_DRIVER(dsi->dev, "hsync_len = %d\n",
> hsync_len);
> +ÂÂÂÂÂÂÂDRM_DEV_DEBUG_DRIVER(dsi->dev, "hdisplay = %d\n", dsi-
> >mode.hdisplay);
> +ÂÂÂÂÂÂÂDRM_DEV_DEBUG_DRIVER(dsi->dev, "vfront_porch = %d\n",
> vfront_porch);
> +ÂÂÂÂÂÂÂDRM_DEV_DEBUG_DRIVER(dsi->dev, "vback_porch = %d\n",
> vback_porch);
> +ÂÂÂÂÂÂÂDRM_DEV_DEBUG_DRIVER(dsi->dev, "vsync_len = %d\n",
> vsync_len);
> +ÂÂÂÂÂÂÂDRM_DEV_DEBUG_DRIVER(dsi->dev, "vactive = %d\n", dsi-
> >mode.vdisplay);
> +ÂÂÂÂÂÂÂDRM_DEV_DEBUG_DRIVER(dsi->dev, "clock = %d kHz\n", dsi-
> >mode.clock);
> +
> +ÂÂÂÂÂÂÂcolor_format = nwl_dsi_get_dpi_pixel_format(dsi->format);
> +ÂÂÂÂÂÂÂif (color_format < 0) {
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂDRM_DEV_ERROR(dsi->dev, "Invalid color format
> 0x%x\n",
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂdsi->format);
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂreturn color_format;
> +ÂÂÂÂÂÂÂ}
> +ÂÂÂÂÂÂÂDRM_DEV_DEBUG_DRIVER(dsi->dev, "pixel fmt = %d\n", dsi-
> >format);
> +
> +ÂÂÂÂÂÂÂnwl_dsi_write(dsi, NWL_DSI_INTERFACE_COLOR_CODING,
> NWL_DSI_DPI_24_BIT);
> +ÂÂÂÂÂÂÂnwl_dsi_write(dsi, NWL_DSI_PIXEL_FORMAT, color_format);
> +ÂÂÂÂÂÂÂ/*
> +ÂÂÂÂÂÂÂÂ* Adjusting input polarity based on the video mode results
> in
> +ÂÂÂÂÂÂÂÂ* a black screen so always pick active low:
> +ÂÂÂÂÂÂÂÂ*/
> +ÂÂÂÂÂÂÂnwl_dsi_write(dsi, NWL_DSI_VSYNC_POLARITY,
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂNWL_DSI_VSYNC_POLARITY_ACTIVE_LOW);
> +ÂÂÂÂÂÂÂnwl_dsi_write(dsi, NWL_DSI_HSYNC_POLARITY,
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂNWL_DSI_HSYNC_POLARITY_ACTIVE_LOW);
> +
> +ÂÂÂÂÂÂÂburst_mode = (dsi->dsi_mode_flags &
> MIPI_DSI_MODE_VIDEO_BURST) &&
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ!(dsi->dsi_mode_flags &
> MIPI_DSI_MODE_VIDEO_SYNC_PULSE);
> +
> +ÂÂÂÂÂÂÂif (burst_mode) {
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂnwl_dsi_write(dsi, NWL_DSI_VIDEO_MODE,
> NWL_DSI_VM_BURST_MODE);
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂnwl_dsi_write(dsi, NWL_DSI_PIXEL_FIFO_SEND_LEVEL,
> 256);
> +ÂÂÂÂÂÂÂ} else {
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂmode = ((dsi->dsi_mode_flags &
> MIPI_DSI_MODE_VIDEO_SYNC_PULSE) ?
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂNWL_DSI_VM_BURST_MODE_WITH_SYNC_PULSE
> S :
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂNWL_DSI_VM_NON_BURST_MODE_WITH_SYNC_E
> VENTS);
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂnwl_dsi_write(dsi, NWL_DSI_VIDEO_MODE, mode);
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂnwl_dsi_write(dsi, NWL_DSI_PIXEL_FIFO_SEND_LEVEL,
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂdsi->mode.hdisplay);
> +ÂÂÂÂÂÂÂ}
> +
> +ÂÂÂÂÂÂÂnwl_dsi_write(dsi, NWL_DSI_HFP, hfront_porch);
> +ÂÂÂÂÂÂÂnwl_dsi_write(dsi, NWL_DSI_HBP, hback_porch);
> +ÂÂÂÂÂÂÂnwl_dsi_write(dsi, NWL_DSI_HSA, hsync_len);
> +
> +ÂÂÂÂÂÂÂnwl_dsi_write(dsi, NWL_DSI_ENABLE_MULT_PKTS, 0x0);
> +ÂÂÂÂÂÂÂnwl_dsi_write(dsi, NWL_DSI_BLLP_MODE, 0x1);
> +ÂÂÂÂÂÂÂnwl_dsi_write(dsi, NWL_DSI_ENABLE_MULT_PKTS, 0x0);
NWL_DSI_ENABLE_MULT_PKTS is written twice, by mistake, so remove this
line. I did the same mistake on NXP tree, I think you got it from there
:)
> +ÂÂÂÂÂÂÂnwl_dsi_write(dsi, NWL_DSI_USE_NULL_PKT_BLLP, 0x0);
> +ÂÂÂÂÂÂÂnwl_dsi_write(dsi, NWL_DSI_VC, 0x0);
> +
> +ÂÂÂÂÂÂÂnwl_dsi_write(dsi, NWL_DSI_PIXEL_PAYLOAD_SIZE, dsi-
> >mode.hdisplay);
> +ÂÂÂÂÂÂÂnwl_dsi_write(dsi, NWL_DSI_VACTIVE, dsi->mode.vdisplay - 1);
VACTIVE shold contain the number of lines in the vertical area. "-1"
subtraction was actually wrong.
> +ÂÂÂÂÂÂÂnwl_dsi_write(dsi, NWL_DSI_VBP, vback_porch);
> +ÂÂÂÂÂÂÂnwl_dsi_write(dsi, NWL_DSI_VFP, vfront_porch);
> +
> +ÂÂÂÂÂÂÂreturn 0;
> +}
> +
> +static void nwl_dsi_init_interrupts(struct nwl_dsi *dsi)
> +{
> +ÂÂÂÂÂÂÂu32 irq_enable;
> +
> +ÂÂÂÂÂÂÂnwl_dsi_write(dsi, NWL_DSI_IRQ_MASK, 0xffffffff);
> +ÂÂÂÂÂÂÂnwl_dsi_write(dsi, NWL_DSI_IRQ_MASK2, 0x7);
> +
> +ÂÂÂÂÂÂÂirq_enable = ~(u32)(NWL_DSI_TX_PKT_DONE_MASK |
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂNWL_DSI_RX_PKT_HDR_RCVD_MASK |
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂNWL_DSI_TX_FIFO_OVFLW_MASK |
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂNWL_DSI_HS_TX_TIMEOUT_MASK);
> +
> +ÂÂÂÂÂÂÂnwl_dsi_write(dsi, NWL_DSI_IRQ_MASK, irq_enable);
> +}
> +
> +static int nwl_dsi_host_attach(struct mipi_dsi_host *dsi_host,
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂstruct mipi_dsi_device *device)
> +{
> +ÂÂÂÂÂÂÂstruct nwl_dsi *dsi = container_of(dsi_host, struct nwl_dsi,
> dsi_host);
> +ÂÂÂÂÂÂÂstruct device *dev = dsi->dev;
> +ÂÂÂÂÂÂÂstruct drm_bridge *bridge;
> +ÂÂÂÂÂÂÂstruct drm_panel *panel;
> +ÂÂÂÂÂÂÂint ret;
> +
> +ÂÂÂÂÂÂÂDRM_DEV_INFO(dev, "lanes=%u, format=0x%x flags=0x%lx\n",
> device->lanes,
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂdevice->format, device->mode_flags);
> +
> +ÂÂÂÂÂÂÂif (device->lanes < 1 || device->lanes > 4)
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂreturn -EINVAL;
> +
> +ÂÂÂÂÂÂÂdsi->lanes = device->lanes;
> +ÂÂÂÂÂÂÂdsi->format = device->format;
> +ÂÂÂÂÂÂÂdsi->dsi_mode_flags = device->mode_flags;
> +
> +ÂÂÂÂÂÂÂret = drm_of_find_panel_or_bridge(dsi->dev->of_node, 1, 0,
> &panel,
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ&bridge);
> +ÂÂÂÂÂÂÂif (ret)
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂreturn ret;
> +
> +ÂÂÂÂÂÂÂif (panel) {
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂbridge = drm_panel_bridge_add(panel,
> DRM_MODE_CONNECTOR_DSI);
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂif (IS_ERR(bridge))
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂreturn PTR_ERR(bridge);
> +ÂÂÂÂÂÂÂ}
> +
> +ÂÂÂÂÂÂÂdsi->panel_bridge = bridge;
> +ÂÂÂÂÂÂÂdrm_bridge_add(&dsi->bridge);
> +
> +ÂÂÂÂÂÂÂreturn 0;
> +}
> +
> +static int nwl_dsi_host_detach(struct mipi_dsi_host *dsi_host,
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂstruct mipi_dsi_device *device)
> +{
> +ÂÂÂÂÂÂÂstruct nwl_dsi *dsi = container_of(dsi_host, struct nwl_dsi,
> dsi_host);
> +
> +ÂÂÂÂÂÂÂdrm_of_panel_bridge_remove(dsi->dev->of_node, 1, 0);
> +ÂÂÂÂÂÂÂdrm_bridge_remove(&dsi->bridge);
> +
> +ÂÂÂÂÂÂÂreturn 0;
> +}
> +
> +static bool nwl_dsi_read_packet(struct nwl_dsi *dsi, u32 status)
> +{
> +ÂÂÂÂÂÂÂstruct device *dev = dsi->dev;
> +ÂÂÂÂÂÂÂstruct nwl_dsi_transfer *xfer = dsi->xfer;
> +ÂÂÂÂÂÂÂu8 *payload = xfer->msg->rx_buf;
> +ÂÂÂÂÂÂÂu32 val;
> +ÂÂÂÂÂÂÂu16 word_count;
> +ÂÂÂÂÂÂÂu8 channel;
> +ÂÂÂÂÂÂÂu8 data_type;
> +
> +ÂÂÂÂÂÂÂxfer->status = 0;
> +
> +ÂÂÂÂÂÂÂif (xfer->rx_word_count == 0) {
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂif (!(status & NWL_DSI_RX_PKT_HDR_RCVD))
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂreturn false;
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ/* Get the RX header and parse it */
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂval = nwl_dsi_read(dsi, NWL_DSI_RX_PKT_HEADER);
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂword_count = NWL_DSI_WC(val);
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂchannel = NWL_DSI_RX_VC(val);
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂdata_type = NWL_DSI_RX_DT(val);
> +
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂif (channel != xfer->msg->channel) {
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂDRM_DEV_ERROR(dev,
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ"[%02X] Channel mismatch (%u !=
> %u)\n",
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂxfer->cmd, channel, xfer->msg-
> >channel);
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂreturn true;
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ}
> +
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂswitch (data_type) {
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂcase MIPI_DSI_RX_GENERIC_SHORT_READ_RESPONSE_2BYTE:
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ/* Fall through */
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂcase MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_2BYTE:
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂif (xfer->msg->rx_len > 1) {
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ/* read second byte */
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂpayload[1] = word_count >> 8;
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ++xfer->rx_len;
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ}
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ/* Fall through */
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂcase MIPI_DSI_RX_GENERIC_SHORT_READ_RESPONSE_1BYTE:
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ/* Fall through */
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂcase MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_1BYTE:
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂif (xfer->msg->rx_len > 0) {
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ/* read first byte */
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂpayload[0] = word_count & 0xff;
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ++xfer->rx_len;
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ}
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂxfer->status = xfer->rx_len;
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂreturn true;
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂcase MIPI_DSI_RX_ACKNOWLEDGE_AND_ERROR_REPORT:
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂword_count &= 0xff;
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂDRM_DEV_ERROR(dev, "[%02X] DSI error report:
> 0x%02x\n",
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂxfer->cmd, word_count);
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂxfer->status = -EPROTO;
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂreturn true;
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ}
> +
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂif (word_count > xfer->msg->rx_len) {
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂDRM_DEV_ERROR(
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂdev,
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ"[%02X] Receive buffer too small: %lu
> (< %u)\n",
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂxfer->cmd, xfer->msg->rx_len,
> word_count);
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂreturn true;
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ}
> +
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂxfer->rx_word_count = word_count;
> +ÂÂÂÂÂÂÂ} else {
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ/* Set word_count from previous header read */
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂword_count = xfer->rx_word_count;
> +ÂÂÂÂÂÂÂ}
> +
> +ÂÂÂÂÂÂÂ/* If RX payload is not yet received, wait for it */
> +ÂÂÂÂÂÂÂif (!(status & NWL_DSI_RX_PKT_PAYLOAD_DATA_RCVD))
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂreturn false;
> +
> +ÂÂÂÂÂÂÂ/* Read the RX payload */
> +ÂÂÂÂÂÂÂwhile (word_count >= 4) {
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂval = nwl_dsi_read(dsi, NWL_DSI_RX_PAYLOAD);
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂpayload[0] = (val >> 0) & 0xff;
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂpayload[1] = (val >> 8) & 0xff;
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂpayload[2] = (val >> 16) & 0xff;
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂpayload[3] = (val >> 24) & 0xff;
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂpayload += 4;
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂxfer->rx_len += 4;
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂword_count -= 4;
> +ÂÂÂÂÂÂÂ}
> +
> +ÂÂÂÂÂÂÂif (word_count > 0) {
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂval = nwl_dsi_read(dsi, NWL_DSI_RX_PAYLOAD);
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂswitch (word_count) {
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂcase 3:
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂpayload[2] = (val >> 16) & 0xff;
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ++xfer->rx_len;
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ/* Fall through */
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂcase 2:
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂpayload[1] = (val >> 8) & 0xff;
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ++xfer->rx_len;
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ/* Fall through */
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂcase 1:
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂpayload[0] = (val >> 0) & 0xff;
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ++xfer->rx_len;
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂbreak;
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ}
> +ÂÂÂÂÂÂÂ}
> +
> +ÂÂÂÂÂÂÂxfer->status = xfer->rx_len;
> +
> +ÂÂÂÂÂÂÂreturn true;
> +}
> +
> +static void nwl_dsi_finish_transmission(struct nwl_dsi *dsi, u32
> status)
> +{
> +ÂÂÂÂÂÂÂstruct nwl_dsi_transfer *xfer = dsi->xfer;
> +ÂÂÂÂÂÂÂbool end_packet = false;
> +
> +ÂÂÂÂÂÂÂif (!xfer)
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂreturn;
> +
> +ÂÂÂÂÂÂÂif (status & NWL_DSI_TX_FIFO_OVFLW) {
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂDRM_DEV_ERROR_RATELIMITED(dsi->dev, "tx fifo
> overflow\n");
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂreturn;
> +ÂÂÂÂÂÂÂ}
> +
> +ÂÂÂÂÂÂÂif (status & NWL_DSI_HS_TX_TIMEOUT) {
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂDRM_DEV_ERROR_RATELIMITED(dsi->dev, "HS tx
> timeout\n");
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂreturn;
> +ÂÂÂÂÂÂÂ}
> +
> +ÂÂÂÂÂÂÂif (xfer->direction == DSI_PACKET_SEND &&
> +ÂÂÂÂÂÂÂÂÂÂÂstatus & NWL_DSI_TX_PKT_DONE) {
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂxfer->status = xfer->tx_len;
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂend_packet = true;
> +ÂÂÂÂÂÂÂ} else if (status & NWL_DSI_DPHY_DIRECTION &&
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ((status & (NWL_DSI_RX_PKT_HDR_RCVD |
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂNWL_DSI_RX_PKT_PAYLOAD_DATA_RCVD)))) {
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂend_packet = nwl_dsi_read_packet(dsi, status);
> +ÂÂÂÂÂÂÂ}
> +
> +ÂÂÂÂÂÂÂif (end_packet)
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂcomplete(&xfer->completed);
> +}
> +
> +static void nwl_dsi_begin_transmission(struct nwl_dsi *dsi)
> +{
> +ÂÂÂÂÂÂÂstruct nwl_dsi_transfer *xfer = dsi->xfer;
> +ÂÂÂÂÂÂÂstruct mipi_dsi_packet *pkt = &xfer->packet;
> +ÂÂÂÂÂÂÂconst u8 *payload;
> +ÂÂÂÂÂÂÂsize_t length;
> +ÂÂÂÂÂÂÂu16 word_count;
> +ÂÂÂÂÂÂÂu8 hs_mode;
> +ÂÂÂÂÂÂÂu32 val;
> +ÂÂÂÂÂÂÂu32 hs_workaround = 0;
> +
> +ÂÂÂÂÂÂÂ/* Send the payload, if any */
> +ÂÂÂÂÂÂÂlength = pkt->payload_length;
> +ÂÂÂÂÂÂÂpayload = pkt->payload;
> +
> +ÂÂÂÂÂÂÂwhile (length >= 4) {
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂval = *(u32 *)payload;
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂhs_workaround |= !(val & 0xFFFF00);
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂnwl_dsi_write(dsi, NWL_DSI_TX_PAYLOAD, val);
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂpayload += 4;
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂlength -= 4;
> +ÂÂÂÂÂÂÂ}
> +ÂÂÂÂÂÂÂ/* Send the rest of the payload */
> +ÂÂÂÂÂÂÂval = 0;
> +ÂÂÂÂÂÂÂswitch (length) {
> +ÂÂÂÂÂÂÂcase 3:
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂval |= payload[2] << 16;
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ/* Fall through */
> +ÂÂÂÂÂÂÂcase 2:
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂval |= payload[1] << 8;
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂhs_workaround |= !(val & 0xFFFF00);
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ/* Fall through */
> +ÂÂÂÂÂÂÂcase 1:
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂval |= payload[0];
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂnwl_dsi_write(dsi, NWL_DSI_TX_PAYLOAD, val);
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂbreak;
> +ÂÂÂÂÂÂÂ}
> +ÂÂÂÂÂÂÂxfer->tx_len = pkt->payload_length;
> +
> +ÂÂÂÂÂÂÂ/*
> +ÂÂÂÂÂÂÂÂ* Send the header
> +ÂÂÂÂÂÂÂÂ* header[0] = Virtual Channel + Data Type
> +ÂÂÂÂÂÂÂÂ* header[1] = Word Count LSB (LP) or first param (SP)
> +ÂÂÂÂÂÂÂÂ* header[2] = Word Count MSB (LP) or second param (SP)
> +ÂÂÂÂÂÂÂÂ*/
> +ÂÂÂÂÂÂÂword_count = pkt->header[1] | (pkt->header[2] << 8);
> +ÂÂÂÂÂÂÂif (hs_workaround && (dsi->quirks & E11418_HS_MODE_QUIRK)) {
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂDRM_DEV_DEBUG_DRIVER(dsi->dev,
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ"Using hs mode workaround for
> cmd 0x%x\n",
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂxfer->cmd);
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂhs_mode = 1;
> +ÂÂÂÂÂÂÂ} else {
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂhs_mode = (xfer->msg->flags & MIPI_DSI_MSG_USE_LPM) ?
> 0 : 1;
> +ÂÂÂÂÂÂÂ}
> +ÂÂÂÂÂÂÂval = NWL_DSI_WC(word_count) | NWL_DSI_TX_VC(xfer->msg-
> >channel) |
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂNWL_DSI_TX_DT(xfer->msg->type) |
> NWL_DSI_HS_SEL(hs_mode) |
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂNWL_DSI_BTA_TX(xfer->need_bta);
> +ÂÂÂÂÂÂÂnwl_dsi_write(dsi, NWL_DSI_PKT_CONTROL, val);
> +
> +ÂÂÂÂÂÂÂ/* Send packet command */
> +ÂÂÂÂÂÂÂnwl_dsi_write(dsi, NWL_DSI_SEND_PACKET, 0x1);
> +}
> +
> +static ssize_t nwl_dsi_host_transfer(struct mipi_dsi_host *dsi_host,
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂconst struct mipi_dsi_msg *msg)
> +{
> +ÂÂÂÂÂÂÂstruct nwl_dsi *dsi = container_of(dsi_host, struct nwl_dsi,
> dsi_host);
> +ÂÂÂÂÂÂÂstruct nwl_dsi_transfer xfer;
> +ÂÂÂÂÂÂÂssize_t ret = 0;
> +
> +ÂÂÂÂÂÂÂ/* Create packet to be sent */
> +ÂÂÂÂÂÂÂdsi->xfer = &xfer;
> +ÂÂÂÂÂÂÂret = mipi_dsi_create_packet(&xfer.packet, msg);
> +ÂÂÂÂÂÂÂif (ret < 0) {
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂdsi->xfer = NULL;
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂreturn ret;
> +ÂÂÂÂÂÂÂ}
> +
> +ÂÂÂÂÂÂÂif ((msg->type & MIPI_DSI_GENERIC_READ_REQUEST_0_PARAM ||
> +ÂÂÂÂÂÂÂÂÂÂÂÂmsg->type & MIPI_DSI_GENERIC_READ_REQUEST_1_PARAM ||
> +ÂÂÂÂÂÂÂÂÂÂÂÂmsg->type & MIPI_DSI_GENERIC_READ_REQUEST_2_PARAM ||
> +ÂÂÂÂÂÂÂÂÂÂÂÂmsg->type & MIPI_DSI_DCS_READ) &&
> +ÂÂÂÂÂÂÂÂÂÂÂmsg->rx_len > 0 && msg->rx_buf != NULL)
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂxfer.direction = DSI_PACKET_RECEIVE;
> +ÂÂÂÂÂÂÂelse
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂxfer.direction = DSI_PACKET_SEND;
> +
> +ÂÂÂÂÂÂÂxfer.need_bta = (xfer.direction == DSI_PACKET_RECEIVE);
> +ÂÂÂÂÂÂÂxfer.need_bta |= (msg->flags & MIPI_DSI_MSG_REQ_ACK) ? 1 : 0;
> +ÂÂÂÂÂÂÂxfer.msg = msg;
> +ÂÂÂÂÂÂÂxfer.status = -ETIMEDOUT;
> +ÂÂÂÂÂÂÂxfer.rx_word_count = 0;
> +ÂÂÂÂÂÂÂxfer.rx_len = 0;
> +ÂÂÂÂÂÂÂxfer.cmd = 0x00;
> +ÂÂÂÂÂÂÂif (msg->tx_len > 0)
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂxfer.cmd = ((u8 *)(msg->tx_buf))[0];
> +ÂÂÂÂÂÂÂinit_completion(&xfer.completed);
> +
> +ÂÂÂÂÂÂÂret = clk_prepare_enable(dsi->rx_esc_clk);
> +ÂÂÂÂÂÂÂif (ret < 0) {
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂDRM_DEV_ERROR(dsi->dev, "Failed to enable rx_esc clk:
> %zd\n",
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂret);
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂreturn ret;
> +ÂÂÂÂÂÂÂ}
> +ÂÂÂÂÂÂÂDRM_DEV_DEBUG_DRIVER(dsi->dev, "Enabled rx_esc clk @%lu
> Hz\n",
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂclk_get_rate(dsi->rx_esc_clk));
This will be printed every time a DSI command is sent, of course, when
debugging only, but still: are you sure you need this info?
> +
> +ÂÂÂÂÂÂÂ/* Initiate the DSI packet transmision */
> +ÂÂÂÂÂÂÂnwl_dsi_begin_transmission(dsi);
> +
> +ÂÂÂÂÂÂÂif (!wait_for_completion_timeout(&xfer.completed,
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂNWL_DSI_MIPI_FIFO_TIMEOUT))
> {
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂDRM_DEV_ERROR(dsi_host->dev, "[%02X] DSI transfer
> timed out\n",
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂxfer.cmd);
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂret = -ETIMEDOUT;
> +ÂÂÂÂÂÂÂ} else {
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂret = xfer.status;
> +ÂÂÂÂÂÂÂ}
> +
> +ÂÂÂÂÂÂÂclk_disable_unprepare(dsi->rx_esc_clk);
> +
> +ÂÂÂÂÂÂÂreturn ret;
> +}
> +
> +const struct mipi_dsi_host_ops nwl_dsi_host_ops = {
> +ÂÂÂÂÂÂÂ.attach = nwl_dsi_host_attach,
> +ÂÂÂÂÂÂÂ.detach = nwl_dsi_host_detach,
> +ÂÂÂÂÂÂÂ.transfer = nwl_dsi_host_transfer,
> +};
> +
> +irqreturn_t nwl_dsi_irq_handler(int irq, void *data)
> +{
> +ÂÂÂÂÂÂÂu32 irq_status;
> +ÂÂÂÂÂÂÂstruct nwl_dsi *dsi = data;
> +
> +ÂÂÂÂÂÂÂirq_status = nwl_dsi_read(dsi, NWL_DSI_IRQ_STATUS);
> +
> +ÂÂÂÂÂÂÂif (irq_status & NWL_DSI_TX_PKT_DONE ||
> +ÂÂÂÂÂÂÂÂÂÂÂirq_status & NWL_DSI_RX_PKT_HDR_RCVD ||
> +ÂÂÂÂÂÂÂÂÂÂÂirq_status & NWL_DSI_RX_PKT_PAYLOAD_DATA_RCVD)
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂnwl_dsi_finish_transmission(dsi, irq_status);
> +
> +ÂÂÂÂÂÂÂreturn IRQ_HANDLED;
> +}
> +
> +int nwl_dsi_enable(struct nwl_dsi *dsi)
> +{
> +ÂÂÂÂÂÂÂstruct device *dev = dsi->dev;
> +ÂÂÂÂÂÂÂunion phy_configure_opts *phy_cfg = &dsi->phy_cfg;
> +ÂÂÂÂÂÂÂint ret;
> +
> +ÂÂÂÂÂÂÂif (!dsi->lanes) {
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂDRM_DEV_ERROR(dev, "Need DSI lanes: %d\n", dsi-
> >lanes);
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂreturn -EINVAL;
> +ÂÂÂÂÂÂÂ}
> +
> +ÂÂÂÂÂÂÂret = phy_init(dsi->phy);
> +ÂÂÂÂÂÂÂif (ret < 0) {
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂDRM_DEV_ERROR(dev, "Failed to init DSI phy: %d\n",
> ret);
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂreturn ret;
> +ÂÂÂÂÂÂÂ}
> +
> +ÂÂÂÂÂÂÂret = phy_configure(dsi->phy, phy_cfg);
> +ÂÂÂÂÂÂÂif (ret < 0) {
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂDRM_DEV_ERROR(dev, "Failed to configure DSI phy:
> %d\n", ret);
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂreturn ret;
> +ÂÂÂÂÂÂÂ}
> +
> +ÂÂÂÂÂÂÂret = clk_prepare_enable(dsi->tx_esc_clk);
> +ÂÂÂÂÂÂÂif (ret < 0) {
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂDRM_DEV_ERROR(dsi->dev, "Failed to enable tx_esc clk:
> %d\n",
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂret);
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂreturn ret;
> +ÂÂÂÂÂÂÂ}
> +ÂÂÂÂÂÂÂDRM_DEV_DEBUG_DRIVER(dsi->dev, "Enabled tx_esc clk @%lu
> Hz\n",
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂclk_get_rate(dsi->tx_esc_clk));
> +
> +ÂÂÂÂÂÂÂret = nwl_dsi_config_host(dsi);
> +ÂÂÂÂÂÂÂif (ret < 0) {
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂDRM_DEV_ERROR(dev, "Failed to set up DSI: %d", ret);
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂreturn ret;
> +ÂÂÂÂÂÂÂ}
> +
> +ÂÂÂÂÂÂÂret = nwl_dsi_config_dpi(dsi);
> +ÂÂÂÂÂÂÂif (ret < 0) {
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂDRM_DEV_ERROR(dev, "Failed to set up DPI: %d", ret);
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂreturn ret;
> +ÂÂÂÂÂÂÂ}
> +
> +ÂÂÂÂÂÂÂret = phy_power_on(dsi->phy);
> +ÂÂÂÂÂÂÂif (ret < 0) {
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂDRM_DEV_ERROR(dev, "Failed to power on DPHY (%d)\n",
> ret);
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂreturn ret;
> +ÂÂÂÂÂÂÂ}
> +
> +ÂÂÂÂÂÂÂnwl_dsi_init_interrupts(dsi);
> +
> +ÂÂÂÂÂÂÂreturn 0;
> +}
I see that you do all the initialization from the pre_enable stage. If
you power on the DPHY, the DSI host block will start transmitting pixel
data on the data lanes. We had some customers that complained about
this, so I think is better to hold on the pixel flow until the whole
pixel pipe is completely initialized.
What I recommend here, is to also use the enable stage in the bridge
and move nwl_dsi_config_hostÂin there. Thus, the call flow will be:
1. bridge->pre_enable (DSI is configured, but not streaming since the
host is not yet configured)
2. panel->prepare (panel will use the DSI APB block to send DSI
commands)
3. bridge->enable (DSI host block is configured, DSI starts streamming
pixels)
3. panel->enable (panel is ready to display the pixel flow)
> +
> +int nwl_dsi_disable(struct nwl_dsi *dsi)
> +{
> +ÂÂÂÂÂÂÂstruct device *dev = dsi->dev;
> +
> +ÂÂÂÂÂÂÂDRM_DEV_DEBUG_DRIVER(dev, "Disabling clocks and phy\n");
> +
> +ÂÂÂÂÂÂÂphy_power_off(dsi->phy);
> +ÂÂÂÂÂÂÂphy_exit(dsi->phy);
> +
> +ÂÂÂÂÂÂÂ/* Disabling the clock before the phy breaks enabling dsi
> again */
> +ÂÂÂÂÂÂÂclk_disable_unprepare(dsi->tx_esc_clk);
> +
> +ÂÂÂÂÂÂÂreturn 0;
> +}
> diff --git a/drivers/gpu/drm/bridge/nwl-dsi/nwl-dsi.h
> b/drivers/gpu/drm/bridge/nwl-dsi/nwl-dsi.h
> new file mode 100644
> index 000000000000..579b366de652
> --- /dev/null
> +++ b/drivers/gpu/drm/bridge/nwl-dsi/nwl-dsi.h
> @@ -0,0 +1,112 @@
> +/* SPDX-License-Identifier: GPL-2.0+ */
> +/*
> + * NWL MIPI DSI host driver
> + *
> + * Copyright (C) 2017 NXP
> + * Copyright (C) 2019 Purism SPC
> + */
> +#ifndef __NWL_DSI_H__
> +#define __NWL_DSI_H__
> +
> +#include <linux/irqreturn.h>
> +
> +#include <drm/drm_mipi_dsi.h>
> +
> +#include "nwl-drv.h"
> +
> +/* DSI HOST registers */
> +#define NWL_DSI_CFG_NUM_LANESÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ0x0
> +#define NWL_DSI_CFG_NONCONTINUOUS_CLKÂÂÂÂÂÂÂÂÂÂ0x4
> +#define NWL_DSI_CFG_T_PREÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ0x8
> +#define NWL_DSI_CFG_T_POSTÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ0xc
> +#define NWL_DSI_CFG_TX_GAPÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ0x10
> +#define NWL_DSI_CFG_AUTOINSERT_EOTPÂÂÂÂÂÂÂÂÂÂÂÂ0x14
> +#define NWL_DSI_CFG_EXTRA_CMDS_AFTER_EOTPÂÂÂÂÂÂ0x18
> +#define NWL_DSI_CFG_HTX_TO_COUNTÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ0x1c
> +#define NWL_DSI_CFG_LRX_H_TO_COUNTÂÂÂÂÂÂÂÂÂÂÂÂÂ0x20
> +#define NWL_DSI_CFG_BTA_H_TO_COUNTÂÂÂÂÂÂÂÂÂÂÂÂÂ0x24
> +#define NWL_DSI_CFG_TWAKEUPÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ0x28
> +#define NWL_DSI_CFG_STATUS_OUTÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ0x2c
> +#define NWL_DSI_RX_ERROR_STATUSÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ0x30
> +
> +/* DSI DPI registers */
> +#define NWL_DSI_PIXEL_PAYLOAD_SIZEÂÂÂÂÂÂÂÂÂÂÂÂÂ0x200
> +#define NWL_DSI_PIXEL_FIFO_SEND_LEVELÂÂÂÂÂÂÂÂÂÂ0x204
> +#define NWL_DSI_INTERFACE_COLOR_CODINGÂÂÂÂÂÂÂÂÂ0x208
> +#define NWL_DSI_PIXEL_FORMATÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ0x20c
> +#define NWL_DSI_VSYNC_POLARITYÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ0x210
> +#define NWL_DSI_VSYNC_POLARITY_ACTIVE_LOWÂÂÂÂÂÂ0
> +#define NWL_DSI_VSYNC_POLARITY_ACTIVE_HIGHÂÂÂÂÂBIT(1)
> +
> +#define NWL_DSI_HSYNC_POLARITYÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ0x214
> +#define NWL_DSI_HSYNC_POLARITY_ACTIVE_LOWÂÂÂÂÂÂ0
> +#define NWL_DSI_HSYNC_POLARITY_ACTIVE_HIGHÂÂÂÂÂBIT(1)
> +
> +#define NWL_DSI_VIDEO_MODEÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ0x218
> +#define NWL_DSI_HFPÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ0x21c
> +#define NWL_DSI_HBPÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ0x220
> +#define NWL_DSI_HSAÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ0x224
> +#define NWL_DSI_ENABLE_MULT_PKTSÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ0x228
> +#define NWL_DSI_VBPÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ0x22c
> +#define NWL_DSI_VFPÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ0x230
> +#define NWL_DSI_BLLP_MODEÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ0x234
> +#define NWL_DSI_USE_NULL_PKT_BLLPÂÂÂÂÂÂÂÂÂÂÂÂÂÂ0x238
> +#define NWL_DSI_VACTIVEÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ0x23c
> +#define NWL_DSI_VCÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ0x240
> +
> +/* DSI APB PKT control */
> +#define NWL_DSI_TX_PAYLOADÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ0x280
> +#define NWL_DSI_PKT_CONTROLÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ0x284
> +#define NWL_DSI_SEND_PACKETÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ0x288
> +#define NWL_DSI_PKT_STATUSÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ0x28c
> +#define NWL_DSI_PKT_FIFO_WR_LEVELÂÂÂÂÂÂÂÂÂÂÂÂÂÂ0x290
> +#define NWL_DSI_PKT_FIFO_RD_LEVELÂÂÂÂÂÂÂÂÂÂÂÂÂÂ0x294
> +#define NWL_DSI_RX_PAYLOADÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ0x298
> +#define NWL_DSI_RX_PKT_HEADERÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ0x29c
> +
> +/* DSI IRQ handling */
> +#define NWL_DSI_IRQ_STATUSÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ0x2a0
> +#define NWL_DSI_SM_NOT_IDLEÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂBIT(0)
> +#define NWL_DSI_TX_PKT_DONEÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂBIT(1)
> +#define NWL_DSI_DPHY_DIRECTIONÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂBIT(2)
> +#define NWL_DSI_TX_FIFO_OVFLWÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂBIT(3)
> +#define NWL_DSI_TX_FIFO_UDFLWÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂBIT(4)
> +#define NWL_DSI_RX_FIFO_OVFLWÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂBIT(5)
> +#define NWL_DSI_RX_FIFO_UDFLWÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂBIT(6)
> +#define NWL_DSI_RX_PKT_HDR_RCVDÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂBIT(7)
> +#define NWL_DSI_RX_PKT_PAYLOAD_DATA_RCVDÂÂÂÂÂÂÂBIT(8)
> +#define NWL_DSI_BTA_TIMEOUTÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂBIT(29)
> +#define NWL_DSI_LP_RX_TIMEOUTÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂBIT(30)
> +#define NWL_DSI_HS_TX_TIMEOUTÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂBIT(31)
> +
> +#define NWL_DSI_IRQ_STATUS2ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ0x2a4
> +#define NWL_DSI_SINGLE_BIT_ECC_ERRÂÂÂÂÂÂÂÂÂÂÂÂÂBIT(0)
> +#define NWL_DSI_MULTI_BIT_ECC_ERRÂÂÂÂÂÂÂÂÂÂÂÂÂÂBIT(1)
> +#define NWL_DSI_CRC_ERRÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂBIT(2)
> +
> +#define NWL_DSI_IRQ_MASKÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ0x2a8
> +#define NWL_DSI_SM_NOT_IDLE_MASKÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂBIT(0)
> +#define NWL_DSI_TX_PKT_DONE_MASKÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂBIT(1)
> +#define NWL_DSI_DPHY_DIRECTION_MASKÂÂÂÂÂÂÂÂÂÂÂÂBIT(2)
> +#define NWL_DSI_TX_FIFO_OVFLW_MASKÂÂÂÂÂÂÂÂÂÂÂÂÂBIT(3)
> +#define NWL_DSI_TX_FIFO_UDFLW_MASKÂÂÂÂÂÂÂÂÂÂÂÂÂBIT(4)
> +#define NWL_DSI_RX_FIFO_OVFLW_MASKÂÂÂÂÂÂÂÂÂÂÂÂÂBIT(5)
> +#define NWL_DSI_RX_FIFO_UDFLW_MASKÂÂÂÂÂÂÂÂÂÂÂÂÂBIT(6)
> +#define NWL_DSI_RX_PKT_HDR_RCVD_MASKÂÂÂÂÂÂÂÂÂÂÂBIT(7)
> +#define NWL_DSI_RX_PKT_PAYLOAD_DATA_RCVD_MASKÂÂBIT(8)
> +#define NWL_DSI_BTA_TIMEOUT_MASKÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂBIT(29)
> +#define NWL_DSI_LP_RX_TIMEOUT_MASKÂÂÂÂÂÂÂÂÂÂÂÂÂBIT(30)
> +#define NWL_DSI_HS_TX_TIMEOUT_MASKÂÂÂÂÂÂÂÂÂÂÂÂÂBIT(31)
> +
> +#define NWL_DSI_IRQ_MASK2ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ0x2ac
> +#define NWL_DSI_SINGLE_BIT_ECC_ERR_MASKÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂBIT(0)
> +#define NWL_DSI_MULTI_BIT_ECC_ERR_MASKÂÂÂÂÂÂÂÂÂBIT(1)
> +#define NWL_DSI_CRC_ERR_MASKÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂBIT(2)
> +
> +extern const struct mipi_dsi_host_ops nwl_dsi_host_ops;
> +
> +irqreturn_t nwl_dsi_irq_handler(int irq, void *data);
> +int nwl_dsi_enable(struct nwl_dsi *dsi);
> +int nwl_dsi_disable(struct nwl_dsi *dsi);
> +
> +#endif /* __NWL_DSI_H__ */
> --
> 2.20.1
>
>
Best regards,
Robert