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 <a...@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

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

Reply via email to