639 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			639 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| // SPDX-License-Identifier: GPL-2.0
 | |
| //
 | |
| // Copyright (C) 2021 Samuel Holland <samuel@sholland.org>
 | |
| 
 | |
| #include <linux/i2c.h>
 | |
| #include <linux/module.h>
 | |
| #include <linux/power_supply.h>
 | |
| #include <linux/regmap.h>
 | |
| 
 | |
| #define IP5XXX_SYS_CTL0			0x01
 | |
| #define IP5XXX_SYS_CTL0_WLED_DET_EN		BIT(4)
 | |
| #define IP5XXX_SYS_CTL0_WLED_EN			BIT(3)
 | |
| #define IP5XXX_SYS_CTL0_BOOST_EN		BIT(2)
 | |
| #define IP5XXX_SYS_CTL0_CHARGER_EN		BIT(1)
 | |
| #define IP5XXX_SYS_CTL1			0x02
 | |
| #define IP5XXX_SYS_CTL1_LIGHT_SHDN_EN		BIT(1)
 | |
| #define IP5XXX_SYS_CTL1_LOAD_PWRUP_EN		BIT(0)
 | |
| #define IP5XXX_SYS_CTL2			0x0c
 | |
| #define IP5XXX_SYS_CTL2_LIGHT_SHDN_TH		GENMASK(7, 3)
 | |
| #define IP5XXX_SYS_CTL3			0x03
 | |
| #define IP5XXX_SYS_CTL3_LONG_PRESS_TIME_SEL	GENMASK(7, 6)
 | |
| #define IP5XXX_SYS_CTL3_BTN_SHDN_EN		BIT(5)
 | |
| #define IP5XXX_SYS_CTL4			0x04
 | |
| #define IP5XXX_SYS_CTL4_SHDN_TIME_SEL		GENMASK(7, 6)
 | |
| #define IP5XXX_SYS_CTL4_VIN_PULLOUT_BOOST_EN	BIT(5)
 | |
| #define IP5XXX_SYS_CTL5			0x07
 | |
| #define IP5XXX_SYS_CTL5_NTC_DIS			BIT(6)
 | |
| #define IP5XXX_SYS_CTL5_WLED_MODE_SEL		BIT(1)
 | |
| #define IP5XXX_SYS_CTL5_BTN_SHDN_SEL		BIT(0)
 | |
| #define IP5XXX_CHG_CTL1			0x22
 | |
| #define IP5XXX_CHG_CTL1_BOOST_UVP_SEL		GENMASK(3, 2)
 | |
| #define IP5XXX_CHG_CTL2			0x24
 | |
| #define IP5XXX_CHG_CTL2_BAT_TYPE_SEL		GENMASK(6, 5)
 | |
| #define IP5XXX_CHG_CTL2_BAT_TYPE_SEL_4_2V	(0x0 << 5)
 | |
| #define IP5XXX_CHG_CTL2_BAT_TYPE_SEL_4_3V	(0x1 << 5)
 | |
| #define IP5XXX_CHG_CTL2_BAT_TYPE_SEL_4_35V	(0x2 << 5)
 | |
| #define IP5XXX_CHG_CTL2_CONST_VOLT_SEL		GENMASK(2, 1)
 | |
| #define IP5XXX_CHG_CTL4			0x26
 | |
| #define IP5XXX_CHG_CTL4_BAT_TYPE_SEL_EN		BIT(6)
 | |
| #define IP5XXX_CHG_CTL4A		0x25
 | |
| #define IP5XXX_CHG_CTL4A_CONST_CUR_SEL		GENMASK(4, 0)
 | |
| #define IP5XXX_MFP_CTL0			0x51
 | |
| #define IP5XXX_MFP_CTL1			0x52
 | |
| #define IP5XXX_GPIO_CTL2		0x53
 | |
| #define IP5XXX_GPIO_CTL2A		0x54
 | |
| #define IP5XXX_GPIO_CTL3		0x55
 | |
| #define IP5XXX_READ0			0x71
 | |
