linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 0/5] Add driver for GE B850v3 LVDS/DP++ Bridge
@ 2016-05-30 16:39 Peter Senna Tschudin
  2016-05-30 16:39 ` [PATCH 1/5] drm/imx-ldb: Add support to drm-bridge Peter Senna Tschudin
                   ` (8 more replies)
  0 siblings, 9 replies; 64+ messages in thread
From: Peter Senna Tschudin @ 2016-05-30 16:39 UTC (permalink / raw)
  To: airlied, akpm, davem, devicetree, dri-devel, enric.balletbo,
	galak, gregkh, heiko, ijc+devicetree, jslaby, kernel,
	linux-arm-kernel, linux, linux-kernel, linux, mark.rutland,
	martin.donnelly, martyn.welch, mchehab, pawel.moll, peter.senna,
	p.zabel, rmk+kernel, robh+dt, shawnguo, tiwai, treding, ykk

The series adds a driver that creates a drm_bridge and a drm_connector for the
LVDS to DP++ display bridge of the GE B850v3. There are two physical bridges on
the video signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++).
However the physical bridges are automatically configured by the input video
signal, and the driver has no access to the video processing pipeline. The
driver is only needed to read EDID from the STDP2690 and to handle HPD events
from the STDP4028. The driver communicates with both bridges over i2c. The
video signal pipeline is as follows:

  Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output

The patches from the series:
 [1/5] Change the imx-ldb driver to allow attaching a bridge and not only a LVDS
       panel.

 [2/5] Configure the mapping between IPUs and external displays on the dts file
       of the B850v3. Needed so the GPU can drive two Full-HD monitors.

 [3/5] Devicetree documentation for the GE B850v3 LVDS/DP++ Bridge

 [4/5] Add the driver, make changes to MAINTAINERS, Kconfig and Makefile

 [5/5] Make the changes to the B850v3 dts file to enalbe the GE B850v3
       LVDS/DP++ Bridge.

Peter Senna Tschudin (5):
  drm/imx-ldb: Add support to drm-bridge
  dts/imx6q-b850v3: Configure IPU assignment order
  Documentation/devicetree/bindings: b850v3_lvds_dp
  drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge
  dts/imx6q-b850v3: Use GE B850v3 LVDS/DP++ Bridge

 .../devicetree/bindings/ge/b850v3_lvds_dp.txt      |  38 ++
 MAINTAINERS                                        |   8 +
 arch/arm/boot/dts/imx6q-b850v3.dts                 |  36 ++
 drivers/gpu/drm/bridge/Kconfig                     |   8 +
 drivers/gpu/drm/bridge/Makefile                    |   1 +
 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c         | 397 +++++++++++++++++++++
 drivers/gpu/drm/imx/imx-ldb.c                      |  75 ++--
 7 files changed, 542 insertions(+), 21 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt
 create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c

-- 
2.5.5

^ permalink raw reply	[flat|nested] 64+ messages in thread

* [PATCH 1/5] drm/imx-ldb: Add support to drm-bridge
  2016-05-30 16:39 [PATCH 0/5] Add driver for GE B850v3 LVDS/DP++ Bridge Peter Senna Tschudin
@ 2016-05-30 16:39 ` Peter Senna Tschudin
  2016-06-02 13:09   ` Philipp Zabel
  2016-05-30 16:39 ` [PATCH 2/5] arm/dts/imx6q-b850v3: Configure IPU assignment order Peter Senna Tschudin
                   ` (7 subsequent siblings)
  8 siblings, 1 reply; 64+ messages in thread
From: Peter Senna Tschudin @ 2016-05-30 16:39 UTC (permalink / raw)
  To: airlied, akpm, davem, devicetree, dri-devel, enric.balletbo,
	galak, gregkh, heiko, ijc+devicetree, jslaby, kernel,
	linux-arm-kernel, linux, linux-kernel, linux, mark.rutland,
	martin.donnelly, martyn.welch, mchehab, pawel.moll, peter.senna,
	p.zabel, rmk+kernel, robh+dt, shawnguo, tiwai, treding, ykk

Add support to attach a drm_bridge to imx-ldb in addition to
existing support to attach a LVDS panel.

Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com>
---
 drivers/gpu/drm/imx/imx-ldb.c | 75 +++++++++++++++++++++++++++++++------------
 1 file changed, 54 insertions(+), 21 deletions(-)

diff --git a/drivers/gpu/drm/imx/imx-ldb.c b/drivers/gpu/drm/imx/imx-ldb.c
index a58eee5..7233a81 100644
--- a/drivers/gpu/drm/imx/imx-ldb.c
+++ b/drivers/gpu/drm/imx/imx-ldb.c
@@ -57,7 +57,11 @@ struct imx_ldb_channel {
 	struct imx_ldb *ldb;
 	struct drm_connector connector;
 	struct drm_encoder encoder;
+
+	/* Defines what is connected to the ldb, only one at a time */
 	struct drm_panel *panel;
+	struct drm_bridge *ext_bridge;
+
 	struct device_node *child;
 	int chno;
 	void *edid;
@@ -295,6 +299,10 @@ static void imx_ldb_encoder_mode_set(struct drm_encoder *encoder,
 	}
 }
 
+static void imx_ldb_encoder_enable(struct drm_encoder *encoder)
+{
+}
+
 static void imx_ldb_encoder_disable(struct drm_encoder *encoder)
 {
 	struct imx_ldb_channel *imx_ldb_ch = enc_to_imx_ldb_ch(encoder);
@@ -373,6 +381,7 @@ static const struct drm_encoder_helper_funcs imx_ldb_encoder_helper_funcs = {
 	.prepare = imx_ldb_encoder_prepare,
 	.commit = imx_ldb_encoder_commit,
 	.mode_set = imx_ldb_encoder_mode_set,
+	.enable = imx_ldb_encoder_enable,
 	.disable = imx_ldb_encoder_disable,
 };
 
@@ -417,16 +426,28 @@ static int imx_ldb_register(struct drm_device *drm,
 	drm_encoder_init(drm, &imx_ldb_ch->encoder, &imx_ldb_encoder_funcs,
 			 DRM_MODE_ENCODER_LVDS, NULL);
 
-	drm_connector_helper_add(&imx_ldb_ch->connector,
-			&imx_ldb_connector_helper_funcs);
-	drm_connector_init(drm, &imx_ldb_ch->connector,
-			   &imx_ldb_connector_funcs, DRM_MODE_CONNECTOR_LVDS);
-
-	if (imx_ldb_ch->panel)
+	if (imx_ldb_ch->panel) {
+		drm_connector_helper_add(&imx_ldb_ch->connector,
+				&imx_ldb_connector_helper_funcs);
+		drm_connector_init(drm, &imx_ldb_ch->connector,
+				&imx_ldb_connector_funcs,
+				DRM_MODE_CONNECTOR_LVDS);
 		drm_panel_attach(imx_ldb_ch->panel, &imx_ldb_ch->connector);
 
-	drm_mode_connector_attach_encoder(&imx_ldb_ch->connector,
-			&imx_ldb_ch->encoder);
+		drm_mode_connector_attach_encoder(&imx_ldb_ch->connector,
+				&imx_ldb_ch->encoder);
+	}
+
+	if (imx_ldb_ch->ext_bridge) {
+		imx_ldb_ch->ext_bridge->encoder = &imx_ldb_ch->encoder;
+
+		imx_ldb_ch->encoder.bridge = imx_ldb_ch->ext_bridge;
+		ret = drm_bridge_attach(drm, imx_ldb_ch->ext_bridge);
+		if (ret) {
+			DRM_ERROR("Failed to initialize bridge with drm\n");
+			return ret;
+		}
+	}
 
 	return 0;
 }
@@ -583,23 +604,35 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data)
 			endpoint = of_get_child_by_name(port, "endpoint");
 			if (endpoint) {
 				remote = of_graph_get_remote_port_parent(endpoint);
-				if (remote)
-					channel->panel = of_drm_find_panel(remote);
-				else
-					return -EPROBE_DEFER;
-				if (!channel->panel) {
-					dev_err(dev, "panel not found: %s\n",
-						remote->full_name);
-					return -EPROBE_DEFER;
+				if (remote) {
+					/* Only one of these two will succeed */
+					channel->panel =
+						of_drm_find_panel(remote);
+
+					channel->ext_bridge =
+						of_drm_find_bridge(remote);
+
+					/*
+					 * If the bridge is compiled as a
+					 * module, it may take some time until
+					 * the bridge driver is available.
+					 * Defer until the bridge driver is
+					 * ready.
+					 */
+					if (!channel->panel &&
+							!channel->ext_bridge)
+						return -EPROBE_DEFER;
 				}
 			}
 		}
 
-		edidp = of_get_property(child, "edid", &channel->edid_len);
-		if (edidp) {
-			channel->edid = kmemdup(edidp, channel->edid_len,
-						GFP_KERNEL);
-		} else if (!channel->panel) {
+		if (channel->panel) {
+			edidp = of_get_property(child, "edid",
+					&channel->edid_len);
+			if (edidp)
+				channel->edid = kmemdup(edidp,
+						channel->edid_len, GFP_KERNEL);
+		} else {
 			ret = of_get_drm_display_mode(child, &channel->mode, 0);
 			if (!ret)
 				channel->mode_valid = 1;
-- 
2.5.5

^ permalink raw reply related	[flat|nested] 64+ messages in thread

* [PATCH 2/5] arm/dts/imx6q-b850v3: Configure IPU assignment order
  2016-05-30 16:39 [PATCH 0/5] Add driver for GE B850v3 LVDS/DP++ Bridge Peter Senna Tschudin
  2016-05-30 16:39 ` [PATCH 1/5] drm/imx-ldb: Add support to drm-bridge Peter Senna Tschudin
@ 2016-05-30 16:39 ` Peter Senna Tschudin
  2016-05-30 16:49   ` Fabio Estevam
  2016-06-02 12:55   ` Philipp Zabel
  2016-05-30 16:39 ` [PATCH 3/5] Documentation/devicetree/bindings: Add b850v3_lvds_dp Peter Senna Tschudin
                   ` (6 subsequent siblings)
  8 siblings, 2 replies; 64+ messages in thread
From: Peter Senna Tschudin @ 2016-05-30 16:39 UTC (permalink / raw)
  To: airlied, akpm, davem, devicetree, dri-devel, enric.balletbo,
	galak, gregkh, heiko, ijc+devicetree, jslaby, kernel,
	linux-arm-kernel, linux, linux-kernel, linux, mark.rutland,
	martin.donnelly, martyn.welch, mchehab, pawel.moll, peter.senna,
	p.zabel, rmk+kernel, robh+dt, shawnguo, tiwai, treding, ykk

Configure the IPU assignment order to assign one IPU per external
display. A single IPU can drive multiple external displays but there are
resolution restrictions. After this patch the GPU is capalbe of driving two
Full-HD monitors.

Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com>
---
 arch/arm/boot/dts/imx6q-b850v3.dts | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/arch/arm/boot/dts/imx6q-b850v3.dts b/arch/arm/boot/dts/imx6q-b850v3.dts
index 167f744..88a70de 100644
--- a/arch/arm/boot/dts/imx6q-b850v3.dts
+++ b/arch/arm/boot/dts/imx6q-b850v3.dts
@@ -51,6 +51,11 @@
 	chosen {
 		stdout-path = &uart3;
 	};
+
+	display-subsystem {
+		compatible = "fsl,imx-display-subsystem";
+		ports = <&ipu1_di0>, <&ipu2_di0>, <&ipu1_di1>, <&ipu2_di1>;
+	};
 };
 
 &clks {
-- 
2.5.5

^ permalink raw reply related	[flat|nested] 64+ messages in thread

* [PATCH 3/5] Documentation/devicetree/bindings: Add b850v3_lvds_dp
  2016-05-30 16:39 [PATCH 0/5] Add driver for GE B850v3 LVDS/DP++ Bridge Peter Senna Tschudin
  2016-05-30 16:39 ` [PATCH 1/5] drm/imx-ldb: Add support to drm-bridge Peter Senna Tschudin
  2016-05-30 16:39 ` [PATCH 2/5] arm/dts/imx6q-b850v3: Configure IPU assignment order Peter Senna Tschudin
@ 2016-05-30 16:39 ` Peter Senna Tschudin
  2016-06-02 12:49   ` Philipp Zabel
  2016-06-02 22:57   ` Rob Herring
  2016-05-30 16:39 ` [PATCH 4/5] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge Peter Senna Tschudin
                   ` (5 subsequent siblings)
  8 siblings, 2 replies; 64+ messages in thread
From: Peter Senna Tschudin @ 2016-05-30 16:39 UTC (permalink / raw)
  To: airlied, akpm, davem, devicetree, dri-devel, enric.balletbo,
	galak, gregkh, heiko, ijc+devicetree, jslaby, kernel,
	linux-arm-kernel, linux, linux-kernel, linux, mark.rutland,
	martin.donnelly, martyn.welch, mchehab, pawel.moll, peter.senna,
	p.zabel, rmk+kernel, robh+dt, shawnguo, tiwai, treding, ykk

Devicetree bindings documentation for the GE B850v3 LVDS/DP++
display bridge.

Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com>
---
 .../devicetree/bindings/ge/b850v3_lvds_dp.txt      | 38 ++++++++++++++++++++++
 1 file changed, 38 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt

diff --git a/Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt b/Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt
new file mode 100644
index 0000000..32e123a
--- /dev/null
+++ b/Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt
@@ -0,0 +1,38 @@
+Driver for GE B850v3 LVDS/DP++ display bridge
+
+Required properties:
+  - compatible : should be "ge,b850v3_lvds_dp".
+  - reg : should contain the address used to ack the interrupts.
+  - interrupt-parent : should link to the gpio used as interrupt
+    source on the host.
+  - interrupts : one interrupt should be described here, as in
+    <0 IRQ_TYPE_LEVEL_HIGH>.
+  - edid-reg : should contain the address used to read edid information
+  - port : should describe the vide signal connection between the host
+    and the bridge.
+
+Example:
+
+&mux2_i2c2 {
+	status = "okay";
+	clock-frequency = <100000>;
+
+	b850v3_dp_bridge {
+		compatible = "ge,b850v3_lvds_dp";
+		#address-cells = <1>;
+		#size-cells = <0>;
+
+		reg = <0x73>;
+		interrupt-parent = <&gpio2>;
+		interrupts = <0 IRQ_TYPE_LEVEL_HIGH>;
+
+		edid-reg = <0x72>;
+
+		port@0 {
+			reg = <0>;
+			b850v3_dp_bridge_in: endpoint {
+				remote-endpoint = <&lvds0_out>;
+			};
+		};
+	};
+};
-- 
2.5.5

^ permalink raw reply related	[flat|nested] 64+ messages in thread

* [PATCH 4/5] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge
  2016-05-30 16:39 [PATCH 0/5] Add driver for GE B850v3 LVDS/DP++ Bridge Peter Senna Tschudin
                   ` (2 preceding siblings ...)
  2016-05-30 16:39 ` [PATCH 3/5] Documentation/devicetree/bindings: Add b850v3_lvds_dp Peter Senna Tschudin
@ 2016-05-30 16:39 ` Peter Senna Tschudin
  2016-05-31  7:48   ` Enric Balletbo Serra
  2016-05-30 16:39 ` [PATCH 5/5] arm/dts/imx6q-b850v3: Use " Peter Senna Tschudin
                   ` (4 subsequent siblings)
  8 siblings, 1 reply; 64+ messages in thread
From: Peter Senna Tschudin @ 2016-05-30 16:39 UTC (permalink / raw)
  To: airlied, akpm, davem, devicetree, dri-devel, enric.balletbo,
	galak, gregkh, heiko, ijc+devicetree, jslaby, kernel,
	linux-arm-kernel, linux, linux-kernel, linux, mark.rutland,
	martin.donnelly, martyn.welch, mchehab, pawel.moll, peter.senna,
	p.zabel, rmk+kernel, robh+dt, shawnguo, tiwai, treding, ykk

This driver creates a drm_bridge and a drm_connector for the LVDS to DP++
display bridge of the GE B850v3(imx6q-b850v3.dts). There are two physical
bridges on the video signal pipeline: a STDP4028(LVDS to DP) and a
STDP2690(DP to DP++). However the physical bridges are automatically
configured by the input video signal, and the driver has no access to
the video processing pipeline. The driver is only needed to read EDID
from the STDP2690 and to handle HPD events from the STDP4028. The driver
communicates with both bridges over i2c. The video signal pipeline is as
follows:

  Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output

Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com>
---
 MAINTAINERS                                |   8 +
 drivers/gpu/drm/bridge/Kconfig             |   8 +
 drivers/gpu/drm/bridge/Makefile            |   1 +
 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c | 397 +++++++++++++++++++++++++++++
 4 files changed, 414 insertions(+)
 create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 3273ffa..7bb5e89 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -5009,6 +5009,14 @@ W:	https://linuxtv.org
 S:	Maintained
 F:	drivers/media/radio/radio-gemtek*
 
+GENERAL ELECTRIC B850V3 LVDS/DP++ BRIDGE
+M:	Martin Donnelly <martin.donnelly@ge.com>
+M:	Peter Senna Tschudin <peter.senna@collabora.com>
+M:	Martyn Welch <martyn.welch@collabora.co.uk>
+S:	Maintained
+F:	drivers/gpu/drm/bridge/ge_b850v3_dp2.c
+F:	Documentation/devicetree/bindings/ge/b850v3_dp2_bridge.txt
+
 GENERIC GPIO I2C DRIVER
 M:	Haavard Skinnemoen <hskinnemoen@gmail.com>
 S:	Supported
diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
index 8f7423f..e9c32fc 100644
--- a/drivers/gpu/drm/bridge/Kconfig
+++ b/drivers/gpu/drm/bridge/Kconfig
@@ -52,4 +52,12 @@ config DRM_PARADE_PS8622
 
 source "drivers/gpu/drm/bridge/analogix/Kconfig"
 
+config DRM_GE_B850V3_LVDS_DP
+	tristate "LVDS/DP bridge"
+	depends on OF
+	select DRM_KMS_HELPER
+	select DRM_PANEL
+	---help---
+	  Driver for GE B850v3 DP2 LVDS to DP+ Bridge
+
 endmenu
diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
index 96b13b3..ba2e355 100644
--- a/drivers/gpu/drm/bridge/Makefile
+++ b/drivers/gpu/drm/bridge/Makefile
@@ -6,3 +6,4 @@ obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o
 obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o
 obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o
 obj-$(CONFIG_DRM_ANALOGIX_DP) += analogix/
+obj-$(CONFIG_DRM_GE_B850V3_LVDS_DP) += ge_b850v3_lvds_dp.o
diff --git a/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c
new file mode 100644
index 0000000..37a4e7a
--- /dev/null
+++ b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c
@@ -0,0 +1,397 @@
+/*
+ * Driver for GE B850v3 DP display bridge
+
+ * Copyright (c) 2016, Collabora Ltd.
+ * Copyright (c) 2016, General Electric Company
+
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+ * This driver creates a drm_bridge and a drm_connector for the LVDS to DP++
+ * display bridge of the GE B850v3. There are two physical bridges on the video
+ * signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). However
+ * the physical bridges are automatically configured by the input video signal,
+ * and the driver has no access to the video processing pipeline. The driver is
+ * only needed to read EDID from the STDP2690 and to handle HPD events from the
+ * STDP4028. The driver communicates with both bridges over i2c. The video
+ * signal pipeline is as follows:
+ *
+ *   Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output
+
+ *
+ */
+#include <linux/gpio.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include "drm_crtc.h"
+#include "drm_crtc_helper.h"
+#include "drm_edid.h"
+#include "drmP.h"
+
+#define EDID_EXT_BLOCK_CNT 0x7E
+
+#define STDP4028_IRQ_OUT_CONF_REG 0x02
+#define STDP4028_DPTX_IRQ_EN_REG 0x3C
+#define STDP4028_DPTX_IRQ_STS_REG 0x3D
+#define STDP4028_DPTX_STS_REG 0x3E
+
+#define STDP4028_DPTX_DP_IRQ_EN 0x1000
+
+#define STDP4028_DPTX_HOTPLUG_IRQ_EN 0x0400
+#define STDP4028_DPTX_LINK_CH_IRQ_EN 0x2000
+#define STDP4028_DPTX_IRQ_CONFIG \
+		(STDP4028_DPTX_LINK_CH_IRQ_EN | STDP4028_DPTX_HOTPLUG_IRQ_EN)
+
+#define STDP4028_DPTX_HOTPLUG_STS 0x0200
+#define STDP4028_DPTX_LINK_STS 0x1000
+#define STDP4028_CON_STATE_CONNECTED \
+		(STDP4028_DPTX_HOTPLUG_STS | STDP4028_DPTX_LINK_STS)
+
+#define STDP4028_DPTX_HOTPLUG_CH_STS 0x0400
+#define STDP4028_DPTX_LINK_CH_STS 0x2000
+#define STDP4028_DPTX_IRQ_CLEAR \
+		(STDP4028_DPTX_LINK_CH_STS | STDP4028_DPTX_HOTPLUG_CH_STS)
+
+struct ge_b850v3_lvds_dp {
+	struct drm_connector connector;
+	struct drm_bridge bridge;
+	struct i2c_client *ge_b850v3_lvds_dp_i2c;
+	struct i2c_client *edid_i2c;
+	struct edid *edid;
+};
+
+static inline struct ge_b850v3_lvds_dp *
+		bridge_to_ge_b850v3_lvds_dp(struct drm_bridge *bridge)
+{
+	return container_of(bridge, struct ge_b850v3_lvds_dp, bridge);
+}
+
+static inline struct ge_b850v3_lvds_dp *
+		connector_to_ge_b850v3_lvds_dp(struct drm_connector *connector)
+{
+	return container_of(connector, struct ge_b850v3_lvds_dp, connector);
+}
+
+static void ge_b850v3_lvds_dp_pre_enable(struct drm_bridge *bridge)
+{
+}
+
+static void ge_b850v3_lvds_dp_enable(struct drm_bridge *bridge)
+{
+}
+
+static void ge_b850v3_lvds_dp_disable(struct drm_bridge *bridge)
+{
+}
+
+static void ge_b850v3_lvds_dp_post_disable(struct drm_bridge *bridge)
+{
+}
+
+u8 *stdp2690_get_edid(struct i2c_client *client)
+{
+	struct i2c_adapter *adapter = client->adapter;
+	unsigned char start = 0x00;
+	unsigned int total_size;
+	u8 *block = kmalloc(EDID_LENGTH, GFP_KERNEL);
+
+	struct i2c_msg msgs[] = {
+		{
+			.addr	= client->addr,
+			.flags	= 0,
+			.len	= 1,
+			.buf	= &start,
+		}, {
+			.addr	= client->addr,
+			.flags	= I2C_M_RD,
+			.len	= EDID_LENGTH,
+			.buf	= block,
+		}
+	};
+
+	if (!block)
+		return NULL;
+
+	if (i2c_transfer(adapter, msgs, 2) != 2) {
+		DRM_ERROR("Unable to read EDID.\n");
+		goto err;
+	}
+
+	if (!drm_edid_block_valid(block, 0, false, NULL)) {
+		DRM_ERROR("Invalid EDID block\n");
+		goto err;
+	}
+
+	total_size = (block[EDID_EXT_BLOCK_CNT] + 1) * EDID_LENGTH;
+	if (total_size > EDID_LENGTH) {
+		kfree(block);
+		block = kmalloc(total_size, GFP_KERNEL);
+		if (!block)
+			return NULL;
+
+		/* Yes, read the entire buffer, and do not skip the first
+		 * EDID_LENGTH bytes.
+		 */
+		start = 0x00;
+		msgs[1].len = total_size;
+		msgs[1].buf = block;
+
+		if (i2c_transfer(adapter, msgs, 2) != 2) {
+			DRM_ERROR("Unable to read EDID extension blocks.\n");
+			goto err;
+		}
+	}
+
+	return block;
+
+err:
+	kfree(block);
+	return NULL;
+}
+
+static int ge_b850v3_lvds_dp_get_modes(struct drm_connector *connector)
+{
+	struct ge_b850v3_lvds_dp *ptn_bridge;
+	struct i2c_client *client;
+	u8 *block;
+	int num_modes;
+
+	ptn_bridge = connector_to_ge_b850v3_lvds_dp(connector);
+	client = ptn_bridge->edid_i2c;
+
+	block = stdp2690_get_edid(client);
+	ptn_bridge->edid = (struct edid *) block;
+
+	drm_mode_connector_update_edid_property(connector, ptn_bridge->edid);
+	num_modes = drm_add_edid_modes(connector, ptn_bridge->edid);
+
+	return num_modes;
+}
+
+static struct drm_encoder
+*ge_b850v3_lvds_dp_best_encoder(struct drm_connector *connector)
+{
+	struct ge_b850v3_lvds_dp *ptn_bridge =
+		connector_to_ge_b850v3_lvds_dp(connector);
+
+	return ptn_bridge->bridge.encoder;
+}
+
+static const struct
+drm_connector_helper_funcs ge_b850v3_lvds_dp_connector_helper_funcs = {
+	.get_modes = ge_b850v3_lvds_dp_get_modes,
+	.best_encoder = ge_b850v3_lvds_dp_best_encoder,
+};
+
+static enum drm_connector_status ge_b850v3_lvds_dp_detect(
+		struct drm_connector *connector, bool force)
+{
+	struct ge_b850v3_lvds_dp *ptn_bridge =
+			connector_to_ge_b850v3_lvds_dp(connector);
+	struct i2c_client *ge_b850v3_lvds_dp_i2c =
+			ptn_bridge->ge_b850v3_lvds_dp_i2c;
+	s32 link_state;
+
+	link_state = i2c_smbus_read_word_data(ge_b850v3_lvds_dp_i2c,
+			STDP4028_DPTX_STS_REG);
+
+	if (link_state == STDP4028_CON_STATE_CONNECTED)
+		return connector_status_connected;
+
+	if (link_state == 0)
+		return connector_status_disconnected;
+
+	return connector_status_unknown;
+}
+
+static void ge_b850v3_lvds_dp_connector_force(struct drm_connector *connector)
+{
+}
+
+static void ge_b850v3_lvds_dp_connector_destroy(struct drm_connector *connector)
+{
+	drm_connector_cleanup(connector);
+}
+
+static const struct drm_connector_funcs ge_b850v3_lvds_dp_connector_funcs = {
+	.dpms = drm_helper_connector_dpms,
+	.fill_modes = drm_helper_probe_single_connector_modes,
+	.detect = ge_b850v3_lvds_dp_detect,
+	.destroy = ge_b850v3_lvds_dp_connector_destroy,
+	.force = ge_b850v3_lvds_dp_connector_force,
+};
+
+static irqreturn_t ge_b850v3_lvds_dp_irq_handler(int irq, void *dev_id)
+{
+	struct ge_b850v3_lvds_dp *ptn_bridge = dev_id;
+	struct i2c_client *ge_b850v3_lvds_dp_i2c
+			= ptn_bridge->ge_b850v3_lvds_dp_i2c;
+
+	i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c,
+			STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR);
+
+	if (ptn_bridge->connector.dev)
+		drm_kms_helper_hotplug_event(ptn_bridge->connector.dev);
+
+	return IRQ_HANDLED;
+
+}
+
+static int ge_b850v3_lvds_dp_attach(struct drm_bridge *bridge)
+{
+	struct ge_b850v3_lvds_dp *ptn_bridge
+			= bridge_to_ge_b850v3_lvds_dp(bridge);
+	struct drm_connector *connector = &ptn_bridge->connector;
+	struct i2c_client *ge_b850v3_lvds_dp_i2c
+			= ptn_bridge->ge_b850v3_lvds_dp_i2c;
+	int ret;
+
+	if (!bridge->encoder) {
+		DRM_ERROR("Parent encoder object not found");
+		return -ENODEV;
+	}
+
+	connector->polled = DRM_CONNECTOR_POLL_HPD;
+
+	drm_connector_helper_add(connector,
+			&ge_b850v3_lvds_dp_connector_helper_funcs);
+
+	ret = drm_connector_init(bridge->dev, connector,
+			&ge_b850v3_lvds_dp_connector_funcs,
+			DRM_MODE_CONNECTOR_DisplayPort);
+	if (ret) {
+		DRM_ERROR("Failed to initialize connector with drm\n");
+		return ret;
+	}
+
+	ret = drm_mode_connector_attach_encoder(connector, bridge->encoder);
+	if (ret)
+		return ret;
+
+	drm_bridge_enable(bridge);
+	if (ge_b850v3_lvds_dp_i2c->irq) {
+		drm_helper_hpd_irq_event(connector->dev);
+
+		ret = devm_request_threaded_irq(&ge_b850v3_lvds_dp_i2c->dev,
+				ge_b850v3_lvds_dp_i2c->irq, NULL,
+				ge_b850v3_lvds_dp_irq_handler,
+				IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
+				"ge_b850v3_lvds_dp", ptn_bridge);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static const struct drm_bridge_funcs ge_b850v3_lvds_dp_funcs = {
+	.pre_enable = ge_b850v3_lvds_dp_pre_enable,
+	.enable = ge_b850v3_lvds_dp_enable,
+	.disable = ge_b850v3_lvds_dp_disable,
+	.post_disable = ge_b850v3_lvds_dp_post_disable,
+	.attach = ge_b850v3_lvds_dp_attach,
+};
+
+static int ge_b850v3_lvds_dp_probe(struct i2c_client *ge_b850v3_lvds_dp_i2c,
+				const struct i2c_device_id *id)
+{
+	struct device *dev = &ge_b850v3_lvds_dp_i2c->dev;
+	struct ge_b850v3_lvds_dp *ptn_bridge;
+
+	int ret;
+	u32 edid_i2c_reg;
+
+	ptn_bridge = devm_kzalloc(dev, sizeof(*ptn_bridge), GFP_KERNEL);
+	if (!ptn_bridge)
+		return -ENOMEM;
+
+	ptn_bridge->ge_b850v3_lvds_dp_i2c = ge_b850v3_lvds_dp_i2c;
+	ptn_bridge->bridge.driver_private = ptn_bridge;
+	i2c_set_clientdata(ge_b850v3_lvds_dp_i2c, ptn_bridge);
+
+	ret = of_property_read_u32(dev->of_node, "edid-reg", &edid_i2c_reg);
+	if (ret) {
+		dev_err(dev, "edid-reg not specified, aborting...\n");
+		return -ENODEV;
+	}
+
+	ptn_bridge->edid_i2c = devm_kzalloc(dev,
+			sizeof(struct i2c_client), GFP_KERNEL);
+
+	if (!ptn_bridge->edid_i2c)
+		return -ENOMEM;
+
+	memcpy(ptn_bridge->edid_i2c, ge_b850v3_lvds_dp_i2c,
+			sizeof(struct i2c_client));
+
+	ptn_bridge->edid_i2c->addr = (unsigned short) edid_i2c_reg;
+
+
+	/* Configures the bridge to re-enable interrupts after each ack */
+	i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c,
+			STDP4028_IRQ_OUT_CONF_REG, STDP4028_DPTX_DP_IRQ_EN);
+	i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c,
+			STDP4028_DPTX_IRQ_EN_REG, STDP4028_DPTX_IRQ_CONFIG);
+
+	/* Clear pending interrupts since power up. */
+	i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c,
+			STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR);
+
+	ptn_bridge->bridge.funcs = &ge_b850v3_lvds_dp_funcs;
+	ptn_bridge->bridge.of_node = dev->of_node;
+	ret = drm_bridge_add(&ptn_bridge->bridge);
+	if (ret) {
+		DRM_ERROR("Failed to add bridge\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static int ge_b850v3_lvds_dp_remove(struct i2c_client *ge_b850v3_lvds_dp_i2c)
+{
+	struct ge_b850v3_lvds_dp *ptn_bridge =
+		i2c_get_clientdata(ge_b850v3_lvds_dp_i2c);
+
+	drm_bridge_remove(&ptn_bridge->bridge);
+
+	return 0;
+}
+
+static const struct i2c_device_id ge_b850v3_lvds_dp_i2c_table[] = {
+	{"b850v3_lvds_dp", 0},
+	{},
+};
+MODULE_DEVICE_TABLE(i2c, ge_b850v3_lvds_dp_i2c_table);
+
+static const struct of_device_id ge_b850v3_lvds_dp_match[] = {
+	{ .compatible = "ge,b850v3_lvds_dp" },
+	{},
+};
+MODULE_DEVICE_TABLE(of, ge_b850v3_lvds_dp_match);
+
+static struct i2c_driver ge_b850v3_lvds_dp_driver = {
+	.id_table	= ge_b850v3_lvds_dp_i2c_table,
+	.probe		= ge_b850v3_lvds_dp_probe,
+	.remove		= ge_b850v3_lvds_dp_remove,
+	.driver		= {
+		.name		= "ge,b850v3_lvds_dp",
+		.of_match_table = ge_b850v3_lvds_dp_match,
+	},
+};
+module_i2c_driver(ge_b850v3_lvds_dp_driver);
+
+MODULE_AUTHOR("Peter Senna Tschudin <peter.senna@collabora.com>");
+MODULE_AUTHOR("Martyn Welch <martyn.welch@collabora.co.uk>");
+MODULE_DESCRIPTION("GE LVDS to DP++ bridge)");
+MODULE_LICENSE("GPL v2");
-- 
2.5.5

^ permalink raw reply related	[flat|nested] 64+ messages in thread

* [PATCH 5/5] arm/dts/imx6q-b850v3: Use GE B850v3 LVDS/DP++ Bridge
  2016-05-30 16:39 [PATCH 0/5] Add driver for GE B850v3 LVDS/DP++ Bridge Peter Senna Tschudin
                   ` (3 preceding siblings ...)
  2016-05-30 16:39 ` [PATCH 4/5] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge Peter Senna Tschudin
@ 2016-05-30 16:39 ` Peter Senna Tschudin
  2016-05-30 16:54   ` Fabio Estevam
  2016-06-09 16:25 ` [PATCH V2 0/5] Add driver for " Peter Senna Tschudin
                   ` (3 subsequent siblings)
  8 siblings, 1 reply; 64+ messages in thread
From: Peter Senna Tschudin @ 2016-05-30 16:39 UTC (permalink / raw)
  To: airlied, akpm, davem, devicetree, dri-devel, enric.balletbo,
	galak, gregkh, heiko, ijc+devicetree, jslaby, kernel,
	linux-arm-kernel, linux, linux-kernel, linux, mark.rutland,
	martin.donnelly, martyn.welch, mchehab, pawel.moll, peter.senna,
	p.zabel, rmk+kernel, robh+dt, shawnguo, tiwai, treding, ykk

Configure the GE B850v3 to use the LVDS/DP++ bridge.

Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com>
---
 arch/arm/boot/dts/imx6q-b850v3.dts | 31 +++++++++++++++++++++++++++++++
 1 file changed, 31 insertions(+)

diff --git a/arch/arm/boot/dts/imx6q-b850v3.dts b/arch/arm/boot/dts/imx6q-b850v3.dts
index 88a70de..d366384 100644
--- a/arch/arm/boot/dts/imx6q-b850v3.dts
+++ b/arch/arm/boot/dts/imx6q-b850v3.dts
@@ -77,6 +77,13 @@
 		fsl,data-mapping = "spwg";
 		fsl,data-width = <24>;
 		status = "okay";
+
+		port@4 {
+			reg = <4>;
+			lvds0_out: endpoint {
+				remote-endpoint = <&b850v3_lvds_dp_bridge_in>;
+			};
+		};
 	};
 };
 
@@ -147,3 +154,27 @@
 		reg = <0x4a>;
 	};
 };
+
+&mux2_i2c2 {
+	status = "okay";
+	clock-frequency = <100000>;
+
+	b850v3_lvds_dp_bridge {
+		compatible = "ge,b850v3_lvds_dp";
+		#address-cells = <1>;
+		#size-cells = <0>;
+
+		reg = <0x73>;
+		interrupt-parent = <&gpio2>;
+		interrupts = <0 IRQ_TYPE_LEVEL_HIGH>;
+
+		edid-reg = <0x72>;
+
+		port@0 {
+			reg = <0>;
+			b850v3_lvds_dp_bridge_in: endpoint {
+				remote-endpoint = <&lvds0_out>;
+			};
+		};
+	};
+};
-- 
2.5.5

^ permalink raw reply related	[flat|nested] 64+ messages in thread

* Re: [PATCH 2/5] arm/dts/imx6q-b850v3: Configure IPU assignment order
  2016-05-30 16:39 ` [PATCH 2/5] arm/dts/imx6q-b850v3: Configure IPU assignment order Peter Senna Tschudin
@ 2016-05-30 16:49   ` Fabio Estevam
  2016-06-02 12:55   ` Philipp Zabel
  1 sibling, 0 replies; 64+ messages in thread
From: Fabio Estevam @ 2016-05-30 16:49 UTC (permalink / raw)
  To: Peter Senna Tschudin
  Cc: David Airlie, Andrew Morton, David S. Miller, devicetree,
	DRI mailing list, enric.balletbo, Kumar Gala, Greg Kroah-Hartman,
	Heiko Stübner, Ian Campbell, Jiri Slaby, Sascha Hauer,
	linux-arm-kernel, Russell King - ARM Linux, linux-kernel,
	Guenter Roeck, Mark Rutland, Martin Donnelly, martyn.welch,
	Mauro Carvalho Chehab, Pawel Moll, Philipp Zabel, rmk+kernel,
	robh+dt, Shawn Guo, Takashi Iwai, Thierry Reding, Yakir Yang

On Mon, May 30, 2016 at 1:39 PM, Peter Senna Tschudin
<peter.senna@collabora.com> wrote:
> Configure the IPU assignment order to assign one IPU per external
> display. A single IPU can drive multiple external displays but there are
> resolution restrictions. After this patch the GPU is capalbe of driving two

I think you meant "the IPU is capable"

^ permalink raw reply	[flat|nested] 64+ messages in thread

* Re: [PATCH 5/5] arm/dts/imx6q-b850v3: Use GE B850v3 LVDS/DP++ Bridge
  2016-05-30 16:39 ` [PATCH 5/5] arm/dts/imx6q-b850v3: Use " Peter Senna Tschudin
@ 2016-05-30 16:54   ` Fabio Estevam
  0 siblings, 0 replies; 64+ messages in thread
From: Fabio Estevam @ 2016-05-30 16:54 UTC (permalink / raw)
  To: Peter Senna Tschudin
  Cc: David Airlie, Andrew Morton, David S. Miller, devicetree,
	DRI mailing list, enric.balletbo, Kumar Gala, Greg Kroah-Hartman,
	Heiko Stübner, Ian Campbell, Jiri Slaby, Sascha Hauer,
	linux-arm-kernel, Russell King - ARM Linux, linux-kernel,
	Guenter Roeck, Mark Rutland, Martin Donnelly, martyn.welch,
	Mauro Carvalho Chehab, Pawel Moll, Philipp Zabel, rmk+kernel,
	robh+dt, Shawn Guo, Takashi Iwai, Thierry Reding, Yakir Yang

On Mon, May 30, 2016 at 1:39 PM, Peter Senna Tschudin
<peter.senna@collabora.com> wrote:

> +&mux2_i2c2 {
> +       status = "okay";
> +       clock-frequency = <100000>;
> +
> +       b850v3_lvds_dp_bridge {
> +               compatible = "ge,b850v3_lvds_dp";
> +               #address-cells = <1>;
> +               #size-cells = <0>;
> +
> +               reg = <0x73>;

You use 'reg = <0x73>' here, but no @73.

^ permalink raw reply	[flat|nested] 64+ messages in thread

* Re: [PATCH 4/5] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge
  2016-05-30 16:39 ` [PATCH 4/5] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge Peter Senna Tschudin
@ 2016-05-31  7:48   ` Enric Balletbo Serra
  0 siblings, 0 replies; 64+ messages in thread
From: Enric Balletbo Serra @ 2016-05-31  7:48 UTC (permalink / raw)
  To: Peter Senna Tschudin
  Cc: airlied, akpm, David Miller, devicetree, dri-devel,
	Enric Balletbo i Serra, Kumar Gala, Greg Kroah-Hartman,
	Heiko Stübner, Ian Campbell, jslaby, kernel,
	linux-arm-kernel, linux, linux-kernel, linux, Mark Rutland,
	martin.donnelly, martyn.welch, Mauro Carvalho Chehab, Pawel Moll,
	Philipp Zabel, rmk+kernel, Rob Herring, shawnguo, Takashi Iwai,
	Thierry Reding, ykk

Hi Peter,

Just some comments that I received when I submitted my bridge patches
and that I think can help you ...

2016-05-30 18:39 GMT+02:00 Peter Senna Tschudin <peter.senna@collabora.com>:
> This driver creates a drm_bridge and a drm_connector for the LVDS to DP++
> display bridge of the GE B850v3(imx6q-b850v3.dts). There are two physical
> bridges on the video signal pipeline: a STDP4028(LVDS to DP) and a
> STDP2690(DP to DP++). However the physical bridges are automatically
> configured by the input video signal, and the driver has no access to
> the video processing pipeline. The driver is only needed to read EDID
> from the STDP2690 and to handle HPD events from the STDP4028. The driver
> communicates with both bridges over i2c. The video signal pipeline is as
> follows:
>
>   Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output
>
> Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com>
> ---
>  MAINTAINERS                                |   8 +
>  drivers/gpu/drm/bridge/Kconfig             |   8 +
>  drivers/gpu/drm/bridge/Makefile            |   1 +
>  drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c | 397 +++++++++++++++++++++++++++++
>  4 files changed, 414 insertions(+)
>  create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 3273ffa..7bb5e89 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -5009,6 +5009,14 @@ W:       https://linuxtv.org
>  S:     Maintained
>  F:     drivers/media/radio/radio-gemtek*
>
> +GENERAL ELECTRIC B850V3 LVDS/DP++ BRIDGE
> +M:     Martin Donnelly <martin.donnelly@ge.com>
> +M:     Peter Senna Tschudin <peter.senna@collabora.com>
> +M:     Martyn Welch <martyn.welch@collabora.co.uk>
> +S:     Maintained
> +F:     drivers/gpu/drm/bridge/ge_b850v3_dp2.c
> +F:     Documentation/devicetree/bindings/ge/b850v3_dp2_bridge.txt
> +
>  GENERIC GPIO I2C DRIVER
>  M:     Haavard Skinnemoen <hskinnemoen@gmail.com>
>  S:     Supported
> diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
> index 8f7423f..e9c32fc 100644
> --- a/drivers/gpu/drm/bridge/Kconfig
> +++ b/drivers/gpu/drm/bridge/Kconfig
> @@ -52,4 +52,12 @@ config DRM_PARADE_PS8622
>
>  source "drivers/gpu/drm/bridge/analogix/Kconfig"
>
> +config DRM_GE_B850V3_LVDS_DP
> +       tristate "LVDS/DP bridge"
> +       depends on OF
> +       select DRM_KMS_HELPER
> +       select DRM_PANEL
> +       ---help---
> +         Driver for GE B850v3 DP2 LVDS to DP+ Bridge
> +

I think the maintainer prefers the entries ordered alphabetically (by
vendor, then name), so your config should go before "config
DRM_NXP_PTN3460"

>  endmenu
> diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
> index 96b13b3..ba2e355 100644
> --- a/drivers/gpu/drm/bridge/Makefile
> +++ b/drivers/gpu/drm/bridge/Makefile
> @@ -6,3 +6,4 @@ obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o
>  obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o
>  obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o
>  obj-$(CONFIG_DRM_ANALOGIX_DP) += analogix/
> +obj-$(CONFIG_DRM_GE_B850V3_LVDS_DP) += ge_b850v3_lvds_dp.o

Same here,  this needs to be sorted by vendor, then name as well. Also
I think is preferred use hyphens like the others drivers.

Hmm, I see now that CONFIG_DRM_ANALOGIX_DP is not sorted, but I guess
the preferred is be sorted.

> diff --git a/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c
> new file mode 100644
> index 0000000..37a4e7a
> --- /dev/null
> +++ b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c
> @@ -0,0 +1,397 @@
> +/*
> + * Driver for GE B850v3 DP display bridge
> +
> + * Copyright (c) 2016, Collabora Ltd.
> + * Copyright (c) 2016, General Electric Company
> +
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms and conditions of the GNU General Public License,
> + * version 2, as published by the Free Software Foundation.
> +
> + * This program is distributed in the hope it will be useful, but WITHOUT
> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
> + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
> + * more details.
> +
> + * You should have received a copy of the GNU General Public License
> + * along with this program.  If not, see <http://www.gnu.org/licenses/>.
> +
> + * This driver creates a drm_bridge and a drm_connector for the LVDS to DP++
> + * display bridge of the GE B850v3. There are two physical bridges on the video
> + * signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). However
> + * the physical bridges are automatically configured by the input video signal,
> + * and the driver has no access to the video processing pipeline. The driver is
> + * only needed to read EDID from the STDP2690 and to handle HPD events from the
> + * STDP4028. The driver communicates with both bridges over i2c. The video
> + * signal pipeline is as follows:
> + *
> + *   Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output
> +
> + *
> + */
> +#include <linux/gpio.h>
> +#include <linux/i2c.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include "drm_crtc.h"
> +#include "drm_crtc_helper.h"
> +#include "drm_edid.h"
> +#include "drmP.h"
> +
> +#define EDID_EXT_BLOCK_CNT 0x7E
> +
> +#define STDP4028_IRQ_OUT_CONF_REG 0x02
> +#define STDP4028_DPTX_IRQ_EN_REG 0x3C
> +#define STDP4028_DPTX_IRQ_STS_REG 0x3D
> +#define STDP4028_DPTX_STS_REG 0x3E
> +
> +#define STDP4028_DPTX_DP_IRQ_EN 0x1000
> +
> +#define STDP4028_DPTX_HOTPLUG_IRQ_EN 0x0400
> +#define STDP4028_DPTX_LINK_CH_IRQ_EN 0x2000
> +#define STDP4028_DPTX_IRQ_CONFIG \
> +               (STDP4028_DPTX_LINK_CH_IRQ_EN | STDP4028_DPTX_HOTPLUG_IRQ_EN)
> +
> +#define STDP4028_DPTX_HOTPLUG_STS 0x0200
> +#define STDP4028_DPTX_LINK_STS 0x1000
> +#define STDP4028_CON_STATE_CONNECTED \
> +               (STDP4028_DPTX_HOTPLUG_STS | STDP4028_DPTX_LINK_STS)
> +
> +#define STDP4028_DPTX_HOTPLUG_CH_STS 0x0400
> +#define STDP4028_DPTX_LINK_CH_STS 0x2000
> +#define STDP4028_DPTX_IRQ_CLEAR \
> +               (STDP4028_DPTX_LINK_CH_STS | STDP4028_DPTX_HOTPLUG_CH_STS)
> +
> +struct ge_b850v3_lvds_dp {
> +       struct drm_connector connector;
> +       struct drm_bridge bridge;
> +       struct i2c_client *ge_b850v3_lvds_dp_i2c;
> +       struct i2c_client *edid_i2c;
> +       struct edid *edid;
> +};
> +
> +static inline struct ge_b850v3_lvds_dp *
> +               bridge_to_ge_b850v3_lvds_dp(struct drm_bridge *bridge)
> +{
> +       return container_of(bridge, struct ge_b850v3_lvds_dp, bridge);
> +}
> +
> +static inline struct ge_b850v3_lvds_dp *
> +               connector_to_ge_b850v3_lvds_dp(struct drm_connector *connector)
> +{
> +       return container_of(connector, struct ge_b850v3_lvds_dp, connector);
> +}
> +
> +static void ge_b850v3_lvds_dp_pre_enable(struct drm_bridge *bridge)
> +{
> +}
> +

You don't need to keep empty callbacks for the (pre/post)
enable/disable drm_bridge ops anymore so you can remove this function
and the below.

> +static void ge_b850v3_lvds_dp_enable(struct drm_bridge *bridge)
> +{
> +}
> +
> +static void ge_b850v3_lvds_dp_disable(struct drm_bridge *bridge)
> +{
> +}
> +
> +static void ge_b850v3_lvds_dp_post_disable(struct drm_bridge *bridge)
> +{
> +}
> +
> +u8 *stdp2690_get_edid(struct i2c_client *client)
> +{
> +       struct i2c_adapter *adapter = client->adapter;
> +       unsigned char start = 0x00;
> +       unsigned int total_size;
> +       u8 *block = kmalloc(EDID_LENGTH, GFP_KERNEL);
> +
> +       struct i2c_msg msgs[] = {
> +               {
> +                       .addr   = client->addr,
> +                       .flags  = 0,
> +                       .len    = 1,
> +                       .buf    = &start,
> +               }, {
> +                       .addr   = client->addr,
> +                       .flags  = I2C_M_RD,
> +                       .len    = EDID_LENGTH,
> +                       .buf    = block,
> +               }
> +       };
> +
> +       if (!block)
> +               return NULL;
> +
> +       if (i2c_transfer(adapter, msgs, 2) != 2) {
> +               DRM_ERROR("Unable to read EDID.\n");
> +               goto err;
> +       }
> +
> +       if (!drm_edid_block_valid(block, 0, false, NULL)) {
> +               DRM_ERROR("Invalid EDID block\n");
> +               goto err;
> +       }
> +
> +       total_size = (block[EDID_EXT_BLOCK_CNT] + 1) * EDID_LENGTH;
> +       if (total_size > EDID_LENGTH) {
> +               kfree(block);
> +               block = kmalloc(total_size, GFP_KERNEL);
> +               if (!block)
> +                       return NULL;
> +
> +               /* Yes, read the entire buffer, and do not skip the first
> +                * EDID_LENGTH bytes.
> +                */
> +               start = 0x00;
> +               msgs[1].len = total_size;
> +               msgs[1].buf = block;
> +
> +               if (i2c_transfer(adapter, msgs, 2) != 2) {
> +                       DRM_ERROR("Unable to read EDID extension blocks.\n");
> +                       goto err;
> +               }
> +       }
> +
> +       return block;
> +
> +err:
> +       kfree(block);
> +       return NULL;
> +}
> +
> +static int ge_b850v3_lvds_dp_get_modes(struct drm_connector *connector)
> +{
> +       struct ge_b850v3_lvds_dp *ptn_bridge;
> +       struct i2c_client *client;
> +       u8 *block;
> +       int num_modes;
> +
> +       ptn_bridge = connector_to_ge_b850v3_lvds_dp(connector);

Move this assignation to ptn_bridge declaration

> +       client = ptn_bridge->edid_i2c;
> +
> +       block = stdp2690_get_edid(client);

I think you can remove the client variable and pass
ptn_bridge->edid_i2c directly to the stdp2690_get_edid function.

Note that get_modes is called several times and can be called from
userspace, you are allocating memory on every call to get_modes that I
think is not released or I'm missing something?

> +       ptn_bridge->edid = (struct edid *) block;
> +

What if block is NULL here ?

> +       drm_mode_connector_update_edid_property(connector, ptn_bridge->edid);
> +       num_modes = drm_add_edid_modes(connector, ptn_bridge->edid);
> +
> +       return num_modes;

nit: return drm_add_edid_modes(connector, ptn_bridge->edid); ? And
remove num_modes ?

> +}
> +
> +static struct drm_encoder
> +*ge_b850v3_lvds_dp_best_encoder(struct drm_connector *connector)
> +{
> +       struct ge_b850v3_lvds_dp *ptn_bridge =
> +               connector_to_ge_b850v3_lvds_dp(connector);
> +
> +       return ptn_bridge->bridge.encoder;
> +}
> +
> +static const struct
> +drm_connector_helper_funcs ge_b850v3_lvds_dp_connector_helper_funcs = {
> +       .get_modes = ge_b850v3_lvds_dp_get_modes,
> +       .best_encoder = ge_b850v3_lvds_dp_best_encoder,
> +};
> +
> +static enum drm_connector_status ge_b850v3_lvds_dp_detect(
> +               struct drm_connector *connector, bool force)
> +{
> +       struct ge_b850v3_lvds_dp *ptn_bridge =
> +                       connector_to_ge_b850v3_lvds_dp(connector);
> +       struct i2c_client *ge_b850v3_lvds_dp_i2c =
> +                       ptn_bridge->ge_b850v3_lvds_dp_i2c;
> +       s32 link_state;
> +
> +       link_state = i2c_smbus_read_word_data(ge_b850v3_lvds_dp_i2c,
> +                       STDP4028_DPTX_STS_REG);
> +
> +       if (link_state == STDP4028_CON_STATE_CONNECTED)
> +               return connector_status_connected;
> +
> +       if (link_state == 0)
> +               return connector_status_disconnected;
> +
> +       return connector_status_unknown;
> +}
> +
> +static void ge_b850v3_lvds_dp_connector_force(struct drm_connector *connector)
> +{
> +}
> +

Guess you can remove this empty callback.

> +static void ge_b850v3_lvds_dp_connector_destroy(struct drm_connector *connector)
> +{
> +       drm_connector_cleanup(connector);
> +}
> +
> +static const struct drm_connector_funcs ge_b850v3_lvds_dp_connector_funcs = {
> +       .dpms = drm_helper_connector_dpms,
> +       .fill_modes = drm_helper_probe_single_connector_modes,
> +       .detect = ge_b850v3_lvds_dp_detect,
> +       .destroy = ge_b850v3_lvds_dp_connector_destroy,
> +       .force = ge_b850v3_lvds_dp_connector_force,

Remove .force

> +};
> +
> +static irqreturn_t ge_b850v3_lvds_dp_irq_handler(int irq, void *dev_id)
> +{
> +       struct ge_b850v3_lvds_dp *ptn_bridge = dev_id;
> +       struct i2c_client *ge_b850v3_lvds_dp_i2c
> +                       = ptn_bridge->ge_b850v3_lvds_dp_i2c;
> +
> +       i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c,
> +                       STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR);
> +
> +       if (ptn_bridge->connector.dev)
> +               drm_kms_helper_hotplug_event(ptn_bridge->connector.dev);
> +
> +       return IRQ_HANDLED;
> +

Remove the empty line.

> +}
> +
> +static int ge_b850v3_lvds_dp_attach(struct drm_bridge *bridge)
> +{
> +       struct ge_b850v3_lvds_dp *ptn_bridge
> +                       = bridge_to_ge_b850v3_lvds_dp(bridge);
> +       struct drm_connector *connector = &ptn_bridge->connector;
> +       struct i2c_client *ge_b850v3_lvds_dp_i2c
> +                       = ptn_bridge->ge_b850v3_lvds_dp_i2c;
> +       int ret;
> +
> +       if (!bridge->encoder) {
> +               DRM_ERROR("Parent encoder object not found");
> +               return -ENODEV;
> +       }
> +
> +       connector->polled = DRM_CONNECTOR_POLL_HPD;
> +
> +       drm_connector_helper_add(connector,
> +                       &ge_b850v3_lvds_dp_connector_helper_funcs);
> +
> +       ret = drm_connector_init(bridge->dev, connector,
> +                       &ge_b850v3_lvds_dp_connector_funcs,
> +                       DRM_MODE_CONNECTOR_DisplayPort);
> +       if (ret) {
> +               DRM_ERROR("Failed to initialize connector with drm\n");
> +               return ret;
> +       }
> +
> +       ret = drm_mode_connector_attach_encoder(connector, bridge->encoder);
> +       if (ret)
> +               return ret;
> +
> +       drm_bridge_enable(bridge);
> +       if (ge_b850v3_lvds_dp_i2c->irq) {
> +               drm_helper_hpd_irq_event(connector->dev);
> +
> +               ret = devm_request_threaded_irq(&ge_b850v3_lvds_dp_i2c->dev,
> +                               ge_b850v3_lvds_dp_i2c->irq, NULL,
> +                               ge_b850v3_lvds_dp_irq_handler,
> +                               IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
> +                               "ge_b850v3_lvds_dp", ptn_bridge);
> +               if (ret)
> +                       return ret;
> +       }
> +
> +       return 0;
> +}
> +
> +static const struct drm_bridge_funcs ge_b850v3_lvds_dp_funcs = {
> +       .pre_enable = ge_b850v3_lvds_dp_pre_enable,
> +       .enable = ge_b850v3_lvds_dp_enable,
> +       .disable = ge_b850v3_lvds_dp_disable,
> +       .post_disable = ge_b850v3_lvds_dp_post_disable,

Remove the above empty callbacks.

> +       .attach = ge_b850v3_lvds_dp_attach,
> +};
> +
> +static int ge_b850v3_lvds_dp_probe(struct i2c_client *ge_b850v3_lvds_dp_i2c,
> +                               const struct i2c_device_id *id)
> +{
> +       struct device *dev = &ge_b850v3_lvds_dp_i2c->dev;
> +       struct ge_b850v3_lvds_dp *ptn_bridge;
> +

Remove this empty line between these two blocks.

> +       int ret;
> +       u32 edid_i2c_reg;
> +
> +       ptn_bridge = devm_kzalloc(dev, sizeof(*ptn_bridge), GFP_KERNEL);
> +       if (!ptn_bridge)
> +               return -ENOMEM;
> +
> +       ptn_bridge->ge_b850v3_lvds_dp_i2c = ge_b850v3_lvds_dp_i2c;
> +       ptn_bridge->bridge.driver_private = ptn_bridge;
> +       i2c_set_clientdata(ge_b850v3_lvds_dp_i2c, ptn_bridge);
> +
> +       ret = of_property_read_u32(dev->of_node, "edid-reg", &edid_i2c_reg);
> +       if (ret) {
> +               dev_err(dev, "edid-reg not specified, aborting...\n");

Use DRM_ERROR?

> +               return -ENODEV;
> +       }
> +
> +       ptn_bridge->edid_i2c = devm_kzalloc(dev,
> +                       sizeof(struct i2c_client), GFP_KERNEL);
> +
> +       if (!ptn_bridge->edid_i2c)
> +               return -ENOMEM;
> +
> +       memcpy(ptn_bridge->edid_i2c, ge_b850v3_lvds_dp_i2c,
> +                       sizeof(struct i2c_client));
> +
> +       ptn_bridge->edid_i2c->addr = (unsigned short) edid_i2c_reg;
> +

Remove empty line.

> +
> +       /* Configures the bridge to re-enable interrupts after each ack */
> +       i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c,
> +                       STDP4028_IRQ_OUT_CONF_REG, STDP4028_DPTX_DP_IRQ_EN);
> +       i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c,
> +                       STDP4028_DPTX_IRQ_EN_REG, STDP4028_DPTX_IRQ_CONFIG);
> +
> +       /* Clear pending interrupts since power up. */
> +       i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c,
> +                       STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR);
> +
> +       ptn_bridge->bridge.funcs = &ge_b850v3_lvds_dp_funcs;
> +       ptn_bridge->bridge.of_node = dev->of_node;
> +       ret = drm_bridge_add(&ptn_bridge->bridge);
> +       if (ret) {
> +               DRM_ERROR("Failed to add bridge\n");
> +               return ret;
> +       }
> +
> +       return 0;
> +}
> +
> +static int ge_b850v3_lvds_dp_remove(struct i2c_client *ge_b850v3_lvds_dp_i2c)
> +{
> +       struct ge_b850v3_lvds_dp *ptn_bridge =
> +               i2c_get_clientdata(ge_b850v3_lvds_dp_i2c);
> +
> +       drm_bridge_remove(&ptn_bridge->bridge);
> +
> +       return 0;
> +}
> +
> +static const struct i2c_device_id ge_b850v3_lvds_dp_i2c_table[] = {
> +       {"b850v3_lvds_dp", 0},
> +       {},
> +};
> +MODULE_DEVICE_TABLE(i2c, ge_b850v3_lvds_dp_i2c_table);
> +
> +static const struct of_device_id ge_b850v3_lvds_dp_match[] = {
> +       { .compatible = "ge,b850v3_lvds_dp" },
> +       {},
> +};
> +MODULE_DEVICE_TABLE(of, ge_b850v3_lvds_dp_match);
> +
> +static struct i2c_driver ge_b850v3_lvds_dp_driver = {
> +       .id_table       = ge_b850v3_lvds_dp_i2c_table,
> +       .probe          = ge_b850v3_lvds_dp_probe,
> +       .remove         = ge_b850v3_lvds_dp_remove,
> +       .driver         = {
> +               .name           = "ge,b850v3_lvds_dp",
> +               .of_match_table = ge_b850v3_lvds_dp_match,
> +       },
> +};
> +module_i2c_driver(ge_b850v3_lvds_dp_driver);
> +
> +MODULE_AUTHOR("Peter Senna Tschudin <peter.senna@collabora.com>");
> +MODULE_AUTHOR("Martyn Welch <martyn.welch@collabora.co.uk>");
> +MODULE_DESCRIPTION("GE LVDS to DP++ bridge)");
> +MODULE_LICENSE("GPL v2");
> --
> 2.5.5
>

Best regards,
   Enric

^ permalink raw reply	[flat|nested] 64+ messages in thread

* Re: [PATCH 3/5] Documentation/devicetree/bindings: Add b850v3_lvds_dp
  2016-05-30 16:39 ` [PATCH 3/5] Documentation/devicetree/bindings: Add b850v3_lvds_dp Peter Senna Tschudin
@ 2016-06-02 12:49   ` Philipp Zabel
  2016-06-02 23:19     ` Peter Senna Tschudin
  2016-06-02 22:57   ` Rob Herring
  1 sibling, 1 reply; 64+ messages in thread
From: Philipp Zabel @ 2016-06-02 12:49 UTC (permalink / raw)
  To: Peter Senna Tschudin
  Cc: airlied, akpm, davem, devicetree, dri-devel, enric.balletbo,
	galak, gregkh, heiko, ijc+devicetree, jslaby, kernel,
	linux-arm-kernel, linux, linux-kernel, linux, mark.rutland,
	martin.donnelly, martyn.welch, mchehab, pawel.moll, rmk+kernel,
	robh+dt, shawnguo, tiwai, treding, ykk

Hi Peter,

Am Montag, den 30.05.2016, 18:39 +0200 schrieb Peter Senna Tschudin:
> Devicetree bindings documentation for the GE B850v3 LVDS/DP++
> display bridge.
> 
> Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com>
> ---
>  .../devicetree/bindings/ge/b850v3_lvds_dp.txt      | 38 ++++++++++++++++++++++
>  1 file changed, 38 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt
> 
> diff --git a/Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt b/Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt
> new file mode 100644
> index 0000000..32e123a
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt
> @@ -0,0 +1,38 @@
> +Driver for GE B850v3 LVDS/DP++ display bridge
> +
> +Required properties:
> +  - compatible : should be "ge,b850v3_lvds_dp".

In the cover mail you write that this is a combination of the STDP4028
DP transmitter and STDP2690 DP/DP++ converter. So shouldn't this binding
comprise two device tree nodes, one with compatible "st,stdp4028", one
with "st,stdp2690", then?
Is the stdp2690 connected to the stdp4028's i2c master?

regards
Philipp

^ permalink raw reply	[flat|nested] 64+ messages in thread

* Re: [PATCH 2/5] arm/dts/imx6q-b850v3: Configure IPU assignment order
  2016-05-30 16:39 ` [PATCH 2/5] arm/dts/imx6q-b850v3: Configure IPU assignment order Peter Senna Tschudin
  2016-05-30 16:49   ` Fabio Estevam
@ 2016-06-02 12:55   ` Philipp Zabel
  1 sibling, 0 replies; 64+ messages in thread
From: Philipp Zabel @ 2016-06-02 12:55 UTC (permalink / raw)
  To: Peter Senna Tschudin
  Cc: airlied, akpm, davem, devicetree, dri-devel, enric.balletbo,
	galak, gregkh, heiko, ijc+devicetree, jslaby, kernel,
	linux-arm-kernel, linux, linux-kernel, linux, mark.rutland,
	martin.donnelly, martyn.welch, mchehab, pawel.moll, rmk+kernel,
	robh+dt, shawnguo, tiwai, treding, ykk

Am Montag, den 30.05.2016, 18:39 +0200 schrieb Peter Senna Tschudin:
> Configure the IPU assignment order to assign one IPU per external
> display. A single IPU can drive multiple external displays but there are
> resolution restrictions. After this patch the GPU is capalbe of driving two
> Full-HD monitors.

It's also capable to do it before this patch, if you use the first and
third crtc. You are just reordering the crtcs.
Unfortunately the IPU has combined limitations across multiple crtcs in
one IPU, which currently can't be communicated to userspace.

regards
Philipp

^ permalink raw reply	[flat|nested] 64+ messages in thread

* Re: [PATCH 1/5] drm/imx-ldb: Add support to drm-bridge
  2016-05-30 16:39 ` [PATCH 1/5] drm/imx-ldb: Add support to drm-bridge Peter Senna Tschudin
@ 2016-06-02 13:09   ` Philipp Zabel
  0 siblings, 0 replies; 64+ messages in thread
From: Philipp Zabel @ 2016-06-02 13:09 UTC (permalink / raw)
  To: Peter Senna Tschudin
  Cc: airlied, akpm, davem, devicetree, dri-devel, enric.balletbo,
	galak, gregkh, heiko, ijc+devicetree, jslaby, kernel,
	linux-arm-kernel, linux, linux-kernel, linux, mark.rutland,
	martin.donnelly, martyn.welch, mchehab, pawel.moll, rmk+kernel,
	robh+dt, shawnguo, tiwai, treding, ykk

Am Montag, den 30.05.2016, 18:39 +0200 schrieb Peter Senna Tschudin:
> Add support to attach a drm_bridge to imx-ldb in addition to
> existing support to attach a LVDS panel.
> 
> Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com>
> ---
>  drivers/gpu/drm/imx/imx-ldb.c | 75 +++++++++++++++++++++++++++++++------------
>  1 file changed, 54 insertions(+), 21 deletions(-)
> 
> diff --git a/drivers/gpu/drm/imx/imx-ldb.c b/drivers/gpu/drm/imx/imx-ldb.c
> index a58eee5..7233a81 100644
> --- a/drivers/gpu/drm/imx/imx-ldb.c
> +++ b/drivers/gpu/drm/imx/imx-ldb.c
> @@ -57,7 +57,11 @@ struct imx_ldb_channel {
>  	struct imx_ldb *ldb;
>  	struct drm_connector connector;
>  	struct drm_encoder encoder;
> +
> +	/* Defines what is connected to the ldb, only one at a time */
>  	struct drm_panel *panel;
> +	struct drm_bridge *ext_bridge;
> +

Just bridge should be clear enough.

>  	struct device_node *child;
>  	int chno;
>  	void *edid;
> @@ -295,6 +299,10 @@ static void imx_ldb_encoder_mode_set(struct drm_encoder *encoder,
>  	}
>  }
>  
> +static void imx_ldb_encoder_enable(struct drm_encoder *encoder)
> +{
> +}
> +

Why?

Note that Liu Ying's atomic series touches this also, and after
"drm/imx: atomic phase 3 step 3: Legacy callback fixups" the enable
callback exists. Depending on how either series proceeds, one will have
to be rebased onto the other.

>  static void imx_ldb_encoder_disable(struct drm_encoder *encoder)
>  {
>  	struct imx_ldb_channel *imx_ldb_ch = enc_to_imx_ldb_ch(encoder);
> @@ -373,6 +381,7 @@ static const struct drm_encoder_helper_funcs imx_ldb_encoder_helper_funcs = {
>  	.prepare = imx_ldb_encoder_prepare,
>  	.commit = imx_ldb_encoder_commit,
>  	.mode_set = imx_ldb_encoder_mode_set,
> +	.enable = imx_ldb_encoder_enable,
>  	.disable = imx_ldb_encoder_disable,
>  };
>  
> @@ -417,16 +426,28 @@ static int imx_ldb_register(struct drm_device *drm,
>  	drm_encoder_init(drm, &imx_ldb_ch->encoder, &imx_ldb_encoder_funcs,
>  			 DRM_MODE_ENCODER_LVDS, NULL);
>  
> -	drm_connector_helper_add(&imx_ldb_ch->connector,
> -			&imx_ldb_connector_helper_funcs);
> -	drm_connector_init(drm, &imx_ldb_ch->connector,
> -			   &imx_ldb_connector_funcs, DRM_MODE_CONNECTOR_LVDS);
> -
> -	if (imx_ldb_ch->panel)
> +	if (imx_ldb_ch->panel) {

	if (!imx_ldb_ch->bridge) {

> +		drm_connector_helper_add(&imx_ldb_ch->connector,
> +				&imx_ldb_connector_helper_funcs);
> +		drm_connector_init(drm, &imx_ldb_ch->connector,
> +				&imx_ldb_connector_funcs,
> +				DRM_MODE_CONNECTOR_LVDS);
>  		drm_panel_attach(imx_ldb_ch->panel, &imx_ldb_ch->connector);
>  
> -	drm_mode_connector_attach_encoder(&imx_ldb_ch->connector,
> -			&imx_ldb_ch->encoder);
> +		drm_mode_connector_attach_encoder(&imx_ldb_ch->connector,
> +				&imx_ldb_ch->encoder);
> +	}
> +
> +	if (imx_ldb_ch->ext_bridge) {
> +		imx_ldb_ch->ext_bridge->encoder = &imx_ldb_ch->encoder;
> +
> +		imx_ldb_ch->encoder.bridge = imx_ldb_ch->ext_bridge;
> +		ret = drm_bridge_attach(drm, imx_ldb_ch->ext_bridge);
> +		if (ret) {
> +			DRM_ERROR("Failed to initialize bridge with drm\n");
> +			return ret;
> +		}
> +	}
>  
>  	return 0;
>  }
> @@ -583,23 +604,35 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data)
>  			endpoint = of_get_child_by_name(port, "endpoint");
>  			if (endpoint) {
>  				remote = of_graph_get_remote_port_parent(endpoint);
> -				if (remote)
> -					channel->panel = of_drm_find_panel(remote);
> -				else
> -					return -EPROBE_DEFER;
> -				if (!channel->panel) {
> -					dev_err(dev, "panel not found: %s\n",
> -						remote->full_name);

Let's keep this message, at least as dev_dbg.

> -					return -EPROBE_DEFER;
> +				if (remote) {
> +					/* Only one of these two will succeed */
> +					channel->panel =
> +						of_drm_find_panel(remote);
> +
> +					channel->ext_bridge =
> +						of_drm_find_bridge(remote);
> +
> +					/*
> +					 * If the bridge is compiled as a
> +					 * module, it may take some time until
> +					 * the bridge driver is available.
> +					 * Defer until the bridge driver is
> +					 * ready.
> +					 */
> +					if (!channel->panel &&
> +							!channel->ext_bridge)
> +						return -EPROBE_DEFER;
>  				}

What happens if (!remote)?

>  			}
>  		}
>  
> -		edidp = of_get_property(child, "edid", &channel->edid_len);
> -		if (edidp) {
> -			channel->edid = kmemdup(edidp, channel->edid_len,
> -						GFP_KERNEL);
> -		} else if (!channel->panel) {
> +		if (channel->panel) {
> +			edidp = of_get_property(child, "edid",
> +					&channel->edid_len);
> +			if (edidp)
> +				channel->edid = kmemdup(edidp,
> +						channel->edid_len, GFP_KERNEL);
> +		} else {

Any reason to disallow overriding EDID from DT if there is no panel? I'd
leave this part as is.

regards
Philipp

^ permalink raw reply	[flat|nested] 64+ messages in thread

* Re: [PATCH 3/5] Documentation/devicetree/bindings: Add b850v3_lvds_dp
  2016-05-30 16:39 ` [PATCH 3/5] Documentation/devicetree/bindings: Add b850v3_lvds_dp Peter Senna Tschudin
  2016-06-02 12:49   ` Philipp Zabel
@ 2016-06-02 22:57   ` Rob Herring
  1 sibling, 0 replies; 64+ messages in thread
From: Rob Herring @ 2016-06-02 22:57 UTC (permalink / raw)
  To: Peter Senna Tschudin
  Cc: airlied, akpm, davem, devicetree, dri-devel, enric.balletbo,
	galak, gregkh, heiko, ijc+devicetree, jslaby, kernel,
	linux-arm-kernel, linux, linux-kernel, linux, mark.rutland,
	martin.donnelly, martyn.welch, mchehab, pawel.moll, p.zabel,
	rmk+kernel, shawnguo, tiwai, treding, ykk

On Mon, May 30, 2016 at 06:39:43PM +0200, Peter Senna Tschudin wrote:
> Devicetree bindings documentation for the GE B850v3 LVDS/DP++
> display bridge.
> 
> Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com>
> ---
>  .../devicetree/bindings/ge/b850v3_lvds_dp.txt      | 38 ++++++++++++++++++++++
>  1 file changed, 38 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt
> 
> diff --git a/Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt b/Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt
> new file mode 100644
> index 0000000..32e123a
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt
> @@ -0,0 +1,38 @@
> +Driver for GE B850v3 LVDS/DP++ display bridge
> +
> +Required properties:
> +  - compatible : should be "ge,b850v3_lvds_dp".
> +  - reg : should contain the address used to ack the interrupts.
> +  - interrupt-parent : should link to the gpio used as interrupt
> +    source on the host.
> +  - interrupts : one interrupt should be described here, as in
> +    <0 IRQ_TYPE_LEVEL_HIGH>.
> +  - edid-reg : should contain the address used to read edid information

This should be known based on the bridge chip you are using.

> +  - port : should describe the vide signal connection between the host
> +    and the bridge.
> +
> +Example:
> +
> +&mux2_i2c2 {
> +	status = "okay";
> +	clock-frequency = <100000>;
> +
> +	b850v3_dp_bridge {

Don't use '_' in node or property names or compatible strings.

> +		compatible = "ge,b850v3_lvds_dp";
> +		#address-cells = <1>;
> +		#size-cells = <0>;
> +
> +		reg = <0x73>;
> +		interrupt-parent = <&gpio2>;
> +		interrupts = <0 IRQ_TYPE_LEVEL_HIGH>;
> +
> +		edid-reg = <0x72>;
> +
> +		port@0 {
> +			reg = <0>;
> +			b850v3_dp_bridge_in: endpoint {
> +				remote-endpoint = <&lvds0_out>;
> +			};
> +		};
> +	};
> +};
> -- 
> 2.5.5
> 

^ permalink raw reply	[flat|nested] 64+ messages in thread

* Re: [PATCH 3/5] Documentation/devicetree/bindings: Add  b850v3_lvds_dp
  2016-06-02 12:49   ` Philipp Zabel
@ 2016-06-02 23:19     ` Peter Senna Tschudin
  0 siblings, 0 replies; 64+ messages in thread
From: Peter Senna Tschudin @ 2016-06-02 23:19 UTC (permalink / raw)
  To: Philipp Zabel
  Cc: martin.donnelly, martyn.welch, linux-arm-kernel, shawnguo, akpm,
	galak, linux-kernel, ijc+devicetree, pawel.moll, treding,
	devicetree, mark.rutland, kernel, robh+dt, airlied,
	enric.balletbo, linux, heiko, mchehab, tiwai, davem, gregkh,
	linux, jslaby, rmk+kernel, ykk, dri-devel, Peter Senna Tschudin

 Hi Philipp,

Thank you very much for the review! I'll send V2 soon, doing my best to avoid collisions with other patches that are under review.

On Thursday, June 2, 2016 14:49 CEST, Philipp Zabel <p.zabel@pengutronix.de> wrote: 
 
> Hi Peter,
> 
> Am Montag, den 30.05.2016, 18:39 +0200 schrieb Peter Senna Tschudin:

> > Devicetree bindings documentation for the GE B850v3 LVDS/DP++
> > display bridge.
> > 
> > Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com>
> > ---
> >  .../devicetree/bindings/ge/b850v3_lvds_dp.txt      | 38 ++++++++++++++++++++++
> >  1 file changed, 38 insertions(+)
> >  create mode 100644 Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt
> > 
> > diff --git a/Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt b/Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt
> > new file mode 100644
> > index 0000000..32e123a
> > --- /dev/null
> > +++ b/Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt
> > @@ -0,0 +1,38 @@
> > +Driver for GE B850v3 LVDS/DP++ display bridge
> > +
> > +Required properties:
> > +  - compatible : should be "ge,b850v3_lvds_dp".
> 
> In the cover mail you write that this is a combination of the STDP4028
> DP transmitter and STDP2690 DP/DP++ converter. So shouldn't this binding
> comprise two device tree nodes, one with compatible "st,stdp4028", one
> with "st,stdp2690", then?
> Is the stdp2690 connected to the stdp4028's i2c master?

The hardware and firmware made it complicated for this binding to comprise two device tree nodes. The hardware and firmware are designed to configure both bridges based on the LVDS signal. This part works very well, but it leave the driver powerless to control the video processing pipeline. The bridges sort of behave as a single bridge, the sort of part is the need of interacting with both bridges: EDID from the STDP2690 and HPD events from the STDP4028. So the driver is only needed for telling the host about EDID / HPD, and for giving the host powers to ack interrupts.

I would find it nicer to be author of two drivers instead of one, but as the behavior handled by the driver is specific of the B850v3, and do not reflect the standard behavior of each bridge, I find it unlikely that the driver could be useful to handle STDP4028 and STPD2690 with stock firmware, or in other designs. On the other hand, I made sure to use the chip name on the defines to maximize chances of reuse.

Peter

^ permalink raw reply	[flat|nested] 64+ messages in thread

* [PATCH V2 0/5] Add driver for GE B850v3 LVDS/DP++ Bridge
  2016-05-30 16:39 [PATCH 0/5] Add driver for GE B850v3 LVDS/DP++ Bridge Peter Senna Tschudin
                   ` (4 preceding siblings ...)
  2016-05-30 16:39 ` [PATCH 5/5] arm/dts/imx6q-b850v3: Use " Peter Senna Tschudin
@ 2016-06-09 16:25 ` Peter Senna Tschudin
  2016-06-09 16:25   ` [PATCH V2 1/5] drm/imx-ldb: Add support to drm-bridge Peter Senna Tschudin
                     ` (4 more replies)
  2016-07-31 19:55 ` [PATCH V3 0/5] Add driver for " Peter Senna Tschudin
                   ` (2 subsequent siblings)
  8 siblings, 5 replies; 64+ messages in thread
From: Peter Senna Tschudin @ 2016-06-09 16:25 UTC (permalink / raw)
  To: airlied, akpm, davem, devicetree, dri-devel, enric.balletbo,
	eballetbo, galak, gregkh, heiko, ijc+devicetree, jslaby, kernel,
	linux-arm-kernel, linux, linux-kernel, linux, mark.rutland,
	martin.donnelly, martyn.welch, mchehab, pawel.moll, peter.senna,
	peter.senna, p.zabel, thierry.reding, rmk+kernel, robh+dt,
	shawnguo, tiwai, treding, ykk

The series adds a driver that creates a drm_bridge and a drm_connector for the
LVDS to DP++ display bridge of the GE B850v3.

There are two physical bridges on the video signal pipeline: a STDP4028(LVDS to
DP) and a STDP2690(DP to DP++).  The hardware and firmware made it complicated
for this binding to comprise two device tree nodes, as the design goal is to
configure both bridges based on the LVDS signal, which leave the driver
powerless to control the video processing pipeline. The two bridges behaves as
a single bridge, and the driver is only needed for telling the host about EDID /
HPD, and for giving the host powers to ack interrupts. The video signal
pipeline is as follows:

  Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output

The patches from the series:
 [1/5] Change the imx-ldb driver to allow attaching a bridge and not only a LVDS
       panel.

 [2/5] Configure the mapping between IPUs and external displays on the dts file
       of the B850v3. Needed if connect two Full-HD monitors.

 [3/5] Devicetree documentation for the GE B850v3 LVDS/DP++ Bridge

 [4/5] Add the driver, make changes to MAINTAINERS, Kconfig and Makefile

 [5/5] Make the changes to the B850v3 dts file to enable the GE B850v3
       LVDS/DP++ Bridge.

Peter Senna Tschudin (5):
  drm/imx-ldb: Add support to drm-bridge
  dts/imx6q-b850v3: Configure IPU assignment order
  Documentation/devicetree/bindings: b850v3_lvds_dp
  drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge
  dts/imx6q-b850v3: Use GE B850v3 LVDS/DP++ Bridge

 .../devicetree/bindings/ge/b850v3_lvds_dp.txt      |  38 ++
 MAINTAINERS                                        |   8 +
 arch/arm/boot/dts/imx6q-b850v3.dts                 |  36 ++
 drivers/gpu/drm/bridge/Kconfig                     |  11 +
 drivers/gpu/drm/bridge/Makefile                    |   1 +
 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c         | 392 +++++++++++++++++++++
 drivers/gpu/drm/imx/imx-ldb.c                      | 121 ++++---
 7 files changed, 565 insertions(+), 42 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt
 create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c

-- 
2.5.5

^ permalink raw reply	[flat|nested] 64+ messages in thread

* [PATCH V2 1/5] drm/imx-ldb: Add support to drm-bridge
  2016-06-09 16:25 ` [PATCH V2 0/5] Add driver for " Peter Senna Tschudin
@ 2016-06-09 16:25   ` Peter Senna Tschudin
  2016-06-09 16:25   ` [PATCH V2 2/5] dts/imx6q-b850v3: Configure IPU assignment order Peter Senna Tschudin
                     ` (3 subsequent siblings)
  4 siblings, 0 replies; 64+ messages in thread
From: Peter Senna Tschudin @ 2016-06-09 16:25 UTC (permalink / raw)
  To: airlied, akpm, davem, devicetree, dri-devel, enric.balletbo,
	eballetbo, galak, gregkh, heiko, ijc+devicetree, jslaby, kernel,
	linux-arm-kernel, linux, linux-kernel, linux, mark.rutland,
	martin.donnelly, martyn.welch, mchehab, pawel.moll, peter.senna,
	peter.senna, p.zabel, thierry.reding, rmk+kernel, robh+dt,
	shawnguo, tiwai, treding, ykk
  Cc: Rob Herring, Fabio Estevam

Add support to attach a drm_bridge to imx-ldb in addition to
existing support to attach a LVDS panel.

This patch does a simple code refactoring by moving code
from for_each_child_of_node iterator to a new function named
imx_ldb_panel_ddc(). This was necessary to allow the panel ddc
code to run only when the imx_ldb is not attached to a bridge.

Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com>
Cc: Philipp Zabel <p.zabel@pengutronix.de>
Cc: Rob Herring <robh@kernel.org>
Cc: Fabio Estevam <fabio.estevam@nxp.com>
Cc: David Airlie <airlied@linux.ie>
Cc: Thierry Reding <treding@nvidia.com>
Cc: Thierry Reding <thierry.reding@gmail.com>
Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com>
---
Changes from V1:
 - Reanmed ext_bridge to bridge
 - Removed empty entry point imx_ldb_encoder_enable()
 - Adapted the code to apply to the latest linux next: next-20160609

 drivers/gpu/drm/imx/imx-ldb.c | 121 +++++++++++++++++++++++++++---------------
 1 file changed, 79 insertions(+), 42 deletions(-)

diff --git a/drivers/gpu/drm/imx/imx-ldb.c b/drivers/gpu/drm/imx/imx-ldb.c
index beff793..55e94cc 100644
--- a/drivers/gpu/drm/imx/imx-ldb.c
+++ b/drivers/gpu/drm/imx/imx-ldb.c
@@ -58,7 +58,11 @@ struct imx_ldb_channel {
 	struct imx_ldb *ldb;
 	struct drm_connector connector;
 	struct drm_encoder encoder;
+
+	/* Defines what is connected to the ldb, only one at a time */
 	struct drm_panel *panel;
+	struct drm_bridge *bridge;
+
 	struct device_node *child;
 	struct i2c_adapter *ddc;
 	int chno;
@@ -422,16 +426,27 @@ static int imx_ldb_register(struct drm_device *drm,
 	drm_encoder_init(drm, &imx_ldb_ch->encoder, &imx_ldb_encoder_funcs,
 			 DRM_MODE_ENCODER_LVDS, NULL);
 
-	drm_connector_helper_add(&imx_ldb_ch->connector,
-			&imx_ldb_connector_helper_funcs);
-	drm_connector_init(drm, &imx_ldb_ch->connector,
-			   &imx_ldb_connector_funcs, DRM_MODE_CONNECTOR_LVDS);
-
-	if (imx_ldb_ch->panel)
+	if (imx_ldb_ch->panel) {
+		drm_connector_helper_add(&imx_ldb_ch->connector,
+				&imx_ldb_connector_helper_funcs);
+		drm_connector_init(drm, &imx_ldb_ch->connector,
+				&imx_ldb_connector_funcs,
+				DRM_MODE_CONNECTOR_LVDS);
 		drm_panel_attach(imx_ldb_ch->panel, &imx_ldb_ch->connector);
+		drm_mode_connector_attach_encoder(&imx_ldb_ch->connector,
+				&imx_ldb_ch->encoder);
+	}
 
-	drm_mode_connector_attach_encoder(&imx_ldb_ch->connector,
-			&imx_ldb_ch->encoder);
+	if (imx_ldb_ch->bridge) {
+		imx_ldb_ch->bridge->encoder = &imx_ldb_ch->encoder;
+
+		imx_ldb_ch->encoder.bridge = imx_ldb_ch->bridge;
+		ret = drm_bridge_attach(drm, imx_ldb_ch->bridge);
+		if (ret) {
+			DRM_ERROR("Failed to initialize bridge with drm\n");
+			return ret;
+		}
+	}
 
 	return 0;
 }
@@ -501,6 +516,45 @@ static const struct of_device_id imx_ldb_dt_ids[] = {
 };
 MODULE_DEVICE_TABLE(of, imx_ldb_dt_ids);
 
+static int imx_ldb_panel_ddc(struct device *dev,
+		struct imx_ldb_channel *channel, struct device_node *child)
+{
+	struct device_node *ddc_node;
+	const u8 *edidp;
+	int ret;
+
+	ddc_node = of_parse_phandle(child, "ddc-i2c-bus", 0);
+	if (ddc_node) {
+		channel->ddc = of_find_i2c_adapter_by_node(ddc_node);
+		of_node_put(ddc_node);
+		if (!channel->ddc) {
+			dev_warn(dev, "failed to get ddc i2c adapter\n");
+			return -EPROBE_DEFER;
+		}
+	}
+
+	if (!channel->ddc) {
+		/* if no DDC available, fallback to hardcoded EDID */
+		dev_dbg(dev, "no ddc available\n");
+
+		edidp = of_get_property(child, "edid",
+					&channel->edid_len);
+		if (edidp) {
+			channel->edid = kmemdup(edidp,
+						channel->edid_len,
+						GFP_KERNEL);
+		} else if (!channel->panel) {
+			/* fallback to display-timings node */
+			ret = of_get_drm_display_mode(child,
+						      &channel->mode,
+						      OF_USE_NATIVE_MODE);
+			if (!ret)
+				channel->mode_valid = 1;
+		}
+	}
+	return 0;
+}
+
 static int imx_ldb_bind(struct device *dev, struct device *master, void *data)
 {
 	struct drm_device *drm = data;
@@ -508,7 +562,6 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data)
 	const struct of_device_id *of_id =
 			of_match_device(imx_ldb_dt_ids, dev);
 	struct device_node *child;
-	const u8 *edidp;
 	struct imx_ldb *imx_ldb;
 	int dual;
 	int ret;
@@ -558,7 +611,6 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data)
 
 	for_each_child_of_node(np, child) {
 		struct imx_ldb_channel *channel;
-		struct device_node *ddc_node;
 		struct device_node *ep;
 
 		ret = of_property_read_u32(child, "reg", &i);
@@ -590,46 +642,31 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data)
 
 			remote = of_graph_get_remote_port_parent(ep);
 			of_node_put(ep);
-			if (remote)
+			if (remote) {
 				channel->panel = of_drm_find_panel(remote);
-			else
+				channel->bridge = of_drm_find_bridge(remote);
+			} else
 				return -EPROBE_DEFER;
+
 			of_node_put(remote);
-			if (!channel->panel) {
-				dev_err(dev, "panel not found: %s\n",
-					remote->full_name);
-				return -EPROBE_DEFER;
-			}
-		}
 
-		ddc_node = of_parse_phandle(child, "ddc-i2c-bus", 0);
-		if (ddc_node) {
-			channel->ddc = of_find_i2c_adapter_by_node(ddc_node);
-			of_node_put(ddc_node);
-			if (!channel->ddc) {
-				dev_warn(dev, "failed to get ddc i2c adapter\n");
+			/*
+			 * If the bridge is compiled as a module, it may take
+			 * some time until the bridge driver is available.
+			 * Defer until the bridge driver is ready.
+			 */
+			if (!channel->panel && !channel->bridge) {
+				dev_err(dev, "panel/bridge not found: %s\n",
+					remote->full_name);
 				return -EPROBE_DEFER;
 			}
 		}
 
-		if (!channel->ddc) {
-			/* if no DDC available, fallback to hardcoded EDID */
-			dev_dbg(dev, "no ddc available\n");
-
-			edidp = of_get_property(child, "edid",
-						&channel->edid_len);
-			if (edidp) {
-				channel->edid = kmemdup(edidp,
-							channel->edid_len,
-							GFP_KERNEL);
-			} else if (!channel->panel) {
-				/* fallback to display-timings node */
-				ret = of_get_drm_display_mode(child,
-							      &channel->mode,
-							      OF_USE_NATIVE_MODE);
-				if (!ret)
-					channel->mode_valid = 1;
-			}
+		/* Only does panel ddc work if there is no bridge */
+		if (!channel->bridge) {
+			ret = imx_ldb_panel_ddc(dev, channel, child);
+			if (ret)
+				return ret;
 		}
 
 		channel->bus_format = of_get_bus_format(dev, child);
-- 
2.5.5

^ permalink raw reply related	[flat|nested] 64+ messages in thread

* [PATCH V2 2/5] dts/imx6q-b850v3: Configure IPU assignment order
  2016-06-09 16:25 ` [PATCH V2 0/5] Add driver for " Peter Senna Tschudin
  2016-06-09 16:25   ` [PATCH V2 1/5] drm/imx-ldb: Add support to drm-bridge Peter Senna Tschudin
@ 2016-06-09 16:25   ` Peter Senna Tschudin
  2016-06-09 16:25   ` [PATCH V2 3/5] Documentation/devicetree/bindings: b850v3_lvds_dp Peter Senna Tschudin
                     ` (2 subsequent siblings)
  4 siblings, 0 replies; 64+ messages in thread
From: Peter Senna Tschudin @ 2016-06-09 16:25 UTC (permalink / raw)
  To: airlied, akpm, davem, devicetree, dri-devel, enric.balletbo,
	eballetbo, galak, gregkh, heiko, ijc+devicetree, jslaby, kernel,
	linux-arm-kernel, linux, linux-kernel, linux, mark.rutland,
	martin.donnelly, martyn.welch, mchehab, pawel.moll, peter.senna,
	peter.senna, p.zabel, thierry.reding, rmk+kernel, robh+dt,
	shawnguo, tiwai, treding, ykk
  Cc: Rob Herring, Fabio Estevam

As the IPU has combined limitations across multiple crtcs, and as that
can't be communicated to userspace at the moment, reorder the crtcs to
allow support to two Full-HD monitors by avoiding assigning two
monitors to a single IPU.

Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com>
Cc: Philipp Zabel <p.zabel@pengutronix.de>
Cc: Rob Herring <robh@kernel.org>
Cc: Fabio Estevam <fabio.estevam@nxp.com>
Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com>
---
Changes from V1:
 - New commit message

 arch/arm/boot/dts/imx6q-b850v3.dts | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/arch/arm/boot/dts/imx6q-b850v3.dts b/arch/arm/boot/dts/imx6q-b850v3.dts
index 167f744..88a70de 100644
--- a/arch/arm/boot/dts/imx6q-b850v3.dts
+++ b/arch/arm/boot/dts/imx6q-b850v3.dts
@@ -51,6 +51,11 @@
 	chosen {
 		stdout-path = &uart3;
 	};
+
+	display-subsystem {
+		compatible = "fsl,imx-display-subsystem";
+		ports = <&ipu1_di0>, <&ipu2_di0>, <&ipu1_di1>, <&ipu2_di1>;
+	};
 };
 
 &clks {
-- 
2.5.5

^ permalink raw reply related	[flat|nested] 64+ messages in thread

* [PATCH V2 3/5] Documentation/devicetree/bindings: b850v3_lvds_dp
  2016-06-09 16:25 ` [PATCH V2 0/5] Add driver for " Peter Senna Tschudin
  2016-06-09 16:25   ` [PATCH V2 1/5] drm/imx-ldb: Add support to drm-bridge Peter Senna Tschudin
  2016-06-09 16:25   ` [PATCH V2 2/5] dts/imx6q-b850v3: Configure IPU assignment order Peter Senna Tschudin
@ 2016-06-09 16:25   ` Peter Senna Tschudin
  2016-06-10 17:42     ` Rob Herring
  2016-06-10 18:54     ` Javier Martinez Canillas
  2016-06-09 16:25   ` [PATCH V2 4/5] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge Peter Senna Tschudin
  2016-06-09 16:25   ` [PATCH V2 5/5] dts/imx6q-b850v3: Use " Peter Senna Tschudin
  4 siblings, 2 replies; 64+ messages in thread
From: Peter Senna Tschudin @ 2016-06-09 16:25 UTC (permalink / raw)
  To: airlied, akpm, davem, devicetree, dri-devel, enric.balletbo,
	eballetbo, galak, gregkh, heiko, ijc+devicetree, jslaby, kernel,
	linux-arm-kernel, linux, linux-kernel, linux, mark.rutland,
	martin.donnelly, martyn.welch, mchehab, pawel.moll, peter.senna,
	peter.senna, p.zabel, thierry.reding, rmk+kernel, robh+dt,
	shawnguo, tiwai, treding, ykk
  Cc: Rob Herring, Fabio Estevam

Devicetree bindings documentation for the GE B850v3 LVDS/DP++
display bridge.

Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com>
Cc: Philipp Zabel <p.zabel@pengutronix.de>
Cc: Rob Herring <robh@kernel.org>
Cc: Fabio Estevam <fabio.estevam@nxp.com>
Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com>
---
Changes from V1:
 - Replaced '_' by '-' in node names or compatible strings
 - Added missing @73 to the example

 .../devicetree/bindings/ge/b850v3_lvds_dp.txt      | 38 ++++++++++++++++++++++
 1 file changed, 38 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt

diff --git a/Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt b/Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt
new file mode 100644
index 0000000..46bbea9
--- /dev/null
+++ b/Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt
@@ -0,0 +1,38 @@
+Driver for GE B850v3 LVDS/DP++ display bridge
+
+Required properties:
+  - compatible : should be "ge,b850v3_lvds_dp".
+  - reg : should contain the address used to ack the interrupts.
+  - interrupt-parent : should link to the gpio used as interrupt
+    source on the host.
+  - interrupts : one interrupt should be described here, as in
+    <0 IRQ_TYPE_LEVEL_HIGH>.
+  - edid-reg : should contain the address used to read edid information
+  - port : should describe the vide signal connection between the host
+    and the bridge.
+
+Example:
+
+&mux2_i2c2 {
+	status = "okay";
+	clock-frequency = <100000>;
+
+	b850v3-lvds-dp-bridge@73  {
+		compatible = "ge,b850v3-lvds-dp";
+		#address-cells = <1>;
+		#size-cells = <0>;
+
+		reg = <0x73>;
+		interrupt-parent = <&gpio2>;
+		interrupts = <0 IRQ_TYPE_LEVEL_HIGH>;
+
+		edid-reg = <0x72>;
+
+		port@0 {
+			reg = <0>;
+			b850v3_dp_bridge_in: endpoint {
+				remote-endpoint = <&lvds0_out>;
+			};
+		};
+	};
+};
-- 
2.5.5

^ permalink raw reply related	[flat|nested] 64+ messages in thread

* [PATCH V2 4/5] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge
  2016-06-09 16:25 ` [PATCH V2 0/5] Add driver for " Peter Senna Tschudin
                     ` (2 preceding siblings ...)
  2016-06-09 16:25   ` [PATCH V2 3/5] Documentation/devicetree/bindings: b850v3_lvds_dp Peter Senna Tschudin
@ 2016-06-09 16:25   ` Peter Senna Tschudin
  2016-06-10  7:39     ` Enric Balletbo Serra
                       ` (2 more replies)
  2016-06-09 16:25   ` [PATCH V2 5/5] dts/imx6q-b850v3: Use " Peter Senna Tschudin
  4 siblings, 3 replies; 64+ messages in thread
From: Peter Senna Tschudin @ 2016-06-09 16:25 UTC (permalink / raw)
  To: airlied, akpm, davem, devicetree, dri-devel, enric.balletbo,
	eballetbo, galak, gregkh, heiko, ijc+devicetree, jslaby, kernel,
	linux-arm-kernel, linux, linux-kernel, linux, mark.rutland,
	martin.donnelly, martyn.welch, mchehab, pawel.moll, peter.senna,
	peter.senna, p.zabel, thierry.reding, rmk+kernel, robh+dt,
	shawnguo, tiwai, treding, ykk
  Cc: Rob Herring, Fabio Estevam

Add a driver that create a drm_bridge and a drm_connector for the LVDS
to DP++ display bridge of the GE B850v3.

There are two physical bridges on the video signal pipeline: a
STDP4028(LVDS to DP) and a STDP2690(DP to DP++).  The hardware and
firmware made it complicated for this binding to comprise two device
tree nodes, as the design goal is to configure both bridges based on
the LVDS signal, which leave the driver powerless to control the video
processing pipeline. The two bridges behaves as a single bridge, and
the driver is only needed for telling the host about EDID / HPD, and
for giving the host powers to ack interrupts. The video signal pipeline
is as follows:

  Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output

Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com>
Cc: Philipp Zabel <p.zabel@pengutronix.de>
Cc: Rob Herring <robh@kernel.org>
Cc: Fabio Estevam <fabio.estevam@nxp.com>
CC: David Airlie <airlied@linux.ie>
CC: Thierry Reding <treding@nvidia.com>
CC: Thierry Reding <thierry.reding@gmail.com>
Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com>
---
Changes from V1:
 - New commit message
 - Removed 3 empty entry points
 - Removed memory leak from ge_b850v3_lvds_dp_get_modes()
 - Added a lock for mode setting
 - Removed a few blank lines
 - Changed the order at Makefile and Kconfig

 MAINTAINERS                                |   8 +
 drivers/gpu/drm/bridge/Kconfig             |  11 +
 drivers/gpu/drm/bridge/Makefile            |   1 +
 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c | 392 +++++++++++++++++++++++++++++
 4 files changed, 412 insertions(+)
 create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 2ce5e91..2dd3d7f 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -5010,6 +5010,14 @@ W:	https://linuxtv.org
 S:	Maintained
 F:	drivers/media/radio/radio-gemtek*
 
+GENERAL ELECTRIC B850V3 LVDS/DP++ BRIDGE
+M:	Martin Donnelly <martin.donnelly@ge.com>
+M:	Peter Senna Tschudin <peter.senna@collabora.com>
+M:	Martyn Welch <martyn.welch@collabora.co.uk>
+S:	Maintained
+F:	drivers/gpu/drm/bridge/ge_b850v3_dp2.c
+F:	Documentation/devicetree/bindings/ge/b850v3_dp2_bridge.txt
+
 GENERIC GPIO I2C DRIVER
 M:	Haavard Skinnemoen <hskinnemoen@gmail.com>
 S:	Supported
diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
index 8f7423f..93dae5bd 100644
--- a/drivers/gpu/drm/bridge/Kconfig
+++ b/drivers/gpu/drm/bridge/Kconfig
@@ -32,6 +32,17 @@ config DRM_DW_HDMI_AHB_AUDIO
 	  Designware HDMI block.  This is used in conjunction with
 	  the i.MX6 HDMI driver.
 
+config DRM_GE_B850V3_LVDS_DP
+	tristate "GE B850v3 LVDS to DP++ display bridge"
+	depends on OF
+	select DRM_KMS_HELPER
+	select DRM_PANEL
+	---help---
+          This is a driver for the display bridge of
+          GE B850v3 that convert dual channel LVDS
+          to DP++. This is used with the i.MX6 imx-ldb
+          driver.
+
 config DRM_NXP_PTN3460
 	tristate "NXP PTN3460 DP/LVDS bridge"
 	depends on OF
diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
index 96b13b3..47ea6c1 100644
--- a/drivers/gpu/drm/bridge/Makefile
+++ b/drivers/gpu/drm/bridge/Makefile
@@ -3,6 +3,7 @@ ccflags-y := -Iinclude/drm
 obj-$(CONFIG_DRM_ANALOGIX_ANX78XX) += analogix-anx78xx.o
 obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o
 obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o
+obj-$(CONFIG_DRM_GE_B850V3_LVDS_DP) += ge_b850v3_lvds_dp.o
 obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o
 obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o
 obj-$(CONFIG_DRM_ANALOGIX_DP) += analogix/
diff --git a/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c
new file mode 100644
index 0000000..c73cd77
--- /dev/null
+++ b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c
@@ -0,0 +1,392 @@
+/*
+ * Driver for GE B850v3 DP display bridge
+
+ * Copyright (c) 2016, Collabora Ltd.
+ * Copyright (c) 2016, General Electric Company
+
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+ * This driver creates a drm_bridge and a drm_connector for the LVDS to DP++
+ * display bridge of the GE B850v3. There are two physical bridges on the video
+ * signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). However
+ * the physical bridges are automatically configured by the input video signal,
+ * and the driver has no access to the video processing pipeline. The driver is
+ * only needed to read EDID from the STDP2690 and to handle HPD events from the
+ * STDP4028. The driver communicates with both bridges over i2c. The video
+ * signal pipeline is as follows:
+ *
+ *   Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output
+
+ *
+ */
+#include <linux/gpio.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include "drm_crtc.h"
+#include "drm_crtc_helper.h"
+#include "drm_edid.h"
+#include "drmP.h"
+
+#define EDID_EXT_BLOCK_CNT 0x7E
+
+#define STDP4028_IRQ_OUT_CONF_REG 0x02
+#define STDP4028_DPTX_IRQ_EN_REG 0x3C
+#define STDP4028_DPTX_IRQ_STS_REG 0x3D
+#define STDP4028_DPTX_STS_REG 0x3E
+
+#define STDP4028_DPTX_DP_IRQ_EN 0x1000
+
+#define STDP4028_DPTX_HOTPLUG_IRQ_EN 0x0400
+#define STDP4028_DPTX_LINK_CH_IRQ_EN 0x2000
+#define STDP4028_DPTX_IRQ_CONFIG \
+		(STDP4028_DPTX_LINK_CH_IRQ_EN | STDP4028_DPTX_HOTPLUG_IRQ_EN)
+
+#define STDP4028_DPTX_HOTPLUG_STS 0x0200
+#define STDP4028_DPTX_LINK_STS 0x1000
+#define STDP4028_CON_STATE_CONNECTED \
+		(STDP4028_DPTX_HOTPLUG_STS | STDP4028_DPTX_LINK_STS)
+
+#define STDP4028_DPTX_HOTPLUG_CH_STS 0x0400
+#define STDP4028_DPTX_LINK_CH_STS 0x2000
+#define STDP4028_DPTX_IRQ_CLEAR \
+		(STDP4028_DPTX_LINK_CH_STS | STDP4028_DPTX_HOTPLUG_CH_STS)
+
+struct ge_b850v3_lvds_dp {
+	struct drm_connector connector;
+	struct drm_bridge bridge;
+	struct i2c_client *ge_b850v3_lvds_dp_i2c;
+	struct i2c_client *edid_i2c;
+	struct edid *edid;
+	struct mutex lock;
+};
+
+static inline struct ge_b850v3_lvds_dp *
+		bridge_to_ge_b850v3_lvds_dp(struct drm_bridge *bridge)
+{
+	return container_of(bridge, struct ge_b850v3_lvds_dp, bridge);
+}
+
+static inline struct ge_b850v3_lvds_dp *
+		connector_to_ge_b850v3_lvds_dp(struct drm_connector *connector)
+{
+	return container_of(connector, struct ge_b850v3_lvds_dp, connector);
+}
+
+static void ge_b850v3_lvds_dp_enable(struct drm_bridge *bridge)
+{
+}
+
+static void ge_b850v3_lvds_dp_disable(struct drm_bridge *bridge)
+{
+}
+
+u8 *stdp2690_get_edid(struct i2c_client *client)
+{
+	struct i2c_adapter *adapter = client->adapter;
+	unsigned char start = 0x00;
+	unsigned int total_size;
+	u8 *block = kmalloc(EDID_LENGTH, GFP_KERNEL);
+
+	struct i2c_msg msgs[] = {
+		{
+			.addr	= client->addr,
+			.flags	= 0,
+			.len	= 1,
+			.buf	= &start,
+		}, {
+			.addr	= client->addr,
+			.flags	= I2C_M_RD,
+			.len	= EDID_LENGTH,
+			.buf	= block,
+		}
+	};
+
+	if (!block)
+		return NULL;
+
+	if (i2c_transfer(adapter, msgs, 2) != 2) {
+		DRM_ERROR("Unable to read EDID.\n");
+		goto err;
+	}
+
+	if (!drm_edid_block_valid(block, 0, false, NULL)) {
+		DRM_ERROR("Invalid EDID block\n");
+		goto err;
+	}
+
+	total_size = (block[EDID_EXT_BLOCK_CNT] + 1) * EDID_LENGTH;
+	if (total_size > EDID_LENGTH) {
+		kfree(block);
+		block = kmalloc(total_size, GFP_KERNEL);
+		if (!block)
+			return NULL;
+
+		/* Yes, read the entire buffer, and do not skip the first
+		 * EDID_LENGTH bytes.
+		 */
+		start = 0x00;
+		msgs[1].len = total_size;
+		msgs[1].buf = block;
+
+		if (i2c_transfer(adapter, msgs, 2) != 2) {
+			DRM_ERROR("Unable to read EDID extension blocks.\n");
+			goto err;
+		}
+	}
+
+	return block;
+
+err:
+	kfree(block);
+	return NULL;
+}
+
+static int ge_b850v3_lvds_dp_get_modes(struct drm_connector *connector)
+{
+	struct ge_b850v3_lvds_dp *ptn_bridge;
+	struct i2c_client *client;
+	int num_modes = 0;
+
+	ptn_bridge = connector_to_ge_b850v3_lvds_dp(connector);
+	client = ptn_bridge->edid_i2c;
+
+	mutex_lock(&ptn_bridge->lock);
+
+	kfree(ptn_bridge->edid);
+	ptn_bridge->edid = (struct edid *) stdp2690_get_edid(client);
+
+	if (ptn_bridge->edid) {
+		drm_mode_connector_update_edid_property(connector,
+				ptn_bridge->edid);
+		num_modes = drm_add_edid_modes(connector, ptn_bridge->edid);
+	}
+
+	mutex_unlock(&ptn_bridge->lock);
+
+	return num_modes;
+}
+
+static struct drm_encoder
+*ge_b850v3_lvds_dp_best_encoder(struct drm_connector *connector)
+{
+	struct ge_b850v3_lvds_dp *ptn_bridge =
+		connector_to_ge_b850v3_lvds_dp(connector);
+
+	return ptn_bridge->bridge.encoder;
+}
+
+static const struct
+drm_connector_helper_funcs ge_b850v3_lvds_dp_connector_helper_funcs = {
+	.get_modes = ge_b850v3_lvds_dp_get_modes,
+	.best_encoder = ge_b850v3_lvds_dp_best_encoder,
+};
+
+static enum drm_connector_status ge_b850v3_lvds_dp_detect(
+		struct drm_connector *connector, bool force)
+{
+	struct ge_b850v3_lvds_dp *ptn_bridge =
+			connector_to_ge_b850v3_lvds_dp(connector);
+	struct i2c_client *ge_b850v3_lvds_dp_i2c =
+			ptn_bridge->ge_b850v3_lvds_dp_i2c;
+	s32 link_state;
+
+	link_state = i2c_smbus_read_word_data(ge_b850v3_lvds_dp_i2c,
+			STDP4028_DPTX_STS_REG);
+
+	if (link_state == STDP4028_CON_STATE_CONNECTED)
+		return connector_status_connected;
+
+	if (link_state == 0)
+		return connector_status_disconnected;
+
+	return connector_status_unknown;
+}
+
+static void ge_b850v3_lvds_dp_connector_destroy(struct drm_connector *connector)
+{
+	drm_connector_cleanup(connector);
+}
+
+static const struct drm_connector_funcs ge_b850v3_lvds_dp_connector_funcs = {
+	.dpms = drm_helper_connector_dpms,
+	.fill_modes = drm_helper_probe_single_connector_modes,
+	.detect = ge_b850v3_lvds_dp_detect,
+	.destroy = ge_b850v3_lvds_dp_connector_destroy,
+};
+
+static irqreturn_t ge_b850v3_lvds_dp_irq_handler(int irq, void *dev_id)
+{
+	struct ge_b850v3_lvds_dp *ptn_bridge = dev_id;
+	struct i2c_client *ge_b850v3_lvds_dp_i2c
+			= ptn_bridge->ge_b850v3_lvds_dp_i2c;
+
+	mutex_lock(&ptn_bridge->lock);
+
+	i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c,
+			STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR);
+
+	mutex_unlock(&ptn_bridge->lock);
+
+	if (ptn_bridge->connector.dev)
+		drm_kms_helper_hotplug_event(ptn_bridge->connector.dev);
+
+	return IRQ_HANDLED;
+}
+
+static int ge_b850v3_lvds_dp_attach(struct drm_bridge *bridge)
+{
+	struct ge_b850v3_lvds_dp *ptn_bridge
+			= bridge_to_ge_b850v3_lvds_dp(bridge);
+	struct drm_connector *connector = &ptn_bridge->connector;
+	struct i2c_client *ge_b850v3_lvds_dp_i2c
+			= ptn_bridge->ge_b850v3_lvds_dp_i2c;
+	int ret;
+
+	if (!bridge->encoder) {
+		DRM_ERROR("Parent encoder object not found");
+		return -ENODEV;
+	}
+
+	connector->polled = DRM_CONNECTOR_POLL_HPD;
+
+	drm_connector_helper_add(connector,
+			&ge_b850v3_lvds_dp_connector_helper_funcs);
+
+	ret = drm_connector_init(bridge->dev, connector,
+			&ge_b850v3_lvds_dp_connector_funcs,
+			DRM_MODE_CONNECTOR_DisplayPort);
+	if (ret) {
+		DRM_ERROR("Failed to initialize connector with drm\n");
+		return ret;
+	}
+
+	ret = drm_mode_connector_attach_encoder(connector, bridge->encoder);
+	if (ret)
+		return ret;
+
+	drm_bridge_enable(bridge);
+	if (ge_b850v3_lvds_dp_i2c->irq) {
+		drm_helper_hpd_irq_event(connector->dev);
+
+		ret = devm_request_threaded_irq(&ge_b850v3_lvds_dp_i2c->dev,
+				ge_b850v3_lvds_dp_i2c->irq, NULL,
+				ge_b850v3_lvds_dp_irq_handler,
+				IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
+				"ge-b850v3-lvds-dp", ptn_bridge);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static const struct drm_bridge_funcs ge_b850v3_lvds_dp_funcs = {
+	.enable = ge_b850v3_lvds_dp_enable,
+	.disable = ge_b850v3_lvds_dp_disable,
+	.attach = ge_b850v3_lvds_dp_attach,
+};
+
+static int ge_b850v3_lvds_dp_probe(struct i2c_client *ge_b850v3_lvds_dp_i2c,
+				const struct i2c_device_id *id)
+{
+	struct device *dev = &ge_b850v3_lvds_dp_i2c->dev;
+	struct ge_b850v3_lvds_dp *ptn_bridge;
+	int ret;
+	u32 edid_i2c_reg;
+
+	ptn_bridge = devm_kzalloc(dev, sizeof(*ptn_bridge), GFP_KERNEL);
+	if (!ptn_bridge)
+		return -ENOMEM;
+
+	mutex_init(&ptn_bridge->lock);
+
+	ptn_bridge->ge_b850v3_lvds_dp_i2c = ge_b850v3_lvds_dp_i2c;
+	ptn_bridge->bridge.driver_private = ptn_bridge;
+	i2c_set_clientdata(ge_b850v3_lvds_dp_i2c, ptn_bridge);
+
+	ret = of_property_read_u32(dev->of_node, "edid-reg", &edid_i2c_reg);
+	if (ret) {
+		dev_err(dev, "edid-reg not specified, aborting...\n");
+		return -ENODEV;
+	}
+
+	ptn_bridge->edid_i2c = devm_kzalloc(dev,
+			sizeof(struct i2c_client), GFP_KERNEL);
+
+	if (!ptn_bridge->edid_i2c)
+		return -ENOMEM;
+
+	memcpy(ptn_bridge->edid_i2c, ge_b850v3_lvds_dp_i2c,
+			sizeof(struct i2c_client));
+
+	ptn_bridge->edid_i2c->addr = (unsigned short) edid_i2c_reg;
+
+	/* Configures the bridge to re-enable interrupts after each ack */
+	i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c,
+			STDP4028_IRQ_OUT_CONF_REG, STDP4028_DPTX_DP_IRQ_EN);
+	i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c,
+			STDP4028_DPTX_IRQ_EN_REG, STDP4028_DPTX_IRQ_CONFIG);
+
+	/* Clear pending interrupts since power up. */
+	i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c,
+			STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR);
+
+	ptn_bridge->bridge.funcs = &ge_b850v3_lvds_dp_funcs;
+	ptn_bridge->bridge.of_node = dev->of_node;
+	ret = drm_bridge_add(&ptn_bridge->bridge);
+	if (ret) {
+		DRM_ERROR("Failed to add bridge\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static int ge_b850v3_lvds_dp_remove(struct i2c_client *ge_b850v3_lvds_dp_i2c)
+{
+	struct ge_b850v3_lvds_dp *ptn_bridge =
+		i2c_get_clientdata(ge_b850v3_lvds_dp_i2c);
+
+	drm_bridge_remove(&ptn_bridge->bridge);
+
+	return 0;
+}
+
+static const struct i2c_device_id ge_b850v3_lvds_dp_i2c_table[] = {
+	{"b850v3-lvds-dp", 0},
+	{},
+};
+MODULE_DEVICE_TABLE(i2c, ge_b850v3_lvds_dp_i2c_table);
+
+static const struct of_device_id ge_b850v3_lvds_dp_match[] = {
+	{ .compatible = "ge,b850v3-lvds-dp" },
+	{},
+};
+MODULE_DEVICE_TABLE(of, ge_b850v3_lvds_dp_match);
+
+static struct i2c_driver ge_b850v3_lvds_dp_driver = {
+	.id_table	= ge_b850v3_lvds_dp_i2c_table,
+	.probe		= ge_b850v3_lvds_dp_probe,
+	.remove		= ge_b850v3_lvds_dp_remove,
+	.driver		= {
+		.name		= "ge,b850v3-lvds-dp",
+		.of_match_table = ge_b850v3_lvds_dp_match,
+	},
+};
+module_i2c_driver(ge_b850v3_lvds_dp_driver);
+
+MODULE_AUTHOR("Peter Senna Tschudin <peter.senna@collabora.com>");
+MODULE_AUTHOR("Martyn Welch <martyn.welch@collabora.co.uk>");
+MODULE_DESCRIPTION("GE LVDS to DP++ bridge)");
+MODULE_LICENSE("GPL v2");
-- 
2.5.5

^ permalink raw reply related	[flat|nested] 64+ messages in thread

* [PATCH V2 5/5] dts/imx6q-b850v3: Use GE B850v3 LVDS/DP++ Bridge
  2016-06-09 16:25 ` [PATCH V2 0/5] Add driver for " Peter Senna Tschudin
                     ` (3 preceding siblings ...)
  2016-06-09 16:25   ` [PATCH V2 4/5] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge Peter Senna Tschudin
@ 2016-06-09 16:25   ` Peter Senna Tschudin
  4 siblings, 0 replies; 64+ messages in thread
From: Peter Senna Tschudin @ 2016-06-09 16:25 UTC (permalink / raw)
  To: airlied, akpm, davem, devicetree, dri-devel, enric.balletbo,
	eballetbo, galak, gregkh, heiko, ijc+devicetree, jslaby, kernel,
	linux-arm-kernel, linux, linux-kernel, linux, mark.rutland,
	martin.donnelly, martyn.welch, mchehab, pawel.moll, peter.senna,
	peter.senna, p.zabel, thierry.reding, rmk+kernel, robh+dt,
	shawnguo, tiwai, treding, ykk
  Cc: Rob Herring, Fabio Estevam

Configures the GE B850v3 LVDS/DP++ bridge on the dts file.

Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com>
Cc: Philipp Zabel <p.zabel@pengutronix.de>
Cc: Rob Herring <robh@kernel.org>
Cc: Fabio Estevam <fabio.estevam@nxp.com>
Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com>
---
Changes from V1:
 - Replaced '_' by '-' in node names or compatible strings
 - Added missing @73 to b850v3-lvds-dp-bridge

 arch/arm/boot/dts/imx6q-b850v3.dts | 31 +++++++++++++++++++++++++++++++
 1 file changed, 31 insertions(+)

diff --git a/arch/arm/boot/dts/imx6q-b850v3.dts b/arch/arm/boot/dts/imx6q-b850v3.dts
index 88a70de..a57dd5b 100644
--- a/arch/arm/boot/dts/imx6q-b850v3.dts
+++ b/arch/arm/boot/dts/imx6q-b850v3.dts
@@ -77,6 +77,13 @@
 		fsl,data-mapping = "spwg";
 		fsl,data-width = <24>;
 		status = "okay";
+
+		port@4 {
+			reg = <4>;
+			lvds0_out: endpoint {
+				remote-endpoint = <&b850v3_lvds_dp_bridge_in>;
+			};
+		};
 	};
 };
 
@@ -147,3 +154,27 @@
 		reg = <0x4a>;
 	};
 };
+
+&mux2_i2c2 {
+	status = "okay";
+	clock-frequency = <100000>;
+
+	b850v3-lvds-dp-bridge@73 {
+		compatible = "ge,b850v3-lvds-dp";
+		#address-cells = <1>;
+		#size-cells = <0>;
+
+		reg = <0x73>;
+		interrupt-parent = <&gpio2>;
+		interrupts = <0 IRQ_TYPE_LEVEL_HIGH>;
+
+		edid-reg = <0x72>;
+
+		port@0 {
+			reg = <0>;
+			b850v3_lvds_dp_bridge_in: endpoint {
+				remote-endpoint = <&lvds0_out>;
+			};
+		};
+	};
+};
-- 
2.5.5

^ permalink raw reply related	[flat|nested] 64+ messages in thread

* Re: [PATCH V2 4/5] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge
  2016-06-09 16:25   ` [PATCH V2 4/5] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge Peter Senna Tschudin
@ 2016-06-10  7:39     ` Enric Balletbo Serra
  2016-06-10  9:44       ` Peter Senna Tschudin
  2016-06-10 14:13     ` Daniel Vetter
  2016-06-22  8:34     ` Archit Taneja
  2 siblings, 1 reply; 64+ messages in thread
From: Enric Balletbo Serra @ 2016-06-10  7:39 UTC (permalink / raw)
  To: Peter Senna Tschudin
  Cc: David Airlie, akpm, David Miller, devicetree, dri-devel,
	Enric Balletbo i Serra, Kumar Gala, Greg Kroah-Hartman,
	Heiko Stübner, Ian Campbell, jslaby, kernel,
	linux-arm-kernel, linux, linux-kernel, Guenter Roeck,
	Mark Rutland, martin.donnelly, martyn.welch,
	Mauro Carvalho Chehab, Pawel Moll, peter.senna, Philipp Zabel,
	Thierry Reding, rmk+kernel, Rob Herring, shawnguo, Takashi Iwai,
	Thierry Reding, Yakir Yang, Rob Herring, Fabio Estevam

Hi Peter,

Only a few comments ;)

2016-06-09 18:25 GMT+02:00 Peter Senna Tschudin <peter.senna@collabora.com>:
> Add a driver that create a drm_bridge and a drm_connector for the LVDS
> to DP++ display bridge of the GE B850v3.
>
> There are two physical bridges on the video signal pipeline: a
> STDP4028(LVDS to DP) and a STDP2690(DP to DP++).  The hardware and
> firmware made it complicated for this binding to comprise two device
> tree nodes, as the design goal is to configure both bridges based on
> the LVDS signal, which leave the driver powerless to control the video
> processing pipeline. The two bridges behaves as a single bridge, and
> the driver is only needed for telling the host about EDID / HPD, and
> for giving the host powers to ack interrupts. The video signal pipeline
> is as follows:
>
>   Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output
>
> Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com>
> Cc: Philipp Zabel <p.zabel@pengutronix.de>
> Cc: Rob Herring <robh@kernel.org>
> Cc: Fabio Estevam <fabio.estevam@nxp.com>
> CC: David Airlie <airlied@linux.ie>
> CC: Thierry Reding <treding@nvidia.com>
> CC: Thierry Reding <thierry.reding@gmail.com>
> Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com>
> ---
> Changes from V1:
>  - New commit message
>  - Removed 3 empty entry points
>  - Removed memory leak from ge_b850v3_lvds_dp_get_modes()
>  - Added a lock for mode setting
>  - Removed a few blank lines
>  - Changed the order at Makefile and Kconfig
>
>  MAINTAINERS                                |   8 +
>  drivers/gpu/drm/bridge/Kconfig             |  11 +
>  drivers/gpu/drm/bridge/Makefile            |   1 +
>  drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c | 392 +++++++++++++++++++++++++++++
>  4 files changed, 412 insertions(+)
>  create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 2ce5e91..2dd3d7f 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -5010,6 +5010,14 @@ W:       https://linuxtv.org
>  S:     Maintained
>  F:     drivers/media/radio/radio-gemtek*
>
> +GENERAL ELECTRIC B850V3 LVDS/DP++ BRIDGE
> +M:     Martin Donnelly <martin.donnelly@ge.com>
> +M:     Peter Senna Tschudin <peter.senna@collabora.com>
> +M:     Martyn Welch <martyn.welch@collabora.co.uk>
> +S:     Maintained
> +F:     drivers/gpu/drm/bridge/ge_b850v3_dp2.c
> +F:     Documentation/devicetree/bindings/ge/b850v3_dp2_bridge.txt
> +
>  GENERIC GPIO I2C DRIVER
>  M:     Haavard Skinnemoen <hskinnemoen@gmail.com>
>  S:     Supported
> diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
> index 8f7423f..93dae5bd 100644
> --- a/drivers/gpu/drm/bridge/Kconfig
> +++ b/drivers/gpu/drm/bridge/Kconfig
> @@ -32,6 +32,17 @@ config DRM_DW_HDMI_AHB_AUDIO
>           Designware HDMI block.  This is used in conjunction with
>           the i.MX6 HDMI driver.
>
> +config DRM_GE_B850V3_LVDS_DP
> +       tristate "GE B850v3 LVDS to DP++ display bridge"
> +       depends on OF
> +       select DRM_KMS_HELPER
> +       select DRM_PANEL
> +       ---help---
> +          This is a driver for the display bridge of
> +          GE B850v3 that convert dual channel LVDS
> +          to DP++. This is used with the i.MX6 imx-ldb
> +          driver.
> +
>  config DRM_NXP_PTN3460
>         tristate "NXP PTN3460 DP/LVDS bridge"
>         depends on OF
> diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
> index 96b13b3..47ea6c1 100644
> --- a/drivers/gpu/drm/bridge/Makefile
> +++ b/drivers/gpu/drm/bridge/Makefile
> @@ -3,6 +3,7 @@ ccflags-y := -Iinclude/drm
>  obj-$(CONFIG_DRM_ANALOGIX_ANX78XX) += analogix-anx78xx.o
>  obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o
>  obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o
> +obj-$(CONFIG_DRM_GE_B850V3_LVDS_DP) += ge_b850v3_lvds_dp.o
>  obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o
>  obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o
>  obj-$(CONFIG_DRM_ANALOGIX_DP) += analogix/
> diff --git a/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c
> new file mode 100644
> index 0000000..c73cd77
> --- /dev/null
> +++ b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c
> @@ -0,0 +1,392 @@
> +/*
> + * Driver for GE B850v3 DP display bridge
> +
> + * Copyright (c) 2016, Collabora Ltd.
> + * Copyright (c) 2016, General Electric Company
> +
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms and conditions of the GNU General Public License,
> + * version 2, as published by the Free Software Foundation.
> +
> + * This program is distributed in the hope it will be useful, but WITHOUT
> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
> + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
> + * more details.
> +
> + * You should have received a copy of the GNU General Public License
> + * along with this program.  If not, see <http://www.gnu.org/licenses/>.
> +
> + * This driver creates a drm_bridge and a drm_connector for the LVDS to DP++
> + * display bridge of the GE B850v3. There are two physical bridges on the video
> + * signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). However
> + * the physical bridges are automatically configured by the input video signal,
> + * and the driver has no access to the video processing pipeline. The driver is
> + * only needed to read EDID from the STDP2690 and to handle HPD events from the
> + * STDP4028. The driver communicates with both bridges over i2c. The video
> + * signal pipeline is as follows:
> + *
> + *   Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output
> +
> + *
> + */
> +#include <linux/gpio.h>
> +#include <linux/i2c.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include "drm_crtc.h"
> +#include "drm_crtc_helper.h"
> +#include "drm_edid.h"
> +#include "drmP.h"
> +
> +#define EDID_EXT_BLOCK_CNT 0x7E
> +
> +#define STDP4028_IRQ_OUT_CONF_REG 0x02
> +#define STDP4028_DPTX_IRQ_EN_REG 0x3C
> +#define STDP4028_DPTX_IRQ_STS_REG 0x3D
> +#define STDP4028_DPTX_STS_REG 0x3E
> +
> +#define STDP4028_DPTX_DP_IRQ_EN 0x1000
> +
> +#define STDP4028_DPTX_HOTPLUG_IRQ_EN 0x0400
> +#define STDP4028_DPTX_LINK_CH_IRQ_EN 0x2000
> +#define STDP4028_DPTX_IRQ_CONFIG \
> +               (STDP4028_DPTX_LINK_CH_IRQ_EN | STDP4028_DPTX_HOTPLUG_IRQ_EN)
> +
> +#define STDP4028_DPTX_HOTPLUG_STS 0x0200
> +#define STDP4028_DPTX_LINK_STS 0x1000
> +#define STDP4028_CON_STATE_CONNECTED \
> +               (STDP4028_DPTX_HOTPLUG_STS | STDP4028_DPTX_LINK_STS)
> +
> +#define STDP4028_DPTX_HOTPLUG_CH_STS 0x0400
> +#define STDP4028_DPTX_LINK_CH_STS 0x2000
> +#define STDP4028_DPTX_IRQ_CLEAR \
> +               (STDP4028_DPTX_LINK_CH_STS | STDP4028_DPTX_HOTPLUG_CH_STS)
> +
> +struct ge_b850v3_lvds_dp {
> +       struct drm_connector connector;
> +       struct drm_bridge bridge;
> +       struct i2c_client *ge_b850v3_lvds_dp_i2c;
> +       struct i2c_client *edid_i2c;
> +       struct edid *edid;
> +       struct mutex lock;
> +};
> +
> +static inline struct ge_b850v3_lvds_dp *
> +               bridge_to_ge_b850v3_lvds_dp(struct drm_bridge *bridge)
> +{
> +       return container_of(bridge, struct ge_b850v3_lvds_dp, bridge);
> +}
> +
> +static inline struct ge_b850v3_lvds_dp *
> +               connector_to_ge_b850v3_lvds_dp(struct drm_connector *connector)
> +{
> +       return container_of(connector, struct ge_b850v3_lvds_dp, connector);
> +}
> +
> +static void ge_b850v3_lvds_dp_enable(struct drm_bridge *bridge)
> +{
> +}
> +

You can remove this function, see
http://lxr.free-electrons.com/source/drivers/gpu/drm/drm_bridge.c#L277

> +static void ge_b850v3_lvds_dp_disable(struct drm_bridge *bridge)
> +{
> +}
> +

And this one, see
http://lxr.free-electrons.com/source/drivers/gpu/drm/drm_bridge.c#L182

> +u8 *stdp2690_get_edid(struct i2c_client *client)
> +{
> +       struct i2c_adapter *adapter = client->adapter;
> +       unsigned char start = 0x00;
> +       unsigned int total_size;
> +       u8 *block = kmalloc(EDID_LENGTH, GFP_KERNEL);
> +
> +       struct i2c_msg msgs[] = {
> +               {
> +                       .addr   = client->addr,
> +                       .flags  = 0,
> +                       .len    = 1,
> +                       .buf    = &start,
> +               }, {
> +                       .addr   = client->addr,
> +                       .flags  = I2C_M_RD,
> +                       .len    = EDID_LENGTH,
> +                       .buf    = block,
> +               }
> +       };
> +
> +       if (!block)
> +               return NULL;
> +
> +       if (i2c_transfer(adapter, msgs, 2) != 2) {
> +               DRM_ERROR("Unable to read EDID.\n");
> +               goto err;
> +       }
> +
> +       if (!drm_edid_block_valid(block, 0, false, NULL)) {
> +               DRM_ERROR("Invalid EDID block\n");
> +               goto err;
> +       }
> +
> +       total_size = (block[EDID_EXT_BLOCK_CNT] + 1) * EDID_LENGTH;
> +       if (total_size > EDID_LENGTH) {
> +               kfree(block);
> +               block = kmalloc(total_size, GFP_KERNEL);
> +               if (!block)
> +                       return NULL;
> +
> +               /* Yes, read the entire buffer, and do not skip the first
> +                * EDID_LENGTH bytes.
> +                */
> +               start = 0x00;
> +               msgs[1].len = total_size;
> +               msgs[1].buf = block;
> +
> +               if (i2c_transfer(adapter, msgs, 2) != 2) {
> +                       DRM_ERROR("Unable to read EDID extension blocks.\n");
> +                       goto err;
> +               }
> +       }
> +
> +       return block;
> +
> +err:
> +       kfree(block);
> +       return NULL;
> +}
> +
> +static int ge_b850v3_lvds_dp_get_modes(struct drm_connector *connector)
> +{
> +       struct ge_b850v3_lvds_dp *ptn_bridge;
> +       struct i2c_client *client;
> +       int num_modes = 0;
> +
> +       ptn_bridge = connector_to_ge_b850v3_lvds_dp(connector);
> +       client = ptn_bridge->edid_i2c;
> +
> +       mutex_lock(&ptn_bridge->lock);
> +
> +       kfree(ptn_bridge->edid);
> +       ptn_bridge->edid = (struct edid *) stdp2690_get_edid(client);
> +
> +       if (ptn_bridge->edid) {
> +               drm_mode_connector_update_edid_property(connector,
> +                               ptn_bridge->edid);
> +               num_modes = drm_add_edid_modes(connector, ptn_bridge->edid);
> +       }
> +
> +       mutex_unlock(&ptn_bridge->lock);
> +
> +       return num_modes;
> +}
> +
> +static struct drm_encoder
> +*ge_b850v3_lvds_dp_best_encoder(struct drm_connector *connector)
> +{
> +       struct ge_b850v3_lvds_dp *ptn_bridge =
> +               connector_to_ge_b850v3_lvds_dp(connector);
> +
> +       return ptn_bridge->bridge.encoder;
> +}
> +
> +static const struct
> +drm_connector_helper_funcs ge_b850v3_lvds_dp_connector_helper_funcs = {
> +       .get_modes = ge_b850v3_lvds_dp_get_modes,
> +       .best_encoder = ge_b850v3_lvds_dp_best_encoder,
> +};
> +
> +static enum drm_connector_status ge_b850v3_lvds_dp_detect(
> +               struct drm_connector *connector, bool force)
> +{
> +       struct ge_b850v3_lvds_dp *ptn_bridge =
> +                       connector_to_ge_b850v3_lvds_dp(connector);
> +       struct i2c_client *ge_b850v3_lvds_dp_i2c =
> +                       ptn_bridge->ge_b850v3_lvds_dp_i2c;
> +       s32 link_state;
> +
> +       link_state = i2c_smbus_read_word_data(ge_b850v3_lvds_dp_i2c,
> +                       STDP4028_DPTX_STS_REG);
> +
> +       if (link_state == STDP4028_CON_STATE_CONNECTED)
> +               return connector_status_connected;
> +
> +       if (link_state == 0)
> +               return connector_status_disconnected;
> +
> +       return connector_status_unknown;
> +}
> +
> +static void ge_b850v3_lvds_dp_connector_destroy(struct drm_connector *connector)
> +{
> +       drm_connector_cleanup(connector);
> +}
> +
> +static const struct drm_connector_funcs ge_b850v3_lvds_dp_connector_funcs = {
> +       .dpms = drm_helper_connector_dpms,
> +       .fill_modes = drm_helper_probe_single_connector_modes,
> +       .detect = ge_b850v3_lvds_dp_detect,
> +       .destroy = ge_b850v3_lvds_dp_connector_destroy,
> +};
> +
> +static irqreturn_t ge_b850v3_lvds_dp_irq_handler(int irq, void *dev_id)
> +{
> +       struct ge_b850v3_lvds_dp *ptn_bridge = dev_id;
> +       struct i2c_client *ge_b850v3_lvds_dp_i2c
> +                       = ptn_bridge->ge_b850v3_lvds_dp_i2c;
> +
> +       mutex_lock(&ptn_bridge->lock);
> +
> +       i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c,
> +                       STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR);
> +
> +       mutex_unlock(&ptn_bridge->lock);
> +
> +       if (ptn_bridge->connector.dev)
> +               drm_kms_helper_hotplug_event(ptn_bridge->connector.dev);
> +
> +       return IRQ_HANDLED;
> +}
> +
> +static int ge_b850v3_lvds_dp_attach(struct drm_bridge *bridge)
> +{
> +       struct ge_b850v3_lvds_dp *ptn_bridge
> +                       = bridge_to_ge_b850v3_lvds_dp(bridge);
> +       struct drm_connector *connector = &ptn_bridge->connector;
> +       struct i2c_client *ge_b850v3_lvds_dp_i2c
> +                       = ptn_bridge->ge_b850v3_lvds_dp_i2c;
> +       int ret;
> +
> +       if (!bridge->encoder) {
> +               DRM_ERROR("Parent encoder object not found");
> +               return -ENODEV;
> +       }
> +
> +       connector->polled = DRM_CONNECTOR_POLL_HPD;
> +
> +       drm_connector_helper_add(connector,
> +                       &ge_b850v3_lvds_dp_connector_helper_funcs);
> +
> +       ret = drm_connector_init(bridge->dev, connector,
> +                       &ge_b850v3_lvds_dp_connector_funcs,
> +                       DRM_MODE_CONNECTOR_DisplayPort);
> +       if (ret) {
> +               DRM_ERROR("Failed to initialize connector with drm\n");
> +               return ret;
> +       }
> +
> +       ret = drm_mode_connector_attach_encoder(connector, bridge->encoder);
> +       if (ret)
> +               return ret;
> +
> +       drm_bridge_enable(bridge);
> +       if (ge_b850v3_lvds_dp_i2c->irq) {
> +               drm_helper_hpd_irq_event(connector->dev);
> +
> +               ret = devm_request_threaded_irq(&ge_b850v3_lvds_dp_i2c->dev,
> +                               ge_b850v3_lvds_dp_i2c->irq, NULL,
> +                               ge_b850v3_lvds_dp_irq_handler,
> +                               IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
> +                               "ge-b850v3-lvds-dp", ptn_bridge);
> +               if (ret)
> +                       return ret;
> +       }
> +
> +       return 0;
> +}
> +
> +static const struct drm_bridge_funcs ge_b850v3_lvds_dp_funcs = {
> +       .enable = ge_b850v3_lvds_dp_enable,
> +       .disable = ge_b850v3_lvds_dp_disable,

Remove the above empty callbacks.

> +       .attach = ge_b850v3_lvds_dp_attach,
> +};
> +
> +static int ge_b850v3_lvds_dp_probe(struct i2c_client *ge_b850v3_lvds_dp_i2c,
> +                               const struct i2c_device_id *id)
> +{
> +       struct device *dev = &ge_b850v3_lvds_dp_i2c->dev;
> +       struct ge_b850v3_lvds_dp *ptn_bridge;
> +       int ret;
> +       u32 edid_i2c_reg;
> +
> +       ptn_bridge = devm_kzalloc(dev, sizeof(*ptn_bridge), GFP_KERNEL);
> +       if (!ptn_bridge)
> +               return -ENOMEM;
> +
> +       mutex_init(&ptn_bridge->lock);
> +
> +       ptn_bridge->ge_b850v3_lvds_dp_i2c = ge_b850v3_lvds_dp_i2c;
> +       ptn_bridge->bridge.driver_private = ptn_bridge;
> +       i2c_set_clientdata(ge_b850v3_lvds_dp_i2c, ptn_bridge);
> +
> +       ret = of_property_read_u32(dev->of_node, "edid-reg", &edid_i2c_reg);
> +       if (ret) {
> +               dev_err(dev, "edid-reg not specified, aborting...\n");
> +               return -ENODEV;
> +       }
> +
> +       ptn_bridge->edid_i2c = devm_kzalloc(dev,
> +                       sizeof(struct i2c_client), GFP_KERNEL);
> +
> +       if (!ptn_bridge->edid_i2c)
> +               return -ENOMEM;
> +
> +       memcpy(ptn_bridge->edid_i2c, ge_b850v3_lvds_dp_i2c,
> +                       sizeof(struct i2c_client));
> +
> +       ptn_bridge->edid_i2c->addr = (unsigned short) edid_i2c_reg;
> +
> +       /* Configures the bridge to re-enable interrupts after each ack */
> +       i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c,
> +                       STDP4028_IRQ_OUT_CONF_REG, STDP4028_DPTX_DP_IRQ_EN);
> +       i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c,
> +                       STDP4028_DPTX_IRQ_EN_REG, STDP4028_DPTX_IRQ_CONFIG);
> +
> +       /* Clear pending interrupts since power up. */
> +       i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c,
> +                       STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR);
> +
> +       ptn_bridge->bridge.funcs = &ge_b850v3_lvds_dp_funcs;
> +       ptn_bridge->bridge.of_node = dev->of_node;
> +       ret = drm_bridge_add(&ptn_bridge->bridge);
> +       if (ret) {
> +               DRM_ERROR("Failed to add bridge\n");
> +               return ret;
> +       }
> +
> +       return 0;
> +}
> +
> +static int ge_b850v3_lvds_dp_remove(struct i2c_client *ge_b850v3_lvds_dp_i2c)
> +{
> +       struct ge_b850v3_lvds_dp *ptn_bridge =
> +               i2c_get_clientdata(ge_b850v3_lvds_dp_i2c);
> +
> +       drm_bridge_remove(&ptn_bridge->bridge);

Guess you need to free ptn_bridge->edid here.

> +
> +       return 0;
> +}
> +
> +static const struct i2c_device_id ge_b850v3_lvds_dp_i2c_table[] = {
> +       {"b850v3-lvds-dp", 0},
> +       {},
> +};
> +MODULE_DEVICE_TABLE(i2c, ge_b850v3_lvds_dp_i2c_table);
> +
> +static const struct of_device_id ge_b850v3_lvds_dp_match[] = {
> +       { .compatible = "ge,b850v3-lvds-dp" },
> +       {},
> +};
> +MODULE_DEVICE_TABLE(of, ge_b850v3_lvds_dp_match);
> +
> +static struct i2c_driver ge_b850v3_lvds_dp_driver = {
> +       .id_table       = ge_b850v3_lvds_dp_i2c_table,
> +       .probe          = ge_b850v3_lvds_dp_probe,
> +       .remove         = ge_b850v3_lvds_dp_remove,
> +       .driver         = {
> +               .name           = "ge,b850v3-lvds-dp",
> +               .of_match_table = ge_b850v3_lvds_dp_match,
> +       },
> +};
> +module_i2c_driver(ge_b850v3_lvds_dp_driver);
> +
> +MODULE_AUTHOR("Peter Senna Tschudin <peter.senna@collabora.com>");
> +MODULE_AUTHOR("Martyn Welch <martyn.welch@collabora.co.uk>");
> +MODULE_DESCRIPTION("GE LVDS to DP++ bridge)");
> +MODULE_LICENSE("GPL v2");
> --
> 2.5.5
>

^ permalink raw reply	[flat|nested] 64+ messages in thread

* Re: [PATCH V2 4/5] drm/bridge: Add driver for GE B850v3  LVDS/DP++ Bridge
  2016-06-10  7:39     ` Enric Balletbo Serra
@ 2016-06-10  9:44       ` Peter Senna Tschudin
  0 siblings, 0 replies; 64+ messages in thread
From: Peter Senna Tschudin @ 2016-06-10  9:44 UTC (permalink / raw)
  To: Enric Balletbo Serra
  Cc: linux-kernel, Kumar Gala, Philipp Zabel, Rob Herring,
	martin.donnelly, Ian Campbell, Rob Herring, akpm,
	linux-arm-kernel, dri-devel, Pawel Moll, Thierry Reding,
	Guenter Roeck, Greg Kroah-Hartman, Thierry Reding,
	Peter Senna Tschudin, Fabio Estevam, jslaby, shawnguo,
	David Miller, martyn.welch, linux, Enric Balletbo i Serra,
	peter.senna, Yakir Yang, Mark Rutland, Heiko Stübner,
	rmk+kernel, David Airlie, kernel, Takashi Iwai, devicetree,
	Mauro Carvalho Chehab

Hi Enric,
 
On Friday, June 10, 2016 09:39 CEST, Enric Balletbo Serra <eballetbo@gmail.com> wrote: 
 
> Hi Peter,
> 
> Only a few comments ;)

Thanks a lot for the review!

> 
> 2016-06-09 18:25 GMT+02:00 Peter Senna Tschudin <peter.senna@collabora.com>:
> > Add a driver that create a drm_bridge and a drm_connector for the LVDS
> > to DP++ display bridge of the GE B850v3.
> >
> > There are two physical bridges on the video signal pipeline: a
> > STDP4028(LVDS to DP) and a STDP2690(DP to DP++).  The hardware and

> > firmware made it complicated for this binding to comprise two device
> > tree nodes, as the design goal is to configure both bridges based on
> > the LVDS signal, which leave the driver powerless to control the video
> > processing pipeline. The two bridges behaves as a single bridge, and
> > the driver is only needed for telling the host about EDID / HPD, and
> > for giving the host powers to ack interrupts. The video signal pipeline
> > is as follows:
> >
> >   Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output
> >
> > Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com>
> > Cc: Philipp Zabel <p.zabel@pengutronix.de>
> > Cc: Rob Herring <robh@kernel.org>
> > Cc: Fabio Estevam <fabio.estevam@nxp.com>
> > CC: David Airlie <airlied@linux.ie>
> > CC: Thierry Reding <treding@nvidia.com>
> > CC: Thierry Reding <thierry.reding@gmail.com>
> > Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com>
> > ---
> > Changes from V1:
> >  - New commit message
> >  - Removed 3 empty entry points
> >  - Removed memory leak from ge_b850v3_lvds_dp_get_modes()
> >  - Added a lock for mode setting
> >  - Removed a few blank lines
> >  - Changed the order at Makefile and Kconfig
> >
> >  MAINTAINERS                                |   8 +
> >  drivers/gpu/drm/bridge/Kconfig             |  11 +
> >  drivers/gpu/drm/bridge/Makefile            |   1 +
> >  drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c | 392 +++++++++++++++++++++++++++++
> >  4 files changed, 412 insertions(+)
> >  create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c
> >
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index 2ce5e91..2dd3d7f 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -5010,6 +5010,14 @@ W:       https://linuxtv.org
> >  S:     Maintained
> >  F:     drivers/media/radio/radio-gemtek*
> >
> > +GENERAL ELECTRIC B850V3 LVDS/DP++ BRIDGE
> > +M:     Martin Donnelly <martin.donnelly@ge.com>
> > +M:     Peter Senna Tschudin <peter.senna@collabora.com>
> > +M:     Martyn Welch <martyn.welch@collabora.co.uk>
> > +S:     Maintained
> > +F:     drivers/gpu/drm/bridge/ge_b850v3_dp2.c
> > +F:     Documentation/devicetree/bindings/ge/b850v3_dp2_bridge.txt
> > +
> >  GENERIC GPIO I2C DRIVER
> >  M:     Haavard Skinnemoen <hskinnemoen@gmail.com>
> >  S:     Supported
> > diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
> > index 8f7423f..93dae5bd 100644
> > --- a/drivers/gpu/drm/bridge/Kconfig
> > +++ b/drivers/gpu/drm/bridge/Kconfig
> > @@ -32,6 +32,17 @@ config DRM_DW_HDMI_AHB_AUDIO
> >           Designware HDMI block.  This is used in conjunction with

> >           the i.MX6 HDMI driver.
> >
> > +config DRM_GE_B850V3_LVDS_DP
> > +       tristate "GE B850v3 LVDS to DP++ display bridge"
> > +       depends on OF
> > +       select DRM_KMS_HELPER
> > +       select DRM_PANEL
> > +       ---help---
> > +          This is a driver for the display bridge of
> > +          GE B850v3 that convert dual channel LVDS
> > +          to DP++. This is used with the i.MX6 imx-ldb
> > +          driver.
> > +
> >  config DRM_NXP_PTN3460
> >         tristate "NXP PTN3460 DP/LVDS bridge"
> >         depends on OF
> > diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
> > index 96b13b3..47ea6c1 100644
> > --- a/drivers/gpu/drm/bridge/Makefile
> > +++ b/drivers/gpu/drm/bridge/Makefile
> > @@ -3,6 +3,7 @@ ccflags-y := -Iinclude/drm
> >  obj-$(CONFIG_DRM_ANALOGIX_ANX78XX) += analogix-anx78xx.o
> >  obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o
> >  obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o
> > +obj-$(CONFIG_DRM_GE_B850V3_LVDS_DP) += ge_b850v3_lvds_dp.o
> >  obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o
> >  obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o
> >  obj-$(CONFIG_DRM_ANALOGIX_DP) += analogix/
> > diff --git a/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c
> > new file mode 100644
> > index 0000000..c73cd77
> > --- /dev/null
> > +++ b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c
> > @@ -0,0 +1,392 @@
> > +/*
> > + * Driver for GE B850v3 DP display bridge
> > +
> > + * Copyright (c) 2016, Collabora Ltd.
> > + * Copyright (c) 2016, General Electric Company
> > +
> > + * This program is free software; you can redistribute it and/or modify it
> > + * under the terms and conditions of the GNU General Public License,
> > + * version 2, as published by the Free Software Foundation.
> > +
> > + * This program is distributed in the hope it will be useful, but WITHOUT
> > + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
> > + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
> > + * more details.
> > +
> > + * You should have received a copy of the GNU General Public License
> > + * along with this program.  If not, see <http://www.gnu.org/licenses/>.
> > +
> > + * This driver creates a drm_bridge and a drm_connector for the LVDS to DP++
> > + * display bridge of the GE B850v3. There are two physical bridges on the video
> > + * signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). However
> > + * the physical bridges are automatically configured by the input video signal,
> > + * and the driver has no access to the video processing pipeline. The driver is
> > + * only needed to read EDID from the STDP2690 and to handle HPD events from the
> > + * STDP4028. The driver communicates with both bridges over i2c. The video
> > + * signal pipeline is as follows:
> > + *
> > + *   Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output
> > +
> > + *
> > + */
> > +#include <linux/gpio.h>
> > +#include <linux/i2c.h>
> > +#include <linux/module.h>
> > +#include <linux/of.h>
> > +#include "drm_crtc.h"
> > +#include "drm_crtc_helper.h"
> > +#include "drm_edid.h"
> > +#include "drmP.h"
> > +
> > +#define EDID_EXT_BLOCK_CNT 0x7E
> > +
> > +#define STDP4028_IRQ_OUT_CONF_REG 0x02
> > +#define STDP4028_DPTX_IRQ_EN_REG 0x3C
> > +#define STDP4028_DPTX_IRQ_STS_REG 0x3D
> > +#define STDP4028_DPTX_STS_REG 0x3E
> > +
> > +#define STDP4028_DPTX_DP_IRQ_EN 0x1000
> > +
> > +#define STDP4028_DPTX_HOTPLUG_IRQ_EN 0x0400
> > +#define STDP4028_DPTX_LINK_CH_IRQ_EN 0x2000
> > +#define STDP4028_DPTX_IRQ_CONFIG \
> > +               (STDP4028_DPTX_LINK_CH_IRQ_EN | STDP4028_DPTX_HOTPLUG_IRQ_EN)
> > +
> > +#define STDP4028_DPTX_HOTPLUG_STS 0x0200
> > +#define STDP4028_DPTX_LINK_STS 0x1000
> > +#define STDP4028_CON_STATE_CONNECTED \
> > +               (STDP4028_DPTX_HOTPLUG_STS | STDP4028_DPTX_LINK_STS)
> > +
> > +#define STDP4028_DPTX_HOTPLUG_CH_STS 0x0400
> > +#define STDP4028_DPTX_LINK_CH_STS 0x2000
> > +#define STDP4028_DPTX_IRQ_CLEAR \
> > +               (STDP4028_DPTX_LINK_CH_STS | STDP4028_DPTX_HOTPLUG_CH_STS)
> > +
> > +struct ge_b850v3_lvds_dp {
> > +       struct drm_connector connector;
> > +       struct drm_bridge bridge;
> > +       struct i2c_client *ge_b850v3_lvds_dp_i2c;
> > +       struct i2c_client *edid_i2c;
> > +       struct edid *edid;
> > +       struct mutex lock;
> > +};
> > +
> > +static inline struct ge_b850v3_lvds_dp *
> > +               bridge_to_ge_b850v3_lvds_dp(struct drm_bridge *bridge)
> > +{
> > +       return container_of(bridge, struct ge_b850v3_lvds_dp, bridge);
> > +}
> > +
> > +static inline struct ge_b850v3_lvds_dp *
> > +               connector_to_ge_b850v3_lvds_dp(struct drm_connector *connector)
> > +{
> > +       return container_of(connector, struct ge_b850v3_lvds_dp, connector);
> > +}
> > +
> > +static void ge_b850v3_lvds_dp_enable(struct drm_bridge *bridge)
> > +{
> > +}
> > +
> 
> You can remove this function, see
> http://lxr.free-electrons.com/source/drivers/gpu/drm/drm_bridge.c#L277

You are right, I removed the empty callbacks, but  they returned due me doing something wrong when rebasing. V3 will be out soon.

> 
> > +static void ge_b850v3_lvds_dp_disable(struct drm_bridge *bridge)
> > +{
> > +}
> > +
> 
> And this one, see
> http://lxr.free-electrons.com/source/drivers/gpu/drm/drm_bridge.c#L182

Same. This should not be here.

> 
> > +u8 *stdp2690_get_edid(struct i2c_client *client)
> > +{
> > +       struct i2c_adapter *adapter = client->adapter;
> > +       unsigned char start = 0x00;
> > +       unsigned int total_size;
> > +       u8 *block = kmalloc(EDID_LENGTH, GFP_KERNEL);
> > +
> > +       struct i2c_msg msgs[] = {
> > +               {
> > +                       .addr   = client->addr,
> > +                       .flags  = 0,
> > +                       .len    = 1,
> > +                       .buf    = &start,
> > +               }, {
> > +                       .addr   = client->addr,
> > +                       .flags  = I2C_M_RD,
> > +                       .len    = EDID_LENGTH,
> > +                       .buf    = block,
> > +               }
> > +       };
> > +
> > +       if (!block)
> > +               return NULL;
> > +
> > +       if (i2c_transfer(adapter, msgs, 2) != 2) {
> > +               DRM_ERROR("Unable to read EDID.\n");
> > +               goto err;
> > +       }
> > +
> > +       if (!drm_edid_block_valid(block, 0, false, NULL)) {
> > +               DRM_ERROR("Invalid EDID block\n");
> > +               goto err;
> > +       }
> > +
> > +       total_size = (block[EDID_EXT_BLOCK_CNT] + 1) * EDID_LENGTH;
> > +       if (total_size > EDID_LENGTH) {
> > +               kfree(block);
> > +               block = kmalloc(total_size, GFP_KERNEL);
> > +               if (!block)
> > +                       return NULL;
> > +
> > +               /* Yes, read the entire buffer, and do not skip the first
> > +                * EDID_LENGTH bytes.
> > +                */
> > +               start = 0x00;
> > +               msgs[1].len = total_size;
> > +               msgs[1].buf = block;
> > +
> > +               if (i2c_transfer(adapter, msgs, 2) != 2) {
> > +                       DRM_ERROR("Unable to read EDID extension blocks.\n");
> > +                       goto err;
> > +               }
> > +       }
> > +
> > +       return block;
> > +
> > +err:
> > +       kfree(block);
> > +       return NULL;
> > +}
> > +
> > +static int ge_b850v3_lvds_dp_get_modes(struct drm_connector *connector)
> > +{
> > +       struct ge_b850v3_lvds_dp *ptn_bridge;
> > +       struct i2c_client *client;
> > +       int num_modes = 0;
> > +
> > +       ptn_bridge = connector_to_ge_b850v3_lvds_dp(connector);
> > +       client = ptn_bridge->edid_i2c;
> > +
> > +       mutex_lock(&ptn_bridge->lock);
> > +
> > +       kfree(ptn_bridge->edid);
> > +       ptn_bridge->edid = (struct edid *) stdp2690_get_edid(client);
> > +
> > +       if (ptn_bridge->edid) {
> > +               drm_mode_connector_update_edid_property(connector,
> > +                               ptn_bridge->edid);
> > +               num_modes = drm_add_edid_modes(connector, ptn_bridge->edid);
> > +       }
> > +
> > +       mutex_unlock(&ptn_bridge->lock);
> > +
> > +       return num_modes;
> > +}
> > +
> > +static struct drm_encoder
> > +*ge_b850v3_lvds_dp_best_encoder(struct drm_connector *connector)
> > +{
> > +       struct ge_b850v3_lvds_dp *ptn_bridge =
> > +               connector_to_ge_b850v3_lvds_dp(connector);
> > +
> > +       return ptn_bridge->bridge.encoder;
> > +}
> > +
> > +static const struct
> > +drm_connector_helper_funcs ge_b850v3_lvds_dp_connector_helper_funcs = {
> > +       .get_modes = ge_b850v3_lvds_dp_get_modes,
> > +       .best_encoder = ge_b850v3_lvds_dp_best_encoder,
> > +};
> > +
> > +static enum drm_connector_status ge_b850v3_lvds_dp_detect(
> > +               struct drm_connector *connector, bool force)
> > +{
> > +       struct ge_b850v3_lvds_dp *ptn_bridge =
> > +                       connector_to_ge_b850v3_lvds_dp(connector);
> > +       struct i2c_client *ge_b850v3_lvds_dp_i2c =
> > +                       ptn_bridge->ge_b850v3_lvds_dp_i2c;
> > +       s32 link_state;
> > +
> > +       link_state = i2c_smbus_read_word_data(ge_b850v3_lvds_dp_i2c,
> > +                       STDP4028_DPTX_STS_REG);
> > +
> > +       if (link_state == STDP4028_CON_STATE_CONNECTED)
> > +               return connector_status_connected;
> > +
> > +       if (link_state == 0)
> > +               return connector_status_disconnected;
> > +
> > +       return connector_status_unknown;
> > +}
> > +
> > +static void ge_b850v3_lvds_dp_connector_destroy(struct drm_connector *connector)
> > +{
> > +       drm_connector_cleanup(connector);
> > +}
> > +
> > +static const struct drm_connector_funcs ge_b850v3_lvds_dp_connector_funcs = {
> > +       .dpms = drm_helper_connector_dpms,
> > +       .fill_modes = drm_helper_probe_single_connector_modes,
> > +       .detect = ge_b850v3_lvds_dp_detect,
> > +       .destroy = ge_b850v3_lvds_dp_connector_destroy,
> > +};
> > +
> > +static irqreturn_t ge_b850v3_lvds_dp_irq_handler(int irq, void *dev_id)
> > +{
> > +       struct ge_b850v3_lvds_dp *ptn_bridge = dev_id;

> > +       struct i2c_client *ge_b850v3_lvds_dp_i2c
> > +                       = ptn_bridge->ge_b850v3_lvds_dp_i2c;
> > +
> > +       mutex_lock(&ptn_bridge->lock);
> > +
> > +       i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c,
> > +                       STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR);
> > +
> > +       mutex_unlock(&ptn_bridge->lock);
> > +
> > +       if (ptn_bridge->connector.dev)
> > +               drm_kms_helper_hotplug_event(ptn_bridge->connector.dev);
> > +
> > +       return IRQ_HANDLED;
> > +}
> > +
> > +static int ge_b850v3_lvds_dp_attach(struct drm_bridge *bridge)
> > +{
> > +       struct ge_b850v3_lvds_dp *ptn_bridge
> > +                       = bridge_to_ge_b850v3_lvds_dp(bridge);
> > +       struct drm_connector *connector = &ptn_bridge->connector;
> > +       struct i2c_client *ge_b850v3_lvds_dp_i2c
> > +                       = ptn_bridge->ge_b850v3_lvds_dp_i2c;
> > +       int ret;
> > +
> > +       if (!bridge->encoder) {
> > +               DRM_ERROR("Parent encoder object not found");
> > +               return -ENODEV;
> > +       }
> > +
> > +       connector->polled = DRM_CONNECTOR_POLL_HPD;
> > +
> > +       drm_connector_helper_add(connector,
> > +                       &ge_b850v3_lvds_dp_connector_helper_funcs);
> > +
> > +       ret = drm_connector_init(bridge->dev, connector,
> > +                       &ge_b850v3_lvds_dp_connector_funcs,
> > +                       DRM_MODE_CONNECTOR_DisplayPort);
> > +       if (ret) {
> > +               DRM_ERROR("Failed to initialize connector with drm\n");
> > +               return ret;
> > +       }
> > +
> > +       ret = drm_mode_connector_attach_encoder(connector, bridge->encoder);
> > +       if (ret)
> > +               return ret;
> > +
> > +       drm_bridge_enable(bridge);
> > +       if (ge_b850v3_lvds_dp_i2c->irq) {
> > +               drm_helper_hpd_irq_event(connector->dev);
> > +
> > +               ret = devm_request_threaded_irq(&ge_b850v3_lvds_dp_i2c->dev,
> > +                               ge_b850v3_lvds_dp_i2c->irq, NULL,
> > +                               ge_b850v3_lvds_dp_irq_handler,
> > +                               IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
> > +                               "ge-b850v3-lvds-dp", ptn_bridge);
> > +               if (ret)
> > +                       return ret;
> > +       }
> > +
> > +       return 0;
> > +}
> > +
> > +static const struct drm_bridge_funcs ge_b850v3_lvds_dp_funcs = {
> > +       .enable = ge_b850v3_lvds_dp_enable,
> > +       .disable = ge_b850v3_lvds_dp_disable,
> 
> Remove the above empty callbacks.

Here too.

> 
> > +       .attach = ge_b850v3_lvds_dp_attach,
> > +};
> > +
> > +static int ge_b850v3_lvds_dp_probe(struct i2c_client *ge_b850v3_lvds_dp_i2c,
> > +                               const struct i2c_device_id *id)
> > +{
> > +       struct device *dev = &ge_b850v3_lvds_dp_i2c->dev;
> > +       struct ge_b850v3_lvds_dp *ptn_bridge;
> > +       int ret;
> > +       u32 edid_i2c_reg;
> > +
> > +       ptn_bridge = devm_kzalloc(dev, sizeof(*ptn_bridge), GFP_KERNEL);
> > +       if (!ptn_bridge)
> > +               return -ENOMEM;
> > +
> > +       mutex_init(&ptn_bridge->lock);
> > +
> > +       ptn_bridge->ge_b850v3_lvds_dp_i2c = ge_b850v3_lvds_dp_i2c;
> > +       ptn_bridge->bridge.driver_private = ptn_bridge;
> > +       i2c_set_clientdata(ge_b850v3_lvds_dp_i2c, ptn_bridge);
> > +
> > +       ret = of_property_read_u32(dev->of_node, "edid-reg", &edid_i2c_reg);
> > +       if (ret) {
> > +               dev_err(dev, "edid-reg not specified, aborting...\n");
> > +               return -ENODEV;
> > +       }
> > +
> > +       ptn_bridge->edid_i2c = devm_kzalloc(dev,
> > +                       sizeof(struct i2c_client), GFP_KERNEL);
> > +
> > +       if (!ptn_bridge->edid_i2c)
> > +               return -ENOMEM;
> > +
> > +       memcpy(ptn_bridge->edid_i2c, ge_b850v3_lvds_dp_i2c,
> > +                       sizeof(struct i2c_client));
> > +
> > +       ptn_bridge->edid_i2c->addr = (unsigned short) edid_i2c_reg;
> > +
> > +       /* Configures the bridge to re-enable interrupts after each ack */
> > +       i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c,
> > +                       STDP4028_IRQ_OUT_CONF_REG, STDP4028_DPTX_DP_IRQ_EN);
> > +       i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c,
> > +                       STDP4028_DPTX_IRQ_EN_REG, STDP4028_DPTX_IRQ_CONFIG);
> > +
> > +       /* Clear pending interrupts since power up. */
> > +       i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c,
> > +                       STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR);
> > +
> > +       ptn_bridge->bridge.funcs = &ge_b850v3_lvds_dp_funcs;
> > +       ptn_bridge->bridge.of_node = dev->of_node;
> > +       ret = drm_bridge_add(&ptn_bridge->bridge);
> > +       if (ret) {
> > +               DRM_ERROR("Failed to add bridge\n");
> > +               return ret;
> > +       }
> > +
> > +       return 0;
> > +}
> > +
> > +static int ge_b850v3_lvds_dp_remove(struct i2c_client *ge_b850v3_lvds_dp_i2c)
> > +{
> > +       struct ge_b850v3_lvds_dp *ptn_bridge =
> > +               i2c_get_clientdata(ge_b850v3_lvds_dp_i2c);
> > +
> > +       drm_bridge_remove(&ptn_bridge->bridge);
> 
> Guess you need to free ptn_bridge->edid here.

Thanks a lot! I'll fix it when sending V3.

Thank you for the review!

^ permalink raw reply	[flat|nested] 64+ messages in thread

* Re: [PATCH V2 4/5] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge
  2016-06-09 16:25   ` [PATCH V2 4/5] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge Peter Senna Tschudin
  2016-06-10  7:39     ` Enric Balletbo Serra
@ 2016-06-10 14:13     ` Daniel Vetter
  2016-06-22  8:34     ` Archit Taneja
  2 siblings, 0 replies; 64+ messages in thread
From: Daniel Vetter @ 2016-06-10 14:13 UTC (permalink / raw)
  To: Peter Senna Tschudin
  Cc: airlied, akpm, davem, devicetree, dri-devel, enric.balletbo,
	eballetbo, galak, gregkh, heiko, ijc+devicetree, jslaby, kernel,
	linux-arm-kernel, linux, linux-kernel, linux, mark.rutland,
	martin.donnelly, martyn.welch, mchehab, pawel.moll, peter.senna,
	p.zabel, thierry.reding, rmk+kernel, robh+dt, shawnguo, tiwai,
	treding, ykk, Fabio Estevam

On Thu, Jun 09, 2016 at 06:25:04PM +0200, Peter Senna Tschudin wrote:
> Add a driver that create a drm_bridge and a drm_connector for the LVDS
> to DP++ display bridge of the GE B850v3.
> 
> There are two physical bridges on the video signal pipeline: a
> STDP4028(LVDS to DP) and a STDP2690(DP to DP++).  The hardware and
> firmware made it complicated for this binding to comprise two device
> tree nodes, as the design goal is to configure both bridges based on
> the LVDS signal, which leave the driver powerless to control the video
> processing pipeline. The two bridges behaves as a single bridge, and
> the driver is only needed for telling the host about EDID / HPD, and
> for giving the host powers to ack interrupts. The video signal pipeline
> is as follows:
> 
>   Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output
> 
> Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com>
> Cc: Philipp Zabel <p.zabel@pengutronix.de>
> Cc: Rob Herring <robh@kernel.org>
> Cc: Fabio Estevam <fabio.estevam@nxp.com>
> CC: David Airlie <airlied@linux.ie>
> CC: Thierry Reding <treding@nvidia.com>
> CC: Thierry Reding <thierry.reding@gmail.com>
> Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com>
> ---
> Changes from V1:
>  - New commit message
>  - Removed 3 empty entry points
>  - Removed memory leak from ge_b850v3_lvds_dp_get_modes()
>  - Added a lock for mode setting
>  - Removed a few blank lines
>  - Changed the order at Makefile and Kconfig

Two comments below.

> 
>  MAINTAINERS                                |   8 +
>  drivers/gpu/drm/bridge/Kconfig             |  11 +
>  drivers/gpu/drm/bridge/Makefile            |   1 +
>  drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c | 392 +++++++++++++++++++++++++++++
>  4 files changed, 412 insertions(+)
>  create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 2ce5e91..2dd3d7f 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -5010,6 +5010,14 @@ W:	https://linuxtv.org
>  S:	Maintained
>  F:	drivers/media/radio/radio-gemtek*
>  
> +GENERAL ELECTRIC B850V3 LVDS/DP++ BRIDGE
> +M:	Martin Donnelly <martin.donnelly@ge.com>
> +M:	Peter Senna Tschudin <peter.senna@collabora.com>
> +M:	Martyn Welch <martyn.welch@collabora.co.uk>
> +S:	Maintained
> +F:	drivers/gpu/drm/bridge/ge_b850v3_dp2.c
> +F:	Documentation/devicetree/bindings/ge/b850v3_dp2_bridge.txt
> +
>  GENERIC GPIO I2C DRIVER
>  M:	Haavard Skinnemoen <hskinnemoen@gmail.com>
>  S:	Supported
> diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
> index 8f7423f..93dae5bd 100644
> --- a/drivers/gpu/drm/bridge/Kconfig
> +++ b/drivers/gpu/drm/bridge/Kconfig
> @@ -32,6 +32,17 @@ config DRM_DW_HDMI_AHB_AUDIO
>  	  Designware HDMI block.  This is used in conjunction with
>  	  the i.MX6 HDMI driver.
>  
> +config DRM_GE_B850V3_LVDS_DP
> +	tristate "GE B850v3 LVDS to DP++ display bridge"
> +	depends on OF
> +	select DRM_KMS_HELPER
> +	select DRM_PANEL
> +	---help---
> +          This is a driver for the display bridge of
> +          GE B850v3 that convert dual channel LVDS
> +          to DP++. This is used with the i.MX6 imx-ldb
> +          driver.
> +
>  config DRM_NXP_PTN3460
>  	tristate "NXP PTN3460 DP/LVDS bridge"
>  	depends on OF
> diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
> index 96b13b3..47ea6c1 100644
> --- a/drivers/gpu/drm/bridge/Makefile
> +++ b/drivers/gpu/drm/bridge/Makefile
> @@ -3,6 +3,7 @@ ccflags-y := -Iinclude/drm
>  obj-$(CONFIG_DRM_ANALOGIX_ANX78XX) += analogix-anx78xx.o
>  obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o
>  obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o
> +obj-$(CONFIG_DRM_GE_B850V3_LVDS_DP) += ge_b850v3_lvds_dp.o
>  obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o
>  obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o
>  obj-$(CONFIG_DRM_ANALOGIX_DP) += analogix/
> diff --git a/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c
> new file mode 100644
> index 0000000..c73cd77
> --- /dev/null
> +++ b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c
> @@ -0,0 +1,392 @@
> +/*
> + * Driver for GE B850v3 DP display bridge
> +
> + * Copyright (c) 2016, Collabora Ltd.
> + * Copyright (c) 2016, General Electric Company
> +
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms and conditions of the GNU General Public License,
> + * version 2, as published by the Free Software Foundation.
> +
> + * This program is distributed in the hope it will be useful, but WITHOUT
> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
> + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
> + * more details.
> +
> + * You should have received a copy of the GNU General Public License
> + * along with this program.  If not, see <http://www.gnu.org/licenses/>.
> +
> + * This driver creates a drm_bridge and a drm_connector for the LVDS to DP++
> + * display bridge of the GE B850v3. There are two physical bridges on the video
> + * signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). However
> + * the physical bridges are automatically configured by the input video signal,
> + * and the driver has no access to the video processing pipeline. The driver is
> + * only needed to read EDID from the STDP2690 and to handle HPD events from the
> + * STDP4028. The driver communicates with both bridges over i2c. The video
> + * signal pipeline is as follows:
> + *
> + *   Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output
> +
> + *
> + */
> +#include <linux/gpio.h>
> +#include <linux/i2c.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include "drm_crtc.h"
> +#include "drm_crtc_helper.h"
> +#include "drm_edid.h"
> +#include "drmP.h"
> +
> +#define EDID_EXT_BLOCK_CNT 0x7E
> +
> +#define STDP4028_IRQ_OUT_CONF_REG 0x02
> +#define STDP4028_DPTX_IRQ_EN_REG 0x3C
> +#define STDP4028_DPTX_IRQ_STS_REG 0x3D
> +#define STDP4028_DPTX_STS_REG 0x3E
> +
> +#define STDP4028_DPTX_DP_IRQ_EN 0x1000
> +
> +#define STDP4028_DPTX_HOTPLUG_IRQ_EN 0x0400
> +#define STDP4028_DPTX_LINK_CH_IRQ_EN 0x2000
> +#define STDP4028_DPTX_IRQ_CONFIG \
> +		(STDP4028_DPTX_LINK_CH_IRQ_EN | STDP4028_DPTX_HOTPLUG_IRQ_EN)
> +
> +#define STDP4028_DPTX_HOTPLUG_STS 0x0200
> +#define STDP4028_DPTX_LINK_STS 0x1000
> +#define STDP4028_CON_STATE_CONNECTED \
> +		(STDP4028_DPTX_HOTPLUG_STS | STDP4028_DPTX_LINK_STS)
> +
> +#define STDP4028_DPTX_HOTPLUG_CH_STS 0x0400
> +#define STDP4028_DPTX_LINK_CH_STS 0x2000
> +#define STDP4028_DPTX_IRQ_CLEAR \
> +		(STDP4028_DPTX_LINK_CH_STS | STDP4028_DPTX_HOTPLUG_CH_STS)
> +
> +struct ge_b850v3_lvds_dp {
> +	struct drm_connector connector;
> +	struct drm_bridge bridge;
> +	struct i2c_client *ge_b850v3_lvds_dp_i2c;
> +	struct i2c_client *edid_i2c;
> +	struct edid *edid;
> +	struct mutex lock;
> +};
> +
> +static inline struct ge_b850v3_lvds_dp *
> +		bridge_to_ge_b850v3_lvds_dp(struct drm_bridge *bridge)
> +{
> +	return container_of(bridge, struct ge_b850v3_lvds_dp, bridge);
> +}
> +
> +static inline struct ge_b850v3_lvds_dp *
> +		connector_to_ge_b850v3_lvds_dp(struct drm_connector *connector)
> +{
> +	return container_of(connector, struct ge_b850v3_lvds_dp, connector);
> +}
> +
> +static void ge_b850v3_lvds_dp_enable(struct drm_bridge *bridge)
> +{
> +}
> +
> +static void ge_b850v3_lvds_dp_disable(struct drm_bridge *bridge)
> +{
> +}

Please remove the above two dummy funcs, they should be unecessary. If
they're not that would be an issue in there core - we want to avoid
boilerplat like this.

> +
> +u8 *stdp2690_get_edid(struct i2c_client *client)
> +{
> +	struct i2c_adapter *adapter = client->adapter;
> +	unsigned char start = 0x00;
> +	unsigned int total_size;
> +	u8 *block = kmalloc(EDID_LENGTH, GFP_KERNEL);
> +
> +	struct i2c_msg msgs[] = {
> +		{
> +			.addr	= client->addr,
> +			.flags	= 0,
> +			.len	= 1,
> +			.buf	= &start,
> +		}, {
> +			.addr	= client->addr,
> +			.flags	= I2C_M_RD,
> +			.len	= EDID_LENGTH,
> +			.buf	= block,
> +		}
> +	};
> +
> +	if (!block)
> +		return NULL;
> +
> +	if (i2c_transfer(adapter, msgs, 2) != 2) {
> +		DRM_ERROR("Unable to read EDID.\n");
> +		goto err;
> +	}
> +
> +	if (!drm_edid_block_valid(block, 0, false, NULL)) {
> +		DRM_ERROR("Invalid EDID block\n");
> +		goto err;
> +	}
> +
> +	total_size = (block[EDID_EXT_BLOCK_CNT] + 1) * EDID_LENGTH;
> +	if (total_size > EDID_LENGTH) {
> +		kfree(block);
> +		block = kmalloc(total_size, GFP_KERNEL);
> +		if (!block)
> +			return NULL;
> +
> +		/* Yes, read the entire buffer, and do not skip the first
> +		 * EDID_LENGTH bytes.
> +		 */
> +		start = 0x00;
> +		msgs[1].len = total_size;
> +		msgs[1].buf = block;
> +
> +		if (i2c_transfer(adapter, msgs, 2) != 2) {
> +			DRM_ERROR("Unable to read EDID extension blocks.\n");
> +			goto err;
> +		}
> +	}
> +
> +	return block;
> +
> +err:
> +	kfree(block);
> +	return NULL;
> +}
> +
> +static int ge_b850v3_lvds_dp_get_modes(struct drm_connector *connector)
> +{
> +	struct ge_b850v3_lvds_dp *ptn_bridge;
> +	struct i2c_client *client;
> +	int num_modes = 0;
> +
> +	ptn_bridge = connector_to_ge_b850v3_lvds_dp(connector);
> +	client = ptn_bridge->edid_i2c;
> +
> +	mutex_lock(&ptn_bridge->lock);
> +
> +	kfree(ptn_bridge->edid);
> +	ptn_bridge->edid = (struct edid *) stdp2690_get_edid(client);
> +
> +	if (ptn_bridge->edid) {
> +		drm_mode_connector_update_edid_property(connector,
> +				ptn_bridge->edid);
> +		num_modes = drm_add_edid_modes(connector, ptn_bridge->edid);
> +	}
> +
> +	mutex_unlock(&ptn_bridge->lock);
> +
> +	return num_modes;
> +}
> +
> +static struct drm_encoder
> +*ge_b850v3_lvds_dp_best_encoder(struct drm_connector *connector)
> +{
> +	struct ge_b850v3_lvds_dp *ptn_bridge =
> +		connector_to_ge_b850v3_lvds_dp(connector);
> +
> +	return ptn_bridge->bridge.encoder;
> +}

Please remove your best_encoder callback, that just became unecessary in
drm-next, thanks to some work from Boris.
-Daniel

> +
> +static const struct
> +drm_connector_helper_funcs ge_b850v3_lvds_dp_connector_helper_funcs = {
> +	.get_modes = ge_b850v3_lvds_dp_get_modes,
> +	.best_encoder = ge_b850v3_lvds_dp_best_encoder,
> +};
> +
> +static enum drm_connector_status ge_b850v3_lvds_dp_detect(
> +		struct drm_connector *connector, bool force)
> +{
> +	struct ge_b850v3_lvds_dp *ptn_bridge =
> +			connector_to_ge_b850v3_lvds_dp(connector);
> +	struct i2c_client *ge_b850v3_lvds_dp_i2c =
> +			ptn_bridge->ge_b850v3_lvds_dp_i2c;
> +	s32 link_state;
> +
> +	link_state = i2c_smbus_read_word_data(ge_b850v3_lvds_dp_i2c,
> +			STDP4028_DPTX_STS_REG);
> +
> +	if (link_state == STDP4028_CON_STATE_CONNECTED)
> +		return connector_status_connected;
> +
> +	if (link_state == 0)
> +		return connector_status_disconnected;
> +
> +	return connector_status_unknown;
> +}
> +
> +static void ge_b850v3_lvds_dp_connector_destroy(struct drm_connector *connector)
> +{
> +	drm_connector_cleanup(connector);
> +}
> +
> +static const struct drm_connector_funcs ge_b850v3_lvds_dp_connector_funcs = {
> +	.dpms = drm_helper_connector_dpms,
> +	.fill_modes = drm_helper_probe_single_connector_modes,
> +	.detect = ge_b850v3_lvds_dp_detect,
> +	.destroy = ge_b850v3_lvds_dp_connector_destroy,
> +};
> +
> +static irqreturn_t ge_b850v3_lvds_dp_irq_handler(int irq, void *dev_id)
> +{
> +	struct ge_b850v3_lvds_dp *ptn_bridge = dev_id;
> +	struct i2c_client *ge_b850v3_lvds_dp_i2c
> +			= ptn_bridge->ge_b850v3_lvds_dp_i2c;
> +
> +	mutex_lock(&ptn_bridge->lock);
> +
> +	i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c,
> +			STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR);
> +
> +	mutex_unlock(&ptn_bridge->lock);
> +
> +	if (ptn_bridge->connector.dev)
> +		drm_kms_helper_hotplug_event(ptn_bridge->connector.dev);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static int ge_b850v3_lvds_dp_attach(struct drm_bridge *bridge)
> +{
> +	struct ge_b850v3_lvds_dp *ptn_bridge
> +			= bridge_to_ge_b850v3_lvds_dp(bridge);
> +	struct drm_connector *connector = &ptn_bridge->connector;
> +	struct i2c_client *ge_b850v3_lvds_dp_i2c
> +			= ptn_bridge->ge_b850v3_lvds_dp_i2c;
> +	int ret;
> +
> +	if (!bridge->encoder) {
> +		DRM_ERROR("Parent encoder object not found");
> +		return -ENODEV;
> +	}
> +
> +	connector->polled = DRM_CONNECTOR_POLL_HPD;
> +
> +	drm_connector_helper_add(connector,
> +			&ge_b850v3_lvds_dp_connector_helper_funcs);
> +
> +	ret = drm_connector_init(bridge->dev, connector,
> +			&ge_b850v3_lvds_dp_connector_funcs,
> +			DRM_MODE_CONNECTOR_DisplayPort);
> +	if (ret) {
> +		DRM_ERROR("Failed to initialize connector with drm\n");
> +		return ret;
> +	}
> +
> +	ret = drm_mode_connector_attach_encoder(connector, bridge->encoder);
> +	if (ret)
> +		return ret;
> +
> +	drm_bridge_enable(bridge);
> +	if (ge_b850v3_lvds_dp_i2c->irq) {
> +		drm_helper_hpd_irq_event(connector->dev);
> +
> +		ret = devm_request_threaded_irq(&ge_b850v3_lvds_dp_i2c->dev,
> +				ge_b850v3_lvds_dp_i2c->irq, NULL,
> +				ge_b850v3_lvds_dp_irq_handler,
> +				IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
> +				"ge-b850v3-lvds-dp", ptn_bridge);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static const struct drm_bridge_funcs ge_b850v3_lvds_dp_funcs = {
> +	.enable = ge_b850v3_lvds_dp_enable,
> +	.disable = ge_b850v3_lvds_dp_disable,
> +	.attach = ge_b850v3_lvds_dp_attach,
> +};
> +
> +static int ge_b850v3_lvds_dp_probe(struct i2c_client *ge_b850v3_lvds_dp_i2c,
> +				const struct i2c_device_id *id)
> +{
> +	struct device *dev = &ge_b850v3_lvds_dp_i2c->dev;
> +	struct ge_b850v3_lvds_dp *ptn_bridge;
> +	int ret;
> +	u32 edid_i2c_reg;
> +
> +	ptn_bridge = devm_kzalloc(dev, sizeof(*ptn_bridge), GFP_KERNEL);
> +	if (!ptn_bridge)
> +		return -ENOMEM;
> +
> +	mutex_init(&ptn_bridge->lock);
> +
> +	ptn_bridge->ge_b850v3_lvds_dp_i2c = ge_b850v3_lvds_dp_i2c;
> +	ptn_bridge->bridge.driver_private = ptn_bridge;
> +	i2c_set_clientdata(ge_b850v3_lvds_dp_i2c, ptn_bridge);
> +
> +	ret = of_property_read_u32(dev->of_node, "edid-reg", &edid_i2c_reg);
> +	if (ret) {
> +		dev_err(dev, "edid-reg not specified, aborting...\n");
> +		return -ENODEV;
> +	}
> +
> +	ptn_bridge->edid_i2c = devm_kzalloc(dev,
> +			sizeof(struct i2c_client), GFP_KERNEL);
> +
> +	if (!ptn_bridge->edid_i2c)
> +		return -ENOMEM;
> +
> +	memcpy(ptn_bridge->edid_i2c, ge_b850v3_lvds_dp_i2c,
> +			sizeof(struct i2c_client));
> +
> +	ptn_bridge->edid_i2c->addr = (unsigned short) edid_i2c_reg;
> +
> +	/* Configures the bridge to re-enable interrupts after each ack */
> +	i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c,
> +			STDP4028_IRQ_OUT_CONF_REG, STDP4028_DPTX_DP_IRQ_EN);
> +	i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c,
> +			STDP4028_DPTX_IRQ_EN_REG, STDP4028_DPTX_IRQ_CONFIG);
> +
> +	/* Clear pending interrupts since power up. */
> +	i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c,
> +			STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR);
> +
> +	ptn_bridge->bridge.funcs = &ge_b850v3_lvds_dp_funcs;
> +	ptn_bridge->bridge.of_node = dev->of_node;
> +	ret = drm_bridge_add(&ptn_bridge->bridge);
> +	if (ret) {
> +		DRM_ERROR("Failed to add bridge\n");
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static int ge_b850v3_lvds_dp_remove(struct i2c_client *ge_b850v3_lvds_dp_i2c)
> +{
> +	struct ge_b850v3_lvds_dp *ptn_bridge =
> +		i2c_get_clientdata(ge_b850v3_lvds_dp_i2c);
> +
> +	drm_bridge_remove(&ptn_bridge->bridge);
> +
> +	return 0;
> +}
> +
> +static const struct i2c_device_id ge_b850v3_lvds_dp_i2c_table[] = {
> +	{"b850v3-lvds-dp", 0},
> +	{},
> +};
> +MODULE_DEVICE_TABLE(i2c, ge_b850v3_lvds_dp_i2c_table);
> +
> +static const struct of_device_id ge_b850v3_lvds_dp_match[] = {
> +	{ .compatible = "ge,b850v3-lvds-dp" },
> +	{},
> +};
> +MODULE_DEVICE_TABLE(of, ge_b850v3_lvds_dp_match);
> +
> +static struct i2c_driver ge_b850v3_lvds_dp_driver = {
> +	.id_table	= ge_b850v3_lvds_dp_i2c_table,
> +	.probe		= ge_b850v3_lvds_dp_probe,
> +	.remove		= ge_b850v3_lvds_dp_remove,
> +	.driver		= {
> +		.name		= "ge,b850v3-lvds-dp",
> +		.of_match_table = ge_b850v3_lvds_dp_match,
> +	},
> +};
> +module_i2c_driver(ge_b850v3_lvds_dp_driver);
> +
> +MODULE_AUTHOR("Peter Senna Tschudin <peter.senna@collabora.com>");
> +MODULE_AUTHOR("Martyn Welch <martyn.welch@collabora.co.uk>");
> +MODULE_DESCRIPTION("GE LVDS to DP++ bridge)");
> +MODULE_LICENSE("GPL v2");
> -- 
> 2.5.5
> 
> _______________________________________________
> dri-devel mailing list
> dri-devel@lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/dri-devel

-- 
Daniel Vetter
Software Engineer, Intel Corporation
http://blog.ffwll.ch

^ permalink raw reply	[flat|nested] 64+ messages in thread

* Re: [PATCH V2 3/5] Documentation/devicetree/bindings: b850v3_lvds_dp
  2016-06-09 16:25   ` [PATCH V2 3/5] Documentation/devicetree/bindings: b850v3_lvds_dp Peter Senna Tschudin
@ 2016-06-10 17:42     ` Rob Herring
  2016-06-10 18:54     ` Javier Martinez Canillas
  1 sibling, 0 replies; 64+ messages in thread
From: Rob Herring @ 2016-06-10 17:42 UTC (permalink / raw)
  To: Peter Senna Tschudin
  Cc: airlied, akpm, davem, devicetree, dri-devel, enric.balletbo,
	eballetbo, galak, gregkh, heiko, ijc+devicetree, jslaby, kernel,
	linux-arm-kernel, linux, linux-kernel, linux, mark.rutland,
	martin.donnelly, martyn.welch, mchehab, pawel.moll, peter.senna,
	p.zabel, thierry.reding, rmk+kernel, shawnguo, tiwai, treding,
	ykk, Fabio Estevam

On Thu, Jun 09, 2016 at 06:25:03PM +0200, Peter Senna Tschudin wrote:
> Devicetree bindings documentation for the GE B850v3 LVDS/DP++
> display bridge.
> 
> Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com>
> Cc: Philipp Zabel <p.zabel@pengutronix.de>
> Cc: Rob Herring <robh@kernel.org>
> Cc: Fabio Estevam <fabio.estevam@nxp.com>
> Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com>
> ---
> Changes from V1:
>  - Replaced '_' by '-' in node names or compatible strings
>  - Added missing @73 to the example
> 
>  .../devicetree/bindings/ge/b850v3_lvds_dp.txt      | 38 ++++++++++++++++++++++
>  1 file changed, 38 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt

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

^ permalink raw reply	[flat|nested] 64+ messages in thread

* Re: [PATCH V2 3/5] Documentation/devicetree/bindings: b850v3_lvds_dp
  2016-06-09 16:25   ` [PATCH V2 3/5] Documentation/devicetree/bindings: b850v3_lvds_dp Peter Senna Tschudin
  2016-06-10 17:42     ` Rob Herring
@ 2016-06-10 18:54     ` Javier Martinez Canillas
  1 sibling, 0 replies; 64+ messages in thread
From: Javier Martinez Canillas @ 2016-06-10 18:54 UTC (permalink / raw)
  To: Peter Senna Tschudin
  Cc: David Airlie, akpm, David S. Miller, devicetree, dri-devel,
	Enric Balletbo i Serra, Enric Balletbo Serra, Kumar Gala,
	Greg Kroah-Hartman, Heiko Stübner, Ian Campbell, jslaby,
	Sascha Hauer, linux-arm-kernel, linux, Linux Kernel,
	Guenter Roeck, Mark Rutland, martin.donnelly, martyn.welch,
	Mauro Carvalho Chehab, Pawel Moll, peter.senna, p.zabel,
	Thierry Reding, rmk+kernel, Rob Herring, shawnguo, tiwai,
	Thierry Reding, ykk, Fabio Estevam

Hello Peter,

On Thu, Jun 9, 2016 at 12:25 PM, Peter Senna Tschudin
<peter.senna@collabora.com> wrote:
> Devicetree bindings documentation for the GE B850v3 LVDS/DP++
> display bridge.
>
> Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com>
> Cc: Philipp Zabel <p.zabel@pengutronix.de>
> Cc: Rob Herring <robh@kernel.org>
> Cc: Fabio Estevam <fabio.estevam@nxp.com>
> Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com>
> ---
> Changes from V1:
>  - Replaced '_' by '-' in node names or compatible strings
>  - Added missing @73 to the example
>
>  .../devicetree/bindings/ge/b850v3_lvds_dp.txt      | 38 ++++++++++++++++++++++
>  1 file changed, 38 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt
>
> diff --git a/Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt b/Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt
> new file mode 100644
> index 0000000..46bbea9
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/ge/b850v3_lvds_dp.txt
> @@ -0,0 +1,38 @@
> +Driver for GE B850v3 LVDS/DP++ display bridge
> +
> +Required properties:
> +  - compatible : should be "ge,b850v3_lvds_dp".

It seems you forgot to replace '_' by '-' (you did in the example though)

> +  - reg : should contain the address used to ack the interrupts.
> +  - interrupt-parent : should link to the gpio used as interrupt
> +    source on the host.

Is the interrupt parent always a GPIO controller since that is what
this description says.

Shouldn't be instead something like instead?

interrupt-parent: phandle of the interrupt controller that services
interrupts to the device

> +  - interrupts : one interrupt should be described here, as in
> +    <0 IRQ_TYPE_LEVEL_HIGH>.
> +  - edid-reg : should contain the address used to read edid information
> +  - port : should describe the vide signal connection between the host

s/vide/video

> +    and the bridge.
> +
> +Example:
> +
> +&mux2_i2c2 {
> +       status = "okay";
> +       clock-frequency = <100000>;
> +
> +       b850v3-lvds-dp-bridge@73  {
> +               compatible = "ge,b850v3-lvds-dp";
> +               #address-cells = <1>;
> +               #size-cells = <0>;
> +
> +               reg = <0x73>;
> +               interrupt-parent = <&gpio2>;
> +               interrupts = <0 IRQ_TYPE_LEVEL_HIGH>;
> +
> +               edid-reg = <0x72>;
> +
> +               port@0 {
> +                       reg = <0>;

AFAIU a unit-address and reg property for ports are only needed if you
have more than one port according to
Documentation/devicetree/bindings/graph.txt and
Documentation/devicetree/bindings/media/video-interfaces.txt.

> +                       b850v3_dp_bridge_in: endpoint {
> +                               remote-endpoint = <&lvds0_out>;
> +                       };
> +               };
> +       };
> +};
> --

Best regards,
Javier

^ permalink raw reply	[flat|nested] 64+ messages in thread

* Re: [PATCH V2 4/5] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge
  2016-06-09 16:25   ` [PATCH V2 4/5] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge Peter Senna Tschudin
  2016-06-10  7:39     ` Enric Balletbo Serra
  2016-06-10 14:13     ` Daniel Vetter
@ 2016-06-22  8:34     ` Archit Taneja
  2 siblings, 0 replies; 64+ messages in thread
From: Archit Taneja @ 2016-06-22  8:34 UTC (permalink / raw)
  To: Peter Senna Tschudin, airlied, akpm, davem, devicetree,
	dri-devel, enric.balletbo, eballetbo, galak, gregkh, heiko,
	ijc+devicetree, jslaby, kernel, linux-arm-kernel, linux,
	linux-kernel, linux, mark.rutland, martin.donnelly, martyn.welch,
	mchehab, pawel.moll, peter.senna, p.zabel, thierry.reding,
	rmk+kernel, robh+dt, shawnguo, tiwai, treding, ykk
  Cc: Rob Herring, Fabio Estevam



On 6/9/2016 9:55 PM, Peter Senna Tschudin wrote:
> Add a driver that create a drm_bridge and a drm_connector for the LVDS
> to DP++ display bridge of the GE B850v3.
>
> There are two physical bridges on the video signal pipeline: a
> STDP4028(LVDS to DP) and a STDP2690(DP to DP++).  The hardware and
> firmware made it complicated for this binding to comprise two device
> tree nodes, as the design goal is to configure both bridges based on
> the LVDS signal, which leave the driver powerless to control the video
> processing pipeline. The two bridges behaves as a single bridge, and
> the driver is only needed for telling the host about EDID / HPD, and
> for giving the host powers to ack interrupts. The video signal pipeline
> is as follows:
>
>    Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output

Are these two chips always expected to be used together? I don't think
it's right to pair up two encoder chips into one driver just for one
board.

Is one device @0x72 and other @0x73? Or is only one of them an i2c
slave?

What's preventing us to create these as two different bridge drivers?
The drm framework allows us to daisy chain encoder bridges. The only
problem I see is that we don't have a clear-cut way to tell the bridge
driver whether we want it to create a connector for us or not. Because,
it looks like both can potentially create connectors. This isn't a big
problem either if we have DT. We just need to check whether our output
port is connected to another bridge or a connector.

Thanks,
Archit

>
> Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com>
> Cc: Philipp Zabel <p.zabel@pengutronix.de>
> Cc: Rob Herring <robh@kernel.org>
> Cc: Fabio Estevam <fabio.estevam@nxp.com>
> CC: David Airlie <airlied@linux.ie>
> CC: Thierry Reding <treding@nvidia.com>
> CC: Thierry Reding <thierry.reding@gmail.com>
> Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com>
> ---
> Changes from V1:
>   - New commit message
>   - Removed 3 empty entry points
>   - Removed memory leak from ge_b850v3_lvds_dp_get_modes()
>   - Added a lock for mode setting
>   - Removed a few blank lines
>   - Changed the order at Makefile and Kconfig
>
>   MAINTAINERS                                |   8 +
>   drivers/gpu/drm/bridge/Kconfig             |  11 +
>   drivers/gpu/drm/bridge/Makefile            |   1 +
>   drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c | 392 +++++++++++++++++++++++++++++
>   4 files changed, 412 insertions(+)
>   create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 2ce5e91..2dd3d7f 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -5010,6 +5010,14 @@ W:	https://linuxtv.org
>   S:	Maintained
>   F:	drivers/media/radio/radio-gemtek*
>
> +GENERAL ELECTRIC B850V3 LVDS/DP++ BRIDGE
> +M:	Martin Donnelly <martin.donnelly@ge.com>
> +M:	Peter Senna Tschudin <peter.senna@collabora.com>
> +M:	Martyn Welch <martyn.welch@collabora.co.uk>
> +S:	Maintained
> +F:	drivers/gpu/drm/bridge/ge_b850v3_dp2.c
> +F:	Documentation/devicetree/bindings/ge/b850v3_dp2_bridge.txt
> +
>   GENERIC GPIO I2C DRIVER
>   M:	Haavard Skinnemoen <hskinnemoen@gmail.com>
>   S:	Supported
> diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
> index 8f7423f..93dae5bd 100644
> --- a/drivers/gpu/drm/bridge/Kconfig
> +++ b/drivers/gpu/drm/bridge/Kconfig
> @@ -32,6 +32,17 @@ config DRM_DW_HDMI_AHB_AUDIO
>   	  Designware HDMI block.  This is used in conjunction with
>   	  the i.MX6 HDMI driver.
>
> +config DRM_GE_B850V3_LVDS_DP
> +	tristate "GE B850v3 LVDS to DP++ display bridge"
> +	depends on OF
> +	select DRM_KMS_HELPER
> +	select DRM_PANEL
> +	---help---
> +          This is a driver for the display bridge of
> +          GE B850v3 that convert dual channel LVDS
> +          to DP++. This is used with the i.MX6 imx-ldb
> +          driver.
> +
>   config DRM_NXP_PTN3460
>   	tristate "NXP PTN3460 DP/LVDS bridge"
>   	depends on OF
> diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
> index 96b13b3..47ea6c1 100644
> --- a/drivers/gpu/drm/bridge/Makefile
> +++ b/drivers/gpu/drm/bridge/Makefile
> @@ -3,6 +3,7 @@ ccflags-y := -Iinclude/drm
>   obj-$(CONFIG_DRM_ANALOGIX_ANX78XX) += analogix-anx78xx.o
>   obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o
>   obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o
> +obj-$(CONFIG_DRM_GE_B850V3_LVDS_DP) += ge_b850v3_lvds_dp.o
>   obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o
>   obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o
>   obj-$(CONFIG_DRM_ANALOGIX_DP) += analogix/
> diff --git a/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c
> new file mode 100644
> index 0000000..c73cd77
> --- /dev/null
> +++ b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c
> @@ -0,0 +1,392 @@
> +/*
> + * Driver for GE B850v3 DP display bridge
> +
> + * Copyright (c) 2016, Collabora Ltd.
> + * Copyright (c) 2016, General Electric Company
> +
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms and conditions of the GNU General Public License,
> + * version 2, as published by the Free Software Foundation.
> +
> + * This program is distributed in the hope it will be useful, but WITHOUT
> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
> + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
> + * more details.
> +
> + * You should have received a copy of the GNU General Public License
> + * along with this program.  If not, see <http://www.gnu.org/licenses/>.
> +
> + * This driver creates a drm_bridge and a drm_connector for the LVDS to DP++
> + * display bridge of the GE B850v3. There are two physical bridges on the video
> + * signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). However
> + * the physical bridges are automatically configured by the input video signal,
> + * and the driver has no access to the video processing pipeline. The driver is
> + * only needed to read EDID from the STDP2690 and to handle HPD events from the
> + * STDP4028. The driver communicates with both bridges over i2c. The video
> + * signal pipeline is as follows:
> + *
> + *   Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output
> +
> + *
> + */
> +#include <linux/gpio.h>
> +#include <linux/i2c.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include "drm_crtc.h"
> +#include "drm_crtc_helper.h"
> +#include "drm_edid.h"
> +#include "drmP.h"
> +
> +#define EDID_EXT_BLOCK_CNT 0x7E
> +
> +#define STDP4028_IRQ_OUT_CONF_REG 0x02
> +#define STDP4028_DPTX_IRQ_EN_REG 0x3C
> +#define STDP4028_DPTX_IRQ_STS_REG 0x3D
> +#define STDP4028_DPTX_STS_REG 0x3E
> +
> +#define STDP4028_DPTX_DP_IRQ_EN 0x1000
> +
> +#define STDP4028_DPTX_HOTPLUG_IRQ_EN 0x0400
> +#define STDP4028_DPTX_LINK_CH_IRQ_EN 0x2000
> +#define STDP4028_DPTX_IRQ_CONFIG \
> +		(STDP4028_DPTX_LINK_CH_IRQ_EN | STDP4028_DPTX_HOTPLUG_IRQ_EN)
> +
> +#define STDP4028_DPTX_HOTPLUG_STS 0x0200
> +#define STDP4028_DPTX_LINK_STS 0x1000
> +#define STDP4028_CON_STATE_CONNECTED \
> +		(STDP4028_DPTX_HOTPLUG_STS | STDP4028_DPTX_LINK_STS)
> +
> +#define STDP4028_DPTX_HOTPLUG_CH_STS 0x0400
> +#define STDP4028_DPTX_LINK_CH_STS 0x2000
> +#define STDP4028_DPTX_IRQ_CLEAR \
> +		(STDP4028_DPTX_LINK_CH_STS | STDP4028_DPTX_HOTPLUG_CH_STS)
> +
> +struct ge_b850v3_lvds_dp {
> +	struct drm_connector connector;
> +	struct drm_bridge bridge;
> +	struct i2c_client *ge_b850v3_lvds_dp_i2c;
> +	struct i2c_client *edid_i2c;
> +	struct edid *edid;
> +	struct mutex lock;
> +};
> +
> +static inline struct ge_b850v3_lvds_dp *
> +		bridge_to_ge_b850v3_lvds_dp(struct drm_bridge *bridge)
> +{
> +	return container_of(bridge, struct ge_b850v3_lvds_dp, bridge);
> +}
> +
> +static inline struct ge_b850v3_lvds_dp *
> +		connector_to_ge_b850v3_lvds_dp(struct drm_connector *connector)
> +{
> +	return container_of(connector, struct ge_b850v3_lvds_dp, connector);
> +}
> +
> +static void ge_b850v3_lvds_dp_enable(struct drm_bridge *bridge)
> +{
> +}
> +
> +static void ge_b850v3_lvds_dp_disable(struct drm_bridge *bridge)
> +{
> +}
> +
> +u8 *stdp2690_get_edid(struct i2c_client *client)
> +{
> +	struct i2c_adapter *adapter = client->adapter;
> +	unsigned char start = 0x00;
> +	unsigned int total_size;
> +	u8 *block = kmalloc(EDID_LENGTH, GFP_KERNEL);
> +
> +	struct i2c_msg msgs[] = {
> +		{
> +			.addr	= client->addr,
> +			.flags	= 0,
> +			.len	= 1,
> +			.buf	= &start,
> +		}, {
> +			.addr	= client->addr,
> +			.flags	= I2C_M_RD,
> +			.len	= EDID_LENGTH,
> +			.buf	= block,
> +		}
> +	};
> +
> +	if (!block)
> +		return NULL;
> +
> +	if (i2c_transfer(adapter, msgs, 2) != 2) {
> +		DRM_ERROR("Unable to read EDID.\n");
> +		goto err;
> +	}
> +
> +	if (!drm_edid_block_valid(block, 0, false, NULL)) {
> +		DRM_ERROR("Invalid EDID block\n");
> +		goto err;
> +	}
> +
> +	total_size = (block[EDID_EXT_BLOCK_CNT] + 1) * EDID_LENGTH;
> +	if (total_size > EDID_LENGTH) {
> +		kfree(block);
> +		block = kmalloc(total_size, GFP_KERNEL);
> +		if (!block)
> +			return NULL;
> +
> +		/* Yes, read the entire buffer, and do not skip the first
> +		 * EDID_LENGTH bytes.
> +		 */
> +		start = 0x00;
> +		msgs[1].len = total_size;
> +		msgs[1].buf = block;
> +
> +		if (i2c_transfer(adapter, msgs, 2) != 2) {
> +			DRM_ERROR("Unable to read EDID extension blocks.\n");
> +			goto err;
> +		}
> +	}
> +
> +	return block;
> +
> +err:
> +	kfree(block);
> +	return NULL;
> +}
> +
> +static int ge_b850v3_lvds_dp_get_modes(struct drm_connector *connector)
> +{
> +	struct ge_b850v3_lvds_dp *ptn_bridge;
> +	struct i2c_client *client;
> +	int num_modes = 0;
> +
> +	ptn_bridge = connector_to_ge_b850v3_lvds_dp(connector);
> +	client = ptn_bridge->edid_i2c;
> +
> +	mutex_lock(&ptn_bridge->lock);
> +
> +	kfree(ptn_bridge->edid);
> +	ptn_bridge->edid = (struct edid *) stdp2690_get_edid(client);
> +
> +	if (ptn_bridge->edid) {
> +		drm_mode_connector_update_edid_property(connector,
> +				ptn_bridge->edid);
> +		num_modes = drm_add_edid_modes(connector, ptn_bridge->edid);
> +	}
> +
> +	mutex_unlock(&ptn_bridge->lock);
> +
> +	return num_modes;
> +}
> +
> +static struct drm_encoder
> +*ge_b850v3_lvds_dp_best_encoder(struct drm_connector *connector)
> +{
> +	struct ge_b850v3_lvds_dp *ptn_bridge =
> +		connector_to_ge_b850v3_lvds_dp(connector);
> +
> +	return ptn_bridge->bridge.encoder;
> +}
> +
> +static const struct
> +drm_connector_helper_funcs ge_b850v3_lvds_dp_connector_helper_funcs = {
> +	.get_modes = ge_b850v3_lvds_dp_get_modes,
> +	.best_encoder = ge_b850v3_lvds_dp_best_encoder,
> +};
> +
> +static enum drm_connector_status ge_b850v3_lvds_dp_detect(
> +		struct drm_connector *connector, bool force)
> +{
> +	struct ge_b850v3_lvds_dp *ptn_bridge =
> +			connector_to_ge_b850v3_lvds_dp(connector);
> +	struct i2c_client *ge_b850v3_lvds_dp_i2c =
> +			ptn_bridge->ge_b850v3_lvds_dp_i2c;
> +	s32 link_state;
> +
> +	link_state = i2c_smbus_read_word_data(ge_b850v3_lvds_dp_i2c,
> +			STDP4028_DPTX_STS_REG);
> +
> +	if (link_state == STDP4028_CON_STATE_CONNECTED)
> +		return connector_status_connected;
> +
> +	if (link_state == 0)
> +		return connector_status_disconnected;
> +
> +	return connector_status_unknown;
> +}
> +
> +static void ge_b850v3_lvds_dp_connector_destroy(struct drm_connector *connector)
> +{
> +	drm_connector_cleanup(connector);
> +}
> +
> +static const struct drm_connector_funcs ge_b850v3_lvds_dp_connector_funcs = {
> +	.dpms = drm_helper_connector_dpms,
> +	.fill_modes = drm_helper_probe_single_connector_modes,
> +	.detect = ge_b850v3_lvds_dp_detect,
> +	.destroy = ge_b850v3_lvds_dp_connector_destroy,
> +};
> +
> +static irqreturn_t ge_b850v3_lvds_dp_irq_handler(int irq, void *dev_id)
> +{
> +	struct ge_b850v3_lvds_dp *ptn_bridge = dev_id;
> +	struct i2c_client *ge_b850v3_lvds_dp_i2c
> +			= ptn_bridge->ge_b850v3_lvds_dp_i2c;
> +
> +	mutex_lock(&ptn_bridge->lock);
> +
> +	i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c,
> +			STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR);
> +
> +	mutex_unlock(&ptn_bridge->lock);
> +
> +	if (ptn_bridge->connector.dev)
> +		drm_kms_helper_hotplug_event(ptn_bridge->connector.dev);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static int ge_b850v3_lvds_dp_attach(struct drm_bridge *bridge)
> +{
> +	struct ge_b850v3_lvds_dp *ptn_bridge
> +			= bridge_to_ge_b850v3_lvds_dp(bridge);
> +	struct drm_connector *connector = &ptn_bridge->connector;
> +	struct i2c_client *ge_b850v3_lvds_dp_i2c
> +			= ptn_bridge->ge_b850v3_lvds_dp_i2c;
> +	int ret;
> +
> +	if (!bridge->encoder) {
> +		DRM_ERROR("Parent encoder object not found");
> +		return -ENODEV;
> +	}
> +
> +	connector->polled = DRM_CONNECTOR_POLL_HPD;
> +
> +	drm_connector_helper_add(connector,
> +			&ge_b850v3_lvds_dp_connector_helper_funcs);
> +
> +	ret = drm_connector_init(bridge->dev, connector,
> +			&ge_b850v3_lvds_dp_connector_funcs,
> +			DRM_MODE_CONNECTOR_DisplayPort);
> +	if (ret) {
> +		DRM_ERROR("Failed to initialize connector with drm\n");
> +		return ret;
> +	}
> +
> +	ret = drm_mode_connector_attach_encoder(connector, bridge->encoder);
> +	if (ret)
> +		return ret;
> +
> +	drm_bridge_enable(bridge);
> +	if (ge_b850v3_lvds_dp_i2c->irq) {
> +		drm_helper_hpd_irq_event(connector->dev);
> +
> +		ret = devm_request_threaded_irq(&ge_b850v3_lvds_dp_i2c->dev,
> +				ge_b850v3_lvds_dp_i2c->irq, NULL,
> +				ge_b850v3_lvds_dp_irq_handler,
> +				IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
> +				"ge-b850v3-lvds-dp", ptn_bridge);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static const struct drm_bridge_funcs ge_b850v3_lvds_dp_funcs = {
> +	.enable = ge_b850v3_lvds_dp_enable,
> +	.disable = ge_b850v3_lvds_dp_disable,
> +	.attach = ge_b850v3_lvds_dp_attach,
> +};
> +
> +static int ge_b850v3_lvds_dp_probe(struct i2c_client *ge_b850v3_lvds_dp_i2c,
> +				const struct i2c_device_id *id)
> +{
> +	struct device *dev = &ge_b850v3_lvds_dp_i2c->dev;
> +	struct ge_b850v3_lvds_dp *ptn_bridge;
> +	int ret;
> +	u32 edid_i2c_reg;
> +
> +	ptn_bridge = devm_kzalloc(dev, sizeof(*ptn_bridge), GFP_KERNEL);
> +	if (!ptn_bridge)
> +		return -ENOMEM;
> +
> +	mutex_init(&ptn_bridge->lock);
> +
> +	ptn_bridge->ge_b850v3_lvds_dp_i2c = ge_b850v3_lvds_dp_i2c;
> +	ptn_bridge->bridge.driver_private = ptn_bridge;
> +	i2c_set_clientdata(ge_b850v3_lvds_dp_i2c, ptn_bridge);
> +
> +	ret = of_property_read_u32(dev->of_node, "edid-reg", &edid_i2c_reg);
> +	if (ret) {
> +		dev_err(dev, "edid-reg not specified, aborting...\n");
> +		return -ENODEV;
> +	}
> +
> +	ptn_bridge->edid_i2c = devm_kzalloc(dev,
> +			sizeof(struct i2c_client), GFP_KERNEL);
> +
> +	if (!ptn_bridge->edid_i2c)
> +		return -ENOMEM;
> +
> +	memcpy(ptn_bridge->edid_i2c, ge_b850v3_lvds_dp_i2c,
> +			sizeof(struct i2c_client));
> +
> +	ptn_bridge->edid_i2c->addr = (unsigned short) edid_i2c_reg;
> +
> +	/* Configures the bridge to re-enable interrupts after each ack */
> +	i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c,
> +			STDP4028_IRQ_OUT_CONF_REG, STDP4028_DPTX_DP_IRQ_EN);
> +	i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c,
> +			STDP4028_DPTX_IRQ_EN_REG, STDP4028_DPTX_IRQ_CONFIG);
> +
> +	/* Clear pending interrupts since power up. */
> +	i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c,
> +			STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR);
> +
> +	ptn_bridge->bridge.funcs = &ge_b850v3_lvds_dp_funcs;
> +	ptn_bridge->bridge.of_node = dev->of_node;
> +	ret = drm_bridge_add(&ptn_bridge->bridge);
> +	if (ret) {
> +		DRM_ERROR("Failed to add bridge\n");
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static int ge_b850v3_lvds_dp_remove(struct i2c_client *ge_b850v3_lvds_dp_i2c)
> +{
> +	struct ge_b850v3_lvds_dp *ptn_bridge =
> +		i2c_get_clientdata(ge_b850v3_lvds_dp_i2c);
> +
> +	drm_bridge_remove(&ptn_bridge->bridge);
> +
> +	return 0;
> +}
> +
> +static const struct i2c_device_id ge_b850v3_lvds_dp_i2c_table[] = {
> +	{"b850v3-lvds-dp", 0},
> +	{},
> +};
> +MODULE_DEVICE_TABLE(i2c, ge_b850v3_lvds_dp_i2c_table);
> +
> +static const struct of_device_id ge_b850v3_lvds_dp_match[] = {
> +	{ .compatible = "ge,b850v3-lvds-dp" },
> +	{},
> +};
> +MODULE_DEVICE_TABLE(of, ge_b850v3_lvds_dp_match);
> +
> +static struct i2c_driver ge_b850v3_lvds_dp_driver = {
> +	.id_table	= ge_b850v3_lvds_dp_i2c_table,
> +	.probe		= ge_b850v3_lvds_dp_probe,
> +	.remove		= ge_b850v3_lvds_dp_remove,
> +	.driver		= {
> +		.name		= "ge,b850v3-lvds-dp",
> +		.of_match_table = ge_b850v3_lvds_dp_match,
> +	},
> +};
> +module_i2c_driver(ge_b850v3_lvds_dp_driver);
> +
> +MODULE_AUTHOR("Peter Senna Tschudin <peter.senna@collabora.com>");
> +MODULE_AUTHOR("Martyn Welch <martyn.welch@collabora.co.uk>");
> +MODULE_DESCRIPTION("GE LVDS to DP++ bridge)");
> +MODULE_LICENSE("GPL v2");
>

-- 
Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum,
a Linux Foundation Collaborative Project

^ permalink raw reply	[flat|nested] 64+ messages in thread

* [PATCH V3 0/5] Add driver for GE B850v3 LVDS/DP++ Bridge
  2016-05-30 16:39 [PATCH 0/5] Add driver for GE B850v3 LVDS/DP++ Bridge Peter Senna Tschudin
                   ` (5 preceding siblings ...)
  2016-06-09 16:25 ` [PATCH V2 0/5] Add driver for " Peter Senna Tschudin
@ 2016-07-31 19:55 ` Peter Senna Tschudin
  2016-07-31 19:55   ` [PATCH V3 1/5] drm/imx-ldb: Add support to drm-bridge Peter Senna Tschudin
                     ` (4 more replies)
  2016-08-04 22:36 ` [PATCH V4 0/4] Add driver for " Peter Senna Tschudin
  2016-08-09 16:41 ` [PATCH V5 0/4] Add driver for " Peter Senna Tschudin
  8 siblings, 5 replies; 64+ messages in thread
From: Peter Senna Tschudin @ 2016-07-31 19:55 UTC (permalink / raw)
  To: robh+dt, mark.rutland, shawnguo, kernel, fabio.estevam, linux,
	airlied, p.zabel, peter.senna, davem, geert, gregkh, akpm,
	mchehab, linux, treding, architt, ykk, andrey.gusakov,
	boris.brezillon, enric.balletbo, devicetree, linux-kernel,
	linux-arm-kernel, dri-devel

The series adds a driver that creates a drm_bridge and a drm_connector for the
LVDS to DP++ display bridge of the GE B850v3.

There are two physical bridges on the video signal pipeline: a STDP4028(LVDS to
DP) and a STDP2690(DP to DP++).  The hardware and firmware made it complicated
for this binding to comprise two device tree nodes, as the design goal is to
configure both bridges based on the LVDS signal, which leave the driver
powerless to control the video processing pipeline. The two bridges behaves as
a single bridge, and the driver is only needed for telling the host about EDID /
HPD, and for giving the host powers to ack interrupts. The video signal
pipeline is as follows:

  Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output

The patches from the series:
 [1/5] Change the imx-ldb driver to allow attaching a bridge and not only a LVDS
       panel.

 [2/5] Configure the mapping between IPUs and external displays on the dts file
       of the B850v3. Needed to support two simultaneos Full-HD monitors.

 [3/5] Devicetree documentation for the GE B850v3 LVDS/DP++ Bridge

 [4/5] Add the driver, make changes to MAINTAINERS, Kconfig and Makefile

 [5/5] Make the changes to the B850v3 dts file to enable the GE B850v3
       LVDS/DP++ Bridge.

Peter Senna Tschudin (5):
  drm/imx-ldb: Add support to drm-bridge
  dts/imx6q-b850v3: Configure IPU assignment order
  Documentation/devicetree/bindings: b850v3_lvds_dp
  drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge
  dts/imx6q-b850v3: Use GE B850v3 LVDS/DP++ Bridge

 .../devicetree/bindings/ge/b850v3-lvds-dp.txt      |  37 ++
 MAINTAINERS                                        |   8 +
 arch/arm/boot/dts/imx6q-b850v3.dts                 |  35 ++
 drivers/gpu/drm/bridge/Kconfig                     |  11 +
 drivers/gpu/drm/bridge/Makefile                    |   1 +
 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c         | 397 +++++++++++++++++++++
 drivers/gpu/drm/imx/imx-ldb.c                      | 111 +++---
 7 files changed, 559 insertions(+), 41 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt
 create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c

-- 
2.5.5

^ permalink raw reply	[flat|nested] 64+ messages in thread

* [PATCH V3 1/5] drm/imx-ldb: Add support to drm-bridge
  2016-07-31 19:55 ` [PATCH V3 0/5] Add driver for " Peter Senna Tschudin
@ 2016-07-31 19:55   ` Peter Senna Tschudin
  2016-08-01 10:21     ` Philipp Zabel
  2016-07-31 19:55   ` [PATCH V3 2/5] dts/imx6q-b850v3: Configure IPU assignment order Peter Senna Tschudin
                     ` (3 subsequent siblings)
  4 siblings, 1 reply; 64+ messages in thread
From: Peter Senna Tschudin @ 2016-07-31 19:55 UTC (permalink / raw)
  To: robh+dt, mark.rutland, shawnguo, kernel, fabio.estevam, linux,
	airlied, p.zabel, peter.senna, davem, geert, gregkh, akpm,
	mchehab, linux, treding, architt, ykk, andrey.gusakov,
	boris.brezillon, enric.balletbo, devicetree, linux-kernel,
	linux-arm-kernel, dri-devel
  Cc: Rob Herring, Thierry Reding

Add support to attach a drm_bridge to imx-ldb in addition to
existing support to attach a LVDS panel.

This patch does a simple code refactoring by moving code
from for_each_child_of_node iterator to a new function named
imx_ldb_panel_ddc(). This was necessary to allow the panel ddc
code to run only when the imx_ldb is not attached to a bridge.

Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com>
Cc: Philipp Zabel <p.zabel@pengutronix.de>
Cc: Rob Herring <robh@kernel.org>
Cc: Fabio Estevam <fabio.estevam@nxp.com>
Cc: David Airlie <airlied@linux.ie>
Cc: Thierry Reding <treding@nvidia.com>
Cc: Thierry Reding <thierry.reding@gmail.com>
Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com>
---
Changes from V2:
 - Updated to be aplied on top of Liu Ying changes that made imx-ldb atomic.
 - Tested on next-20160729.

Changes from V1:
 - Reanmed ext_bridge to bridge
 - Removed empty entry point imx_ldb_encoder_enable()
 - Adapted the code to apply to the latest linux next: next-20160609

 drivers/gpu/drm/imx/imx-ldb.c | 111 ++++++++++++++++++++++++++----------------
 1 file changed, 70 insertions(+), 41 deletions(-)

diff --git a/drivers/gpu/drm/imx/imx-ldb.c b/drivers/gpu/drm/imx/imx-ldb.c
index 5d2831d..ebe9abd 100644
--- a/drivers/gpu/drm/imx/imx-ldb.c
+++ b/drivers/gpu/drm/imx/imx-ldb.c
@@ -57,7 +57,11 @@ struct imx_ldb_channel {
 	struct imx_ldb *ldb;
 	struct drm_connector connector;
 	struct drm_encoder encoder;
+
+	/* Defines what is connected to the ldb, only one at a time */
 	struct drm_panel *panel;
+	struct drm_bridge *bridge;
+
 	struct device_node *child;
 	struct i2c_adapter *ddc;
 	int chno;
@@ -469,19 +473,28 @@ static int imx_ldb_register(struct drm_device *drm,
 	drm_encoder_init(drm, encoder, &imx_ldb_encoder_funcs,
 			 DRM_MODE_ENCODER_LVDS, NULL);
 
-	drm_connector_helper_add(&imx_ldb_ch->connector,
-			&imx_ldb_connector_helper_funcs);
-	drm_connector_init(drm, &imx_ldb_ch->connector,
-			   &imx_ldb_connector_funcs, DRM_MODE_CONNECTOR_LVDS);
-
 	if (imx_ldb_ch->panel) {
+		drm_connector_helper_add(&imx_ldb_ch->connector,
+				&imx_ldb_connector_helper_funcs);
+		drm_connector_init(drm, &imx_ldb_ch->connector,
+				&imx_ldb_connector_funcs,
+				DRM_MODE_CONNECTOR_LVDS);
 		ret = drm_panel_attach(imx_ldb_ch->panel,
-				       &imx_ldb_ch->connector);
+				&imx_ldb_ch->connector);
 		if (ret)
 			return ret;
 	}
 
-	drm_mode_connector_attach_encoder(&imx_ldb_ch->connector, encoder);
+	if (imx_ldb_ch->bridge) {
+		imx_ldb_ch->bridge->encoder = encoder;
+
+		imx_ldb_ch->encoder.bridge = imx_ldb_ch->bridge;
+		ret = drm_bridge_attach(drm, imx_ldb_ch->bridge);
+		if (ret) {
+			DRM_ERROR("Failed to initialize bridge with drm\n");
+			return ret;
+		}
+	}
 
 	return 0;
 }
@@ -551,6 +564,45 @@ static const struct of_device_id imx_ldb_dt_ids[] = {
 };
 MODULE_DEVICE_TABLE(of, imx_ldb_dt_ids);
 
+static int imx_ldb_panel_ddc(struct device *dev,
+		struct imx_ldb_channel *channel, struct device_node *child)
+{
+	struct device_node *ddc_node;
+	const u8 *edidp;
+	int ret;
+
+	ddc_node = of_parse_phandle(child, "ddc-i2c-bus", 0);
+	if (ddc_node) {
+		channel->ddc = of_find_i2c_adapter_by_node(ddc_node);
+		of_node_put(ddc_node);
+		if (!channel->ddc) {
+			dev_warn(dev, "failed to get ddc i2c adapter\n");
+			return -EPROBE_DEFER;
+		}
+	}
+
+	if (!channel->ddc) {
+		/* if no DDC available, fallback to hardcoded EDID */
+		dev_dbg(dev, "no ddc available\n");
+
+		edidp = of_get_property(child, "edid",
+					&channel->edid_len);
+		if (edidp) {
+			channel->edid = kmemdup(edidp,
+						channel->edid_len,
+						GFP_KERNEL);
+		} else if (!channel->panel) {
+			/* fallback to display-timings node */
+			ret = of_get_drm_display_mode(child,
+						      &channel->mode,
+						      OF_USE_NATIVE_MODE);
+			if (!ret)
+				channel->mode_valid = 1;
+		}
+	}
+	return 0;
+}
+
 static int imx_ldb_bind(struct device *dev, struct device *master, void *data)
 {
 	struct drm_device *drm = data;
@@ -558,7 +610,6 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data)
 	const struct of_device_id *of_id =
 			of_match_device(imx_ldb_dt_ids, dev);
 	struct device_node *child;
-	const u8 *edidp;
 	struct imx_ldb *imx_ldb;
 	int dual;
 	int ret;
@@ -608,7 +659,6 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data)
 
 	for_each_child_of_node(np, child) {
 		struct imx_ldb_channel *channel;
-		struct device_node *ddc_node;
 		struct device_node *ep;
 		int bus_format;
 
@@ -641,46 +691,25 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data)
 
 			remote = of_graph_get_remote_port_parent(ep);
 			of_node_put(ep);
-			if (remote)
+			if (remote) {
 				channel->panel = of_drm_find_panel(remote);
-			else
+				channel->bridge = of_drm_find_bridge(remote);
+			} else
 				return -EPROBE_DEFER;
 			of_node_put(remote);
-			if (!channel->panel) {
-				dev_err(dev, "panel not found: %s\n",
-					remote->full_name);
-				return -EPROBE_DEFER;
-			}
-		}
 
-		ddc_node = of_parse_phandle(child, "ddc-i2c-bus", 0);
-		if (ddc_node) {
-			channel->ddc = of_find_i2c_adapter_by_node(ddc_node);
-			of_node_put(ddc_node);
-			if (!channel->ddc) {
-				dev_warn(dev, "failed to get ddc i2c adapter\n");
+			if (!channel->panel && !channel->bridge) {
+				dev_err(dev, "panel/bridge not found: %s\n",
+					remote->full_name);
 				return -EPROBE_DEFER;
 			}
 		}
 
-		if (!channel->ddc) {
-			/* if no DDC available, fallback to hardcoded EDID */
-			dev_dbg(dev, "no ddc available\n");
-
-			edidp = of_get_property(child, "edid",
-						&channel->edid_len);
-			if (edidp) {
-				channel->edid = kmemdup(edidp,
-							channel->edid_len,
-							GFP_KERNEL);
-			} else if (!channel->panel) {
-				/* fallback to display-timings node */
-				ret = of_get_drm_display_mode(child,
-							      &channel->mode,
-							      OF_USE_NATIVE_MODE);
-				if (!ret)
-					channel->mode_valid = 1;
-			}
+		/* panel ddc only if there is no bridge */
+		if (!channel->bridge) {
+			ret = imx_ldb_panel_ddc(dev, channel, child);
+			if (ret)
+				return ret;
 		}
 
 		bus_format = of_get_bus_format(dev, child);
-- 
2.5.5

^ permalink raw reply related	[flat|nested] 64+ messages in thread

* [PATCH V3 2/5] dts/imx6q-b850v3: Configure IPU assignment order
  2016-07-31 19:55 ` [PATCH V3 0/5] Add driver for " Peter Senna Tschudin
  2016-07-31 19:55   ` [PATCH V3 1/5] drm/imx-ldb: Add support to drm-bridge Peter Senna Tschudin
@ 2016-07-31 19:55   ` Peter Senna Tschudin
  2016-08-01  8:54     ` Lucas Stach
  2016-07-31 19:55   ` [PATCH V3 3/5] Documentation/devicetree/bindings: b850v3_lvds_dp Peter Senna Tschudin
                     ` (2 subsequent siblings)
  4 siblings, 1 reply; 64+ messages in thread
From: Peter Senna Tschudin @ 2016-07-31 19:55 UTC (permalink / raw)
  To: robh+dt, mark.rutland, shawnguo, kernel, fabio.estevam, linux,
	airlied, p.zabel, peter.senna, davem, geert, gregkh, akpm,
	mchehab, linux, treding, architt, ykk, andrey.gusakov,
	boris.brezillon, enric.balletbo, devicetree, linux-kernel,
	linux-arm-kernel, dri-devel
  Cc: Rob Herring

As the IPU has combined limitations across multiple crtcs, and as that
can't be communicated to userspace at the moment, reorder the crtcs to
allow support to two Full-HD monitors by avoiding assigning two
monitors to a single IPU.

Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com>
Cc: Philipp Zabel <p.zabel@pengutronix.de>
Cc: Rob Herring <robh@kernel.org>
Cc: Fabio Estevam <fabio.estevam@nxp.com>
Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com>
---
Unchanged from V2.

Changes from V1:
 - New commit message

 arch/arm/boot/dts/imx6q-b850v3.dts | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/arch/arm/boot/dts/imx6q-b850v3.dts b/arch/arm/boot/dts/imx6q-b850v3.dts
index 167f744..88a70de 100644
--- a/arch/arm/boot/dts/imx6q-b850v3.dts
+++ b/arch/arm/boot/dts/imx6q-b850v3.dts
@@ -51,6 +51,11 @@
 	chosen {
 		stdout-path = &uart3;
 	};
+
+	display-subsystem {
+		compatible = "fsl,imx-display-subsystem";
+		ports = <&ipu1_di0>, <&ipu2_di0>, <&ipu1_di1>, <&ipu2_di1>;
+	};
 };
 
 &clks {
-- 
2.5.5

^ permalink raw reply related	[flat|nested] 64+ messages in thread

* [PATCH V3 3/5] Documentation/devicetree/bindings: b850v3_lvds_dp
  2016-07-31 19:55 ` [PATCH V3 0/5] Add driver for " Peter Senna Tschudin
  2016-07-31 19:55   ` [PATCH V3 1/5] drm/imx-ldb: Add support to drm-bridge Peter Senna Tschudin
  2016-07-31 19:55   ` [PATCH V3 2/5] dts/imx6q-b850v3: Configure IPU assignment order Peter Senna Tschudin
@ 2016-07-31 19:55   ` Peter Senna Tschudin
  2016-08-01 16:59     ` Rob Herring
  2016-07-31 19:55   ` [PATCH V3 4/5] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge Peter Senna Tschudin
  2016-07-31 19:55   ` [PATCH V3 5/5] dts/imx6q-b850v3: Use " Peter Senna Tschudin
  4 siblings, 1 reply; 64+ messages in thread
From: Peter Senna Tschudin @ 2016-07-31 19:55 UTC (permalink / raw)
  To: robh+dt, mark.rutland, shawnguo, kernel, fabio.estevam, linux,
	airlied, p.zabel, peter.senna, davem, geert, gregkh, akpm,
	mchehab, linux, treding, architt, ykk, andrey.gusakov,
	boris.brezillon, enric.balletbo, devicetree, linux-kernel,
	linux-arm-kernel, dri-devel
  Cc: Javier Martinez Canillas, Rob Herring

Devicetree bindings documentation for the GE B850v3 LVDS/DP++
display bridge.

Cc: Javier Martinez Canillas <javier@dowhile0.org>
Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com>
Cc: Philipp Zabel <p.zabel@pengutronix.de>
Cc: Rob Herring <robh@kernel.org>
Cc: Fabio Estevam <fabio.estevam@nxp.com>
Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com>
---
Unchanged from V2.

Changes from V1:
 - Replaced '_' by '-' in node names or compatible strings
 - Added missing @73 to the example

 .../devicetree/bindings/ge/b850v3-lvds-dp.txt      | 37 ++++++++++++++++++++++
 1 file changed, 37 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt

diff --git a/Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt b/Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt
new file mode 100644
index 0000000..f05c3e9
--- /dev/null
+++ b/Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt
@@ -0,0 +1,37 @@
+Driver for GE B850v3 LVDS/DP++ display bridge
+
+Required properties:
+  - compatible : should be "ge,b850v3-lvds-dp".
+  - reg : should contain the address used to ack the interrupts.
+  - interrupt-parent : phandle of the interrupt controller that services
+    interrupts to the device
+  - interrupts : one interrupt should be described here, as in
+    <0 IRQ_TYPE_LEVEL_HIGH>.
+  - edid-reg : should contain the address used to read edid information
+  - port : should describe the video signal connection between the host
+    and the bridge.
+
+Example:
+
+&mux2_i2c2 {
+	status = "okay";
+	clock-frequency = <100000>;
+
+	b850v3-lvds-dp-bridge@73  {
+		compatible = "ge,b850v3-lvds-dp";
+		#address-cells = <1>;
+		#size-cells = <0>;
+
+		reg = <0x73>;
+		interrupt-parent = <&gpio2>;
+		interrupts = <0 IRQ_TYPE_LEVEL_HIGH>;
+
+		edid-reg = <0x72>;
+
+		port {
+			b850v3_dp_bridge_in: endpoint {
+				remote-endpoint = <&lvds0_out>;
+			};
+		};
+	};
+};
-- 
2.5.5

^ permalink raw reply related	[flat|nested] 64+ messages in thread

* [PATCH V3 4/5] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge
  2016-07-31 19:55 ` [PATCH V3 0/5] Add driver for " Peter Senna Tschudin
                     ` (2 preceding siblings ...)
  2016-07-31 19:55   ` [PATCH V3 3/5] Documentation/devicetree/bindings: b850v3_lvds_dp Peter Senna Tschudin
@ 2016-07-31 19:55   ` Peter Senna Tschudin
  2016-07-31 19:55   ` [PATCH V3 5/5] dts/imx6q-b850v3: Use " Peter Senna Tschudin
  4 siblings, 0 replies; 64+ messages in thread
From: Peter Senna Tschudin @ 2016-07-31 19:55 UTC (permalink / raw)
  To: robh+dt, mark.rutland, shawnguo, kernel, fabio.estevam, linux,
	airlied, p.zabel, peter.senna, davem, geert, gregkh, akpm,
	mchehab, linux, treding, architt, ykk, andrey.gusakov,
	boris.brezillon, enric.balletbo, devicetree, linux-kernel,
	linux-arm-kernel, dri-devel
  Cc: Daniel Vetter, Rob Herring, Thierry Reding

Add a driver that create a drm_bridge and a drm_connector for the LVDS
to DP++ display bridge of the GE B850v3.

There are two physical bridges on the video signal pipeline: a
STDP4028(LVDS to DP) and a STDP2690(DP to DP++).  The hardware and
firmware made it complicated for this binding to comprise two device
tree nodes, as the design goal is to configure both bridges based on
the LVDS signal, which leave the driver powerless to control the video
processing pipeline. The two bridges behaves as a single bridge, and
the driver is only needed for telling the host about EDID / HPD, and
for giving the host powers to ack interrupts. The video signal pipeline
is as follows:

  Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output

Cc: Daniel Vetter <daniel.vetter@ffwll.ch>
Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com>
Cc: Philipp Zabel <p.zabel@pengutronix.de>
Cc: Rob Herring <robh@kernel.org>
Cc: Fabio Estevam <fabio.estevam@nxp.com>
CC: David Airlie <airlied@linux.ie>
CC: Thierry Reding <treding@nvidia.com>
CC: Thierry Reding <thierry.reding@gmail.com>
Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com>
---
 - Made it atomic to be applied on next-20160729 on top of Liu Ying changes
   that made imx-ldb atomic.

Changes from V1:
 - New commit message
 - Removed 3 empty entry points
 - Removed memory leak from ge_b850v3_lvds_dp_get_modes()
 - Added a lock for mode setting
 - Removed a few blank lines
 - Changed the order at Makefile and Kconfig

 MAINTAINERS                                |   8 +
 drivers/gpu/drm/bridge/Kconfig             |  11 +
 drivers/gpu/drm/bridge/Makefile            |   1 +
 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c | 397 +++++++++++++++++++++++++++++
 4 files changed, 417 insertions(+)
 create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c

diff --git a/MAINTAINERS b/MAINTAINERS
index aaf36c0..ec52e17 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -5139,6 +5139,14 @@ W:	https://linuxtv.org
 S:	Maintained
 F:	drivers/media/radio/radio-gemtek*
 
+GENERAL ELECTRIC B850V3 LVDS/DP++ BRIDGE
+M:	Peter Senna Tschudin <peter.senna@collabora.com>
+M:	Martin Donnelly <martin.donnelly@ge.com>
+M:	Martyn Welch <martyn.welch@collabora.co.uk>
+S:	Maintained
+F:	drivers/gpu/drm/bridge/ge_b850v3_dp2.c
+F:	Documentation/devicetree/bindings/ge/b850v3_dp2_bridge.txt
+
 GENERIC GPIO I2C DRIVER
 M:	Haavard Skinnemoen <hskinnemoen@gmail.com>
 S:	Supported
diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
index b590e67..b4b70fb 100644
--- a/drivers/gpu/drm/bridge/Kconfig
+++ b/drivers/gpu/drm/bridge/Kconfig
@@ -32,6 +32,17 @@ config DRM_DW_HDMI_AHB_AUDIO
 	  Designware HDMI block.  This is used in conjunction with
 	  the i.MX6 HDMI driver.
 
+config DRM_GE_B850V3_LVDS_DP
+	tristate "GE B850v3 LVDS to DP++ display bridge"
+	depends on OF
+	select DRM_KMS_HELPER
+	select DRM_PANEL
+	---help---
+          This is a driver for the display bridge of
+          GE B850v3 that convert dual channel LVDS
+          to DP++. This is used with the i.MX6 imx-ldb
+          driver.
+
 config DRM_NXP_PTN3460
 	tristate "NXP PTN3460 DP/LVDS bridge"
 	depends on OF
diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
index efdb07e..b9606f3 100644
--- a/drivers/gpu/drm/bridge/Makefile
+++ b/drivers/gpu/drm/bridge/Makefile
@@ -3,6 +3,7 @@ ccflags-y := -Iinclude/drm
 obj-$(CONFIG_DRM_ANALOGIX_ANX78XX) += analogix-anx78xx.o
 obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o
 obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o
+obj-$(CONFIG_DRM_GE_B850V3_LVDS_DP) += ge_b850v3_lvds_dp.o
 obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o
 obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o
 obj-$(CONFIG_DRM_SII902X) += sii902x.o
diff --git a/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c
new file mode 100644
index 0000000..eee8eac
--- /dev/null
+++ b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c
@@ -0,0 +1,397 @@
+/*
+ * Driver for GE B850v3 DP display bridge
+
+ * Copyright (c) 2016, Collabora Ltd.
+ * Copyright (c) 2016, General Electric Company
+
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+ * This driver creates a drm_bridge and a drm_connector for the LVDS to DP++
+ * display bridge of the GE B850v3. There are two physical bridges on the video
+ * signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). However
+ * the physical bridges are automatically configured by the input video signal,
+ * and the driver has no access to the video processing pipeline. The driver is
+ * only needed to read EDID from the STDP2690 and to handle HPD events from the
+ * STDP4028. The driver communicates with both bridges over i2c. The video
+ * signal pipeline is as follows:
+ *
+ *   Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output
+ *
+ */
+
+#include <linux/gpio.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_edid.h>
+#include <drm/drmP.h>
+
+/*
+ * 220Mhz is a limitation of the host, as the bridge is capable of up to
+ * 330Mhz. See section 9.2.1.2.4 of the i.MX 6Dual/6Quad Applications
+ * Processor Reference Manual for more information about the 220Mhz limit.
+ * The imx-ldb driver will warn about clocks over 170Mhz, but it seem to work
+ * fine.
+ */
+#define MAX_PIXEL_CLOCK 220000
+
+#define EDID_EXT_BLOCK_CNT 0x7E
+
+#define STDP4028_IRQ_OUT_CONF_REG 0x02
+#define STDP4028_DPTX_IRQ_EN_REG 0x3C
+#define STDP4028_DPTX_IRQ_STS_REG 0x3D
+#define STDP4028_DPTX_STS_REG 0x3E
+
+#define STDP4028_DPTX_DP_IRQ_EN 0x1000
+
+#define STDP4028_DPTX_HOTPLUG_IRQ_EN 0x0400
+#define STDP4028_DPTX_LINK_CH_IRQ_EN 0x2000
+#define STDP4028_DPTX_IRQ_CONFIG \
+		(STDP4028_DPTX_LINK_CH_IRQ_EN | STDP4028_DPTX_HOTPLUG_IRQ_EN)
+
+#define STDP4028_DPTX_HOTPLUG_STS 0x0200
+#define STDP4028_DPTX_LINK_STS 0x1000
+#define STDP4028_CON_STATE_CONNECTED \
+		(STDP4028_DPTX_HOTPLUG_STS | STDP4028_DPTX_LINK_STS)
+
+#define STDP4028_DPTX_HOTPLUG_CH_STS 0x0400
+#define STDP4028_DPTX_LINK_CH_STS 0x2000
+#define STDP4028_DPTX_IRQ_CLEAR \
+		(STDP4028_DPTX_LINK_CH_STS | STDP4028_DPTX_HOTPLUG_CH_STS)
+
+struct ge_b850v3_lvds_dp {
+	struct drm_connector connector;
+	struct drm_bridge bridge;
+	struct i2c_client *ge_b850v3_lvds_dp_i2c;
+	struct i2c_client *edid_i2c;
+	struct edid *edid;
+	struct mutex lock;
+};
+
+static inline struct ge_b850v3_lvds_dp *
+		bridge_to_ge_b850v3_lvds_dp(struct drm_bridge *bridge)
+{
+	return container_of(bridge, struct ge_b850v3_lvds_dp, bridge);
+}
+
+static inline struct ge_b850v3_lvds_dp *
+		connector_to_ge_b850v3_lvds_dp(struct drm_connector *connector)
+{
+	return container_of(connector, struct ge_b850v3_lvds_dp, connector);
+}
+
+u8 *stdp2690_get_edid(struct i2c_client *client)
+{
+	struct i2c_adapter *adapter = client->adapter;
+	unsigned char start = 0x00;
+	unsigned int total_size;
+	u8 *block = kmalloc(EDID_LENGTH, GFP_KERNEL);
+
+	struct i2c_msg msgs[] = {
+		{
+			.addr	= client->addr,
+			.flags	= 0,
+			.len	= 1,
+			.buf	= &start,
+		}, {
+			.addr	= client->addr,
+			.flags	= I2C_M_RD,
+			.len	= EDID_LENGTH,
+			.buf	= block,
+		}
+	};
+
+	if (!block)
+		return NULL;
+
+	if (i2c_transfer(adapter, msgs, 2) != 2) {
+		DRM_ERROR("Unable to read EDID.\n");
+		goto err;
+	}
+
+	if (!drm_edid_block_valid(block, 0, false, NULL)) {
+		DRM_ERROR("Invalid EDID block\n");
+		goto err;
+	}
+
+	total_size = (block[EDID_EXT_BLOCK_CNT] + 1) * EDID_LENGTH;
+	if (total_size > EDID_LENGTH) {
+		kfree(block);
+		block = kmalloc(total_size, GFP_KERNEL);
+		if (!block)
+			return NULL;
+
+		/* Yes, read the entire buffer, and do not skip the first
+		 * EDID_LENGTH bytes.
+		 */
+		start = 0x00;
+		msgs[1].len = total_size;
+		msgs[1].buf = block;
+
+		if (i2c_transfer(adapter, msgs, 2) != 2) {
+			DRM_ERROR("Unable to read EDID extension blocks.\n");
+			goto err;
+		}
+	}
+
+	return block;
+
+err:
+	kfree(block);
+	return NULL;
+}
+
+static int ge_b850v3_lvds_dp_get_modes(struct drm_connector *connector)
+{
+	struct ge_b850v3_lvds_dp *ptn_bridge;
+	struct i2c_client *client;
+	int num_modes = 0;
+
+	ptn_bridge = connector_to_ge_b850v3_lvds_dp(connector);
+	client = ptn_bridge->edid_i2c;
+
+	mutex_lock(&ptn_bridge->lock);
+
+	kfree(ptn_bridge->edid);
+	ptn_bridge->edid = (struct edid *) stdp2690_get_edid(client);
+
+	if (ptn_bridge->edid) {
+		drm_mode_connector_update_edid_property(connector,
+				ptn_bridge->edid);
+		num_modes = drm_add_edid_modes(connector, ptn_bridge->edid);
+	}
+
+	mutex_unlock(&ptn_bridge->lock);
+
+	return num_modes;
+}
+
+
+static enum drm_mode_status ge_b850v3_lvds_dp_mode_valid(
+		struct drm_connector *connector, struct drm_display_mode *mode)
+{
+	if (mode->clock > MAX_PIXEL_CLOCK) {
+		DRM_INFO("The pixel clock for the mode %s is too high, and not supported.",
+				mode->name);
+		return MODE_CLOCK_HIGH;
+	}
+
+	return MODE_OK;
+}
+
+static const struct
+drm_connector_helper_funcs ge_b850v3_lvds_dp_connector_helper_funcs = {
+	.get_modes = ge_b850v3_lvds_dp_get_modes,
+	.mode_valid = ge_b850v3_lvds_dp_mode_valid,
+};
+
+static enum drm_connector_status ge_b850v3_lvds_dp_detect(
+		struct drm_connector *connector, bool force)
+{
+	struct ge_b850v3_lvds_dp *ptn_bridge =
+			connector_to_ge_b850v3_lvds_dp(connector);
+	struct i2c_client *ge_b850v3_lvds_dp_i2c =
+			ptn_bridge->ge_b850v3_lvds_dp_i2c;
+	s32 link_state;
+
+	link_state = i2c_smbus_read_word_data(ge_b850v3_lvds_dp_i2c,
+			STDP4028_DPTX_STS_REG);
+
+	if (link_state == STDP4028_CON_STATE_CONNECTED)
+		return connector_status_connected;
+
+	if (link_state == 0)
+		return connector_status_disconnected;
+
+	return connector_status_unknown;
+}
+
+static const struct drm_connector_funcs ge_b850v3_lvds_dp_connector_funcs = {
+	.dpms = drm_atomic_helper_connector_dpms,
+	.fill_modes = drm_helper_probe_single_connector_modes,
+	.detect = ge_b850v3_lvds_dp_detect,
+	.destroy = drm_connector_cleanup,
+	.reset = drm_atomic_helper_connector_reset,
+	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
+static irqreturn_t ge_b850v3_lvds_dp_irq_handler(int irq, void *dev_id)
+{
+	struct ge_b850v3_lvds_dp *ptn_bridge = dev_id;
+	struct i2c_client *ge_b850v3_lvds_dp_i2c
+			= ptn_bridge->ge_b850v3_lvds_dp_i2c;
+
+	mutex_lock(&ptn_bridge->lock);
+
+	i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c,
+			STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR);
+
+	mutex_unlock(&ptn_bridge->lock);
+
+	if (ptn_bridge->connector.dev)
+		drm_kms_helper_hotplug_event(ptn_bridge->connector.dev);
+
+	return IRQ_HANDLED;
+}
+
+static int ge_b850v3_lvds_dp_attach(struct drm_bridge *bridge)
+{
+	struct ge_b850v3_lvds_dp *ptn_bridge
+			= bridge_to_ge_b850v3_lvds_dp(bridge);
+	struct drm_connector *connector = &ptn_bridge->connector;
+	struct i2c_client *ge_b850v3_lvds_dp_i2c
+			= ptn_bridge->ge_b850v3_lvds_dp_i2c;
+	int ret;
+
+	if (!bridge->encoder) {
+		DRM_ERROR("Parent encoder object not found");
+		return -ENODEV;
+	}
+
+	connector->polled = DRM_CONNECTOR_POLL_HPD;
+
+	drm_connector_helper_add(connector,
+			&ge_b850v3_lvds_dp_connector_helper_funcs);
+
+	ret = drm_connector_init(bridge->dev, connector,
+			&ge_b850v3_lvds_dp_connector_funcs,
+			DRM_MODE_CONNECTOR_DisplayPort);
+	if (ret) {
+		DRM_ERROR("Failed to initialize connector with drm\n");
+		return ret;
+	}
+
+	drm_connector_register(connector);
+	ret = drm_mode_connector_attach_encoder(connector, bridge->encoder);
+	if (ret)
+		return ret;
+
+	drm_bridge_enable(bridge);
+	if (ge_b850v3_lvds_dp_i2c->irq) {
+		drm_helper_hpd_irq_event(connector->dev);
+
+		ret = devm_request_threaded_irq(&ge_b850v3_lvds_dp_i2c->dev,
+				ge_b850v3_lvds_dp_i2c->irq, NULL,
+				ge_b850v3_lvds_dp_irq_handler,
+				IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
+				"ge-b850v3-lvds-dp", ptn_bridge);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static const struct drm_bridge_funcs ge_b850v3_lvds_dp_funcs = {
+	.attach = ge_b850v3_lvds_dp_attach,
+};
+
+static int ge_b850v3_lvds_dp_probe(struct i2c_client *ge_b850v3_lvds_dp_i2c,
+				const struct i2c_device_id *id)
+{
+	struct device *dev = &ge_b850v3_lvds_dp_i2c->dev;
+	struct ge_b850v3_lvds_dp *ptn_bridge;
+	int ret;
+	u32 edid_i2c_reg;
+
+	ptn_bridge = devm_kzalloc(dev, sizeof(*ptn_bridge), GFP_KERNEL);
+	if (!ptn_bridge)
+		return -ENOMEM;
+
+	mutex_init(&ptn_bridge->lock);
+
+	ptn_bridge->ge_b850v3_lvds_dp_i2c = ge_b850v3_lvds_dp_i2c;
+	ptn_bridge->bridge.driver_private = ptn_bridge;
+	i2c_set_clientdata(ge_b850v3_lvds_dp_i2c, ptn_bridge);
+
+	ret = of_property_read_u32(dev->of_node, "edid-reg", &edid_i2c_reg);
+	if (ret) {
+		dev_err(dev, "edid-reg not specified, aborting...\n");
+		return -ENODEV;
+	}
+
+	ptn_bridge->edid_i2c = devm_kzalloc(dev,
+			sizeof(struct i2c_client), GFP_KERNEL);
+
+	if (!ptn_bridge->edid_i2c)
+		return -ENOMEM;
+
+	memcpy(ptn_bridge->edid_i2c, ge_b850v3_lvds_dp_i2c,
+			sizeof(struct i2c_client));
+
+	ptn_bridge->edid_i2c->addr = (unsigned short) edid_i2c_reg;
+
+	/* Configures the bridge to re-enable interrupts after each ack */
+	i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c,
+			STDP4028_IRQ_OUT_CONF_REG, STDP4028_DPTX_DP_IRQ_EN);
+	i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c,
+			STDP4028_DPTX_IRQ_EN_REG, STDP4028_DPTX_IRQ_CONFIG);
+
+	/* Clear pending interrupts since power up. */
+	i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c,
+			STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR);
+
+	ptn_bridge->bridge.funcs = &ge_b850v3_lvds_dp_funcs;
+	ptn_bridge->bridge.of_node = dev->of_node;
+	ret = drm_bridge_add(&ptn_bridge->bridge);
+	if (ret) {
+		DRM_ERROR("Failed to add bridge\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static int ge_b850v3_lvds_dp_remove(struct i2c_client *ge_b850v3_lvds_dp_i2c)
+{
+	struct ge_b850v3_lvds_dp *ptn_bridge =
+		i2c_get_clientdata(ge_b850v3_lvds_dp_i2c);
+
+	drm_bridge_remove(&ptn_bridge->bridge);
+
+	kfree(ptn_bridge->edid);
+
+	return 0;
+}
+
+static const struct i2c_device_id ge_b850v3_lvds_dp_i2c_table[] = {
+	{"b850v3-lvds-dp", 0},
+	{},
+};
+MODULE_DEVICE_TABLE(i2c, ge_b850v3_lvds_dp_i2c_table);
+
+static const struct of_device_id ge_b850v3_lvds_dp_match[] = {
+	{ .compatible = "ge,b850v3-lvds-dp" },
+	{},
+};
+MODULE_DEVICE_TABLE(of, ge_b850v3_lvds_dp_match);
+
+static struct i2c_driver ge_b850v3_lvds_dp_driver = {
+	.id_table	= ge_b850v3_lvds_dp_i2c_table,
+	.probe		= ge_b850v3_lvds_dp_probe,
+	.remove		= ge_b850v3_lvds_dp_remove,
+	.driver		= {
+		.name		= "ge,b850v3-lvds-dp",
+		.of_match_table = ge_b850v3_lvds_dp_match,
+	},
+};
+module_i2c_driver(ge_b850v3_lvds_dp_driver);
+
+MODULE_AUTHOR("Peter Senna Tschudin <peter.senna@collabora.com>");
+MODULE_AUTHOR("Martyn Welch <martyn.welch@collabora.co.uk>");
+MODULE_DESCRIPTION("GE LVDS to DP++ display bridge)");
+MODULE_LICENSE("GPL v2");
-- 
2.5.5

^ permalink raw reply related	[flat|nested] 64+ messages in thread

* [PATCH V3 5/5] dts/imx6q-b850v3: Use GE B850v3 LVDS/DP++ Bridge
  2016-07-31 19:55 ` [PATCH V3 0/5] Add driver for " Peter Senna Tschudin
                     ` (3 preceding siblings ...)
  2016-07-31 19:55   ` [PATCH V3 4/5] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge Peter Senna Tschudin
@ 2016-07-31 19:55   ` Peter Senna Tschudin
  4 siblings, 0 replies; 64+ messages in thread
From: Peter Senna Tschudin @ 2016-07-31 19:55 UTC (permalink / raw)
  To: robh+dt, mark.rutland, shawnguo, kernel, fabio.estevam, linux,
	airlied, p.zabel, peter.senna, davem, geert, gregkh, akpm,
	mchehab, linux, treding, architt, ykk, andrey.gusakov,
	boris.brezillon, enric.balletbo, devicetree, linux-kernel,
	linux-arm-kernel, dri-devel
  Cc: Javier Martinez Canillas, Rob Herring

Configures the GE B850v3 LVDS/DP++ bridge on the dts file.

Cc: Javier Martinez Canillas <javier@dowhile0.org>
Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com>
Cc: Philipp Zabel <p.zabel@pengutronix.de>
Cc: Rob Herring <robh@kernel.org>
Cc: Fabio Estevam <fabio.estevam@nxp.com>
Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com>
---
Unchanged from V2.

Changes from V1:
 - Replaced '_' by '-' in node names or compatible strings
 - Added missing @73 to b850v3-lvds-dp-bridge

 arch/arm/boot/dts/imx6q-b850v3.dts | 30 ++++++++++++++++++++++++++++++
 1 file changed, 30 insertions(+)

diff --git a/arch/arm/boot/dts/imx6q-b850v3.dts b/arch/arm/boot/dts/imx6q-b850v3.dts
index 88a70de..10dfc3b 100644
--- a/arch/arm/boot/dts/imx6q-b850v3.dts
+++ b/arch/arm/boot/dts/imx6q-b850v3.dts
@@ -77,6 +77,13 @@
 		fsl,data-mapping = "spwg";
 		fsl,data-width = <24>;
 		status = "okay";
+
+		port@4 {
+			reg = <4>;
+			lvds0_out: endpoint {
+				remote-endpoint = <&b850v3_lvds_dp_bridge_in>;
+			};
+		};
 	};
 };
 
@@ -147,3 +154,26 @@
 		reg = <0x4a>;
 	};
 };
+
+&mux2_i2c2 {
+	status = "okay";
+	clock-frequency = <100000>;
+
+	b850v3-lvds-dp-bridge@73 {
+		compatible = "ge,b850v3-lvds-dp";
+		#address-cells = <1>;
+		#size-cells = <0>;
+
+		reg = <0x73>;
+		interrupt-parent = <&gpio2>;
+		interrupts = <0 IRQ_TYPE_LEVEL_HIGH>;
+
+		edid-reg = <0x72>;
+
+		port {
+			b850v3_lvds_dp_bridge_in: endpoint {
+				remote-endpoint = <&lvds0_out>;
+			};
+		};
+	};
+};
-- 
2.5.5

^ permalink raw reply related	[flat|nested] 64+ messages in thread

* Re: [PATCH V3 2/5] dts/imx6q-b850v3: Configure IPU assignment order
  2016-07-31 19:55   ` [PATCH V3 2/5] dts/imx6q-b850v3: Configure IPU assignment order Peter Senna Tschudin
@ 2016-08-01  8:54     ` Lucas Stach
  2016-08-01 12:30       ` Peter Senna Tschudin
  0 siblings, 1 reply; 64+ messages in thread
From: Lucas Stach @ 2016-08-01  8:54 UTC (permalink / raw)
  To: Peter Senna Tschudin
  Cc: robh+dt, mark.rutland, shawnguo, kernel, fabio.estevam, linux,
	airlied, p.zabel, davem, geert, gregkh, akpm, mchehab, linux,
	treding, architt, ykk, andrey.gusakov, boris.brezillon,
	enric.balletbo, devicetree, linux-kernel, linux-arm-kernel,
	dri-devel, Rob Herring

Am Sonntag, den 31.07.2016, 21:55 +0200 schrieb Peter Senna Tschudin:
> As the IPU has combined limitations across multiple crtcs, and as that
> can't be communicated to userspace at the moment, reorder the crtcs to
> allow support to two Full-HD monitors by avoiding assigning two
> monitors to a single IPU.
> 
> Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com>
> Cc: Philipp Zabel <p.zabel@pengutronix.de>
> Cc: Rob Herring <robh@kernel.org>
> Cc: Fabio Estevam <fabio.estevam@nxp.com>
> Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com>

NACK. This is a userspace issue. Changing the assignment order of the
CRTCs just shifts the failure to a userspace that want to use CRTC 0 and
2 now.

imx-drm just got atomic support and with the atomic check it should be
possible to inform userspace in a reasonable way about such issues.

Regards,
Lucas

> ---
> Unchanged from V2.
> 
> Changes from V1:
>  - New commit message
> 
>  arch/arm/boot/dts/imx6q-b850v3.dts | 5 +++++
>  1 file changed, 5 insertions(+)
> 
> diff --git a/arch/arm/boot/dts/imx6q-b850v3.dts b/arch/arm/boot/dts/imx6q-b850v3.dts
> index 167f744..88a70de 100644
> --- a/arch/arm/boot/dts/imx6q-b850v3.dts
> +++ b/arch/arm/boot/dts/imx6q-b850v3.dts
> @@ -51,6 +51,11 @@
>  	chosen {
>  		stdout-path = &uart3;
>  	};
> +
> +	display-subsystem {
> +		compatible = "fsl,imx-display-subsystem";
> +		ports = <&ipu1_di0>, <&ipu2_di0>, <&ipu1_di1>, <&ipu2_di1>;
> +	};
>  };
>  
>  &clks {

^ permalink raw reply	[flat|nested] 64+ messages in thread

* Re: [PATCH V3 1/5] drm/imx-ldb: Add support to drm-bridge
  2016-07-31 19:55   ` [PATCH V3 1/5] drm/imx-ldb: Add support to drm-bridge Peter Senna Tschudin
@ 2016-08-01 10:21     ` Philipp Zabel
  2016-08-02 18:46       ` Peter Senna Tschudin
  0 siblings, 1 reply; 64+ messages in thread
From: Philipp Zabel @ 2016-08-01 10:21 UTC (permalink / raw)
  To: Peter Senna Tschudin
  Cc: robh+dt, mark.rutland, shawnguo, kernel, fabio.estevam, linux,
	airlied, davem, geert, gregkh, akpm, mchehab, linux, treding,
	architt, ykk, andrey.gusakov, boris.brezillon, enric.balletbo,
	devicetree, linux-kernel, linux-arm-kernel, dri-devel,
	Rob Herring, Thierry Reding

Am Sonntag, den 31.07.2016, 21:55 +0200 schrieb Peter Senna Tschudin:
> Add support to attach a drm_bridge to imx-ldb in addition to
> existing support to attach a LVDS panel.
> 
> This patch does a simple code refactoring by moving code
> from for_each_child_of_node iterator to a new function named
> imx_ldb_panel_ddc(). This was necessary to allow the panel ddc
> code to run only when the imx_ldb is not attached to a bridge.
> 
> Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com>
> Cc: Philipp Zabel <p.zabel@pengutronix.de>
> Cc: Rob Herring <robh@kernel.org>
> Cc: Fabio Estevam <fabio.estevam@nxp.com>
> Cc: David Airlie <airlied@linux.ie>
> Cc: Thierry Reding <treding@nvidia.com>
> Cc: Thierry Reding <thierry.reding@gmail.com>
> Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com>
> ---
> Changes from V2:
>  - Updated to be aplied on top of Liu Ying changes that made imx-ldb atomic.
>  - Tested on next-20160729.
[...]
> @@ -469,19 +473,28 @@ static int imx_ldb_register(struct drm_device *drm,
>  	drm_encoder_init(drm, encoder, &imx_ldb_encoder_funcs,
>  			 DRM_MODE_ENCODER_LVDS, NULL);
>  
> -	drm_connector_helper_add(&imx_ldb_ch->connector,
> -			&imx_ldb_connector_helper_funcs);
> -	drm_connector_init(drm, &imx_ldb_ch->connector,
> -			   &imx_ldb_connector_funcs, DRM_MODE_CONNECTOR_LVDS);
> -
>  	if (imx_ldb_ch->panel) {
> +		drm_connector_helper_add(&imx_ldb_ch->connector,
> +				&imx_ldb_connector_helper_funcs);
> +		drm_connector_init(drm, &imx_ldb_ch->connector,
> +				&imx_ldb_connector_funcs,
> +				DRM_MODE_CONNECTOR_LVDS);

This is still not right. We want to add the connector whenever there is
no bridge that brings its own, not only when there is a panel. For
historical reasons, the ldb driver can also work without a panel.

>  		ret = drm_panel_attach(imx_ldb_ch->panel,
> -				       &imx_ldb_ch->connector);
> +				&imx_ldb_ch->connector);

What is the purpose of this change?

>  		if (ret)
>  			return ret;
>  	}
>  
> -	drm_mode_connector_attach_encoder(&imx_ldb_ch->connector, encoder);

Where is this gone?

> +	if (imx_ldb_ch->bridge) {
> +		imx_ldb_ch->bridge->encoder = encoder;
> +
> +		imx_ldb_ch->encoder.bridge = imx_ldb_ch->bridge;
> +		ret = drm_bridge_attach(drm, imx_ldb_ch->bridge);
> +		if (ret) {
> +			DRM_ERROR("Failed to initialize bridge with drm\n");
> +			return ret;
> +		}
> +	}
>  
>  	return 0;
>  }

regards
Philipp

^ permalink raw reply	[flat|nested] 64+ messages in thread

* Re: [PATCH V3 2/5] dts/imx6q-b850v3: Configure IPU assignment order
  2016-08-01  8:54     ` Lucas Stach
@ 2016-08-01 12:30       ` Peter Senna Tschudin
  2016-08-02 13:13         ` Daniel Vetter
  0 siblings, 1 reply; 64+ messages in thread
From: Peter Senna Tschudin @ 2016-08-01 12:30 UTC (permalink / raw)
  To: Lucas Stach
  Cc: andrey.gusakov, boris.brezillon, mchehab, dri-devel,
	mark.rutland, airlied, treding, geert, devicetree, Daniel Stone,
	kernel, ykk, akpm, linux-arm-kernel, robh+dt, linux, linux,
	davem, enric.balletbo, Rob Herring, shawnguo, p.zabel, architt,
	gregkh, linux-kernel, fabio.estevam, Peter Senna Tschudin

Hi Lucas,

Thank you for the prompt review.
 
On Monday, August 1, 2016 10:54 CEST, Lucas Stach <l.stach@pengutronix.de> wrote: 
 
> Am Sonntag, den 31.07.2016, 21:55 +0200 schrieb Peter Senna Tschudin:
> > As the IPU has combined limitations across multiple crtcs, and as that
> > can't be communicated to userspace at the moment, reorder the crtcs to
> > allow support to two Full-HD monitors by avoiding assigning two
> > monitors to a single IPU.
> > 
> > Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com>
> > Cc: Philipp Zabel <p.zabel@pengutronix.de>
> > Cc: Rob Herring <robh@kernel.org>
> > Cc: Fabio Estevam <fabio.estevam@nxp.com>
> > Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com>
> 
> NACK. This is a userspace issue. Changing the assignment order of the
> CRTCs just shifts the failure to a userspace that want to use CRTC 0 and
> 2 now.

Err, yeah user space issue... But how the kernel is currently telling user space about what exactly went wrong and how user space might fix it? How Weston(our user space) is going to know  that reshuffling crtcs is going to lead to success; how could it? I guess some  platform-specific code in user space is needed for this to work...

> 
> imx-drm just got atomic support and with the atomic check it should be
> possible to inform userspace in a reasonable way about such issues.

Should be possible, but I guess it isn't, and wont be until a considerable effort is put on both kernel and user space. Or am I missing something? What do you propose?

I got inspiration from: arch/arm/boot/dts/imx6q.dtsi
...
        display-subsystem {
                compatible = "fsl,imx-display-subsystem";
                ports = <&ipu1_di0>, <&ipu1_di1>, <&ipu2_di0>, <&ipu2_di1>;
        };
...

This is there for more than 2 years now, and I get that the idea here is not ordering, but just declaring.

However even if this patch is not the perfect solution, it allows us to stay close to upstream now without creating problems(does it create any issue?).

Can you reconsider or propose a concrete solution that is not more complex than our entire driver?

Thanks a lot!

^ permalink raw reply	[flat|nested] 64+ messages in thread

* Re: [PATCH V3 3/5] Documentation/devicetree/bindings: b850v3_lvds_dp
  2016-07-31 19:55   ` [PATCH V3 3/5] Documentation/devicetree/bindings: b850v3_lvds_dp Peter Senna Tschudin
@ 2016-08-01 16:59     ` Rob Herring
  0 siblings, 0 replies; 64+ messages in thread
From: Rob Herring @ 2016-08-01 16:59 UTC (permalink / raw)
  To: Peter Senna Tschudin
  Cc: mark.rutland, shawnguo, kernel, fabio.estevam, linux, airlied,
	p.zabel, davem, geert, gregkh, akpm, mchehab, linux, treding,
	architt, ykk, andrey.gusakov, boris.brezillon, enric.balletbo,
	devicetree, linux-kernel, linux-arm-kernel, dri-devel,
	Javier Martinez Canillas

On Sun, Jul 31, 2016 at 09:55:36PM +0200, Peter Senna Tschudin wrote:
> Devicetree bindings documentation for the GE B850v3 LVDS/DP++
> display bridge.
> 
> Cc: Javier Martinez Canillas <javier@dowhile0.org>
> Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com>
> Cc: Philipp Zabel <p.zabel@pengutronix.de>
> Cc: Rob Herring <robh@kernel.org>
> Cc: Fabio Estevam <fabio.estevam@nxp.com>
> Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com>
> ---
> Unchanged from V2.
> 
> Changes from V1:
>  - Replaced '_' by '-' in node names or compatible strings
>  - Added missing @73 to the example
> 
>  .../devicetree/bindings/ge/b850v3-lvds-dp.txt      | 37 ++++++++++++++++++++++
>  1 file changed, 37 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt

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

^ permalink raw reply	[flat|nested] 64+ messages in thread

* Re: [PATCH V3 2/5] dts/imx6q-b850v3: Configure IPU assignment order
  2016-08-01 12:30       ` Peter Senna Tschudin
@ 2016-08-02 13:13         ` Daniel Vetter
  0 siblings, 0 replies; 64+ messages in thread
From: Daniel Vetter @ 2016-08-02 13:13 UTC (permalink / raw)
  To: Peter Senna Tschudin
  Cc: Lucas Stach, mark.rutland, dri-devel, linux, geert,
	Peter Senna Tschudin, treding, linux, devicetree, kernel,
	andrey.gusakov, fabio.estevam, robh+dt, mchehab,
	linux-arm-kernel, gregkh, linux-kernel, Daniel Stone,
	enric.balletbo, akpm, shawnguo, davem

On Mon, Aug 01, 2016 at 01:30:57PM +0100, Peter Senna Tschudin wrote:
> Hi Lucas,
> 
> Thank you for the prompt review.
>  
> On Monday, August 1, 2016 10:54 CEST, Lucas Stach <l.stach@pengutronix.de> wrote: 
>  
> > Am Sonntag, den 31.07.2016, 21:55 +0200 schrieb Peter Senna Tschudin:
> > > As the IPU has combined limitations across multiple crtcs, and as that
> > > can't be communicated to userspace at the moment, reorder the crtcs to
> > > allow support to two Full-HD monitors by avoiding assigning two
> > > monitors to a single IPU.
> > > 
> > > Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com>
> > > Cc: Philipp Zabel <p.zabel@pengutronix.de>
> > > Cc: Rob Herring <robh@kernel.org>
> > > Cc: Fabio Estevam <fabio.estevam@nxp.com>
> > > Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com>
> > 
> > NACK. This is a userspace issue. Changing the assignment order of the
> > CRTCs just shifts the failure to a userspace that want to use CRTC 0 and
> > 2 now.
> 
> Err, yeah user space issue... But how the kernel is currently telling
> user space about what exactly went wrong and how user space might fix
> it? How Weston(our user space) is going to know  that reshuffling crtcs
> is going to lead to success; how could it? I guess some
> platform-specific code in user space is needed for this to work...

atomic with the TEST_ONLY flag. This is what userspace should do:
1. submit atomic TEST_ONLY request with 1 screen on first crtc
2. If reject, move to another crtc or if those are all tried, reduce mode
(this should never happen for the 1st screen, kernel /should/ filter out
should impossible modes. But for 2nd/3rd screen combined modes might not
all work).
3. Once you have a successfuly config for the 1st screen, add 2nd screen.
4. goto 2 with that 2nd screen.
5. Once all the screens have a mode/crtc they can be used on, do the real
atomic request without TEST_ONLY.

Like Lucas said, no need to fumble around with ordering of CRTCs. The only
thing we do in the driver is move the preferred output (if there is any)
to the front of the _connector_ list, e.g. for built-in panels.

No need at all for platform specific code.

Cheers, Daniel
> 
> > 
> > imx-drm just got atomic support and with the atomic check it should be
> > possible to inform userspace in a reasonable way about such issues.
> 
> Should be possible, but I guess it isn't, and wont be until a considerable effort is put on both kernel and user space. Or am I missing something? What do you propose?
> 
> I got inspiration from: arch/arm/boot/dts/imx6q.dtsi
> ...
>         display-subsystem {
>                 compatible = "fsl,imx-display-subsystem";
>                 ports = <&ipu1_di0>, <&ipu1_di1>, <&ipu2_di0>, <&ipu2_di1>;
>         };
> ...
> 
> This is there for more than 2 years now, and I get that the idea here is not ordering, but just declaring.
> 
> However even if this patch is not the perfect solution, it allows us to stay close to upstream now without creating problems(does it create any issue?).
> 
> Can you reconsider or propose a concrete solution that is not more complex than our entire driver?
> 
> Thanks a lot!
> 
> _______________________________________________
> dri-devel mailing list
> dri-devel@lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/dri-devel

-- 
Daniel Vetter
Software Engineer, Intel Corporation
http://blog.ffwll.ch

^ permalink raw reply	[flat|nested] 64+ messages in thread

* Re: [PATCH V3 1/5] drm/imx-ldb: Add support to drm-bridge
  2016-08-01 10:21     ` Philipp Zabel
@ 2016-08-02 18:46       ` Peter Senna Tschudin
  0 siblings, 0 replies; 64+ messages in thread
From: Peter Senna Tschudin @ 2016-08-02 18:46 UTC (permalink / raw)
  To: Philipp Zabel
  Cc: robh+dt, mark.rutland, shawnguo, kernel, fabio.estevam, linux,
	airlied, davem, geert, gregkh, akpm, mchehab, linux, treding,
	architt, ykk, andrey.gusakov, boris.brezillon, enric.balletbo,
	devicetree, linux-kernel, linux-arm-kernel, dri-devel,
	Rob Herring, Thierry Reding

Hi Philipp,

Thank you for the review. I'm preparing V4, what about this:

---
 drivers/gpu/drm/imx/imx-ldb.c | 118 ++++++++++++++++++++++++++++--------------
 1 file changed, 78 insertions(+), 40 deletions(-)

diff --git a/drivers/gpu/drm/imx/imx-ldb.c b/drivers/gpu/drm/imx/imx-ldb.c
index b03919e..4a33077 100644
--- a/drivers/gpu/drm/imx/imx-ldb.c
+++ b/drivers/gpu/drm/imx/imx-ldb.c
@@ -57,7 +57,11 @@ struct imx_ldb_channel {
 	struct imx_ldb *ldb;
 	struct drm_connector connector;
 	struct drm_encoder encoder;
+
+	/* Defines what is connected to the ldb, only one at a time */
 	struct drm_panel *panel;
+	struct drm_bridge *bridge;
+
 	struct device_node *child;
 	struct i2c_adapter *ddc;
 	int chno;
@@ -466,10 +470,30 @@ static int imx_ldb_register(struct drm_device *drm,
 	drm_encoder_init(drm, encoder, &imx_ldb_encoder_funcs,
 			 DRM_MODE_ENCODER_LVDS, NULL);
 
-	drm_connector_helper_add(&imx_ldb_ch->connector,
-			&imx_ldb_connector_helper_funcs);
-	drm_connector_init(drm, &imx_ldb_ch->connector,
-			   &imx_ldb_connector_funcs, DRM_MODE_CONNECTOR_LVDS);
+	if (imx_ldb_ch->bridge) {
+		imx_ldb_ch->bridge->encoder = encoder;
+
+		imx_ldb_ch->encoder.bridge = imx_ldb_ch->bridge;
+		ret = drm_bridge_attach(drm, imx_ldb_ch->bridge);
+		if (ret) {
+			DRM_ERROR("Failed to initialize bridge with drm\n");
+			return ret;
+		}
+	} else {
+		/*
+		 * We want to add the connector whenever there is no bridge
+		 * that brings its own, not only when there is a panel. For
+		 * historical reasons, the ldb driver can also work without
+		 * a panel.
+		 */
+		drm_connector_helper_add(&imx_ldb_ch->connector,
+				&imx_ldb_connector_helper_funcs);
+		drm_connector_init(drm, &imx_ldb_ch->connector,
+				&imx_ldb_connector_funcs,
+				DRM_MODE_CONNECTOR_LVDS);
+		drm_mode_connector_attach_encoder(&imx_ldb_ch->connector,
+				encoder);
+	}
 
 	if (imx_ldb_ch->panel) {
 		ret = drm_panel_attach(imx_ldb_ch->panel,
@@ -478,8 +502,6 @@ static int imx_ldb_register(struct drm_device *drm,
 			return ret;
 	}
 
-	drm_mode_connector_attach_encoder(&imx_ldb_ch->connector, encoder);
-
 	return 0;
 }
 
@@ -548,6 +570,45 @@ static const struct of_device_id imx_ldb_dt_ids[] = {
 };
 MODULE_DEVICE_TABLE(of, imx_ldb_dt_ids);
 
+static int imx_ldb_panel_ddc(struct device *dev,
+		struct imx_ldb_channel *channel, struct device_node *child)
+{
+	struct device_node *ddc_node;
+	const u8 *edidp;
+	int ret;
+
+	ddc_node = of_parse_phandle(child, "ddc-i2c-bus", 0);
+	if (ddc_node) {
+		channel->ddc = of_find_i2c_adapter_by_node(ddc_node);
+		of_node_put(ddc_node);
+		if (!channel->ddc) {
+			dev_warn(dev, "failed to get ddc i2c adapter\n");
+			return -EPROBE_DEFER;
+		}
+	}
+
+	if (!channel->ddc) {
+		/* if no DDC available, fallback to hardcoded EDID */
+		dev_dbg(dev, "no ddc available\n");
+
+		edidp = of_get_property(child, "edid",
+					&channel->edid_len);
+		if (edidp) {
+			channel->edid = kmemdup(edidp,
+						channel->edid_len,
+						GFP_KERNEL);
+		} else if (!channel->panel) {
+			/* fallback to display-timings node */
+			ret = of_get_drm_display_mode(child,
+						      &channel->mode,
+						      OF_USE_NATIVE_MODE);
+			if (!ret)
+				channel->mode_valid = 1;
+		}
+	}
+	return 0;
+}
+
 static int imx_ldb_bind(struct device *dev, struct device *master, void *data)
 {
 	struct drm_device *drm = data;
@@ -555,7 +616,6 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data)
 	const struct of_device_id *of_id =
 			of_match_device(imx_ldb_dt_ids, dev);
 	struct device_node *child;
-	const u8 *edidp;
 	struct imx_ldb *imx_ldb;
 	int dual;
 	int ret;
@@ -605,7 +665,6 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data)
 
 	for_each_child_of_node(np, child) {
 		struct imx_ldb_channel *channel;
-		struct device_node *ddc_node;
 		struct device_node *ep;
 		int bus_format;
 
@@ -638,46 +697,25 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data)
 
 			remote = of_graph_get_remote_port_parent(ep);
 			of_node_put(ep);
-			if (remote)
+			if (remote) {
 				channel->panel = of_drm_find_panel(remote);
-			else
+				channel->bridge = of_drm_find_bridge(remote);
+			} else
 				return -EPROBE_DEFER;
 			of_node_put(remote);
-			if (!channel->panel) {
-				dev_err(dev, "panel not found: %s\n",
-					remote->full_name);
-				return -EPROBE_DEFER;
-			}
-		}
 
-		ddc_node = of_parse_phandle(child, "ddc-i2c-bus", 0);
-		if (ddc_node) {
-			channel->ddc = of_find_i2c_adapter_by_node(ddc_node);
-			of_node_put(ddc_node);
-			if (!channel->ddc) {
-				dev_warn(dev, "failed to get ddc i2c adapter\n");
+			if (!channel->panel && !channel->bridge) {
+				dev_err(dev, "panel/bridge not found: %s\n",
+					remote->full_name);
 				return -EPROBE_DEFER;
 			}
 		}
 
-		if (!channel->ddc) {
-			/* if no DDC available, fallback to hardcoded EDID */
-			dev_dbg(dev, "no ddc available\n");
-
-			edidp = of_get_property(child, "edid",
-						&channel->edid_len);
-			if (edidp) {
-				channel->edid = kmemdup(edidp,
-							channel->edid_len,
-							GFP_KERNEL);
-			} else if (!channel->panel) {
-				/* fallback to display-timings node */
-				ret = of_get_drm_display_mode(child,
-							      &channel->mode,
-							      OF_USE_NATIVE_MODE);
-				if (!ret)
-					channel->mode_valid = 1;
-			}
+		/* panel ddc only if there is no bridge */
+		if (!channel->bridge) {
+			ret = imx_ldb_panel_ddc(dev, channel, child);
+			if (ret)
+				return ret;
 		}
 
 		bus_format = of_get_bus_format(dev, child);
-- 
2.5.5

^ permalink raw reply related	[flat|nested] 64+ messages in thread

* [PATCH V4 0/4] Add driver for GE B850v3 LVDS/DP++ Bridge
  2016-05-30 16:39 [PATCH 0/5] Add driver for GE B850v3 LVDS/DP++ Bridge Peter Senna Tschudin
                   ` (6 preceding siblings ...)
  2016-07-31 19:55 ` [PATCH V3 0/5] Add driver for " Peter Senna Tschudin
@ 2016-08-04 22:36 ` Peter Senna Tschudin
  2016-08-04 22:36   ` [PATCH V4 1/4] drm/imx-ldb: Add support to drm-bridge Peter Senna Tschudin
                     ` (3 more replies)
  2016-08-09 16:41 ` [PATCH V5 0/4] Add driver for " Peter Senna Tschudin
  8 siblings, 4 replies; 64+ messages in thread
From: Peter Senna Tschudin @ 2016-08-04 22:36 UTC (permalink / raw)
  To: robh+dt, mark.rutland, shawnguo, kernel, fabio.estevam, linux,
	airlied, p.zabel, martyn.welch, martin.donnelly, peter.senna,
	peter.senna, treding, architt, devicetree, linux-kernel,
	linux-arm-kernel, dri-devel

The series adds a driver that creates a drm_bridge and a drm_connector for the
LVDS to DP++ display bridge of the GE B850v3.

There are two physical bridges on the video signal pipeline: a STDP4028(LVDS to
DP) and a STDP2690(DP to DP++).  The hardware and firmware made it complicated
for this binding to comprise two device tree nodes, as the design goal is to
configure both bridges based on the LVDS signal, which leave the driver
powerless to control the video processing pipeline. The two bridges behaves as
a single bridge, and the driver is only needed for telling the host about EDID /
HPD, and for giving the host powers to ack interrupts. The video signal
pipeline is as follows:

  Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output

The patches from the series:
 [1/4] Change the imx-ldb driver to allow attaching a bridge and not only a LVDS
       panel.

 [2/4] Devicetree documentation for the GE B850v3 LVDS/DP++ Bridge

 [3/4] Add the driver, make changes to MAINTAINERS, Kconfig and Makefile

 [4/4] Make the changes to the B850v3 dts file to enable the GE B850v3
       LVDS/DP++ Bridge.

Changes from V3:
 - Removed the patch that was configuring the mapping between IPUs and external
   displays on the dts file

Peter Senna Tschudin (4):
  drm/imx-ldb: Add support to drm-bridge
  Documentation/devicetree/bindings: b850v3_lvds_dp
  drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge
  dts/imx6q-b850v3: Use GE B850v3 LVDS/DP++ Bridge

 .../devicetree/bindings/ge/b850v3-lvds-dp.txt      |  37 ++
 MAINTAINERS                                        |   8 +
 arch/arm/boot/dts/imx6q-b850v3.dts                 |  30 ++
 drivers/gpu/drm/bridge/Kconfig                     |  11 +
 drivers/gpu/drm/bridge/Makefile                    |   1 +
 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c         | 397 +++++++++++++++++++++
 drivers/gpu/drm/imx/imx-ldb.c                      | 118 +++---
 7 files changed, 562 insertions(+), 40 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt
 create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c

-- 
2.5.5

^ permalink raw reply	[flat|nested] 64+ messages in thread

* [PATCH V4 1/4] drm/imx-ldb: Add support to drm-bridge
  2016-08-04 22:36 ` [PATCH V4 0/4] Add driver for " Peter Senna Tschudin
@ 2016-08-04 22:36   ` Peter Senna Tschudin
  2016-08-16 15:40     ` Martyn Welch
  2016-08-04 22:36   ` [PATCH V4 2/4] Documentation/devicetree/bindings: b850v3_lvds_dp Peter Senna Tschudin
                     ` (2 subsequent siblings)
  3 siblings, 1 reply; 64+ messages in thread
From: Peter Senna Tschudin @ 2016-08-04 22:36 UTC (permalink / raw)
  To: robh+dt, mark.rutland, shawnguo, kernel, fabio.estevam, linux,
	airlied, p.zabel, martyn.welch, martin.donnelly, peter.senna,
	peter.senna, treding, architt, devicetree, linux-kernel,
	linux-arm-kernel, dri-devel
  Cc: Enric Balletbo i Serra, Rob Herring, Thierry Reding

Add support to attach a drm_bridge to imx-ldb in addition to
existing support to attach a LVDS panel.

This patch does a simple code refactoring by moving code
from for_each_child_of_node iterator to a new function named
imx_ldb_panel_ddc(). This was necessary to allow the panel ddc
code to run only when the imx_ldb is not attached to a bridge.

Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com>
Cc: Philipp Zabel <p.zabel@pengutronix.de>
Cc: Rob Herring <robh@kernel.org>
Cc: Fabio Estevam <fabio.estevam@nxp.com>
Cc: David Airlie <airlied@linux.ie>
Cc: Thierry Reding <treding@nvidia.com>
Cc: Thierry Reding <thierry.reding@gmail.com>
Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com>
---
Changes from V3:
 - The connector is created when there is no bridge instead of only when
   there is a pannel
 - Tested on next-20160804

Changes from V2:
 - Updated to be aplied on top of Liu Ying changes that made imx-ldb atomic
 - Tested on next-20160729

Changes from V1:
 - Reanmed ext_bridge to bridge
 - Removed empty entry point imx_ldb_encoder_enable()
 - Adapted the code to apply to the latest linux next: next-20160609

 drivers/gpu/drm/imx/imx-ldb.c | 118 ++++++++++++++++++++++++++++--------------
 1 file changed, 78 insertions(+), 40 deletions(-)

diff --git a/drivers/gpu/drm/imx/imx-ldb.c b/drivers/gpu/drm/imx/imx-ldb.c
index b03919e..4a33077 100644
--- a/drivers/gpu/drm/imx/imx-ldb.c
+++ b/drivers/gpu/drm/imx/imx-ldb.c
@@ -57,7 +57,11 @@ struct imx_ldb_channel {
 	struct imx_ldb *ldb;
 	struct drm_connector connector;
 	struct drm_encoder encoder;
+
+	/* Defines what is connected to the ldb, only one at a time */
 	struct drm_panel *panel;
+	struct drm_bridge *bridge;
+
 	struct device_node *child;
 	struct i2c_adapter *ddc;
 	int chno;
@@ -466,10 +470,30 @@ static int imx_ldb_register(struct drm_device *drm,
 	drm_encoder_init(drm, encoder, &imx_ldb_encoder_funcs,
 			 DRM_MODE_ENCODER_LVDS, NULL);
 
-	drm_connector_helper_add(&imx_ldb_ch->connector,
-			&imx_ldb_connector_helper_funcs);
-	drm_connector_init(drm, &imx_ldb_ch->connector,
-			   &imx_ldb_connector_funcs, DRM_MODE_CONNECTOR_LVDS);
+	if (imx_ldb_ch->bridge) {
+		imx_ldb_ch->bridge->encoder = encoder;
+
+		imx_ldb_ch->encoder.bridge = imx_ldb_ch->bridge;
+		ret = drm_bridge_attach(drm, imx_ldb_ch->bridge);
+		if (ret) {
+			DRM_ERROR("Failed to initialize bridge with drm\n");
+			return ret;
+		}
+	} else {
+		/*
+		 * We want to add the connector whenever there is no bridge
+		 * that brings its own, not only when there is a panel. For
+		 * historical reasons, the ldb driver can also work without
+		 * a panel.
+		 */
+		drm_connector_helper_add(&imx_ldb_ch->connector,
+				&imx_ldb_connector_helper_funcs);
+		drm_connector_init(drm, &imx_ldb_ch->connector,
+				&imx_ldb_connector_funcs,
+				DRM_MODE_CONNECTOR_LVDS);
+		drm_mode_connector_attach_encoder(&imx_ldb_ch->connector,
+				encoder);
+	}
 
 	if (imx_ldb_ch->panel) {
 		ret = drm_panel_attach(imx_ldb_ch->panel,
@@ -478,8 +502,6 @@ static int imx_ldb_register(struct drm_device *drm,
 			return ret;
 	}
 
-	drm_mode_connector_attach_encoder(&imx_ldb_ch->connector, encoder);
-
 	return 0;
 }
 
@@ -548,6 +570,45 @@ static const struct of_device_id imx_ldb_dt_ids[] = {
 };
 MODULE_DEVICE_TABLE(of, imx_ldb_dt_ids);
 
+static int imx_ldb_panel_ddc(struct device *dev,
+		struct imx_ldb_channel *channel, struct device_node *child)
+{
+	struct device_node *ddc_node;
+	const u8 *edidp;
+	int ret;
+
+	ddc_node = of_parse_phandle(child, "ddc-i2c-bus", 0);
+	if (ddc_node) {
+		channel->ddc = of_find_i2c_adapter_by_node(ddc_node);
+		of_node_put(ddc_node);
+		if (!channel->ddc) {
+			dev_warn(dev, "failed to get ddc i2c adapter\n");
+			return -EPROBE_DEFER;
+		}
+	}
+
+	if (!channel->ddc) {
+		/* if no DDC available, fallback to hardcoded EDID */
+		dev_dbg(dev, "no ddc available\n");
+
+		edidp = of_get_property(child, "edid",
+					&channel->edid_len);
+		if (edidp) {
+			channel->edid = kmemdup(edidp,
+						channel->edid_len,
+						GFP_KERNEL);
+		} else if (!channel->panel) {
+			/* fallback to display-timings node */
+			ret = of_get_drm_display_mode(child,
+						      &channel->mode,
+						      OF_USE_NATIVE_MODE);
+			if (!ret)
+				channel->mode_valid = 1;
+		}
+	}
+	return 0;
+}
+
 static int imx_ldb_bind(struct device *dev, struct device *master, void *data)
 {
 	struct drm_device *drm = data;
@@ -555,7 +616,6 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data)
 	const struct of_device_id *of_id =
 			of_match_device(imx_ldb_dt_ids, dev);
 	struct device_node *child;
-	const u8 *edidp;
 	struct imx_ldb *imx_ldb;
 	int dual;
 	int ret;
@@ -605,7 +665,6 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data)
 
 	for_each_child_of_node(np, child) {
 		struct imx_ldb_channel *channel;
-		struct device_node *ddc_node;
 		struct device_node *ep;
 		int bus_format;
 
@@ -638,46 +697,25 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data)
 
 			remote = of_graph_get_remote_port_parent(ep);
 			of_node_put(ep);
-			if (remote)
+			if (remote) {
 				channel->panel = of_drm_find_panel(remote);
-			else
+				channel->bridge = of_drm_find_bridge(remote);
+			} else
 				return -EPROBE_DEFER;
 			of_node_put(remote);
-			if (!channel->panel) {
-				dev_err(dev, "panel not found: %s\n",
-					remote->full_name);
-				return -EPROBE_DEFER;
-			}
-		}
 
-		ddc_node = of_parse_phandle(child, "ddc-i2c-bus", 0);
-		if (ddc_node) {
-			channel->ddc = of_find_i2c_adapter_by_node(ddc_node);
-			of_node_put(ddc_node);
-			if (!channel->ddc) {
-				dev_warn(dev, "failed to get ddc i2c adapter\n");
+			if (!channel->panel && !channel->bridge) {
+				dev_err(dev, "panel/bridge not found: %s\n",
+					remote->full_name);
 				return -EPROBE_DEFER;
 			}
 		}
 
-		if (!channel->ddc) {
-			/* if no DDC available, fallback to hardcoded EDID */
-			dev_dbg(dev, "no ddc available\n");
-
-			edidp = of_get_property(child, "edid",
-						&channel->edid_len);
-			if (edidp) {
-				channel->edid = kmemdup(edidp,
-							channel->edid_len,
-							GFP_KERNEL);
-			} else if (!channel->panel) {
-				/* fallback to display-timings node */
-				ret = of_get_drm_display_mode(child,
-							      &channel->mode,
-							      OF_USE_NATIVE_MODE);
-				if (!ret)
-					channel->mode_valid = 1;
-			}
+		/* panel ddc only if there is no bridge */
+		if (!channel->bridge) {
+			ret = imx_ldb_panel_ddc(dev, channel, child);
+			if (ret)
+				return ret;
 		}
 
 		bus_format = of_get_bus_format(dev, child);
-- 
2.5.5

^ permalink raw reply related	[flat|nested] 64+ messages in thread

* [PATCH V4 2/4] Documentation/devicetree/bindings: b850v3_lvds_dp
  2016-08-04 22:36 ` [PATCH V4 0/4] Add driver for " Peter Senna Tschudin
  2016-08-04 22:36   ` [PATCH V4 1/4] drm/imx-ldb: Add support to drm-bridge Peter Senna Tschudin
@ 2016-08-04 22:36   ` Peter Senna Tschudin
  2016-08-05  7:28     ` Enric Balletbo Serra
  2016-08-16 15:59     ` Martyn Welch
  2016-08-04 22:37   ` [PATCH V4 3/4] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge Peter Senna Tschudin
  2016-08-04 22:37   ` [PATCH V4 4/4] dts/imx6q-b850v3: Use " Peter Senna Tschudin
  3 siblings, 2 replies; 64+ messages in thread
From: Peter Senna Tschudin @ 2016-08-04 22:36 UTC (permalink / raw)
  To: robh+dt, mark.rutland, shawnguo, kernel, fabio.estevam, linux,
	airlied, p.zabel, martyn.welch, martin.donnelly, peter.senna,
	peter.senna, treding, architt, devicetree, linux-kernel,
	linux-arm-kernel, dri-devel
  Cc: Javier Martinez Canillas, Enric Balletbo i Serra, Rob Herring

Devicetree bindings documentation for the GE B850v3 LVDS/DP++
display bridge.

Cc: Javier Martinez Canillas <javier@dowhile0.org>
Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com>
Cc: Philipp Zabel <p.zabel@pengutronix.de>
Cc: Rob Herring <robh@kernel.org>
Cc: Fabio Estevam <fabio.estevam@nxp.com>
Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com>
---
Changes from V3:
 - 2/4 instead of 3/5

Unchanged from V2

Changes from V1:
 - Replaced '_' by '-' in node names or compatible strings
 - Added missing @73 to the example

 .../devicetree/bindings/ge/b850v3-lvds-dp.txt      | 37 ++++++++++++++++++++++
 1 file changed, 37 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt

diff --git a/Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt b/Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt
new file mode 100644
index 0000000..f05c3e9
--- /dev/null
+++ b/Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt
@@ -0,0 +1,37 @@
+Driver for GE B850v3 LVDS/DP++ display bridge
+
+Required properties:
+  - compatible : should be "ge,b850v3-lvds-dp".
+  - reg : should contain the address used to ack the interrupts.
+  - interrupt-parent : phandle of the interrupt controller that services
+    interrupts to the device
+  - interrupts : one interrupt should be described here, as in
+    <0 IRQ_TYPE_LEVEL_HIGH>.
+  - edid-reg : should contain the address used to read edid information
+  - port : should describe the video signal connection between the host
+    and the bridge.
+
+Example:
+
+&mux2_i2c2 {
+	status = "okay";
+	clock-frequency = <100000>;
+
+	b850v3-lvds-dp-bridge@73  {
+		compatible = "ge,b850v3-lvds-dp";
+		#address-cells = <1>;
+		#size-cells = <0>;
+
+		reg = <0x73>;
+		interrupt-parent = <&gpio2>;
+		interrupts = <0 IRQ_TYPE_LEVEL_HIGH>;
+
+		edid-reg = <0x72>;
+
+		port {
+			b850v3_dp_bridge_in: endpoint {
+				remote-endpoint = <&lvds0_out>;
+			};
+		};
+	};
+};
-- 
2.5.5

^ permalink raw reply related	[flat|nested] 64+ messages in thread

* [PATCH V4 3/4] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge
  2016-08-04 22:36 ` [PATCH V4 0/4] Add driver for " Peter Senna Tschudin
  2016-08-04 22:36   ` [PATCH V4 1/4] drm/imx-ldb: Add support to drm-bridge Peter Senna Tschudin
  2016-08-04 22:36   ` [PATCH V4 2/4] Documentation/devicetree/bindings: b850v3_lvds_dp Peter Senna Tschudin
@ 2016-08-04 22:37   ` Peter Senna Tschudin
  2016-08-05  7:38     ` Enric Balletbo Serra
  2016-08-04 22:37   ` [PATCH V4 4/4] dts/imx6q-b850v3: Use " Peter Senna Tschudin
  3 siblings, 1 reply; 64+ messages in thread
From: Peter Senna Tschudin @ 2016-08-04 22:37 UTC (permalink / raw)
  To: robh+dt, mark.rutland, shawnguo, kernel, fabio.estevam, linux,
	airlied, p.zabel, martyn.welch, martin.donnelly, peter.senna,
	peter.senna, treding, architt, devicetree, linux-kernel,
	linux-arm-kernel, dri-devel
  Cc: Daniel Vetter, Enric Balletbo i Serra, Rob Herring, Thierry Reding

Add a driver that create a drm_bridge and a drm_connector for the LVDS
to DP++ display bridge of the GE B850v3.

There are two physical bridges on the video signal pipeline: a
STDP4028(LVDS to DP) and a STDP2690(DP to DP++).  The hardware and
firmware made it complicated for this binding to comprise two device
tree nodes, as the design goal is to configure both bridges based on
the LVDS signal, which leave the driver powerless to control the video
processing pipeline. The two bridges behaves as a single bridge, and
the driver is only needed for telling the host about EDID / HPD, and
for giving the host powers to ack interrupts. The video signal pipeline
is as follows:

  Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output

Cc: Daniel Vetter <daniel.vetter@ffwll.ch>
Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com>
Cc: Philipp Zabel <p.zabel@pengutronix.de>
Cc: Rob Herring <robh@kernel.org>
Cc: Fabio Estevam <fabio.estevam@nxp.com>
CC: David Airlie <airlied@linux.ie>
CC: Thierry Reding <treding@nvidia.com>
CC: Thierry Reding <thierry.reding@gmail.com>
Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com>
---
Changes from V3:
 - 3/4 instead of 4/5
 - Tested on next-20160804

Changes from V2:
 - Made it atomic to be applied on next-20160729 on top of Liu Ying changes
   that made imx-ldb atomic

Changes from V1:
 - New commit message
 - Removed 3 empty entry points
 - Removed memory leak from ge_b850v3_lvds_dp_get_modes()
 - Added a lock for mode setting
 - Removed a few blank lines
 - Changed the order at Makefile and Kconfig

 MAINTAINERS                                |   8 +
 drivers/gpu/drm/bridge/Kconfig             |  11 +
 drivers/gpu/drm/bridge/Makefile            |   1 +
 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c | 397 +++++++++++++++++++++++++++++
 4 files changed, 417 insertions(+)
 create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c

diff --git a/MAINTAINERS b/MAINTAINERS
index a5b6569..f51123c 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -5142,6 +5142,14 @@ W:	https://linuxtv.org
 S:	Maintained
 F:	drivers/media/radio/radio-gemtek*
 
+GENERAL ELECTRIC B850V3 LVDS/DP++ BRIDGE
+M:	Peter Senna Tschudin <peter.senna@collabora.com>
+M:	Martin Donnelly <martin.donnelly@ge.com>
+M:	Martyn Welch <martyn.welch@collabora.co.uk>
+S:	Maintained
+F:	drivers/gpu/drm/bridge/ge_b850v3_dp2.c
+F:	Documentation/devicetree/bindings/ge/b850v3_dp2_bridge.txt
+
 GENERIC GPIO I2C DRIVER
 M:	Haavard Skinnemoen <hskinnemoen@gmail.com>
 S:	Supported
diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
index b590e67..b4b70fb 100644
--- a/drivers/gpu/drm/bridge/Kconfig
+++ b/drivers/gpu/drm/bridge/Kconfig
@@ -32,6 +32,17 @@ config DRM_DW_HDMI_AHB_AUDIO
 	  Designware HDMI block.  This is used in conjunction with
 	  the i.MX6 HDMI driver.
 
+config DRM_GE_B850V3_LVDS_DP
+	tristate "GE B850v3 LVDS to DP++ display bridge"
+	depends on OF
+	select DRM_KMS_HELPER
+	select DRM_PANEL
+	---help---
+          This is a driver for the display bridge of
+          GE B850v3 that convert dual channel LVDS
+          to DP++. This is used with the i.MX6 imx-ldb
+          driver.
+
 config DRM_NXP_PTN3460
 	tristate "NXP PTN3460 DP/LVDS bridge"
 	depends on OF
diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
index efdb07e..b9606f3 100644
--- a/drivers/gpu/drm/bridge/Makefile
+++ b/drivers/gpu/drm/bridge/Makefile
@@ -3,6 +3,7 @@ ccflags-y := -Iinclude/drm
 obj-$(CONFIG_DRM_ANALOGIX_ANX78XX) += analogix-anx78xx.o
 obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o
 obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o
+obj-$(CONFIG_DRM_GE_B850V3_LVDS_DP) += ge_b850v3_lvds_dp.o
 obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o
 obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o
 obj-$(CONFIG_DRM_SII902X) += sii902x.o
diff --git a/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c
new file mode 100644
index 0000000..eee8eac
--- /dev/null
+++ b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c
@@ -0,0 +1,397 @@
+/*
+ * Driver for GE B850v3 DP display bridge
+
+ * Copyright (c) 2016, Collabora Ltd.
+ * Copyright (c) 2016, General Electric Company
+
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+ * This driver creates a drm_bridge and a drm_connector for the LVDS to DP++
+ * display bridge of the GE B850v3. There are two physical bridges on the video
+ * signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). However
+ * the physical bridges are automatically configured by the input video signal,
+ * and the driver has no access to the video processing pipeline. The driver is
+ * only needed to read EDID from the STDP2690 and to handle HPD events from the
+ * STDP4028. The driver communicates with both bridges over i2c. The video
+ * signal pipeline is as follows:
+ *
+ *   Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output
+ *
+ */
+
+#include <linux/gpio.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_edid.h>
+#include <drm/drmP.h>
+
+/*
+ * 220Mhz is a limitation of the host, as the bridge is capable of up to
+ * 330Mhz. See section 9.2.1.2.4 of the i.MX 6Dual/6Quad Applications
+ * Processor Reference Manual for more information about the 220Mhz limit.
+ * The imx-ldb driver will warn about clocks over 170Mhz, but it seem to work
+ * fine.
+ */
+#define MAX_PIXEL_CLOCK 220000
+
+#define EDID_EXT_BLOCK_CNT 0x7E
+
+#define STDP4028_IRQ_OUT_CONF_REG 0x02
+#define STDP4028_DPTX_IRQ_EN_REG 0x3C
+#define STDP4028_DPTX_IRQ_STS_REG 0x3D
+#define STDP4028_DPTX_STS_REG 0x3E
+
+#define STDP4028_DPTX_DP_IRQ_EN 0x1000
+
+#define STDP4028_DPTX_HOTPLUG_IRQ_EN 0x0400
+#define STDP4028_DPTX_LINK_CH_IRQ_EN 0x2000
+#define STDP4028_DPTX_IRQ_CONFIG \
+		(STDP4028_DPTX_LINK_CH_IRQ_EN | STDP4028_DPTX_HOTPLUG_IRQ_EN)
+
+#define STDP4028_DPTX_HOTPLUG_STS 0x0200
+#define STDP4028_DPTX_LINK_STS 0x1000
+#define STDP4028_CON_STATE_CONNECTED \
+		(STDP4028_DPTX_HOTPLUG_STS | STDP4028_DPTX_LINK_STS)
+
+#define STDP4028_DPTX_HOTPLUG_CH_STS 0x0400
+#define STDP4028_DPTX_LINK_CH_STS 0x2000
+#define STDP4028_DPTX_IRQ_CLEAR \
+		(STDP4028_DPTX_LINK_CH_STS | STDP4028_DPTX_HOTPLUG_CH_STS)
+
+struct ge_b850v3_lvds_dp {
+	struct drm_connector connector;
+	struct drm_bridge bridge;
+	struct i2c_client *ge_b850v3_lvds_dp_i2c;
+	struct i2c_client *edid_i2c;
+	struct edid *edid;
+	struct mutex lock;
+};
+
+static inline struct ge_b850v3_lvds_dp *
+		bridge_to_ge_b850v3_lvds_dp(struct drm_bridge *bridge)
+{
+	return container_of(bridge, struct ge_b850v3_lvds_dp, bridge);
+}
+
+static inline struct ge_b850v3_lvds_dp *
+		connector_to_ge_b850v3_lvds_dp(struct drm_connector *connector)
+{
+	return container_of(connector, struct ge_b850v3_lvds_dp, connector);
+}
+
+u8 *stdp2690_get_edid(struct i2c_client *client)
+{
+	struct i2c_adapter *adapter = client->adapter;
+	unsigned char start = 0x00;
+	unsigned int total_size;
+	u8 *block = kmalloc(EDID_LENGTH, GFP_KERNEL);
+
+	struct i2c_msg msgs[] = {
+		{
+			.addr	= client->addr,
+			.flags	= 0,
+			.len	= 1,
+			.buf	= &start,
+		}, {
+			.addr	= client->addr,
+			.flags	= I2C_M_RD,
+			.len	= EDID_LENGTH,
+			.buf	= block,
+		}
+	};
+
+	if (!block)
+		return NULL;
+
+	if (i2c_transfer(adapter, msgs, 2) != 2) {
+		DRM_ERROR("Unable to read EDID.\n");
+		goto err;
+	}
+
+	if (!drm_edid_block_valid(block, 0, false, NULL)) {
+		DRM_ERROR("Invalid EDID block\n");
+		goto err;
+	}
+
+	total_size = (block[EDID_EXT_BLOCK_CNT] + 1) * EDID_LENGTH;
+	if (total_size > EDID_LENGTH) {
+		kfree(block);
+		block = kmalloc(total_size, GFP_KERNEL);
+		if (!block)
+			return NULL;
+
+		/* Yes, read the entire buffer, and do not skip the first
+		 * EDID_LENGTH bytes.
+		 */
+		start = 0x00;
+		msgs[1].len = total_size;
+		msgs[1].buf = block;
+
+		if (i2c_transfer(adapter, msgs, 2) != 2) {
+			DRM_ERROR("Unable to read EDID extension blocks.\n");
+			goto err;
+		}
+	}
+
+	return block;
+
+err:
+	kfree(block);
+	return NULL;
+}
+
+static int ge_b850v3_lvds_dp_get_modes(struct drm_connector *connector)
+{
+	struct ge_b850v3_lvds_dp *ptn_bridge;
+	struct i2c_client *client;
+	int num_modes = 0;
+
+	ptn_bridge = connector_to_ge_b850v3_lvds_dp(connector);
+	client = ptn_bridge->edid_i2c;
+
+	mutex_lock(&ptn_bridge->lock);
+
+	kfree(ptn_bridge->edid);
+	ptn_bridge->edid = (struct edid *) stdp2690_get_edid(client);
+
+	if (ptn_bridge->edid) {
+		drm_mode_connector_update_edid_property(connector,
+				ptn_bridge->edid);
+		num_modes = drm_add_edid_modes(connector, ptn_bridge->edid);
+	}
+
+	mutex_unlock(&ptn_bridge->lock);
+
+	return num_modes;
+}
+
+
+static enum drm_mode_status ge_b850v3_lvds_dp_mode_valid(
+		struct drm_connector *connector, struct drm_display_mode *mode)
+{
+	if (mode->clock > MAX_PIXEL_CLOCK) {
+		DRM_INFO("The pixel clock for the mode %s is too high, and not supported.",
+				mode->name);
+		return MODE_CLOCK_HIGH;
+	}
+
+	return MODE_OK;
+}
+
+static const struct
+drm_connector_helper_funcs ge_b850v3_lvds_dp_connector_helper_funcs = {
+	.get_modes = ge_b850v3_lvds_dp_get_modes,
+	.mode_valid = ge_b850v3_lvds_dp_mode_valid,
+};
+
+static enum drm_connector_status ge_b850v3_lvds_dp_detect(
+		struct drm_connector *connector, bool force)
+{
+	struct ge_b850v3_lvds_dp *ptn_bridge =
+			connector_to_ge_b850v3_lvds_dp(connector);
+	struct i2c_client *ge_b850v3_lvds_dp_i2c =
+			ptn_bridge->ge_b850v3_lvds_dp_i2c;
+	s32 link_state;
+
+	link_state = i2c_smbus_read_word_data(ge_b850v3_lvds_dp_i2c,
+			STDP4028_DPTX_STS_REG);
+
+	if (link_state == STDP4028_CON_STATE_CONNECTED)
+		return connector_status_connected;
+
+	if (link_state == 0)
+		return connector_status_disconnected;
+
+	return connector_status_unknown;
+}
+
+static const struct drm_connector_funcs ge_b850v3_lvds_dp_connector_funcs = {
+	.dpms = drm_atomic_helper_connector_dpms,
+	.fill_modes = drm_helper_probe_single_connector_modes,
+	.detect = ge_b850v3_lvds_dp_detect,
+	.destroy = drm_connector_cleanup,
+	.reset = drm_atomic_helper_connector_reset,
+	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
+static irqreturn_t ge_b850v3_lvds_dp_irq_handler(int irq, void *dev_id)
+{
+	struct ge_b850v3_lvds_dp *ptn_bridge = dev_id;
+	struct i2c_client *ge_b850v3_lvds_dp_i2c
+			= ptn_bridge->ge_b850v3_lvds_dp_i2c;
+
+	mutex_lock(&ptn_bridge->lock);
+
+	i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c,
+			STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR);
+
+	mutex_unlock(&ptn_bridge->lock);
+
+	if (ptn_bridge->connector.dev)
+		drm_kms_helper_hotplug_event(ptn_bridge->connector.dev);
+
+	return IRQ_HANDLED;
+}
+
+static int ge_b850v3_lvds_dp_attach(struct drm_bridge *bridge)
+{
+	struct ge_b850v3_lvds_dp *ptn_bridge
+			= bridge_to_ge_b850v3_lvds_dp(bridge);
+	struct drm_connector *connector = &ptn_bridge->connector;
+	struct i2c_client *ge_b850v3_lvds_dp_i2c
+			= ptn_bridge->ge_b850v3_lvds_dp_i2c;
+	int ret;
+
+	if (!bridge->encoder) {
+		DRM_ERROR("Parent encoder object not found");
+		return -ENODEV;
+	}
+
+	connector->polled = DRM_CONNECTOR_POLL_HPD;
+
+	drm_connector_helper_add(connector,
+			&ge_b850v3_lvds_dp_connector_helper_funcs);
+
+	ret = drm_connector_init(bridge->dev, connector,
+			&ge_b850v3_lvds_dp_connector_funcs,
+			DRM_MODE_CONNECTOR_DisplayPort);
+	if (ret) {
+		DRM_ERROR("Failed to initialize connector with drm\n");
+		return ret;
+	}
+
+	drm_connector_register(connector);
+	ret = drm_mode_connector_attach_encoder(connector, bridge->encoder);
+	if (ret)
+		return ret;
+
+	drm_bridge_enable(bridge);
+	if (ge_b850v3_lvds_dp_i2c->irq) {
+		drm_helper_hpd_irq_event(connector->dev);
+
+		ret = devm_request_threaded_irq(&ge_b850v3_lvds_dp_i2c->dev,
+				ge_b850v3_lvds_dp_i2c->irq, NULL,
+				ge_b850v3_lvds_dp_irq_handler,
+				IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
+				"ge-b850v3-lvds-dp", ptn_bridge);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static const struct drm_bridge_funcs ge_b850v3_lvds_dp_funcs = {
+	.attach = ge_b850v3_lvds_dp_attach,
+};
+
+static int ge_b850v3_lvds_dp_probe(struct i2c_client *ge_b850v3_lvds_dp_i2c,
+				const struct i2c_device_id *id)
+{
+	struct device *dev = &ge_b850v3_lvds_dp_i2c->dev;
+	struct ge_b850v3_lvds_dp *ptn_bridge;
+	int ret;
+	u32 edid_i2c_reg;
+
+	ptn_bridge = devm_kzalloc(dev, sizeof(*ptn_bridge), GFP_KERNEL);
+	if (!ptn_bridge)
+		return -ENOMEM;
+
+	mutex_init(&ptn_bridge->lock);
+
+	ptn_bridge->ge_b850v3_lvds_dp_i2c = ge_b850v3_lvds_dp_i2c;
+	ptn_bridge->bridge.driver_private = ptn_bridge;
+	i2c_set_clientdata(ge_b850v3_lvds_dp_i2c, ptn_bridge);
+
+	ret = of_property_read_u32(dev->of_node, "edid-reg", &edid_i2c_reg);
+	if (ret) {
+		dev_err(dev, "edid-reg not specified, aborting...\n");
+		return -ENODEV;
+	}
+
+	ptn_bridge->edid_i2c = devm_kzalloc(dev,
+			sizeof(struct i2c_client), GFP_KERNEL);
+
+	if (!ptn_bridge->edid_i2c)
+		return -ENOMEM;
+
+	memcpy(ptn_bridge->edid_i2c, ge_b850v3_lvds_dp_i2c,
+			sizeof(struct i2c_client));
+
+	ptn_bridge->edid_i2c->addr = (unsigned short) edid_i2c_reg;
+
+	/* Configures the bridge to re-enable interrupts after each ack */
+	i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c,
+			STDP4028_IRQ_OUT_CONF_REG, STDP4028_DPTX_DP_IRQ_EN);
+	i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c,
+			STDP4028_DPTX_IRQ_EN_REG, STDP4028_DPTX_IRQ_CONFIG);
+
+	/* Clear pending interrupts since power up. */
+	i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c,
+			STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR);
+
+	ptn_bridge->bridge.funcs = &ge_b850v3_lvds_dp_funcs;
+	ptn_bridge->bridge.of_node = dev->of_node;
+	ret = drm_bridge_add(&ptn_bridge->bridge);
+	if (ret) {
+		DRM_ERROR("Failed to add bridge\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static int ge_b850v3_lvds_dp_remove(struct i2c_client *ge_b850v3_lvds_dp_i2c)
+{
+	struct ge_b850v3_lvds_dp *ptn_bridge =
+		i2c_get_clientdata(ge_b850v3_lvds_dp_i2c);
+
+	drm_bridge_remove(&ptn_bridge->bridge);
+
+	kfree(ptn_bridge->edid);
+
+	return 0;
+}
+
+static const struct i2c_device_id ge_b850v3_lvds_dp_i2c_table[] = {
+	{"b850v3-lvds-dp", 0},
+	{},
+};
+MODULE_DEVICE_TABLE(i2c, ge_b850v3_lvds_dp_i2c_table);
+
+static const struct of_device_id ge_b850v3_lvds_dp_match[] = {
+	{ .compatible = "ge,b850v3-lvds-dp" },
+	{},
+};
+MODULE_DEVICE_TABLE(of, ge_b850v3_lvds_dp_match);
+
+static struct i2c_driver ge_b850v3_lvds_dp_driver = {
+	.id_table	= ge_b850v3_lvds_dp_i2c_table,
+	.probe		= ge_b850v3_lvds_dp_probe,
+	.remove		= ge_b850v3_lvds_dp_remove,
+	.driver		= {
+		.name		= "ge,b850v3-lvds-dp",
+		.of_match_table = ge_b850v3_lvds_dp_match,
+	},
+};
+module_i2c_driver(ge_b850v3_lvds_dp_driver);
+
+MODULE_AUTHOR("Peter Senna Tschudin <peter.senna@collabora.com>");
+MODULE_AUTHOR("Martyn Welch <martyn.welch@collabora.co.uk>");
+MODULE_DESCRIPTION("GE LVDS to DP++ display bridge)");
+MODULE_LICENSE("GPL v2");
-- 
2.5.5

^ permalink raw reply related	[flat|nested] 64+ messages in thread

* [PATCH V4 4/4] dts/imx6q-b850v3: Use GE B850v3 LVDS/DP++ Bridge
  2016-08-04 22:36 ` [PATCH V4 0/4] Add driver for " Peter Senna Tschudin
                     ` (2 preceding siblings ...)
  2016-08-04 22:37   ` [PATCH V4 3/4] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge Peter Senna Tschudin
@ 2016-08-04 22:37   ` Peter Senna Tschudin
  3 siblings, 0 replies; 64+ messages in thread
From: Peter Senna Tschudin @ 2016-08-04 22:37 UTC (permalink / raw)
  To: robh+dt, mark.rutland, shawnguo, kernel, fabio.estevam, linux,
	airlied, p.zabel, martyn.welch, martin.donnelly, peter.senna,
	peter.senna, treding, architt, devicetree, linux-kernel,
	linux-arm-kernel, dri-devel
  Cc: Javier Martinez Canillas, Enric Balletbo i Serra, Rob Herring

Configures the GE B850v3 LVDS/DP++ bridge on the dts file.

Cc: Javier Martinez Canillas <javier@dowhile0.org>
Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com>
Cc: Philipp Zabel <p.zabel@pengutronix.de>
Cc: Rob Herring <robh@kernel.org>
Cc: Fabio Estevam <fabio.estevam@nxp.com>
Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com>
---
Changes from V3:
 - 4/4 instead of 5/5

Unchanged from V2

Changes from V1:
 - Replaced '_' by '-' in node names or compatible strings
 - Added missing @73 to b850v3-lvds-dp-bridge

 arch/arm/boot/dts/imx6q-b850v3.dts | 30 ++++++++++++++++++++++++++++++
 1 file changed, 30 insertions(+)

diff --git a/arch/arm/boot/dts/imx6q-b850v3.dts b/arch/arm/boot/dts/imx6q-b850v3.dts
index 167f744..8db3bf2 100644
--- a/arch/arm/boot/dts/imx6q-b850v3.dts
+++ b/arch/arm/boot/dts/imx6q-b850v3.dts
@@ -72,6 +72,13 @@
 		fsl,data-mapping = "spwg";
 		fsl,data-width = <24>;
 		status = "okay";
+
+		port@4 {
+			reg = <4>;
+			lvds0_out: endpoint {
+				remote-endpoint = <&b850v3_lvds_dp_bridge_in>;
+			};
+		};
 	};
 };
 
@@ -142,3 +149,26 @@
 		reg = <0x4a>;
 	};
 };
+
+&mux2_i2c2 {
+	status = "okay";
+	clock-frequency = <100000>;
+
+	b850v3-lvds-dp-bridge@73 {
+		compatible = "ge,b850v3-lvds-dp";
+		#address-cells = <1>;
+		#size-cells = <0>;
+
+		reg = <0x73>;
+		interrupt-parent = <&gpio2>;
+		interrupts = <0 IRQ_TYPE_LEVEL_HIGH>;
+
+		edid-reg = <0x72>;
+
+		port {
+			b850v3_lvds_dp_bridge_in: endpoint {
+				remote-endpoint = <&lvds0_out>;
+			};
+		};
+	};
+};
-- 
2.5.5

^ permalink raw reply related	[flat|nested] 64+ messages in thread

* Re: [PATCH V4 2/4] Documentation/devicetree/bindings: b850v3_lvds_dp
  2016-08-04 22:36   ` [PATCH V4 2/4] Documentation/devicetree/bindings: b850v3_lvds_dp Peter Senna Tschudin
@ 2016-08-05  7:28     ` Enric Balletbo Serra
  2016-08-16 15:59     ` Martyn Welch
  1 sibling, 0 replies; 64+ messages in thread
From: Enric Balletbo Serra @ 2016-08-05  7:28 UTC (permalink / raw)
  To: Peter Senna Tschudin
  Cc: Rob Herring, Mark Rutland, shawnguo, Sascha Hauer, Fabio Estevam,
	linux, David Airlie, Philipp Zabel, Martyn Welch,
	martin.donnelly, Peter Senna Tschudin, Thierry Reding, architt,
	devicetree, linux-kernel, linux-arm-kernel, dri-devel,
	Javier Martinez Canillas, Enric Balletbo i Serra, Rob Herring

Hi Peter,

2016-08-05 0:36 GMT+02:00 Peter Senna Tschudin <peter.senna@collabora.com>:
> Devicetree bindings documentation for the GE B850v3 LVDS/DP++
> display bridge.
>
> Cc: Javier Martinez Canillas <javier@dowhile0.org>
> Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com>
> Cc: Philipp Zabel <p.zabel@pengutronix.de>
> Cc: Rob Herring <robh@kernel.org>
> Cc: Fabio Estevam <fabio.estevam@nxp.com>
> Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com>

I think your previous version was acked by Rob and this version has no
changes from previous so would be good add here the Acked-by: Rob
Herring <robh@kernel.org>


> ---
> Changes from V3:
>  - 2/4 instead of 3/5
>
> Unchanged from V2
>
> Changes from V1:
>  - Replaced '_' by '-' in node names or compatible strings
>  - Added missing @73 to the example
>
>  .../devicetree/bindings/ge/b850v3-lvds-dp.txt      | 37 ++++++++++++++++++++++
>  1 file changed, 37 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt
>
> diff --git a/Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt b/Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt
> new file mode 100644
> index 0000000..f05c3e9
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt
> @@ -0,0 +1,37 @@
> +Driver for GE B850v3 LVDS/DP++ display bridge
> +
> +Required properties:
> +  - compatible : should be "ge,b850v3-lvds-dp".
> +  - reg : should contain the address used to ack the interrupts.
> +  - interrupt-parent : phandle of the interrupt controller that services
> +    interrupts to the device
> +  - interrupts : one interrupt should be described here, as in
> +    <0 IRQ_TYPE_LEVEL_HIGH>.
> +  - edid-reg : should contain the address used to read edid information
> +  - port : should describe the video signal connection between the host
> +    and the bridge.
> +
> +Example:
> +
> +&mux2_i2c2 {
> +       status = "okay";
> +       clock-frequency = <100000>;
> +
> +       b850v3-lvds-dp-bridge@73  {
> +               compatible = "ge,b850v3-lvds-dp";
> +               #address-cells = <1>;
> +               #size-cells = <0>;
> +
> +               reg = <0x73>;
> +               interrupt-parent = <&gpio2>;
> +               interrupts = <0 IRQ_TYPE_LEVEL_HIGH>;
> +
> +               edid-reg = <0x72>;
> +
> +               port {
> +                       b850v3_dp_bridge_in: endpoint {
> +                               remote-endpoint = <&lvds0_out>;
> +                       };
> +               };
> +       };
> +};
> --
> 2.5.5
>

^ permalink raw reply	[flat|nested] 64+ messages in thread

* Re: [PATCH V4 3/4] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge
  2016-08-04 22:37   ` [PATCH V4 3/4] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge Peter Senna Tschudin
@ 2016-08-05  7:38     ` Enric Balletbo Serra
  0 siblings, 0 replies; 64+ messages in thread
From: Enric Balletbo Serra @ 2016-08-05  7:38 UTC (permalink / raw)
  To: Peter Senna Tschudin
  Cc: Rob Herring, Mark Rutland, shawnguo, Sascha Hauer, Fabio Estevam,
	linux, David Airlie, Philipp Zabel, Martyn Welch,
	martin.donnelly, Peter Senna Tschudin, Thierry Reding, architt,
	devicetree, linux-kernel, linux-arm-kernel, dri-devel,
	Daniel Vetter, Enric Balletbo i Serra, Rob Herring,
	Thierry Reding

Hi Peter,

Only one comment below and one nitpick

2016-08-05 0:37 GMT+02:00 Peter Senna Tschudin <peter.senna@collabora.com>:
> Add a driver that create a drm_bridge and a drm_connector for the LVDS
> to DP++ display bridge of the GE B850v3.
>
> There are two physical bridges on the video signal pipeline: a
> STDP4028(LVDS to DP) and a STDP2690(DP to DP++).  The hardware and
> firmware made it complicated for this binding to comprise two device
> tree nodes, as the design goal is to configure both bridges based on
> the LVDS signal, which leave the driver powerless to control the video
> processing pipeline. The two bridges behaves as a single bridge, and
> the driver is only needed for telling the host about EDID / HPD, and
> for giving the host powers to ack interrupts. The video signal pipeline
> is as follows:
>
>   Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output
>
> Cc: Daniel Vetter <daniel.vetter@ffwll.ch>
> Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com>
> Cc: Philipp Zabel <p.zabel@pengutronix.de>
> Cc: Rob Herring <robh@kernel.org>
> Cc: Fabio Estevam <fabio.estevam@nxp.com>
> CC: David Airlie <airlied@linux.ie>
> CC: Thierry Reding <treding@nvidia.com>
> CC: Thierry Reding <thierry.reding@gmail.com>
> Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com>
> ---
> Changes from V3:
>  - 3/4 instead of 4/5
>  - Tested on next-20160804
>
> Changes from V2:
>  - Made it atomic to be applied on next-20160729 on top of Liu Ying changes
>    that made imx-ldb atomic
>
> Changes from V1:
>  - New commit message
>  - Removed 3 empty entry points
>  - Removed memory leak from ge_b850v3_lvds_dp_get_modes()
>  - Added a lock for mode setting
>  - Removed a few blank lines
>  - Changed the order at Makefile and Kconfig
>
>  MAINTAINERS                                |   8 +
>  drivers/gpu/drm/bridge/Kconfig             |  11 +
>  drivers/gpu/drm/bridge/Makefile            |   1 +
>  drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c | 397 +++++++++++++++++++++++++++++
>  4 files changed, 417 insertions(+)
>  create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index a5b6569..f51123c 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -5142,6 +5142,14 @@ W:       https://linuxtv.org
>  S:     Maintained
>  F:     drivers/media/radio/radio-gemtek*
>
> +GENERAL ELECTRIC B850V3 LVDS/DP++ BRIDGE
> +M:     Peter Senna Tschudin <peter.senna@collabora.com>
> +M:     Martin Donnelly <martin.donnelly@ge.com>
> +M:     Martyn Welch <martyn.welch@collabora.co.uk>
> +S:     Maintained
> +F:     drivers/gpu/drm/bridge/ge_b850v3_dp2.c
> +F:     Documentation/devicetree/bindings/ge/b850v3_dp2_bridge.txt
> +
>  GENERIC GPIO I2C DRIVER
>  M:     Haavard Skinnemoen <hskinnemoen@gmail.com>
>  S:     Supported
> diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
> index b590e67..b4b70fb 100644
> --- a/drivers/gpu/drm/bridge/Kconfig
> +++ b/drivers/gpu/drm/bridge/Kconfig
> @@ -32,6 +32,17 @@ config DRM_DW_HDMI_AHB_AUDIO
>           Designware HDMI block.  This is used in conjunction with
>           the i.MX6 HDMI driver.
>
> +config DRM_GE_B850V3_LVDS_DP
> +       tristate "GE B850v3 LVDS to DP++ display bridge"
> +       depends on OF
> +       select DRM_KMS_HELPER
> +       select DRM_PANEL
> +       ---help---
> +          This is a driver for the display bridge of
> +          GE B850v3 that convert dual channel LVDS
> +          to DP++. This is used with the i.MX6 imx-ldb
> +          driver.
> +
>  config DRM_NXP_PTN3460
>         tristate "NXP PTN3460 DP/LVDS bridge"
>         depends on OF
> diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
> index efdb07e..b9606f3 100644
> --- a/drivers/gpu/drm/bridge/Makefile
> +++ b/drivers/gpu/drm/bridge/Makefile
> @@ -3,6 +3,7 @@ ccflags-y := -Iinclude/drm
>  obj-$(CONFIG_DRM_ANALOGIX_ANX78XX) += analogix-anx78xx.o
>  obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o
>  obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o
> +obj-$(CONFIG_DRM_GE_B850V3_LVDS_DP) += ge_b850v3_lvds_dp.o
>  obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o
>  obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o
>  obj-$(CONFIG_DRM_SII902X) += sii902x.o
> diff --git a/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c
> new file mode 100644
> index 0000000..eee8eac
> --- /dev/null
> +++ b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c
> @@ -0,0 +1,397 @@
> +/*
> + * Driver for GE B850v3 DP display bridge
> +
> + * Copyright (c) 2016, Collabora Ltd.
> + * Copyright (c) 2016, General Electric Company
> +
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms and conditions of the GNU General Public License,
> + * version 2, as published by the Free Software Foundation.
> +
> + * This program is distributed in the hope it will be useful, but WITHOUT
> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
> + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
> + * more details.
> +
> + * You should have received a copy of the GNU General Public License
> + * along with this program.  If not, see <http://www.gnu.org/licenses/>.
> +
> + * This driver creates a drm_bridge and a drm_connector for the LVDS to DP++
> + * display bridge of the GE B850v3. There are two physical bridges on the video
> + * signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). However
> + * the physical bridges are automatically configured by the input video signal,
> + * and the driver has no access to the video processing pipeline. The driver is
> + * only needed to read EDID from the STDP2690 and to handle HPD events from the
> + * STDP4028. The driver communicates with both bridges over i2c. The video
> + * signal pipeline is as follows:
> + *
> + *   Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output
> + *
> + */
> +
> +#include <linux/gpio.h>
> +#include <linux/i2c.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <drm/drm_atomic.h>
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_crtc_helper.h>
> +#include <drm/drm_edid.h>
> +#include <drm/drmP.h>
> +
> +/*
> + * 220Mhz is a limitation of the host, as the bridge is capable of up to
> + * 330Mhz. See section 9.2.1.2.4 of the i.MX 6Dual/6Quad Applications
> + * Processor Reference Manual for more information about the 220Mhz limit.
> + * The imx-ldb driver will warn about clocks over 170Mhz, but it seem to work
> + * fine.
> + */
> +#define MAX_PIXEL_CLOCK 220000
> +
> +#define EDID_EXT_BLOCK_CNT 0x7E
> +
> +#define STDP4028_IRQ_OUT_CONF_REG 0x02
> +#define STDP4028_DPTX_IRQ_EN_REG 0x3C
> +#define STDP4028_DPTX_IRQ_STS_REG 0x3D
> +#define STDP4028_DPTX_STS_REG 0x3E
> +
> +#define STDP4028_DPTX_DP_IRQ_EN 0x1000
> +
> +#define STDP4028_DPTX_HOTPLUG_IRQ_EN 0x0400
> +#define STDP4028_DPTX_LINK_CH_IRQ_EN 0x2000
> +#define STDP4028_DPTX_IRQ_CONFIG \
> +               (STDP4028_DPTX_LINK_CH_IRQ_EN | STDP4028_DPTX_HOTPLUG_IRQ_EN)
> +
> +#define STDP4028_DPTX_HOTPLUG_STS 0x0200
> +#define STDP4028_DPTX_LINK_STS 0x1000
> +#define STDP4028_CON_STATE_CONNECTED \
> +               (STDP4028_DPTX_HOTPLUG_STS | STDP4028_DPTX_LINK_STS)
> +
> +#define STDP4028_DPTX_HOTPLUG_CH_STS 0x0400
> +#define STDP4028_DPTX_LINK_CH_STS 0x2000
> +#define STDP4028_DPTX_IRQ_CLEAR \
> +               (STDP4028_DPTX_LINK_CH_STS | STDP4028_DPTX_HOTPLUG_CH_STS)
> +
> +struct ge_b850v3_lvds_dp {
> +       struct drm_connector connector;
> +       struct drm_bridge bridge;
> +       struct i2c_client *ge_b850v3_lvds_dp_i2c;
> +       struct i2c_client *edid_i2c;
> +       struct edid *edid;
> +       struct mutex lock;
> +};
> +
> +static inline struct ge_b850v3_lvds_dp *
> +               bridge_to_ge_b850v3_lvds_dp(struct drm_bridge *bridge)
> +{
> +       return container_of(bridge, struct ge_b850v3_lvds_dp, bridge);
> +}
> +
> +static inline struct ge_b850v3_lvds_dp *
> +               connector_to_ge_b850v3_lvds_dp(struct drm_connector *connector)
> +{
> +       return container_of(connector, struct ge_b850v3_lvds_dp, connector);
> +}
> +
> +u8 *stdp2690_get_edid(struct i2c_client *client)
> +{
> +       struct i2c_adapter *adapter = client->adapter;
> +       unsigned char start = 0x00;
> +       unsigned int total_size;
> +       u8 *block = kmalloc(EDID_LENGTH, GFP_KERNEL);
> +
> +       struct i2c_msg msgs[] = {
> +               {
> +                       .addr   = client->addr,
> +                       .flags  = 0,
> +                       .len    = 1,
> +                       .buf    = &start,
> +               }, {
> +                       .addr   = client->addr,
> +                       .flags  = I2C_M_RD,
> +                       .len    = EDID_LENGTH,
> +                       .buf    = block,
> +               }
> +       };
> +
> +       if (!block)
> +               return NULL;
> +
> +       if (i2c_transfer(adapter, msgs, 2) != 2) {
> +               DRM_ERROR("Unable to read EDID.\n");
> +               goto err;
> +       }
> +
> +       if (!drm_edid_block_valid(block, 0, false, NULL)) {
> +               DRM_ERROR("Invalid EDID block\n");
> +               goto err;
> +       }
> +
> +       total_size = (block[EDID_EXT_BLOCK_CNT] + 1) * EDID_LENGTH;
> +       if (total_size > EDID_LENGTH) {
> +               kfree(block);
> +               block = kmalloc(total_size, GFP_KERNEL);
> +               if (!block)
> +                       return NULL;
> +
> +               /* Yes, read the entire buffer, and do not skip the first
> +                * EDID_LENGTH bytes.
> +                */
> +               start = 0x00;
> +               msgs[1].len = total_size;
> +               msgs[1].buf = block;
> +
> +               if (i2c_transfer(adapter, msgs, 2) != 2) {
> +                       DRM_ERROR("Unable to read EDID extension blocks.\n");
> +                       goto err;
> +               }
> +       }
> +
> +       return block;
> +
> +err:
> +       kfree(block);
> +       return NULL;
> +}
> +
> +static int ge_b850v3_lvds_dp_get_modes(struct drm_connector *connector)
> +{
> +       struct ge_b850v3_lvds_dp *ptn_bridge;
> +       struct i2c_client *client;
> +       int num_modes = 0;
> +
> +       ptn_bridge = connector_to_ge_b850v3_lvds_dp(connector);
> +       client = ptn_bridge->edid_i2c;
> +
> +       mutex_lock(&ptn_bridge->lock);
> +
> +       kfree(ptn_bridge->edid);
> +       ptn_bridge->edid = (struct edid *) stdp2690_get_edid(client);
> +
> +       if (ptn_bridge->edid) {
> +               drm_mode_connector_update_edid_property(connector,
> +                               ptn_bridge->edid);
> +               num_modes = drm_add_edid_modes(connector, ptn_bridge->edid);
> +       }
> +
> +       mutex_unlock(&ptn_bridge->lock);
> +
> +       return num_modes;
> +}
> +
> +
> +static enum drm_mode_status ge_b850v3_lvds_dp_mode_valid(
> +               struct drm_connector *connector, struct drm_display_mode *mode)
> +{
> +       if (mode->clock > MAX_PIXEL_CLOCK) {
> +               DRM_INFO("The pixel clock for the mode %s is too high, and not supported.",
> +                               mode->name);
> +               return MODE_CLOCK_HIGH;
> +       }
> +
> +       return MODE_OK;
> +}
> +
> +static const struct
> +drm_connector_helper_funcs ge_b850v3_lvds_dp_connector_helper_funcs = {
> +       .get_modes = ge_b850v3_lvds_dp_get_modes,
> +       .mode_valid = ge_b850v3_lvds_dp_mode_valid,
> +};
> +
> +static enum drm_connector_status ge_b850v3_lvds_dp_detect(
> +               struct drm_connector *connector, bool force)
> +{
> +       struct ge_b850v3_lvds_dp *ptn_bridge =
> +                       connector_to_ge_b850v3_lvds_dp(connector);
> +       struct i2c_client *ge_b850v3_lvds_dp_i2c =
> +                       ptn_bridge->ge_b850v3_lvds_dp_i2c;
> +       s32 link_state;
> +
> +       link_state = i2c_smbus_read_word_data(ge_b850v3_lvds_dp_i2c,
> +                       STDP4028_DPTX_STS_REG);
> +
> +       if (link_state == STDP4028_CON_STATE_CONNECTED)
> +               return connector_status_connected;
> +
> +       if (link_state == 0)
> +               return connector_status_disconnected;
> +
> +       return connector_status_unknown;
> +}
> +
> +static const struct drm_connector_funcs ge_b850v3_lvds_dp_connector_funcs = {
> +       .dpms = drm_atomic_helper_connector_dpms,
> +       .fill_modes = drm_helper_probe_single_connector_modes,
> +       .detect = ge_b850v3_lvds_dp_detect,
> +       .destroy = drm_connector_cleanup,
> +       .reset = drm_atomic_helper_connector_reset,
> +       .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
> +       .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
> +};
> +
> +static irqreturn_t ge_b850v3_lvds_dp_irq_handler(int irq, void *dev_id)
> +{
> +       struct ge_b850v3_lvds_dp *ptn_bridge = dev_id;
> +       struct i2c_client *ge_b850v3_lvds_dp_i2c
> +                       = ptn_bridge->ge_b850v3_lvds_dp_i2c;
> +
> +       mutex_lock(&ptn_bridge->lock);
> +
> +       i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c,
> +                       STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR);
> +
> +       mutex_unlock(&ptn_bridge->lock);
> +
> +       if (ptn_bridge->connector.dev)
> +               drm_kms_helper_hotplug_event(ptn_bridge->connector.dev);
> +
> +       return IRQ_HANDLED;
> +}
> +
> +static int ge_b850v3_lvds_dp_attach(struct drm_bridge *bridge)
> +{
> +       struct ge_b850v3_lvds_dp *ptn_bridge
> +                       = bridge_to_ge_b850v3_lvds_dp(bridge);
> +       struct drm_connector *connector = &ptn_bridge->connector;
> +       struct i2c_client *ge_b850v3_lvds_dp_i2c
> +                       = ptn_bridge->ge_b850v3_lvds_dp_i2c;
> +       int ret;
> +
> +       if (!bridge->encoder) {
> +               DRM_ERROR("Parent encoder object not found");
> +               return -ENODEV;
> +       }
> +
> +       connector->polled = DRM_CONNECTOR_POLL_HPD;
> +
> +       drm_connector_helper_add(connector,
> +                       &ge_b850v3_lvds_dp_connector_helper_funcs);
> +
> +       ret = drm_connector_init(bridge->dev, connector,
> +                       &ge_b850v3_lvds_dp_connector_funcs,
> +                       DRM_MODE_CONNECTOR_DisplayPort);
> +       if (ret) {
> +               DRM_ERROR("Failed to initialize connector with drm\n");
> +               return ret;
> +       }
> +
> +       drm_connector_register(connector);
> +       ret = drm_mode_connector_attach_encoder(connector, bridge->encoder);
> +       if (ret)
> +               return ret;
> +
> +       drm_bridge_enable(bridge);
> +       if (ge_b850v3_lvds_dp_i2c->irq) {
> +               drm_helper_hpd_irq_event(connector->dev);
> +
> +               ret = devm_request_threaded_irq(&ge_b850v3_lvds_dp_i2c->dev,
> +                               ge_b850v3_lvds_dp_i2c->irq, NULL,
> +                               ge_b850v3_lvds_dp_irq_handler,
> +                               IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
> +                               "ge-b850v3-lvds-dp", ptn_bridge);
> +               if (ret)
> +                       return ret;
> +       }
> +
> +       return 0;
> +}
> +
> +static const struct drm_bridge_funcs ge_b850v3_lvds_dp_funcs = {
> +       .attach = ge_b850v3_lvds_dp_attach,
> +};
> +
> +static int ge_b850v3_lvds_dp_probe(struct i2c_client *ge_b850v3_lvds_dp_i2c,
> +                               const struct i2c_device_id *id)
> +{
> +       struct device *dev = &ge_b850v3_lvds_dp_i2c->dev;
> +       struct ge_b850v3_lvds_dp *ptn_bridge;
> +       int ret;
> +       u32 edid_i2c_reg;
> +
> +       ptn_bridge = devm_kzalloc(dev, sizeof(*ptn_bridge), GFP_KERNEL);
> +       if (!ptn_bridge)
> +               return -ENOMEM;
> +
> +       mutex_init(&ptn_bridge->lock);
> +
> +       ptn_bridge->ge_b850v3_lvds_dp_i2c = ge_b850v3_lvds_dp_i2c;
> +       ptn_bridge->bridge.driver_private = ptn_bridge;
> +       i2c_set_clientdata(ge_b850v3_lvds_dp_i2c, ptn_bridge);
> +
> +       ret = of_property_read_u32(dev->of_node, "edid-reg", &edid_i2c_reg);
> +       if (ret) {
> +               dev_err(dev, "edid-reg not specified, aborting...\n");
> +               return -ENODEV;
> +       }
> +
> +       ptn_bridge->edid_i2c = devm_kzalloc(dev,
> +                       sizeof(struct i2c_client), GFP_KERNEL);
> +
> +       if (!ptn_bridge->edid_i2c)
> +               return -ENOMEM;
> +
> +       memcpy(ptn_bridge->edid_i2c, ge_b850v3_lvds_dp_i2c,
> +                       sizeof(struct i2c_client));
> +
> +       ptn_bridge->edid_i2c->addr = (unsigned short) edid_i2c_reg;
> +
> +       /* Configures the bridge to re-enable interrupts after each ack */
> +       i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c,
> +                       STDP4028_IRQ_OUT_CONF_REG, STDP4028_DPTX_DP_IRQ_EN);

I'd check here and below the return value. Guess this is the first
place where the comunication with the chip takes place, if something
is wrong with the i2c communication, i.e the dt configures a wrong
address, you should fail.

> +       i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c,
> +                       STDP4028_DPTX_IRQ_EN_REG, STDP4028_DPTX_IRQ_CONFIG);
> +
> +       /* Clear pending interrupts since power up. */
> +       i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c,
> +                       STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR);
> +
> +       ptn_bridge->bridge.funcs = &ge_b850v3_lvds_dp_funcs;
> +       ptn_bridge->bridge.of_node = dev->of_node;
> +       ret = drm_bridge_add(&ptn_bridge->bridge);
> +       if (ret) {
> +               DRM_ERROR("Failed to add bridge\n");
> +               return ret;
> +       }
> +
> +       return 0;
> +}
> +
> +static int ge_b850v3_lvds_dp_remove(struct i2c_client *ge_b850v3_lvds_dp_i2c)
> +{
> +       struct ge_b850v3_lvds_dp *ptn_bridge =
> +               i2c_get_clientdata(ge_b850v3_lvds_dp_i2c);
> +
> +       drm_bridge_remove(&ptn_bridge->bridge);
> +
> +       kfree(ptn_bridge->edid);
> +
> +       return 0;
> +}
> +
> +static const struct i2c_device_id ge_b850v3_lvds_dp_i2c_table[] = {
> +       {"b850v3-lvds-dp", 0},
> +       {},

nit: Use { /* sentinel */ }

> +};
> +MODULE_DEVICE_TABLE(i2c, ge_b850v3_lvds_dp_i2c_table);
> +
> +static const struct of_device_id ge_b850v3_lvds_dp_match[] = {
> +       { .compatible = "ge,b850v3-lvds-dp" },
> +       {},

nit: Use { /* sentinel */ }

> +};
> +MODULE_DEVICE_TABLE(of, ge_b850v3_lvds_dp_match);
> +
> +static struct i2c_driver ge_b850v3_lvds_dp_driver = {
> +       .id_table       = ge_b850v3_lvds_dp_i2c_table,
> +       .probe          = ge_b850v3_lvds_dp_probe,
> +       .remove         = ge_b850v3_lvds_dp_remove,
> +       .driver         = {
> +               .name           = "ge,b850v3-lvds-dp",
> +               .of_match_table = ge_b850v3_lvds_dp_match,
> +       },
> +};
> +module_i2c_driver(ge_b850v3_lvds_dp_driver);
> +
> +MODULE_AUTHOR("Peter Senna Tschudin <peter.senna@collabora.com>");
> +MODULE_AUTHOR("Martyn Welch <martyn.welch@collabora.co.uk>");
> +MODULE_DESCRIPTION("GE LVDS to DP++ display bridge)");
> +MODULE_LICENSE("GPL v2");
> --
> 2.5.5
>

After solving the i2c thing,

Reviewed-by: Enric Balletbo i Serra <enric.balletbo@collabora.com>

^ permalink raw reply	[flat|nested] 64+ messages in thread

* [PATCH V5 0/4] Add driver for GE B850v3 LVDS/DP++ Bridge
  2016-05-30 16:39 [PATCH 0/5] Add driver for GE B850v3 LVDS/DP++ Bridge Peter Senna Tschudin
                   ` (7 preceding siblings ...)
  2016-08-04 22:36 ` [PATCH V4 0/4] Add driver for " Peter Senna Tschudin
@ 2016-08-09 16:41 ` Peter Senna Tschudin
  2016-08-09 16:41   ` [PATCH V5 1/4] drm/imx-ldb: Add support to drm-bridge Peter Senna Tschudin
                     ` (3 more replies)
  8 siblings, 4 replies; 64+ messages in thread
From: Peter Senna Tschudin @ 2016-08-09 16:41 UTC (permalink / raw)
  To: airlied, akpm, daniel.vetter, davem, devicetree, dri-devel,
	enric.balletbo, eballetbo, galak, gregkh, heiko, ijc+devicetree,
	javier, jslaby, kernel, linux-arm-kernel, linux, linux-kernel,
	linux, mark.rutland, martin.donnelly, martyn.welch, mchehab,
	pawel.moll, peter.senna, peter.senna, p.zabel, thierry.reding,
	rmk+kernel, robh+dt, shawnguo, tiwai, treding, ykk

The series adds a driver that creates a drm_bridge and a drm_connector for the
LVDS to DP++ display bridge of the GE B850v3.

There are two physical bridges on the video signal pipeline: a STDP4028(LVDS to
DP) and a STDP2690(DP to DP++).  The hardware and firmware made it complicated
for this binding to comprise two device tree nodes, as the design goal is to
configure both bridges based on the LVDS signal, which leave the driver
powerless to control the video processing pipeline. The two bridges behaves as
a single bridge, and the driver is only needed for telling the host about EDID /
HPD, and for giving the host powers to ack interrupts. The video signal
pipeline is as follows:

  Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output

The patches from the series:
 [1/4] Change the imx-ldb driver to allow attaching a bridge and not only a LVDS
       panel.

 [2/4] Devicetree documentation for the GE B850v3 LVDS/DP++ Bridge

 [3/4] Add the driver, make changes to MAINTAINERS, Kconfig and Makefile

 [4/4] Make the changes to the B850v3 dts file to enable the GE B850v3
       LVDS/DP++ Bridge.

Changes from V3:
 - Removed the patch that was configuring the mapping between IPUs and external
   displays on the dts file

Peter Senna Tschudin (4):
  drm/imx-ldb: Add support to drm-bridge
  Documentation/devicetree/bindings: b850v3_lvds_dp
  drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge
  dts/imx6q-b850v3: Use GE B850v3 LVDS/DP++ Bridge

 .../devicetree/bindings/ge/b850v3-lvds-dp.txt      |  37 ++
 MAINTAINERS                                        |   8 +
 arch/arm/boot/dts/imx6q-b850v3.dts                 |  30 ++
 drivers/gpu/drm/bridge/Kconfig                     |  11 +
 drivers/gpu/drm/bridge/Makefile                    |   1 +
 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c         | 405 +++++++++++++++++++++
 drivers/gpu/drm/imx/imx-ldb.c                      | 118 ++++--
 7 files changed, 570 insertions(+), 40 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt
 create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c

-- 
2.5.5

^ permalink raw reply	[flat|nested] 64+ messages in thread

* [PATCH V5 1/4] drm/imx-ldb: Add support to drm-bridge
  2016-08-09 16:41 ` [PATCH V5 0/4] Add driver for " Peter Senna Tschudin
@ 2016-08-09 16:41   ` Peter Senna Tschudin
  2016-08-11  9:38     ` Philipp Zabel
  2016-08-09 16:41   ` [PATCH V5 2/4] Documentation/devicetree/bindings: b850v3_lvds_dp Peter Senna Tschudin
                     ` (2 subsequent siblings)
  3 siblings, 1 reply; 64+ messages in thread
From: Peter Senna Tschudin @ 2016-08-09 16:41 UTC (permalink / raw)
  To: airlied, akpm, daniel.vetter, davem, devicetree, dri-devel,
	enric.balletbo, eballetbo, galak, gregkh, heiko, ijc+devicetree,
	javier, jslaby, kernel, linux-arm-kernel, linux, linux-kernel,
	linux, mark.rutland, martin.donnelly, martyn.welch, mchehab,
	pawel.moll, peter.senna, peter.senna, p.zabel, thierry.reding,
	rmk+kernel, robh+dt, shawnguo, tiwai, treding, ykk
  Cc: Rob Herring, Fabio Estevam

Add support to attach a drm_bridge to imx-ldb in addition to
existing support to attach a LVDS panel.

This patch does a simple code refactoring by moving code
from for_each_child_of_node iterator to a new function named
imx_ldb_panel_ddc(). This was necessary to allow the panel ddc
code to run only when the imx_ldb is not attached to a bridge.

Cc: Martyn Welch <martyn.welch@collabora.co.uk>
Cc: Martin Donnelly <martin.donnelly@ge.com>
Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com>
Cc: Philipp Zabel <p.zabel@pengutronix.de>
Cc: Rob Herring <robh@kernel.org>
Cc: Fabio Estevam <fabio.estevam@nxp.com>
Cc: David Airlie <airlied@linux.ie>
Cc: Thierry Reding <treding@nvidia.com>
Cc: Thierry Reding <thierry.reding@gmail.com>
Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com>
---
Unchanged from V4

Changes from V3:
 - The connector is created when there is no bridge instead of only when
   there is a pannel
 - Tested on next-20160804

Changes from V2:
 - Updated to be aplied on top of Liu Ying changes that made imx-ldb atomic
 - Tested on next-20160729

Changes from V1:
 - Reanmed ext_bridge to bridge
 - Removed empty entry point imx_ldb_encoder_enable()
 - Adapted the code to apply to the latest linux next: next-20160609

 drivers/gpu/drm/imx/imx-ldb.c | 118 ++++++++++++++++++++++++++++--------------
 1 file changed, 78 insertions(+), 40 deletions(-)

diff --git a/drivers/gpu/drm/imx/imx-ldb.c b/drivers/gpu/drm/imx/imx-ldb.c
index b03919e..4a33077 100644
--- a/drivers/gpu/drm/imx/imx-ldb.c
+++ b/drivers/gpu/drm/imx/imx-ldb.c
@@ -57,7 +57,11 @@ struct imx_ldb_channel {
 	struct imx_ldb *ldb;
 	struct drm_connector connector;
 	struct drm_encoder encoder;
+
+	/* Defines what is connected to the ldb, only one at a time */
 	struct drm_panel *panel;
+	struct drm_bridge *bridge;
+
 	struct device_node *child;
 	struct i2c_adapter *ddc;
 	int chno;
@@ -466,10 +470,30 @@ static int imx_ldb_register(struct drm_device *drm,
 	drm_encoder_init(drm, encoder, &imx_ldb_encoder_funcs,
 			 DRM_MODE_ENCODER_LVDS, NULL);
 
-	drm_connector_helper_add(&imx_ldb_ch->connector,
-			&imx_ldb_connector_helper_funcs);
-	drm_connector_init(drm, &imx_ldb_ch->connector,
-			   &imx_ldb_connector_funcs, DRM_MODE_CONNECTOR_LVDS);
+	if (imx_ldb_ch->bridge) {
+		imx_ldb_ch->bridge->encoder = encoder;
+
+		imx_ldb_ch->encoder.bridge = imx_ldb_ch->bridge;
+		ret = drm_bridge_attach(drm, imx_ldb_ch->bridge);
+		if (ret) {
+			DRM_ERROR("Failed to initialize bridge with drm\n");
+			return ret;
+		}
+	} else {
+		/*
+		 * We want to add the connector whenever there is no bridge
+		 * that brings its own, not only when there is a panel. For
+		 * historical reasons, the ldb driver can also work without
+		 * a panel.
+		 */
+		drm_connector_helper_add(&imx_ldb_ch->connector,
+				&imx_ldb_connector_helper_funcs);
+		drm_connector_init(drm, &imx_ldb_ch->connector,
+				&imx_ldb_connector_funcs,
+				DRM_MODE_CONNECTOR_LVDS);
+		drm_mode_connector_attach_encoder(&imx_ldb_ch->connector,
+				encoder);
+	}
 
 	if (imx_ldb_ch->panel) {
 		ret = drm_panel_attach(imx_ldb_ch->panel,
@@ -478,8 +502,6 @@ static int imx_ldb_register(struct drm_device *drm,
 			return ret;
 	}
 
-	drm_mode_connector_attach_encoder(&imx_ldb_ch->connector, encoder);
-
 	return 0;
 }
 
@@ -548,6 +570,45 @@ static const struct of_device_id imx_ldb_dt_ids[] = {
 };
 MODULE_DEVICE_TABLE(of, imx_ldb_dt_ids);
 
+static int imx_ldb_panel_ddc(struct device *dev,
+		struct imx_ldb_channel *channel, struct device_node *child)
+{
+	struct device_node *ddc_node;
+	const u8 *edidp;
+	int ret;
+
+	ddc_node = of_parse_phandle(child, "ddc-i2c-bus", 0);
+	if (ddc_node) {
+		channel->ddc = of_find_i2c_adapter_by_node(ddc_node);
+		of_node_put(ddc_node);
+		if (!channel->ddc) {
+			dev_warn(dev, "failed to get ddc i2c adapter\n");
+			return -EPROBE_DEFER;
+		}
+	}
+
+	if (!channel->ddc) {
+		/* if no DDC available, fallback to hardcoded EDID */
+		dev_dbg(dev, "no ddc available\n");
+
+		edidp = of_get_property(child, "edid",
+					&channel->edid_len);
+		if (edidp) {
+			channel->edid = kmemdup(edidp,
+						channel->edid_len,
+						GFP_KERNEL);
+		} else if (!channel->panel) {
+			/* fallback to display-timings node */
+			ret = of_get_drm_display_mode(child,
+						      &channel->mode,
+						      OF_USE_NATIVE_MODE);
+			if (!ret)
+				channel->mode_valid = 1;
+		}
+	}
+	return 0;
+}
+
 static int imx_ldb_bind(struct device *dev, struct device *master, void *data)
 {
 	struct drm_device *drm = data;
@@ -555,7 +616,6 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data)
 	const struct of_device_id *of_id =
 			of_match_device(imx_ldb_dt_ids, dev);
 	struct device_node *child;
-	const u8 *edidp;
 	struct imx_ldb *imx_ldb;
 	int dual;
 	int ret;
@@ -605,7 +665,6 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data)
 
 	for_each_child_of_node(np, child) {
 		struct imx_ldb_channel *channel;
-		struct device_node *ddc_node;
 		struct device_node *ep;
 		int bus_format;
 
@@ -638,46 +697,25 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data)
 
 			remote = of_graph_get_remote_port_parent(ep);
 			of_node_put(ep);
-			if (remote)
+			if (remote) {
 				channel->panel = of_drm_find_panel(remote);
-			else
+				channel->bridge = of_drm_find_bridge(remote);
+			} else
 				return -EPROBE_DEFER;
 			of_node_put(remote);
-			if (!channel->panel) {
-				dev_err(dev, "panel not found: %s\n",
-					remote->full_name);
-				return -EPROBE_DEFER;
-			}
-		}
 
-		ddc_node = of_parse_phandle(child, "ddc-i2c-bus", 0);
-		if (ddc_node) {
-			channel->ddc = of_find_i2c_adapter_by_node(ddc_node);
-			of_node_put(ddc_node);
-			if (!channel->ddc) {
-				dev_warn(dev, "failed to get ddc i2c adapter\n");
+			if (!channel->panel && !channel->bridge) {
+				dev_err(dev, "panel/bridge not found: %s\n",
+					remote->full_name);
 				return -EPROBE_DEFER;
 			}
 		}
 
-		if (!channel->ddc) {
-			/* if no DDC available, fallback to hardcoded EDID */
-			dev_dbg(dev, "no ddc available\n");
-
-			edidp = of_get_property(child, "edid",
-						&channel->edid_len);
-			if (edidp) {
-				channel->edid = kmemdup(edidp,
-							channel->edid_len,
-							GFP_KERNEL);
-			} else if (!channel->panel) {
-				/* fallback to display-timings node */
-				ret = of_get_drm_display_mode(child,
-							      &channel->mode,
-							      OF_USE_NATIVE_MODE);
-				if (!ret)
-					channel->mode_valid = 1;
-			}
+		/* panel ddc only if there is no bridge */
+		if (!channel->bridge) {
+			ret = imx_ldb_panel_ddc(dev, channel, child);
+			if (ret)
+				return ret;
 		}
 
 		bus_format = of_get_bus_format(dev, child);
-- 
2.5.5

^ permalink raw reply related	[flat|nested] 64+ messages in thread

* [PATCH V5 2/4] Documentation/devicetree/bindings: b850v3_lvds_dp
  2016-08-09 16:41 ` [PATCH V5 0/4] Add driver for " Peter Senna Tschudin
  2016-08-09 16:41   ` [PATCH V5 1/4] drm/imx-ldb: Add support to drm-bridge Peter Senna Tschudin
@ 2016-08-09 16:41   ` Peter Senna Tschudin
  2016-09-26  8:26     ` Peter Senna Tschudin
  2016-08-09 16:41   ` [PATCH V5 3/4] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge Peter Senna Tschudin
  2016-08-09 16:41   ` [PATCH V5 4/4] dts/imx6q-b850v3: Use " Peter Senna Tschudin
  3 siblings, 1 reply; 64+ messages in thread
From: Peter Senna Tschudin @ 2016-08-09 16:41 UTC (permalink / raw)
  To: airlied, akpm, daniel.vetter, davem, devicetree, dri-devel,
	enric.balletbo, eballetbo, galak, gregkh, heiko, ijc+devicetree,
	javier, jslaby, kernel, linux-arm-kernel, linux, linux-kernel,
	linux, mark.rutland, martin.donnelly, martyn.welch, mchehab,
	pawel.moll, peter.senna, peter.senna, p.zabel, thierry.reding,
	rmk+kernel, robh+dt, shawnguo, tiwai, treding, ykk
  Cc: Rob Herring, Fabio Estevam

Devicetree bindings documentation for the GE B850v3 LVDS/DP++
display bridge.

Cc: Martyn Welch <martyn.welch@collabora.co.uk>
Cc: Martin Donnelly <martin.donnelly@ge.com>
Cc: Javier Martinez Canillas <javier@dowhile0.org>
Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com>
Cc: Philipp Zabel <p.zabel@pengutronix.de>
Cc: Rob Herring <robh@kernel.org>
Cc: Fabio Estevam <fabio.estevam@nxp.com>
Acked-by: Rob Herring <robh@kernel.org>
Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com>
---
Unchanged from V4

Changes from V3:
 - 2/4 instead of 3/5

Unchanged from V2

Changes from V1:
 - Replaced '_' by '-' in node names or compatible strings
 - Added missing @73 to the example


 .../devicetree/bindings/ge/b850v3-lvds-dp.txt      | 37 ++++++++++++++++++++++
 1 file changed, 37 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt

diff --git a/Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt b/Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt
new file mode 100644
index 0000000..f05c3e9
--- /dev/null
+++ b/Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt
@@ -0,0 +1,37 @@
+Driver for GE B850v3 LVDS/DP++ display bridge
+
+Required properties:
+  - compatible : should be "ge,b850v3-lvds-dp".
+  - reg : should contain the address used to ack the interrupts.
+  - interrupt-parent : phandle of the interrupt controller that services
+    interrupts to the device
+  - interrupts : one interrupt should be described here, as in
+    <0 IRQ_TYPE_LEVEL_HIGH>.
+  - edid-reg : should contain the address used to read edid information
+  - port : should describe the video signal connection between the host
+    and the bridge.
+
+Example:
+
+&mux2_i2c2 {
+	status = "okay";
+	clock-frequency = <100000>;
+
+	b850v3-lvds-dp-bridge@73  {
+		compatible = "ge,b850v3-lvds-dp";
+		#address-cells = <1>;
+		#size-cells = <0>;
+
+		reg = <0x73>;
+		interrupt-parent = <&gpio2>;
+		interrupts = <0 IRQ_TYPE_LEVEL_HIGH>;
+
+		edid-reg = <0x72>;
+
+		port {
+			b850v3_dp_bridge_in: endpoint {
+				remote-endpoint = <&lvds0_out>;
+			};
+		};
+	};
+};
-- 
2.5.5

^ permalink raw reply related	[flat|nested] 64+ messages in thread

* [PATCH V5 3/4] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge
  2016-08-09 16:41 ` [PATCH V5 0/4] Add driver for " Peter Senna Tschudin
  2016-08-09 16:41   ` [PATCH V5 1/4] drm/imx-ldb: Add support to drm-bridge Peter Senna Tschudin
  2016-08-09 16:41   ` [PATCH V5 2/4] Documentation/devicetree/bindings: b850v3_lvds_dp Peter Senna Tschudin
@ 2016-08-09 16:41   ` Peter Senna Tschudin
  2016-08-16  4:15     ` Archit Taneja
                       ` (2 more replies)
  2016-08-09 16:41   ` [PATCH V5 4/4] dts/imx6q-b850v3: Use " Peter Senna Tschudin
  3 siblings, 3 replies; 64+ messages in thread
From: Peter Senna Tschudin @ 2016-08-09 16:41 UTC (permalink / raw)
  To: airlied, akpm, daniel.vetter, davem, devicetree, dri-devel,
	enric.balletbo, eballetbo, galak, gregkh, heiko, ijc+devicetree,
	javier, jslaby, kernel, linux-arm-kernel, linux, linux-kernel,
	linux, mark.rutland, martin.donnelly, martyn.welch, mchehab,
	pawel.moll, peter.senna, peter.senna, p.zabel, thierry.reding,
	rmk+kernel, robh+dt, shawnguo, tiwai, treding, ykk
  Cc: Rob Herring, Fabio Estevam, Archit Taneja

Add a driver that create a drm_bridge and a drm_connector for the LVDS
to DP++ display bridge of the GE B850v3.

There are two physical bridges on the video signal pipeline: a
STDP4028(LVDS to DP) and a STDP2690(DP to DP++).  The hardware and
firmware made it complicated for this binding to comprise two device
tree nodes, as the design goal is to configure both bridges based on
the LVDS signal, which leave the driver powerless to control the video
processing pipeline. The two bridges behaves as a single bridge, and
the driver is only needed for telling the host about EDID / HPD, and
for giving the host powers to ack interrupts. The video signal pipeline
is as follows:

  Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output

Cc: Martyn Welch <martyn.welch@collabora.co.uk>
Cc: Martin Donnelly <martin.donnelly@ge.com>
Cc: Daniel Vetter <daniel.vetter@ffwll.ch>
Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com>
Cc: Philipp Zabel <p.zabel@pengutronix.de>
Cc: Rob Herring <robh@kernel.org>
Cc: Fabio Estevam <fabio.estevam@nxp.com>
CC: David Airlie <airlied@linux.ie>
CC: Thierry Reding <treding@nvidia.com>
CC: Thierry Reding <thierry.reding@gmail.com>
Reviewed-by: Enric Balletbo <enric.balletbo@collabora.com>
Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com>
---
Changes from V4:
 - Check the output of the first call to i2c_smbus_write_word_data() and return
   it's error code for failing gracefully on i2c issues
 - Renamed the i2c_driver.name from "ge,b850v3-lvds-dp" to "b850v3-lvds-dp" to
   remove the comma from the driver name

Changes from V3:
 - 3/4 instead of 4/5
 - Tested on next-20160804

Changes from V2:
 - Made it atomic to be applied on next-20160729 on top of Liu Ying changes
   that made imx-ldb atomic

Changes from V1:
 - New commit message
 - Removed 3 empty entry points
 - Removed memory leak from ge_b850v3_lvds_dp_get_modes()
 - Added a lock for mode setting
 - Removed a few blank lines
 - Changed the order at Makefile and Kconfig

 MAINTAINERS                                |   8 +
 drivers/gpu/drm/bridge/Kconfig             |  11 +
 drivers/gpu/drm/bridge/Makefile            |   1 +
 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c | 405 +++++++++++++++++++++++++++++
 4 files changed, 425 insertions(+)
 create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c

diff --git a/MAINTAINERS b/MAINTAINERS
index a306795..e8d106a 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -5142,6 +5142,14 @@ W:	https://linuxtv.org
 S:	Maintained
 F:	drivers/media/radio/radio-gemtek*
 
+GENERAL ELECTRIC B850V3 LVDS/DP++ BRIDGE
+M:	Peter Senna Tschudin <peter.senna@collabora.com>
+M:	Martin Donnelly <martin.donnelly@ge.com>
+M:	Martyn Welch <martyn.welch@collabora.co.uk>
+S:	Maintained
+F:	drivers/gpu/drm/bridge/ge_b850v3_dp2.c
+F:	Documentation/devicetree/bindings/ge/b850v3_dp2_bridge.txt
+
 GENERIC GPIO I2C DRIVER
 M:	Haavard Skinnemoen <hskinnemoen@gmail.com>
 S:	Supported
diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
index b590e67..b4b70fb 100644
--- a/drivers/gpu/drm/bridge/Kconfig
+++ b/drivers/gpu/drm/bridge/Kconfig
@@ -32,6 +32,17 @@ config DRM_DW_HDMI_AHB_AUDIO
 	  Designware HDMI block.  This is used in conjunction with
 	  the i.MX6 HDMI driver.
 
+config DRM_GE_B850V3_LVDS_DP
+	tristate "GE B850v3 LVDS to DP++ display bridge"
+	depends on OF
+	select DRM_KMS_HELPER
+	select DRM_PANEL
+	---help---
+          This is a driver for the display bridge of
+          GE B850v3 that convert dual channel LVDS
+          to DP++. This is used with the i.MX6 imx-ldb
+          driver.
+
 config DRM_NXP_PTN3460
 	tristate "NXP PTN3460 DP/LVDS bridge"
 	depends on OF
diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
index efdb07e..b9606f3 100644
--- a/drivers/gpu/drm/bridge/Makefile
+++ b/drivers/gpu/drm/bridge/Makefile
@@ -3,6 +3,7 @@ ccflags-y := -Iinclude/drm
 obj-$(CONFIG_DRM_ANALOGIX_ANX78XX) += analogix-anx78xx.o
 obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o
 obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o
+obj-$(CONFIG_DRM_GE_B850V3_LVDS_DP) += ge_b850v3_lvds_dp.o
 obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o
 obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o
 obj-$(CONFIG_DRM_SII902X) += sii902x.o
diff --git a/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c
new file mode 100644
index 0000000..81e9279
--- /dev/null
+++ b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c
@@ -0,0 +1,405 @@
+/*
+ * Driver for GE B850v3 DP display bridge
+
+ * Copyright (c) 2016, Collabora Ltd.
+ * Copyright (c) 2016, General Electric Company
+
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+ * This driver creates a drm_bridge and a drm_connector for the LVDS to DP++
+ * display bridge of the GE B850v3. There are two physical bridges on the video
+ * signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). However
+ * the physical bridges are automatically configured by the input video signal,
+ * and the driver has no access to the video processing pipeline. The driver is
+ * only needed to read EDID from the STDP2690 and to handle HPD events from the
+ * STDP4028. The driver communicates with both bridges over i2c. The video
+ * signal pipeline is as follows:
+ *
+ *   Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output
+ *
+ */
+
+#include <linux/gpio.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_edid.h>
+#include <drm/drmP.h>
+
+/*
+ * 220Mhz is a limitation of the host, as the bridge is capable of up to
+ * 330Mhz. See section 9.2.1.2.4 of the i.MX 6Dual/6Quad Applications
+ * Processor Reference Manual for more information about the 220Mhz limit.
+ * The imx-ldb driver will warn about clocks over 170Mhz, but it seem to work
+ * fine.
+ */
+#define MAX_PIXEL_CLOCK 220000
+
+#define EDID_EXT_BLOCK_CNT 0x7E
+
+#define STDP4028_IRQ_OUT_CONF_REG 0x02
+#define STDP4028_DPTX_IRQ_EN_REG 0x3C
+#define STDP4028_DPTX_IRQ_STS_REG 0x3D
+#define STDP4028_DPTX_STS_REG 0x3E
+
+#define STDP4028_DPTX_DP_IRQ_EN 0x1000
+
+#define STDP4028_DPTX_HOTPLUG_IRQ_EN 0x0400
+#define STDP4028_DPTX_LINK_CH_IRQ_EN 0x2000
+#define STDP4028_DPTX_IRQ_CONFIG \
+		(STDP4028_DPTX_LINK_CH_IRQ_EN | STDP4028_DPTX_HOTPLUG_IRQ_EN)
+
+#define STDP4028_DPTX_HOTPLUG_STS 0x0200
+#define STDP4028_DPTX_LINK_STS 0x1000
+#define STDP4028_CON_STATE_CONNECTED \
+		(STDP4028_DPTX_HOTPLUG_STS | STDP4028_DPTX_LINK_STS)
+
+#define STDP4028_DPTX_HOTPLUG_CH_STS 0x0400
+#define STDP4028_DPTX_LINK_CH_STS 0x2000
+#define STDP4028_DPTX_IRQ_CLEAR \
+		(STDP4028_DPTX_LINK_CH_STS | STDP4028_DPTX_HOTPLUG_CH_STS)
+
+struct ge_b850v3_lvds_dp {
+	struct drm_connector connector;
+	struct drm_bridge bridge;
+	struct i2c_client *ge_b850v3_lvds_dp_i2c;
+	struct i2c_client *edid_i2c;
+	struct edid *edid;
+	struct mutex lock;
+};
+
+static inline struct ge_b850v3_lvds_dp *
+		bridge_to_ge_b850v3_lvds_dp(struct drm_bridge *bridge)
+{
+	return container_of(bridge, struct ge_b850v3_lvds_dp, bridge);
+}
+
+static inline struct ge_b850v3_lvds_dp *
+		connector_to_ge_b850v3_lvds_dp(struct drm_connector *connector)
+{
+	return container_of(connector, struct ge_b850v3_lvds_dp, connector);
+}
+
+u8 *stdp2690_get_edid(struct i2c_client *client)
+{
+	struct i2c_adapter *adapter = client->adapter;
+	unsigned char start = 0x00;
+	unsigned int total_size;
+	u8 *block = kmalloc(EDID_LENGTH, GFP_KERNEL);
+
+	struct i2c_msg msgs[] = {
+		{
+			.addr	= client->addr,
+			.flags	= 0,
+			.len	= 1,
+			.buf	= &start,
+		}, {
+			.addr	= client->addr,
+			.flags	= I2C_M_RD,
+			.len	= EDID_LENGTH,
+			.buf	= block,
+		}
+	};
+
+	if (!block)
+		return NULL;
+
+	if (i2c_transfer(adapter, msgs, 2) != 2) {
+		DRM_ERROR("Unable to read EDID.\n");
+		goto err;
+	}
+
+	if (!drm_edid_block_valid(block, 0, false, NULL)) {
+		DRM_ERROR("Invalid EDID block\n");
+		goto err;
+	}
+
+	total_size = (block[EDID_EXT_BLOCK_CNT] + 1) * EDID_LENGTH;
+	if (total_size > EDID_LENGTH) {
+		kfree(block);
+		block = kmalloc(total_size, GFP_KERNEL);
+		if (!block)
+			return NULL;
+
+		/* Yes, read the entire buffer, and do not skip the first
+		 * EDID_LENGTH bytes.
+		 */
+		start = 0x00;
+		msgs[1].len = total_size;
+		msgs[1].buf = block;
+
+		if (i2c_transfer(adapter, msgs, 2) != 2) {
+			DRM_ERROR("Unable to read EDID extension blocks.\n");
+			goto err;
+		}
+	}
+
+	return block;
+
+err:
+	kfree(block);
+	return NULL;
+}
+
+static int ge_b850v3_lvds_dp_get_modes(struct drm_connector *connector)
+{
+	struct ge_b850v3_lvds_dp *ptn_bridge;
+	struct i2c_client *client;
+	int num_modes = 0;
+
+	ptn_bridge = connector_to_ge_b850v3_lvds_dp(connector);
+	client = ptn_bridge->edid_i2c;
+
+	mutex_lock(&ptn_bridge->lock);
+
+	kfree(ptn_bridge->edid);
+	ptn_bridge->edid = (struct edid *) stdp2690_get_edid(client);
+
+	if (ptn_bridge->edid) {
+		drm_mode_connector_update_edid_property(connector,
+				ptn_bridge->edid);
+		num_modes = drm_add_edid_modes(connector, ptn_bridge->edid);
+	}
+
+	mutex_unlock(&ptn_bridge->lock);
+
+	return num_modes;
+}
+
+
+static enum drm_mode_status ge_b850v3_lvds_dp_mode_valid(
+		struct drm_connector *connector, struct drm_display_mode *mode)
+{
+	if (mode->clock > MAX_PIXEL_CLOCK) {
+		DRM_INFO("The pixel clock for the mode %s is too high, and not supported.",
+				mode->name);
+		return MODE_CLOCK_HIGH;
+	}
+
+	return MODE_OK;
+}
+
+static const struct
+drm_connector_helper_funcs ge_b850v3_lvds_dp_connector_helper_funcs = {
+	.get_modes = ge_b850v3_lvds_dp_get_modes,
+	.mode_valid = ge_b850v3_lvds_dp_mode_valid,
+};
+
+static enum drm_connector_status ge_b850v3_lvds_dp_detect(
+		struct drm_connector *connector, bool force)
+{
+	struct ge_b850v3_lvds_dp *ptn_bridge =
+			connector_to_ge_b850v3_lvds_dp(connector);
+	struct i2c_client *ge_b850v3_lvds_dp_i2c =
+			ptn_bridge->ge_b850v3_lvds_dp_i2c;
+	s32 link_state;
+
+	link_state = i2c_smbus_read_word_data(ge_b850v3_lvds_dp_i2c,
+			STDP4028_DPTX_STS_REG);
+
+	if (link_state == STDP4028_CON_STATE_CONNECTED)
+		return connector_status_connected;
+
+	if (link_state == 0)
+		return connector_status_disconnected;
+
+	return connector_status_unknown;
+}
+
+static const struct drm_connector_funcs ge_b850v3_lvds_dp_connector_funcs = {
+	.dpms = drm_atomic_helper_connector_dpms,
+	.fill_modes = drm_helper_probe_single_connector_modes,
+	.detect = ge_b850v3_lvds_dp_detect,
+	.destroy = drm_connector_cleanup,
+	.reset = drm_atomic_helper_connector_reset,
+	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
+static irqreturn_t ge_b850v3_lvds_dp_irq_handler(int irq, void *dev_id)
+{
+	struct ge_b850v3_lvds_dp *ptn_bridge = dev_id;
+	struct i2c_client *ge_b850v3_lvds_dp_i2c
+			= ptn_bridge->ge_b850v3_lvds_dp_i2c;
+
+	mutex_lock(&ptn_bridge->lock);
+
+	i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c,
+			STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR);
+
+	mutex_unlock(&ptn_bridge->lock);
+
+	if (ptn_bridge->connector.dev)
+		drm_kms_helper_hotplug_event(ptn_bridge->connector.dev);
+
+	return IRQ_HANDLED;
+}
+
+static int ge_b850v3_lvds_dp_attach(struct drm_bridge *bridge)
+{
+	struct ge_b850v3_lvds_dp *ptn_bridge
+			= bridge_to_ge_b850v3_lvds_dp(bridge);
+	struct drm_connector *connector = &ptn_bridge->connector;
+	struct i2c_client *ge_b850v3_lvds_dp_i2c
+			= ptn_bridge->ge_b850v3_lvds_dp_i2c;
+	int ret;
+
+	if (!bridge->encoder) {
+		DRM_ERROR("Parent encoder object not found");
+		return -ENODEV;
+	}
+
+	connector->polled = DRM_CONNECTOR_POLL_HPD;
+
+	drm_connector_helper_add(connector,
+			&ge_b850v3_lvds_dp_connector_helper_funcs);
+
+	ret = drm_connector_init(bridge->dev, connector,
+			&ge_b850v3_lvds_dp_connector_funcs,
+			DRM_MODE_CONNECTOR_DisplayPort);
+	if (ret) {
+		DRM_ERROR("Failed to initialize connector with drm\n");
+		return ret;
+	}
+
+	drm_connector_register(connector);
+	ret = drm_mode_connector_attach_encoder(connector, bridge->encoder);
+	if (ret)
+		return ret;
+
+	drm_bridge_enable(bridge);
+	if (ge_b850v3_lvds_dp_i2c->irq) {
+		drm_helper_hpd_irq_event(connector->dev);
+
+		ret = devm_request_threaded_irq(&ge_b850v3_lvds_dp_i2c->dev,
+				ge_b850v3_lvds_dp_i2c->irq, NULL,
+				ge_b850v3_lvds_dp_irq_handler,
+				IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
+				"ge-b850v3-lvds-dp", ptn_bridge);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static const struct drm_bridge_funcs ge_b850v3_lvds_dp_funcs = {
+	.attach = ge_b850v3_lvds_dp_attach,
+};
+
+static int ge_b850v3_lvds_dp_probe(struct i2c_client *ge_b850v3_lvds_dp_i2c,
+				const struct i2c_device_id *id)
+{
+	struct device *dev = &ge_b850v3_lvds_dp_i2c->dev;
+	struct ge_b850v3_lvds_dp *ptn_bridge;
+	int ret;
+	u32 edid_i2c_reg;
+
+	ptn_bridge = devm_kzalloc(dev, sizeof(*ptn_bridge), GFP_KERNEL);
+	if (!ptn_bridge)
+		return -ENOMEM;
+
+	mutex_init(&ptn_bridge->lock);
+
+	ptn_bridge->ge_b850v3_lvds_dp_i2c = ge_b850v3_lvds_dp_i2c;
+	ptn_bridge->bridge.driver_private = ptn_bridge;
+	i2c_set_clientdata(ge_b850v3_lvds_dp_i2c, ptn_bridge);
+
+	ret = of_property_read_u32(dev->of_node, "edid-reg", &edid_i2c_reg);
+	if (ret) {
+		dev_err(dev, "edid-reg not specified, aborting...\n");
+		return -ENODEV;
+	}
+
+	ptn_bridge->edid_i2c = devm_kzalloc(dev,
+			sizeof(struct i2c_client), GFP_KERNEL);
+
+	if (!ptn_bridge->edid_i2c)
+		return -ENOMEM;
+
+	memcpy(ptn_bridge->edid_i2c, ge_b850v3_lvds_dp_i2c,
+			sizeof(struct i2c_client));
+
+	ptn_bridge->edid_i2c->addr = (unsigned short) edid_i2c_reg;
+
+	/*
+	 * Configures the bridge to re-enable interrupts after each ack. As
+	 * this is the first communication with the chip, fail on error.
+	 */
+	ret = i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c,
+			STDP4028_IRQ_OUT_CONF_REG, STDP4028_DPTX_DP_IRQ_EN);
+	if (ret) {
+		dev_err(dev, "i2c communication failed, aborting...\n");
+		return ret;
+	}
+
+	i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c,
+			STDP4028_DPTX_IRQ_EN_REG, STDP4028_DPTX_IRQ_CONFIG);
+
+	/* Clear pending interrupts since power up. */
+	i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c,
+			STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR);
+
+	ptn_bridge->bridge.funcs = &ge_b850v3_lvds_dp_funcs;
+	ptn_bridge->bridge.of_node = dev->of_node;
+	ret = drm_bridge_add(&ptn_bridge->bridge);
+	if (ret) {
+		DRM_ERROR("Failed to add bridge\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static int ge_b850v3_lvds_dp_remove(struct i2c_client *ge_b850v3_lvds_dp_i2c)
+{
+	struct ge_b850v3_lvds_dp *ptn_bridge =
+		i2c_get_clientdata(ge_b850v3_lvds_dp_i2c);
+
+	drm_bridge_remove(&ptn_bridge->bridge);
+
+	kfree(ptn_bridge->edid);
+
+	return 0;
+}
+
+static const struct i2c_device_id ge_b850v3_lvds_dp_i2c_table[] = {
+	{"b850v3-lvds-dp", 0},
+	{},
+};
+MODULE_DEVICE_TABLE(i2c, ge_b850v3_lvds_dp_i2c_table);
+
+static const struct of_device_id ge_b850v3_lvds_dp_match[] = {
+	{ .compatible = "ge,b850v3-lvds-dp" },
+	{},
+};
+MODULE_DEVICE_TABLE(of, ge_b850v3_lvds_dp_match);
+
+static struct i2c_driver ge_b850v3_lvds_dp_driver = {
+	.id_table	= ge_b850v3_lvds_dp_i2c_table,
+	.probe		= ge_b850v3_lvds_dp_probe,
+	.remove		= ge_b850v3_lvds_dp_remove,
+	.driver		= {
+		.name		= "b850v3-lvds-dp",
+		.of_match_table = ge_b850v3_lvds_dp_match,
+	},
+};
+module_i2c_driver(ge_b850v3_lvds_dp_driver);
+
+MODULE_AUTHOR("Peter Senna Tschudin <peter.senna@collabora.com>");
+MODULE_AUTHOR("Martyn Welch <martyn.welch@collabora.co.uk>");
+MODULE_DESCRIPTION("GE LVDS to DP++ display bridge)");
+MODULE_LICENSE("GPL v2");
-- 
2.5.5

^ permalink raw reply related	[flat|nested] 64+ messages in thread

* [PATCH V5 4/4] dts/imx6q-b850v3: Use GE B850v3 LVDS/DP++ Bridge
  2016-08-09 16:41 ` [PATCH V5 0/4] Add driver for " Peter Senna Tschudin
                     ` (2 preceding siblings ...)
  2016-08-09 16:41   ` [PATCH V5 3/4] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge Peter Senna Tschudin
@ 2016-08-09 16:41   ` Peter Senna Tschudin
  2016-09-26  8:27     ` Peter Senna Tschudin
  3 siblings, 1 reply; 64+ messages in thread
From: Peter Senna Tschudin @ 2016-08-09 16:41 UTC (permalink / raw)
  To: airlied, akpm, daniel.vetter, davem, devicetree, dri-devel,
	enric.balletbo, eballetbo, galak, gregkh, heiko, ijc+devicetree,
	javier, jslaby, kernel, linux-arm-kernel, linux, linux-kernel,
	linux, mark.rutland, martin.donnelly, martyn.welch, mchehab,
	pawel.moll, peter.senna, peter.senna, p.zabel, thierry.reding,
	rmk+kernel, robh+dt, shawnguo, tiwai, treding, ykk
  Cc: Rob Herring, Fabio Estevam

Configures the GE B850v3 LVDS/DP++ bridge on the dts file.

Cc: Martyn Welch <martyn.welch@collabora.co.uk>
Cc: Martin Donnelly <martin.donnelly@ge.com>
Cc: Javier Martinez Canillas <javier@dowhile0.org>
Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com>
Cc: Philipp Zabel <p.zabel@pengutronix.de>
Cc: Rob Herring <robh@kernel.org>
Cc: Fabio Estevam <fabio.estevam@nxp.com>
Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com>
---
Unchanged from V4

Changes from V3:
 - 4/4 instead of 5/5

Unchanged from V2

Changes from V1:
 - Replaced '_' by '-' in node names or compatible strings
 - Added missing @73 to b850v3-lvds-dp-bridge

 arch/arm/boot/dts/imx6q-b850v3.dts | 30 ++++++++++++++++++++++++++++++
 1 file changed, 30 insertions(+)

diff --git a/arch/arm/boot/dts/imx6q-b850v3.dts b/arch/arm/boot/dts/imx6q-b850v3.dts
index 167f744..8db3bf2 100644
--- a/arch/arm/boot/dts/imx6q-b850v3.dts
+++ b/arch/arm/boot/dts/imx6q-b850v3.dts
@@ -72,6 +72,13 @@
 		fsl,data-mapping = "spwg";
 		fsl,data-width = <24>;
 		status = "okay";
+
+		port@4 {
+			reg = <4>;
+			lvds0_out: endpoint {
+				remote-endpoint = <&b850v3_lvds_dp_bridge_in>;
+			};
+		};
 	};
 };
 
@@ -142,3 +149,26 @@
 		reg = <0x4a>;
 	};
 };
+
+&mux2_i2c2 {
+	status = "okay";
+	clock-frequency = <100000>;
+
+	b850v3-lvds-dp-bridge@73 {
+		compatible = "ge,b850v3-lvds-dp";
+		#address-cells = <1>;
+		#size-cells = <0>;
+
+		reg = <0x73>;
+		interrupt-parent = <&gpio2>;
+		interrupts = <0 IRQ_TYPE_LEVEL_HIGH>;
+
+		edid-reg = <0x72>;
+
+		port {
+			b850v3_lvds_dp_bridge_in: endpoint {
+				remote-endpoint = <&lvds0_out>;
+			};
+		};
+	};
+};
-- 
2.5.5

^ permalink raw reply related	[flat|nested] 64+ messages in thread

* Re: [PATCH V5 1/4] drm/imx-ldb: Add support to drm-bridge
  2016-08-09 16:41   ` [PATCH V5 1/4] drm/imx-ldb: Add support to drm-bridge Peter Senna Tschudin
@ 2016-08-11  9:38     ` Philipp Zabel
  0 siblings, 0 replies; 64+ messages in thread
From: Philipp Zabel @ 2016-08-11  9:38 UTC (permalink / raw)
  To: Peter Senna Tschudin
  Cc: airlied, akpm, daniel.vetter, davem, devicetree, dri-devel,
	enric.balletbo, eballetbo, galak, gregkh, heiko, ijc+devicetree,
	javier, jslaby, kernel, linux-arm-kernel, linux, linux-kernel,
	linux, mark.rutland, martin.donnelly, martyn.welch, mchehab,
	pawel.moll, peter.senna, thierry.reding, rmk+kernel, robh+dt,
	shawnguo, tiwai, treding, ykk, Rob Herring, Fabio Estevam

Am Dienstag, den 09.08.2016, 18:41 +0200 schrieb Peter Senna Tschudin:
> Add support to attach a drm_bridge to imx-ldb in addition to
> existing support to attach a LVDS panel.
> 
> This patch does a simple code refactoring by moving code
> from for_each_child_of_node iterator to a new function named
> imx_ldb_panel_ddc(). This was necessary to allow the panel ddc
> code to run only when the imx_ldb is not attached to a bridge.
> 
> Cc: Martyn Welch <martyn.welch@collabora.co.uk>
> Cc: Martin Donnelly <martin.donnelly@ge.com>
> Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com>
> Cc: Philipp Zabel <p.zabel@pengutronix.de>
> Cc: Rob Herring <robh@kernel.org>
> Cc: Fabio Estevam <fabio.estevam@nxp.com>
> Cc: David Airlie <airlied@linux.ie>
> Cc: Thierry Reding <treding@nvidia.com>
> Cc: Thierry Reding <thierry.reding@gmail.com>
> Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com>
> ---
> Unchanged from V4

I've rebased and applied patch 1.

thanks
Philipp

^ permalink raw reply	[flat|nested] 64+ messages in thread

* Re: [PATCH V5 3/4] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge
  2016-08-09 16:41   ` [PATCH V5 3/4] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge Peter Senna Tschudin
@ 2016-08-16  4:15     ` Archit Taneja
  2016-09-26  8:27     ` Peter Senna Tschudin
  2016-09-26 10:29     ` Archit Taneja
  2 siblings, 0 replies; 64+ messages in thread
From: Archit Taneja @ 2016-08-16  4:15 UTC (permalink / raw)
  To: Peter Senna Tschudin, airlied, akpm, daniel.vetter, davem,
	devicetree, dri-devel, enric.balletbo, eballetbo, galak, gregkh,
	heiko, ijc+devicetree, javier, jslaby, kernel, linux-arm-kernel,
	linux, linux-kernel, linux, mark.rutland, martin.donnelly,
	martyn.welch, mchehab, pawel.moll, peter.senna, p.zabel,
	thierry.reding, rmk+kernel, robh+dt, shawnguo, tiwai, treding,
	ykk
  Cc: Rob Herring, Fabio Estevam

Hi,

On 08/09/2016 10:11 PM, Peter Senna Tschudin wrote:
> Add a driver that create a drm_bridge and a drm_connector for the LVDS
> to DP++ display bridge of the GE B850v3.
>
> There are two physical bridges on the video signal pipeline: a
> STDP4028(LVDS to DP) and a STDP2690(DP to DP++).  The hardware and
> firmware made it complicated for this binding to comprise two device
> tree nodes, as the design goal is to configure both bridges based on
> the LVDS signal, which leave the driver powerless to control the video
> processing pipeline. The two bridges behaves as a single bridge, and
> the driver is only needed for telling the host about EDID / HPD, and
> for giving the host powers to ack interrupts. The video signal pipeline
> is as follows:
>
>    Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output
>

I'd commented on an earlier revision (v2) of this patch, but hadn't got
a response on it. Pasting the query again:

Are these two chips always expected to be used together? I don't think
it's right to pair up two encoder chips into one driver just for one
board.

Is one device @0x72 and other @0x73? Or is only one of them an i2c
slave?

What's preventing us to create these as two different bridge drivers?
The drm framework allows us to daisy chain encoder bridges. The only
problem I see is that we don't have a clear-cut way to tell the bridge
driver whether we want it to create a connector for us or not. Because,
it looks like both can potentially create connectors. This isn't a big
problem either if we have DT. We just need to check whether our output
port is connected to another bridge or a connector.

Thanks,
Archit

> Cc: Martyn Welch <martyn.welch@collabora.co.uk>
> Cc: Martin Donnelly <martin.donnelly@ge.com>
> Cc: Daniel Vetter <daniel.vetter@ffwll.ch>
> Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com>
> Cc: Philipp Zabel <p.zabel@pengutronix.de>
> Cc: Rob Herring <robh@kernel.org>
> Cc: Fabio Estevam <fabio.estevam@nxp.com>
> CC: David Airlie <airlied@linux.ie>
> CC: Thierry Reding <treding@nvidia.com>
> CC: Thierry Reding <thierry.reding@gmail.com>
> Reviewed-by: Enric Balletbo <enric.balletbo@collabora.com>
> Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com>
> ---
> Changes from V4:
>   - Check the output of the first call to i2c_smbus_write_word_data() and return
>     it's error code for failing gracefully on i2c issues
>   - Renamed the i2c_driver.name from "ge,b850v3-lvds-dp" to "b850v3-lvds-dp" to
>     remove the comma from the driver name
>
> Changes from V3:
>   - 3/4 instead of 4/5
>   - Tested on next-20160804
>
> Changes from V2:
>   - Made it atomic to be applied on next-20160729 on top of Liu Ying changes
>     that made imx-ldb atomic
>
> Changes from V1:
>   - New commit message
>   - Removed 3 empty entry points
>   - Removed memory leak from ge_b850v3_lvds_dp_get_modes()
>   - Added a lock for mode setting
>   - Removed a few blank lines
>   - Changed the order at Makefile and Kconfig
>
>   MAINTAINERS                                |   8 +
>   drivers/gpu/drm/bridge/Kconfig             |  11 +
>   drivers/gpu/drm/bridge/Makefile            |   1 +
>   drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c | 405 +++++++++++++++++++++++++++++
>   4 files changed, 425 insertions(+)
>   create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index a306795..e8d106a 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -5142,6 +5142,14 @@ W:	https://linuxtv.org
>   S:	Maintained
>   F:	drivers/media/radio/radio-gemtek*
>
> +GENERAL ELECTRIC B850V3 LVDS/DP++ BRIDGE
> +M:	Peter Senna Tschudin <peter.senna@collabora.com>
> +M:	Martin Donnelly <martin.donnelly@ge.com>
> +M:	Martyn Welch <martyn.welch@collabora.co.uk>
> +S:	Maintained
> +F:	drivers/gpu/drm/bridge/ge_b850v3_dp2.c
> +F:	Documentation/devicetree/bindings/ge/b850v3_dp2_bridge.txt
> +
>   GENERIC GPIO I2C DRIVER
>   M:	Haavard Skinnemoen <hskinnemoen@gmail.com>
>   S:	Supported
> diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
> index b590e67..b4b70fb 100644
> --- a/drivers/gpu/drm/bridge/Kconfig
> +++ b/drivers/gpu/drm/bridge/Kconfig
> @@ -32,6 +32,17 @@ config DRM_DW_HDMI_AHB_AUDIO
>   	  Designware HDMI block.  This is used in conjunction with
>   	  the i.MX6 HDMI driver.
>
> +config DRM_GE_B850V3_LVDS_DP
> +	tristate "GE B850v3 LVDS to DP++ display bridge"
> +	depends on OF
> +	select DRM_KMS_HELPER
> +	select DRM_PANEL
> +	---help---
> +          This is a driver for the display bridge of
> +          GE B850v3 that convert dual channel LVDS
> +          to DP++. This is used with the i.MX6 imx-ldb
> +          driver.
> +
>   config DRM_NXP_PTN3460
>   	tristate "NXP PTN3460 DP/LVDS bridge"
>   	depends on OF
> diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
> index efdb07e..b9606f3 100644
> --- a/drivers/gpu/drm/bridge/Makefile
> +++ b/drivers/gpu/drm/bridge/Makefile
> @@ -3,6 +3,7 @@ ccflags-y := -Iinclude/drm
>   obj-$(CONFIG_DRM_ANALOGIX_ANX78XX) += analogix-anx78xx.o
>   obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o
>   obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o
> +obj-$(CONFIG_DRM_GE_B850V3_LVDS_DP) += ge_b850v3_lvds_dp.o
>   obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o
>   obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o
>   obj-$(CONFIG_DRM_SII902X) += sii902x.o
> diff --git a/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c
> new file mode 100644
> index 0000000..81e9279
> --- /dev/null
> +++ b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c
> @@ -0,0 +1,405 @@
> +/*
> + * Driver for GE B850v3 DP display bridge
> +
> + * Copyright (c) 2016, Collabora Ltd.
> + * Copyright (c) 2016, General Electric Company
> +
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms and conditions of the GNU General Public License,
> + * version 2, as published by the Free Software Foundation.
> +
> + * This program is distributed in the hope it will be useful, but WITHOUT
> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
> + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
> + * more details.
> +
> + * You should have received a copy of the GNU General Public License
> + * along with this program.  If not, see <http://www.gnu.org/licenses/>.
> +
> + * This driver creates a drm_bridge and a drm_connector for the LVDS to DP++
> + * display bridge of the GE B850v3. There are two physical bridges on the video
> + * signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). However
> + * the physical bridges are automatically configured by the input video signal,
> + * and the driver has no access to the video processing pipeline. The driver is
> + * only needed to read EDID from the STDP2690 and to handle HPD events from the
> + * STDP4028. The driver communicates with both bridges over i2c. The video
> + * signal pipeline is as follows:
> + *
> + *   Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output
> + *
> + */
> +
> +#include <linux/gpio.h>
> +#include <linux/i2c.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <drm/drm_atomic.h>
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_crtc_helper.h>
> +#include <drm/drm_edid.h>
> +#include <drm/drmP.h>
> +
> +/*
> + * 220Mhz is a limitation of the host, as the bridge is capable of up to
> + * 330Mhz. See section 9.2.1.2.4 of the i.MX 6Dual/6Quad Applications
> + * Processor Reference Manual for more information about the 220Mhz limit.
> + * The imx-ldb driver will warn about clocks over 170Mhz, but it seem to work
> + * fine.
> + */
> +#define MAX_PIXEL_CLOCK 220000
> +
> +#define EDID_EXT_BLOCK_CNT 0x7E
> +
> +#define STDP4028_IRQ_OUT_CONF_REG 0x02
> +#define STDP4028_DPTX_IRQ_EN_REG 0x3C
> +#define STDP4028_DPTX_IRQ_STS_REG 0x3D
> +#define STDP4028_DPTX_STS_REG 0x3E
> +
> +#define STDP4028_DPTX_DP_IRQ_EN 0x1000
> +
> +#define STDP4028_DPTX_HOTPLUG_IRQ_EN 0x0400
> +#define STDP4028_DPTX_LINK_CH_IRQ_EN 0x2000
> +#define STDP4028_DPTX_IRQ_CONFIG \
> +		(STDP4028_DPTX_LINK_CH_IRQ_EN | STDP4028_DPTX_HOTPLUG_IRQ_EN)
> +
> +#define STDP4028_DPTX_HOTPLUG_STS 0x0200
> +#define STDP4028_DPTX_LINK_STS 0x1000
> +#define STDP4028_CON_STATE_CONNECTED \
> +		(STDP4028_DPTX_HOTPLUG_STS | STDP4028_DPTX_LINK_STS)
> +
> +#define STDP4028_DPTX_HOTPLUG_CH_STS 0x0400
> +#define STDP4028_DPTX_LINK_CH_STS 0x2000
> +#define STDP4028_DPTX_IRQ_CLEAR \
> +		(STDP4028_DPTX_LINK_CH_STS | STDP4028_DPTX_HOTPLUG_CH_STS)
> +
> +struct ge_b850v3_lvds_dp {
> +	struct drm_connector connector;
> +	struct drm_bridge bridge;
> +	struct i2c_client *ge_b850v3_lvds_dp_i2c;
> +	struct i2c_client *edid_i2c;
> +	struct edid *edid;
> +	struct mutex lock;
> +};
> +
> +static inline struct ge_b850v3_lvds_dp *
> +		bridge_to_ge_b850v3_lvds_dp(struct drm_bridge *bridge)
> +{
> +	return container_of(bridge, struct ge_b850v3_lvds_dp, bridge);
> +}
> +
> +static inline struct ge_b850v3_lvds_dp *
> +		connector_to_ge_b850v3_lvds_dp(struct drm_connector *connector)
> +{
> +	return container_of(connector, struct ge_b850v3_lvds_dp, connector);
> +}
> +
> +u8 *stdp2690_get_edid(struct i2c_client *client)
> +{
> +	struct i2c_adapter *adapter = client->adapter;
> +	unsigned char start = 0x00;
> +	unsigned int total_size;
> +	u8 *block = kmalloc(EDID_LENGTH, GFP_KERNEL);
> +
> +	struct i2c_msg msgs[] = {
> +		{
> +			.addr	= client->addr,
> +			.flags	= 0,
> +			.len	= 1,
> +			.buf	= &start,
> +		}, {
> +			.addr	= client->addr,
> +			.flags	= I2C_M_RD,
> +			.len	= EDID_LENGTH,
> +			.buf	= block,
> +		}
> +	};
> +
> +	if (!block)
> +		return NULL;
> +
> +	if (i2c_transfer(adapter, msgs, 2) != 2) {
> +		DRM_ERROR("Unable to read EDID.\n");
> +		goto err;
> +	}
> +
> +	if (!drm_edid_block_valid(block, 0, false, NULL)) {
> +		DRM_ERROR("Invalid EDID block\n");
> +		goto err;
> +	}
> +
> +	total_size = (block[EDID_EXT_BLOCK_CNT] + 1) * EDID_LENGTH;
> +	if (total_size > EDID_LENGTH) {
> +		kfree(block);
> +		block = kmalloc(total_size, GFP_KERNEL);
> +		if (!block)
> +			return NULL;
> +
> +		/* Yes, read the entire buffer, and do not skip the first
> +		 * EDID_LENGTH bytes.
> +		 */
> +		start = 0x00;
> +		msgs[1].len = total_size;
> +		msgs[1].buf = block;
> +
> +		if (i2c_transfer(adapter, msgs, 2) != 2) {
> +			DRM_ERROR("Unable to read EDID extension blocks.\n");
> +			goto err;
> +		}
> +	}
> +
> +	return block;
> +
> +err:
> +	kfree(block);
> +	return NULL;
> +}
> +
> +static int ge_b850v3_lvds_dp_get_modes(struct drm_connector *connector)
> +{
> +	struct ge_b850v3_lvds_dp *ptn_bridge;
> +	struct i2c_client *client;
> +	int num_modes = 0;
> +
> +	ptn_bridge = connector_to_ge_b850v3_lvds_dp(connector);
> +	client = ptn_bridge->edid_i2c;
> +
> +	mutex_lock(&ptn_bridge->lock);
> +
> +	kfree(ptn_bridge->edid);
> +	ptn_bridge->edid = (struct edid *) stdp2690_get_edid(client);
> +
> +	if (ptn_bridge->edid) {
> +		drm_mode_connector_update_edid_property(connector,
> +				ptn_bridge->edid);
> +		num_modes = drm_add_edid_modes(connector, ptn_bridge->edid);
> +	}
> +
> +	mutex_unlock(&ptn_bridge->lock);
> +
> +	return num_modes;
> +}
> +
> +
> +static enum drm_mode_status ge_b850v3_lvds_dp_mode_valid(
> +		struct drm_connector *connector, struct drm_display_mode *mode)
> +{
> +	if (mode->clock > MAX_PIXEL_CLOCK) {
> +		DRM_INFO("The pixel clock for the mode %s is too high, and not supported.",
> +				mode->name);
> +		return MODE_CLOCK_HIGH;
> +	}
> +
> +	return MODE_OK;
> +}
> +
> +static const struct
> +drm_connector_helper_funcs ge_b850v3_lvds_dp_connector_helper_funcs = {
> +	.get_modes = ge_b850v3_lvds_dp_get_modes,
> +	.mode_valid = ge_b850v3_lvds_dp_mode_valid,
> +};
> +
> +static enum drm_connector_status ge_b850v3_lvds_dp_detect(
> +		struct drm_connector *connector, bool force)
> +{
> +	struct ge_b850v3_lvds_dp *ptn_bridge =
> +			connector_to_ge_b850v3_lvds_dp(connector);
> +	struct i2c_client *ge_b850v3_lvds_dp_i2c =
> +			ptn_bridge->ge_b850v3_lvds_dp_i2c;
> +	s32 link_state;
> +
> +	link_state = i2c_smbus_read_word_data(ge_b850v3_lvds_dp_i2c,
> +			STDP4028_DPTX_STS_REG);
> +
> +	if (link_state == STDP4028_CON_STATE_CONNECTED)
> +		return connector_status_connected;
> +
> +	if (link_state == 0)
> +		return connector_status_disconnected;
> +
> +	return connector_status_unknown;
> +}
> +
> +static const struct drm_connector_funcs ge_b850v3_lvds_dp_connector_funcs = {
> +	.dpms = drm_atomic_helper_connector_dpms,
> +	.fill_modes = drm_helper_probe_single_connector_modes,
> +	.detect = ge_b850v3_lvds_dp_detect,
> +	.destroy = drm_connector_cleanup,
> +	.reset = drm_atomic_helper_connector_reset,
> +	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
> +	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
> +};
> +
> +static irqreturn_t ge_b850v3_lvds_dp_irq_handler(int irq, void *dev_id)
> +{
> +	struct ge_b850v3_lvds_dp *ptn_bridge = dev_id;
> +	struct i2c_client *ge_b850v3_lvds_dp_i2c
> +			= ptn_bridge->ge_b850v3_lvds_dp_i2c;
> +
> +	mutex_lock(&ptn_bridge->lock);
> +
> +	i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c,
> +			STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR);
> +
> +	mutex_unlock(&ptn_bridge->lock);
> +
> +	if (ptn_bridge->connector.dev)
> +		drm_kms_helper_hotplug_event(ptn_bridge->connector.dev);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static int ge_b850v3_lvds_dp_attach(struct drm_bridge *bridge)
> +{
> +	struct ge_b850v3_lvds_dp *ptn_bridge
> +			= bridge_to_ge_b850v3_lvds_dp(bridge);
> +	struct drm_connector *connector = &ptn_bridge->connector;
> +	struct i2c_client *ge_b850v3_lvds_dp_i2c
> +			= ptn_bridge->ge_b850v3_lvds_dp_i2c;
> +	int ret;
> +
> +	if (!bridge->encoder) {
> +		DRM_ERROR("Parent encoder object not found");
> +		return -ENODEV;
> +	}
> +
> +	connector->polled = DRM_CONNECTOR_POLL_HPD;
> +
> +	drm_connector_helper_add(connector,
> +			&ge_b850v3_lvds_dp_connector_helper_funcs);
> +
> +	ret = drm_connector_init(bridge->dev, connector,
> +			&ge_b850v3_lvds_dp_connector_funcs,
> +			DRM_MODE_CONNECTOR_DisplayPort);
> +	if (ret) {
> +		DRM_ERROR("Failed to initialize connector with drm\n");
> +		return ret;
> +	}
> +
> +	drm_connector_register(connector);
> +	ret = drm_mode_connector_attach_encoder(connector, bridge->encoder);
> +	if (ret)
> +		return ret;
> +
> +	drm_bridge_enable(bridge);
> +	if (ge_b850v3_lvds_dp_i2c->irq) {
> +		drm_helper_hpd_irq_event(connector->dev);
> +
> +		ret = devm_request_threaded_irq(&ge_b850v3_lvds_dp_i2c->dev,
> +				ge_b850v3_lvds_dp_i2c->irq, NULL,
> +				ge_b850v3_lvds_dp_irq_handler,
> +				IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
> +				"ge-b850v3-lvds-dp", ptn_bridge);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static const struct drm_bridge_funcs ge_b850v3_lvds_dp_funcs = {
> +	.attach = ge_b850v3_lvds_dp_attach,
> +};
> +
> +static int ge_b850v3_lvds_dp_probe(struct i2c_client *ge_b850v3_lvds_dp_i2c,
> +				const struct i2c_device_id *id)
> +{
> +	struct device *dev = &ge_b850v3_lvds_dp_i2c->dev;
> +	struct ge_b850v3_lvds_dp *ptn_bridge;
> +	int ret;
> +	u32 edid_i2c_reg;
> +
> +	ptn_bridge = devm_kzalloc(dev, sizeof(*ptn_bridge), GFP_KERNEL);
> +	if (!ptn_bridge)
> +		return -ENOMEM;
> +
> +	mutex_init(&ptn_bridge->lock);
> +
> +	ptn_bridge->ge_b850v3_lvds_dp_i2c = ge_b850v3_lvds_dp_i2c;
> +	ptn_bridge->bridge.driver_private = ptn_bridge;
> +	i2c_set_clientdata(ge_b850v3_lvds_dp_i2c, ptn_bridge);
> +
> +	ret = of_property_read_u32(dev->of_node, "edid-reg", &edid_i2c_reg);
> +	if (ret) {
> +		dev_err(dev, "edid-reg not specified, aborting...\n");
> +		return -ENODEV;
> +	}
> +
> +	ptn_bridge->edid_i2c = devm_kzalloc(dev,
> +			sizeof(struct i2c_client), GFP_KERNEL);
> +
> +	if (!ptn_bridge->edid_i2c)
> +		return -ENOMEM;
> +
> +	memcpy(ptn_bridge->edid_i2c, ge_b850v3_lvds_dp_i2c,
> +			sizeof(struct i2c_client));
> +
> +	ptn_bridge->edid_i2c->addr = (unsigned short) edid_i2c_reg;
> +
> +	/*
> +	 * Configures the bridge to re-enable interrupts after each ack. As
> +	 * this is the first communication with the chip, fail on error.
> +	 */
> +	ret = i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c,
> +			STDP4028_IRQ_OUT_CONF_REG, STDP4028_DPTX_DP_IRQ_EN);
> +	if (ret) {
> +		dev_err(dev, "i2c communication failed, aborting...\n");
> +		return ret;
> +	}
> +
> +	i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c,
> +			STDP4028_DPTX_IRQ_EN_REG, STDP4028_DPTX_IRQ_CONFIG);
> +
> +	/* Clear pending interrupts since power up. */
> +	i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c,
> +			STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR);
> +
> +	ptn_bridge->bridge.funcs = &ge_b850v3_lvds_dp_funcs;
> +	ptn_bridge->bridge.of_node = dev->of_node;
> +	ret = drm_bridge_add(&ptn_bridge->bridge);
> +	if (ret) {
> +		DRM_ERROR("Failed to add bridge\n");
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static int ge_b850v3_lvds_dp_remove(struct i2c_client *ge_b850v3_lvds_dp_i2c)
> +{
> +	struct ge_b850v3_lvds_dp *ptn_bridge =
> +		i2c_get_clientdata(ge_b850v3_lvds_dp_i2c);
> +
> +	drm_bridge_remove(&ptn_bridge->bridge);
> +
> +	kfree(ptn_bridge->edid);
> +
> +	return 0;
> +}
> +
> +static const struct i2c_device_id ge_b850v3_lvds_dp_i2c_table[] = {
> +	{"b850v3-lvds-dp", 0},
> +	{},
> +};
> +MODULE_DEVICE_TABLE(i2c, ge_b850v3_lvds_dp_i2c_table);
> +
> +static const struct of_device_id ge_b850v3_lvds_dp_match[] = {
> +	{ .compatible = "ge,b850v3-lvds-dp" },
> +	{},
> +};
> +MODULE_DEVICE_TABLE(of, ge_b850v3_lvds_dp_match);
> +
> +static struct i2c_driver ge_b850v3_lvds_dp_driver = {
> +	.id_table	= ge_b850v3_lvds_dp_i2c_table,
> +	.probe		= ge_b850v3_lvds_dp_probe,
> +	.remove		= ge_b850v3_lvds_dp_remove,
> +	.driver		= {
> +		.name		= "b850v3-lvds-dp",
> +		.of_match_table = ge_b850v3_lvds_dp_match,
> +	},
> +};
> +module_i2c_driver(ge_b850v3_lvds_dp_driver);
> +
> +MODULE_AUTHOR("Peter Senna Tschudin <peter.senna@collabora.com>");
> +MODULE_AUTHOR("Martyn Welch <martyn.welch@collabora.co.uk>");
> +MODULE_DESCRIPTION("GE LVDS to DP++ display bridge)");
> +MODULE_LICENSE("GPL v2");
>

-- 
Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum,
a Linux Foundation Collaborative Project

^ permalink raw reply	[flat|nested] 64+ messages in thread

* Re: [PATCH V4 1/4] drm/imx-ldb: Add support to drm-bridge
  2016-08-04 22:36   ` [PATCH V4 1/4] drm/imx-ldb: Add support to drm-bridge Peter Senna Tschudin
@ 2016-08-16 15:40     ` Martyn Welch
  0 siblings, 0 replies; 64+ messages in thread
From: Martyn Welch @ 2016-08-16 15:40 UTC (permalink / raw)
  To: Peter Senna Tschudin, robh+dt, mark.rutland, shawnguo, kernel,
	fabio.estevam, linux, airlied, p.zabel, martin.donnelly,
	peter.senna, treding, architt, devicetree, linux-kernel,
	linux-arm-kernel, dri-devel
  Cc: Enric Balletbo i Serra, Rob Herring, Thierry Reding



On 04/08/16 23:36, Peter Senna Tschudin wrote:
> Add support to attach a drm_bridge to imx-ldb in addition to
> existing support to attach a LVDS panel.
>
> This patch does a simple code refactoring by moving code
> from for_each_child_of_node iterator to a new function named
> imx_ldb_panel_ddc(). This was necessary to allow the panel ddc
> code to run only when the imx_ldb is not attached to a bridge.
>
> Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com>
> Cc: Philipp Zabel <p.zabel@pengutronix.de>
> Cc: Rob Herring <robh@kernel.org>
> Cc: Fabio Estevam <fabio.estevam@nxp.com>
> Cc: David Airlie <airlied@linux.ie>
> Cc: Thierry Reding <treding@nvidia.com>
> Cc: Thierry Reding <thierry.reding@gmail.com>
> Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com>

Acked-by: Martyn Welch <martyn.welch@collabora.co.uk>

> ---
> Changes from V3:
>  - The connector is created when there is no bridge instead of only when
>    there is a pannel
>  - Tested on next-20160804
>
> Changes from V2:
>  - Updated to be aplied on top of Liu Ying changes that made imx-ldb atomic
>  - Tested on next-20160729
>
> Changes from V1:
>  - Reanmed ext_bridge to bridge
>  - Removed empty entry point imx_ldb_encoder_enable()
>  - Adapted the code to apply to the latest linux next: next-20160609
>
>  drivers/gpu/drm/imx/imx-ldb.c | 118 ++++++++++++++++++++++++++++--------------
>  1 file changed, 78 insertions(+), 40 deletions(-)
>
> diff --git a/drivers/gpu/drm/imx/imx-ldb.c b/drivers/gpu/drm/imx/imx-ldb.c
> index b03919e..4a33077 100644
> --- a/drivers/gpu/drm/imx/imx-ldb.c
> +++ b/drivers/gpu/drm/imx/imx-ldb.c
> @@ -57,7 +57,11 @@ struct imx_ldb_channel {
>  	struct imx_ldb *ldb;
>  	struct drm_connector connector;
>  	struct drm_encoder encoder;
> +
> +	/* Defines what is connected to the ldb, only one at a time */
>  	struct drm_panel *panel;
> +	struct drm_bridge *bridge;
> +
>  	struct device_node *child;
>  	struct i2c_adapter *ddc;
>  	int chno;
> @@ -466,10 +470,30 @@ static int imx_ldb_register(struct drm_device *drm,
>  	drm_encoder_init(drm, encoder, &imx_ldb_encoder_funcs,
>  			 DRM_MODE_ENCODER_LVDS, NULL);
>
> -	drm_connector_helper_add(&imx_ldb_ch->connector,
> -			&imx_ldb_connector_helper_funcs);
> -	drm_connector_init(drm, &imx_ldb_ch->connector,
> -			   &imx_ldb_connector_funcs, DRM_MODE_CONNECTOR_LVDS);
> +	if (imx_ldb_ch->bridge) {
> +		imx_ldb_ch->bridge->encoder = encoder;
> +
> +		imx_ldb_ch->encoder.bridge = imx_ldb_ch->bridge;
> +		ret = drm_bridge_attach(drm, imx_ldb_ch->bridge);
> +		if (ret) {
> +			DRM_ERROR("Failed to initialize bridge with drm\n");
> +			return ret;
> +		}
> +	} else {
> +		/*
> +		 * We want to add the connector whenever there is no bridge
> +		 * that brings its own, not only when there is a panel. For
> +		 * historical reasons, the ldb driver can also work without
> +		 * a panel.
> +		 */
> +		drm_connector_helper_add(&imx_ldb_ch->connector,
> +				&imx_ldb_connector_helper_funcs);
> +		drm_connector_init(drm, &imx_ldb_ch->connector,
> +				&imx_ldb_connector_funcs,
> +				DRM_MODE_CONNECTOR_LVDS);
> +		drm_mode_connector_attach_encoder(&imx_ldb_ch->connector,
> +				encoder);
> +	}
>
>  	if (imx_ldb_ch->panel) {
>  		ret = drm_panel_attach(imx_ldb_ch->panel,
> @@ -478,8 +502,6 @@ static int imx_ldb_register(struct drm_device *drm,
>  			return ret;
>  	}
>
> -	drm_mode_connector_attach_encoder(&imx_ldb_ch->connector, encoder);
> -
>  	return 0;
>  }
>
> @@ -548,6 +570,45 @@ static const struct of_device_id imx_ldb_dt_ids[] = {
>  };
>  MODULE_DEVICE_TABLE(of, imx_ldb_dt_ids);
>
> +static int imx_ldb_panel_ddc(struct device *dev,
> +		struct imx_ldb_channel *channel, struct device_node *child)
> +{
> +	struct device_node *ddc_node;
> +	const u8 *edidp;
> +	int ret;
> +
> +	ddc_node = of_parse_phandle(child, "ddc-i2c-bus", 0);
> +	if (ddc_node) {
> +		channel->ddc = of_find_i2c_adapter_by_node(ddc_node);
> +		of_node_put(ddc_node);
> +		if (!channel->ddc) {
> +			dev_warn(dev, "failed to get ddc i2c adapter\n");
> +			return -EPROBE_DEFER;
> +		}
> +	}
> +
> +	if (!channel->ddc) {
> +		/* if no DDC available, fallback to hardcoded EDID */
> +		dev_dbg(dev, "no ddc available\n");
> +
> +		edidp = of_get_property(child, "edid",
> +					&channel->edid_len);
> +		if (edidp) {
> +			channel->edid = kmemdup(edidp,
> +						channel->edid_len,
> +						GFP_KERNEL);
> +		} else if (!channel->panel) {
> +			/* fallback to display-timings node */
> +			ret = of_get_drm_display_mode(child,
> +						      &channel->mode,
> +						      OF_USE_NATIVE_MODE);
> +			if (!ret)
> +				channel->mode_valid = 1;
> +		}
> +	}
> +	return 0;
> +}
> +
>  static int imx_ldb_bind(struct device *dev, struct device *master, void *data)
>  {
>  	struct drm_device *drm = data;
> @@ -555,7 +616,6 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data)
>  	const struct of_device_id *of_id =
>  			of_match_device(imx_ldb_dt_ids, dev);
>  	struct device_node *child;
> -	const u8 *edidp;
>  	struct imx_ldb *imx_ldb;
>  	int dual;
>  	int ret;
> @@ -605,7 +665,6 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data)
>
>  	for_each_child_of_node(np, child) {
>  		struct imx_ldb_channel *channel;
> -		struct device_node *ddc_node;
>  		struct device_node *ep;
>  		int bus_format;
>
> @@ -638,46 +697,25 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data)
>
>  			remote = of_graph_get_remote_port_parent(ep);
>  			of_node_put(ep);
> -			if (remote)
> +			if (remote) {
>  				channel->panel = of_drm_find_panel(remote);
> -			else
> +				channel->bridge = of_drm_find_bridge(remote);
> +			} else
>  				return -EPROBE_DEFER;
>  			of_node_put(remote);
> -			if (!channel->panel) {
> -				dev_err(dev, "panel not found: %s\n",
> -					remote->full_name);
> -				return -EPROBE_DEFER;
> -			}
> -		}
>
> -		ddc_node = of_parse_phandle(child, "ddc-i2c-bus", 0);
> -		if (ddc_node) {
> -			channel->ddc = of_find_i2c_adapter_by_node(ddc_node);
> -			of_node_put(ddc_node);
> -			if (!channel->ddc) {
> -				dev_warn(dev, "failed to get ddc i2c adapter\n");
> +			if (!channel->panel && !channel->bridge) {
> +				dev_err(dev, "panel/bridge not found: %s\n",
> +					remote->full_name);
>  				return -EPROBE_DEFER;
>  			}
>  		}
>
> -		if (!channel->ddc) {
> -			/* if no DDC available, fallback to hardcoded EDID */
> -			dev_dbg(dev, "no ddc available\n");
> -
> -			edidp = of_get_property(child, "edid",
> -						&channel->edid_len);
> -			if (edidp) {
> -				channel->edid = kmemdup(edidp,
> -							channel->edid_len,
> -							GFP_KERNEL);
> -			} else if (!channel->panel) {
> -				/* fallback to display-timings node */
> -				ret = of_get_drm_display_mode(child,
> -							      &channel->mode,
> -							      OF_USE_NATIVE_MODE);
> -				if (!ret)
> -					channel->mode_valid = 1;
> -			}
> +		/* panel ddc only if there is no bridge */
> +		if (!channel->bridge) {
> +			ret = imx_ldb_panel_ddc(dev, channel, child);
> +			if (ret)
> +				return ret;
>  		}
>
>  		bus_format = of_get_bus_format(dev, child);
>

^ permalink raw reply	[flat|nested] 64+ messages in thread

* Re: [PATCH V4 2/4] Documentation/devicetree/bindings: b850v3_lvds_dp
  2016-08-04 22:36   ` [PATCH V4 2/4] Documentation/devicetree/bindings: b850v3_lvds_dp Peter Senna Tschudin
  2016-08-05  7:28     ` Enric Balletbo Serra
@ 2016-08-16 15:59     ` Martyn Welch
  1 sibling, 0 replies; 64+ messages in thread
From: Martyn Welch @ 2016-08-16 15:59 UTC (permalink / raw)
  To: Peter Senna Tschudin, robh+dt, mark.rutland, shawnguo, kernel,
	fabio.estevam, linux, airlied, p.zabel, martin.donnelly,
	peter.senna, treding, architt, devicetree, linux-kernel,
	linux-arm-kernel, dri-devel
  Cc: Javier Martinez Canillas, Enric Balletbo i Serra, Rob Herring



On 04/08/16 23:36, Peter Senna Tschudin wrote:
> Devicetree bindings documentation for the GE B850v3 LVDS/DP++
> display bridge.
>
> Cc: Javier Martinez Canillas <javier@dowhile0.org>
> Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com>
> Cc: Philipp Zabel <p.zabel@pengutronix.de>
> Cc: Rob Herring <robh@kernel.org>
> Cc: Fabio Estevam <fabio.estevam@nxp.com>
> Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com>

Acked-by: Martyn Welch <martyn.welch@collabora.co.uk>

> ---
> Changes from V3:
>  - 2/4 instead of 3/5
>
> Unchanged from V2
>
> Changes from V1:
>  - Replaced '_' by '-' in node names or compatible strings
>  - Added missing @73 to the example
>
>  .../devicetree/bindings/ge/b850v3-lvds-dp.txt      | 37 ++++++++++++++++++++++
>  1 file changed, 37 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt
>
> diff --git a/Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt b/Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt
> new file mode 100644
> index 0000000..f05c3e9
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt
> @@ -0,0 +1,37 @@
> +Driver for GE B850v3 LVDS/DP++ display bridge
> +
> +Required properties:
> +  - compatible : should be "ge,b850v3-lvds-dp".
> +  - reg : should contain the address used to ack the interrupts.
> +  - interrupt-parent : phandle of the interrupt controller that services
> +    interrupts to the device
> +  - interrupts : one interrupt should be described here, as in
> +    <0 IRQ_TYPE_LEVEL_HIGH>.
> +  - edid-reg : should contain the address used to read edid information
> +  - port : should describe the video signal connection between the host
> +    and the bridge.
> +
> +Example:
> +
> +&mux2_i2c2 {
> +	status = "okay";
> +	clock-frequency = <100000>;
> +
> +	b850v3-lvds-dp-bridge@73  {
> +		compatible = "ge,b850v3-lvds-dp";
> +		#address-cells = <1>;
> +		#size-cells = <0>;
> +
> +		reg = <0x73>;
> +		interrupt-parent = <&gpio2>;
> +		interrupts = <0 IRQ_TYPE_LEVEL_HIGH>;
> +
> +		edid-reg = <0x72>;
> +
> +		port {
> +			b850v3_dp_bridge_in: endpoint {
> +				remote-endpoint = <&lvds0_out>;
> +			};
> +		};
> +	};
> +};
>

^ permalink raw reply	[flat|nested] 64+ messages in thread

* Re: [PATCH V5 2/4] Documentation/devicetree/bindings: b850v3_lvds_dp
  2016-08-09 16:41   ` [PATCH V5 2/4] Documentation/devicetree/bindings: b850v3_lvds_dp Peter Senna Tschudin
@ 2016-09-26  8:26     ` Peter Senna Tschudin
  0 siblings, 0 replies; 64+ messages in thread
From: Peter Senna Tschudin @ 2016-09-26  8:26 UTC (permalink / raw)
  To: Peter Senna Tschudin
  Cc: linux-arm-kernel, eballetbo, pawel.moll, treding, linux, heiko,
	thierry.reding, daniel.vetter, Fabio Estevam, jslaby, dri-devel,
	martyn.welch, shawnguo, linux, galak, peter.senna, airlied, ykk,
	ijc+devicetree, rmk+kernel, davem, mark.rutland, kernel,
	enric.balletbo, mchehab, tiwai, linux-kernel, gregkh, p.zabel,
	akpm, Rob Herring, javier, robh+dt, devicetree, martin.donnelly

Patch 1/4 is already on linux-next, but what about this one? Ping?

On Tuesday, August 9, 2016 18:41 CEST, Peter Senna Tschudin <peter.senna@collabora.com> wrote: 
 
> Devicetree bindings documentation for the GE B850v3 LVDS/DP++
> display bridge.
> 
> Cc: Martyn Welch <martyn.welch@collabora.co.uk>
> Cc: Martin Donnelly <martin.donnelly@ge.com>
> Cc: Javier Martinez Canillas <javier@dowhile0.org>
> Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com>
> Cc: Philipp Zabel <p.zabel@pengutronix.de>
> Cc: Rob Herring <robh@kernel.org>
> Cc: Fabio Estevam <fabio.estevam@nxp.com>
> Acked-by: Rob Herring <robh@kernel.org>
> Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com>
> ---
> Unchanged from V4
> 
> Changes from V3:
>  - 2/4 instead of 3/5
> 
> Unchanged from V2
> 
> Changes from V1:
>  - Replaced '_' by '-' in node names or compatible strings
>  - Added missing @73 to the example
> 
> 
>  .../devicetree/bindings/ge/b850v3-lvds-dp.txt      | 37 ++++++++++++++++++++++
>  1 file changed, 37 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt
> 
> diff --git a/Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt b/Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt
> new file mode 100644
> index 0000000..f05c3e9
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/ge/b850v3-lvds-dp.txt
> @@ -0,0 +1,37 @@
> +Driver for GE B850v3 LVDS/DP++ display bridge
> +
> +Required properties:
> +  - compatible : should be "ge,b850v3-lvds-dp".
> +  - reg : should contain the address used to ack the interrupts.
> +  - interrupt-parent : phandle of the interrupt controller that services
> +    interrupts to the device
> +  - interrupts : one interrupt should be described here, as in
> +    <0 IRQ_TYPE_LEVEL_HIGH>.
> +  - edid-reg : should contain the address used to read edid information
> +  - port : should describe the video signal connection between the host
> +    and the bridge.
> +
> +Example:
> +
> +&mux2_i2c2 {
> +	status = "okay";
> +	clock-frequency = <100000>;
> +
> +	b850v3-lvds-dp-bridge@73  {
> +		compatible = "ge,b850v3-lvds-dp";
> +		#address-cells = <1>;
> +		#size-cells = <0>;
> +
> +		reg = <0x73>;
> +		interrupt-parent = <&gpio2>;
> +		interrupts = <0 IRQ_TYPE_LEVEL_HIGH>;
> +
> +		edid-reg = <0x72>;
> +
> +		port {
> +			b850v3_dp_bridge_in: endpoint {
> +				remote-endpoint = <&lvds0_out>;
> +			};
> +		};
> +	};
> +};
> -- 
> 2.5.5
> 
 
 
 
 

^ permalink raw reply	[flat|nested] 64+ messages in thread

* Re: [PATCH V5 3/4] drm/bridge: Add driver for GE B850v3  LVDS/DP++ Bridge
  2016-08-09 16:41   ` [PATCH V5 3/4] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge Peter Senna Tschudin
  2016-08-16  4:15     ` Archit Taneja
@ 2016-09-26  8:27     ` Peter Senna Tschudin
  2016-09-26  8:31       ` Archit Taneja
  2016-09-26 10:29     ` Archit Taneja
  2 siblings, 1 reply; 64+ messages in thread
From: Peter Senna Tschudin @ 2016-09-26  8:27 UTC (permalink / raw)
  To: Peter Senna Tschudin
  Cc: linux-arm-kernel, eballetbo, pawel.moll, treding, linux, heiko,
	thierry.reding, daniel.vetter, Fabio Estevam, jslaby, dri-devel,
	martyn.welch, shawnguo, linux, galak, peter.senna, airlied, ykk,
	ijc+devicetree, rmk+kernel, davem, mark.rutland, Archit Taneja,
	kernel, enric.balletbo, mchehab, tiwai, linux-kernel, gregkh,
	p.zabel, akpm, Rob Herring, javier, robh+dt, devicetree,
	martin.donnelly

Patch 1/4 is already on linux-next, but what about this one? Ping?

On Tuesday, August 9, 2016 18:41 CEST, Peter Senna Tschudin <peter.senna@collabora.com> wrote: 
 
> Add a driver that create a drm_bridge and a drm_connector for the LVDS
> to DP++ display bridge of the GE B850v3.
> 
> There are two physical bridges on the video signal pipeline: a
> STDP4028(LVDS to DP) and a STDP2690(DP to DP++).  The hardware and
> firmware made it complicated for this binding to comprise two device

> tree nodes, as the design goal is to configure both bridges based on

> the LVDS signal, which leave the driver powerless to control the video
> processing pipeline. The two bridges behaves as a single bridge, and

> the driver is only needed for telling the host about EDID / HPD, and

> for giving the host powers to ack interrupts. The video signal pipeline
> is as follows:
> 
>   Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output
> 
> Cc: Martyn Welch <martyn.welch@collabora.co.uk>
> Cc: Martin Donnelly <martin.donnelly@ge.com>
> Cc: Daniel Vetter <daniel.vetter@ffwll.ch>
> Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com>
> Cc: Philipp Zabel <p.zabel@pengutronix.de>
> Cc: Rob Herring <robh@kernel.org>
> Cc: Fabio Estevam <fabio.estevam@nxp.com>
> CC: David Airlie <airlied@linux.ie>
> CC: Thierry Reding <treding@nvidia.com>
> CC: Thierry Reding <thierry.reding@gmail.com>
> Reviewed-by: Enric Balletbo <enric.balletbo@collabora.com>
> Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com>
> ---
> Changes from V4:
>  - Check the output of the first call to i2c_smbus_write_word_data() and return
>    it's error code for failing gracefully on i2c issues
>  - Renamed the i2c_driver.name from "ge,b850v3-lvds-dp" to "b850v3-lvds-dp" to
>    remove the comma from the driver name
> 
> Changes from V3:
>  - 3/4 instead of 4/5
>  - Tested on next-20160804
> 
> Changes from V2:
>  - Made it atomic to be applied on next-20160729 on top of Liu Ying changes
>    that made imx-ldb atomic
> 
> Changes from V1:
>  - New commit message
>  - Removed 3 empty entry points
>  - Removed memory leak from ge_b850v3_lvds_dp_get_modes()
>  - Added a lock for mode setting
>  - Removed a few blank lines
>  - Changed the order at Makefile and Kconfig
> 
>  MAINTAINERS                                |   8 +
>  drivers/gpu/drm/bridge/Kconfig             |  11 +
>  drivers/gpu/drm/bridge/Makefile            |   1 +
>  drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c | 405 +++++++++++++++++++++++++++++
>  4 files changed, 425 insertions(+)
>  create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c

> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index a306795..e8d106a 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -5142,6 +5142,14 @@ W:	https://linuxtv.org
>  S:	Maintained
>  F:	drivers/media/radio/radio-gemtek*
>  
> +GENERAL ELECTRIC B850V3 LVDS/DP++ BRIDGE
> +M:	Peter Senna Tschudin <peter.senna@collabora.com>
> +M:	Martin Donnelly <martin.donnelly@ge.com>
> +M:	Martyn Welch <martyn.welch@collabora.co.uk>
> +S:	Maintained
> +F:	drivers/gpu/drm/bridge/ge_b850v3_dp2.c
> +F:	Documentation/devicetree/bindings/ge/b850v3_dp2_bridge.txt
> +
>  GENERIC GPIO I2C DRIVER
>  M:	Haavard Skinnemoen <hskinnemoen@gmail.com>
>  S:	Supported
> diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
> index b590e67..b4b70fb 100644
> --- a/drivers/gpu/drm/bridge/Kconfig
> +++ b/drivers/gpu/drm/bridge/Kconfig
> @@ -32,6 +32,17 @@ config DRM_DW_HDMI_AHB_AUDIO
>  	  Designware HDMI block.  This is used in conjunction with
>  	  the i.MX6 HDMI driver.
>  
> +config DRM_GE_B850V3_LVDS_DP
> +	tristate "GE B850v3 LVDS to DP++ display bridge"
> +	depends on OF
> +	select DRM_KMS_HELPER
> +	select DRM_PANEL
> +	---help---
> +          This is a driver for the display bridge of
> +          GE B850v3 that convert dual channel LVDS
> +          to DP++. This is used with the i.MX6 imx-ldb
> +          driver.
> +
>  config DRM_NXP_PTN3460
>  	tristate "NXP PTN3460 DP/LVDS bridge"
>  	depends on OF
> diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
> index efdb07e..b9606f3 100644
> --- a/drivers/gpu/drm/bridge/Makefile
> +++ b/drivers/gpu/drm/bridge/Makefile
> @@ -3,6 +3,7 @@ ccflags-y := -Iinclude/drm
>  obj-$(CONFIG_DRM_ANALOGIX_ANX78XX) += analogix-anx78xx.o
>  obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o
>  obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o
> +obj-$(CONFIG_DRM_GE_B850V3_LVDS_DP) += ge_b850v3_lvds_dp.o
>  obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o
>  obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o
>  obj-$(CONFIG_DRM_SII902X) += sii902x.o
> diff --git a/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c
> new file mode 100644
> index 0000000..81e9279
> --- /dev/null
> +++ b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c
> @@ -0,0 +1,405 @@
> +/*
> + * Driver for GE B850v3 DP display bridge
> +
> + * Copyright (c) 2016, Collabora Ltd.
> + * Copyright (c) 2016, General Electric Company
> +
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms and conditions of the GNU General Public License,
> + * version 2, as published by the Free Software Foundation.
> +
> + * This program is distributed in the hope it will be useful, but WITHOUT
> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
> + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
> + * more details.
> +
> + * You should have received a copy of the GNU General Public License
> + * along with this program.  If not, see <http://www.gnu.org/licenses/>.
> +
> + * This driver creates a drm_bridge and a drm_connector for the LVDS to DP++
> + * display bridge of the GE B850v3. There are two physical bridges on the video
> + * signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). However
> + * the physical bridges are automatically configured by the input video signal,
> + * and the driver has no access to the video processing pipeline. The driver is
> + * only needed to read EDID from the STDP2690 and to handle HPD events from the
> + * STDP4028. The driver communicates with both bridges over i2c. The video
> + * signal pipeline is as follows:
> + *
> + *   Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output
> + *
> + */
> +
> +#include <linux/gpio.h>
> +#include <linux/i2c.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <drm/drm_atomic.h>
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_crtc_helper.h>
> +#include <drm/drm_edid.h>
> +#include <drm/drmP.h>
> +
> +/*
> + * 220Mhz is a limitation of the host, as the bridge is capable of up to
> + * 330Mhz. See section 9.2.1.2.4 of the i.MX 6Dual/6Quad Applications
> + * Processor Reference Manual for more information about the 220Mhz limit.
> + * The imx-ldb driver will warn about clocks over 170Mhz, but it seem to work
> + * fine.
> + */
> +#define MAX_PIXEL_CLOCK 220000
> +
> +#define EDID_EXT_BLOCK_CNT 0x7E
> +
> +#define STDP4028_IRQ_OUT_CONF_REG 0x02
> +#define STDP4028_DPTX_IRQ_EN_REG 0x3C
> +#define STDP4028_DPTX_IRQ_STS_REG 0x3D
> +#define STDP4028_DPTX_STS_REG 0x3E
> +
> +#define STDP4028_DPTX_DP_IRQ_EN 0x1000
> +
> +#define STDP4028_DPTX_HOTPLUG_IRQ_EN 0x0400
> +#define STDP4028_DPTX_LINK_CH_IRQ_EN 0x2000
> +#define STDP4028_DPTX_IRQ_CONFIG \
> +		(STDP4028_DPTX_LINK_CH_IRQ_EN | STDP4028_DPTX_HOTPLUG_IRQ_EN)
> +
> +#define STDP4028_DPTX_HOTPLUG_STS 0x0200
> +#define STDP4028_DPTX_LINK_STS 0x1000
> +#define STDP4028_CON_STATE_CONNECTED \
> +		(STDP4028_DPTX_HOTPLUG_STS | STDP4028_DPTX_LINK_STS)
> +
> +#define STDP4028_DPTX_HOTPLUG_CH_STS 0x0400
> +#define STDP4028_DPTX_LINK_CH_STS 0x2000
> +#define STDP4028_DPTX_IRQ_CLEAR \
> +		(STDP4028_DPTX_LINK_CH_STS | STDP4028_DPTX_HOTPLUG_CH_STS)
> +
> +struct ge_b850v3_lvds_dp {
> +	struct drm_connector connector;
> +	struct drm_bridge bridge;
> +	struct i2c_client *ge_b850v3_lvds_dp_i2c;
> +	struct i2c_client *edid_i2c;
> +	struct edid *edid;
> +	struct mutex lock;
> +};
> +
> +static inline struct ge_b850v3_lvds_dp *
> +		bridge_to_ge_b850v3_lvds_dp(struct drm_bridge *bridge)
> +{
> +	return container_of(bridge, struct ge_b850v3_lvds_dp, bridge);
> +}
> +
> +static inline struct ge_b850v3_lvds_dp *
> +		connector_to_ge_b850v3_lvds_dp(struct drm_connector *connector)
> +{
> +	return container_of(connector, struct ge_b850v3_lvds_dp, connector);
> +}
> +
> +u8 *stdp2690_get_edid(struct i2c_client *client)
> +{
> +	struct i2c_adapter *adapter = client->adapter;
> +	unsigned char start = 0x00;
> +	unsigned int total_size;
> +	u8 *block = kmalloc(EDID_LENGTH, GFP_KERNEL);
> +
> +	struct i2c_msg msgs[] = {
> +		{
> +			.addr	= client->addr,
> +			.flags	= 0,
> +			.len	= 1,
> +			.buf	= &start,
> +		}, {
> +			.addr	= client->addr,
> +			.flags	= I2C_M_RD,
> +			.len	= EDID_LENGTH,
> +			.buf	= block,
> +		}
> +	};
> +
> +	if (!block)
> +		return NULL;
> +
> +	if (i2c_transfer(adapter, msgs, 2) != 2) {
> +		DRM_ERROR("Unable to read EDID.\n");
> +		goto err;
> +	}
> +
> +	if (!drm_edid_block_valid(block, 0, false, NULL)) {
> +		DRM_ERROR("Invalid EDID block\n");
> +		goto err;
> +	}
> +
> +	total_size = (block[EDID_EXT_BLOCK_CNT] + 1) * EDID_LENGTH;
> +	if (total_size > EDID_LENGTH) {
> +		kfree(block);
> +		block = kmalloc(total_size, GFP_KERNEL);
> +		if (!block)
> +			return NULL;
> +
> +		/* Yes, read the entire buffer, and do not skip the first
> +		 * EDID_LENGTH bytes.
> +		 */
> +		start = 0x00;
> +		msgs[1].len = total_size;
> +		msgs[1].buf = block;
> +
> +		if (i2c_transfer(adapter, msgs, 2) != 2) {
> +			DRM_ERROR("Unable to read EDID extension blocks.\n");
> +			goto err;
> +		}
> +	}
> +
> +	return block;
> +
> +err:
> +	kfree(block);
> +	return NULL;
> +}
> +
> +static int ge_b850v3_lvds_dp_get_modes(struct drm_connector *connector)
> +{
> +	struct ge_b850v3_lvds_dp *ptn_bridge;
> +	struct i2c_client *client;
> +	int num_modes = 0;
> +
> +	ptn_bridge = connector_to_ge_b850v3_lvds_dp(connector);
> +	client = ptn_bridge->edid_i2c;
> +
> +	mutex_lock(&ptn_bridge->lock);
> +
> +	kfree(ptn_bridge->edid);
> +	ptn_bridge->edid = (struct edid *) stdp2690_get_edid(client);
> +
> +	if (ptn_bridge->edid) {
> +		drm_mode_connector_update_edid_property(connector,
> +				ptn_bridge->edid);
> +		num_modes = drm_add_edid_modes(connector, ptn_bridge->edid);
> +	}
> +
> +	mutex_unlock(&ptn_bridge->lock);
> +
> +	return num_modes;
> +}
> +
> +
> +static enum drm_mode_status ge_b850v3_lvds_dp_mode_valid(
> +		struct drm_connector *connector, struct drm_display_mode *mode)
> +{
> +	if (mode->clock > MAX_PIXEL_CLOCK) {
> +		DRM_INFO("The pixel clock for the mode %s is too high, and not supported.",
> +				mode->name);
> +		return MODE_CLOCK_HIGH;
> +	}
> +
> +	return MODE_OK;
> +}
> +
> +static const struct
> +drm_connector_helper_funcs ge_b850v3_lvds_dp_connector_helper_funcs = {
> +	.get_modes = ge_b850v3_lvds_dp_get_modes,
> +	.mode_valid = ge_b850v3_lvds_dp_mode_valid,
> +};
> +
> +static enum drm_connector_status ge_b850v3_lvds_dp_detect(
> +		struct drm_connector *connector, bool force)
> +{
> +	struct ge_b850v3_lvds_dp *ptn_bridge =
> +			connector_to_ge_b850v3_lvds_dp(connector);
> +	struct i2c_client *ge_b850v3_lvds_dp_i2c =
> +			ptn_bridge->ge_b850v3_lvds_dp_i2c;
> +	s32 link_state;
> +
> +	link_state = i2c_smbus_read_word_data(ge_b850v3_lvds_dp_i2c,
> +			STDP4028_DPTX_STS_REG);
> +
> +	if (link_state == STDP4028_CON_STATE_CONNECTED)
> +		return connector_status_connected;
> +
> +	if (link_state == 0)
> +		return connector_status_disconnected;
> +
> +	return connector_status_unknown;
> +}
> +
> +static const struct drm_connector_funcs ge_b850v3_lvds_dp_connector_funcs = {
> +	.dpms = drm_atomic_helper_connector_dpms,
> +	.fill_modes = drm_helper_probe_single_connector_modes,
> +	.detect = ge_b850v3_lvds_dp_detect,
> +	.destroy = drm_connector_cleanup,
> +	.reset = drm_atomic_helper_connector_reset,
> +	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
> +	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
> +};
> +
> +static irqreturn_t ge_b850v3_lvds_dp_irq_handler(int irq, void *dev_id)
> +{
> +	struct ge_b850v3_lvds_dp *ptn_bridge = dev_id;
> +	struct i2c_client *ge_b850v3_lvds_dp_i2c
> +			= ptn_bridge->ge_b850v3_lvds_dp_i2c;
> +
> +	mutex_lock(&ptn_bridge->lock);
> +
> +	i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c,
> +			STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR);
> +
> +	mutex_unlock(&ptn_bridge->lock);
> +
> +	if (ptn_bridge->connector.dev)
> +		drm_kms_helper_hotplug_event(ptn_bridge->connector.dev);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static int ge_b850v3_lvds_dp_attach(struct drm_bridge *bridge)
> +{
> +	struct ge_b850v3_lvds_dp *ptn_bridge
> +			= bridge_to_ge_b850v3_lvds_dp(bridge);
> +	struct drm_connector *connector = &ptn_bridge->connector;
> +	struct i2c_client *ge_b850v3_lvds_dp_i2c
> +			= ptn_bridge->ge_b850v3_lvds_dp_i2c;
> +	int ret;
> +
> +	if (!bridge->encoder) {
> +		DRM_ERROR("Parent encoder object not found");
> +		return -ENODEV;
> +	}
> +
> +	connector->polled = DRM_CONNECTOR_POLL_HPD;
> +
> +	drm_connector_helper_add(connector,
> +			&ge_b850v3_lvds_dp_connector_helper_funcs);
> +
> +	ret = drm_connector_init(bridge->dev, connector,
> +			&ge_b850v3_lvds_dp_connector_funcs,
> +			DRM_MODE_CONNECTOR_DisplayPort);
> +	if (ret) {
> +		DRM_ERROR("Failed to initialize connector with drm\n");
> +		return ret;
> +	}
> +
> +	drm_connector_register(connector);
> +	ret = drm_mode_connector_attach_encoder(connector, bridge->encoder);
> +	if (ret)
> +		return ret;
> +
> +	drm_bridge_enable(bridge);
> +	if (ge_b850v3_lvds_dp_i2c->irq) {
> +		drm_helper_hpd_irq_event(connector->dev);
> +
> +		ret = devm_request_threaded_irq(&ge_b850v3_lvds_dp_i2c->dev,
> +				ge_b850v3_lvds_dp_i2c->irq, NULL,
> +				ge_b850v3_lvds_dp_irq_handler,
> +				IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
> +				"ge-b850v3-lvds-dp", ptn_bridge);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static const struct drm_bridge_funcs ge_b850v3_lvds_dp_funcs = {
> +	.attach = ge_b850v3_lvds_dp_attach,
> +};
> +
> +static int ge_b850v3_lvds_dp_probe(struct i2c_client *ge_b850v3_lvds_dp_i2c,
> +				const struct i2c_device_id *id)
> +{
> +	struct device *dev = &ge_b850v3_lvds_dp_i2c->dev;
> +	struct ge_b850v3_lvds_dp *ptn_bridge;
> +	int ret;
> +	u32 edid_i2c_reg;
> +
> +	ptn_bridge = devm_kzalloc(dev, sizeof(*ptn_bridge), GFP_KERNEL);
> +	if (!ptn_bridge)
> +		return -ENOMEM;
> +
> +	mutex_init(&ptn_bridge->lock);
> +
> +	ptn_bridge->ge_b850v3_lvds_dp_i2c = ge_b850v3_lvds_dp_i2c;
> +	ptn_bridge->bridge.driver_private = ptn_bridge;
> +	i2c_set_clientdata(ge_b850v3_lvds_dp_i2c, ptn_bridge);
> +
> +	ret = of_property_read_u32(dev->of_node, "edid-reg", &edid_i2c_reg);
> +	if (ret) {
> +		dev_err(dev, "edid-reg not specified, aborting...\n");
> +		return -ENODEV;
> +	}
> +
> +	ptn_bridge->edid_i2c = devm_kzalloc(dev,
> +			sizeof(struct i2c_client), GFP_KERNEL);
> +
> +	if (!ptn_bridge->edid_i2c)
> +		return -ENOMEM;
> +
> +	memcpy(ptn_bridge->edid_i2c, ge_b850v3_lvds_dp_i2c,
> +			sizeof(struct i2c_client));
> +
> +	ptn_bridge->edid_i2c->addr = (unsigned short) edid_i2c_reg;
> +
> +	/*
> +	 * Configures the bridge to re-enable interrupts after each ack. As
> +	 * this is the first communication with the chip, fail on error.
> +	 */
> +	ret = i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c,
> +			STDP4028_IRQ_OUT_CONF_REG, STDP4028_DPTX_DP_IRQ_EN);
> +	if (ret) {
> +		dev_err(dev, "i2c communication failed, aborting...\n");
> +		return ret;
> +	}
> +
> +	i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c,
> +			STDP4028_DPTX_IRQ_EN_REG, STDP4028_DPTX_IRQ_CONFIG);
> +
> +	/* Clear pending interrupts since power up. */
> +	i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c,
> +			STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR);
> +
> +	ptn_bridge->bridge.funcs = &ge_b850v3_lvds_dp_funcs;
> +	ptn_bridge->bridge.of_node = dev->of_node;
> +	ret = drm_bridge_add(&ptn_bridge->bridge);
> +	if (ret) {
> +		DRM_ERROR("Failed to add bridge\n");
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static int ge_b850v3_lvds_dp_remove(struct i2c_client *ge_b850v3_lvds_dp_i2c)
> +{
> +	struct ge_b850v3_lvds_dp *ptn_bridge =
> +		i2c_get_clientdata(ge_b850v3_lvds_dp_i2c);
> +
> +	drm_bridge_remove(&ptn_bridge->bridge);
> +
> +	kfree(ptn_bridge->edid);
> +
> +	return 0;
> +}
> +
> +static const struct i2c_device_id ge_b850v3_lvds_dp_i2c_table[] = {
> +	{"b850v3-lvds-dp", 0},
> +	{},
> +};
> +MODULE_DEVICE_TABLE(i2c, ge_b850v3_lvds_dp_i2c_table);
> +
> +static const struct of_device_id ge_b850v3_lvds_dp_match[] = {
> +	{ .compatible = "ge,b850v3-lvds-dp" },
> +	{},
> +};
> +MODULE_DEVICE_TABLE(of, ge_b850v3_lvds_dp_match);
> +
> +static struct i2c_driver ge_b850v3_lvds_dp_driver = {
> +	.id_table	= ge_b850v3_lvds_dp_i2c_table,
> +	.probe		= ge_b850v3_lvds_dp_probe,
> +	.remove		= ge_b850v3_lvds_dp_remove,
> +	.driver		= {
> +		.name		= "b850v3-lvds-dp",
> +		.of_match_table = ge_b850v3_lvds_dp_match,
> +	},
> +};
> +module_i2c_driver(ge_b850v3_lvds_dp_driver);
> +
> +MODULE_AUTHOR("Peter Senna Tschudin <peter.senna@collabora.com>");
> +MODULE_AUTHOR("Martyn Welch <martyn.welch@collabora.co.uk>");
> +MODULE_DESCRIPTION("GE LVDS to DP++ display bridge)");
> +MODULE_LICENSE("GPL v2");
> -- 
> 2.5.5
> 
 
 
 
 

^ permalink raw reply	[flat|nested] 64+ messages in thread

* Re: [PATCH V5 4/4] dts/imx6q-b850v3: Use GE B850v3 LVDS/DP++  Bridge
  2016-08-09 16:41   ` [PATCH V5 4/4] dts/imx6q-b850v3: Use " Peter Senna Tschudin
@ 2016-09-26  8:27     ` Peter Senna Tschudin
  2016-09-29 10:39       ` Shawn Guo
  0 siblings, 1 reply; 64+ messages in thread
From: Peter Senna Tschudin @ 2016-09-26  8:27 UTC (permalink / raw)
  To: Peter Senna Tschudin
  Cc: linux-arm-kernel, eballetbo, pawel.moll, treding, linux, heiko,
	thierry.reding, daniel.vetter, Fabio Estevam, jslaby, dri-devel,
	martyn.welch, shawnguo, linux, galak, peter.senna, airlied, ykk,
	ijc+devicetree, rmk+kernel, davem, mark.rutland, kernel,
	enric.balletbo, mchehab, tiwai, linux-kernel, gregkh, p.zabel,
	akpm, Rob Herring, javier, robh+dt, devicetree, martin.donnelly

Patch 1/4 is already on linux-next, but what about this one? Ping?

On Tuesday, August 9, 2016 18:41 CEST, Peter Senna Tschudin <peter.senna@collabora.com> wrote: 
 
> Configures the GE B850v3 LVDS/DP++ bridge on the dts file.
> 
> Cc: Martyn Welch <martyn.welch@collabora.co.uk>
> Cc: Martin Donnelly <martin.donnelly@ge.com>
> Cc: Javier Martinez Canillas <javier@dowhile0.org>
> Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com>
> Cc: Philipp Zabel <p.zabel@pengutronix.de>
> Cc: Rob Herring <robh@kernel.org>
> Cc: Fabio Estevam <fabio.estevam@nxp.com>
> Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com>
> ---
> Unchanged from V4
> 
> Changes from V3:
>  - 4/4 instead of 5/5
> 
> Unchanged from V2
> 
> Changes from V1:
>  - Replaced '_' by '-' in node names or compatible strings
>  - Added missing @73 to b850v3-lvds-dp-bridge
> 
>  arch/arm/boot/dts/imx6q-b850v3.dts | 30 ++++++++++++++++++++++++++++++
>  1 file changed, 30 insertions(+)
> 
> diff --git a/arch/arm/boot/dts/imx6q-b850v3.dts b/arch/arm/boot/dts/imx6q-b850v3.dts
> index 167f744..8db3bf2 100644
> --- a/arch/arm/boot/dts/imx6q-b850v3.dts
> +++ b/arch/arm/boot/dts/imx6q-b850v3.dts
> @@ -72,6 +72,13 @@
>  		fsl,data-mapping = "spwg";
>  		fsl,data-width = <24>;
>  		status = "okay";
> +
> +		port@4 {
> +			reg = <4>;
> +			lvds0_out: endpoint {
> +				remote-endpoint = <&b850v3_lvds_dp_bridge_in>;
> +			};
> +		};
>  	};
>  };
>  
> @@ -142,3 +149,26 @@
>  		reg = <0x4a>;
>  	};
>  };
> +
> +&mux2_i2c2 {
> +	status = "okay";
> +	clock-frequency = <100000>;
> +
> +	b850v3-lvds-dp-bridge@73 {
> +		compatible = "ge,b850v3-lvds-dp";
> +		#address-cells = <1>;
> +		#size-cells = <0>;
> +
> +		reg = <0x73>;
> +		interrupt-parent = <&gpio2>;
> +		interrupts = <0 IRQ_TYPE_LEVEL_HIGH>;
> +
> +		edid-reg = <0x72>;
> +
> +		port {
> +			b850v3_lvds_dp_bridge_in: endpoint {
> +				remote-endpoint = <&lvds0_out>;
> +			};
> +		};
> +	};
> +};
> -- 
> 2.5.5
> 
 
 
 
 

^ permalink raw reply	[flat|nested] 64+ messages in thread

* Re: [PATCH V5 3/4] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge
  2016-09-26  8:27     ` Peter Senna Tschudin
@ 2016-09-26  8:31       ` Archit Taneja
  2016-09-26  8:58         ` Peter Senna Tschudin
  0 siblings, 1 reply; 64+ messages in thread
From: Archit Taneja @ 2016-09-26  8:31 UTC (permalink / raw)
  To: Peter Senna Tschudin, Peter Senna Tschudin
  Cc: linux-arm-kernel, eballetbo, pawel.moll, treding, linux, heiko,
	thierry.reding, daniel.vetter, Fabio Estevam, jslaby, dri-devel,
	martyn.welch, shawnguo, linux, galak, peter.senna, airlied, ykk,
	ijc+devicetree, rmk+kernel, davem, mark.rutland, kernel,
	enric.balletbo, mchehab, tiwai, linux-kernel, gregkh, p.zabel,
	akpm, Rob Herring, javier, robh+dt, devicetree, martin.donnelly

Hi Peter,

On 09/26/2016 01:57 PM, Peter Senna Tschudin wrote:
> Patch 1/4 is already on linux-next, but what about this one? Ping?

I'd posted some queries a couple of times which you didn't answer to.
Could you please respond to them before we try to get this merged?

Archit

>
> On Tuesday, August 9, 2016 18:41 CEST, Peter Senna Tschudin <peter.senna@collabora.com> wrote:
>
>> Add a driver that create a drm_bridge and a drm_connector for the LVDS
>> to DP++ display bridge of the GE B850v3.
>>
>> There are two physical bridges on the video signal pipeline: a
>> STDP4028(LVDS to DP) and a STDP2690(DP to DP++).  The hardware and
>> firmware made it complicated for this binding to comprise two device
>> tree nodes, as the design goal is to configure both bridges based on
>> the LVDS signal, which leave the driver powerless to control the video
>> processing pipeline. The two bridges behaves as a single bridge, and
>> the driver is only needed for telling the host about EDID / HPD, and
>> for giving the host powers to ack interrupts. The video signal pipeline
>> is as follows:
>>
>>   Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output
>>
>> Cc: Martyn Welch <martyn.welch@collabora.co.uk>
>> Cc: Martin Donnelly <martin.donnelly@ge.com>
>> Cc: Daniel Vetter <daniel.vetter@ffwll.ch>
>> Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com>
>> Cc: Philipp Zabel <p.zabel@pengutronix.de>
>> Cc: Rob Herring <robh@kernel.org>
>> Cc: Fabio Estevam <fabio.estevam@nxp.com>
>> CC: David Airlie <airlied@linux.ie>
>> CC: Thierry Reding <treding@nvidia.com>
>> CC: Thierry Reding <thierry.reding@gmail.com>
>> Reviewed-by: Enric Balletbo <enric.balletbo@collabora.com>
>> Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com>
>> ---
>> Changes from V4:
>>  - Check the output of the first call to i2c_smbus_write_word_data() and return
>>    it's error code for failing gracefully on i2c issues
>>  - Renamed the i2c_driver.name from "ge,b850v3-lvds-dp" to "b850v3-lvds-dp" to
>>    remove the comma from the driver name
>>
>> Changes from V3:
>>  - 3/4 instead of 4/5
>>  - Tested on next-20160804
>>
>> Changes from V2:
>>  - Made it atomic to be applied on next-20160729 on top of Liu Ying changes
>>    that made imx-ldb atomic
>>
>> Changes from V1:
>>  - New commit message
>>  - Removed 3 empty entry points
>>  - Removed memory leak from ge_b850v3_lvds_dp_get_modes()
>>  - Added a lock for mode setting
>>  - Removed a few blank lines
>>  - Changed the order at Makefile and Kconfig
>>
>>  MAINTAINERS                                |   8 +
>>  drivers/gpu/drm/bridge/Kconfig             |  11 +
>>  drivers/gpu/drm/bridge/Makefile            |   1 +
>>  drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c | 405 +++++++++++++++++++++++++++++
>>  4 files changed, 425 insertions(+)
>>  create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c
>>
>> diff --git a/MAINTAINERS b/MAINTAINERS
>> index a306795..e8d106a 100644
>> --- a/MAINTAINERS
>> +++ b/MAINTAINERS
>> @@ -5142,6 +5142,14 @@ W:	https://linuxtv.org
>>  S:	Maintained
>>  F:	drivers/media/radio/radio-gemtek*
>>
>> +GENERAL ELECTRIC B850V3 LVDS/DP++ BRIDGE
>> +M:	Peter Senna Tschudin <peter.senna@collabora.com>
>> +M:	Martin Donnelly <martin.donnelly@ge.com>
>> +M:	Martyn Welch <martyn.welch@collabora.co.uk>
>> +S:	Maintained
>> +F:	drivers/gpu/drm/bridge/ge_b850v3_dp2.c
>> +F:	Documentation/devicetree/bindings/ge/b850v3_dp2_bridge.txt
>> +
>>  GENERIC GPIO I2C DRIVER
>>  M:	Haavard Skinnemoen <hskinnemoen@gmail.com>
>>  S:	Supported
>> diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
>> index b590e67..b4b70fb 100644
>> --- a/drivers/gpu/drm/bridge/Kconfig
>> +++ b/drivers/gpu/drm/bridge/Kconfig
>> @@ -32,6 +32,17 @@ config DRM_DW_HDMI_AHB_AUDIO
>>  	  Designware HDMI block.  This is used in conjunction with
>>  	  the i.MX6 HDMI driver.
>>
>> +config DRM_GE_B850V3_LVDS_DP
>> +	tristate "GE B850v3 LVDS to DP++ display bridge"
>> +	depends on OF
>> +	select DRM_KMS_HELPER
>> +	select DRM_PANEL
>> +	---help---
>> +          This is a driver for the display bridge of
>> +          GE B850v3 that convert dual channel LVDS
>> +          to DP++. This is used with the i.MX6 imx-ldb
>> +          driver.
>> +
>>  config DRM_NXP_PTN3460
>>  	tristate "NXP PTN3460 DP/LVDS bridge"
>>  	depends on OF
>> diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
>> index efdb07e..b9606f3 100644
>> --- a/drivers/gpu/drm/bridge/Makefile
>> +++ b/drivers/gpu/drm/bridge/Makefile
>> @@ -3,6 +3,7 @@ ccflags-y := -Iinclude/drm
>>  obj-$(CONFIG_DRM_ANALOGIX_ANX78XX) += analogix-anx78xx.o
>>  obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o
>>  obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o
>> +obj-$(CONFIG_DRM_GE_B850V3_LVDS_DP) += ge_b850v3_lvds_dp.o
>>  obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o
>>  obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o
>>  obj-$(CONFIG_DRM_SII902X) += sii902x.o
>> diff --git a/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c
>> new file mode 100644
>> index 0000000..81e9279
>> --- /dev/null
>> +++ b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c
>> @@ -0,0 +1,405 @@
>> +/*
>> + * Driver for GE B850v3 DP display bridge
>> +
>> + * Copyright (c) 2016, Collabora Ltd.
>> + * Copyright (c) 2016, General Electric Company
>> +
>> + * This program is free software; you can redistribute it and/or modify it
>> + * under the terms and conditions of the GNU General Public License,
>> + * version 2, as published by the Free Software Foundation.
>> +
>> + * This program is distributed in the hope it will be useful, but WITHOUT
>> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
>> + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
>> + * more details.
>> +
>> + * You should have received a copy of the GNU General Public License
>> + * along with this program.  If not, see <http://www.gnu.org/licenses/>.
>> +
>> + * This driver creates a drm_bridge and a drm_connector for the LVDS to DP++
>> + * display bridge of the GE B850v3. There are two physical bridges on the video
>> + * signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). However
>> + * the physical bridges are automatically configured by the input video signal,
>> + * and the driver has no access to the video processing pipeline. The driver is
>> + * only needed to read EDID from the STDP2690 and to handle HPD events from the
>> + * STDP4028. The driver communicates with both bridges over i2c. The video
>> + * signal pipeline is as follows:
>> + *
>> + *   Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output
>> + *
>> + */
>> +
>> +#include <linux/gpio.h>
>> +#include <linux/i2c.h>
>> +#include <linux/module.h>
>> +#include <linux/of.h>
>> +#include <drm/drm_atomic.h>
>> +#include <drm/drm_atomic_helper.h>
>> +#include <drm/drm_crtc_helper.h>
>> +#include <drm/drm_edid.h>
>> +#include <drm/drmP.h>
>> +
>> +/*
>> + * 220Mhz is a limitation of the host, as the bridge is capable of up to
>> + * 330Mhz. See section 9.2.1.2.4 of the i.MX 6Dual/6Quad Applications
>> + * Processor Reference Manual for more information about the 220Mhz limit.
>> + * The imx-ldb driver will warn about clocks over 170Mhz, but it seem to work
>> + * fine.
>> + */
>> +#define MAX_PIXEL_CLOCK 220000
>> +
>> +#define EDID_EXT_BLOCK_CNT 0x7E
>> +
>> +#define STDP4028_IRQ_OUT_CONF_REG 0x02
>> +#define STDP4028_DPTX_IRQ_EN_REG 0x3C
>> +#define STDP4028_DPTX_IRQ_STS_REG 0x3D
>> +#define STDP4028_DPTX_STS_REG 0x3E
>> +
>> +#define STDP4028_DPTX_DP_IRQ_EN 0x1000
>> +
>> +#define STDP4028_DPTX_HOTPLUG_IRQ_EN 0x0400
>> +#define STDP4028_DPTX_LINK_CH_IRQ_EN 0x2000
>> +#define STDP4028_DPTX_IRQ_CONFIG \
>> +		(STDP4028_DPTX_LINK_CH_IRQ_EN | STDP4028_DPTX_HOTPLUG_IRQ_EN)
>> +
>> +#define STDP4028_DPTX_HOTPLUG_STS 0x0200
>> +#define STDP4028_DPTX_LINK_STS 0x1000
>> +#define STDP4028_CON_STATE_CONNECTED \
>> +		(STDP4028_DPTX_HOTPLUG_STS | STDP4028_DPTX_LINK_STS)
>> +
>> +#define STDP4028_DPTX_HOTPLUG_CH_STS 0x0400
>> +#define STDP4028_DPTX_LINK_CH_STS 0x2000
>> +#define STDP4028_DPTX_IRQ_CLEAR \
>> +		(STDP4028_DPTX_LINK_CH_STS | STDP4028_DPTX_HOTPLUG_CH_STS)
>> +
>> +struct ge_b850v3_lvds_dp {
>> +	struct drm_connector connector;
>> +	struct drm_bridge bridge;
>> +	struct i2c_client *ge_b850v3_lvds_dp_i2c;
>> +	struct i2c_client *edid_i2c;
>> +	struct edid *edid;
>> +	struct mutex lock;
>> +};
>> +
>> +static inline struct ge_b850v3_lvds_dp *
>> +		bridge_to_ge_b850v3_lvds_dp(struct drm_bridge *bridge)
>> +{
>> +	return container_of(bridge, struct ge_b850v3_lvds_dp, bridge);
>> +}
>> +
>> +static inline struct ge_b850v3_lvds_dp *
>> +		connector_to_ge_b850v3_lvds_dp(struct drm_connector *connector)
>> +{
>> +	return container_of(connector, struct ge_b850v3_lvds_dp, connector);
>> +}
>> +
>> +u8 *stdp2690_get_edid(struct i2c_client *client)
>> +{
>> +	struct i2c_adapter *adapter = client->adapter;
>> +	unsigned char start = 0x00;
>> +	unsigned int total_size;
>> +	u8 *block = kmalloc(EDID_LENGTH, GFP_KERNEL);
>> +
>> +	struct i2c_msg msgs[] = {
>> +		{
>> +			.addr	= client->addr,
>> +			.flags	= 0,
>> +			.len	= 1,
>> +			.buf	= &start,
>> +		}, {
>> +			.addr	= client->addr,
>> +			.flags	= I2C_M_RD,
>> +			.len	= EDID_LENGTH,
>> +			.buf	= block,
>> +		}
>> +	};
>> +
>> +	if (!block)
>> +		return NULL;
>> +
>> +	if (i2c_transfer(adapter, msgs, 2) != 2) {
>> +		DRM_ERROR("Unable to read EDID.\n");
>> +		goto err;
>> +	}
>> +
>> +	if (!drm_edid_block_valid(block, 0, false, NULL)) {
>> +		DRM_ERROR("Invalid EDID block\n");
>> +		goto err;
>> +	}
>> +
>> +	total_size = (block[EDID_EXT_BLOCK_CNT] + 1) * EDID_LENGTH;
>> +	if (total_size > EDID_LENGTH) {
>> +		kfree(block);
>> +		block = kmalloc(total_size, GFP_KERNEL);
>> +		if (!block)
>> +			return NULL;
>> +
>> +		/* Yes, read the entire buffer, and do not skip the first
>> +		 * EDID_LENGTH bytes.
>> +		 */
>> +		start = 0x00;
>> +		msgs[1].len = total_size;
>> +		msgs[1].buf = block;
>> +
>> +		if (i2c_transfer(adapter, msgs, 2) != 2) {
>> +			DRM_ERROR("Unable to read EDID extension blocks.\n");
>> +			goto err;
>> +		}
>> +	}
>> +
>> +	return block;
>> +
>> +err:
>> +	kfree(block);
>> +	return NULL;
>> +}
>> +
>> +static int ge_b850v3_lvds_dp_get_modes(struct drm_connector *connector)
>> +{
>> +	struct ge_b850v3_lvds_dp *ptn_bridge;
>> +	struct i2c_client *client;
>> +	int num_modes = 0;
>> +
>> +	ptn_bridge = connector_to_ge_b850v3_lvds_dp(connector);
>> +	client = ptn_bridge->edid_i2c;
>> +
>> +	mutex_lock(&ptn_bridge->lock);
>> +
>> +	kfree(ptn_bridge->edid);
>> +	ptn_bridge->edid = (struct edid *) stdp2690_get_edid(client);
>> +
>> +	if (ptn_bridge->edid) {
>> +		drm_mode_connector_update_edid_property(connector,
>> +				ptn_bridge->edid);
>> +		num_modes = drm_add_edid_modes(connector, ptn_bridge->edid);
>> +	}
>> +
>> +	mutex_unlock(&ptn_bridge->lock);
>> +
>> +	return num_modes;
>> +}
>> +
>> +
>> +static enum drm_mode_status ge_b850v3_lvds_dp_mode_valid(
>> +		struct drm_connector *connector, struct drm_display_mode *mode)
>> +{
>> +	if (mode->clock > MAX_PIXEL_CLOCK) {
>> +		DRM_INFO("The pixel clock for the mode %s is too high, and not supported.",
>> +				mode->name);
>> +		return MODE_CLOCK_HIGH;
>> +	}
>> +
>> +	return MODE_OK;
>> +}
>> +
>> +static const struct
>> +drm_connector_helper_funcs ge_b850v3_lvds_dp_connector_helper_funcs = {
>> +	.get_modes = ge_b850v3_lvds_dp_get_modes,
>> +	.mode_valid = ge_b850v3_lvds_dp_mode_valid,
>> +};
>> +
>> +static enum drm_connector_status ge_b850v3_lvds_dp_detect(
>> +		struct drm_connector *connector, bool force)
>> +{
>> +	struct ge_b850v3_lvds_dp *ptn_bridge =
>> +			connector_to_ge_b850v3_lvds_dp(connector);
>> +	struct i2c_client *ge_b850v3_lvds_dp_i2c =
>> +			ptn_bridge->ge_b850v3_lvds_dp_i2c;
>> +	s32 link_state;
>> +
>> +	link_state = i2c_smbus_read_word_data(ge_b850v3_lvds_dp_i2c,
>> +			STDP4028_DPTX_STS_REG);
>> +
>> +	if (link_state == STDP4028_CON_STATE_CONNECTED)
>> +		return connector_status_connected;
>> +
>> +	if (link_state == 0)
>> +		return connector_status_disconnected;
>> +
>> +	return connector_status_unknown;
>> +}
>> +
>> +static const struct drm_connector_funcs ge_b850v3_lvds_dp_connector_funcs = {
>> +	.dpms = drm_atomic_helper_connector_dpms,
>> +	.fill_modes = drm_helper_probe_single_connector_modes,
>> +	.detect = ge_b850v3_lvds_dp_detect,
>> +	.destroy = drm_connector_cleanup,
>> +	.reset = drm_atomic_helper_connector_reset,
>> +	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
>> +	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
>> +};
>> +
>> +static irqreturn_t ge_b850v3_lvds_dp_irq_handler(int irq, void *dev_id)
>> +{
>> +	struct ge_b850v3_lvds_dp *ptn_bridge = dev_id;
>> +	struct i2c_client *ge_b850v3_lvds_dp_i2c
>> +			= ptn_bridge->ge_b850v3_lvds_dp_i2c;
>> +
>> +	mutex_lock(&ptn_bridge->lock);
>> +
>> +	i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c,
>> +			STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR);
>> +
>> +	mutex_unlock(&ptn_bridge->lock);
>> +
>> +	if (ptn_bridge->connector.dev)
>> +		drm_kms_helper_hotplug_event(ptn_bridge->connector.dev);
>> +
>> +	return IRQ_HANDLED;
>> +}
>> +
>> +static int ge_b850v3_lvds_dp_attach(struct drm_bridge *bridge)
>> +{
>> +	struct ge_b850v3_lvds_dp *ptn_bridge
>> +			= bridge_to_ge_b850v3_lvds_dp(bridge);
>> +	struct drm_connector *connector = &ptn_bridge->connector;
>> +	struct i2c_client *ge_b850v3_lvds_dp_i2c
>> +			= ptn_bridge->ge_b850v3_lvds_dp_i2c;
>> +	int ret;
>> +
>> +	if (!bridge->encoder) {
>> +		DRM_ERROR("Parent encoder object not found");
>> +		return -ENODEV;
>> +	}
>> +
>> +	connector->polled = DRM_CONNECTOR_POLL_HPD;
>> +
>> +	drm_connector_helper_add(connector,
>> +			&ge_b850v3_lvds_dp_connector_helper_funcs);
>> +
>> +	ret = drm_connector_init(bridge->dev, connector,
>> +			&ge_b850v3_lvds_dp_connector_funcs,
>> +			DRM_MODE_CONNECTOR_DisplayPort);
>> +	if (ret) {
>> +		DRM_ERROR("Failed to initialize connector with drm\n");
>> +		return ret;
>> +	}
>> +
>> +	drm_connector_register(connector);
>> +	ret = drm_mode_connector_attach_encoder(connector, bridge->encoder);
>> +	if (ret)
>> +		return ret;
>> +
>> +	drm_bridge_enable(bridge);
>> +	if (ge_b850v3_lvds_dp_i2c->irq) {
>> +		drm_helper_hpd_irq_event(connector->dev);
>> +
>> +		ret = devm_request_threaded_irq(&ge_b850v3_lvds_dp_i2c->dev,
>> +				ge_b850v3_lvds_dp_i2c->irq, NULL,
>> +				ge_b850v3_lvds_dp_irq_handler,
>> +				IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
>> +				"ge-b850v3-lvds-dp", ptn_bridge);
>> +		if (ret)
>> +			return ret;
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +static const struct drm_bridge_funcs ge_b850v3_lvds_dp_funcs = {
>> +	.attach = ge_b850v3_lvds_dp_attach,
>> +};
>> +
>> +static int ge_b850v3_lvds_dp_probe(struct i2c_client *ge_b850v3_lvds_dp_i2c,
>> +				const struct i2c_device_id *id)
>> +{
>> +	struct device *dev = &ge_b850v3_lvds_dp_i2c->dev;
>> +	struct ge_b850v3_lvds_dp *ptn_bridge;
>> +	int ret;
>> +	u32 edid_i2c_reg;
>> +
>> +	ptn_bridge = devm_kzalloc(dev, sizeof(*ptn_bridge), GFP_KERNEL);
>> +	if (!ptn_bridge)
>> +		return -ENOMEM;
>> +
>> +	mutex_init(&ptn_bridge->lock);
>> +
>> +	ptn_bridge->ge_b850v3_lvds_dp_i2c = ge_b850v3_lvds_dp_i2c;
>> +	ptn_bridge->bridge.driver_private = ptn_bridge;
>> +	i2c_set_clientdata(ge_b850v3_lvds_dp_i2c, ptn_bridge);
>> +
>> +	ret = of_property_read_u32(dev->of_node, "edid-reg", &edid_i2c_reg);
>> +	if (ret) {
>> +		dev_err(dev, "edid-reg not specified, aborting...\n");
>> +		return -ENODEV;
>> +	}
>> +
>> +	ptn_bridge->edid_i2c = devm_kzalloc(dev,
>> +			sizeof(struct i2c_client), GFP_KERNEL);
>> +
>> +	if (!ptn_bridge->edid_i2c)
>> +		return -ENOMEM;
>> +
>> +	memcpy(ptn_bridge->edid_i2c, ge_b850v3_lvds_dp_i2c,
>> +			sizeof(struct i2c_client));
>> +
>> +	ptn_bridge->edid_i2c->addr = (unsigned short) edid_i2c_reg;
>> +
>> +	/*
>> +	 * Configures the bridge to re-enable interrupts after each ack. As
>> +	 * this is the first communication with the chip, fail on error.
>> +	 */
>> +	ret = i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c,
>> +			STDP4028_IRQ_OUT_CONF_REG, STDP4028_DPTX_DP_IRQ_EN);
>> +	if (ret) {
>> +		dev_err(dev, "i2c communication failed, aborting...\n");
>> +		return ret;
>> +	}
>> +
>> +	i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c,
>> +			STDP4028_DPTX_IRQ_EN_REG, STDP4028_DPTX_IRQ_CONFIG);
>> +
>> +	/* Clear pending interrupts since power up. */
>> +	i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c,
>> +			STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR);
>> +
>> +	ptn_bridge->bridge.funcs = &ge_b850v3_lvds_dp_funcs;
>> +	ptn_bridge->bridge.of_node = dev->of_node;
>> +	ret = drm_bridge_add(&ptn_bridge->bridge);
>> +	if (ret) {
>> +		DRM_ERROR("Failed to add bridge\n");
>> +		return ret;
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +static int ge_b850v3_lvds_dp_remove(struct i2c_client *ge_b850v3_lvds_dp_i2c)
>> +{
>> +	struct ge_b850v3_lvds_dp *ptn_bridge =
>> +		i2c_get_clientdata(ge_b850v3_lvds_dp_i2c);
>> +
>> +	drm_bridge_remove(&ptn_bridge->bridge);
>> +
>> +	kfree(ptn_bridge->edid);
>> +
>> +	return 0;
>> +}
>> +
>> +static const struct i2c_device_id ge_b850v3_lvds_dp_i2c_table[] = {
>> +	{"b850v3-lvds-dp", 0},
>> +	{},
>> +};
>> +MODULE_DEVICE_TABLE(i2c, ge_b850v3_lvds_dp_i2c_table);
>> +
>> +static const struct of_device_id ge_b850v3_lvds_dp_match[] = {
>> +	{ .compatible = "ge,b850v3-lvds-dp" },
>> +	{},
>> +};
>> +MODULE_DEVICE_TABLE(of, ge_b850v3_lvds_dp_match);
>> +
>> +static struct i2c_driver ge_b850v3_lvds_dp_driver = {
>> +	.id_table	= ge_b850v3_lvds_dp_i2c_table,
>> +	.probe		= ge_b850v3_lvds_dp_probe,
>> +	.remove		= ge_b850v3_lvds_dp_remove,
>> +	.driver		= {
>> +		.name		= "b850v3-lvds-dp",
>> +		.of_match_table = ge_b850v3_lvds_dp_match,
>> +	},
>> +};
>> +module_i2c_driver(ge_b850v3_lvds_dp_driver);
>> +
>> +MODULE_AUTHOR("Peter Senna Tschudin <peter.senna@collabora.com>");
>> +MODULE_AUTHOR("Martyn Welch <martyn.welch@collabora.co.uk>");
>> +MODULE_DESCRIPTION("GE LVDS to DP++ display bridge)");
>> +MODULE_LICENSE("GPL v2");
>> --
>> 2.5.5
>>
>
>
>
>
>
>

-- 
Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum,
a Linux Foundation Collaborative Project

^ permalink raw reply	[flat|nested] 64+ messages in thread

* Re: [PATCH V5 3/4] drm/bridge: Add driver for GE B850v3  LVDS/DP++ Bridge
  2016-09-26  8:31       ` Archit Taneja
@ 2016-09-26  8:58         ` Peter Senna Tschudin
  2016-09-26 10:28           ` Archit Taneja
  0 siblings, 1 reply; 64+ messages in thread
From: Peter Senna Tschudin @ 2016-09-26  8:58 UTC (permalink / raw)
  To: Archit Taneja
  Cc: galak, thierry.reding, kernel, Rob Herring, ykk, jslaby, tiwai,
	eballetbo, devicetree, shawnguo, linux, davem, p.zabel,
	peter.senna, daniel.vetter, enric.balletbo, Peter Senna Tschudin,
	javier, dri-devel, linux-kernel, pawel.moll, ijc+devicetree,
	martin.donnelly, linux, heiko, mark.rutland, akpm, airlied,
	Fabio Estevam, mchehab, linux-arm-kernel, robh+dt, martyn.welch,
	gregkh, treding, rmk+kernel

 Hi Archit,

On Monday, September 26, 2016 10:31 CEST, Archit Taneja <architt@codeaurora.org> wrote: 
 
> Hi Peter,
> 
> On 09/26/2016 01:57 PM, Peter Senna Tschudin wrote:
> > Patch 1/4 is already on linux-next, but what about this one? Ping?

> 
> I'd posted some queries a couple of times which you didn't answer to.
> Could you please respond to them before we try to get this merged?

Your queries were already answered by similar questions. The commit messages and cover letter also addresses the design decisions of the code. But basically the driver usefulness to other scenarios is severely limited by the firmware used by both chips. And when using the firmware that goes with this specific hardware, then yes, the two chips are always expected to work together. But the main point is that with the custom firmware each chip do not behave as independent bridges anymore.

On the other side, I was careful to use meaningful names for the registers, so a future implementation based on same chips can take the basics from this work, at least as a starting point.

Thanks,

Peter

^ permalink raw reply	[flat|nested] 64+ messages in thread

* Re: [PATCH V5 3/4] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge
  2016-09-26  8:58         ` Peter Senna Tschudin
@ 2016-09-26 10:28           ` Archit Taneja
  0 siblings, 0 replies; 64+ messages in thread
From: Archit Taneja @ 2016-09-26 10:28 UTC (permalink / raw)
  To: Peter Senna Tschudin
  Cc: galak, thierry.reding, kernel, Rob Herring, ykk, jslaby, tiwai,
	eballetbo, devicetree, shawnguo, linux, davem, p.zabel,
	peter.senna, daniel.vetter, enric.balletbo, Peter Senna Tschudin,
	javier, dri-devel, linux-kernel, pawel.moll, ijc+devicetree,
	martin.donnelly, linux, heiko, mark.rutland, akpm, airlied,
	Fabio Estevam, mchehab, linux-arm-kernel, robh+dt, martyn.welch,
	gregkh, treding, rmk+kernel

Hi,

On 09/26/2016 02:28 PM, Peter Senna Tschudin wrote:
>  Hi Archit,
>
> On Monday, September 26, 2016 10:31 CEST, Archit Taneja <architt@codeaurora.org> wrote:
>
>> Hi Peter,
>>
>> On 09/26/2016 01:57 PM, Peter Senna Tschudin wrote:
>>> Patch 1/4 is already on linux-next, but what about this one? Ping?
>
>>
>> I'd posted some queries a couple of times which you didn't answer to.
>> Could you please respond to them before we try to get this merged?
>
> Your queries were already answered by similar questions. The commit messages and cover letter also addresses the design decisions of the code. But basically the driver usefulness to other scenarios is severely limited by the firmware used by both chips. And when using the firmware that goes with this specific hardware, then yes, the two chips are always expected to work together. But the main point is that with the custom firmware each chip do not behave as independent bridges anymore.

Thanks for the reply.

It wasn't entirely clear from the commit message that a custom firmware
was exclusively used on this board to program these chips in order to
get these 2 working together.

I browsed the earlier versions of the patch and saw you explained
the same thing to someone else. Sorry about that, I missed reading
that before.

Could you please specify this explicitly in the commit message? Perhaps,
also mention that there is an external microcontroller with a custom
firmware that manages most of the video operations. For the sake of
completeness, could you also mention the part name of the controller
that's running this firmware?

Also, in the comments in the beginning of the driver:

"However the physical bridges are automatically configured by the input 
video signal, and the driver has no access to the video processing 
pipeline."

Is the automatic configuration done by the firmware, or is it a
feature of the chips itself?

>
> On the other side, I was careful to use meaningful names for the registers, so a future implementation based on same chips can take the basics from this work, at least as a starting point.

Thanks, that would be handy for later.

I had some comments on the code. I'll share those in another reply.

Archit

>
> Thanks,
>
> Peter
>

-- 
Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum,
a Linux Foundation Collaborative Project

^ permalink raw reply	[flat|nested] 64+ messages in thread

* Re: [PATCH V5 3/4] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge
  2016-08-09 16:41   ` [PATCH V5 3/4] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge Peter Senna Tschudin
  2016-08-16  4:15     ` Archit Taneja
  2016-09-26  8:27     ` Peter Senna Tschudin
@ 2016-09-26 10:29     ` Archit Taneja
  2016-09-26 11:54       ` Peter Senna Tschudin
  2 siblings, 1 reply; 64+ messages in thread
From: Archit Taneja @ 2016-09-26 10:29 UTC (permalink / raw)
  To: Peter Senna Tschudin, airlied, akpm, daniel.vetter, davem,
	devicetree, dri-devel, enric.balletbo, eballetbo, galak, gregkh,
	heiko, ijc+devicetree, javier, jslaby, kernel, linux-arm-kernel,
	linux, linux-kernel, linux, mark.rutland, martin.donnelly,
	martyn.welch, mchehab, pawel.moll, peter.senna, p.zabel,
	thierry.reding, rmk+kernel, robh+dt, shawnguo, tiwai, treding,
	ykk
  Cc: Rob Herring, Fabio Estevam

Hi,

Some comments.

On 08/09/2016 10:11 PM, Peter Senna Tschudin wrote:
> Add a driver that create a drm_bridge and a drm_connector for the LVDS
> to DP++ display bridge of the GE B850v3.
>
> There are two physical bridges on the video signal pipeline: a
> STDP4028(LVDS to DP) and a STDP2690(DP to DP++).  The hardware and
> firmware made it complicated for this binding to comprise two device
> tree nodes, as the design goal is to configure both bridges based on
> the LVDS signal, which leave the driver powerless to control the video
> processing pipeline. The two bridges behaves as a single bridge, and
> the driver is only needed for telling the host about EDID / HPD, and
> for giving the host powers to ack interrupts. The video signal pipeline
> is as follows:
>
>   Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output
>
> Cc: Martyn Welch <martyn.welch@collabora.co.uk>
> Cc: Martin Donnelly <martin.donnelly@ge.com>
> Cc: Daniel Vetter <daniel.vetter@ffwll.ch>
> Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com>
> Cc: Philipp Zabel <p.zabel@pengutronix.de>
> Cc: Rob Herring <robh@kernel.org>
> Cc: Fabio Estevam <fabio.estevam@nxp.com>
> CC: David Airlie <airlied@linux.ie>
> CC: Thierry Reding <treding@nvidia.com>
> CC: Thierry Reding <thierry.reding@gmail.com>
> Reviewed-by: Enric Balletbo <enric.balletbo@collabora.com>
> Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com>
> ---
> Changes from V4:
>  - Check the output of the first call to i2c_smbus_write_word_data() and return
>    it's error code for failing gracefully on i2c issues
>  - Renamed the i2c_driver.name from "ge,b850v3-lvds-dp" to "b850v3-lvds-dp" to
>    remove the comma from the driver name
>
> Changes from V3:
>  - 3/4 instead of 4/5
>  - Tested on next-20160804
>
> Changes from V2:
>  - Made it atomic to be applied on next-20160729 on top of Liu Ying changes
>    that made imx-ldb atomic
>
> Changes from V1:
>  - New commit message
>  - Removed 3 empty entry points
>  - Removed memory leak from ge_b850v3_lvds_dp_get_modes()
>  - Added a lock for mode setting
>  - Removed a few blank lines
>  - Changed the order at Makefile and Kconfig
>
>  MAINTAINERS                                |   8 +
>  drivers/gpu/drm/bridge/Kconfig             |  11 +
>  drivers/gpu/drm/bridge/Makefile            |   1 +
>  drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c | 405 +++++++++++++++++++++++++++++
>  4 files changed, 425 insertions(+)
>  create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index a306795..e8d106a 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -5142,6 +5142,14 @@ W:	https://linuxtv.org
>  S:	Maintained
>  F:	drivers/media/radio/radio-gemtek*
>
> +GENERAL ELECTRIC B850V3 LVDS/DP++ BRIDGE
> +M:	Peter Senna Tschudin <peter.senna@collabora.com>
> +M:	Martin Donnelly <martin.donnelly@ge.com>
> +M:	Martyn Welch <martyn.welch@collabora.co.uk>
> +S:	Maintained
> +F:	drivers/gpu/drm/bridge/ge_b850v3_dp2.c
> +F:	Documentation/devicetree/bindings/ge/b850v3_dp2_bridge.txt
> +
>  GENERIC GPIO I2C DRIVER
>  M:	Haavard Skinnemoen <hskinnemoen@gmail.com>
>  S:	Supported

Could you move the MAINTAINERS change to a different patch? It would 
make it easier to integrate separately.

> diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
> index b590e67..b4b70fb 100644
> --- a/drivers/gpu/drm/bridge/Kconfig
> +++ b/drivers/gpu/drm/bridge/Kconfig
> @@ -32,6 +32,17 @@ config DRM_DW_HDMI_AHB_AUDIO
>  	  Designware HDMI block.  This is used in conjunction with
>  	  the i.MX6 HDMI driver.
>
> +config DRM_GE_B850V3_LVDS_DP
> +	tristate "GE B850v3 LVDS to DP++ display bridge"
> +	depends on OF
> +	select DRM_KMS_HELPER
> +	select DRM_PANEL
> +	---help---
> +          This is a driver for the display bridge of
> +          GE B850v3 that convert dual channel LVDS
> +          to DP++. This is used with the i.MX6 imx-ldb
> +          driver.
> +
>  config DRM_NXP_PTN3460
>  	tristate "NXP PTN3460 DP/LVDS bridge"
>  	depends on OF
> diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
> index efdb07e..b9606f3 100644
> --- a/drivers/gpu/drm/bridge/Makefile
> +++ b/drivers/gpu/drm/bridge/Makefile
> @@ -3,6 +3,7 @@ ccflags-y := -Iinclude/drm
>  obj-$(CONFIG_DRM_ANALOGIX_ANX78XX) += analogix-anx78xx.o
>  obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o
>  obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o
> +obj-$(CONFIG_DRM_GE_B850V3_LVDS_DP) += ge_b850v3_lvds_dp.o
>  obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o
>  obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o
>  obj-$(CONFIG_DRM_SII902X) += sii902x.o
> diff --git a/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c
> new file mode 100644
> index 0000000..81e9279
> --- /dev/null
> +++ b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c
> @@ -0,0 +1,405 @@
> +/*
> + * Driver for GE B850v3 DP display bridge
> +
> + * Copyright (c) 2016, Collabora Ltd.
> + * Copyright (c) 2016, General Electric Company
> +
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms and conditions of the GNU General Public License,
> + * version 2, as published by the Free Software Foundation.
> +
> + * This program is distributed in the hope it will be useful, but WITHOUT
> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
> + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
> + * more details.
> +
> + * You should have received a copy of the GNU General Public License
> + * along with this program.  If not, see <http://www.gnu.org/licenses/>.
> +
> + * This driver creates a drm_bridge and a drm_connector for the LVDS to DP++
> + * display bridge of the GE B850v3. There are two physical bridges on the video
> + * signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). However
> + * the physical bridges are automatically configured by the input video signal,
> + * and the driver has no access to the video processing pipeline. The driver is
> + * only needed to read EDID from the STDP2690 and to handle HPD events from the
> + * STDP4028. The driver communicates with both bridges over i2c. The video
> + * signal pipeline is as follows:
> + *
> + *   Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output
> + *
> + */
> +
> +#include <linux/gpio.h>
> +#include <linux/i2c.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <drm/drm_atomic.h>
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_crtc_helper.h>
> +#include <drm/drm_edid.h>
> +#include <drm/drmP.h>
> +
> +/*
> + * 220Mhz is a limitation of the host, as the bridge is capable of up to
> + * 330Mhz. See section 9.2.1.2.4 of the i.MX 6Dual/6Quad Applications
> + * Processor Reference Manual for more information about the 220Mhz limit.
> + * The imx-ldb driver will warn about clocks over 170Mhz, but it seem to work
> + * fine.
> + */
> +#define MAX_PIXEL_CLOCK 220000
> +
> +#define EDID_EXT_BLOCK_CNT 0x7E
> +
> +#define STDP4028_IRQ_OUT_CONF_REG 0x02
> +#define STDP4028_DPTX_IRQ_EN_REG 0x3C
> +#define STDP4028_DPTX_IRQ_STS_REG 0x3D
> +#define STDP4028_DPTX_STS_REG 0x3E
> +
> +#define STDP4028_DPTX_DP_IRQ_EN 0x1000
> +
> +#define STDP4028_DPTX_HOTPLUG_IRQ_EN 0x0400
> +#define STDP4028_DPTX_LINK_CH_IRQ_EN 0x2000
> +#define STDP4028_DPTX_IRQ_CONFIG \
> +		(STDP4028_DPTX_LINK_CH_IRQ_EN | STDP4028_DPTX_HOTPLUG_IRQ_EN)
> +
> +#define STDP4028_DPTX_HOTPLUG_STS 0x0200
> +#define STDP4028_DPTX_LINK_STS 0x1000
> +#define STDP4028_CON_STATE_CONNECTED \
> +		(STDP4028_DPTX_HOTPLUG_STS | STDP4028_DPTX_LINK_STS)
> +
> +#define STDP4028_DPTX_HOTPLUG_CH_STS 0x0400
> +#define STDP4028_DPTX_LINK_CH_STS 0x2000
> +#define STDP4028_DPTX_IRQ_CLEAR \
> +		(STDP4028_DPTX_LINK_CH_STS | STDP4028_DPTX_HOTPLUG_CH_STS)
> +
> +struct ge_b850v3_lvds_dp {
> +	struct drm_connector connector;
> +	struct drm_bridge bridge;
> +	struct i2c_client *ge_b850v3_lvds_dp_i2c;
> +	struct i2c_client *edid_i2c;
> +	struct edid *edid;
> +	struct mutex lock;
> +};
> +
> +static inline struct ge_b850v3_lvds_dp *
> +		bridge_to_ge_b850v3_lvds_dp(struct drm_bridge *bridge)
> +{
> +	return container_of(bridge, struct ge_b850v3_lvds_dp, bridge);
> +}
> +
> +static inline struct ge_b850v3_lvds_dp *
> +		connector_to_ge_b850v3_lvds_dp(struct drm_connector *connector)
> +{
> +	return container_of(connector, struct ge_b850v3_lvds_dp, connector);
> +}
> +
> +u8 *stdp2690_get_edid(struct i2c_client *client)
> +{
> +	struct i2c_adapter *adapter = client->adapter;
> +	unsigned char start = 0x00;
> +	unsigned int total_size;
> +	u8 *block = kmalloc(EDID_LENGTH, GFP_KERNEL);
> +
> +	struct i2c_msg msgs[] = {
> +		{
> +			.addr	= client->addr,
> +			.flags	= 0,
> +			.len	= 1,
> +			.buf	= &start,
> +		}, {
> +			.addr	= client->addr,
> +			.flags	= I2C_M_RD,
> +			.len	= EDID_LENGTH,
> +			.buf	= block,
> +		}
> +	};
> +
> +	if (!block)
> +		return NULL;
> +
> +	if (i2c_transfer(adapter, msgs, 2) != 2) {
> +		DRM_ERROR("Unable to read EDID.\n");
> +		goto err;
> +	}
> +
> +	if (!drm_edid_block_valid(block, 0, false, NULL)) {
> +		DRM_ERROR("Invalid EDID block\n");
> +		goto err;
> +	}
> +
> +	total_size = (block[EDID_EXT_BLOCK_CNT] + 1) * EDID_LENGTH;
> +	if (total_size > EDID_LENGTH) {
> +		kfree(block);
> +		block = kmalloc(total_size, GFP_KERNEL);
> +		if (!block)
> +			return NULL;
> +
> +		/* Yes, read the entire buffer, and do not skip the first
> +		 * EDID_LENGTH bytes.
> +		 */
> +		start = 0x00;
> +		msgs[1].len = total_size;
> +		msgs[1].buf = block;
> +
> +		if (i2c_transfer(adapter, msgs, 2) != 2) {
> +			DRM_ERROR("Unable to read EDID extension blocks.\n");
> +			goto err;
> +		}
> +	}
> +
> +	return block;
> +
> +err:
> +	kfree(block);
> +	return NULL;
> +}
> +
> +static int ge_b850v3_lvds_dp_get_modes(struct drm_connector *connector)
> +{
> +	struct ge_b850v3_lvds_dp *ptn_bridge;
> +	struct i2c_client *client;
> +	int num_modes = 0;
> +
> +	ptn_bridge = connector_to_ge_b850v3_lvds_dp(connector);
> +	client = ptn_bridge->edid_i2c;
> +
> +	mutex_lock(&ptn_bridge->lock);
> +
> +	kfree(ptn_bridge->edid);
> +	ptn_bridge->edid = (struct edid *) stdp2690_get_edid(client);
> +
> +	if (ptn_bridge->edid) {
> +		drm_mode_connector_update_edid_property(connector,
> +				ptn_bridge->edid);
> +		num_modes = drm_add_edid_modes(connector, ptn_bridge->edid);
> +	}
> +
> +	mutex_unlock(&ptn_bridge->lock);
> +
> +	return num_modes;
> +}
> +
> +
> +static enum drm_mode_status ge_b850v3_lvds_dp_mode_valid(
> +		struct drm_connector *connector, struct drm_display_mode *mode)
> +{
> +	if (mode->clock > MAX_PIXEL_CLOCK) {
> +		DRM_INFO("The pixel clock for the mode %s is too high, and not supported.",
> +				mode->name);
> +		return MODE_CLOCK_HIGH;
> +	}
> +
> +	return MODE_OK;
> +}
> +
> +static const struct
> +drm_connector_helper_funcs ge_b850v3_lvds_dp_connector_helper_funcs = {
> +	.get_modes = ge_b850v3_lvds_dp_get_modes,
> +	.mode_valid = ge_b850v3_lvds_dp_mode_valid,
> +};
> +
> +static enum drm_connector_status ge_b850v3_lvds_dp_detect(
> +		struct drm_connector *connector, bool force)
> +{
> +	struct ge_b850v3_lvds_dp *ptn_bridge =
> +			connector_to_ge_b850v3_lvds_dp(connector);
> +	struct i2c_client *ge_b850v3_lvds_dp_i2c =
> +			ptn_bridge->ge_b850v3_lvds_dp_i2c;
> +	s32 link_state;
> +
> +	link_state = i2c_smbus_read_word_data(ge_b850v3_lvds_dp_i2c,
> +			STDP4028_DPTX_STS_REG);
> +
> +	if (link_state == STDP4028_CON_STATE_CONNECTED)
> +		return connector_status_connected;
> +
> +	if (link_state == 0)
> +		return connector_status_disconnected;
> +
> +	return connector_status_unknown;
> +}
> +
> +static const struct drm_connector_funcs ge_b850v3_lvds_dp_connector_funcs = {
> +	.dpms = drm_atomic_helper_connector_dpms,
> +	.fill_modes = drm_helper_probe_single_connector_modes,
> +	.detect = ge_b850v3_lvds_dp_detect,
> +	.destroy = drm_connector_cleanup,
> +	.reset = drm_atomic_helper_connector_reset,
> +	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
> +	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
> +};
> +
> +static irqreturn_t ge_b850v3_lvds_dp_irq_handler(int irq, void *dev_id)
> +{
> +	struct ge_b850v3_lvds_dp *ptn_bridge = dev_id;
> +	struct i2c_client *ge_b850v3_lvds_dp_i2c
> +			= ptn_bridge->ge_b850v3_lvds_dp_i2c;
> +
> +	mutex_lock(&ptn_bridge->lock);
> +
> +	i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c,
> +			STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR);
> +
> +	mutex_unlock(&ptn_bridge->lock);
> +
> +	if (ptn_bridge->connector.dev)
> +		drm_kms_helper_hotplug_event(ptn_bridge->connector.dev);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static int ge_b850v3_lvds_dp_attach(struct drm_bridge *bridge)
> +{
> +	struct ge_b850v3_lvds_dp *ptn_bridge
> +			= bridge_to_ge_b850v3_lvds_dp(bridge);
> +	struct drm_connector *connector = &ptn_bridge->connector;
> +	struct i2c_client *ge_b850v3_lvds_dp_i2c
> +			= ptn_bridge->ge_b850v3_lvds_dp_i2c;
> +	int ret;
> +
> +	if (!bridge->encoder) {
> +		DRM_ERROR("Parent encoder object not found");
> +		return -ENODEV;
> +	}
> +
> +	connector->polled = DRM_CONNECTOR_POLL_HPD;
> +
> +	drm_connector_helper_add(connector,
> +			&ge_b850v3_lvds_dp_connector_helper_funcs);
> +
> +	ret = drm_connector_init(bridge->dev, connector,
> +			&ge_b850v3_lvds_dp_connector_funcs,
> +			DRM_MODE_CONNECTOR_DisplayPort);
> +	if (ret) {
> +		DRM_ERROR("Failed to initialize connector with drm\n");
> +		return ret;
> +	}
> +
> +	drm_connector_register(connector);

Connectors shouldn't be registered in the bridge driver, they should
be registered by the kms driver after drm_dev_register is called.
The drm_connector_register_all() API is used for that.

> +	ret = drm_mode_connector_attach_encoder(connector, bridge->encoder);
> +	if (ret)
> +		return ret;
> +
> +	drm_bridge_enable(bridge);

This drm_bridge_enable() doesn't seem to serve any purpose here. It also
doesn't seem to make much sense to call drm_bridge_() funcs within a
bridge op itself.

> +	if (ge_b850v3_lvds_dp_i2c->irq) {
> +		drm_helper_hpd_irq_event(connector->dev);
> +
> +		ret = devm_request_threaded_irq(&ge_b850v3_lvds_dp_i2c->dev,
> +				ge_b850v3_lvds_dp_i2c->irq, NULL,
> +				ge_b850v3_lvds_dp_irq_handler,
> +				IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
> +				"ge-b850v3-lvds-dp", ptn_bridge);

Is there a reason why we register the interrupt handler here and not in
probe?

Thanks,
Archit

-- 
Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum,
a Linux Foundation Collaborative Project

^ permalink raw reply	[flat|nested] 64+ messages in thread

* Re: [PATCH V5 3/4] drm/bridge: Add driver for GE B850v3  LVDS/DP++ Bridge
  2016-09-26 10:29     ` Archit Taneja
@ 2016-09-26 11:54       ` Peter Senna Tschudin
  2016-09-26 12:54         ` Archit Taneja
  0 siblings, 1 reply; 64+ messages in thread
From: Peter Senna Tschudin @ 2016-09-26 11:54 UTC (permalink / raw)
  To: Archit Taneja
  Cc: kernel, enric.balletbo, mchehab, tiwai, linux-kernel, gregkh,
	p.zabel, akpm, Rob Herring, javier, devicetree, martin.donnelly,
	robh+dt, linux-arm-kernel, eballetbo, pawel.moll,
	Peter Senna Tschudin, treding, heiko, thierry.reding,
	daniel.vetter, linux, Fabio Estevam, jslaby, dri-devel,
	martyn.welch, shawnguo, linux, galak, peter.senna, airlied, ykk,
	ijc+devicetree, rmk+kernel, davem, mark.rutland

 
On Monday, September 26, 2016 12:29 CEST, Archit Taneja <architt@codeaurora.org> wrote: 
 
> Hi,
> 
> Some comments.

Thank you for the review!

> 
> On 08/09/2016 10:11 PM, Peter Senna Tschudin wrote:
> > Add a driver that create a drm_bridge and a drm_connector for the LVDS
> > to DP++ display bridge of the GE B850v3.
> >
> > There are two physical bridges on the video signal pipeline: a
> > STDP4028(LVDS to DP) and a STDP2690(DP to DP++).  The hardware and

> > firmware made it complicated for this binding to comprise two device
> > tree nodes, as the design goal is to configure both bridges based on
> > the LVDS signal, which leave the driver powerless to control the video
> > processing pipeline. The two bridges behaves as a single bridge, and
> > the driver is only needed for telling the host about EDID / HPD, and
> > for giving the host powers to ack interrupts. The video signal pipeline
> > is as follows:
> >
> >   Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output
> >
> > Cc: Martyn Welch <martyn.welch@collabora.co.uk>
> > Cc: Martin Donnelly <martin.donnelly@ge.com>
> > Cc: Daniel Vetter <daniel.vetter@ffwll.ch>
> > Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com>
> > Cc: Philipp Zabel <p.zabel@pengutronix.de>
> > Cc: Rob Herring <robh@kernel.org>
> > Cc: Fabio Estevam <fabio.estevam@nxp.com>
> > CC: David Airlie <airlied@linux.ie>
> > CC: Thierry Reding <treding@nvidia.com>
> > CC: Thierry Reding <thierry.reding@gmail.com>
> > Reviewed-by: Enric Balletbo <enric.balletbo@collabora.com>
> > Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com>
> > ---
> > Changes from V4:
> >  - Check the output of the first call to i2c_smbus_write_word_data() and return
> >    it's error code for failing gracefully on i2c issues
> >  - Renamed the i2c_driver.name from "ge,b850v3-lvds-dp" to "b850v3-lvds-dp" to
> >    remove the comma from the driver name
> >
> > Changes from V3:
> >  - 3/4 instead of 4/5
> >  - Tested on next-20160804
> >
> > Changes from V2:
> >  - Made it atomic to be applied on next-20160729 on top of Liu Ying changes
> >    that made imx-ldb atomic
> >
> > Changes from V1:
> >  - New commit message
> >  - Removed 3 empty entry points
> >  - Removed memory leak from ge_b850v3_lvds_dp_get_modes()
> >  - Added a lock for mode setting
> >  - Removed a few blank lines
> >  - Changed the order at Makefile and Kconfig
> >
> >  MAINTAINERS                                |   8 +
> >  drivers/gpu/drm/bridge/Kconfig             |  11 +
> >  drivers/gpu/drm/bridge/Makefile            |   1 +
> >  drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c | 405 +++++++++++++++++++++++++++++
> >  4 files changed, 425 insertions(+)
> >  create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c
> >
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index a306795..e8d106a 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -5142,6 +5142,14 @@ W:	https://linuxtv.org
> >  S:	Maintained
> >  F:	drivers/media/radio/radio-gemtek*
> >
> > +GENERAL ELECTRIC B850V3 LVDS/DP++ BRIDGE
> > +M:	Peter Senna Tschudin <peter.senna@collabora.com>
> > +M:	Martin Donnelly <martin.donnelly@ge.com>
> > +M:	Martyn Welch <martyn.welch@collabora.co.uk>
> > +S:	Maintained
> > +F:	drivers/gpu/drm/bridge/ge_b850v3_dp2.c
> > +F:	Documentation/devicetree/bindings/ge/b850v3_dp2_bridge.txt

> > +
> >  GENERIC GPIO I2C DRIVER
> >  M:	Haavard Skinnemoen <hskinnemoen@gmail.com>
> >  S:	Supported
> 
> Could you move the MAINTAINERS change to a different patch? It would 
> make it easier to integrate separately.

If needed, yes sure.

> 
> > diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
> > index b590e67..b4b70fb 100644
> > --- a/drivers/gpu/drm/bridge/Kconfig
> > +++ b/drivers/gpu/drm/bridge/Kconfig
> > @@ -32,6 +32,17 @@ config DRM_DW_HDMI_AHB_AUDIO
> >  	  Designware HDMI block.  This is used in conjunction with
> >  	  the i.MX6 HDMI driver.
> >
> > +config DRM_GE_B850V3_LVDS_DP
> > +	tristate "GE B850v3 LVDS to DP++ display bridge"
> > +	depends on OF
> > +	select DRM_KMS_HELPER
> > +	select DRM_PANEL
> > +	---help---
> > +          This is a driver for the display bridge of
> > +          GE B850v3 that convert dual channel LVDS
> > +          to DP++. This is used with the i.MX6 imx-ldb
> > +          driver.
> > +
> >  config DRM_NXP_PTN3460
> >  	tristate "NXP PTN3460 DP/LVDS bridge"
> >  	depends on OF
> > diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
> > index efdb07e..b9606f3 100644
> > --- a/drivers/gpu/drm/bridge/Makefile
> > +++ b/drivers/gpu/drm/bridge/Makefile
> > @@ -3,6 +3,7 @@ ccflags-y := -Iinclude/drm
> >  obj-$(CONFIG_DRM_ANALOGIX_ANX78XX) += analogix-anx78xx.o
> >  obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o
> >  obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o
> > +obj-$(CONFIG_DRM_GE_B850V3_LVDS_DP) += ge_b850v3_lvds_dp.o
> >  obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o
> >  obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o
> >  obj-$(CONFIG_DRM_SII902X) += sii902x.o
> > diff --git a/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c
> > new file mode 100644
> > index 0000000..81e9279
> > --- /dev/null
> > +++ b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c
> > @@ -0,0 +1,405 @@
> > +/*
> > + * Driver for GE B850v3 DP display bridge
> > +
> > + * Copyright (c) 2016, Collabora Ltd.
> > + * Copyright (c) 2016, General Electric Company
> > +
> > + * This program is free software; you can redistribute it and/or modify it
> > + * under the terms and conditions of the GNU General Public License,
> > + * version 2, as published by the Free Software Foundation.
> > +
> > + * This program is distributed in the hope it will be useful, but WITHOUT
> > + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
> > + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
> > + * more details.
> > +
> > + * You should have received a copy of the GNU General Public License
> > + * along with this program.  If not, see <http://www.gnu.org/licenses/>.
> > +
> > + * This driver creates a drm_bridge and a drm_connector for the LVDS to DP++
> > + * display bridge of the GE B850v3. There are two physical bridges on the video
> > + * signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). However
> > + * the physical bridges are automatically configured by the input video signal,
> > + * and the driver has no access to the video processing pipeline. The driver is
> > + * only needed to read EDID from the STDP2690 and to handle HPD events from the
> > + * STDP4028. The driver communicates with both bridges over i2c. The video
> > + * signal pipeline is as follows:
> > + *
> > + *   Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output
> > + *
> > + */
> > +
> > +#include <linux/gpio.h>
> > +#include <linux/i2c.h>
> > +#include <linux/module.h>
> > +#include <linux/of.h>
> > +#include <drm/drm_atomic.h>
> > +#include <drm/drm_atomic_helper.h>
> > +#include <drm/drm_crtc_helper.h>
> > +#include <drm/drm_edid.h>
> > +#include <drm/drmP.h>
> > +
> > +/*
> > + * 220Mhz is a limitation of the host, as the bridge is capable of up to
> > + * 330Mhz. See section 9.2.1.2.4 of the i.MX 6Dual/6Quad Applications
> > + * Processor Reference Manual for more information about the 220Mhz limit.
> > + * The imx-ldb driver will warn about clocks over 170Mhz, but it seem to work
> > + * fine.
> > + */
> > +#define MAX_PIXEL_CLOCK 220000
> > +
> > +#define EDID_EXT_BLOCK_CNT 0x7E
> > +
> > +#define STDP4028_IRQ_OUT_CONF_REG 0x02
> > +#define STDP4028_DPTX_IRQ_EN_REG 0x3C
> > +#define STDP4028_DPTX_IRQ_STS_REG 0x3D
> > +#define STDP4028_DPTX_STS_REG 0x3E
> > +
> > +#define STDP4028_DPTX_DP_IRQ_EN 0x1000
> > +
> > +#define STDP4028_DPTX_HOTPLUG_IRQ_EN 0x0400
> > +#define STDP4028_DPTX_LINK_CH_IRQ_EN 0x2000
> > +#define STDP4028_DPTX_IRQ_CONFIG \
> > +		(STDP4028_DPTX_LINK_CH_IRQ_EN | STDP4028_DPTX_HOTPLUG_IRQ_EN)
> > +
> > +#define STDP4028_DPTX_HOTPLUG_STS 0x0200
> > +#define STDP4028_DPTX_LINK_STS 0x1000
> > +#define STDP4028_CON_STATE_CONNECTED \
> > +		(STDP4028_DPTX_HOTPLUG_STS | STDP4028_DPTX_LINK_STS)
> > +
> > +#define STDP4028_DPTX_HOTPLUG_CH_STS 0x0400
> > +#define STDP4028_DPTX_LINK_CH_STS 0x2000
> > +#define STDP4028_DPTX_IRQ_CLEAR \
> > +		(STDP4028_DPTX_LINK_CH_STS | STDP4028_DPTX_HOTPLUG_CH_STS)
> > +
> > +struct ge_b850v3_lvds_dp {
> > +	struct drm_connector connector;
> > +	struct drm_bridge bridge;
> > +	struct i2c_client *ge_b850v3_lvds_dp_i2c;
> > +	struct i2c_client *edid_i2c;
> > +	struct edid *edid;
> > +	struct mutex lock;
> > +};
> > +
> > +static inline struct ge_b850v3_lvds_dp *
> > +		bridge_to_ge_b850v3_lvds_dp(struct drm_bridge *bridge)
> > +{
> > +	return container_of(bridge, struct ge_b850v3_lvds_dp, bridge);
> > +}
> > +
> > +static inline struct ge_b850v3_lvds_dp *
> > +		connector_to_ge_b850v3_lvds_dp(struct drm_connector *connector)
> > +{
> > +	return container_of(connector, struct ge_b850v3_lvds_dp, connector);
> > +}
> > +
> > +u8 *stdp2690_get_edid(struct i2c_client *client)
> > +{
> > +	struct i2c_adapter *adapter = client->adapter;
> > +	unsigned char start = 0x00;
> > +	unsigned int total_size;
> > +	u8 *block = kmalloc(EDID_LENGTH, GFP_KERNEL);
> > +
> > +	struct i2c_msg msgs[] = {
> > +		{
> > +			.addr	= client->addr,
> > +			.flags	= 0,
> > +			.len	= 1,
> > +			.buf	= &start,
> > +		}, {
> > +			.addr	= client->addr,
> > +			.flags	= I2C_M_RD,
> > +			.len	= EDID_LENGTH,
> > +			.buf	= block,
> > +		}
> > +	};
> > +
> > +	if (!block)
> > +		return NULL;
> > +
> > +	if (i2c_transfer(adapter, msgs, 2) != 2) {
> > +		DRM_ERROR("Unable to read EDID.\n");
> > +		goto err;
> > +	}
> > +
> > +	if (!drm_edid_block_valid(block, 0, false, NULL)) {
> > +		DRM_ERROR("Invalid EDID block\n");
> > +		goto err;
> > +	}
> > +
> > +	total_size = (block[EDID_EXT_BLOCK_CNT] + 1) * EDID_LENGTH;
> > +	if (total_size > EDID_LENGTH) {
> > +		kfree(block);
> > +		block = kmalloc(total_size, GFP_KERNEL);
> > +		if (!block)
> > +			return NULL;
> > +
> > +		/* Yes, read the entire buffer, and do not skip the first
> > +		 * EDID_LENGTH bytes.
> > +		 */
> > +		start = 0x00;
> > +		msgs[1].len = total_size;
> > +		msgs[1].buf = block;
> > +
> > +		if (i2c_transfer(adapter, msgs, 2) != 2) {
> > +			DRM_ERROR("Unable to read EDID extension blocks.\n");
> > +			goto err;
> > +		}
> > +	}
> > +
> > +	return block;
> > +
> > +err:
> > +	kfree(block);
> > +	return NULL;
> > +}
> > +
> > +static int ge_b850v3_lvds_dp_get_modes(struct drm_connector *connector)
> > +{
> > +	struct ge_b850v3_lvds_dp *ptn_bridge;
> > +	struct i2c_client *client;
> > +	int num_modes = 0;
> > +
> > +	ptn_bridge = connector_to_ge_b850v3_lvds_dp(connector);
> > +	client = ptn_bridge->edid_i2c;
> > +
> > +	mutex_lock(&ptn_bridge->lock);
> > +
> > +	kfree(ptn_bridge->edid);
> > +	ptn_bridge->edid = (struct edid *) stdp2690_get_edid(client);
> > +
> > +	if (ptn_bridge->edid) {
> > +		drm_mode_connector_update_edid_property(connector,
> > +				ptn_bridge->edid);
> > +		num_modes = drm_add_edid_modes(connector, ptn_bridge->edid);
> > +	}
> > +
> > +	mutex_unlock(&ptn_bridge->lock);
> > +
> > +	return num_modes;
> > +}
> > +
> > +
> > +static enum drm_mode_status ge_b850v3_lvds_dp_mode_valid(
> > +		struct drm_connector *connector, struct drm_display_mode *mode)
> > +{
> > +	if (mode->clock > MAX_PIXEL_CLOCK) {
> > +		DRM_INFO("The pixel clock for the mode %s is too high, and not supported.",
> > +				mode->name);
> > +		return MODE_CLOCK_HIGH;
> > +	}
> > +
> > +	return MODE_OK;
> > +}
> > +
> > +static const struct
> > +drm_connector_helper_funcs ge_b850v3_lvds_dp_connector_helper_funcs = {
> > +	.get_modes = ge_b850v3_lvds_dp_get_modes,
> > +	.mode_valid = ge_b850v3_lvds_dp_mode_valid,
> > +};
> > +
> > +static enum drm_connector_status ge_b850v3_lvds_dp_detect(
> > +		struct drm_connector *connector, bool force)
> > +{
> > +	struct ge_b850v3_lvds_dp *ptn_bridge =
> > +			connector_to_ge_b850v3_lvds_dp(connector);
> > +	struct i2c_client *ge_b850v3_lvds_dp_i2c =
> > +			ptn_bridge->ge_b850v3_lvds_dp_i2c;
> > +	s32 link_state;
> > +
> > +	link_state = i2c_smbus_read_word_data(ge_b850v3_lvds_dp_i2c,
> > +			STDP4028_DPTX_STS_REG);
> > +
> > +	if (link_state == STDP4028_CON_STATE_CONNECTED)
> > +		return connector_status_connected;
> > +
> > +	if (link_state == 0)
> > +		return connector_status_disconnected;
> > +
> > +	return connector_status_unknown;
> > +}
> > +
> > +static const struct drm_connector_funcs ge_b850v3_lvds_dp_connector_funcs = {
> > +	.dpms = drm_atomic_helper_connector_dpms,
> > +	.fill_modes = drm_helper_probe_single_connector_modes,
> > +	.detect = ge_b850v3_lvds_dp_detect,
> > +	.destroy = drm_connector_cleanup,
> > +	.reset = drm_atomic_helper_connector_reset,
> > +	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
> > +	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
> > +};
> > +
> > +static irqreturn_t ge_b850v3_lvds_dp_irq_handler(int irq, void *dev_id)
> > +{
> > +	struct ge_b850v3_lvds_dp *ptn_bridge = dev_id;
> > +	struct i2c_client *ge_b850v3_lvds_dp_i2c
> > +			= ptn_bridge->ge_b850v3_lvds_dp_i2c;
> > +
> > +	mutex_lock(&ptn_bridge->lock);
> > +
> > +	i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c,

> > +			STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR);
> > +
> > +	mutex_unlock(&ptn_bridge->lock);
> > +
> > +	if (ptn_bridge->connector.dev)
> > +		drm_kms_helper_hotplug_event(ptn_bridge->connector.dev);
> > +
> > +	return IRQ_HANDLED;
> > +}
> > +
> > +static int ge_b850v3_lvds_dp_attach(struct drm_bridge *bridge)
> > +{
> > +	struct ge_b850v3_lvds_dp *ptn_bridge
> > +			= bridge_to_ge_b850v3_lvds_dp(bridge);
> > +	struct drm_connector *connector = &ptn_bridge->connector;
> > +	struct i2c_client *ge_b850v3_lvds_dp_i2c
> > +			= ptn_bridge->ge_b850v3_lvds_dp_i2c;
> > +	int ret;
> > +
> > +	if (!bridge->encoder) {
> > +		DRM_ERROR("Parent encoder object not found");
> > +		return -ENODEV;
> > +	}
> > +
> > +	connector->polled = DRM_CONNECTOR_POLL_HPD;
> > +
> > +	drm_connector_helper_add(connector,
> > +			&ge_b850v3_lvds_dp_connector_helper_funcs);
> > +
> > +	ret = drm_connector_init(bridge->dev, connector,
> > +			&ge_b850v3_lvds_dp_connector_funcs,
> > +			DRM_MODE_CONNECTOR_DisplayPort);
> > +	if (ret) {
> > +		DRM_ERROR("Failed to initialize connector with drm\n");
> > +		return ret;
> > +	}
> > +
> > +	drm_connector_register(connector);
> 
> Connectors shouldn't be registered in the bridge driver, they should

> be registered by the kms driver after drm_dev_register is called.
> The drm_connector_register_all() API is used for that.

Hmm, I got this from:

drivers/gpu/drm/bridge/nxp-ptn3460.c:	drm_connector_register(&ptn_bridge->connector);
drivers/gpu/drm/bridge/parade-ps8622.c:	drm_connector_register(&ps8622->connector);
drivers/gpu/drm/bridge/analogix-anx78xx.c:	err = drm_connector_register(&anx78xx->connector);


> 
> > +	ret = drm_mode_connector_attach_encoder(connector, bridge->encoder);
> > +	if (ret)
> > +		return ret;
> > +
> > +	drm_bridge_enable(bridge);
> 
> This drm_bridge_enable() doesn't seem to serve any purpose here. It also
> doesn't seem to make much sense to call drm_bridge_() funcs within a
> bridge op itself.

Yes, removing this line has no effect. I'll send V6. Thank you!

> 
> > +	if (ge_b850v3_lvds_dp_i2c->irq) {
> > +		drm_helper_hpd_irq_event(connector->dev);
> > +
> > +		ret = devm_request_threaded_irq(&ge_b850v3_lvds_dp_i2c->dev,
> > +				ge_b850v3_lvds_dp_i2c->irq, NULL,
> > +				ge_b850v3_lvds_dp_irq_handler,
> > +				IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
> > +				"ge-b850v3-lvds-dp", ptn_bridge);
> 
> Is there a reason why we register the interrupt handler here and not in
> probe?

Yes, drm_bridge_funcs.attach() is called when drm decides it is time to initialize the bridge, which means the drm is already running. i2c_driver.probe() is likely to be called way before drm initializes, and if for some reason the bridge is not attached, there is no need for the interrupt handler. In this case the interrupt handler is mostly used for events related to display plugging/unplugging.

^ permalink raw reply	[flat|nested] 64+ messages in thread

* Re: [PATCH V5 3/4] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge
  2016-09-26 11:54       ` Peter Senna Tschudin
@ 2016-09-26 12:54         ` Archit Taneja
  0 siblings, 0 replies; 64+ messages in thread
From: Archit Taneja @ 2016-09-26 12:54 UTC (permalink / raw)
  To: Peter Senna Tschudin
  Cc: kernel, enric.balletbo, mchehab, tiwai, linux-kernel, gregkh,
	p.zabel, akpm, Rob Herring, javier, devicetree, martin.donnelly,
	robh+dt, linux-arm-kernel, eballetbo, pawel.moll,
	Peter Senna Tschudin, treding, heiko, thierry.reding,
	daniel.vetter, linux, Fabio Estevam, jslaby, dri-devel,
	martyn.welch, shawnguo, linux, galak, peter.senna, airlied, ykk,
	ijc+devicetree, rmk+kernel, davem, mark.rutland



On 09/26/2016 05:24 PM, Peter Senna Tschudin wrote:
>
> On Monday, September 26, 2016 12:29 CEST, Archit Taneja <architt@codeaurora.org> wrote:
>
>> Hi,
>>
>> Some comments.
>
> Thank you for the review!
>
>>
>> On 08/09/2016 10:11 PM, Peter Senna Tschudin wrote:
>>> Add a driver that create a drm_bridge and a drm_connector for the LVDS
>>> to DP++ display bridge of the GE B850v3.
>>>
>>> There are two physical bridges on the video signal pipeline: a
>>> STDP4028(LVDS to DP) and a STDP2690(DP to DP++).  The hardware and
>>> firmware made it complicated for this binding to comprise two device
>>> tree nodes, as the design goal is to configure both bridges based on
>>> the LVDS signal, which leave the driver powerless to control the video
>>> processing pipeline. The two bridges behaves as a single bridge, and
>>> the driver is only needed for telling the host about EDID / HPD, and
>>> for giving the host powers to ack interrupts. The video signal pipeline
>>> is as follows:
>>>
>>>   Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output
>>>
>>> Cc: Martyn Welch <martyn.welch@collabora.co.uk>
>>> Cc: Martin Donnelly <martin.donnelly@ge.com>
>>> Cc: Daniel Vetter <daniel.vetter@ffwll.ch>
>>> Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com>
>>> Cc: Philipp Zabel <p.zabel@pengutronix.de>
>>> Cc: Rob Herring <robh@kernel.org>
>>> Cc: Fabio Estevam <fabio.estevam@nxp.com>
>>> CC: David Airlie <airlied@linux.ie>
>>> CC: Thierry Reding <treding@nvidia.com>
>>> CC: Thierry Reding <thierry.reding@gmail.com>
>>> Reviewed-by: Enric Balletbo <enric.balletbo@collabora.com>
>>> Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com>
>>> ---
>>> Changes from V4:
>>>  - Check the output of the first call to i2c_smbus_write_word_data() and return
>>>    it's error code for failing gracefully on i2c issues
>>>  - Renamed the i2c_driver.name from "ge,b850v3-lvds-dp" to "b850v3-lvds-dp" to
>>>    remove the comma from the driver name
>>>
>>> Changes from V3:
>>>  - 3/4 instead of 4/5
>>>  - Tested on next-20160804
>>>
>>> Changes from V2:
>>>  - Made it atomic to be applied on next-20160729 on top of Liu Ying changes
>>>    that made imx-ldb atomic
>>>
>>> Changes from V1:
>>>  - New commit message
>>>  - Removed 3 empty entry points
>>>  - Removed memory leak from ge_b850v3_lvds_dp_get_modes()
>>>  - Added a lock for mode setting
>>>  - Removed a few blank lines
>>>  - Changed the order at Makefile and Kconfig
>>>
>>>  MAINTAINERS                                |   8 +
>>>  drivers/gpu/drm/bridge/Kconfig             |  11 +
>>>  drivers/gpu/drm/bridge/Makefile            |   1 +
>>>  drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c | 405 +++++++++++++++++++++++++++++
>>>  4 files changed, 425 insertions(+)
>>>  create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c
>>>
>>> diff --git a/MAINTAINERS b/MAINTAINERS
>>> index a306795..e8d106a 100644
>>> --- a/MAINTAINERS
>>> +++ b/MAINTAINERS
>>> @@ -5142,6 +5142,14 @@ W:	https://linuxtv.org
>>>  S:	Maintained
>>>  F:	drivers/media/radio/radio-gemtek*
>>>
>>> +GENERAL ELECTRIC B850V3 LVDS/DP++ BRIDGE
>>> +M:	Peter Senna Tschudin <peter.senna@collabora.com>
>>> +M:	Martin Donnelly <martin.donnelly@ge.com>
>>> +M:	Martyn Welch <martyn.welch@collabora.co.uk>
>>> +S:	Maintained
>>> +F:	drivers/gpu/drm/bridge/ge_b850v3_dp2.c
>>> +F:	Documentation/devicetree/bindings/ge/b850v3_dp2_bridge.txt
>>> +
>>>  GENERIC GPIO I2C DRIVER
>>>  M:	Haavard Skinnemoen <hskinnemoen@gmail.com>
>>>  S:	Supported
>>
>> Could you move the MAINTAINERS change to a different patch? It would
>> make it easier to integrate separately.
>
> If needed, yes sure.
>
>>
>>> diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
>>> index b590e67..b4b70fb 100644
>>> --- a/drivers/gpu/drm/bridge/Kconfig
>>> +++ b/drivers/gpu/drm/bridge/Kconfig
>>> @@ -32,6 +32,17 @@ config DRM_DW_HDMI_AHB_AUDIO
>>>  	  Designware HDMI block.  This is used in conjunction with
>>>  	  the i.MX6 HDMI driver.
>>>
>>> +config DRM_GE_B850V3_LVDS_DP
>>> +	tristate "GE B850v3 LVDS to DP++ display bridge"
>>> +	depends on OF
>>> +	select DRM_KMS_HELPER
>>> +	select DRM_PANEL
>>> +	---help---
>>> +          This is a driver for the display bridge of
>>> +          GE B850v3 that convert dual channel LVDS
>>> +          to DP++. This is used with the i.MX6 imx-ldb
>>> +          driver.
>>> +
>>>  config DRM_NXP_PTN3460
>>>  	tristate "NXP PTN3460 DP/LVDS bridge"
>>>  	depends on OF
>>> diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
>>> index efdb07e..b9606f3 100644
>>> --- a/drivers/gpu/drm/bridge/Makefile
>>> +++ b/drivers/gpu/drm/bridge/Makefile
>>> @@ -3,6 +3,7 @@ ccflags-y := -Iinclude/drm
>>>  obj-$(CONFIG_DRM_ANALOGIX_ANX78XX) += analogix-anx78xx.o
>>>  obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o
>>>  obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o
>>> +obj-$(CONFIG_DRM_GE_B850V3_LVDS_DP) += ge_b850v3_lvds_dp.o
>>>  obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o
>>>  obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o
>>>  obj-$(CONFIG_DRM_SII902X) += sii902x.o
>>> diff --git a/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c
>>> new file mode 100644
>>> index 0000000..81e9279
>>> --- /dev/null
>>> +++ b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c
>>> @@ -0,0 +1,405 @@
>>> +/*
>>> + * Driver for GE B850v3 DP display bridge
>>> +
>>> + * Copyright (c) 2016, Collabora Ltd.
>>> + * Copyright (c) 2016, General Electric Company
>>> +
>>> + * This program is free software; you can redistribute it and/or modify it
>>> + * under the terms and conditions of the GNU General Public License,
>>> + * version 2, as published by the Free Software Foundation.
>>> +
>>> + * This program is distributed in the hope it will be useful, but WITHOUT
>>> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
>>> + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
>>> + * more details.
>>> +
>>> + * You should have received a copy of the GNU General Public License
>>> + * along with this program.  If not, see <http://www.gnu.org/licenses/>.
>>> +
>>> + * This driver creates a drm_bridge and a drm_connector for the LVDS to DP++
>>> + * display bridge of the GE B850v3. There are two physical bridges on the video
>>> + * signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). However
>>> + * the physical bridges are automatically configured by the input video signal,
>>> + * and the driver has no access to the video processing pipeline. The driver is
>>> + * only needed to read EDID from the STDP2690 and to handle HPD events from the
>>> + * STDP4028. The driver communicates with both bridges over i2c. The video
>>> + * signal pipeline is as follows:
>>> + *
>>> + *   Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output
>>> + *
>>> + */
>>> +
>>> +#include <linux/gpio.h>
>>> +#include <linux/i2c.h>
>>> +#include <linux/module.h>
>>> +#include <linux/of.h>
>>> +#include <drm/drm_atomic.h>
>>> +#include <drm/drm_atomic_helper.h>
>>> +#include <drm/drm_crtc_helper.h>
>>> +#include <drm/drm_edid.h>
>>> +#include <drm/drmP.h>
>>> +
>>> +/*
>>> + * 220Mhz is a limitation of the host, as the bridge is capable of up to
>>> + * 330Mhz. See section 9.2.1.2.4 of the i.MX 6Dual/6Quad Applications
>>> + * Processor Reference Manual for more information about the 220Mhz limit.
>>> + * The imx-ldb driver will warn about clocks over 170Mhz, but it seem to work
>>> + * fine.
>>> + */
>>> +#define MAX_PIXEL_CLOCK 220000
>>> +
>>> +#define EDID_EXT_BLOCK_CNT 0x7E
>>> +
>>> +#define STDP4028_IRQ_OUT_CONF_REG 0x02
>>> +#define STDP4028_DPTX_IRQ_EN_REG 0x3C
>>> +#define STDP4028_DPTX_IRQ_STS_REG 0x3D
>>> +#define STDP4028_DPTX_STS_REG 0x3E
>>> +
>>> +#define STDP4028_DPTX_DP_IRQ_EN 0x1000
>>> +
>>> +#define STDP4028_DPTX_HOTPLUG_IRQ_EN 0x0400
>>> +#define STDP4028_DPTX_LINK_CH_IRQ_EN 0x2000
>>> +#define STDP4028_DPTX_IRQ_CONFIG \
>>> +		(STDP4028_DPTX_LINK_CH_IRQ_EN | STDP4028_DPTX_HOTPLUG_IRQ_EN)
>>> +
>>> +#define STDP4028_DPTX_HOTPLUG_STS 0x0200
>>> +#define STDP4028_DPTX_LINK_STS 0x1000
>>> +#define STDP4028_CON_STATE_CONNECTED \
>>> +		(STDP4028_DPTX_HOTPLUG_STS | STDP4028_DPTX_LINK_STS)
>>> +
>>> +#define STDP4028_DPTX_HOTPLUG_CH_STS 0x0400
>>> +#define STDP4028_DPTX_LINK_CH_STS 0x2000
>>> +#define STDP4028_DPTX_IRQ_CLEAR \
>>> +		(STDP4028_DPTX_LINK_CH_STS | STDP4028_DPTX_HOTPLUG_CH_STS)
>>> +
>>> +struct ge_b850v3_lvds_dp {
>>> +	struct drm_connector connector;
>>> +	struct drm_bridge bridge;
>>> +	struct i2c_client *ge_b850v3_lvds_dp_i2c;
>>> +	struct i2c_client *edid_i2c;
>>> +	struct edid *edid;
>>> +	struct mutex lock;
>>> +};
>>> +
>>> +static inline struct ge_b850v3_lvds_dp *
>>> +		bridge_to_ge_b850v3_lvds_dp(struct drm_bridge *bridge)
>>> +{
>>> +	return container_of(bridge, struct ge_b850v3_lvds_dp, bridge);
>>> +}
>>> +
>>> +static inline struct ge_b850v3_lvds_dp *
>>> +		connector_to_ge_b850v3_lvds_dp(struct drm_connector *connector)
>>> +{
>>> +	return container_of(connector, struct ge_b850v3_lvds_dp, connector);
>>> +}
>>> +
>>> +u8 *stdp2690_get_edid(struct i2c_client *client)
>>> +{
>>> +	struct i2c_adapter *adapter = client->adapter;
>>> +	unsigned char start = 0x00;
>>> +	unsigned int total_size;
>>> +	u8 *block = kmalloc(EDID_LENGTH, GFP_KERNEL);
>>> +
>>> +	struct i2c_msg msgs[] = {
>>> +		{
>>> +			.addr	= client->addr,
>>> +			.flags	= 0,
>>> +			.len	= 1,
>>> +			.buf	= &start,
>>> +		}, {
>>> +			.addr	= client->addr,
>>> +			.flags	= I2C_M_RD,
>>> +			.len	= EDID_LENGTH,
>>> +			.buf	= block,
>>> +		}
>>> +	};
>>> +
>>> +	if (!block)
>>> +		return NULL;
>>> +
>>> +	if (i2c_transfer(adapter, msgs, 2) != 2) {
>>> +		DRM_ERROR("Unable to read EDID.\n");
>>> +		goto err;
>>> +	}
>>> +
>>> +	if (!drm_edid_block_valid(block, 0, false, NULL)) {
>>> +		DRM_ERROR("Invalid EDID block\n");
>>> +		goto err;
>>> +	}
>>> +
>>> +	total_size = (block[EDID_EXT_BLOCK_CNT] + 1) * EDID_LENGTH;
>>> +	if (total_size > EDID_LENGTH) {
>>> +		kfree(block);
>>> +		block = kmalloc(total_size, GFP_KERNEL);
>>> +		if (!block)
>>> +			return NULL;
>>> +
>>> +		/* Yes, read the entire buffer, and do not skip the first
>>> +		 * EDID_LENGTH bytes.
>>> +		 */
>>> +		start = 0x00;
>>> +		msgs[1].len = total_size;
>>> +		msgs[1].buf = block;
>>> +
>>> +		if (i2c_transfer(adapter, msgs, 2) != 2) {
>>> +			DRM_ERROR("Unable to read EDID extension blocks.\n");
>>> +			goto err;
>>> +		}
>>> +	}
>>> +
>>> +	return block;
>>> +
>>> +err:
>>> +	kfree(block);
>>> +	return NULL;
>>> +}
>>> +
>>> +static int ge_b850v3_lvds_dp_get_modes(struct drm_connector *connector)
>>> +{
>>> +	struct ge_b850v3_lvds_dp *ptn_bridge;
>>> +	struct i2c_client *client;
>>> +	int num_modes = 0;
>>> +
>>> +	ptn_bridge = connector_to_ge_b850v3_lvds_dp(connector);
>>> +	client = ptn_bridge->edid_i2c;
>>> +
>>> +	mutex_lock(&ptn_bridge->lock);
>>> +
>>> +	kfree(ptn_bridge->edid);
>>> +	ptn_bridge->edid = (struct edid *) stdp2690_get_edid(client);
>>> +
>>> +	if (ptn_bridge->edid) {
>>> +		drm_mode_connector_update_edid_property(connector,
>>> +				ptn_bridge->edid);
>>> +		num_modes = drm_add_edid_modes(connector, ptn_bridge->edid);
>>> +	}
>>> +
>>> +	mutex_unlock(&ptn_bridge->lock);
>>> +
>>> +	return num_modes;
>>> +}
>>> +
>>> +
>>> +static enum drm_mode_status ge_b850v3_lvds_dp_mode_valid(
>>> +		struct drm_connector *connector, struct drm_display_mode *mode)
>>> +{
>>> +	if (mode->clock > MAX_PIXEL_CLOCK) {
>>> +		DRM_INFO("The pixel clock for the mode %s is too high, and not supported.",
>>> +				mode->name);
>>> +		return MODE_CLOCK_HIGH;
>>> +	}
>>> +
>>> +	return MODE_OK;
>>> +}
>>> +
>>> +static const struct
>>> +drm_connector_helper_funcs ge_b850v3_lvds_dp_connector_helper_funcs = {
>>> +	.get_modes = ge_b850v3_lvds_dp_get_modes,
>>> +	.mode_valid = ge_b850v3_lvds_dp_mode_valid,
>>> +};
>>> +
>>> +static enum drm_connector_status ge_b850v3_lvds_dp_detect(
>>> +		struct drm_connector *connector, bool force)
>>> +{
>>> +	struct ge_b850v3_lvds_dp *ptn_bridge =
>>> +			connector_to_ge_b850v3_lvds_dp(connector);
>>> +	struct i2c_client *ge_b850v3_lvds_dp_i2c =
>>> +			ptn_bridge->ge_b850v3_lvds_dp_i2c;
>>> +	s32 link_state;
>>> +
>>> +	link_state = i2c_smbus_read_word_data(ge_b850v3_lvds_dp_i2c,
>>> +			STDP4028_DPTX_STS_REG);
>>> +
>>> +	if (link_state == STDP4028_CON_STATE_CONNECTED)
>>> +		return connector_status_connected;
>>> +
>>> +	if (link_state == 0)
>>> +		return connector_status_disconnected;
>>> +
>>> +	return connector_status_unknown;
>>> +}
>>> +
>>> +static const struct drm_connector_funcs ge_b850v3_lvds_dp_connector_funcs = {
>>> +	.dpms = drm_atomic_helper_connector_dpms,
>>> +	.fill_modes = drm_helper_probe_single_connector_modes,
>>> +	.detect = ge_b850v3_lvds_dp_detect,
>>> +	.destroy = drm_connector_cleanup,
>>> +	.reset = drm_atomic_helper_connector_reset,
>>> +	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
>>> +	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
>>> +};
>>> +
>>> +static irqreturn_t ge_b850v3_lvds_dp_irq_handler(int irq, void *dev_id)
>>> +{
>>> +	struct ge_b850v3_lvds_dp *ptn_bridge = dev_id;
>>> +	struct i2c_client *ge_b850v3_lvds_dp_i2c
>>> +			= ptn_bridge->ge_b850v3_lvds_dp_i2c;
>>> +
>>> +	mutex_lock(&ptn_bridge->lock);
>>> +
>>> +	i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c,
>>> +			STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR);
>>> +
>>> +	mutex_unlock(&ptn_bridge->lock);
>>> +
>>> +	if (ptn_bridge->connector.dev)
>>> +		drm_kms_helper_hotplug_event(ptn_bridge->connector.dev);
>>> +
>>> +	return IRQ_HANDLED;
>>> +}
>>> +
>>> +static int ge_b850v3_lvds_dp_attach(struct drm_bridge *bridge)
>>> +{
>>> +	struct ge_b850v3_lvds_dp *ptn_bridge
>>> +			= bridge_to_ge_b850v3_lvds_dp(bridge);
>>> +	struct drm_connector *connector = &ptn_bridge->connector;
>>> +	struct i2c_client *ge_b850v3_lvds_dp_i2c
>>> +			= ptn_bridge->ge_b850v3_lvds_dp_i2c;
>>> +	int ret;
>>> +
>>> +	if (!bridge->encoder) {
>>> +		DRM_ERROR("Parent encoder object not found");
>>> +		return -ENODEV;
>>> +	}
>>> +
>>> +	connector->polled = DRM_CONNECTOR_POLL_HPD;
>>> +
>>> +	drm_connector_helper_add(connector,
>>> +			&ge_b850v3_lvds_dp_connector_helper_funcs);
>>> +
>>> +	ret = drm_connector_init(bridge->dev, connector,
>>> +			&ge_b850v3_lvds_dp_connector_funcs,
>>> +			DRM_MODE_CONNECTOR_DisplayPort);
>>> +	if (ret) {
>>> +		DRM_ERROR("Failed to initialize connector with drm\n");
>>> +		return ret;
>>> +	}
>>> +
>>> +	drm_connector_register(connector);
>>
>> Connectors shouldn't be registered in the bridge driver, they should
>> be registered by the kms driver after drm_dev_register is called.
>> The drm_connector_register_all() API is used for that.
>
> Hmm, I got this from:
>
> drivers/gpu/drm/bridge/nxp-ptn3460.c:	drm_connector_register(&ptn_bridge->connector);
> drivers/gpu/drm/bridge/parade-ps8622.c:	drm_connector_register(&ps8622->connector);
> drivers/gpu/drm/bridge/analogix-anx78xx.c:	err = drm_connector_register(&anx78xx->connector);

Yeah, we need to get rid of these too, eventually.

The connectors should be registered only after the
drm device has been registered. Relying on the drm
core to do that ensures that. See drm_modeset_register_all()
for more info.

Also, btw, the imx driver shouldn't use the load() drm_driver ops
anymore. You would want to change that too. See the comments in
drm_dev_register() in drm_drv.c for more details.

>
>
>>
>>> +	ret = drm_mode_connector_attach_encoder(connector, bridge->encoder);
>>> +	if (ret)
>>> +		return ret;
>>> +
>>> +	drm_bridge_enable(bridge);
>>
>> This drm_bridge_enable() doesn't seem to serve any purpose here. It also
>> doesn't seem to make much sense to call drm_bridge_() funcs within a
>> bridge op itself.
>
> Yes, removing this line has no effect. I'll send V6. Thank you!
>
>>
>>> +	if (ge_b850v3_lvds_dp_i2c->irq) {
>>> +		drm_helper_hpd_irq_event(connector->dev);
>>> +
>>> +		ret = devm_request_threaded_irq(&ge_b850v3_lvds_dp_i2c->dev,
>>> +				ge_b850v3_lvds_dp_i2c->irq, NULL,
>>> +				ge_b850v3_lvds_dp_irq_handler,
>>> +				IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
>>> +				"ge-b850v3-lvds-dp", ptn_bridge);
>>
>> Is there a reason why we register the interrupt handler here and not in
>> probe?
>
> Yes, drm_bridge_funcs.attach() is called when drm decides it is time to initialize the bridge, which means the drm is already running. i2c_driver.probe() is likely to be called way before drm initializes, and if for some reason the bridge is not attached, there is no need for the interrupt handler. In this case the interrupt handler is mostly used for events related to display plugging/unplugging.

The i2c device can probe before drm, this should be fixed by making sure
the device's interrupts are masked before we request the handler, and
unmasking it when we know it is ready for use.

If a bridge device exists and isn't attached, then we should question
why it probed in the first place if it wasn't to be used.

Besides, in the current scenario, consider the case where the imx
driver's call to drm_bridge_attach() succeeds, but then later fails at
some point. There would be no one to remove this interrupt handler. The
devm_ api attaches the lifetime of the handler with the i2c device, not
the drm device.

Also, the call to drm_helper_hpd_irq_event(drm_dev) should go too. These
should be called only when the drm device is registered.

Thanks,
Archit

-- 
Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum,
a Linux Foundation Collaborative Project

^ permalink raw reply	[flat|nested] 64+ messages in thread

* Re: [PATCH V5 4/4] dts/imx6q-b850v3: Use GE B850v3 LVDS/DP++ Bridge
  2016-09-26  8:27     ` Peter Senna Tschudin
@ 2016-09-29 10:39       ` Shawn Guo
  0 siblings, 0 replies; 64+ messages in thread
From: Shawn Guo @ 2016-09-29 10:39 UTC (permalink / raw)
  To: Peter Senna Tschudin
  Cc: Peter Senna Tschudin, linux-arm-kernel, eballetbo, pawel.moll,
	treding, linux, heiko, thierry.reding, daniel.vetter,
	Fabio Estevam, jslaby, dri-devel, martyn.welch, linux, galak,
	peter.senna, airlied, ykk, ijc+devicetree, rmk+kernel, davem,
	mark.rutland, kernel, enric.balletbo, mchehab, tiwai,
	linux-kernel, gregkh, p.zabel, akpm, Rob Herring, javier,
	robh+dt, devicetree, martin.donnelly

On Mon, Sep 26, 2016 at 09:27:59AM +0100, Peter Senna Tschudin wrote:
> Patch 1/4 is already on linux-next, but what about this one? Ping?

Ping me after driver part (patch #3) lands on mainline.

Shawn

^ permalink raw reply	[flat|nested] 64+ messages in thread

end of thread, other threads:[~2016-09-29 10:39 UTC | newest]

Thread overview: 64+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2016-05-30 16:39 [PATCH 0/5] Add driver for GE B850v3 LVDS/DP++ Bridge Peter Senna Tschudin
2016-05-30 16:39 ` [PATCH 1/5] drm/imx-ldb: Add support to drm-bridge Peter Senna Tschudin
2016-06-02 13:09   ` Philipp Zabel
2016-05-30 16:39 ` [PATCH 2/5] arm/dts/imx6q-b850v3: Configure IPU assignment order Peter Senna Tschudin
2016-05-30 16:49   ` Fabio Estevam
2016-06-02 12:55   ` Philipp Zabel
2016-05-30 16:39 ` [PATCH 3/5] Documentation/devicetree/bindings: Add b850v3_lvds_dp Peter Senna Tschudin
2016-06-02 12:49   ` Philipp Zabel
2016-06-02 23:19     ` Peter Senna Tschudin
2016-06-02 22:57   ` Rob Herring
2016-05-30 16:39 ` [PATCH 4/5] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge Peter Senna Tschudin
2016-05-31  7:48   ` Enric Balletbo Serra
2016-05-30 16:39 ` [PATCH 5/5] arm/dts/imx6q-b850v3: Use " Peter Senna Tschudin
2016-05-30 16:54   ` Fabio Estevam
2016-06-09 16:25 ` [PATCH V2 0/5] Add driver for " Peter Senna Tschudin
2016-06-09 16:25   ` [PATCH V2 1/5] drm/imx-ldb: Add support to drm-bridge Peter Senna Tschudin
2016-06-09 16:25   ` [PATCH V2 2/5] dts/imx6q-b850v3: Configure IPU assignment order Peter Senna Tschudin
2016-06-09 16:25   ` [PATCH V2 3/5] Documentation/devicetree/bindings: b850v3_lvds_dp Peter Senna Tschudin
2016-06-10 17:42     ` Rob Herring
2016-06-10 18:54     ` Javier Martinez Canillas
2016-06-09 16:25   ` [PATCH V2 4/5] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge Peter Senna Tschudin
2016-06-10  7:39     ` Enric Balletbo Serra
2016-06-10  9:44       ` Peter Senna Tschudin
2016-06-10 14:13     ` Daniel Vetter
2016-06-22  8:34     ` Archit Taneja
2016-06-09 16:25   ` [PATCH V2 5/5] dts/imx6q-b850v3: Use " Peter Senna Tschudin
2016-07-31 19:55 ` [PATCH V3 0/5] Add driver for " Peter Senna Tschudin
2016-07-31 19:55   ` [PATCH V3 1/5] drm/imx-ldb: Add support to drm-bridge Peter Senna Tschudin
2016-08-01 10:21     ` Philipp Zabel
2016-08-02 18:46       ` Peter Senna Tschudin
2016-07-31 19:55   ` [PATCH V3 2/5] dts/imx6q-b850v3: Configure IPU assignment order Peter Senna Tschudin
2016-08-01  8:54     ` Lucas Stach
2016-08-01 12:30       ` Peter Senna Tschudin
2016-08-02 13:13         ` Daniel Vetter
2016-07-31 19:55   ` [PATCH V3 3/5] Documentation/devicetree/bindings: b850v3_lvds_dp Peter Senna Tschudin
2016-08-01 16:59     ` Rob Herring
2016-07-31 19:55   ` [PATCH V3 4/5] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge Peter Senna Tschudin
2016-07-31 19:55   ` [PATCH V3 5/5] dts/imx6q-b850v3: Use " Peter Senna Tschudin
2016-08-04 22:36 ` [PATCH V4 0/4] Add driver for " Peter Senna Tschudin
2016-08-04 22:36   ` [PATCH V4 1/4] drm/imx-ldb: Add support to drm-bridge Peter Senna Tschudin
2016-08-16 15:40     ` Martyn Welch
2016-08-04 22:36   ` [PATCH V4 2/4] Documentation/devicetree/bindings: b850v3_lvds_dp Peter Senna Tschudin
2016-08-05  7:28     ` Enric Balletbo Serra
2016-08-16 15:59     ` Martyn Welch
2016-08-04 22:37   ` [PATCH V4 3/4] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge Peter Senna Tschudin
2016-08-05  7:38     ` Enric Balletbo Serra
2016-08-04 22:37   ` [PATCH V4 4/4] dts/imx6q-b850v3: Use " Peter Senna Tschudin
2016-08-09 16:41 ` [PATCH V5 0/4] Add driver for " Peter Senna Tschudin
2016-08-09 16:41   ` [PATCH V5 1/4] drm/imx-ldb: Add support to drm-bridge Peter Senna Tschudin
2016-08-11  9:38     ` Philipp Zabel
2016-08-09 16:41   ` [PATCH V5 2/4] Documentation/devicetree/bindings: b850v3_lvds_dp Peter Senna Tschudin
2016-09-26  8:26     ` Peter Senna Tschudin
2016-08-09 16:41   ` [PATCH V5 3/4] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge Peter Senna Tschudin
2016-08-16  4:15     ` Archit Taneja
2016-09-26  8:27     ` Peter Senna Tschudin
2016-09-26  8:31       ` Archit Taneja
2016-09-26  8:58         ` Peter Senna Tschudin
2016-09-26 10:28           ` Archit Taneja
2016-09-26 10:29     ` Archit Taneja
2016-09-26 11:54       ` Peter Senna Tschudin
2016-09-26 12:54         ` Archit Taneja
2016-08-09 16:41   ` [PATCH V5 4/4] dts/imx6q-b850v3: Use " Peter Senna Tschudin
2016-09-26  8:27     ` Peter Senna Tschudin
2016-09-29 10:39       ` Shawn Guo

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).