1414 lines
		
	
	
		
			34 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1414 lines
		
	
	
		
			34 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| // SPDX-License-Identifier: GPL-2.0
 | |
| /*
 | |
|  * Chrager driver for Sgm4154x
 | |
|  *
 | |
|  * Copyright (c) 2022 Rockchip Electronics Co., Ltd.
 | |
|  *
 | |
|  * Author: Xu Shengfei <xsf@rock-chips.com>
 | |
|  */
 | |
| 
 | |
| #include <linux/delay.h>
 | |
| #include <linux/i2c.h>
 | |
| #include <linux/interrupt.h>
 | |
| #include <linux/module.h>
 | |
| #include <linux/power_supply.h>
 | |
| #include <linux/regmap.h>
 | |
| #include <linux/regulator/driver.h>
 | |
| #include <linux/regulator/of_regulator.h>
 | |
| #include <linux/regulator/machine.h>
 | |
| #include <linux/types.h>
 | |
| 
 | |
| static int dbg_enable;
 | |
| 
 | |
| module_param_named(dbg_level, dbg_enable, int, 0644);
 | |
| 
 | |
| #define DBG(args...) \
 | |
| 	do { \
 | |
| 		if (dbg_enable) { \
 | |
| 			pr_info(args); \
 | |
| 		} \
 | |
| 	} while (0)
 | |
| 
 | |
| #define SGM4154x_MANUFACTURER	"SGMICRO"
 | |
| #define SGM4154x_NAME		"sgm41542"
 | |
| #define SGM4154x_PN_ID		(BIT(6) | BIT(5) | BIT(3))
 | |
| 
 | |
| /* define register */
 | |
| #define SGM4154x_CHRG_CTRL_0	0x00
 | |
| #define SGM4154x_CHRG_CTRL_1	0x01
 | |
| #define SGM4154x_CHRG_CTRL_2	0x02
 | |
| #define SGM4154x_CHRG_CTRL_3	0x03
 | |
| #define SGM4154x_CHRG_CTRL_4	0x04
 | |
| #define SGM4154x_CHRG_CTRL_5	0x05
 | |
| #define SGM4154x_CHRG_CTRL_6	0x06
 | |
| #define SGM4154x_CHRG_CTRL_7	0x07
 | |
| #define SGM4154x_CHRG_STAT	0x08
 | |
| #define SGM4154x_CHRG_FAULT	0x09
 | |
| #define SGM4154x_CHRG_CTRL_a	0x0a
 | |
| #define SGM4154x_CHRG_CTRL_b	0x0b
 | |
| #define SGM4154x_CHRG_CTRL_c	0x0c
 | |
| #define SGM4154x_CHRG_CTRL_d	0x0d
 | |
| #define SGM4154x_INPUT_DET	0x0e
 | |
| #define SGM4154x_CHRG_CTRL_f	0x0f
 | |
| 
 | |
| /* charge status flags */
 | |
| #define SGM4154x_CHRG_EN		BIT(4)
 | |
| #define SGM4154x_HIZ_EN			BIT(7)
 | |
| #define SGM4154x_TERM_EN		BIT(7)
 | |
| #define SGM4154x_VAC_OVP_MASK		GENMASK(7, 6)
 | |
| #define SGM4154x_DPDM_ONGOING		BIT(7)
 | |
| #define SGM4154x_VBUS_GOOD		BIT(7)
 | |
| 
 | |
| #define SGM4154x_BOOSTV			GENMASK(5, 4)
 | |
| #define SGM4154x_BOOST_LIM		BIT(7)
 | |
| #define SGM4154x_OTG_EN			BIT(5)
 | |
| 
 | |
| /* Part ID */
 | |
| #define SGM4154x_PN_MASK		GENMASK(6, 3)
 | |
| 
 | |
| /* WDT TIMER SET */
 | |
| #define SGM4154x_WDT_TIMER_MASK		GENMASK(5, 4)
 | |
| #define SGM4154x_WDT_TIMER_DISABLE	0
 | |
| #define SGM4154x_WDT_TIMER_40S		BIT(4)
 | |
| #define SGM4154x_WDT_TIMER_80S		BIT(5)
 | |
| #define SGM4154x_WDT_TIMER_160S		(BIT(4) | BIT(5))
 | |
| 
 | |
| #define SGM4154x_WDT_RST_MASK		BIT(6)
 | |
| #define SGM4154x_WDT_RST		BIT(6)
 | |
| 
 | |
| /* SAFETY TIMER SET */
 | |
| #define SGM4154x_SAFETY_TIMER_MASK	GENMASK(3, 3)
 | |
| #define SGM4154x_SAFETY_TIMER_DISABLE	0
 | |
| #define SGM4154x_SAFETY_TIMER_EN	BIT(3)
 | |
| #define SGM4154x_SAFETY_TIMER_5H	0
 | |
| #define SGM4154x_SAFETY_TIMER_10H	BIT(2)
 | |
| 
 | |
| /* recharge voltage */
 | |
| #define SGM4154x_VRECHARGE		BIT(0)
 | |
| #define SGM4154x_VRECHRG_STEP_mV	100
 | |
| #define SGM4154x_VRECHRG_OFFSET_mV	100
 | |
| 
 | |
| /* charge status */
 | |
| #define SGM4154x_VSYS_STAT		BIT(0)
 | |
| #define SGM4154x_THERM_STAT		BIT(1)
 | |
| #define SGM4154x_PG_STAT		BIT(2)
 | |
| #define SGM4154x_CHG_STAT_MASK		GENMASK(4, 3)
 | |
| #define SGM4154x_PRECHRG		BIT(3)
 | |
| #define SGM4154x_FAST_CHRG		BIT(4)
 | |
| #define SGM4154x_TERM_CHRG		(BIT(3) | BIT(4))
 | |
| 
 | |
| /* charge type */
 | |
| #define SGM4154x_VBUS_STAT_MASK		GENMASK(7, 5)
 | |
| #define SGM4154x_NOT_CHRGING		0
 | |
| #define SGM4154x_USB_SDP		BIT(5)
 | |
| #define SGM4154x_USB_CDP		BIT(6)
 | |
| #define SGM4154x_USB_DCP		(BIT(5) | BIT(6))
 | |
| #define SGM4154x_UNKNOWN		(BIT(7) | BIT(5))
 | |
| #define SGM4154x_NON_STANDARD		(BIT(7) | BIT(6))
 | |
| #define SGM4154x_OTG_MODE		(BIT(7) | BIT(6) | BIT(5))
 | |
| 
 | |
| /* TEMP Status */
 | |
| #define SGM4154x_TEMP_MASK			GENMASK(2, 0)
 | |
| #define SGM4154x_TEMP_NORMAL			BIT(0)
 | |
| #define SGM4154x_TEMP_WARM			BIT(1)
 | |
| #define SGM4154x_TEMP_COOL			(BIT(0) | BIT(1))
 | |
| #define SGM4154x_TEMP_COLD			(BIT(0) | BIT(3))
 | |
| #define SGM4154x_TEMP_HOT			(BIT(2) | BIT(3))
 | |
| 
 | |
| /* precharge current */
 | |
| #define SGM4154x_PRECHRG_CUR_MASK		GENMASK(7, 4)
 | |
| #define SGM4154x_PRECHRG_CURRENT_STEP_uA	60000
 | |
| #define SGM4154x_PRECHRG_I_MIN_uA		60000
 | |
| #define SGM4154x_PRECHRG_I_MAX_uA		780000
 | |
| #define SGM4154x_PRECHRG_I_DEF_uA		180000
 | |
| 
 | |
| /* termination current */
 | |
| #define SGM4154x_TERMCHRG_CUR_MASK		GENMASK(3, 0)
 | |
| #define SGM4154x_TERMCHRG_CURRENT_STEP_uA	60000
 | |
| #define SGM4154x_TERMCHRG_I_MIN_uA		60000
 | |
| #define SGM4154x_TERMCHRG_I_MAX_uA		960000
 | |
| #define SGM4154x_TERMCHRG_I_DEF_uA		180000
 | |
| 
 | |
| /* charge current */
 | |
| #define SGM4154x_ICHRG_CUR_MASK		GENMASK(5, 0)
 | |
| #define SGM4154x_ICHRG_I_STEP_uA	60000
 | |
| #define SGM4154x_ICHRG_I_MIN_uA		0
 | |
| #define SGM4154x_ICHRG_I_MAX_uA		3000000
 | |
| #define SGM4154x_ICHRG_I_DEF_uA		2040000
 | |
| 
 | |
| /* charge voltage */
 | |
| #define SGM4154x_VREG_V_MASK		GENMASK(7, 3)
 | |
