[v10,08/14] usb: otg: add OTG/dual-role core

Message ID 1465564043-27163-9-git-send-email-rogerq@ti.com
State New
Headers show

Commit Message

Roger Quadros June 10, 2016, 1:07 p.m.
It provides APIs for the following tasks

- Registering an OTG/dual-role capable controller
- Registering Host and Gadget controllers to OTG core
- Providing inputs to and kicking the OTG state machine

Provide a dual-role device (DRD) state machine.
DRD mode is a reduced functionality OTG mode. In this mode
we don't support SRP, HNP and dynamic role-swap.

In DRD operation, the controller mode (Host or Peripheral)
is decided based on the ID pin status. Once a cable plug (Type-A
or Type-B) is attached the controller selects the state
and doesn't change till the cable in unplugged and a different
cable type is inserted.

As we don't need most of the complex OTG states and OTG timers
we implement a lean DRD state machine in usb-otg.c.
The DRD state machine is only interested in 2 hardware inputs
'id' and 'b_sess_vld'.

Signed-off-by: Roger Quadros <rogerq@ti.com>

---
 drivers/usb/Kconfig          |  18 +
 drivers/usb/Makefile         |   1 +
 drivers/usb/common/Makefile  |   6 +-
 drivers/usb/common/usb-otg.c | 906 +++++++++++++++++++++++++++++++++++++++++++
 drivers/usb/core/Kconfig     |  14 -
 drivers/usb/gadget/Kconfig   |   1 +
 include/linux/usb/gadget.h   |   2 +
 include/linux/usb/hcd.h      |   1 +
 include/linux/usb/otg-fsm.h  |   7 +
 include/linux/usb/otg.h      | 180 ++++++++-
 10 files changed, 1106 insertions(+), 30 deletions(-)
 create mode 100644 drivers/usb/common/usb-otg.c

-- 
2.7.4

--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Comments

Roger Quadros June 22, 2016, 7:49 a.m. | #1
Hi Felipe,

On 22/06/16 09:56, Felipe Balbi wrote:
> 

> Hi,

> 

> Peter Chen <hzpeterchen@gmail.com> writes:

>>> Peter Chen <hzpeterchen@gmail.com> writes:

>>>>>>> So far, I haven't seen anybody talking about real USB OTG (the spec)

>>>>>>> when they say OTG. Usually they just mean "a method for swapping between

>>>>>>> host and peripheral roles, but we really don't want all the extra cost

>>>>>>> of the OTG specification".

>>>>>>>

>>>>>>

>>>>>> That's what I thought before, but the request from the Marketing guy is

>>>>>> "To prove the SoC is OTG compliance, support HNP and SRP", don't you

>>>>>> see the SoC reference manual say "it supports HNP and SRP"?

>>>>>>

>>>>>> If there is no request, who else wants to implement so complicated FSM

>>>>>> but seldom use cases, and go to pass OTG compliance test (tested by PET).

>>>>>

>>>>> I stand corrected :-)

>>>>>

>>>>> So there is one user for this layer. And this user has its own role

>>>>> control registers. I'm not convinced we need this large generic layer

>>>>> for one user.

>>>>>

>>>>

>>>> You mean chipidea or dwc3? I have more comments below.

>>>

>>> chipidea. From the point of OTG (or DRD) dwc3 is very

>>> self-sufficient. HW itself tracks state machine, much like MUSB does.

>>

>> You mean HW can do state machine switch? If we are A device,

>> - Does the hardware knows if B device is HNP enabled or not?

> 

> that's enabled through control message, keep a flag.

> 

>> - And if B device is HNP enabled, does it can switch itself from host

>> to peripheral when the B device is disconnected (a_suspend->a_peripheral)

> 

> It cannot. It must rely on hnp polling which is, again, a control message.

> 

>> Does hardware can really follow Figure 7-1: OTG A-device with HNP State

>> Diagram at On-The-Go and Embedded Host Supplement to the USB Revision

>> 2.0 Specification? And can pass PET test?

> 

> Seriously, what does this add to the conversation? It has already been

> stated that there's nobody asking for OTG certification on dwc3. So all

> of this is vaporware from the point of view of dwc3.

> 

>>>>>>>> For the real use case, some Carplay platforms need it.

>>>>>>>

>>>>>>> Carplay does *NOT* rely on OTG. Apple has its own proprietary and closed

>>>>>>> specification which is not OTG-compliant.

>>>>>>>

>>>>>>

>>>>>> Yes, it is not OTG-compliant, but it can co-work with some standard OTG FSM

>>>>>> states to finish role swap.

>>>>>

>>>>> What are you referring to as "finish role swap"? I don't get that.

>>>>

>>>> Change current role from host to peripheral.

>>>

>>> Okay, we have two scenarios here:

>>>

>>> 1. You need full OTG compliance

>>>

>>> 	For this, granted, you need the state machine if your HW doesn't

>>> 	track it. This is a given. With only one user, however, perhaps

>>> 	we don't need a generic layer. There are not enough different

>>> 	setups to design a good enough generic layer. We will end up

>>> 	with a pseudo-generic framework which is coupled with its only

>>> 	user.

>>>

>>> 2. Dual-role support, without OTG compliance

>>>

>>> 	In this case, you don't need a stack. All you need is a signal

>>> 	to tell you state of ID pin and another to tell you state of

>>> 	VBUS level. If you have those, you don't need to walk an OTG

>>> 	state machine at all. You don't need any of those quirky OTG

>>> 	timers, agreed?

>>>

>>> 	Given the above, why would you even want to use a subset of OTG

>>> 	state machine to implement something that's _usually_ as simple

>>> 	as:

>>>

>>> 8<----------------------------------------------------------------------

>>> 	vbus = read(VBUS_STATE); /* could be a gpio_get_value() */

>>>         id = read(ID_STATE); /* could be a gpio_get_value() */

>>>

>>>         set_role(id);

>>>         set_vbus(vbus);

>>> ------------------------------------------------------------------------

>>>

>>

>> In fact, the individual driver can do it by itself. The chipidea driver

>> handles OTG and dual-role well currently. By considering this OTG/DRD

>> framework is worthwhile or not, we would like to see if it can

>> simplify DRD design for each driver, and can benefit the platforms which

>> has different drivers for host and peripheral to finish the role switch

>> well.

> 

> simplify how?  By adding unnecessary workqueues and a level indirection

> that just goes back to the same driver?


What do you mean by same driver?
Gadget driver, host driver and PHY (or MUX) driver (for ID/VBUS) can be 3 totally
independent drivers unlike dwc3 where you have a single driver in control of
both host and gadget.

