Эх сурвалжийг харах

usb: cdns3: Implement Idle state for Type-C

For Type-C we need to have clear indication of
cable detached state so that we can keep Host and
Gadget controllers in idle state and program
the Lane swap feature during the next cable attach,
before starting Host/Gadget controllers.

Signed-off-by: Roger Quadros <rogerq@ti.com>
Signed-off-by: Sekhar Nori <nsekhar@ti.com>
Roger Quadros 6 жил өмнө
parent
commit
2a71bd6af4

+ 82 - 31
drivers/usb/cdns3/core.c

@@ -103,33 +103,45 @@ void cdns3_role_stop(struct cdns3 *cdns)
 	mutex_unlock(&cdns->mutex);
 }
 
-/*
- * cdns->role gets from cdns3_get_initial_role, and this API tells role at the
- * runtime.
- * If both roles are supported, the role is selected based on vbus/id.
- * It could be read from OTG register or external connector.
- * If only single role is supported, only one role structure
- * is allocated, cdns->roles[CDNS3_ROLE_HOST] or cdns->roles[CDNS3_ROLE_GADGET].
- */
-static enum cdns3_roles cdns3_get_initial_role(struct cdns3 *cdns)
-{
-	if (cdns->roles[CDNS3_ROLE_HOST] && cdns->roles[CDNS3_ROLE_GADGET]) {
-		if (cdns3_is_host(cdns))
-			return CDNS3_ROLE_HOST;
-		if (cdns3_is_device(cdns))
-			return CDNS3_ROLE_GADGET;
-	}
-	return cdns->roles[CDNS3_ROLE_HOST]
-		? CDNS3_ROLE_HOST
-		: CDNS3_ROLE_GADGET;
-}
-
 static void cdns3_exit_roles(struct cdns3 *cdns)
 {
 	cdns3_role_stop(cdns);
 	cdns3_drd_exit(cdns);
 }
 
+enum cdns3_roles cdsn3_get_real_role(struct cdns3 *cdns);
+
+static int cdns3_idle_role_start(struct cdns3 *cnds)
+{
+	/* Hold PHY in RESET */
+	return 0;
+}
+
+static void cdns3_idle_role_stop(struct cdns3 *cnds)
+{
+	/* Program Lane swap and bring PHY out of RESET */
+}
+
+static int cdns3_idle_init(struct cdns3 *cdns)
+{
+	struct cdns3_role_driver *rdrv;
+
+	rdrv = devm_kzalloc(cdns->dev, sizeof(*rdrv), GFP_KERNEL);
+	if (!rdrv)
+		return -ENOMEM;
+
+	rdrv->start = cdns3_idle_role_start;
+	rdrv->stop = cdns3_idle_role_stop;
+	rdrv->state = CDNS3_ROLE_STATE_INACTIVE;
+	rdrv->suspend = NULL;
+	rdrv->resume = NULL;
+	rdrv->name = "idle";
+
+	cdns->roles[CDNS3_ROLE_IDLE] = rdrv;
+
+	return 0;
+}
+
 /**
  * cdns3_core_init_role - initialize role of operation
  * @cdns: Pointer to cdns3 structure
@@ -178,6 +190,10 @@ static int cdns3_core_init_role(struct cdns3 *cdns)
 
 	dr_mode = best_dr_mode;
 
+	ret = cdns3_idle_init(cdns);
+	if (ret)
+		return ret;
+
 	if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_HOST) {
 		ret = cdns3_host_init(cdns);
 		if (ret) {
@@ -207,7 +223,7 @@ static int cdns3_core_init_role(struct cdns3 *cdns)
 	if (ret)
 		goto err;
 
-	cdns->role = cdns3_get_initial_role(cdns);
+	cdns->role = cdsn3_get_real_role(cdns);
 
 	ret = cdns3_role_start(cdns, cdns->role);
 	if (ret) {
@@ -230,20 +246,55 @@ err:
  */
 enum cdns3_roles cdsn3_get_real_role(struct cdns3 *cdns)
 {
-	enum cdns3_roles role = CDNS3_ROLE_END;
+	enum cdns3_roles role;
+	int id, vbus;
 
-	if (cdns->current_dr_mode == USB_DR_MODE_OTG) {
-		if (cdns3_get_id(cdns))
-			role = CDNS3_ROLE_GADGET;
-		else
-			role = CDNS3_ROLE_HOST;
-	} else {
-		if (cdns3_is_host(cdns))
+	if (cdns->current_dr_mode != USB_DR_MODE_OTG)
+		goto not_otg;
+
+	id = cdns3_get_id(cdns);
+	vbus = cdns3_get_vbus(cdns);
+
+	dev_info(cdns->dev, "id: %d, vbus: %d\n", id, vbus);
+	/* Role change state machine
+	 * Inputs: ID, VBUS
+	 * Previous state: cdns->role
+	 * Next state: role
+	 */
+
+	role = cdns->role;
+	switch (role) {
+	case CDNS3_ROLE_IDLE: /* from IDLE, we can change to HOST or GADGET */
+		if (!id)
 			role = CDNS3_ROLE_HOST;
-		if (cdns3_is_device(cdns))
+		else if (vbus)
 			role = CDNS3_ROLE_GADGET;
+		break;
+
+	case CDNS3_ROLE_HOST: /* from HOST, we can only change to IDLE */
+		if (id)
+			role = CDNS3_ROLE_IDLE;
+		break;
+
+	case CDNS3_ROLE_GADGET: /* from GADGET, we can only change to IDLE */
+		if (!vbus)
+			role = CDNS3_ROLE_IDLE;
+		break;
+	case CDNS3_ROLE_END:	/* only at initialization, move to IDLE */
+		role = CDNS3_ROLE_IDLE;
+		break;
 	}
 
+	dev_info(cdns->dev, "role %d -> %d\n", cdns->role, role);
+
+	return role;
+
+not_otg:
+	if (cdns3_is_host(cdns))
+		role = CDNS3_ROLE_HOST;
+	if (cdns3_is_device(cdns))
+		role = CDNS3_ROLE_GADGET;
+
 	return role;
 }
 

+ 2 - 1
drivers/usb/cdns3/core.h

@@ -15,7 +15,8 @@
 
 struct cdns3;
 enum cdns3_roles {
-	CDNS3_ROLE_HOST = 0,
+	CDNS3_ROLE_IDLE = 0,
+	CDNS3_ROLE_HOST,
 	CDNS3_ROLE_GADGET,
 	CDNS3_ROLE_END,
 };

+ 19 - 1
drivers/usb/cdns3/drd.c

@@ -72,6 +72,15 @@ int cdns3_get_id(struct cdns3 *cdns)
 	return id;
 }
 