| #define IP5XXX_READ0_CHG_STAT			GENMASK(7, 5)
 | |
| #define IP5XXX_READ0_CHG_STAT_IDLE		(0x0 << 5)
 | |
| #define IP5XXX_READ0_CHG_STAT_TRICKLE		(0x1 << 5)
 | |
| #define IP5XXX_READ0_CHG_STAT_CONST_VOLT	(0x2 << 5)
 | |
| #define IP5XXX_READ0_CHG_STAT_CONST_CUR		(0x3 << 5)
 | |
| #define IP5XXX_READ0_CHG_STAT_CONST_VOLT_STOP	(0x4 << 5)
 | |
| #define IP5XXX_READ0_CHG_STAT_FULL		(0x5 << 5)
 | |
| #define IP5XXX_READ0_CHG_STAT_TIMEOUT		(0x6 << 5)
 | |
| #define IP5XXX_READ0_CHG_OP			BIT(4)
 | |
| #define IP5XXX_READ0_CHG_END			BIT(3)
 | |
| #define IP5XXX_READ0_CONST_VOLT_TIMEOUT		BIT(2)
 | |
| #define IP5XXX_READ0_CHG_TIMEOUT		BIT(1)
 | |
| #define IP5XXX_READ0_TRICKLE_TIMEOUT		BIT(0)
 | |
| #define IP5XXX_READ0_TIMEOUT			GENMASK(2, 0)
 | |
| #define IP5XXX_READ1			0x72
 | |
| #define IP5XXX_READ1_WLED_PRESENT		BIT(7)
 | |
| #define IP5XXX_READ1_LIGHT_LOAD			BIT(6)
 | |
| #define IP5XXX_READ1_VIN_OVERVOLT		BIT(5)
 | |
| #define IP5XXX_READ2			0x77
 | |
| #define IP5XXX_READ2_BTN_PRESS			BIT(3)
 | |
| #define IP5XXX_READ2_BTN_LONG_PRESS		BIT(1)
 | |
| #define IP5XXX_READ2_BTN_SHORT_PRESS		BIT(0)
 | |
| #define IP5XXX_BATVADC_DAT0		0xa2
 | |
| #define IP5XXX_BATVADC_DAT1		0xa3
 | |
| #define IP5XXX_BATIADC_DAT0		0xa4
 | |
| #define IP5XXX_BATIADC_DAT1		0xa5
 | |
| #define IP5XXX_BATOCV_DAT0		0xa8
 | |
| #define IP5XXX_BATOCV_DAT1		0xa9
 | |
| 
 | |
| struct ip5xxx {
 | |
| 	struct regmap *regmap;
 | |
| 	bool initialized;
 | |
| };
 | |
| 
 | |
| /*
 | |
|  * The IP5xxx charger only responds on I2C when it is "awake". The charger is
 | |
|  * generally only awake when VIN is powered or when its boost converter is
 | |
|  * enabled. Going into shutdown resets all register values. To handle this:
 | |
|  *  1) When any bus error occurs, assume the charger has gone into shutdown.
 | |
|  *  2) Attempt the initialization sequence on each subsequent register access
 | |
|  *     until it succeeds.
 | |
|  */
 | |
| static int ip5xxx_read(struct ip5xxx *ip5xxx, unsigned int reg,
 | |
| 		       unsigned int *val)
 | |
