xref: /openbmc/linux/drivers/usb/chipidea/otg.c (revision cff3608ec622c7447c95f42d55d79da14cfbf53f)
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