linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [RFC PATCH 0/4] drm: bridge: anx7688 and mux drivers
@ 2016-06-27  7:48 Nicolas Boichat
  2016-06-27  7:48 ` [RFC PATCHv2 1/4] FROMLIST: drm: bridge: anx7688: Add anx7688 bridge driver support Nicolas Boichat
                   ` (4 more replies)
  0 siblings, 5 replies; 9+ messages in thread
From: Nicolas Boichat @ 2016-06-27  7:48 UTC (permalink / raw)
  To: dri-devel
  Cc: Archit Taneja, Thierry Reding, Nicolas Boichat, Russell King,
	linux-kernel, p.zabel, marcheu

Hi all,

This is a follow up to the 2 patches to add support for ANX7688 sent here:
https://patchwork.kernel.org/patch/9187809/, thanks Archit and Philipp for
the comments.

I also added 2 patches to add support for a simple display MUX, as I'm facing
similar issues while trying to implement it, i.e. the current DRM core does not
seem to support this kind of simple pass-thru bridge very well: it is not very
clear where connectors should be defined and attached. In this case, not
defining any connectors in the 2 bridges (and reusing the connector in MTK
HDMI driver) seem to work best, but makes little logical sense as the physical
connectors are actually attached to the bridges.

In any case, the board has the following layout:
 - MT8173 HDMI bridge
   - HDMI mux with 2 ports
     1. ANX7688 for HDMI->DP over USB-C conversion
     2. Native HDMI

The mux is controlled by hardware, looking at the HPD signals from both ANX7688
and native HDMI, with a priority on the native HDMI output.

The whole setup works fairly well without any Linux kernel drivers, except the
2 following cases:
 1. When ANX7688 is active, DP bandwidth may be limited, so we need to filter
    resolutions that would exceed the available bandwidth.
 2. When both outputs HPD signals are active, the kernel does not receive an
    HPD pulse when the HDMI input is unplugged.

ANX7688 driver fixes issue 1. The mux driver fixes 2 by forcing the kernel to
re-read the EDID on mux output change, and also issue 1 by filtering only when
ANX7688 is active.

I understand this patch series might not be acceptable as-is, but I hope this
sort of setup can be taken into account when better support for connector
drivers is introduced.

Thanks!

Best,

Nicolas

Nicolas Boichat (4):
  drm: bridge: anx7688: Add anx7688 bridge driver support.
  devicetree: Add ANX7688 transmitter binding
  drm: bridge: Generic GPIO mux driver
  devicetree: Add GPIO display mux binding

 .../devicetree/bindings/drm/bridge/anx7688.txt     |  32 ++
 .../devicetree/bindings/drm/bridge/gpio-mux.txt    |  59 ++++
 drivers/gpu/drm/bridge/Kconfig                     |  20 ++
 drivers/gpu/drm/bridge/Makefile                    |   2 +
 drivers/gpu/drm/bridge/analogix-anx7688.c          | 233 ++++++++++++++
 drivers/gpu/drm/bridge/generic-gpio-mux.c          | 347 +++++++++++++++++++++
 6 files changed, 693 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/drm/bridge/anx7688.txt
 create mode 100644 Documentation/devicetree/bindings/drm/bridge/gpio-mux.txt
 create mode 100644 drivers/gpu/drm/bridge/analogix-anx7688.c
 create mode 100644 drivers/gpu/drm/bridge/generic-gpio-mux.c

-- 
2.8.0.rc3.226.g39d4020

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

* [RFC PATCHv2 1/4] FROMLIST: drm: bridge: anx7688: Add anx7688 bridge driver support.
  2016-06-27  7:48 [RFC PATCH 0/4] drm: bridge: anx7688 and mux drivers Nicolas Boichat