Questions not clear to me are:
1) Which driver handles ID/VBUS events and makes a decision to do the role swap?
Probably the PHY/MUX driver?
2) How does it perform the role swap? Probably a register write to the PHY/MUX
without needing to stop/start controllers? Easy case is both controllers can
run in co-existence without interference. Is there any platform other than dwc3
where this is not the case?
3) Even if host and gadget controllers can operate in coexistence, there is no need for
both to be running for embedded applications which are usually power conservative.
How can we achieve that?

> 

>>>> commit 11c011a5e777c83819078a18672543f04482b3ec

>>>> Author: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>

>>>> Date:   Thu May 19 11:12:56 2016 +0100

>>>>

>>>>     usb: echi-hcd: Add ehci_setup check before echi_shutdown

>>>>         

>>>>

>>>>

>>>> In some cases, the USB code (gadget/hcd->start/stop) needs to be called

>>>> during the role swap. For example, if you have mux driver, you may

>>>> need to call usb_remove_hcd when ID from 0 to 1. Without Roger's framework,

>>>> how can we do that?

>>>

>>> You don't really need to remove the gadget. Just mask its interrupts and

>>> ignore any calls to any gadget_driver ops, right? Likewise for

>>> XHCI. Just clear RUN/STOP and no events will ever reach XHCI. But, from

>>> the point of view of dwc3, it's simpler to unregister the platform

>>> device we create for xhci-plat.c. I need no changes in XHCI to do that

>>> and driver model will make sure to call xhci-plat's ->remove() which

>>> will handle everything for me correctly.

>>>

>>

>> I admit it can do in a IP driver, eg both host and peripheral for the

>> single IP, eg chipidea, dwc3, etc. But how can we clear RUN/STOP bit

>> or what else for HCD at mux driver?

> 

> dwc3's OTG block has control of that, however, what I'll do is

> platform_device_del() xhci-plat's device. Not one line changes inside

> XHCI.

> 


Let's talk about how non dwc3 based platforms can get it done.

Yoshihiro-san, could you please share your platform requirements from dual-role
perspective?

cheers,
-roger

Patch

diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig
index 8689dcb..ed596ec 100644
--- a/drivers/usb/Kconfig
+++ b/drivers/usb/Kconfig
@@ -32,6 +32,23 @@  if USB_SUPPORT
 config USB_COMMON
 	tristate
 
+config USB_OTG_CORE
+	tristate
+
+config USB_OTG
+	bool "OTG/Dual-role support"
+	depends on PM && USB && USB_GADGET
+	default n
+	---help---
+	  The most notable feature of USB OTG is support for a
+	  "Dual-Role" device, which can act as either a device
+	  or a host. The initial role is decided by the type of
+	  plug inserted and can be changed later when two dual
+	  role devices talk to each other.
+
+	  Select this only if your board has Mini-AB/Micro-AB
+	  connector.
+
 config USB_ARCH_HAS_HCD
 	def_bool y
 
@@ -40,6 +57,7 @@  config USB
 	tristate "Support for Host-side USB"
 	depends on USB_ARCH_HAS_HCD
 	select USB_COMMON
+	select USB_OTG_CORE
 	select NLS  # for UTF-8 strings
 	---help---
 	  Universal Serial Bus (USB) is a specification for a serial bus
diff --git a/drivers/usb/Makefile b/drivers/usb/Makefile
index dca7856..03f7204 100644
--- a/drivers/usb/Makefile
+++ b/drivers/usb/Makefile
@@ -59,5 +59,6 @@  obj-$(CONFIG_USB_RENESAS_USBHS)	+= renesas_usbhs/
 obj-$(CONFIG_USB_GADGET)	+= gadget/
 
 obj-$(CONFIG_USB_COMMON)	+= common/
+obj-$(CONFIG_USB_OTG_CORE)	+= common/
 
 obj-$(CONFIG_USBIP_CORE)	+= usbip/
diff --git a/drivers/usb/common/Makefile b/drivers/usb/common/Makefile
index f8f2c88..5122b3f 100644
--- a/drivers/usb/common/Makefile
+++ b/drivers/usb/common/Makefile
@@ -7,5 +7,7 @@  usb-common-y			  += common.o
 usb-common-$(CONFIG_USB_LED_TRIG) += led.o
 
 obj-$(CONFIG_USB_ULPI_BUS)	+= ulpi.o
