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-usb" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html