| {
 | |
| 	int ret;
 | |
| 
 | |
| 	ret = regmap_read(ip5xxx->regmap, reg, val);
 | |
| 	if (ret)
 | |
| 		ip5xxx->initialized = false;
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static int ip5xxx_update_bits(struct ip5xxx *ip5xxx, unsigned int reg,
 | |
| 			      unsigned int mask, unsigned int val)
 | |
| {
 | |
| 	int ret;
 | |
| 
 | |
| 	ret = regmap_update_bits(ip5xxx->regmap, reg, mask, val);
 | |
| 	if (ret)
 | |
| 		ip5xxx->initialized = false;
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static int ip5xxx_initialize(struct power_supply *psy)
 | |
| {
 | |
| 	struct ip5xxx *ip5xxx = power_supply_get_drvdata(psy);
 | |
| 	int ret;
 | |
| 
 | |
| 	if (ip5xxx->initialized)
 | |
| 		return 0;
 | |
| 
 | |
| 	/*
 | |
| 	 * Disable shutdown under light load.
 | |
| 	 * Enable power on when under load.
 | |
| 	 */
 | |
| 	ret = ip5xxx_update_bits(ip5xxx, IP5XXX_SYS_CTL1,
 | |
| 				 IP5XXX_SYS_CTL1_LIGHT_SHDN_EN |
 | |
| 				 IP5XXX_SYS_CTL1_LOAD_PWRUP_EN,
 | |
| 				 IP5XXX_SYS_CTL1_LOAD_PWRUP_EN);
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 
 | |
| 	/*
 | |
| 	 * Enable shutdown after a long button press (as configured below).
 | |
| 	 */
 | |
| 	ret = ip5xxx_update_bits(ip5xxx, IP5XXX_SYS_CTL3,
 | |
| 				 IP5XXX_SYS_CTL3_BTN_SHDN_EN,
 | |
| 				 IP5XXX_SYS_CTL3_BTN_SHDN_EN);
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 
 | |
| 	/*
 | |
| 	 * Power on automatically when VIN is removed.
 | |
| 	 */
 | |
| 	ret = ip5xxx_update_bits(ip5xxx, IP5XXX_SYS_CTL4,
 | |
| 				 IP5XXX_SYS_CTL4_VIN_PULLOUT_BOOST_EN,
 | |
| 				 IP5XXX_SYS_CTL4_VIN_PULLOUT_BOOST_EN);
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 
 | |
| 	/*
 | |
| 	 * Enable the NTC.
 | |
| 	 * Configure the button for two presses => LED, long press => shutdown.
 | |
| 	 */
 | |
| 	ret = ip5xxx_update_bits(ip5xxx, IP5XXX_SYS_CTL5,
 | |
| 				 IP5XXX_SYS_CTL5_NTC_DIS |
 | |
| 				 IP5XXX_SYS_CTL5_WLED_MODE_SEL |
 | |
| 				 IP5XXX_SYS_CTL5_BTN_SHDN_SEL,
 | |
| 				 IP5XXX_SYS_CTL5_WLED_MODE_SEL |
 | |
| 				 IP5XXX_SYS_CTL5_BTN_SHDN_SEL);
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 
 | |
| 	ip5xxx->initialized = true;
 | |
| 	dev_dbg(psy->dev.parent, "Initialized after power on\n");
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static const enum power_supply_property ip5xxx_battery_properties[] = {
 | |
| 	POWER_SUPPLY_PROP_STATUS,
 | |
| 	POWER_SUPPLY_PROP_CHARGE_TYPE,
 | |
| 	POWER_SUPPLY_PROP_HEALTH,
 | |
| 	POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
 | |
| 	POWER_SUPPLY_PROP_VOLTAGE_NOW,
 | |
| 	POWER_SUPPLY_PROP_VOLTAGE_OCV,
 | |
| 	POWER_SUPPLY_PROP_CURRENT_NOW,
 | |
| 	POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT,
 | |
| 	POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
 | |
| 	POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE,
 | |
| 	POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX,
 | |
| };
 | |
| 
 | |
| static int ip5xxx_battery_get_status(struct ip5xxx *ip5xxx, int *val)
 | |
| {
 | |
| 	unsigned int rval;
 | |
| 	int ret;
 | |
| 
 | |
| 	ret = ip5xxx_read(ip5xxx, IP5XXX_READ0, &rval);
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 
 | |
| 	switch (rval & IP5XXX_READ0_CHG_STAT) {
 | |
| 	case IP5XXX_READ0_CHG_STAT_IDLE:
 | |
| 		*val = POWER_SUPPLY_STATUS_DISCHARGING;
 | |
| 		break;
 | |
| 	case IP5XXX_READ0_CHG_STAT_TRICKLE:
 | |
| 	case IP5XXX_READ0_CHG_STAT_CONST_CUR:
 | |
| 	case IP5XXX_READ0_CHG_STAT_CONST_VOLT:
 | |
| 		*val = POWER_SUPPLY_STATUS_CHARGING;
 | |
| 		break;
 | |
| 	case IP5XXX_READ0_CHG_STAT_CONST_VOLT_STOP:
 | |
| 	case IP5XXX_READ0_CHG_STAT_FULL:
 | |
| 		*val = POWER_SUPPLY_STATUS_FULL;
 | |
| 		break;
 | |
| 	case IP5XXX_READ0_CHG_STAT_TIMEOUT:
 | |
| 		*val = POWER_SUPPLY_STATUS_NOT_CHARGING;
 | |
| 		break;
 | |
| 	default:
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int ip5xxx_battery_get_charge_type(struct ip5xxx *ip5xxx, int *val)
 | |
| {
 | |
| 	unsigned int rval;
 | |
| 	int ret;
 | |
| 
 | |
| 	ret = ip5xxx_read(ip5xxx, IP5XXX_READ0, &rval);
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 
 | |
| 	switch (rval & IP5XXX_READ0_CHG_STAT) {
 | |
| 	case IP5XXX_READ0_CHG_STAT_IDLE:
 | |
| 	case IP5XXX_READ0_CHG_STAT_CONST_VOLT_STOP:
 | |
| 	case IP5XXX_READ0_CHG_STAT_FULL:
 | |
| 	case IP5XXX_READ0_CHG_STAT_TIMEOUT:
 | |
| 		*val = POWER_SUPPLY_CHARGE_TYPE_NONE;
 | |
| 		break;
 | |
| 	case IP5XXX_READ0_CHG_STAT_TRICKLE:
 | |
| 		*val = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
 | |
| 		break;
 | |
| 	case IP5XXX_READ0_CHG_STAT_CONST_CUR:
 | |
| 	case IP5XXX_READ0_CHG_STAT_CONST_VOLT:
 | |
| 		*val = POWER_SUPPLY_CHARGE_TYPE_STANDARD;
 | |
| 		break;
 | |
| 	default:
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int ip5xxx_battery_get_health(struct ip5xxx *ip5xxx, int *val)
 | |
| {
 | |
| 	unsigned int rval;
 | |
| 	int ret;
 | |
| 
 | |
| 	ret = ip5xxx_read(ip5xxx, IP5XXX_READ0, &rval);
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 
 | |
| 	if (rval & IP5XXX_READ0_TIMEOUT)
 | |
| 		*val = POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE;
 | |
| 	else
 | |
| 		*val = POWER_SUPPLY_HEALTH_GOOD;
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int ip5xxx_battery_get_voltage_max(struct ip5xxx *ip5xxx, int *val)
 | |
| {
 | |
| 	unsigned int rval;
 | |
| 	int ret;
 | |
| 
 | |
| 	ret = ip5xxx_read(ip5xxx, IP5XXX_CHG_CTL2, &rval);
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 
 | |
| 	/*
 | |
| 	 * It is not clear what this will return if
 | |
| 	 * IP5XXX_CHG_CTL4_BAT_TYPE_SEL_EN is not set...
 | |
| 	 */
 | |
| 	switch (rval & IP5XXX_CHG_CTL2_BAT_TYPE_SEL) {
 | |
| 	case IP5XXX_CHG_CTL2_BAT_TYPE_SEL_4_2V:
 | |
| 		*val = 4200000;
 | |
| 		break;
 | |
| 	case IP5XXX_CHG_CTL2_BAT_TYPE_SEL_4_3V:
 | |
| 		*val = 4300000;
 | |
| 		break;
 | |
| 	case IP5XXX_CHG_CTL2_BAT_TYPE_SEL_4_35V:
 | |
| 		*val = 4350000;
 | |
| 		break;
 | |
| 	default:
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int ip5xxx_battery_read_adc(struct ip5xxx *ip5xxx,
 | |
| 				   u8 lo_reg, u8 hi_reg, int *val)
 | |
| {
 | |
| 	unsigned int hi, lo;
 | |
| 	int ret;
 | |
| 
 | |
| 	ret = ip5xxx_read(ip5xxx, lo_reg, &lo);
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 
 | |
| 	ret = ip5xxx_read(ip5xxx, hi_reg, &hi);
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 
 | |
| 	*val = sign_extend32(hi << 8 | lo, 13);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int ip5xxx_battery_get_property(struct power_supply *psy,
 | |
| 				       enum power_supply_property psp,
 | |
| 				       union power_supply_propval *val)
 | |
| {
 | |
| 	struct ip5xxx *ip5xxx = power_supply_get_drvdata(psy);
 | |
| 	int raw, ret, vmax;
 | |
| 	unsigned int rval;
 | |
| 
 | |
| 	ret = ip5xxx_initialize(psy);
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 
 | |
| 	switch (psp) {
 | |
| 	case POWER_SUPPLY_PROP_STATUS:
 | |
| 		return ip5xxx_battery_get_status(ip5xxx, &val->intval);
 | |
| 
 | |
| 	case POWER_SUPPLY_PROP_CHARGE_TYPE:
 | |
| 		return ip5xxx_battery_get_charge_type(ip5xxx, &val->intval);
 | |
| 
 | |
| 	case POWER_SUPPLY_PROP_HEALTH:
 | |
| 		return ip5xxx_battery_get_health(ip5xxx, &val->intval);
 | |
| 
 | |
| 	case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
 | |
| 		return ip5xxx_battery_get_voltage_max(ip5xxx, &val->intval);
 | |
| 
 | |
| 	case POWER_SUPPLY_PROP_VOLTAGE_NOW:
 | |
| 		ret = ip5xxx_battery_read_adc(ip5xxx, IP5XXX_BATVADC_DAT0,
 | |
| 					      IP5XXX_BATVADC_DAT1, &raw);
 | |
| 
 | |
| 		val->intval = 2600000 + DIV_ROUND_CLOSEST(raw * 26855, 100);
 | |
| 		return 0;
 | |
| 
 | |
| 	case POWER_SUPPLY_PROP_VOLTAGE_OCV:
 | |
| 		ret = ip5xxx_battery_read_adc(ip5xxx, IP5XXX_BATOCV_DAT0,
 | |
| 					      IP5XXX_BATOCV_DAT1, &raw);
 | |
| 
 | |
| 		val->intval = 2600000 + DIV_ROUND_CLOSEST(raw * 26855, 100);
 | |
| 		return 0;
 | |
| 
 | |
| 	case POWER_SUPPLY_PROP_CURRENT_NOW:
 | |
| 		ret = ip5xxx_battery_read_adc(ip5xxx, IP5XXX_BATIADC_DAT0,
 | |
| 					      IP5XXX_BATIADC_DAT1, &raw);
 | |
| 
 | |
| 		val->intval = DIV_ROUND_CLOSEST(raw * 149197, 200);
 | |
| 		return 0;
 | |
| 
 | |
| 	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
 | |
| 		ret = ip5xxx_read(ip5xxx, IP5XXX_CHG_CTL4A, &rval);
 | |
| 		if (ret)
 | |
| 			return ret;
 | |
| 
 | |
| 		rval &= IP5XXX_CHG_CTL4A_CONST_CUR_SEL;
 | |
| 		val->intval = 100000 * rval;
 | |
| 		return 0;
 | |
| 
 | |
| 	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
 | |
| 		val->intval = 100000 * 0x1f;
 | |
| 		return 0;
 | |
| 
 | |
| 	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
 | |
| 		ret = ip5xxx_battery_get_voltage_max(ip5xxx, &vmax);
 | |
| 		if (ret)
 | |
| 			return ret;
 | |
| 
 | |
| 		ret = ip5xxx_read(ip5xxx, IP5XXX_CHG_CTL2, &rval);
 | |
| 		if (ret)
 | |
| 			return ret;
 | |
| 
 | |
| 		rval &= IP5XXX_CHG_CTL2_CONST_VOLT_SEL;
 | |
| 		val->intval = vmax + 14000 * (rval >> 1);
 | |
| 		return 0;
 | |
| 
 | |
| 	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
 | |
| 		ret = ip5xxx_battery_get_voltage_max(ip5xxx, &vmax);
 | |
| 		if (ret)
 | |
| 			return ret;
 | |
| 
 | |
| 		val->intval = vmax + 14000 * 3;
 | |
| 		return 0;
 | |
| 
 | |
| 	default:
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static int ip5xxx_battery_set_voltage_max(struct ip5xxx *ip5xxx, int val)
 | |
| {
 | |
| 	unsigned int rval;
 | |
| 	int ret;
 | |
| 
 | |
| 	switch (val) {
 | |
| 	case 4200000:
 | |
| 		rval = IP5XXX_CHG_CTL2_BAT_TYPE_SEL_4_2V;
 | |
| 		break;
 | |
| 	case 4300000:
 | |
| 		rval = IP5XXX_CHG_CTL2_BAT_TYPE_SEL_4_3V;
 | |
| 		break;
 | |
| 	case 4350000:
 | |
| 		rval = IP5XXX_CHG_CTL2_BAT_TYPE_SEL_4_35V;
 | |
| 		break;
 | |
| 	default:
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| 
 | |
| 	ret = ip5xxx_update_bits(ip5xxx, IP5XXX_CHG_CTL2,
 | |
| 				 IP5XXX_CHG_CTL2_BAT_TYPE_SEL, rval);
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 
 | |
| 	ret = ip5xxx_update_bits(ip5xxx, IP5XXX_CHG_CTL4,
 | |
| 				 IP5XXX_CHG_CTL4_BAT_TYPE_SEL_EN,
 | |
| 				 IP5XXX_CHG_CTL4_BAT_TYPE_SEL_EN);
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int ip5xxx_battery_set_property(struct power_supply *psy,
 | |
| 				       enum power_supply_property psp,
 | |
| 				       const union power_supply_propval *val)
 | |
| {
 | |
| 	struct ip5xxx *ip5xxx = power_supply_get_drvdata(psy);
 | |
| 	unsigned int rval;
 | |
| 	int ret, vmax;
 | |
| 
 | |
| 	ret = ip5xxx_initialize(psy);
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 
 | |
| 	switch (psp) {
 | |
| 	case POWER_SUPPLY_PROP_STATUS:
 | |
| 		switch (val->intval) {
 | |
| 		case POWER_SUPPLY_STATUS_CHARGING:
 | |
| 			rval = IP5XXX_SYS_CTL0_CHARGER_EN;
 | |
| 			break;
 | |
| 		case POWER_SUPPLY_STATUS_DISCHARGING:
 | |
| 		case POWER_SUPPLY_STATUS_NOT_CHARGING:
 | |
| 			rval = 0;
 | |
| 			break;
 | |
| 		default:
 | |
| 			return -EINVAL;
 | |
| 		}
 | |
| 		return ip5xxx_update_bits(ip5xxx, IP5XXX_SYS_CTL0,
 | |
| 					  IP5XXX_SYS_CTL0_CHARGER_EN, rval);
 | |
| 
 | |
| 	case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
 | |
| 		return ip5xxx_battery_set_voltage_max(ip5xxx, val->intval);
 | |
| 
 | |
| 	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
 | |
| 		rval = val->intval / 100000;
 | |
| 		return ip5xxx_update_bits(ip5xxx, IP5XXX_CHG_CTL4A,
 | |
| 					  IP5XXX_CHG_CTL4A_CONST_CUR_SEL, rval);
 | |
| 
 | |
| 	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
 | |
| 		ret = ip5xxx_battery_get_voltage_max(ip5xxx, &vmax);
 | |
| 		if (ret)
 | |
| 			return ret;
 | |
| 
 | |
| 		rval = ((val->intval - vmax) / 14000) << 1;
 | |
| 		return ip5xxx_update_bits(ip5xxx, IP5XXX_CHG_CTL2,
 | |
| 					  IP5XXX_CHG_CTL2_CONST_VOLT_SEL, rval);
 | |
| 
 | |
| 	default:
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static int ip5xxx_battery_property_is_writeable(struct power_supply *psy,
 | |
| 						enum power_supply_property psp)
 | |
| {
 | |
| 	return psp == POWER_SUPPLY_PROP_STATUS ||
 | |
| 	       psp == POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN ||
 | |
| 	       psp == POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT ||
 | |
| 	       psp == POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE;
 | |
| }
 | |
| 
 | |
| static const struct power_supply_desc ip5xxx_battery_desc = {
 | |
| 	.name			= "ip5xxx-battery",
 | |
| 	.type			= POWER_SUPPLY_TYPE_BATTERY,
 | |
| 	.properties		= ip5xxx_battery_properties,
 | |
| 	.num_properties		= ARRAY_SIZE(ip5xxx_battery_properties),
 | |
| 	.get_property		= ip5xxx_battery_get_property,
 | |
| 	.set_property		= ip5xxx_battery_set_property,
 | |
| 	.property_is_writeable	= ip5xxx_battery_property_is_writeable,
 | |
| };
 | |
| 
 | |
| static const enum power_supply_property ip5xxx_boost_properties[] = {
 | |
| 	POWER_SUPPLY_PROP_ONLINE,
 | |
| 	POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
 | |
| };
 | |
| 
 | |
| static int ip5xxx_boost_get_property(struct power_supply *psy,
 | |
| 				     enum power_supply_property psp,
 | |
| 				     union power_supply_propval *val)
 | |
| {
 | |
| 	struct ip5xxx *ip5xxx = power_supply_get_drvdata(psy);
 | |
| 	unsigned int rval;
 | |
| 	int ret;
 | |
| 
 | |
| 	ret = ip5xxx_initialize(psy);
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 
 | |
| 	switch (psp) {
 | |
| 	case POWER_SUPPLY_PROP_ONLINE:
 | |
| 		ret = ip5xxx_read(ip5xxx, IP5XXX_SYS_CTL0, &rval);
 | |
| 		if (ret)
 | |
| 			return ret;
 | |
| 
 | |
| 		val->intval = !!(rval & IP5XXX_SYS_CTL0_BOOST_EN);
 | |
| 		return 0;
 | |
| 
 | |
| 	case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
 | |
| 		ret = ip5xxx_read(ip5xxx, IP5XXX_CHG_CTL1, &rval);
 | |
| 		if (ret)
 | |
| 			return ret;
 | |
| 
 | |
| 		rval &= IP5XXX_CHG_CTL1_BOOST_UVP_SEL;
 | |
| 		val->intval = 4530000 + 100000 * (rval >> 2);
 | |
| 		return 0;
 | |
| 
 | |
| 	default:
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static int ip5xxx_boost_set_property(struct power_supply *psy,
 | |
| 				     enum power_supply_property psp,
 | |
| 				     const union power_supply_propval *val)
 | |
| {
 | |
| 	struct ip5xxx *ip5xxx = power_supply_get_drvdata(psy);
 | |
| 	unsigned int rval;
 | |
| 	int ret;
 | |
| 
 | |
| 	ret = ip5xxx_initialize(psy);
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 
 | |
| 	switch (psp) {
 | |
| 	case POWER_SUPPLY_PROP_ONLINE:
 | |
| 		rval = val->intval ? IP5XXX_SYS_CTL0_BOOST_EN : 0;
 | |
| 		return ip5xxx_update_bits(ip5xxx, IP5XXX_SYS_CTL0,
 | |
| 					  IP5XXX_SYS_CTL0_BOOST_EN, rval);
 | |
| 
 | |
| 	case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
 | |
| 		rval = ((val->intval - 4530000) / 100000) << 2;
 | |
| 		return ip5xxx_update_bits(ip5xxx, IP5XXX_CHG_CTL1,
 | |
| 					  IP5XXX_CHG_CTL1_BOOST_UVP_SEL, rval);
 | |
| 
 | |
| 	default:
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static int ip5xxx_boost_property_is_writeable(struct power_supply *psy,
 | |
| 					      enum power_supply_property psp)
 | |
| {
 | |
| 	return true;
 | |
| }
 | |
| 
 | |
| static const struct power_supply_desc ip5xxx_boost_desc = {
 | |
| 	.name			= "ip5xxx-boost",
 | |
| 	.type			= POWER_SUPPLY_TYPE_USB,
 | |
| 	.properties		= ip5xxx_boost_properties,
 | |
| 	.num_properties		= ARRAY_SIZE(ip5xxx_boost_properties),
 | |
| 	.get_property		= ip5xxx_boost_get_property,
 | |
| 	.set_property		= ip5xxx_boost_set_property,
 | |
| 	.property_is_writeable	= ip5xxx_boost_property_is_writeable,
 | |
| };
 | |
| 
 | |
| static const struct regmap_config ip5xxx_regmap_config = {
 | |
| 	.reg_bits		= 8,
 | |
| 	.val_bits		= 8,
 | |
| 	.max_register		= IP5XXX_BATOCV_DAT1,
 | |
| };
 | |
| 
 | |
| static int ip5xxx_power_probe(struct i2c_client *client)
 | |
| {
 | |
| 	struct power_supply_config psy_cfg = {};
 | |
| 	struct device *dev = &client->dev;
 | |
| 	struct power_supply *psy;
 | |
| 	struct ip5xxx *ip5xxx;
 | |
| 
 | |
| 	ip5xxx = devm_kzalloc(dev, sizeof(*ip5xxx), GFP_KERNEL);
 | |
| 	if (!ip5xxx)
 | |
| 		return -ENOMEM;
 | |
| 
 | |
| 	ip5xxx->regmap = devm_regmap_init_i2c(client, &ip5xxx_regmap_config);
 | |
| 	if (IS_ERR(ip5xxx->regmap))
 | |
| 		return PTR_ERR(ip5xxx->regmap);
 | |
| 
 | |
| 	psy_cfg.of_node = dev->of_node;
 | |
| 	psy_cfg.drv_data = ip5xxx;
 | |
| 
 | |
| 	psy = devm_power_supply_register(dev, &ip5xxx_battery_desc, &psy_cfg);
 | |
| 	if (IS_ERR(psy))
 | |
| 		return PTR_ERR(psy);
 | |
| 
 | |
| 	psy = devm_power_supply_register(dev, &ip5xxx_boost_desc, &psy_cfg);
 | |
| 	if (IS_ERR(psy))
 | |
| 		return PTR_ERR(psy);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static const struct of_device_id ip5xxx_power_of_match[] = {
 | |
| 	{ .compatible = "injoinic,ip5108" },
 | |
| 	{ .compatible = "injoinic,ip5109" },
 | |
| 	{ .compatible = "injoinic,ip5207" },
 | |
| 	{ .compatible = "injoinic,ip5209" },
 | |
| 	{ }
 | |
| };
 | |
| MODULE_DEVICE_TABLE(of, ip5xxx_power_of_match);
 | |
| 
 | |
| static struct i2c_driver ip5xxx_power_driver = {
 | |
| 	.probe_new	= ip5xxx_power_probe,
 | |
| 	.driver		= {
 | |
| 		.name		= "ip5xxx-power",
 | |
| 		.of_match_table	= ip5xxx_power_of_match,
 | |
| 	}
 | |
| };
 | |
| module_i2c_driver(ip5xxx_power_driver);
 | |
| 
 | |
| MODULE_AUTHOR("Samuel Holland <samuel@sholland.org>");
 | |
| MODULE_DESCRIPTION("Injoinic IP5xxx power bank IC driver");
 | |
| MODULE_LICENSE("GPL");
 |