usb: chipidea: add vbus interrupt handler
We add vbus interrupt handler at ci_otg_work, it uses OTGSC_BSV(at otgsc) to know it is connect or disconnet event. Meanwhile, we introduce two flags id_event and b_sess_valid_event to indicate it is an id interrupt or a vbus interrupt. Tested-by: Marek Vasut <marex@denx.de> Signed-off-by: Peter Chen <peter.chen@freescale.com> Signed-off-by: Alexander Shishkin <alexander.shishkin@linux.intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
		
							parent
							
								
									cbec6bd55a
								
							
						
					
					
						commit
						a107f8c505
					
				| @ -132,6 +132,9 @@ struct hw_bank { | ||||
|  * @transceiver: pointer to USB PHY, if any | ||||
|  * @hcd: pointer to usb_hcd for ehci host driver | ||||
|  * @debugfs: root dentry for this controller in debugfs | ||||
|  * @id_event: indicates there is an id event, and handled at ci_otg_work | ||||
|  * @b_sess_valid_event: indicates there is a vbus event, and handled | ||||
|  * at ci_otg_work | ||||
|  */ | ||||
| struct ci_hdrc { | ||||
| 	struct device			*dev; | ||||
| @ -168,6 +171,8 @@ struct ci_hdrc { | ||||
| 	struct usb_phy			*transceiver; | ||||
| 	struct usb_hcd			*hcd; | ||||
| 	struct dentry			*debugfs; | ||||
| 	bool				id_event; | ||||
| 	bool				b_sess_valid_event; | ||||
| }; | ||||
| 
 | ||||
| static inline struct ci_role_driver *ci_role(struct ci_hdrc *ci) | ||||
|  | ||||
| @ -303,16 +303,34 @@ static irqreturn_t ci_irq(int irq, void *data) | ||||
| 	if (ci->is_otg) | ||||
| 		otgsc = hw_read(ci, OP_OTGSC, ~0); | ||||
| 
 | ||||
| 	if (ci->role != CI_ROLE_END) | ||||
| 		ret = ci_role(ci)->irq(ci); | ||||
| 
 | ||||
| 	if (ci->is_otg && (otgsc & OTGSC_IDIS)) { | ||||
| 		hw_write(ci, OP_OTGSC, OTGSC_IDIS, OTGSC_IDIS); | ||||
| 	/*
 | ||||
| 	 * Handle id change interrupt, it indicates device/host function | ||||
| 	 * switch. | ||||
| 	 */ | ||||
| 	if (ci->is_otg && (otgsc & OTGSC_IDIE) && (otgsc & OTGSC_IDIS)) { | ||||
| 		ci->id_event = true; | ||||
| 		ci_clear_otg_interrupt(ci, OTGSC_IDIS); | ||||
| 		disable_irq_nosync(ci->irq); | ||||
| 		queue_work(ci->wq, &ci->work); | ||||
| 		ret = IRQ_HANDLED; | ||||
| 		return IRQ_HANDLED; | ||||
| 	} | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * Handle vbus change interrupt, it indicates device connection | ||||
| 	 * and disconnection events. | ||||
| 	 */ | ||||
| 	if (ci->is_otg && (otgsc & OTGSC_BSVIE) && (otgsc & OTGSC_BSVIS)) { | ||||
| 		ci->b_sess_valid_event = true; | ||||
| 		ci_clear_otg_interrupt(ci, OTGSC_BSVIS); | ||||
| 		disable_irq_nosync(ci->irq); | ||||
| 		queue_work(ci->wq, &ci->work); | ||||
| 		return IRQ_HANDLED; | ||||
| 	} | ||||
| 
 | ||||
| 	/* Handle device/host interrupt */ | ||||
| 	if (ci->role != CI_ROLE_END) | ||||
| 		ret = ci_role(ci)->irq(ci); | ||||
| 
 | ||||
| 	return ret; | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -37,13 +37,23 @@ enum ci_role ci_otg_role(struct ci_hdrc *ci) | ||||
| 	return role; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * ci_role_work - perform role changing based on ID pin | ||||
|  * @work: work struct | ||||
|  */ | ||||
| static void ci_role_work(struct work_struct *work) | ||||
| void ci_handle_vbus_change(struct ci_hdrc *ci) | ||||
| { | ||||
| 	u32 otgsc; | ||||
| 
 | ||||
| 	if (!ci->is_otg) | ||||
| 		return; | ||||
| 
 | ||||
| 	otgsc = hw_read(ci, OP_OTGSC, ~0); | ||||
| 
 | ||||
| 	if (otgsc & OTGSC_BSV) | ||||
| 		usb_gadget_vbus_connect(&ci->gadget); | ||||
| 	else | ||||
| 		usb_gadget_vbus_disconnect(&ci->gadget); | ||||
| } | ||||
| 
 | ||||
| static void ci_handle_id_switch(struct ci_hdrc *ci) | ||||
| { | ||||
| 	struct ci_hdrc *ci = container_of(work, struct ci_hdrc, work); | ||||
| 	enum ci_role role = ci_otg_role(ci); | ||||
| 
 | ||||
| 	if (role != ci->role) { | ||||
| @ -53,17 +63,35 @@ static void ci_role_work(struct work_struct *work) | ||||
| 		ci_role_stop(ci); | ||||
| 		ci_role_start(ci, role); | ||||
| 	} | ||||
| } | ||||
| /**
 | ||||
|  * ci_otg_work - perform otg (vbus/id) event handle | ||||
|  * @work: work struct | ||||
|  */ | ||||
| static void ci_otg_work(struct work_struct *work) | ||||
| { | ||||
| 	struct ci_hdrc *ci = container_of(work, struct ci_hdrc, work); | ||||
| 
 | ||||
| 	if (ci->id_event) { | ||||
| 		ci->id_event = false; | ||||
| 		ci_handle_id_switch(ci); | ||||
| 	} else if (ci->b_sess_valid_event) { | ||||
| 		ci->b_sess_valid_event = false; | ||||
| 		ci_handle_vbus_change(ci); | ||||
| 	} else | ||||
| 		dev_err(ci->dev, "unexpected event occurs at %s\n", __func__); | ||||
| 
 | ||||
| 	enable_irq(ci->irq); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /**
 | ||||
|  * ci_hdrc_otg_init - initialize otg struct | ||||
|  * ci: the controller | ||||
|  */ | ||||
| int ci_hdrc_otg_init(struct ci_hdrc *ci) | ||||
| { | ||||
| 	INIT_WORK(&ci->work, ci_role_work); | ||||
| 	INIT_WORK(&ci->work, ci_otg_work); | ||||
| 	ci->wq = create_singlethread_workqueue("ci_otg"); | ||||
| 	if (!ci->wq) { | ||||
| 		dev_err(ci->dev, "can't create workqueue\n"); | ||||
|  | ||||
| @ -30,5 +30,6 @@ static inline void ci_disable_otg_interrupt(struct ci_hdrc *ci, u32 bits) | ||||
| int ci_hdrc_otg_init(struct ci_hdrc *ci); | ||||
| void ci_hdrc_otg_destroy(struct ci_hdrc *ci); | ||||
| enum ci_role ci_otg_role(struct ci_hdrc *ci); | ||||
| void ci_handle_vbus_change(struct ci_hdrc *ci); | ||||
| 
 | ||||
| #endif /* __DRIVERS_USB_CHIPIDEA_OTG_H */ | ||||
|  | ||||
| @ -85,8 +85,10 @@ static int hw_device_state(struct ci_hdrc *ci, u32 dma) | ||||
| 		/* interrupt, error, port change, reset, sleep/suspend */ | ||||
| 		hw_write(ci, OP_USBINTR, ~0, | ||||
| 			     USBi_UI|USBi_UEI|USBi_PCI|USBi_URI|USBi_SLI); | ||||
| 		hw_write(ci, OP_USBCMD, USBCMD_RS, USBCMD_RS); | ||||
| 	} else { | ||||
| 		hw_write(ci, OP_USBINTR, ~0, 0); | ||||
| 		hw_write(ci, OP_USBCMD, USBCMD_RS, 0); | ||||
| 	} | ||||
| 	return 0; | ||||
| } | ||||
| @ -1460,6 +1462,7 @@ static int ci_udc_vbus_session(struct usb_gadget *_gadget, int is_active) | ||||
| 			pm_runtime_get_sync(&_gadget->dev); | ||||
| 			hw_device_reset(ci, USBMODE_CM_DC); | ||||
| 			hw_device_state(ci, ci->ep0out->qh.dma); | ||||
| 			dev_dbg(ci->dev, "Connected to host\n"); | ||||
| 		} else { | ||||
| 			hw_device_state(ci, 0); | ||||
| 			if (ci->platdata->notify_event) | ||||
| @ -1467,6 +1470,7 @@ static int ci_udc_vbus_session(struct usb_gadget *_gadget, int is_active) | ||||
| 				CI_HDRC_CONTROLLER_STOPPED_EVENT); | ||||
| 			_gadget_stop_activity(&ci->gadget); | ||||
| 			pm_runtime_put_sync(&_gadget->dev); | ||||
| 			dev_dbg(ci->dev, "Disconnected from host\n"); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| @ -1822,6 +1826,9 @@ static int udc_start(struct ci_hdrc *ci) | ||||
| 	pm_runtime_no_callbacks(&ci->gadget.dev); | ||||
| 	pm_runtime_enable(&ci->gadget.dev); | ||||
| 
 | ||||
| 	/* Update ci->vbus_active */ | ||||
| 	ci_handle_vbus_change(ci); | ||||
| 
 | ||||
| 	return retval; | ||||
| 
 | ||||
| remove_trans: | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user