| 
									
										
										
										
											2014-04-23 15:56:44 +08:00
										 |  |  | /*
 | 
					
						
							|  |  |  |  * otg_fsm.c - ChipIdea USB IP core OTG FSM driver | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Copyright (C) 2014 Freescale Semiconductor, Inc. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Author: Jun Li | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * This program is free software; you can redistribute it and/or modify | 
					
						
							|  |  |  |  * it under the terms of the GNU General Public License version 2 as | 
					
						
							|  |  |  |  * published by the Free Software Foundation. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*
 | 
					
						
							|  |  |  |  * This file mainly handles OTG fsm, it includes OTG fsm operations | 
					
						
							|  |  |  |  * for HNP and SRP. | 
					
						
							| 
									
										
										
										
											2014-04-23 15:56:50 +08:00
										 |  |  |  * | 
					
						
							|  |  |  |  * TODO List | 
					
						
							|  |  |  |  * - ADP | 
					
						
							|  |  |  |  * - OTG test device | 
					
						
							| 
									
										
										
										
											2014-04-23 15:56:44 +08:00
										 |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include <linux/usb/otg.h>
 | 
					
						
							|  |  |  | #include <linux/usb/gadget.h>
 | 
					
						
							|  |  |  | #include <linux/usb/hcd.h>
 | 
					
						
							|  |  |  | #include <linux/usb/chipidea.h>
 | 
					
						
							| 
									
										
										
										
											2014-04-23 15:56:48 +08:00
										 |  |  | #include <linux/regulator/consumer.h>
 | 
					
						
							| 
									
										
										
										
											2014-04-23 15:56:44 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | #include "ci.h"
 | 
					
						
							|  |  |  | #include "bits.h"
 | 
					
						
							|  |  |  | #include "otg.h"
 | 
					
						
							|  |  |  | #include "otg_fsm.h"
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-04-23 15:56:49 +08:00
										 |  |  | static struct ci_otg_fsm_timer *otg_timer_initializer | 
					
						
							|  |  |  | (struct ci_hdrc *ci, void (*function)(void *, unsigned long), | 
					
						
							|  |  |  | 			unsigned long expires, unsigned long data) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct ci_otg_fsm_timer *timer; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	timer = devm_kzalloc(ci->dev, sizeof(struct ci_otg_fsm_timer), | 
					
						
							|  |  |  | 								GFP_KERNEL); | 
					
						
							|  |  |  | 	if (!timer) | 
					
						
							|  |  |  | 		return NULL; | 
					
						
							|  |  |  | 	timer->function = function; | 
					
						
							|  |  |  | 	timer->expires = expires; | 
					
						
							|  |  |  | 	timer->data = data; | 
					
						
							|  |  |  | 	return timer; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-04-23 15:56:51 +08:00
										 |  |  | /* Add for otg: interact with user space app */ | 
					
						
							|  |  |  | static ssize_t | 
					
						
							|  |  |  | get_a_bus_req(struct device *dev, struct device_attribute *attr, char *buf) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	char		*next; | 
					
						
							|  |  |  | 	unsigned	size, t; | 
					
						
							|  |  |  | 	struct ci_hdrc	*ci = dev_get_drvdata(dev); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	next = buf; | 
					
						
							|  |  |  | 	size = PAGE_SIZE; | 
					
						
							|  |  |  | 	t = scnprintf(next, size, "%d\n", ci->fsm.a_bus_req); | 
					
						
							|  |  |  | 	size -= t; | 
					
						
							|  |  |  | 	next += t; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return PAGE_SIZE - size; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static ssize_t | 
					
						
							|  |  |  | set_a_bus_req(struct device *dev, struct device_attribute *attr, | 
					
						
							|  |  |  | 					const char *buf, size_t count) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct ci_hdrc *ci = dev_get_drvdata(dev); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (count > 2) | 
					
						
							|  |  |  | 		return -1; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	mutex_lock(&ci->fsm.lock); | 
					
						
							|  |  |  | 	if (buf[0] == '0') { | 
					
						
							|  |  |  | 		ci->fsm.a_bus_req = 0; | 
					
						
							|  |  |  | 	} else if (buf[0] == '1') { | 
					
						
							|  |  |  | 		/* If a_bus_drop is TRUE, a_bus_req can't be set */ | 
					
						
							|  |  |  | 		if (ci->fsm.a_bus_drop) { | 
					
						
							|  |  |  | 			mutex_unlock(&ci->fsm.lock); | 
					
						
							|  |  |  | 			return count; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		ci->fsm.a_bus_req = 1; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-05-23 08:12:49 +08:00
										 |  |  | 	ci_otg_queue_work(ci); | 
					
						
							| 
									
										
										
										
											2014-04-23 15:56:51 +08:00
										 |  |  | 	mutex_unlock(&ci->fsm.lock); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return count; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | static DEVICE_ATTR(a_bus_req, S_IRUGO | S_IWUSR, get_a_bus_req, set_a_bus_req); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static ssize_t | 
					
						
							|  |  |  | get_a_bus_drop(struct device *dev, struct device_attribute *attr, char *buf) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	char		*next; | 
					
						
							|  |  |  | 	unsigned	size, t; | 
					
						
							|  |  |  | 	struct ci_hdrc	*ci = dev_get_drvdata(dev); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	next = buf; | 
					
						
							|  |  |  | 	size = PAGE_SIZE; | 
					
						
							|  |  |  | 	t = scnprintf(next, size, "%d\n", ci->fsm.a_bus_drop); | 
					
						
							|  |  |  | 	size -= t; | 
					
						
							|  |  |  | 	next += t; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return PAGE_SIZE - size; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static ssize_t | 
					
						
							|  |  |  | set_a_bus_drop(struct device *dev, struct device_attribute *attr, | 
					
						
							|  |  |  | 					const char *buf, size_t count) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct ci_hdrc	*ci = dev_get_drvdata(dev); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (count > 2) | 
					
						
							|  |  |  | 		return -1; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	mutex_lock(&ci->fsm.lock); | 
					
						
							|  |  |  | 	if (buf[0] == '0') { | 
					
						
							|  |  |  | 		ci->fsm.a_bus_drop = 0; | 
					
						
							|  |  |  | 	} else if (buf[0] == '1') { | 
					
						
							|  |  |  | 		ci->fsm.a_bus_drop = 1; | 
					
						
							|  |  |  | 		ci->fsm.a_bus_req = 0; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-05-23 08:12:49 +08:00
										 |  |  | 	ci_otg_queue_work(ci); | 
					
						
							| 
									
										
										
										
											2014-04-23 15:56:51 +08:00
										 |  |  | 	mutex_unlock(&ci->fsm.lock); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return count; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | static DEVICE_ATTR(a_bus_drop, S_IRUGO | S_IWUSR, get_a_bus_drop, | 
					
						
							|  |  |  | 						set_a_bus_drop); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static ssize_t | 
					
						
							|  |  |  | get_b_bus_req(struct device *dev, struct device_attribute *attr, char *buf) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	char		*next; | 
					
						
							|  |  |  | 	unsigned	size, t; | 
					
						
							|  |  |  | 	struct ci_hdrc	*ci = dev_get_drvdata(dev); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	next = buf; | 
					
						
							|  |  |  | 	size = PAGE_SIZE; | 
					
						
							|  |  |  | 	t = scnprintf(next, size, "%d\n", ci->fsm.b_bus_req); | 
					
						
							|  |  |  | 	size -= t; | 
					
						
							|  |  |  | 	next += t; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return PAGE_SIZE - size; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static ssize_t | 
					
						
							|  |  |  | set_b_bus_req(struct device *dev, struct device_attribute *attr, | 
					
						
							|  |  |  | 					const char *buf, size_t count) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct ci_hdrc	*ci = dev_get_drvdata(dev); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (count > 2) | 
					
						
							|  |  |  | 		return -1; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	mutex_lock(&ci->fsm.lock); | 
					
						
							|  |  |  | 	if (buf[0] == '0') | 
					
						
							|  |  |  | 		ci->fsm.b_bus_req = 0; | 
					
						
							|  |  |  | 	else if (buf[0] == '1') | 
					
						
							|  |  |  | 		ci->fsm.b_bus_req = 1; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-05-23 08:12:49 +08:00
										 |  |  | 	ci_otg_queue_work(ci); | 
					
						
							| 
									
										
										
										
											2014-04-23 15:56:51 +08:00
										 |  |  | 	mutex_unlock(&ci->fsm.lock); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return count; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | static DEVICE_ATTR(b_bus_req, S_IRUGO | S_IWUSR, get_b_bus_req, set_b_bus_req); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static ssize_t | 
					
						
							|  |  |  | set_a_clr_err(struct device *dev, struct device_attribute *attr, | 
					
						
							|  |  |  | 					const char *buf, size_t count) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct ci_hdrc	*ci = dev_get_drvdata(dev); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (count > 2) | 
					
						
							|  |  |  | 		return -1; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	mutex_lock(&ci->fsm.lock); | 
					
						
							|  |  |  | 	if (buf[0] == '1') | 
					
						
							|  |  |  | 		ci->fsm.a_clr_err = 1; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-05-23 08:12:49 +08:00
										 |  |  | 	ci_otg_queue_work(ci); | 
					
						
							| 
									
										
										
										
											2014-04-23 15:56:51 +08:00
										 |  |  | 	mutex_unlock(&ci->fsm.lock); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return count; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | static DEVICE_ATTR(a_clr_err, S_IWUSR, NULL, set_a_clr_err); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static struct attribute *inputs_attrs[] = { | 
					
						
							|  |  |  | 	&dev_attr_a_bus_req.attr, | 
					
						
							|  |  |  | 	&dev_attr_a_bus_drop.attr, | 
					
						
							|  |  |  | 	&dev_attr_b_bus_req.attr, | 
					
						
							|  |  |  | 	&dev_attr_a_clr_err.attr, | 
					
						
							|  |  |  | 	NULL, | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static struct attribute_group inputs_attr_group = { | 
					
						
							|  |  |  | 	.name = "inputs", | 
					
						
							|  |  |  | 	.attrs = inputs_attrs, | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-04-23 15:56:48 +08:00
										 |  |  | /*
 | 
					
						
							|  |  |  |  * Add timer to active timer list | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | static void ci_otg_add_timer(struct ci_hdrc *ci, enum ci_otg_fsm_timer_index t) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct ci_otg_fsm_timer *tmp_timer; | 
					
						
							|  |  |  | 	struct ci_otg_fsm_timer *timer = ci->fsm_timer->timer_list[t]; | 
					
						
							|  |  |  | 	struct list_head *active_timers = &ci->fsm_timer->active_timers; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (t >= NUM_CI_OTG_FSM_TIMERS) | 
					
						
							|  |  |  | 		return; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/*
 | 
					
						
							|  |  |  | 	 * Check if the timer is already in the active list, | 
					
						
							|  |  |  | 	 * if so update timer count | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	list_for_each_entry(tmp_timer, active_timers, list) | 
					
						
							|  |  |  | 		if (tmp_timer == timer) { | 
					
						
							|  |  |  | 			timer->count = timer->expires; | 
					
						
							|  |  |  | 			return; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	timer->count = timer->expires; | 
					
						
							|  |  |  | 	list_add_tail(&timer->list, active_timers); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* Enable 1ms irq */ | 
					
						
							|  |  |  | 	if (!(hw_read_otgsc(ci, OTGSC_1MSIE))) | 
					
						
							|  |  |  | 		hw_write_otgsc(ci, OTGSC_1MSIE, OTGSC_1MSIE); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*
 | 
					
						
							|  |  |  |  * Remove timer from active timer list | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | static void ci_otg_del_timer(struct ci_hdrc *ci, enum ci_otg_fsm_timer_index t) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct ci_otg_fsm_timer *tmp_timer, *del_tmp; | 
					
						
							|  |  |  | 	struct ci_otg_fsm_timer *timer = ci->fsm_timer->timer_list[t]; | 
					
						
							|  |  |  | 	struct list_head *active_timers = &ci->fsm_timer->active_timers; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (t >= NUM_CI_OTG_FSM_TIMERS) | 
					
						
							|  |  |  | 		return; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	list_for_each_entry_safe(tmp_timer, del_tmp, active_timers, list) | 
					
						
							|  |  |  | 		if (tmp_timer == timer) | 
					
						
							|  |  |  | 			list_del(&timer->list); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* Disable 1ms irq if there is no any active timer */ | 
					
						
							|  |  |  | 	if (list_empty(active_timers)) | 
					
						
							|  |  |  | 		hw_write_otgsc(ci, OTGSC_1MSIE, 0); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-04-23 15:56:50 +08:00
										 |  |  | /*
 | 
					
						
							|  |  |  |  * Reduce timer count by 1, and find timeout conditions. | 
					
						
							|  |  |  |  * Called by otg 1ms timer interrupt | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | static inline int ci_otg_tick_timer(struct ci_hdrc *ci) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct ci_otg_fsm_timer *tmp_timer, *del_tmp; | 
					
						
							|  |  |  | 	struct list_head *active_timers = &ci->fsm_timer->active_timers; | 
					
						
							|  |  |  | 	int expired = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	list_for_each_entry_safe(tmp_timer, del_tmp, active_timers, list) { | 
					
						
							|  |  |  | 		tmp_timer->count--; | 
					
						
							|  |  |  | 		/* check if timer expires */ | 
					
						
							|  |  |  | 		if (!tmp_timer->count) { | 
					
						
							|  |  |  | 			list_del(&tmp_timer->list); | 
					
						
							|  |  |  | 			tmp_timer->function(ci, tmp_timer->data); | 
					
						
							|  |  |  | 			expired = 1; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* disable 1ms irq if there is no any timer active */ | 
					
						
							|  |  |  | 	if ((expired == 1) && list_empty(active_timers)) | 
					
						
							|  |  |  | 		hw_write_otgsc(ci, OTGSC_1MSIE, 0); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return expired; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-04-23 15:56:49 +08:00
										 |  |  | /* The timeout callback function to set time out bit */ | 
					
						
							|  |  |  | static void set_tmout(void *ptr, unsigned long indicator) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	*(int *)indicator = 1; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void set_tmout_and_fsm(void *ptr, unsigned long indicator) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct ci_hdrc *ci = (struct ci_hdrc *)ptr; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	set_tmout(ci, indicator); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-05-23 08:12:49 +08:00
										 |  |  | 	ci_otg_queue_work(ci); | 
					
						
							| 
									
										
										
										
											2014-04-23 15:56:49 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void a_wait_vfall_tmout_func(void *ptr, unsigned long indicator) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct ci_hdrc *ci = (struct ci_hdrc *)ptr; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	set_tmout(ci, indicator); | 
					
						
							|  |  |  | 	/* Disable port power */ | 
					
						
							|  |  |  | 	hw_write(ci, OP_PORTSC, PORTSC_W1C_BITS | PORTSC_PP, 0); | 
					
						
							|  |  |  | 	/* Clear exsiting DP irq */ | 
					
						
							|  |  |  | 	hw_write_otgsc(ci, OTGSC_DPIS, OTGSC_DPIS); | 
					
						
							|  |  |  | 	/* Enable data pulse irq */ | 
					
						
							|  |  |  | 	hw_write_otgsc(ci, OTGSC_DPIE, OTGSC_DPIE); | 
					
						
							| 
									
										
										
										
											2014-05-23 08:12:49 +08:00
										 |  |  | 	ci_otg_queue_work(ci); | 
					
						
							| 
									
										
										
										
											2014-04-23 15:56:49 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void b_ase0_brst_tmout_func(void *ptr, unsigned long indicator) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct ci_hdrc *ci = (struct ci_hdrc *)ptr; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	set_tmout(ci, indicator); | 
					
						
							|  |  |  | 	if (!hw_read_otgsc(ci, OTGSC_BSV)) | 
					
						
							|  |  |  | 		ci->fsm.b_sess_vld = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-05-23 08:12:49 +08:00
										 |  |  | 	ci_otg_queue_work(ci); | 
					
						
							| 
									
										
										
										
											2014-04-23 15:56:49 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void b_ssend_srp_tmout_func(void *ptr, unsigned long indicator) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct ci_hdrc *ci = (struct ci_hdrc *)ptr; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	set_tmout(ci, indicator); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* only vbus fall below B_sess_vld in b_idle state */ | 
					
						
							| 
									
										
										
										
											2014-05-23 08:12:49 +08:00
										 |  |  | 	if (ci->transceiver->state == OTG_STATE_B_IDLE) | 
					
						
							|  |  |  | 		ci_otg_queue_work(ci); | 
					
						
							| 
									
										
										
										
											2014-04-23 15:56:49 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void b_sess_vld_tmout_func(void *ptr, unsigned long indicator) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct ci_hdrc *ci = (struct ci_hdrc *)ptr; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* Check if A detached */ | 
					
						
							|  |  |  | 	if (!(hw_read_otgsc(ci, OTGSC_BSV))) { | 
					
						
							|  |  |  | 		ci->fsm.b_sess_vld = 0; | 
					
						
							|  |  |  | 		ci_otg_add_timer(ci, B_SSEND_SRP); | 
					
						
							| 
									
										
										
										
											2014-05-23 08:12:49 +08:00
										 |  |  | 		ci_otg_queue_work(ci); | 
					
						
							| 
									
										
										
										
											2014-04-23 15:56:49 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void b_data_pulse_end(void *ptr, unsigned long indicator) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct ci_hdrc *ci = (struct ci_hdrc *)ptr; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ci->fsm.b_srp_done = 1; | 
					
						
							|  |  |  | 	ci->fsm.b_bus_req = 0; | 
					
						
							|  |  |  | 	if (ci->fsm.power_up) | 
					
						
							|  |  |  | 		ci->fsm.power_up = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	hw_write_otgsc(ci, OTGSC_HABA, 0); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-05-23 08:12:49 +08:00
										 |  |  | 	ci_otg_queue_work(ci); | 
					
						
							| 
									
										
										
										
											2014-04-23 15:56:49 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* Initialize timers */ | 
					
						
							|  |  |  | static int ci_otg_init_timers(struct ci_hdrc *ci) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct otg_fsm *fsm = &ci->fsm; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* FSM used timers */ | 
					
						
							|  |  |  | 	ci->fsm_timer->timer_list[A_WAIT_VRISE] = | 
					
						
							|  |  |  | 		otg_timer_initializer(ci, &set_tmout_and_fsm, TA_WAIT_VRISE, | 
					
						
							|  |  |  | 			(unsigned long)&fsm->a_wait_vrise_tmout); | 
					
						
							|  |  |  | 	if (ci->fsm_timer->timer_list[A_WAIT_VRISE] == NULL) | 
					
						
							|  |  |  | 		return -ENOMEM; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ci->fsm_timer->timer_list[A_WAIT_VFALL] = | 
					
						
							|  |  |  | 		otg_timer_initializer(ci, &a_wait_vfall_tmout_func, | 
					
						
							|  |  |  | 		TA_WAIT_VFALL, (unsigned long)&fsm->a_wait_vfall_tmout); | 
					
						
							|  |  |  | 	if (ci->fsm_timer->timer_list[A_WAIT_VFALL] == NULL) | 
					
						
							|  |  |  | 		return -ENOMEM; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ci->fsm_timer->timer_list[A_WAIT_BCON] = | 
					
						
							|  |  |  | 		otg_timer_initializer(ci, &set_tmout_and_fsm, TA_WAIT_BCON, | 
					
						
							|  |  |  | 				(unsigned long)&fsm->a_wait_bcon_tmout); | 
					
						
							|  |  |  | 	if (ci->fsm_timer->timer_list[A_WAIT_BCON] == NULL) | 
					
						
							|  |  |  | 		return -ENOMEM; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ci->fsm_timer->timer_list[A_AIDL_BDIS] = | 
					
						
							|  |  |  | 		otg_timer_initializer(ci, &set_tmout_and_fsm, TA_AIDL_BDIS, | 
					
						
							|  |  |  | 				(unsigned long)&fsm->a_aidl_bdis_tmout); | 
					
						
							|  |  |  | 	if (ci->fsm_timer->timer_list[A_AIDL_BDIS] == NULL) | 
					
						
							|  |  |  | 		return -ENOMEM; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ci->fsm_timer->timer_list[A_BIDL_ADIS] = | 
					
						
							|  |  |  | 		otg_timer_initializer(ci, &set_tmout_and_fsm, TA_BIDL_ADIS, | 
					
						
							|  |  |  | 				(unsigned long)&fsm->a_bidl_adis_tmout); | 
					
						
							|  |  |  | 	if (ci->fsm_timer->timer_list[A_BIDL_ADIS] == NULL) | 
					
						
							|  |  |  | 		return -ENOMEM; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ci->fsm_timer->timer_list[B_ASE0_BRST] = | 
					
						
							|  |  |  | 		otg_timer_initializer(ci, &b_ase0_brst_tmout_func, TB_ASE0_BRST, | 
					
						
							|  |  |  | 					(unsigned long)&fsm->b_ase0_brst_tmout); | 
					
						
							|  |  |  | 	if (ci->fsm_timer->timer_list[B_ASE0_BRST] == NULL) | 
					
						
							|  |  |  | 		return -ENOMEM; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ci->fsm_timer->timer_list[B_SE0_SRP] = | 
					
						
							|  |  |  | 		otg_timer_initializer(ci, &set_tmout_and_fsm, TB_SE0_SRP, | 
					
						
							|  |  |  | 					(unsigned long)&fsm->b_se0_srp); | 
					
						
							|  |  |  | 	if (ci->fsm_timer->timer_list[B_SE0_SRP] == NULL) | 
					
						
							|  |  |  | 		return -ENOMEM; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ci->fsm_timer->timer_list[B_SSEND_SRP] = | 
					
						
							|  |  |  | 		otg_timer_initializer(ci, &b_ssend_srp_tmout_func, TB_SSEND_SRP, | 
					
						
							|  |  |  | 					(unsigned long)&fsm->b_ssend_srp); | 
					
						
							|  |  |  | 	if (ci->fsm_timer->timer_list[B_SSEND_SRP] == NULL) | 
					
						
							|  |  |  | 		return -ENOMEM; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ci->fsm_timer->timer_list[B_SRP_FAIL] = | 
					
						
							|  |  |  | 		otg_timer_initializer(ci, &set_tmout, TB_SRP_FAIL, | 
					
						
							|  |  |  | 				(unsigned long)&fsm->b_srp_done); | 
					
						
							|  |  |  | 	if (ci->fsm_timer->timer_list[B_SRP_FAIL] == NULL) | 
					
						
							|  |  |  | 		return -ENOMEM; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ci->fsm_timer->timer_list[B_DATA_PLS] = | 
					
						
							|  |  |  | 		otg_timer_initializer(ci, &b_data_pulse_end, TB_DATA_PLS, 0); | 
					
						
							|  |  |  | 	if (ci->fsm_timer->timer_list[B_DATA_PLS] == NULL) | 
					
						
							|  |  |  | 		return -ENOMEM; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ci->fsm_timer->timer_list[B_SESS_VLD] =	otg_timer_initializer(ci, | 
					
						
							|  |  |  | 					&b_sess_vld_tmout_func, TB_SESS_VLD, 0); | 
					
						
							|  |  |  | 	if (ci->fsm_timer->timer_list[B_SESS_VLD] == NULL) | 
					
						
							|  |  |  | 		return -ENOMEM; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-04-23 15:56:48 +08:00
										 |  |  | /* -------------------------------------------------------------*/ | 
					
						
							|  |  |  | /* Operations that will be called from OTG Finite State Machine */ | 
					
						
							|  |  |  | /* -------------------------------------------------------------*/ | 
					
						
							|  |  |  | static void ci_otg_fsm_add_timer(struct otg_fsm *fsm, enum otg_fsm_timer t) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct ci_hdrc	*ci = container_of(fsm, struct ci_hdrc, fsm); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (t < NUM_OTG_FSM_TIMERS) | 
					
						
							|  |  |  | 		ci_otg_add_timer(ci, t); | 
					
						
							|  |  |  | 	return; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void ci_otg_fsm_del_timer(struct otg_fsm *fsm, enum otg_fsm_timer t) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct ci_hdrc	*ci = container_of(fsm, struct ci_hdrc, fsm); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (t < NUM_OTG_FSM_TIMERS) | 
					
						
							|  |  |  | 		ci_otg_del_timer(ci, t); | 
					
						
							|  |  |  | 	return; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*
 | 
					
						
							|  |  |  |  * A-device drive vbus: turn on vbus regulator and enable port power | 
					
						
							|  |  |  |  * Data pulse irq should be disabled while vbus is on. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | static void ci_otg_drv_vbus(struct otg_fsm *fsm, int on) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	int ret; | 
					
						
							|  |  |  | 	struct ci_hdrc	*ci = container_of(fsm, struct ci_hdrc, fsm); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (on) { | 
					
						
							|  |  |  | 		/* Enable power power */ | 
					
						
							|  |  |  | 		hw_write(ci, OP_PORTSC, PORTSC_W1C_BITS | PORTSC_PP, | 
					
						
							|  |  |  | 							PORTSC_PP); | 
					
						
							|  |  |  | 		if (ci->platdata->reg_vbus) { | 
					
						
							|  |  |  | 			ret = regulator_enable(ci->platdata->reg_vbus); | 
					
						
							|  |  |  | 			if (ret) { | 
					
						
							|  |  |  | 				dev_err(ci->dev, | 
					
						
							|  |  |  | 				"Failed to enable vbus regulator, ret=%d\n", | 
					
						
							|  |  |  | 				ret); | 
					
						
							|  |  |  | 				return; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		/* Disable data pulse irq */ | 
					
						
							|  |  |  | 		hw_write_otgsc(ci, OTGSC_DPIE, 0); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		fsm->a_srp_det = 0; | 
					
						
							|  |  |  | 		fsm->power_up = 0; | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		if (ci->platdata->reg_vbus) | 
					
						
							|  |  |  | 			regulator_disable(ci->platdata->reg_vbus); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		fsm->a_bus_drop = 1; | 
					
						
							|  |  |  | 		fsm->a_bus_req = 0; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*
 | 
					
						
							|  |  |  |  * Control data line by Run Stop bit. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | static void ci_otg_loc_conn(struct otg_fsm *fsm, int on) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct ci_hdrc	*ci = container_of(fsm, struct ci_hdrc, fsm); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (on) | 
					
						
							|  |  |  | 		hw_write(ci, OP_USBCMD, USBCMD_RS, USBCMD_RS); | 
					
						
							|  |  |  | 	else | 
					
						
							|  |  |  | 		hw_write(ci, OP_USBCMD, USBCMD_RS, 0); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*
 | 
					
						
							|  |  |  |  * Generate SOF by host. | 
					
						
							|  |  |  |  * This is controlled through suspend/resume the port. | 
					
						
							|  |  |  |  * In host mode, controller will automatically send SOF. | 
					
						
							|  |  |  |  * Suspend will block the data on the port. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | static void ci_otg_loc_sof(struct otg_fsm *fsm, int on) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct ci_hdrc	*ci = container_of(fsm, struct ci_hdrc, fsm); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (on) | 
					
						
							|  |  |  | 		hw_write(ci, OP_PORTSC, PORTSC_W1C_BITS | PORTSC_FPR, | 
					
						
							|  |  |  | 							PORTSC_FPR); | 
					
						
							|  |  |  | 	else | 
					
						
							|  |  |  | 		hw_write(ci, OP_PORTSC, PORTSC_W1C_BITS | PORTSC_SUSP, | 
					
						
							|  |  |  | 							PORTSC_SUSP); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*
 | 
					
						
							|  |  |  |  * Start SRP pulsing by data-line pulsing, | 
					
						
							|  |  |  |  * no v-bus pulsing followed | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | static void ci_otg_start_pulse(struct otg_fsm *fsm) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct ci_hdrc	*ci = container_of(fsm, struct ci_hdrc, fsm); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* Hardware Assistant Data pulse */ | 
					
						
							|  |  |  | 	hw_write_otgsc(ci, OTGSC_HADP, OTGSC_HADP); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ci_otg_add_timer(ci, B_DATA_PLS); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int ci_otg_start_host(struct otg_fsm *fsm, int on) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct ci_hdrc	*ci = container_of(fsm, struct ci_hdrc, fsm); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	mutex_unlock(&fsm->lock); | 
					
						
							|  |  |  | 	if (on) { | 
					
						
							|  |  |  | 		ci_role_stop(ci); | 
					
						
							|  |  |  | 		ci_role_start(ci, CI_ROLE_HOST); | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		ci_role_stop(ci); | 
					
						
							|  |  |  | 		hw_device_reset(ci, USBMODE_CM_DC); | 
					
						
							|  |  |  | 		ci_role_start(ci, CI_ROLE_GADGET); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	mutex_lock(&fsm->lock); | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int ci_otg_start_gadget(struct otg_fsm *fsm, int on) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct ci_hdrc	*ci = container_of(fsm, struct ci_hdrc, fsm); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	mutex_unlock(&fsm->lock); | 
					
						
							|  |  |  | 	if (on) | 
					
						
							|  |  |  | 		usb_gadget_vbus_connect(&ci->gadget); | 
					
						
							|  |  |  | 	else | 
					
						
							|  |  |  | 		usb_gadget_vbus_disconnect(&ci->gadget); | 
					
						
							|  |  |  | 	mutex_lock(&fsm->lock); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static struct otg_fsm_ops ci_otg_ops = { | 
					
						
							|  |  |  | 	.drv_vbus = ci_otg_drv_vbus, | 
					
						
							|  |  |  | 	.loc_conn = ci_otg_loc_conn, | 
					
						
							|  |  |  | 	.loc_sof = ci_otg_loc_sof, | 
					
						
							|  |  |  | 	.start_pulse = ci_otg_start_pulse, | 
					
						
							|  |  |  | 	.add_timer = ci_otg_fsm_add_timer, | 
					
						
							|  |  |  | 	.del_timer = ci_otg_fsm_del_timer, | 
					
						
							|  |  |  | 	.start_host = ci_otg_start_host, | 
					
						
							|  |  |  | 	.start_gadget = ci_otg_start_gadget, | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-04-23 15:56:50 +08:00
										 |  |  | int ci_otg_fsm_work(struct ci_hdrc *ci) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	/*
 | 
					
						
							|  |  |  | 	 * Don't do fsm transition for B device | 
					
						
							|  |  |  | 	 * when there is no gadget class driver | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	if (ci->fsm.id && !(ci->driver) && | 
					
						
							|  |  |  | 		ci->transceiver->state < OTG_STATE_A_IDLE) | 
					
						
							|  |  |  | 		return 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (otg_statemachine(&ci->fsm)) { | 
					
						
							|  |  |  | 		if (ci->transceiver->state == OTG_STATE_A_IDLE) { | 
					
						
							|  |  |  | 			/*
 | 
					
						
							|  |  |  | 			 * Further state change for cases: | 
					
						
							|  |  |  | 			 * a_idle to b_idle; or | 
					
						
							|  |  |  | 			 * a_idle to a_wait_vrise due to ID change(1->0), so | 
					
						
							|  |  |  | 			 * B-dev becomes A-dev can try to start new session | 
					
						
							|  |  |  | 			 * consequently; or | 
					
						
							|  |  |  | 			 * a_idle to a_wait_vrise when power up | 
					
						
							|  |  |  | 			 */ | 
					
						
							|  |  |  | 			if ((ci->fsm.id) || (ci->id_event) || | 
					
						
							| 
									
										
										
										
											2014-05-23 08:12:49 +08:00
										 |  |  | 						(ci->fsm.power_up)) | 
					
						
							|  |  |  | 				ci_otg_queue_work(ci); | 
					
						
							| 
									
										
										
										
											2014-04-23 15:56:50 +08:00
										 |  |  | 			if (ci->id_event) | 
					
						
							|  |  |  | 				ci->id_event = false; | 
					
						
							|  |  |  | 		} else if (ci->transceiver->state == OTG_STATE_B_IDLE) { | 
					
						
							|  |  |  | 			if (ci->fsm.b_sess_vld) { | 
					
						
							|  |  |  | 				ci->fsm.power_up = 0; | 
					
						
							|  |  |  | 				/*
 | 
					
						
							|  |  |  | 				 * Further transite to b_periphearl state | 
					
						
							|  |  |  | 				 * when register gadget driver with vbus on | 
					
						
							|  |  |  | 				 */ | 
					
						
							| 
									
										
										
										
											2014-05-23 08:12:49 +08:00
										 |  |  | 				ci_otg_queue_work(ci); | 
					
						
							| 
									
										
										
										
											2014-04-23 15:56:50 +08:00
										 |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*
 | 
					
						
							|  |  |  |  * Update fsm variables in each state if catching expected interrupts, | 
					
						
							|  |  |  |  * called by otg fsm isr. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | static void ci_otg_fsm_event(struct ci_hdrc *ci) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	u32 intr_sts, otg_bsess_vld, port_conn; | 
					
						
							|  |  |  | 	struct otg_fsm *fsm = &ci->fsm; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	intr_sts = hw_read_intr_status(ci); | 
					
						
							|  |  |  | 	otg_bsess_vld = hw_read_otgsc(ci, OTGSC_BSV); | 
					
						
							|  |  |  | 	port_conn = hw_read(ci, OP_PORTSC, PORTSC_CCS); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	switch (ci->transceiver->state) { | 
					
						
							|  |  |  | 	case OTG_STATE_A_WAIT_BCON: | 
					
						
							|  |  |  | 		if (port_conn) { | 
					
						
							|  |  |  | 			fsm->b_conn = 1; | 
					
						
							|  |  |  | 			fsm->a_bus_req = 1; | 
					
						
							| 
									
										
										
										
											2014-05-23 08:12:49 +08:00
										 |  |  | 			ci_otg_queue_work(ci); | 
					
						
							| 
									
										
										
										
											2014-04-23 15:56:50 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 	case OTG_STATE_B_IDLE: | 
					
						
							|  |  |  | 		if (otg_bsess_vld && (intr_sts & USBi_PCI) && port_conn) { | 
					
						
							|  |  |  | 			fsm->b_sess_vld = 1; | 
					
						
							| 
									
										
										
										
											2014-05-23 08:12:49 +08:00
										 |  |  | 			ci_otg_queue_work(ci); | 
					
						
							| 
									
										
										
										
											2014-04-23 15:56:50 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 	case OTG_STATE_B_PERIPHERAL: | 
					
						
							|  |  |  | 		if ((intr_sts & USBi_SLI) && port_conn && otg_bsess_vld) { | 
					
						
							|  |  |  | 			fsm->a_bus_suspend = 1; | 
					
						
							| 
									
										
										
										
											2014-05-23 08:12:49 +08:00
										 |  |  | 			ci_otg_queue_work(ci); | 
					
						
							| 
									
										
										
										
											2014-04-23 15:56:50 +08:00
										 |  |  | 		} else if (intr_sts & USBi_PCI) { | 
					
						
							|  |  |  | 			if (fsm->a_bus_suspend == 1) | 
					
						
							|  |  |  | 				fsm->a_bus_suspend = 0; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 	case OTG_STATE_B_HOST: | 
					
						
							|  |  |  | 		if ((intr_sts & USBi_PCI) && !port_conn) { | 
					
						
							|  |  |  | 			fsm->a_conn = 0; | 
					
						
							|  |  |  | 			fsm->b_bus_req = 0; | 
					
						
							| 
									
										
										
										
											2014-05-23 08:12:49 +08:00
										 |  |  | 			ci_otg_queue_work(ci); | 
					
						
							| 
									
										
										
										
											2014-04-23 15:56:50 +08:00
										 |  |  | 			ci_otg_add_timer(ci, B_SESS_VLD); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 	case OTG_STATE_A_PERIPHERAL: | 
					
						
							|  |  |  | 		if (intr_sts & USBi_SLI) { | 
					
						
							|  |  |  | 			 fsm->b_bus_suspend = 1; | 
					
						
							|  |  |  | 			/*
 | 
					
						
							|  |  |  | 			 * Init a timer to know how long this suspend | 
					
						
							|  |  |  | 			 * will contine, if time out, indicates B no longer | 
					
						
							|  |  |  | 			 * wants to be host role | 
					
						
							|  |  |  | 			 */ | 
					
						
							|  |  |  | 			 ci_otg_add_timer(ci, A_BIDL_ADIS); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (intr_sts & USBi_URI) | 
					
						
							|  |  |  | 			ci_otg_del_timer(ci, A_BIDL_ADIS); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (intr_sts & USBi_PCI) { | 
					
						
							|  |  |  | 			if (fsm->b_bus_suspend == 1) { | 
					
						
							|  |  |  | 				ci_otg_del_timer(ci, A_BIDL_ADIS); | 
					
						
							|  |  |  | 				fsm->b_bus_suspend = 0; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 	case OTG_STATE_A_SUSPEND: | 
					
						
							|  |  |  | 		if ((intr_sts & USBi_PCI) && !port_conn) { | 
					
						
							|  |  |  | 			fsm->b_conn = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			/* if gadget driver is binded */ | 
					
						
							|  |  |  | 			if (ci->driver) { | 
					
						
							|  |  |  | 				/* A device to be peripheral mode */ | 
					
						
							|  |  |  | 				ci->gadget.is_a_peripheral = 1; | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2014-05-23 08:12:49 +08:00
										 |  |  | 			ci_otg_queue_work(ci); | 
					
						
							| 
									
										
										
										
											2014-04-23 15:56:50 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 	case OTG_STATE_A_HOST: | 
					
						
							|  |  |  | 		if ((intr_sts & USBi_PCI) && !port_conn) { | 
					
						
							|  |  |  | 			fsm->b_conn = 0; | 
					
						
							| 
									
										
										
										
											2014-05-23 08:12:49 +08:00
										 |  |  | 			ci_otg_queue_work(ci); | 
					
						
							| 
									
										
										
										
											2014-04-23 15:56:50 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 	case OTG_STATE_B_WAIT_ACON: | 
					
						
							|  |  |  | 		if ((intr_sts & USBi_PCI) && port_conn) { | 
					
						
							|  |  |  | 			fsm->a_conn = 1; | 
					
						
							| 
									
										
										
										
											2014-05-23 08:12:49 +08:00
										 |  |  | 			ci_otg_queue_work(ci); | 
					
						
							| 
									
										
										
										
											2014-04-23 15:56:50 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 	default: | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*
 | 
					
						
							|  |  |  |  * ci_otg_irq - otg fsm related irq handling | 
					
						
							|  |  |  |  * and also update otg fsm variable by monitoring usb host and udc | 
					
						
							|  |  |  |  * state change interrupts. | 
					
						
							|  |  |  |  * @ci: ci_hdrc | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | irqreturn_t ci_otg_fsm_irq(struct ci_hdrc *ci) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	irqreturn_t retval =  IRQ_NONE; | 
					
						
							|  |  |  | 	u32 otgsc, otg_int_src = 0; | 
					
						
							|  |  |  | 	struct otg_fsm *fsm = &ci->fsm; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	otgsc = hw_read_otgsc(ci, ~0); | 
					
						
							|  |  |  | 	otg_int_src = otgsc & OTGSC_INT_STATUS_BITS & (otgsc >> 8); | 
					
						
							|  |  |  | 	fsm->id = (otgsc & OTGSC_ID) ? 1 : 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (otg_int_src) { | 
					
						
							|  |  |  | 		if (otg_int_src & OTGSC_1MSIS) { | 
					
						
							|  |  |  | 			hw_write_otgsc(ci, OTGSC_1MSIS, OTGSC_1MSIS); | 
					
						
							|  |  |  | 			retval = ci_otg_tick_timer(ci); | 
					
						
							|  |  |  | 			return IRQ_HANDLED; | 
					
						
							|  |  |  | 		} else if (otg_int_src & OTGSC_DPIS) { | 
					
						
							|  |  |  | 			hw_write_otgsc(ci, OTGSC_DPIS, OTGSC_DPIS); | 
					
						
							|  |  |  | 			fsm->a_srp_det = 1; | 
					
						
							|  |  |  | 			fsm->a_bus_drop = 0; | 
					
						
							|  |  |  | 		} else if (otg_int_src & OTGSC_IDIS) { | 
					
						
							|  |  |  | 			hw_write_otgsc(ci, OTGSC_IDIS, OTGSC_IDIS); | 
					
						
							|  |  |  | 			if (fsm->id == 0) { | 
					
						
							|  |  |  | 				fsm->a_bus_drop = 0; | 
					
						
							|  |  |  | 				fsm->a_bus_req = 1; | 
					
						
							|  |  |  | 				ci->id_event = true; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} else if (otg_int_src & OTGSC_BSVIS) { | 
					
						
							|  |  |  | 			hw_write_otgsc(ci, OTGSC_BSVIS, OTGSC_BSVIS); | 
					
						
							|  |  |  | 			if (otgsc & OTGSC_BSV) { | 
					
						
							|  |  |  | 				fsm->b_sess_vld = 1; | 
					
						
							|  |  |  | 				ci_otg_del_timer(ci, B_SSEND_SRP); | 
					
						
							|  |  |  | 				ci_otg_del_timer(ci, B_SRP_FAIL); | 
					
						
							|  |  |  | 				fsm->b_ssend_srp = 0; | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				fsm->b_sess_vld = 0; | 
					
						
							|  |  |  | 				if (fsm->id) | 
					
						
							|  |  |  | 					ci_otg_add_timer(ci, B_SSEND_SRP); | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} else if (otg_int_src & OTGSC_AVVIS) { | 
					
						
							|  |  |  | 			hw_write_otgsc(ci, OTGSC_AVVIS, OTGSC_AVVIS); | 
					
						
							|  |  |  | 			if (otgsc & OTGSC_AVV) { | 
					
						
							|  |  |  | 				fsm->a_vbus_vld = 1; | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				fsm->a_vbus_vld = 0; | 
					
						
							|  |  |  | 				fsm->b_conn = 0; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2014-05-23 08:12:49 +08:00
										 |  |  | 		ci_otg_queue_work(ci); | 
					
						
							| 
									
										
										
										
											2014-04-23 15:56:50 +08:00
										 |  |  | 		return IRQ_HANDLED; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ci_otg_fsm_event(ci); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return retval; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void ci_hdrc_otg_fsm_start(struct ci_hdrc *ci) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2014-05-23 08:12:49 +08:00
										 |  |  | 	ci_otg_queue_work(ci); | 
					
						
							| 
									
										
										
										
											2014-04-23 15:56:50 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-04-23 15:56:44 +08:00
										 |  |  | int ci_hdrc_otg_fsm_init(struct ci_hdrc *ci) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2014-04-23 15:56:49 +08:00
										 |  |  | 	int retval = 0; | 
					
						
							| 
									
										
										
										
											2014-04-23 15:56:44 +08:00
										 |  |  | 	struct usb_otg *otg; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	otg = devm_kzalloc(ci->dev, | 
					
						
							|  |  |  | 			sizeof(struct usb_otg), GFP_KERNEL); | 
					
						
							|  |  |  | 	if (!otg) { | 
					
						
							|  |  |  | 		dev_err(ci->dev, | 
					
						
							|  |  |  | 		"Failed to allocate usb_otg structure for ci hdrc otg!\n"); | 
					
						
							|  |  |  | 		return -ENOMEM; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	otg->phy = ci->transceiver; | 
					
						
							|  |  |  | 	otg->gadget = &ci->gadget; | 
					
						
							|  |  |  | 	ci->fsm.otg = otg; | 
					
						
							|  |  |  | 	ci->transceiver->otg = ci->fsm.otg; | 
					
						
							|  |  |  | 	ci->fsm.power_up = 1; | 
					
						
							|  |  |  | 	ci->fsm.id = hw_read_otgsc(ci, OTGSC_ID) ? 1 : 0; | 
					
						
							|  |  |  | 	ci->transceiver->state = OTG_STATE_UNDEFINED; | 
					
						
							| 
									
										
										
										
											2014-04-23 15:56:48 +08:00
										 |  |  | 	ci->fsm.ops = &ci_otg_ops; | 
					
						
							| 
									
										
										
										
											2014-04-23 15:56:44 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	mutex_init(&ci->fsm.lock); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-04-23 15:56:49 +08:00
										 |  |  | 	ci->fsm_timer = devm_kzalloc(ci->dev, | 
					
						
							|  |  |  | 			sizeof(struct ci_otg_fsm_timer_list), GFP_KERNEL); | 
					
						
							|  |  |  | 	if (!ci->fsm_timer) { | 
					
						
							|  |  |  | 		dev_err(ci->dev, | 
					
						
							|  |  |  | 		"Failed to allocate timer structure for ci hdrc otg!\n"); | 
					
						
							|  |  |  | 		return -ENOMEM; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	INIT_LIST_HEAD(&ci->fsm_timer->active_timers); | 
					
						
							|  |  |  | 	retval = ci_otg_init_timers(ci); | 
					
						
							|  |  |  | 	if (retval) { | 
					
						
							|  |  |  | 		dev_err(ci->dev, "Couldn't init OTG timers\n"); | 
					
						
							|  |  |  | 		return retval; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-04-23 15:56:51 +08:00
										 |  |  | 	retval = sysfs_create_group(&ci->dev->kobj, &inputs_attr_group); | 
					
						
							|  |  |  | 	if (retval < 0) { | 
					
						
							|  |  |  | 		dev_dbg(ci->dev, | 
					
						
							|  |  |  | 			"Can't register sysfs attr group: %d\n", retval); | 
					
						
							|  |  |  | 		return retval; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-04-23 15:56:44 +08:00
										 |  |  | 	/* Enable A vbus valid irq */ | 
					
						
							|  |  |  | 	hw_write_otgsc(ci, OTGSC_AVVIE, OTGSC_AVVIE); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (ci->fsm.id) { | 
					
						
							|  |  |  | 		ci->fsm.b_ssend_srp = | 
					
						
							|  |  |  | 			hw_read_otgsc(ci, OTGSC_BSV) ? 0 : 1; | 
					
						
							|  |  |  | 		ci->fsm.b_sess_vld = | 
					
						
							|  |  |  | 			hw_read_otgsc(ci, OTGSC_BSV) ? 1 : 0; | 
					
						
							|  |  |  | 		/* Enable BSV irq */ | 
					
						
							|  |  |  | 		hw_write_otgsc(ci, OTGSC_BSVIE, OTGSC_BSVIE); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2014-04-23 15:56:51 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | void ci_hdrc_otg_fsm_remove(struct ci_hdrc *ci) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	sysfs_remove_group(&ci->dev->kobj, &inputs_attr_group); | 
					
						
							|  |  |  | } |