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

Commit 63066f2d authored by Harry Yang's avatar Harry Yang
Browse files

power: smb5-lib: support liquid presence detection



For PMIC5 Chargers, circuits are added for Type-C water submerge detection.
Here is the general approach:

 1. While in DRP, CC pin source crude sensor generates an interrupt on
    detecting some resistance on CC pins.
 2. Use VADC to measure SBUx to GND resistance.
 3. Combined with CC Logic detection result, determine whether liquid is
    detected.
 4. If Yes, configure CC Logic to SNK mode and stop DRP toggling to prevent
    CC pins from getting corroded.

Change-Id: If0f1fb4866986b29fcdd1a274a461ea423a47b09
Signed-off-by: default avatarHarry Yang <harryy@codeaurora.org>
parent 9ae54a8e
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -2610,6 +2610,12 @@ static int smb5_probe(struct platform_device *pdev)
		return rc;
	}

	if (alarmtimer_get_rtcdev())
		alarm_init(&chg->lpd_recheck_timer, ALARM_REALTIME,
				smblib_lpd_recheck_timer);
	else
		return -EPROBE_DEFER;

	rc = smblib_init(chg);
	if (rc < 0) {
		pr_err("Smblib_init failed rc=%d\n", rc);
+193 −0
Original line number Diff line number Diff line
@@ -2299,6 +2299,9 @@ static int smblib_get_prop_dfp_mode(struct smb_charger *chg)
	int rc;
	u8 stat;

	if (chg->lpd_stage == LPD_STAGE_COMMIT)
		return POWER_SUPPLY_TYPEC_NONE;

	rc = smblib_read(chg, TYPE_C_SRC_STATUS_REG, &stat);
	if (rc < 0) {
		smblib_err(chg, "Couldn't read TYPE_C_SRC_STATUS_REG rc=%d\n",
@@ -3661,6 +3664,70 @@ irqreturn_t usb_source_change_irq_handler(int irq, void *data)
	return IRQ_HANDLED;
}

enum alarmtimer_restart smblib_lpd_recheck_timer(struct alarm *alarm,
						ktime_t time)
{
	union power_supply_propval pval;
	struct smb_charger *chg = container_of(alarm, struct smb_charger,
							lpd_recheck_timer);
	int rc;

	pval.intval = POWER_SUPPLY_TYPEC_PR_DUAL;
	rc = smblib_set_prop_typec_power_role(chg, &pval);
	if (rc < 0) {
		smblib_err(chg, "Couldn't write 0x%02x to TYPE_C_INTRPT_ENB_SOFTWARE_CTRL rc=%d\n",
			pval.intval, rc);
		return ALARMTIMER_NORESTART;
	}

	chg->lpd_stage = LPD_STAGE_NONE;

	return ALARMTIMER_NORESTART;
}

#define RSBU_K_300K_UV	3000000
static bool smblib_src_lpd(struct smb_charger *chg)
{
	union power_supply_propval pval;
	bool lpd_flag = false;
	u8 stat;
	int rc;

	rc = smblib_read(chg, TYPE_C_SRC_STATUS_REG, &stat);
	if (rc < 0) {
		smblib_err(chg, "Couldn't read TYPE_C_SRC_STATUS_REG rc=%d\n",
				rc);
		return false;
	}

	switch (stat & DETECTED_SNK_TYPE_MASK) {
	case SRC_DEBUG_ACCESS_BIT:
		if (smblib_rsbux_low(chg, RSBU_K_300K_UV))
			lpd_flag = true;
		break;
	case SRC_RD_RA_VCONN_BIT:
	case SRC_RD_OPEN_BIT:
	case AUDIO_ACCESS_RA_RA_BIT:
	default:
		break;
	}

	if (lpd_flag) {
		chg->lpd_stage = LPD_STAGE_COMMIT;
		pval.intval = POWER_SUPPLY_TYPEC_PR_SINK;
		rc = smblib_set_prop_typec_power_role(chg, &pval);
		if (rc < 0)
			smblib_err(chg, "Couldn't write 0x%02x to TYPE_C_INTRPT_ENB_SOFTWARE_CTRL rc=%d\n",
				pval.intval, rc);
		alarm_start_relative(&chg->lpd_recheck_timer,
						ms_to_ktime(60000));
	} else {
		chg->typec_mode = smblib_get_prop_typec_mode(chg);
	}

	return lpd_flag;
}

static void typec_sink_insertion(struct smb_charger *chg)
{
	vote(chg->usb_icl_votable, OTG_VOTER, true, 0);
@@ -3845,6 +3912,10 @@ irqreturn_t typec_or_rid_detection_change_irq_handler(int irq, void *data)
{
	struct smb_irq_data *irq_data = data;
	struct smb_charger *chg = irq_data->parent_data;
	u8 stat;
	int rc;

	smblib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name);

	if (chg->connector_type == POWER_SUPPLY_CONNECTOR_MICRO_USB) {
		cancel_delayed_work_sync(&chg->uusb_otg_work);
@@ -3855,6 +3926,26 @@ irqreturn_t typec_or_rid_detection_change_irq_handler(int irq, void *data)
		return IRQ_HANDLED;
	}

	if (chg->pr_swap_in_progress || chg->pd_hard_reset)
		return IRQ_HANDLED;

	rc = smblib_read(chg, TYPE_C_MISC_STATUS_REG, &stat);
	if (rc < 0) {
		smblib_err(chg, "Couldn't read TYPE_C_MISC_STATUS_REG rc=%d\n",
			rc);
		return IRQ_HANDLED;
	}

	/* liquid presence detected, to check further */
	if ((stat & TYPEC_WATER_DETECTION_STATUS_BIT)
			&& chg->lpd_stage == LPD_STAGE_NONE) {
		chg->lpd_stage = LPD_STAGE_FLOAT;
		cancel_delayed_work_sync(&chg->lpd_ra_open_work);
		vote(chg->awake_votable, LPD_VOTER, true, 0);
		schedule_delayed_work(&chg->lpd_ra_open_work,
						msecs_to_jiffies(300));
	}

	return IRQ_HANDLED;
}

@@ -3902,6 +3993,10 @@ irqreturn_t typec_attach_detach_irq_handler(int irq, void *data)
	}

	if (stat & TYPEC_ATTACH_DETACH_STATE_BIT) {
		chg->lpd_stage = LPD_STAGE_ATTACHED;
		cancel_delayed_work_sync(&chg->lpd_ra_open_work);
		vote(chg->awake_votable, LPD_VOTER, false, 0);

		rc = smblib_read(chg, TYPE_C_MISC_STATUS_REG, &stat);
		if (rc < 0) {
			smblib_err(chg, "Couldn't read TYPE_C_MISC_STATUS_REG rc=%d\n",
@@ -3910,6 +4005,8 @@ irqreturn_t typec_attach_detach_irq_handler(int irq, void *data)
		}

		if (stat & SNK_SRC_MODE_BIT) {
			if (smblib_src_lpd(chg))
				return IRQ_HANDLED;
			chg->sink_src_mode = SRC_MODE;
			typec_sink_insertion(chg);
		} else {
@@ -3934,6 +4031,10 @@ irqreturn_t typec_attach_detach_irq_handler(int irq, void *data)
			chg->sink_src_mode = UNATTACHED_MODE;
			chg->early_usb_attach = false;
		}

		chg->lpd_stage = LPD_STAGE_DETACHED;
		schedule_delayed_work(&chg->lpd_detach_work,
					msecs_to_jiffies(100));
	}

	power_supply_changed(chg->usb_psy);
@@ -4405,6 +4506,94 @@ static void jeita_update_work(struct work_struct *work)
	chg->jeita_configured = true;
}

static void smblib_lpd_ra_open_work(struct work_struct *work)
{
	struct smb_charger *chg = container_of(work, struct smb_charger,
							lpd_ra_open_work.work);
	union power_supply_propval pval;
	u8 stat;
	int rc;

	if (chg->pr_swap_in_progress || chg->pd_hard_reset) {
		chg->lpd_stage = LPD_STAGE_NONE;
		goto out;
	}

	if (chg->lpd_stage != LPD_STAGE_FLOAT)
		goto out;

	rc = smblib_read(chg, TYPE_C_MISC_STATUS_REG, &stat);
	if (rc < 0) {
		smblib_err(chg, "Couldn't read TYPE_C_MISC_STATUS_REG rc=%d\n",
			rc);
		goto out;
	}

	/* double check water detection status bit */
	if (!(stat & TYPEC_WATER_DETECTION_STATUS_BIT)) {
		chg->lpd_stage = LPD_STAGE_NONE;
		goto out;
	}

	chg->lpd_stage = LPD_STAGE_COMMIT;

	/* Enable source only mode */
	pval.intval = POWER_SUPPLY_TYPEC_PR_SOURCE;
	rc = smblib_set_prop_typec_power_role(chg, &pval);
	if (rc < 0) {
		smblib_err(chg, "Couldn't write 0x%02x to TYPE_C_INTRPT_ENB_SOFTWARE_CTRL rc=%d\n",
			pval.intval, rc);
		goto out;
	}

	/* Wait 1.5ms to read src status */
	usleep_range(1500, 1510);

	rc = smblib_read(chg, TYPE_C_SRC_STATUS_REG, &stat);
	if (rc < 0) {
		smblib_err(chg, "Couldn't read TYPE_C_SRC_STATUS_REG rc=%d\n",
				rc);
		goto out;
	}

	/* Emark cable */
	if ((stat & SRC_RA_OPEN_BIT) &&
			!smblib_rsbux_low(chg, RSBU_K_300K_UV)) {
		/* restore DRP mode */
		pval.intval = POWER_SUPPLY_TYPEC_PR_DUAL;
		rc = smblib_set_prop_typec_power_role(chg, &pval);
		if (rc < 0)
			smblib_err(chg, "Couldn't write 0x%02x to TYPE_C_INTRPT_ENB_SOFTWARE_CTRL rc=%d\n",
				pval.intval, rc);

		chg->lpd_stage = LPD_STAGE_NONE;
		goto out;
	}

	/* Moisture detected, enable sink only mode */
	pval.intval = POWER_SUPPLY_TYPEC_PR_SINK;
	rc = smblib_set_prop_typec_power_role(chg, &pval);
	if (rc < 0) {
		smblib_err(chg, "Couldn't write 0x%02x to TYPE_C_INTRPT_ENB_SOFTWARE_CTRL rc=%d\n",
			pval.intval, rc);
		goto out;
	}

	/* recheck in 60 seconds */
	alarm_start_relative(&chg->lpd_recheck_timer, ms_to_ktime(60000));
out:
	vote(chg->awake_votable, LPD_VOTER, false, 0);
}

static void smblib_lpd_detach_work(struct work_struct *work)
{
	struct smb_charger *chg = container_of(work, struct smb_charger,
							lpd_detach_work.work);

	if (chg->lpd_stage == LPD_STAGE_DETACHED)
		chg->lpd_stage = LPD_STAGE_NONE;
}

static int smblib_create_votables(struct smb_charger *chg)
{
	int rc = 0;
@@ -4528,6 +4717,8 @@ int smblib_init(struct smb_charger *chg)
	INIT_DELAYED_WORK(&chg->pl_enable_work, smblib_pl_enable_work);
	INIT_DELAYED_WORK(&chg->uusb_otg_work, smblib_uusb_otg_work);
	INIT_DELAYED_WORK(&chg->bb_removal_work, smblib_bb_removal_work);
	INIT_DELAYED_WORK(&chg->lpd_ra_open_work, smblib_lpd_ra_open_work);
	INIT_DELAYED_WORK(&chg->lpd_detach_work, smblib_lpd_detach_work);
	chg->fake_capacity = -EINVAL;
	chg->fake_input_current_limited = -EINVAL;
	chg->fake_batt_status = -EINVAL;
@@ -4628,6 +4819,8 @@ int smblib_deinit(struct smb_charger *chg)
		cancel_delayed_work_sync(&chg->pl_enable_work);
		cancel_delayed_work_sync(&chg->uusb_otg_work);
		cancel_delayed_work_sync(&chg->bb_removal_work);
		cancel_delayed_work_sync(&chg->lpd_ra_open_work);
		cancel_delayed_work_sync(&chg->lpd_detach_work);
		power_supply_unreg_notifier(&chg->nb);
		smblib_destroy_votables(chg);
		qcom_step_chg_deinit();
+18 −0
Original line number Diff line number Diff line
@@ -12,6 +12,8 @@

#ifndef __SMB5_CHARGER_H
#define __SMB5_CHARGER_H
#include <linux/alarmtimer.h>
#include <linux/ktime.h>
#include <linux/types.h>
#include <linux/interrupt.h>
#include <linux/irqreturn.h>
@@ -60,6 +62,7 @@ enum print_reason {
#define HW_LIMIT_VOTER			"HW_LIMIT_VOTER"
#define PL_SMB_EN_VOTER			"PL_SMB_EN_VOTER"
#define FORCE_RECHARGE_VOTER		"FORCE_RECHARGE_VOTER"
#define LPD_VOTER			"LPD_VOTER"

#define BOOST_BACK_STORM_COUNT	3
#define WEAK_CHG_STORM_COUNT	8
@@ -194,6 +197,14 @@ static const unsigned int smblib_extcon_cable[] = {
	EXTCON_NONE,
};

enum lpd_stage {
	LPD_STAGE_NONE,
	LPD_STAGE_FLOAT,
	LPD_STAGE_ATTACHED,
	LPD_STAGE_DETACHED,
	LPD_STAGE_COMMIT,
};

/* EXTCON_USB and EXTCON_USB_HOST are mutually exclusive */
static const u32 smblib_extcon_exclusive[] = {0x3, 0};

@@ -325,6 +336,10 @@ struct smb_charger {
	struct delayed_work	pl_enable_work;
	struct delayed_work	uusb_otg_work;
	struct delayed_work	bb_removal_work;
	struct delayed_work	lpd_ra_open_work;
	struct delayed_work	lpd_detach_work;

	struct alarm		lpd_recheck_timer;

	/* secondary charger config */
	bool			sec_pl_present;
@@ -376,6 +391,7 @@ struct smb_charger {
	int			charger_temp_max;
	int			smb_temp_max;
	u8			typec_try_mode;
	enum lpd_stage		lpd_stage;

	/* workaround flag */
	u32			wa_flags;
@@ -575,6 +591,8 @@ int smblib_get_prop_from_bms(struct smb_charger *chg,
				union power_supply_propval *val);
int smblib_configure_hvdcp_apsd(struct smb_charger *chg, bool enable);
int smblib_icl_override(struct smb_charger *chg, bool override);
enum alarmtimer_restart smblib_lpd_recheck_timer(struct alarm *alarm,
				ktime_t time);

int smblib_init(struct smb_charger *chg);
int smblib_deinit(struct smb_charger *chg);
+1 −0
Original line number Diff line number Diff line
@@ -308,6 +308,7 @@ enum {
#define TYPEC_ATTACH_DETACH_STATE_BIT		BIT(5)

#define TYPE_C_MISC_STATUS_REG			(TYPEC_BASE + 0x0B)
#define TYPEC_WATER_DETECTION_STATUS_BIT	BIT(7)
#define SNK_SRC_MODE_BIT			BIT(6)
#define TYPEC_VBUS_ERROR_STATUS_BIT		BIT(4)
#define CC_ORIENTATION_BIT			BIT(1)