| #define SGM4154x_VREG_V_MAX_uV		4624000
 | |
| #define SGM4154x_VREG_V_MIN_uV		3856000
 | |
| #define SGM4154x_VREG_V_DEF_uV		4208000
 | |
| #define SGM4154x_VREG_V_STEP_uV		32000
 | |
| 
 | |
| /* VREG Fine Tuning */
 | |
| #define SGM4154x_VREG_FT_MASK		GENMASK(7, 6)
 | |
| #define SGM4154x_VREG_FT_UP_8mV		BIT(6)
 | |
| #define SGM4154x_VREG_FT_DN_8mV		BIT(7)
 | |
| #define SGM4154x_VREG_FT_DN_16mV	(BIT(7) | BIT(6))
 | |
| 
 | |
| /* iindpm current */
 | |
| #define SGM4154x_IINDPM_I_MASK		GENMASK(4, 0)
 | |
| #define SGM4154x_IINDPM_I_MIN_uA	100000
 | |
| #define SGM4154x_IINDPM_I_MAX_uA	3800000
 | |
| #define SGM4154x_IINDPM_STEP_uA		100000
 | |
| #define SGM4154x_IINDPM_DEF_uA		2400000
 | |
| 
 | |
| #define SGM4154x_VINDPM_INT_MASK	BIT(1)
 | |
| #define SGM4154x_VINDPM_INT_DIS		BIT(1)
 | |
| #define SGM4154x_IINDPM_INT_MASK	BIT(0)
 | |
| #define SGM4154x_IINDPM_INT_DIS		BIT(0)
 | |
| 
 | |
| /* vindpm voltage */
 | |
| #define SGM4154x_VINDPM_V_MASK		GENMASK(3, 0)
 | |
| #define SGM4154x_VINDPM_V_MIN_uV	3900000
 | |
| #define SGM4154x_VINDPM_V_MAX_uV	12000000
 | |
| #define SGM4154x_VINDPM_STEP_uV		100000
 | |
| #define SGM4154x_VINDPM_DEF_uV		4500000
 | |
| #define SGM4154x_VINDPM_OS_MASK		GENMASK(1, 0)
 | |
| 
 | |
| /* DP DM SEL */
 | |
| #define SGM4154x_DP_VSEL_MASK		GENMASK(4, 3)
 | |
| #define SGM4154x_DM_VSEL_MASK		GENMASK(2, 1)
 | |
| 
 | |
| /* PUMPX SET */
 | |
| #define SGM4154x_EN_PUMPX		BIT(7)
 | |
| #define SGM4154x_PUMPX_UP		BIT(6)
 | |
| #define SGM4154x_PUMPX_DN		BIT(5)
 | |
| 
 | |
| #define SGM4154x_BATFET_DIS_MSK		BIT(5)
 | |
| #define SGM4154x_BATFET_DIS_DIS		BIT(5)
 | |
| #define SGM4154x_BATFET_DLY_MSK		BIT(3)
 | |
| #define SGM4154x_BATFET_DLY_EN		BIT(3)
 | |
| 
 | |
| #define DEFAULT_INPUT_CURRENT		(500 * 1000)
 | |
| 
 | |
| struct sgm4154x_init_data {
 | |
| 	int ichg;	/* charge current */
 | |
| 	int ilim;	/* input current */
 | |
| 	int vreg;	/* regulation voltage */
 | |
| 	int iterm;	/* termination current */
 | |
| 	int iprechg;	/* precharge current */
 | |
| 	int vlim;	/* minimum system voltage limit */
 | |
| 	int max_ichg;
 | |
| 	int max_vreg;
 | |
| };
 | |
| 
 | |
| struct sgm4154x_state {
 | |
| 	bool vsys_stat;
 | |
| 	bool therm_stat;
 | |
| 	bool online;
 | |
| 	u8 chrg_stat;
 | |
| 	u8 vbus_status;
 | |
| 
 | |
| 	bool chrg_en;
 | |
| 	bool hiz_en;
 | |
| 	bool term_en;
 | |
| 	bool vbus_gd;
 | |
| 	u8 chrg_type;
 | |
| 	u8 health;
 | |
| 	u8 chrg_fault;
 | |
| 	u8 ntc_fault;
 | |
| };
 | |
| 
 | |
| struct sgm4154x_device {
 | |
| 	struct i2c_client *client;
 | |
| 	struct device *dev;
 | |
| 	struct power_supply *charger;
 | |
| 	struct mutex lock;
 | |
| 	struct mutex i2c_rw_lock;
 | |
| 	struct regmap *regmap;
 | |
| 
 | |
| 	char model_name[I2C_NAME_SIZE];
 | |
| 	int device_id;
 | |
| 
 | |
| 	struct sgm4154x_init_data init_data;
 | |
| 	struct sgm4154x_state state;
 | |
| 	struct regulator_dev *otg_rdev;
 | |
| 	struct notifier_block pm_nb;
 | |
| 	int input_current;
 | |
| 	bool sgm4154x_suspend_flag;
 | |
| 	bool watchdog_enable;
 | |
| 	struct workqueue_struct *sgm_monitor_wq;
 | |
| 	struct delayed_work sgm_delay_work;
 | |
| };
 | |
| 
 | |
| /* SGM4154x REG06 BOOST_LIM[5:4], uV */
 | |
| static const unsigned int BOOST_VOLT_LIMIT[] = {
 | |
| 	4850000, 5000000, 5150000, 5300000
 | |
| };
 | |
| 
 | |
| static const unsigned int BOOST_CURRENT_LIMIT[] = {
 | |
| 	1200000, 2000000
 | |
| };
 | |
| 
 | |
| enum SGM4154x_VINDPM_OS {
 | |
| 	VINDPM_OS_3900mV,
 | |
| 	VINDPM_OS_5900mV,
 | |
| 	VINDPM_OS_7500mV,
 | |
| 	VINDPM_OS_10500mV,
 | |
| };
 | |
| 
 | |
| static int sgm4154x_set_term_curr(struct sgm4154x_device *sgm, int uA)
 | |
| {
 | |
| 	int reg_val;
 | |
| 	int ret;
 | |
| 
 | |
| 	if (uA < SGM4154x_TERMCHRG_I_MIN_uA)
 | |
| 		uA = SGM4154x_TERMCHRG_I_MIN_uA;
 | |
| 	else if (uA > SGM4154x_TERMCHRG_I_MAX_uA)
 | |
| 		uA = SGM4154x_TERMCHRG_I_MAX_uA;
 | |
| 
 | |
| 	reg_val = (uA - SGM4154x_TERMCHRG_I_MIN_uA) / SGM4154x_TERMCHRG_CURRENT_STEP_uA;
 | |
| 
 | |
| 	ret = regmap_update_bits(sgm->regmap,
 | |
| 				 SGM4154x_CHRG_CTRL_3,
 | |
| 				 SGM4154x_TERMCHRG_CUR_MASK,
 | |
| 				 reg_val);
 | |
| 	if (ret)
 | |
| 		dev_err(sgm->dev, "set term current error!\n");
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static int sgm4154x_set_prechrg_curr(struct sgm4154x_device *sgm, int uA)
 | |
| {
 | |
| 	int reg_val;
 | |
| 	int ret;
 | |
| 
 | |
| 	if (uA < SGM4154x_PRECHRG_I_MIN_uA)
 | |
| 		uA = SGM4154x_PRECHRG_I_MIN_uA;
 | |
| 	else if (uA > SGM4154x_PRECHRG_I_MAX_uA)
 | |
| 		uA = SGM4154x_PRECHRG_I_MAX_uA;
 | |
| 
 | |
| 	reg_val = (uA - SGM4154x_PRECHRG_I_MIN_uA) / SGM4154x_PRECHRG_CURRENT_STEP_uA;
 | |
| 
 | |
| 	reg_val = reg_val << 4;
 | |
| 	ret = regmap_update_bits(sgm->regmap,
 | |
| 				 SGM4154x_CHRG_CTRL_3,
 | |
| 				 SGM4154x_PRECHRG_CUR_MASK,
 | |
| 				 reg_val);
 | |
| 	if (ret)
 | |
| 		dev_err(sgm->dev, "set precharge current error!\n");
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static int sgm4154x_set_ichrg_curr(struct sgm4154x_device *sgm, int uA)
 | |
| {
 | |
| 	int reg_val;
 | |
| 	int ret;
 | |
| 
 | |
| 	if (uA < SGM4154x_ICHRG_I_MIN_uA)
 | |
| 		uA = SGM4154x_ICHRG_I_MIN_uA;
 | |
| 	else {
 | |
| 		if ((sgm->init_data.max_ichg > 0) && (uA > sgm->init_data.max_ichg))
 | |
| 			uA = sgm->init_data.max_ichg;
 | |
| 		uA = min(uA, SGM4154x_ICHRG_I_MAX_uA);
 | |
| 	}
 | |
| 
 | |
| 	reg_val = uA / SGM4154x_ICHRG_I_STEP_uA;
 | |
| 
 | |
| 	ret = regmap_update_bits(sgm->regmap,
 | |
| 				 SGM4154x_CHRG_CTRL_2,
 | |
| 				 SGM4154x_ICHRG_CUR_MASK,
 | |
| 				 reg_val);
 | |
| 	if (ret)
 | |
| 		dev_err(sgm->dev, "set icharge current error!\n");
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static int sgm4154x_set_chrg_volt(struct sgm4154x_device *sgm, int chrg_volt)
 | |
| {
 | |
| 	int reg_val;
 | |
| 	int ret;
 | |
| 
 | |
| 	if (chrg_volt < SGM4154x_VREG_V_MIN_uV)
 | |
| 		chrg_volt = SGM4154x_VREG_V_MIN_uV;
 | |
| 	else {
 | |
| 		if ((sgm->init_data.max_vreg > 0) && (chrg_volt > sgm->init_data.max_vreg))
 | |
| 			chrg_volt = sgm->init_data.max_vreg;
 | |
| 		chrg_volt = min(chrg_volt, SGM4154x_VREG_V_MAX_uV);
 | |
| 	}
 | |
| 
 | |
| 	reg_val = (chrg_volt - SGM4154x_VREG_V_MIN_uV) / SGM4154x_VREG_V_STEP_uV;
 | |
| 	if (chrg_volt == 4200 * 1000)
 | |
| 		reg_val = 0x0b;
 | |
| 	reg_val = reg_val << 3;
 | |
| 	ret = regmap_update_bits(sgm->regmap,
 | |
| 				 SGM4154x_CHRG_CTRL_4,
 | |
| 				 SGM4154x_VREG_V_MASK,
 | |
| 				 reg_val);
 | |
| 	if (ret)
 | |
| 		dev_err(sgm->dev, "set charge voltage error!\n");
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static int sgm4154x_set_vindpm_offset_os(struct sgm4154x_device *sgm,
 | |
| 					 enum SGM4154x_VINDPM_OS offset_os)
 | |
| {
 | |
| 	int ret;
 | |
| 
 | |
| 	ret = regmap_update_bits(sgm->regmap,
 | |
| 				 SGM4154x_CHRG_CTRL_f,
 | |
| 				 SGM4154x_VINDPM_OS_MASK,
 | |
| 				 offset_os);
 | |
| 
 | |
| 	if (ret)
 | |
| 		dev_err(sgm->dev, "set vindpm offset os error!\n");
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static int sgm4154x_set_input_volt_lim(struct sgm4154x_device *sgm,
 | |
| 				       unsigned int vindpm)
 | |
| {
 | |
| 	enum SGM4154x_VINDPM_OS os_val;
 | |
| 	unsigned int offset;
 | |
| 	u8 reg_val;
 | |
| 	int ret;
 | |
| 
 | |
| 
 | |
| 	if (vindpm < SGM4154x_VINDPM_V_MIN_uV ||
 | |
| 	    vindpm > SGM4154x_VINDPM_V_MAX_uV)
 | |
| 		return -EINVAL;
 | |
| 
 | |
| 	if (vindpm < 5900000) {
 | |
| 		os_val = VINDPM_OS_3900mV;
 | |
| 		offset = 3900000;
 | |
| 	} else if (vindpm >= 5900000 && vindpm < 7500000) {
 | |
| 		os_val = VINDPM_OS_5900mV;
 | |
| 		offset = 5900000;
 | |
| 	} else if (vindpm >= 7500000 && vindpm < 10500000) {
 | |
| 		os_val = VINDPM_OS_7500mV;
 | |
| 		offset = 7500000;
 | |
| 	} else {
 | |
| 		os_val = VINDPM_OS_10500mV;
 | |
| 		offset = 10500000;
 | |
| 	}
 | |
| 
 | |
| 	ret = sgm4154x_set_vindpm_offset_os(sgm, os_val);
 | |
| 	if (ret) {
 | |
| 		dev_err(sgm->dev, "set vin dpm error!\n");
 | |
| 		return ret;
 | |
| 	}
 | |
| 
 | |
| 	reg_val = (vindpm - offset) / SGM4154x_VINDPM_STEP_uV;
 | |
| 
 | |
| 	ret = regmap_update_bits(sgm->regmap, SGM4154x_CHRG_CTRL_6,
 | |
| 				 SGM4154x_VINDPM_V_MASK, reg_val);
 | |
| 	if (ret) {
 | |
| 		dev_err(sgm->dev, "input voltage error!\n");
 | |
| 		return ret;
 | |
| 	}
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static int sgm4154x_set_input_curr_lim(struct sgm4154x_device *sgm, int iindpm)
 | |
| {
 | |
| 	int reg_val;
 | |
| 	int ret;
 | |
| 
 | |
| 	if (iindpm < SGM4154x_IINDPM_I_MIN_uA)
 | |
| 		return -EINVAL;
 | |
| 
 | |
| 	if ((iindpm > SGM4154x_IINDPM_I_MAX_uA) || (iindpm > sgm->init_data.ilim))
 | |
| 		iindpm = min(SGM4154x_IINDPM_I_MAX_uA, sgm->init_data.ilim);
 | |
| 
 | |
| 	sgm->input_current = iindpm;
 | |
| 	reg_val = (iindpm-SGM4154x_IINDPM_I_MIN_uA) / SGM4154x_IINDPM_STEP_uA;
 | |
| 
 | |
| 	ret = regmap_update_bits(sgm->regmap,
 | |
| 				 SGM4154x_CHRG_CTRL_0,
 | |
| 				 SGM4154x_IINDPM_I_MASK,
 | |
| 				 reg_val);
 | |
| 	if (ret)
 | |
| 		dev_err(sgm->dev, "set input current limit error!\n");
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static int sgm4154x_get_input_curr_lim(struct sgm4154x_device *sgm)
 | |
| {
 | |
| 	int ret;
 | |
| 	int ilim;
 | |
| 
 | |
| 	ret = regmap_read(sgm->regmap, SGM4154x_CHRG_CTRL_0, &ilim);
 | |
| 	if (ret) {
 | |
| 		dev_err(sgm->dev, "get input current limit error!\n");
 | |
| 		return ret;
 | |
| 	}
 | |
| 	if (SGM4154x_IINDPM_I_MASK == (ilim & SGM4154x_IINDPM_I_MASK))
 | |
| 		return SGM4154x_IINDPM_I_MAX_uA;
 | |
| 
 | |
| 	ilim = (ilim & SGM4154x_IINDPM_I_MASK) * SGM4154x_IINDPM_STEP_uA + SGM4154x_IINDPM_I_MIN_uA;
 | |
| 
 | |
| 	return ilim;
 | |
| }
 | |
| 
 | |
| static int sgm4154x_watchdog_timer_reset(struct sgm4154x_device *sgm)
 | |
| {
 | |
| 	int ret;
 | |
| 
 | |
| 	ret = regmap_update_bits(sgm->regmap,
 | |
| 				 SGM4154x_CHRG_CTRL_1,
 | |
| 				 SGM4154x_WDT_RST_MASK,
 | |
| 				 SGM4154x_WDT_RST);
 | |
| 
 | |
| 	if (ret)
 | |
| 		dev_err(sgm->dev, "set watchdog timer error!\n");
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static int sgm4154x_set_watchdog_timer(struct sgm4154x_device *sgm, int time)
 | |
| {
 | |
| 	u8 reg_val;
 | |
| 	int ret;
 | |
| 
 | |
| 	if (time == 0)
 | |
| 		reg_val = SGM4154x_WDT_TIMER_DISABLE;
 | |
| 	else if (time == 40)
 | |
| 		reg_val = SGM4154x_WDT_TIMER_40S;
 | |
| 	else if (time == 80)
 | |
| 		reg_val = SGM4154x_WDT_TIMER_80S;
 | |
| 	else
 | |
| 		reg_val = SGM4154x_WDT_TIMER_160S;
 | |
| 
 | |
| 	ret = regmap_update_bits(sgm->regmap,
 | |
| 				 SGM4154x_CHRG_CTRL_5,
 | |
| 				 SGM4154x_WDT_TIMER_MASK,
 | |
| 				 reg_val);
 | |
| 
 | |
| 	if (ret) {
 | |
| 		dev_err(sgm->dev, "set watchdog timer error!\n");
 | |
| 		return ret;
 | |
| 	}
 | |
| 
 | |
| 	if (time) {
 | |
| 		DBG("sgm41542: enable watchdog\n");
 | |
| 		if (!sgm->watchdog_enable)
 | |
| 			queue_delayed_work(sgm->sgm_monitor_wq,
 | |
| 					   &sgm->sgm_delay_work,
 | |
| 					   msecs_to_jiffies(1000 * 5));
 | |
| 		sgm->watchdog_enable = true;
 | |
| 	} else {
 | |
| 		DBG("sgm41542: disable watchdog\n");
 | |
| 		sgm->watchdog_enable = false;
 | |
| 		sgm4154x_watchdog_timer_reset(sgm);
 | |
| 	}
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static int sgm4154x_enable_charger(struct sgm4154x_device *sgm)
 | |
| {
 | |
| 	int ret;
 | |
| 
 | |
| 	ret = regmap_update_bits(sgm->regmap,
 | |
| 				 SGM4154x_CHRG_CTRL_1,
 | |
| 				 SGM4154x_CHRG_EN,
 | |
| 				 SGM4154x_CHRG_EN);
 | |
| 	if (ret)
 | |
| 		dev_err(sgm->dev, "enable charger error!\n");
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static int sgm4154x_disable_charger(struct sgm4154x_device *sgm)
 | |
| {
 | |
| 	int ret;
 | |
| 
 | |
| 	ret = regmap_update_bits(sgm->regmap,
 | |
| 				 SGM4154x_CHRG_CTRL_1,
 | |
| 				 SGM4154x_CHRG_EN,
 | |
| 				 0);
 | |
| 	if (ret)
 | |
| 		dev_err(sgm->dev, "disable charger error!\n");
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static int sgm4154x_set_vac_ovp(struct sgm4154x_device *sgm)
 | |
| {
 | |
| 	int reg_val;
 | |
| 	int ret;
 | |
| 
 | |
| 	reg_val = 0xFF & SGM4154x_VAC_OVP_MASK;
 | |
| 
 | |
| 	ret = regmap_update_bits(sgm->regmap,
 | |
| 				 SGM4154x_CHRG_CTRL_6,
 | |
| 				 SGM4154x_VAC_OVP_MASK,
 | |
| 				 reg_val);
 | |
| 	if (ret)
 | |
| 		dev_err(sgm->dev, "set vac ovp error!\n");
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static int sgm4154x_set_recharge_volt(struct sgm4154x_device *sgm, int recharge_volt)
 | |
| {
 | |
| 	int reg_val;
 | |
| 	int ret;
 | |
| 
 | |
| 	reg_val = (recharge_volt - SGM4154x_VRECHRG_OFFSET_mV) / SGM4154x_VRECHRG_STEP_mV;
 | |
| 
 | |
| 	ret = regmap_update_bits(sgm->regmap,
 | |
| 				 SGM4154x_CHRG_CTRL_4,
 | |
| 				 SGM4154x_VRECHARGE,
 | |
| 				 reg_val);
 | |
| 	if (ret)
 | |
| 		dev_err(sgm->dev, "set recharger error!\n");
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static int sgm4154x_get_state(struct sgm4154x_device *sgm,
 | |
| 			      struct sgm4154x_state *state)
 | |
| {
 | |
| 	int chrg_param_0, chrg_param_1, chrg_param_2;
 | |
| 	int chrg_stat;
 | |
| 	int fault;
 | |
| 	int ret;
 | |
| 
 | |
| 	ret = regmap_read(sgm->regmap, SGM4154x_CHRG_STAT, &chrg_stat);
 | |
| 	if (ret) {
 | |
| 		pr_err("read SGM4154x_CHRG_STAT fail\n");
 | |
| 		return ret;
 | |
| 	}
 | |
| 
 | |
| 	DBG("SGM4154x_CHRG_STAT[0x%x]: 0x%x\n", SGM4154x_CHRG_STAT, chrg_stat);
 | |
| 	state->chrg_type = chrg_stat & SGM4154x_VBUS_STAT_MASK;
 | |
| 	state->chrg_stat = chrg_stat & SGM4154x_CHG_STAT_MASK;
 | |
| 	state->online = !!(chrg_stat & SGM4154x_PG_STAT);
 | |
| 	state->therm_stat = !!(chrg_stat & SGM4154x_THERM_STAT);
 | |
| 	state->vsys_stat = !!(chrg_stat & SGM4154x_VSYS_STAT);
 | |
| 
 | |
| 	ret = regmap_read(sgm->regmap, SGM4154x_CHRG_FAULT, &fault);
 | |
| 	if (ret) {
 | |
| 		pr_err("read SGM4154x_CHRG_FAULT fail\n");
 | |
| 		return ret;
 | |
| 	}
 | |
| 	DBG("SGM4154x_CHRG_FAULT[0x%x]: 0x%x\n", SGM4154x_CHRG_FAULT, fault);
 | |
| 
 | |
| 	state->chrg_fault = fault;
 | |
| 	state->ntc_fault = fault & SGM4154x_TEMP_MASK;
 | |
| 	state->health = state->ntc_fault;
 | |
| 	ret = regmap_read(sgm->regmap, SGM4154x_CHRG_CTRL_0, &chrg_param_0);
 | |
| 	if (ret) {
 | |
| 		pr_err("read SGM4154x_CHRG_CTRL_0 fail\n");
 | |
| 		return ret;
 | |
| 	}
 | |
| 	state->hiz_en = !!(chrg_param_0 & SGM4154x_HIZ_EN);
 | |
| 	DBG("SGM4154x_CHRG_CTRL_0[0x%x]: 0x%x\n", SGM4154x_CHRG_CTRL_0, chrg_param_0);
 | |
| 
 | |
| 	ret = regmap_read(sgm->regmap, SGM4154x_CHRG_CTRL_5, &chrg_param_1);
 | |
| 	if (ret) {
 | |
| 		pr_err("read SGM4154x_CHRG_CTRL_5 fail\n");
 | |
| 		return ret;
 | |
| 	}
 | |
| 	state->term_en = !!(chrg_param_1 & SGM4154x_TERM_EN);
 | |
| 	DBG("SGM4154x_CHRG_CTRL_5[0x%x]: 0x%x\n", SGM4154x_CHRG_CTRL_5, chrg_param_1);
 | |
| 
 | |
| 	ret = regmap_read(sgm->regmap, SGM4154x_CHRG_CTRL_a, &chrg_param_2);
 | |
| 	if (ret) {
 | |
| 		pr_err("read SGM4154x_CHRG_CTRL_a fail\n");
 | |
| 		return ret;
 | |
| 	}
 | |
| 	state->vbus_gd = !!(chrg_param_2 & SGM4154x_VBUS_GOOD);
 | |
| 	DBG("SGM4154x_CHRG_CTRL_a[0x%x]: 0x%x\n", SGM4154x_CHRG_CTRL_a, chrg_param_2);
 | |
| 
 | |
| 	DBG("chrg_type: 0x%x\n", state->chrg_type);
 | |
| 	DBG("chrg_stat: 0x%x\n", state->chrg_stat);
 | |
| 	DBG("online: 0x%x\n", state->online);
 | |
| 	DBG("therm_stat: 0x%x\n", state->therm_stat);
 | |
| 	DBG("vsys_stat: 0x%x\n", state->vsys_stat);
 | |
| 	DBG("chrg_fault: 0x%x\n", state->chrg_fault);
 | |
| 	DBG("ntc_fault: 0x%x\n", state->ntc_fault);
 | |
| 	DBG("health: 0x%x\n", state->health);
 | |
| 	DBG("hiz_en: 0x%x\n", state->hiz_en);
 | |
| 	DBG("term_en: 0x%x\n", state->term_en);
 | |
| 	DBG("vbus_gd: 0x%x\n", state->vbus_gd);
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static int sgm4154x_property_is_writeable(struct power_supply *psy,
 | |
| 					  enum power_supply_property prop)
 | |
| {
 | |
| 	switch (prop) {
 | |
| 	case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
 | |
| 	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
 | |
| 	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
 | |
| 	case POWER_SUPPLY_PROP_ONLINE:
 | |
| 		return true;
 | |
| 	default:
 | |
| 		return false;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static int sgm4154x_charger_set_property(struct power_supply *psy,
 | |
| 					 enum power_supply_property prop,
 | |
| 					 const union power_supply_propval *val)
 | |
| {
 | |
| 	struct sgm4154x_device *sgm = power_supply_get_drvdata(psy);
 | |
| 	int ret = -EINVAL;
 | |
| 
 | |
| 	switch (prop) {
 | |
| 	case POWER_SUPPLY_PROP_ONLINE:
 | |
| 		if (val->intval) {
 | |
| 			ret = sgm4154x_enable_charger(sgm);
 | |
| 			sgm4154x_set_watchdog_timer(sgm, SGM4154x_WDT_TIMER_40S);
 | |
| 		} else {
 | |
| 			sgm4154x_set_watchdog_timer(sgm, 0);
 | |
| 			ret = sgm4154x_disable_charger(sgm);
 | |
| 		}
 | |
| 		break;
 | |
| 
 | |
| 	case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
 | |
| 		ret = sgm4154x_set_input_curr_lim(sgm, val->intval);
 | |
| 		break;
 | |
| 	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
 | |
| 		ret = sgm4154x_set_ichrg_curr(sgm, val->intval);
 | |
| 		break;
 | |
| 	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
 | |
| 		ret = sgm4154x_set_chrg_volt(sgm, val->intval);
 | |
| 		break;
 | |
| 
 | |
| 	default:
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static int sgm4154x_charger_get_property(struct power_supply *psy,
 | |
| 					 enum power_supply_property psp,
 | |
| 					 union power_supply_propval *val)
 | |
| {
 | |
| 	struct sgm4154x_device *sgm = power_supply_get_drvdata(psy);
 | |
| 	struct sgm4154x_state state;
 | |
| 	int ret = 0;
 | |
| 
 | |
| 	mutex_lock(&sgm->lock);
 | |
| 	ret = sgm4154x_get_state(sgm, &state);
 | |
| 	if (ret) {
 | |
| 		dev_err(sgm->dev, "get state error!\n");
 | |
| 		mutex_unlock(&sgm->lock);
 | |
| 		return ret;
 | |
| 	}
 | |
| 	sgm->state = state;
 | |
| 	mutex_unlock(&sgm->lock);
 | |
| 
 | |
| 	switch (psp) {
 | |
| 	case POWER_SUPPLY_PROP_STATUS:
 | |
| 		if (!state.chrg_type || (state.chrg_type == SGM4154x_OTG_MODE))
 | |
| 			val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
 | |
| 		else if (!state.chrg_stat)
 | |
| 			val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
 | |
| 		else if (state.chrg_stat == SGM4154x_TERM_CHRG)
 | |
| 			val->intval = POWER_SUPPLY_STATUS_FULL;
 | |
| 		else
 | |
| 			val->intval = POWER_SUPPLY_STATUS_CHARGING;
 | |
| 		break;
 | |
| 	case POWER_SUPPLY_PROP_CHARGE_TYPE:
 | |
| 		switch (state.chrg_stat) {
 | |
| 		case SGM4154x_PRECHRG:
 | |
| 			val->intval = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
 | |
| 			break;
 | |
| 		case SGM4154x_FAST_CHRG:
 | |
| 			val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST;
 | |
| 			break;
 | |
| 		case SGM4154x_TERM_CHRG:
 | |
| 			val->intval = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
 | |
| 			break;
 | |
| 		case SGM4154x_NOT_CHRGING:
 | |
| 			val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE;
 | |
| 			break;
 | |
| 		default:
 | |
| 			val->intval = POWER_SUPPLY_CHARGE_TYPE_UNKNOWN;
 | |
| 		}
 | |
| 		break;
 | |
| 	case POWER_SUPPLY_PROP_MANUFACTURER:
 | |
| 		val->strval = SGM4154x_MANUFACTURER;
 | |
| 		break;
 | |
| 
 | |
| 	case POWER_SUPPLY_PROP_MODEL_NAME:
 | |
| 		val->strval = SGM4154x_NAME;
 | |
| 		break;
 | |
| 
 | |
| 	case POWER_SUPPLY_PROP_ONLINE:
 | |
| 		val->intval = state.online;
 | |
| 		break;
 | |
| 	case POWER_SUPPLY_PROP_PRESENT:
 | |
| 		val->intval = state.vbus_gd;
 | |
| 		break;
 | |
| 	case POWER_SUPPLY_PROP_TYPE:
 | |
| 		val->intval = POWER_SUPPLY_TYPE_USB;
 | |
| 		break;
 | |
| 	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
 | |
| 		val->intval = sgm->init_data.max_vreg;
 | |
| 		break;
 | |
| 	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
 | |
| 		val->intval = SGM4154x_ICHRG_I_MAX_uA;
 | |
| 		break;
 | |
| 	case POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT:
 | |
| 		val->intval = 12 * 1000 * 1000;
 | |
| 		break;
 | |
| 
 | |
| 	case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
 | |
| 		 val->intval = sgm4154x_get_input_curr_lim(sgm);
 | |
| 		if (val->intval < 0)
 | |
| 			return  -EINVAL;
 | |
| 		break;
 | |
| 	default:
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static ssize_t registers_show(struct device *dev,
 | |
| 			      struct device_attribute *attr,
 | |
| 			      char *buf)
 | |
| {
 | |
| 	struct sgm4154x_device *sgm4154x = dev_get_drvdata(dev);
 | |
| 	u8 tmpbuf[30];
 | |
| 	int idx = 0;
 | |
| 	u8 addr;
 | |
| 	int val;
 | |
| 	int len;
 | |
| 	int ret;
 | |
| 
 | |
| 	for (addr = 0x0; addr <= SGM4154x_CHRG_CTRL_f; addr++) {
 | |
| 		ret = regmap_read(sgm4154x->regmap, addr, &val);
 | |
| 		if (ret == 0) {
 | |
| 			len = snprintf(tmpbuf, 30,
 | |
| 				       "Reg[%.2X] = 0x%.2x\n",
 | |
| 				       addr,
 | |
| 				       val);
 | |
| 			memcpy(&buf[idx], tmpbuf, len);
 | |
| 			idx += len;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return idx;
 | |
| }
 | |
| 
 | |
| static ssize_t registers_store(struct device *dev,
 | |
| 			       struct device_attribute *attr,
 | |
| 			       const char *buf, size_t count)
 | |
| {
 | |
| 	struct sgm4154x_device *sgm4154x = dev_get_drvdata(dev);
 | |
| 	unsigned int reg;
 | |
| 	int ret;
 | |
| 	int val;
 | |
| 
 | |
| 	ret = sscanf(buf, "%x %x", ®, &val);
 | |
| 	if (ret == 2 && reg <= SGM4154x_CHRG_CTRL_f)
 | |
| 		regmap_write(sgm4154x->regmap, (unsigned char)reg, val);
 | |
| 
 | |
| 	return count;
 | |
| }
 | |
| static DEVICE_ATTR_RW(registers);
 | |
| 
 | |
| static void sgm4154x_create_device_node(struct device *dev)
 | |
| {
 | |
| 	device_create_file(dev, &dev_attr_registers);
 | |
| }
 | |
| 
 | |
| static irqreturn_t sgm4154x_irq_handler_thread(int irq, void *private)
 | |
| {
 | |
| 	struct sgm4154x_device *sgm4154x = private;
 | |
| 	struct sgm4154x_state state;
 | |
| 	int ret;
 | |
| 	u8 addr;
 | |
| 	int val;
 | |
| 
 | |
| 	for (addr = 0x0; addr <= SGM4154x_CHRG_CTRL_f; addr++) {
 | |
| 		ret = regmap_read(sgm4154x->regmap, addr, &val);
 | |
| 		if (ret)
 | |
| 			dev_err(sgm4154x->dev, "read addr[0x%x] error!\n", addr);
 | |
| 		DBG("[0x%x]: 0x%x\n", addr, val);
 | |
| 	}
 | |
| 	ret = sgm4154x_get_state(sgm4154x, &state);
 | |
| 	if (ret) {
 | |
| 		dev_err(sgm4154x->dev, "get state error!\n");
 | |
| 		return IRQ_NONE;
 | |
| 	}
 | |
| 	sgm4154x->state = state;
 | |
| 	if (state.vbus_gd) {
 | |
| 		if (sgm4154x->input_current >= DEFAULT_INPUT_CURRENT) {
 | |
| 			ret = sgm4154x_set_input_curr_lim(sgm4154x, sgm4154x->input_current);
 | |
| 			if (ret) {
 | |
| 				dev_err(sgm4154x->dev, "set input current error!\n");
 | |
| 				return IRQ_NONE;
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	power_supply_changed(sgm4154x->charger);
 | |
| 
 | |
| 	return IRQ_HANDLED;
 | |
| }
 | |
| 
 | |
| static enum power_supply_property sgm4154x_power_supply_props[] = {
 | |
| 	POWER_SUPPLY_PROP_TYPE,
 | |
| 	POWER_SUPPLY_PROP_MANUFACTURER,
 | |
| 	POWER_SUPPLY_PROP_MODEL_NAME,
 | |
| 	POWER_SUPPLY_PROP_STATUS,
 | |
| 	POWER_SUPPLY_PROP_ONLINE,
 | |
| 	POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT,
 | |
| 	POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT,
 | |
| 	POWER_SUPPLY_PROP_CHARGE_TYPE,
 | |
| 	POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE,
 | |
| 	POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
 | |
| 	POWER_SUPPLY_PROP_PRESENT
 | |
| };
 | |
| 
 | |
| static char *sgm4154x_charger_supplied_to[] = {
 | |
| 	"usb",
 | |
| };
 | |
| 
 | |
| static struct power_supply_desc sgm4154x_power_supply_desc = {
 | |
| 	.name = "sgm4154x-charger",
 | |
| 	.type = POWER_SUPPLY_TYPE_USB,
 | |
| 	.properties = sgm4154x_power_supply_props,
 | |
| 	.num_properties = ARRAY_SIZE(sgm4154x_power_supply_props),
 | |
| 	.get_property = sgm4154x_charger_get_property,
 | |
| 	.set_property = sgm4154x_charger_set_property,
 | |
| 	.property_is_writeable = sgm4154x_property_is_writeable,
 | |
| };
 | |
| 
 | |
| static bool sgm4154x_is_volatile_reg(struct device *dev, unsigned int reg)
 | |
| {
 | |
| 	switch (reg) {
 | |
| 	case SGM4154x_CHRG_CTRL_0 ... SGM4154x_CHRG_CTRL_f:
 | |
| 		return true;
 | |
| 	default:
 | |
| 		return false;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static const struct regmap_config sgm4154x_regmap_config = {
 | |
| 	.reg_bits = 8,
 | |
| 	.val_bits = 8,
 | |
| 
 | |
| 	.max_register = SGM4154x_CHRG_CTRL_f,
 | |
| 
 | |
| 	.cache_type = REGCACHE_RBTREE,
 | |
| 	.volatile_reg = sgm4154x_is_volatile_reg,
 | |
| };
 | |
| 
 | |
| static int sgm4154x_power_supply_init(struct sgm4154x_device *sgm,
 | |
| 				      struct device *dev)
 | |
| {
 | |
| 	struct power_supply_config psy_cfg = { .drv_data = sgm,
 | |
| 					       .of_node = dev->of_node, };
 | |
| 
 | |
| 	psy_cfg.supplied_to = sgm4154x_charger_supplied_to;
 | |
| 	psy_cfg.num_supplicants = ARRAY_SIZE(sgm4154x_charger_supplied_to);
 | |
| 	psy_cfg.of_node = dev->of_node;
 | |
| 	sgm->charger = devm_power_supply_register(sgm->dev,
 | |
| 						  &sgm4154x_power_supply_desc,
 | |
| 						  &psy_cfg);
 | |
| 	if (IS_ERR(sgm->charger))
 | |
| 		return -EINVAL;
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int sgm4154x_hw_init(struct sgm4154x_device *sgm)
 | |
| {
 | |
| 	struct power_supply_battery_info *bat_info;
 | |
| 	int chrg_stat, ret = 0;
 | |
| 
 | |
| 	ret = power_supply_get_battery_info(sgm->charger, &bat_info);
 | |
| 	if (ret) {
 | |
| 		/* Allocate an empty battery */
 | |
| 		bat_info = devm_kzalloc(sgm->dev, sizeof(*bat_info), GFP_KERNEL);
 | |
| 		if (!bat_info)
 | |
| 			return -ENOMEM;
 | |
| 		pr_info("sgm4154x: no battery information is supplied\n");
 | |
| 		/*
 | |
| 		 * If no battery information is supplied, we should set
 | |
| 		 * default charge termination current to 120 mA, and default
 | |
| 		 * charge termination voltage to 4.35V.
 | |
| 		 */
 | |
| 		bat_info->constant_charge_current_max_ua =
 | |
| 			SGM4154x_ICHRG_I_DEF_uA;
 | |
| 		bat_info->constant_charge_voltage_max_uv =
 | |
| 			SGM4154x_VREG_V_DEF_uV;
 | |
| 		bat_info->precharge_current_ua =
 | |
| 			SGM4154x_PRECHRG_I_DEF_uA;
 | |
| 		bat_info->charge_term_current_ua =
 | |
| 			SGM4154x_TERMCHRG_I_DEF_uA;
 | |
| 		sgm->init_data.max_ichg =
 | |
| 			SGM4154x_ICHRG_I_MAX_uA;
 | |
| 		sgm->init_data.max_vreg = SGM4154x_VREG_V_DEF_uV;
 | |
| 	}
 | |
| 	if (!bat_info->constant_charge_current_max_ua)
 | |
| 		bat_info->constant_charge_current_max_ua =
 | |
| 					SGM4154x_ICHRG_I_MAX_uA;
 | |
| 	if (!bat_info->constant_charge_voltage_max_uv)
 | |
| 		bat_info->constant_charge_voltage_max_uv =
 | |
| 			SGM4154x_VREG_V_DEF_uV;
 | |
| 	if (!bat_info->precharge_current_ua)
 | |
| 		bat_info->precharge_current_ua =
 | |
| 			SGM4154x_PRECHRG_I_DEF_uA;
 | |
| 	if (!bat_info->charge_term_current_ua)
 | |
| 		bat_info->charge_term_current_ua =
 | |
| 			SGM4154x_TERMCHRG_I_DEF_uA;
 | |
| 	if (!sgm->init_data.max_ichg)
 | |
| 		sgm->init_data.max_ichg =
 | |
| 			SGM4154x_ICHRG_I_MAX_uA;
 | |
| 
 | |
| 	if (bat_info->constant_charge_voltage_max_uv)
 | |
| 		sgm->init_data.max_vreg = bat_info->constant_charge_voltage_max_uv;
 | |
| 
 | |
| 	ret = sgm4154x_set_watchdog_timer(sgm, 0);
 | |
| 	if (ret)
 | |
| 		goto err_out;
 | |
| 
 | |
| 
 | |
| 	ret = sgm4154x_set_prechrg_curr(sgm, bat_info->precharge_current_ua);
 | |
| 	if (ret)
 | |
| 		goto err_out;
 | |
| 
 | |
| 	ret = sgm4154x_set_chrg_volt(sgm,
 | |
| 				     sgm->init_data.max_vreg);
 | |
| 	if (ret)
 | |
| 		goto err_out;
 | |
| 
 | |
| 	ret = sgm4154x_set_term_curr(sgm,
 | |
| 				     bat_info->charge_term_current_ua);
 | |
| 	if (ret)
 | |
| 		goto err_out;
 | |
| 
 | |
| 	ret = sgm4154x_set_input_volt_lim(sgm, sgm->init_data.vlim);
 | |
| 	if (ret)
 | |
| 		goto err_out;
 | |
| 
 | |
| 	ret = regmap_read(sgm->regmap, SGM4154x_CHRG_STAT, &chrg_stat);
 | |
| 	if (ret) {
 | |
| 		pr_err("read SGM4154x_CHRG_STAT fail\n");
 | |
| 		goto err_out;
 | |
| 	}
 | |
| 
 | |
| 	if (!(chrg_stat & SGM4154x_PG_STAT)) {
 | |
| 		ret = sgm4154x_set_input_curr_lim(sgm, DEFAULT_INPUT_CURRENT);
 | |
| 		if (ret)
 | |
| 			goto err_out;
 | |
| 		ret = sgm4154x_set_ichrg_curr(sgm,
 | |
| 			      bat_info->constant_charge_current_max_ua);
 | |
| 		if (ret)
 | |
| 			goto err_out;
 | |
| 
 | |
| 		ret = sgm4154x_disable_charger(sgm);
 | |
| 		if (ret)
 | |
| 			goto err_out;
 | |
| 	}
 | |
| 	ret = sgm4154x_set_vac_ovp(sgm);
 | |
| 	if (ret)
 | |
| 		goto err_out;
 | |
| 
 | |
| 	regmap_update_bits(sgm->regmap,
 | |
| 			   SGM4154x_CHRG_CTRL_d,
 | |
| 			   0x01,
 | |
| 			   0x00);
 | |
| 
 | |
| 	regmap_update_bits(sgm->regmap,
 | |
| 			   SGM4154x_CHRG_CTRL_a,
 | |
| 			   SGM4154x_IINDPM_INT_MASK,
 | |
| 			   SGM4154x_IINDPM_INT_DIS);
 | |
| 
 | |
| 	regmap_update_bits(sgm->regmap,
 | |
| 			   SGM4154x_CHRG_CTRL_a,
 | |
| 			   SGM4154x_VINDPM_INT_MASK,
 | |
| 			   SGM4154x_VINDPM_INT_DIS);
 | |
| 
 | |
| 
 | |
| 	ret = sgm4154x_set_recharge_volt(sgm, 200);
 | |
| 	if (ret)
 | |
| 		goto err_out;
 | |
| 
 | |
| 	DBG("ichrg_curr:%d\n"
 | |
| 	    "prechrg_curr:%d\n"
 | |
| 	    "chrg_vol:%d\n"
 | |
| 	    "term_curr:%d\n"
 | |
| 	    "input_curr_lim:%d\n",
 | |
| 	    bat_info->constant_charge_current_max_ua,
 | |
| 	    bat_info->precharge_current_ua,
 | |
| 	    bat_info->constant_charge_voltage_max_uv,
 | |
| 	    bat_info->charge_term_current_ua,
 | |
| 	    sgm->init_data.ilim);
 | |
| 
 | |
| 	return 0;
 | |
| 
 | |
| err_out:
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static int sgm4154x_parse_dt(struct sgm4154x_device *sgm)
 | |
| {
 | |
| 	int ret;
 | |
| 
 | |
| 	ret = device_property_read_u32(sgm->dev,
 | |
| 				       "input-voltage-limit-microvolt",
 | |
| 				       &sgm->init_data.vlim);
 | |
| 	if (ret)
 | |
| 		sgm->init_data.vlim = SGM4154x_VINDPM_DEF_uV;
 | |
| 
 | |
| 	if (sgm->init_data.vlim > SGM4154x_VINDPM_V_MAX_uV ||
 | |
| 	    sgm->init_data.vlim < SGM4154x_VINDPM_V_MIN_uV)
 | |
| 		return -EINVAL;
 | |
| 
 | |
| 	ret = device_property_read_u32(sgm->dev,
 | |
| 				       "input-current-limit-microamp",
 | |
| 				       &sgm->init_data.ilim);
 | |
| 	if (ret)
 | |
| 		sgm->init_data.ilim = SGM4154x_IINDPM_DEF_uA;
 | |
| 
 | |
| 	if (sgm->init_data.ilim > SGM4154x_IINDPM_I_MAX_uA ||
 | |
| 	    sgm->init_data.ilim < SGM4154x_IINDPM_I_MIN_uA)
 | |
| 		return -EINVAL;
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int sgm4154x_set_otg_voltage(struct sgm4154x_device *sgm, int uv)
 | |
| {
 | |
| 	int ret = 0;
 | |
| 	int reg_val = -1;
 | |
| 	int i = 0;
 | |
| 
 | |
| 	while (i < 4) {
 | |
| 		if (uv == BOOST_VOLT_LIMIT[i]) {
 | |
| 			reg_val = i;
 | |
| 			break;
 | |
| 		}
 | |
| 		i++;
 | |
| 	}
 | |
| 	if (reg_val < 0)
 | |
| 		return reg_val;
 | |
| 	reg_val = reg_val << 4;
 | |
| 	ret = regmap_update_bits(sgm->regmap,
 | |
| 				 SGM4154x_CHRG_CTRL_6,
 | |
| 				 SGM4154x_BOOSTV,
 | |
| 				 reg_val);
 | |
| 	if (ret) {
 | |
| 		dev_err(sgm->dev, "set otg voltage error!\n");
 | |
| 		return ret;
 | |
| 	}
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static int sgm4154x_set_otg_current(struct sgm4154x_device *sgm, int ua)
 | |
| {
 | |
| 	int ret = 0;
 | |
| 
 | |
| 	if (ua == BOOST_CURRENT_LIMIT[0]) {
 | |
| 		ret = regmap_update_bits(sgm->regmap,
 | |
| 					 SGM4154x_CHRG_CTRL_2,
 | |
| 					 SGM4154x_BOOST_LIM,
 | |
| 					 0);
 | |
| 		if (ret) {
 | |
| 			dev_err(sgm->dev, "set boost current limit error!\n");
 | |
| 			return ret;
 | |
| 		}
 | |
| 	} else if (ua == BOOST_CURRENT_LIMIT[1]) {
 | |
| 		ret = regmap_update_bits(sgm->regmap,
 | |
| 					 SGM4154x_CHRG_CTRL_2,
 | |
| 					 SGM4154x_BOOST_LIM,
 | |
| 					 BIT(7));
 | |
| 		if (ret) {
 | |
| 			dev_err(sgm->dev, "set boost current limit error!\n");
 | |
| 			return ret;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static int sgm4154x_enable_vbus(struct regulator_dev *rdev)
 | |
| {
 | |
| 	struct sgm4154x_device *sgm = rdev_get_drvdata(rdev);
 | |
| 	int ret = 0;
 | |
| 
 | |
| 	ret = regmap_update_bits(sgm->regmap,
 | |
| 				 SGM4154x_CHRG_CTRL_1,
 | |
| 				 SGM4154x_OTG_EN,
 | |
| 				 SGM4154x_OTG_EN);
 | |
| 	if (ret) {
 | |
| 		dev_err(sgm->dev, "set OTG enable error!\n");
 | |
| 		return ret;
 | |
| 	}
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static int sgm4154x_disable_vbus(struct regulator_dev *rdev)
 | |
| {
 | |
| 	struct sgm4154x_device *sgm = rdev_get_drvdata(rdev);
 | |
| 	int ret = 0;
 | |
| 
 | |
| 	ret = regmap_update_bits(sgm->regmap,
 | |
| 				 SGM4154x_CHRG_CTRL_1,
 | |
| 				 SGM4154x_OTG_EN,
 | |
| 				 0);
 | |
| 	if (ret) {
 | |
| 		dev_err(sgm->dev, "set OTG disable error!\n");
 | |
| 		return ret;
 | |
| 	}
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static int sgm4154x_is_enabled_vbus(struct regulator_dev *rdev)
 | |
| {
 | |
| 	struct sgm4154x_device *sgm = rdev_get_drvdata(rdev);
 | |
| 	int temp = 0;
 | |
| 	int ret = 0;
 | |
| 
 | |
| 	ret = regmap_read(sgm->regmap, SGM4154x_CHRG_CTRL_1, &temp);
 | |
| 	if (ret) {
 | |
| 		dev_err(sgm->dev, "get vbus status error!\n");
 | |
| 		return ret;
 | |
| 	}
 | |
| 
 | |
| 	return (temp & SGM4154x_OTG_EN) ? 1 : 0;
 | |
| }
 | |
| 
 | |
| static const struct regulator_ops sgm4154x_vbus_ops = {
 | |
| 	.enable = sgm4154x_enable_vbus,
 | |
| 	.disable = sgm4154x_disable_vbus,
 | |
| 	.is_enabled = sgm4154x_is_enabled_vbus,
 | |
| };
 | |
| 
 | |
| static struct regulator_desc sgm4154x_otg_rdesc = {
 | |
| 	.of_match = "otg-vbus",
 | |
| 	.name = "otg-vbus",
 | |
| 	.regulators_node = of_match_ptr("regulators"),
 | |
| 	.ops = &sgm4154x_vbus_ops,
 | |
| 	.owner = THIS_MODULE,
 | |
| 	.type = REGULATOR_VOLTAGE,
 | |
| 	.fixed_uV = 5000000,
 | |
| 	.n_voltages = 1,
 | |
| };
 | |
| 
 | |
| static int sgm4154x_vbus_regulator_register(struct sgm4154x_device *sgm)
 | |
| {
 | |
| 	struct device_node *np;
 | |
| 	struct regulator_config config = {};
 | |
| 	int ret = 0;
 | |
| 
 | |
| 	np = of_get_child_by_name(sgm->dev->of_node, "regulators");
 | |
| 	if (!np) {
 | |
| 		dev_warn(sgm->dev, "cannot find regulators node\n");
 | |
| 		return -ENXIO;
 | |
| 	}
 | |
| 
 | |
| 	/* otg regulator */
 | |
| 	config.dev = sgm->dev;
 | |
| 	config.driver_data = sgm;
 | |
| 	sgm->otg_rdev = devm_regulator_register(sgm->dev,
 | |
| 						&sgm4154x_otg_rdesc,
 | |
| 						&config);
 | |
| 	if (IS_ERR(sgm->otg_rdev))
 | |
| 		ret = PTR_ERR(sgm->otg_rdev);
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static int sgm4154x_suspend_notifier(struct notifier_block *nb,
 | |
| 				     unsigned long event,
 | |
| 				     void *dummy)
 | |
| {
 | |
| 	struct sgm4154x_device *sgm = container_of(nb, struct sgm4154x_device, pm_nb);
 | |
| 
 | |
| 	switch (event) {
 | |
| 
 | |
| 	case PM_SUSPEND_PREPARE:
 | |
| 		sgm->sgm4154x_suspend_flag = 1;
 | |
| 		return NOTIFY_OK;
 | |
| 
 | |
| 	case PM_POST_SUSPEND:
 | |
| 		sgm->sgm4154x_suspend_flag = 0;
 | |
| 		return NOTIFY_OK;
 | |
| 
 | |
| 	default:
 | |
| 		return NOTIFY_DONE;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static int sgm4154x_hw_chipid_detect(struct sgm4154x_device *sgm)
 | |
| {
 | |
| 	int ret = 0;
 | |
| 	int val = 0;
 | |
| 
 | |
| 	ret = regmap_read(sgm->regmap, SGM4154x_CHRG_CTRL_b, &val);
 | |
| 	if (ret < 0)
 | |
| 		return ret;
 | |
| 
 | |
| 	return val;
 | |
| }
 | |
| 
 | |
| static void sgm_charger_work(struct work_struct *work)
 | |
| {
 | |
| 	struct sgm4154x_device *sgm =
 | |
| 		container_of(work,
 | |
| 			     struct sgm4154x_device,
 | |
| 			     sgm_delay_work.work);
 | |
| 
 | |
| 	sgm4154x_watchdog_timer_reset(sgm);
 | |
| 	if (sgm->watchdog_enable)
 | |
| 		queue_delayed_work(sgm->sgm_monitor_wq,
 | |
| 				   &sgm->sgm_delay_work,
 | |
| 				   msecs_to_jiffies(1000 * 5));
 | |
| }
 | |
| 
 | |
| static int sgm4154x_probe(struct i2c_client *client,
 | |
| 			  const struct i2c_device_id *id)
 | |
| {
 | |
| 	struct device *dev = &client->dev;
 | |
| 	struct sgm4154x_device *sgm;
 | |
| 	int ret;
 | |
| 
 | |
| 	sgm = devm_kzalloc(dev, sizeof(*sgm), GFP_KERNEL);
 | |
| 	if (!sgm)
 | |
| 		return -ENOMEM;
 | |
| 
 | |
| 	sgm->client = client;
 | |
| 	sgm->dev = dev;
 | |
| 
 | |
| 	mutex_init(&sgm->lock);
 | |
| 
 | |
| 	strncpy(sgm->model_name, id->name, I2C_NAME_SIZE);
 | |
| 
 | |
| 	sgm->regmap = devm_regmap_init_i2c(client, &sgm4154x_regmap_config);
 | |
| 	if (IS_ERR(sgm->regmap)) {
 | |
| 		dev_err(dev, "Failed to allocate register map\n");
 | |
| 		return PTR_ERR(sgm->regmap);
 | |
| 	}
 | |
| 
 | |
| 	i2c_set_clientdata(client, sgm);
 | |
| 
 | |
| 	ret = sgm4154x_parse_dt(sgm);
 | |
| 	if (ret) {
 | |
| 		dev_err(dev, "Failed to read device tree properties%d\n", ret);
 | |
| 		return ret;
 | |
| 	}
 | |
| 
 | |
| 	ret = sgm4154x_hw_chipid_detect(sgm);
 | |
| 	if ((ret & SGM4154x_PN_MASK) != SGM4154x_PN_ID) {
 | |
| 		pr_info("[%s] device not found !\n", __func__);
 | |
| 		return ret;
 | |
| 	}
 | |
| 
 | |
| 	device_init_wakeup(dev, 1);
 | |
| 
 | |
| 	if (client->irq) {
 | |
| 		ret = devm_request_threaded_irq(dev, client->irq, NULL,
 | |
| 						sgm4154x_irq_handler_thread,
 | |
| 						IRQF_TRIGGER_FALLING |
 | |
| 						IRQF_ONESHOT,
 | |
| 						"sgm41542-irq", sgm);
 | |
| 		if (ret)
 | |
| 			goto error_out;
 | |
| 		enable_irq_wake(client->irq);
 | |
| 	}
 | |
| 
 | |
| 	sgm->pm_nb.notifier_call = sgm4154x_suspend_notifier;
 | |
| 	register_pm_notifier(&sgm->pm_nb);
 | |
| 
 | |
| 	ret = sgm4154x_power_supply_init(sgm, dev);
 | |
| 	if (ret) {
 | |
| 		dev_err(dev, "Failed to register power supply\n");
 | |
| 		goto error_out;
 | |
| 	}
 | |
| 
 | |
| 	ret = sgm4154x_hw_init(sgm);
 | |
| 	if (ret) {
 | |
| 		dev_err(dev, "Cannot initialize the chip.\n");
 | |
| 		goto error_out;
 | |
| 	}
 | |
| 
 | |
| 	/* OTG setting 5V/1.2A */
 | |
| 	ret = sgm4154x_set_otg_voltage(sgm, 5000000);
 | |
| 	if (ret) {
 | |
| 		dev_err(sgm->dev, "set OTG voltage error!\n");
 | |
| 		return ret;
 | |
| 	}
 | |
| 
 | |
| 	ret = sgm4154x_set_otg_current(sgm, 1200000);
 | |
| 	if (ret) {
 | |
| 		dev_err(sgm->dev, "set OTG current error!\n");
 | |
| 		return ret;
 | |
| 	}
 | |
| 
 | |
| 	sgm->sgm_monitor_wq = alloc_ordered_workqueue("%s",
 | |
| 			WQ_MEM_RECLAIM | WQ_FREEZABLE, "sgm-monitor-wq");
 | |
| 	INIT_DELAYED_WORK(&sgm->sgm_delay_work, sgm_charger_work);
 | |
| 
 | |
| 	sgm4154x_vbus_regulator_register(sgm);
 | |
| 	sgm4154x_create_device_node(sgm->dev);
 | |
| 	return ret;
 | |
| error_out:
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static void sgm4154x_charger_remove(struct i2c_client *client)
 | |
| {
 | |
| 	struct sgm4154x_device *sgm = i2c_get_clientdata(client);
 | |
| 
 | |
| 	destroy_workqueue(sgm->sgm_monitor_wq);
 | |
| 	regulator_unregister(sgm->otg_rdev);
 | |
| 	power_supply_unregister(sgm->charger);
 | |
| 	mutex_destroy(&sgm->lock);
 | |
| }
 | |
| 
 | |
| static void sgm4154x_charger_shutdown(struct i2c_client *client)
 | |
| {
 | |
| 	struct sgm4154x_device *sgm = i2c_get_clientdata(client);
 | |
| 	int ret = 0;
 | |
| 
 | |
| 	sgm4154x_set_prechrg_curr(sgm, SGM4154x_PRECHRG_I_DEF_uA);
 | |
| 	ret = sgm4154x_disable_charger(sgm);
 | |
| 	if (ret)
 | |
| 		pr_err("Failed to disable charger, ret = %d\n", ret);
 | |
| }
 | |
| 
 | |
| static const struct i2c_device_id sgm4154x_i2c_ids[] = {
 | |
| 	{ "sgm41542", 0 },
 | |
| 	{},
 | |
| };
 | |
| MODULE_DEVICE_TABLE(i2c, sgm4154x_i2c_ids);
 | |
| 
 | |
| static const struct of_device_id sgm4154x_of_match[] = {
 | |
| 	{ .compatible = "sgm,sgm41542", },
 | |
| 	{ },
 | |
| };
 | |
| MODULE_DEVICE_TABLE(of, sgm4154x_of_match);
 | |
| 
 | |
| static struct i2c_driver sgm4154x_driver = {
 | |
| 	.driver = {
 | |
| 		.name = "sgm4154x-charger",
 | |
| 		.of_match_table = sgm4154x_of_match,
 | |
| 	},
 | |
| 	.probe = sgm4154x_probe,
 | |
| 	.remove = sgm4154x_charger_remove,
 | |
| 	.shutdown = sgm4154x_charger_shutdown,
 | |
| 	.id_table = sgm4154x_i2c_ids,
 | |
| };
 | |
| module_i2c_driver(sgm4154x_driver);
 | |
| 
 | |
| MODULE_AUTHOR("Xu Shengfei <xsf@rock-chips.com>");
 | |
| MODULE_DESCRIPTION("sgm4154x charger driver");
 | |
| MODULE_LICENSE("GPL");
 |