+int cdns3_get_vbus(struct cdns3 *cdns)
+{
+	int vbus;
+
+	vbus = !!(readl(&cdns->otg_regs->sts) & OTGSTS_VBUS_VALID);
+	dev_dbg(cdns->dev, "OTG VBUS: %d", vbus);
+	return vbus;
+}
+
 int cdns3_is_host(struct cdns3 *cdns)
 {
 	if (cdns->current_dr_mode == USB_DR_MODE_HOST)
@@ -271,11 +280,20 @@ static irqreturn_t cdns3_drd_irq(int irq, void *data)
 		dev_dbg(cdns->dev, "OTG IRQ: new ID: %d\n",
 			cdns3_get_id(cdns));
 
-		queue_work(system_freezable_wq, &cdns->role_switch_wq);
+		ret = IRQ_HANDLED;
+	}
+
+	if (reg & OTGIEN_VBUSVALID_RISE_INT ||
+	    reg & OTGIEN_VBUSVALID_FALL_INT) {
+		dev_dbg(cdns->dev, "OTG IRQ: new VBUS: %d\n",
+			cdns3_get_vbus(cdns));
 
 		ret = IRQ_HANDLED;
 	}
 
+	if (ret == IRQ_HANDLED)
+		queue_work(system_freezable_wq, &cdns->role_switch_wq);
+
 	writel(~0, &cdns->otg_regs->ivect);
 	return ret;
 }

+ 1 - 0
drivers/usb/cdns3/drd.h

@@ -156,6 +156,7 @@ struct cdns3_otg_common_regs {
 int cdns3_is_host(struct cdns3 *cdns);
 int cdns3_is_device(struct cdns3 *cdns);
 int cdns3_get_id(struct cdns3 *cdns);
+int cdns3_get_vbus(struct cdns3 *cdns);
 int cdns3_drd_init(struct cdns3 *cdns);
 int cdns3_drd_exit(struct cdns3 *cdns);
 int cdns3_drd_update_mode(struct cdns3 *cdns);