| /* |
| * 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); |
| } |