Commit | Line | Data |
---|---|---|
57677be5 LJ |
1 | /* |
2 | * otg_fsm.c - ChipIdea USB IP core OTG FSM driver | |
3 | * | |
4 | * Copyright (C) 2014 Freescale Semiconductor, Inc. | |
5 | * | |
6 | * Author: Jun Li | |
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 as | |
10 | * published by the Free Software Foundation. | |
11 | */ | |
12 | ||
13 | /* | |
14 | * This file mainly handles OTG fsm, it includes OTG fsm operations | |
15 | * for HNP and SRP. | |
16 | */ | |
17 | ||
18 | #include <linux/usb/otg.h> | |
19 | #include <linux/usb/gadget.h> | |
20 | #include <linux/usb/hcd.h> | |
21 | #include <linux/usb/chipidea.h> | |
826cfe75 | 22 | #include <linux/regulator/consumer.h> |
57677be5 LJ |
23 | |
24 | #include "ci.h" | |
25 | #include "bits.h" | |
26 | #include "otg.h" | |
27 | #include "otg_fsm.h" | |
28 | ||
826cfe75 LJ |
29 | /* |
30 | * Add timer to active timer list | |
31 | */ | |
32 | static void ci_otg_add_timer(struct ci_hdrc *ci, enum ci_otg_fsm_timer_index t) | |
33 | { | |
34 | struct ci_otg_fsm_timer *tmp_timer; | |
35 | struct ci_otg_fsm_timer *timer = ci->fsm_timer->timer_list[t]; | |
36 | struct list_head *active_timers = &ci->fsm_timer->active_timers; | |
37 | ||
38 | if (t >= NUM_CI_OTG_FSM_TIMERS) | |
39 | return; | |
40 | ||
41 | /* | |
42 | * Check if the timer is already in the active list, | |
43 | * if so update timer count | |
44 | */ | |
45 | list_for_each_entry(tmp_timer, active_timers, list) | |
46 | if (tmp_timer == timer) { | |
47 | timer->count = timer->expires; | |
48 | return; | |
49 | } | |
50 | ||
51 | timer->count = timer->expires; | |
52 | list_add_tail(&timer->list, active_timers); | |
53 | ||
54 | /* Enable 1ms irq */ | |
55 | if (!(hw_read_otgsc(ci, OTGSC_1MSIE))) | |
56 | hw_write_otgsc(ci, OTGSC_1MSIE, OTGSC_1MSIE); | |
57 | } | |
58 | ||
59 | /* | |
60 | * Remove timer from active timer list | |
61 | */ | |
62 | static void ci_otg_del_timer(struct ci_hdrc *ci, enum ci_otg_fsm_timer_index t) | |
63 | { | |
64 | struct ci_otg_fsm_timer *tmp_timer, *del_tmp; | |
65 | struct ci_otg_fsm_timer *timer = ci->fsm_timer->timer_list[t]; | |
66 | struct list_head *active_timers = &ci->fsm_timer->active_timers; | |
67 | ||
68 | if (t >= NUM_CI_OTG_FSM_TIMERS) | |
69 | return; | |
70 | ||
71 | list_for_each_entry_safe(tmp_timer, del_tmp, active_timers, list) | |
72 | if (tmp_timer == timer) | |
73 | list_del(&timer->list); | |
74 | ||
75 | /* Disable 1ms irq if there is no any active timer */ | |
76 | if (list_empty(active_timers)) | |
77 | hw_write_otgsc(ci, OTGSC_1MSIE, 0); | |
78 | } | |
79 | ||
80 | /* -------------------------------------------------------------*/ | |
81 | /* Operations that will be called from OTG Finite State Machine */ | |
82 | /* -------------------------------------------------------------*/ | |
83 | static void ci_otg_fsm_add_timer(struct otg_fsm *fsm, enum otg_fsm_timer t) | |
84 | { | |
85 | struct ci_hdrc *ci = container_of(fsm, struct ci_hdrc, fsm); | |
86 | ||
87 | if (t < NUM_OTG_FSM_TIMERS) | |
88 | ci_otg_add_timer(ci, t); | |
89 | return; | |
90 | } | |
91 | ||
92 | static void ci_otg_fsm_del_timer(struct otg_fsm *fsm, enum otg_fsm_timer t) | |
93 | { | |
94 | struct ci_hdrc *ci = container_of(fsm, struct ci_hdrc, fsm); | |
95 | ||
96 | if (t < NUM_OTG_FSM_TIMERS) | |
97 | ci_otg_del_timer(ci, t); | |
98 | return; | |
99 | } | |
100 | ||
101 | /* | |
102 | * A-device drive vbus: turn on vbus regulator and enable port power | |
103 | * Data pulse irq should be disabled while vbus is on. | |
104 | */ | |
105 | static void ci_otg_drv_vbus(struct otg_fsm *fsm, int on) | |
106 | { | |
107 | int ret; | |
108 | struct ci_hdrc *ci = container_of(fsm, struct ci_hdrc, fsm); | |
109 | ||
110 | if (on) { | |
111 | /* Enable power power */ | |
112 | hw_write(ci, OP_PORTSC, PORTSC_W1C_BITS | PORTSC_PP, | |
113 | PORTSC_PP); | |
114 | if (ci->platdata->reg_vbus) { | |
115 | ret = regulator_enable(ci->platdata->reg_vbus); | |
116 | if (ret) { | |
117 | dev_err(ci->dev, | |
118 | "Failed to enable vbus regulator, ret=%d\n", | |
119 | ret); | |
120 | return; | |
121 | } | |
122 | } | |
123 | /* Disable data pulse irq */ | |
124 | hw_write_otgsc(ci, OTGSC_DPIE, 0); | |
125 | ||
126 | fsm->a_srp_det = 0; | |
127 | fsm->power_up = 0; | |
128 | } else { | |
129 | if (ci->platdata->reg_vbus) | |
130 | regulator_disable(ci->platdata->reg_vbus); | |
131 | ||
132 | fsm->a_bus_drop = 1; | |
133 | fsm->a_bus_req = 0; | |
134 | } | |
135 | } | |
136 | ||
137 | /* | |
138 | * Control data line by Run Stop bit. | |
139 | */ | |
140 | static void ci_otg_loc_conn(struct otg_fsm *fsm, int on) | |
141 | { | |
142 | struct ci_hdrc *ci = container_of(fsm, struct ci_hdrc, fsm); | |
143 | ||
144 | if (on) | |
145 | hw_write(ci, OP_USBCMD, USBCMD_RS, USBCMD_RS); | |
146 | else | |
147 | hw_write(ci, OP_USBCMD, USBCMD_RS, 0); | |
148 | } | |
149 | ||
150 | /* | |
151 | * Generate SOF by host. | |
152 | * This is controlled through suspend/resume the port. | |
153 | * In host mode, controller will automatically send SOF. | |
154 | * Suspend will block the data on the port. | |
155 | */ | |
156 | static void ci_otg_loc_sof(struct otg_fsm *fsm, int on) | |
157 | { | |
158 | struct ci_hdrc *ci = container_of(fsm, struct ci_hdrc, fsm); | |
159 | ||
160 | if (on) | |
161 | hw_write(ci, OP_PORTSC, PORTSC_W1C_BITS | PORTSC_FPR, | |
162 | PORTSC_FPR); | |
163 | else | |
164 | hw_write(ci, OP_PORTSC, PORTSC_W1C_BITS | PORTSC_SUSP, | |
165 | PORTSC_SUSP); | |
166 | } | |
167 | ||
168 | /* | |
169 | * Start SRP pulsing by data-line pulsing, | |
170 | * no v-bus pulsing followed | |
171 | */ | |
172 | static void ci_otg_start_pulse(struct otg_fsm *fsm) | |
173 | { | |
174 | struct ci_hdrc *ci = container_of(fsm, struct ci_hdrc, fsm); | |
175 | ||
176 | /* Hardware Assistant Data pulse */ | |
177 | hw_write_otgsc(ci, OTGSC_HADP, OTGSC_HADP); | |
178 | ||
179 | ci_otg_add_timer(ci, B_DATA_PLS); | |
180 | } | |
181 | ||
182 | static int ci_otg_start_host(struct otg_fsm *fsm, int on) | |
183 | { | |
184 | struct ci_hdrc *ci = container_of(fsm, struct ci_hdrc, fsm); | |
185 | ||
186 | mutex_unlock(&fsm->lock); | |
187 | if (on) { | |
188 | ci_role_stop(ci); | |
189 | ci_role_start(ci, CI_ROLE_HOST); | |
190 | } else { | |
191 | ci_role_stop(ci); | |
192 | hw_device_reset(ci, USBMODE_CM_DC); | |
193 | ci_role_start(ci, CI_ROLE_GADGET); | |
194 | } | |
195 | mutex_lock(&fsm->lock); | |
196 | return 0; | |
197 | } | |
198 | ||
199 | static int ci_otg_start_gadget(struct otg_fsm *fsm, int on) | |
200 | { | |
201 | struct ci_hdrc *ci = container_of(fsm, struct ci_hdrc, fsm); | |
202 | ||
203 | mutex_unlock(&fsm->lock); | |
204 | if (on) | |
205 | usb_gadget_vbus_connect(&ci->gadget); | |
206 | else | |
207 | usb_gadget_vbus_disconnect(&ci->gadget); | |
208 | mutex_lock(&fsm->lock); | |
209 | ||
210 | return 0; | |
211 | } | |
212 | ||
213 | static struct otg_fsm_ops ci_otg_ops = { | |
214 | .drv_vbus = ci_otg_drv_vbus, | |
215 | .loc_conn = ci_otg_loc_conn, | |
216 | .loc_sof = ci_otg_loc_sof, | |
217 | .start_pulse = ci_otg_start_pulse, | |
218 | .add_timer = ci_otg_fsm_add_timer, | |
219 | .del_timer = ci_otg_fsm_del_timer, | |
220 | .start_host = ci_otg_start_host, | |
221 | .start_gadget = ci_otg_start_gadget, | |
222 | }; | |
223 | ||
57677be5 LJ |
224 | int ci_hdrc_otg_fsm_init(struct ci_hdrc *ci) |
225 | { | |
226 | struct usb_otg *otg; | |
227 | ||
228 | otg = devm_kzalloc(ci->dev, | |
229 | sizeof(struct usb_otg), GFP_KERNEL); | |
230 | if (!otg) { | |
231 | dev_err(ci->dev, | |
232 | "Failed to allocate usb_otg structure for ci hdrc otg!\n"); | |
233 | return -ENOMEM; | |
234 | } | |
235 | ||
236 | otg->phy = ci->transceiver; | |
237 | otg->gadget = &ci->gadget; | |
238 | ci->fsm.otg = otg; | |
239 | ci->transceiver->otg = ci->fsm.otg; | |
240 | ci->fsm.power_up = 1; | |
241 | ci->fsm.id = hw_read_otgsc(ci, OTGSC_ID) ? 1 : 0; | |
242 | ci->transceiver->state = OTG_STATE_UNDEFINED; | |
826cfe75 | 243 | ci->fsm.ops = &ci_otg_ops; |
57677be5 LJ |
244 | |
245 | mutex_init(&ci->fsm.lock); | |
246 | ||
247 | /* Enable A vbus valid irq */ | |
248 | hw_write_otgsc(ci, OTGSC_AVVIE, OTGSC_AVVIE); | |
249 | ||
250 | if (ci->fsm.id) { | |
251 | ci->fsm.b_ssend_srp = | |
252 | hw_read_otgsc(ci, OTGSC_BSV) ? 0 : 1; | |
253 | ci->fsm.b_sess_vld = | |
254 | hw_read_otgsc(ci, OTGSC_BSV) ? 1 : 0; | |
255 | /* Enable BSV irq */ | |
256 | hw_write_otgsc(ci, OTGSC_BSVIE, OTGSC_BSVIE); | |
257 | } | |
258 | ||
259 | return 0; | |
260 | } |