[RFC PATCH 2/2] usb: otg: OTG 2.0 state machine

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



This is only the idea. Work in progress.

The transceiver is now separated from otg, so introducing
struct otg. It has more or less the same members as what
struct otg_transceiver has. The few hooks that were clearly
for transceivers are dropped. They are now part of struct
usb_transceiver. The controller drivers are now expected to
hold the struct otg.

The state machine is added. The idea is that the controller
drivers don't have to care about the state machine or even
the otg states. This is the ideal. Let's see how it goes.

NYET-Signed-off-by: Heikki Krogerus <heikki.krogerus@xxxxxxxxxxxxxxx>
---
 drivers/usb/otg/otg.c   |  787 +++++++++++++++++++++++++++++++++++++++++++++++
 include/linux/usb/otg.h |  120 +++++++
 2 files changed, 907 insertions(+), 0 deletions(-)

diff --git a/drivers/usb/otg/otg.c b/drivers/usb/otg/otg.c
index fb7adef..037aba2 100644
--- a/drivers/usb/otg/otg.c
+++ b/drivers/usb/otg/otg.c
@@ -12,6 +12,7 @@
 #include <linux/kernel.h>
 #include <linux/device.h>
 
+#include <linux/usb.h>
 #include <linux/usb/otg.h>
 
 static struct otg_transceiver *xceiv;
@@ -99,3 +100,789 @@ const char *otg_state_string(enum usb_otg_state state)
 	}
 }
 EXPORT_SYMBOL(otg_state_string);
