usb: dwc3: Add dual-role support
[linux-2.6-block.git] / drivers / usb / dwc3 / drd.c
1 /**
2  * drd.c - DesignWare USB3 DRD Controller Dual-role support
3  *
4  * Copyright (C) 2017 Texas Instruments Incorporated - http://www.ti.com
5  *
6  * Authors: Roger Quadros <rogerq@ti.com>
7  *
8  * This program is free software: you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License version 2  of
10  * the License as published by the Free Software Foundation.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
19  */
20
21 #include <linux/extcon.h>
22
23 #include "debug.h"
24 #include "core.h"
25 #include "gadget.h"
26
27 static void dwc3_drd_update(struct dwc3 *dwc)
28 {
29         int id;
30
31         id = extcon_get_state(dwc->edev, EXTCON_USB_HOST);
32         if (id < 0)
33                 id = 0;
34
35         dwc3_set_mode(dwc, id ?
36                       DWC3_GCTL_PRTCAP_HOST :
37                       DWC3_GCTL_PRTCAP_DEVICE);
38 }
39
40 static int dwc3_drd_notifier(struct notifier_block *nb,
41                              unsigned long event, void *ptr)
42 {
43         struct dwc3 *dwc = container_of(nb, struct dwc3, edev_nb);
44
45         dwc3_set_mode(dwc, event ?
46                       DWC3_GCTL_PRTCAP_HOST :
47                       DWC3_GCTL_PRTCAP_DEVICE);
48
49         return NOTIFY_DONE;
50 }
51
52 int dwc3_drd_init(struct dwc3 *dwc)
53 {
54         int ret;
55
56         if (dwc->dev->of_node) {
57                 if (of_property_read_bool(dwc->dev->of_node, "extcon"))
58                         dwc->edev = extcon_get_edev_by_phandle(dwc->dev, 0);
59
60                 if (IS_ERR(dwc->edev))
61                         return PTR_ERR(dwc->edev);
62
63                 dwc->edev_nb.notifier_call = dwc3_drd_notifier;
64                 ret = extcon_register_notifier(dwc->edev, EXTCON_USB_HOST,
65                                                &dwc->edev_nb);
66                 if (ret < 0) {
67                         dev_err(dwc->dev, "couldn't register cable notifier\n");
68                         return ret;
69                 }
70         }
71
72         dwc3_drd_update(dwc);
73
74         return 0;
75 }
76
77 void dwc3_drd_exit(struct dwc3 *dwc)
78 {
79         extcon_unregister_notifier(dwc->edev, EXTCON_USB_HOST,
80                                    &dwc->edev_nb);
81
82         dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_DEVICE);
83         flush_work(&dwc->drd_work);
84         dwc3_gadget_exit(dwc);
85 }