[RFC PATCH 2/6] drm/bridge: Add mux-input bridge

Guido Günther
2020-05-15T13:12:33Z

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

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");
--
2.26.1