diff options
Diffstat (limited to 'drivers/usb/chipidea/otg_fsm.c')
| -rw-r--r-- | drivers/usb/chipidea/otg_fsm.c | 842 | 
1 files changed, 842 insertions, 0 deletions
diff --git a/drivers/usb/chipidea/otg_fsm.c b/drivers/usb/chipidea/otg_fsm.c new file mode 100644 index 00000000000..caaabc58021 --- /dev/null +++ b/drivers/usb/chipidea/otg_fsm.c @@ -0,0 +1,842 @@ +/* + * 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. + * + * TODO List + * - ADP + * - OTG test device + */ + +#include <linux/usb/otg.h> +#include <linux/usb/gadget.h> +#include <linux/usb/hcd.h> +#include <linux/usb/chipidea.h> +#include <linux/regulator/consumer.h> + +#include "ci.h" +#include "bits.h" +#include "otg.h" +#include "otg_fsm.h" + +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; +} + +/* 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; +	} + +	ci_otg_queue_work(ci); +	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; +	} + +	ci_otg_queue_work(ci); +	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; + +	ci_otg_queue_work(ci); +	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; + +	ci_otg_queue_work(ci); +	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, +}; + +/* + * 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); +} + +/* + * 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; +} + +/* 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); + +	ci_otg_queue_work(ci); +} + +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); +	ci_otg_queue_work(ci); +} + +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; + +	ci_otg_queue_work(ci); +} + +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 */ +	if (ci->transceiver->state == OTG_STATE_B_IDLE) +		ci_otg_queue_work(ci); +} + +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); +		ci_otg_queue_work(ci); +	} +} + +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); + +	ci_otg_queue_work(ci); +} + +/* 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; +} + +/* -------------------------------------------------------------*/ +/* 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, +}; + +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) || +						(ci->fsm.power_up)) +				ci_otg_queue_work(ci); +			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 +				 */ +				ci_otg_queue_work(ci); +			} +		} +	} +	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; +			ci_otg_queue_work(ci); +		} +		break; +	case OTG_STATE_B_IDLE: +		if (otg_bsess_vld && (intr_sts & USBi_PCI) && port_conn) { +			fsm->b_sess_vld = 1; +			ci_otg_queue_work(ci); +		} +		break; +	case OTG_STATE_B_PERIPHERAL: +		if ((intr_sts & USBi_SLI) && port_conn && otg_bsess_vld) { +			fsm->a_bus_suspend = 1; +			ci_otg_queue_work(ci); +		} 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; +			ci_otg_queue_work(ci); +			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; +			} +			ci_otg_queue_work(ci); +		} +		break; +	case OTG_STATE_A_HOST: +		if ((intr_sts & USBi_PCI) && !port_conn) { +			fsm->b_conn = 0; +			ci_otg_queue_work(ci); +		} +		break; +	case OTG_STATE_B_WAIT_ACON: +		if ((intr_sts & USBi_PCI) && port_conn) { +			fsm->a_conn = 1; +			ci_otg_queue_work(ci); +		} +		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; +			} +		} +		ci_otg_queue_work(ci); +		return IRQ_HANDLED; +	} + +	ci_otg_fsm_event(ci); + +	return retval; +} + +void ci_hdrc_otg_fsm_start(struct ci_hdrc *ci) +{ +	ci_otg_queue_work(ci); +} + +int ci_hdrc_otg_fsm_init(struct ci_hdrc *ci) +{ +	int retval = 0; +	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; +	ci->fsm.ops = &ci_otg_ops; + +	mutex_init(&ci->fsm.lock); + +	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; +	} + +	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; +	} + +	/* 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; +} + +void ci_hdrc_otg_fsm_remove(struct ci_hdrc *ci) +{ +	sysfs_remove_group(&ci->dev->kobj, &inputs_attr_group); +}  | 
