|
@@ -0,0 +1,208 @@
|
|
|
+/*
|
|
|
+ * Copyright (c) 2019-2021, The Linux Foundation. All rights reserved.
|
|
|
+ *
|
|
|
+ * This program is free software; you can redistribute it and/or modify
|
|
|
+ * it under the terms of the GNU General Public License version 2 and
|
|
|
+ * only version 2 as published by the Free Software Foundation.
|
|
|
+ *
|
|
|
+ * 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.
|
|
|
+ *
|
|
|
+ */
|
|
|
+#define pr_fmt(fmt) "[drm-dp] %s: " fmt, __func__
|
|
|
+
|
|
|
+#include <linux/interrupt.h>
|
|
|
+#include <linux/delay.h>
|
|
|
+#include <linux/kernel.h>
|
|
|
+#include <linux/kthread.h>
|
|
|
+#include <linux/slab.h>
|
|
|
+#include <linux/device.h>
|
|
|
+#include "dp_bridge_hpd.h"
|
|
|
+
|
|
|
+struct dp_bridge_hpd_private {
|
|
|
+ struct device *dev;
|
|
|
+ struct dp_hpd base;
|
|
|
+ struct dp_aux_bridge *bridge;
|
|
|
+ struct delayed_work work;
|
|
|
+ struct dp_hpd_cb *cb;
|
|
|
+ bool hpd;
|
|
|
+ bool hpd_irq;
|
|
|
+ struct mutex hpd_lock;
|
|
|
+};
|
|
|
+
|
|
|
+static int dp_bridge_hpd_connect(struct dp_bridge_hpd_private *bridge_hpd,
|
|
|
+ bool hpd)
|
|
|
+{
|
|
|
+ int rc = 0;
|
|
|
+
|
|
|
+ if (!bridge_hpd) {
|
|
|
+ pr_err("invalid input\n");
|
|
|
+ rc = -EINVAL;
|
|
|
+ goto error;
|
|
|
+ }
|
|
|
+
|
|
|
+ bridge_hpd->base.hpd_high = hpd;
|
|
|
+ bridge_hpd->base.alt_mode_cfg_done = hpd;
|
|
|
+ bridge_hpd->base.hpd_irq = false;
|
|
|
+
|
|
|
+ if (!bridge_hpd->cb ||
|
|
|
+ !bridge_hpd->cb->configure ||
|
|
|
+ !bridge_hpd->cb->disconnect) {
|
|
|
+ pr_err("invalid cb\n");
|
|
|
+ rc = -EINVAL;
|
|
|
+ goto error;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (hpd)
|
|
|
+ rc = bridge_hpd->cb->configure(bridge_hpd->dev);
|
|
|
+ else
|
|
|
+ rc = bridge_hpd->cb->disconnect(bridge_hpd->dev);
|
|
|
+
|
|
|
+error:
|
|
|
+ return rc;
|
|
|
+}
|
|
|
+
|
|
|
+static int dp_bridge_hpd_attention(struct dp_bridge_hpd_private *bridge_hpd)
|
|
|
+{
|
|
|
+ int rc = 0;
|
|
|
+
|
|
|
+ if (!bridge_hpd) {
|
|
|
+ pr_err("invalid input\n");
|
|
|
+ rc = -EINVAL;
|
|
|
+ goto error;
|
|
|
+ }
|
|
|
+
|
|
|
+ bridge_hpd->base.hpd_irq = true;
|
|
|
+
|
|
|
+ if (bridge_hpd->cb && bridge_hpd->cb->attention)
|
|
|
+ rc = bridge_hpd->cb->attention(bridge_hpd->dev);
|
|
|
+
|
|
|
+error:
|
|
|
+ return rc;
|
|
|
+}
|
|
|
+
|
|
|
+static void dp_bridge_hpd_work(struct work_struct *work)
|
|
|
+{
|
|
|
+ struct delayed_work *dw = to_delayed_work(work);
|
|
|
+ struct dp_bridge_hpd_private *bridge_hpd = container_of(dw,
|
|
|
+ struct dp_bridge_hpd_private, work);
|
|
|
+
|
|
|
+ mutex_lock(&bridge_hpd->hpd_lock);
|
|
|
+
|
|
|
+ if (bridge_hpd->hpd_irq)
|
|
|
+ dp_bridge_hpd_attention(bridge_hpd);
|
|
|
+ else
|
|
|
+ dp_bridge_hpd_connect(bridge_hpd, bridge_hpd->hpd);
|
|
|
+
|
|
|
+ mutex_unlock(&bridge_hpd->hpd_lock);
|
|
|
+}
|
|
|
+
|
|
|
+static int dp_bridge_hpd_simulate_connect(struct dp_hpd *dp_hpd, bool hpd)
|
|
|
+{
|
|
|
+ int rc = 0;
|
|
|
+ struct dp_bridge_hpd_private *bridge_hpd;
|
|
|
+
|
|
|
+ if (!dp_hpd) {
|
|
|
+ pr_err("invalid input\n");
|
|
|
+ rc = -EINVAL;
|
|
|
+ goto error;
|
|
|
+ }
|
|
|
+
|
|
|
+ bridge_hpd = container_of(dp_hpd, struct dp_bridge_hpd_private, base);
|
|
|
+
|
|
|
+ dp_bridge_hpd_connect(bridge_hpd, hpd);
|
|
|
+error:
|
|
|
+ return rc;
|
|
|
+}
|
|
|
+
|
|
|
+static int dp_bridge_hpd_simulate_attention(struct dp_hpd *dp_hpd, int vdo)
|
|
|
+{
|
|
|
+ int rc = 0;
|
|
|
+ struct dp_bridge_hpd_private *bridge_hpd;
|
|
|
+
|
|
|
+ if (!dp_hpd) {
|
|
|
+ pr_err("invalid input\n");
|
|
|
+ rc = -EINVAL;
|
|
|
+ goto error;
|
|
|
+ }
|
|
|
+
|
|
|
+ bridge_hpd = container_of(dp_hpd, struct dp_bridge_hpd_private, base);
|
|
|
+
|
|
|
+ dp_bridge_hpd_attention(bridge_hpd);
|
|
|
+error:
|
|
|
+ return rc;
|
|
|
+}
|
|
|
+
|
|
|
+static int dp_bridge_hpd_cb(void *dp_hpd, bool hpd, bool hpd_irq)
|
|
|
+{
|
|
|
+ struct dp_bridge_hpd_private *bridge_hpd = dp_hpd;
|
|
|
+
|
|
|
+ mutex_lock(&bridge_hpd->hpd_lock);
|
|
|
+
|
|
|
+ bridge_hpd->hpd = hpd;
|
|
|
+ bridge_hpd->hpd_irq = hpd_irq;
|
|
|
+ queue_delayed_work(system_wq, &bridge_hpd->work, 0);
|
|
|
+
|
|
|
+ mutex_unlock(&bridge_hpd->hpd_lock);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int dp_bridge_hpd_register(struct dp_hpd *dp_hpd)
|
|
|
+{
|
|
|
+ struct dp_bridge_hpd_private *bridge_hpd;
|
|
|
+
|
|
|
+ if (!dp_hpd)
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ bridge_hpd = container_of(dp_hpd, struct dp_bridge_hpd_private, base);
|
|
|
+
|
|
|
+ return bridge_hpd->bridge->register_hpd(bridge_hpd->bridge,
|
|
|
+ dp_bridge_hpd_cb, bridge_hpd);
|
|
|
+}
|
|
|
+
|
|
|
+struct dp_hpd *dp_bridge_hpd_get(struct device *dev,
|
|
|
+ struct dp_hpd_cb *cb, struct dp_aux_bridge *aux_bridge)
|
|
|
+{
|
|
|
+ int rc = 0;
|
|
|
+ struct dp_bridge_hpd_private *bridge_hpd;
|
|
|
+
|
|
|
+ if (!dev || !cb) {
|
|
|
+ pr_err("invalid device\n");
|
|
|
+ rc = -EINVAL;
|
|
|
+ goto error;
|
|
|
+ }
|
|
|
+
|
|
|
+ bridge_hpd = devm_kzalloc(dev, sizeof(*bridge_hpd), GFP_KERNEL);
|
|
|
+ if (!bridge_hpd) {
|
|
|
+ rc = -ENOMEM;
|
|
|
+ goto error;
|
|
|
+ }
|
|
|
+
|
|
|
+ bridge_hpd->dev = dev;
|
|
|
+ bridge_hpd->cb = cb;
|
|
|
+ bridge_hpd->bridge = aux_bridge;
|
|
|
+ mutex_init(&bridge_hpd->hpd_lock);
|
|
|
+ INIT_DELAYED_WORK(&bridge_hpd->work, dp_bridge_hpd_work);
|
|
|
+ bridge_hpd->base.simulate_connect = dp_bridge_hpd_simulate_connect;
|
|
|
+ bridge_hpd->base.simulate_attention = dp_bridge_hpd_simulate_attention;
|
|
|
+ bridge_hpd->base.register_hpd = dp_bridge_hpd_register;
|
|
|
+
|
|
|
+ return &bridge_hpd->base;
|
|
|
+error:
|
|
|
+ return ERR_PTR(rc);
|
|
|
+}
|
|
|
+
|
|
|
+void dp_bridge_hpd_put(struct dp_hpd *dp_hpd)
|
|
|
+{
|
|
|
+ struct dp_bridge_hpd_private *bridge_hpd;
|
|
|
+
|
|
|
+ if (!dp_hpd)
|
|
|
+ return;
|
|
|
+
|
|
|
+ bridge_hpd = container_of(dp_hpd, struct dp_bridge_hpd_private, base);
|
|
|
+
|
|
|
+ devm_kfree(bridge_hpd->dev, bridge_hpd);
|
|
|
+}
|