@ 2016-06-27  7:48 ` Nicolas Boichat
  2016-06-27  7:48 ` [RFC PATCH 2/4] devicetree: Add ANX7688 transmitter binding Nicolas Boichat
                   ` (3 subsequent siblings)
  4 siblings, 0 replies; 9+ messages in thread
From: Nicolas Boichat @ 2016-06-27  7:48 UTC (permalink / raw)
  To: dri-devel
  Cc: Archit Taneja, Thierry Reding, Nicolas Boichat, Russell King,
	linux-kernel, p.zabel, marcheu

ANX7688 is a HDMI to DP converter (as well as USB-C port controller),
that has an internal microcontroller.

The only reason a Linux kernel driver is necessary is to reject
resolutions that require more bandwidth than what is available on
the DP side. DP bandwidth and lane count are reported by the bridge
via 2 registers on I2C.

Change-Id: Icd19a6784317563be671f1c9c007df90e9c5e3c5
---

v2: Fix Archit's review comments, and a few other minor changes.

 drivers/gpu/drm/bridge/Kconfig            |   9 ++
 drivers/gpu/drm/bridge/Makefile           |   1 +
 drivers/gpu/drm/bridge/analogix-anx7688.c | 233 ++++++++++++++++++++++++++++++
 3 files changed, 243 insertions(+)
 create mode 100644 drivers/gpu/drm/bridge/analogix-anx7688.c

diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
index a1419214..da489f0 100644
--- a/drivers/gpu/drm/bridge/Kconfig
+++ b/drivers/gpu/drm/bridge/Kconfig
@@ -7,6 +7,15 @@ config DRM_BRIDGE
 menu "Display Interface Bridges"
 	depends on DRM && DRM_BRIDGE
 
+config DRM_ANALOGIX_ANX7688
+	tristate "Analogix ANX7688 bridge"
+	depends on DRM
+	select DRM_KMS_HELPER
+	---help---
+	  ANX7688 is a transmitter to support DisplayPort over USB-C for
+	  smartphone and tablets.
+	  This driver only supports the HDMI to DP component of the chip.
+
 config DRM_ANALOGIX_ANX78XX
 	tristate "Analogix ANX78XX bridge"
 	select DRM_KMS_HELPER
diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
index bfec9f8..4846465 100644
--- a/drivers/gpu/drm/bridge/Makefile
+++ b/drivers/gpu/drm/bridge/Makefile
@@ -1,5 +1,6 @@
 ccflags-y := -Iinclude/drm
 
+obj-$(CONFIG_DRM_ANALOGIX_ANX7688) += analogix-anx7688.o
 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
diff --git a/drivers/gpu/drm/bridge/analogix-anx7688.c b/drivers/gpu/drm/bridge/analogix-anx7688.c
new file mode 100644
index 0000000..cc995c8
--- /dev/null
+++ b/drivers/gpu/drm/bridge/analogix-anx7688.c
@@ -0,0 +1,233 @@
+/*
+ * ANX7688 HDMI->DP bridge driver
+ *
+ * Copyright (C) 2016 Google, Inc.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that 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.
+ */
+
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <drm/drm_crtc.h>
+
+/* Register addresses */
+#define VENDOR_ID_REG 0x00
+#define DEVICE_ID_REG 0x02
+
+#define FW_VERSION_REG 0x80
+
+#define DP_BANDWIDTH_REG 0x85
+#define DP_LANE_COUNT_REG 0x86
+
+#define VENDOR_ID 0x1f29
+#define DEVICE_ID 0x7688
+
+/* First supported firmware version (0.85) */
+#define MINIMUM_FW_VERSION 0x0085
+
+struct anx7688 {
+	struct drm_bridge bridge;
+	struct i2c_client *client;
+
+	bool filter;
+};
+
+static int anx7688_read(struct i2c_client *client, u8 reg, u8 *data,
+			u16 data_len)
+{
+	int ret;
+	struct i2c_msg msgs[] = {
+		{
+		 .addr = client->addr,
+		 .flags = 0,
+		 .len = 1,
+		 .buf = &reg,
+		 },
+		{
+		 .addr = client->addr,
+		 .flags = I2C_M_RD,
+		 .len = data_len,
+		 .buf = data,
+		 }
+	};
+
+	ret = i2c_transfer(client->adapter, msgs, 2);
+
+	if (ret == 2)
+		return 0;
+	if (ret < 0)
+		return ret;
+	else
+		return -EIO;
+}
+
+static inline struct anx7688 *bridge_to_anx7688(struct drm_bridge *bridge)
+{
+	return container_of(bridge, struct anx7688, bridge);
+}
+
+static bool anx7688_bridge_mode_fixup(struct drm_bridge *bridge,
+				      const struct drm_display_mode *mode,
+				      struct drm_display_mode *adjusted_mode)
+{
+	struct anx7688 *anx7688 = bridge_to_anx7688(bridge);
+	u8 regs[2];
+	u8 dpbw, lanecount;
+	int totalbw, requiredbw;
+	int ret;
+
+	if (!anx7688->filter)
+		return true;
+
+	/* Read both regs 0x85 (bandwidth) and 0x86 (lane count). */
+	ret = anx7688_read(anx7688->client, DP_BANDWIDTH_REG, regs, 2);
+	if (ret < 0) {
+		dev_err(&anx7688->client->dev,
+			"Failed to read bandwidth/lane count\n");
+		return false;
+	}
+	dpbw = regs[0];
+	lanecount = regs[1];
+
+	/* Maximum 0x19 bandwidth (6.75 Gbps Turbo mode), 2 lanes */
+	if (dpbw > 0x19 || lanecount > 2) {
+		dev_err(&anx7688->client->dev,
+			"Invalid bandwidth/lane count (%02x/%d)\n",
+			dpbw, lanecount);
+		return false;
+	}
+
+	/* Compute available bandwidth (kHz) */
+	totalbw = dpbw * lanecount * 270000 * 8 / 10;
+
+	/* Required bandwidth (8 bpc, kHz) */
+	requiredbw = mode->clock * 8 * 3;
+
+	dev_dbg(&anx7688->client->dev,
+		"DP bandwidth: %d kHz (%02x/%d); mode requires %d Khz\n",
+		totalbw, dpbw, lanecount, requiredbw);
+
+	if (totalbw == 0) {
+		dev_warn(&anx7688->client->dev,
+			 "Bandwidth/lane count are 0, not rejecting modes\n");
+		return true;
+	}
+
+	return totalbw >= requiredbw;
+}
+
+static const struct drm_bridge_funcs anx7688_bridge_funcs = {
+	.mode_fixup	= anx7688_bridge_mode_fixup,
+};
+
+static int anx7688_i2c_probe(struct i2c_client *client,
+			     const struct i2c_device_id *id)
+{
+	struct anx7688 *anx7688;
+	int ret;
+	u8 buffer[4];
+	u16 vendor, device, fwversion;
+
+	anx7688 = devm_kzalloc(&client->dev, sizeof(*anx7688), GFP_KERNEL);
+	if (!anx7688)
+		return -ENOMEM;
+
+#if IS_ENABLED(CONFIG_OF)
+	anx7688->bridge.of_node = client->dev.of_node;
+#endif
+
+	anx7688->client = client;
+	i2c_set_clientdata(client, anx7688);
+
+	/* Read both vendor and device id (4 bytes). */
+	ret = anx7688_read(client, VENDOR_ID_REG, buffer, 4);
+	if (ret) {
+		dev_err(&client->dev, "Failed to read chip vendor/device id\n");
+		return ret;
+	}
+
+	vendor = (u16)buffer[1] << 8 | buffer[0];
+	device = (u16)buffer[3] << 8 | buffer[2];
+	if (vendor != VENDOR_ID || device != DEVICE_ID) {
+		dev_err(&client->dev,
+			"Invalid vendor/device id %04x/%04x\n",
+			vendor, device);
+		return -ENODEV;
+	}
+
+	ret = anx7688_read(client, FW_VERSION_REG, buffer, 2);
+	if (ret) {
+		dev_err(&client->dev, "Failed to read firmware version\n");
+		return ret;
+	}
+
+	fwversion = (u16)buffer[0] << 8 | buffer[1];
+	dev_info(&client->dev, "ANX7688 firwmare version %02x.%02x\n",
+		 buffer[0], buffer[1]);
+
+	/* FW version >= 0.85 supports bandwidth/lane count registers */
+	if (fwversion >= MINIMUM_FW_VERSION) {
+		anx7688->filter = true;
+	} else {
+		/* Warn, but not fail, for backwards compatibility. */
+		dev_warn(&client->dev,
+			 "Old ANX7688 FW version (%02x.%02x), not filtering\n",
+			 buffer[0], buffer[1]);
+	}
+
+	anx7688->bridge.funcs = &anx7688_bridge_funcs;
+	ret = drm_bridge_add(&anx7688->bridge);
+	if (ret < 0) {
+		dev_err(&client->dev, "Failed to add drm bridge\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static int anx7688_i2c_remove(struct i2c_client *client)
+{
+	struct anx7688 *anx7688 = i2c_get_clientdata(client);
+
+	drm_bridge_remove(&anx7688->bridge);
+
+	return 0;
+}
+
+static const struct i2c_device_id anx7688_id[] = {
+	{ "anx7688", 0 },
+	{ /* sentinel */ }
+};
+
+MODULE_DEVICE_TABLE(i2c, anx7688_id);
+
+#if IS_ENABLED(CONFIG_OF)
+static const struct of_device_id anx7688_match_table[] = {
+	{ .compatible = "analogix,anx7688", },
+	{ /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, anx7688_match_table);
+#endif
+
+static struct i2c_driver anx7688_driver = {
+	.driver = {
+		   .name = "anx7688",
+		   .of_match_table = of_match_ptr(anx7688_match_table),
+		  },
+	.probe = anx7688_i2c_probe,
+	.remove = anx7688_i2c_remove,
+	.id_table = anx7688_id,
+};
+
+module_i2c_driver(anx7688_driver);
+
+MODULE_DESCRIPTION("ANX7688 SlimPort Transmitter driver");
+MODULE_AUTHOR("Nicolas Boichat <drinkcat@chromium.org>");
+MODULE_LICENSE("GPL v2");
-- 
2.8.0.rc3.226.g39d4020

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

* [RFC PATCH 2/4] devicetree: Add ANX7688 transmitter binding
  2016-06-27  7:48 [RFC PATCH 0/4] drm: bridge: anx7688 and mux drivers Nicolas Boichat
  2016-06-27  7:48 ` [RFC PATCHv2 1/4] FROMLIST: drm: bridge: anx7688: Add anx7688 bridge driver support Nicolas Boichat
@ 2016-06-27  7:48 ` Nicolas Boichat
  2016-06-27  7:48 ` [RFC PATCH 3/4] CHROMIUM: drm: bridge: Generic GPIO mux driver Nicolas Boichat
                   ` (2 subsequent siblings)
  4 siblings, 0 replies; 9+ messages in thread
From: Nicolas Boichat @ 2016-06-27  7:48 UTC (permalink / raw)
  To: dri-devel
  Cc: Archit Taneja, Thierry Reding, Nicolas Boichat, Russell King,
	linux-kernel, p.zabel, marcheu

Signed-off-by: Nicolas Boichat <drinkcat@chromium.org>

Change-Id: I618ddf114bb93700daf6572d954191e10b6961c8
---
 .../devicetree/bindings/drm/bridge/anx7688.txt     | 32 ++++++++++++++++++++++
 1 file changed, 32 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/drm/bridge/anx7688.txt

diff --git a/Documentation/devicetree/bindings/drm/bridge/anx7688.txt b/Documentation/devicetree/bindings/drm/bridge/anx7688.txt
new file mode 100644
index 0000000..78b55bd
--- /dev/null
+++ b/Documentation/devicetree/bindings/drm/bridge/anx7688.txt
@@ -0,0 +1,32 @@
+Analogix ANX7688 SlimPort (Single-Chip Transmitter for DP over USB-C)
+---------------------------------------------------------------------
+
+The ANX7688 is a single-chip mobile transmitter to support 4K 60 frames per
+second (4096x2160p60) or FHD 120 frames per second (1920x1080p120) video
+resolution from a smartphone or tablet with full function USB-C.
+
+This binding only describes the HDMI to DP display bridge.
+
+Required properties:
+
+ - compatible          : "analogix,anx7688"
+ - reg                 : I2C address of the device (fixed at 0x2c)
+
+Optional properties:
+
+ - Video port for HDMI input, using the DT bindings defined in [1].
+
+[1]: Documentation/devicetree/bindings/media/video-interfaces.txt
+
+Example:
+
+	anx7688: anx7688@2c {
+		compatible = "analogix,anx7688";
+		reg = <0x2c>;
+
+		port {
+			anx7688_in: endpoint {
+				remote-endpoint = <&hdmi0_out>;
+			};
+		};
+	};
-- 
2.8.0.rc3.226.g39d4020

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

* [RFC PATCH 3/4] CHROMIUM: drm: bridge: Generic GPIO mux driver
  2016-06-27  7:48 [RFC PATCH 0/4] drm: bridge: anx7688 and mux drivers Nicolas Boichat
  2016-06-27  7:48 ` [RFC PATCHv2 1/4] FROMLIST: drm: bridge: anx7688: Add anx7688 bridge driver support Nicolas Boichat
  2016-06-27  7:48 ` [RFC PATCH 2/4] devicetree: Add ANX7688 transmitter binding Nicolas Boichat
@ 2016-06-27  7:48 ` Nicolas Boichat
  2016-06-28  8:52   ` Archit Taneja
  2016-06-27  7:48 ` [RFC PATCH 4/4] CHROMIUM: devicetree: Add GPIO display mux binding Nicolas Boichat
  2016-06-28  8:28 ` [RFC PATCH 0/4] drm: bridge: anx7688 and mux drivers Archit Taneja
  4 siblings, 1 reply; 9+ messages in thread
From: Nicolas Boichat @ 2016-06-27  7:48 UTC (permalink / raw)
  To: dri-devel
  Cc: Archit Taneja, Thierry Reding, Nicolas Boichat, Russell King,
	linux-kernel, p.zabel, marcheu

This driver supports single input, 2 output display mux (e.g.
HDMI mux), that provides its status via a GPIO.

Signed-off-by: Nicolas Boichat <drinkcat@chromium.org>
---
 drivers/gpu/drm/bridge/Kconfig            |  11 +
 drivers/gpu/drm/bridge/Makefile           |   1 +
 drivers/gpu/drm/bridge/generic-gpio-mux.c | 347 ++++++++++++++++++++++++++++++
 3 files changed, 359 insertions(+)
 create mode 100644 drivers/gpu/drm/bridge/generic-gpio-mux.c

diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
index da489f0..f1f6fc6 100644
--- a/drivers/gpu/drm/bridge/Kconfig
+++ b/drivers/gpu/drm/bridge/Kconfig
@@ -41,6 +41,17 @@ config DRM_DW_HDMI_AHB_AUDIO
 	  Designware HDMI block.  This is used in conjunction with
 	  the i.MX6 HDMI driver.
 
+config DRM_GENERIC_GPIO_MUX
+	tristate "Generic GPIO-controlled mux"
+	depends on DRM
+	depends on OF
+	select DRM_KMS_HELPER
+	---help---
+	  This bridge driver models a GPIO-controlled display mux with one
+	  input, 2 outputs (e.g. an HDMI mux). The hardware decides which output
+	  is active, reports it as a GPIO, and the driver redirects calls to the
+	  appropriate downstream bridge (if any).
+
 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 4846465..cb97274fd 100644
--- a/drivers/gpu/drm/bridge/Makefile
+++ b/drivers/gpu/drm/bridge/Makefile
@@ -4,6 +4,7 @@ obj-$(CONFIG_DRM_ANALOGIX_ANX7688) += analogix-anx7688.o
 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_GENERIC_GPIO_MUX) += generic-gpio-mux.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/generic-gpio-mux.c b/drivers/gpu/drm/bridge/generic-gpio-mux.c
new file mode 100644
index 0000000..d3367e2
--- /dev/null
+++ b/drivers/gpu/drm/bridge/generic-gpio-mux.c
@@ -0,0 +1,347 @@
+/*
+ * ANX7688 HDMI->DP bridge driver
+ *
+ * Copyright (C) 2016 Google, Inc.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that 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.
+ */
+
+#include <linux/gpio.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+#include <linux/of_graph.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_crtc_helper.h>
+
+struct gpio_display_mux {
+	struct device *dev;
+
+	struct gpio_desc *gpiod_detect;
+	int detect_irq;
+
+	struct drm_bridge bridge;
+
+	struct drm_bridge *next[2];
+
+	struct mutex lock;
+	int active;
+	bool enabled;
+};
+
+static inline struct gpio_display_mux *bridge_to_gpio_display_mux(
+		struct drm_bridge *bridge)
+{
+	return container_of(bridge, struct gpio_display_mux, bridge);
+}
+
+static irqreturn_t gpio_display_mux_det_threaded_handler(int unused, void *data)
+{
+	struct gpio_display_mux *gpio_display_mux = data;
+	struct drm_bridge *next;
+	int active;
+
+	active = gpiod_get_value(gpio_display_mux->gpiod_detect);
+
+	dev_dbg(gpio_display_mux->dev, "Interrupt %d!\n", active);
+
+	if (active == gpio_display_mux->active)
+		return IRQ_HANDLED;
+
+	/* Disable previous bridge */
+	mutex_lock(&gpio_display_mux->lock);
+	if (gpio_display_mux->enabled) {
+		next = gpio_display_mux->next[gpio_display_mux->active];
+		if (next && next->funcs->disable)
+			next->funcs->disable(next);
+	}
+	mutex_unlock(&gpio_display_mux->lock);
+
+	if (gpio_display_mux->bridge.dev)
+		drm_kms_helper_hotplug_event(gpio_display_mux->bridge.dev);
+
+	/* Enable current bridge */
+	mutex_lock(&gpio_display_mux->lock);
+	if (gpio_display_mux->enabled) {
+		next = gpio_display_mux->next[active];
+		if (next && next->funcs->enable)
+			next->funcs->enable(next);
+	}
+	gpio_display_mux->active = active;
+	mutex_unlock(&gpio_display_mux->lock);
+
+	return IRQ_HANDLED;
+}
+
+static int gpio_display_mux_attach(struct drm_bridge *bridge)
+{
+	struct gpio_display_mux *gpio_display_mux =
+			bridge_to_gpio_display_mux(bridge);
+	struct drm_bridge *next;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(gpio_display_mux->next); i++) {
+		next = gpio_display_mux->next[i];
+		if (next)
+			next->encoder = bridge->encoder;
+	}
+
+	return 0;
+}
+
+static bool gpio_display_mux_mode_fixup(struct drm_bridge *bridge,
+				const struct drm_display_mode *mode,
+				struct drm_display_mode *adjusted_mode)
+{
+	struct gpio_display_mux *gpio_display_mux =
+		bridge_to_gpio_display_mux(bridge);
+	struct drm_bridge *next;
+
+	next = gpio_display_mux->next[gpio_display_mux->active];
+
+	if (next && next->funcs->mode_fixup)
+		return next->funcs->mode_fixup(next, mode, adjusted_mode);
+	else
+		return true;
+}
+
+static void gpio_display_mux_mode_set(struct drm_bridge *bridge,
+				struct drm_display_mode *mode,
+				struct drm_display_mode *adjusted_mode)
+{
+	struct gpio_display_mux *gpio_display_mux =
+		bridge_to_gpio_display_mux(bridge);
+	struct drm_bridge *next;
+
+	next = gpio_display_mux->next[gpio_display_mux->active];
+
+	if (next && next->funcs->mode_set)
+		next->funcs->mode_set(next, mode, adjusted_mode);
+}
+
+static void gpio_display_mux_enable(struct drm_bridge *bridge)
+{
+	struct gpio_display_mux *gpio_display_mux =
+		bridge_to_gpio_display_mux(bridge);
+	struct drm_bridge *next;
+
+	mutex_lock(&gpio_display_mux->lock);
+
+	next = gpio_display_mux->next[gpio_display_mux->active];
+	if (next && next->funcs->enable)
+		next->funcs->enable(next);
+
+	gpio_display_mux->enabled = true;
+
+	mutex_unlock(&gpio_display_mux->lock);
+}
+
+static void gpio_display_mux_disable(struct drm_bridge *bridge)
+{
+	struct gpio_display_mux *gpio_display_mux =
+		bridge_to_gpio_display_mux(bridge);
+	struct drm_bridge *next;
+
+	mutex_lock(&gpio_display_mux->lock);
+
+	next = gpio_display_mux->next[gpio_display_mux->active];
+	if (next && next->funcs->disable)
+		next->funcs->disable(next);
+
+	gpio_display_mux->enabled = false;
+
+	mutex_unlock(&gpio_display_mux->lock);
+}
+
+/**
+ * Since this driver _reacts_ to mux changes, we need to make sure all
+ * downstream bridges are pre-enabled.
+ */
+static void gpio_display_mux_pre_enable(struct drm_bridge *bridge)
+{
+	struct gpio_display_mux *gpio_display_mux =
+		bridge_to_gpio_display_mux(bridge);
+	struct drm_bridge *next;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(gpio_display_mux->next); i++) {
+		next = gpio_display_mux->next[i];
+		if (next && next->funcs->pre_enable)
+			next->funcs->pre_enable(next);
+	}
+}
+
+static void gpio_display_mux_post_disable(struct drm_bridge *bridge)
+{
+	struct gpio_display_mux *gpio_display_mux =
+		bridge_to_gpio_display_mux(bridge);
+	struct drm_bridge *next;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(gpio_display_mux->next); i++) {
+		next = gpio_display_mux->next[i];
+		if (next && next->funcs->post_disable)
+			next->funcs->post_disable(next);
+	}
+}
+
+static const struct drm_bridge_funcs gpio_display_mux_bridge_funcs = {
+	.attach = gpio_display_mux_attach,
+	.mode_fixup = gpio_display_mux_mode_fixup,
+	.disable = gpio_display_mux_disable,
+	.post_disable = gpio_display_mux_post_disable,
+	.mode_set = gpio_display_mux_mode_set,
+	.pre_enable = gpio_display_mux_pre_enable,
+	.enable = gpio_display_mux_enable,
+};
+
+static int gpio_display_mux_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct gpio_display_mux *gpio_display_mux;
+	struct device_node *port, *ep, *remote;
+	int ret;
+	u32 reg;
+
+	gpio_display_mux = devm_kzalloc(dev, sizeof(*gpio_display_mux),
+					GFP_KERNEL);
+	if (!gpio_display_mux)
+		return -ENOMEM;
+
+	mutex_init(&gpio_display_mux->lock);
+
+	platform_set_drvdata(pdev, gpio_display_mux);
+	gpio_display_mux->dev = &pdev->dev;
+
+	gpio_display_mux->bridge.of_node = dev->of_node;
+
+	gpio_display_mux->gpiod_detect =
+		devm_gpiod_get(dev, "detect", GPIOD_IN);
+	if (IS_ERR(gpio_display_mux->gpiod_detect))
+		return PTR_ERR(gpio_display_mux->gpiod_detect);
+
+	gpio_display_mux->detect_irq =
+		gpiod_to_irq(gpio_display_mux->gpiod_detect);
+	if (gpio_display_mux->detect_irq < 0) {
+		dev_err(dev, "Failed to get output irq %d\n",
+			gpio_display_mux->detect_irq);
+		return -ENODEV;
+	}
+
+	port = of_graph_get_port_by_id(dev->of_node, 1);
+	if (!port) {
+		dev_err(dev, "Missing output port node\n");
+		return -EINVAL;
+	}
+
+	for_each_child_of_node(port, ep) {
+		if (!ep->name || (of_node_cmp(ep->name, "endpoint") != 0)) {
+			of_node_put(ep);
+			continue;
+		}
+
+		if (of_property_read_u32(ep, "reg", &reg) < 0 ||
+				reg >= ARRAY_SIZE(gpio_display_mux->next)) {
+			dev_err(dev,
+			    "Missing/invalid reg property for endpoint %s\n",
+				ep->full_name);
+			of_node_put(ep);
+			of_node_put(port);
+			return -EINVAL;
+		}
+
+		remote = of_graph_get_remote_port_parent(ep);
+		if (!remote) {
+			dev_err(dev,
+			    "Missing connector/bridge node for endpoint %s\n",
+				ep->full_name);
+			of_node_put(ep);
+			of_node_put(port);
+			return -EINVAL;
+		}
+		of_node_put(ep);
+
+		if (of_device_is_compatible(remote, "hdmi-connector")) {
+			of_node_put(remote);
+			continue;
+		}
+
+		gpio_display_mux->next[reg] = of_drm_find_bridge(remote);
+		if (!gpio_display_mux->next[reg]) {
+			dev_err(dev, "Waiting for external bridge %s\n",
+				remote->name);
+			of_node_put(remote);
+			of_node_put(port);
+			return -EPROBE_DEFER;
+		}
+
+		of_node_put(remote);
+	}
+	of_node_put(port);
+
+	gpio_display_mux->bridge.funcs = &gpio_display_mux_bridge_funcs;
+	ret = drm_bridge_add(&gpio_display_mux->bridge);
+	if (ret < 0) {
+		dev_err(dev, "Failed to add drm bridge\n");
+		return ret;
+	}
+
+	ret = devm_request_threaded_irq(dev, gpio_display_mux->detect_irq,
+				NULL,
+				gpio_display_mux_det_threaded_handler,
+				IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING |
+					IRQF_ONESHOT,
+				"gpio-display-mux-det", gpio_display_mux);
+	if (ret) {
+		dev_err(dev, "Failed to request MUX_DET threaded irq\n");
+		goto err_bridge_remove;
+	}
+
+	gpio_display_mux->active =
+			gpiod_get_value(gpio_display_mux->gpiod_detect);
+
+	return 0;
+
+err_bridge_remove:
+	drm_bridge_remove(&gpio_display_mux->bridge);
+
+	return ret;
+}
+
+static int gpio_display_mux_remove(struct platform_device *pdev)
+{
+	struct gpio_display_mux *gpio_display_mux = platform_get_drvdata(pdev);
+
+	drm_bridge_remove(&gpio_display_mux->bridge);
+
+	return 0;
+}
+
+static const struct of_device_id gpio_display_mux_match[] = {
+	{ .compatible = "gpio-display-mux", },
+	{},
+};
+
+struct platform_driver gpio_display_mux_driver = {
+	.probe = gpio_display_mux_probe,
+	.remove = gpio_display_mux_remove,
+	.driver = {
+		.name = "gpio-display-mux",
+		.of_match_table = gpio_display_mux_match,
+	},
+};
+
+module_platform_driver(gpio_display_mux_driver);
+
+MODULE_DESCRIPTION("GPIO-controlled display mux");
+MODULE_AUTHOR("Nicolas Boichat <drinkcat@chromium.org>");
+MODULE_LICENSE("GPL v2");
-- 
2.8.0.rc3.226.g39d4020

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

* [RFC PATCH 4/4] CHROMIUM: devicetree: Add GPIO display mux binding
  2016-06-27  7:48 [RFC PATCH 0/4] drm: bridge: anx7688 and mux drivers Nicolas Boichat
                   ` (2 preceding siblings ...)
  2016-06-27  7:48 ` [RFC PATCH 3/4] CHROMIUM: drm: bridge: Generic GPIO mux driver Nicolas Boichat
@ 2016-06-27  7:48 ` Nicolas Boichat
  2016-06-28  8:28 ` [RFC PATCH 0/4] drm: bridge: anx7688 and mux drivers Archit Taneja
  4 siblings, 0 replies; 9+ messages in thread
From: Nicolas Boichat @ 2016-06-27  7:48 UTC (permalink / raw)
  To: dri-devel
  Cc: Archit Taneja, Thierry Reding, Nicolas Boichat, Russell King,
	linux-kernel, p.zabel, marcheu

Signed-off-by: Nicolas Boichat <drinkcat@chromium.org>
---
 .../devicetree/bindings/drm/bridge/gpio-mux.txt    | 59 ++++++++++++++++++++++
 1 file changed, 59 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/drm/bridge/gpio-mux.txt

diff --git a/Documentation/devicetree/bindings/drm/bridge/gpio-mux.txt b/Documentation/devicetree/bindings/drm/bridge/gpio-mux.txt
new file mode 100644
index 0000000..cce410c
--- /dev/null
+++ b/Documentation/devicetree/bindings/drm/bridge/gpio-mux.txt
@@ -0,0 +1,59 @@
+Generic display mux (1 input, 2 outputs)
+----------------------------------------
+
+This bindings describes a simple display (e.g. HDMI) mux, that has 2
+inputs, and 1 output. The mux status is controlled by hardware, and
+its status is read back using a GPIO.
+
+Required properties:
+
+ - compatible          : "gpio-display-mux"
+ - detect-gpios        : GPIO that indicates the active output
+
+ - Video port for input, using the DT bindings defined in [1].
+ - 2 video port for output, using the DT bindings defined in [1].
+   The reg value in the endpoints matches the GPIO status: when
+   GPIO is active, endpoint with reg value <1> is selected,.
+
+[1]: Documentation/devicetree/bindings/media/video-interfaces.txt
+
+Example:
+
+	hdmi_mux: hdmi_mux {
+		compatible = "gpio-display-mux";
+		status = "okay";
+		detect-gpios = <&pio 36 GPIO_ACTIVE_HIGH>;
+		pinctrl-names = "default";
+		pinctrl-0 = <&hdmi_mux_pins>;
+		ddc-i2c-bus = <&hdmiddc0>;
+
+		ports {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			port@0 { /* input */
+				reg = <0>;
+
+				hdmi_mux_in: endpoint {
+					remote-endpoint = <&hdmi0_out>;
+				};
+			};
+
+			port@1 { /* output */
+				reg = <1>;
+
+				#address-cells = <1>;
+				#size-cells = <0>;
+
+				hdmi_mux_out_anx: endpoint@0 {
+					reg = <0>;
+					remote-endpoint = <&anx7688_in>;
+				};
+
+				hdmi_mux_out_hdmi: endpoint@1 {
+					reg = <1>;
+					remote-endpoint = <&hdmi_connector_in>;
+				};
+			};
+		};
+	};
-- 
2.8.0.rc3.226.g39d4020

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

* Re: [RFC PATCH 0/4] drm: bridge: anx7688 and mux drivers
  2016-06-27  7:48 [RFC PATCH 0/4] drm: bridge: anx7688 and mux drivers Nicolas Boichat
                   ` (3 preceding siblings ...)
  2016-06-27  7:48 ` [RFC PATCH 4/4] CHROMIUM: devicetree: Add GPIO display mux binding Nicolas Boichat
@ 2016-06-28  8:28 ` Archit Taneja
  2016-07-06  1:58   ` Nicolas Boichat
  4 siblings, 1 reply; 9+ messages in thread
From: Archit Taneja @ 2016-06-28  8:28 UTC (permalink / raw)
  To: Nicolas Boichat, dri-devel
  Cc: Thierry Reding, Russell King, linux-kernel, p.zabel, marcheu



On 06/27/2016 01:18 PM, Nicolas Boichat wrote:
> Hi all,
>
> This is a follow up to the 2 patches to add support for ANX7688 sent here:
> https://patchwork.kernel.org/patch/9187809/, thanks Archit and Philipp for
> the comments.
>
> I also added 2 patches to add support for a simple display MUX, as I'm facing
> similar issues while trying to implement it, i.e. the current DRM core does not
> seem to support this kind of simple pass-thru bridge very well: it is not very
> clear where connectors should be defined and attached. In this case, not
> defining any connectors in the 2 bridges (and reusing the connector in MTK
> HDMI driver) seem to work best, but makes little logical sense as the physical
> connectors are actually attached to the bridges.

Bridges aren't really drm objects in themselves, they can just be
thought of as entities that attach to an encoder. From a drm
perspective, the connector is only linked to an encoder. It
doesn't see any bridges. Therefore, it doesn't matter much
if the bridge driver doesn't create connectors. The DT bindings,
however, should be close to the physical connections.

>
> In any case, the board has the following layout:
>   - MT8173 HDMI bridge
>     - HDMI mux with 2 ports
>       1. ANX7688 for HDMI->DP over USB-C conversion
>       2. Native HDMI
>

So, the MTK SoC's HDMI output (TMDS lines) can be routed to the
connector on the board directly (native mode), or via the ANX7688
bridge using the gpio mux. Did I get this part right?

Is there only one connector at the end of both the output paths?


> The mux is controlled by hardware, looking at the HPD signals from both ANX7688
> and native HDMI, with a priority on the native HDMI output.

I didn't understand this. I can see that ANX7688 could generate a HPD
signal on behalf of the connected monitor, but why would the native MTK
HDMI controller generate a HPD signal? I would expect it to receive HPD
and trigger a CPU interrupt.

Could you also give an idea about why the hardware switches between the
two paths? It it based on what kind of device plugs into the connector?

Thanks,
Archit

>
> The whole setup works fairly well without any Linux kernel drivers, except the
> 2 following cases:
>   1. When ANX7688 is active, DP bandwidth may be limited, so we need to filter
>      resolutions that would exceed the available bandwidth.
>   2. When both outputs HPD signals are active, the kernel does not receive an
>      HPD pulse when the HDMI input is unplugged.
>
> ANX7688 driver fixes issue 1. The mux driver fixes 2 by forcing the kernel to
> re-read the EDID on mux output change, and also issue 1 by filtering only when
> ANX7688 is active.
>
> I understand this patch series might not be acceptable as-is, but I hope this
> sort of setup can be taken into account when better support for connector
> drivers is introduced.
>
> Thanks!
>
> Best,
>
> Nicolas
>
> Nicolas Boichat (4):
>    drm: bridge: anx7688: Add anx7688 bridge driver support.
>    devicetree: Add ANX7688 transmitter binding
>    drm: bridge: Generic GPIO mux driver
>    devicetree: Add GPIO display mux binding
>
>   .../devicetree/bindings/drm/bridge/anx7688.txt     |  32 ++
>   .../devicetree/bindings/drm/bridge/gpio-mux.txt    |  59 ++++
>   drivers/gpu/drm/bridge/Kconfig                     |  20 ++
>   drivers/gpu/drm/bridge/Makefile                    |   2 +
>   drivers/gpu/drm/bridge/analogix-anx7688.c          | 233 ++++++++++++++
>   drivers/gpu/drm/bridge/generic-gpio-mux.c          | 347 +++++++++++++++++++++
>   6 files changed, 693 insertions(+)
>   create mode 100644 Documentation/devicetree/bindings/drm/bridge/anx7688.txt
>   create mode 100644 Documentation/devicetree/bindings/drm/bridge/gpio-mux.txt
>   create mode 100644 drivers/gpu/drm/bridge/analogix-anx7688.c
>   create mode 100644 drivers/gpu/drm/bridge/generic-gpio-mux.c
>

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

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

* Re: [RFC PATCH 3/4] CHROMIUM: drm: bridge: Generic GPIO mux driver
  2016-06-27  7:48 ` [RFC PATCH 3/4] CHROMIUM: drm: bridge: Generic GPIO mux driver Nicolas Boichat
@ 2016-06-28  8:52   ` Archit Taneja
  0 siblings, 0 replies; 9+ messages in thread
From: Archit Taneja @ 2016-06-28  8:52 UTC (permalink / raw)
  To: Nicolas Boichat, dri-devel
  Cc: Thierry Reding, Russell King, linux-kernel, p.zabel, marcheu



On 06/27/2016 01:18 PM, Nicolas Boichat wrote:
> This driver supports single input, 2 output display mux (e.g.
> HDMI mux), that provides its status via a GPIO.

This might not work if we had a 2 or more bridges connected
one after the other at the output ports. It would be nicer
if the interrupt handler directly modified the drm_bridge's
next pointer rather than managing things by its own.

That being said, the bridge chains (by setting the next
pointers) aren't expected to change after they are set up.
In order to use make this a truly generic driver, we'd
probably need to add some sort of locking for the entire
bridge chain to make sure we don't change things in the
middle of a modeset. We don't really need to add this
functionality unless there are many more platforms like
these have non-static muxes in the display chain.

It would be better if this driver was considered to be
used only for the mux hardware that's used on the board.

Archit

>
> Signed-off-by: Nicolas Boichat <drinkcat@chromium.org>
> ---
>   drivers/gpu/drm/bridge/Kconfig            |  11 +
>   drivers/gpu/drm/bridge/Makefile           |   1 +
>   drivers/gpu/drm/bridge/generic-gpio-mux.c | 347 ++++++++++++++++++++++++++++++
>   3 files changed, 359 insertions(+)
>   create mode 100644 drivers/gpu/drm/bridge/generic-gpio-mux.c
>
> diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
> index da489f0..f1f6fc6 100644
> --- a/drivers/gpu/drm/bridge/Kconfig
> +++ b/drivers/gpu/drm/bridge/Kconfig
> @@ -41,6 +41,17 @@ config DRM_DW_HDMI_AHB_AUDIO
>   	  Designware HDMI block.  This is used in conjunction with
>   	  the i.MX6 HDMI driver.
>
> +config DRM_GENERIC_GPIO_MUX
> +	tristate "Generic GPIO-controlled mux"
> +	depends on DRM
> +	depends on OF
> +	select DRM_KMS_HELPER
> +	---help---
> +	  This bridge driver models a GPIO-controlled display mux with one
> +	  input, 2 outputs (e.g. an HDMI mux). The hardware decides which output
> +	  is active, reports it as a GPIO, and the driver redirects calls to the
> +	  appropriate downstream bridge (if any).
> +
>   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 4846465..cb97274fd 100644
> --- a/drivers/gpu/drm/bridge/Makefile
> +++ b/drivers/gpu/drm/bridge/Makefile
> @@ -4,6 +4,7 @@ obj-$(CONFIG_DRM_ANALOGIX_ANX7688) += analogix-anx7688.o
>   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_GENERIC_GPIO_MUX) += generic-gpio-mux.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/generic-gpio-mux.c b/drivers/gpu/drm/bridge/generic-gpio-mux.c
> new file mode 100644
> index 0000000..d3367e2
> --- /dev/null
> +++ b/drivers/gpu/drm/bridge/generic-gpio-mux.c
> @@ -0,0 +1,347 @@
> +/*
> + * ANX7688 HDMI->DP bridge driver
> + *
> + * Copyright (C) 2016 Google, Inc.
> + *
> + * This software is licensed under the terms of the GNU General Public
> + * License version 2, as published by the Free Software Foundation, and
> + * may be copied, distributed, and modified under those terms.
> + *
> + * This program is distributed in the hope that 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.
> + */
> +
> +#include <linux/gpio.h>
> +#include <linux/interrupt.h>
> +#include <linux/platform_device.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_gpio.h>
> +#include <linux/of_graph.h>
> +#include <drm/drm_crtc.h>
> +#include <drm/drm_crtc_helper.h>
> +
> +struct gpio_display_mux {
> +	struct device *dev;
> +
> +	struct gpio_desc *gpiod_detect;
> +	int detect_irq;
> +
> +	struct drm_bridge bridge;
> +
> +	struct drm_bridge *next[2];
> +
> +	struct mutex lock;
> +	int active;
> +	bool enabled;
> +};
> +
> +static inline struct gpio_display_mux *bridge_to_gpio_display_mux(
> +		struct drm_bridge *bridge)
> +{
> +	return container_of(bridge, struct gpio_display_mux, bridge);
> +}
> +
> +static irqreturn_t gpio_display_mux_det_threaded_handler(int unused, void *data)
> +{
> +	struct gpio_display_mux *gpio_display_mux = data;
> +	struct drm_bridge *next;
> +	int active;
> +
> +	active = gpiod_get_value(gpio_display_mux->gpiod_detect);
> +
> +	dev_dbg(gpio_display_mux->dev, "Interrupt %d!\n", active);
> +
> +	if (active == gpio_display_mux->active)
> +		return IRQ_HANDLED;
> +
> +	/* Disable previous bridge */
> +	mutex_lock(&gpio_display_mux->lock);
> +	if (gpio_display_mux->enabled) {
> +		next = gpio_display_mux->next[gpio_display_mux->active];
> +		if (next && next->funcs->disable)
> +			next->funcs->disable(next);
> +	}
> +	mutex_unlock(&gpio_display_mux->lock);
> +
> +	if (gpio_display_mux->bridge.dev)
> +		drm_kms_helper_hotplug_event(gpio_display_mux->bridge.dev);
> +
> +	/* Enable current bridge */
> +	mutex_lock(&gpio_display_mux->lock);
> +	if (gpio_display_mux->enabled) {
> +		next = gpio_display_mux->next[active];
> +		if (next && next->funcs->enable)
> +			next->funcs->enable(next);
> +	}
> +	gpio_display_mux->active = active;
> +	mutex_unlock(&gpio_display_mux->lock);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static int gpio_display_mux_attach(struct drm_bridge *bridge)
> +{
> +	struct gpio_display_mux *gpio_display_mux =
> +			bridge_to_gpio_display_mux(bridge);
> +	struct drm_bridge *next;
> +	int i;
> +
> +	for (i = 0; i < ARRAY_SIZE(gpio_display_mux->next); i++) {
> +		next = gpio_display_mux->next[i];
> +		if (next)
> +			next->encoder = bridge->encoder;
> +	}
> +
> +	return 0;
> +}
> +
> +static bool gpio_display_mux_mode_fixup(struct drm_bridge *bridge,
> +				const struct drm_display_mode *mode,
> +				struct drm_display_mode *adjusted_mode)
> +{
> +	struct gpio_display_mux *gpio_display_mux =
> +		bridge_to_gpio_display_mux(bridge);
> +	struct drm_bridge *next;
> +
> +	next = gpio_display_mux->next[gpio_display_mux->active];
> +
> +	if (next && next->funcs->mode_fixup)
> +		return next->funcs->mode_fixup(next, mode, adjusted_mode);
> +	else
> +		return true;
> +}
> +
> +static void gpio_display_mux_mode_set(struct drm_bridge *bridge,
> +				struct drm_display_mode *mode,
> +				struct drm_display_mode *adjusted_mode)
> +{
> +	struct gpio_display_mux *gpio_display_mux =
> +		bridge_to_gpio_display_mux(bridge);
> +	struct drm_bridge *next;
> +
> +	next = gpio_display_mux->next[gpio_display_mux->active];
> +
> +	if (next && next->funcs->mode_set)
> +		next->funcs->mode_set(next, mode, adjusted_mode);
> +}
> +
> +static void gpio_display_mux_enable(struct drm_bridge *bridge)
> +{
> +	struct gpio_display_mux *gpio_display_mux =
> +		bridge_to_gpio_display_mux(bridge);
> +	struct drm_bridge *next;
> +
> +	mutex_lock(&gpio_display_mux->lock);
> +
> +	next = gpio_display_mux->next[gpio_display_mux->active];
> +	if (next && next->funcs->enable)
> +		next->funcs->enable(next);
> +
> +	gpio_display_mux->enabled = true;
> +
> +	mutex_unlock(&gpio_display_mux->lock);
> +}
> +
> +static void gpio_display_mux_disable(struct drm_bridge *bridge)
> +{
> +	struct gpio_display_mux *gpio_display_mux =
> +		bridge_to_gpio_display_mux(bridge);
> +	struct drm_bridge *next;
> +
> +	mutex_lock(&gpio_display_mux->lock);
> +
> +	next = gpio_display_mux->next[gpio_display_mux->active];
> +	if (next && next->funcs->disable)
> +		next->funcs->disable(next);
> +
> +	gpio_display_mux->enabled = false;
> +
> +	mutex_unlock(&gpio_display_mux->lock);
> +}
> +
> +/**
> + * Since this driver _reacts_ to mux changes, we need to make sure all
> + * downstream bridges are pre-enabled.
> + */
> +static void gpio_display_mux_pre_enable(struct drm_bridge *bridge)
> +{
> +	struct gpio_display_mux *gpio_display_mux =
> +		bridge_to_gpio_display_mux(bridge);
> +	struct drm_bridge *next;
> +	int i;
> +
> +	for (i = 0; i < ARRAY_SIZE(gpio_display_mux->next); i++) {
> +		next = gpio_display_mux->next[i];
> +		if (next && next->funcs->pre_enable)
> +			next->funcs->pre_enable(next);
> +	}
> +}
> +
> +static void gpio_display_mux_post_disable(struct drm_bridge *bridge)
> +{
> +	struct gpio_display_mux *gpio_display_mux =
> +		bridge_to_gpio_display_mux(bridge);
> +	struct drm_bridge *next;
> +	int i;
> +
> +	for (i = 0; i < ARRAY_SIZE(gpio_display_mux->next); i++) {
> +		next = gpio_display_mux->next[i];
> +		if (next && next->funcs->post_disable)
> +			next->funcs->post_disable(next);
> +	}
> +}
> +
> +static const struct drm_bridge_funcs gpio_display_mux_bridge_funcs = {
> +	.attach = gpio_display_mux_attach,
> +	.mode_fixup = gpio_display_mux_mode_fixup,
> +	.disable = gpio_display_mux_disable,
> +	.post_disable = gpio_display_mux_post_disable,
> +	.mode_set = gpio_display_mux_mode_set,
> +	.pre_enable = gpio_display_mux_pre_enable,
> +	.enable = gpio_display_mux_enable,
> +};
> +
> +static int gpio_display_mux_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct gpio_display_mux *gpio_display_mux;
> +	struct device_node *port, *ep, *remote;
> +	int ret;
> +	u32 reg;
> +
> +	gpio_display_mux = devm_kzalloc(dev, sizeof(*gpio_display_mux),
> +					GFP_KERNEL);
> +	if (!gpio_display_mux)
> +		return -ENOMEM;
> +
> +	mutex_init(&gpio_display_mux->lock);
> +
> +	platform_set_drvdata(pdev, gpio_display_mux);
> +	gpio_display_mux->dev = &pdev->dev;
> +
> +	gpio_display_mux->bridge.of_node = dev->of_node;
> +
> +	gpio_display_mux->gpiod_detect =
> +		devm_gpiod_get(dev, "detect", GPIOD_IN);
> +	if (IS_ERR(gpio_display_mux->gpiod_detect))
> +		return PTR_ERR(gpio_display_mux->gpiod_detect);
> +
> +	gpio_display_mux->detect_irq =
> +		gpiod_to_irq(gpio_display_mux->gpiod_detect);
> +	if (gpio_display_mux->detect_irq < 0) {
> +		dev_err(dev, "Failed to get output irq %d\n",
> +			gpio_display_mux->detect_irq);
> +		return -ENODEV;
> +	}
> +
> +	port = of_graph_get_port_by_id(dev->of_node, 1);
> +	if (!port) {
> +		dev_err(dev, "Missing output port node\n");
> +		return -EINVAL;
> +	}
> +
> +	for_each_child_of_node(port, ep) {
> +		if (!ep->name || (of_node_cmp(ep->name, "endpoint") != 0)) {
> +			of_node_put(ep);
> +			continue;
> +		}
> +
> +		if (of_property_read_u32(ep, "reg", &reg) < 0 ||
> +				reg >= ARRAY_SIZE(gpio_display_mux->next)) {
> +			dev_err(dev,
> +			    "Missing/invalid reg property for endpoint %s\n",
> +				ep->full_name);
> +			of_node_put(ep);
> +			of_node_put(port);
> +			return -EINVAL;
> +		}
> +
> +		remote = of_graph_get_remote_port_parent(ep);
> +		if (!remote) {
> +			dev_err(dev,
> +			    "Missing connector/bridge node for endpoint %s\n",
> +				ep->full_name);
> +			of_node_put(ep);
> +			of_node_put(port);
> +			return -EINVAL;
> +		}
> +		of_node_put(ep);
> +
> +		if (of_device_is_compatible(remote, "hdmi-connector")) {
> +			of_node_put(remote);
> +			continue;
> +		}
> +
> +		gpio_display_mux->next[reg] = of_drm_find_bridge(remote);
> +		if (!gpio_display_mux->next[reg]) {
> +			dev_err(dev, "Waiting for external bridge %s\n",
> +				remote->name);
> +			of_node_put(remote);
> +			of_node_put(port);
> +			return -EPROBE_DEFER;
> +		}
> +
> +		of_node_put(remote);
> +	}
> +	of_node_put(port);
> +
> +	gpio_display_mux->bridge.funcs = &gpio_display_mux_bridge_funcs;
> +	ret = drm_bridge_add(&gpio_display_mux->bridge);
> +	if (ret < 0) {
> +		dev_err(dev, "Failed to add drm bridge\n");
> +		return ret;
> +	}
> +
> +	ret = devm_request_threaded_irq(dev, gpio_display_mux->detect_irq,
> +				NULL,
> +				gpio_display_mux_det_threaded_handler,
> +				IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING |
> +					IRQF_ONESHOT,
> +				"gpio-display-mux-det", gpio_display_mux);
> +	if (ret) {
> +		dev_err(dev, "Failed to request MUX_DET threaded irq\n");
> +		goto err_bridge_remove;
> +	}
> +
> +	gpio_display_mux->active =
> +			gpiod_get_value(gpio_display_mux->gpiod_detect);
> +
> +	return 0;
> +
> +err_bridge_remove:
> +	drm_bridge_remove(&gpio_display_mux->bridge);
> +
> +	return ret;
> +}
> +
> +static int gpio_display_mux_remove(struct platform_device *pdev)
> +{
> +	struct gpio_display_mux *gpio_display_mux = platform_get_drvdata(pdev);
> +
> +	drm_bridge_remove(&gpio_display_mux->bridge);
> +
> +	return 0;
> +}
> +
> +static const struct of_device_id gpio_display_mux_match[] = {
> +	{ .compatible = "gpio-display-mux", },
> +	{},
> +};
> +
> +struct platform_driver gpio_display_mux_driver = {
> +	.probe = gpio_display_mux_probe,
> +	.remove = gpio_display_mux_remove,
> +	.driver = {
> +		.name = "gpio-display-mux",
> +		.of_match_table = gpio_display_mux_match,
> +	},
> +};
> +
> +module_platform_driver(gpio_display_mux_driver);
> +
> +MODULE_DESCRIPTION("GPIO-controlled display mux");
> +MODULE_AUTHOR("Nicolas Boichat <drinkcat@chromium.org>");
> +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] 9+ messages in thread

* Re: [RFC PATCH 0/4] drm: bridge: anx7688 and mux drivers
  2016-06-28  8:28 ` [RFC PATCH 0/4] drm: bridge: anx7688 and mux drivers Archit Taneja
