summaryrefslogtreecommitdiffstats
path: root/drivers/usb/common
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/common')
-rw-r--r--drivers/usb/common/Makefile11
-rw-r--r--drivers/usb/common/common.c278
-rw-r--r--drivers/usb/common/led.c53
-rw-r--r--drivers/usb/common/ulpi.c328
-rw-r--r--drivers/usb/common/usb-otg-fsm.c452
5 files changed, 1122 insertions, 0 deletions
diff --git a/drivers/usb/common/Makefile b/drivers/usb/common/Makefile
new file mode 100644
index 000000000..0a7c45e85
--- /dev/null
+++ b/drivers/usb/common/Makefile
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for the usb common parts.
+#
+
+obj-$(CONFIG_USB_COMMON) += usb-common.o
+usb-common-y += common.o
+usb-common-$(CONFIG_USB_LED_TRIG) += led.o
+
+obj-$(CONFIG_USB_OTG_FSM) += usb-otg-fsm.o
+obj-$(CONFIG_USB_ULPI_BUS) += ulpi.o
diff --git a/drivers/usb/common/common.c b/drivers/usb/common/common.c
new file mode 100644
index 000000000..73c8e6591
--- /dev/null
+++ b/drivers/usb/common/common.c
@@ -0,0 +1,278 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Provides code common for host and device side USB.
+ *
+ * If either host side (ie. CONFIG_USB=y) or device side USB stack
+ * (ie. CONFIG_USB_GADGET=y) is compiled in the kernel, this module is
+ * compiled-in as well. Otherwise, if either of the two stacks is
+ * compiled as module, this file is compiled as module as well.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/usb/ch9.h>
+#include <linux/usb/of.h>
+#include <linux/usb/otg.h>
+#include <linux/of_platform.h>
+
+const char *usb_otg_state_string(enum usb_otg_state state)
+{
+ static const char *const names[] = {
+ [OTG_STATE_A_IDLE] = "a_idle",
+ [OTG_STATE_A_WAIT_VRISE] = "a_wait_vrise",
+ [OTG_STATE_A_WAIT_BCON] = "a_wait_bcon",
+ [OTG_STATE_A_HOST] = "a_host",
+ [OTG_STATE_A_SUSPEND] = "a_suspend",
+ [OTG_STATE_A_PERIPHERAL] = "a_peripheral",
+ [OTG_STATE_A_WAIT_VFALL] = "a_wait_vfall",
+ [OTG_STATE_A_VBUS_ERR] = "a_vbus_err",
+ [OTG_STATE_B_IDLE] = "b_idle",
+ [OTG_STATE_B_SRP_INIT] = "b_srp_init",
+ [OTG_STATE_B_PERIPHERAL] = "b_peripheral",
+ [OTG_STATE_B_WAIT_ACON] = "b_wait_acon",
+ [OTG_STATE_B_HOST] = "b_host",
+ };
+
+ if (state < 0 || state >= ARRAY_SIZE(names))
+ return "UNDEFINED";
+
+ return names[state];
+}
+EXPORT_SYMBOL_GPL(usb_otg_state_string);
+
+static const char *const speed_names[] = {
+ [USB_SPEED_UNKNOWN] = "UNKNOWN",
+ [USB_SPEED_LOW] = "low-speed",
+ [USB_SPEED_FULL] = "full-speed",
+ [USB_SPEED_HIGH] = "high-speed",
+ [USB_SPEED_WIRELESS] = "wireless",
+ [USB_SPEED_SUPER] = "super-speed",
+ [USB_SPEED_SUPER_PLUS] = "super-speed-plus",
+};
+
+const char *usb_speed_string(enum usb_device_speed speed)
+{
+ if (speed < 0 || speed >= ARRAY_SIZE(speed_names))
+ speed = USB_SPEED_UNKNOWN;
+ return speed_names[speed];
+}
+EXPORT_SYMBOL_GPL(usb_speed_string);
+
+enum usb_device_speed usb_get_maximum_speed(struct device *dev)
+{
+ const char *maximum_speed;
+ int ret;
+
+ ret = device_property_read_string(dev, "maximum-speed", &maximum_speed);
+ if (ret < 0)
+ return USB_SPEED_UNKNOWN;
+
+ ret = match_string(speed_names, ARRAY_SIZE(speed_names), maximum_speed);
+
+ return (ret < 0) ? USB_SPEED_UNKNOWN : ret;
+}
+EXPORT_SYMBOL_GPL(usb_get_maximum_speed);
+
+const char *usb_state_string(enum usb_device_state state)
+{
+ static const char *const names[] = {
+ [USB_STATE_NOTATTACHED] = "not attached",
+ [USB_STATE_ATTACHED] = "attached",
+ [USB_STATE_POWERED] = "powered",
+ [USB_STATE_RECONNECTING] = "reconnecting",
+ [USB_STATE_UNAUTHENTICATED] = "unauthenticated",
+ [USB_STATE_DEFAULT] = "default",
+ [USB_STATE_ADDRESS] = "addressed",
+ [USB_STATE_CONFIGURED] = "configured",
+ [USB_STATE_SUSPENDED] = "suspended",
+ };
+
+ if (state < 0 || state >= ARRAY_SIZE(names))
+ return "UNKNOWN";
+
+ return names[state];
+}
+EXPORT_SYMBOL_GPL(usb_state_string);
+
+static const char *const usb_dr_modes[] = {
+ [USB_DR_MODE_UNKNOWN] = "",
+ [USB_DR_MODE_HOST] = "host",
+ [USB_DR_MODE_PERIPHERAL] = "peripheral",
+ [USB_DR_MODE_OTG] = "otg",
+};
+
+static enum usb_dr_mode usb_get_dr_mode_from_string(const char *str)
+{
+ int ret;
+
+ ret = match_string(usb_dr_modes, ARRAY_SIZE(usb_dr_modes), str);
+ return (ret < 0) ? USB_DR_MODE_UNKNOWN : ret;
+}
+
+enum usb_dr_mode usb_get_dr_mode(struct device *dev)
+{
+ const char *dr_mode;
+ int err;
+
+ err = device_property_read_string(dev, "dr_mode", &dr_mode);
+ if (err < 0)
+ return USB_DR_MODE_UNKNOWN;
+
+ return usb_get_dr_mode_from_string(dr_mode);
+}
+EXPORT_SYMBOL_GPL(usb_get_dr_mode);
+
+#ifdef CONFIG_OF
+/**
+ * of_usb_get_dr_mode_by_phy - Get dual role mode for the controller device
+ * which is associated with the given phy device_node
+ * @np: Pointer to the given phy device_node
+ * @arg0: phandle args[0] for phy's with #phy-cells >= 1, or -1 for
+ * phys which do not have phy-cells
+ *
+ * In dts a usb controller associates with phy devices. The function gets
+ * the string from property 'dr_mode' of the controller associated with the
+ * given phy device node, and returns the correspondig enum usb_dr_mode.
+ */
+enum usb_dr_mode of_usb_get_dr_mode_by_phy(struct device_node *np, int arg0)
+{
+ struct device_node *controller = NULL;
+ struct of_phandle_args args;
+ const char *dr_mode;
+ int index;
+ int err;
+
+ do {
+ controller = of_find_node_with_property(controller, "phys");
+ if (!of_device_is_available(controller))
+ continue;
+ index = 0;
+ do {
+ if (arg0 == -1) {
+ args.np = of_parse_phandle(controller, "phys",
+ index);
+ args.args_count = 0;
+ } else {
+ err = of_parse_phandle_with_args(controller,
+ "phys", "#phy-cells",
+ index, &args);
+ if (err)
+ break;
+ }
+
+ of_node_put(args.np);
+ if (args.np == np && (args.args_count == 0 ||
+ args.args[0] == arg0))
+ goto finish;
+ index++;
+ } while (args.np);
+ } while (controller);
+
+finish:
+ err = of_property_read_string(controller, "dr_mode", &dr_mode);
+ of_node_put(controller);
+
+ if (err < 0)
+ return USB_DR_MODE_UNKNOWN;
+
+ return usb_get_dr_mode_from_string(dr_mode);
+}
+EXPORT_SYMBOL_GPL(of_usb_get_dr_mode_by_phy);
+
+/**
+ * of_usb_host_tpl_support - to get if Targeted Peripheral List is supported
+ * for given targeted hosts (non-PC hosts)
+ * @np: Pointer to the given device_node
+ *
+ * The function gets if the targeted hosts support TPL or not
+ */
+bool of_usb_host_tpl_support(struct device_node *np)
+{
+ return of_property_read_bool(np, "tpl-support");
+}
+EXPORT_SYMBOL_GPL(of_usb_host_tpl_support);
+
+/**
+ * of_usb_update_otg_caps - to update usb otg capabilities according to
+ * the passed properties in DT.
+ * @np: Pointer to the given device_node
+ * @otg_caps: Pointer to the target usb_otg_caps to be set
+ *
+ * The function updates the otg capabilities
+ */
+int of_usb_update_otg_caps(struct device_node *np,
+ struct usb_otg_caps *otg_caps)
+{
+ u32 otg_rev;
+
+ if (!otg_caps)
+ return -EINVAL;
+
+ if (!of_property_read_u32(np, "otg-rev", &otg_rev)) {
+ switch (otg_rev) {
+ case 0x0100:
+ case 0x0120:
+ case 0x0130:
+ case 0x0200:
+ /* Choose the lesser one if it's already been set */
+ if (otg_caps->otg_rev)
+ otg_caps->otg_rev = min_t(u16, otg_rev,
+ otg_caps->otg_rev);
+ else
+ otg_caps->otg_rev = otg_rev;
+ break;
+ default:
+ pr_err("%pOF: unsupported otg-rev: 0x%x\n",
+ np, otg_rev);
+ return -EINVAL;
+ }
+ } else {
+ /*
+ * otg-rev is mandatory for otg properties, if not passed
+ * we set it to be 0 and assume it's a legacy otg device.
+ * Non-dt platform can set it afterwards.
+ */
+ otg_caps->otg_rev = 0;
+ }
+
+ if (of_property_read_bool(np, "hnp-disable"))
+ otg_caps->hnp_support = false;
+ if (of_property_read_bool(np, "srp-disable"))
+ otg_caps->srp_support = false;
+ if (of_property_read_bool(np, "adp-disable") ||
+ (otg_caps->otg_rev < 0x0200))
+ otg_caps->adp_support = false;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(of_usb_update_otg_caps);
+
+/**
+ * usb_of_get_companion_dev - Find the companion device
+ * @dev: the device pointer to find a companion
+ *
+ * Find the companion device from platform bus.
+ *
+ * Takes a reference to the returned struct device which needs to be dropped
+ * after use.
+ *
+ * Return: On success, a pointer to the companion device, %NULL on failure.
+ */
+struct device *usb_of_get_companion_dev(struct device *dev)
+{
+ struct device_node *node;
+ struct platform_device *pdev = NULL;
+
+ node = of_parse_phandle(dev->of_node, "companion", 0);
+ if (node)
+ pdev = of_find_device_by_node(node);
+
+ of_node_put(node);
+
+ return pdev ? &pdev->dev : NULL;
+}
+EXPORT_SYMBOL_GPL(usb_of_get_companion_dev);
+#endif
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/usb/common/led.c b/drivers/usb/common/led.c
new file mode 100644
index 000000000..7bd81166b
--- /dev/null
+++ b/drivers/usb/common/led.c
@@ -0,0 +1,53 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * LED Triggers for USB Activity
+ *
+ * Copyright 2014 Michal Sojka <sojka@merica.cz>
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/leds.h>
+#include <linux/usb.h>
+
+#define BLINK_DELAY 30
+
+static unsigned long usb_blink_delay = BLINK_DELAY;
+
+DEFINE_LED_TRIGGER(ledtrig_usb_gadget);
+DEFINE_LED_TRIGGER(ledtrig_usb_host);
+
+void usb_led_activity(enum usb_led_event ev)
+{
+ struct led_trigger *trig = NULL;
+
+ switch (ev) {
+ case USB_LED_EVENT_GADGET:
+ trig = ledtrig_usb_gadget;
+ break;
+ case USB_LED_EVENT_HOST:
+ trig = ledtrig_usb_host;
+ break;
+ }
+ /* led_trigger_blink_oneshot() handles trig == NULL gracefully */
+ led_trigger_blink_oneshot(trig, &usb_blink_delay, &usb_blink_delay, 0);
+}
+EXPORT_SYMBOL_GPL(usb_led_activity);
+
+
+static int __init ledtrig_usb_init(void)
+{
+ led_trigger_register_simple("usb-gadget", &ledtrig_usb_gadget);
+ led_trigger_register_simple("usb-host", &ledtrig_usb_host);
+ return 0;
+}
+
+static void __exit ledtrig_usb_exit(void)
+{
+ led_trigger_unregister_simple(ledtrig_usb_gadget);
+ led_trigger_unregister_simple(ledtrig_usb_host);
+}
+
+module_init(ledtrig_usb_init);
+module_exit(ledtrig_usb_exit);
diff --git a/drivers/usb/common/ulpi.c b/drivers/usb/common/ulpi.c
new file mode 100644
index 000000000..c42c152bb
--- /dev/null
+++ b/drivers/usb/common/ulpi.c
@@ -0,0 +1,328 @@
+// SPDX-License-Identifier: GPL-2.0
+/**
+ * ulpi.c - USB ULPI PHY bus
+ *
+ * Copyright (C) 2015 Intel Corporation
+ *
+ * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com>
+ */
+
+#include <linux/ulpi/interface.h>
+#include <linux/ulpi/driver.h>
+#include <linux/ulpi/regs.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/acpi.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/clk/clk-conf.h>
+
+/* -------------------------------------------------------------------------- */
+
+int ulpi_read(struct ulpi *ulpi, u8 addr)
+{
+ return ulpi->ops->read(ulpi->dev.parent, addr);
+}
+EXPORT_SYMBOL_GPL(ulpi_read);
+
+int ulpi_write(struct ulpi *ulpi, u8 addr, u8 val)
+{
+ return ulpi->ops->write(ulpi->dev.parent, addr, val);
+}
+EXPORT_SYMBOL_GPL(ulpi_write);
+
+/* -------------------------------------------------------------------------- */
+
+static int ulpi_match(struct device *dev, struct device_driver *driver)
+{
+ struct ulpi_driver *drv = to_ulpi_driver(driver);
+ struct ulpi *ulpi = to_ulpi_dev(dev);
+ const struct ulpi_device_id *id;
+
+ /*
+ * Some ULPI devices don't have a vendor id
+ * or provide an id_table so rely on OF match.
+ */
+ if (ulpi->id.vendor == 0 || !drv->id_table)
+ return of_driver_match_device(dev, driver);
+
+ for (id = drv->id_table; id->vendor; id++)
+ if (id->vendor == ulpi->id.vendor &&
+ id->product == ulpi->id.product)
+ return 1;
+
+ return 0;
+}
+
+static int ulpi_uevent(struct device *dev, struct kobj_uevent_env *env)
+{
+ struct ulpi *ulpi = to_ulpi_dev(dev);
+ int ret;
+
+ ret = of_device_uevent_modalias(dev, env);
+ if (ret != -ENODEV)
+ return ret;
+
+ if (add_uevent_var(env, "MODALIAS=ulpi:v%04xp%04x",
+ ulpi->id.vendor, ulpi->id.product))
+ return -ENOMEM;
+ return 0;
+}
+
+static int ulpi_probe(struct device *dev)
+{
+ struct ulpi_driver *drv = to_ulpi_driver(dev->driver);
+ int ret;
+
+ ret = of_clk_set_defaults(dev->of_node, false);
+ if (ret < 0)
+ return ret;
+
+ return drv->probe(to_ulpi_dev(dev));
+}
+
+static int ulpi_remove(struct device *dev)
+{
+ struct ulpi_driver *drv = to_ulpi_driver(dev->driver);
+
+ if (drv->remove)
+ drv->remove(to_ulpi_dev(dev));
+
+ return 0;
+}
+
+static struct bus_type ulpi_bus = {
+ .name = "ulpi",
+ .match = ulpi_match,
+ .uevent = ulpi_uevent,
+ .probe = ulpi_probe,
+ .remove = ulpi_remove,
+};
+
+/* -------------------------------------------------------------------------- */
+
+static ssize_t modalias_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ int len;
+ struct ulpi *ulpi = to_ulpi_dev(dev);
+
+ len = of_device_modalias(dev, buf, PAGE_SIZE);
+ if (len != -ENODEV)
+ return len;
+
+ return sprintf(buf, "ulpi:v%04xp%04x\n",
+ ulpi->id.vendor, ulpi->id.product);
+}
+static DEVICE_ATTR_RO(modalias);
+
+static struct attribute *ulpi_dev_attrs[] = {
+ &dev_attr_modalias.attr,
+ NULL
+};
+
+static struct attribute_group ulpi_dev_attr_group = {
+ .attrs = ulpi_dev_attrs,
+};
+
+static const struct attribute_group *ulpi_dev_attr_groups[] = {
+ &ulpi_dev_attr_group,
+ NULL
+};
+
+static void ulpi_dev_release(struct device *dev)
+{
+ of_node_put(dev->of_node);
+ kfree(to_ulpi_dev(dev));
+}
+
+static const struct device_type ulpi_dev_type = {
+ .name = "ulpi_device",
+ .groups = ulpi_dev_attr_groups,
+ .release = ulpi_dev_release,
+};
+
+/* -------------------------------------------------------------------------- */
+
+/**
+ * ulpi_register_driver - register a driver with the ULPI bus
+ * @drv: driver being registered
+ *
+ * Registers a driver with the ULPI bus.
+ */
+int __ulpi_register_driver(struct ulpi_driver *drv, struct module *module)
+{
+ if (!drv->probe)
+ return -EINVAL;
+
+ drv->driver.owner = module;
+ drv->driver.bus = &ulpi_bus;
+
+ return driver_register(&drv->driver);
+}
+EXPORT_SYMBOL_GPL(__ulpi_register_driver);
+
+/**
+ * ulpi_unregister_driver - unregister a driver with the ULPI bus
+ * @drv: driver to unregister
+ *
+ * Unregisters a driver with the ULPI bus.
+ */
+void ulpi_unregister_driver(struct ulpi_driver *drv)
+{
+ driver_unregister(&drv->driver);
+}
+EXPORT_SYMBOL_GPL(ulpi_unregister_driver);
+
+/* -------------------------------------------------------------------------- */
+
+static int ulpi_of_register(struct ulpi *ulpi)
+{
+ struct device_node *np = NULL, *child;
+ struct device *parent;
+
+ /* Find a ulpi bus underneath the parent or the grandparent */
+ parent = ulpi->dev.parent;
+ if (parent->of_node)
+ np = of_get_child_by_name(parent->of_node, "ulpi");
+ else if (parent->parent && parent->parent->of_node)
+ np = of_get_child_by_name(parent->parent->of_node, "ulpi");
+ if (!np)
+ return 0;
+
+ child = of_get_next_available_child(np, NULL);
+ of_node_put(np);
+ if (!child)
+ return -EINVAL;
+
+ ulpi->dev.of_node = child;
+
+ return 0;
+}
+
+static int ulpi_read_id(struct ulpi *ulpi)
+{
+ int ret;
+
+ /* Test the interface */
+ ret = ulpi_write(ulpi, ULPI_SCRATCH, 0xaa);
+ if (ret < 0)
+ goto err;
+
+ ret = ulpi_read(ulpi, ULPI_SCRATCH);
+ if (ret < 0)
+ return ret;
+
+ if (ret != 0xaa)
+ goto err;
+
+ ulpi->id.vendor = ulpi_read(ulpi, ULPI_VENDOR_ID_LOW);
+ ulpi->id.vendor |= ulpi_read(ulpi, ULPI_VENDOR_ID_HIGH) << 8;
+
+ ulpi->id.product = ulpi_read(ulpi, ULPI_PRODUCT_ID_LOW);
+ ulpi->id.product |= ulpi_read(ulpi, ULPI_PRODUCT_ID_HIGH) << 8;
+
+ /* Some ULPI devices don't have a vendor id so rely on OF match */
+ if (ulpi->id.vendor == 0)
+ goto err;
+
+ request_module("ulpi:v%04xp%04x", ulpi->id.vendor, ulpi->id.product);
+ return 0;
+err:
+ of_device_request_module(&ulpi->dev);
+ return 0;
+}
+
+static int ulpi_register(struct device *dev, struct ulpi *ulpi)
+{
+ int ret;
+
+ ulpi->dev.parent = dev; /* needed early for ops */
+ ulpi->dev.bus = &ulpi_bus;
+ ulpi->dev.type = &ulpi_dev_type;
+ dev_set_name(&ulpi->dev, "%s.ulpi", dev_name(dev));
+
+ ACPI_COMPANION_SET(&ulpi->dev, ACPI_COMPANION(dev));
+
+ ret = ulpi_of_register(ulpi);
+ if (ret)
+ return ret;
+
+ ret = ulpi_read_id(ulpi);
+ if (ret) {
+ of_node_put(ulpi->dev.of_node);
+ return ret;
+ }
+
+ ret = device_register(&ulpi->dev);
+ if (ret) {
+ put_device(&ulpi->dev);
+ return ret;
+ }
+
+ dev_dbg(&ulpi->dev, "registered ULPI PHY: vendor %04x, product %04x\n",
+ ulpi->id.vendor, ulpi->id.product);
+
+ return 0;
+}
+
+/**
+ * ulpi_register_interface - instantiate new ULPI device
+ * @dev: USB controller's device interface
+ * @ops: ULPI register access
+ *
+ * Allocates and registers a ULPI device and an interface for it. Called from
+ * the USB controller that provides the ULPI interface.
+ */
+struct ulpi *ulpi_register_interface(struct device *dev,
+ const struct ulpi_ops *ops)
+{
+ struct ulpi *ulpi;
+ int ret;
+
+ ulpi = kzalloc(sizeof(*ulpi), GFP_KERNEL);
+ if (!ulpi)
+ return ERR_PTR(-ENOMEM);
+
+ ulpi->ops = ops;
+
+ ret = ulpi_register(dev, ulpi);
+ if (ret) {
+ kfree(ulpi);
+ return ERR_PTR(ret);
+ }
+
+ return ulpi;
+}
+EXPORT_SYMBOL_GPL(ulpi_register_interface);
+
+/**
+ * ulpi_unregister_interface - unregister ULPI interface
+ * @intrf: struct ulpi_interface
+ *
+ * Unregisters a ULPI device and it's interface that was created with
+ * ulpi_create_interface().
+ */
+void ulpi_unregister_interface(struct ulpi *ulpi)
+{
+ device_unregister(&ulpi->dev);
+}
+EXPORT_SYMBOL_GPL(ulpi_unregister_interface);
+
+/* -------------------------------------------------------------------------- */
+
+static int __init ulpi_init(void)
+{
+ return bus_register(&ulpi_bus);
+}
+subsys_initcall(ulpi_init);
+
+static void __exit ulpi_exit(void)
+{
+ bus_unregister(&ulpi_bus);
+}
+module_exit(ulpi_exit);
+
+MODULE_AUTHOR("Intel Corporation");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("USB ULPI PHY bus");
diff --git a/drivers/usb/common/usb-otg-fsm.c b/drivers/usb/common/usb-otg-fsm.c
new file mode 100644
index 000000000..0697fde51
--- /dev/null
+++ b/drivers/usb/common/usb-otg-fsm.c
@@ -0,0 +1,452 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * OTG Finite State Machine from OTG spec
+ *
+ * Copyright (C) 2007,2008 Freescale Semiconductor, Inc.
+ *
+ * Author: Li Yang <LeoLi@freescale.com>
+ * Jerry Huang <Chang-Ming.Huang@freescale.com>
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/mutex.h>
+#include <linux/delay.h>
+#include <linux/usb.h>
+#include <linux/usb/gadget.h>
+#include <linux/usb/otg.h>
+#include <linux/usb/otg-fsm.h>
+
+#ifdef VERBOSE
+#define VDBG(fmt, args...) pr_debug("[%s] " fmt, \
+ __func__, ## args)
+#else
+#define VDBG(stuff...) do {} while (0)
+#endif
+
+/* Change USB protocol when there is a protocol change */
+static int otg_set_protocol(struct otg_fsm *fsm, int protocol)
+{
+ int ret = 0;
+
+ if (fsm->protocol != protocol) {
+ VDBG("Changing role fsm->protocol= %d; new protocol= %d\n",
+ fsm->protocol, protocol);
+ /* stop old protocol */
+ if (fsm->protocol == PROTO_HOST)
+ ret = otg_start_host(fsm, 0);
+ else if (fsm->protocol == PROTO_GADGET)
+ ret = otg_start_gadget(fsm, 0);
+ if (ret)
+ return ret;
+
+ /* start new protocol */
+ if (protocol == PROTO_HOST)
+ ret = otg_start_host(fsm, 1);
+ else if (protocol == PROTO_GADGET)
+ ret = otg_start_gadget(fsm, 1);
+ if (ret)
+ return ret;
+
+ fsm->protocol = protocol;
+ return 0;
+ }
+
+ return 0;
+}
+
+/* Called when leaving a state. Do state clean up jobs here */
+static void otg_leave_state(struct otg_fsm *fsm, enum usb_otg_state old_state)
+{
+ switch (old_state) {
+ case OTG_STATE_B_IDLE:
+ otg_del_timer(fsm, B_SE0_SRP);
+ fsm->b_se0_srp = 0;
+ fsm->adp_sns = 0;
+ fsm->adp_prb = 0;
+ break;
+ case OTG_STATE_B_SRP_INIT:
+ fsm->data_pulse = 0;
+ fsm->b_srp_done = 0;
+ break;
+ case OTG_STATE_B_PERIPHERAL:
+ if (fsm->otg->gadget)
+ fsm->otg->gadget->host_request_flag = 0;
+ break;
+ case OTG_STATE_B_WAIT_ACON:
+ otg_del_timer(fsm, B_ASE0_BRST);
+ fsm->b_ase0_brst_tmout = 0;
+ break;
+ case OTG_STATE_B_HOST:
+ break;
+ case OTG_STATE_A_IDLE:
+ fsm->adp_prb = 0;
+ break;
+ case OTG_STATE_A_WAIT_VRISE:
+ otg_del_timer(fsm, A_WAIT_VRISE);
+ fsm->a_wait_vrise_tmout = 0;
+ break;
+ case OTG_STATE_A_WAIT_BCON:
+ otg_del_timer(fsm, A_WAIT_BCON);
+ fsm->a_wait_bcon_tmout = 0;
+ break;
+ case OTG_STATE_A_HOST:
+ otg_del_timer(fsm, A_WAIT_ENUM);
+ break;
+ case OTG_STATE_A_SUSPEND:
+ otg_del_timer(fsm, A_AIDL_BDIS);
+ fsm->a_aidl_bdis_tmout = 0;
+ fsm->a_suspend_req_inf = 0;
+ break;
+ case OTG_STATE_A_PERIPHERAL:
+ otg_del_timer(fsm, A_BIDL_ADIS);
+ fsm->a_bidl_adis_tmout = 0;
+ if (fsm->otg->gadget)
+ fsm->otg->gadget->host_request_flag = 0;
+ break;
+ case OTG_STATE_A_WAIT_VFALL:
+ otg_del_timer(fsm, A_WAIT_VFALL);
+ fsm->a_wait_vfall_tmout = 0;
+ otg_del_timer(fsm, A_WAIT_VRISE);
+ break;
+ case OTG_STATE_A_VBUS_ERR:
+ break;
+ default:
+ break;
+ }
+}
+
+static void otg_hnp_polling_work(struct work_struct *work)
+{
+ struct otg_fsm *fsm = container_of(to_delayed_work(work),
+ struct otg_fsm, hnp_polling_work);
+ struct usb_device *udev;
+ enum usb_otg_state state = fsm->otg->state;
+ u8 flag;
+ int retval;
+
+ if (state != OTG_STATE_A_HOST && state != OTG_STATE_B_HOST)
+ return;
+
+ udev = usb_hub_find_child(fsm->otg->host->root_hub, 1);
+ if (!udev) {
+ dev_err(fsm->otg->host->controller,
+ "no usb dev connected, can't start HNP polling\n");
+ return;
+ }
+
+ *fsm->host_req_flag = 0;
+ /* Get host request flag from connected USB device */
+ retval = usb_control_msg(udev,
+ usb_rcvctrlpipe(udev, 0),
+ USB_REQ_GET_STATUS,
+ USB_DIR_IN | USB_RECIP_DEVICE,
+ 0,
+ OTG_STS_SELECTOR,
+ fsm->host_req_flag,
+ 1,
+ USB_CTRL_GET_TIMEOUT);
+ if (retval != 1) {
+ dev_err(&udev->dev, "Get one byte OTG status failed\n");
+ return;
+ }
+
+ flag = *fsm->host_req_flag;
+ if (flag == 0) {
+ /* Continue HNP polling */
+ schedule_delayed_work(&fsm->hnp_polling_work,
+ msecs_to_jiffies(T_HOST_REQ_POLL));
+ return;
+ } else if (flag != HOST_REQUEST_FLAG) {
+ dev_err(&udev->dev, "host request flag %d is invalid\n", flag);
+ return;
+ }
+
+ /* Host request flag is set */
+ if (state == OTG_STATE_A_HOST) {
+ /* Set b_hnp_enable */
+ if (!fsm->otg->host->b_hnp_enable) {
+ retval = usb_control_msg(udev,
+ usb_sndctrlpipe(udev, 0),
+ USB_REQ_SET_FEATURE, 0,
+ USB_DEVICE_B_HNP_ENABLE,
+ 0, NULL, 0,
+ USB_CTRL_SET_TIMEOUT);
+ if (retval >= 0)
+ fsm->otg->host->b_hnp_enable = 1;
+ }
+ fsm->a_bus_req = 0;
+ } else if (state == OTG_STATE_B_HOST) {
+ fsm->b_bus_req = 0;
+ }
+
+ otg_statemachine(fsm);
+}
+
+static void otg_start_hnp_polling(struct otg_fsm *fsm)
+{
+ /*
+ * The memory of host_req_flag should be allocated by
+ * controller driver, otherwise, hnp polling is not started.
+ */
+ if (!fsm->host_req_flag)
+ return;
+
+ if (!fsm->hnp_work_inited) {
+ INIT_DELAYED_WORK(&fsm->hnp_polling_work, otg_hnp_polling_work);
+ fsm->hnp_work_inited = true;
+ }
+
+ schedule_delayed_work(&fsm->hnp_polling_work,
+ msecs_to_jiffies(T_HOST_REQ_POLL));
+}
+
+/* Called when entering a state */
+static int otg_set_state(struct otg_fsm *fsm, enum usb_otg_state new_state)
+{
+ if (fsm->otg->state == new_state)
+ return 0;
+ VDBG("Set state: %s\n", usb_otg_state_string(new_state));
+ otg_leave_state(fsm, fsm->otg->state);
+ switch (new_state) {
+ case OTG_STATE_B_IDLE:
+ otg_drv_vbus(fsm, 0);
+ otg_chrg_vbus(fsm, 0);
+ otg_loc_conn(fsm, 0);
+ otg_loc_sof(fsm, 0);
+ /*
+ * Driver is responsible for starting ADP probing
+ * if ADP sensing times out.
+ */
+ otg_start_adp_sns(fsm);
+ otg_set_protocol(fsm, PROTO_UNDEF);
+ otg_add_timer(fsm, B_SE0_SRP);
+ break;
+ case OTG_STATE_B_SRP_INIT:
+ otg_start_pulse(fsm);
+ otg_loc_sof(fsm, 0);
+ otg_set_protocol(fsm, PROTO_UNDEF);
+ otg_add_timer(fsm, B_SRP_FAIL);
+ break;
+ case OTG_STATE_B_PERIPHERAL:
+ otg_chrg_vbus(fsm, 0);
+ otg_loc_sof(fsm, 0);
+ otg_set_protocol(fsm, PROTO_GADGET);
+ otg_loc_conn(fsm, 1);
+ break;
+ case OTG_STATE_B_WAIT_ACON:
+ otg_chrg_vbus(fsm, 0);
+ otg_loc_conn(fsm, 0);
+ otg_loc_sof(fsm, 0);
+ otg_set_protocol(fsm, PROTO_HOST);
+ otg_add_timer(fsm, B_ASE0_BRST);
+ fsm->a_bus_suspend = 0;
+ break;
+ case OTG_STATE_B_HOST:
+ otg_chrg_vbus(fsm, 0);
+ otg_loc_conn(fsm, 0);
+ otg_loc_sof(fsm, 1);
+ otg_set_protocol(fsm, PROTO_HOST);
+ usb_bus_start_enum(fsm->otg->host,
+ fsm->otg->host->otg_port);
+ otg_start_hnp_polling(fsm);
+ break;
+ case OTG_STATE_A_IDLE:
+ otg_drv_vbus(fsm, 0);
+ otg_chrg_vbus(fsm, 0);
+ otg_loc_conn(fsm, 0);
+ otg_loc_sof(fsm, 0);
+ otg_start_adp_prb(fsm);
+ otg_set_protocol(fsm, PROTO_HOST);
+ break;
+ case OTG_STATE_A_WAIT_VRISE:
+ otg_drv_vbus(fsm, 1);
+ otg_loc_conn(fsm, 0);
+ otg_loc_sof(fsm, 0);
+ otg_set_protocol(fsm, PROTO_HOST);
+ otg_add_timer(fsm, A_WAIT_VRISE);
+ break;
+ case OTG_STATE_A_WAIT_BCON:
+ otg_drv_vbus(fsm, 1);
+ otg_loc_conn(fsm, 0);
+ otg_loc_sof(fsm, 0);
+ otg_set_protocol(fsm, PROTO_HOST);
+ otg_add_timer(fsm, A_WAIT_BCON);
+ break;
+ case OTG_STATE_A_HOST:
+ otg_drv_vbus(fsm, 1);
+ otg_loc_conn(fsm, 0);
+ otg_loc_sof(fsm, 1);
+ otg_set_protocol(fsm, PROTO_HOST);
+ /*
+ * When HNP is triggered while a_bus_req = 0, a_host will
+ * suspend too fast to complete a_set_b_hnp_en
+ */
+ if (!fsm->a_bus_req || fsm->a_suspend_req_inf)
+ otg_add_timer(fsm, A_WAIT_ENUM);
+ otg_start_hnp_polling(fsm);
+ break;
+ case OTG_STATE_A_SUSPEND:
+ otg_drv_vbus(fsm, 1);
+ otg_loc_conn(fsm, 0);
+ otg_loc_sof(fsm, 0);
+ otg_set_protocol(fsm, PROTO_HOST);
+ otg_add_timer(fsm, A_AIDL_BDIS);
+
+ break;
+ case OTG_STATE_A_PERIPHERAL:
+ otg_loc_sof(fsm, 0);
+ otg_set_protocol(fsm, PROTO_GADGET);
+ otg_drv_vbus(fsm, 1);
+ otg_loc_conn(fsm, 1);
+ otg_add_timer(fsm, A_BIDL_ADIS);
+ break;
+ case OTG_STATE_A_WAIT_VFALL:
+ otg_drv_vbus(fsm, 0);
+ otg_loc_conn(fsm, 0);
+ otg_loc_sof(fsm, 0);
+ otg_set_protocol(fsm, PROTO_HOST);
+ otg_add_timer(fsm, A_WAIT_VFALL);
+ break;
+ case OTG_STATE_A_VBUS_ERR:
+ otg_drv_vbus(fsm, 0);
+ otg_loc_conn(fsm, 0);
+ otg_loc_sof(fsm, 0);
+ otg_set_protocol(fsm, PROTO_UNDEF);
+ break;
+ default:
+ break;
+ }
+
+ fsm->otg->state = new_state;
+ fsm->state_changed = 1;
+ return 0;
+}
+
+/* State change judgement */
+int otg_statemachine(struct otg_fsm *fsm)
+{
+ enum usb_otg_state state;
+
+ mutex_lock(&fsm->lock);
+
+ state = fsm->otg->state;
+ fsm->state_changed = 0;
+ /* State machine state change judgement */
+
+ switch (state) {
+ case OTG_STATE_UNDEFINED:
+ VDBG("fsm->id = %d\n", fsm->id);
+ if (fsm->id)
+ otg_set_state(fsm, OTG_STATE_B_IDLE);
+ else
+ otg_set_state(fsm, OTG_STATE_A_IDLE);
+ break;
+ case OTG_STATE_B_IDLE:
+ if (!fsm->id)
+ otg_set_state(fsm, OTG_STATE_A_IDLE);
+ else if (fsm->b_sess_vld && fsm->otg->gadget)
+ otg_set_state(fsm, OTG_STATE_B_PERIPHERAL);
+ else if ((fsm->b_bus_req || fsm->adp_change || fsm->power_up) &&
+ fsm->b_ssend_srp && fsm->b_se0_srp)
+ otg_set_state(fsm, OTG_STATE_B_SRP_INIT);
+ break;
+ case OTG_STATE_B_SRP_INIT:
+ if (!fsm->id || fsm->b_srp_done)
+ otg_set_state(fsm, OTG_STATE_B_IDLE);
+ break;
+ case OTG_STATE_B_PERIPHERAL:
+ if (!fsm->id || !fsm->b_sess_vld)
+ otg_set_state(fsm, OTG_STATE_B_IDLE);
+ else if (fsm->b_bus_req && fsm->otg->
+ gadget->b_hnp_enable && fsm->a_bus_suspend)
+ otg_set_state(fsm, OTG_STATE_B_WAIT_ACON);
+ break;
+ case OTG_STATE_B_WAIT_ACON:
+ if (fsm->a_conn)
+ otg_set_state(fsm, OTG_STATE_B_HOST);
+ else if (!fsm->id || !fsm->b_sess_vld)
+ otg_set_state(fsm, OTG_STATE_B_IDLE);
+ else if (fsm->a_bus_resume || fsm->b_ase0_brst_tmout) {
+ fsm->b_ase0_brst_tmout = 0;
+ otg_set_state(fsm, OTG_STATE_B_PERIPHERAL);
+ }
+ break;
+ case OTG_STATE_B_HOST:
+ if (!fsm->id || !fsm->b_sess_vld)
+ otg_set_state(fsm, OTG_STATE_B_IDLE);
+ else if (!fsm->b_bus_req || !fsm->a_conn || fsm->test_device)
+ otg_set_state(fsm, OTG_STATE_B_PERIPHERAL);
+ break;
+ case OTG_STATE_A_IDLE:
+ if (fsm->id)
+ otg_set_state(fsm, OTG_STATE_B_IDLE);
+ else if (!fsm->a_bus_drop && (fsm->a_bus_req ||
+ fsm->a_srp_det || fsm->adp_change || fsm->power_up))
+ otg_set_state(fsm, OTG_STATE_A_WAIT_VRISE);
+ break;
+ case OTG_STATE_A_WAIT_VRISE:
+ if (fsm->a_vbus_vld)
+ otg_set_state(fsm, OTG_STATE_A_WAIT_BCON);
+ else if (fsm->id || fsm->a_bus_drop ||
+ fsm->a_wait_vrise_tmout)
+ otg_set_state(fsm, OTG_STATE_A_WAIT_VFALL);
+ break;
+ case OTG_STATE_A_WAIT_BCON:
+ if (!fsm->a_vbus_vld)
+ otg_set_state(fsm, OTG_STATE_A_VBUS_ERR);
+ else if (fsm->b_conn)
+ otg_set_state(fsm, OTG_STATE_A_HOST);
+ else if (fsm->id || fsm->a_bus_drop || fsm->a_wait_bcon_tmout)
+ otg_set_state(fsm, OTG_STATE_A_WAIT_VFALL);
+ break;
+ case OTG_STATE_A_HOST:
+ if (fsm->id || fsm->a_bus_drop)
+ otg_set_state(fsm, OTG_STATE_A_WAIT_VFALL);
+ else if ((!fsm->a_bus_req || fsm->a_suspend_req_inf) &&
+ fsm->otg->host->b_hnp_enable)
+ otg_set_state(fsm, OTG_STATE_A_SUSPEND);
+ else if (!fsm->b_conn)
+ otg_set_state(fsm, OTG_STATE_A_WAIT_BCON);
+ else if (!fsm->a_vbus_vld)
+ otg_set_state(fsm, OTG_STATE_A_VBUS_ERR);
+ break;
+ case OTG_STATE_A_SUSPEND:
+ if (!fsm->b_conn && fsm->otg->host->b_hnp_enable)
+ otg_set_state(fsm, OTG_STATE_A_PERIPHERAL);
+ else if (!fsm->b_conn && !fsm->otg->host->b_hnp_enable)
+ otg_set_state(fsm, OTG_STATE_A_WAIT_BCON);
+ else if (fsm->a_bus_req || fsm->b_bus_resume)
+ otg_set_state(fsm, OTG_STATE_A_HOST);
+ else if (fsm->id || fsm->a_bus_drop || fsm->a_aidl_bdis_tmout)
+ otg_set_state(fsm, OTG_STATE_A_WAIT_VFALL);
+ else if (!fsm->a_vbus_vld)
+ otg_set_state(fsm, OTG_STATE_A_VBUS_ERR);
+ break;
+ case OTG_STATE_A_PERIPHERAL:
+ if (fsm->id || fsm->a_bus_drop)
+ otg_set_state(fsm, OTG_STATE_A_WAIT_VFALL);
+ else if (fsm->a_bidl_adis_tmout || fsm->b_bus_suspend)
+ otg_set_state(fsm, OTG_STATE_A_WAIT_BCON);
+ else if (!fsm->a_vbus_vld)
+ otg_set_state(fsm, OTG_STATE_A_VBUS_ERR);
+ break;
+ case OTG_STATE_A_WAIT_VFALL:
+ if (fsm->a_wait_vfall_tmout)
+ otg_set_state(fsm, OTG_STATE_A_IDLE);
+ break;
+ case OTG_STATE_A_VBUS_ERR:
+ if (fsm->id || fsm->a_bus_drop || fsm->a_clr_err)
+ otg_set_state(fsm, OTG_STATE_A_WAIT_VFALL);
+ break;
+ default:
+ break;
+ }
+ mutex_unlock(&fsm->lock);
+
+ VDBG("quit statemachine, changed = %d\n", fsm->state_changed);
+ return fsm->state_changed;
+}
+EXPORT_SYMBOL_GPL(otg_statemachine);
+MODULE_LICENSE("GPL");