usb: musb: add softconnect for host mode

Add a debugfs interface - softconnect - for host mode to
connect/disconnect the devices without physically remove the
them.

This adds the capability to re-enumerate the devices which are
permanently mounted on the board with the MUSB controller
together.

Signed-off-by: Bin Liu <b-liu@ti.com>
Signed-off-by: Felipe Balbi <balbi@ti.com>
diff --git a/drivers/usb/musb/musb_debugfs.c b/drivers/usb/musb/musb_debugfs.c
index 04382ec..9b22d94 100644
--- a/drivers/usb/musb/musb_debugfs.c
+++ b/drivers/usb/musb/musb_debugfs.c
@@ -245,6 +245,90 @@
 	.release		= single_release,
 };
 
+static int musb_softconnect_show(struct seq_file *s, void *unused)
+{
+	struct musb	*musb = s->private;
+	u8		reg;
+	int		connect;
+
+	switch (musb->xceiv->otg->state) {
+	case OTG_STATE_A_HOST:
+	case OTG_STATE_A_WAIT_BCON:
+		reg = musb_readb(musb->mregs, MUSB_DEVCTL);
+		connect = reg & MUSB_DEVCTL_SESSION ? 1 : 0;
+		break;
+	default:
+		connect = -1;
+	}
+
+	seq_printf(s, "%d\n", connect);
+
+	return 0;
+}
+
+static int musb_softconnect_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, musb_softconnect_show, inode->i_private);
+}
+
+static ssize_t musb_softconnect_write(struct file *file,
+		const char __user *ubuf, size_t count, loff_t *ppos)
+{
+	struct seq_file		*s = file->private_data;
+	struct musb		*musb = s->private;
+	char			buf[2];
+	u8			reg;
+
+	memset(buf, 0x00, sizeof(buf));
+
+	if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count)))
+		return -EFAULT;
+
+	if (!strncmp(buf, "0", 1)) {
+		switch (musb->xceiv->otg->state) {
+		case OTG_STATE_A_HOST:
+			musb_root_disconnect(musb);
+			reg = musb_readb(musb->mregs, MUSB_DEVCTL);
+			reg &= ~MUSB_DEVCTL_SESSION;
+			musb_writeb(musb->mregs, MUSB_DEVCTL, reg);
+			break;
+		default:
+			break;
+		}
+	} else if (!strncmp(buf, "1", 1)) {
+		switch (musb->xceiv->otg->state) {
+		case OTG_STATE_A_WAIT_BCON:
+			/*
+			 * musb_save_context() called in musb_runtime_suspend()
+			 * might cache devctl with SESSION bit cleared during
+			 * soft-disconnect, so specifically set SESSION bit
+			 * here to preserve it for musb_runtime_resume().
+			 */
+			musb->context.devctl |= MUSB_DEVCTL_SESSION;
+			reg = musb_readb(musb->mregs, MUSB_DEVCTL);
+			reg |= MUSB_DEVCTL_SESSION;
+			musb_writeb(musb->mregs, MUSB_DEVCTL, reg);
+			break;
+		default:
+			break;
+		}
+	}
+
+	return count;
+}
+
+/*
+ * In host mode, connect/disconnect the bus without physically
+ * remove the devices.
+ */
+static const struct file_operations musb_softconnect_fops = {
+	.open			= musb_softconnect_open,
+	.write			= musb_softconnect_write,
+	.read			= seq_read,
+	.llseek			= seq_lseek,
+	.release		= single_release,
+};
+
 int musb_init_debugfs(struct musb *musb)
 {
 	struct dentry		*root;
@@ -271,6 +355,13 @@
 		goto err1;
 	}
 
+	file = debugfs_create_file("softconnect", S_IRUGO | S_IWUSR,
+			root, musb, &musb_softconnect_fops);
+	if (!file) {
+		ret = -ENOMEM;
+		goto err1;
+	}
+
 	musb->debugfs_root = root;
 
 	return 0;