[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-id: <1426687464-2563-17-git-send-email-r.baldyga@samsung.com>
Date: Wed, 18 Mar 2015 15:04:21 +0100
From: Robert Baldyga <r.baldyga@...sung.com>
To: balbi@...com
Cc: gregkh@...uxfoundation.org, myungjoo.ham@...sung.com,
cw00.choi@...sung.com, linux-usb@...r.kernel.org,
linux-kernel@...r.kernel.org, m.szyprowski@...sung.com,
Robert Baldyga <r.baldyga@...sung.com>
Subject: [RFC 16/19] dwc3: exynos: add software role switching code
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@...sung.com>
---
.../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@...r.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/
Powered by blists - more mailing lists