From 87f295ccaf081b4ef06218fc8028d3c3d075ede7 Mon Sep 17 00:00:00 2001
From: hongjiujin <hongjiujin@xxx.com>
Date: Tue, 2 Jan 2018 10:39:04 +0800
Subject: [PATCH] imx6: chipidea
The hardware didn't use OTG_ID pin in design and use USB A for OTG.
Chipidea will initialize otg to HOST once boot completed, which read OTGSC_IDIE.
So we implement otg role switch in SW only(/sys/kernel/debug/ci_hdrc.0/otgswitch).
---
debug.c | 110 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 110 insertions(+)
diff --git a/debug.c b/debug.c
index a4f7db2..b215674 100644
--- a/debug.c
+++ b/debug.c
@@ -16,6 +16,7 @@
#include "udc.h"
#include "bits.h"
#include "otg.h"
+#include "host.h"
/**
* ci_device_show: prints information about device capabilities and status
@@ -343,6 +344,110 @@ static const struct file_operations ci_role_fops = {
.release = single_release,
};
+#define CI_VBUS_STABLE_TIMEOUT_MS 100
+
+void handle_otg_switch(struct ci_hdrc *ci, enum ci_role role)
+{
+ int ret = 0;
+
+ if (role != ci->role) {
+ dev_dbg(ci->dev, "switching from %s to %s
",
+ ci_role(ci)->name, ci->roles[role]->name);
+
+ while (ci_hdrc_host_has_device(ci)) {
+ enable_irq(ci->irq);
+ usleep_range(10000, 15000);
+ disable_irq_nosync(ci->irq);
+ }
+
+ ci_role_stop(ci);
+
+ if (role == CI_ROLE_GADGET) {
+ /* wait vbus lower than OTGSC_BSV */
+ ret = hw_wait_reg(ci, OP_OTGSC, OTGSC_BSV, 0,
+ CI_VBUS_STABLE_TIMEOUT_MS);
+ }
+ else if (ci->vbus_active)
+ /*
+ * If the role switch happens(e.g. during
+ * system sleep), and we lose vbus drop
+ * event, disconnect gadget for it before
+ * start host.
+ */
+ usb_gadget_vbus_disconnect(&ci->gadget);
+
+ ci_role_start(ci, role);
+ /*
+ * If the role switch happens(e.g. during system
+ * sleep) and vbus keeps on afterwards, we connect
+ * gadget as vbus connect event lost.
+ */
+ if (ret == -ETIMEDOUT)
+ usb_gadget_vbus_connect(&ci->gadget);
+ }
+}
+
+static int ci_otgswitch_show(struct seq_file *s, void *data)
+{
+ struct ci_hdrc *ci = s->private;
+
+ seq_printf(s, "%s
", ci_role(ci)->name);
+
+ return 0;
+}
+
+static ssize_t ci_otgswitch_write(struct file *file, const char __user *ubuf,
+ size_t count, loff_t *ppos)
+{
+ struct seq_file *s = file->private_data;
+ struct ci_hdrc *ci = s->private;
+ enum ci_role role;
+ char buf[8];
+ int ret;
+
+ if (copy_from_user(buf, ubuf, min_t(size_t, sizeof(buf) - 1, count)))
+ return -EFAULT;
+
+ for (role = CI_ROLE_HOST; role < CI_ROLE_END; role++)
+ if (ci->roles[role] &&
+ !strncmp(buf, ci->roles[role]->name,
+ strlen(ci->roles[role]->name)))
+ break;
+
+ if (role == CI_ROLE_END || role == ci->role)
+ return -EINVAL;
+
+ pm_runtime_get_sync(ci->dev);
+ disable_irq(ci->irq);
+
+ if (role == CI_ROLE_HOST) {
+ ci->fsm.id = 0;
+
+ } else if (role == CI_ROLE_GADGET) {
+ ci->fsm.id = 1;
+ }
+
+ handle_otg_switch(ci, role);
+ enable_irq(ci->irq);
+ pm_runtime_put_sync(ci->dev);
+
+ return ret ? ret : count;
+}
+
+
+static int ci_otgswitch_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, ci_otgswitch_show, inode->i_private);
+}
+
+static const struct file_operations ci_otgswitch_fops = {
+ .open = ci_otgswitch_open,
+ .write = ci_otgswitch_write,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
static int ci_registers_show(struct seq_file *s, void *unused)
{
struct ci_hdrc *ci = s->private;
@@ -433,6 +538,11 @@ int dbg_create_files(struct ci_hdrc *ci)
if (!dent)
goto err;
+ dent = debugfs_create_file("otgswitch", S_IRUGO | S_IWUSR, ci->debugfs, ci,
+ &ci_otgswitch_fops);
+ if (!dent)
+ goto err;
+
dent = debugfs_create_file("registers", S_IRUGO, ci->debugfs, ci,
&ci_registers_fops);
--
2.7.4