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

Commit a2bba45d authored by Sahil Chandna's avatar Sahil Chandna Committed by Harry Yang
Browse files

power: smb1390-psy: Add support for dual charge Pumps



Dual SMB1390 PMICs, master and slave, are supported as companion chargers
to the primary charge PMIC. The CP driver is split into master and slave
drivers, the slave mirrors master's operations.

The master acts as the proxy for both, interacting with main charger and
userspace, with the inherited interface and die temp of master alone is
exposed.

The Master SMB1390 handles:
- All IRQ
- Switching of slave SMB1390
- Configuring ILIM of slave

Currently, the supported mode is MID(input)-VBAT(output). When ILIM falls
below threshold ilim, both SMB are turned off. In the absence of slave
charge pump, the drivers fall back to support a single charge pump like
before, for backward compatibility.

Change-Id: I7c16d8d5e205ddc4cd212b0e7c5d2e54eea78df1
Signed-off-by: default avatarHarry Yang <harryy@codeaurora.org>
Signed-off-by: default avatarSahil Chandna <chandna@codeaurora.org>
parent d2ae2dfd
Loading
Loading
Loading
Loading
+402 −57
Original line number Diff line number Diff line
@@ -94,6 +94,8 @@
#define SWITCHER_TOGGLE_VOTER	"SWITCHER_TOGGLE_VOTER"
#define SOC_LEVEL_VOTER		"SOC_LEVEL_VOTER"

#define CP_MASTER		0
#define CP_SLAVE		1
#define THERMAL_SUSPEND_DECIDEGC	1400
#define MAX_ILIM_UA			3200000

@@ -119,6 +121,12 @@ enum {
	NUM_IRQS,
};

enum isns_mode {
	ISNS_MODE_OFF = 0,
	ISNS_MODE_ACTIVE,
	ISNS_MODE_STANDBY,
};

