xref: /linux/drivers/usb/chipidea/otg.c (revision 5fd54ace4721fc5ce2bb5aef6318fcf17f421460)
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * otg.c - ChipIdea USB IP core OTG driver
4  *
5  * Copyright (C) 2013 Freescale Semiconductor, Inc.
6  *
7  * Author: Peter Chen
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License version 2 as
11  * published by the Free Software Foundation.
12  */
13 
14 /*
15  * This file mainly handles otgsc register, OTG fsm operations for HNP and SRP
16  * are also included.
17  */
18 
19 #include <linux/usb/otg.h>
20 #include <linux/usb/gadget.h>
21 #include <linux/usb/chipidea.h>
22 
23 #include "ci.h"
24 #include "bits.h"
25 #include "otg.h"
26 #include "otg_fsm.h"
27 
28 /**
29  * hw_read_otgsc returns otgsc register bits value.
30  * @mask: bitfield mask
31  */
32 u32 hw_read_otgsc(struct ci_hdrc *ci, u32 mask)
33 {
34 	struct ci_hdrc_cable *cable;
35 	u32 val = hw_read(ci, OP_OTGSC, mask);
36 
37 	/*
38 	 * If using extcon framework for VBUS and/or ID signal
39 	 * detection overwrite OTGSC register value
40 	 */
41 	cable = &ci->platdata->vbus_extcon;
42 	if (!IS_ERR(cable->edev)) {
43 		if (cable->changed)
44 			val |= OTGSC_BSVIS;
45 		else
46 			val &= ~OTGSC_BSVIS;
47 
48 		if (cable->connected)
49 			val |= OTGSC_BSV;
50 		else
51 			val &= ~OTGSC_BSV;
52 
53 		if (cable->enabled)
54 			val |= OTGSC_BSVIE;
55 		else
56 			val &= ~OTGSC_BSVIE;
57 	}
58 
59 	cable = &ci->platdata->id_extcon;
60 	if (!IS_ERR(cable->edev)) {
61 		if (cable->changed)
62 			val |= OTGSC_IDIS;
63 		else
64 			val &= ~OTGSC_IDIS;
65 
66 		if (cable->connected)
67 			val &= ~OTGSC_ID; /* host */
68 		else
69 			val |= OTGSC_ID; /* device */
70 
71 		if (cable->enabled)
72 			val |= OTGSC_IDIE;
73 		else
74 			val &= ~OTGSC_IDIE;
75 	}
76 
77 	return val & mask;
78 }
79 
80 /**
81  * hw_write_otgsc updates target bits of OTGSC register.
82  * @mask: bitfield mask
83  * @data: to be written
84  */
85 void hw_write_otgsc(struct ci_hdrc *ci, u32 mask, u32 data)
86 {
87 	struct ci_hdrc_cable *cable;
88 
89 	cable = &ci->platdata->vbus_extcon;
90 	if (!IS_ERR(cable->edev)) {
91 		if (data & mask & OTGSC_BSVIS)
92 			cable->changed = false;
93 
94 		/* Don't enable vbus interrupt if using external notifier */
95 		if (data & mask & OTGSC_BSVIE) {
96 			cable->enabled = true;
97 			data &= ~OTGSC_BSVIE;
98 		} else if (mask & OTGSC_BSVIE) {
99 			cable->enabled = false;
100 		}
101 	}
102 
103 	cable = &ci->platdata->id_extcon;
104 	if (!IS_ERR(cable->edev)) {
105 		if (data & mask & OTGSC_IDIS)
106 			cable->changed = false;
107 
108 		/* Don't enable id interrupt if using external notifier */
109 		if (data & mask & OTGSC_IDIE) {
110 			cable->enabled = true;
111 			data &= ~OTGSC_IDIE;
112 		} else if (mask & OTGSC_IDIE) {
113 			cable->enabled = false;
114 		}
115 	}
116 
117 	hw_write(ci, OP_OTGSC, mask | OTGSC_INT_STATUS_BITS, data);
118 }
119 
120 /**
121  * ci_otg_role - pick role based on ID pin state
122  * @ci: the controller
123  */
124 enum ci_role ci_otg_role(struct ci_hdrc *ci)
125 {
126 	enum ci_role role = hw_read_otgsc(ci, OTGSC_ID)
127 		? CI_ROLE_GADGET
128 		: CI_ROLE_HOST;
129 
130 	return role;
131 }
132 
133 void ci_handle_vbus_change(struct ci_hdrc *ci)
134 {
135 	if (!ci->is_otg)
136 		return;
137 
138 	if (hw_read_otgsc(ci, OTGSC_BSV) && !ci->vbus_active)
139 		usb_gadget_vbus_connect(&ci->gadget);
140 	else if (!hw_read_otgsc(ci, OTGSC_BSV) && ci->vbus_active)
141 		usb_gadget_vbus_disconnect(&ci->gadget);
142 }
143 
144 /**
145  * When we switch to device mode, the vbus value should be lower
146  * than OTGSC_BSV before connecting to host.
147  *
148  * @ci: the controller
149  *
150  * This function returns an error code if timeout
151  */
152 static int hw_wait_vbus_lower_bsv(struct ci_hdrc *ci)
153 {
154 	unsigned long elapse = jiffies + msecs_to_jiffies(5000);
155 	u32 mask = OTGSC_BSV;
156 
157 	while (hw_read_otgsc(ci, mask)) {
158 		if (time_after(jiffies, elapse)) {
159 			dev_err(ci->dev, "timeout waiting for %08x in OTGSC\n",
160 					mask);
161 			return -ETIMEDOUT;
162 		}
163 		msleep(20);
164 	}
165 
166 	return 0;
167 }
168 
169 static void ci_handle_id_switch(struct ci_hdrc *ci)
170 {
171 	enum ci_role role = ci_otg_role(ci);
172 
173 	if (role != ci->role) {
174 		dev_dbg(ci->dev, "switching from %s to %s\n",
175 			ci_role(ci)->name, ci->roles[role]->name);
176 
177 		ci_role_stop(ci);
178 
179 		if (role == CI_ROLE_GADGET &&
180 				IS_ERR(ci->platdata->vbus_extcon.edev))
181 			/*
182 			 * Wait vbus lower than OTGSC_BSV before connecting
183 			 * to host. If connecting status is from an external
184 			 * connector instead of register, we don't need to
185 			 * care vbus on the board, since it will not affect
186 			 * external connector status.
187 			 */
188 			hw_wait_vbus_lower_bsv(ci);
189 
190 		ci_role_start(ci, role);
191 		/* vbus change may have already occurred */
192 		if (role == CI_ROLE_GADGET)
193 			ci_handle_vbus_change(ci);
194 	}
195 }
196 /**
197  * ci_otg_work - perform otg (vbus/id) event handle
198  * @work: work struct
199  */
200 static void ci_otg_work(struct work_struct *work)
201 {
202 	struct ci_hdrc *ci = container_of(work, struct ci_hdrc, work);
203 
204 	if (ci_otg_is_fsm_mode(ci) && !ci_otg_fsm_work(ci)) {
205 		enable_irq(ci->irq);
206 		return;
207 	}
208 
209 	pm_runtime_get_sync(ci->dev);
210 	if (ci->id_event) {
211 		ci->id_event = false;
212 		ci_handle_id_switch(ci);
213 	} else if (ci->b_sess_valid_event) {
214 		ci->b_sess_valid_event = false;
215 		ci_handle_vbus_change(ci);
216 	} else
217 		dev_err(ci->dev, "unexpected event occurs at %s\n", __func__);
218 	pm_runtime_put_sync(ci->dev);
219 
220 	enable_irq(ci->irq);
221 }
222 
223 
224 /**
225  * ci_hdrc_otg_init - initialize otg struct
226  * ci: the controller
227  */
228 int ci_hdrc_otg_init(struct ci_hdrc *ci)
229 {
230 	INIT_WORK(&ci->work, ci_otg_work);
231 	ci->wq = create_freezable_workqueue("ci_otg");
232 	if (!ci->wq) {
233 		dev_err(ci->dev, "can't create workqueue\n");
234 		return -ENODEV;
235 	}
236 
237 	if (ci_otg_is_fsm_mode(ci))
238 		return ci_hdrc_otg_fsm_init(ci);
239 
240 	return 0;
241 }
242 
243 /**
244  * ci_hdrc_otg_destroy - destroy otg struct
245  * ci: the controller
246  */
247 void ci_hdrc_otg_destroy(struct ci_hdrc *ci)
248 {
249 	if (ci->wq) {
250 		flush_workqueue(ci->wq);
251 		destroy_workqueue(ci->wq);
252 	}
253 	/* Disable all OTG irq and clear status */
254 	hw_write_otgsc(ci, OTGSC_INT_EN_BITS | OTGSC_INT_STATUS_BITS,
255 						OTGSC_INT_STATUS_BITS);
256 	if (ci_otg_is_fsm_mode(ci))
257 		ci_hdrc_otg_fsm_remove(ci);
258 }
259