Devicetree
 help / color / mirror / Atom feed
* Re: [PATCH 2/2] dt-bindings: leds: document new "power-supply" property
From: Rob Herring @ 2019-07-24 16:47 UTC (permalink / raw)
  To: Jean-Jacques Hiblot
  Cc: jacek.anaszewski, pavel, mark.rutland, daniel.thompson, dmurphy,
	linux-leds, linux-kernel, devicetree
In-Reply-To: <20190708103547.23528-3-jjhiblot@ti.com>

On Mon, Jul 08, 2019 at 12:35:47PM +0200, Jean-Jacques Hiblot wrote:
> Most of the LEDs are powered by a voltage/current regulator. describing in
> the device-tree makes it possible for the LED core to enable/disable it
> when needed.
> 
> Signed-off-by: Jean-Jacques Hiblot <jjhiblot@ti.com>
> ---
>  Documentation/devicetree/bindings/leds/common.txt | 5 +++++
>  1 file changed, 5 insertions(+)
> 
> diff --git a/Documentation/devicetree/bindings/leds/common.txt b/Documentation/devicetree/bindings/leds/common.txt
> index 70876ac11367..e093a2b7eb90 100644
> --- a/Documentation/devicetree/bindings/leds/common.txt
> +++ b/Documentation/devicetree/bindings/leds/common.txt
> @@ -61,6 +61,11 @@ Optional properties for child nodes:
>  - panic-indicator : This property specifies that the LED should be used,
>  		    if at all possible, as a panic indicator.
>  
> +- power-supply : A voltage/current regulator used to to power the LED. When a
> +		 LED is turned off, the LED core disable its regulator. The
> +		 same regulator can power many LED (or other) devices. It is
> +		 turned off only when all of its users disabled it.

Not sure this should be common. It wouldn't apply to cases where we have 
an LED controller parent nor gpio and pwm LEDs and those are most cases.

Perhaps what makes sense here is an regulator-led binding.

> +
>  - trigger-sources : List of devices which should be used as a source triggering
>  		    this LED activity. Some LEDs can be related to a specific
>  		    device and should somehow indicate its state. E.g. USB 2.0
> -- 
> 2.17.1
> 

^ permalink raw reply

* Re: [PATCH] ARM: dts: stm32: add audio codec support on stm32mp157a-dk1 board
From: Alexandre Torgue @ 2019-07-24 16:40 UTC (permalink / raw)
  To: Olivier Moysan, linux-stm32, robh, mark.rutland, devicetree,
	linux-arm-kernel, linux-kernel
In-Reply-To: <1562327580-19647-1-git-send-email-olivier.moysan@st.com>

Hi Olivier

On 7/5/19 1:53 PM, Olivier Moysan wrote:
> Add support of Cirrus cs42l51 audio codec on stm32mp157a-dk1 board.
> Configuration overview:
> - SAI2A is the CPU interface used for the codec audio playback
> - SAI2B is the CPU interface used for the codec audio record
> - SAI2A is configured as a clock provider for the audio codec
> - SAI2A&B are configured as slave of the audio codec
> - SAI2A&B share the same interface of the audio codec
> 
> Note:
> In master mode, cs42l51 audio codec provides a bitclock
> at 64 x FS, regardless of data width. This means that
> slot width is always 32 bits.
> Set slot width to 32 bits and slot number to 2
> in SAI2A&B endpoint nodes, to match this constraint.
> dai-tdm-slot-num and dai-tdm-slot-width properties are used here,
> assuming that i2s is a special case of tdm, where slot number is 2.
> 
> Signed-off-by: Olivier Moysan <olivier.moysan@st.com>
> ---
>   arch/arm/boot/dts/stm32mp157a-dk1.dts | 89 +++++++++++++++++++++++++++++++++++
>   1 file changed, 89 insertions(+)
> 

...

