summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/usb/chipidea/ci.h4
-rw-r--r--drivers/usb/chipidea/core.c6
-rw-r--r--drivers/usb/chipidea/otg_fsm.c160
-rw-r--r--drivers/usb/chipidea/otg_fsm.h8
-rw-r--r--include/linux/usb/chipidea.h8
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;