google_cpm: add support for WLCDC

Preliminary support for WLC DC:
= delegate the selection of the input to the PPS_Work workloop
= add the gcpm_pps power supply

Bug: 177471684
Signed-off-by: AleX Pelosi <[email protected]>
Change-Id: Ibd44d3d4e7d5077dcc4a2f1fa09085e1ae6772e6
diff --git a/google_charger.c b/google_charger.c
index c5a4646..a0f07e8 100644
--- a/google_charger.c
+++ b/google_charger.c
@@ -2303,7 +2303,7 @@
 	} else {
 		const char *name = tcpm_psy->desc->name;
 
-		ret = pps_init(&chg_drv->pps_data, chg_drv->device);
+		ret = pps_init(&chg_drv->pps_data, chg_drv->device, tcpm_psy);
 		if (ret == 0 && chg_drv->debug_entry)
 			pps_init_fs(&chg_drv->pps_data, chg_drv->debug_entry);
 		if (ret < 0)
@@ -2472,8 +2472,6 @@
 		}
 	}
 
-
-
 	INIT_DELAYED_WORK(&chg_drv->init_work, google_charger_init_work);
 	INIT_DELAYED_WORK(&chg_drv->chg_work, chg_work);
 	INIT_WORK(&chg_drv->chg_psy_work, chg_psy_work);
diff --git a/google_cpm.c b/google_cpm.c
index a2d6428..8696a32 100644
--- a/google_cpm.c
+++ b/google_cpm.c
@@ -50,7 +50,8 @@
 #define DC_VBATT_HEADROOM_MV	500000
 
 enum gcpm_dc_state_t {
-	DC_DISABLED = 0,
+	DC_DISABLED = -1,
+	DC_IDLE = 0,
 	DC_ENABLE,
 	DC_RUNNING,
 	DC_ENABLE_PASSTHROUGH,
@@ -62,25 +63,35 @@
 	struct power_supply *psy;
 	struct delayed_work init_work;
 
+	/* combine PPS, route to the active PPS source */
+	struct power_supply *pps_psy;
+
 	int chg_psy_retries;
 	struct power_supply *chg_psy_avail[GCPM_MAX_CHARGERS];
 	const char *chg_psy_names[GCPM_MAX_CHARGERS];
 	struct mutex chg_psy_lock;
 	int chg_psy_active;
 	int chg_psy_count;
+
 	/* force a charger, this might have side effects */
 	int force_active;
 
-	/* TCPM state for PPS charging */
-	struct power_supply *tcpm_psy;
+	/* TCPM state for wired PPS charging */
 	const char *tcpm_psy_name;
+	struct power_supply *tcpm_psy;
+	struct pd_pps_data tcpm_pps_data;
 	int log_psy_ratelimit;
 	u32 tcpm_phandle;
 
+	/* TCPM state for wireless PPS charging */
+	const char *wlc_dc_name;
+	struct power_supply *wlc_dc_psy;
+	struct pd_pps_data wlc_pps_data;
+	u32 wlc_phandle;
+
 	/* set to force PPS negoatiation */
 	bool force_pps;
 	/* pps state and detect */
-	struct pd_pps_data pps_data;
 	struct delayed_work pps_work;
 	/* request of output ua, */
 	int out_ua;
@@ -89,12 +100,14 @@
 	int dcen_gpio;
 	u32 dcen_gpio_default;
 
-	/* >0 when enabled */
+	/* >0 when enabled, pps charger to use */
+	int pps_index;
+	/* >0 when enabled, dc_charger */
 	int dc_index;
 	/* dc_charging state */
 	int dc_state;
 
-	/* force check of the DC limit again */
+	/* force check of the DC limit again (debug) */
 	bool new_dc_limit;
 	/* force disable */
 	bool taper_control;
@@ -161,7 +174,8 @@
 	if (ret == 0)
 		gcpm->chg_psy_active = -1;
 
-	pr_debug("active=%d offline=%d\n", gcpm->chg_psy_active, ret == 0);
+	pr_debug("%s: active=%d offline=%d\n", __func__,
+		 gcpm->chg_psy_active, ret == 0);
 	return ret;
 }
 
@@ -197,7 +211,8 @@
 	gcpm->chg_psy_active = index;
 
 error_exit:
-	pr_info("active charger %d->%d (%d)\n", index_old, index, ret);
+	pr_info("%s: active charger %d->%d (%d)\n",
+		__func__, index_old, index, ret);
 	return ret;
 }
 
@@ -215,47 +230,6 @@
 	return chg_state.v;
 }
 
-/*
- * Select the DC charger using the thermal policy.
- * NOTE: program target before enabling chaging.
- */
-static int gcpm_chg_dc_select(const struct gcpm_drv *gcpm)
-{
-	struct power_supply *chg_psy = gcpm_chg_get_active(gcpm);
-	int batt_demand, index = 0; /* 0 is the default */
-
-	/* keep on default */
-	if (gcpm->taper_control || !chg_psy)
-		return 0;
-	if (gcpm->cc_max <= 0 || gcpm->fv_uv <= 0)
-		return 0;
-
-	/* */
-	batt_demand = (gcpm->cc_max / 1000) * (gcpm->fv_uv / 1000);
-	if (batt_demand > gcpm->dc_limit_demand)
-		index = 1;
-	if (index == 0) {
-		int vbatt;
-
-		vbatt = GPSY_GET_PROP(chg_psy, POWER_SUPPLY_PROP_VOLTAGE_NOW);
-		if (vbatt > 0 && gcpm->dc_limit_vbatt)
-			index = 1;
-	}
-
-	pr_info("%s: index=%d count=%d demand=%d dc_limit_demand=%d\n",
-		__func__, index, gcpm->chg_psy_count,
-		batt_demand, gcpm->dc_limit_demand);
-
-	if (index >= gcpm->chg_psy_count)
-		index = 0;
-
-	/* could select different modes here depending on capabilities */
-
-	/* add margin .... debounce etc... */
-
-	return index;
-}
-
 /* Enable DirectCharge mode, PPS and DC charger must be already initialized */
 static int gcpm_dc_enable(struct gcpm_drv *gcpm, bool enabled)
 {
@@ -273,8 +247,11 @@
 				  enabled);
 }
 
-/* disable DC, and switch back to the default charger */
-/* NOTE: call with a lock around gcpm->chg_psy_lock */
+/*
+ * disable DC and switch back to the default charger. Final DC statate is
+ * DC_IDLE (i.e. this can be used to reset dc_state from DC_DISABLED).
+ * NOTE: call with a lock around gcpm->chg_psy_lock
+ */
 static int gcpm_dc_stop(struct gcpm_drv *gcpm)
 {
 	int ret;
@@ -303,7 +280,7 @@
 		}
 		/* Fall Through */
 	default:
-		gcpm->dc_state  = DC_DISABLED;
+		gcpm->dc_state = DC_IDLE;
 		ret = 0;
 		break;
 	}
@@ -359,6 +336,7 @@
 	if (gcpm->dcen_gpio >= 0 && !gcpm->dcen_gpio_default)
 		gpio_set_value(gcpm->dcen_gpio, 1);
 
+	/* vote on MODE */
 	ret = gcpm_dc_enable(gcpm, true);
 	if (ret < 0) {
 		pr_info("PPS_DC: dc_ready failed=%d\n", ret);
@@ -371,47 +349,78 @@
 	return 0;
 }
 
-/* NOTE: call with a lock around gcpm->chg_psy_lock */
-static int gcpm_dc_charging(struct gcpm_drv *gcpm)
-{
-	struct power_supply *dc_psy;
-	int vchg, ichg, status;
-
-	dc_psy = gcpm_chg_get_active(gcpm);
-	if (!dc_psy) {
-		pr_err("DC_CHG: invalid charger\n");
-		return -ENODEV;
-	}
-
-	vchg = GPSY_GET_PROP(dc_psy, POWER_SUPPLY_PROP_VOLTAGE_NOW);
-	ichg = GPSY_GET_PROP(dc_psy, POWER_SUPPLY_PROP_CURRENT_NOW);
-	status = GPSY_GET_PROP(dc_psy, POWER_SUPPLY_PROP_STATUS);
-
-	pr_err("DC_CHG: vchg=%d, ichg=%d status=%d\n",
-	       vchg, ichg, status);
-
-	return 0;
-}
-
 /* Will need to handle capabilities based on index number */
 #define GCPM_INDEX_DC_ENABLE	1
 
