[PATCH 3/3] drm/bridge: Add NWL MIPI DSI host controller support

Fabio Estevam festevam at gmail.com
Fri Jul 26 20:01:52 UTC 2019


Hi Guido,

Thanks for your work on this driver!

On Wed, Jul 24, 2019 at 12:52 PM Guido Günther <agx at sigxcpu.org> wrote:

> --- /dev/null
> +++ b/drivers/gpu/drm/bridge/imx-nwl/Kconfig
> @@ -0,0 +1,15 @@
> +config DRM_IMX_NWL_DSI
> +       tristate "Support for Northwest Logic MIPI DSI Host controller"
> +       depends on DRM && (ARCH_MXC || ARCH_MULTIPLATFORM || COMPILE_TEST)


This IP could potentially be found on other SoCs, so no need to make
it depend on ARCH_MXC.

> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_of.h>
> +#include <drm/drm_panel.h>
> +#include <drm/drm_print.h>
> +#include <drm/drm_probe_helper.h>
> +#include <linux/clk-provider.h>
> +#include <linux/clk.h>
> +#include <linux/component.h>
> +#include <linux/gpio/consumer.h>

I did not find gpio AP used in this driver.

> +static void imx_nwl_dsi_set_clocks(struct imx_nwl_dsi *dsi, bool enable)

Better make it to return 'int' instead...

> +{
> +       struct device *dev = dsi->dev;
> +       const char *id;
> +       struct clk *clk;
> +       unsigned long new_rate, cur_rate;
> +       bool enabled;
> +       size_t i;
> +       int ret;
> +
> +       DRM_DEV_DEBUG_DRIVER(dev, "%sabling platform clocks",

Please remove the letter 's' from 'sabling'.

> +                            enable ? "en" : "dis");
> +                       ret = clk_prepare_enable(clk);
> +                       if (ret < 0) {
> +                               DRM_DEV_ERROR(dev, "Failed to enable clock %s",
> +                                             id);

and propagate the error in case of clk_prepare_enable() failure.

> +                       }
> +                       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);
> +               }
> +       }
> +}
> +
> +static void imx_nwl_dsi_enable(struct imx_nwl_dsi *dsi)

Same here. Please return 'int' instead.