-usbotg-y		:= usb-otg-fsm.o
-obj-$(CONFIG_USB_OTG)	+= usbotg.o
+ifeq ($(CONFIG_USB_OTG),y)
+usbotg-y		:= usb-otg.o usb-otg-fsm.o
+obj-$(CONFIG_USB_OTG_CORE)	+= usbotg.o
+endif
diff --git a/drivers/usb/common/usb-otg.c b/drivers/usb/common/usb-otg.c
new file mode 100644
index 0000000..4185954
--- /dev/null
+++ b/drivers/usb/common/usb-otg.c
@@ -0,0 +1,906 @@ 
+/**
+ * drivers/usb/common/usb-otg.c - USB OTG core
+ *
+ * Copyright (C) 2016 Texas Instruments Incorporated - http://www.ti.com
+ * Author: Roger Quadros <rogerq@ti.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License 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.
+ */
+
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/usb/of.h>
+#include <linux/usb/otg.h>
+#include <linux/usb/gadget.h>
+#include <linux/workqueue.h>
+
+/* OTG device list */
+LIST_HEAD(otg_list);
+static DEFINE_MUTEX(otg_list_mutex);
+
+static int usb_otg_hcd_is_primary_hcd(struct usb_hcd *hcd)
+{
+	if (!hcd->primary_hcd)
+		return 1;
+	return hcd == hcd->primary_hcd;
+}
+
+/**
+ * usb_otg_get_data() - get usb_otg data structa
+ * @otg_dev:	OTG controller device
+ *
+ * Check if the OTG device is in our OTG list and return
+ * usb_otg data, else NULL.
+ *
+ * otg_list_mutex must be held.
+ *
+ * Return: usb_otg data on success, NULL otherwise.
+ */
+static struct usb_otg *usb_otg_get_data(struct device *otg_dev)
+{
+	struct usb_otg *otg;
+
+	if (!otg_dev)
+		return NULL;
+
+	list_for_each_entry(otg, &otg_list, list) {
+		if (otg->dev == otg_dev)
+			return otg;
+	}
+
+	return NULL;
+}
+
+/**
+ * usb_otg_start_host() - start/stop the host controller
+ * @otg:	usb_otg instance
+ * @on:		true to start, false to stop
+ *
+ * Start/stop the USB host controller. This function is meant
+ * for use by the OTG controller driver.
+ *
+ * Return: 0 on success, error value otherwise.
+ */
+int usb_otg_start_host(struct usb_otg *otg, int on)
+{
+	struct otg_hcd_ops *hcd_ops = otg->hcd_ops;
+	int ret;
+
+	dev_dbg(otg->dev, "otg: %s %d\n", __func__, on);
+	if (!otg->host) {
+		WARN_ONCE(1, "otg: fsm running without host\n");
+		return 0;
+	}
+
+	if (on) {
+		if (otg->flags & OTG_FLAG_HOST_RUNNING)
+			return 0;
+
+		/* start host */
+		ret = hcd_ops->add(otg->primary_hcd.hcd,
+				   otg->primary_hcd.irqnum,
+				   otg->primary_hcd.irqflags);
+		if (ret) {
+			dev_err(otg->dev, "otg: host add failed %d\n", ret);
+			return ret;
+		}
+
+		if (otg->shared_hcd.hcd) {
+			ret = hcd_ops->add(otg->shared_hcd.hcd,
+					   otg->shared_hcd.irqnum,
+					   otg->shared_hcd.irqflags);
+			if (ret) {
+				dev_err(otg->dev, "otg: shared host add failed %d\n",
+					ret);
+				hcd_ops->remove(otg->primary_hcd.hcd);
+				return ret;
+			}
+		}
+		otg->flags |= OTG_FLAG_HOST_RUNNING;
+	} else {
+		if (!(otg->flags & OTG_FLAG_HOST_RUNNING))
+			return 0;
+
+		otg->flags &= ~OTG_FLAG_HOST_RUNNING;
+
+		/* stop host */
+		if (otg->shared_hcd.hcd)
+			hcd_ops->remove(otg->shared_hcd.hcd);
+
+		hcd_ops->remove(otg->primary_hcd.hcd);
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(usb_otg_start_host);
+
+/**
+ * usb_otg_start_gadget() - start/stop the gadget controller
+ * @otg:	usb_otg instance
+ * @on:		true to start, false to stop
+ *
+ * Start/stop the USB gadget controller. This function is meant
+ * for use by the OTG controller driver.
+ *
+ * Return: 0 on success, error value otherwise.
+ */
+int usb_otg_start_gadget(struct usb_otg *otg, int on)
+{
+	struct usb_gadget *gadget = otg->gadget;
+	int ret;
+
+	dev_dbg(otg->dev, "otg: %s %d\n", __func__, on);
+	if (!gadget) {
+		WARN_ONCE(1, "otg: fsm running without gadget\n");
+		return 0;
+	}
+
+	if (on) {
+		if (otg->flags & OTG_FLAG_GADGET_RUNNING)
+			return 0;
+
+		ret = otg->gadget_ops->start(otg->gadget);
+		if (ret) {
+			dev_err(otg->dev, "otg: gadget start failed: %d\n",
+				ret);
+			return ret;
+		}
+
+		otg->flags |= OTG_FLAG_GADGET_RUNNING;
+	} else {
+		if (!(otg->flags & OTG_FLAG_GADGET_RUNNING))
+			return 0;
+
+		ret = otg->gadget_ops->stop(otg->gadget);
+		if (ret) {
+			dev_err(otg->dev, "otg: gadget stop failed: %d\n",
+				ret);
+			return ret;
+		}
+		otg->flags &= ~OTG_FLAG_GADGET_RUNNING;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(usb_otg_start_gadget);
+
+/**
+ * drd_set_protocol() -  Set USB protocol if possible
+ * @fsm:	DRD FSM instance
+ * @protocol:	USB protocol to set the state machine to
+ *
+ * Sets the OTG FSM protocol to @protocol if it changed.
+ * fsm->lock must be held.
+ *
+ * Return: 0 on success, error value otherwise.
+ */
+static int drd_set_protocol(struct otg_fsm *fsm, int protocol)
+{
+	struct usb_otg *otg = container_of(fsm, struct usb_otg, fsm);
+	int ret = 0;
+
+	if (fsm->protocol != protocol) {
+		dev_dbg(otg->dev, "otg: changing role fsm->protocol= %d; new protocol= %d\n",
+			fsm->protocol, protocol);
+		/* stop old protocol */
+		if (fsm->protocol == PROTO_HOST) {
+			ret = otg_start_host(otg, 0);
+		} else if (fsm->protocol == PROTO_GADGET) {
+			otg->gadget_ops->connect_control(otg->gadget, false);
+			ret = otg_start_gadget(otg, 0);
+		}
+
+		if (ret)
+			return ret;
+
+		/* start new protocol */
+		if (protocol == PROTO_HOST) {
+			ret = otg_start_host(otg, 1);
+		} else if (protocol == PROTO_GADGET) {
+			ret = otg_start_gadget(otg, 1);
+			otg->gadget_ops->connect_control(otg->gadget, true);
+		}
+
+		if (ret)
+			return ret;
+
+		fsm->protocol = protocol;
+		return 0;
+	}
+
+	return 0;
+}
+
+/**
+ * drd_set_state() - Set the DRD state machine state.
+ * @fsm:	DRD FSM instance
+ * @new_state:	the new state the DRD FSM must be set to
+ *
+ * Sets the state of the DRD state machine.
+ * fsm->lock must be held.
+ */
+static void drd_set_state(struct otg_fsm *fsm, enum usb_otg_state new_state)
+{
+	struct usb_otg *otg = container_of(fsm, struct usb_otg, fsm);
+
+	if (otg->state == new_state)
+		return;
+
+	fsm->state_changed = 1;
+	dev_dbg(otg->dev, "otg: set state: %s\n",
+		usb_otg_state_string(new_state));
+	switch (new_state) {
+	case OTG_STATE_B_IDLE:
+		drd_set_protocol(fsm, PROTO_UNDEF);
+		otg_drv_vbus(otg, 0);
+		break;
+	case OTG_STATE_B_PERIPHERAL:
+		drd_set_protocol(fsm, PROTO_GADGET);
+		otg_drv_vbus(otg, 0);
+		break;
+	case OTG_STATE_A_HOST:
+		drd_set_protocol(fsm, PROTO_HOST);
+		otg_drv_vbus(otg, 1);
+		break;
+	default:
+		dev_warn(otg->dev, "%s: otg: invalid state: %s\n",
+			 __func__, usb_otg_state_string(new_state));
+		break;
+	}
+
+	otg->state = new_state;
+}
+
+/**
+ * drd_statemachine() - DRD state change judgement
+ * @otg:	usb_otg instance
+ *
+ * Checks the state machine inputs and state and makes a state change
+ * if required.
+ *
+ * For DRD we're only interested in some of the OTG states
+ * i.e. OTG_STATE_B_IDLE: both peripheral and host are stopped
+ *	OTG_STATE_B_PERIPHERAL: peripheral active
+ *	OTG_STATE_A_HOST: host active
+ * we're only interested in the following inputs
+ *	fsm->id, fsm->b_sess_vld
+ *
+ * Return: 0 if state wasn't changed, 1 if state changed.
+ */
+int drd_statemachine(struct usb_otg *otg)
+{
+	struct otg_fsm *fsm = &otg->fsm;
+	enum usb_otg_state state;
+	int ret;
+
+	mutex_lock(&fsm->lock);
+
+	fsm->state_changed = 0;
+	state = otg->state;
+
+	switch (state) {
+	case OTG_STATE_UNDEFINED:
+		if (!fsm->id)
+			drd_set_state(fsm, OTG_STATE_A_HOST);
+		else if (fsm->id && fsm->b_sess_vld)
+			drd_set_state(fsm, OTG_STATE_B_PERIPHERAL);
+		else
+			drd_set_state(fsm, OTG_STATE_B_IDLE);
+		break;
+	case OTG_STATE_B_IDLE:
+		if (!fsm->id)
+			drd_set_state(fsm, OTG_STATE_A_HOST);
+		else if (fsm->b_sess_vld)
+			drd_set_state(fsm, OTG_STATE_B_PERIPHERAL);
+		break;
+	case OTG_STATE_B_PERIPHERAL:
+		if (!fsm->id)
+			drd_set_state(fsm, OTG_STATE_A_HOST);
+		else if (!fsm->b_sess_vld)
+			drd_set_state(fsm, OTG_STATE_B_IDLE);
+		break;
+	case OTG_STATE_A_HOST:
+		if (fsm->id && fsm->b_sess_vld)
+			drd_set_state(fsm, OTG_STATE_B_PERIPHERAL);
+		else if (fsm->id && !fsm->b_sess_vld)
+			drd_set_state(fsm, OTG_STATE_B_IDLE);
+		break;
+
+	default:
+		dev_err(otg->dev, "%s: otg: invalid usb-drd state: %s\n",
+			__func__, usb_otg_state_string(state));
+		break;
+	}
+
+	ret = fsm->state_changed;
+	mutex_unlock(&fsm->lock);
+	dev_dbg(otg->dev, "otg: quit statemachine, changed %d\n",
+		fsm->state_changed);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(drd_statemachine);
+
+/**
+ * usb_drd_work() - Dual-role state machine work function
+ * @work: work_struct context
+ *
+ * Runs the DRD state machine. Scheduled whenever there is a change
+ * in FSM inputs.
+ */
+static void usb_drd_work(struct work_struct *work)
+{
+	struct usb_otg *otg = container_of(work, struct usb_otg, work);
+
+	pm_runtime_get_sync(otg->dev);
+	while (drd_statemachine(otg))
+		;
+	pm_runtime_put_sync(otg->dev);
+}
+
+/**
+ * usb_otg_register() - Register the OTG/dual-role device to OTG core
+ * @dev: OTG/dual-role controller device.
+ * @config: OTG configuration.
+ *
+ * Registers the OTG/dual-role controller device with the USB OTG core.
+ *
+ * Return: struct usb_otg * if success, ERR_PTR() otherwise.
+ */
+struct usb_otg *usb_otg_register(struct device *dev,
+				 struct usb_otg_config *config)
+{
+	struct usb_otg *otg;
+	int ret = 0;
+
+	if (!dev || !config || !config->fsm_ops)
+		return ERR_PTR(-EINVAL);
+
+	/* already in list? */
+	mutex_lock(&otg_list_mutex);
+	if (usb_otg_get_data(dev)) {
+		dev_err(dev, "otg: %s: device already in otg list\n",
+			__func__);
+		ret = -EINVAL;
+		goto unlock;
+	}
+
+	/* allocate and add to list */
+	otg = kzalloc(sizeof(*otg), GFP_KERNEL);
+	if (!otg) {
+		ret = -ENOMEM;
+		goto unlock;
+	}
+
+	otg->dev = dev;
+	/* otg->caps is controller caps + DT overrides */
+	otg->caps = *config->otg_caps;
+	ret = of_usb_update_otg_caps(dev->of_node, &otg->caps);
+	if (ret)
+		goto err_wq;
+
+	if ((otg->caps.hnp_support || otg->caps.srp_support ||
+	     otg->caps.adp_support) && !config->otg_work) {
+		dev_err(dev,
+			"otg: otg_work must be provided for OTG support\n");
+		ret = -EINVAL;
+		goto err_wq;
+	}
+
+	if (config->otg_work)	/* custom otg_work ? */
+		INIT_WORK(&otg->work, config->otg_work);
+	else
+		INIT_WORK(&otg->work, usb_drd_work);
+
+	otg->wq = create_freezable_workqueue("usb_otg");
+	if (!otg->wq) {
+		dev_err(dev, "otg: %s: can't create workqueue\n",
+			__func__);
+		ret = -ENOMEM;
+		goto err_wq;
+	}
+
+	/* set otg ops */
+	otg->fsm.ops = config->fsm_ops;
+
+	mutex_init(&otg->fsm.lock);
+
+	list_add_tail(&otg->list, &otg_list);
+	mutex_unlock(&otg_list_mutex);
+
+	return otg;
+
+err_wq:
+	kfree(otg);
+unlock:
+	mutex_unlock(&otg_list_mutex);
+	return ERR_PTR(ret);
+}
+EXPORT_SYMBOL_GPL(usb_otg_register);
+
+/**
+ * usb_otg_unregister() - Unregister the OTG/dual-role device from USB OTG core
+ * @dev: OTG controller device.
+ *
+ * Unregisters the OTG/dual-role controller device from USB OTG core.
+ * Prevents unregistering till both the associated Host and Gadget controllers
+ * have unregistered from the OTG core.
+ *
+ * Return: 0 on success, error value otherwise.
+ */
+int usb_otg_unregister(struct device *dev)
+{
+	struct usb_otg *otg;
+
+	mutex_lock(&otg_list_mutex);
+	otg = usb_otg_get_data(dev);
+	if (!otg) {
+		dev_err(dev, "otg: %s: device not in otg list\n",
+			__func__);
+		mutex_unlock(&otg_list_mutex);
+		return -EINVAL;
+	}
+
+	/* prevent unregister till both host & gadget have unregistered */
+	if (otg->host || otg->gadget) {
+		dev_err(dev, "otg: %s: host/gadget still registered\n",
+			__func__);
+		mutex_unlock(&otg_list_mutex);
+		return -EBUSY;
+	}
+
+	/* OTG FSM is halted when host/gadget unregistered */
+	destroy_workqueue(otg->wq);
+
+	/* remove from otg list */
+	list_del(&otg->list);
+	kfree(otg);
+	mutex_unlock(&otg_list_mutex);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(usb_otg_unregister);
+
+/**
+ * usb_otg_start_fsm() - Start the OTG FSM
+ * @otg:	usb_otg instance
+ *
+ * Start the OTG FSM if we can. The HCD, UDC and gadget function driver
+ * must be ready for the OTG FSM to start.
+ *
+ * fsm->lock must be held.
+ */
+static void usb_otg_start_fsm(struct usb_otg *otg)
+{
+	struct otg_fsm *fsm = &otg->fsm;
+
+	if (fsm->running)
+		goto kick_fsm;
+
+	if (!otg->host) {
+		dev_info(otg->dev, "otg: can't start till host registers\n");
+		return;
+	}
+
+	if (!otg->gadget) {
+		dev_info(otg->dev,
+			 "otg: can't start till gadget UDC registers\n");
+		return;
+	}
+
+	if (!otg->gadget_ready) {
+		dev_info(otg->dev,
+			 "otg: can't start till gadget function registers\n");
+		return;
+	}
+
+	fsm->running = true;
+kick_fsm:
+	queue_work(otg->wq, &otg->work);
+}
+
+/**
+ * usb_otg_stop_fsm() - Stop the OTG FSM
+ * @otg:	usb_otg instance
+ *
+ * Stops the HCD, UDC and the OTG FSM.
+ *
+ * fsm->lock must be held.
+ */
+static void usb_otg_stop_fsm(struct usb_otg *otg)
+{
+	struct otg_fsm *fsm = &otg->fsm;
+
+	if (!fsm->running)
+		return;
+
+	/* no more new events queued */
+	fsm->running = false;
+
+	flush_workqueue(otg->wq);
+	otg->state = OTG_STATE_UNDEFINED;
+
+	/* stop host/gadget immediately */
+	if (fsm->protocol == PROTO_HOST) {
+		otg_start_host(otg, 0);
+	} else if (fsm->protocol == PROTO_GADGET) {
+		otg->gadget_ops->connect_control(otg->gadget, false);
+		otg_start_gadget(otg, 0);
+	}
+	fsm->protocol = PROTO_UNDEF;
+}
+
+/**
+ * usb_otg_sync_inputs() - Sync OTG inputs with the OTG state machine
+ * @otg:	usb_otg instance
+ *
+ * Used by the OTG driver to update the inputs to the OTG
+ * state machine.
+ *
+ * Can be called in IRQ context.
+ */
+void usb_otg_sync_inputs(struct usb_otg *otg)
+{
+	/* Don't kick FSM till it has started */
+	if (!otg->fsm.running)
+		return;
+
+	/* Kick FSM */
+	queue_work(otg->wq, &otg->work);
+}
+EXPORT_SYMBOL_GPL(usb_otg_sync_inputs);
+
+/**
+ * usb_otg_kick_fsm() - Kick the OTG state machine
+ * @otg_dev:	OTG controller device
+ *
+ * Used by USB host/gadget stack to sync OTG related
+ * events to the OTG state machine.
+ * e.g. change in host_bus->b_hnp_enable, gadget->b_hnp_enable
+ *
+ * Return: 0 on success, error value otherwise.
+ */
+int usb_otg_kick_fsm(struct device *otg_dev)
+{
+	struct usb_otg *otg;
+
+	mutex_lock(&otg_list_mutex);
+	otg = usb_otg_get_data(otg_dev);
+	mutex_unlock(&otg_list_mutex);
+	if (!otg) {
+		dev_dbg(otg_dev, "otg: %s: invalid otg device\n",
+			__func__);
+		return -ENODEV;
+	}
+
+	usb_otg_sync_inputs(otg);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(usb_otg_kick_fsm);
+
+/**
+ * usb_otg_register_hcd() - Register the host controller to OTG core
+ * @hcd:	host controller
+ * @irqnum:	interrupt number
+ * @irqflags:	interrupt flags
+ * @ops:	HCD ops to interface with the HCD
+ *
+ * This is used by the USB Host stack to register the host controller
+ * to the OTG core. Host controller must not be started by the
+ * caller as it is left upto the OTG state machine to do so.
+ * hcd->otg_dev must contain the related otg controller device.
+ *
+ * Return: 0 on success, error value otherwise.
+ */
+int usb_otg_register_hcd(struct usb_hcd *hcd, unsigned int irqnum,
+			 unsigned long irqflags, struct otg_hcd_ops *ops)
+{
+	struct usb_otg *otg;
+	struct device *hcd_dev = hcd->self.controller;
+	struct device *otg_dev = hcd->otg_dev;
+
+	if (!otg_dev)
+		return -EINVAL;
+
+	/* we're otg but otg controller might not yet be registered */
+	mutex_lock(&otg_list_mutex);
+	otg = usb_otg_get_data(otg_dev);
+	mutex_unlock(&otg_list_mutex);
+	if (!otg) {
+		dev_dbg(hcd_dev,
+			"otg: controller not yet registered. deferring.\n");
+		return -EPROBE_DEFER;
+	}
+
+	/* HCD will be started by OTG fsm when needed */
+	mutex_lock(&otg->fsm.lock);
+	if (otg->primary_hcd.hcd) {
+		/* probably a shared HCD ? */
+		if (usb_otg_hcd_is_primary_hcd(hcd)) {
+			dev_err(otg_dev, "otg: primary host already registered\n");
+			goto err;
+		}
+
+		if (hcd->shared_hcd == otg->primary_hcd.hcd) {
+			if (otg->shared_hcd.hcd) {
+				dev_err(otg_dev, "otg: shared host already registered\n");
+				goto err;
+			}
+
+			otg->shared_hcd.hcd = hcd;
+			otg->shared_hcd.irqnum = irqnum;
+			otg->shared_hcd.irqflags = irqflags;
+			otg->shared_hcd.ops = ops;
+			dev_info(otg_dev, "otg: shared host %s registered\n",
+				 dev_name(hcd->self.controller));
+		} else {
+			dev_err(otg_dev, "otg: invalid shared host %s\n",
+				dev_name(hcd->self.controller));
+			goto err;
+		}
+	} else {
+		if (!usb_otg_hcd_is_primary_hcd(hcd)) {
+			dev_err(otg_dev, "otg: primary host must be registered first\n");
+			goto err;
+		}
+
+		otg->primary_hcd.hcd = hcd;
+		otg->primary_hcd.irqnum = irqnum;
+		otg->primary_hcd.irqflags = irqflags;
+		otg->primary_hcd.ops = ops;
+		otg->hcd_ops = ops;
+		dev_info(otg_dev, "otg: primary host %s registered\n",
+			 dev_name(hcd->self.controller));
+	}
+
+	/*
+	 * we're ready only if we have shared HCD
+	 * or we don't need shared HCD.
+	 */
+	if (otg->shared_hcd.hcd || !otg->primary_hcd.hcd->shared_hcd) {
+		otg->host = hcd_to_bus(hcd);
+		/* FIXME: set bus->otg_port if this is true OTG port with HNP */
+
+		/* start FSM */
+		usb_otg_start_fsm(otg);
+	} else {
+		dev_dbg(otg_dev, "otg: can't start till shared host registers\n");
+	}
+
+	mutex_unlock(&otg->fsm.lock);
+
+	return 0;
+
+err:
+	mutex_unlock(&otg->fsm.lock);
+	return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(usb_otg_register_hcd);
+
+/**
+ * usb_otg_unregister_hcd() - Unregister the host controller from OTG core
+ * @hcd:	host controller device
+ *
+ * This is used by the USB Host stack to unregister the host controller
+ * from the OTG core. Ensures that host controller is not running
+ * on successful return.
+ *
+ * Returns: 0 on success, error value otherwise.
+ */
+int usb_otg_unregister_hcd(struct usb_hcd *hcd)
+{
+	struct usb_otg *otg;
+	struct device *hcd_dev = hcd_to_bus(hcd)->controller;
+	struct device *otg_dev = hcd->otg_dev;
+
+	if (!otg_dev)
+		return -EINVAL;	/* we're definitely not OTG */
+
+	mutex_lock(&otg_list_mutex);
+	otg = usb_otg_get_data(otg_dev);
+	mutex_unlock(&otg_list_mutex);
+	if (!otg) {
+		dev_err(hcd_dev, "otg: host %s wasn't registered with otg\n",
+			dev_name(hcd_dev));
+		return -EINVAL;
+	}
+
+	mutex_lock(&otg->fsm.lock);
+	if (hcd == otg->primary_hcd.hcd) {
+		otg->primary_hcd.hcd = NULL;
+		dev_info(otg_dev, "otg: primary host %s unregistered\n",
+			 dev_name(hcd_dev));
+	} else if (hcd == otg->shared_hcd.hcd) {
+		otg->shared_hcd.hcd = NULL;
+		dev_info(otg_dev, "otg: shared host %s unregistered\n",
+			 dev_name(hcd_dev));
+	} else {
+		mutex_unlock(&otg->fsm.lock);
+		dev_err(otg_dev, "otg: host %s wasn't registered with otg\n",
+			dev_name(hcd_dev));
+		return -EINVAL;
+	}
+
+	/* stop FSM & Host */
+	usb_otg_stop_fsm(otg);
+	otg->host = NULL;
+
+	mutex_unlock(&otg->fsm.lock);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(usb_otg_unregister_hcd);
+
+/**
+ * usb_otg_register_gadget() - Register the gadget controller to OTG core
+ * @gadget:	gadget controller instance
+ * @ops:	gadget interface ops
+ *
+ * This is used by the USB gadget stack to register the gadget controller
+ * to the OTG core. Gadget controller must not be started by the
+ * caller as it is left upto the OTG state machine to do so.
+ *
+ * Returns: 0 on success, error value otherwise.
+ */
+int usb_otg_register_gadget(struct usb_gadget *gadget,
+			    struct otg_gadget_ops *ops)
+{
+	struct usb_otg *otg;
+	struct device *gadget_dev = &gadget->dev;
+	struct device *otg_dev = gadget->otg_dev;
+
+	if (!otg_dev)
+		return -EINVAL;	/* we're definitely not OTG */
+
+	/* we're otg but otg controller might not yet be registered */
+	mutex_lock(&otg_list_mutex);
+	otg = usb_otg_get_data(otg_dev);
+	mutex_unlock(&otg_list_mutex);
+	if (!otg) {
+		dev_dbg(gadget_dev,
+			"otg: controller not yet registered, deferring.\n");
+		return -EPROBE_DEFER;
+	}
+
+	mutex_lock(&otg->fsm.lock);
+	if (otg->gadget) {
+		dev_err(otg_dev, "otg: gadget already registered with otg\n");
+		mutex_unlock(&otg->fsm.lock);
+		return -EINVAL;
+	}
+
+	otg->gadget = gadget;
+	otg->gadget_ops = ops;
+	dev_info(otg_dev, "otg: gadget %s registered\n",
+		 dev_name(&gadget->dev));
+
+	/* FSM will be started in usb_otg_gadget_ready() */
+	mutex_unlock(&otg->fsm.lock);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(usb_otg_register_gadget);
+
+/**
+ * usb_otg_unregister_gadget() - Unregister the gadget controller from OTG core
+ * @gadget:	gadget controller
+ *
+ * This is used by the USB gadget stack to unregister the gadget controller
+ * from the OTG core. Ensures that gadget controller is not running
+ * on successful return.
+ *
+ * Returns: 0 on success, error value otherwise.
+ */
+int usb_otg_unregister_gadget(struct usb_gadget *gadget)
+{
+	struct usb_otg *otg;
+	struct device *gadget_dev = &gadget->dev;
+	struct device *otg_dev = gadget->otg_dev;
+
+	if (!otg_dev)
+		return -EINVAL;
+
+	mutex_lock(&otg_list_mutex);
+	otg = usb_otg_get_data(otg_dev);
+	mutex_unlock(&otg_list_mutex);
+	if (!otg) {
+		dev_err(gadget_dev,
+			"otg: gadget %s wasn't registered with otg\n",
+			dev_name(&gadget->dev));
+		return -EINVAL;
+	}
+
+	mutex_lock(&otg->fsm.lock);
+	if (otg->gadget != gadget) {
+		mutex_unlock(&otg->fsm.lock);
+		dev_err(otg_dev, "otg: gadget %s wasn't registered with otg\n",
+			dev_name(&gadget->dev));
+		return -EINVAL;
+	}
+
+	/* FSM must be stopped in usb_otg_gadget_ready() */
+	if (otg->gadget_ready) {
+		dev_err(otg_dev,
+			"otg: gadget %s unregistered before being unready, forcing stop\n",
+			dev_name(&gadget->dev));
+		usb_otg_stop_fsm(otg);
+	}
+
+	otg->gadget = NULL;
+	mutex_unlock(&otg->fsm.lock);
+
+	dev_info(otg_dev, "otg: gadget %s unregistered\n",
+		 dev_name(&gadget->dev));
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(usb_otg_unregister_gadget);
+
+/**
+ * usb_otg_gadget_ready() - Notify gadget function driver ready status
+ * @gadget:	gadget controller
+ * @ready:	0: function driver ready, 1: function driver not ready
+ *
+ * Notify the OTG core about status of the gadget function driver.
+ * As OTG core is responsible to start/stop the gadget controller, it
+ * must be aware when the gadget function driver is available or not.
+ * This function is used by the Gadget core to inform the OTG core
+ * about the gadget function driver readyness.
+ *
+ * Return: 0 on sucess, error value otherwise.
+ */
+int usb_otg_gadget_ready(struct usb_gadget *gadget, bool ready)
+{
+	struct usb_otg *otg;
+	struct device *gadget_dev = &gadget->dev;
+	struct device *otg_dev = gadget->otg_dev;
+
+	if (!otg_dev)
+		return -EINVAL;
+
+	mutex_lock(&otg_list_mutex);
+	otg = usb_otg_get_data(otg_dev);
+	mutex_unlock(&otg_list_mutex);
+	if (!otg) {
+		dev_err(gadget_dev,
+			"otg: gadget %s wasn't registered with otg\n",
+			dev_name(&gadget->dev));
+		return -EINVAL;
+	}
+
+	mutex_lock(&otg->fsm.lock);
+	if (otg->gadget != gadget) {
+		mutex_unlock(&otg->fsm.lock);
+		dev_err(otg_dev, "otg: gadget %s wasn't registered with otg\n",
+			dev_name(&gadget->dev));
+		return -EINVAL;
+	}
+
+	/* Start/stop FSM & gadget */
+	otg->gadget_ready = ready;
+	if (ready)
+		usb_otg_start_fsm(otg);
+	else
+		usb_otg_stop_fsm(otg);
+
+	dev_dbg(otg_dev, "otg: gadget %s %sready\n", dev_name(&gadget->dev),
+		ready ? "" : "not ");
+
+	mutex_unlock(&otg->fsm.lock);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(usb_otg_gadget_ready);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/usb/core/Kconfig b/drivers/usb/core/Kconfig
index ae228d0..37f8c54 100644
--- a/drivers/usb/core/Kconfig
+++ b/drivers/usb/core/Kconfig
@@ -41,20 +41,6 @@  config USB_DYNAMIC_MINORS
 
 	  If you are unsure about this, say N here.
 
-config USB_OTG
-	bool "OTG support"
-	depends on PM
-	default n
-	help
-	  The most notable feature of USB OTG is support for a
-	  "Dual-Role" device, which can act as either a device
-	  or a host. The initial role is decided by the type of
-	  plug inserted and can be changed later when two dual
-	  role devices talk to each other.
-
-	  Select this only if your board has Mini-AB/Micro-AB
-	  connector.
-
 config USB_OTG_WHITELIST
 	bool "Rely on OTG and EH Targeted Peripherals List"
 	depends on USB
diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig
index 3c3f31c..5fc9095 100644
--- a/drivers/usb/gadget/Kconfig
+++ b/drivers/usb/gadget/Kconfig
@@ -16,6 +16,7 @@ 
 menuconfig USB_GADGET
 	tristate "USB Gadget Support"
 	select USB_COMMON
+	select USB_OTG_CORE
 	select NLS
 	help
 	   USB is a master/slave protocol, organized with one master
diff --git a/include/linux/usb/gadget.h b/include/linux/usb/gadget.h
index f4fc0aa..1d74fb8 100644
--- a/include/linux/usb/gadget.h
+++ b/include/linux/usb/gadget.h
@@ -328,6 +328,7 @@  struct usb_gadget_ops {
  * @in_epnum: last used in ep number
  * @mA: last set mA value
  * @otg_caps: OTG capabilities of this gadget.
+ * @otg_dev: OTG controller device, if needs to be used with OTG core.
  * @sg_supported: true if we can handle scatter-gather
  * @is_otg: True if the USB device port uses a Mini-AB jack, so that the
  *	gadget driver must provide a USB OTG descriptor.
@@ -385,6 +386,7 @@  struct usb_gadget {
 	unsigned			in_epnum;
 	unsigned			mA;
 	struct usb_otg_caps		*otg_caps;
+	struct device			*otg_dev;
 
 	unsigned			sg_supported:1;
 	unsigned			is_otg:1;
diff --git a/include/linux/usb/hcd.h b/include/linux/usb/hcd.h
index 7729c1f..36bd54f 100644
--- a/include/linux/usb/hcd.h
+++ b/include/linux/usb/hcd.h
@@ -185,6 +185,7 @@  struct usb_hcd {
 	struct mutex		*bandwidth_mutex;
 	struct usb_hcd		*shared_hcd;
 	struct usb_hcd		*primary_hcd;
+	struct device		*otg_dev;	/* OTG controller device */
 
 
 #define HCD_BUFFER_POOLS	4
diff --git a/include/linux/usb/otg-fsm.h b/include/linux/usb/otg-fsm.h
index 26e6531..943714a 100644
--- a/include/linux/usb/otg-fsm.h
+++ b/include/linux/usb/otg-fsm.h
@@ -60,6 +60,11 @@  enum otg_fsm_timer {
 /**
  * struct otg_fsm - OTG state machine according to the OTG spec
  *
+ * DRD mode hardware Inputs
+ *
+ * @id:		TRUE for B-device, FALSE for A-device.
+ * @b_sess_vld:	VBUS voltage in regulation.
+ *
  * OTG hardware Inputs
  *
  *	Common inputs for A and B device
@@ -132,6 +137,7 @@  enum otg_fsm_timer {
  * a_clr_err:	Asserted (by application ?) to clear a_vbus_err due to an
  *		overcurrent condition and causes the A-device to transition
  *		to a_wait_vfall
+ * running:	state machine running/stopped indicator
  */
 struct otg_fsm {
 	/* Input */
@@ -187,6 +193,7 @@  struct otg_fsm {
 	int b_ase0_brst_tmout;
 	int a_bidl_adis_tmout;
 
+	bool running;
 	struct otg_fsm_ops *ops;
 
 	/* Current usb protocol used: 0:undefine; 1:host; 2:client */
diff --git a/include/linux/usb/otg.h b/include/linux/usb/otg.h
index 85b8fb5..2175fb7 100644
--- a/include/linux/usb/otg.h
+++ b/include/linux/usb/otg.h
@@ -10,10 +10,67 @@ 
 #define __LINUX_USB_OTG_H
 
 #include <linux/phy/phy.h>
-#include <linux/usb/phy.h>
-#include <linux/usb/otg-fsm.h>
+#include <linux/device.h>
+#include <linux/usb.h>
 #include <linux/usb/hcd.h>
+#include <linux/usb/gadget.h>
+#include <linux/usb/otg-fsm.h>
+#include <linux/usb/phy.h>
+
+/**
+ * struct otg_hcd - host controller state and interface
+ *
+ * @hcd: host controller
+ * @irqnum: IRQ number
+ * @irqflags: IRQ flags
+ * @ops: OTG to host controller interface
+ * @otg_dev: OTG controller device
+ */
+struct otg_hcd {
+	struct usb_hcd *hcd;
+	unsigned int irqnum;
+	unsigned long irqflags;
+	struct otg_hcd_ops *ops;
+	struct device *otg_dev;
+};
+
+/**
+ * struct usb_otg_caps - describes the otg capabilities of the device
+ * @otg_rev: The OTG revision number the device is compliant with, it's
+ *		in binary-coded decimal (i.e. 2.0 is 0200H).
+ * @hnp_support: Indicates if the device supports HNP.
+ * @srp_support: Indicates if the device supports SRP.
+ * @adp_support: Indicates if the device supports ADP.
+ */
+struct usb_otg_caps {
+	u16 otg_rev;
+	bool hnp_support;
+	bool srp_support;
+	bool adp_support;
+};
 
+/**
+ * struct usb_otg - usb otg controller state
+ *
+ * @default_a: Indicates we are an A device. i.e. Host.
+ * @phy: USB PHY interface
+ * @usb_phy: old usb_phy interface
+ * @host: host controller bus
+ * @gadget: gadget device
+ * @state: current OTG state
+ * @dev: OTG controller device
+ * @caps: OTG capabilities revision, hnp, srp, etc
+ * @fsm: OTG finite state machine
+ * @hcd_ops: host controller interface
+ * ------- internal use only -------
+ * @primary_hcd: primary host state and interface
+ * @shared_hcd: shared host state and interface
+ * @gadget_ops: gadget controller interface
+ * @list: list of OTG controllers
+ * @work: OTG state machine work
+ * @wq: OTG state machine work queue
+ * @flags: to track if host/gadget is running
+ */
 struct usb_otg {
 	u8			default_a;
 
@@ -24,9 +81,25 @@  struct usb_otg {
 	struct usb_gadget	*gadget;
 
 	enum usb_otg_state	state;
+	struct device *dev;
+	struct usb_otg_caps	caps;
 	struct otg_fsm fsm;
 	struct otg_hcd_ops	*hcd_ops;
 
+	/* internal use only */
+	struct otg_hcd primary_hcd;
+	struct otg_hcd shared_hcd;
+	struct otg_gadget_ops *gadget_ops;
+	bool gadget_ready;
+	struct list_head list;
+	struct work_struct work;
+	struct workqueue_struct *wq;
+	u32 flags;
+#define OTG_FLAG_GADGET_RUNNING (1 << 0)
+#define OTG_FLAG_HOST_RUNNING (1 << 1)
+	/* use otg->fsm.lock for serializing access */
+
+/*------------- deprecated interface -----------------------------*/
 	/* bind/unbind the host controller */
 	int	(*set_host)(struct usb_otg *otg, struct usb_bus *host);
 
@@ -42,26 +115,101 @@  struct usb_otg {
 
 	/* start or continue HNP role switch */
 	int	(*start_hnp)(struct usb_otg *otg);
-
+/*---------------------------------------------------------------*/
 };
 
 /**
- * struct usb_otg_caps - describes the otg capabilities of the device
- * @otg_rev: The OTG revision number the device is compliant with, it's
- *		in binary-coded decimal (i.e. 2.0 is 0200H).
- * @hnp_support: Indicates if the device supports HNP.
- * @srp_support: Indicates if the device supports SRP.
- * @adp_support: Indicates if the device supports ADP.
+ * struct usb_otg_config - OTG controller configuration
+ * @caps: OTG capabilities of the controller
+ * @ops: OTG FSM operations
+ * @otg_work: optional custom OTG state machine work function
  */
-struct usb_otg_caps {
-	u16 otg_rev;
-	bool hnp_support;
-	bool srp_support;
-	bool adp_support;
+struct usb_otg_config {
+	struct usb_otg_caps *otg_caps;
+	struct otg_fsm_ops *fsm_ops;
+	void (*otg_work)(struct work_struct *work);
 };
 
 extern const char *usb_otg_state_string(enum usb_otg_state state);
 
+#if IS_ENABLED(CONFIG_USB_OTG)
+struct usb_otg *usb_otg_register(struct device *dev,
+				 struct usb_otg_config *config);
+int usb_otg_unregister(struct device *dev);
+int usb_otg_register_hcd(struct usb_hcd *hcd, unsigned int irqnum,
+			 unsigned long irqflags, struct otg_hcd_ops *ops);
+int usb_otg_unregister_hcd(struct usb_hcd *hcd);
+int usb_otg_register_gadget(struct usb_gadget *gadget,
+			    struct otg_gadget_ops *ops);
+int usb_otg_unregister_gadget(struct usb_gadget *gadget);
+void usb_otg_sync_inputs(struct usb_otg *otg);
+int usb_otg_kick_fsm(struct device *otg_dev);
+int usb_otg_start_host(struct usb_otg *otg, int on);
+int usb_otg_start_gadget(struct usb_otg *otg, int on);
+int usb_otg_gadget_ready(struct usb_gadget *gadget, bool ready);
+
+#else /* CONFIG_USB_OTG */
+
+static inline struct usb_otg *usb_otg_register(struct device *dev,
+					       struct usb_otg_config *config)
+{
+	return ERR_PTR(-ENOTSUPP);
+}
+
+static inline int usb_otg_unregister(struct device *dev)
+{
+	return -ENOTSUPP;
+}
+
+static inline int usb_otg_register_hcd(struct usb_hcd *hcd, unsigned int irqnum,
+				       unsigned long irqflags,
+				       struct otg_hcd_ops *ops)
+{
+	return -ENOTSUPP;
+}
+
+static inline int usb_otg_unregister_hcd(struct usb_hcd *hcd)
+{
+	return -ENOTSUPP;
+}
+
+static inline int usb_otg_register_gadget(struct usb_gadget *gadget,
+					  struct otg_gadget_ops *ops)
+{
+	return -ENOTSUPP;
+}
+
+static inline int usb_otg_unregister_gadget(struct usb_gadget *gadget)
+{
+	return -ENOTSUPP;
+}
+
+static inline void usb_otg_sync_inputs(struct usb_otg *otg)
+{
+}
+
+static inline int usb_otg_kick_fsm(struct device *otg_dev)
+{
+	return -ENOTSUPP;
+}
+
+static inline int usb_otg_start_host(struct usb_otg *otg, int on)
+{
+	return -ENOTSUPP;
+}
+
+static inline int usb_otg_start_gadget(struct usb_otg *otg, int on)
+{
+	return -ENOTSUPP;
+}
+
+static inline int usb_otg_gadget_ready(struct usb_gadget *gadget, bool ready)
+{
+	return -ENOTSUPP;
+}
+#endif /* CONFIG_USB_OTG */
+
+/*------------- deprecated interface -----------------------------*/
 /* Context: can sleep */
 static inline int
 otg_start_hnp(struct usb_otg *otg)
@@ -113,6 +261,8 @@  otg_start_srp(struct usb_otg *otg)
 	return -ENOTSUPP;
 }
 
+/*---------------------------------------------------------------*/
+
 /* for OTG controller drivers (and maybe other stuff) */
 extern int usb_bus_start_enum(struct usb_bus *bus, unsigned port_num);
 
@@ -237,4 +387,6 @@  static inline int otg_start_gadget(struct usb_otg *otg, int on)
 	return otg->fsm.ops->start_gadget(otg, on);
 }
 
+int drd_statemachine(struct usb_otg *otg);
+
 #endif /* __LINUX_USB_OTG_H */