diff options
-rw-r--r-- | drivers/usb/chipidea/ci.h | 4 | ||||
-rw-r--r-- | drivers/usb/chipidea/core.c | 6 | ||||
-rw-r--r-- | drivers/usb/chipidea/otg_fsm.c | 160 | ||||
-rw-r--r-- | drivers/usb/chipidea/otg_fsm.h | 8 | ||||
-rw-r--r-- | include/linux/usb/chipidea.h | 8 |
5 files changed, 185 insertions, 1 deletions
diff --git a/drivers/usb/chipidea/ci.h b/drivers/usb/chipidea/ci.h index e3fe643d063b..4347e729d5b8 100644 --- a/drivers/usb/chipidea/ci.h +++ b/drivers/usb/chipidea/ci.h @@ -205,6 +205,8 @@ struct hw_bank { * @wakeup_int: if wakeup interrupt occur * @rev: The revision number for controller * @hnp_enable: indicates if OTG full HNP is enabled + * @adp_probe_event: indicates to enable adp probe + * @adp_sense_event: indicates to enable adp sense */ struct ci_hdrc { struct device *dev; @@ -270,6 +272,8 @@ struct ci_hdrc { u32 pm_usbmode; struct work_struct power_lost_work; bool hnp_enable; + bool adp_probe_event; + bool adp_sense_event; }; static inline struct ci_role_driver *ci_role(struct ci_hdrc *ci) diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c index 8a91b5a1bb8c..b5ed56eaf0c7 100644 --- a/drivers/usb/chipidea/core.c +++ b/drivers/usb/chipidea/core.c @@ -623,6 +623,12 @@ static int ci_get_platdata(struct device *dev, of_usb_host_tpl_support(dev->of_node); } + if (platdata->dr_mode == USB_DR_MODE_OTG) { + if (!platdata->adp_support) + platdata->adp_support = + of_usb_otg_adp_support(dev->of_node); + } + if (of_usb_get_maximum_speed(dev->of_node) == USB_SPEED_FULL) platdata->flags |= CI_HDRC_FORCE_FULLSPEED; diff --git a/drivers/usb/chipidea/otg_fsm.c b/drivers/usb/chipidea/otg_fsm.c index 48e5a26328cc..1ccaf35eea5a 100644 --- a/drivers/usb/chipidea/otg_fsm.c +++ b/drivers/usb/chipidea/otg_fsm.c @@ -273,6 +273,9 @@ static unsigned otg_timer_ms[] = { TA_TST_MAINT, TB_SRP_REQD, TB_TST_SUSP, + TA_ADP_PRB, + TB_ADP_PRB, + TB_ADP_SNS, 0, }; @@ -473,6 +476,19 @@ static int b_tst_susp_tmout(struct ci_hdrc *ci) return 1; } +/* used to enable ADP probe irq for next */ +static int adp_prb_tmout(struct ci_hdrc *ci) +{ + ci->adp_probe_event = true; + return 0; +} + +static int b_adp_sns_tmout(struct ci_hdrc *ci) +{ + ci->adp_sense_event = true; + return 0; +} + /* * Keep this list in the same order as timers indexed * by enum otg_fsm_timer in include/linux/usb/otg-fsm.h @@ -494,6 +510,9 @@ static int (*otg_timer_handlers[])(struct ci_hdrc *) = { a_tst_maint_tmout, /* A_TST_MAINT */ b_srp_reqd_tmout, /* B_SRP_REQD */ b_tst_susp_tmout, /* B_TST_SUSP */ + adp_prb_tmout, /* ADP_PRB for A */ + adp_prb_tmout, /* ADP_PRB for B */ + b_adp_sns_tmout, /* B_ADP_SNS */ NULL, /* HNP_POLLING */ }; @@ -721,6 +740,44 @@ static int ci_otg_start_gadget(struct otg_fsm *fsm, int on) return 0; } +static void ci_otg_start_adp_prb(struct otg_fsm *fsm) +{ + struct ci_hdrc *ci = container_of(fsm, struct ci_hdrc, fsm); + + if (!ci->platdata->adp_support || !ci->hnp_enable) + return; + + if (ci->platdata->notify_event) + ci->platdata->notify_event(ci, + CI_HDRC_IMX_ADP_PROBE_START); + pm_runtime_get(ci->dev); +} + +static void ci_otg_start_adp_sns(struct otg_fsm *fsm) +{ + struct ci_hdrc *ci = container_of(fsm, struct ci_hdrc, fsm); + + if (!ci->platdata->adp_support || !ci->hnp_enable || !ci->driver) + return; + + /* TODO If power_up and vbus is off, do one ADP probe before SRP */ + + /* + * start a timer to see if the ADP sense irq + * can be generated before time out, if yes, means + * the A device still connected and is doing + * ADP probe; if no, means the connection + * lost(after VBus off, OTG A-device can NOT + * connect with B-dev but does not do ADP probe) + * then B-dev start ADP probe. + */ + otg_add_timer(fsm, B_ADP_SNS); + if (ci->platdata->notify_event) + ci->platdata->notify_event(ci, + CI_HDRC_IMX_ADP_SENSE_ENABLE); + return; +} + static struct otg_fsm_ops ci_otg_ops = { .drv_vbus = ci_otg_drv_vbus, .loc_conn = ci_otg_loc_conn, @@ -730,8 +787,49 @@ static struct otg_fsm_ops ci_otg_ops = { .del_timer = ci_otg_fsm_del_timer, .start_host = ci_otg_start_host, .start_gadget = ci_otg_start_gadget, + .start_adp_prb = ci_otg_start_adp_prb, + .start_adp_sns = ci_otg_start_adp_sns, }; +static int ci_otg_fsm_adp_work(struct ci_hdrc *ci) +{ + struct otg_fsm *fsm = &ci->fsm; + + if (!ci->platdata->notify_event || !ci->platdata->adp_support) + return -ENOTSUPP; + + if (ci->adp_probe_event) { + ci->adp_probe_event = false; + /* + * Continue ADP probe if probe is on-going + * Do not release pm since the charge + * time is very short, rely on probe + * irq handler to release pm count + */ + if (fsm->adp_prb) + ci->platdata->notify_event(ci, + CI_HDRC_IMX_ADP_PROBE_ENABLE); + else + pm_runtime_put_sync(ci->dev); + return 0; + } else if (ci->adp_sense_event) { + ci->adp_sense_event = false; + + /* If connection is still there, continue sense */ + if (ci->platdata->notify_event(ci, + CI_HDRC_IMX_ADP_SENSE_CONNECTION)) { + otg_add_timer(&ci->fsm, B_ADP_SNS); + } else { + ci->fsm.adp_sns = 0; + /* start do probe after sense failed */ + otg_start_adp_prb(fsm); + } + pm_runtime_put_sync(ci->dev); + return 0; + } + return 1; +} + int ci_otg_fsm_work(struct ci_hdrc *ci) { if (ci->fsm.id && ci->fsm.otg->state < OTG_STATE_A_IDLE) { @@ -756,6 +854,9 @@ int ci_otg_fsm_work(struct ci_hdrc *ci) } pm_runtime_get_sync(ci->dev); + if (!ci_otg_fsm_adp_work(ci)) + return 0; + if (otg_statemachine(&ci->fsm)) { if (ci->fsm.otg->state == OTG_STATE_A_IDLE) { /* @@ -891,6 +992,60 @@ static void ci_otg_fsm_event(struct ci_hdrc *ci) } } +static irqreturn_t ci_otg_fsm_adp_int(struct ci_hdrc *ci) +{ + struct otg_fsm *fsm = &ci->fsm; + irqreturn_t retval = IRQ_NONE; + bool adp_int = false; + + if (!ci->platdata->notify_event || !ci->platdata->adp_support) + return retval; + + adp_int = ci->platdata->notify_event(ci, + CI_HDRC_IMX_ADP_IS_PROBE_INT); + if (adp_int) { + adp_int = ci->platdata->notify_event(ci, + CI_HDRC_IMX_ADP_ATTACH_EVENT); + if (adp_int) { + if (!fsm->id) + fsm->a_bus_drop = 0; + /* + * For B device, ADP may come after BSV rise, + * this case should be handle by BSV irq + */ + if (!fsm->id || !fsm->b_sess_vld) + fsm->adp_change = 1; + + ci_otg_queue_work(ci); + } else { + /* contine probe */ + if (fsm->id) + otg_add_timer(fsm, B_ADP_PRB); + else + otg_add_timer(fsm, A_ADP_PRB); + } + + pm_runtime_put(ci->dev); + return IRQ_HANDLED; + } + + adp_int = ci->platdata->notify_event(ci, + CI_HDRC_IMX_ADP_IS_SENSE_INT); + if (adp_int) { + /* + * Indicates the A-dev is doing ADP probe. + * continue sense, and reset B_ADP_SNS + * timer, this irq will be disabled by the + * timer time out func. + * If A-dev start session by turn on Vbus + * B-dev should disable adp sense. + */ + if (!fsm->b_sess_vld) + otg_add_timer(fsm, B_ADP_SNS); + } + return retval; +} + /* * ci_otg_irq - otg fsm related irq handling * and also update otg fsm variable by monitoring usb host and udc @@ -904,9 +1059,12 @@ irqreturn_t ci_otg_fsm_irq(struct ci_hdrc *ci) 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 (ci_otg_fsm_adp_int(ci) == IRQ_HANDLED) + return IRQ_HANDLED; + + otg_int_src = otgsc & OTGSC_INT_STATUS_BITS & (otgsc >> 8); if (otg_int_src) { if (otg_int_src & OTGSC_DPIS) { hw_write_otgsc(ci, OTGSC_DPIS, OTGSC_DPIS); diff --git a/drivers/usb/chipidea/otg_fsm.h b/drivers/usb/chipidea/otg_fsm.h index 3eec4d2523b3..168cdce26ce3 100644 --- a/drivers/usb/chipidea/otg_fsm.h +++ b/drivers/usb/chipidea/otg_fsm.h @@ -47,6 +47,8 @@ * timer, 9.9s~10.1s */ +#define TA_ADP_PRB (1750) /* ADP probe for A typical value */ + /* * B-device timing constants */ @@ -77,6 +79,12 @@ * max: 100ms */ +#define TB_ADP_SNS (2000) /* max duration B-dev dose not get + * sense irq. + */ + +#define TB_ADP_PRB (2000) /* ADP probe for B */ + #define T_HOST_REQ_POLL (1500) /* HNP polling interval 1s~2s */ #ifdef CONFIG_USB_OTG_FSM diff --git a/include/linux/usb/chipidea.h b/include/linux/usb/chipidea.h index d1f8f550766f..5fcb7b70f374 100644 --- a/include/linux/usb/chipidea.h +++ b/include/linux/usb/chipidea.h @@ -44,9 +44,17 @@ struct ci_hdrc_platform_data { #define CI_HDRC_CONTROLLER_CHARGER_POST_EVENT 4 #define CI_HDRC_IMX_HSIC_ACTIVE_EVENT 5 #define CI_HDRC_IMX_HSIC_SUSPEND_EVENT 6 +#define CI_HDRC_IMX_ADP_PROBE_ENABLE 7 +#define CI_HDRC_IMX_ADP_PROBE_START 8 +#define CI_HDRC_IMX_ADP_SENSE_ENABLE 9 +#define CI_HDRC_IMX_ADP_IS_PROBE_INT 10 +#define CI_HDRC_IMX_ADP_IS_SENSE_INT 11 +#define CI_HDRC_IMX_ADP_SENSE_CONNECTION 12 +#define CI_HDRC_IMX_ADP_ATTACH_EVENT 13 int (*notify_event)(struct ci_hdrc *ci, unsigned event); struct regulator *reg_vbus; bool tpl_support; + bool adp_support; u32 ahbburst_config; u32 burst_length; u32 phy_clkgate_delay_us; |