> +{
> +       struct device *dev = dsi->dev;
> +       int ret;
> +
> +       imx_nwl_dsi_set_clocks(dsi, true);
> +
> +       ret = dsi->pdata->poweron(dsi);
> +       if (ret < 0)
> +               DRM_DEV_ERROR(dev, "Failed to power on DSI (%d)\n", ret);

If the power domain failed to turn on, it is better to propagate the error.

> +       phy_ref_rate = clk_get_rate(dsi->phy_ref_clk);
> +       DRM_DEV_DEBUG_DRIVER(dev, "PHY at ref rate: %lu\n", phy_ref_rate);
> +       if (ret < 0) {

This check looks wrong. At this point ret is always 0.

> +               DRM_DEV_ERROR(dsi->dev,
> +                             "Cannot setup PHY for mode: %ux%u @%d Hz\n",
> +                             adjusted_mode->hdisplay, adjusted_mode->vdisplay,
> +                             adjusted_mode->clock);
> +               DRM_DEV_ERROR(dsi->dev, "PHY ref clk: %lu, bit clk: %lu\n",
> +                             phy_ref_rate, new_cfg.mipi_dphy.hs_clk_rate);
> +       } else {
> +               /* Save the new desired phy config */
> +               memcpy(&dsi->phy_cfg, &new_cfg, sizeof(new_cfg));
> +       }
> +
> +       /* LCDIF + NWL needs active high sync */

Would this still work if DCSS is used instead?

> +       adjusted_mode->flags |= (DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC);
> +       adjusted_mode->flags &= ~(DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC);
> +
> +       drm_display_mode_to_videomode(adjusted_mode, &dsi->vm);
> +       drm_mode_debug_printmodeline(adjusted_mode);
> +
> +       return ret == 0;

At this point ret is always 0.

> +static void imx_nwl_dsi_bridge_pre_enable(struct drm_bridge *bridge)
> +{
> +       struct imx_nwl_dsi *dsi = bridge_to_dsi(bridge);
> +
> +       if (dsi->dpms_mode == DRM_MODE_DPMS_ON)
> +               return;
> +
> +       imx_nwl_select_input_source(dsi);

This function is i.MX8M specific, so better protect it to run only for
the i.MX8M variant.

> +       pm_runtime_get_sync(dsi->dev);
> +       imx_nwl_dsi_enable(dsi);
> +       nwl_dsi_enable(dsi);

Please check the error and propagate in the case of failure.

> +       dsi->dpms_mode = DRM_MODE_DPMS_ON;
> +}
> +

> +       dsi->csr = syscon_regmap_lookup_by_phandle(np, "csr");
> +       if (IS_ERR(dsi->csr) && dsi->pdata->ext_regs & IMX_REG_CSR) {
> +               ret = PTR_ERR(dsi->csr);
> +               DRM_DEV_ERROR(dsi->dev, "Failed to get CSR regmap: %d\n",

In this function (and globally in the driver) there is a mix of
DRM_DEV_ERROR() and dev_err().

Can we just pick one of the two and use it consistently?

Not sure what is the norm in drm code, but IMHO dev_err() looks prettier :-)

> +
> +       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +       base = devm_ioremap_resource(dsi->dev, res);

Could use devm_platform_ioremap_resource(), which makes it simpler.

> +err_cleanup:
> +       devm_free_irq(dev, dsi->irq, dsi);

No need to call devm_free_irq() here. The devm functions do not need
to be freed on probe.

> diff --git a/drivers/gpu/drm/bridge/imx-nwl/nwl-dsi.c b/drivers/gpu/drm/bridge/imx-nwl/nwl-dsi.c
> new file mode 100644
> index 000000000000..0e1463af162f
> --- /dev/null
> +++ b/drivers/gpu/drm/bridge/imx-nwl/nwl-dsi.c
> @@ -0,0 +1,745 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * NWL DSI host driver
> + *
> + * Copyright (C) 2017 NXP
> + * Copyright (C) 2019 Purism SPC
> + */
> +
> +#include <asm/unaligned.h>

Is this asm header required?

> +/*
> + * DSI Video mode
> + */

Single line comment would suffice.

> +#define VIDEO_MODE_BURST_MODE_WITH_SYNC_PULSES         0
> +#define VIDEO_MODE_NON_BURST_MODE_WITH_SYNC_EVENTS     BIT(0)
> +#define VIDEO_MODE_BURST_MODE                          BIT(1)
> +
> +/*
> + * DPI color coding
> + */

Ditto.

> +#define DPI_16_BIT_565_PACKED  0
> +#define DPI_16_BIT_565_ALIGNED 1
> +#define DPI_16_BIT_565_SHIFTED 2
> +#define DPI_18_BIT_PACKED      3
> +#define DPI_18_BIT_ALIGNED     4
> +#define DPI_24_BIT             5
> +
> +/*
> + * DPI Pixel format
> + */

Ditto.

> +#define PIXEL_FORMAT_16  0
> +#define PIXEL_FORMAT_18  BIT(0)
> +#define PIXEL_FORMAT_18L BIT(1)
> +#define PIXEL_FORMAT_24  (BIT(0) | BIT(1))
> +
> +enum transfer_direction { DSI_PACKET_SEND, DSI_PACKET_RECEIVE };
> +
> +struct mipi_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; /* bytes sent */
> +       size_t rx_len; /* bytes received */
> +};

The comments here are kind of obvious, so I would just remove them.

> +static inline int nwl_dsi_write(struct imx_nwl_dsi *dsi, unsigned int reg,

inline can be dropped.

> +                               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 inline u32 nwl_dsi_read(struct imx_nwl_dsi *dsi, u32 reg)

Same here.

> +{
> +       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;
> +}

It seems that we could simply use regmap_read/write() directly instead
of these functions.

> +int nwl_dsi_get_dphy_params(struct imx_nwl_dsi *dsi,
> +                           const struct drm_display_mode *mode,
> +                           union phy_configure_opts *phy_opts)
> +{
> +       unsigned long rate;
> +
> +       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
> +        */
> +       phy_mipi_dphy_get_default_config(
> +               mode->crtc_clock * 1000,
> +               mipi_dsi_pixel_format_to_bpp(dsi->format), dsi->lanes,
> +               &phy_opts->mipi_dphy);
> +       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;
> +}
> +EXPORT_SYMBOL_GPL(nwl_dsi_get_dphy_params);

Does it really need to be exported? Why can't it be placed inside
nwl-drv.c and be made static?

> +/**

/* is enough


> + * ui2bc - UI time periods to byte clock cycles
> + */
> +static u32 ui2bc(struct imx_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->vm.pixelclock * bpp);
> +}
> +
> +#define USEC_PER_SEC 1000000L

This definition already exists in include/linux/time64.h. No need to
redefine it.

> +static int nwl_dsi_enable_tx_clock(struct imx_nwl_dsi *dsi)
> +{
> +       struct device *dev = dsi->dev;
> +       int ret;
> +
> +       ret = clk_prepare_enable(dsi->tx_esc_clk);
> +       if (ret < 0) {
> +               DRM_DEV_ERROR(dev, "Failed to enable tx_esc clk: %d\n", ret);
> +               return ret;
> +       }
> +
> +       DRM_DEV_DEBUG_DRIVER(dev, "Enabled tx_esc clk @%lu Hz\n",
> +                            clk_get_rate(dsi->tx_esc_clk));
> +       return 0;
> +}

Do we really need this function? It looks like it would be simpler
just to call clk_prepare_enable() directly.

> +
> +static int nwl_dsi_enable_rx_clock(struct imx_nwl_dsi *dsi)
> +{
> +       struct device *dev = dsi->dev;
> +       int ret;
> +
> +       ret = clk_prepare_enable(dsi->rx_esc_clk);
> +       if (ret < 0) {
> +               DRM_DEV_ERROR(dev, "Failed to enable rx_esc clk: %d\n", ret);
> +               return ret;
> +       }
> +
> +       DRM_DEV_DEBUG_DRIVER(dev, "Enabled rx_esc clk @%lu Hz\n",
> +                            clk_get_rate(dsi->rx_esc_clk));
> +       return 0;
> +}

Same here.

> +static ssize_t nwl_dsi_host_transfer(struct mipi_dsi_host *dsi_host,
> +                                    const struct mipi_dsi_msg *msg)
> +{
> +       struct imx_nwl_dsi *dsi =
> +               container_of(dsi_host, struct imx_nwl_dsi, dsi_host);
> +       struct mipi_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);
> +
> +       nwl_dsi_enable_rx_clock(dsi);

This may fail, so better check the error.

ret = clk_prepare_enable()
if (ret < 0)
   return ret;

> +irqreturn_t nwl_dsi_irq_handler(int irq, void *data)
> +{
> +       u32 irq_status;
> +       struct imx_nwl_dsi *dsi = data;
> +
> +       irq_status = nwl_dsi_read(dsi, IRQ_STATUS);
> +
> +       if (irq_status & TX_PKT_DONE || irq_status & RX_PKT_HDR_RCVD ||
> +           irq_status & RX_PKT_PAYLOAD_DATA_RCVD)
> +               nwl_dsi_finish_transmission(dsi, irq_status);
> +
> +       return IRQ_HANDLED;
> +}
> +EXPORT_SYMBOL_GPL(nwl_dsi_irq_handler);

What about placing this function inside nwl-drv.c and make it static?

> +
> +int nwl_dsi_enable(struct imx_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 = nwl_dsi_enable_tx_clock(dsi);
> +       if (ret < 0) {
> +               DRM_DEV_ERROR(dev, "Failed to enable tx clock: %d\n", ret);
> +               return ret;
> +       }
> +
> +       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;
> +}
> +EXPORT_SYMBOL_GPL(nwl_dsi_enable);

Same here.

> +
> +int nwl_dsi_disable(struct imx_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;
> +}
> +EXPORT_SYMBOL_GPL(nwl_dsi_disable);

Same here.


More information about the dri-devel mailing list