>   
> +&sai2 {
> +	clocks = <&rcc SAI2>, <&rcc PLL3_Q>, <&rcc PLL3_R>;
> +	clock-names = "pclk", "x8k", "x11k";
> +	pinctrl-names = "default", "sleep";
> +	pinctrl-0 = <&sai2a_pins_a>, <&sai2b_pins_b>;
> +	pinctrl-1 = <&sai2a_sleep_pins_a>, <&sai2b_sleep_pins_b>;
> +	status = "okay";
> +
> +	sai2a: audio-controller@4400b004 {
> +		#clock-cells = <0>;
> +		dma-names = "tx";
> +		clocks = <&rcc SAI2_K>;
> +		clock-names = "sai_ck";
> +		status = "okay";
> +
> +		sai2a_port: port {
> +			sai2a_endpoint: endpoint {
> +				remote-endpoint = <&cs42l51_tx_endpoint>;
> +				format = "i2s";
> +				mclk-fs = <256>;
> +				dai-tdm-slot-num = <2>;
> +				dai-tdm-slot-width = <32>;
> +			};
> +		};
> +	};
> +
You could use label to overload sai2a and sai2b. no ?

> +	sai2b: audio-controller@4400b024 {
> +		dma-names = "rx";
> +		st,sync = <&sai2a 2>;
> +		clocks = <&rcc SAI2_K>, <&sai2a>;
> +		clock-names = "sai_ck", "MCLK";
> +		status = "okay";
> +
> +		sai2b_port: port {
> +			sai2b_endpoint: endpoint {
> +				remote-endpoint = <&cs42l51_rx_endpoint>;
> +				format = "i2s";
> +				mclk-fs = <256>;
> +				dai-tdm-slot-num = <2>;
> +				dai-tdm-slot-width = <32>;
> +			};
> +		};
> +	};
> +};
> +
>   &sdmmc1 {
>   	pinctrl-names = "default", "opendrain", "sleep";
>   	pinctrl-0 = <&sdmmc1_b4_pins_a>;
> 

^ permalink raw reply

* Re: [PATCH net-next v1 1/4] enetc: Clean up local mdio bus allocation
From: Andrew Lunn @ 2019-07-24 16:39 UTC (permalink / raw)
  To: Claudiu Manoil
  Cc: devicetree@vger.kernel.org, netdev@vger.kernel.org,
	Alexandru Marginean, linux-kernel@vger.kernel.org, Leo Li,
	Rob Herring, David S . Miller,
	linux-arm-kernel@lists.infradead.org
In-Reply-To: <VI1PR04MB4880CD977A5D58DA0A7EE56696C60@VI1PR04MB4880.eurprd04.prod.outlook.com>

> >All the horrible casts go away, the driver is structured like every
> >other driver, sparse is probably happy, etc.
> >
> 
> This looks more like a matter cosmetic preferences.  I mean, I didn't
> notice anything "horrible" in the code so far.

#define bus_to_enetc_regs(bus)  (struct enetc_mdio_regs __iomem *)((bus)->priv)

You should not need a cast here, bus->priv is a void *. But bus->priv
is being abused to hold a __iomem pointer.

enetc_wr_reg(&regs->mdio_cfg, mdio_cfg);

This is also rather odd, passing the address of something to an IO
operator? I also don't know the C standard well enough to know if it
is guaranteed that:

struct enetc_mdio_regs {
        u32     mdio_cfg;       /* MDIO configuration and status */
        u32     mdio_ctl;       /* MDIO control */
        u32     mdio_data;      /* MDIO data */
        u32     mdio_addr;      /* MDIO address */
};

actually works. On a 64bit system is the compiler allowed to put in
padding to keep the u32 64 bit aligned?

> I actually find it more
> ugly to define a new structure with only one element inside, like:
> struct enetc_mdio_priv {
>        struct enetc_hw *hw;
> }

One advantage of this is that struct enetc_hw correctly has all the
__iomem attributes. All the casts to __iomem go away, and sparse is
happy.

> Anyway, if others already did this in the kernel, what can I do?

Clean it up. Make the code more readable and easy to maintain.

      Andrew

^ permalink raw reply

* Re: [PATCH 1/4] ARM: dts: stm32: add FMC2 NAND controller support on stm32mp157c
From: Alexandre Torgue @ 2019-07-24 16:38 UTC (permalink / raw)
  To: Christophe Kerello, robh+dt, mark.rutland, linux, olof, arnd
  Cc: linux-arm-kernel, devicetree, linux-stm32, mcoquelin.stm32,
	linux-kernel
In-Reply-To: <1561128590-14621-2-git-send-email-christophe.kerello@st.com>

Hi Christophe

On 6/21/19 4:49 PM, Christophe Kerello wrote:
> This patch adds FMC2 NAND controller support used by stm32mp157c SOC.
> 
> Signed-off-by: Christophe Kerello <christophe.kerello@st.com>
> ---
>   arch/arm/boot/dts/stm32mp157c.dtsi | 19 +++++++++++++++++++
>   1 file changed, 19 insertions(+)
> 
> diff --git a/arch/arm/boot/dts/stm32mp157c.dtsi b/arch/arm/boot/dts/stm32mp157c.dtsi
> index 0c4e6eb..f2bda28 100644
> --- a/arch/arm/boot/dts/stm32mp157c.dtsi
> +++ b/arch/arm/boot/dts/stm32mp157c.dtsi
> @@ -1239,6 +1239,25 @@
>   			dma-requests = <48>;
>   		};
>   
> +		fmc: nand-controller@58002000 {
> +			compatible = "st,stm32mp15-fmc2";
> +			reg = <0x58002000 0x1000>,
> +			      <0x80000000 0x1000>,
> +			      <0x88010000 0x1000>,
> +			      <0x88020000 0x1000>,
> +			      <0x81000000 0x1000>,
> +			      <0x89010000 0x1000>,
> +			      <0x89020000 0x1000>;
> +			interrupts = <GIC_SPI 48 IRQ_TYPE_LEVEL_HIGH>;
> +			dmas = <&mdma1 20 0x10 0x12000A02 0x0 0x0>,
> +			       <&mdma1 20 0x10 0x12000A08 0x0 0x0>,
> +			       <&mdma1 21 0x10 0x12000A0A 0x0 0x0>;

Please, don't use capital letter here.

> +			dma-names = "tx", "rx", "ecc";
> +			clocks = <&rcc FMC_K>;
> +			resets = <&rcc FMC_R>;
> +			status = "disabled";
> +		};
> +
>   		qspi: spi@58003000 {
>   			compatible = "st,stm32f469-qspi";
>   			reg = <0x58003000 0x1000>, <0x70000000 0x10000000>;
> 

^ permalink raw reply

* Re: [PATCH 1/2] media: i2c: dw9768: Add DT support and MAINTAINERS entry
From: Rob Herring @ 2019-07-24 16:37 UTC (permalink / raw)
  To: dongchun.zhu-NuS5LvNUpcJWk0Htik3J/w
  Cc: mark.rutland-5wv7dgnIgG8, devicetree-u79uwXL29TY76Z2rM5mHXA,
	srv_heupstream-NuS5LvNUpcJWk0Htik3J/w,
	shengnan.wang-NuS5LvNUpcJWk0Htik3J/w,
	tfiga-F7+t8E8rja9g9hUCZPvPmw, louis.kuo-NuS5LvNUpcJWk0Htik3J/w,
	sj.huang-NuS5LvNUpcJWk0Htik3J/w,
	linux-mediatek-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
	sakari.ailus-VuQAYsv1563Yd54FQh9/CA,
	matthias.bgg-Re5JQEeQqe8AvxtiuMwx3w,
	bingbu.cao-ral2JQCrhuEAvxtiuMwx3w, mchehab-DgEjT+Ai2ygdnm+yROfE0A,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
	linux-media-u79uwXL29TY76Z2rM5mHXA
In-Reply-To: <20190708100641.2702-2-dongchun.zhu-NuS5LvNUpcJWk0Htik3J/w@public.gmane.org>

On Mon, Jul 08, 2019 at 06:06:40PM +0800, dongchun.zhu-NuS5LvNUpcJWk0Htik3J/w@public.gmane.org wrote:
> From: Dongchun Zhu <dongchun.zhu-NuS5LvNUpcJWk0Htik3J/w@public.gmane.org>
> 
> Add the Devicetree binding documentation and MAINTAINERS entry for dw9768.
> 
> Signed-off-by: Dongchun Zhu <dongchun.zhu-NuS5LvNUpcJWk0Htik3J/w@public.gmane.org>
> ---
>  Documentation/devicetree/bindings/media/i2c/dongwoon,dw9768.txt | 9 +++++++++
>  MAINTAINERS                                                     | 7 +++++++
>  2 files changed, 16 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/media/i2c/dongwoon,dw9768.txt
> 
> diff --git a/Documentation/devicetree/bindings/media/i2c/dongwoon,dw9768.txt b/Documentation/devicetree/bindings/media/i2c/dongwoon,dw9768.txt
> new file mode 100644
> index 0000000..94b8ec3
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/media/i2c/dongwoon,dw9768.txt
> @@ -0,0 +1,9 @@
> +Dongwoon Anatech DW9768 camera voice coil lens driver
> +
> +DW9768 is a 10-bit DAC with current sink capability. It is intended
> +for driving voice coil lenses in camera modules.
> +
> +Mandatory properties:
> +
> +- compatible: "dongwoon,dw9768"
> +- reg: I²C slave address

Why not shared with the existing DW9807 binding? Or this could be a 
trivial device.

> diff --git a/MAINTAINERS b/MAINTAINERS
> index 5cfbea4..8f6ac93 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -4872,6 +4872,13 @@ S:	Maintained
>  F:	drivers/media/i2c/dw9714.c
>  F:	Documentation/devicetree/bindings/media/i2c/dongwoon,dw9714.txt
>  
> +DONGWOON DW9768 LENS VOICE COIL DRIVER
> +M:	Dongchun Zhu <dongchun.zhu-NuS5LvNUpcJWk0Htik3J/w@public.gmane.org>
> +L:	linux-media-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
> +T:	git git://linuxtv.org/media_tree.git
> +S:	Maintained
> +F:	Documentation/devicetree/bindings/media/i2c/dongwoon,dw9768.txt
> +
>  DONGWOON DW9807 LENS VOICE COIL DRIVER
>  M:	Sakari Ailus <sakari.ailus-VuQAYsv1563Yd54FQh9/CA@public.gmane.org>
>  L:	linux-media-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
> -- 
> 2.9.2
> 

^ permalink raw reply

* Re: [PATCH 1/6] dt-bindings: irqchip: Add PRUSS interrupt controller bindings
From: Rob Herring @ 2019-07-24 16:34 UTC (permalink / raw)
  Cc: Marc Zyngier, Thomas Gleixner, Jason Cooper, Tony Lindgren,
	Andrew F. Davis, Roger Quadros, Lokesh Vutla, Grygorii Strashko,
	Sekhar Nori, David Lechner, Murali Karicheri, devicetree,
	linux-omap, linux-arm-kernel, linux-kernel, Suman Anna
In-Reply-To: <20190708035243.12170-2-s-anna@ti.com>

On Sun, 7 Jul 2019 22:52:38 -0500, Suman Anna wrote:
> The Programmable Real-Time Unit Subsystem (PRUSS) contains an interrupt
> controller (INTC) that can handle various system input events and post
> interrupts back to the device-level initiators. The INTC can support
> upto 64 input events on most SoCs with individual control configuration
> and hardware prioritization. These events are mapped onto 10 interrupt
> lines through two levels of many-to-one mapping support. Different
> interrupt lines are routed to the individual PRU cores or to the
> host CPU or to other PRUSS instances.
> 
> The K3 AM65x and J721E SoCs have the next generation of the PRU-ICSS IP,
> commonly called ICSSG. The ICSSG interrupt controller on K3 SoCs provide
> a higher number of host interrupts (20 vs 10) and can handle an increased
> number of input events (160 vs 64) from various SoC interrupt sources.
> 
> Add the bindings document for these interrupt controllers on all the
> applicable SoCs. It covers the OMAP architecture SoCs - AM33xx, AM437x
> and AM57xx; the Keystone 2 architecture based 66AK2G SoC; the Davinci
> architecture based OMAPL138 SoCs, and the K3 architecture based AM65x
> and J721E SoCs.
> 
> Signed-off-by: Suman Anna <s-anna@ti.com>
> Signed-off-by: Andrew F. Davis <afd@ti.com>
> Signed-off-by: Roger Quadros <rogerq@ti.com>
> ---
> Prior version: https://patchwork.kernel.org/patch/10795771/
> 
>  .../interrupt-controller/ti,pruss-intc.txt    | 92 +++++++++++++++++++
>  1 file changed, 92 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/interrupt-controller/ti,pruss-intc.txt
> 

Reviewed-by: Rob Herring <robh@kernel.org>

^ permalink raw reply

* [PATCH 6/6] ASoC: codec2codec: fill some of the runtime stream parameters
From: Jerome Brunet @ 2019-07-24 16:24 UTC (permalink / raw)
  To: Mark Brown, Liam Girdwood, Kevin Hilman
  Cc: Jerome Brunet, alsa-devel, devicetree, linux-kernel,
	linux-amlogic
In-Reply-To: <20190724162405.6574-1-jbrunet@baylibre.com>

Set the information provided struct snd_soc_pcm_stream in the
struct snd_pcm_runtime of the codec to codec link.

Signed-off-by: Jerome Brunet <jbrunet@baylibre.com>
---
 sound/soc/soc-dapm.c | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c
index e0eedff5fe94..a6bc3eda275d 100644
--- a/sound/soc/soc-dapm.c
+++ b/sound/soc/soc-dapm.c
@@ -3892,6 +3892,11 @@ static int snd_soc_dai_link_event(struct snd_soc_dapm_widget *w,
 			dapm_update_dai_unlocked(substream, &params, sink);
 		}
 
+		runtime->format = params_format(&params);
+		runtime->subformat = params_subformat(&params);
+		runtime->channels = params_channels(&params);
+		runtime->rate = params_rate(&params);
+
 		break;
 
 	case SND_SOC_DAPM_POST_PMU:
-- 
2.21.0

^ permalink raw reply related

* [PATCH 5/6] ASoC: codec2codec: remove ephemeral variables
From: Jerome Brunet @ 2019-07-24 16:24 UTC (permalink / raw)
  To: Mark Brown, Liam Girdwood, Kevin Hilman
  Cc: Jerome Brunet, alsa-devel, devicetree, linux-kernel,
	linux-amlogic
In-Reply-To: <20190724162405.6574-1-jbrunet@baylibre.com>

Now that codec to codec links struct snd_soc_pcm_runtime have lasting pcm
and substreams, let's use them. Alsa allocate and keep the
struct snd_pcm_runtime as long as the link is powered.

Signed-off-by: Jerome Brunet <jbrunet@baylibre.com>
---
 sound/soc/soc-dapm.c | 86 ++++++++++++++++++++++++--------------------
 1 file changed, 48 insertions(+), 38 deletions(-)

diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c
index aa6e47beaec3..e0eedff5fe94 100644
--- a/sound/soc/soc-dapm.c
+++ b/sound/soc/soc-dapm.c
@@ -3802,48 +3802,45 @@ static int snd_soc_dai_link_event(struct snd_soc_dapm_widget *w,
 {
 	struct snd_soc_dapm_path *path;
 	struct snd_soc_dai *source, *sink;
-	struct snd_soc_pcm_runtime *rtd = w->priv;
 	const struct snd_soc_pcm_stream *config;
-	struct snd_pcm_substream substream;
+	struct snd_pcm_substream *substream = w->priv;
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
 	struct snd_pcm_hw_params params;
 	struct snd_pcm_runtime *runtime = NULL;
-	int ret = 0;
+	int ret = 0, saved_stream = substream->stream;
 
 	if (WARN_ON(list_empty(&w->edges[SND_SOC_DAPM_DIR_OUT]) ||
 		    list_empty(&w->edges[SND_SOC_DAPM_DIR_IN])))
 		return -EINVAL;
 
-	memset(&substream, 0, sizeof(substream));
-
-	/* Allocate a dummy snd_pcm_runtime for startup() and other ops() */
-	runtime = kzalloc(sizeof(*runtime), GFP_KERNEL);
-	if (!runtime) {
-		ret = -ENOMEM;
-		goto out;
-	}
-	substream.runtime = runtime;
-	substream.private_data = rtd;
-
 	switch (event) {
 	case SND_SOC_DAPM_PRE_PMU:
-		substream.stream = SNDRV_PCM_STREAM_CAPTURE;
+		runtime = kzalloc(sizeof(*runtime), GFP_KERNEL);
+		if (!runtime) {
+			ret = -ENOMEM;
+			goto out;
+		}
+		substream->runtime = runtime;
+
+		substream->stream = SNDRV_PCM_STREAM_CAPTURE;
 		snd_soc_dapm_widget_for_each_source_path(w, path) {
 			source = path->source->priv;
 
-			ret = snd_soc_dai_startup(source, &substream);
+			ret = snd_soc_dai_startup(source, substream);
 			if (ret < 0) {
 				dev_err(source->dev,
 					"ASoC: startup() failed: %d\n", ret);
 				goto out;
+
 			}
 			source->active++;
 		}
 
-		substream.stream = SNDRV_PCM_STREAM_PLAYBACK;
+		substream->stream = SNDRV_PCM_STREAM_PLAYBACK;
 		snd_soc_dapm_widget_for_each_sink_path(w, path) {
 			sink = path->sink->priv;
 
-			ret = snd_soc_dai_startup(sink, &substream);
+			ret = snd_soc_dai_startup(sink, substream);
 			if (ret < 0) {
 				dev_err(sink->dev,
 					"ASoC: startup() failed: %d\n", ret);
@@ -3852,6 +3849,8 @@ static int snd_soc_dai_link_event(struct snd_soc_dapm_widget *w,
 			sink->active++;
 		}
 
+		substream->hw_opened = 1;
+
 		/*
 		 * Note: getting the config after .startup() gives a chance to
 		 * either party on the link to alter the configuration if
@@ -3871,26 +3870,26 @@ static int snd_soc_dai_link_event(struct snd_soc_dapm_widget *w,
 			goto out;
 		}
 
-		substream.stream = SNDRV_PCM_STREAM_CAPTURE;
+		substream->stream = SNDRV_PCM_STREAM_CAPTURE;
 		snd_soc_dapm_widget_for_each_source_path(w, path) {
 			source = path->source->priv;
 
-			ret = soc_dai_hw_params(&substream, &params, source);
+			ret = soc_dai_hw_params(substream, &params, source);
 			if (ret < 0)
 				goto out;
 
-			dapm_update_dai_unlocked(&substream, &params, source);
+			dapm_update_dai_unlocked(substream, &params, source);
 		}
 
-		substream.stream = SNDRV_PCM_STREAM_PLAYBACK;
+		substream->stream = SNDRV_PCM_STREAM_PLAYBACK;
 		snd_soc_dapm_widget_for_each_sink_path(w, path) {
 			sink = path->sink->priv;
 
-			ret = soc_dai_hw_params(&substream, &params, sink);
+			ret = soc_dai_hw_params(substream, &params, sink);
 			if (ret < 0)
 				goto out;
 
-			dapm_update_dai_unlocked(&substream, &params, sink);
+			dapm_update_dai_unlocked(substream, &params, sink);
 		}
 
 		break;
@@ -3920,40 +3919,45 @@ static int snd_soc_dai_link_event(struct snd_soc_dapm_widget *w,
 			ret = 0;
 		}
 
-		substream.stream = SNDRV_PCM_STREAM_CAPTURE;
+		substream->stream = SNDRV_PCM_STREAM_CAPTURE;
 		snd_soc_dapm_widget_for_each_source_path(w, path) {
 			source = path->source->priv;
-			snd_soc_dai_hw_free(source, &substream);
+			snd_soc_dai_hw_free(source, substream);
 		}
 
-		substream.stream = SNDRV_PCM_STREAM_PLAYBACK;
+		substream->stream = SNDRV_PCM_STREAM_PLAYBACK;
 		snd_soc_dapm_widget_for_each_sink_path(w, path) {
 			sink = path->sink->priv;
-			snd_soc_dai_hw_free(sink, &substream);
+			snd_soc_dai_hw_free(sink, substream);
 		}
 
-		substream.stream = SNDRV_PCM_STREAM_CAPTURE;
+		substream->stream = SNDRV_PCM_STREAM_CAPTURE;
 		snd_soc_dapm_widget_for_each_source_path(w, path) {
 			source = path->source->priv;
 			source->active--;
-			snd_soc_dai_shutdown(source, &substream);
+			snd_soc_dai_shutdown(source, substream);
 		}
 
-		substream.stream = SNDRV_PCM_STREAM_PLAYBACK;
+		substream->stream = SNDRV_PCM_STREAM_PLAYBACK;
 		snd_soc_dapm_widget_for_each_sink_path(w, path) {
 			sink = path->sink->priv;
 			sink->active--;
-			snd_soc_dai_shutdown(sink, &substream);
+			snd_soc_dai_shutdown(sink, substream);
 		}
 		break;
 
+	case SND_SOC_DAPM_POST_PMD:
+		kfree(substream->runtime);
+		break;
+
 	default:
 		WARN(1, "Unknown event %d\n", event);
 		ret = -EINVAL;
 	}
 
 out:
-	kfree(runtime);
+	/* Restore the substream direction */
+	substream->stream = saved_stream;
 	return ret;
 }
 
@@ -4076,9 +4080,11 @@ snd_soc_dapm_alloc_kcontrol(struct snd_soc_card *card,
 }
 
 static struct snd_soc_dapm_widget *
-snd_soc_dapm_new_dai(struct snd_soc_card *card, struct snd_soc_pcm_runtime *rtd,
+snd_soc_dapm_new_dai(struct snd_soc_card *card,
+		     struct snd_pcm_substream *substream,
 		     char *id)
 {
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
 	struct snd_soc_dapm_widget template;
 	struct snd_soc_dapm_widget *w;
 	const char **w_param_text;
@@ -4097,7 +4103,7 @@ snd_soc_dapm_new_dai(struct snd_soc_card *card, struct snd_soc_pcm_runtime *rtd,
 	template.name = link_name;
 	template.event = snd_soc_dai_link_event;
 	template.event_flags = SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU |
-		SND_SOC_DAPM_PRE_PMD;
+		SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD;
 	template.kcontrol_news = NULL;
 
 	/* allocate memory for control, only in case of multiple configs */
@@ -4132,7 +4138,7 @@ snd_soc_dapm_new_dai(struct snd_soc_card *card, struct snd_soc_pcm_runtime *rtd,
 		goto outfree_kcontrol_news;
 	}
 
-	w->priv = rtd;
+	w->priv = substream;
 
 	return w;
 
@@ -4254,6 +4260,8 @@ static void dapm_connect_dai_link_widgets(struct snd_soc_card *card,
 	struct snd_soc_dai *codec_dai;
 	struct snd_soc_dapm_widget *playback = NULL, *capture = NULL;
 	struct snd_soc_dapm_widget *codec, *playback_cpu, *capture_cpu;
+	struct snd_pcm_substream *substream;
+	struct snd_pcm_str *streams = rtd->pcm->streams;
 	int i;
 
 	if (rtd->dai_link->params) {
@@ -4272,7 +4280,8 @@ static void dapm_connect_dai_link_widgets(struct snd_soc_card *card,
 
 		if (playback_cpu && codec) {
 			if (!playback) {
-				playback = snd_soc_dapm_new_dai(card, rtd,
+				substream = streams[SNDRV_PCM_STREAM_PLAYBACK].substream;
+				playback = snd_soc_dapm_new_dai(card, substream,
 								"playback");
 				if (IS_ERR(playback)) {
 					dev_err(rtd->dev,
@@ -4301,7 +4310,8 @@ static void dapm_connect_dai_link_widgets(struct snd_soc_card *card,
 
 		if (codec && capture_cpu) {
 			if (!capture) {
-				capture = snd_soc_dapm_new_dai(card, rtd,
+				substream = streams[SNDRV_PCM_STREAM_CAPTURE].substream;
+				capture = snd_soc_dapm_new_dai(card, substream,
 							       "capture");
 				if (IS_ERR(capture)) {
 					dev_err(rtd->dev,
-- 
2.21.0

^ permalink raw reply related

* [PATCH 4/6] ASoC: create pcm for codec2codec links as well
From: Jerome Brunet @ 2019-07-24 16:24 UTC (permalink / raw)
  To: Mark Brown, Liam Girdwood, Kevin Hilman
  Cc: Jerome Brunet, alsa-devel, devicetree, linux-kernel,
	linux-amlogic
In-Reply-To: <20190724162405.6574-1-jbrunet@baylibre.com>

At the moment, codec to codec links uses an ephemeral variable for
the struct snd_pcm_substream. Also the struct snd_soc_pcm_runtime
does not have real struct snd_pcm.

This might a problem if the functions used by a codec on codec to
codec link expect these structures to exist, and keep on existing
during the life of the codec.

For example, it is the case of the hdmi-codec, which uses
snd_pcm_add_chmap_ctls(). For the controls to works, the pcm and
substream must to exist.

This change is first step, it create pcm (and substreams) for codec
to codec links, in the same way as dpcm backend links.

Signed-off-by: Jerome Brunet <jbrunet@baylibre.com>
---
 sound/soc/soc-core.c | 42 ++++++++++++------------------------------
 sound/soc/soc-pcm.c  | 35 ++++++++++++++++++++++++++++++++---
 2 files changed, 44 insertions(+), 33 deletions(-)

diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c
index 7ecfe641ca46..9e8eb93c8a3f 100644
--- a/sound/soc/soc-core.c
+++ b/sound/soc/soc-core.c
@@ -447,16 +447,6 @@ static void snd_soc_flush_all_delayed_work(struct snd_soc_card *card)
 		flush_delayed_work(&rtd->delayed_work);
 }
 
-static void codec2codec_close_delayed_work(struct work_struct *work)
-{
-	/*
-	 * Currently nothing to do for c2c links
-	 * Since c2c links are internal nodes in the DAPM graph and
-	 * don't interface with the outside world or application layer
-	 * we don't have to do any special handling on close.
-	 */
-}
-
 #ifdef CONFIG_PM_SLEEP
 /* powers down audio subsystem for suspend */
 int snd_soc_suspend(struct device *dev)
@@ -1552,27 +1542,19 @@ static int soc_probe_link_dais(struct snd_soc_card *card,
 		return ret;
 	}
 
-	if (!dai_link->params) {
-		/* create the pcm */
-		ret = soc_new_pcm(rtd, num);
-		if (ret < 0) {
-			dev_err(card->dev, "ASoC: can't create pcm %s :%d\n",
-				dai_link->stream_name, ret);
-			return ret;
-		}
-		ret = soc_link_dai_pcm_new(&cpu_dai, 1, rtd);
-		if (ret < 0)
-			return ret;
-		ret = soc_link_dai_pcm_new(rtd->codec_dais,
-					   rtd->num_codecs, rtd);
-		if (ret < 0)
-			return ret;
-	} else {
-		INIT_DELAYED_WORK(&rtd->delayed_work,
-				  codec2codec_close_delayed_work);
+	/* create the pcm */
+	ret = soc_new_pcm(rtd, num);
+	if (ret < 0) {
+		dev_err(card->dev, "ASoC: can't create pcm %s :%d\n",
+			dai_link->stream_name, ret);
+		return ret;
 	}
-
-	return 0;
+	ret = soc_link_dai_pcm_new(&cpu_dai, 1, rtd);
+	if (ret < 0)
+		return ret;
+	ret = soc_link_dai_pcm_new(rtd->codec_dais,
+				   rtd->num_codecs, rtd);
+	return ret;
 }
 
 static int soc_bind_aux_dev(struct snd_soc_card *card, int num)
diff --git a/sound/soc/soc-pcm.c b/sound/soc/soc-pcm.c
index fabeac164a6c..30264bc592f6 100644
--- a/sound/soc/soc-pcm.c
+++ b/sound/soc/soc-pcm.c
@@ -678,6 +678,16 @@ static void close_delayed_work(struct work_struct *work)
 	mutex_unlock(&rtd->pcm_mutex);
 }
 
+static void codec2codec_close_delayed_work(struct work_struct *work)
+{
+	/*
+	 * Currently nothing to do for c2c links
+	 * Since c2c links are internal nodes in the DAPM graph and
+	 * don't interface with the outside world or application layer
+	 * we don't have to do any special handling on close.
+	 */
+}
+
 /*
  * Called by ALSA when a PCM substream is closed. Private data can be
  * freed here. The cpu DAI, codec DAI, machine and components are also
@@ -3011,6 +3021,12 @@ int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num)
 		playback = rtd->dai_link->dpcm_playback;
 		capture = rtd->dai_link->dpcm_capture;
 	} else {
+		/* Adapt stream for codec2codec links */
+		struct snd_soc_pcm_stream *cpu_capture = rtd->dai_link->params ?
+			&cpu_dai->driver->playback : &cpu_dai->driver->capture;
+		struct snd_soc_pcm_stream *cpu_playback = rtd->dai_link->params ?
+			&cpu_dai->driver->capture : &cpu_dai->driver->playback;
+
 		for_each_rtd_codec_dai(rtd, i, codec_dai) {
 			if (snd_soc_dai_stream_valid(codec_dai, SNDRV_PCM_STREAM_PLAYBACK) &&
 			    snd_soc_dai_stream_valid(cpu_dai,   SNDRV_PCM_STREAM_PLAYBACK))
@@ -3019,6 +3035,9 @@ int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num)
 			    snd_soc_dai_stream_valid(cpu_dai,   SNDRV_PCM_STREAM_CAPTURE))
 				capture = 1;
 		}
+
+		capture = capture && cpu_capture->channels_min;
+		playback = playback && cpu_playback->channels_min;
 	}
 
 	if (rtd->dai_link->playback_only) {
@@ -3032,7 +3051,13 @@ int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num)
 	}
 
 	/* create the PCM */
-	if (rtd->dai_link->no_pcm) {
+	if (rtd->dai_link->params) {
+		snprintf(new_name, sizeof(new_name), "codec2codec(%s)",
+			 rtd->dai_link->stream_name);
+
+		ret = snd_pcm_new_internal(rtd->card->snd_card, new_name, num,
+					   playback, capture, &pcm);
+	} else if (rtd->dai_link->no_pcm) {
 		snprintf(new_name, sizeof(new_name), "(%s)",
 			rtd->dai_link->stream_name);
 
@@ -3059,13 +3084,17 @@ int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num)
 	dev_dbg(rtd->card->dev, "ASoC: registered pcm #%d %s\n",num, new_name);
 
 	/* DAPM dai link stream work */
-	INIT_DELAYED_WORK(&rtd->delayed_work, close_delayed_work);
+	if (rtd->dai_link->params)
+		INIT_DELAYED_WORK(&rtd->delayed_work,
+				  codec2codec_close_delayed_work);
+	else
+		INIT_DELAYED_WORK(&rtd->delayed_work, close_delayed_work);
 
 	pcm->nonatomic = rtd->dai_link->nonatomic;
 	rtd->pcm = pcm;
 	pcm->private_data = rtd;
 
-	if (rtd->dai_link->no_pcm) {
+	if (rtd->dai_link->no_pcm || rtd->dai_link->params) {
 		if (playback)
 			pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream->private_data = rtd;
 		if (capture)
-- 
2.21.0

^ permalink raw reply related

* [PATCH 3/6] ASoC: codec2codec: deal with params when necessary
From: Jerome Brunet @ 2019-07-24 16:24 UTC (permalink / raw)
  To: Mark Brown, Liam Girdwood, Kevin Hilman
  Cc: Jerome Brunet, alsa-devel, devicetree, linux-kernel,
	linux-amlogic
In-Reply-To: <20190724162405.6574-1-jbrunet@baylibre.com>

When there is an event on codec to codec dai_link, we only need to deal
with params if the event is SND_SOC_DAPM_PRE_PMU, when .hw_params() is
called. For the other events, it is useless.

Also, params does not need to be dynamically allocated as it does not
need to survive the event.

Last, dealing with the codec to codec params just before calling
.hw_params() callbacks give change to either party on the link to alter
params content in .startup(), which might be useful in some cases

Signed-off-by: Jerome Brunet <jbrunet@baylibre.com>
---
 sound/soc/soc-dapm.c | 81 ++++++++++++++++++++++++++++----------------
 1 file changed, 51 insertions(+), 30 deletions(-)

diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c
index d20cd89513a4..aa6e47beaec3 100644
--- a/sound/soc/soc-dapm.c
+++ b/sound/soc/soc-dapm.c
@@ -3764,25 +3764,12 @@ int snd_soc_dapm_new_controls(struct snd_soc_dapm_context *dapm,
 }
 EXPORT_SYMBOL_GPL(snd_soc_dapm_new_controls);
 
-static int snd_soc_dai_link_event(struct snd_soc_dapm_widget *w,
-				  struct snd_kcontrol *kcontrol, int event)
+static int
+snd_soc_dai_link_prepare_params(struct snd_soc_dapm_widget *w,
+				struct snd_pcm_hw_params *params,
+				const struct snd_soc_pcm_stream *config)
 {
-	struct snd_soc_dapm_path *path;
-	struct snd_soc_dai *source, *sink;
-	struct snd_soc_pcm_runtime *rtd = w->priv;
-	const struct snd_soc_pcm_stream *config;
-	struct snd_pcm_substream substream;
-	struct snd_pcm_hw_params *params = NULL;
-	struct snd_pcm_runtime *runtime = NULL;
 	unsigned int fmt;
-	int ret = 0;
-
-	config = rtd->dai_link->params + rtd->params_select;
-
-	if (WARN_ON(!config) ||
-	    WARN_ON(list_empty(&w->edges[SND_SOC_DAPM_DIR_OUT]) ||
-		    list_empty(&w->edges[SND_SOC_DAPM_DIR_IN])))
-		return -EINVAL;
 
 	/* Be a little careful as we don't want to overflow the mask array */
 	if (config->formats) {
@@ -3791,26 +3778,41 @@ static int snd_soc_dai_link_event(struct snd_soc_dapm_widget *w,
 		dev_warn(w->dapm->dev, "ASoC: Invalid format %llx specified\n",
 			 config->formats);
 		fmt = 0;
-	}
 
-	/* Currently very limited parameter selection */
-	params = kzalloc(sizeof(*params), GFP_KERNEL);
-	if (!params) {
-		ret = -ENOMEM;
-		goto out;
+		return -EINVAL;
 	}
-	snd_mask_set(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), fmt);
 
+	memset(params, 0, sizeof(*params));
+
+	snd_mask_set(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), fmt);
 	hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE)->min =
 		config->rate_min;
 	hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE)->max =
 		config->rate_max;
-
 	hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS)->min
 		= config->channels_min;
 	hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS)->max
 		= config->channels_max;
 
+	return 0;
+}
+
+static int snd_soc_dai_link_event(struct snd_soc_dapm_widget *w,
+				  struct snd_kcontrol *kcontrol, int event)
+{
+	struct snd_soc_dapm_path *path;
+	struct snd_soc_dai *source, *sink;
+	struct snd_soc_pcm_runtime *rtd = w->priv;
+	const struct snd_soc_pcm_stream *config;
+	struct snd_pcm_substream substream;
+	struct snd_pcm_hw_params params;
+	struct snd_pcm_runtime *runtime = NULL;
+	int ret = 0;
+
+	if (WARN_ON(list_empty(&w->edges[SND_SOC_DAPM_DIR_OUT]) ||
+		    list_empty(&w->edges[SND_SOC_DAPM_DIR_IN])))
+		return -EINVAL;
+
 	memset(&substream, 0, sizeof(substream));
 
 	/* Allocate a dummy snd_pcm_runtime for startup() and other ops() */
@@ -3850,27 +3852,47 @@ static int snd_soc_dai_link_event(struct snd_soc_dapm_widget *w,
 			sink->active++;
 		}
 
+		/*
+		 * Note: getting the config after .startup() gives a chance to
+		 * either party on the link to alter the configuration if
+		 * necessary
+		 */
+		config = rtd->dai_link->params + rtd->params_select;
+		if (WARN_ON(!config)) {
+			dev_err(w->dapm->dev, "ASoC: link config missing\n");
+			ret = -EINVAL;
+			goto out;
+		}
+
+		ret = snd_soc_dai_link_prepare_params(w, &params, config);
+		if (ret < 0) {
+			dev_err(w->dapm->dev, "ASoC: link params prepare failed: %d\n",
+				ret);
+			goto out;
+		}
+
 		substream.stream = SNDRV_PCM_STREAM_CAPTURE;
 		snd_soc_dapm_widget_for_each_source_path(w, path) {
 			source = path->source->priv;
 
-			ret = soc_dai_hw_params(&substream, params, source);
+			ret = soc_dai_hw_params(&substream, &params, source);
 			if (ret < 0)
 				goto out;
 
-			dapm_update_dai_unlocked(&substream, params, source);
+			dapm_update_dai_unlocked(&substream, &params, source);
 		}
 
 		substream.stream = SNDRV_PCM_STREAM_PLAYBACK;
 		snd_soc_dapm_widget_for_each_sink_path(w, path) {
 			sink = path->sink->priv;
 
-			ret = soc_dai_hw_params(&substream, params, sink);
+			ret = soc_dai_hw_params(&substream, &params, sink);
 			if (ret < 0)
 				goto out;
 
-			dapm_update_dai_unlocked(&substream, params, sink);
+			dapm_update_dai_unlocked(&substream, &params, sink);
 		}
+
 		break;
 
 	case SND_SOC_DAPM_POST_PMU:
@@ -3932,7 +3954,6 @@ static int snd_soc_dai_link_event(struct snd_soc_dapm_widget *w,
 
 out:
 	kfree(runtime);
-	kfree(params);
 	return ret;
 }
 
-- 
2.21.0

^ permalink raw reply related

* [PATCH 2/6] ASoC: codec2codec: name link using stream direction
From: Jerome Brunet @ 2019-07-24 16:24 UTC (permalink / raw)
  To: Mark Brown, Liam Girdwood, Kevin Hilman
  Cc: Jerome Brunet, alsa-devel, devicetree, linux-kernel,
	linux-amlogic
In-Reply-To: <20190724162405.6574-1-jbrunet@baylibre.com>

At the moment, codec to codec dai link widgets are named after the
cpu dai and the 1st codec valid on the link. This might be confusing
if there is multiple valid codecs on the link for one stream
direction.

Instead, use the dai link name and the stream direction to name the
the dai link widget

Signed-off-by: Jerome Brunet <jbrunet@baylibre.com>
---
 sound/soc/soc-dapm.c | 12 ++++--------
 1 file changed, 4 insertions(+), 8 deletions(-)

diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c
index 5348abda7ce2..d20cd89513a4 100644
--- a/sound/soc/soc-dapm.c
+++ b/sound/soc/soc-dapm.c
@@ -4056,8 +4056,7 @@ snd_soc_dapm_alloc_kcontrol(struct snd_soc_card *card,
 
 static struct snd_soc_dapm_widget *
 snd_soc_dapm_new_dai(struct snd_soc_card *card, struct snd_soc_pcm_runtime *rtd,
-		     struct snd_soc_dapm_widget *source,
-		     struct snd_soc_dapm_widget *sink)
+		     char *id)
 {
 	struct snd_soc_dapm_widget template;
 	struct snd_soc_dapm_widget *w;
@@ -4067,7 +4066,7 @@ snd_soc_dapm_new_dai(struct snd_soc_card *card, struct snd_soc_pcm_runtime *rtd,
 	int ret;
 
 	link_name = devm_kasprintf(card->dev, GFP_KERNEL, "%s-%s",
-				   source->name, sink->name);
+				   rtd->dai_link->name, id);
 	if (!link_name)
 		return ERR_PTR(-ENOMEM);
 
@@ -4247,15 +4246,13 @@ static void dapm_connect_dai_link_widgets(struct snd_soc_card *card,
 	}
 
 	for_each_rtd_codec_dai(rtd, i, codec_dai) {
-
 		/* connect BE DAI playback if widgets are valid */
 		codec = codec_dai->playback_widget;
 
 		if (playback_cpu && codec) {
 			if (!playback) {
 				playback = snd_soc_dapm_new_dai(card, rtd,
-								playback_cpu,
-								codec);
+								"playback");
 				if (IS_ERR(playback)) {
 					dev_err(rtd->dev,
 						"ASoC: Failed to create DAI %s: %ld\n",
@@ -4284,8 +4281,7 @@ static void dapm_connect_dai_link_widgets(struct snd_soc_card *card,
 		if (codec && capture_cpu) {
 			if (!capture) {
 				capture = snd_soc_dapm_new_dai(card, rtd,
-							       codec,
-							       capture_cpu);
+							       "capture");
 				if (IS_ERR(capture)) {
 					dev_err(rtd->dev,
 						"ASoC: Failed to create DAI %s: %ld\n",
-- 
2.21.0

^ permalink raw reply related

* [PATCH 1/6] ASoC: codec2codec: run callbacks in order
From: Jerome Brunet @ 2019-07-24 16:24 UTC (permalink / raw)
  To: Mark Brown, Liam Girdwood, Kevin Hilman
  Cc: Jerome Brunet, alsa-devel, devicetree, linux-kernel,
	linux-amlogic
In-Reply-To: <20190724162405.6574-1-jbrunet@baylibre.com>

When handling dai_link events on codec to codec links, run all .startup()
callbacks on sinks and sources before running any .hw_params(). Same goes
for hw_free() and shutdown(). This is closer to the behavior of regular
dai links

Signed-off-by: Jerome Brunet <jbrunet@baylibre.com>
---
 sound/soc/soc-dapm.c | 38 ++++++++++++++++++++++++++++----------
 1 file changed, 28 insertions(+), 10 deletions(-)

diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c
index 1d04612601ad..5348abda7ce2 100644
--- a/sound/soc/soc-dapm.c
+++ b/sound/soc/soc-dapm.c
@@ -3835,11 +3835,6 @@ static int snd_soc_dai_link_event(struct snd_soc_dapm_widget *w,
 				goto out;
 			}
 			source->active++;
-			ret = snd_soc_dai_hw_params(source, &substream, params);
-			if (ret < 0)
-				goto out;
-
-			dapm_update_dai_unlocked(&substream, params, source);
 		}
 
 		substream.stream = SNDRV_PCM_STREAM_PLAYBACK;
@@ -3853,7 +3848,24 @@ static int snd_soc_dai_link_event(struct snd_soc_dapm_widget *w,
 				goto out;
 			}
 			sink->active++;
-			ret = snd_soc_dai_hw_params(sink, &substream, params);
+		}
+
+		substream.stream = SNDRV_PCM_STREAM_CAPTURE;
+		snd_soc_dapm_widget_for_each_source_path(w, path) {
+			source = path->source->priv;
+
+			ret = soc_dai_hw_params(&substream, params, source);
+			if (ret < 0)
+				goto out;
+
+			dapm_update_dai_unlocked(&substream, params, source);
+		}
+
+		substream.stream = SNDRV_PCM_STREAM_PLAYBACK;
+		snd_soc_dapm_widget_for_each_sink_path(w, path) {
+			sink = path->sink->priv;
+
+			ret = soc_dai_hw_params(&substream, params, sink);
 			if (ret < 0)
 				goto out;
 
@@ -3889,9 +3901,18 @@ static int snd_soc_dai_link_event(struct snd_soc_dapm_widget *w,
 		substream.stream = SNDRV_PCM_STREAM_CAPTURE;
 		snd_soc_dapm_widget_for_each_source_path(w, path) {
 			source = path->source->priv;
-
 			snd_soc_dai_hw_free(source, &substream);
+		}
 
+		substream.stream = SNDRV_PCM_STREAM_PLAYBACK;
+		snd_soc_dapm_widget_for_each_sink_path(w, path) {
+			sink = path->sink->priv;
+			snd_soc_dai_hw_free(sink, &substream);
+		}
+
+		substream.stream = SNDRV_PCM_STREAM_CAPTURE;
+		snd_soc_dapm_widget_for_each_source_path(w, path) {
+			source = path->source->priv;
 			source->active--;
 			snd_soc_dai_shutdown(source, &substream);
 		}
@@ -3899,9 +3920,6 @@ static int snd_soc_dai_link_event(struct snd_soc_dapm_widget *w,
 		substream.stream = SNDRV_PCM_STREAM_PLAYBACK;
 		snd_soc_dapm_widget_for_each_sink_path(w, path) {
 			sink = path->sink->priv;
-
-			snd_soc_dai_hw_free(sink, &substream);
-
 			sink->active--;
 			snd_soc_dai_shutdown(sink, &substream);
 		}
-- 
2.21.0

^ permalink raw reply related

* [PATCH 0/6] ASoC: improve codec to codec link support
From: Jerome Brunet @ 2019-07-24 16:23 UTC (permalink / raw)
  To: Mark Brown, Liam Girdwood, Kevin Hilman
  Cc: Jerome Brunet, alsa-devel, devicetree, linux-kernel,
	linux-amlogic

As explained in this previous series [0], on Amlogic, we are using codec to
codec links to deal with the glue which is between the i2s backends and the
synopsys hdmi controller.

This worked well until I tried to .get_eld() support in the dw-hdmi-i2s
driver.  Doing so adds channel mapping controls to the codec. This shown
several problem

1) .pcm_new() is not called on codec to codec links.
   struct snd_soc_pcm_runtime do not even have a valid .pcm
2) struct snd_pcm_substream and struct snd_pcm_runtime are ephemeral
   This is a problem if a control needs to access them

The goal of this patchset is to resolve the above issues and improve the
codec to codec link support enough to correctly handle the hdmi-codec

The support of these codec to codec links could probably be improved in the
future to behave like any other link and use soc_pcm_open(),
soc_pcm_hw_params(), etc...

The challenge lies in the dapm mutex. The soc_pcm call dapm function locking
this mutex but the dapm mutex is already held in snd_soc_dai_link_event()

[0]: https://lkml.kernel.org/r/20190515131858.32130-1-jbrunet@baylibre.com

Jerome Brunet (6):
  ASoC: codec2codec: run callbacks in order
  ASoC: codec2codec: name link using stream direction
  ASoC: codec2codec: deal with params when necessary
  ASoC: create pcm for codec2codec links as well
  ASoC: codec2codec: remove ephemeral variables
  ASoC: codec2codec: fill some of the runtime stream parameters

 sound/soc/soc-core.c |  42 +++-------
 sound/soc/soc-dapm.c | 186 +++++++++++++++++++++++++++----------------
 sound/soc/soc-pcm.c  |  35 +++++++-
 3 files changed, 162 insertions(+), 101 deletions(-)

-- 
2.21.0

^ permalink raw reply

* Re: [PATCH 16/60] dt-bindings: Add vendor prefix for LG Display
From: Rob Herring @ 2019-07-24 16:22 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: Mark Rutland, devicetree, Maxime Ripard, Sebastian Reichel,
	dri-devel, Tomi Valkeinen, Sean Paul
In-Reply-To: <20190707181937.6250-13-laurent.pinchart@ideasonboard.com>

On Sun,  7 Jul 2019 21:18:53 +0300, Laurent Pinchart wrote:
> LG Display is an LCD display manufacturer. Originally formed as a joint
> venture by LG Electronics and Philips Electronics, it was formerly known
> as LG.Philips LCD, hence the DT vendor prefix lgphilips (which is
> already in active use in the kernel).
> 
> More information is available at
> https://en.wikipedia.org/wiki/LG_Display.
> 
> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> ---
>  Documentation/devicetree/bindings/vendor-prefixes.yaml | 2 ++
>  1 file changed, 2 insertions(+)
> 

Reviewed-by: Rob Herring <robh@kernel.org>
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply

* Re: [PATCH 1/6] dt-bindings: interrupt-controller: Document RTD129x
From: Rob Herring @ 2019-07-24 16:21 UTC (permalink / raw)
  To: Aleix Roca Nonell
  Cc: Andreas Färber, Mark Rutland, Thomas Gleixner, Jason Cooper,
	Marc Zyngier, Matthias Brugger, linux-arm-kernel, devicetree,
	linux-kernel
In-Reply-To: <20190707132246.GB13340@arks.localdomain>

On Sun, Jul 07, 2019 at 03:22:46PM +0200, Aleix Roca Nonell wrote:
> Add binding for Realtek RTD129x interrupt controller.
> 
> Signed-off-by: Aleix Roca Nonell <kernelrocks@gmail.com>
> ---
>  .../realtek,rtd129x-intc.txt                  | 24 +++++++++++++++++++
>  1 file changed, 24 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/interrupt-controller/realtek,rtd129x-intc.txt
> 
> diff --git a/Documentation/devicetree/bindings/interrupt-controller/realtek,rtd129x-intc.txt b/Documentation/devicetree/bindings/interrupt-controller/realtek,rtd129x-intc.txt
> new file mode 100644
> index 000000000000..3ebb7c02afe5
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/interrupt-controller/realtek,rtd129x-intc.txt
> @@ -0,0 +1,24 @@
> +Realtek RTD129x IRQ Interrupt Controller
> +=======================================
> +
> +Required properties:
> +
> +- compatible           :  Should be one of the following:
> +                          - "realtek,rtd129x-intc-misc"
> +                          - "realtek,rtd129x-intc-iso"

Don't use wildcards in compatible strings.

> +- reg                  :  Specifies the address of the ISR, IER and Unmask
> +                          register in couples of "address length".
> +- interrupts           :  Specifies the interrupt line which is mux'ed.
> +- interrupt-controller :  Presence indicates the node as interrupt controller.
> +- #interrupt-cells     :  Shall be 1. See common bindings in interrupt.txt.
> +
> +
> +Example:
> +
> +	interrupt-controller@98007000 {
> +		compatible = "realtek,rtd129x-iso-irq-mux";
> +		reg = <0x98007000 0x4 0x98007040 0x4 0x98007004 0x4>;

What's in the holes?

> +		interrupts = <GIC_SPI 41 IRQ_TYPE_LEVEL_HIGH>;
> +		interrupt-controller;
> +		#interrupt-cells = <1>;
> +	};
> -- 
> 2.21.0
> 

^ permalink raw reply

* Re: [PATCH 1/5] dt-bindings: clock: Add Bitmain BM1880 SoC clock controller binding
From: Rob Herring @ 2019-07-24 16:18 UTC (permalink / raw)
  To: Manivannan Sadhasivam
  Cc: sboyd, mturquette, linux-clk, linux-arm-kernel, linux-kernel,
	devicetree, haitao.suo, darren.tsao, fisher.cheng, alec.lin
In-Reply-To: <20190705151440.20844-2-manivannan.sadhasivam@linaro.org>

On Fri, Jul 05, 2019 at 08:44:36PM +0530, Manivannan Sadhasivam wrote:
> Add devicetree binding for Bitmain BM1880 SoC clock controller.
> 
> Signed-off-by: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
> ---
>  .../bindings/clock/bitmain,bm1880-clk.txt     | 47 +++++++++++
>  include/dt-bindings/clock/bm1880-clock.h      | 82 +++++++++++++++++++
>  2 files changed, 129 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/clock/bitmain,bm1880-clk.txt
>  create mode 100644 include/dt-bindings/clock/bm1880-clock.h
> 
> diff --git a/Documentation/devicetree/bindings/clock/bitmain,bm1880-clk.txt b/Documentation/devicetree/bindings/clock/bitmain,bm1880-clk.txt
> new file mode 100644
> index 000000000000..9c967095d430
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/clock/bitmain,bm1880-clk.txt
> @@ -0,0 +1,47 @@
> +* Bitmain BM1880 Clock Controller
> +
> +The Bitmain BM1880 clock controler generates and supplies clock to

checkpatch.pl reports a spelling error...

> +various peripherals within the SoC.
> +
> +Required Properties:
> +
> +- compatible: Should be "bitmain,bm1880-clk"
> +- reg :	Register address and size of PLL and SYS control domains
> +- reg-names : Register domain names: "pll" and "sys"
> +- clocks : Phandle of the input reference clock.
> +- #clock-cells: Should be 1.
> +
> +Each clock is assigned an identifier, and client nodes can use this identifier
> +to specify the clock which they consume.
> +
> +All available clocks are defined as preprocessor macros in corresponding
> +dt-bindings/clock/bm1880-clock.h header and can be used in device tree sources.
> +
> +External clocks:
> +
> +The osc clock used as the input for the plls is generated outside the SoC.
> +It is expected that it is defined using standard clock bindings as "osc".
> +
> +Example: 
> +
> +        clk: clock-controller@800 {

Usually the unit-address is from the first entry.

> +                compatible = "bitmain,bm1880-clk";
> +                reg = <0xe8 0x0c>,<0x800 0xb0>;

space                                ^

> +                reg-names = "pll", "sys";
> +                clocks = <&osc>;
> +                #clock-cells = <1>;
> +        };
> +
> +Example: UART controller node that consumes clock generated by the clock
> +controller:
> +
> +        uart0: serial@58018000 {
> +                compatible = "snps,dw-apb-uart";
> +                reg = <0x0 0x58018000 0x0 0x2000>;
> +                clocks = <&clk BM1880_CLK_UART_500M>;
> +                         <&clk BM1880_CLK_APB_UART>;
> +                clock-names = "baudclk", "apb_pclk";
> +                interrupts = <GIC_SPI 9 IRQ_TYPE_LEVEL_HIGH>;
> +                reg-shift = <2>;
> +                reg-io-width = <4>;
> +        };
> diff --git a/include/dt-bindings/clock/bm1880-clock.h b/include/dt-bindings/clock/bm1880-clock.h
> new file mode 100644
> index 000000000000..764472b9a4fd
> --- /dev/null
> +++ b/include/dt-bindings/clock/bm1880-clock.h
> @@ -0,0 +1,82 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Device Tree binding constants for Bitmain BM1880 SoC
> + *
> + * Copyright (c) 2019 Linaro Ltd.
> + */
> +
> +#ifndef __DT_BINDINGS_CLOCK_BM1880_H
> +#define __DT_BINDINGS_CLOCK_BM1880_H
> +
> +#define BM1880_CLK_OSC			0
> +#define BM1880_CLK_MPLL			1
> +#define BM1880_CLK_SPLL			2
> +#define BM1880_CLK_FPLL			3
> +#define BM1880_CLK_DDRPLL 		4

space before tab

> +#define BM1880_CLK_A53			5
> +#define BM1880_CLK_50M_A53		6
> +#define BM1880_CLK_AHB_ROM		7
> +#define BM1880_CLK_AXI_SRAM		8
> +#define BM1880_CLK_DDR_AXI		9
> +#define BM1880_CLK_EFUSE		10
> +#define BM1880_CLK_APB_EFUSE		11
> +#define BM1880_CLK_AXI5_EMMC		12
> +#define BM1880_CLK_EMMC			13
> +#define BM1880_CLK_100K_EMMC		14
> +#define BM1880_CLK_AXI5_SD		15
> +#define BM1880_CLK_SD			16
> +#define BM1880_CLK_100K_SD		17
> +#define BM1880_CLK_500M_ETH0		18
> +#define BM1880_CLK_AXI4_ETH0		19
> +#define BM1880_CLK_500M_ETH1		20
> +#define BM1880_CLK_AXI4_ETH1		21
> +#define BM1880_CLK_AXI1_GDMA		22
> +#define BM1880_CLK_APB_GPIO		23
> +#define BM1880_CLK_APB_GPIO_INTR	24
> +#define BM1880_CLK_GPIO_DB		25
> +#define BM1880_CLK_AXI1_MINER		26
> +#define BM1880_CLK_AHB_SF		27
> +#define BM1880_CLK_SDMA_AXI		28
> +#define BM1880_CLK_SDMA_AUD		29
> +#define BM1880_CLK_APB_I2C		30
> +#define BM1880_CLK_APB_WDT		31
> +#define BM1880_CLK_APB_JPEG		32
> +#define BM1880_CLK_JPEG_AXI		33
> +#define BM1880_CLK_AXI5_NF		34
> +#define BM1880_CLK_APB_NF		35
> +#define BM1880_CLK_NF			36
> +#define BM1880_CLK_APB_PWM		37
> +#define BM1880_CLK_DIV_0_RV		38
> +#define BM1880_CLK_DIV_1_RV		39
> +#define BM1880_CLK_MUX_RV		40
> +#define BM1880_CLK_RV			41
> +#define BM1880_CLK_APB_SPI		42
> +#define BM1880_CLK_TPU_AXI		43
> +#define BM1880_CLK_DIV_UART_500M	44
> +#define BM1880_CLK_UART_500M		45
> +#define BM1880_CLK_APB_UART		46
> +#define BM1880_CLK_APB_I2S		47
> +#define BM1880_CLK_AXI4_USB		48
> +#define BM1880_CLK_APB_USB		49
> +#define BM1880_CLK_125M_USB		50
> +#define BM1880_CLK_33K_USB		51
> +#define BM1880_CLK_DIV_12M_USB		52
> +#define BM1880_CLK_12M_USB		53
> +#define BM1880_CLK_APB_VIDEO		54
> +#define BM1880_CLK_VIDEO_AXI		55
> +#define BM1880_CLK_VPP_AXI		56
> +#define BM1880_CLK_APB_VPP		57
> +#define BM1880_CLK_DIV_0_AXI1		58
> +#define BM1880_CLK_DIV_1_AXI1		59
> +#define BM1880_CLK_AXI1			60
> +#define BM1880_CLK_AXI2			61
> +#define BM1880_CLK_AXI3			62
> +#define BM1880_CLK_AXI4			63
> +#define BM1880_CLK_AXI5			64
> +#define BM1880_CLK_DIV_0_AXI6		65
> +#define BM1880_CLK_DIV_1_AXI6		66
> +#define BM1880_CLK_MUX_AXI6		67
> +#define BM1880_CLK_AXI6			68
> +#define BM1880_NR_CLKS			69
> +
> +#endif /* __DT_BINDINGS_CLOCK_BM1880_H */
> -- 
> 2.17.1
> 

^ permalink raw reply

* Re: [Patch V5 4/8] dt-bindings: usb: Add NVIDIA Tegra XUSB device mode controller binding
From: Rob Herring @ 2019-07-24 16:14 UTC (permalink / raw)
  To: Nagarjuna Kristam
  Cc: balbi, gregkh, thierry.reding, jonathanh, mark.rutland,
	devicetree, linux-tegra, linux-usb
In-Reply-To: <1562326911-26855-5-git-send-email-nkristam@nvidia.com>

On Fri, Jul 05, 2019 at 05:11:47PM +0530, Nagarjuna Kristam wrote:
> Add device-tree binding documentation for the XUSB device mode controller
> present on Tegra210 SoC. This controller supports the USB 3.0
> specification.
> 
> Signed-off-by: Nagarjuna Kristam <nkristam@nvidia.com>
> Reviewed-by: JC Kuo <jckuo@nvidia.com>
> ---
>  .../devicetree/bindings/usb/nvidia,tegra-xudc.txt  | 110 +++++++++++++++++++++
>  1 file changed, 110 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/usb/nvidia,tegra-xudc.txt
> 
> diff --git a/Documentation/devicetree/bindings/usb/nvidia,tegra-xudc.txt b/Documentation/devicetree/bindings/usb/nvidia,tegra-xudc.txt
> new file mode 100644
> index 0000000..c6a1b81
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/usb/nvidia,tegra-xudc.txt
> @@ -0,0 +1,110 @@
> +Device tree binding for NVIDIA Tegra XUSB device mode controller (XUDC)
> +=======================================================================
> +
> +The Tegra XUDC controller supports both USB 2.0 HighSpeed/FullSpeed and
> +USB 3.0 SuperSpeed protocols.
> +
> +Required properties:
> +--------------------
> +- compatible: For Tegra210, must contain "nvidia,tegra210-xudc".
> +- reg: Must contain the base and length of all registers used.
> +- interrupts: Must contain the XUSB device interrupt.
> +- clocks: Must contain an entry for all clocks used.
> +  See ../clock/clock-bindings.txt for details.
> +- clock-names: Must include the following entries:
> +   - dev: Clock to enable core XUSB dev clock.
> +   - ss: Clock to enable XUSB super speed clock.
> +   - ss_src: Clock to enable XUSB super speed dev clock.
> +   - hs_src: Clock to enable XUSB high speed dev clock.
> +   - fs_src: Clock to enable XUSB full speed dev clock.
> +- nvidia,xusb-padctl: phandle to the XUSB pad controller that is used to
> +  configure the USB pads used by the XUDC controller.
> +- power-domains: A list of PM domain specifiers that reference each power-domain
> +  used by the XUSB device mode controller. This list must comprise of a specifier
> +  for the XUSBA and XUSBB power-domains. See ../power/power_domain.txt and
> +  ../arm/tegra/nvidia,tegra20-pmc.txt for details.
> +- power-domain-names: A list of names that represent each of the specifiers in
> +  the 'power-domains' property. Must include 'ss' and 'dev'.

The order matters and is the opposite of the example.

> +- phys: Must contain an entry for each entry in phy-names.
> +  See ../phy/phy-bindings.txt for details.
> +- phy-names: Should include an entry for each PHY used by the controller.
> +  Names must be "usb2", and "usb3" if support SuperSpeed device mode.
> +  - "usb3" phy, SuperSpeed (SSTX+/SSTX-/SSRX+/SSRX-) data lines.
> +  - "usb2" phy, USB 2.0 (D+/D-) data lines.
> +
> +For Tegra210:
> +- reg-names: Must include the following entries:
> +   - base: XUSB device controller registers.
> +   - fpci: XUSB device PCI Config registers.
> +   - ipfs: XUSB device registers.
> +- avddio-usb-supply: PCIe/USB3 analog logic power supply. Must supply 1.05 V.
> +- hvdd-usb-supply: USB controller power supply. Must supply 3.3 V.
> +
> +
> +Optional properties:
> +--------------------
> +- usb-role-switch: boolean property to indicate use of USB Role Switch driver.
> +
> +Sub-nodes:
> +----------
> +- The port would be added as subnode if use "usb-role-switch" property.
> +  see graph.txt.
> +
> +Example:
> +--------
> +	pmc: pmc@7000e400 {
> +		compatible = "nvidia,tegra210-pmc";
> +		reg = <0x0 0x7000e400 0x0 0x400>;
> +		clocks = <&tegra_car TEGRA210_CLK_PCLK>, <&clk32k_in>;
> +		clock-names = "pclk", "clk32k_in";
> +
> +		powergates {
> +			pd_xusbss: xusba {
> +				clocks = <&tegra_car TEGRA210_CLK_XUSB_SS>;
> +				resets = <&tegra_car 156>;
> +				#power-domain-cells = <0>;
> +			};
> +
> +			pd_xusbdev: xusbb {
> +				clocks = <&tegra_car TEGRA210_CLK_XUSB_DEV>;
> +				resets = <&tegra_car 95>;
> +				#power-domain-cells = <0>;
> +			};
> +		};
> +	};
> +
> +	xudc@700d0000 {

usb@...

> +		compatible = "nvidia,tegra210-xudc";
> +		reg = <0x0 0x700d0000 0x0 0x8000>,
> +		      <0x0 0x700d8000 0x0 0x1000>,
> +		      <0x0 0x700d9000 0x0 0x1000>;
> +		reg-names = "base", "fpci", "ipfs";
> +
> +		interrupts = <GIC_SPI 44 IRQ_TYPE_LEVEL_HIGH>;
> +
> +		clocks = <&tegra_car TEGRA210_CLK_XUSB_DEV>,
> +			 <&tegra_car TEGRA210_CLK_XUSB_SS>,
> +			 <&tegra_car TEGRA210_CLK_XUSB_SSP_SRC>,
> +			 <&tegra_car TEGRA210_CLK_XUSB_HS_SRC>,
> +			 <&tegra_car TEGRA210_CLK_XUSB_FS_SRC>;
> +		clock-names = "dev", "ss", "ss_src", "hs_src", "fs_src";
> +
> +		power-domains = <&pd_xusbdev>, <&pd_xusbss>;
> +		power-domain-names = "dev", "ss";
> +
> +		nvidia,xusb-padctl = <&padctl>;
> +
> +		phys = <&{/padctl@7009f000/pads/usb2/lanes/usb2-0}>;

Use a label.

> +		phy-names = "usb2;
> +
> +		avddio-usb-supply = <&vdd_pex_1v05>;
> +		hvdd-usb-supply = <&vdd_3v3_sys>;
> +
> +		usb-role-switch;
> +		port {
> +			usb_role_switch: endpoint@0 {

Don't need a unit address.

> +				remote-endpoint = <&bconn_ep>;
> +			};
> +		};
> +
> +	};
> -- 
> 2.7.4
> 

^ permalink raw reply

* Re: [PATCH 09/13] dt-bindings: power: avs: Add support for CPR (Core Power Reduction)
From: Rob Herring @ 2019-07-24 16:06 UTC (permalink / raw)
  Cc: Niklas Cassel, Jorge Ramirez-Ortiz, linux-arm-msm, sboyd, vireshk,
	bjorn.andersson, ulf.hansson, Mark Rutland, linux-pm, devicetree,
	linux-kernel
In-Reply-To: <20190705095726.21433-10-niklas.cassel@linaro.org>

On Fri,  5 Jul 2019 11:57:20 +0200, Niklas Cassel wrote:
> Add DT bindings to describe the CPR HW found on certain Qualcomm SoCs.
> 
> Changes since RFC:
> -Make compatible string SoC specific.
> -Changed interrupt definition.
> -Use clock binding for reference clock.
> -Clarified qcom,vdd-apc-step-up-limit description.
> -Added missing properties.
> -Updated the example.
> 
> Co-developed-by: Jorge Ramirez-Ortiz <jorge.ramirez-ortiz@linaro.org>
> Signed-off-by: Jorge Ramirez-Ortiz <jorge.ramirez-ortiz@linaro.org>
> Signed-off-by: Niklas Cassel <niklas.cassel@linaro.org>
> ---
>  .../bindings/power/avs/qcom,cpr.txt           | 193 ++++++++++++++++++
>  1 file changed, 193 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/power/avs/qcom,cpr.txt
> 

Reviewed-by: Rob Herring <robh@kernel.org>

^ permalink raw reply

* Re: [PATCH 08/13] dt-bindings: opp: Add qcom-opp bindings with properties needed for CPR
From: Rob Herring @ 2019-07-24 16:03 UTC (permalink / raw)
  To: Niklas Cassel
  Cc: Andy Gross, Viresh Kumar, Nishanth Menon, Stephen Boyd,
	linux-arm-msm, jorge.ramirez-ortiz, bjorn.andersson, ulf.hansson,
	Mark Rutland, linux-pm, devicetree, linux-kernel
In-Reply-To: <20190705095726.21433-9-niklas.cassel@linaro.org>

On Fri, Jul 05, 2019 at 11:57:19AM +0200, Niklas Cassel wrote:
> Add qcom-opp bindings with properties needed for Core Power Reduction
> (CPR).
> 
> CPR is included in a great variety of Qualcomm SoCs, e.g. msm8916 and
> msm8996. CPR was first introduced in msm8974.
> 
> Changes since RFC:
> -Removed opp-hz. It is already an optional property in opp.txt
> so no need to specify it with the same definition here.
> 
> Co-developed-by: Jorge Ramirez-Ortiz <jorge.ramirez-ortiz@linaro.org>
> Signed-off-by: Jorge Ramirez-Ortiz <jorge.ramirez-ortiz@linaro.org>
> Signed-off-by: Niklas Cassel <niklas.cassel@linaro.org>
> ---
>  .../devicetree/bindings/opp/qcom-opp.txt      | 19 +++++++++++++++++++
>  1 file changed, 19 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/opp/qcom-opp.txt
> 
> diff --git a/Documentation/devicetree/bindings/opp/qcom-opp.txt b/Documentation/devicetree/bindings/opp/qcom-opp.txt
> new file mode 100644
> index 000000000000..f204685d029c
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/opp/qcom-opp.txt
> @@ -0,0 +1,19 @@
> +Qualcomm OPP bindings to describe OPP nodes
> +
> +The bindings are based on top of the operating-points-v2 bindings
> +described in Documentation/devicetree/bindings/opp/opp.txt
> +Additional properties are described below.
> +
> +* OPP Table Node
> +
> +Required properties:
> +- compatible: Allow OPPs to express their compatibility. It should be:
> +  "operating-points-v2-qcom-level"
> +
> +* OPP Node
> +
> +Optional properties:
> +- qcom,opp-fuse-level: A positive value representing the fuse corner/level
> +  associated with this OPP node. Sometimes several corners/levels shares
> +  a certain fuse corner/level. A fuse corner/level contains e.g. ref uV,
> +  min uV, and max uV.

Doesn't match the example in patch 5.

> -- 
> 2.21.0
> 

^ permalink raw reply

* RE: [PATCH net-next v1 1/4] enetc: Clean up local mdio bus allocation
From: Claudiu Manoil @ 2019-07-24 16:03 UTC (permalink / raw)
  To: Andrew Lunn
  Cc: David S . Miller, Rob Herring, Leo Li, Alexandru Marginean,
	netdev@vger.kernel.org, devicetree@vger.kernel.org,
	linux-arm-kernel@lists.infradead.org,
	linux-kernel@vger.kernel.org
In-Reply-To: <20190724151803.GR25635@lunn.ch>



>-----Original Message-----
>From: Andrew Lunn <andrew@lunn.ch>
>Sent: Wednesday, July 24, 2019 6:18 PM
>To: Claudiu Manoil <claudiu.manoil@nxp.com>
>Cc: David S . Miller <davem@davemloft.net>; Rob Herring
><robh+dt@kernel.org>; Leo Li <leoyang.li@nxp.com>; Alexandru Marginean
><alexandru.marginean@nxp.com>; netdev@vger.kernel.org;
>devicetree@vger.kernel.org; linux-arm-kernel@lists.infradead.org; linux-
>kernel@vger.kernel.org
>Subject: Re: [PATCH net-next v1 1/4] enetc: Clean up local mdio bus allocation
>
>On Wed, Jul 24, 2019 at 05:41:38PM +0300, Claudiu Manoil wrote:
>> Though it works, this is not how it should have been.
>> What's needed is a pointer to the mdio registers.
>> Store it properly inside bus->priv allocated space.
>> Use devm_* variant to further clean up the init error /
>> remove paths.
>>
>> Fixes following sparse warning:
>>  warning: incorrect type in assignment (different address spaces)
>>     expected void *priv
>>     got struct enetc_mdio_regs [noderef] <asn:2>*[assigned] regs
>>
>> Fixes: ebfcb23d62ab ("enetc: Add ENETC PF level external MDIO support")
>>
>> Signed-off-by: Claudiu Manoil <claudiu.manoil@nxp.com>
>> ---
>> v1 - added this patch
>>
>>  .../net/ethernet/freescale/enetc/enetc_mdio.c | 31 +++++++------------
>>  1 file changed, 12 insertions(+), 19 deletions(-)
>>
>> diff --git a/drivers/net/ethernet/freescale/enetc/enetc_mdio.c
>b/drivers/net/ethernet/freescale/enetc/enetc_mdio.c
>> index 77b9cd10ba2b..1e3cd21c13ee 100644
>> --- a/drivers/net/ethernet/freescale/enetc/enetc_mdio.c
>> +++ b/drivers/net/ethernet/freescale/enetc/enetc_mdio.c
>> @@ -15,7 +15,8 @@ struct enetc_mdio_regs {
>>  	u32	mdio_addr;	/* MDIO address */
>>  };
>>
>> -#define bus_to_enetc_regs(bus)	(struct enetc_mdio_regs __iomem
>*)((bus)->priv)
>> +#define bus_to_enetc_regs(bus)	(*(struct enetc_mdio_regs __iomem
>**) \
>> +				((bus)->priv))
>>
>>  #define ENETC_MDIO_REG_OFFSET	0x1c00
>>  #define ENETC_MDC_DIV		258
>> @@ -146,12 +147,12 @@ static int enetc_mdio_read(struct mii_bus *bus, int
>phy_id, int regnum)
>>  int enetc_mdio_probe(struct enetc_pf *pf)
>>  {
>>  	struct device *dev = &pf->si->pdev->dev;
>> -	struct enetc_mdio_regs __iomem *regs;
>> +	struct enetc_mdio_regs __iomem **regsp;
>>  	struct device_node *np;
>>  	struct mii_bus *bus;
>> -	int ret;
>> +	int err;
>>
>> -	bus = mdiobus_alloc_size(sizeof(regs));
>> +	bus = devm_mdiobus_alloc_size(dev, sizeof(*regsp));
>>  	if (!bus)
>>  		return -ENOMEM;
>>
>> @@ -159,41 +160,33 @@ int enetc_mdio_probe(struct enetc_pf *pf)
>>  	bus->read = enetc_mdio_read;
>>  	bus->write = enetc_mdio_write;
>>  	bus->parent = dev;
>> +	regsp = bus->priv;
>>  	snprintf(bus->id, MII_BUS_ID_SIZE, "%s", dev_name(dev));
>>
>>  	/* store the enetc mdio base address for this bus */
>> -	regs = pf->si->hw.port + ENETC_MDIO_REG_OFFSET;
>> -	bus->priv = regs;
>> +	*regsp = pf->si->hw.port + ENETC_MDIO_REG_OFFSET;
>
>This is all very odd and different to every other driver.
>
>If i get the code write, there are 4 registers, each u32 in size,
>starting at pf->si->hw.port + ENETC_MDIO_REG_OFFSET?
>
>There are macros like enetc_port_wr() and enetc_global_wr(). It think
>it would be much cleaner to add a macro enet_mdio_wr() which takes
>hw, off, val.
>
>#define enet_mdio_wr(hw, off, val) enet_port_wr(hw, off +
>ENETC_MDIO_REG_OFFSET, val)
>
>struct enetc_mdio_priv {
>       struct enetc_hw *hw;
>}
>
>	struct enetc_mdio_priv *mdio_priv;
>
>	bus = devm_mdiobus_alloc_size(dev, sizeof(*mdio_priv));
>
>	mdio_priv = bus->priv;
>	mdio_priv->hw = pf->si->hw;
>
>
>static int enetc_mdio_write(struct mii_bus *bus, int phy_id, int regnum,
>                            u16 value)
>{
>	struct enetc_mdio_priv *mdio_priv = bus->priv;
>...
>	enet_mdio_wr(priv->hw, ENETC_MDIO_CFG, mdio_cfg);
>}
>
>All the horrible casts go away, the driver is structured like every
>other driver, sparse is probably happy, etc.
>

This looks more like a matter cosmetic preferences.  I mean, I didn't
notice anything "horrible" in the code so far.  I actually find it more
ugly to define a new structure with only one element inside, like:
struct enetc_mdio_priv {
       struct enetc_hw *hw;
}
What is this technique called? Looks like a second type definition for
another type.
Anyway, if others already did this in the kernel, what can I do?

^ permalink raw reply

* Re: [PATCH v2 14/19] dt-bindings: pci: add PHY properties to Armada 7K/8K controller bindings
From: Miquel Raynal @ 2019-07-24 15:56 UTC (permalink / raw)
  To: Rob Herring
  Cc: Andrew Lunn, Jason Cooper, devicetree, Antoine Tenart,
	Grzegorz Jaszczyk, Gregory Clement, Russell King,
	Kishon Vijay Abraham I, Nadav Haklai, Thomas Petazzoni,
	Maxime Chevallier, linux-arm-kernel, Sebastian Hesselbarth
In-Reply-To: <20190722175225.GA13801@bogus>

Hi Rob,

Rob Herring <robh@kernel.org> wrote on Mon, 22 Jul 2019 11:52:25 -0600:

> On Thu, Jun 27, 2019 at 02:25:00PM +0200, Miquel Raynal wrote:
> > Armada CP110 PCIe controller can have a PHY (for configuring SERDES
> > lanes). Describe these two properties in the bindings.
> > 
> > Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
> > ---
> >  Documentation/devicetree/bindings/pci/pci-armada8k.txt | 4 ++++
> >  1 file changed, 4 insertions(+)
> > 
> > diff --git a/Documentation/devicetree/bindings/pci/pci-armada8k.txt b/Documentation/devicetree/bindings/pci/pci-armada8k.txt
> > index 9e3fc15e1af8..a373a80524db 100644
> > --- a/Documentation/devicetree/bindings/pci/pci-armada8k.txt
> > +++ b/Documentation/devicetree/bindings/pci/pci-armada8k.txt
> > @@ -17,6 +17,10 @@ Required properties:
> >     name must be "core" for the first clock and "reg" for the second
> >     one
> >  
> > +Optional properties:
> > +- phys: phandle to the PHY node (generic PHY bindings).
> > +- phy-names: names of the PHYs.  
> 
> How many? If only 1, you don't really need phy-names.

Actually it depends on the board. For instance, the MacchiatoBin has
four of them:

--- a/arch/arm64/boot/dts/marvell/armada-8040-mcbin.dtsi
+++ b/arch/arm64/boot/dts/marvell/armada-8040-mcbin.dtsi
@@ -186,6 +186,10 @@
        reset-gpios = <&cp0_gpio2 20 GPIO_ACTIVE_LOW>;
        ranges = <0x81000000 0x0 0xf9010000 0x0 0xf9010000 0x0 0x10000
                  0x82000000 0x0 0xc0000000 0x0 0xc0000000 0x0 0x20000000>;
+       phys = <&cp0_comphy0 0>, <&cp0_comphy1 0>,
+              <&cp0_comphy2 0>, <&cp0_comphy3 0>;
+       phy-names = "cp0-pcie0-x4-lane0-phy", "cp0-pcie0-x4-lane1-phy",
+                   "cp0-pcie0-x4-lane2-phy", "cp0-pcie0-x4-lane3-phy";
        status = "okay";
 };


Thanks,
Miquèl

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply

* [PATCH 3/3] drm/bridge: Add NWL MIPI DSI host controller support
From: Guido Günther @ 2019-07-24 15:52 UTC (permalink / raw)
  To: David Airlie, Daniel Vetter, Rob Herring, Mark Rutland, Shawn Guo,
	Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam,
	NXP Linux Team, Andrzej Hajda, Neil Armstrong, Laurent Pinchart,
	Jonas Karlman, Jernej Skrabec, Lee Jones, Guido Günther,
	dri-devel, devicetree, linux-arm-kernel, linux-kernel,
	Robert Chiras
In-Reply-To: <cover.1563983037.git.agx@sigxcpu.org>

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@sigxcpu.org>
Co-developed-by: Robert Chiras <robert.chiras@nxp.com>
---
 drivers/gpu/drm/bridge/Kconfig           |   2 +
 drivers/gpu/drm/bridge/Makefile          |   1 +
 drivers/gpu/drm/bridge/imx-nwl/Kconfig   |  15 +
 drivers/gpu/drm/bridge/imx-nwl/Makefile  |   2 +
 drivers/gpu/drm/bridge/imx-nwl/nwl-drv.c | 529 ++++++++++++++++
 drivers/gpu/drm/bridge/imx-nwl/nwl-drv.h |  72 +++
 drivers/gpu/drm/bridge/imx-nwl/nwl-dsi.c | 745 +++++++++++++++++++++++
 drivers/gpu/drm/bridge/imx-nwl/nwl-dsi.h | 111 ++++
 8 files changed, 1477 insertions(+)
 create mode 100644 drivers/gpu/drm/bridge/imx-nwl/Kconfig
 create mode 100644 drivers/gpu/drm/bridge/imx-nwl/Makefile
 create mode 100644 drivers/gpu/drm/bridge/imx-nwl/nwl-drv.c
 create mode 100644 drivers/gpu/drm/bridge/imx-nwl/nwl-drv.h
 create mode 100644 drivers/gpu/drm/bridge/imx-nwl/nwl-dsi.c
 create mode 100644 drivers/gpu/drm/bridge/imx-nwl/nwl-dsi.h

diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
index a6eec908c43e..38c3145a7e57 100644
--- a/drivers/gpu/drm/bridge/Kconfig
+++ b/drivers/gpu/drm/bridge/Kconfig
@@ -152,6 +152,8 @@ source "drivers/gpu/drm/bridge/analogix/Kconfig"
 
 source "drivers/gpu/drm/bridge/adv7511/Kconfig"
 
+source "drivers/gpu/drm/bridge/imx-nwl/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..904a9eb3a20a 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-y += imx-nwl/
 obj-y += synopsys/
diff --git a/drivers/gpu/drm/bridge/imx-nwl/Kconfig b/drivers/gpu/drm/bridge/imx-nwl/Kconfig
new file mode 100644
index 000000000000..822dba1b380a
--- /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)
+	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 REGMAP_MMIO
+	help
+	  This enables the Northwest Logic MIPI DSI Host controller as
+	  found on NXP's i.MX8 Processors.
+
diff --git a/drivers/gpu/drm/bridge/imx-nwl/Makefile b/drivers/gpu/drm/bridge/imx-nwl/Makefile
new file mode 100644
index 000000000000..9fa63483da5b
--- /dev/null
+++ b/drivers/gpu/drm/bridge/imx-nwl/Makefile
@@ -0,0 +1,2 @@
+imx-nwl-objs := nwl-drv.o nwl-dsi.o
+obj-$(CONFIG_DRM_IMX_NWL_DSI) += imx-nwl.o
diff --git a/drivers/gpu/drm/bridge/imx-nwl/nwl-drv.c b/drivers/gpu/drm/bridge/imx-nwl/nwl-drv.c
new file mode 100644
index 000000000000..451f8f067c6f
--- /dev/null
+++ b/drivers/gpu/drm/bridge/imx-nwl/nwl-drv.c
@@ -0,0 +1,529 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * i.MX8 NWL MIPI DSI host driver
+ *
+ * Copyright (C) 2017 NXP
+ * Copyright (C) 2019 Purism SPC
+ */
+
+#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>
+#include <linux/irq.h>
+#include <linux/mfd/syscon.h>
+#include <linux/mfd/syscon/imx8mq-iomuxc-gpr.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/phy/phy.h>
+#include <linux/regmap.h>
+#include <linux/sys_soc.h>
+#include <video/videomode.h>
+
+#include "nwl-drv.h"
+#include "nwl-dsi.h"
+
+#define DRV_NAME "imx-nwl-dsi"
+
+/* 8MQ SRC specific registers */
+#define SRC_MIPIPHY_RCR	0x28
+#define RESET_BYTE_N	BIT(1)
+#define RESET_N		BIT(2)
+#define DPI_RESET_N	BIT(3)
+#define ESC_RESET_N	BIT(4)
+#define PCLK_RESET_N	BIT(5)
+
+/* Possible clocks */
+#define CLK_PIXEL	"pixel"
+#define CLK_CORE	"core"
+#define CLK_BYPASS	"bypass"
+
+enum imx_ext_regs {
+	IMX_REG_CSR = BIT(1),
+	IMX_REG_SRC = BIT(2),
+	IMX_REG_GPR = BIT(3),
+};
+
+static const struct regmap_config nwl_dsi_regmap_config = {
+	.reg_bits = 16,
+	.val_bits = 32,
+	.reg_stride = 4,
+	.max_register = IRQ_MASK2,
+	.name = DRV_NAME,
+};
+
+struct imx_nwl_platform_data {
+	int (*poweron)(struct imx_nwl_dsi *dsi);
+	int (*poweroff)(struct imx_nwl_dsi *dsi);
+	u32 ext_regs; /* required external registers */
+	struct imx_nwl_clk_config clk_config[NWL_MAX_PLATFORM_CLOCKS];
+};
+
+static inline struct imx_nwl_dsi *bridge_to_dsi(struct drm_bridge *bridge)
+{
+	return container_of(bridge, struct imx_nwl_dsi, bridge);
+}
+
+static void imx_nwl_dsi_set_clocks(struct imx_nwl_dsi *dsi, bool enable)
+{
+	struct device *dev = dsi->dev;
+	const char *id;
+	struct clk *clk;
+	unsigned long new_rate, cur_rate;
+	bool enabled;
+	size_t i;
+	int ret;
+
+	DRM_DEV_DEBUG_DRIVER(dev, "%sabling platform clocks",
+			     enable ? "en" : "dis");
+	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;
+		new_rate = dsi->clk_config[i].rate;
+		cur_rate = clk_get_rate(clk);
+		enabled = dsi->clk_config[i].enabled;
+
+		/* BYPASS clk must have the same rate as PHY_REF clk */
+		if (!strcmp(id, CLK_BYPASS))
+			new_rate = clk_get_rate(dsi->phy_ref_clk);
+
+		if (enable) {
+			if (enabled && new_rate != cur_rate)
+				clk_disable_unprepare(clk);
+			else if (enabled && new_rate == cur_rate)
+				continue;
+			if (new_rate > 0)
+				clk_set_rate(clk, new_rate);
+			ret = clk_prepare_enable(clk);
+			if (ret < 0) {
+				DRM_DEV_ERROR(dev, "Failed to enable clock %s",
+					      id);
+			}
+			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)
+{
+	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);
+}
+
+static void imx_nwl_dsi_disable(struct imx_nwl_dsi *dsi)
+{
+	struct device *dev = dsi->dev;
+
+	if (dsi->dpms_mode != DRM_MODE_DPMS_ON)
+		return;
+
+	DRM_DEV_DEBUG_DRIVER(dev, "Disabling encoder");
+	dsi->pdata->poweroff(dsi);
+	imx_nwl_dsi_set_clocks(dsi, false);
+}
+
+static void imx_nwl_select_input_source(struct imx_nwl_dsi *dsi)
+{
+	struct device_node *remote;
+	u32 mux_val = IMX8MQ_GPR13_MIPI_MUX_SEL;
+
+	remote = of_graph_get_remote_node(dsi->dev->of_node, 0, 0);
+	if (strcmp(remote->name, "lcdif") == 0)
+		mux_val = 0;
+
+	DRM_DEV_INFO(dsi->dev, "Using %s as input source\n",
+		     (mux_val) ? "DCSS" : "LCDIF");
+	regmap_update_bits(dsi->mux_sel, IOMUXC_GPR13,
+			   IMX8MQ_GPR13_MIPI_MUX_SEL, mux_val);
+	of_node_put(remote);
+}
+
+static void imx_nwl_dsi_bridge_disable(struct drm_bridge *bridge)
+{
+	struct imx_nwl_dsi *dsi = bridge_to_dsi(bridge);
+
+	if (dsi->dpms_mode != DRM_MODE_DPMS_ON)
+		return;
+
+	nwl_dsi_disable(dsi);
+	imx_nwl_dsi_disable(dsi);
+	pm_runtime_put_sync(dsi->dev);
+	dsi->dpms_mode = DRM_MODE_DPMS_OFF;
+}
+
+static bool
+imx_nwl_dsi_bridge_mode_fixup(struct drm_bridge *bridge,
+			      const struct drm_display_mode *mode,
+			      struct drm_display_mode *adjusted_mode)
+{
+	struct imx_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 ret;
+
+	/*
+	 * 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 true;
+
+	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) {
+		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 */
+	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;
+}
+
+static enum drm_mode_status
+imx_nwl_dsi_bridge_mode_valid(struct drm_bridge *bridge,
+			      const struct drm_display_mode *mode)
+{
+	struct imx_nwl_dsi *dsi = bridge_to_dsi(bridge);
+	int bpp = mipi_dsi_pixel_format_to_bpp(dsi->format);
+
+	if (bpp < 0) {
+		DRM_DEV_ERROR(dsi->dev, "Invalid pixel format: %d\n",
+			      dsi->format);
+		return MODE_BAD;
+	}
+
+	return MODE_OK;
+}
+
+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);
+	pm_runtime_get_sync(dsi->dev);
+	imx_nwl_dsi_enable(dsi);
+	nwl_dsi_enable(dsi);
+	dsi->dpms_mode = DRM_MODE_DPMS_ON;
+}
+
+static int imx_nwl_dsi_bridge_attach(struct drm_bridge *bridge)
+{
+	struct imx_nwl_dsi *dsi = bridge->driver_private;
+	struct drm_encoder *encoder = bridge->encoder;
+
+	if (!encoder) {
+		DRM_DEV_ERROR(dsi->dev, "Parent encoder object not found\n");
+		return -ENODEV;
+	}
+
+	/* Set the encoder type as caller does not know it */
+	bridge->encoder->encoder_type = DRM_MODE_ENCODER_DSI;
+
+	/* Attach the panel-bridge to the dsi bridge */
+	return drm_bridge_attach(bridge->encoder, dsi->panel_bridge, bridge);
+}
+
+static void imx_nwl_dsi_bridge_detach(struct drm_bridge *bridge)
+{
+	struct imx_nwl_dsi *dsi = bridge->driver_private;
+
+	drm_of_panel_bridge_remove(dsi->dev->of_node, 1, 0);
+}
+
+/* see dw-mipi-dsi.c */
+static const struct drm_bridge_funcs imx_nwl_dsi_bridge_funcs = {
+	.pre_enable	= imx_nwl_dsi_bridge_pre_enable,
+	.disable    = imx_nwl_dsi_bridge_disable,
+	.mode_fixup = imx_nwl_dsi_bridge_mode_fixup,
+	.mode_valid = imx_nwl_dsi_bridge_mode_valid,
+	.attach	    = imx_nwl_dsi_bridge_attach,
+	.detach	    = imx_nwl_dsi_bridge_detach,
+};
+
+static int imx_nwl_dsi_parse_dt(struct imx_nwl_dsi *dsi)
+{
+	struct device_node *np = dsi->dev->of_node;
+	struct platform_device *pdev = to_platform_device(dsi->dev);
+	struct resource *res;
+	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);
+		dev_err(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);
+			dev_err(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);
+		dev_err(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);
+		dev_err(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);
+		dev_err(dsi->dev, "Failed to get tx_esc clock: %d\n", ret);
+		return ret;
+	}
+	dsi->tx_esc_clk = clk;
+
+	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",
+			      ret);
+		return ret;
+	}
+	dsi->reset = syscon_regmap_lookup_by_phandle(np, "src");
+	if (IS_ERR(dsi->reset) && (dsi->pdata->ext_regs & IMX_REG_SRC)) {
+		ret = PTR_ERR(dsi->reset);
+		DRM_DEV_ERROR(dsi->dev, "Failed to get SRC regmap: %d\n",
+			      ret);
+		return ret;
+	}
+	dsi->mux_sel = syscon_regmap_lookup_by_phandle(np, "mux-sel");
+	if (IS_ERR(dsi->mux_sel) && (dsi->pdata->ext_regs & IMX_REG_GPR)) {
+		ret = PTR_ERR(dsi->mux_sel);
+		DRM_DEV_ERROR(dsi->dev, "Failed to get GPR regmap: %d\n",
+			      ret);
+		return ret;
+	}
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	base = devm_ioremap_resource(dsi->dev, res);
+	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;
+	}
+
+	return 0;
+}
+
+static int imx8mq_dsi_poweron(struct imx_nwl_dsi *dsi)
+{
+	/* otherwise the display stays blank */
+	usleep_range(200, 300);
+
+	regmap_update_bits(dsi->reset, SRC_MIPIPHY_RCR, PCLK_RESET_N,
+			   PCLK_RESET_N);
+	regmap_update_bits(dsi->reset, SRC_MIPIPHY_RCR, ESC_RESET_N,
+			   ESC_RESET_N);
+	regmap_update_bits(dsi->reset, SRC_MIPIPHY_RCR, RESET_BYTE_N,
+			   RESET_BYTE_N);
+	regmap_update_bits(dsi->reset, SRC_MIPIPHY_RCR, DPI_RESET_N,
+			   DPI_RESET_N);
+
+	return 0;
+}
+
+static int imx8mq_dsi_poweroff(struct imx_nwl_dsi *dsi)
+{
+	if (USE_SRC_RESET_QUIRK(dsi->quirks))
+		return 0;
+
+	regmap_update_bits(dsi->reset, SRC_MIPIPHY_RCR, PCLK_RESET_N, 0);
+	regmap_update_bits(dsi->reset, SRC_MIPIPHY_RCR, ESC_RESET_N, 0);
+	regmap_update_bits(dsi->reset, SRC_MIPIPHY_RCR, RESET_BYTE_N, 0);
+	regmap_update_bits(dsi->reset, SRC_MIPIPHY_RCR, DPI_RESET_N, 0);
+	return 0;
+}
+
+static const struct drm_bridge_timings imx_nwl_timings = {
+	.input_bus_flags = DRM_BUS_FLAG_DE_LOW,
+};
+
+static struct imx_nwl_platform_data imx8mq_dev = {
+	.poweron = &imx8mq_dsi_poweron,
+	.poweroff = &imx8mq_dsi_poweroff,
+	.clk_config = {
+		{ .id = CLK_CORE,   .present = true },
+		{ .id = CLK_PIXEL,  .present = false },
+		{ .id = CLK_BYPASS, .present = false },
+	},
+	.ext_regs = IMX_REG_SRC | IMX_REG_GPR,
+};
+
+static const struct of_device_id imx_nwl_dsi_dt_ids[] = {
+	{ .compatible = "fsl,imx8mq-nwl-dsi", .data = &imx8mq_dev, },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, imx_nwl_dsi_dt_ids);
+
+static const struct soc_device_attribute imx_nwl_quirks_match[] = {
+	{ .soc_id = "i.MX8MQ", .revision = "2.0",
+	  .data = (void *)(E11418_HS_MODE_QUIRK | SRC_RESET_QUIRK) },
+	{ /* sentinel. */ },
+};
+
+static int imx_nwl_dsi_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	const struct of_device_id *of_id =
+		of_match_device(imx_nwl_dsi_dt_ids, dev);
+	const struct imx_nwl_platform_data *pdata = of_id->data;
+	const struct soc_device_attribute *attr;
+	struct imx_nwl_dsi *dsi;
+	int ret;
+
+	dsi = devm_kzalloc(dev, sizeof(*dsi), GFP_KERNEL);
+	if (!dsi)
+		return -ENOMEM;
+
+	dsi->dev = dev;
+	dsi->pdata = pdata;
+	dsi->dpms_mode = DRM_MODE_DPMS_OFF;
+
+	ret = imx_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);
+		goto err_cleanup;
+	}
+
+	attr = soc_device_match(imx_nwl_quirks_match);
+	if (attr)
+		dsi->quirks = (uintptr_t)attr->data;
+
+	dsi->bridge.driver_private = dsi;
+	dsi->bridge.funcs = &imx_nwl_dsi_bridge_funcs;
+	dsi->bridge.of_node = dev->of_node;
+	dsi->bridge.timings = &imx_nwl_timings;
+
+	drm_bridge_add(&dsi->bridge);
+
+	dev_set_drvdata(dev, dsi);
+	pm_runtime_enable(dev);
+	return 0;
+
+err_cleanup:
+	devm_free_irq(dev, dsi->irq, dsi);
+	return ret;
+}
+
+static int imx_nwl_dsi_remove(struct platform_device *pdev)
+{
+	pm_runtime_disable(&pdev->dev);
+	return 0;
+}
+
+static struct platform_driver imx_nwl_dsi_driver = {
+	.probe		= imx_nwl_dsi_probe,
+	.remove		= imx_nwl_dsi_remove,
+	.driver		= {
+		.of_match_table = imx_nwl_dsi_dt_ids,
+		.name	= DRV_NAME,
+	},
+};
+
+module_platform_driver(imx_nwl_dsi_driver);
+
+MODULE_AUTHOR("NXP Semiconductor");
+MODULE_AUTHOR("Purism SPC");
+MODULE_DESCRIPTION("i.MX8 Northwest Logic MIPI-DSI driver");
+MODULE_LICENSE("GPL"); /* GPLv2 or later */
diff --git a/drivers/gpu/drm/bridge/imx-nwl/nwl-drv.h b/drivers/gpu/drm/bridge/imx-nwl/nwl-drv.h
new file mode 100644
index 000000000000..a1e30c58b627
--- /dev/null
+++ b/drivers/gpu/drm/bridge/imx-nwl/nwl-drv.h
@@ -0,0 +1,72 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * i.MX8 NWL MIPI DSI host driver
+ *
+ * Copyright (C) 2017 NXP
+ * Copyright (C) 2019 Purism SPC
+ */
+
+#ifndef __NWL_DRV_H__
+#define __NWL_DRV_H__
+
+#include <drm/drm_mipi_dsi.h>
+#include <linux/phy/phy.h>
+
+struct imx_nwl_platform_data;
+
+/* i.MX8 NWL quirks */
+/* i.MX8MQ errata E11418 */
+#define E11418_HS_MODE_QUIRK	    BIT(0)
+#define USE_E11418_HS_MODE_QUIRK(x) ((x) & E11418_HS_MODE_QUIRK)
+
+/* Skip DSI bits in SRC on disable to avoid blank display on enable */
+#define SRC_RESET_QUIRK		    BIT(1)
+#define USE_SRC_RESET_QUIRK(x)	    ((x) & SRC_RESET_QUIRK)
+
+#define NWL_MAX_PLATFORM_CLOCKS 3
+struct imx_nwl_clk_config {
+	const char *id;
+	struct clk *clk;
+	bool present;
+	bool enabled;
+	u32 rate;
+};
+
+struct imx_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;
+
+	/* External registers */
+	struct regmap *csr;
+	struct regmap *mux_sel;
+	struct regmap *reset;
+
+	/* Platform dependent clocks */
+	struct imx_nwl_clk_config clk_config[3];
+	/* DSI clocks */
+	struct clk *phy_ref_clk;
+	struct clk *rx_esc_clk;
+	struct clk *tx_esc_clk;
+
+	/* dsi lanes */
+	u32 lanes;
+	enum mipi_dsi_pixel_format format;
+	struct videomode vm;
+	unsigned long dsi_mode_flags;
+
+	int dpms_mode;
+
+	struct mipi_dsi_transfer *xfer;
+
+	const struct imx_nwl_platform_data *pdata;
+};
+
+#endif /* __NWL_DRV_H__ */
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>
+#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 <linux/clk.h>
+#include <linux/irq.h>
+#include <linux/regmap.h>
+#include <video/mipi_display.h>
+#include <video/videomode.h>
+
+#include "nwl-drv.h"
+#include "nwl-dsi.h"
+
+#define MIPI_FIFO_TIMEOUT msecs_to_jiffies(500)
+
+/* PKT reg bit manipulation */
+#define REG_MASK(e, s) (((1 << ((e) - (s) + 1)) - 1) << (s))
+#define REG_PUT(x, e, s) (((x) << (s)) & REG_MASK(e, s))
+#define REG_GET(x, e, s) (((x) & REG_MASK(e, s)) >> (s))
+
+/*
+ * 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 WC(x)		REG_PUT((x), 15,  0)
+#define TX_VC(x)	REG_PUT((x), 17, 16)
+#define TX_DT(x)	REG_PUT((x), 23, 18)
+#define HS_SEL(x)	REG_PUT((x), 24, 24)
+#define BTA_TX(x)	REG_PUT((x), 25, 25)
+#define BTA_NO_TX(x)	REG_PUT((x), 26, 26)
+
+/*
+ * RX_PKT_HEADER format:
+ * [15: 0] - word count
+ * [21:16] - data type
+ * [23:22] - virtual channel
+ */
+#define RX_DT(x) REG_GET((x), 21, 16)
+#define RX_VC(x) REG_GET((x), 23, 22)
+
+/*
+ * DSI Video mode
+ */
+#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
+ */
+#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
+ */
+#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 */
+};
+
+static inline int nwl_dsi_write(struct imx_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 inline u32 nwl_dsi_read(struct imx_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 PIXEL_FORMAT_16;
+	case MIPI_DSI_FMT_RGB666:
+		return PIXEL_FORMAT_18L;
+	case MIPI_DSI_FMT_RGB666_PACKED:
+		return PIXEL_FORMAT_18;
+	case MIPI_DSI_FMT_RGB888:
+		return PIXEL_FORMAT_24;
+	default:
+		return -EINVAL;
+	}
+}
+
+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);
+
+#define PSEC_PER_SEC 1000000000000LL
+/*
+ * ps2bc - Picoseconds to byte clock cycles
+ */
+static u32 ps2bc(struct imx_nwl_dsi *dsi, unsigned long long ps)
+{
+	int bpp = mipi_dsi_pixel_format_to_bpp(dsi->format);
+
+	return DIV_ROUND_UP(ps * dsi->vm.pixelclock * bpp,
+			    dsi->lanes * 8 * PSEC_PER_SEC);
+}
+
+/**
+ * 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
+/*
+ * 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 imx_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, CFG_NUM_LANES, dsi->lanes - 1);
+
+	if (dsi->dsi_mode_flags & MIPI_DSI_CLOCK_NON_CONTINUOUS) {
+		nwl_dsi_write(dsi, CFG_NONCONTINUOUS_CLK, 0x01);
+		nwl_dsi_write(dsi, CFG_AUTOINSERT_EOTP, 0x01);
+	} else {
+		nwl_dsi_write(dsi, CFG_NONCONTINUOUS_CLK, 0x00);
+		nwl_dsi_write(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, 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, 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, CFG_TX_GAP, cycles);
+
+	nwl_dsi_write(dsi, CFG_EXTRA_CMDS_AFTER_EOTP, 0x01);
+	nwl_dsi_write(dsi, CFG_HTX_TO_COUNT, 0x00);
+	nwl_dsi_write(dsi, CFG_LRX_H_TO_COUNT, 0x00);
+	nwl_dsi_write(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, CFG_TWAKEUP, cycles);
+
+	return 0;
+}
+
+static int nwl_dsi_config_dpi(struct imx_nwl_dsi *dsi)
+{
+	struct videomode *vm = &dsi->vm;
+	u32 color_format, mode;
+	bool burst_mode;
+
+	DRM_DEV_DEBUG_DRIVER(dsi->dev, "hfront_porch = %d\n", vm->hfront_porch);
+	DRM_DEV_DEBUG_DRIVER(dsi->dev, "hback_porch = %d\n", vm->hback_porch);
+	DRM_DEV_DEBUG_DRIVER(dsi->dev, "hsync_len = %d\n", vm->hsync_len);
+	DRM_DEV_DEBUG_DRIVER(dsi->dev, "hactive = %d\n", vm->hactive);
+	DRM_DEV_DEBUG_DRIVER(dsi->dev, "vfront_porch = %d\n", vm->vfront_porch);
+	DRM_DEV_DEBUG_DRIVER(dsi->dev, "vback_porch = %d\n", vm->vback_porch);
+	DRM_DEV_DEBUG_DRIVER(dsi->dev, "vsync_len = %d\n", vm->vsync_len);
+	DRM_DEV_DEBUG_DRIVER(dsi->dev, "vactive = %d\n", vm->vactive);
+	DRM_DEV_DEBUG_DRIVER(dsi->dev, "clock = %lu kHz\n",
+			     vm->pixelclock / 1000);
+
+	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, INTERFACE_COLOR_CODING, DPI_24_BIT);
+	nwl_dsi_write(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, VSYNC_POLARITY, VSYNC_POLARITY_ACTIVE_LOW);
+	nwl_dsi_write(dsi, HSYNC_POLARITY, 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, VIDEO_MODE, VIDEO_MODE_BURST_MODE);
+		nwl_dsi_write(dsi, PIXEL_FIFO_SEND_LEVEL, 256);
+	} else {
+		mode = ((dsi->dsi_mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) ?
+				VIDEO_MODE_BURST_MODE_WITH_SYNC_PULSES :
+				VIDEO_MODE_NON_BURST_MODE_WITH_SYNC_EVENTS);
+		nwl_dsi_write(dsi, VIDEO_MODE, mode);
+		nwl_dsi_write(dsi, PIXEL_FIFO_SEND_LEVEL, vm->hactive);
+	}
+
+	nwl_dsi_write(dsi, HFP, vm->hfront_porch);
+	nwl_dsi_write(dsi, HBP, vm->hback_porch);
+	nwl_dsi_write(dsi, HSA, vm->hsync_len);
+
+	nwl_dsi_write(dsi, ENABLE_MULT_PKTS, 0x0);
+	nwl_dsi_write(dsi, BLLP_MODE, 0x1);
+	nwl_dsi_write(dsi, ENABLE_MULT_PKTS, 0x0);
+	nwl_dsi_write(dsi, USE_NULL_PKT_BLLP, 0x0);
+	nwl_dsi_write(dsi, VC, 0x0);
+
+	nwl_dsi_write(dsi, PIXEL_PAYLOAD_SIZE, vm->hactive);
+	nwl_dsi_write(dsi, VACTIVE, vm->vactive - 1);
+	nwl_dsi_write(dsi, VBP, vm->vback_porch);
+	nwl_dsi_write(dsi, VFP, vm->vfront_porch);
+
+	return 0;
+}
+
+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;
+}
+
+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;
+}
+
+static void nwl_dsi_init_interrupts(struct imx_nwl_dsi *dsi)
+{
+	u32 irq_enable;
+
+	nwl_dsi_write(dsi, IRQ_MASK, 0xffffffff);
+	nwl_dsi_write(dsi, IRQ_MASK2, 0x7);
+
+	irq_enable = ~(u32)(TX_PKT_DONE_MASK | RX_PKT_HDR_RCVD_MASK |
+			    TX_FIFO_OVFLW_MASK | HS_TX_TIMEOUT_MASK);
+
+	nwl_dsi_write(dsi, IRQ_MASK, irq_enable);
+}
+
+static int nwl_dsi_host_attach(struct mipi_dsi_host *dsi_host,
+			       struct mipi_dsi_device *device)
+{
+	struct imx_nwl_dsi *dsi =
+		container_of(dsi_host, struct imx_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 imx_nwl_dsi *dsi =
+		container_of(dsi_host, struct imx_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 imx_nwl_dsi *dsi, u32 status)
+{
+	struct device *dev = dsi->dev;
+	struct mipi_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 & RX_PKT_HDR_RCVD))
+			return false;
+		/* Get the RX header and parse it */
+		val = nwl_dsi_read(dsi, RX_PKT_HEADER);
+		word_count = WC(val);
+		channel = RX_VC(val);
+		data_type = 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 & RX_PKT_PAYLOAD_DATA_RCVD))
+		return false;
+
+	/* Read the RX payload */
+	while (word_count >= 4) {
+		val = nwl_dsi_read(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, 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 imx_nwl_dsi *dsi, u32 status)
+{
+	struct mipi_dsi_transfer *xfer = dsi->xfer;
+	bool end_packet = false;
+
+	if (!xfer)
+		return;
+
+	if (status & TX_FIFO_OVFLW) {
+		DRM_DEV_ERROR_RATELIMITED(dsi->dev, "tx fifo overflow\n");
+		return;
+	}
+
+	if (status & HS_TX_TIMEOUT) {
+		DRM_DEV_ERROR_RATELIMITED(dsi->dev, "HS tx timeout\n");
+		return;
+	}
+
+	if (xfer->direction == DSI_PACKET_SEND && status & TX_PKT_DONE) {
+		xfer->status = xfer->tx_len;
+		end_packet = true;
+	} else if (status & DPHY_DIRECTION &&
+		   ((status & (RX_PKT_HDR_RCVD | 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 imx_nwl_dsi *dsi)
+{
+	struct mipi_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 = get_unaligned_le32(payload);
+		hs_workaround |= !(val & 0xFFFF00);
+		nwl_dsi_write(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, 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 && USE_E11418_HS_MODE_QUIRK(dsi->quirks))) {
+		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 = WC(word_count) |
+		TX_VC(xfer->msg->channel) |
+		TX_DT(xfer->msg->type) |
+		HS_SEL(hs_mode) |
+		BTA_TX(xfer->need_bta);
+	nwl_dsi_write(dsi, PKT_CONTROL, val);
+
+	/* Send packet command */
+	nwl_dsi_write(dsi, SEND_PACKET, 0x1);
+}
+
+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);
+
+	/* Initiate the DSI packet transmision */
+	nwl_dsi_begin_transmission(dsi);
+
+	if (!wait_for_completion_timeout(&xfer.completed, 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 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);
+
+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);
+
+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);
diff --git a/drivers/gpu/drm/bridge/imx-nwl/nwl-dsi.h b/drivers/gpu/drm/bridge/imx-nwl/nwl-dsi.h
new file mode 100644
index 000000000000..7bcf804843e2
--- /dev/null
+++ b/drivers/gpu/drm/bridge/imx-nwl/nwl-dsi.h
@@ -0,0 +1,111 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * i.MX8 NWL MIPI DSI host driver
+ *
+ * Copyright (C) 2017 NXP
+ * Copyright (C) 2019 Purism SPC
+ */
+#ifndef __NWL_DSI_H__
+#define __NWL_DSI_H__
+
+#include <drm/drm_mipi_dsi.h>
+
+/* DSI HOST registers */
+#define CFG_NUM_LANES			0x0
+#define CFG_NONCONTINUOUS_CLK		0x4
+#define CFG_T_PRE			0x8
+#define CFG_T_POST			0xc
+#define CFG_TX_GAP			0x10
+#define CFG_AUTOINSERT_EOTP		0x14
+#define CFG_EXTRA_CMDS_AFTER_EOTP	0x18
+#define CFG_HTX_TO_COUNT		0x1c
+#define CFG_LRX_H_TO_COUNT		0x20
+#define CFG_BTA_H_TO_COUNT		0x24
+#define CFG_TWAKEUP			0x28
+#define CFG_STATUS_OUT			0x2c
+#define RX_ERROR_STATUS			0x30
+
+/* DSI DPI registers */
+#define PIXEL_PAYLOAD_SIZE		0x200
+#define PIXEL_FIFO_SEND_LEVEL		0x204
+#define INTERFACE_COLOR_CODING		0x208
+#define PIXEL_FORMAT			0x20c
+#define VSYNC_POLARITY			0x210
+#define VSYNC_POLARITY_ACTIVE_LOW	0
+#define VSYNC_POLARITY_ACTIVE_HIGH	BIT(1)
+
+#define HSYNC_POLARITY			0x214
+#define HSYNC_POLARITY_ACTIVE_LOW	0
+#define HSYNC_POLARITY_ACTIVE_HIGH	BIT(1)
+
+#define VIDEO_MODE			0x218
+#define HFP				0x21c
+#define HBP				0x220
+#define HSA				0x224
+#define ENABLE_MULT_PKTS		0x228
+#define VBP				0x22c
+#define VFP				0x230
+#define BLLP_MODE			0x234
+#define USE_NULL_PKT_BLLP		0x238
+#define VACTIVE				0x23c
+#define VC				0x240
+
+/* DSI APB PKT control */
+#define TX_PAYLOAD			0x280
+#define PKT_CONTROL			0x284
+#define SEND_PACKET			0x288
+#define PKT_STATUS			0x28c
+#define PKT_FIFO_WR_LEVEL		0x290
+#define PKT_FIFO_RD_LEVEL		0x294
+#define RX_PAYLOAD			0x298
+#define RX_PKT_HEADER			0x29c
+
+/* DSI IRQ handling */
+#define IRQ_STATUS			0x2a0
+#define SM_NOT_IDLE			BIT(0)
+#define TX_PKT_DONE			BIT(1)
+#define DPHY_DIRECTION			BIT(2)
+#define TX_FIFO_OVFLW			BIT(3)
+#define TX_FIFO_UDFLW			BIT(4)
+#define RX_FIFO_OVFLW			BIT(5)
+#define RX_FIFO_UDFLW			BIT(6)
+#define RX_PKT_HDR_RCVD			BIT(7)
+#define RX_PKT_PAYLOAD_DATA_RCVD	BIT(8)
+#define BTA_TIMEOUT			BIT(29)
+#define LP_RX_TIMEOUT			BIT(30)
+#define HS_TX_TIMEOUT			BIT(31)
+
+#define IRQ_STATUS2			0x2a4
+#define SINGLE_BIT_ECC_ERR		BIT(0)
+#define MULTI_BIT_ECC_ERR		BIT(1)
+#define CRC_ERR				BIT(2)
+
+#define IRQ_MASK			0x2a8
+#define SM_NOT_IDLE_MASK		BIT(0)
+#define TX_PKT_DONE_MASK		BIT(1)
+#define DPHY_DIRECTION_MASK		BIT(2)
+#define TX_FIFO_OVFLW_MASK		BIT(3)
+#define TX_FIFO_UDFLW_MASK		BIT(4)
+#define RX_FIFO_OVFLW_MASK		BIT(5)
+#define RX_FIFO_UDFLW_MASK		BIT(6)
+#define RX_PKT_HDR_RCVD_MASK		BIT(7)
+#define RX_PKT_PAYLOAD_DATA_RCVD_MASK	BIT(8)
+#define BTA_TIMEOUT_MASK		BIT(29)
+#define LP_RX_TIMEOUT_MASK		BIT(30)
+#define HS_TX_TIMEOUT_MASK		BIT(31)
+
+#define IRQ_MASK2			0x2ac
+#define SINGLE_BIT_ECC_ERR_MASK		BIT(0)
+#define MULTI_BIT_ECC_ERR_MASK		BIT(1)
+#define 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 imx_nwl_dsi *dsi);
+int nwl_dsi_disable(struct imx_nwl_dsi *dsi);
+int nwl_dsi_get_dphy_params(struct imx_nwl_dsi *dsi,
+			    const struct drm_display_mode *mode,
+			    union phy_configure_opts *phy_opts);
+
+#endif /* __NWL_DSI_H__ */
-- 
2.20.1


_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related

* [PATCH 2/3] dt-bindings: display/bridge: Add binding for IMX NWL mipi dsi host controller
From: Guido Günther @ 2019-07-24 15:52 UTC (permalink / raw)
  To: David Airlie, Daniel Vetter, Rob Herring, Mark Rutland, Shawn Guo,
	Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam,
	NXP Linux Team, Andrzej Hajda, Neil Armstrong, Laurent Pinchart,
	Jonas Karlman, Jernej Skrabec, Lee Jones, Guido Günther,
	dri-devel, devicetree, linux-arm-kernel, linux-kernel
In-Reply-To: <cover.1563983037.git.agx@sigxcpu.org>

The Northwest Logic MIPI DSI IP core can be found in NXPs i.MX8 SoCs.

Signed-off-by: Guido Günther <agx@sigxcpu.org>
---
 .../bindings/display/bridge/imx-nwl-dsi.txt   | 89 +++++++++++++++++++
 1 file changed, 89 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/display/bridge/imx-nwl-dsi.txt

diff --git a/Documentation/devicetree/bindings/display/bridge/imx-nwl-dsi.txt b/Documentation/devicetree/bindings/display/bridge/imx-nwl-dsi.txt
new file mode 100644
index 000000000000..288fdb726d5a
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/bridge/imx-nwl-dsi.txt
@@ -0,0 +1,89 @@
+Northwest Logic MIPI-DSI on imx SoCs
+=====================================
+
+NWL MIPI-DSI host controller found on i.MX8 platforms. This is a
+dsi bridge for the for the NWL MIPI-DSI host.
+
+Required properties:
+- compatible: 		"fsl,<chip>-nwl-dsi"
+	The following strings are expected:
+			"fsl,imx8mq-nwl-dsi"
+- reg: 			the register range of the MIPI-DSI controller
+- interrupts: 		the interrupt number for this module
+- clock, clock-names: 	phandles to the MIPI-DSI clocks
+	The following clocks are expected on all platforms:
+		"core"    - DSI core clock
+		"tx_esc"  - TX_ESC clock (used in escape mode)
+		"rx_esc"  - RX_ESC clock (used in escape mode)
+		"phy_ref" - PHY_REF clock. Clock is managed by the phy. Only
+                            used to read the clock rate.
+- assigned-clocks:	phandles to clocks that require initial configuration
+- assigned-clock-rates:	rates of the clocks that require initial configuration
+	The following clocks need to have an initial configuration:
+	"tx_esc" (20 MHz) and "rx_esc" (80 Mhz).
+- phys: 		phandle to the phy module representing the DPHY
+			inside the MIPI-DSI IP block
+- phy-names: 		should be "dphy"
+
+Optional properties:
+- power-domains 	phandle to the power domain
+- src			phandle to the system reset controller (required on
+			i.MX8MQ)
+- mux-sel		phandle to the MUX register set (required on i.MX8MQ)
+- assigned-clock-parents phandles to parent clocks that needs to be assigned as
+			parents to clocks defined in assigned-clocks
+
+Example:
+	mipi_dsi: mipi_dsi@30a00000 {
+		#address-cells = <1>;
+		#size-cells = <0>;
+		compatible = "fsl,imx8mq-nwl-dsi";
+		reg = <0x30A00000 0x300>;
+		clocks = <&clk IMX8MQ_CLK_DSI_CORE>,
+			 <&clk IMX8MQ_CLK_DSI_AHB>,
+			 <&clk IMX8MQ_CLK_DSI_IPG_DIV>,
+			 <&clk IMX8MQ_CLK_DSI_PHY_REF>;
+		clock-names = "core", "rx_esc", "tx_esc", "phy_ref";
+		assigned-clocks = <&clk IMX8MQ_CLK_DSI_AHB>,
+				  <&clk IMX8MQ_CLK_DSI_CORE>,
+				  <&clk IMX8MQ_CLK_DSI_IPG_DIV>;
+		assigned-clock-parents = <&clk IMX8MQ_SYS1_PLL_80M>,
+					 <&clk IMX8MQ_SYS1_PLL_266M>;
+		assigned-clock-rates = <80000000>,
+				       <266000000>,
+				       <20000000>;
+		interrupts = <GIC_SPI 34 IRQ_TYPE_LEVEL_HIGH>;
+		power-domains = <&pgc_mipi>;
+		src = <&src>;
+		mux-sel = <&iomuxc_gpr>;
+		phys = <&dphy>;
+		phy-names = "dphy";
+		status = "okay";
+
+		panel@0 {
+			compatible = "...";
+			port {
+			     panel_in: endpoint {
+				       remote-endpoint = <&mipi_dsi_out>;
+			     };
+			};
+		};
+
+		ports {
+		      #address-cells = <1>;
+		      #size-cells = <0>;
+
+		      port@0 {
+			     reg = <0>;
+			     mipi_dsi_in: endpoint {
+					  remote-endpoint = <&dcss_disp0_mipi_dsi>;
+			     };
+		      };
+		      port@1 {
+			     reg = <1>;
+			     mipi_dsi_out: endpoint {
+					   remote-endpoint = <&panel_in>;
+			     };
+		      };
+		};
+	};
-- 
2.20.1

^ permalink raw reply related

* [PATCH 1/3] arm64: imx8mq: add imx8mq iomux-gpr field defines
From: Guido Günther @ 2019-07-24 15:52 UTC (permalink / raw)
  To: David Airlie, Daniel Vetter, Rob Herring, Mark Rutland, Shawn Guo,
	Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam,
	NXP Linux Team, Andrzej Hajda, Neil Armstrong, Laurent Pinchart,
	Jonas Karlman, Jernej Skrabec, Lee Jones, Guido Günther,
	dri-devel, devicetree, linux-arm-kernel, linux-kernel,
	Robert Chiras
In-Reply-To: <cover.1563983037.git.agx@sigxcpu.org>

This adds all the gpr registers and the define needed for selecting
the input source in the imx-nwl drm bridge.

Signed-off-by: Guido Günther <agx@sigxcpu.org>
---
 include/linux/mfd/syscon/imx8mq-iomuxc-gpr.h | 62 ++++++++++++++++++++
 1 file changed, 62 insertions(+)
 create mode 100644 include/linux/mfd/syscon/imx8mq-iomuxc-gpr.h

diff --git a/include/linux/mfd/syscon/imx8mq-iomuxc-gpr.h b/include/linux/mfd/syscon/imx8mq-iomuxc-gpr.h
new file mode 100644
index 000000000000..62e85ffacfad
--- /dev/null
+++ b/include/linux/mfd/syscon/imx8mq-iomuxc-gpr.h
@@ -0,0 +1,62 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2017 NXP
+ *               2019 Purism SPC
+ */
+
+#ifndef __LINUX_IMX8MQ_IOMUXC_GPR_H
+#define __LINUX_IMX8MQ_IOMUXC_GPR_H
+
+#define IOMUXC_GPR0	0x00
+#define IOMUXC_GPR1	0x04
+#define IOMUXC_GPR2	0x08
+#define IOMUXC_GPR3	0x0c
+#define IOMUXC_GPR4	0x10
+#define IOMUXC_GPR5	0x14
+#define IOMUXC_GPR6	0x18
+#define IOMUXC_GPR7	0x1c
+#define IOMUXC_GPR8	0x20
+#define IOMUXC_GPR9	0x24
+#define IOMUXC_GPR10	0x28
+#define IOMUXC_GPR11	0x2c
+#define IOMUXC_GPR12	0x30
+#define IOMUXC_GPR13	0x34
+#define IOMUXC_GPR14	0x38
+#define IOMUXC_GPR15	0x3c
+#define IOMUXC_GPR16	0x40
+#define IOMUXC_GPR17	0x44
+#define IOMUXC_GPR18	0x48
+#define IOMUXC_GPR19	0x4c
+#define IOMUXC_GPR20	0x50
+#define IOMUXC_GPR21	0x54
+#define IOMUXC_GPR22	0x58
+#define IOMUXC_GPR23	0x5c
+#define IOMUXC_GPR24	0x60
+#define IOMUXC_GPR25	0x64
+#define IOMUXC_GPR26	0x68
+#define IOMUXC_GPR27	0x6c
+#define IOMUXC_GPR28	0x70
+#define IOMUXC_GPR29	0x74
+#define IOMUXC_GPR30	0x78
+#define IOMUXC_GPR31	0x7c
+#define IOMUXC_GPR32	0x80
+#define IOMUXC_GPR33	0x84
+#define IOMUXC_GPR34	0x88
+#define IOMUXC_GPR35	0x8c
+#define IOMUXC_GPR36	0x90
+#define IOMUXC_GPR37	0x94
+#define IOMUXC_GPR38	0x98
+#define IOMUXC_GPR39	0x9c
+#define IOMUXC_GPR40	0xa0
+#define IOMUXC_GPR41	0xa4
+#define IOMUXC_GPR42	0xa8
+#define IOMUXC_GPR43	0xac
+#define IOMUXC_GPR44	0xb0
+#define IOMUXC_GPR45	0xb4
+#define IOMUXC_GPR46	0xb8
+#define IOMUXC_GPR47	0xbc
+
+/* i.MX8Mq iomux gpr register field defines */
+#define IMX8MQ_GPR13_MIPI_MUX_SEL		BIT(2)
+
+#endif /* __LINUX_IMX8MQ_IOMUXC_GPR_H */
-- 
2.20.1

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related

* [PATCH 0/3] drm: bridge: Add NWL MIPI DSI host controller support
From: Guido Günther @ 2019-07-24 15:52 UTC (permalink / raw)
  To: David Airlie, Daniel Vetter, Rob Herring, Mark Rutland, Shawn Guo,
	Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam,
	NXP Linux Team, Andrzej Hajda, Neil Armstrong, Laurent Pinchart,
	Jonas Karlman, Jernej Skrabec, Lee Jones, Guido Günther,
	dri-devel, devicetree, linux-arm-kernel, linux-kernel

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 core can also be found on e.g.
i.MX8QXP. I added the necessary hooks to support other imx8 variants but since
I only have imx8mq boards to test I omitted the platform data for other SoCs.

The code is based on NXPs BSP so I added Robert Chiras as
Co-authored-by. Robert, if this looks sane could you add your
Signed-off-by:?

The most notable changes over the BSP driver are
 - Calculate HS mode timing from phy_configure_opts_mipi_dphy
 - Perform all clock setup via DT
 - Merge nwl-imx and nwl drivers
 - Add B0 silion revision quirk
 - become a bridge driver to hook into mxsfb (from what I read[0] DCSS, which
   also can drive the nwl on the imx8mq will likely not become part of
   imx-display-subsystem so it makes sense to make it drive a bridge for dsi as
   well).
 - Use panel_bridge to attach the panel

This has been tested on a Librem 5 devkit using mxsfb with Robert's patches[1]
and the rocktech-jh057n00900 panel driver. The DCSS can later on also act as
input source too.

Changes from v0:
- Add quirk for IMQ8MQ silicon B0 revision to not mess with the
  system reset controller on power down since enable() won't work
  otherwise.
- Drop devm_free_irq() handled by the device driver core
- Disable tx esc clock after the phy power down to unbreak
  disable/enable (unblank/blank)
- Add ports to dt binding docs
- Select GENERIC_PHY_MIPI_DPHY instead of GENERIC_PHY for
  phy_mipi_dphy_get_default_config
- Select DRM_MIPI_DSI
- Include drm_print.h to fix build on next-20190408
- Drop some debugging messages
- Newline terminate all DRM_ printouts
- Turn component driver into a drm bridge

[0]: https://lists.freedesktop.org/archives/dri-devel/2019-May/219484.html
[1]: https://patchwork.freedesktop.org/series/62822/

Guido Günther (3):
  arm64: imx8mq: add imx8mq iomux-gpr field defines
  dt-bindings: display/bridge: Add binding for IMX NWL mipi dsi host
    controller
  drm/bridge: Add NWL MIPI DSI host controller support

 .../bindings/display/bridge/imx-nwl-dsi.txt   |  89 +++
 drivers/gpu/drm/bridge/Kconfig                |   2 +
 drivers/gpu/drm/bridge/Makefile               |   1 +
 drivers/gpu/drm/bridge/imx-nwl/Kconfig        |  15 +
 drivers/gpu/drm/bridge/imx-nwl/Makefile       |   2 +
 drivers/gpu/drm/bridge/imx-nwl/nwl-drv.c      | 529 +++++++++++++
 drivers/gpu/drm/bridge/imx-nwl/nwl-drv.h      |  72 ++
 drivers/gpu/drm/bridge/imx-nwl/nwl-dsi.c      | 745 ++++++++++++++++++
 drivers/gpu/drm/bridge/imx-nwl/nwl-dsi.h      | 111 +++
 include/linux/mfd/syscon/imx8mq-iomuxc-gpr.h  |  62 ++
 10 files changed, 1628 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/display/bridge/imx-nwl-dsi.txt
 create mode 100644 drivers/gpu/drm/bridge/imx-nwl/Kconfig
 create mode 100644 drivers/gpu/drm/bridge/imx-nwl/Makefile
 create mode 100644 drivers/gpu/drm/bridge/imx-nwl/nwl-drv.c
 create mode 100644 drivers/gpu/drm/bridge/imx-nwl/nwl-drv.h
 create mode 100644 drivers/gpu/drm/bridge/imx-nwl/nwl-dsi.c
 create mode 100644 drivers/gpu/drm/bridge/imx-nwl/nwl-dsi.h
 create mode 100644 include/linux/mfd/syscon/imx8mq-iomuxc-gpr.h

-- 
2.20.1

^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox