[RFC,2/6] drm/bridge: Add mux-input bridge
diff mbox series

Message ID 1d4a97de9acea438f156092dfd1434eb160bf861.1589548223.git.agx@sigxcpu.org
State New
Headers show
Series
  • drm/bridge: Add mux input selection bridge
Related show

Commit Message

Guido Günther May 15, 2020, 1:12 p.m. UTC
This bridge allows to select the input source
via a mux controller. The input source is
determined via DT but it could become rutime
selectable in the future.

Signed-off-by: Guido Günther <agx@sigxcpu.org>
---
 drivers/gpu/drm/bridge/Kconfig     |   9 ++
 drivers/gpu/drm/bridge/Makefile    |   1 +
 drivers/gpu/drm/bridge/mux-input.c | 238 +++++++++++++++++++++++++++++
 3 files changed, 248 insertions(+)
 create mode 100644 drivers/gpu/drm/bridge/mux-input.c

Patch
diff mbox series

diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
index 04f876e985de..3886c0f41bdd 100644
--- a/drivers/gpu/drm/bridge/Kconfig
+++ b/drivers/gpu/drm/bridge/Kconfig
@@ -206,6 +206,15 @@  config DRM_TI_TPD12S015
 	  Texas Instruments TPD12S015 HDMI level shifter and ESD protection
 	  driver.
 
+config DRM_MUX_INPUT
+	tristate "Bridge to select a video input source"
+	depends on OF
+	depends on DRM_BRIDGE
+	select MULTIPLEXER
+	help
+	  Select this option if you want to select the input source to
+	  a DRM bridge or panel via a separate mux chip.
+
 source "drivers/gpu/drm/bridge/analogix/Kconfig"
 
 source "drivers/gpu/drm/bridge/adv7511/Kconfig"
diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
index d63d4b7e4347..9f3370ce7e07 100644
--- a/drivers/gpu/drm/bridge/Makefile
+++ b/drivers/gpu/drm/bridge/Makefile
@@ -4,6 +4,7 @@  obj-$(CONFIG_DRM_CHRONTEL_CH7033) += chrontel-ch7033.o
 obj-$(CONFIG_DRM_DISPLAY_CONNECTOR) += display-connector.o
 obj-$(CONFIG_DRM_LVDS_CODEC) += lvds-codec.o
 obj-$(CONFIG_DRM_MEGACHIPS_STDPXXXX_GE_B850V3_FW) += megachips-stdpxxxx-ge-b850v3-fw.o
+obj-$(CONFIG_DRM_MUX_INPUT) += mux-input.o
 obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o
 obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o
 obj-$(CONFIG_DRM_PARADE_PS8640) += parade-ps8640.o
