Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c447a1b6 authored by Jack Pham's avatar Jack Pham
Browse files

usb: pd: Fix PR_SWAP handling



Fix several issues found with PR Swap robustness.
- Change Rd->Rp as part of PE_PRS_SNK_SRC_TRANSITION_TO_OFF
  instead of PE_PRS_SNK_SRC_SOURCE_ON since the latter state
  is entered once the connection is re-established post-swap.
  Make sure to also notify the charger to stop drawing power.
- Properly handle/ignore CC disconnect in psy_changed() which
  happens during the swap operation. Similarly, don't queue the
  state worker when detecting CC re-connect after becoming sink
  as the transition is already handled via PD message exchange.
- Restore DRP toggling as soon as the operation is completed
  in order to cycle properly after the next disconnection.
- Add tSwapSourceStart delay after becoming new source and
  before sending the source capabilities messages.
- Add extra time (< tNewSrc) after enabling VBUS to allow it
  to fully ramp up after becoming the new source.
- Unconditionally disable Vconn regulator during disconnect,
  since it's now possible to have it on even in sink mode.

Change-Id: I9ee694d56dac11c315b45c8fc1e8be9426d06719
Signed-off-by: default avatarJack Pham <jackp@codeaurora.org>
parent 172cec3a
Loading
Loading
Loading
Loading
+74 −46
Original line number Diff line number Diff line
@@ -168,6 +168,7 @@ static void *usbpd_ipc_log;
#define PS_HARD_RESET_TIME	25
#define PS_SOURCE_ON		400
#define PS_SOURCE_OFF		750
#define SWAP_SOURCE_START_TIME	20
#define VDM_BUSY_TIME		50

/* tPSHardReset + tSafe0V + tSrcRecover + tSrcTurnOn */
@@ -596,6 +597,11 @@ static void usbpd_set_state(struct usbpd *pd, enum usbpd_state next_state)
			/* Defer starting USB host mode until after PD */
		}

		/* Set CC back to DRP toggle for the next disconnect */
		val.intval = POWER_SUPPLY_TYPEC_PR_DUAL;
		power_supply_set_property(pd->usb_psy,
				POWER_SUPPLY_PROP_TYPEC_POWER_ROLE, &val);

		pd->rx_msg_len = 0;
		pd->rx_msg_type = 0;
		pd->rx_msgid = -1;
@@ -625,10 +631,15 @@ static void usbpd_set_state(struct usbpd *pd, enum usbpd_state next_state)
		power_supply_set_property(pd->usb_psy,
				POWER_SUPPLY_PROP_PD_ACTIVE, &val);

		pd->in_pr_swap = false;
		pd->current_state = PE_SRC_SEND_CAPABILITIES;
		usbpd_dbg(&pd->dev, "Enter %s\n",
				usbpd_state_strings[pd->current_state]);
		if (pd->in_pr_swap) {
			pd->in_pr_swap = false;
			hrtimer_start(&pd->timer,
					ms_to_ktime(SWAP_SOURCE_START_TIME),
					HRTIMER_MODE_REL);
			break;
		}

		/* fall-through */

	case PE_SRC_SEND_CAPABILITIES:
@@ -879,6 +890,24 @@ static void usbpd_set_state(struct usbpd *pd, enum usbpd_state next_state)
				HRTIMER_MODE_REL);
		break;

	case PE_PRS_SNK_SRC_TRANSITION_TO_OFF:
		val.intval = pd->requested_current = 0; /* suspend charging */
		power_supply_set_property(pd->usb_psy,
				POWER_SUPPLY_PROP_CURRENT_MAX, &val);

		pd->in_explicit_contract = false;

		/*
		 * need to update PR bit in message header so that
		 * proper GoodCRC is sent when receiving next PS_RDY
		 */
		pd_phy_update_roles(pd->current_dr, PR_SRC);

		/* wait for PS_RDY */
		hrtimer_start(&pd->timer, ms_to_ktime(PS_SOURCE_OFF),
				HRTIMER_MODE_REL);
		break;

	default:
		usbpd_dbg(&pd->dev, "No action for state %s\n",
				usbpd_state_strings[pd->current_state]);
