Received: by 2002:a25:c593:0:0:0:0:0 with SMTP id v141csp570348ybe; Fri, 13 Sep 2019 02:34:52 -0700 (PDT) X-Google-Smtp-Source: APXvYqxcNAxyEcmgMmgzxOXxYJW/4fbBTxXvIPeEbPPf12j7yjAyIZBcq+T3ezTzuoFiCzPjNBvO X-Received: by 2002:a05:6402:7d1:: with SMTP id u17mr46543239edy.132.1568367292673; Fri, 13 Sep 2019 02:34:52 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1568367292; cv=none; d=google.com; s=arc-20160816; b=eCcnOHNwzvn994KMMDZ5dYTH8tKCtmyrZpyNuKctDs8cJvDfDD7ACwwx4UqzwQDnwK TXVLWAKOhxR8miaoCKUuC33/DoZumM0QCSTruzH//jay+ysRDT5iktd3PGdnLctQoqvo eRBgGfCVfss7QYpj4/DcxXc5vymavXKFG+YqKWSrUbXyg2jBlqaXGBOwtBdDU86+akr3 OlQzxgbFXH0Idlr48lpm1TnxwuU3SPetl4Ckkt3mQ/MSDXUpG01JIuFfNfEiIjl4H4P+ wK/ze/eEM+T9vfpDMnBHlfPjlC87NXsHwTIzoCL0gJ1bavqi8FMpYkpadPrDFv97Q0L0 N7Lg== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:sender:references:cms-type:content-language :content-transfer-encoding:in-reply-to:mime-version:user-agent:date :message-id:from:to:subject:dkim-signature:dkim-filter; bh=qFH0mGDpovF46yO6PShERBdGSq//ZAwjjC8guiiD9mw=; b=IinKCb4b8HWu29rMr4NLNt8tPuca7TohGPV83BX4uUDtv1sCsuyLqPOHLqylHZKgQT UnyBTGSIdqGAuir9lJRuaX5pM4xWt7ITAyIBmq4ZWTFyaK9XBq2MA/AmvueOodD16B0c iHbhrfAp+yFuDlSKNhD9hB2m1disYgTAkfiz9KtvD81swOf9aS3GQWMCOODFx9wZwD56 Ed7OI7ORtlBFg8aTWj9CDqf6dac+4WTDaNsH0uaQmsEMj7jjd5jDgFtaApQCc9a0gujE 28MQQrjwfl3s57cfXO86CnXD+4HoTmsaQaHKwEAaSCEsmLSS5CS0hv/KXkQJNA5VfmeQ IsGw== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@samsung.com header.s=mail20170921 header.b=HT2XiFju; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=samsung.com Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id j18si14464898ejb.240.2019.09.13.02.34.28; Fri, 13 Sep 2019 02:34:52 -0700 (PDT) Received-SPF: pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) client-ip=209.132.180.67; Authentication-Results: mx.google.com; dkim=pass header.i=@samsung.com header.s=mail20170921 header.b=HT2XiFju; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=samsung.com Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S2387954AbfIMJbv (ORCPT + 99 others); Fri, 13 Sep 2019 05:31:51 -0400 Received: from mailout1.w1.samsung.com ([210.118.77.11]:53408 "EHLO mailout1.w1.samsung.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S2387424AbfIMJbu (ORCPT ); Fri, 13 Sep 2019 05:31:50 -0400 Received: from eucas1p2.samsung.com (unknown [182.198.249.207]) by mailout1.w1.samsung.com (KnoxPortal) with ESMTP id 20190913093147euoutp0196bb19a5c05933ea8641ae166a1af288~D9ZMzEKm52528725287euoutp01e for ; Fri, 13 Sep 2019 09:31:47 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 mailout1.w1.samsung.com 20190913093147euoutp0196bb19a5c05933ea8641ae166a1af288~D9ZMzEKm52528725287euoutp01e DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=samsung.com; s=mail20170921; t=1568367107; bh=qFH0mGDpovF46yO6PShERBdGSq//ZAwjjC8guiiD9mw=; h=Subject:To:From:Date:In-Reply-To:References:From; b=HT2XiFju2b5KCTSdCHXcZpita5cNxNAbDazxD9OdFQpTHdrF9x58yITt+sSRnzFPe nJF35YOhGVRDqPTCI1OzfIRe0j2P65P3pW8xbFkGViwTv1yxf6h39bsyD02YVKNMgK DlvCPfpWoa99XqepR1KesM0kX0OMuV2TswsBmEZI= Received: from eusmges3new.samsung.com (unknown [203.254.199.245]) by eucas1p2.samsung.com (KnoxPortal) with ESMTP id 20190913093146eucas1p2e556bed182f951c668721cb480b33ee2~D9ZLvq_y92054720547eucas1p26; Fri, 13 Sep 2019 09:31:46 +0000 (GMT) Received: from eucas1p2.samsung.com ( [182.198.249.207]) by eusmges3new.samsung.com (EUCPMTA) with SMTP id E3.20.04374.2026B7D5; Fri, 13 Sep 2019 10:31:46 +0100 (BST) Received: from eusmtrp1.samsung.com (unknown [182.198.249.138]) by eucas1p2.samsung.com (KnoxPortal) with ESMTPA id 20190913093145eucas1p226ae637e39b06e58be33a98e012aec6a~D9ZK8ni6D0852908529eucas1p2J; Fri, 13 Sep 2019 09:31:45 +0000 (GMT) Received: from eusmgms1.samsung.com (unknown [182.198.249.179]) by eusmtrp1.samsung.com (KnoxPortal) with ESMTP id 20190913093145eusmtrp10ec76e5744ba99425807f0876185b0b9~D9ZKtsyJj2190421904eusmtrp1H; Fri, 13 Sep 2019 09:31:45 +0000 (GMT) X-AuditID: cbfec7f5-4ddff70000001116-e4-5d7b6202c579 Received: from eusmtip2.samsung.com ( [203.254.199.222]) by eusmgms1.samsung.com (EUCPMTA) with SMTP id FC.45.04166.1026B7D5; Fri, 13 Sep 2019 10:31:45 +0100 (BST) Received: from [106.120.51.74] (unknown [106.120.51.74]) by eusmtip2.samsung.com (KnoxPortal) with ESMTPA id 20190913093144eusmtip2d0e1b7b04a13973a1d1fa087279c76af~D9ZJpP8r_0629506295eusmtip2I; Fri, 13 Sep 2019 09:31:44 +0000 (GMT) Subject: Re: [PATCH v5 2/2] drm/bridge: Add NWL MIPI DSI host controller support To: =?UTF-8?Q?Guido_G=c3=bcnther?= , David Airlie , Daniel Vetter , Rob Herring , Mark Rutland , Shawn Guo , Sascha Hauer , Pengutronix Kernel Team , Fabio Estevam , NXP Linux Team , Neil Armstrong , Laurent Pinchart , Jonas Karlman , Jernej Skrabec , Lee Jones , dri-devel@lists.freedesktop.org, devicetree@vger.kernel.org, linux-arm-kernel@lists.infradead.org, linux-kernel@vger.kernel.org, Robert Chiras , Sam Ravnborg , Arnd Bergmann From: Andrzej Hajda Message-ID: Date: Fri, 13 Sep 2019 11:31:43 +0200 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Thunderbird/60.8.0 MIME-Version: 1.0 In-Reply-To: <3ce1891ea41249bf4a9985e2cee8640fb36de42e.1567995854.git.agx@sigxcpu.org> Content-Transfer-Encoding: 8bit Content-Language: en-US X-Brightmail-Tracker: H4sIAAAAAAAAA01SfUyMcRz3e97uKa49nbjvYrPOGCYyxm8Tw8x+8z4zMxadepaXXuyeosLy VipvXV7SnYl2lDC6uutl2LpS8pZwnKirKUQJJypC15Ppv8/38/L9fb7bj6dVlzlffktktKiL 1IZrOE/GWtld409t2hUU4HKo8Zd9TSw++qiawr3plQr8x6qncVbFIxY/6+zgcJN9Bbb/aKVx dZudwXmnShicojcpsLPzDsLmN89ZnNpzmcZPS89y+OKLWgrbjq3HZVk2FifeqlDg3iIzg3O7 LQi/L1TPG0munruKyM+edEQ6HIkKcuv7eYaUGOoVxJicyRJzXgpH7qY9ocjr5zc5UvS9kSXO w1UUKTAlkPxPxRQ53htATKftHDEctqKVqnWegaFi+JYdom7q3GDPzff2T9x+p52KbUu5RO1F ZSYqFXnwIMyA691ZilTkyauEXAT3mmuRPHxD0JJ/kpUHF4JfF+rpf5GTBVc4N1YJOQhK6+bI pnYEN380IbcwXFgFHWmH+vf6CNUc6LMNjFvghInQW/CyP60U5kJxib6vCM8zwjhwOAPd9Ahh LXxtLGdlizdUZzb3Rz2ENfA7J7s/Sgtj4IDFSMtYDXXNWZT7LRAaebjUUD7QdCFcePBtAA+H D1WFChmPhvsnjjAyTgBn7kFaDicjsNwoGQjMhvKqWtZdju4rfb10qkzPB3PmA+SmQfACR7u3 3MEL0q0ZtEwrITlJJbv9wPnQMrBQDRcfd3IyJpCT/ZFKQ36GQVcaBl1mGHSZ4X+H84jJQ2ox RooIE6XpkeLOKZI2QoqJDJsSEhVhRn0f+P7vqs5idPvXJhsSeKQZpsST44NUrHaHFBdhQ8DT Gh/l0ra4IJUyVBsXL+qiNupiwkXJhkbxjEat3DWkcb1KCNNGi9tEcbuo+6dSvIfvXpTUEjL2 2ot3++bU53zKDJm13FjzmaEWfVWubh3VFYgbKu56MS1dQUkLGM08/W7sWzsylM3wObvHEjBm wvhlo2FdcOv7GYvtQ9861MaetQVRRVICLnNmnFG7llg3fHDphnjMTKzx89/TFRuc/84nv5Ad Fo8qW98a93v7b3WZbr96pWGkzdppk2idpP0Ld6VH07wDAAA= X-Brightmail-Tracker: H4sIAAAAAAAAA+NgFtrAKsWRmVeSWpSXmKPExsVy+t/xe7qMSdWxBp9uKVh8bHzIatF77iST xd9Jx9gt/m+byGwx/8g5VosrX9+zWTy86m9x9ftLZouTb66yWKyaupPFonPiEnaL+1+PMlps enyN1aLr10pmi8u75rBZLL1+kcniUF+0xcH5h1gtWvceYbf4u30Ti8WKn1sZLV5sEXcQ81gz bw2jx+9fkxg93t9oZffY+20Bi8fOWXfZPWZ3zGT12LSqk83jxIRLTB53ru1h89j+7QGrx/3u 40wem5fUe2x8t4PJo/+vgceSaVfZPGZ1b2MMEIrSsynKLy1JVcjILy6xVYo2tDDSM7S00DMy sdQzNDaPtTIyVdK3s0lJzcksSy3St0vQyzjVpFlw9C1TxZvOZUwNjAeXMHUxcnJICJhITNm8 mg3EFhJYyihxZoopRFxcYvf8t8wQtrDEn2tdQDVcQDWvGSVeNHeANQsLBEm8n9DODpIQETjN JvGvdzdU1SVGie8Nh1lAqtgENCX+br4JtoJXwE5ix86JQN0cHCwCqhI37tuAhEUFIiQO75jF CFEiKHFy5hOwVk6BMIl/yxeBtTILqEv8mXeJGcKWl2jeOhvKFpe49WQ+0wRGwVlI2mchaZmF pGUWkpYFjCyrGEVSS4tz03OLDfWKE3OLS/PS9ZLzczcxAlPLtmM/N+9gvLQx+BCjAAejEg+v hU5VrBBrYllxZe4hRgkOZiURXp83lbFCvCmJlVWpRfnxRaU5qcWHGE2BfpvILCWanA9Me3kl 8YamhuYWlobmxubGZhZK4rwdAgdjhATSE0tSs1NTC1KLYPqYODilGhh1N23jaX/568nqTu+8 Kcflf/i1bH6WP/n5lVC5MtsMF5fC9htLW+MFXCUmMM40WBwiEvIg6HmC1JrL3clhdiGs8/VO 31AMWWVZGdDbU7ziU/xuYeFN7RtfzT3tWDJddPYCQaVo5mLXnqrPnq28c2c5lU75PqM1/tj7 whfvFi9rX7a01nlyZIkSS3FGoqEWc1FxIgC6DEERQwMAAA== X-CMS-MailID: 20190913093145eucas1p226ae637e39b06e58be33a98e012aec6a X-Msg-Generator: CA Content-Type: text/plain; charset="utf-8" X-RootMTR: 20190909022600epcas3p19955ec815e4ceb54097ede895bc57b52 X-EPHeader: CA CMS-TYPE: 201P X-CMS-RootMailID: 20190909022600epcas3p19955ec815e4ceb54097ede895bc57b52 References: <3ce1891ea41249bf4a9985e2cee8640fb36de42e.1567995854.git.agx@sigxcpu.org> Sender: linux-kernel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org On 09.09.2019 04:25, 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 > Co-developed-by: Robert Chiras > Signed-off-by: Robert Chiras > Tested-by: Robert Chiras > --- > 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 | 499 ++++++++++++++++ > drivers/gpu/drm/bridge/nwl-dsi/nwl-drv.h | 65 +++ > drivers/gpu/drm/bridge/nwl-dsi/nwl-dsi.c | 696 +++++++++++++++++++++++ > drivers/gpu/drm/bridge/nwl-dsi/nwl-dsi.h | 112 ++++ Why do you need separate files nwl-drv.[ch] and nwl-dsi.[ch] ? I guess you can merge all into one file, maybe with separate file for NWL register definitions. > 8 files changed, 1395 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..7fa678e3b5e2 > --- /dev/null > +++ b/drivers/gpu/drm/bridge/nwl-dsi/Kconfig > @@ -0,0 +1,16 @@ > +config DRM_NWL_MIPI_DSI > + tristate "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..9ff43d2de127 > --- /dev/null > +++ b/drivers/gpu/drm/bridge/nwl-dsi/nwl-drv.c > @@ -0,0 +1,499 @@ > +// SPDX-License-Identifier: GPL-2.0+ > +/* > + * i.MX8 NWL MIPI DSI host driver > + * > + * Copyright (C) 2017 NXP > + * Copyright (C) 2019 Purism SPC > + */ > + > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include Alphabetic order > +#include > + > +#include > +#include > +#include > +#include > + > +#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, > +}; What is the point in using regmap here, why not simple writel/readl. > + > +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]; > +}; Another construct which do not have justification, at least for now. Please simplify the driver, remove callbacks/intermediate structs/quirks - for now they are useless. Unless there is a serious reason - in such case please describe it in comments. > + > +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 * dsi->lanes) > + return MODE_CLOCK_HIGH; > + > + if (mode->clock * bpp < 80000 * dsi->lanes) > + return MODE_CLOCK_LOW; > + > + 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; Relying on node name seems to me wrong. I am not sure if whole logic for input select should be here. My 1st impression is that selecting should be done rather in DCSS or LCDIF driver, why do you want to put it here? Regards Andrzej > + > + 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; > + > + 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 > +#include > + > +#include > +#include > + > +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..e6038cb4e849 > --- /dev/null > +++ b/drivers/gpu/drm/bridge/nwl-dsi/nwl-dsi.c > @@ -0,0 +1,696 @@ > +// SPDX-License-Identifier: GPL-2.0+ > +/* > + * NWL MIPI DSI host driver > + * > + * Copyright (C) 2017 NXP > + * Copyright (C) 2019 Purism SPC > + */ > + > +#include > +#include > +#include > +#include > +#include > +#include > + > +#include