@ 2016-07-06  1:58   ` Nicolas Boichat
  2016-07-12  4:59     ` Archit Taneja
  0 siblings, 1 reply; 9+ messages in thread
From: Nicolas Boichat @ 2016-07-06  1:58 UTC (permalink / raw)
  To: Archit Taneja
  Cc: dri-devel, Thierry Reding, Russell King, linux-kernel,
	Philipp Zabel, Stéphane Marchesin

Hi Archit,

Sorry got swamped by other things and forgot to reply.

On Tue, Jun 28, 2016 at 4:28 PM, Archit Taneja <architt@codeaurora.org> wrote:
>
>
> On 06/27/2016 01:18 PM, Nicolas Boichat wrote:
>>
>> Hi all,
>>
>> This is a follow up to the 2 patches to add support for ANX7688 sent here:
>> https://patchwork.kernel.org/patch/9187809/, thanks Archit and Philipp for
>> the comments.
>>
>> I also added 2 patches to add support for a simple display MUX, as I'm
>> facing
>> similar issues while trying to implement it, i.e. the current DRM core
>> does not
>> seem to support this kind of simple pass-thru bridge very well: it is not
>> very
>> clear where connectors should be defined and attached. In this case, not
>> defining any connectors in the 2 bridges (and reusing the connector in MTK
>> HDMI driver) seem to work best, but makes little logical sense as the
>> physical
>> connectors are actually attached to the bridges.
>
>
> Bridges aren't really drm objects in themselves, they can just be
> thought of as entities that attach to an encoder. From a drm
> perspective, the connector is only linked to an encoder. It
> doesn't see any bridges. Therefore, it doesn't matter much
> if the bridge driver doesn't create connectors. The DT bindings,
> however, should be close to the physical connections.
>
>>
>> In any case, the board has the following layout:
>>   - MT8173 HDMI bridge
>>     - HDMI mux with 2 ports
>>       1. ANX7688 for HDMI->DP over USB-C conversion
>>       2. Native HDMI
>>
>
> So, the MTK SoC's HDMI output (TMDS lines) can be routed to the
> connector on the board directly (native mode), or via the ANX7688
> bridge using the gpio mux. Did I get this part right?

Yes.

> Is there only one connector at the end of both the output paths?

Err, there are:
 - 2 physical connectors (HDMI, USB-C)
 - 1 DT connector in the device tree (native HDMI), I don't bother
adding a connector to anx7688, but in theory I could.
 - 1 DRM connector object (defined in the mtk hdmi driver)

>> The mux is controlled by hardware, looking at the HPD signals from both
>> ANX7688
>> and native HDMI, with a priority on the native HDMI output.
>
>
> I didn't understand this. I can see that ANX7688 could generate a HPD
> signal on behalf of the connected monitor, but why would the native MTK
> HDMI controller generate a HPD signal? I would expect it to receive HPD
> and trigger a CPU interrupt.
>
> Could you also give an idea about why the hardware switches between the
> two paths? It it based on what kind of device plugs into the connector?

The mux is controlled in hardware, by looking at both HPD lines:
 - USB-C (HPD controlled by ANX7688, depending if there is a DP over
USB-C device connected)
 - native HDMI (HPD controlled by monitor on remote side)

Note that, like the other HDMI lines, HPD is "muxed" (depending on the
logic above), and wired up to MTK SoC HDMI/CEC module, which generates
the interrupts.

Priority is set to native HDMI port, so if both HPD signals are
active, the output will stay on the HDMI port.

Best,

Nicolas

> Thanks,
> Archit
>
>
>>
>> The whole setup works fairly well without any Linux kernel drivers, except
>> the
>> 2 following cases:
>>   1. When ANX7688 is active, DP bandwidth may be limited, so we need to
>> filter
>>      resolutions that would exceed the available bandwidth.
>>   2. When both outputs HPD signals are active, the kernel does not receive
>> an
>>      HPD pulse when the HDMI input is unplugged.
>>
>> ANX7688 driver fixes issue 1. The mux driver fixes 2 by forcing the kernel
>> to
>> re-read the EDID on mux output change, and also issue 1 by filtering only
>> when
>> ANX7688 is active.
>>
>> I understand this patch series might not be acceptable as-is, but I hope
>> this
>> sort of setup can be taken into account when better support for connector
>> drivers is introduced.
>>
>> Thanks!
>>
>> Best,
>>
>> Nicolas
>>
>> Nicolas Boichat (4):
>>    drm: bridge: anx7688: Add anx7688 bridge driver support.
>>    devicetree: Add ANX7688 transmitter binding
>>    drm: bridge: Generic GPIO mux driver
>>    devicetree: Add GPIO display mux binding
>>
>>   .../devicetree/bindings/drm/bridge/anx7688.txt     |  32 ++
>>   .../devicetree/bindings/drm/bridge/gpio-mux.txt    |  59 ++++
>>   drivers/gpu/drm/bridge/Kconfig                     |  20 ++
>>   drivers/gpu/drm/bridge/Makefile                    |   2 +
>>   drivers/gpu/drm/bridge/analogix-anx7688.c          | 233 ++++++++++++++
>>   drivers/gpu/drm/bridge/generic-gpio-mux.c          | 347
>> +++++++++++++++++++++
>>   6 files changed, 693 insertions(+)
>>   create mode 100644
>> Documentation/devicetree/bindings/drm/bridge/anx7688.txt
>>   create mode 100644
>> Documentation/devicetree/bindings/drm/bridge/gpio-mux.txt
>>   create mode 100644 drivers/gpu/drm/bridge/analogix-anx7688.c
>>   create mode 100644 drivers/gpu/drm/bridge/generic-gpio-mux.c
>>
>
> --
> Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum,
> a Linux Foundation Collaborative Project

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

* Re: [RFC PATCH 0/4] drm: bridge: anx7688 and mux drivers
  2016-07-06  1:58   ` Nicolas Boichat
@ 2016-07-12  4:59     ` Archit Taneja
  0 siblings, 0 replies; 9+ messages in thread
From: Archit Taneja @ 2016-07-12  4:59 UTC (permalink / raw)
  To: Nicolas Boichat
  Cc: dri-devel, Thierry Reding, Russell King, linux-kernel,
	Philipp Zabel, Stéphane Marchesin



On 07/06/2016 07:28 AM, Nicolas Boichat wrote:
> Hi Archit,
>
> Sorry got swamped by other things and forgot to reply.
>
> On Tue, Jun 28, 2016 at 4:28 PM, Archit Taneja <architt@codeaurora.org> wrote:
>>
>>
>> On 06/27/2016 01:18 PM, Nicolas Boichat wrote:
>>>
>>> Hi all,
>>>
>>> This is a follow up to the 2 patches to add support for ANX7688 sent here:
>>> https://patchwork.kernel.org/patch/9187809/, thanks Archit and Philipp for
>>> the comments.
>>>
>>> I also added 2 patches to add support for a simple display MUX, as I'm
>>> facing
>>> similar issues while trying to implement it, i.e. the current DRM core
>>> does not
>>> seem to support this kind of simple pass-thru bridge very well: it is not
>>> very
>>> clear where connectors should be defined and attached. In this case, not
>>> defining any connectors in the 2 bridges (and reusing the connector in MTK
>>> HDMI driver) seem to work best, but makes little logical sense as the
>>> physical
>>> connectors are actually attached to the bridges.
>>
>>
>> Bridges aren't really drm objects in themselves, they can just be
>> thought of as entities that attach to an encoder. From a drm
>> perspective, the connector is only linked to an encoder. It
>> doesn't see any bridges. Therefore, it doesn't matter much
>> if the bridge driver doesn't create connectors. The DT bindings,
>> however, should be close to the physical connections.
>>
>>>
>>> In any case, the board has the following layout:
>>>    - MT8173 HDMI bridge
>>>      - HDMI mux with 2 ports
>>>        1. ANX7688 for HDMI->DP over USB-C conversion
>>>        2. Native HDMI
>>>
>>
>> So, the MTK SoC's HDMI output (TMDS lines) can be routed to the
>> connector on the board directly (native mode), or via the ANX7688
>> bridge using the gpio mux. Did I get this part right?
>
> Yes.
>
>> Is there only one connector at the end of both the output paths?
>
> Err, there are:
>   - 2 physical connectors (HDMI, USB-C)
>   - 1 DT connector in the device tree (native HDMI), I don't bother
> adding a connector to anx7688, but in theory I could.
>   - 1 DRM connector object (defined in the mtk hdmi driver)
>
>>> The mux is controlled by hardware, looking at the HPD signals from both
>>> ANX7688
>>> and native HDMI, with a priority on the native HDMI output.
>>
>>
>> I didn't understand this. I can see that ANX7688 could generate a HPD
>> signal on behalf of the connected monitor, but why would the native MTK
>> HDMI controller generate a HPD signal? I would expect it to receive HPD
>> and trigger a CPU interrupt.
>>
>> Could you also give an idea about why the hardware switches between the
>> two paths? It it based on what kind of device plugs into the connector?
>
> The mux is controlled in hardware, by looking at both HPD lines:
>   - USB-C (HPD controlled by ANX7688, depending if there is a DP over
> USB-C device connected)
>   - native HDMI (HPD controlled by monitor on remote side)
>
> Note that, like the other HDMI lines, HPD is "muxed" (depending on the
> logic above), and wired up to MTK SoC HDMI/CEC module, which generates
> the interrupts.
>
> Priority is set to native HDMI port, so if both HPD signals are
> active, the output will stay on the HDMI port.

Thanks for the clarification.

It's a strange setup. It would be ideal to have 2 connectors visible to
userspace, with only one of them being 'DRM_MODE_CONNECTED' at a time.
There would be a bit of an overkill whenever userspace gets a hotplug
event, resulting in tearing down and setting up crtcs, encoders even
though nothing really changes much upstream.

I think the mux bridge part looks fine, but I don't know what's the best
way how to describe the connectors here :) (one drm_connector
representing both the physical connectors, or 2 separate drm_connector
entities which both can't be connected at the same time). Maybe someone
else from the list could help us out here.

Archit

>
> Best,
>
> Nicolas
>
>> Thanks,
>> Archit
>>
>>
>>>
>>> The whole setup works fairly well without any Linux kernel drivers, except
>>> the
>>> 2 following cases:
>>>    1. When ANX7688 is active, DP bandwidth may be limited, so we need to
>>> filter
>>>       resolutions that would exceed the available bandwidth.
>>>    2. When both outputs HPD signals are active, the kernel does not receive
>>> an
>>>       HPD pulse when the HDMI input is unplugged.
>>>
>>> ANX7688 driver fixes issue 1. The mux driver fixes 2 by forcing the kernel
>>> to
>>> re-read the EDID on mux output change, and also issue 1 by filtering only
>>> when
>>> ANX7688 is active.
>>>
>>> I understand this patch series might not be acceptable as-is, but I hope
>>> this
>>> sort of setup can be taken into account when better support for connector
>>> drivers is introduced.
>>>
>>> Thanks!
>>>
>>> Best,
>>>
>>> Nicolas
>>>
>>> Nicolas Boichat (4):
>>>     drm: bridge: anx7688: Add anx7688 bridge driver support.
>>>     devicetree: Add ANX7688 transmitter binding
>>>     drm: bridge: Generic GPIO mux driver
>>>     devicetree: Add GPIO display mux binding
>>>
>>>    .../devicetree/bindings/drm/bridge/anx7688.txt     |  32 ++
>>>    .../devicetree/bindings/drm/bridge/gpio-mux.txt    |  59 ++++
>>>    drivers/gpu/drm/bridge/Kconfig                     |  20 ++
>>>    drivers/gpu/drm/bridge/Makefile                    |   2 +
>>>    drivers/gpu/drm/bridge/analogix-anx7688.c          | 233 ++++++++++++++
>>>    drivers/gpu/drm/bridge/generic-gpio-mux.c          | 347
>>> +++++++++++++++++++++
>>>    6 files changed, 693 insertions(+)
>>>    create mode 100644
>>> Documentation/devicetree/bindings/drm/bridge/anx7688.txt
>>>    create mode 100644
>>> Documentation/devicetree/bindings/drm/bridge/gpio-mux.txt
>>>    create mode 100644 drivers/gpu/drm/bridge/analogix-anx7688.c
>>>    create mode 100644 drivers/gpu/drm/bridge/generic-gpio-mux.c
>>>
>>
>> --
>> Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum,
>> a Linux Foundation Collaborative Project

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

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

end of thread, other threads:[~2016-07-12  4:59 UTC | newest]

Thread overview: 9+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2016-06-27  7:48 [RFC PATCH 0/4] drm: bridge: anx7688 and mux drivers Nicolas Boichat
2016-06-27  7:48 ` [RFC PATCHv2 1/4] FROMLIST: drm: bridge: anx7688: Add anx7688 bridge driver support Nicolas Boichat
2016-06-27  7:48 ` [RFC PATCH 2/4] devicetree: Add ANX7688 transmitter binding Nicolas Boichat
2016-06-27  7:48 ` [RFC PATCH 3/4] CHROMIUM: drm: bridge: Generic GPIO mux driver Nicolas Boichat
2016-06-28  8:52   ` Archit Taneja
2016-06-27  7:48 ` [RFC PATCH 4/4] CHROMIUM: devicetree: Add GPIO display mux binding Nicolas Boichat
2016-06-28  8:28 ` [RFC PATCH 0/4] drm: bridge: anx7688 and mux drivers Archit Taneja
2016-07-06  1:58   ` Nicolas Boichat
2016-07-12  4:59     ` Archit Taneja

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).