@@ -1269,13 +1298,13 @@ static void usbpd_sm(struct work_struct *w)
		power_supply_set_property(pd->usb_psy,
				POWER_SUPPLY_PROP_PD_ACTIVE, &val);

		if (pd->current_pr == PR_SRC) {
		if (pd->current_pr == PR_SRC)
			regulator_disable(pd->vbus);

		if (pd->vconn_enabled) {
			regulator_disable(pd->vconn);
			pd->vconn_enabled = false;
		}
		}

		if (pd->current_dr == DR_UFP)
			extcon_set_cable_state_(pd->extcon, EXTCON_USB, 0);
@@ -1381,7 +1410,7 @@ static void usbpd_sm(struct work_struct *w)
		pd->hard_reset_count = 0;
		pd->pd_connected = true; /* we know peer is PD capable */

		val.intval = POWER_SUPPLY_TYPE_USB_PD;
		val.intval = pd->psy_type = POWER_SUPPLY_TYPE_USB_PD;
		power_supply_set_property(pd->usb_psy,
				POWER_SUPPLY_PROP_TYPE, &val);

@@ -1429,6 +1458,9 @@ static void usbpd_sm(struct work_struct *w)
			dr_swap(pd);
			kobject_uevent(&pd->dev.kobj, KOBJ_CHANGE);
		} else if (ctrl_recvd == MSG_PR_SWAP) {
			/* lock in current mode */
			set_power_role(pd, pd->current_pr);

			/* we'll happily accept Src->Sink requests anytime */
			ret = pd_send_msg(pd, MSG_ACCEPT, NULL, 0, SOP_MSG);
			if (ret) {
@@ -1438,7 +1470,9 @@ static void usbpd_sm(struct work_struct *w)
			}

			pd->current_state = PE_PRS_SRC_SNK_TRANSITION_TO_OFF;
			queue_work(pd->wq, &pd->sm_work);
			hrtimer_start(&pd->timer,
					ms_to_ktime(SRC_TRANSITION_TIME),
					HRTIMER_MODE_REL);
			break;
		} else {
			if (data_recvd == MSG_VDM)
@@ -1464,7 +1498,7 @@ static void usbpd_sm(struct work_struct *w)
			power_supply_set_property(pd->usb_psy,
					POWER_SUPPLY_PROP_PD_ACTIVE, &val);

			val.intval = POWER_SUPPLY_TYPE_USB_PD;
			val.intval = pd->psy_type = POWER_SUPPLY_TYPE_USB_PD;
			power_supply_set_property(pd->usb_psy,
					POWER_SUPPLY_PROP_TYPE, &val);

@@ -1558,6 +1592,9 @@ static void usbpd_sm(struct work_struct *w)
			dr_swap(pd);
			kobject_uevent(&pd->dev.kobj, KOBJ_CHANGE);
		} else if (ctrl_recvd == MSG_PR_SWAP) {
			/* lock in current mode */
			set_power_role(pd, pd->current_pr);

			/* TODO: should we Reject in certain circumstances? */
			ret = pd_send_msg(pd, MSG_ACCEPT, NULL, 0, SOP_MSG);
			if (ret) {
@@ -1567,19 +1604,7 @@ static void usbpd_sm(struct work_struct *w)
			}

			pd->in_pr_swap = true;
			pd->current_state = PE_PRS_SNK_SRC_TRANSITION_TO_OFF;
			/* turn off sink */
			pd->in_explicit_contract = false;

			/*
			 * need to update PR bit in message header so that
			 * proper GoodCRC is sent when receiving next PS_RDY
			 */
			pd->current_pr = PR_SRC;
			pd_phy_update_roles(pd->current_dr, pd->current_pr);

			hrtimer_start(&pd->timer, ms_to_ktime(PS_SOURCE_OFF),
					HRTIMER_MODE_REL);
			usbpd_set_state(pd, PE_PRS_SNK_SRC_TRANSITION_TO_OFF);
			break;
		} else {
			if (data_recvd == MSG_VDM)
@@ -1676,16 +1701,24 @@ static void usbpd_sm(struct work_struct *w)
		}

		pd->current_state = PE_PRS_SRC_SNK_TRANSITION_TO_OFF;
		/* fall-through */
		hrtimer_start(&pd->timer, ms_to_ktime(SRC_TRANSITION_TIME),
				HRTIMER_MODE_REL);
		break;

	case PE_PRS_SRC_SNK_TRANSITION_TO_OFF:
		pd->in_pr_swap = true;
		pd->in_explicit_contract = false;

		regulator_disable(pd->vbus);
		set_power_role(pd, PR_SINK); /* switch Rp->Rd */

		/* PE_PRS_SRC_SNK_Assert_Rd */
		pd->current_pr = PR_SINK;
		set_power_role(pd, pd->current_pr);
		pd_phy_update_roles(pd->current_dr, pd->current_pr);

		/* allow time for Vbus discharge, must be < tSrcSwapStdby */
		msleep(500);

		ret = pd_send_msg(pd, MSG_PS_RDY, NULL, 0, SOP_MSG);
		if (ret) {
			usbpd_err(&pd->dev, "Error sending PS_RDY\n");
@@ -1712,19 +1745,7 @@ static void usbpd_sm(struct work_struct *w)
		}

		pd->in_pr_swap = true;
		pd->current_state = PE_PRS_SNK_SRC_TRANSITION_TO_OFF;
		/* turn off sink */
		pd->in_explicit_contract = false;

		/*
		 * need to update PR bit in message header so that
		 * proper GoodCRC is sent when receiving next PS_RDY
		 */
		pd->current_pr = PR_SRC;
		pd_phy_update_roles(pd->current_dr, pd->current_pr);

		hrtimer_start(&pd->timer, ms_to_ktime(PS_SOURCE_OFF),
				HRTIMER_MODE_REL);
		usbpd_set_state(pd, PE_PRS_SNK_SRC_TRANSITION_TO_OFF);
		break;

	case PE_PRS_SNK_SRC_TRANSITION_TO_OFF:
@@ -1733,14 +1754,20 @@ static void usbpd_sm(struct work_struct *w)
			break;
		}

		/* PE_PRS_SNK_SRC_Assert_Rp */
		pd->current_pr = PR_SRC;
		set_power_role(pd, pd->current_pr);
		pd->current_state = PE_PRS_SNK_SRC_SOURCE_ON;

		/* fall-through */

	case PE_PRS_SNK_SRC_SOURCE_ON:
		set_power_role(pd, PR_SRC);
		ret = regulator_enable(pd->vbus);
		if (ret)
			usbpd_err(&pd->dev, "Unable to enable vbus\n");

		msleep(200); /* allow time VBUS ramp-up, must be < tNewSrc */

		ret = pd_send_msg(pd, MSG_PS_RDY, NULL, 0, SOP_MSG);
		if (ret) {
			usbpd_err(&pd->dev, "Error sending PS_RDY\n");
@@ -1838,10 +1865,10 @@ static int psy_changed(struct notifier_block *nb, unsigned long evt, void *ptr)
	 * until the HW bug is fixed: in which disconnection won't be reported
	 * on VBUS loss alone unless pullup is also removed from CC.
	 */
	if ((pd->current_state == PE_SNK_TRANSITION_TO_DEFAULT ||
				pd->in_pr_swap) &&
			typec_mode == POWER_SUPPLY_TYPEC_NONE &&
			pd->psy_type != POWER_SUPPLY_TYPE_USB) {
	if (typec_mode == POWER_SUPPLY_TYPEC_NONE &&
		(pd->in_pr_swap ||
		 (pd->psy_type != POWER_SUPPLY_TYPE_USB &&
		  pd->current_state == PE_SNK_TRANSITION_TO_DEFAULT))) {
		usbpd_dbg(&pd->dev, "Ignoring disconnect due to %s\n",
				pd->in_pr_swap ? "PR swap" : "hard reset");
		return 0;
@@ -1856,8 +1883,9 @@ static int psy_changed(struct notifier_block *nb, unsigned long evt, void *ptr)

	psy_type = val.intval;

	usbpd_dbg(&pd->dev, "typec mode:%d present:%d type:%d\n", typec_mode,
			pd->vbus_present, psy_type);
	usbpd_dbg(&pd->dev, "typec mode:%d present:%d type:%d orientation:%d\n",
			typec_mode, pd->vbus_present, psy_type,
			usbpd_get_plug_orientation(pd));

	/* any change? */
	if (pd->typec_mode == typec_mode && pd->psy_type == psy_type)