[RFC 16/19] dwc3: exynos: add software role switching code

From: Robert Baldyga
Date: Wed Mar 18 2015 - 10:06:46 EST


Exynos platform doesn't have hardware OTG support in DWC3 block, so we
need to supply custom mechanism of notification about cable change. For
this purpose we use extcon framework - it would allow to use this code
with each Exynos board equipped with cable detecting hardware which has
its extcon driver.

Signed-off-by: Robert Baldyga <r.baldyga@xxxxxxxxxxx>
---
.../devicetree/bindings/usb/exynos-usb.txt | 4 +
drivers/usb/dwc3/dwc3-exynos.c | 162 +++++++++++++++++++++
drivers/usb/dwc3/otg.c | 25 ++++
drivers/usb/dwc3/otg.h | 9 ++
4 files changed, 200 insertions(+)

diff --git a/Documentation/devicetree/bindings/usb/exynos-usb.txt b/Documentation/devicetree/bindings/usb/exynos-usb.txt
index 9b4dbe3..34b049f 100644
--- a/Documentation/devicetree/bindings/usb/exynos-usb.txt
+++ b/Documentation/devicetree/bindings/usb/exynos-usb.txt
@@ -93,6 +93,10 @@ Required properties:
- clocks: Clock IDs array as required by the controller.
- clock-names: names of clocks correseponding to IDs in the clock property

+Optional properties:
+ - extcon: Phandle to extcon device which notifies about USB cable changes.
+ It's used in OTG mode.
+
Sub-nodes:
The dwc3 core should be added as subnode to Exynos dwc3 glue.
- dwc3 :
diff --git a/drivers/usb/dwc3/dwc3-exynos.c b/drivers/usb/dwc3/dwc3-exynos.c
index 7bd0a95..30a0560 100644
--- a/drivers/usb/dwc3/dwc3-exynos.c
+++ b/drivers/usb/dwc3/dwc3-exynos.c
@@ -27,6 +27,10 @@
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/regulator/consumer.h>
+#include <linux/extcon.h>
+#include <linux/workqueue.h>
+
+#include "otg.h"