+
+/*
+ * Support for OTG 2.0 state machine.
+ */
+
+static int otg_state_machine(struct otg *otg);
+
+static int otg_add_timer(struct otg *otg, enum usb_otg_timer timer)
+{
+	unsigned long timeout = 0;
+
+	if (timer_pending(&otg->timer))
+		return -EBUSY;
+
+	/* FIXME: provide definitions for tmouts */
+	switch (timer) {
+	case B_ASE0_BRST_TMR:
+		timeout = jiffies + msecs_to_jiffies(155);
+		break;
+	case A_WAIT_VRISE_TMR:
+		timeout = jiffies + msecs_to_jiffies(100);
+		break;
+	case A_WAIT_VFALL_TMR:
+		timeout = jiffies + msecs_to_jiffies(1000);
+		break;
+	case A_WAIT_BCON_TMR:
+		timeout = jiffies + msecs_to_jiffies(1100);
+		break;
+	case A_AIDL_BDIS_TMR:
+		timeout = jiffies + msecs_to_jiffies(200);
+		break;
+	case A_BIDL_ADIS_TMR:
+		timeout = jiffies + msecs_to_jiffies(155);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	otg->tmr = timer;
+
+	return mod_timer(&otg->timer, timeout);
+}
+
+static void otg_del_timer(struct otg *otg, enum usb_otg_timer timer)
+{
+	del_timer(&otg->timer);
+}
+
+/*
+ * Helpper for OTG_STATE_A_WAIT_VFALL
+ * There are six places where this can be called
+ */
+static int __a_wait_vfall(struct otg *otg)
+{
+	int err = 0;
+
+	/* Comming from A_VBUS_ERR, vbus is already disabled */
+	if (otg->state != OTG_STATE_A_VBUS_ERR)
+		err = otg->set_vbus(otg, false);
+
+	otg->state = OTG_STATE_A_WAIT_VFALL;
+
+	if (err) {
+		dev_err(otg->host->controller, "Failed to disable vbus\n");
+		otg->state = OTG_STATE_UNDEFINED;
+		goto fail;
+	}
+
+	err = otg_add_timer(otg, A_WAIT_VFALL_TMR);
+	if (err && err != -EBUSY)
+		dev_err(otg->host->controller, "Failed to start timer\n");
+fail:
+	return err;
+}
+
+/*
+ * Helpper for OTG_STATE_A_VBUS_ERR
+ * There are four places where this can be called
+ */
+static int __a_vbus_err(struct otg *otg)
+{
+	int err;
+
+	otg->state = OTG_STATE_A_VBUS_ERR;
+
+	err = otg->set_vbus(otg, false);
+	if (err) {
+		dev_err(otg->host->controller, "Failed to disable vbus\n");
+		otg->state = OTG_STATE_UNDEFINED;
+	} else
+		otg->a_vbus_err(otg);
+
+	return err;
+}
+
+static int otg_state_b_idle(struct otg *otg)
+{
+	int		ret = 1;
+	struct otg_sm	*sm = &otg->sm;
+
+	if (!sm->id) {
+		/*
+		 * Switch To OTG_STATE_A_IDLE
+		 */
+
+		otg->state = OTG_STATE_A_IDLE;
+		ret = otg->set_mode(otg, OTG_MODE_HOST);
+	} else if (sm->b_sess_vld) {
+		/*
+		 * Switch To OTG_STATE_B_PERIPHERAL
+		 */
+
+		ret = otg->set_device_connect(otg, true);
+		if (!ret)
+			otg->state = OTG_STATE_B_PERIPHERAL;
+	} else if ((sm->b_bus_req || sm->adp_change || sm->power_up)
+			&& sm->b_ssend_srp && sm->b_se0_srp) {
+		/*
+		 * Switch To OTG_STATE_B_SRP_INIT
+		 */
+
+		sm->adp_change = 0;
+		sm->power_up = 0;
+		sm->b_ssend_srp = 0;
+		sm->b_se0_srp = 0;
+
+		ret = otg->start_srp(otg);
+		if (!ret)
+			otg->state = OTG_STATE_B_SRP_INIT;
+	}
+
+	return ret;
+}
+
+static int otg_state_b_srp_idle(struct otg *otg)
+{
+	int		ret = 1;
+	struct otg_sm	*sm = &otg->sm;
+
+	if (!sm->id || sm->b_srp_done) {
+		/*
+		 * Switch To OTG_STATE_B_IDLE
+		 */
+
+		sm->b_srp_done = 0;
+
+		otg->state = OTG_STATE_B_IDLE;
+
+		ret = otg_state_machine(otg);
+		if (ret >= 0)
+			ret = 0;
+	}
+
+	return ret;
+}
+
+static int otg_state_b_peripheral(struct otg *otg)
+{
+	int		ret = 1;
+	struct otg_sm	*sm = &otg->sm;
+
+	if (!sm->id || !sm->b_sess_vld) {
+		/*
+		 * Switch To OTG_STATE_B_IDLE
+		 */
+
+		otg->state = OTG_STATE_B_IDLE;
+		ret = otg->set_device_connect(otg, false);
+	} else if (sm->b_bus_req && sm->b_hnp_en && sm->a_bus_suspend) {
+		/*
+		 * Switch To OTG_STATE_B_WAIT_ACON
+		 */
+
+		sm->a_bus_suspend = 0;
+
+		/* Disable pullups */
+		ret = otg->set_device_connect(otg, false);
+		if (ret)
+			goto err;
+
+		otg->state = OTG_STATE_B_WAIT_ACON;
+
+		/* Start b_ase0_brst_tmr */
+		ret = otg_add_timer(otg, B_ASE0_BRST_TMR);
+	}
+err:
+	return ret;
+}
+
+static int otg_state_b_wait_acon(struct otg *otg)
+{
+	int		ret = 1;
+	struct otg_sm	*sm = &otg->sm;
+
+	otg_del_timer(otg, B_ASE0_BRST_TMR);
+
+	if (!sm->id || !sm->b_sess_vld) {
+		/*
+		 * Switch To OTG_STATE_B_IDLE
+		 */
+
+		otg->state = OTG_STATE_B_IDLE;
+
+		/* FIXME: is this necessary? */
+		ret = otg_state_machine(otg);
+		if (ret >= 0)
+			ret = 0;
+	} else if (sm->a_bus_resume || sm->b_ase0_brst_tmout) {
+		/*
+		 * Switch To OTG_STATE_B_PERIPHERAL
+		 */
+
+		sm->a_bus_resume = 0;
+		sm->b_ase0_brst_tmout = 0;
+
+		otg->state = OTG_STATE_B_PERIPHERAL;
+
+		/* Enable pullups */
+		ret = otg->set_device_connect(otg, true);
+	} else if (sm->a_conn) {
+		/*
+		 * Switch To OTG_STATE_B_HOST
+		 */
+
+		/* Prepare host mode */
+		ret = otg->set_mode(otg, OTG_MODE_HOST);
+		if (ret)
+			goto err;
+
+		otg->state = OTG_STATE_B_HOST;
+
+		/* Enable host */
+		ret = otg->set_host_connect(otg, true);
+	}
+err:
+	return ret;
+}
+
+static int otg_state_b_host(struct otg *otg)
+{
+	int		ret = 1;
+	struct otg_sm	*sm = &otg->sm;
+
+	if (!sm->b_bus_req || !sm->a_conn || sm->test_device) {
+		/*
+		 * Switch To OTG_STATE_B_PERIPHERAL
+		 */
+
+		otg->state = OTG_STATE_B_PERIPHERAL;
+
+		/* Disable host */
+		ret = otg->set_host_connect(otg, false);
+		if (ret)
+			goto err;
+
+		/* Prepare device */
+		ret = otg->set_mode(otg, OTG_MODE_DEVICE);
+		if (ret)
+			goto err;
+
+		/* Enable pullups */
+		ret = otg->set_device_connect(otg, true);
+	}
+err:
+	return ret;
+}
+
+static int otg_state_a_idle(struct otg *otg)
+{
+	int		ret = 1;
+	struct otg_sm	*sm = &otg->sm;
+
+	if (sm->id) {
+		/*
+		 * Switch To OTG_STATE_B_IDLE
+		 */
+
+		otg->state = OTG_STATE_B_IDLE;
+		ret = otg->set_mode(otg, OTG_MODE_DEVICE);
+	} else if (!sm->a_bus_drop && (sm->a_bus_req || sm->a_srp_det
+				|| sm->adp_change || sm->power_up)) {
+		/*
+		 * Switch To OTG_STATE_A_WAIT_VRISE
+		 */
+
+		sm->a_srp_det = 0;
+		sm->adp_change = 0;
+		sm->power_up = 0;
+
+		ret = otg->set_vbus(otg, true);
+		if (ret)
+			goto err;
+
+		otg->state = OTG_STATE_A_WAIT_VRISE;
+
+		/* start a_wait_vrise_tmr */
+		ret = otg_add_timer(otg, A_WAIT_VRISE_TMR);
+	}
+err:
+	return ret;
+}
+
+static int otg_state_a_wait_vrise(struct otg *otg)
+{
+	int		ret = 1;
+	struct otg_sm	*sm = &otg->sm;
+
+	otg_del_timer(otg, A_WAIT_VRISE_TMR);
+
+	if (sm->id || sm->a_wait_vrise_tmout || sm->a_bus_drop) {
+		sm->a_wait_vrise_tmout = 0;
+		ret = __a_wait_vfall(otg);
+	} else if (sm->a_vbus_vld) {
+		/*
+		 * Switch To OTG_STATE_A_WAIT_BCON
+		 */
+
+		otg->state = OTG_STATE_A_WAIT_BCON;
+
+		/* start a_wait_bcon_tmr */
+		ret = otg_add_timer(otg, A_WAIT_BCON_TMR);
+	}
+
+	return ret;
+}
+
+static int otg_state_a_wait_bcon(struct otg *otg)
+{
+	int		ret = 1;
+	struct otg_sm	*sm = &otg->sm;
+
+	otg_del_timer(otg, A_WAIT_BCON_TMR);
+
+	if (sm->id || sm->a_bus_drop || sm->a_wait_bcon_tmout) {
+		sm->a_wait_bcon_tmout = 0;
+		ret = __a_wait_vfall(otg);
+	} else if (!sm->a_vbus_vld)
+		ret = __a_vbus_err(otg);
+	else if (sm->b_conn) {
+		/*
+		 * Switch To OTG_STATE_A_HOST
+		 */
+
+		otg->state = OTG_STATE_A_HOST;
+		ret = otg->set_host_connect(otg, true);
+	}
+
+	return ret;
+}
+
+static int otg_state_a_host(struct otg *otg)
+{
+	int		ret = 1;
+	struct otg_sm	*sm = &otg->sm;
+
+	/* Host disabled in all cases */
+
+	if (sm->id || sm->a_bus_drop) {
+		ret = otg->set_host_connect(otg, false);
+		if (ret)
+			goto err;
+
+		ret = __a_wait_vfall(otg);
+	} else if (!sm->a_vbus_vld) {
+		ret = otg->set_host_connect(otg, false);
+		if (ret)
+			goto err;
+
+		ret = __a_vbus_err(otg);
+	} else if (!sm->b_conn) {
+		/*
+		 * Switch To OTG_STATE_A_WAIT_BCON
+		 */
+
+		ret = otg->set_host_connect(otg, false);
+		if (ret)
+			goto err;
+
+		otg->state = OTG_STATE_A_WAIT_BCON;
+
+		ret = otg_add_timer(otg, A_WAIT_BCON_TMR);
+	} else if (!sm->a_bus_req) {
+		/*
+		 * Switch To OTG_STATE_A_SUSPEND
+		 */
+
+		/* Disable host */
+		ret = otg->set_host_connect(otg, false);
+		if (ret)
+			goto err;
+
+		/* Prepare device */
+		ret = otg->set_mode(otg, OTG_MODE_DEVICE);
+		if (ret)
+			goto err;
+
+		otg->state = OTG_STATE_A_SUSPEND;
+
+		/* Start a_aidl_bdis_tmr */
+		ret = otg_add_timer(otg, A_AIDL_BDIS_TMR);
+	}
+err:
+	return ret;
+}
+
+static int otg_state_a_suspend(struct otg *otg)
+{
+	int		ret = 1;
+	struct otg_sm	*sm = &otg->sm;
+
+	otg_del_timer(otg, A_AIDL_BDIS_TMR);
+
+	if (sm->id || sm->a_bus_drop || sm->a_aidl_bdis_tmout) {
+		sm->a_aidl_bdis_tmout = 0;
+		ret = __a_wait_vfall(otg);
+	} else if (!sm->a_vbus_vld) {
+		ret = __a_vbus_err(otg);
+	} else if (sm->a_bus_req) {
+		/*
+		 * Switch To OTG_STATE_A_HOST
+		 */
+
+		/* Prepare host */
+		ret = otg->set_mode(otg, OTG_MODE_HOST);
+		if (ret)
+			goto err;
+
+		otg->state = OTG_STATE_A_HOST;
+
+		/* Enable host */
+		ret = otg->set_host_connect(otg, true);
+	} else if (!sm->b_conn && sm->a_set_b_hnp_en) {
+		/*
+		 * Switch To OTG_STATE_A_PERIPHERAL
+		 */
+
+		/* Enable pullups */
+		ret = otg->set_device_connect(otg, true);
+		if (ret)
+			goto err;
+
+		otg->state = OTG_STATE_A_PERIPHERAL;
+
+		ret = otg_add_timer(otg, A_BIDL_ADIS_TMR);
+	} else if (!sm->b_conn && !sm->a_set_b_hnp_en) {
+		/*
+		 * Switch To OTG_STATE_A_WAIT_BCON
+		 */
+
+		/* Prepare host mode */
+		ret = otg->set_mode(otg, OTG_MODE_HOST);
+		if (ret)
+			goto err;
+
+		otg->state = OTG_STATE_A_WAIT_BCON;
+
+		ret = otg_add_timer(otg, A_WAIT_BCON_TMR);
+	}
+err:
+	return ret;
+}
+
+static int otg_state_a_peripheral(struct otg *otg)
+{
+	int		ret = 1;
+	struct otg_sm	*sm = &otg->sm;
+
+	otg_del_timer(otg, A_BIDL_ADIS_TMR);
+
+	if (sm->id || sm->a_bus_drop)
+		ret = __a_wait_vfall(otg);
+	else if (!sm->a_vbus_vld)
+		ret = __a_vbus_err(otg);
+	else if (sm->a_bidl_adis_tmout) {
+		/*
+		 * Switch To OTG_STATE_A_WAIT_BCON
+		 */
+
+		sm->a_bidl_adis_tmout = 0;
+
+		/* Disable pullups */
+		ret = otg->set_device_connect(otg, false);
+		if (ret)
+			goto err;
+
+		/* Prepare host mode */
+		ret = otg->set_mode(otg, OTG_MODE_HOST);
+		if (ret)
+			goto err;
+
+		otg->state = OTG_STATE_A_WAIT_BCON;
+
+		ret = otg_add_timer(otg, A_WAIT_BCON_TMR);
+	}
+err:
+	return ret;
+}
+
+static int otg_state_a_wait_vfall(struct otg *otg)
+{
+	int		ret = 1;
+	struct otg_sm	*sm = &otg->sm;
+
+	otg_del_timer(otg, A_WAIT_VFALL_TMR);
+
+	if (sm->a_wait_vfall_tmout)
+		otg->state = OTG_STATE_A_IDLE;
+	/* FIXME: should we recall the state machine
+	 * after this? */
+
+	sm->a_wait_vfall_tmout = 0;
+
+	return ret;
+}
+
+static int otg_state_a_vbus_err(struct otg *otg)
+{
+	int		ret = 1;
+	struct otg_sm	*sm = &otg->sm;
+
+	if (sm->id || sm->a_bus_drop || sm->a_clr_err) {
+		sm->a_clr_err = 0;
+		ret = __a_wait_vfall(otg);
+	}
+
+	return ret;
+}
+
+/* OTG State Machine */
+static int otg_state_machine(struct otg *otg)
+{
+	int		ret = 1;
+
+	switch (otg->state) {
+	case OTG_STATE_UNDEFINED:
+		if (otg->sm.id) {
+			otg->state = OTG_STATE_B_IDLE;
+			ret = otg->set_mode(otg, OTG_MODE_DEVICE);
+		} else {
+			otg->state = OTG_STATE_A_IDLE;
+			ret = otg->set_mode(otg, OTG_MODE_HOST);
+		}
+		break;
+	case OTG_STATE_B_IDLE:
+		/* FIXME: timers for TB_SSEND_SRP and TB_SE0_SRP? */
+		ret = otg_state_b_idle(otg);
+		break;
+	case OTG_STATE_B_SRP_INIT:
+		ret = otg_state_b_srp_idle(otg);
+		break;
+	case OTG_STATE_B_PERIPHERAL:
+		ret = otg_state_b_peripheral(otg);
+		break;
+	case OTG_STATE_B_WAIT_ACON:
+		ret = otg_state_b_wait_acon(otg);
+		break;
+	case OTG_STATE_B_HOST:
+		ret = otg_state_b_host(otg);
+		break;
+	case OTG_STATE_A_IDLE:
+		ret = otg_state_a_idle(otg);
+		break;
+	case OTG_STATE_A_WAIT_VRISE:
+		ret = otg_state_a_wait_vrise(otg);
+		break;
+	case OTG_STATE_A_WAIT_BCON:
+		ret = otg_state_a_wait_bcon(otg);
+		break;
+	case OTG_STATE_A_HOST:
+		ret = otg_state_a_host(otg);
+		break;
+	case OTG_STATE_A_SUSPEND:
+		ret = otg_state_a_suspend(otg);
+		break;
+	case OTG_STATE_A_PERIPHERAL:
+		ret = otg_state_a_peripheral(otg);
+		break;
+	case OTG_STATE_A_WAIT_VFALL:
+		ret = otg_state_a_wait_vfall(otg);
+		break;
+	case OTG_STATE_A_VBUS_ERR:
+		ret = otg_state_a_vbus_err(otg);
+	default:
+		break;
+	}
+
+	return ret;
+}
+
+static void otg_timer(unsigned long _otg)
+{
+	struct otg *otg = (void *)_otg;
+
+	switch (otg->tmr) {
+	case B_ASE0_BRST_TMR:
+		otg->sm.b_ase0_brst_tmout = 1;
+		break;
+	case A_WAIT_VRISE_TMR:
+		otg->sm.a_wait_vrise_tmout = 1;
+		break;
+	case A_WAIT_VFALL_TMR:
+		otg->sm.a_wait_vfall_tmout = 1;
+		break;
+	case A_WAIT_BCON_TMR:
+		otg->sm.a_wait_bcon_tmout = 1;
+		break;
+	case A_AIDL_BDIS_TMR:
+		otg->sm.a_aidl_bdis_tmout = 1;
+		break;
+	case A_BIDL_ADIS_TMR:
+		otg->sm.a_bidl_adis_tmout = 1;
+	default:
+		break;
+	}
+
+	otg_state_machine(otg);
+}
+
+/*
+ * otg_init_sm - initialize OTG state machine
+ * @otg: OTG state machine to be initialized
+ *
+ * Check all the callbacks and launch the state machine for the first
+ * time.
+ */
+int otg_init_sm(struct otg *otg)
+{
+	/*
+	 * Excepting all the callbacks to be in place except
+	 * set_host_connect, set_adp_sense, set_adp_probe and
+	 * set_hnp_poll
+	 */
+	if (!otg->update_levels || !otg->set_mode || !otg->set_vbus
+		|| !otg->a_vbus_err || !otg->set_device_connect
+		|| !otg->start_srp)
+		goto fail;
+
+	setup_timer(&otg->timer, otg_timer, (unsigned long)otg);
+
+	otg->update_levels(otg);
+	otg->sm.power_up = 1;
+
+	return otg_state_machine(otg);
+fail:
+	return -EINVAL;
+}
+EXPORT_SYMBOL(otg_init_sm);
+
+/*
+ * otg_cable_connected - notify OTG state machine about connection
+ * @otg: OTG state machine
+ *
+ * Called when VBUS or ID false is detected.
+ */
+int otg_cable_connected(struct otg *otg)
+{
+	otg->update_levels(otg);
+	return otg_state_machine(otg);
+}
+EXPORT_SYMBOL(otg_cable_connected);
+
+/*
+ * otg_host_connect_device - device connected to the host
+ * @otg: OTG state machine
+ *
+ * Called from the host driver when a device is plugged.
+ */
+int otg_host_connect_device(struct otg *otg)
+{
+	otg->update_levels(otg);
+
+	if (otg->default_a)
+		otg->sm.b_conn = 1;
+	else
+		otg->sm.a_conn = 1;
+
+	return otg_state_machine(otg);
+}
+EXPORT_SYMBOL(otg_host_connect_device);
+
+/* A-device and B-device: bus reset */
+int otg_reset(struct otg *otg)
+{
+	otg->update_levels(otg);
+
+	/* REVISIT: OTG_STATE_A_SUSPEND; a_set_b_hnp_en must be set */
+	switch (otg->state) {
+	case OTG_STATE_B_IDLE:
+		otg->sm.b_bus_req = 1;
+	default:
+		break;
+	}
+
+	return otg_state_machine(otg);
+}
+EXPORT_SYMBOL(otg_reset);
+
+/* host and device: disconnect */
+int otg_disconnect(struct otg *otg)
+{
+	otg->update_levels(otg);
+
+	switch (otg->state) {
+	case OTG_STATE_A_HOST:
+		otg->sm.b_conn = 0;
+		break;
+	case OTG_STATE_B_HOST:
+		otg->sm.a_conn = 0;
+		break;
+	default:
+		break;
+	}
+
+	return otg_state_machine(otg);
+}
+EXPORT_SYMBOL(otg_disconnect);
+
+/* host and device: suspend */
+int otg_suspend(struct otg *otg)
+{
+	otg->update_levels(otg);
+
+	switch (otg->state) {
+	case OTG_STATE_B_HOST:
+		/* Relying on disconnect to clear a_conn */
+		otg->sm.b_bus_req = 0;
+		break;
+	case OTG_STATE_B_PERIPHERAL:
+		otg->sm.a_bus_suspend = 1;
+		break;
+	case OTG_STATE_A_HOST:
+		otg->sm.a_bus_req = 0;
+		break;
+	case OTG_STATE_A_PERIPHERAL:
+		/* running a_bidl_adis_tmr again */
+		return otg_add_timer(otg, A_BIDL_ADIS_TMR);
+	default:
+		return 1;
+	}
+
+	return otg_state_machine(otg);
+}
+EXPORT_SYMBOL(otg_suspend);
+
+/* host and device: resume */
+int otg_resume(struct otg *otg)
+{
+	otg->update_levels(otg);
+
+	switch (otg->state) {
+	case OTG_STATE_B_WAIT_ACON:
+		otg->sm.a_bus_resume = 1;
+		break;
+	case OTG_STATE_A_SUSPEND:
+		otg->sm.a_bus_req = 1;
+	default:
+		return 1;
+	}
+	return otg_state_machine(otg);
+}
+EXPORT_SYMBOL(otg_resume);
+
+/* B-device: called when srp initiating has finished */
+int otg_srp_done(struct otg *otg)
+{
+	otg->update_levels(otg);
+
+	otg->sm.b_srp_done = 1;
+
+	return otg_state_machine(otg);
+}
+EXPORT_SYMBOL(otg_srp_done);
+
+/* A-device: called after a_vbus_err */
+int otg_clear_vbus_err(struct otg *otg)
+{
+	if (otg->state == OTG_STATE_A_VBUS_ERR)
+		otg->sm.a_clr_err = 1;
+	else
+		return 1;
+
+	otg->update_levels(otg);
+
+	return otg_state_machine(otg);
+}
+EXPORT_SYMBOL(otg_clear_vbus_err);
diff --git a/include/linux/usb/otg.h b/include/linux/usb/otg.h
index d87f44f..018274d 100644
--- a/include/linux/usb/otg.h
+++ b/include/linux/usb/otg.h
@@ -35,6 +35,21 @@ enum usb_otg_state {
 	OTG_STATE_A_VBUS_ERR,
 };
 
+enum usb_otg_timer {
+	A_WAIT_VRISE_TMR,
+	A_WAIT_VFALL_TMR,
+	A_WAIT_BCON_TMR,
+	A_AIDL_BDIS_TMR,
+	B_ASE0_BRST_TMR,
+	A_BIDL_ADIS_TMR,
+};
+
+enum usb_otg_mode {
+	OTG_MODE_UNDEF,
+	OTG_MODE_DEVICE,
+	OTG_MODE_HOST,
+};
+
 enum usb_xceiv_events {
 	USB_EVENT_NONE,         /* no events or cable disconnected */
 	USB_EVENT_VBUS,         /* vbus valid event */
@@ -45,6 +60,111 @@ enum usb_xceiv_events {
 
 struct otg_transceiver;
 
+/*
+ * USB OTG state machine parameters as described in USB OTG 2.0
+ * specification.
+ */
+struct otg_sm {
+	/* Inputs */
+	unsigned	id:1;
+	unsigned	adp_change:1;
+	unsigned	power_up:1;
+	unsigned	test_device:1;
+	unsigned	a_bus_drop:1;
+	unsigned	a_bus_req:1;
+	unsigned	a_sess_vld:1;
+	unsigned	a_srp_det:1;
+	unsigned	a_vbus_vld:1;
+	unsigned	b_conn:1;
+	unsigned	a_bus_resume:1;
+	unsigned	a_bus_suspend:1;
+	unsigned	a_conn:1;
+	unsigned	b_bus_req:1;
+	unsigned	b_se0_srp:1;
+	unsigned	b_ssend_srp:1;
+	unsigned	b_sess_vld:1;
+
+	/* Outputs */
+	unsigned	data_pulse:1;
+	unsigned	drv_vbus:1;
+	unsigned	loc_conn:1;
+	unsigned	loc_sof:1;
+	unsigned	adp_prb:1;
+	unsigned	adp_sns:1;
+
+	/* Internal Variables */
+	unsigned	a_set_b_hnp_en:1;
+	unsigned	b_srp_done:1;
+	unsigned	b_hnp_en:1;
+	unsigned	a_clr_err:1;
+
+	/* Timeout indicators */
+	unsigned	a_wait_vrise_tmout:1;
+	unsigned	a_wait_vfall_tmout:1;
+	unsigned	a_wait_bcon_tmout:1;
+	unsigned	a_aidl_bdis_tmout:1;
+	unsigned	b_ase0_brst_tmout:1;
+	unsigned	a_bidl_adis_tmout:1;
+};
+
+struct otg {
+	u8			default_a;
+	enum usb_otg_state	state;
+	struct otg_sm		sm;
+
+	enum usb_otg_timer	tmr;
+	struct timer_list	timer;
+
+	struct usb_transceiver	*xceiv;
+	struct usb_bus		*host;
+	struct usb_gadget	*gadget;
+
+	/* update id, a_sess_vld, a_vbus_vld and b_sess_vld state
+	 * machine parameters */
+	void	(*update_levels)(struct otg *otg);
+
+	/* Change mode between host, device and undefined */
+	int	(*set_mode)(struct otg *otg, enum usb_otg_mode mode);
+
+	/* A devices: drive VBUS */
+	int	(*set_vbus)(struct otg *otg, bool enabled);
+
+	/* Overcurrent condition */
+	void	(*a_vbus_err)(struct otg *otg);
+
+	/* A-Host and B-Host (local SOF): start/stop bus activity */
+	int	(*set_host_connect)(struct otg *otg, bool enabled);
+
+	/* A/B-Device (local connect): ideally enable/disable D+ pullup */
+	int	(*set_device_connect)(struct otg *otg, bool enabled);
+
+	/* Protocol callbacks */
+
+	/* for B devices only: start session with A-Host */
+	int	(*start_srp)(struct otg *otg);
+
+	/* start or continue HNP role switch */
+	int	(*start_hnp)(struct otg *otg);
+
+	/* start/stop ADP sense/probe function */
+	int	(*set_adp_sense)(struct otg *otg, bool enabled);
+	int	(*set_adp_probe)(struct otg *otg, bool enabled);
+
+	/* start/stop HNP Polling function */
+	int	(*set_hnp_poll)(struct otg *otg, bool enabled);
+
+};
+
+extern int otg_init_sm(struct otg *);
+extern int otg_cable_connected(struct otg *otg);
+extern int otg_host_connect_device(struct otg *);
+extern int otg_reset(struct otg *);
+extern int otg_disconnect(struct otg *);
+extern int otg_suspend(struct otg *);
+extern int otg_resume(struct otg *);
+extern int otg_srp_done(struct otg *);
+extern int otg_clear_vbus_err(struct otg *);
+
 /* for transceivers connected thru an ULPI interface, the user must
  * provide access ops
  */
-- 
1.7.4.1

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


[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux