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

Commit 2a2adad6 authored by Harry Yang's avatar Harry Yang
Browse files

qcom: smb1355: introduction of smb1355 driver



This driver supports the SMB1355, as a slave charger device, to be
paired with QTI's standalone chargers.

Register access is provided by the parent device via regmap. Interrupts
are controlled by the parent device, and handlers are registered by the
SMB1355 charger driver.

The power supply framework is used to communicate battery and usb
properties to userspace and other driver consumers such as fuel gauge
and USB.

Change-Id: I74f283ac1547ee8ce76150c64d8bbbed2efeeb0c
Signed-off-by: default avatarHarry Yang <harryy@codeaurora.org>
parent 0e6032e3
Loading
Loading
Loading
Loading
+73 −0
Original line number Diff line number Diff line
Qualcomm Technologies, Inc. SMB1355 Charger Specific Bindings

SMB1355 slave charger is paired with QTI family of standalone chargers to
enable a high current, low profile Li+ battery charging system.

The device provides 28V DC withstand, wide operating input range of 3.8 to
14.2V for standard 5V USB inputs as well as a wide variety of HVDCP Travel
Adapters and is compatible with QTI's Quick Charge technology.

=======================
Required Node Structure
=======================

SMB1355 Charger must be described in two levels of device nodes.

==================================
First Level Node - SMB1355 Charger
==================================

Charger specific properties:
- compatible
  Usage:      required
  Value type: <string>
  Definition: "qcom,smb1355".

- qcom,pmic-revid
  Usage:      required
  Value type: phandle
  Definition: Should specify the phandle of SMB's revid module. This is used
	      to identify the SMB subtype.

================================================
Second Level Nodes - SMB1355 Charger Peripherals
================================================

Peripheral specific properties:
- reg
  Usage:      required
  Value type: <prop-encoded-array>
  Definition: Address and size of the peripheral's register block.

- interrupts
  Usage:      required
  Value type: <prop-encoded-array>
  Definition: Peripheral interrupt specifier.

- interrupt-names
  Usage:      required
  Value type: <stringlist>
  Definition: Interrupt names.  This list must match up 1-to-1 with the
	      interrupts specified in the 'interrupts' property.

=======
Example
=======

smb1355_charger: qcom,smb1355-charger {
	compatible = "qcom,smb1355";
	#address-cells = <1>;
	#size-cells = <1>;

	qcom,chgr@1000 {
		reg = <0x1000 0x100>;
		interrupts = <0x10 0x1 IRQ_TYPE_EDGE_BOTH>;
		interrupt-names = "chg-state-change";
	};

	qcom,chgr-misc@1600 {
		reg = <0x1600 0x100>;
		interrupts = <0x16 0x1 IRQ_TYPE_EDGE_BOTH>;
		interrupt-names = "wdog-bark";
	};
};
+10 −0
Original line number Diff line number Diff line
@@ -20,6 +20,16 @@ config SMB135X_CHARGER
	  The driver reports the charger status via the power supply framework.
	  A charger status change triggers an IRQ via the device STAT pin.

config SMB1355_SLAVE_CHARGER
	tristate "SMB1355 Slave Battery Charger"
	depends on MFD_I2C_PMIC
	help
	  Say Y to include support for SMB1355 Battery Charger.
	  SMB1355 is a single phase 5A battery charger.
	  The driver supports charger enable/disable.
	  The driver reports the charger status via the power supply framework.
	  A charger status change triggers an IRQ via the device STAT pin.

config SMB1351_USB_CHARGER
	tristate "smb1351 usb charger (with VBUS detection)"
	depends on I2C
+1 −0
Original line number Diff line number Diff line
obj-$(CONFIG_QPNP_FG_GEN3)     += qpnp-fg-gen3.o fg-memif.o fg-util.o
obj-$(CONFIG_SMB135X_CHARGER)   += smb135x-charger.o pmic-voter.o
obj-$(CONFIG_SMB1355_SLAVE_CHARGER)   += smb1355-charger.o pmic-voter.o
obj-$(CONFIG_SMB1351_USB_CHARGER) += smb1351-charger.o pmic-voter.o battery.o
obj-$(CONFIG_QPNP_SMB2)		+= qpnp-smb2.o smb-lib.o pmic-voter.o storm-watch.o battery.o
obj-$(CONFIG_SMB138X_CHARGER)	+= smb138x-charger.o smb-lib.o pmic-voter.o storm-watch.o battery.o
+675 −0
Original line number Diff line number Diff line
/* Copyright (c) 2016-2017 The Linux Foundation. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 and
 * only version 2 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */

#define pr_fmt(fmt) "SMB1355: %s: " fmt, __func__

#include <linux/device.h>
#include <linux/regmap.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/qpnp/qpnp-revid.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_irq.h>
#include <linux/regulator/driver.h>
#include <linux/regulator/machine.h>
#include <linux/regulator/of_regulator.h>
#include <linux/power_supply.h>
#include <linux/pmic-voter.h>

#define SMB1355_DEFAULT_FCC_UA 1000000

/* SMB1355 registers, different than mentioned in smb-reg.h */

#define CHGR_BASE	0x1000
#define BATIF_BASE	0x1200
#define USBIN_BASE	0x1300
#define MISC_BASE	0x1600

#define BATTERY_STATUS_2_REG			(CHGR_BASE + 0x0B)
#define DISABLE_CHARGING_BIT			BIT(3)

#define BATTERY_STATUS_3_REG			(CHGR_BASE + 0x0C)
#define BATT_GT_PRE_TO_FAST_BIT			BIT(4)
#define ENABLE_CHARGING_BIT			BIT(3)

#define CHGR_CFG2_REG				(CHGR_BASE + 0x51)
#define CHG_EN_SRC_BIT				BIT(7)
#define CHG_EN_POLARITY_BIT			BIT(6)

#define CFG_REG					(CHGR_BASE + 0x53)
#define CHG_OPTION_PIN_TRIM_BIT			BIT(7)
#define BATN_SNS_CFG_BIT			BIT(4)
#define CFG_TAPER_DIS_AFVC_BIT			BIT(3)
#define BATFET_SHUTDOWN_CFG_BIT			BIT(2)
#define VDISCHG_EN_CFG_BIT			BIT(1)
#define VCHG_EN_CFG_BIT				BIT(0)

#define FAST_CHARGE_CURRENT_CFG_REG		(CHGR_BASE + 0x61)
#define FAST_CHARGE_CURRENT_SETTING_MASK	GENMASK(7, 0)

#define CHGR_BATTOV_CFG_REG			(CHGR_BASE + 0x70)
#define BATTOV_SETTING_MASK			GENMASK(7, 0)

#define BARK_BITE_WDOG_PET_REG			(MISC_BASE + 0x43)
#define BARK_BITE_WDOG_PET_BIT			BIT(0)

#define WD_CFG_REG				(MISC_BASE + 0x51)
#define WATCHDOG_TRIGGER_AFP_EN_BIT		BIT(7)
#define BARK_WDOG_INT_EN_BIT			BIT(6)
#define BITE_WDOG_INT_EN_BIT			BIT(5)
#define WDOG_IRQ_SFT_BIT			BIT(2)
#define WDOG_TIMER_EN_ON_PLUGIN_BIT		BIT(1)
#define WDOG_TIMER_EN_BIT			BIT(0)

#define SNARL_BARK_BITE_WD_CFG_REG		(MISC_BASE + 0x53)
#define BITE_WDOG_DISABLE_CHARGING_CFG_BIT	BIT(7)
#define SNARL_WDOG_TIMEOUT_MASK			GENMASK(6, 4)
#define BARK_WDOG_TIMEOUT_MASK			GENMASK(3, 2)
#define BITE_WDOG_TIMEOUT_MASK			GENMASK(1, 0)

struct smb_chg_param {
	const char	*name;
	u16		reg;
	int		min_u;
	int		max_u;
	int		step_u;
};

struct smb_params {
	struct smb_chg_param	fcc;
	struct smb_chg_param	ov;
};

static struct smb_params v1_params = {
	.fcc		= {
		.name	= "fast charge current",
		.reg	= FAST_CHARGE_CURRENT_CFG_REG,
		.min_u	= 0,
		.max_u	= 6000000,
		.step_u	= 25000,
	},
	.ov		= {
		.name	= "battery over voltage",
		.reg	= CHGR_BATTOV_CFG_REG,
		.min_u	= 2450000,
		.max_u	= 5000000,
		.step_u	= 10000,
	},
};

struct smb_irq_info {
	const char		*name;
	const irq_handler_t	handler;
	const bool		wake;
	int			irq;
};

struct smb1355 {
	struct device		*dev;
	char			*name;
	struct regmap		*regmap;

	struct smb_params	param;

	struct mutex		write_lock;

	struct power_supply	*parallel_psy;
	struct pmic_revid_data	*pmic_rev_id;
};

static bool is_secure(struct smb1355 *chip, int addr)
{
	/* assume everything above 0xA0 is secure */
	return (addr & 0xFF) >= 0xA0;
}

static int smb1355_read(struct smb1355 *chip, u16 addr, u8 *val)
{
	unsigned int temp;
	int rc;

	rc = regmap_read(chip->regmap, addr, &temp);
	if (rc >= 0)
		*val = (u8)temp;

	return rc;
}

static int smb1355_masked_write(struct smb1355 *chip, u16 addr, u8 mask, u8 val)
{
	int rc;

	mutex_lock(&chip->write_lock);
	if (is_secure(chip, addr)) {
		rc = regmap_write(chip->regmap, (addr & 0xFF00) | 0xD0, 0xA5);
		if (rc < 0)
			goto unlock;
	}

	rc = regmap_update_bits(chip->regmap, addr, mask, val);

unlock:
	mutex_unlock(&chip->write_lock);
	return rc;
}

static int smb1355_write(struct smb1355 *chip, u16 addr, u8 val)
{
	int rc;

	mutex_lock(&chip->write_lock);

	if (is_secure(chip, addr)) {
		rc = regmap_write(chip->regmap, (addr & ~(0xFF)) | 0xD0, 0xA5);
		if (rc < 0)
			goto unlock;
	}

	rc = regmap_write(chip->regmap, addr, val);

unlock:
	mutex_unlock(&chip->write_lock);
	return rc;
}

static int smb1355_set_charge_param(struct smb1355 *chip,
			struct smb_chg_param *param, int val_u)
{
	int rc;
	u8 val_raw;

	if (val_u > param->max_u || val_u < param->min_u) {
		pr_err("%s: %d is out of range [%d, %d]\n",
			param->name, val_u, param->min_u, param->max_u);
		return -EINVAL;
	}

	val_raw = (val_u - param->min_u) / param->step_u;

	rc = smb1355_write(chip, param->reg, val_raw);
	if (rc < 0) {
		pr_err("%s: Couldn't write 0x%02x to 0x%04x rc=%d\n",
			param->name, val_raw, param->reg, rc);
		return rc;
	}

	return rc;
}

static int smb1355_get_charge_param(struct smb1355 *chip,
			struct smb_chg_param *param, int *val_u)
{
	int rc;
	u8 val_raw;

	rc = smb1355_read(chip, param->reg, &val_raw);
	if (rc < 0) {
		pr_err("%s: Couldn't read from 0x%04x rc=%d\n",
			param->name, param->reg, rc);
		return rc;
	}

	*val_u = val_raw * param->step_u + param->min_u;

	return rc;
}

static irqreturn_t smb1355_handle_chg_state_change(int irq, void *data)
{
	struct smb1355 *chip = data;

	if (chip->parallel_psy)
		power_supply_changed(chip->parallel_psy);

	return IRQ_HANDLED;
}

static irqreturn_t smb1355_handle_wdog_bark(int irq, void *data)
{
	struct smb1355 *chip = data;
	int rc;

	rc = smb1355_write(chip, BARK_BITE_WDOG_PET_REG,
					BARK_BITE_WDOG_PET_BIT);
	if (rc < 0)
		pr_err("Couldn't pet the dog rc=%d\n", rc);

	return IRQ_HANDLED;
}

/*****************************
 * PARALLEL PSY REGISTRATION *
 *****************************/

static enum power_supply_property smb1355_parallel_props[] = {
	POWER_SUPPLY_PROP_CHARGE_TYPE,
	POWER_SUPPLY_PROP_CHARGING_ENABLED,
	POWER_SUPPLY_PROP_PIN_ENABLED,
	POWER_SUPPLY_PROP_INPUT_SUSPEND,
	POWER_SUPPLY_PROP_VOLTAGE_MAX,
	POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
	POWER_SUPPLY_PROP_MODEL_NAME,
};

static int smb1355_get_prop_batt_charge_type(struct smb1355 *chip,
				union power_supply_propval *val)
{
	int rc;
	u8 stat;

	rc = smb1355_read(chip, BATTERY_STATUS_3_REG, &stat);
	if (rc < 0) {
		pr_err("Couldn't read SMB1355_BATTERY_STATUS_3 rc=%d\n", rc);
		return rc;
	}

	if (stat & ENABLE_CHARGING_BIT) {
		if (stat & BATT_GT_PRE_TO_FAST_BIT)
			val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST;
		else
			val->intval = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
	} else {
		val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE;
	}

	return rc;
}

static int smb1355_get_parallel_charging(struct smb1355 *chip, int *disabled)
{
	int rc;
	u8 cfg2;

	rc = smb1355_read(chip, CHGR_CFG2_REG, &cfg2);
	if (rc < 0) {
		pr_err("Couldn't read en_cmg_reg rc=%d\n", rc);
		return rc;
	}

	if (cfg2 & CHG_EN_SRC_BIT)
		*disabled = 0;
	else
		*disabled = 1;

	return 0;
}

static int smb1355_parallel_get_prop(struct power_supply *psy,
				     enum power_supply_property prop,
				     union power_supply_propval *val)
{
	struct smb1355 *chip = power_supply_get_drvdata(psy);
	u8 stat;
	int rc = 0;

	switch (prop) {
	case POWER_SUPPLY_PROP_CHARGE_TYPE:
		rc = smb1355_get_prop_batt_charge_type(chip, val);
		break;
	case POWER_SUPPLY_PROP_CHARGING_ENABLED:
		rc = smb1355_read(chip, BATTERY_STATUS_3_REG, &stat);
		if (rc >= 0)
			val->intval = (bool)(stat & ENABLE_CHARGING_BIT);
		break;
	case POWER_SUPPLY_PROP_PIN_ENABLED:
		rc = smb1355_read(chip, BATTERY_STATUS_2_REG, &stat);
		if (rc >= 0)
			val->intval = !(stat & DISABLE_CHARGING_BIT);
		break;
	case POWER_SUPPLY_PROP_INPUT_SUSPEND:
		rc = smb1355_get_parallel_charging(chip, &val->intval);
		break;
	case POWER_SUPPLY_PROP_VOLTAGE_MAX:
		rc = smb1355_get_charge_param(chip, &chip->param.ov,
						&val->intval);
		break;
	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
		rc = smb1355_get_charge_param(chip, &chip->param.fcc,
						&val->intval);
		break;
	case POWER_SUPPLY_PROP_MODEL_NAME:
		val->strval = chip->name;
		break;
	case POWER_SUPPLY_PROP_PARALLEL_MODE:
		val->intval = POWER_SUPPLY_PL_USBMID_USBMID;
		break;
	default:
		pr_err_ratelimited("parallel psy get prop %d not supported\n",
			prop);
		return -EINVAL;
	}

	if (rc < 0) {
		pr_debug("Couldn't get prop %d rc = %d\n", prop, rc);
		return -ENODATA;
	}

	return rc;
}

static int smb1355_set_parallel_charging(struct smb1355 *chip, bool disable)
{
	int rc;

	rc = smb1355_masked_write(chip, WD_CFG_REG, WDOG_TIMER_EN_BIT,
				 disable ? 0 : WDOG_TIMER_EN_BIT);
	if (rc < 0) {
		pr_err("Couldn't %s watchdog rc=%d\n",
		       disable ? "disable" : "enable", rc);
		disable = true;
	}

	/*
	 * Configure charge enable for high polarity and
	 * When disabling charging set it to cmd register control(cmd bit=0)
	 * When enabling charging set it to pin control
	 */
	rc = smb1355_masked_write(chip, CHGR_CFG2_REG,
			CHG_EN_POLARITY_BIT | CHG_EN_SRC_BIT,
			disable ? 0 : CHG_EN_SRC_BIT);
	if (rc < 0) {
		pr_err("Couldn't configure charge enable source rc=%d\n", rc);
		return rc;
	}

	return 0;
}

static int smb1355_parallel_set_prop(struct power_supply *psy,
				     enum power_supply_property prop,
				     const union power_supply_propval *val)
{
	struct smb1355 *chip = power_supply_get_drvdata(psy);
	int rc = 0;

	switch (prop) {
	case POWER_SUPPLY_PROP_INPUT_SUSPEND:
		rc = smb1355_set_parallel_charging(chip, (bool)val->intval);
		break;
	case POWER_SUPPLY_PROP_VOLTAGE_MAX:
		rc = smb1355_set_charge_param(chip, &chip->param.ov,
						val->intval);
		break;
	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
		rc = smb1355_set_charge_param(chip, &chip->param.fcc,
						val->intval);
		break;
	default:
		pr_debug("parallel power supply set prop %d not supported\n",
			prop);
		return -EINVAL;
	}

	return rc;
}

static int smb1355_parallel_prop_is_writeable(struct power_supply *psy,
					      enum power_supply_property prop)
{
	return 0;
}

static struct power_supply_desc parallel_psy_desc = {
	.name			= "parallel",
	.type			= POWER_SUPPLY_TYPE_PARALLEL,
	.properties		= smb1355_parallel_props,
	.num_properties		= ARRAY_SIZE(smb1355_parallel_props),
	.get_property		= smb1355_parallel_get_prop,
	.set_property		= smb1355_parallel_set_prop,
	.property_is_writeable	= smb1355_parallel_prop_is_writeable,
};

static int smb1355_init_parallel_psy(struct smb1355 *chip)
{
	struct power_supply_config parallel_cfg = {};

	parallel_cfg.drv_data = chip;
	parallel_cfg.of_node = chip->dev->of_node;

	/* change to smb1355's property list */
	parallel_psy_desc.properties = smb1355_parallel_props;
	parallel_psy_desc.num_properties = ARRAY_SIZE(smb1355_parallel_props);
	chip->parallel_psy = devm_power_supply_register(chip->dev,
						   &parallel_psy_desc,
						   &parallel_cfg);
	if (IS_ERR(chip->parallel_psy)) {
		pr_err("Couldn't register parallel power supply\n");
		return PTR_ERR(chip->parallel_psy);
	}

	return 0;
}

/***************************
 * HARDWARE INITIALIZATION *
 ***************************/

static int smb1355_init_hw(struct smb1355 *chip)
{
	int rc;

	/* enable watchdog bark and bite interrupts, and disable the watchdog */
	rc = smb1355_masked_write(chip, WD_CFG_REG, WDOG_TIMER_EN_BIT
			| WDOG_TIMER_EN_ON_PLUGIN_BIT | BITE_WDOG_INT_EN_BIT
			| BARK_WDOG_INT_EN_BIT,
			BITE_WDOG_INT_EN_BIT | BARK_WDOG_INT_EN_BIT);
	if (rc < 0) {
		pr_err("Couldn't configure the watchdog rc=%d\n", rc);
		return rc;
	}

	/* disable charging when watchdog bites */
	rc = smb1355_masked_write(chip, SNARL_BARK_BITE_WD_CFG_REG,
				 BITE_WDOG_DISABLE_CHARGING_CFG_BIT,
				 BITE_WDOG_DISABLE_CHARGING_CFG_BIT);
	if (rc < 0) {
		pr_err("Couldn't configure the watchdog bite rc=%d\n", rc);
		return rc;
	}

	/* disable parallel charging path */
	rc = smb1355_set_parallel_charging(chip, true);
	if (rc < 0) {
		pr_err("Couldn't disable parallel path rc=%d\n", rc);
		return rc;
	}

	/* initialize FCC to 0 */
	rc = smb1355_set_charge_param(chip, &chip->param.fcc, 0);
	if (rc < 0) {
		pr_err("Couldn't set 0 FCC rc=%d\n", rc);
		return rc;
	}

	/* enable parallel current sensing */
	rc = smb1355_masked_write(chip, CFG_REG,
				 VCHG_EN_CFG_BIT, VCHG_EN_CFG_BIT);
	if (rc < 0) {
		pr_err("Couldn't enable parallel current sensing rc=%d\n",
			rc);
		return rc;
	}

	return 0;
}

/**************************
 * INTERRUPT REGISTRATION *
 **************************/