struct dwc3_exynos {
struct platform_device *usb2_phy;
@@ -39,8 +43,107 @@ struct dwc3_exynos {

struct regulator *vdd33;
struct regulator *vdd10;
+
+ struct otg_fsm *fsm;
+
+ struct extcon_dev *extcon;
+ struct extcon_specific_cable_nb extcon_usb_dev;
+ struct extcon_specific_cable_nb extcon_usb_host_dev;
+ struct notifier_block usb_nb;
+ struct notifier_block usb_host_nb;
+ struct work_struct work;
};

+static int dwc3_exynos_usb_notifier(struct notifier_block *nb,
+ unsigned long event, void *ptr);
+
+static int dwc3_exynos_usb_host_notifier(struct notifier_block *nb,
+ unsigned long event, void *ptr);
+
+int dwc3_exynos_rsw_start(struct device *dev)
+{
+ struct dwc3_exynos *exynos = dev_get_drvdata(dev);
+ int ret;
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ if (!exynos->extcon)
+ return -ENODEV;
+
+ exynos->usb_nb.notifier_call = dwc3_exynos_usb_notifier;
+ exynos->usb_host_nb.notifier_call = dwc3_exynos_usb_host_notifier;
+
+ ret = extcon_register_interest(&exynos->extcon_usb_dev,
+ exynos->extcon->name, "USB", &exynos->usb_nb);
+ if (ret < 0) {
+ dev_dbg(dev, "failed to register notifier for USB");
+ return -ENODEV;
+ }
+
+ ret = extcon_register_interest(&exynos->extcon_usb_host_dev,
+ exynos->extcon->name, "USB-HOST", &exynos->usb_host_nb);
+ if (ret < 0) {
+ dev_dbg(dev, "failed to register notifier for USB HOST");
+ extcon_unregister_interest(&exynos->extcon_usb_dev);
+ return -ENODEV;
+ }
+
+ if (extcon_get_cable_state(exynos->extcon, "USB")) {
+ exynos->fsm->b_sess_vld = 1;
+ exynos->fsm->id = 1;
+ } else if (extcon_get_cable_state(exynos->extcon, "USB-HOST")) {
+ exynos->fsm->b_sess_vld = 0;
+ exynos->fsm->id = 0;
+ } else {
+ exynos->fsm->b_sess_vld = 0;
+ exynos->fsm->id = 1;
+ }
+
+ dwc3_otg_run_sm(exynos->fsm);
+
+ return 0;
+}
+
+void dwc3_exynos_rsw_stop(struct device *dev)
+{
+ struct dwc3_exynos *exynos = dev_get_drvdata(dev);
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ if (exynos->extcon_usb_dev.edev)
+ extcon_unregister_interest(&exynos->extcon_usb_dev);
+ if (exynos->extcon_usb_host_dev.edev)
+ extcon_unregister_interest(&exynos->extcon_usb_host_dev);
+}
+
+int dwc3_exynos_rsw_setup(struct device *dev, struct otg_fsm *fsm)
+{
+ struct dwc3_exynos *exynos = dev_get_drvdata(dev);
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ exynos->fsm = fsm;
+
+ return 0;
+}
+
+void dwc3_exynos_rsw_exit(struct device *dev)
+{
+ struct dwc3_exynos *exynos = dev_get_drvdata(dev);
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ exynos->fsm = NULL;
+}
+
+bool dwc3_exynos_rsw_available(struct device *dev)
+{
+ if (of_property_read_bool(dev->of_node, "extcon"))
+ return true;
+
+ return false;
+}
+
static int dwc3_exynos_register_phys(struct dwc3_exynos *exynos)
{
struct usb_phy_generic_platform_data pdata;
@@ -105,18 +208,77 @@ static int dwc3_exynos_remove_child(struct device *dev, void *unused)
return 0;
}

+static void dwc3_exynos_worker(struct work_struct *work)
+{
+ struct dwc3_exynos *exynos =
+ container_of(work, struct dwc3_exynos, work);
+
+ dwc3_otg_run_sm(exynos->fsm);
+}
+
+static int dwc3_exynos_usb_notifier(struct notifier_block *nb,
+ unsigned long event, void *ptr)
+{
+ struct dwc3_exynos *exynos =
+ container_of(nb, struct dwc3_exynos, usb_nb);
+
+ if (event) {
+ exynos->fsm->b_sess_vld = 1;
+ exynos->fsm->id = 1;
+ } else {
+ exynos->fsm->b_sess_vld = 0;
+ exynos->fsm->id = 1;
+ }
+
+ schedule_work(&exynos->work);
+
+ return 0;
+}
+
+static int dwc3_exynos_usb_host_notifier(struct notifier_block *nb,
+ unsigned long event, void *ptr)
+{
+ struct dwc3_exynos *exynos =
+ container_of(nb, struct dwc3_exynos, usb_host_nb);
+
+ if (event) {
+ exynos->fsm->b_sess_vld = 0;
+ exynos->fsm->id = 0;
+ } else {
+ exynos->fsm->b_sess_vld = 0;
+ exynos->fsm->id = 1;
+ }
+
+ schedule_work(&exynos->work);
+
+ return 0;
+}
+
static int dwc3_exynos_probe(struct platform_device *pdev)
{
struct dwc3_exynos *exynos;
struct device *dev = &pdev->dev;
struct device_node *node = dev->of_node;
+ struct extcon_dev *extcon = NULL;

int ret;

+ if (of_property_read_bool(node, "extcon")) {
+ extcon = extcon_get_edev_by_phandle(dev, 0);
+ if (IS_ERR(extcon)) {
+ dev_vdbg(dev, "couldn't get extcon device\n");
+ return -EPROBE_DEFER;
+ }
+ }
+
exynos = devm_kzalloc(dev, sizeof(*exynos), GFP_KERNEL);
if (!exynos)
return -ENOMEM;

+ exynos->extcon = extcon;
+
+ INIT_WORK(&exynos->work, dwc3_exynos_worker);
+
/*
* Right now device-tree probed devices don't get dma_mask set.
* Since shared usb code relies on it, set it here for now.
diff --git a/drivers/usb/dwc3/otg.c b/drivers/usb/dwc3/otg.c
index 99dfd2c..507bade 100644
--- a/drivers/usb/dwc3/otg.c
+++ b/drivers/usb/dwc3/otg.c
@@ -29,10 +29,35 @@
#include "otg.h"
#include "io.h"

+#if IS_ENABLED(CONFIG_USB_DWC3_EXYNOS)
+static struct dwc3_ext_otg_ops *dwc3_otg_rsw_probe(struct dwc3 *dwc)
+{
+ struct dwc3_ext_otg_ops *ops;
+ bool ext_otg;
+
+ ext_otg = dwc3_exynos_rsw_available(dwc->dev->parent);
+ if (!ext_otg)
+ return NULL;
+
+ /* Allocate and init otg instance */
+ ops = devm_kzalloc(dwc->dev, sizeof(struct dwc3_ext_otg_ops),
+ GFP_KERNEL);
+ if (!ops)
+ return NULL;
+
+ ops->setup = dwc3_exynos_rsw_setup;
+ ops->exit = dwc3_exynos_rsw_exit;
+ ops->start = dwc3_exynos_rsw_start;
+ ops->stop = dwc3_exynos_rsw_stop;
+
+ return ops;
+}
+#else
static struct dwc3_ext_otg_ops *dwc3_otg_rsw_probe(struct dwc3 *dwc)
{
return NULL;
}
+#endif

static void dwc3_otg_set_host_mode(struct dwc3_otg *dotg)
{
diff --git a/drivers/usb/dwc3/otg.h b/drivers/usb/dwc3/otg.h
index 4be7165..efed700 100644
--- a/drivers/usb/dwc3/otg.h
+++ b/drivers/usb/dwc3/otg.h
@@ -101,4 +101,13 @@ void dwc3_otg_exit(struct dwc3 *dwc);

void dwc3_otg_run_sm(struct otg_fsm *fsm);

+/* prototypes */
+#if IS_ENABLED(CONFIG_USB_DWC3_EXYNOS)
+bool dwc3_exynos_rsw_available(struct device *dev);
+int dwc3_exynos_rsw_setup(struct device *dev, struct otg_fsm *fsm);
+void dwc3_exynos_rsw_exit(struct device *dev);
+int dwc3_exynos_rsw_start(struct device *dev);
+void dwc3_exynos_rsw_stop(struct device *dev);
+#endif
+
#endif /* __LINUX_USB_DWC3_OTG_H */
--
1.9.1

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