diff --git a/drivers/gpu/drm/bridge/mux-input.c b/drivers/gpu/drm/bridge/mux-input.c
new file mode 100644
index 000000000000..24961d41ac30
--- /dev/null
+++ b/drivers/gpu/drm/bridge/mux-input.c
@@ -0,0 +1,238 @@ 
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2020 Purism SPC
+ */
+
+#include <linux/module.h>
+#include <linux/mux/consumer.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/pm_runtime.h>
+
+#include <drm/drm_bridge.h>
+#include <drm/drm_of.h>
+#include <drm/drm_panel.h>
+#include <drm/drm_print.h>
+
+#define DRV_NAME "mux-input-bridge"
+
+struct mux_input {
+	struct drm_bridge bridge;
+	struct drm_bridge *out;
+	struct device *dev;
+	struct mux_control *mux;
+	unsigned int n_inputs;
+	unsigned int input;
+	struct drm_bridge_timings timings;
+};
+
+static inline struct mux_input *bridge_to_mux_input(struct drm_bridge *bridge)
+{
+	return container_of(bridge, struct mux_input, bridge);
+}
+
+static void mux_input_bridge_disable(struct drm_bridge *bridge)
+{
+	struct mux_input *mux_input = bridge_to_mux_input(bridge);
+
+	pm_runtime_put(mux_input->dev);
+}
+
+static void mux_input_bridge_pre_enable(struct drm_bridge *bridge)
+{
+	struct mux_input *mux_input = bridge_to_mux_input(bridge);
+
+	pm_runtime_get(mux_input->dev);
+}
+
+static int mux_input_bridge_attach(struct drm_bridge *bridge,
+				   enum drm_bridge_attach_flags flags)
+{
+	struct mux_input *mux_input = bridge_to_mux_input(bridge);
+	struct drm_bridge *panel_bridge;
+	struct drm_panel *panel;
+	struct device *dev;
+	struct device_node *remote;
+	int ret;
+
+	/* Only attach to the selected input */
+	remote = of_graph_get_remote_node(mux_input->dev->of_node,
+					  mux_input->input,
+					  0);
+	if (!remote)
+		return -EINVAL;
+
+	if (bridge->dev) {
+		dev = bridge->dev->dev;
+		if (dev->of_node != remote) {
+			DRM_DEV_DEBUG(mux_input->dev,
+				      "Not attaching to endpoint %s",
+				      dev->of_node->name);
+			return -EINVAL;
+		}
+	}
+	of_node_put(remote);
+
+	ret = drm_of_find_panel_or_bridge(mux_input->dev->of_node,
+					  mux_input->n_inputs - 1, 0, &panel,
+					  &panel_bridge);
+	if (ret)
+		return ret;
+
+	if (panel) {
+		panel_bridge = drm_panel_bridge_add(panel);
+		if (IS_ERR(panel_bridge))
+			return PTR_ERR(panel_bridge);
+	}
+	mux_input->out = panel_bridge;
+
+	if (!mux_input->out)
+		return -EPROBE_DEFER;
+
+	/* Bubble downstream bridge timings upwards */
+	memcpy(&mux_input->timings, mux_input->out->timings,
+	       sizeof(mux_input->timings));
+	mux_input->bridge.timings = &mux_input->timings;
+	return drm_bridge_attach(bridge->encoder, mux_input->out, bridge,
+				 flags);
+}
+
+static void mux_input_bridge_detach(struct drm_bridge *bridge)
+{	struct mux_input *mux_input = bridge_to_mux_input(bridge);
+
+	drm_of_panel_bridge_remove(mux_input->dev->of_node,
+				   mux_input->n_inputs - 1, 0);
+}
+
+static const struct drm_bridge_funcs mux_input_bridge_funcs = {
+	.pre_enable = mux_input_bridge_pre_enable,
+	.disable    = mux_input_bridge_disable,
+	.attach	    = mux_input_bridge_attach,
+	.detach	    = mux_input_bridge_detach,
+};
+
+static int mux_input_select_input(struct mux_input *mux_input)
+{
+	int ret;
+
+	DRM_DEV_DEBUG(mux_input->dev, "Using input %d as pixel source\n",
+		      mux_input->input);
+	ret = mux_control_try_select(mux_input->mux, mux_input->input);
+	if (ret < 0) {
+		DRM_DEV_ERROR(mux_input->dev, "Failed to select input: %d\n",
+			      ret);
+	}
+
+	return ret;
+}
+
+static int mux_input_deselect_input(struct mux_input *mux_input)
+{
+	int ret;
+
+	ret = mux_control_deselect(mux_input->mux);
+	if (ret < 0) {
+		DRM_DEV_ERROR(mux_input->dev, "Failed to deselect input: %d\n",
+			      ret);
+	}
+
+	return ret;
+}
+
+static const struct of_device_id mux_input_dt_ids[] = {
+	{ .compatible = "mux-input-bridge", },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, mux_input_dt_ids);
+
+static int mux_input_probe(struct platform_device *pdev)
+{
+	struct device_node *np = pdev->dev.of_node;
+	struct device *dev = &pdev->dev;
+	struct device_node *ep;
+	struct mux_input *mux_input;
+	int ret;
+
+	mux_input = devm_kzalloc(dev, sizeof(*mux_input), GFP_KERNEL);
+	if (!mux_input)
+		return -ENOMEM;
+
+	mux_input->dev = dev;
+
+	/*
+	 * The largest numbered port is the output port. It determines
+	 * total number of ports.
+	 */
+	for_each_endpoint_of_node(np, ep) {
+		struct of_endpoint endpoint;
+
+		of_graph_parse_endpoint(ep, &endpoint);
+		mux_input->n_inputs = max(mux_input->n_inputs,
+					  endpoint.port + 1);
+	}
+
+	if (mux_input->n_inputs < 2) {
+		DRM_DEV_ERROR(dev, "Not enough ports %d\n",
+			      mux_input->n_inputs);
+		return -EINVAL;
+	}
+
+	if (device_property_read_u32(dev, "default-input",
+				     &mux_input->input))
+		mux_input->input = 0;
+
+	if (mux_input->input > mux_input->n_inputs - 2) {
+		DRM_DEV_ERROR(dev, "Invalid default port %d\n",
+			      mux_input->input);
+		return -EINVAL;
+	}
+
+	mux_input->mux = devm_mux_control_get(dev, NULL);
+	if (IS_ERR(mux_input->mux)) {
+		ret = PTR_ERR(mux_input->mux);
+		if (ret != -EPROBE_DEFER)
+			DRM_DEV_ERROR(dev, "Failed to get mux: %d\n", ret);
+		return ret;
+	}
+
+	mux_input->bridge.driver_private = mux_input;
+	mux_input->bridge.funcs = &mux_input_bridge_funcs;
+	mux_input->bridge.of_node = dev->of_node;
+
+	dev_set_drvdata(dev, mux_input);
+	pm_runtime_enable(dev);
+
+	ret = mux_input_select_input(mux_input);
+	if (ret < 0) {
+		pm_runtime_disable(&pdev->dev);
+		return ret;
+	}
+
+	drm_bridge_add(&mux_input->bridge);
+	return 0;
+}
+
+static int mux_input_remove(struct platform_device *pdev)
+{
+	struct mux_input *mux_input = platform_get_drvdata(pdev);
+
+	mux_input_deselect_input(mux_input);
+	drm_bridge_remove(&mux_input->bridge);
+	pm_runtime_disable(&pdev->dev);
+	return 0;
+}
+
+static struct platform_driver mux_input_driver = {
+	.probe		= mux_input_probe,
+	.remove		= mux_input_remove,
+	.driver		= {
+		.of_match_table = mux_input_dt_ids,
+		.name	= DRV_NAME,
+	},
+};
+
+module_platform_driver(mux_input_driver);
+
+MODULE_AUTHOR("Purism SPC");
+MODULE_DESCRIPTION("Mux input bridge");
+MODULE_LICENSE("GPL");