From: Robert Baldyga Date: Mon, 9 Mar 2015 12:28:18 +0000 (+0100) Subject: dwc3: exynos: add software role switching code X-Git-Tag: submit/tizen/20150416.081342~31 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=0bb5e94bc26bc3b8d962fc3c0d05e4613f23c7ec;p=platform%2Fkernel%2Flinux-exynos.git dwc3: exynos: add software role switching code Exynos platform doesn't have hardware OTG support, so we need to supply mechanism of notification about cable change. Signed-off-by: Robert Baldyga --- diff --git a/drivers/usb/dwc3/dwc3-exynos.c b/drivers/usb/dwc3/dwc3-exynos.c index 7bd0a95b2815..30a0560fe21f 100644 --- a/drivers/usb/dwc3/dwc3-exynos.c +++ b/drivers/usb/dwc3/dwc3-exynos.c @@ -27,6 +27,10 @@ #include #include #include +#include +#include + +#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 56bc6b667ff0..9c0da03da407 100644 --- a/drivers/usb/dwc3/otg.c +++ b/drivers/usb/dwc3/otg.c @@ -28,10 +28,37 @@ #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) { + dev_err(dwc->dev, "unable to allocate dwc3_ext_otg_ops\n"); + 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 int dwc3_otg_statemachine(struct otg_fsm *fsm, bool reset) { diff --git a/drivers/usb/dwc3/otg.h b/drivers/usb/dwc3/otg.h index 25bc02734b3d..024b8850692d 100644 --- a/drivers/usb/dwc3/otg.h +++ b/drivers/usb/dwc3/otg.h @@ -96,4 +96,13 @@ static inline int dwc3_ext_otg_stop(struct dwc3_otg *dotg) 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 */