static struct smb_irq_info smb1355_irqs[] = {
	[0] = {
		.name		= "wdog-bark",
		.handler	= smb1355_handle_wdog_bark,
	},
	[1] = {
		.name		= "chg-state-change",
		.handler	= smb1355_handle_chg_state_change,
		.wake		= true,
	},
};

static int smb1355_get_irq_index_byname(const char *irq_name)
{
	int i;

	for (i = 0; i < ARRAY_SIZE(smb1355_irqs); i++) {
		if (strcmp(smb1355_irqs[i].name, irq_name) == 0)
			return i;
	}

	return -ENOENT;
}

static int smb1355_request_interrupt(struct smb1355 *chip,
				struct device_node *node,
				const char *irq_name)
{
	int rc = 0, irq, irq_index;

	irq = of_irq_get_byname(node, irq_name);
	if (irq < 0) {
		pr_err("Couldn't get irq %s byname\n", irq_name);
		return irq;
	}

	irq_index = smb1355_get_irq_index_byname(irq_name);
	if (irq_index < 0) {
		pr_err("%s is not a defined irq\n", irq_name);
		return irq_index;
	}

	if (!smb1355_irqs[irq_index].handler)
		return 0;

	rc = devm_request_threaded_irq(chip->dev, irq, NULL,
				smb1355_irqs[irq_index].handler,
				IRQF_ONESHOT, irq_name, chip);
	if (rc < 0) {
		pr_err("Couldn't request irq %d rc=%d\n", irq, rc);
		return rc;
	}

	if (smb1355_irqs[irq_index].wake)
		enable_irq_wake(irq);

	return rc;
}

static int smb1355_request_interrupts(struct smb1355 *chip)
{
	struct device_node *node = chip->dev->of_node;
	struct device_node *child;
	int rc = 0;
	const char *name;
	struct property *prop;

	for_each_available_child_of_node(node, child) {
		of_property_for_each_string(child, "interrupt-names",
					prop, name) {
			rc = smb1355_request_interrupt(chip, child, name);
			if (rc < 0) {
				pr_err("Couldn't request interrupt %s rc=%d\n",
					name, rc);
				return rc;
			}
		}
	}

	return rc;
}

/*********
 * PROBE *
 *********/
static const struct of_device_id match_table[] = {
	{
		.compatible	= "qcom,smb1355",
	},
	{ },
};

static int smb1355_probe(struct platform_device *pdev)
{
	struct smb1355 *chip;
	const struct of_device_id *id;
	int rc = 0;

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

	chip->dev = &pdev->dev;
	chip->param = v1_params;
	chip->name = "smb1355";
	mutex_init(&chip->write_lock);

	chip->regmap = dev_get_regmap(chip->dev->parent, NULL);
	if (!chip->regmap) {
		pr_err("parent regmap is missing\n");
		return -EINVAL;
	}

	id = of_match_device(of_match_ptr(match_table), chip->dev);
	if (!id) {
		pr_err("Couldn't find a matching device\n");
		return -ENODEV;
	}

	platform_set_drvdata(pdev, chip);

	rc = smb1355_init_hw(chip);
	if (rc < 0) {
		pr_err("Couldn't initialize hardware rc=%d\n", rc);
		goto cleanup;
	}

	rc = smb1355_init_parallel_psy(chip);
	if (rc < 0) {
		pr_err("Couldn't initialize parallel psy rc=%d\n", rc);
		goto cleanup;
	}

	rc = smb1355_request_interrupts(chip);
	if (rc < 0) {
		pr_err("Couldn't request interrupts rc=%d\n", rc);
		goto cleanup;
	}

	pr_info("%s probed successfully\n", chip->name);
	return rc;

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

static int smb1355_remove(struct platform_device *pdev)
{
	platform_set_drvdata(pdev, NULL);
	return 0;
}

static struct platform_driver smb1355_driver = {
	.driver	= {
		.name		= "qcom,smb1355-charger",
		.owner		= THIS_MODULE,
		.of_match_table	= match_table,
	},
	.probe	= smb1355_probe,
	.remove	= smb1355_remove,
};
module_platform_driver(smb1355_driver);

MODULE_DESCRIPTION("QPNP SMB1355 Charger Driver");
MODULE_LICENSE("GPL v2");