diff options
Diffstat (limited to 'drivers/usb/chipidea/otg.c')
-rw-r--r-- | drivers/usb/chipidea/otg.c | 101 |
1 files changed, 91 insertions, 10 deletions
diff --git a/drivers/usb/chipidea/otg.c b/drivers/usb/chipidea/otg.c index b8650210be0f..6b2603c963dd 100644 --- a/drivers/usb/chipidea/otg.c +++ b/drivers/usb/chipidea/otg.c @@ -1,7 +1,8 @@ /* * otg.c - ChipIdea USB IP core OTG driver * - * Copyright (C) 2013 Freescale Semiconductor, Inc. + * Copyright (C) 2013-2016 Freescale Semiconductor, Inc. + * Copyright 2017 NXP * * Author: Peter Chen * @@ -23,6 +24,7 @@ #include "bits.h" #include "otg.h" #include "otg_fsm.h" +#include "host.h" /** * hw_read_otgsc returns otgsc register bits value. @@ -44,7 +46,7 @@ u32 hw_read_otgsc(struct ci_hdrc *ci, u32 mask) else val &= ~OTGSC_BSVIS; - if (cable->state) + if (cable->connected) val |= OTGSC_BSV; else val &= ~OTGSC_BSV; @@ -62,10 +64,10 @@ u32 hw_read_otgsc(struct ci_hdrc *ci, u32 mask) else val &= ~OTGSC_IDIS; - if (cable->state) - val |= OTGSC_ID; + if (cable->connected) + val &= ~OTGSC_ID; /* host */ else - val &= ~OTGSC_ID; + val |= OTGSC_ID; /* device */ if (cable->enabled) val |= OTGSC_IDIE; @@ -129,6 +131,46 @@ enum ci_role ci_otg_role(struct ci_hdrc *ci) return role; } +/* + * Handling vbus glitch + * We only need to consider glitch for without usb connection, + * With usb connection, we consider it as real disconnection. + * + * If the vbus can't be kept above B session valid for timeout value, + * we think it is a vbus glitch, otherwise it's a valid vbus. + */ +#define CI_VBUS_CONNECT_TIMEOUT_MS 300 +static int ci_is_vbus_glitch(struct ci_hdrc *ci) +{ + int i; + + for (i = 0; i < CI_VBUS_CONNECT_TIMEOUT_MS/20; i++) { + if (hw_read_otgsc(ci, OTGSC_AVV)) { + return 0; + } else if (!hw_read_otgsc(ci, OTGSC_BSV)) { + dev_warn(ci->dev, "there is a vbus glitch\n"); + return 1; + } + msleep(20); + } + + return 0; +} + +void ci_handle_vbus_connected(struct ci_hdrc *ci) +{ + /* + * TODO: if the platform does not supply 5v to udc, or use other way + * to supply 5v, it needs to use other conditions to call + * usb_gadget_vbus_connect. + */ + if (!ci->is_otg) + return; + + if (hw_read_otgsc(ci, OTGSC_BSV) && !ci_is_vbus_glitch(ci)) + usb_gadget_vbus_connect(&ci->gadget); +} + void ci_handle_vbus_change(struct ci_hdrc *ci) { if (!ci->is_otg) @@ -165,10 +207,12 @@ static int hw_wait_vbus_lower_bsv(struct ci_hdrc *ci) return 0; } -static void ci_handle_id_switch(struct ci_hdrc *ci) +void ci_handle_id_switch(struct ci_hdrc *ci) { - enum ci_role role = ci_otg_role(ci); + enum ci_role role; + mutex_lock(&ci->mutex); + role = ci_otg_role(ci); if (role != ci->role) { dev_dbg(ci->dev, "switching from %s to %s\n", ci_role(ci)->name, ci->roles[role]->name); @@ -191,7 +235,34 @@ static void ci_handle_id_switch(struct ci_hdrc *ci) if (role == CI_ROLE_GADGET) ci_handle_vbus_change(ci); } + mutex_unlock(&ci->mutex); +} + +static void ci_handle_vbus_glitch(struct ci_hdrc *ci) +{ + bool valid_vbus_change = false; + + if (hw_read_otgsc(ci, OTGSC_BSV)) { + if (!ci_is_vbus_glitch(ci)) { + if (ci_otg_is_fsm_mode(ci)) { + ci->fsm.b_sess_vld = 1; + ci->fsm.b_ssend_srp = 0; + otg_del_timer(&ci->fsm, B_SSEND_SRP); + otg_del_timer(&ci->fsm, B_SRP_FAIL); + } + valid_vbus_change = true; + } + } else { + if (ci->vbus_active && !ci_otg_is_fsm_mode(ci)) + valid_vbus_change = true; + } + + if (valid_vbus_change) { + ci->b_sess_valid_event = true; + ci_otg_queue_work(ci); + } } + /** * ci_otg_work - perform otg (vbus/id) event handle * @work: work struct @@ -200,6 +271,15 @@ static void ci_otg_work(struct work_struct *work) { struct ci_hdrc *ci = container_of(work, struct ci_hdrc, work); + if (ci->vbus_glitch_check_event) { + ci->vbus_glitch_check_event = false; + pm_runtime_get_sync(ci->dev); + ci_handle_vbus_glitch(ci); + pm_runtime_put_sync(ci->dev); + enable_irq(ci->irq); + return; + } + if (ci_otg_is_fsm_mode(ci) && !ci_otg_fsm_work(ci)) { enable_irq(ci->irq); return; @@ -248,13 +328,14 @@ int ci_hdrc_otg_init(struct ci_hdrc *ci) */ void ci_hdrc_otg_destroy(struct ci_hdrc *ci) { + /* Disable all OTG irq and clear status */ + hw_write_otgsc(ci, OTGSC_INT_EN_BITS | OTGSC_INT_STATUS_BITS, + OTGSC_INT_STATUS_BITS); if (ci->wq) { flush_workqueue(ci->wq); destroy_workqueue(ci->wq); + ci->wq = NULL; } - /* Disable all OTG irq and clear status */ - hw_write_otgsc(ci, OTGSC_INT_EN_BITS | OTGSC_INT_STATUS_BITS, - OTGSC_INT_STATUS_BITS); if (ci_otg_is_fsm_mode(ci)) ci_hdrc_otg_fsm_remove(ci); } |