enum {
	SWITCHER_EN = 0,
	SMB_PIN_EN,
@@ -157,8 +165,10 @@ struct smb1390 {
	struct votable		*ilim_votable;
	struct votable		*fcc_votable;
	struct votable		*fv_votable;
	struct votable		*cp_awake_votable;

	/* power supplies */
	struct power_supply	*cps_psy;
	struct power_supply	*usb_psy;
	struct power_supply	*batt_psy;
	struct power_supply	*dc_psy;
@@ -179,6 +189,8 @@ struct smb1390 {
	u32			max_temp_alarm_degc;
	u32			max_cutoff_soc;
	u32			pl_output_mode;
	u32			cp_role;
	enum isns_mode		current_capability;
};

struct smb_irq {
@@ -265,6 +277,46 @@ static bool is_psy_voter_available(struct smb1390 *chip)
	return true;
}

static int smb1390_isns_mode_control(struct smb1390 *chip, enum isns_mode mode)
{
	int rc;
	u8 val;

	switch  (mode) {
	case ISNS_MODE_ACTIVE:
		val = ATEST1_OUTPUT_ENABLE_BIT | ISNS_INT_VAL;
		break;
	case ISNS_MODE_STANDBY:
		val = ATEST1_OUTPUT_ENABLE_BIT;
		break;
	case ISNS_MODE_OFF:
	default:
		val = 0;
		break;
	}

	rc = smb1390_masked_write(chip, CORE_ATEST1_SEL_REG,
				ATEST1_OUTPUT_ENABLE_BIT | ATEST1_SEL_MASK,
				val);
	if (rc < 0)
		pr_err("Couldn't set CORE_ATEST1_SEL_REG, rc = %d\n", rc);

	return rc;
}

static bool is_cps_available(struct smb1390 *chip)
{
	if (!chip->cps_psy)
		chip->cps_psy = power_supply_get_by_name("cp_slave");
	else
		return true;

	if (!chip->cps_psy)
		return false;

	return true;
}

static void cp_toggle_switcher(struct smb1390 *chip)
{
	int rc;
@@ -320,6 +372,18 @@ static int smb1390_get_cp_en_status(struct smb1390 *chip, int id, bool *enable)
	return rc;
}

static int smb1390_set_ilim(struct smb1390 *chip, int ilim_ua)
{
	int rc;

	rc = smb1390_masked_write(chip, CORE_FTRIM_ILIM_REG,
			CFG_ILIM_MASK, ilim_ua);
	if (rc < 0)
		pr_err("Failed to write ILIM Register, rc=%d\n", rc);

	return rc;
}

static irqreturn_t default_irq_handler(int irq, void *data)
{
	struct smb1390 *chip = data;
@@ -425,7 +489,13 @@ static int smb1390_get_die_temp(struct smb1390 *chip,
	return rc;
}

static int smb1390_get_isns(struct smb1390 *chip,
static int smb1390_get_isns(int temp)
{
	/* ISNS = 2 * (1496 - 1390_therm_input * 0.00356) * 1000 uA */
	return ((1496 * 1000 - div_s64((s64)temp * 3560, 1000)) * 2);
}

static int smb1390_get_isns_master(struct smb1390 *chip,
			union power_supply_propval *val)
{
	int temp = 0;
@@ -445,12 +515,105 @@ static int smb1390_get_isns(struct smb1390 *chip,
	if (!enable)
		return -ENODATA;

	/*
	 * Since master and slave share temp_pin line
	 * which is re-used to measure isns, configure the
	 * master as follows:
	 * 1. Put slave in standby mode
	 * 2. Configure master to provide current reading
	 * 3. Read current value
	 * 4. Configure master back to report temperature
	 */
	mutex_lock(&chip->die_chan_lock);
	rc = smb1390_masked_write(chip, CORE_ATEST1_SEL_REG,
				ATEST1_OUTPUT_ENABLE_BIT | ATEST1_SEL_MASK,
				ATEST1_OUTPUT_ENABLE_BIT | ISNS_INT_VAL);
	if (is_cps_available(chip)) {
		val->intval = ISNS_MODE_STANDBY;
		rc = power_supply_set_property(chip->cps_psy,
				POWER_SUPPLY_PROP_CURRENT_CAPABILITY, val);
		if (rc < 0) {
		pr_err("Couldn't set CORE_ATEST1_SEL_REG, rc = %d\n", rc);
			pr_err("Couldn't change slave charging state rc=%d\n",
				rc);
			goto unlock;
		}
	}

	rc = smb1390_isns_mode_control(chip, ISNS_MODE_ACTIVE);
	if (rc < 0) {
		pr_err("Failed to set master in Active mode, rc=%d\n", rc);
		goto unlock;
	}

	rc = iio_read_channel_processed(chip->iio.die_temp_chan,
			&temp);
	if (rc < 0) {
		pr_err("Couldn't read die_temp chan for isns, rc = %d\n", rc);
		goto unlock;
	}

	rc = smb1390_isns_mode_control(chip, ISNS_MODE_OFF);
	if (rc < 0)
		pr_err("Couldn't set master to off mode, rc = %d\n", rc);

	if (is_cps_available(chip)) {
		val->intval = ISNS_MODE_OFF;
		rc = power_supply_set_property(chip->cps_psy,
				POWER_SUPPLY_PROP_CURRENT_CAPABILITY, val);
		if (rc < 0)
			pr_err("Couldn't change slave charging state rc=%d\n",
				rc);
	}

unlock:
	mutex_unlock(&chip->die_chan_lock);

	if (rc >= 0)
		val->intval = smb1390_get_isns(temp);

	return rc;
}

static int smb1390_get_isns_slave(struct smb1390 *chip,
			union power_supply_propval *val)
{
	int temp = 0;
	int rc;
	bool enable;

	if (!is_cps_available(chip)) {
		val->intval = 0;
		return 0;
	}
	/*
	 * If SMB1390 chip is not enabled, adc channel read may render
	 * erroneous value. Return error to signify, adc read is not admissible
	 */
	rc = smb1390_get_cp_en_status(chip, SMB_PIN_EN, &enable);
	if (rc < 0) {
		pr_err("Couldn't get SMB_PIN enable status, rc=%d\n", rc);
		return rc;
	}

	if (!enable)
		return -ENODATA;

	/*
	 * Since master and slave share temp_pin line
	 * which is re-used to measure isns, configure the
	 * slave as follows:
	 * 1. Put slave in standby mode
	 * 2. Configure slave to in Active mode to provide current reading
	 * 3. Read current value
	 */
	mutex_lock(&chip->die_chan_lock);
	rc = smb1390_isns_mode_control(chip, ISNS_MODE_STANDBY);
	if (rc < 0)
		goto unlock;

	val->intval = ISNS_MODE_ACTIVE;
	rc = power_supply_set_property(chip->cps_psy,
			POWER_SUPPLY_PROP_CURRENT_CAPABILITY, val);
	if (rc < 0) {
		pr_err("Couldn't change slave charging state rc=%d\n",
			rc);
		goto unlock;
	}

@@ -461,18 +624,23 @@ static int smb1390_get_isns(struct smb1390 *chip,
		goto unlock;
	}

	rc = smb1390_masked_write(chip, CORE_ATEST1_SEL_REG,
				ATEST1_OUTPUT_ENABLE_BIT | ATEST1_SEL_MASK, 0);
	val->intval = ISNS_MODE_OFF;
	rc = power_supply_set_property(chip->cps_psy,
			POWER_SUPPLY_PROP_CURRENT_CAPABILITY, val);
	if (rc < 0)
		pr_err("Couldn't change slave charging state rc=%d\n",
			rc);

	rc = smb1390_isns_mode_control(chip, ISNS_MODE_OFF);
	if (rc < 0)
		pr_err("Couldn't set CORE_ATEST1_SEL_REG, rc = %d\n", rc);


unlock:
	mutex_unlock(&chip->die_chan_lock);

	/* ISNS = 2 * (1496 - 1390_therm_input * 0.00356) * 1000 uA */
	if (rc >= 0)
		val->intval = (1496 * 1000 - div_s64((s64)temp * 3560,
							1000)) * 2;
		val->intval = smb1390_get_isns(temp);

	return rc;
}
@@ -505,19 +673,17 @@ static int smb1390_disable_vote_cb(struct votable *votable, void *data,
{
	struct smb1390 *chip = data;
	int rc = 0;
	u8 mask, val;

	if (!is_psy_voter_available(chip) || chip->suspended)
		return -EAGAIN;

	if (disable) {
		rc = smb1390_masked_write(chip, CORE_CONTROL1_REG,
				   CMD_EN_SWITCHER_BIT, 0);
		if (rc < 0)
			return rc;
	} else {
		rc = smb1390_masked_write(chip, CORE_CONTROL1_REG,
				   CMD_EN_SWITCHER_BIT, CMD_EN_SWITCHER_BIT);
		if (rc < 0)
	mask = CMD_EN_SWITCHER_BIT | CMD_EN_SL_BIT;
	val = is_cps_available(chip) ? mask : CMD_EN_SWITCHER_BIT;
	rc = smb1390_masked_write(chip, CORE_CONTROL1_REG, mask,
				  disable ? 0 : val);
	if (rc < 0) {
		pr_err("Couldn't write CORE_CONTROL1_REG, rc=%d\n", rc);
		return rc;
	}

@@ -533,6 +699,7 @@ static int smb1390_ilim_vote_cb(struct votable *votable, void *data,
			      int ilim_uA, const char *client)
{
	struct smb1390 *chip = data;
	union power_supply_propval pval = {0, };
	int rc = 0;

	if (!is_psy_voter_available(chip) || chip->suspended)
@@ -545,20 +712,31 @@ static int smb1390_ilim_vote_cb(struct votable *votable, void *data,
	}

	ilim_uA = min(ilim_uA, MAX_ILIM_UA);
	rc = smb1390_masked_write(chip, CORE_FTRIM_ILIM_REG,
		CFG_ILIM_MASK,
		DIV_ROUND_CLOSEST(max(ilim_uA, 500000) - 500000, 100000));
	if (rc < 0) {
		pr_err("Failed to write ILIM Register, rc=%d\n", rc);
		return rc;
	}

	/* ILIM less than min_ilim_ua, disable charging */
	if (ilim_uA < chip->min_ilim_ua) {
		smb1390_dbg(chip, PR_INFO, "ILIM %duA is too low to allow charging\n",
			ilim_uA);
		vote(chip->disable_votable, ILIM_VOTER, true, 0);
	} else {
		if (is_cps_available(chip)) {
			ilim_uA /= 2;
			pval.intval = DIV_ROUND_CLOSEST(ilim_uA - 500000,
					100000);
			rc = power_supply_set_property(chip->cps_psy,
					POWER_SUPPLY_PROP_INPUT_CURRENT_MAX,
					&pval);
			if (rc < 0)
				pr_err("Couldn't change slave ilim  rc=%d\n",
					rc);
		}

		rc = smb1390_set_ilim(chip,
		      DIV_ROUND_CLOSEST(ilim_uA - 500000, 100000));
		if (rc < 0) {
			pr_err("Failed to set ILIM, rc=%d\n", rc);
			return rc;
		}

		smb1390_dbg(chip, PR_INFO, "ILIM set to %duA\n", ilim_uA);
		vote(chip->disable_votable, ILIM_VOTER, false, 0);
	}
@@ -566,6 +744,21 @@ static int smb1390_ilim_vote_cb(struct votable *votable, void *data,
	return rc;
}

static int smb1390_awake_vote_cb(struct votable *votable, void *data,
				 int awake, const char *client)
{
	struct smb1390 *chip = data;

	if (awake)
		__pm_stay_awake(chip->cp_ws);
	else
		__pm_relax(chip->cp_ws);

	smb1390_dbg(chip, PR_INFO, "client: %s awake: %d\n", client, awake);

	return 0;
}

static int smb1390_notifier_cb(struct notifier_block *nb,
			       unsigned long event, void *data)
{
@@ -761,6 +954,7 @@ static enum power_supply_property smb1390_charge_pump_props[] = {
	POWER_SUPPLY_PROP_CP_SWITCHER_EN,
	POWER_SUPPLY_PROP_CP_DIE_TEMP,
	POWER_SUPPLY_PROP_CP_ISNS,
	POWER_SUPPLY_PROP_CP_ISNS_SLAVE,
	POWER_SUPPLY_PROP_CP_TOGGLE_SWITCHER,
	POWER_SUPPLY_PROP_CP_IRQ_STATUS,
	POWER_SUPPLY_PROP_CP_ILIM,
@@ -832,7 +1026,10 @@ static int smb1390_get_prop(struct power_supply *psy,
		}
		break;
	case POWER_SUPPLY_PROP_CP_ISNS:
		rc = smb1390_get_isns(chip, val);
		rc = smb1390_get_isns_master(chip, val);
		break;
	case POWER_SUPPLY_PROP_CP_ISNS_SLAVE:
		rc = smb1390_get_isns_slave(chip, val);
		break;
	case POWER_SUPPLY_PROP_CP_TOGGLE_SWITCHER:
		val->intval = 0;
@@ -905,6 +1102,8 @@ static int smb1390_prop_is_writeable(struct power_supply *psy,
	case POWER_SUPPLY_PROP_CP_ENABLE:
	case POWER_SUPPLY_PROP_CP_TOGGLE_SWITCHER:
	case POWER_SUPPLY_PROP_CP_IRQ_STATUS:
	case POWER_SUPPLY_PROP_INPUT_CURRENT_MAX:
	case POWER_SUPPLY_PROP_CURRENT_CAPABILITY:
		return 1;
	default:
		break;
@@ -990,6 +1189,8 @@ static void smb1390_release_channels(struct smb1390 *chip)

static int smb1390_create_votables(struct smb1390 *chip)
{
	chip->cp_awake_votable = create_votable("CP_AWAKE",
			VOTE_SET_ANY, smb1390_awake_vote_cb, chip);
	chip->disable_votable = create_votable("CP_DISABLE",
			VOTE_SET_ANY, smb1390_disable_vote_cb, chip);
	if (IS_ERR(chip->disable_votable))
@@ -1164,14 +1365,23 @@ static void smb1390_create_debugfs(struct smb1390 *chip)
}
#endif

static int smb1390_probe(struct platform_device *pdev)
static const struct of_device_id match_table[] = {
	{ .compatible = "qcom,smb1390-charger-psy",
	  .data = (void *)CP_MASTER
	},
	{ .compatible = "qcom,smb1390-slave",
	  .data = (void *)CP_SLAVE
	},
	{ },
};

static int smb1390_master_probe(struct smb1390 *chip)
{
	struct smb1390 *chip;
	int rc;
	struct device_node *revid_dev_node;
	struct pmic_revid_data *pmic_rev_id;
	int rc;

	revid_dev_node = of_parse_phandle(pdev->dev.of_node,
	revid_dev_node = of_parse_phandle(chip->dev->of_node,
					  "qcom,pmic-revid", 0);
	if (!revid_dev_node) {
		pr_err("Missing qcom,pmic-revid property\n");
@@ -1189,23 +1399,9 @@ static int smb1390_probe(struct platform_device *pdev)
		return -EPROBE_DEFER;
	}

	chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
	if (!chip)
		return -ENOMEM;

	chip->dev = &pdev->dev;
	chip->pmic_rev_id = pmic_rev_id;
	spin_lock_init(&chip->status_change_lock);
	mutex_init(&chip->die_chan_lock);
	chip->die_temp = -ENODATA;
	chip->pmic_rev_id = pmic_rev_id;
	chip->disabled = true;
	platform_set_drvdata(pdev, chip);

	chip->regmap = dev_get_regmap(chip->dev->parent, NULL);
	if (!chip->regmap) {
		pr_err("Couldn't get regmap\n");
		return -EINVAL;
	}

	rc = smb1390_parse_dt(chip);
	if (rc < 0) {
@@ -1252,9 +1448,6 @@ static int smb1390_probe(struct platform_device *pdev)
	}

	smb1390_create_debugfs(chip);

	pr_debug("smb1390 probed successfully chip_version=%d\n",
			chip->pmic_rev_id->rev4);
	return 0;

out_notifier:
@@ -1268,10 +1461,166 @@ static int smb1390_probe(struct platform_device *pdev)
	return rc;
}

static enum power_supply_property smb1390_cp_slave_props[] = {
	POWER_SUPPLY_PROP_INPUT_CURRENT_MAX,
	POWER_SUPPLY_PROP_CURRENT_CAPABILITY,
};

static int smb1390_cp_slave_get_prop(struct power_supply *psy,
		enum power_supply_property psp,
		union power_supply_propval *val)
{
	struct smb1390 *chip = power_supply_get_drvdata(psy);

	switch (psp) {
	case POWER_SUPPLY_PROP_INPUT_CURRENT_MAX:
		val->intval = 0;
		if (!chip->ilim_votable)
			chip->ilim_votable = find_votable("CP_ILIM");
		if (chip->ilim_votable)
			val->intval =
				get_effective_result_locked(chip->ilim_votable);
		break;
	case POWER_SUPPLY_PROP_CURRENT_CAPABILITY:
		val->intval = (int)chip->current_capability;
		break;
	default:
		smb1390_dbg(chip, PR_MISC, "SMB 1390 slave power supply get prop %d not supported\n",
			psp);
		return -EINVAL;
	}

	return 0;
}

static int smb1390_cp_slave_set_prop(struct power_supply *psy,
		enum power_supply_property psp,
		const union power_supply_propval *val)
{
	struct smb1390 *chip = power_supply_get_drvdata(psy);
	int rc = 0;

	switch (psp) {
	case POWER_SUPPLY_PROP_INPUT_CURRENT_MAX:
		rc = smb1390_set_ilim(chip, val->intval);
		break;
	case POWER_SUPPLY_PROP_CURRENT_CAPABILITY:
		chip->current_capability = (enum isns_mode)val->intval;
		rc = smb1390_isns_mode_control(chip, val->intval);
		break;
	default:
		smb1390_dbg(chip, PR_MISC, "SMB 1390 slave power supply set prop %d not supported\n",
			psp);
		return -EINVAL;
	}

	return 0;
};

static const struct power_supply_desc cps_psy_desc = {
	.name = "cp_slave",
	.type = POWER_SUPPLY_TYPE_PARALLEL,
	.properties = smb1390_cp_slave_props,
	.num_properties = ARRAY_SIZE(smb1390_cp_slave_props),
	.get_property = smb1390_cp_slave_get_prop,
	.set_property = smb1390_cp_slave_set_prop,
	.property_is_writeable	= smb1390_prop_is_writeable,
};

static int smb1390_init_cps_psy(struct smb1390 *chip)
{
	struct power_supply_config cps_cfg = {};

	cps_cfg.drv_data = chip;
	cps_cfg.of_node = chip->dev->of_node;
	chip->cps_psy = devm_power_supply_register(chip->dev,
						  &cps_psy_desc,
						  &cps_cfg);
	if (IS_ERR(chip->cps_psy)) {
		pr_err("Couldn't register CP slave power supply\n");
		return PTR_ERR(chip->cps_psy);
	}

	return 0;
}

static int smb1390_slave_probe(struct smb1390 *chip)
{
	int stat, rc;

	/* a "hello" read to test the presence of the slave PMIC */
	rc = smb1390_read(chip, CORE_STATUS1_REG, &stat);
	if (rc < 0) {
		pr_err("Couldn't find slave SMB1390\n");
		return -EINVAL;
	}

	rc = smb1390_init_cps_psy(chip);
	if (rc < 0)
		pr_err("Couldn't initialize cps psy rc=%d\n", rc);

	return rc;
}

static int smb1390_probe(struct platform_device *pdev)
{
	struct smb1390 *chip;
	int rc;

	chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
	if (!chip)
		return -ENOMEM;

	chip->dev = &pdev->dev;
	chip->die_temp = -ENODATA;
	chip->disabled = true;

	chip->regmap = dev_get_regmap(chip->dev->parent, NULL);
	if (!chip->regmap) {
		pr_err("Couldn't get regmap\n");
		return -EINVAL;
	}

	platform_set_drvdata(pdev, chip);
	chip->cp_role = (int)of_device_get_match_data(chip->dev);
	switch (chip->cp_role) {
	case CP_MASTER:
		rc = smb1390_master_probe(chip);
		break;
	case CP_SLAVE:
		rc = smb1390_slave_probe(chip);
		break;
	default:
		pr_err("Couldn't find a matching role %d\n", chip->cp_role);
		rc = -EINVAL;
		goto cleanup;
	}

	if (rc < 0) {
		if (rc != -EPROBE_DEFER)
			pr_err("Couldn't probe SMB1390 %s rc=%d\n",
			       chip->cp_role ? "Slave" : "Master", rc);
		goto cleanup;
	}

	pr_info("smb1390 %s probed successfully\n", chip->cp_role ? "Slave" :
		"Master");
	return 0;

cleanup:
	platform_set_drvdata(pdev, NULL);
	return rc;
}

static int smb1390_remove(struct platform_device *pdev)
{
	struct smb1390 *chip = platform_get_drvdata(pdev);

	if (chip->cp_role !=  CP_MASTER) {
		platform_set_drvdata(pdev, NULL);
		return 0;
	}

	power_supply_unreg_notifier(&chip->nb);

	/* explicitly disable charging */
@@ -1282,6 +1631,7 @@ static int smb1390_remove(struct platform_device *pdev)
	wakeup_source_unregister(chip->cp_ws);
	smb1390_destroy_votables(chip);
	smb1390_release_channels(chip);
	platform_set_drvdata(pdev, NULL);
	return 0;
}

@@ -1323,11 +1673,6 @@ static const struct dev_pm_ops smb1390_pm_ops = {
	.resume		= smb1390_resume,
};

static const struct of_device_id match_table[] = {
	{ .compatible = "qcom,smb1390-charger-psy", },
	{ },
};

static struct platform_driver smb1390_driver = {
	.driver	= {
		.name		= "qcom,smb1390-charger-psy",