15fd54aceSGreg Kroah-Hartman // SPDX-License-Identifier: GPL-2.0
2c10b4f03SPeter Chen /*
3c10b4f03SPeter Chen * otg.c - ChipIdea USB IP core OTG driver
4c10b4f03SPeter Chen *
5c10b4f03SPeter Chen * Copyright (C) 2013 Freescale Semiconductor, Inc.
6c10b4f03SPeter Chen *
7c10b4f03SPeter Chen * Author: Peter Chen
8c10b4f03SPeter Chen */
9c10b4f03SPeter Chen
10c10b4f03SPeter Chen /*
114dcf720cSLi Jun * This file mainly handles otgsc register, OTG fsm operations for HNP and SRP
124dcf720cSLi Jun * are also included.
13c10b4f03SPeter Chen */
14c10b4f03SPeter Chen
15c10b4f03SPeter Chen #include <linux/usb/otg.h>
16c10b4f03SPeter Chen #include <linux/usb/gadget.h>
17c10b4f03SPeter Chen #include <linux/usb/chipidea.h>
18c10b4f03SPeter Chen
19c10b4f03SPeter Chen #include "ci.h"
20c10b4f03SPeter Chen #include "bits.h"
21c10b4f03SPeter Chen #include "otg.h"
2257677be5SLi Jun #include "otg_fsm.h"
23c10b4f03SPeter Chen
24c10b4f03SPeter Chen /**
25953c3a3cSLee Jones * hw_read_otgsc - returns otgsc register bits value.
266a005f0fSLee Jones * @ci: the controller
270c33bf78SLi Jun * @mask: bitfield mask
280c33bf78SLi Jun */
hw_read_otgsc(struct ci_hdrc * ci,u32 mask)290c33bf78SLi Jun u32 hw_read_otgsc(struct ci_hdrc *ci, u32 mask)
300c33bf78SLi Jun {
313ecb3e09SIvan T. Ivanov struct ci_hdrc_cable *cable;
323ecb3e09SIvan T. Ivanov u32 val = hw_read(ci, OP_OTGSC, mask);
333ecb3e09SIvan T. Ivanov
343ecb3e09SIvan T. Ivanov /*
353ecb3e09SIvan T. Ivanov * If using extcon framework for VBUS and/or ID signal
363ecb3e09SIvan T. Ivanov * detection overwrite OTGSC register value
373ecb3e09SIvan T. Ivanov */
383ecb3e09SIvan T. Ivanov cable = &ci->platdata->vbus_extcon;
3905559f10SLi Jun if (!IS_ERR(cable->edev) || ci->role_switch) {
403ecb3e09SIvan T. Ivanov if (cable->changed)
413ecb3e09SIvan T. Ivanov val |= OTGSC_BSVIS;
423ecb3e09SIvan T. Ivanov else
433ecb3e09SIvan T. Ivanov val &= ~OTGSC_BSVIS;
443ecb3e09SIvan T. Ivanov
455cc49268SStephen Boyd if (cable->connected)
463ecb3e09SIvan T. Ivanov val |= OTGSC_BSV;
473ecb3e09SIvan T. Ivanov else
483ecb3e09SIvan T. Ivanov val &= ~OTGSC_BSV;
49a89b94b5SStephen Boyd
50a89b94b5SStephen Boyd if (cable->enabled)
51a89b94b5SStephen Boyd val |= OTGSC_BSVIE;
52a89b94b5SStephen Boyd else
53a89b94b5SStephen Boyd val &= ~OTGSC_BSVIE;
543ecb3e09SIvan T. Ivanov }
553ecb3e09SIvan T. Ivanov
563ecb3e09SIvan T. Ivanov cable = &ci->platdata->id_extcon;
5705559f10SLi Jun if (!IS_ERR(cable->edev) || ci->role_switch) {
583ecb3e09SIvan T. Ivanov if (cable->changed)
593ecb3e09SIvan T. Ivanov val |= OTGSC_IDIS;
603ecb3e09SIvan T. Ivanov else
613ecb3e09SIvan T. Ivanov val &= ~OTGSC_IDIS;
623ecb3e09SIvan T. Ivanov
635cc49268SStephen Boyd if (cable->connected)
645cc49268SStephen Boyd val &= ~OTGSC_ID; /* host */
653ecb3e09SIvan T. Ivanov else
665cc49268SStephen Boyd val |= OTGSC_ID; /* device */
67a89b94b5SStephen Boyd
68a89b94b5SStephen Boyd if (cable->enabled)
69a89b94b5SStephen Boyd val |= OTGSC_IDIE;
70a89b94b5SStephen Boyd else
71a89b94b5SStephen Boyd val &= ~OTGSC_IDIE;
723ecb3e09SIvan T. Ivanov }
733ecb3e09SIvan T. Ivanov
74a89b94b5SStephen Boyd return val & mask;
750c33bf78SLi Jun }
760c33bf78SLi Jun
770c33bf78SLi Jun /**
78953c3a3cSLee Jones * hw_write_otgsc - updates target bits of OTGSC register.
796a005f0fSLee Jones * @ci: the controller
800c33bf78SLi Jun * @mask: bitfield mask
810c33bf78SLi Jun * @data: to be written
820c33bf78SLi Jun */
hw_write_otgsc(struct ci_hdrc * ci,u32 mask,u32 data)830c33bf78SLi Jun void hw_write_otgsc(struct ci_hdrc *ci, u32 mask, u32 data)
840c33bf78SLi Jun {
85a89b94b5SStephen Boyd struct ci_hdrc_cable *cable;
86a89b94b5SStephen Boyd
87a89b94b5SStephen Boyd cable = &ci->platdata->vbus_extcon;
8805559f10SLi Jun if (!IS_ERR(cable->edev) || ci->role_switch) {
89a89b94b5SStephen Boyd if (data & mask & OTGSC_BSVIS)
90a89b94b5SStephen Boyd cable->changed = false;
91a89b94b5SStephen Boyd
92a89b94b5SStephen Boyd /* Don't enable vbus interrupt if using external notifier */
93a89b94b5SStephen Boyd if (data & mask & OTGSC_BSVIE) {
94a89b94b5SStephen Boyd cable->enabled = true;
95a89b94b5SStephen Boyd data &= ~OTGSC_BSVIE;
96a89b94b5SStephen Boyd } else if (mask & OTGSC_BSVIE) {
97a89b94b5SStephen Boyd cable->enabled = false;
98a89b94b5SStephen Boyd }
99a89b94b5SStephen Boyd }
100a89b94b5SStephen Boyd
101a89b94b5SStephen Boyd cable = &ci->platdata->id_extcon;
10205559f10SLi Jun if (!IS_ERR(cable->edev) || ci->role_switch) {
103a89b94b5SStephen Boyd if (data & mask & OTGSC_IDIS)
104a89b94b5SStephen Boyd cable->changed = false;
105a89b94b5SStephen Boyd
106a89b94b5SStephen Boyd /* Don't enable id interrupt if using external notifier */
107a89b94b5SStephen Boyd if (data & mask & OTGSC_IDIE) {
108a89b94b5SStephen Boyd cable->enabled = true;
109a89b94b5SStephen Boyd data &= ~OTGSC_IDIE;
110a89b94b5SStephen Boyd } else if (mask & OTGSC_IDIE) {
111a89b94b5SStephen Boyd cable->enabled = false;
112a89b94b5SStephen Boyd }
113a89b94b5SStephen Boyd }
114a89b94b5SStephen Boyd
1150c33bf78SLi Jun hw_write(ci, OP_OTGSC, mask | OTGSC_INT_STATUS_BITS, data);
1160c33bf78SLi Jun }
1170c33bf78SLi Jun
1180c33bf78SLi Jun /**
119cbec6bd5SPeter Chen * ci_otg_role - pick role based on ID pin state
120cbec6bd5SPeter Chen * @ci: the controller
121cbec6bd5SPeter Chen */
ci_otg_role(struct ci_hdrc * ci)122cbec6bd5SPeter Chen enum ci_role ci_otg_role(struct ci_hdrc *ci)
123cbec6bd5SPeter Chen {
1240c33bf78SLi Jun enum ci_role role = hw_read_otgsc(ci, OTGSC_ID)
125cbec6bd5SPeter Chen ? CI_ROLE_GADGET
126cbec6bd5SPeter Chen : CI_ROLE_HOST;
127cbec6bd5SPeter Chen
128cbec6bd5SPeter Chen return role;
129cbec6bd5SPeter Chen }
130cbec6bd5SPeter Chen
ci_handle_vbus_change(struct ci_hdrc * ci)131a107f8c5SPeter Chen void ci_handle_vbus_change(struct ci_hdrc *ci)
132cbec6bd5SPeter Chen {
133*cff3608eSTomer Maimon if (!ci->is_otg) {
134*cff3608eSTomer Maimon if (ci->platdata->flags & CI_HDRC_FORCE_VBUS_ACTIVE_ALWAYS)
135*cff3608eSTomer Maimon usb_gadget_vbus_connect(&ci->gadget);
136a107f8c5SPeter Chen return;
137*cff3608eSTomer Maimon }
138a107f8c5SPeter Chen
139c3b674a0SPeter Chen if (hw_read_otgsc(ci, OTGSC_BSV) && !ci->vbus_active)
140a107f8c5SPeter Chen usb_gadget_vbus_connect(&ci->gadget);
141c3b674a0SPeter Chen else if (!hw_read_otgsc(ci, OTGSC_BSV) && ci->vbus_active)
142a107f8c5SPeter Chen usb_gadget_vbus_disconnect(&ci->gadget);
143a107f8c5SPeter Chen }
144a107f8c5SPeter Chen
145f60f8ccdSStephen Boyd /**
146953c3a3cSLee Jones * hw_wait_vbus_lower_bsv - When we switch to device mode, the vbus value
147953c3a3cSLee Jones * should be lower than OTGSC_BSV before connecting
148953c3a3cSLee Jones * to host.
149f60f8ccdSStephen Boyd *
150f60f8ccdSStephen Boyd * @ci: the controller
151f60f8ccdSStephen Boyd *
152f60f8ccdSStephen Boyd * This function returns an error code if timeout
153f60f8ccdSStephen Boyd */
hw_wait_vbus_lower_bsv(struct ci_hdrc * ci)154f60f8ccdSStephen Boyd static int hw_wait_vbus_lower_bsv(struct ci_hdrc *ci)
155f60f8ccdSStephen Boyd {
156f60f8ccdSStephen Boyd unsigned long elapse = jiffies + msecs_to_jiffies(5000);
157f60f8ccdSStephen Boyd u32 mask = OTGSC_BSV;
158f60f8ccdSStephen Boyd
159f60f8ccdSStephen Boyd while (hw_read_otgsc(ci, mask)) {
160f60f8ccdSStephen Boyd if (time_after(jiffies, elapse)) {
161f60f8ccdSStephen Boyd dev_err(ci->dev, "timeout waiting for %08x in OTGSC\n",
162f60f8ccdSStephen Boyd mask);
163f60f8ccdSStephen Boyd return -ETIMEDOUT;
164f60f8ccdSStephen Boyd }
165f60f8ccdSStephen Boyd msleep(20);
166f60f8ccdSStephen Boyd }
167f60f8ccdSStephen Boyd
168f60f8ccdSStephen Boyd return 0;
169f60f8ccdSStephen Boyd }
170f60f8ccdSStephen Boyd
ci_handle_id_switch(struct ci_hdrc * ci)17174494b33SXu Yang void ci_handle_id_switch(struct ci_hdrc *ci)
172a107f8c5SPeter Chen {
173451b15edSXu Yang enum ci_role role;
174cbec6bd5SPeter Chen
175451b15edSXu Yang mutex_lock(&ci->mutex);
176451b15edSXu Yang role = ci_otg_role(ci);
177cbec6bd5SPeter Chen if (role != ci->role) {
178cbec6bd5SPeter Chen dev_dbg(ci->dev, "switching from %s to %s\n",
179cbec6bd5SPeter Chen ci_role(ci)->name, ci->roles[role]->name);
180cbec6bd5SPeter Chen
1813ac82cf3SPeter Chen if (ci->vbus_active && ci->role == CI_ROLE_GADGET)
1823ac82cf3SPeter Chen /*
1833ac82cf3SPeter Chen * vbus disconnect event is lost due to role
1843ac82cf3SPeter Chen * switch occurs during system suspend.
1853ac82cf3SPeter Chen */
1863ac82cf3SPeter Chen usb_gadget_vbus_disconnect(&ci->gadget);
1873ac82cf3SPeter Chen
188cbec6bd5SPeter Chen ci_role_stop(ci);
189851ce932SLi Jun
190c3b674a0SPeter Chen if (role == CI_ROLE_GADGET &&
191c3b674a0SPeter Chen IS_ERR(ci->platdata->vbus_extcon.edev))
192f60f8ccdSStephen Boyd /*
193c3b674a0SPeter Chen * Wait vbus lower than OTGSC_BSV before connecting
194c3b674a0SPeter Chen * to host. If connecting status is from an external
195c3b674a0SPeter Chen * connector instead of register, we don't need to
196c3b674a0SPeter Chen * care vbus on the board, since it will not affect
197c3b674a0SPeter Chen * external connector status.
198f60f8ccdSStephen Boyd */
199f60f8ccdSStephen Boyd hw_wait_vbus_lower_bsv(ci);
200851ce932SLi Jun
201cbec6bd5SPeter Chen ci_role_start(ci, role);
202c3b674a0SPeter Chen /* vbus change may have already occurred */
203c3b674a0SPeter Chen if (role == CI_ROLE_GADGET)
204c3b674a0SPeter Chen ci_handle_vbus_change(ci);
205cbec6bd5SPeter Chen }
206451b15edSXu Yang mutex_unlock(&ci->mutex);
207a107f8c5SPeter Chen }
208a107f8c5SPeter Chen /**
209a107f8c5SPeter Chen * ci_otg_work - perform otg (vbus/id) event handle
210a107f8c5SPeter Chen * @work: work struct
211a107f8c5SPeter Chen */
ci_otg_work(struct work_struct * work)212a107f8c5SPeter Chen static void ci_otg_work(struct work_struct *work)
213a107f8c5SPeter Chen {
214a107f8c5SPeter Chen struct ci_hdrc *ci = container_of(work, struct ci_hdrc, work);
215a107f8c5SPeter Chen
2164dcf720cSLi Jun if (ci_otg_is_fsm_mode(ci) && !ci_otg_fsm_work(ci)) {
2174dcf720cSLi Jun enable_irq(ci->irq);
2184dcf720cSLi Jun return;
2194dcf720cSLi Jun }
2204dcf720cSLi Jun
2211f874edcSPeter Chen pm_runtime_get_sync(ci->dev);
22259739131SLoic Poulain
223a107f8c5SPeter Chen if (ci->id_event) {
224a107f8c5SPeter Chen ci->id_event = false;
225a107f8c5SPeter Chen ci_handle_id_switch(ci);
22659739131SLoic Poulain }
22759739131SLoic Poulain
22859739131SLoic Poulain if (ci->b_sess_valid_event) {
229a107f8c5SPeter Chen ci->b_sess_valid_event = false;
230a107f8c5SPeter Chen ci_handle_vbus_change(ci);
23159739131SLoic Poulain }
23259739131SLoic Poulain
2331f874edcSPeter Chen pm_runtime_put_sync(ci->dev);
234cbec6bd5SPeter Chen
235cbec6bd5SPeter Chen enable_irq(ci->irq);
236cbec6bd5SPeter Chen }
237cbec6bd5SPeter Chen
238a107f8c5SPeter Chen
239cbec6bd5SPeter Chen /**
240cbec6bd5SPeter Chen * ci_hdrc_otg_init - initialize otg struct
2416a005f0fSLee Jones * @ci: the controller
242c10b4f03SPeter Chen */
ci_hdrc_otg_init(struct ci_hdrc * ci)243c10b4f03SPeter Chen int ci_hdrc_otg_init(struct ci_hdrc *ci)
244c10b4f03SPeter Chen {
245a107f8c5SPeter Chen INIT_WORK(&ci->work, ci_otg_work);
246d144dfeaSPeter Chen ci->wq = create_freezable_workqueue("ci_otg");
247cbec6bd5SPeter Chen if (!ci->wq) {
248cbec6bd5SPeter Chen dev_err(ci->dev, "can't create workqueue\n");
249cbec6bd5SPeter Chen return -ENODEV;
250cbec6bd5SPeter Chen }
251c10b4f03SPeter Chen
25257677be5SLi Jun if (ci_otg_is_fsm_mode(ci))
25357677be5SLi Jun return ci_hdrc_otg_fsm_init(ci);
25457677be5SLi Jun
255c10b4f03SPeter Chen return 0;
256c10b4f03SPeter Chen }
257cbec6bd5SPeter Chen
258cbec6bd5SPeter Chen /**
259cbec6bd5SPeter Chen * ci_hdrc_otg_destroy - destroy otg struct
2606a005f0fSLee Jones * @ci: the controller
261cbec6bd5SPeter Chen */
ci_hdrc_otg_destroy(struct ci_hdrc * ci)262cbec6bd5SPeter Chen void ci_hdrc_otg_destroy(struct ci_hdrc *ci)
263cbec6bd5SPeter Chen {
264f057a1d4SChristophe JAILLET if (ci->wq)
265cbec6bd5SPeter Chen destroy_workqueue(ci->wq);
266f057a1d4SChristophe JAILLET
2670c33bf78SLi Jun /* Disable all OTG irq and clear status */
2680c33bf78SLi Jun hw_write_otgsc(ci, OTGSC_INT_EN_BITS | OTGSC_INT_STATUS_BITS,
2690c33bf78SLi Jun OTGSC_INT_STATUS_BITS);
27015f75defSLi Jun if (ci_otg_is_fsm_mode(ci))
27115f75defSLi Jun ci_hdrc_otg_fsm_remove(ci);
272cbec6bd5SPeter Chen }
273