-
-/* TODO: do not enable PPS when ->taper_control is set? */
-/* TODO: revert to fixed profile if cc_max is 0, remember failures */
-/* TODO: use a structure to describe charger types "needs_pps", "is_dc" etc */
-static int gcpm_chg_pps_check_enable(const struct gcpm_drv *gcpm, int index)
+/*
+ * Select the DC charger using the thermal policy.
+ * NOTE: program target before enabling chaging.
+ */
+static int gcpm_chg_dc_select(const struct gcpm_drv *gcpm)
 {
-	return gcpm->force_pps || index == GCPM_INDEX_DC_ENABLE;
+	struct power_supply *chg_psy = gcpm_chg_get_active(gcpm);
+	int batt_demand, index = 0; /* 0 is the default */
+
+	/* keep on default */
+	if (gcpm->taper_control || !chg_psy)
+		return 0;
+	if (gcpm->cc_max <= 0 || gcpm->fv_uv <= 0)
+		return 0;
+
+	/* battery demand comes from charging tier */
+	batt_demand = (gcpm->cc_max / 1000) * (gcpm->fv_uv / 1000);
+	if (batt_demand > gcpm->dc_limit_demand)
+		index = 1;
+	if (index == 0) {
+		int vbatt;
+
+		vbatt = GPSY_GET_PROP(chg_psy, POWER_SUPPLY_PROP_VOLTAGE_NOW);
+		if (vbatt > 0 && gcpm->dc_limit_vbatt)
+			index = 1;
+	}
+
+	pr_info("%s: index=%d count=%d demand=%d dc_limit_demand=%d\n",
+		__func__, index, gcpm->chg_psy_count,
+		batt_demand, gcpm->dc_limit_demand);
+
+	if (index >= gcpm->chg_psy_count)
+		index = 0;
+
+	/* could select different modes here depending on capabilities */
+
+	/* add margin .... debounce etc... */
+
+	return index;
 }
 
-/* TODO: handle PPS-DC and WLC-DC, return dc_stage*/
 /* NOTE: DC requires PPS, disable DC in taper control */
-static int gcpm_chg_dc_check_enable(const struct gcpm_drv *gcpm, int index)
+static bool gcpm_chg_dc_check_enable(const struct gcpm_drv *gcpm, int index)
 {
+	if (gcpm->taper_control)
+		return false;
+
+	/* Will run detection only the first time */
+	if (gcpm->tcpm_pps_data.stage == PPS_NOTSUPP &&
+	    gcpm->wlc_pps_data.stage == PPS_NOTSUPP )
+		return false;
+
 	return index == GCPM_INDEX_DC_ENABLE;
 }
 
+static void gcpm_pps_online(struct gcpm_drv *gcpm)
+{
+	/* reset setpoint */
+	gcpm->out_ua = -1;
+	gcpm->out_uv = -1;
+
+	/* reset detection */
+	if (gcpm->tcpm_pps_data.pps_psy)
+		pps_init_state(&gcpm->tcpm_pps_data);
+	if (gcpm->wlc_pps_data.pps_psy)
+		pps_init_state(&gcpm->wlc_pps_data);
+	gcpm->pps_index = 0;
+}
+
 /*
  * need to run through the whole function even when index == gcpm->force_active
  * because I have multiple steps and multiple failure points.
@@ -419,75 +428,35 @@
  */
 static int gcpm_chg_check(struct gcpm_drv *gcpm)
 {
-	struct pd_pps_data *pps_data = &gcpm->pps_data;
 	bool schedule_pps_dc = false;
-	bool dc_ena = 0, pps_ena;
 	int ret = 0, index;
+	bool dc_ena;
 
-	/* might have more than one policy */
 	index = gcpm_chg_dc_select(gcpm);
 	if (gcpm->force_active >= 0)
 		index = gcpm->force_active;
 
-	pr_debug("CHG_CHK: index:%d->%d\n", gcpm->chg_psy_active, index);
-
-	/* first check if PPS needs to be enabled (or disable it) */
-	pps_ena = gcpm->tcpm_psy && gcpm_chg_pps_check_enable(gcpm, index);
-	pr_debug("CHG_CHK: PPS pps_stage=%d pps_ena=%d\n",
-		 pps_data->stage, pps_ena);
-
-	if (pps_ena && pps_data->stage != PPS_NOTSUPP) {
-
-		/* Could reset gcpm->out_uv and gcpm->out_ua to -1 */
-		if (pps_data->stage == PPS_DISABLED) {
-			pps_data->stage = PPS_NONE;
-			schedule_pps_dc = true;
-		}
-
-	} else if (pps_data->stage == PPS_NOTSUPP) {
-		/* keep PPS_NOTSUPP, continue to make sure DC is disabled */
-	} else if (pps_data->stage != PPS_DISABLED) {
-
-		/* force state, PPS_DC will take cate of it */
-
-		ret = pps_prog_offline(&gcpm->pps_data, gcpm->tcpm_psy);
-		pr_debug("CHG_CHK: PPS offline ret=%d\n", ret);
-		if (ret < 0) {
-			pr_debug("CHG_CHK: PPS pps_ena=%d dc_ena=%d->%d ret=%d\n",
-				 pps_ena, gcpm->dc_index, dc_ena, ret);
-
-			/* force state, PPS_DC will take care of it */
-			pps_data->stage = PPS_DISABLED;
-
-			/*
-			 * transitions between MAIN<->PPS<->PPS+DC are handled
-			 * in gcpm_pps_dc_work() and the state of DC for this
-			 * index must be handled regardless of the success of
-			 * the immediate disable of pps.
-			 */
-		}
-
-		/* needs to continue of DC was enabled */
-		schedule_pps_dc = true;
-	}
-
 	/*
 	 * NOTE: disabling DC might need to transition to charger mode 0
-	 * same might apply when switching between WLC-DC and PPS-DC.
+	 *       same might apply when switching between WLC-DC and PPS-DC.
+	 *       Figure out a way to do this if needed.
 	 */
 	dc_ena = gcpm_chg_dc_check_enable(gcpm, index);
-	pr_debug("CHG_CHK: DC pps_ena=%d dc_ena=%d->%d \n",
-			pps_ena, gcpm->dc_index, dc_ena);
+	pr_debug("CHG_CHK: DC dc_ena=%d dc_state=%d dc_index=%d->%d\n",
+		 dc_ena, gcpm->dc_state, gcpm->dc_index, index);
 	if (!dc_ena) {
 
 		if (gcpm->dc_index) {
-			gcpm->dc_index = 0;
 			schedule_pps_dc = true;
+			gcpm->dc_state = DC_IDLE;
+			gcpm->dc_index = 0;
 		}
 	} else if (gcpm->dc_state == DC_DISABLED) {
-		gcpm->out_ua = -1;
-		gcpm->out_uv = -1;
+		pr_debug("CHG_CHK: DC disabled for the session\n");
+	} else if (gcpm->dc_state == DC_IDLE) {
+		gcpm_pps_online(gcpm);
 
+		pr_debug("CHG_CHK: start DC Charging\n");
 		/* TODO: DC_ENABLE or DC_PASSTHROUGH depending on index */
 		gcpm->dc_state = DC_ENABLE_PASSTHROUGH;
 		gcpm->dc_index = index;
@@ -500,212 +469,194 @@
 	return ret;
 }
 
-#define PPS_ERROR_RETRY_MS 1000
-
 /* DC_ERROR_RETRY_MS <= DC_RUN_DELAY_MS */
 #define DC_RUN_DELAY_MS		5000
 #define DC_ERROR_RETRY_MS	PPS_ERROR_RETRY_MS
 
+#define PPS_WAIT_RETRY_MS	3000
+#define PPS_ERROR_RETRY_MS	1000
+
+enum {
+	PPS_INDEX_NOT_SUPP = -1,
+	PPS_INDEX_TCPM = 1,
+	PPS_INDEX_WLC = 2,
+	PPS_INDEX_MAX = 2,
+};
+
+static struct pd_pps_data *gcpm_pps_data(struct gcpm_drv *gcpm)
+{
+	struct pd_pps_data *pps_data = NULL;
+
+	if (gcpm->pps_index == PPS_INDEX_TCPM)
+		pps_data = &gcpm->tcpm_pps_data;
+	else if (gcpm->pps_index == PPS_INDEX_WLC)
+		pps_data = &gcpm->wlc_pps_data;
+
+	return pps_data;
+}
+
+/*
+ * DC depends on PPS so run PPS first.
+ * - read source capabilities when stage transition from PPS_NONE to
+ *   PPS_AVAILABLE (NOTE: pd_online=TCPM_PSY_PROG_ONLINE in this case)
+ *
+ * DISABLED => NONE -> AVAILABLE -> ACTIVE -> DISABLED
+ *	    -> DISABLED
+ * 	    -> NOTSUPP
+ *
+ * return NULL until a PPS source becomes valid.
+ * TODO: set gcpm->pps_data to the gpm
+ */
+static int gcpm_pps_work(struct gcpm_drv *gcpm)
+{
+	int ret = 0, pps_index = 0;
+	int not_supported = 0;
+
+	if (gcpm->tcpm_pps_data.stage != PPS_NOTSUPP) {
+		struct pd_pps_data *pps_data = &gcpm->tcpm_pps_data;
+		int pps_ui;
+
+		pps_ui = pps_work(pps_data, pps_data->pps_psy);
+		if (pps_ui >= 0 && pps_data->stage == PPS_ACTIVE)
+			pps_index = PPS_INDEX_TCPM;
+		if (pps_data->pd_online < PPS_PSY_PROG_ONLINE)
+			pr_debug("PPS_Work: TCPM Wait pps_ui=%d online=%d, stage=%d\n",
+				pps_ui, pps_data->pd_online, pps_data->stage);
+	} else {
+		not_supported += 1;
+	}
+
+	if (gcpm->wlc_pps_data.stage != PPS_NOTSUPP) {
+		struct pd_pps_data *pps_data = &gcpm->wlc_pps_data;
+		int pps_ui;
+
+		pps_ui = pps_work(pps_data, pps_data->pps_psy);
+		if (pps_ui >= 0 && pps_data->stage == PPS_ACTIVE)
+			pps_index = PPS_INDEX_WLC;
+
+		if (pps_data->pd_online < PPS_PSY_PROG_ONLINE)
+			pr_debug("PPS_Work: WLC Wait pps_ui=%d online=%d, stage=%d\n",
+				pps_ui, pps_data->pd_online, pps_data->stage);
+	} else {
+		not_supported += 1;
+	}
+
+	/* 2 sources */
+	if (not_supported == PPS_INDEX_MAX)
+		return -ENODEV;
+
+	/* index==0 meand detecting */
+	if (gcpm->pps_index != pps_index)
+		pr_debug("PPS_Work: pps_index %d->%d\n",
+			gcpm->pps_index, pps_index);
+	if (gcpm->pps_index && !pps_index)
+		ret = -ENODEV;
+
+	gcpm->pps_index = pps_index;
+	return ret;
+}
+
+static int gcpm_pps_offline(struct gcpm_drv *gcpm)
+{
+	int ret;
+
+	if (gcpm->tcpm_pps_data.pps_psy) {
+		ret = pps_prog_offline(&gcpm->tcpm_pps_data,
+				       gcpm->tcpm_pps_data.pps_psy);
+		if (ret < 0)
+			pr_err("PPS_DC: fail tcpm offline (%d)\n", ret);
+	}
+
+	if (gcpm->wlc_pps_data.pps_psy) {
+		ret = pps_prog_offline(&gcpm->wlc_pps_data,
+				       gcpm->wlc_pps_data.pps_psy);
+		if (ret < 0)
+			pr_err("PPS_DC: fail wlc offline (%d)\n", ret);
+	}
+
+	gcpm->pps_index = 0;
+	return 0;
+}
+
 /*
  * pps_data->stage:
  *  PPS_NONE -> PPS_AVAILABLE -> PPS_ACTIVE
  * 	     -> PPS_DISABLED  -> PPS_DISABLED
  */
-static void gcpm_pps_dc_work(struct work_struct *work)
+static void gcpm_pps_wlc_dc_work(struct work_struct *work)
 {
 	struct gcpm_drv *gcpm =
 		container_of(work, struct gcpm_drv, pps_work.work);
-	struct pd_pps_data *pps_data = &gcpm->pps_data;
-	struct power_supply *tcpm_psy = gcpm->tcpm_psy;
-	const bool log_on_done = tcpm_psy != 0 && pps_data->pd_online &&
-			         (gcpm->pps_data.stage || gcpm->dc_state);
-	const int pre_out_ua = pps_data->op_ua;
-	const int pre_out_uv = pps_data->out_uv;
+	struct pd_pps_data *pps_data;
 	int ret, pps_ui = -ENODEV;
 
+	/* spurious during init */
 	mutex_lock(&gcpm->chg_psy_lock);
-
-	pr_info("PPS_Work: tcpm_psy_ok=%d pd_online=%d pps_stage=%d dc_state=%d",
-		 tcpm_psy != 0, pps_data->pd_online,  gcpm->pps_data.stage,
-		 gcpm->dc_state);
-
-	/* should not be calling this with !tcpm_psy */
-	if (!gcpm->tcpm_psy)
-		goto pps_dc_done;
-
-	/* disable DC if not enabled */
-	if (gcpm->dc_index <= 0 && gcpm->dc_state != DC_DISABLED) {
-		const int dc_state = gcpm->dc_state;
-
-		ret = gcpm_dc_stop(gcpm);
-		if (ret < 0 || gcpm->dc_state != DC_DISABLED) {
-			pr_info("PPS_DC: fail disable dc_state=%d (%d)\n",
-				gcpm->dc_state, ret);
-
-			pps_ui = DC_ERROR_RETRY_MS;
-			goto pps_dc_reschedule;
-		}
-
-		pr_info("PPS_DC: disable DC dc_state=%d->%d\n",
-			dc_state, gcpm->dc_state);
+	if (!gcpm->resume_complete || !gcpm->init_complete) {
+		mutex_unlock(&gcpm->chg_psy_lock);
+		return;
 	}
 
-	if (pps_is_disabled(pps_data->stage))
-		goto pps_dc_done;
+	/* disconnect, gcpm_chg_check() and most errors reset ->dc_index */
+	if (gcpm->dc_index <= 0) {
+		const int dc_state = gcpm->dc_state;
+		const int tgt_state = gcpm->dc_index < 0 ?
+				      DC_DISABLED : DC_IDLE;
 
-	/* DC depends on PPS so run PPS first.
-	 * - read source capabilities when stage transition from PPS_NONE to
-	 *   PPS_AVAILABLE (NOTE: pd_online=TCPM_PSY_PROG_ONLINE in this case)
-	 * DISABLED  ->DISABLED
-	 * 	     ->NONE
-	 *    NONE   ->AVAILABLE
-	 * 	     ->DISABLED
-	 * 	     ->NOTSUPP
-	 * AVAILABLE ->ACTIVE
-	 *	     ->DISABLED
-	 * NOTSUPP   ->NOTSUPP
-	 */
-	pps_ui = pps_work(pps_data, tcpm_psy);
-	pr_info("PPS_Work: pps_ui=%d pd_online=%d pps_stage=%d dc_state=%d\n",
-		pps_ui, pps_data->pd_online,
-		gcpm->pps_data.stage, gcpm->dc_state);
-	if (pps_ui < 0) {
-		pps_ui = PPS_ERROR_RETRY_MS;
-	} else if (!pps_data->pd_online) {
-
-		/* disable DC, revert to default charger */
-		if (gcpm->dc_state != DC_DISABLED) {
+		/* try to disable DC, gcpm_chg_check() will re-enable if idle */
+		if (dc_state != tgt_state)
 			ret = gcpm_dc_stop(gcpm);
-			if (ret < 0 || gcpm->dc_state != DC_DISABLED) {
-				pr_info("PPS_DC: fail disable dc_state=%d (%d)\n",
-					gcpm->dc_state, ret);
-
-				/* will keep trying to disable */
-				pps_ui = DC_ERROR_RETRY_MS;
+		if (gcpm->dc_state == DC_IDLE && tgt_state == DC_DISABLED)
+			gcpm->dc_state = DC_DISABLED;
+		if (gcpm->dc_state != tgt_state) {
+			pr_err("PPS_DC: fail disable dc_state=%d->%d (%d)\n",
+				dc_state, gcpm->dc_state, ret);
+			pps_ui = DC_ERROR_RETRY_MS;
+		} else {
+			/* and then disable PPS as well */
+			ret = gcpm_pps_offline(gcpm);
+			if (ret < 0) {
+				pr_err("PPS_DC: fail offline (%d)\n", ret);
+				pps_ui = PPS_ERROR_RETRY_MS;
 			}
 		}
 
-		/* reset state on offline (should be already done) */
-		pr_info("PPS_Work: PPS Offline\n");
-		pps_init_state(pps_data);
-	} else if (pps_data->stage == PPS_DISABLED) {
-		/* PPS was disabled (not available) for this TA */
-		pr_info("PPS_Work: PPS Disabled dc_ena=%d, dc_state=%d\n",
+		/* default pps_ui == -ENODEV */
+		goto pps_dc_reschedule;
+	}
+
+	/* check for one of the sources to come online, <0 when not supported */
+	ret = gcpm_pps_work(gcpm);
+	if (ret < 0) {
+		pr_info("PPS_Work: PPS Offline dc_index:%d->0 dc_state=%d\n",
 			gcpm->dc_index, gcpm->dc_state);
 
-		/* reschedule to clear dc_ena for this session */
-		if (gcpm->dc_index > 0) {
-			pps_ui = PPS_ERROR_RETRY_MS;
-			gcpm->dc_index = 0;
-		}
-	} else if (pps_data->stage != PPS_ACTIVE) {
+		pps_ui = PPS_ERROR_RETRY_MS;
+		gcpm->dc_index = 0;
+		goto pps_dc_reschedule;
+	}
+
+	/* will have PPS data when one of the sources becomes onliune */
+	pps_data = gcpm_pps_data(gcpm);
+	if (!pps_data) {
 		/*
-		 * DC run only when PPS is active, the only sane state for DC
-		 * when PPS is not ACTIVE is DC_DISABLE:
-		 * DC state is forced to DC_DISABLE when ->dc_index <= 0 at
-		 * the beginning of the loop
+		 * Wait for PPS to be enabled: DC run only when PPS is active.
+		 * NOTE: DC state is forced to DC_DISABLE and PPS is set to
+		 * offline at the beginning of the loop when ->dc_index <= 0
 		 */
 
-		pr_info("PPS_Work: PPS Wait stage=%d, pps_ui=%d dc_ena=%d dc_state=%d \n",
-			pps_data->stage, pps_ui, gcpm->dc_index, gcpm->dc_state);
+		pr_debug("PPS_Work: PPS Wait for source dc_index=%d dc_state=%d\n",
+			gcpm->dc_index, gcpm->dc_state);
 
-		/* TODO: keep track of time waiting for ACTIVE */
-	} else if (gcpm->dc_state == DC_ENABLE) {
-		struct pd_pps_data *pps_data = &gcpm->pps_data;
-		bool pwr_ok;
-
-		/* must run at the end of PPS negotiation */
-		if (gcpm->out_ua == -1)
-			gcpm->out_ua = min(gcpm->cc_max, pps_data->max_ua);
-		if (gcpm->out_uv == -1) {
-			struct power_supply *chg_psy =
-						gcpm_chg_get_active(gcpm);
-			unsigned long ta_max_v, value;
-			int vbatt = -1;
-
-			ta_max_v = pps_data->max_ua * pps_data->max_uv;
-			ta_max_v /= gcpm->out_ua;
-			if (ta_max_v > DC_TA_VMAX_MV)
-				ta_max_v = DC_TA_VMAX_MV;
-
-			if (chg_psy)
-				vbatt = GPSY_GET_PROP(chg_psy,
-						POWER_SUPPLY_PROP_VOLTAGE_NOW);
-			if (vbatt < 0)
-				vbatt = gcpm->fv_uv;
-			if (vbatt < 0)
-				vbatt = 0;
-
-			/* good for pca9468 */
-			value = 2 * vbatt + DC_VBATT_HEADROOM_MV;
-			if (value < DC_TA_VMIN_MV)
-				value = DC_TA_VMIN_MV;
-
-			/* PPS voltage in 20mV steps */
-			gcpm->out_uv = value - value % 20000;
-		}
-
-		pr_info("CHG_CHK: max_uv=%d,max_ua=%d  out_uv=%d,out_ua=%d\n",
-			pps_data->max_uv, pps_data->max_ua,
-			gcpm->out_uv, gcpm->out_ua);
-
-		pps_ui = pps_update_adapter(pps_data, gcpm->out_uv,
-					    gcpm->out_ua, tcpm_psy);
-		if (pps_ui < 0)
-			pps_ui = PPS_ERROR_RETRY_MS;
-
-		/* wait until adapter is at or over request */
-		pwr_ok = pps_data->out_uv == gcpm->out_uv &&
-			 pps_data->op_ua == gcpm->out_ua;
-		if (pwr_ok) {
-			ret = gcpm_chg_offline(gcpm);
-			if (ret == 0)
-				ret = gcpm_dc_start(gcpm, gcpm->dc_index);
-			if (ret == 0) {
-				gcpm->dc_state = DC_RUNNING;
-				pps_ui = DC_RUN_DELAY_MS;
-			}  else if (pps_ui > DC_ERROR_RETRY_MS) {
-				pps_ui = DC_ERROR_RETRY_MS;
-			}
-		}
-
-		/*
-		 * TODO: add retries and switch to DC_ENABLE again or to
-		 * DC_DISABLED on timeout.
-		 */
-
-		pr_info("PPS_DC: dc_state=%d out_uv=%d %d->%d, out_ua=%d %d->%d\n",
-			gcpm->dc_state,
-			pps_data->out_uv, pre_out_uv, gcpm->out_uv,
-			pps_data->op_ua, pre_out_ua, gcpm->out_ua);
-	} else if (gcpm->dc_state == DC_RUNNING)  {
-
-		ret = gcpm_chg_ping(gcpm, 0, 0);
-		if (ret < 0)
-			pr_err("PPS_DC: ping failed with %d\n", ret);
-
-		/* update gcpm->out_uv, gcpm->out_ua */
-		pr_info("PPS_DC: dc_state=%d out_uv=%d %d->%d out_ua=%d %d->%d\n",
-			gcpm->dc_state,
-			pps_data->out_uv, pre_out_uv, gcpm->out_uv,
-			pps_data->op_ua, pre_out_ua, gcpm->out_ua);
-
-		ret = gcpm_dc_charging(gcpm);
-		if (ret < 0)
-			pps_ui = DC_ERROR_RETRY_MS;
-
-		ret = pps_update_adapter(&gcpm->pps_data,
-						gcpm->out_uv, gcpm->out_ua,
-						tcpm_psy);
-		if (ret < 0)
-			pps_ui = PPS_ERROR_RETRY_MS;
+		/* TODO: keep track of time waiting for ACTIVE and give up? */
+		pps_ui = PPS_WAIT_RETRY_MS;
 	} else if (gcpm->dc_state == DC_ENABLE_PASSTHROUGH) {
-		struct pd_pps_data *pps_data = &gcpm->pps_data;
+		struct power_supply *pps_psy = pps_data->pps_psy;
 
-		pps_ui = pps_update_adapter(pps_data, gcpm->out_uv,
-					    gcpm->out_ua, tcpm_psy);
+		/* steady on PPS, DC is not enabled */
+		pps_ui = pps_update_adapter(pps_data, -1, -1, pps_psy);
 		if (pps_ui < 0)
-			pps_ui = PPS_ERROR_RETRY_MS;
+			pps_ui = DC_ERROR_RETRY_MS;
 
 		/* TODO: handoff handling of PPS to the DC charger */
 		ret = gcpm_chg_offline(gcpm);
@@ -718,37 +669,48 @@
 			pps_ui = DC_ERROR_RETRY_MS;
 		}
 	} else if (gcpm->dc_state == DC_PASSTHROUGH) {
+		const int pps_index = gcpm->pps_index;
 		struct power_supply *dc_psy;
 
 		dc_psy = gcpm_chg_get_active(gcpm);
 		if (!dc_psy) {
+			/* TODO: somethign went wrong, exit from it */
 			pr_err("PPS_Work: no adapter while in DC_PASSTHROUGH\n");
 			pps_ui = DC_ERROR_RETRY_MS;
 		} else {
-			ret = GPSY_SET_PROP(dc_psy,
-					    GBMS_PROP_CHARGING_ENABLED,
-					    1);
+			/* Keep enabling charging and pinging the watchdog */
+			ret = GPSY_SET_PROP(dc_psy, GBMS_PROP_CHARGING_ENABLED,
+					    pps_index);
 			if (ret == 0) {
 				ret = gcpm_chg_ping(gcpm, 0, 0);
 				if (ret < 0)
 					pr_err("PPS_DC: ping failed with %d\n",
 					       ret);
 
+				/* keep running to ping the adapters */
+				pps_ui = DC_RUN_DELAY_MS;
 			} else if (ret == -EBUSY) {
 				pps_ui = DC_ERROR_RETRY_MS;
 			} else {
-				pr_err("PPS_Work: cannot enable DC_charging\n");
+				pr_err("PPS_Work: cannot enable DC_charging (%d)\n",
+				       ret);
 
 				ret = gcpm_chg_set_online(gcpm, 0);
-				if (ret < 0)
-					pr_err("PPS_Work: online default %d\n",
-					       ret);
+				if (ret < 0) {
+					pr_err("PPS_Work: online default %d\n", ret);
+					pps_ui = DC_ERROR_RETRY_MS;
+				} else {
+					pr_err("PPS_Work: dc offline\n");
+					pps_ui = 0;
+				}
 			}
 		}
 
-	} else {
+	} else if (pps_data) {
+		struct power_supply *pps_psy = pps_data->pps_psy;
+
 		/* steady on PPS, DC is not enabled */
-		pps_ui = pps_update_adapter(&gcpm->pps_data, -1, -1, tcpm_psy);
+		pps_ui = pps_update_adapter(pps_data, -1, -1, pps_psy);
 
 		pr_info("PPS_Work: STEADY pd_online=%d pps_ui=%d dc_ena=%d dc_state=%d\n",
 			pps_data->pd_online, pps_ui, gcpm->dc_index,
@@ -757,24 +719,22 @@
 			pps_ui = PPS_ERROR_RETRY_MS;
 	}
 
+	if (pps_data)
+		pr_debug("PPS_Work: pps_stage=%d out_uv=%d op_ua=%d",
+			pps_data->stage, pps_data->out_uv, pps_data->op_ua);
+
 pps_dc_reschedule:
 	if (pps_ui <= 0) {
-		pr_info("PPS_Work: done=%d pps_stage=%d dc_state=%d",
-			pps_ui, gcpm->pps_data.stage, gcpm->dc_state);
-		if (log_on_done)
-			pps_log(pps_data, "PPS_Work: done=%d pd_ol=%d pps_stage=%d dc_state=%d\n",
-				pps_ui, pps_data->pd_online, gcpm->pps_data.stage,
-				gcpm->dc_state);
+		pr_debug("PPS_Work: pps_ui=%d dc_state=%d",
+			 pps_ui, gcpm->dc_state);
 	} else {
-		pr_info("PPS_Work: reschedule in %d pps_stage=%d (%d:%d) dc_state=%d (%d:%d)",
-			pps_ui, gcpm->pps_data.stage,
-			gcpm->pps_data.out_uv, gcpm->pps_data.op_ua,
-			gcpm->dc_state, gcpm->out_uv, gcpm->out_ua);
+		pr_debug("PPS_Work: reschedule in %d dc_state=%d (%d:%d)",
+			 pps_ui, gcpm->dc_state, gcpm->out_uv, gcpm->out_ua);
+
 		schedule_delayed_work(&gcpm->pps_work,
 				      msecs_to_jiffies(pps_ui));
 	}
 
-pps_dc_done:
 	mutex_unlock(&gcpm->chg_psy_lock);
 }
 
@@ -797,32 +757,41 @@
 
 	mutex_lock(&gcpm->chg_psy_lock);
 	switch (psp) {
+	/* do not route to the active charger */
+	case GBMS_PROP_TAPER_CONTROL:
+		taper_control = (pval->intval != 0);
+		ta_check = taper_control != gcpm->taper_control;
+		gcpm->taper_control = taper_control;
+		route = false;
+		break;
+	/* also route to the active charger */
+	case GBMS_PROP_CHARGE_DISABLE:
+		/*
+		 * google_charger send this on disconnect.
+		 * TODO: reset DC state and PPS detection, disable dc
+		 */
+		pr_info("%s: ChargeDisable value=%d\n", __func__, pval->intval);
+		ta_check = true;
+		break;
+	case POWER_SUPPLY_PROP_ONLINE:
+		ta_check = true;
+		break;
+	case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+		psp = POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX;
+		/* compat, fall through */
+	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
+		ta_check = gcpm->fv_uv != pval->intval;
+		gcpm->fv_uv = pval->intval;
+		break;
 
-		/* do not route to the active charger */
-		case GBMS_PROP_TAPER_CONTROL:
-			taper_control = (pval->intval != 0);
-			ta_check = taper_control != gcpm->taper_control;
-			gcpm->taper_control = taper_control;
-			route = false;
-			break;
+	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+		ta_check = gcpm->cc_max != pval->intval;
+		gcpm->cc_max = pval->intval;
+		break;
 
-		/* also route to the active charger */
-		case POWER_SUPPLY_PROP_VOLTAGE_MAX:
-			psp = POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX;
-			/* compat, fall through */
-		case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
-			ta_check = gcpm->fv_uv != pval->intval;
-			gcpm->fv_uv = pval->intval;
-			break;
-
-		case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
-			ta_check = gcpm->cc_max != pval->intval;
-			gcpm->cc_max = pval->intval;
-			break;
-
-		/* just route to the active charger */
-		default:
-			break;
+	/* just route to the active charger */
+	default:
+		break;
 	}
 
 	/* used only for debug */
@@ -847,7 +816,7 @@
 				psp, gcpm->chg_psy_active, name);
 		}
 	} else {
-		pr_err("invalid charger=%d for prop=%d\n",
+		pr_err("invalid active charger = %d for prop=%d\n",
 			gcpm->chg_psy_active, psp);
 	}
 
@@ -877,15 +846,14 @@
 		return -ENODEV;
 
 	switch (psp) {
+	/* handle locally for now */
+	case GBMS_PROP_CHARGE_CHARGER_STATE:
+		gbms_propval_int64val(pval) = gcpm_get_charger_state(gcpm, chg_psy);
+		return 0;
 
-		/* handle locally for now */
-		case GBMS_PROP_CHARGE_CHARGER_STATE:
-			gbms_propval_int64val(pval) = gcpm_get_charger_state(gcpm, chg_psy);
-			return 0;
-
-		/* route to the active charger */
-		default:
-			break;
+	/* route to the active charger */
+	default:
+		break;
 	}
 
 	return power_supply_get_property(chg_psy, psp, pval);
@@ -971,11 +939,16 @@
 
 #define GCPM_TCPM_PSY_MAX 2
 
+/*thiscan run */
 static void gcpm_init_work(struct work_struct *work)
 {
 	struct gcpm_drv *gcpm = container_of(work, struct gcpm_drv,
 					     init_work.work);
 	int i, found = 0, ret = 0;
+	bool dc_not_done;
+
+	/* might run along set_property() */
+	mutex_lock(&gcpm->chg_psy_lock);
 
 	/*
 	 * could call pps_init() in probe() and use lazy init for ->tcpm_psy
@@ -987,24 +960,31 @@
 		tcpm_psy = pps_get_tcpm_psy(gcpm->device->of_node,
 					    GCPM_TCPM_PSY_MAX);
 		if (!IS_ERR_OR_NULL(tcpm_psy)) {
-			gcpm->tcpm_psy_name = tcpm_psy->desc->name;
+			const char *name = tcpm_psy->desc->name;
+
+			gcpm->tcpm_psy_name = name;
 			gcpm->tcpm_psy = tcpm_psy;
 
 			/* PPS charging: needs an APDO */
-			ret = pps_init(&gcpm->pps_data, gcpm->device);
+			ret = pps_init(&gcpm->tcpm_pps_data, gcpm->device,
+				       gcpm->tcpm_psy);
 			if (ret == 0 && gcpm->debug_entry)
-				pps_init_fs(&gcpm->pps_data, gcpm->debug_entry);
+				pps_init_fs(&gcpm->tcpm_pps_data, gcpm->debug_entry);
 			if (ret < 0) {
 				pr_err("PPS init failure for %s (%d)\n",
-				       tcpm_psy->desc->name, ret);
+				       name, ret);
 			} else {
-				pps_init_state(&gcpm->pps_data);
+				gcpm->tcpm_pps_data.port_data =
+					power_supply_get_drvdata(tcpm_psy);
+
+				pps_init_state(&gcpm->tcpm_pps_data);
 				pr_info("PPS available for %s\n",
 					gcpm->tcpm_psy_name);
 			}
 
 		} else if (!tcpm_psy || !gcpm->log_psy_ratelimit) {
-			pr_warn("PPS not available\n");
+			/* abort on an error */
+			pr_warn("PPS not available for tcpm\n");
 			gcpm->tcpm_phandle = 0;
 		} else {
 			pr_warn("tcpm power supply not found, retrying... ret:%d\n",
@@ -1014,6 +994,42 @@
 
 	}
 
+	/* TODO: lookup by phandle as the dude above */
+	if (gcpm->wlc_dc_name && !gcpm->wlc_dc_psy) {
+		struct power_supply *wlc_dc_psy;
+
+		wlc_dc_psy = power_supply_get_by_name(gcpm->wlc_dc_name);
+		if (wlc_dc_psy) {
+			const char *name = gcpm->wlc_dc_name;
+
+			gcpm->wlc_dc_psy = wlc_dc_psy;
+
+			/* PPS charging: needs an APDO */
+			ret = pps_init(&gcpm->wlc_pps_data, gcpm->device,
+					gcpm->wlc_dc_psy);
+			if (ret == 0 && gcpm->debug_entry)
+				pps_init_fs(&gcpm->wlc_pps_data, gcpm->debug_entry);
+			if (ret < 0) {
+				pr_err("PPS init failure for %s (%d)\n",
+				       name, ret);
+			} else {
+				/* TODO: TBD */
+				gcpm->wlc_pps_data.port_data = NULL;
+				pps_init_state(&gcpm->wlc_pps_data);
+				pr_info("PPS available for %s\n", name);
+			}
+
+		} else if (!gcpm->log_psy_ratelimit) {
+			/* give up if wlc_dc_psy return an error */
+			pr_warn("PPS not available for %s\n", gcpm->wlc_dc_name);
+			gcpm->wlc_dc_name = NULL;
+		} else {
+			pr_warn("%s power supply not found, retrying... ret:%d\n",
+				gcpm->wlc_dc_name, ret);
+			gcpm->log_psy_ratelimit--;
+		}
+	}
+
 	/* default is index 0 */
 	for (i = 0; i < gcpm->chg_psy_count; i++) {
 		if (!gcpm->chg_psy_avail[i]) {
@@ -1039,24 +1055,43 @@
 				ret);
 		}
 
+		/* this is the reason why we need a lock here */
 		gcpm->resume_complete = true;
 		gcpm->init_complete = true;
 	}
 
-	/* keep looking for late arrivals && TCPM */
-	if (found != gcpm->chg_psy_count && gcpm->chg_psy_retries)
+	/* keep looking for late arrivals, TCPM and WLC if set */
+	if (found == gcpm->chg_psy_count)
+		gcpm->chg_psy_retries = 0;
+	else if (gcpm->chg_psy_retries)
 		gcpm->chg_psy_retries--;
 
-	if (gcpm->chg_psy_retries || (gcpm->tcpm_phandle && !gcpm->tcpm_psy)) {
+	dc_not_done = (gcpm->tcpm_phandle && !gcpm->tcpm_psy) ||
+		      (gcpm->wlc_dc_name && !gcpm->wlc_dc_psy);
+
+	pr_warn("%s:%d %s retries=%d dc_not_done=%d tcpm_ok=%d wlc_ok=%d\n",
+		__FILE__, __LINE__, __func__,
+		gcpm->chg_psy_retries,
+		dc_not_done,
+		(!gcpm->tcpm_phandle || gcpm->tcpm_psy),
+		(!gcpm->wlc_dc_name || gcpm->wlc_dc_psy));
+
+	if (gcpm->chg_psy_retries || dc_not_done) {
 		const unsigned long jif = msecs_to_jiffies(INIT_RETRY_DELAY_MS);
 
 		schedule_delayed_work(&gcpm->init_work, jif);
 	} else {
-		pr_info("google_cpm init_work done %d/%d pps=%d\n", found,
-			gcpm->chg_psy_count, !!gcpm->tcpm_psy);
+		pr_info("google_cpm init_work done %d/%d pps=%d wlc_dc=%d\n",
+			found, gcpm->chg_psy_count,
+			!!gcpm->tcpm_psy, !!gcpm->wlc_dc_psy);
 	}
+
+	/* might run along set_property() */
+	mutex_unlock(&gcpm->chg_psy_lock);
 }
 
+/* ------------------------------------------------------------------------ */
+
 static int gcpm_debug_get_active(void *data, u64 *val)
 {
 	struct gcpm_drv *gcpm = data;
@@ -1089,7 +1124,6 @@
 DEFINE_SIMPLE_ATTRIBUTE(gcpm_debug_active_fops, gcpm_debug_get_active,
 			gcpm_debug_set_active, "%llu\n");
 
-
 static int gcpm_debug_dc_limit_demand_show(void *data, u64 *val)
 {
 	struct gcpm_drv *gcpm = data;
@@ -1113,6 +1147,7 @@
 	return 0;
 }
 
+
 DEFINE_SIMPLE_ATTRIBUTE(gcpm_debug_dc_limit_demand_fops,
 			gcpm_debug_dc_limit_demand_show,
                         gcpm_debug_dc_limit_demand_set,
@@ -1122,9 +1157,12 @@
 static int gcpm_debug_pps_stage_get(void *data, u64 *val)
 {
 	struct gcpm_drv *gcpm = data;
+	struct pd_pps_data *pps_data;
 
 	mutex_lock(&gcpm->chg_psy_lock);
-	*val = gcpm->pps_data.stage;
+	pps_data = gcpm_pps_data(gcpm);
+	if (pps_data)
+		*val = pps_data->stage;
 	mutex_unlock(&gcpm->chg_psy_lock);
 	return 0;
 }
@@ -1133,12 +1171,15 @@
 {
 	struct gcpm_drv *gcpm = data;
 	const int intval = (int)val;
+	struct pd_pps_data *pps_data;
 
 	if (intval < PPS_DISABLED || intval > PPS_ACTIVE)
 		return -EINVAL;
 
 	mutex_lock(&gcpm->chg_psy_lock);
-	gcpm->pps_data.stage = intval;
+	pps_data = gcpm_pps_data(gcpm);
+	if (pps_data)
+		pps_data->stage = intval;
 	gcpm->force_pps = !pps_is_disabled(intval);
 	mod_delayed_work(system_wq, &gcpm->pps_work, 0);
 	mutex_unlock(&gcpm->chg_psy_lock);
@@ -1149,6 +1190,36 @@
 DEFINE_SIMPLE_ATTRIBUTE(gcpm_debug_pps_stage_fops, gcpm_debug_pps_stage_get,
 			gcpm_debug_pps_stage_set, "%llu\n");
 
+static int gcpm_debug_dc_state_get(void *data, u64 *val)
+{
+	struct gcpm_drv *gcpm = data;
+
+	mutex_lock(&gcpm->chg_psy_lock);
+	*val = gcpm->dc_state;
+	mutex_unlock(&gcpm->chg_psy_lock);
+	return 0;
+}
+
+static int gcpm_debug_dc_state_set(void *data, u64 val)
+{
+	struct gcpm_drv *gcpm = data;
+	const int intval = (int)val;
+	int ret;
+
+	if (intval < DC_DISABLED || intval > DC_PASSTHROUGH)
+		return -EINVAL;
+
+	mutex_lock(&gcpm->chg_psy_lock);
+	gcpm->dc_state = intval;
+	ret = gcpm_chg_check(gcpm);
+	pr_debug("%s: gcpm_chg_check regt=%d\n", __func__, ret);
+	mutex_unlock(&gcpm->chg_psy_lock);
+	return 0;
+}
+
+DEFINE_SIMPLE_ATTRIBUTE(gcpm_debug_dc_state_fops, gcpm_debug_dc_state_get,
+			gcpm_debug_dc_state_set, "%llu\n");
+
 static struct dentry *gcpm_init_fs(struct gcpm_drv *gcpm)
 {
 	struct dentry *de;
@@ -1157,6 +1228,7 @@
 	if (IS_ERR_OR_NULL(de))
 		return NULL;
 
+	debugfs_create_file("dc_state", 0644, de, gcpm, &gcpm_debug_dc_state_fops);
 	debugfs_create_file("active", 0644, de, gcpm, &gcpm_debug_active_fops);
 	debugfs_create_file("dc_limit_demand", 0644, de, gcpm,
 			    &gcpm_debug_dc_limit_demand_fops);
@@ -1166,6 +1238,8 @@
 	return de;
 }
 
+/* ------------------------------------------------------------------------ */
+
 static int gcpm_probe_psy_names(struct gcpm_drv *gcpm)
 {
 	struct device *dev = gcpm->device;
@@ -1192,10 +1266,125 @@
 	return count;
 }
 
+/* -------------------------------------------------------------------------
+ *  Use to abstract the PPS adapter if needed.
+ */
+
+static int gcpm_pps_psy_set_property(struct power_supply *psy,
+				    enum power_supply_property prop,
+				    const union power_supply_propval *val)
+{
+	struct gcpm_drv *gcpm = power_supply_get_drvdata(psy);
+	struct pd_pps_data *pps_data;
+	int ret = 0;
+
+	mutex_lock(&gcpm->chg_psy_lock);
+
+	pps_data = gcpm_pps_data(gcpm);
+	if (!pps_data || !pps_data->pps_psy) {
+		pr_debug("%s: no target prop=%d ret=%d\n", __func__, prop, ret);
+		mutex_unlock(&gcpm->chg_psy_lock);
+		return -EAGAIN;
+	}
+
+	switch (prop) {
+	default:
+		ret = power_supply_set_property(pps_data->pps_psy, prop, val);
+		break;
+	}
+
+	mutex_unlock(&gcpm->chg_psy_lock);
+	pr_debug("%s: prop=%d val=%d ret=%d\n", __func__,
+		 prop, val->intval, ret);
+
+	return ret;
+}
+
+static int gcpm_pps_psy_get_property(struct power_supply *psy,
+				    enum power_supply_property prop,
+				    union power_supply_propval *val)
+{
+	struct gcpm_drv *gcpm = power_supply_get_drvdata(psy);
+	struct pd_pps_data *pps_data;
+	int ret = 0;
+
+	mutex_lock(&gcpm->chg_psy_lock);
+
+	pps_data = gcpm_pps_data(gcpm);
+	if (!pps_data || !pps_data->pps_psy) {
+		pr_debug("%s: no target prop=%d ret=%d\n", __func__, prop, ret);
+		mutex_unlock(&gcpm->chg_psy_lock);
+		return -EAGAIN;
+	}
+
+	switch (prop) {
+	default:
+		ret = power_supply_get_property(pps_data->pps_psy, prop, val);
+		break;
+	}
+
+	mutex_unlock(&gcpm->chg_psy_lock);
+
+	pr_debug("%s: prop=%d val=%d ret=%d\n", __func__,
+		 prop, val->intval, ret);
+
+	return ret;
+}
+
+/* check pps_is_avail(), pps_prog_online() and pps_check_type() */
+static enum power_supply_property gcpm_pps_psy_properties[] = {
+	POWER_SUPPLY_PROP_VOLTAGE_MAX,
+	POWER_SUPPLY_PROP_VOLTAGE_MIN,
+	POWER_SUPPLY_PROP_CURRENT_MAX,
+	POWER_SUPPLY_PROP_CURRENT_NOW,	/* 17 */
+	POWER_SUPPLY_PROP_ONLINE,	/* 4 */
+	POWER_SUPPLY_PROP_PRESENT,	/* 3 */
+	POWER_SUPPLY_PROP_TYPE,		/* */
+	POWER_SUPPLY_PROP_USB_TYPE,	/* */
+	POWER_SUPPLY_PROP_VOLTAGE_NOW,	/* */
+};
+
+static int gcpm_pps_psy_is_writeable(struct power_supply *psy,
+				   enum power_supply_property psp)
+{
+	switch (psp) {
+	case POWER_SUPPLY_PROP_PRESENT:
+	case POWER_SUPPLY_PROP_ONLINE:
+	case POWER_SUPPLY_PROP_CURRENT_NOW:
+	case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+		return 1;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static enum power_supply_usb_type gcpm_pps_usb_types[] = {
+	POWER_SUPPLY_USB_TYPE_PD_PPS
+};
+
+static const struct power_supply_desc gcpm_pps_psy_desc = {
+	.name		= "gcpm_pps",
+	.type		= POWER_SUPPLY_TYPE_UNKNOWN,
+	.get_property	= gcpm_pps_psy_get_property,
+	.set_property 	= gcpm_pps_psy_set_property,
+	.properties	= gcpm_pps_psy_properties,
+	.property_is_writeable = gcpm_pps_psy_is_writeable,
+	.num_properties	= ARRAY_SIZE(gcpm_pps_psy_properties),
+
+	/* POWER_SUPPLY_PROP_USB_TYPE requires an array of these */
+	.usb_types	= gcpm_pps_usb_types,
+	.num_usb_types	= ARRAY_SIZE(gcpm_pps_usb_types),
+};
+
+/* ------------------------------------------------------------------------- */
+
 #define LOG_PSY_RATELIMIT_CNT	200
 
 static int google_cpm_probe(struct platform_device *pdev)
 {
+	struct power_supply_config gcpm_pps_psy_cfg = { 0 };
 	struct power_supply_config psy_cfg = { 0 };
 	const char *tmp_name = NULL;
 	struct gcpm_drv *gcpm;
@@ -1211,7 +1400,7 @@
 	gcpm->chg_psy_retries = 10; /* chg_psy_retries *  INIT_RETRY_DELAY_MS */
 	gcpm->out_uv = -1;
 	gcpm->out_ua = -1;
-	INIT_DELAYED_WORK(&gcpm->pps_work, gcpm_pps_dc_work);
+	INIT_DELAYED_WORK(&gcpm->pps_work, gcpm_pps_wlc_dc_work);
 	INIT_DELAYED_WORK(&gcpm->init_work, gcpm_init_work);
 	mutex_init(&gcpm->chg_psy_lock);
 
@@ -1225,19 +1414,29 @@
 			return -ENOMEM;
 	}
 
-	/* sub power supply names */
+	/* subs power supply names */
 	gcpm->chg_psy_count = gcpm_probe_psy_names(gcpm);
 	if (gcpm->chg_psy_count <= 0)
 		return -ENODEV;
 
-	/* PPS needs a TCPM power supply */
+	/* DC/PPS needs at least one power supply of this type */
 	ret = of_property_read_u32(pdev->dev.of_node,
 				   "google,tcpm-power-supply",
 				   &gcpm->tcpm_phandle);
 	if (ret < 0)
 		pr_warn("google,tcpm-power-supply not defined\n");
 
-	/* Direct Charging: valid only with PPS */
+	ret = of_property_read_string(pdev->dev.of_node,
+				      "google,wlc_dc-power-supply",
+				      &tmp_name);
+	if (ret == 0) {
+		gcpm->wlc_dc_name = devm_kstrdup(&pdev->dev, tmp_name,
+						     GFP_KERNEL);
+		if (!gcpm->wlc_dc_name)
+			return -ENOMEM;
+	}
+
+	/* GCPM might need a gpio to enable/disable DC/PPS */
 	gcpm->dcen_gpio = of_get_named_gpio(pdev->dev.of_node,
 					    "google,dc-en", 0);
 	if (gcpm->dcen_gpio >= 0) {
@@ -1251,6 +1450,7 @@
 			gcpm->dcen_gpio_default, ret);
 	}
 
+	/* Triggers to enable dc charging */
 	ret = of_property_read_u32(pdev->dev.of_node, "google,dc_limit-demand",
 				   &gcpm->dc_limit_demand);
 	if (ret < 0)
@@ -1269,16 +1469,31 @@
 
 	psy_cfg.drv_data = gcpm;
 	psy_cfg.of_node = pdev->dev.of_node;
-	gcpm->psy = devm_power_supply_register(gcpm->device, &gcpm_psy_desc,
+	gcpm->psy = devm_power_supply_register(gcpm->device,
+					       &gcpm_psy_desc,
 					       &psy_cfg);
 	if (IS_ERR(gcpm->psy)) {
 		ret = PTR_ERR(gcpm->psy);
 		if (ret == -EPROBE_DEFER)
 			return -EPROBE_DEFER;
 
-		/* TODO: fail with -ENODEV */
-		dev_err(gcpm->device, "Couldn't register as power supply, ret=%d\n",
-			ret);
+		dev_err(gcpm->device, "Couldn't register gcpm, (%d)\n", ret);
+		return -ENODEV;
+	}
+
+	/* gcpm_pps_psy_cfg.of_node is used to find out the snk_pdos */
+	gcpm_pps_psy_cfg.drv_data = gcpm;
+	gcpm_pps_psy_cfg.of_node = pdev->dev.of_node;
+	gcpm->pps_psy = devm_power_supply_register(gcpm->device,
+						   &gcpm_pps_psy_desc,
+						   &gcpm_pps_psy_cfg);
+	if (IS_ERR(gcpm->pps_psy)) {
+		ret = PTR_ERR(gcpm->pps_psy);
+		if (ret == -EPROBE_DEFER)
+			return -EPROBE_DEFER;
+
+		dev_err(gcpm->device, "Couldn't register gcpm_pps (%d)\n", ret);
+		return -ENODEV;
 	}
 
 	/* give time to fg driver to start */
@@ -1304,6 +1519,9 @@
 		gcpm->chg_psy_avail[i] = NULL;
 	}
 
+	if (gcpm->wlc_dc_psy)
+		power_supply_put(gcpm->wlc_dc_psy);
+
 	return 0;
 }
 
@@ -1333,3 +1551,130 @@
 MODULE_DESCRIPTION("Google Charging Policy Manager");
 MODULE_AUTHOR("AleX Pelosi <[email protected]>");
 MODULE_LICENSE("GPL");
+
+#if 0
+
+/* NOTE: call with a lock around gcpm->chg_psy_lock */
+static int gcpm_dc_charging(struct gcpm_drv *gcpm)
+{
+	struct power_supply *dc_psy;
+	int vchg, ichg, status;
+
+	dc_psy = gcpm_chg_get_active(gcpm);
+	if (!dc_psy) {
+		pr_err("DC_CHG: invalid charger\n");
+		return -ENODEV;
+	}
+
+	vchg = GPSY_GET_PROP(dc_psy, POWER_SUPPLY_PROP_VOLTAGE_NOW);
+	ichg = GPSY_GET_PROP(dc_psy, POWER_SUPPLY_PROP_CURRENT_NOW);
+	status = GPSY_GET_PROP(dc_psy, POWER_SUPPLY_PROP_STATUS);
+
+	pr_err("DC_CHG: vchg=%d, ichg=%d status=%d\n",
+	       vchg, ichg, status);
+
+	return 0;
+}
+
+static void gcpm_pps_dc_charging(struct gcpm_drv *gcpm)
+{
+	struct pd_pps_data *pps_data = &gcpm->pps_data;
+	struct power_supply *pps_psy = gcpm->tcpm_psy;
+	const int pre_out_ua = pps_data->op_ua;
+	const int pre_out_uv = pps_data->out_uv;
+	int ret, pps_ui = -ENODEV;
+
+	if (gcpm->dc_state == DC_ENABLE) {
+		struct pd_pps_data *pps_data = &gcpm->pps_data;
+		bool pwr_ok;
+
+		/* must run at the end of PPS negotiation */
+		if (gcpm->out_ua == -1)
+			gcpm->out_ua = min(gcpm->cc_max, pps_data->max_ua);
+		if (gcpm->out_uv == -1) {
+			struct power_supply *chg_psy =
+						gcpm_chg_get_active(gcpm);
+			unsigned long ta_max_v, value;
+			int vbatt = -1;
+
+			ta_max_v = pps_data->max_ua * pps_data->max_uv;
+			ta_max_v /= gcpm->out_ua;
+			if (ta_max_v > DC_TA_VMAX_MV)
+				ta_max_v = DC_TA_VMAX_MV;
+
+			if (chg_psy)
+				vbatt = GPSY_GET_PROP(chg_psy,
+						POWER_SUPPLY_PROP_VOLTAGE_NOW);
+			if (vbatt < 0)
+				vbatt = gcpm->fv_uv;
+			if (vbatt < 0)
+				vbatt = 0;
+
+			/* good for pca9468 */
+			value = 2 * vbatt + DC_VBATT_HEADROOM_MV;
+			if (value < DC_TA_VMIN_MV)
+				value = DC_TA_VMIN_MV;
+
+			/* PPS voltage in 20mV steps */
+			gcpm->out_uv = value - value % 20000;
+		}
+
+		pr_info("CHG_CHK: max_uv=%d,max_ua=%d  out_uv=%d,out_ua=%d\n",
+			pps_data->max_uv, pps_data->max_ua,
+			gcpm->out_uv, gcpm->out_ua);
+
+		pps_ui = pps_update_adapter(pps_data, gcpm->out_uv,
+					    gcpm->out_ua, pps_psy);
+		if (pps_ui < 0)
+			pps_ui = PPS_ERROR_RETRY_MS;
+
+		/* wait until adapter is at or over request */
+		pwr_ok = pps_data->out_uv == gcpm->out_uv &&
+				pps_data->op_ua == gcpm->out_ua;
+		if (pwr_ok) {
+			ret = gcpm_chg_offline(gcpm);
+			if (ret == 0)
+				ret = gcpm_dc_start(gcpm, gcpm->dc_index);
+			if (ret == 0) {
+				gcpm->dc_state = DC_RUNNING;
+				pps_ui = DC_RUN_DELAY_MS;
+			}  else if (pps_ui > DC_ERROR_RETRY_MS) {
+				pps_ui = DC_ERROR_RETRY_MS;
+			}
+		}
+
+		/*
+			* TODO: add retries and switch to DC_ENABLE again or to
+			* DC_DISABLED on timeout.
+			*/
+
+		pr_info("PPS_DC: dc_state=%d out_uv=%d %d->%d, out_ua=%d %d->%d\n",
+			gcpm->dc_state,
+			pps_data->out_uv, pre_out_uv, gcpm->out_uv,
+			pps_data->op_ua, pre_out_ua, gcpm->out_ua);
+	} else if (gcpm->dc_state == DC_RUNNING)  {
+
+		ret = gcpm_chg_ping(gcpm, 0, 0);
+		if (ret < 0)
+			pr_err("PPS_DC: ping failed with %d\n", ret);
+
+		/* update gcpm->out_uv, gcpm->out_ua */
+		pr_info("PPS_DC: dc_state=%d out_uv=%d %d->%d out_ua=%d %d->%d\n",
+			gcpm->dc_state,
+			pps_data->out_uv, pre_out_uv, gcpm->out_uv,
+			pps_data->op_ua, pre_out_ua, gcpm->out_ua);
+
+		ret = gcpm_dc_charging(gcpm);
+		if (ret < 0)
+			pps_ui = DC_ERROR_RETRY_MS;
+
+		ret = pps_update_adapter(&gcpm->pps_data,
+						gcpm->out_uv, gcpm->out_ua,
+						pps_psy);
+		if (ret < 0)
+			pps_ui = PPS_ERROR_RETRY_MS;
+	}
+
+	return pps_ui;
+}
+#endif
\ No newline at end of file
diff --git a/google_dc_pps.c b/google_dc_pps.c
index e48ab26..4a4a305 100644
--- a/google_dc_pps.c
+++ b/google_dc_pps.c
@@ -79,6 +79,9 @@
 #if 0 //TODO
 	tcpm_put_partner_src_caps(&pps_data->src_caps);
 #endif
+	pr_debug("%s: %s src_caps=%d\n", __func__,
+		 pps_data->pps_psy ? pps_data->pps_psy->desc->name : "<>",
+		 !!pps_data->src_caps);
 
 	pps_data->pd_online = TCPM_PSY_OFFLINE;
 	pps_data->stage = PPS_DISABLED;
@@ -233,6 +236,7 @@
 	if (pd_online != TCPM_PSY_PROG_ONLINE)
 		goto not_supp;
 
+	/* make the transition to active */
 	if (pps_data->stage != PPS_ACTIVE) {
 		int rc;
 
@@ -276,7 +280,9 @@
 
 	ret = GPSY_SET_PROP(tcpm_psy, POWER_SUPPLY_PROP_ONLINE,
 			    TCPM_PSY_PROG_ONLINE);
-	if (ret == 0) {
+	if (ret == -EOPNOTSUPP) {
+		pps->stage = PPS_NOTSUPP;
+	} else if (ret == 0) {
 		pps->pd_online = TCPM_PSY_PROG_ONLINE;
 		pps->stage = PPS_NONE;
 	}
@@ -432,6 +438,18 @@
 
 int pps_init_fs(struct pd_pps_data *pps_data, struct dentry *de)
 {
+	struct power_supply *pps_psy = pps_data->pps_psy;
+
+	if (pps_psy) {
+		char name[32];
+
+		scnprintf(name, sizeof(name), "%s_pps", pps_psy->desc->name);
+
+		pps_data->log = logbuffer_register(name);
+		if (IS_ERR(pps_data->log))
+			pps_data->log = NULL;
+	}
+
 	if (!de)
 		return 0;
 
@@ -561,7 +579,8 @@
 /* Look for the connector and retrieve source capabilities.
  * pps_data->nr_snk_pdo == 0 means no PPS
  */
-int pps_init(struct pd_pps_data *pps_data, struct device *dev)
+int pps_init(struct pd_pps_data *pps_data, struct device *dev,
+	     struct power_supply *pps_psy)
 {
 	const char *psy_name;
 	int ret;
@@ -572,6 +591,8 @@
 	ret = pps_init_snk(pps_data, dev->of_node);
 	if (ret < 0)
 		return ret;
+	if (!pps_data->nr_snk_pdo)
+		pr_err("nr_sink_pdo=%d this is a fake one\n", pps_data->nr_snk_pdo);
 
 	psy_name = pps_psy->desc->name ? pps_psy->desc->name : "<>";
 
@@ -597,6 +618,7 @@
 		}
 	}
 
+	pps_data->pps_psy = pps_psy;
 	return 0;
 }
 // EXPORT_SYMBOL_GPL(pps_init);
diff --git a/google_dc_pps.h b/google_dc_pps.h
index 384d1e2..05560e5 100644
--- a/google_dc_pps.h
+++ b/google_dc_pps.h
@@ -79,7 +79,8 @@
 #define pps_is_disabled(x) (((x) == PPS_NOTSUPP) || ((x) == PPS_DISABLED))
 
 struct dentry;
-int pps_init(struct pd_pps_data *pps_data, struct device *dev);
+int pps_init(struct pd_pps_data *pps_data, struct device *dev,
+	     struct power_supply *pps_psy);
 int pps_init_fs(struct pd_pps_data *pps_data, struct dentry *de);
 /* reset state and leave in DISABLED  */
 void pps_init_state(struct pd_pps_data *pps_data);
diff --git a/pca9468_charger.c b/pca9468_charger.c
index 037f609..d8444d4 100644
--- a/pca9468_charger.c
+++ b/pca9468_charger.c
@@ -684,6 +684,15 @@
 	pca9468->tcpm_psy_name = tcpm_psy->desc->name;
 	pca9468->pd = tcpm_psy;
 
+	/* setup PPS, not needed if tcpm-power-supply is not there */
+	ret = pps_init(&pca9468->pps_data, pca9468->dev, tcpm_psy);
+	if (ret == 0 && pca9468->debug_root)
+		pps_init_fs(&pca9468->pps_data, pca9468->debug_root);
+	if (ret == 0) {
+		pps_init_state(&pca9468->pps_data);
+		pr_info("pca9468: PPS direct available\n");
+	}
+
 check_online:
 	online = pps_prog_check_online(&pca9468->pps_data, pca9468->pd);
 	if (!online)
@@ -5251,16 +5260,6 @@
 	if (ret < 0)
 		dev_err(dev, "error while registering debugfs %d\n", ret);
 
-	/* setup PPS, not needed if tcpm-power-supply is not there */
-	ret = pps_init(&pca9468_chg->pps_data, pca9468_chg->dev);
-	if (ret == 0 && pca9468_chg->debug_root)
-		pps_init_fs(&pca9468_chg->pps_data,
-				pca9468_chg->debug_root);
-	if (ret == 0) {
-		pps_init_state(&pca9468_chg->pps_data);
-		pr_info("pca9468: PPS direct available\n");
-	}
-
 #ifdef CONFIG_DC_STEP_CHARGING
 	ret = pca9468_step_chg_init(pca9468_chg->dev);
 	if (ret < 0) {