mbox series

[v4,RESEND,0/4] power: supply: Add Support for RK817 Charger

Message ID 20210916194208.10387-1-macroalpha82@gmail.com
Headers show
Series power: supply: Add Support for RK817 Charger | expand

Message

Chris Morgan Sept. 16, 2021, 7:42 p.m. UTC
From: Chris Morgan <macromorgan@hotmail.com>

This series is to add support for the Rockchip rk817 battery charger
which is present in all Rockchip RK817 PMICs. The driver was written
as a joint effort by Maya Matuszczyk <maccraft123mc@gmail.com> and
myself Chris Morgan <macromorgan@hotmail.com>.

The driver requires some basic parameters be described about the
battery in the devicetree such as the maximum capacity, the minimum
and maximum voltage for the battery, the maximum charge current, the
maximum charge voltage, and the value of sample resistors and sleep
currents.

The hardware itself contains an ADC capable of measuring the voltage,
current, and temperature of the battery (though my implementation of an
Odroid Go Advance lacks a thermistor for temperature). It also contains
a columb counter, registers for tracking the measured voltage and
current at boot, and a few bytes of nvram for storing data.

Changes from V3:

 - Corrected issues in device tree documentation.
 - Added additional logic to battery to correct for columb counter
   drift when the device stays plugged in at a full charge state.

Changes from V2:

 - Updated devicetree bindings to use common property units.
 - Removed unneeded includes.
 - Updated rk817_chg_cur_to_reg to make more legible.
 - Simplified formula for displaying calibrated voltage.
 - Updated power supply type to POWER_SUPPLY_TYPE_USB.
 - Implemented get/put_unaligned macros for bulk reads and writes.
 - Changed numerous dev_err() to dev_err_probe().
 - Call power_supply_put_battery_info() at end of probe function.
 - Removed unneeded whitespace.

Changes from V1:

 - Fixed a CLANG warning regarding an uninitalized variable.
 - Fixed a CLANG warning regarding a pointer as a bool value always
   returning as true.
 - Added Maya Matuszczyk to the Signed-off-by.

Chris Morgan (4):
  dt-bindings: Add Rockchip rk817 battery charger support
  mfd: Add Rockchip rk817 battery charger support
  power: supply: Add charger driver for Rockchip RK817
  arm64: dts: rockchip: add rk817 charger to Odroid Go Advance

 .../devicetree/bindings/mfd/rk808.txt         |  38 +
 .../boot/dts/rockchip/rk3326-odroid-go2.dts   |  26 +
 drivers/mfd/rk808.c                           |  16 +-
 drivers/power/supply/Kconfig                  |   6 +
 drivers/power/supply/Makefile                 |   1 +
 drivers/power/supply/rk817_charger.c          | 959 ++++++++++++++++++
 include/linux/mfd/rk808.h                     |  87 ++
 7 files changed, 1132 insertions(+), 1 deletion(-)
 create mode 100644 drivers/power/supply/rk817_charger.c

Comments

Sebastian Reichel Oct. 13, 2021, 5:40 p.m. UTC | #1
Hi,

On Thu, Sep 16, 2021 at 02:42:07PM -0500, Chris Morgan wrote:
> Add support for the Rockchip rk817 battery charger integrated into the
> rk817 PMIC.
> 
> Signed-off-by: Chris Morgan <macromorgan@hotmail.com>
> Signed-off-by: Maya Matuszczyk <maccraft123mc@gmail.com>
> ---
>  drivers/power/supply/Kconfig         |   6 +
>  drivers/power/supply/Makefile        |   1 +
>  drivers/power/supply/rk817_charger.c | 959 +++++++++++++++++++++++++++
>  3 files changed, 966 insertions(+)
>  create mode 100644 drivers/power/supply/rk817_charger.c

Acked-by: Sebastian Reichel <sebastian.reichel@collabora.com>

-- Sebastian

> diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
> index 11f5368e810e..311130da36ff 100644
> --- a/drivers/power/supply/Kconfig
> +++ b/drivers/power/supply/Kconfig
> @@ -666,6 +666,12 @@ config CHARGER_BQ256XX
>  	  charge management and system power path management devices for single
>  	  cell Li-ion and Li-polymer batteries.
>  
> +config CHARGER_RK817
> +	tristate "Rockchip RK817 PMIC Battery Charger"
> +	depends on MFD_RK808
> +	help
> +	  Say Y to include support for Rockchip RK817 Battery Charger.
> +
>  config CHARGER_SMB347
>  	tristate "Summit Microelectronics SMB3XX Battery Charger"
>  	depends on I2C
> diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile
> index 33059a91f60c..9497d2105712 100644
> --- a/drivers/power/supply/Makefile
> +++ b/drivers/power/supply/Makefile
> @@ -87,6 +87,7 @@ obj-$(CONFIG_CHARGER_BQ2515X)	+= bq2515x_charger.o
>  obj-$(CONFIG_CHARGER_BQ25890)	+= bq25890_charger.o
>  obj-$(CONFIG_CHARGER_BQ25980)	+= bq25980_charger.o
>  obj-$(CONFIG_CHARGER_BQ256XX)	+= bq256xx_charger.o
> +obj-$(CONFIG_CHARGER_RK817)	+= rk817_charger.o
>  obj-$(CONFIG_CHARGER_SMB347)	+= smb347-charger.o
>  obj-$(CONFIG_CHARGER_TPS65090)	+= tps65090-charger.o
>  obj-$(CONFIG_CHARGER_TPS65217)	+= tps65217_charger.o
> diff --git a/drivers/power/supply/rk817_charger.c b/drivers/power/supply/rk817_charger.c
> new file mode 100644
> index 000000000000..34338aebe269
> --- /dev/null
> +++ b/drivers/power/supply/rk817_charger.c
> @@ -0,0 +1,959 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Charger Driver for Rockchip rk817
> + *
> + * Copyright (c) 2021
> + *
> + * Authors: Maya Matuszczyk <maccraft123mc@gmail.com>
> + *	    Chris Morgan <macromorgan@hotmail.com>
> + */
> +
> +#include <linux/mfd/rk808.h>
> +#include <linux/irq.h>
> +#include <linux/of_gpio.h>
> +#include <linux/platform_device.h>
> +#include <linux/regmap.h>
> +#include <linux/power_supply.h>
> +#include <asm/unaligned.h>
> +
> +/* Charging statuses reported by hardware register */
> +enum rk817_charge_status {
> +	CHRG_OFF,
> +	DEAD_CHRG,
> +	TRICKLE_CHRG,
> +	CC_OR_CV_CHRG,
> +	CHARGE_FINISH,
> +	USB_OVER_VOL,
> +	BAT_TMP_ERR,
> +	BAT_TIM_ERR,
> +};
> +
> +/* Max charging current read to/written from hardware register.
> + * Note how highest value corresponding to 0x7 is the lowest
> + * current, this is per the datasheet.
> + */
> +enum rk817_chg_cur {
> +	CHG_1A,
> +	CHG_1_5A,
> +	CHG_2A,
> +	CHG_2_5A,
> +	CHG_2_75A,
> +	CHG_3A,
> +	CHG_3_5A,
> +	CHG_0_5A,
> +};
> +
> +struct rk817_charger {
> +	struct device *dev;
> +	struct rk808 *rk808;
> +
> +	struct power_supply *bat_ps;
> +	struct power_supply *chg_ps;
> +	bool plugged_in;
> +
> +	/* The voltage_k and voltage_b values are used to calibrate the ADC
> +	 * voltage readings. While they are documented in the BSP kernel and
> +	 * datasheet as voltage_k and voltage_b, there is no further
> +	 * information explaining them in more detail.
> +	 */
> +
> +	uint32_t voltage_k;
> +	uint32_t voltage_b;
> +
> +	/* Storing immutable values of battery here so we can release
> +	 * get_battery_info after the probe and use these values.
> +	 */
> +	int bat_charge_full_design_uah;
> +	int bat_voltage_min_design_uv;
> +	int bat_voltage_max_design_uv;
> +
> +	/* dsoc seems to be difference between full charge and actual charge in
> +	 * BSP stored as a percentage, to the thousandth.
> +	 */
> +	int dsoc;
> +
> +	/* Calibrate the DSOC on a fully charged battery, this way we can use
> +	 * the calibrated DSOC value to correct for columb counter drift.
> +	 */
> +	bool dsoc_cal;
> +
> +	/* Implementation specific properties from device tree */
> +	int res_div;
> +	int sleep_enter_current;
> +	int sleep_filter_current;
> +};
> +
> +/* ADC coefficients extracted from BSP kernel */
> +#define ADC_TO_CURRENT(adc_value, res_div)	\
> +	(adc_value * 172 / res_div)
> +
> +#define CURRENT_TO_ADC(current, samp_res)	\
> +	(current * samp_res / 172)
> +
> +#define CHARGE_TO_ADC(capacity, res_div)	\
> +	(capacity * res_div * 3600 / 172 * 1000)
> +
> +#define ADC_TO_CHARGE_UAH(adc_value, res_div)	\
> +	(adc_value / 3600 * 172 / res_div)
> +
> +#define ADC_TO_CAPACITY(adc_value, res_div)	\
> +	(adc_value / 1000 * 172 / 3600 / res_div)
> +
> +static u8 rk817_chg_cur_to_reg(u32 chg_cur_ma)
> +{
> +	if (chg_cur_ma > 3500)
> +		return CHG_3_5A;
> +	else if (chg_cur_ma > 3000)
> +		return CHG_3A;
> +	else if (chg_cur_ma > 2750)
> +		return CHG_2_75A;
> +	else if (chg_cur_ma > 2500)
> +		return CHG_2_5A;
> +	else if (chg_cur_ma > 2000)
> +		return CHG_2A;
> +	else if (chg_cur_ma > 1500)
> +		return CHG_1_5A;
> +	else if (chg_cur_ma > 1000)
> +		return CHG_1A;
> +	else if (chg_cur_ma > 500)
> +		return CHG_0_5A;
> +	else
> +		return -EINVAL;
> +}
> +
> +static int rk817_chg_cur_from_reg(u8 reg)
> +{
> +	switch (reg) {
> +	case CHG_0_5A:
> +		return 500000;
> +	case CHG_1A:
> +		return 1000000;
> +	case CHG_1_5A:
> +		return 1500000;
> +	case CHG_2A:
> +		return 2000000;
> +	case CHG_2_5A:
> +		return 2500000;
> +	case CHG_2_75A:
> +		return 2750000;
> +	case CHG_3A:
> +		return 3000000;
> +	case CHG_3_5A:
> +		return 3500000;
> +	default:
> +		return -EINVAL;
> +	}
> +}
> +
> +static void rk817_bat_calib_vol(struct rk817_charger *charger)
> +{
> +	uint32_t vcalib0 = 0;
> +	uint32_t vcalib1 = 0;
> +	u8 bulk_reg[2];
> +
> +	/* calibrate voltage */
> +	regmap_bulk_read(charger->rk808->regmap, RK817_GAS_GAUGE_VCALIB0_H,
> +			 bulk_reg, 2);
> +	vcalib0 = get_unaligned_be16(bulk_reg);
> +
> +	regmap_bulk_read(charger->rk808->regmap, RK817_GAS_GAUGE_VCALIB1_H,
> +			 bulk_reg, 2);
> +	vcalib1 = get_unaligned_be16(bulk_reg);
> +
> +	/* values were taken from BSP kernel */
> +	charger->voltage_k = (4025 - 2300) * 1000 /
> +			     ((vcalib1 - vcalib0) ? (vcalib1 - vcalib0) : 1);
> +	charger->voltage_b = 4025 - (charger->voltage_k * vcalib1) / 1000;
> +}
> +
> +static void rk817_bat_calib_cur(struct rk817_charger *charger)
> +{
> +	u8 bulk_reg[2];
> +
> +	/* calibrate current */
> +	regmap_bulk_read(charger->rk808->regmap, RK817_GAS_GAUGE_IOFFSET_H,
> +			 bulk_reg, 2);
> +	regmap_bulk_write(charger->rk808->regmap, RK817_GAS_GAUGE_CAL_OFFSET_H,
> +			  bulk_reg, 2);
> +}
> +
> +static int rk817_bat_calib_cap(struct rk817_charger *charger)
> +{
> +	struct rk808 *rk808 = charger->rk808;
> +	int reg, tmp, charge_now, charge_now_adc, dsoc_value;
> +	u8 bulk_reg[4];
> +
> +	/* Calibrate the dsoc on a fully charged battery */
> +
> +	regmap_read(rk808->regmap, RK817_PMIC_CHRG_STS, &reg);
> +	tmp = (reg >> 4) & 0x07;
> +	if (tmp == CHARGE_FINISH) {
> +		/* Read the columb counter */
> +		regmap_bulk_read(rk808->regmap, RK817_GAS_GAUGE_Q_PRES_H3,
> +				 bulk_reg, 4);
> +		charge_now_adc = get_unaligned_be32(bulk_reg);
> +		if (charge_now_adc < 0)
> +			charge_now_adc = 0;
> +		charge_now = ADC_TO_CHARGE_UAH(charge_now_adc, charger->res_div);
> +
> +		/* Get and set our DSOC value with a full charge */
> +
> +		dsoc_value = ((charge_now * 100) /
> +			      (charger->bat_charge_full_design_uah / 1000));
> +
> +		if (!charger->dsoc_cal) {
> +			if (dsoc_value > 100000)
> +				charger->dsoc = 100000;
> +			if (dsoc_value != charger->dsoc) {
> +				charger->dsoc = dsoc_value;
> +				put_unaligned_le24(dsoc_value, bulk_reg);
> +				regmap_bulk_write(rk808->regmap,
> +						  RK817_GAS_GAUGE_BAT_R1,
> +						  bulk_reg, 3);
> +			}
> +			/* Mark our dsoc as calibrated. */
> +			charger->dsoc_cal = 1;
> +		}
> +
> +		/* In the event our columb counter has drifted over the
> +		 * calibrated dsoc of the battery, adjust the columb counter
> +		 * to correct the drift. Don't do this unless we already
> +		 * calibrated our dsoc at a fully charged state.
> +		 */
> +
> +		if (dsoc_value > charger->dsoc && charger->dsoc_cal) {
> +			/* Order of operations matters here to ensure we keep
> +			 * enough precision until the last step to keep from
> +			 * making needless updates to columb counter.
> +			 */
> +			charge_now = charger->dsoc *
> +				     (charger->bat_charge_full_design_uah
> +				     / 1000) / 100;
> +			charge_now_adc = CHARGE_TO_ADC((charge_now / 1000),
> +					 charger->res_div);
> +
> +			put_unaligned_be32(charge_now_adc, bulk_reg);
> +			regmap_bulk_write(rk808->regmap,
> +					  RK817_GAS_GAUGE_Q_INIT_H3,
> +					  bulk_reg, 4);
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +static int rk817_bat_get_prop(struct power_supply *ps,
> +		enum power_supply_property prop,
> +		union power_supply_propval *val)
> +{
> +	struct rk817_charger *charger = power_supply_get_drvdata(ps);
> +	uint32_t tmp = 0;
> +	/* Registers for current is a signed 16bit int */
> +	short int cur = 0;
> +	/* Registers for capacity-now is a signed 32bit int */
> +	int32_t charge_now = 0;
> +	int ret = 0;
> +	int reg = 0;
> +	u8 bulk_reg[4];
> +	struct rk808 *rk808 = charger->rk808;
> +
> +	/* Recalibrate voltage and current readings if we need to BSP does both
> +	 * on CUR_CALIB_UPD, ignoring VOL_CALIB_UPD. Curiously enough, both
> +	 * documentation and the BSP show that you perform an update if bit 7
> +	 * is 1, but you clear the status by writing a 1 to bit 7.
> +	 */
> +	regmap_read(rk808->regmap, RK817_GAS_GAUGE_ADC_CONFIG1, &reg);
> +	tmp = (reg >> 7) & 0x01;
> +	if (tmp) {
> +		rk817_bat_calib_cur(charger);
> +		rk817_bat_calib_vol(charger);
> +		regmap_write_bits(rk808->regmap, RK817_GAS_GAUGE_ADC_CONFIG1,
> +				   RK817_CUR_CALIB_UPD, (1 << 7));
> +	}
> +
> +	rk817_bat_calib_cap(charger);
> +
> +	switch (prop) {
> +	case POWER_SUPPLY_PROP_PRESENT:
> +		regmap_read(rk808->regmap, RK817_PMIC_CHRG_STS, &reg);
> +		val->intval = (reg >> 7);
> +		break;
> +	case POWER_SUPPLY_PROP_STATUS:
> +		if (!charger->plugged_in) {
> +			val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
> +			break;
> +		}
> +		ret = regmap_read(rk808->regmap, RK817_PMIC_CHRG_STS, &reg);
> +		if (ret)
> +			return ret;
> +		tmp = (reg >> 4) & 0x07;
> +		switch (tmp) {
> +		case CHRG_OFF:
> +			val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
> +			break;
> +		/* Dead charge is documented, but not explained. I never
> +		 * observed it but assume it's a pre-charge for a dead
> +		 * battery.
> +		 */
> +		case DEAD_CHRG:
> +		case TRICKLE_CHRG:
> +		case CC_OR_CV_CHRG:
> +			val->intval = POWER_SUPPLY_STATUS_CHARGING;
> +			break;
> +		case CHARGE_FINISH:
> +			val->intval = POWER_SUPPLY_STATUS_FULL;
> +			break;
> +		default:
> +			val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
> +			return -EINVAL;
> +
> +		}
> +		break;
> +	case POWER_SUPPLY_PROP_CHARGE_TYPE:
> +		ret = regmap_read(rk808->regmap, RK817_PMIC_CHRG_STS, &reg);
> +		if (ret)
> +			return ret;
> +		tmp = (reg >> 4) & 0x07;
> +		switch (tmp) {
> +		case CHRG_OFF:
> +		case CHARGE_FINISH:
> +			val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE;
> +			break;
> +		case TRICKLE_CHRG:
> +			val->intval = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
> +			break;
> +		case DEAD_CHRG:
> +		case CC_OR_CV_CHRG:
> +			val->intval = POWER_SUPPLY_CHARGE_TYPE_STANDARD;
> +			break;
> +		default:
> +			val->intval = POWER_SUPPLY_CHARGE_TYPE_UNKNOWN;
> +			break;
> +		}
> +		break;
> +	case POWER_SUPPLY_PROP_CHARGE_FULL:
> +		val->intval = ((charger->bat_charge_full_design_uah /
> +			       1000) * charger->dsoc) / 100;
> +		break;
> +	case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
> +		val->intval = charger->bat_charge_full_design_uah;
> +		break;
> +	case POWER_SUPPLY_PROP_CHARGE_EMPTY_DESIGN:
> +		val->intval = 0;
> +		break;
> +	case POWER_SUPPLY_PROP_CHARGE_NOW:
> +		regmap_bulk_read(rk808->regmap, RK817_GAS_GAUGE_Q_PRES_H3,
> +				 bulk_reg, 4);
> +		charge_now = get_unaligned_be32(bulk_reg);
> +		if (charge_now < 0)
> +			charge_now = 0;
> +		val->intval = ADC_TO_CHARGE_UAH(charge_now, charger->res_div);
> +		break;
> +	case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
> +		val->intval = charger->bat_voltage_min_design_uv;
> +		break;
> +	case POWER_SUPPLY_PROP_VOLTAGE_BOOT:
> +		regmap_bulk_read(rk808->regmap, RK817_GAS_GAUGE_PWRON_VOL_H,
> +				 bulk_reg, 2);
> +		tmp = get_unaligned_be16(bulk_reg);
> +		val->intval = (charger->voltage_k * tmp) +
> +			       1000 * charger->voltage_b;
> +		break;
> +	case POWER_SUPPLY_PROP_VOLTAGE_AVG:
> +		regmap_bulk_read(rk808->regmap, RK817_GAS_GAUGE_BAT_VOL_H,
> +				 bulk_reg, 2);
> +		tmp = get_unaligned_be16(bulk_reg);
> +		val->intval = (charger->voltage_k * tmp) +
> +			       1000 * charger->voltage_b;
> +		break;
> +	case POWER_SUPPLY_PROP_VOLTAGE_OCV:
> +		regmap_bulk_read(rk808->regmap, RK817_GAS_GAUGE_OCV_VOL_H,
> +				 bulk_reg, 2);
> +		tmp = get_unaligned_be16(bulk_reg);
> +		val->intval = (charger->voltage_k * tmp) +
> +			       1000 * charger->voltage_b;
> +		break;
> +	case POWER_SUPPLY_PROP_CURRENT_BOOT:
> +		regmap_bulk_read(rk808->regmap, RK817_GAS_GAUGE_PWRON_CUR_H,
> +				 bulk_reg, 2);
> +		cur = get_unaligned_be16(bulk_reg);
> +		val->intval = ADC_TO_CURRENT(cur, charger->res_div);
> +		break;
> +	case POWER_SUPPLY_PROP_CURRENT_AVG:
> +		regmap_bulk_read(rk808->regmap, RK817_GAS_GAUGE_BAT_CUR_H,
> +				 bulk_reg, 2);
> +		cur = get_unaligned_be16(bulk_reg);
> +		val->intval = ADC_TO_CURRENT(cur, charger->res_div);
> +		break;
> +	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
> +		regmap_read(rk808->regmap, RK817_PMIC_CHRG_OUT, &tmp);
> +		val->intval = rk817_chg_cur_from_reg(tmp & RK817_CHRG_CUR_SEL);
> +		break;
> +	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
> +		regmap_read(rk808->regmap, RK817_PMIC_CHRG_OUT, &tmp);
> +		val->intval = ((((tmp & RK817_CHRG_VOL_SEL) >> 4) * 50000) +
> +			       4100000);
> +		break;
> +	case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
> +		val->intval = charger->bat_voltage_max_design_uv;
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +	return 0;
> +}
> +
> +static int rk817_chg_get_prop(struct power_supply *ps,
> +			      enum power_supply_property prop,
> +			      union power_supply_propval *val)
> +{
> +	struct rk817_charger *charger = power_supply_get_drvdata(ps);
> +	int vol, tmp = 0;
> +	u8 bulk_reg[2];
> +
> +	switch (prop) {
> +	case POWER_SUPPLY_PROP_ONLINE:
> +		val->intval = charger->plugged_in;
> +		break;
> +	case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
> +		/* max voltage from datasheet at 5.5v (default 5.0v) */
> +		val->intval = 5500000;
> +		break;
> +	case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
> +		/* min voltage from datasheet at 3.8v (default 5.0v) */
> +		val->intval = 3800000;
> +		break;
> +	case POWER_SUPPLY_PROP_VOLTAGE_AVG:
> +		/* Note that on my example hardware (an Odroid Go Advance) the
> +		 * voltage of the power connector is measured on the register
> +		 * labelled USB in the datasheet; I don't know if this is how
> +		 * it is designed or just a quirk of the implementation. I
> +		 * believe this will also measure the voltage of the USB output
> +		 * when in OTG mode, if that is the case we may need to change
> +		 * this in the future to return 0 if the power supply status
> +		 * is offline.
> +		 */
> +		regmap_bulk_read(charger->rk808->regmap,
> +				 RK817_GAS_GAUGE_USB_VOL_H,
> +				 bulk_reg, 2);
> +		tmp = get_unaligned_be16(bulk_reg);
> +		vol = ((charger->voltage_k * tmp / 1000 + charger->voltage_b) *
> +		       60 / 46);
> +		val->intval = vol * 1000;
> +		break;
> +	/* While it's possible that other implementations could use different
> +	 * USB types, the current implementation for this PMIC (the Odroid Go
> +	 * Advance) only uses a dedicated charging port with no rx/tx lines.
> +	 */
> +	case POWER_SUPPLY_PROP_USB_TYPE:
> +		val->intval = POWER_SUPPLY_USB_TYPE_DCP;
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +	return 0;
> +
> +}
> +
> +static irqreturn_t rk817_plug_in_isr(int irq, void *cg)
> +{
> +	struct rk817_charger *charger;
> +
> +	charger = (struct rk817_charger *)cg;
> +	charger->plugged_in = 1;
> +	power_supply_changed(charger->chg_ps);
> +	power_supply_changed(charger->bat_ps);
> +	dev_dbg(charger->dev, "Power Cord Inserted\n");
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static irqreturn_t rk817_plug_out_isr(int irq, void *cg)
> +{
> +	struct rk817_charger *charger;
> +	struct rk808 *rk808;
> +
> +	charger = (struct rk817_charger *)cg;
> +	rk808 = charger->rk808;
> +	charger->plugged_in = 0;
> +	power_supply_changed(charger->bat_ps);
> +	power_supply_changed(charger->chg_ps);
> +
> +	/* For some reason the bits of RK817_PMIC_CHRG_IN reset whenever the
> +	 * power cord is unplugged. This was not documented in the BSP kernel
> +	 * or the datasheet and only discovered by trial and error. Set minimum
> +	 * USB input voltage to 4.5v and enable USB voltage input limit.
> +	 */
> +	regmap_write_bits(rk808->regmap, RK817_PMIC_CHRG_IN,
> +			  RK817_USB_VLIM_SEL, (0x05 << 4));
> +	regmap_write_bits(rk808->regmap, RK817_PMIC_CHRG_IN, RK817_USB_VLIM_EN,
> +			  (0x01 << 7));
> +
> +	/* Set average USB input current limit to 1.5A and enable USB current
> +	 * input limit.
> +	 */
> +	regmap_write_bits(rk808->regmap, RK817_PMIC_CHRG_IN,
> +			  RK817_USB_ILIM_SEL, 0x03);
> +	regmap_write_bits(rk808->regmap, RK817_PMIC_CHRG_IN, RK817_USB_ILIM_EN,
> +			  (0x01 << 3));
> +
> +	dev_dbg(charger->dev, "Power Cord Removed\n");
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static enum power_supply_property rk817_bat_props[] = {
> +	POWER_SUPPLY_PROP_PRESENT,
> +	POWER_SUPPLY_PROP_STATUS,
> +	POWER_SUPPLY_PROP_CHARGE_TYPE,
> +	POWER_SUPPLY_PROP_CHARGE_FULL,
> +	POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
> +	POWER_SUPPLY_PROP_CHARGE_EMPTY_DESIGN,
> +	POWER_SUPPLY_PROP_CHARGE_NOW,
> +	POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX,
> +	POWER_SUPPLY_PROP_VOLTAGE_BOOT,
> +	POWER_SUPPLY_PROP_VOLTAGE_AVG,
> +	POWER_SUPPLY_PROP_VOLTAGE_OCV,
> +	POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
> +	POWER_SUPPLY_PROP_CURRENT_BOOT,
> +	POWER_SUPPLY_PROP_CURRENT_AVG,
> +	POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
> +	POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
> +};
> +
> +static enum power_supply_property rk817_chg_props[] = {
> +	POWER_SUPPLY_PROP_ONLINE,
> +	POWER_SUPPLY_PROP_USB_TYPE,
> +	POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
> +	POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
> +	POWER_SUPPLY_PROP_VOLTAGE_AVG,
> +};
> +
> +static enum power_supply_usb_type rk817_usb_type[] = {
> +	POWER_SUPPLY_USB_TYPE_DCP,
> +	POWER_SUPPLY_USB_TYPE_UNKNOWN,
> +};
> +
> +static const struct power_supply_desc rk817_bat_desc = {
> +	.name = "rk817-battery",
> +	.type = POWER_SUPPLY_TYPE_BATTERY,
> +	.properties = rk817_bat_props,
> +	.num_properties = ARRAY_SIZE(rk817_bat_props),
> +	.get_property = rk817_bat_get_prop,
> +};
> +
> +static const struct power_supply_desc rk817_chg_desc = {
> +	.name = "rk817-charger",
> +	.type = POWER_SUPPLY_TYPE_USB,
> +	.usb_types = rk817_usb_type,
> +	.num_usb_types = ARRAY_SIZE(rk817_usb_type),
> +	.properties = rk817_chg_props,
> +	.num_properties = ARRAY_SIZE(rk817_chg_props),
> +	.get_property = rk817_chg_get_prop,
> +};
> +
> +static int
> +rk817_read_or_set_full_charge_on_boot(struct rk817_charger *charger,
> +				      struct power_supply_battery_info *bat_info)
> +{
> +	struct rk808 *rk808 = charger->rk808;
> +	u8 bulk_reg[4];
> +	u32 design_charge_mah = (charger->bat_charge_full_design_uah / 1000);
> +	u32 boot_voltage, boot_charge, tmp, full_charge_cap;
> +	int ret, boot_capacity;
> +
> +	/* Read DSOC value if pre-existing. If not, initialize at 100%.
> +	 * Note endianness, also register says it's for resistance,
> +	 * however BSP kernel treats this as an nvram field for the DSOC
> +	 * as best I can tell. Doing the same for backwards compatibility.
> +	 */
> +	ret = regmap_bulk_read(rk808->regmap, RK817_GAS_GAUGE_BAT_R1, bulk_reg, 3);
> +	if (ret < 0)
> +		return ret;
> +	charger->dsoc = get_unaligned_le24(bulk_reg);
> +	/* If we have an invalid DSOC, write 100 (100000) as default. */
> +	if (charger->dsoc < 1000 || charger->dsoc > 100000) {
> +		charger->dsoc = 100000;
> +		put_unaligned_le24(charger->dsoc, bulk_reg);
> +		regmap_bulk_write(rk808->regmap, RK817_GAS_GAUGE_BAT_R1,
> +				  bulk_reg, 3);
> +	}
> +
> +	/* Register appears to be nvram that stores capacity in mAH. Note
> +	 * endianness, keeping consistent with BSP kernel, however it looks
> +	 * like we can use any arbitrary method to store value if we don't care
> +	 * about compatibility. Additionally, it doesn't appear that this value
> +	 * is used for anything, so realistically getting it and setting it is
> +	 * to ensure backward compatibility with BSP and serves no purpose with
> +	 * this driver, and I'm not sure if the BSP driver does anything with
> +	 * this value either.
> +	 */
> +
> +	ret = regmap_bulk_read(rk808->regmap, RK817_GAS_GAUGE_DATA3, bulk_reg, 3);
> +	if (ret < 0)
> +		return ret;
> +
> +	full_charge_cap = get_unaligned_le24(bulk_reg);
> +
> +	/* Sanity checking for values equal to zero or less than would be
> +	 * practical for this device (BSP Kernel assumes 500mAH or less) for
> +	 * practicality purposes.
> +	 */
> +	if (full_charge_cap < 500) {
> +		put_unaligned_le24(design_charge_mah, bulk_reg);
> +		ret = regmap_bulk_write(rk808->regmap, RK817_GAS_GAUGE_DATA3, bulk_reg, 3);
> +		if (ret < 0)
> +			return ret;
> +		dev_info(charger->dev,
> +			 "Invalid NVRAM Data for max charge, setting to design capacity %u uAH\n",
> +			 design_charge_mah*1000);
> +	}
> +
> +	/* Capture boot voltage and look up boot capacity from OCV tables. */
> +
> +	regmap_bulk_read(rk808->regmap, RK817_GAS_GAUGE_PWRON_VOL_H,
> +			 bulk_reg, 2);
> +	tmp = get_unaligned_be16(bulk_reg);
> +	boot_voltage = (charger->voltage_k * tmp) + 1000 * charger->voltage_b;
> +	/* Since only implementation has no working thermistor, assume 20C for
> +	 * OCV lookup. If lookup fails, report error with OCV table.
> +	 */
> +	boot_capacity = power_supply_batinfo_ocv2cap(bat_info, boot_voltage, 20);
> +	if (boot_capacity < 0) {
> +		return dev_err_probe(charger->dev,
> +				     boot_capacity,
> +				     "Unable to read boot charge from OCV table: %i\n",
> +				     boot_capacity);
> +	}
> +
> +	/* Write boot charge to registers, estimate boot charge based on
> +	 * capacity and max charge of battery.
> +	 */
> +	boot_charge = (boot_capacity * charger->bat_charge_full_design_uah) / 100;
> +	tmp = CHARGE_TO_ADC((boot_charge / 1000), charger->res_div);
> +	put_unaligned_be32(tmp, bulk_reg);
> +	regmap_bulk_write(rk808->regmap, RK817_GAS_GAUGE_Q_INIT_H3,
> +			  bulk_reg, 4);
> +
> +	/* Set QMAX value to max design capacity. */
> +	tmp = CHARGE_TO_ADC((charger->bat_charge_full_design_uah / 1000),
> +			    charger->res_div);
> +	put_unaligned_be32(tmp, bulk_reg);
> +	ret = regmap_bulk_write(rk808->regmap, RK817_GAS_GAUGE_Q_MAX_H3,
> +				bulk_reg, 4);
> +	if (ret < 0)
> +		return ret;
> +
> +	return 0;
> +}
> +
> +static int rk817_battery_init(struct rk817_charger *charger,
> +			      struct power_supply_battery_info *bat_info)
> +{
> +	struct rk808 *rk808 = charger->rk808;
> +	u32 tmp, max_chg_vol_mv, max_chg_cur_ma;
> +	u8 max_chg_vol_reg, chg_term_i_reg, max_chg_cur_reg;
> +	int ret, chg_term_ma;
> +	u8 bulk_reg[2];
> +
> +	/* Get initial plug state */
> +	regmap_read(rk808->regmap, RK817_SYS_STS, &tmp);
> +	charger->plugged_in = (tmp & RK817_PLUG_IN_STS);
> +
> +	/* Turn on all ADC functions to measure battery, USB, and sys voltage,
> +	 * as well as batt temp. Note only tested implementation so far does
> +	 * not use a battery with a thermistor.
> +	 */
> +	regmap_write(rk808->regmap, RK817_GAS_GAUGE_ADC_CONFIG0, 0xfc);
> +
> +	/* Set relax mode voltage sampling interval and ADC offset calibration
> +	 * interval to 8 minutes to mirror BSP kernel. Set voltage and current
> +	 * modes to average to mirror BSP kernel.
> +	 */
> +	regmap_write(rk808->regmap, RK817_GAS_GAUGE_GG_CON, 0x04);
> +
> +	/* Write relax threshold, derived from sleep enter current. */
> +	tmp = CURRENT_TO_ADC(charger->sleep_enter_current, charger->res_div);
> +	put_unaligned_be16(tmp, bulk_reg);
> +	regmap_bulk_write(rk808->regmap, RK817_GAS_GAUGE_RELAX_THRE_H,
> +			  bulk_reg, 2);
> +
> +	/* Write sleep sample current, derived from sleep filter current. */
> +	tmp = CURRENT_TO_ADC(charger->sleep_filter_current, charger->res_div);
> +	put_unaligned_be16(tmp, bulk_reg);
> +	regmap_bulk_write(rk808->regmap, RK817_GAS_GAUGE_SLEEP_CON_SAMP_CUR_H,
> +			  bulk_reg, 2);
> +
> +	/* Restart battery relax voltage */
> +	regmap_write_bits(rk808->regmap, RK817_GAS_GAUGE_GG_STS,
> +			  RK817_RELAX_VOL_UPD, (0x0 << 2));
> +
> +	/* Set OCV Threshold Voltage to 127.5mV. This was hard coded like this
> +	 * in the BSP.
> +	 */
> +	regmap_write(rk808->regmap, RK817_GAS_GAUGE_OCV_THRE_VOL, 0xff);
> +
> +	/* Set maximum charging voltage to battery max voltage. Trying to be
> +	 * incredibly safe with these value, as setting them wrong could
> +	 * overcharge the battery, which would be very bad.
> +	 */
> +	max_chg_vol_mv = bat_info->constant_charge_voltage_max_uv / 1000;
> +	max_chg_cur_ma = bat_info->constant_charge_current_max_ua / 1000;
> +
> +	if (max_chg_vol_mv < 4100) {
> +		return dev_err_probe(charger->dev, -EINVAL,
> +		       "invalid max charger voltage, value %u unsupported\n",
> +			max_chg_vol_mv * 1000);
> +	}
> +	if (max_chg_vol_mv > 4450) {
> +		dev_info(charger->dev,
> +			 "Setting max charge voltage to 4450000uv\n");
> +		max_chg_vol_mv = 4450;
> +	}
> +
> +	if (max_chg_cur_ma < 500) {
> +		return dev_err_probe(charger->dev, -EINVAL,
> +		       "invalid max charger current, value %u unsupported\n",
> +		       max_chg_cur_ma * 1000);
> +	}
> +	if (max_chg_cur_ma > 3500)
> +		dev_info(charger->dev,
> +			 "Setting max charge current to 3500000ua\n");
> +
> +	/* Now that the values are sanity checked, if we subtract 4100 from the
> +	 * max voltage and divide by 50, we conviently get the exact value for
> +	 * the registers, which are 4.1v, 4.15v, 4.2v, 4.25v, 4.3v, 4.35v,
> +	 * 4.4v, and 4.45v; these correspond to values 0x00 through 0x07.
> +	 */
> +	max_chg_vol_reg = (max_chg_vol_mv - 4100) / 50;
> +
> +	max_chg_cur_reg = rk817_chg_cur_to_reg(max_chg_cur_ma);
> +
> +	if (max_chg_vol_reg < 0 || max_chg_vol_reg > 7) {
> +		return dev_err_probe(charger->dev, -EINVAL,
> +		       "invalid max charger voltage, value %u unsupported\n",
> +		       max_chg_vol_mv * 1000);
> +	}
> +	if (max_chg_cur_reg < 0 || max_chg_cur_reg > 7) {
> +		return dev_err_probe(charger->dev, -EINVAL,
> +		       "invalid max charger current, value %u unsupported\n",
> +		       max_chg_cur_ma * 1000);
> +	}
> +
> +	/* Write the values to the registers, and deliver an emergency warning
> +	 * in the event they are not written correctly.
> +	 */
> +	ret = regmap_write_bits(rk808->regmap, RK817_PMIC_CHRG_OUT,
> +				RK817_CHRG_VOL_SEL, (max_chg_vol_reg << 4));
> +	if (ret) {
> +		dev_emerg(charger->dev,
> +			  "Danger, unable to set max charger voltage: %u\n",
> +			  ret);
> +	}
> +
> +	ret = regmap_write_bits(rk808->regmap, RK817_PMIC_CHRG_OUT,
> +				RK817_CHRG_CUR_SEL, max_chg_cur_reg);
> +	if (ret) {
> +		dev_emerg(charger->dev,
> +			  "Danger, unable to set max charger current: %u\n",
> +			  ret);
> +	}
> +
> +	/* Set charge finishing mode to analog */
> +	regmap_write_bits(rk808->regmap, RK817_PMIC_CHRG_TERM,
> +			  RK817_CHRG_TERM_ANA_DIG, (0x0 << 2));
> +
> +	/* Set charge finish current, warn if value not in range and keep
> +	 * default.
> +	 */
> +	chg_term_ma = bat_info->charge_term_current_ua / 1000;
> +	if (chg_term_ma < 150 || chg_term_ma > 400) {
> +		dev_warn(charger->dev,
> +			 "Invalid charge termination value %u, keeping default\n",
> +			 chg_term_ma * 1000);
> +		chg_term_ma = 200;
> +	}
> +
> +	/* Values of 150ma, 200ma, 300ma, and 400ma correspond to 00, 01, 10,
> +	 * and 11.
> +	 */
> +	chg_term_i_reg = (chg_term_ma - 100) / 100;
> +	regmap_write_bits(rk808->regmap, RK817_PMIC_CHRG_TERM,
> +			  RK817_CHRG_TERM_ANA_SEL, chg_term_i_reg);
> +
> +	ret = rk817_read_or_set_full_charge_on_boot(charger, bat_info);
> +	if (ret < 0)
> +		return ret;
> +
> +	/* Set minimum USB input voltage to 4.5v and enable USB voltage input
> +	 * limit.
> +	 */
> +	regmap_write_bits(rk808->regmap, RK817_PMIC_CHRG_IN,
> +			  RK817_USB_VLIM_SEL, (0x05 << 4));
> +	regmap_write_bits(rk808->regmap, RK817_PMIC_CHRG_IN, RK817_USB_VLIM_EN,
> +			  (0x01 << 7));
> +
> +	/* Set average USB input current limit to 1.5A and enable USB current
> +	 * input limit.
> +	 */
> +	regmap_write_bits(rk808->regmap, RK817_PMIC_CHRG_IN,
> +			  RK817_USB_ILIM_SEL, 0x03);
> +	regmap_write_bits(rk808->regmap, RK817_PMIC_CHRG_IN, RK817_USB_ILIM_EN,
> +			  (0x01 << 3));
> +
> +	return 0;
> +}
> +
> +static int rk817_charger_probe(struct platform_device *pdev)
> +{
> +	struct rk808 *rk808 = dev_get_drvdata(pdev->dev.parent);
> +	struct rk817_charger *charger;
> +	struct device_node *node;
> +	struct power_supply_battery_info bat_info = { };
> +	struct device *dev = &pdev->dev;
> +	struct power_supply_config pscfg = {};
> +	int plugin_irq, plugout_irq;
> +	int of_value;
> +	int ret;
> +
> +	node = of_get_child_by_name(dev->parent->of_node, "battery");
> +	if (!node)
> +		return -ENODEV;
> +
> +	charger = devm_kzalloc(&pdev->dev, sizeof(*charger), GFP_KERNEL);
> +	if (!charger)
> +		return -ENOMEM;
> +
> +	charger->rk808 = rk808;
> +
> +	charger->dev = &pdev->dev;
> +	platform_set_drvdata(pdev, charger);
> +
> +	rk817_bat_calib_vol(charger);
> +
> +	pscfg.drv_data = charger;
> +	pscfg.of_node = node;
> +
> +	/* Get sample resistor value. Note only values of 10000 or 20000
> +	 * microohms are allowed. Schematic for my test implementation (an
> +	 * Odroid Go Advance) shows a 10 milliohm resistor for reference.
> +	 */
> +	ret = of_property_read_u32(node, "rockchip,resistor-sense-micro-ohms",
> +				   &of_value);
> +	if (ret < 0) {
> +		return dev_err_probe(dev, ret,
> +				     "Error reading sample resistor value\n");
> +	}
> +	/* Store as a 1 or a 2, since all we really use the value for is as a
> +	 * divisor in some calculations.
> +	 */
> +	charger->res_div = (of_value == 20000) ? 2 : 1;
> +
> +	/* Get sleep enter current value. Not sure what this value is for
> +	 * other than to help calibrate the relax threshold.
> +	 */
> +	ret = of_property_read_u32(node,
> +				   "rockchip,sleep-enter-current-microamp",
> +				   &of_value);
> +	if (ret < 0) {
> +		return dev_err_probe(dev, ret,
> +				     "Error reading sleep enter cur value\n");
> +	}
> +	charger->sleep_enter_current = of_value;
> +
> +	/* Get sleep filter current value */
> +	ret = of_property_read_u32(node,
> +				   "rockchip,sleep-filter-current-microamp",
> +				   &of_value);
> +	if (ret < 0) {
> +		return dev_err_probe(dev, ret,
> +				     "Error reading sleep filter cur value\n");
> +	}
> +
> +	charger->sleep_filter_current = of_value;
> +
> +	charger->bat_ps = devm_power_supply_register(&pdev->dev,
> +						     &rk817_bat_desc, &pscfg);
> +
> +	charger->chg_ps = devm_power_supply_register(&pdev->dev,
> +						     &rk817_chg_desc, &pscfg);
> +
> +	if (IS_ERR(charger->chg_ps))
> +		return dev_err_probe(dev, -EINVAL,
> +				     "Battery failed to probe\n");
> +
> +	if (IS_ERR(charger->chg_ps))
> +		return dev_err_probe(dev, -EINVAL,
> +				     "Charger failed to probe\n");
> +
> +	ret = power_supply_get_battery_info(charger->bat_ps,
> +					    &bat_info);
> +	if (ret) {
> +		return dev_err_probe(dev, ret,
> +				     "Unable to get battery info: %d\n", ret);
> +	}
> +
> +	if ((!bat_info.charge_full_design_uah) ||
> +	    (!bat_info.voltage_min_design_uv) ||
> +	    (!bat_info.voltage_max_design_uv) ||
> +	    (!bat_info.constant_charge_voltage_max_uv) ||
> +	    (!bat_info.constant_charge_current_max_ua) ||
> +	    (!bat_info.charge_term_current_ua)) {
> +		return dev_err_probe(dev, -EINVAL,
> +				     "Required battery info missing.\n");
> +	}
> +
> +	charger->bat_charge_full_design_uah = bat_info.charge_full_design_uah;
> +	charger->bat_voltage_min_design_uv = bat_info.voltage_min_design_uv;
> +	charger->bat_voltage_max_design_uv = bat_info.voltage_max_design_uv;
> +
> +	/* Has to run after power_supply_get_battery_info as it depends on some
> +	 * values discovered from that routine.
> +	 */
> +	ret = rk817_battery_init(charger, &bat_info);
> +	if (ret)
> +		return ret;
> +
> +	power_supply_put_battery_info(charger->bat_ps, &bat_info);
> +
> +	plugin_irq = platform_get_irq(pdev, 0);
> +	if (plugin_irq < 0)
> +		return plugin_irq;
> +
> +	plugout_irq = platform_get_irq(pdev, 1);
> +	if (plugout_irq < 0)
> +		return plugout_irq;
> +
> +	ret = devm_request_threaded_irq(charger->dev, plugin_irq, NULL,
> +					rk817_plug_in_isr,
> +					IRQF_TRIGGER_RISING | IRQF_ONESHOT,
> +					"rk817_plug_in", charger);
> +	if (ret) {
> +		return dev_err_probe(&pdev->dev, ret,
> +				      "plug_in_irq request failed!\n");
> +	}
> +
> +	ret = devm_request_threaded_irq(charger->dev, plugout_irq, NULL,
> +					rk817_plug_out_isr,
> +					IRQF_TRIGGER_RISING | IRQF_ONESHOT,
> +					"rk817_plug_out", charger);
> +	if (ret) {
> +		return dev_err_probe(&pdev->dev, ret,
> +				     "plug_out_irq request failed!\n");
> +	}
> +
> +	return 0;
> +}
> +
> +
> +static struct platform_driver rk817_charger_driver = {
> +	.probe    = rk817_charger_probe,
> +	.driver   = {
> +		.name  = "rk817-charger",
> +	},
> +};
> +module_platform_driver(rk817_charger_driver);
> +
> +MODULE_DESCRIPTION("Battery power supply driver for RK817 PMIC");
> +MODULE_AUTHOR("Maya Matuszczyk <maccraft123mc@gmail.com>");
> +MODULE_LICENSE("GPL");
> -- 
> 2.25.1
>
Nicolas Frattaroli Nov. 30, 2021, 2:03 a.m. UTC | #2
On Donnerstag, 16. September 2021 21:42:04 CET Chris Morgan wrote:
> From: Chris Morgan <macromorgan@hotmail.com>
> 
> This series is to add support for the Rockchip rk817 battery charger
> which is present in all Rockchip RK817 PMICs. The driver was written
> as a joint effort by Maya Matuszczyk <maccraft123mc@gmail.com> and
> myself Chris Morgan <macromorgan@hotmail.com>.

Hi Chris and Maya,

Gave this a whirl on my Quartz64 Model A. I noticed that this will
happily let me discharge past voltage_min_design:

 $ cat /sys/class/power_supply/rk817-battery/voltage_min_design 
 3625000
 $ cat /sys/class/power_supply/rk817-battery/voltage_avg 
 3381360

Is this normal? It went all the way to under 3V before the
board finally locked up.

Does the minimum voltage not affect some sort of cutout on
the RK817? Does it even have one? Is it the driver's job to
do something here or not?

Regards,
Nicolas Frattaroli
Nicolas Frattaroli Nov. 30, 2021, 6:12 p.m. UTC | #3
On Dienstag, 30. November 2021 17:10:21 CET Chris Morgan wrote:
> On Tue, Nov 30, 2021 at 03:03:03AM +0100, Nicolas Frattaroli wrote:
> > On Donnerstag, 16. September 2021 21:42:04 CET Chris Morgan wrote:
> > > From: Chris Morgan <macromorgan@hotmail.com>
> > > 
> > > This series is to add support for the Rockchip rk817 battery charger
> > > which is present in all Rockchip RK817 PMICs. The driver was written
> > > as a joint effort by Maya Matuszczyk <maccraft123mc@gmail.com> and
> > > myself Chris Morgan <macromorgan@hotmail.com>.
> > 
> > Hi Chris and Maya,
> > 
> > Gave this a whirl on my Quartz64 Model A. I noticed that this will
> > happily let me discharge past voltage_min_design:
> > 
> >  $ cat /sys/class/power_supply/rk817-battery/voltage_min_design 
> >  3625000
> >  $ cat /sys/class/power_supply/rk817-battery/voltage_avg 
> >  3381360
> > 
> > Is this normal? It went all the way to under 3V before the
> > board finally locked up.
> > 
> > Does the minimum voltage not affect some sort of cutout on
> > the RK817? Does it even have one? Is it the driver's job to
> > do something here or not?

Hi Chris,

> It does not look like I coded that, but I can. The PMIC has a
> selectable register (RK817_PMIC_SYS_CFG0 = 0xf1) to automatically shut
> down the system at a certain voltage (between 2.7v and 3.4v in
> increments of 100mv; bits 6-4), a register to set a low voltage value
> (between 2.8v and 3.5v in increments of 100mv; bits 2-0), and a
> register to set a low voltage action (either shut down the machine or
> trigger an interrupt; bit 3 and then I believe the interrupt is read at
> bit 7 of RK817_PMIC_INT_STS0 - 0xf8).

Excellent, I think shutting down the system is definitely the way to
go here, as by reaching this voltage we can assume userspace has gone
missing and we don't know if the kernel is still alive.

> I guess I just assumed userspace would handle it, but that's probably
> a bad assumption.

Yes, I didn't have upower installed, but over discharge protection
shouldn't be handled by userspace at all. Had I connected an unprotected
cell that didn't cut out at below 3V, it would've been irreversibly
damaged due to a simple forgotten package install. Even if unprotected
batteries are a bad idea, I think we best assume the worst case
situation here.

> What if I set the low voltage value to either 3.5v
> or the min design voltage, whichever is less; and then set the low
> voltage trigger to shut down the system? This would prevent your
> battery from dropping below 3.5v or the min design voltage (whichever
> is less, sadly 3.5v is as high as I can go for the minimum) in the
> event userspace doesn't take action first? We could also set the
> shutdown voltage to be 100mv less than the min design voltage and use
> the low voltage interrupt to trigger an action, but I'm not sure what
> action would be appropriate (and if userspace isn't listening it would
> be moot anyway).

A very simple solution would be to simply set the shutoff point to the
voltage reaching less than 3.1V. The RK817 only charges Lithium/Li-Po
batteries, and they should generally not be discharged below 3.0V. Of
the protected batteries I own, over-discharge protection generally
kicks in at the 3V threshold.

This gets rid of a lot of logic that could lead to bugs by simply setting
a known good value to begin with.

I think I can summarise how I understand this is supposed to work as
follows:

 - if battery reaches 0% and the battery alarm is rang, userspace should
   power down the system
     - this prevents unclean shutdowns
 - if the battery reaches a dangerously low voltage for its chemistry,
   the PMIC should cut power to the system as instructed by the kernel
     - last resort measure in case userspace is absent and the battery
       happens to be unprotected
 - there may or may not be battery protection which kicks in at 3V, but
   we should not rely on this working
      - some 18650 cases infamously aren't sized to fit cells with a
        protection circuit connected to it. I shan't name names.

In a freak accident where the kernel is completely locked up and the
battery is unprotected and being drained, I presume that having set
this PMIC register will still make it kick in, which lets me sleep
a little sounder at night.

Regards,
Nicolas Frattaroli

> 
> Thank you.
> 
> > 
> > Regards,
> > Nicolas Frattaroli
> > 
> > 
>
Nicolas Frattaroli Nov. 30, 2021, 7:16 p.m. UTC | #4
On Dienstag, 30. November 2021 19:33:02 CET you wrote:
> On Tue, Nov 30, 2021 at 07:12:11PM +0100, Nicolas Frattaroli wrote:
> > On Dienstag, 30. November 2021 17:10:21 CET Chris Morgan wrote:
> > > On Tue, Nov 30, 2021 at 03:03:03AM +0100, Nicolas Frattaroli wrote:
> > > > On Donnerstag, 16. September 2021 21:42:04 CET Chris Morgan wrote:
> > > > > From: Chris Morgan <macromorgan@hotmail.com>
> > > > > 
> > > > > This series is to add support for the Rockchip rk817 battery charger
> > > > > which is present in all Rockchip RK817 PMICs. The driver was written
> > > > > as a joint effort by Maya Matuszczyk <maccraft123mc@gmail.com> and
> > > > > myself Chris Morgan <macromorgan@hotmail.com>.
> > > > 
> > > > Hi Chris and Maya,
> > > > 
> > > > Gave this a whirl on my Quartz64 Model A. I noticed that this will
> > > > happily let me discharge past voltage_min_design:
> > > > 
> > > >  $ cat /sys/class/power_supply/rk817-battery/voltage_min_design 
> > > >  3625000
> > > >  $ cat /sys/class/power_supply/rk817-battery/voltage_avg 
> > > >  3381360
> > > > 
> > > > Is this normal? It went all the way to under 3V before the
> > > > board finally locked up.
> > > > 
> > > > Does the minimum voltage not affect some sort of cutout on
> > > > the RK817? Does it even have one? Is it the driver's job to
> > > > do something here or not?
> > 
> > Hi Chris,
> > 
> > > It does not look like I coded that, but I can. The PMIC has a
> > > selectable register (RK817_PMIC_SYS_CFG0 = 0xf1) to automatically shut
> > > down the system at a certain voltage (between 2.7v and 3.4v in
> > > increments of 100mv; bits 6-4), a register to set a low voltage value
> > > (between 2.8v and 3.5v in increments of 100mv; bits 2-0), and a
> > > register to set a low voltage action (either shut down the machine or
> > > trigger an interrupt; bit 3 and then I believe the interrupt is read at
> > > bit 7 of RK817_PMIC_INT_STS0 - 0xf8).
> > 
> > Excellent, I think shutting down the system is definitely the way to
> > go here, as by reaching this voltage we can assume userspace has gone
> > missing and we don't know if the kernel is still alive.
> > 
> > > I guess I just assumed userspace would handle it, but that's probably
> > > a bad assumption.
> > 
> > Yes, I didn't have upower installed, but over discharge protection
> > shouldn't be handled by userspace at all. Had I connected an unprotected
> > cell that didn't cut out at below 3V, it would've been irreversibly
> > damaged due to a simple forgotten package install. Even if unprotected
> > batteries are a bad idea, I think we best assume the worst case
> > situation here.
> > 
> > > What if I set the low voltage value to either 3.5v
> > > or the min design voltage, whichever is less; and then set the low
> > > voltage trigger to shut down the system? This would prevent your
> > > battery from dropping below 3.5v or the min design voltage (whichever
> > > is less, sadly 3.5v is as high as I can go for the minimum) in the
> > > event userspace doesn't take action first? We could also set the
> > > shutdown voltage to be 100mv less than the min design voltage and use
> > > the low voltage interrupt to trigger an action, but I'm not sure what
> > > action would be appropriate (and if userspace isn't listening it would
> > > be moot anyway).
> > 
> > A very simple solution would be to simply set the shutoff point to the
> > voltage reaching less than 3.1V. The RK817 only charges Lithium/Li-Po
> > batteries, and they should generally not be discharged below 3.0V. Of
> > the protected batteries I own, over-discharge protection generally
> > kicks in at the 3V threshold.
> > 
> > This gets rid of a lot of logic that could lead to bugs by simply setting
> > a known good value to begin with.
> > 
> > I think I can summarise how I understand this is supposed to work as
> > follows:
> > 
> >  - if battery reaches 0% and the battery alarm is rang, userspace should
> >    power down the system
> >      - this prevents unclean shutdowns
> >  - if the battery reaches a dangerously low voltage for its chemistry,
> >    the PMIC should cut power to the system as instructed by the kernel
> >      - last resort measure in case userspace is absent and the battery
> >        happens to be unprotected
> >  - there may or may not be battery protection which kicks in at 3V, but
> >    we should not rely on this working
> >       - some 18650 cases infamously aren't sized to fit cells with a
> >         protection circuit connected to it. I shan't name names.
> > 
> > In a freak accident where the kernel is completely locked up and the
> > battery is unprotected and being drained, I presume that having set
> > this PMIC register will still make it kick in, which lets me sleep
> > a little sounder at night.
> > 
> 
> Okay. The default value for automatic shutdown is 2.7v, but that's
> probably too low. I'm wondering though if this should go in the main
> MFD driver, rather than the battery driver? The reason why I ask is
> that without a battery driver to raise the shutdown voltage to 3.1v,
> the shutdown will not happen until 2.7v which risks damage to the
> battery. This means devices in production today (such as anyone running
> the Odroid Go Advance on a mainline kernel) on a battery will
> experience this behavior. Setting that register value shouldn't do
> anything harmful if you don't have a battery, so I assume if it's safe
> to set it 100% of the time for battery users it's safe to set 100% of
> the time in total. Which also begs the question, if this is such a
> danger to the hardware, should we even be doing it in Linux? We could
> be setting it in U-Boot, because if the MFD driver fails to load or the
> kernel freezes before the MFD driver loads there is STILL the risk you
> could undervolt the battery, regardless of fixing this in the MFD driver
> or the battery driver.

I believe 2.7V is still fine. The protection circuits may cut off before
that because they want to keep the longevity of the battery and there
isn't much juice left between 2.7V and 3.0V anyway. If the PMIC always
shuts down at 2.7V then there isn't anything to fear really, that's
still above the 2.5V that Wikipedia states as being the lower end of
the safe voltage range. While that's not the source to end all sources,
the PMIC having a lower cutoff probably means that some engineer did
think of this and pulled up the correct numbers.

Now that I've learned of this, I suspect there isn't really an issue
at all here. If we shut off at 2.7V we're good. Userspace should shut
down before then, and if there's battery protection that kicks in
before that, it makes really no difference to the end result, as
long as there is some cutoff somewhere

Thanks for your quick responses, my concerns regarding this are
alleviated. Devices that are already out there should be fine
in this regard.

> 
> Thank you.
> 
> > Regards,
> > Nicolas Frattaroli
> > 
> > > 
> > > Thank you.
> > > 
> > > > 
> > > > Regards,
> > > > Nicolas Frattaroli
> > > > 
> > > > 
> > > 
> > 
> > 
> > 
> > 
>
Peter Geis Jan. 27, 2022, 11:27 p.m. UTC | #5
On Wed, Oct 13, 2021 at 1:42 PM Sebastian Reichel
<sebastian.reichel@collabora.com> wrote:
>
> Hi,

Good Evening,

I've started looking into the feasibility of supporting rk818 as well with this.
While doing this, I noticed a quirk, which is addressed below.
Do you happen to have experience with the rk818?

Thanks,
Peter

>
> On Thu, Sep 16, 2021 at 02:42:07PM -0500, Chris Morgan wrote:
> > Add support for the Rockchip rk817 battery charger integrated into the
> > rk817 PMIC.
> >
> > Signed-off-by: Chris Morgan <macromorgan@hotmail.com>
> > Signed-off-by: Maya Matuszczyk <maccraft123mc@gmail.com>
> > ---
> >  drivers/power/supply/Kconfig         |   6 +
> >  drivers/power/supply/Makefile        |   1 +
> >  drivers/power/supply/rk817_charger.c | 959 +++++++++++++++++++++++++++
> >  3 files changed, 966 insertions(+)
> >  create mode 100644 drivers/power/supply/rk817_charger.c
>
> Acked-by: Sebastian Reichel <sebastian.reichel@collabora.com>
>
> -- Sebastian
>
> > diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
> > index 11f5368e810e..311130da36ff 100644
> > --- a/drivers/power/supply/Kconfig
> > +++ b/drivers/power/supply/Kconfig
> > @@ -666,6 +666,12 @@ config CHARGER_BQ256XX
> >         charge management and system power path management devices for single
> >         cell Li-ion and Li-polymer batteries.
> >
> > +config CHARGER_RK817
> > +     tristate "Rockchip RK817 PMIC Battery Charger"
> > +     depends on MFD_RK808
> > +     help
> > +       Say Y to include support for Rockchip RK817 Battery Charger.
> > +
> >  config CHARGER_SMB347
> >       tristate "Summit Microelectronics SMB3XX Battery Charger"
> >       depends on I2C
> > diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile
> > index 33059a91f60c..9497d2105712 100644
> > --- a/drivers/power/supply/Makefile
> > +++ b/drivers/power/supply/Makefile
> > @@ -87,6 +87,7 @@ obj-$(CONFIG_CHARGER_BQ2515X)       += bq2515x_charger.o
> >  obj-$(CONFIG_CHARGER_BQ25890)        += bq25890_charger.o
> >  obj-$(CONFIG_CHARGER_BQ25980)        += bq25980_charger.o
> >  obj-$(CONFIG_CHARGER_BQ256XX)        += bq256xx_charger.o
> > +obj-$(CONFIG_CHARGER_RK817)  += rk817_charger.o
> >  obj-$(CONFIG_CHARGER_SMB347) += smb347-charger.o
> >  obj-$(CONFIG_CHARGER_TPS65090)       += tps65090-charger.o
> >  obj-$(CONFIG_CHARGER_TPS65217)       += tps65217_charger.o
> > diff --git a/drivers/power/supply/rk817_charger.c b/drivers/power/supply/rk817_charger.c
> > new file mode 100644
> > index 000000000000..34338aebe269
> > --- /dev/null
> > +++ b/drivers/power/supply/rk817_charger.c
> > @@ -0,0 +1,959 @@
> > +// SPDX-License-Identifier: GPL-2.0-only
> > +/*
> > + * Charger Driver for Rockchip rk817
> > + *
> > + * Copyright (c) 2021
> > + *
> > + * Authors: Maya Matuszczyk <maccraft123mc@gmail.com>
> > + *       Chris Morgan <macromorgan@hotmail.com>
> > + */
> > +
> > +#include <linux/mfd/rk808.h>
> > +#include <linux/irq.h>
> > +#include <linux/of_gpio.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/regmap.h>
> > +#include <linux/power_supply.h>
> > +#include <asm/unaligned.h>
> > +
> > +/* Charging statuses reported by hardware register */
> > +enum rk817_charge_status {
> > +     CHRG_OFF,
> > +     DEAD_CHRG,
> > +     TRICKLE_CHRG,
> > +     CC_OR_CV_CHRG,
> > +     CHARGE_FINISH,
> > +     USB_OVER_VOL,
> > +     BAT_TMP_ERR,
> > +     BAT_TIM_ERR,
> > +};
> > +
> > +/* Max charging current read to/written from hardware register.
> > + * Note how highest value corresponding to 0x7 is the lowest
> > + * current, this is per the datasheet.
> > + */
> > +enum rk817_chg_cur {
> > +     CHG_1A,
> > +     CHG_1_5A,
> > +     CHG_2A,
> > +     CHG_2_5A,
> > +     CHG_2_75A,
> > +     CHG_3A,
> > +     CHG_3_5A,
> > +     CHG_0_5A,
> > +};
> > +
> > +struct rk817_charger {
> > +     struct device *dev;
> > +     struct rk808 *rk808;
> > +
> > +     struct power_supply *bat_ps;
> > +     struct power_supply *chg_ps;
> > +     bool plugged_in;
> > +
> > +     /* The voltage_k and voltage_b values are used to calibrate the ADC
> > +      * voltage readings. While they are documented in the BSP kernel and
> > +      * datasheet as voltage_k and voltage_b, there is no further
> > +      * information explaining them in more detail.
> > +      */
> > +
> > +     uint32_t voltage_k;
> > +     uint32_t voltage_b;
> > +
> > +     /* Storing immutable values of battery here so we can release
> > +      * get_battery_info after the probe and use these values.
> > +      */
> > +     int bat_charge_full_design_uah;
> > +     int bat_voltage_min_design_uv;
> > +     int bat_voltage_max_design_uv;
> > +
> > +     /* dsoc seems to be difference between full charge and actual charge in
> > +      * BSP stored as a percentage, to the thousandth.
> > +      */
> > +     int dsoc;
> > +
> > +     /* Calibrate the DSOC on a fully charged battery, this way we can use
> > +      * the calibrated DSOC value to correct for columb counter drift.
> > +      */
> > +     bool dsoc_cal;
> > +
> > +     /* Implementation specific properties from device tree */
> > +     int res_div;
> > +     int sleep_enter_current;
> > +     int sleep_filter_current;
> > +};
> > +
> > +/* ADC coefficients extracted from BSP kernel */
> > +#define ADC_TO_CURRENT(adc_value, res_div)   \
> > +     (adc_value * 172 / res_div)
> > +
> > +#define CURRENT_TO_ADC(current, samp_res)    \
> > +     (current * samp_res / 172)
> > +
> > +#define CHARGE_TO_ADC(capacity, res_div)     \
> > +     (capacity * res_div * 3600 / 172 * 1000)
> > +
> > +#define ADC_TO_CHARGE_UAH(adc_value, res_div)        \
> > +     (adc_value / 3600 * 172 / res_div)
> > +
> > +#define ADC_TO_CAPACITY(adc_value, res_div)  \
> > +     (adc_value / 1000 * 172 / 3600 / res_div)
> > +
> > +static u8 rk817_chg_cur_to_reg(u32 chg_cur_ma)
> > +{
> > +     if (chg_cur_ma > 3500)
> > +             return CHG_3_5A;
> > +     else if (chg_cur_ma > 3000)
> > +             return CHG_3A;
> > +     else if (chg_cur_ma > 2750)
> > +             return CHG_2_75A;
> > +     else if (chg_cur_ma > 2500)
> > +             return CHG_2_5A;
> > +     else if (chg_cur_ma > 2000)
> > +             return CHG_2A;
> > +     else if (chg_cur_ma > 1500)
> > +             return CHG_1_5A;
> > +     else if (chg_cur_ma > 1000)
> > +             return CHG_1A;
> > +     else if (chg_cur_ma > 500)
> > +             return CHG_0_5A;

Shouldn't these be >= ? Otherwise entering the exact value will cause
the next value below to be selected.

> > +     else
> > +             return -EINVAL;
> > +}
> > +
> > +static int rk817_chg_cur_from_reg(u8 reg)
> > +{
> > +     switch (reg) {
> > +     case CHG_0_5A:
> > +             return 500000;
> > +     case CHG_1A:
> > +             return 1000000;
> > +     case CHG_1_5A:
> > +             return 1500000;
> > +     case CHG_2A:
> > +             return 2000000;
> > +     case CHG_2_5A:
> > +             return 2500000;
> > +     case CHG_2_75A:
> > +             return 2750000;
> > +     case CHG_3A:
> > +             return 3000000;
> > +     case CHG_3_5A:
> > +             return 3500000;
> > +     default:
> > +             return -EINVAL;
> > +     }
> > +}
> > +
> > +static void rk817_bat_calib_vol(struct rk817_charger *charger)
> > +{
> > +     uint32_t vcalib0 = 0;
> > +     uint32_t vcalib1 = 0;
> > +     u8 bulk_reg[2];
> > +
> > +     /* calibrate voltage */
> > +     regmap_bulk_read(charger->rk808->regmap, RK817_GAS_GAUGE_VCALIB0_H,
> > +                      bulk_reg, 2);
> > +     vcalib0 = get_unaligned_be16(bulk_reg);
> > +
> > +     regmap_bulk_read(charger->rk808->regmap, RK817_GAS_GAUGE_VCALIB1_H,
> > +                      bulk_reg, 2);
> > +     vcalib1 = get_unaligned_be16(bulk_reg);
> > +
> > +     /* values were taken from BSP kernel */
> > +     charger->voltage_k = (4025 - 2300) * 1000 /
> > +                          ((vcalib1 - vcalib0) ? (vcalib1 - vcalib0) : 1);
> > +     charger->voltage_b = 4025 - (charger->voltage_k * vcalib1) / 1000;
> > +}
> > +
> > +static void rk817_bat_calib_cur(struct rk817_charger *charger)
> > +{
> > +     u8 bulk_reg[2];
> > +
> > +     /* calibrate current */
> > +     regmap_bulk_read(charger->rk808->regmap, RK817_GAS_GAUGE_IOFFSET_H,
> > +                      bulk_reg, 2);
> > +     regmap_bulk_write(charger->rk808->regmap, RK817_GAS_GAUGE_CAL_OFFSET_H,
> > +                       bulk_reg, 2);
> > +}
> > +
> > +static int rk817_bat_calib_cap(struct rk817_charger *charger)
> > +{
> > +     struct rk808 *rk808 = charger->rk808;
> > +     int reg, tmp, charge_now, charge_now_adc, dsoc_value;
> > +     u8 bulk_reg[4];
> > +
> > +     /* Calibrate the dsoc on a fully charged battery */
> > +
> > +     regmap_read(rk808->regmap, RK817_PMIC_CHRG_STS, &reg);
> > +     tmp = (reg >> 4) & 0x07;
> > +     if (tmp == CHARGE_FINISH) {
> > +             /* Read the columb counter */
> > +             regmap_bulk_read(rk808->regmap, RK817_GAS_GAUGE_Q_PRES_H3,
> > +                              bulk_reg, 4);
> > +             charge_now_adc = get_unaligned_be32(bulk_reg);
> > +             if (charge_now_adc < 0)
> > +                     charge_now_adc = 0;
> > +             charge_now = ADC_TO_CHARGE_UAH(charge_now_adc, charger->res_div);
> > +
> > +             /* Get and set our DSOC value with a full charge */
> > +
> > +             dsoc_value = ((charge_now * 100) /
> > +                           (charger->bat_charge_full_design_uah / 1000));
> > +
> > +             if (!charger->dsoc_cal) {
> > +                     if (dsoc_value > 100000)
> > +                             charger->dsoc = 100000;
> > +                     if (dsoc_value != charger->dsoc) {
> > +                             charger->dsoc = dsoc_value;
> > +                             put_unaligned_le24(dsoc_value, bulk_reg);
> > +                             regmap_bulk_write(rk808->regmap,
> > +                                               RK817_GAS_GAUGE_BAT_R1,
> > +                                               bulk_reg, 3);
> > +                     }
> > +                     /* Mark our dsoc as calibrated. */
> > +                     charger->dsoc_cal = 1;
> > +             }
> > +
> > +             /* In the event our columb counter has drifted over the
> > +              * calibrated dsoc of the battery, adjust the columb counter
> > +              * to correct the drift. Don't do this unless we already
> > +              * calibrated our dsoc at a fully charged state.
> > +              */
> > +
> > +             if (dsoc_value > charger->dsoc && charger->dsoc_cal) {
> > +                     /* Order of operations matters here to ensure we keep
> > +                      * enough precision until the last step to keep from
> > +                      * making needless updates to columb counter.
> > +                      */
> > +                     charge_now = charger->dsoc *
> > +                                  (charger->bat_charge_full_design_uah
> > +                                  / 1000) / 100;
> > +                     charge_now_adc = CHARGE_TO_ADC((charge_now / 1000),
> > +                                      charger->res_div);
> > +
> > +                     put_unaligned_be32(charge_now_adc, bulk_reg);
> > +                     regmap_bulk_write(rk808->regmap,
> > +                                       RK817_GAS_GAUGE_Q_INIT_H3,
> > +                                       bulk_reg, 4);
> > +             }
> > +     }
> > +
> > +     return 0;
> > +}
> > +
> > +static int rk817_bat_get_prop(struct power_supply *ps,
> > +             enum power_supply_property prop,
> > +             union power_supply_propval *val)
> > +{
> > +     struct rk817_charger *charger = power_supply_get_drvdata(ps);
> > +     uint32_t tmp = 0;
> > +     /* Registers for current is a signed 16bit int */
> > +     short int cur = 0;
> > +     /* Registers for capacity-now is a signed 32bit int */
> > +     int32_t charge_now = 0;
> > +     int ret = 0;
> > +     int reg = 0;
> > +     u8 bulk_reg[4];
> > +     struct rk808 *rk808 = charger->rk808;
> > +
> > +     /* Recalibrate voltage and current readings if we need to BSP does both
> > +      * on CUR_CALIB_UPD, ignoring VOL_CALIB_UPD. Curiously enough, both
> > +      * documentation and the BSP show that you perform an update if bit 7
> > +      * is 1, but you clear the status by writing a 1 to bit 7.
> > +      */
> > +     regmap_read(rk808->regmap, RK817_GAS_GAUGE_ADC_CONFIG1, &reg);
> > +     tmp = (reg >> 7) & 0x01;
> > +     if (tmp) {
> > +             rk817_bat_calib_cur(charger);
> > +             rk817_bat_calib_vol(charger);
> > +             regmap_write_bits(rk808->regmap, RK817_GAS_GAUGE_ADC_CONFIG1,
> > +                                RK817_CUR_CALIB_UPD, (1 << 7));
> > +     }
> > +
> > +     rk817_bat_calib_cap(charger);
> > +
> > +     switch (prop) {
> > +     case POWER_SUPPLY_PROP_PRESENT:
> > +             regmap_read(rk808->regmap, RK817_PMIC_CHRG_STS, &reg);
> > +             val->intval = (reg >> 7);
> > +             break;
> > +     case POWER_SUPPLY_PROP_STATUS:
> > +             if (!charger->plugged_in) {
> > +                     val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
> > +                     break;
> > +             }
> > +             ret = regmap_read(rk808->regmap, RK817_PMIC_CHRG_STS, &reg);
> > +             if (ret)
> > +                     return ret;
> > +             tmp = (reg >> 4) & 0x07;
> > +             switch (tmp) {
> > +             case CHRG_OFF:
> > +                     val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
> > +                     break;
> > +             /* Dead charge is documented, but not explained. I never
> > +              * observed it but assume it's a pre-charge for a dead
> > +              * battery.
> > +              */
> > +             case DEAD_CHRG:
> > +             case TRICKLE_CHRG:
> > +             case CC_OR_CV_CHRG:
> > +                     val->intval = POWER_SUPPLY_STATUS_CHARGING;
> > +                     break;
> > +             case CHARGE_FINISH:
> > +                     val->intval = POWER_SUPPLY_STATUS_FULL;
> > +                     break;
> > +             default:
> > +                     val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
> > +                     return -EINVAL;
> > +
> > +             }
> > +             break;
> > +     case POWER_SUPPLY_PROP_CHARGE_TYPE:
> > +             ret = regmap_read(rk808->regmap, RK817_PMIC_CHRG_STS, &reg);
> > +             if (ret)
> > +                     return ret;
> > +             tmp = (reg >> 4) & 0x07;
> > +             switch (tmp) {
> > +             case CHRG_OFF:
> > +             case CHARGE_FINISH:
> > +                     val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE;
> > +                     break;
> > +             case TRICKLE_CHRG:
> > +                     val->intval = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
> > +                     break;
> > +             case DEAD_CHRG:
> > +             case CC_OR_CV_CHRG:
> > +                     val->intval = POWER_SUPPLY_CHARGE_TYPE_STANDARD;
> > +                     break;
> > +             default:
> > +                     val->intval = POWER_SUPPLY_CHARGE_TYPE_UNKNOWN;
> > +                     break;
> > +             }
> > +             break;
> > +     case POWER_SUPPLY_PROP_CHARGE_FULL:
> > +             val->intval = ((charger->bat_charge_full_design_uah /
> > +                            1000) * charger->dsoc) / 100;
> > +             break;
> > +     case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
> > +             val->intval = charger->bat_charge_full_design_uah;
> > +             break;
> > +     case POWER_SUPPLY_PROP_CHARGE_EMPTY_DESIGN:
> > +             val->intval = 0;
> > +             break;
> > +     case POWER_SUPPLY_PROP_CHARGE_NOW:
> > +             regmap_bulk_read(rk808->regmap, RK817_GAS_GAUGE_Q_PRES_H3,
> > +                              bulk_reg, 4);
> > +             charge_now = get_unaligned_be32(bulk_reg);
> > +             if (charge_now < 0)
> > +                     charge_now = 0;
> > +             val->intval = ADC_TO_CHARGE_UAH(charge_now, charger->res_div);
> > +             break;
> > +     case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
> > +             val->intval = charger->bat_voltage_min_design_uv;
> > +             break;
> > +     case POWER_SUPPLY_PROP_VOLTAGE_BOOT:
> > +             regmap_bulk_read(rk808->regmap, RK817_GAS_GAUGE_PWRON_VOL_H,
> > +                              bulk_reg, 2);
> > +             tmp = get_unaligned_be16(bulk_reg);
> > +             val->intval = (charger->voltage_k * tmp) +
> > +                            1000 * charger->voltage_b;
> > +             break;
> > +     case POWER_SUPPLY_PROP_VOLTAGE_AVG:
> > +             regmap_bulk_read(rk808->regmap, RK817_GAS_GAUGE_BAT_VOL_H,
> > +                              bulk_reg, 2);
> > +             tmp = get_unaligned_be16(bulk_reg);
> > +             val->intval = (charger->voltage_k * tmp) +
> > +                            1000 * charger->voltage_b;
> > +             break;
> > +     case POWER_SUPPLY_PROP_VOLTAGE_OCV:
> > +             regmap_bulk_read(rk808->regmap, RK817_GAS_GAUGE_OCV_VOL_H,
> > +                              bulk_reg, 2);
> > +             tmp = get_unaligned_be16(bulk_reg);
> > +             val->intval = (charger->voltage_k * tmp) +
> > +                            1000 * charger->voltage_b;
> > +             break;
> > +     case POWER_SUPPLY_PROP_CURRENT_BOOT:
> > +             regmap_bulk_read(rk808->regmap, RK817_GAS_GAUGE_PWRON_CUR_H,
> > +                              bulk_reg, 2);
> > +             cur = get_unaligned_be16(bulk_reg);
> > +             val->intval = ADC_TO_CURRENT(cur, charger->res_div);
> > +             break;
> > +     case POWER_SUPPLY_PROP_CURRENT_AVG:
> > +             regmap_bulk_read(rk808->regmap, RK817_GAS_GAUGE_BAT_CUR_H,
> > +                              bulk_reg, 2);
> > +             cur = get_unaligned_be16(bulk_reg);
> > +             val->intval = ADC_TO_CURRENT(cur, charger->res_div);
> > +             break;
> > +     case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
> > +             regmap_read(rk808->regmap, RK817_PMIC_CHRG_OUT, &tmp);
> > +             val->intval = rk817_chg_cur_from_reg(tmp & RK817_CHRG_CUR_SEL);
> > +             break;
> > +     case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
> > +             regmap_read(rk808->regmap, RK817_PMIC_CHRG_OUT, &tmp);
> > +             val->intval = ((((tmp & RK817_CHRG_VOL_SEL) >> 4) * 50000) +
> > +                            4100000);
> > +             break;
> > +     case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
> > +             val->intval = charger->bat_voltage_max_design_uv;
> > +             break;
> > +     default:
> > +             return -EINVAL;
> > +     }
> > +     return 0;
> > +}
> > +
> > +static int rk817_chg_get_prop(struct power_supply *ps,
> > +                           enum power_supply_property prop,
> > +                           union power_supply_propval *val)
> > +{
> > +     struct rk817_charger *charger = power_supply_get_drvdata(ps);
> > +     int vol, tmp = 0;
> > +     u8 bulk_reg[2];
> > +
> > +     switch (prop) {
> > +     case POWER_SUPPLY_PROP_ONLINE:
> > +             val->intval = charger->plugged_in;
> > +             break;
> > +     case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
> > +             /* max voltage from datasheet at 5.5v (default 5.0v) */
> > +             val->intval = 5500000;
> > +             break;
> > +     case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
> > +             /* min voltage from datasheet at 3.8v (default 5.0v) */
> > +             val->intval = 3800000;
> > +             break;
> > +     case POWER_SUPPLY_PROP_VOLTAGE_AVG:
> > +             /* Note that on my example hardware (an Odroid Go Advance) the
> > +              * voltage of the power connector is measured on the register
> > +              * labelled USB in the datasheet; I don't know if this is how
> > +              * it is designed or just a quirk of the implementation. I
> > +              * believe this will also measure the voltage of the USB output
> > +              * when in OTG mode, if that is the case we may need to change
> > +              * this in the future to return 0 if the power supply status
> > +              * is offline.
> > +              */
> > +             regmap_bulk_read(charger->rk808->regmap,
> > +                              RK817_GAS_GAUGE_USB_VOL_H,
> > +                              bulk_reg, 2);
> > +             tmp = get_unaligned_be16(bulk_reg);
> > +             vol = ((charger->voltage_k * tmp / 1000 + charger->voltage_b) *
> > +                    60 / 46);
> > +             val->intval = vol * 1000;
> > +             break;
> > +     /* While it's possible that other implementations could use different
> > +      * USB types, the current implementation for this PMIC (the Odroid Go
> > +      * Advance) only uses a dedicated charging port with no rx/tx lines.
> > +      */
> > +     case POWER_SUPPLY_PROP_USB_TYPE:
> > +             val->intval = POWER_SUPPLY_USB_TYPE_DCP;
> > +             break;
> > +     default:
> > +             return -EINVAL;
> > +     }
> > +     return 0;
> > +
> > +}
> > +
> > +static irqreturn_t rk817_plug_in_isr(int irq, void *cg)
> > +{
> > +     struct rk817_charger *charger;
> > +
> > +     charger = (struct rk817_charger *)cg;
> > +     charger->plugged_in = 1;
> > +     power_supply_changed(charger->chg_ps);
> > +     power_supply_changed(charger->bat_ps);
> > +     dev_dbg(charger->dev, "Power Cord Inserted\n");
> > +
> > +     return IRQ_HANDLED;
> > +}
> > +
> > +static irqreturn_t rk817_plug_out_isr(int irq, void *cg)
> > +{
> > +     struct rk817_charger *charger;
> > +     struct rk808 *rk808;
> > +
> > +     charger = (struct rk817_charger *)cg;
> > +     rk808 = charger->rk808;
> > +     charger->plugged_in = 0;
> > +     power_supply_changed(charger->bat_ps);
> > +     power_supply_changed(charger->chg_ps);
> > +
> > +     /* For some reason the bits of RK817_PMIC_CHRG_IN reset whenever the
> > +      * power cord is unplugged. This was not documented in the BSP kernel
> > +      * or the datasheet and only discovered by trial and error. Set minimum
> > +      * USB input voltage to 4.5v and enable USB voltage input limit.
> > +      */
> > +     regmap_write_bits(rk808->regmap, RK817_PMIC_CHRG_IN,
> > +                       RK817_USB_VLIM_SEL, (0x05 << 4));
> > +     regmap_write_bits(rk808->regmap, RK817_PMIC_CHRG_IN, RK817_USB_VLIM_EN,
> > +                       (0x01 << 7));
> > +
> > +     /* Set average USB input current limit to 1.5A and enable USB current
> > +      * input limit.
> > +      */
> > +     regmap_write_bits(rk808->regmap, RK817_PMIC_CHRG_IN,
> > +                       RK817_USB_ILIM_SEL, 0x03);
> > +     regmap_write_bits(rk808->regmap, RK817_PMIC_CHRG_IN, RK817_USB_ILIM_EN,
> > +                       (0x01 << 3));
> > +
> > +     dev_dbg(charger->dev, "Power Cord Removed\n");
> > +
> > +     return IRQ_HANDLED;
> > +}
> > +
> > +static enum power_supply_property rk817_bat_props[] = {
> > +     POWER_SUPPLY_PROP_PRESENT,
> > +     POWER_SUPPLY_PROP_STATUS,
> > +     POWER_SUPPLY_PROP_CHARGE_TYPE,
> > +     POWER_SUPPLY_PROP_CHARGE_FULL,
> > +     POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
> > +     POWER_SUPPLY_PROP_CHARGE_EMPTY_DESIGN,
> > +     POWER_SUPPLY_PROP_CHARGE_NOW,
> > +     POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX,
> > +     POWER_SUPPLY_PROP_VOLTAGE_BOOT,
> > +     POWER_SUPPLY_PROP_VOLTAGE_AVG,
> > +     POWER_SUPPLY_PROP_VOLTAGE_OCV,
> > +     POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
> > +     POWER_SUPPLY_PROP_CURRENT_BOOT,
> > +     POWER_SUPPLY_PROP_CURRENT_AVG,
> > +     POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
> > +     POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
> > +};
> > +
> > +static enum power_supply_property rk817_chg_props[] = {
> > +     POWER_SUPPLY_PROP_ONLINE,
> > +     POWER_SUPPLY_PROP_USB_TYPE,
> > +     POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
> > +     POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
> > +     POWER_SUPPLY_PROP_VOLTAGE_AVG,
> > +};
> > +
> > +static enum power_supply_usb_type rk817_usb_type[] = {
> > +     POWER_SUPPLY_USB_TYPE_DCP,
> > +     POWER_SUPPLY_USB_TYPE_UNKNOWN,
> > +};
> > +
> > +static const struct power_supply_desc rk817_bat_desc = {
> > +     .name = "rk817-battery",
> > +     .type = POWER_SUPPLY_TYPE_BATTERY,
> > +     .properties = rk817_bat_props,
> > +     .num_properties = ARRAY_SIZE(rk817_bat_props),
> > +     .get_property = rk817_bat_get_prop,
> > +};
> > +
> > +static const struct power_supply_desc rk817_chg_desc = {
> > +     .name = "rk817-charger",
> > +     .type = POWER_SUPPLY_TYPE_USB,
> > +     .usb_types = rk817_usb_type,
> > +     .num_usb_types = ARRAY_SIZE(rk817_usb_type),
> > +     .properties = rk817_chg_props,
> > +     .num_properties = ARRAY_SIZE(rk817_chg_props),
> > +     .get_property = rk817_chg_get_prop,
> > +};
> > +
> > +static int
> > +rk817_read_or_set_full_charge_on_boot(struct rk817_charger *charger,
> > +                                   struct power_supply_battery_info *bat_info)
> > +{
> > +     struct rk808 *rk808 = charger->rk808;
> > +     u8 bulk_reg[4];
> > +     u32 design_charge_mah = (charger->bat_charge_full_design_uah / 1000);
> > +     u32 boot_voltage, boot_charge, tmp, full_charge_cap;
> > +     int ret, boot_capacity;
> > +
> > +     /* Read DSOC value if pre-existing. If not, initialize at 100%.
> > +      * Note endianness, also register says it's for resistance,
> > +      * however BSP kernel treats this as an nvram field for the DSOC
> > +      * as best I can tell. Doing the same for backwards compatibility.
> > +      */
> > +     ret = regmap_bulk_read(rk808->regmap, RK817_GAS_GAUGE_BAT_R1, bulk_reg, 3);
> > +     if (ret < 0)
> > +             return ret;
> > +     charger->dsoc = get_unaligned_le24(bulk_reg);
> > +     /* If we have an invalid DSOC, write 100 (100000) as default. */
> > +     if (charger->dsoc < 1000 || charger->dsoc > 100000) {
> > +             charger->dsoc = 100000;
> > +             put_unaligned_le24(charger->dsoc, bulk_reg);
> > +             regmap_bulk_write(rk808->regmap, RK817_GAS_GAUGE_BAT_R1,
> > +                               bulk_reg, 3);
> > +     }
> > +
> > +     /* Register appears to be nvram that stores capacity in mAH. Note
> > +      * endianness, keeping consistent with BSP kernel, however it looks
> > +      * like we can use any arbitrary method to store value if we don't care
> > +      * about compatibility. Additionally, it doesn't appear that this value
> > +      * is used for anything, so realistically getting it and setting it is
> > +      * to ensure backward compatibility with BSP and serves no purpose with
> > +      * this driver, and I'm not sure if the BSP driver does anything with
> > +      * this value either.
> > +      */
> > +
> > +     ret = regmap_bulk_read(rk808->regmap, RK817_GAS_GAUGE_DATA3, bulk_reg, 3);
> > +     if (ret < 0)
> > +             return ret;
> > +
> > +     full_charge_cap = get_unaligned_le24(bulk_reg);
> > +
> > +     /* Sanity checking for values equal to zero or less than would be
> > +      * practical for this device (BSP Kernel assumes 500mAH or less) for
> > +      * practicality purposes.
> > +      */
> > +     if (full_charge_cap < 500) {
> > +             put_unaligned_le24(design_charge_mah, bulk_reg);
> > +             ret = regmap_bulk_write(rk808->regmap, RK817_GAS_GAUGE_DATA3, bulk_reg, 3);
> > +             if (ret < 0)
> > +                     return ret;
> > +             dev_info(charger->dev,
> > +                      "Invalid NVRAM Data for max charge, setting to design capacity %u uAH\n",
> > +                      design_charge_mah*1000);
> > +     }
> > +
> > +     /* Capture boot voltage and look up boot capacity from OCV tables. */
> > +
> > +     regmap_bulk_read(rk808->regmap, RK817_GAS_GAUGE_PWRON_VOL_H,
> > +                      bulk_reg, 2);
> > +     tmp = get_unaligned_be16(bulk_reg);
> > +     boot_voltage = (charger->voltage_k * tmp) + 1000 * charger->voltage_b;
> > +     /* Since only implementation has no working thermistor, assume 20C for
> > +      * OCV lookup. If lookup fails, report error with OCV table.
> > +      */
> > +     boot_capacity = power_supply_batinfo_ocv2cap(bat_info, boot_voltage, 20);
> > +     if (boot_capacity < 0) {
> > +             return dev_err_probe(charger->dev,
> > +                                  boot_capacity,
> > +                                  "Unable to read boot charge from OCV table: %i\n",
> > +                                  boot_capacity);
> > +     }
> > +
> > +     /* Write boot charge to registers, estimate boot charge based on
> > +      * capacity and max charge of battery.
> > +      */
> > +     boot_charge = (boot_capacity * charger->bat_charge_full_design_uah) / 100;
> > +     tmp = CHARGE_TO_ADC((boot_charge / 1000), charger->res_div);
> > +     put_unaligned_be32(tmp, bulk_reg);
> > +     regmap_bulk_write(rk808->regmap, RK817_GAS_GAUGE_Q_INIT_H3,
> > +                       bulk_reg, 4);
> > +
> > +     /* Set QMAX value to max design capacity. */
> > +     tmp = CHARGE_TO_ADC((charger->bat_charge_full_design_uah / 1000),
> > +                         charger->res_div);
> > +     put_unaligned_be32(tmp, bulk_reg);
> > +     ret = regmap_bulk_write(rk808->regmap, RK817_GAS_GAUGE_Q_MAX_H3,
> > +                             bulk_reg, 4);
> > +     if (ret < 0)
> > +             return ret;
> > +
> > +     return 0;
> > +}
> > +
> > +static int rk817_battery_init(struct rk817_charger *charger,
> > +                           struct power_supply_battery_info *bat_info)
> > +{
> > +     struct rk808 *rk808 = charger->rk808;
> > +     u32 tmp, max_chg_vol_mv, max_chg_cur_ma;
> > +     u8 max_chg_vol_reg, chg_term_i_reg, max_chg_cur_reg;
> > +     int ret, chg_term_ma;
> > +     u8 bulk_reg[2];
> > +
> > +     /* Get initial plug state */
> > +     regmap_read(rk808->regmap, RK817_SYS_STS, &tmp);
> > +     charger->plugged_in = (tmp & RK817_PLUG_IN_STS);
> > +
> > +     /* Turn on all ADC functions to measure battery, USB, and sys voltage,
> > +      * as well as batt temp. Note only tested implementation so far does
> > +      * not use a battery with a thermistor.
> > +      */
> > +     regmap_write(rk808->regmap, RK817_GAS_GAUGE_ADC_CONFIG0, 0xfc);
> > +
> > +     /* Set relax mode voltage sampling interval and ADC offset calibration
> > +      * interval to 8 minutes to mirror BSP kernel. Set voltage and current
> > +      * modes to average to mirror BSP kernel.
> > +      */
> > +     regmap_write(rk808->regmap, RK817_GAS_GAUGE_GG_CON, 0x04);
> > +
> > +     /* Write relax threshold, derived from sleep enter current. */
> > +     tmp = CURRENT_TO_ADC(charger->sleep_enter_current, charger->res_div);
> > +     put_unaligned_be16(tmp, bulk_reg);
> > +     regmap_bulk_write(rk808->regmap, RK817_GAS_GAUGE_RELAX_THRE_H,
> > +                       bulk_reg, 2);
> > +
> > +     /* Write sleep sample current, derived from sleep filter current. */
> > +     tmp = CURRENT_TO_ADC(charger->sleep_filter_current, charger->res_div);
> > +     put_unaligned_be16(tmp, bulk_reg);
> > +     regmap_bulk_write(rk808->regmap, RK817_GAS_GAUGE_SLEEP_CON_SAMP_CUR_H,
> > +                       bulk_reg, 2);
> > +
> > +     /* Restart battery relax voltage */
> > +     regmap_write_bits(rk808->regmap, RK817_GAS_GAUGE_GG_STS,
> > +                       RK817_RELAX_VOL_UPD, (0x0 << 2));
> > +
> > +     /* Set OCV Threshold Voltage to 127.5mV. This was hard coded like this
> > +      * in the BSP.
> > +      */
> > +     regmap_write(rk808->regmap, RK817_GAS_GAUGE_OCV_THRE_VOL, 0xff);
> > +
> > +     /* Set maximum charging voltage to battery max voltage. Trying to be
> > +      * incredibly safe with these value, as setting them wrong could
> > +      * overcharge the battery, which would be very bad.
> > +      */
> > +     max_chg_vol_mv = bat_info->constant_charge_voltage_max_uv / 1000;
> > +     max_chg_cur_ma = bat_info->constant_charge_current_max_ua / 1000;
> > +
> > +     if (max_chg_vol_mv < 4100) {
> > +             return dev_err_probe(charger->dev, -EINVAL,
> > +                    "invalid max charger voltage, value %u unsupported\n",
> > +                     max_chg_vol_mv * 1000);
> > +     }
> > +     if (max_chg_vol_mv > 4450) {
> > +             dev_info(charger->dev,
> > +                      "Setting max charge voltage to 4450000uv\n");
> > +             max_chg_vol_mv = 4450;
> > +     }
> > +
> > +     if (max_chg_cur_ma < 500) {
> > +             return dev_err_probe(charger->dev, -EINVAL,
> > +                    "invalid max charger current, value %u unsupported\n",
> > +                    max_chg_cur_ma * 1000);
> > +     }
> > +     if (max_chg_cur_ma > 3500)
> > +             dev_info(charger->dev,
> > +                      "Setting max charge current to 3500000ua\n");
> > +
> > +     /* Now that the values are sanity checked, if we subtract 4100 from the
> > +      * max voltage and divide by 50, we conviently get the exact value for
> > +      * the registers, which are 4.1v, 4.15v, 4.2v, 4.25v, 4.3v, 4.35v,
> > +      * 4.4v, and 4.45v; these correspond to values 0x00 through 0x07.
> > +      */
> > +     max_chg_vol_reg = (max_chg_vol_mv - 4100) / 50;
> > +
> > +     max_chg_cur_reg = rk817_chg_cur_to_reg(max_chg_cur_ma);
> > +
> > +     if (max_chg_vol_reg < 0 || max_chg_vol_reg > 7) {
> > +             return dev_err_probe(charger->dev, -EINVAL,
> > +                    "invalid max charger voltage, value %u unsupported\n",
> > +                    max_chg_vol_mv * 1000);
> > +     }
> > +     if (max_chg_cur_reg < 0 || max_chg_cur_reg > 7) {
> > +             return dev_err_probe(charger->dev, -EINVAL,
> > +                    "invalid max charger current, value %u unsupported\n",
> > +                    max_chg_cur_ma * 1000);
> > +     }
> > +
> > +     /* Write the values to the registers, and deliver an emergency warning
> > +      * in the event they are not written correctly.
> > +      */
> > +     ret = regmap_write_bits(rk808->regmap, RK817_PMIC_CHRG_OUT,
> > +                             RK817_CHRG_VOL_SEL, (max_chg_vol_reg << 4));
> > +     if (ret) {
> > +             dev_emerg(charger->dev,
> > +                       "Danger, unable to set max charger voltage: %u\n",
> > +                       ret);
> > +     }
> > +
> > +     ret = regmap_write_bits(rk808->regmap, RK817_PMIC_CHRG_OUT,
> > +                             RK817_CHRG_CUR_SEL, max_chg_cur_reg);
> > +     if (ret) {
> > +             dev_emerg(charger->dev,
> > +                       "Danger, unable to set max charger current: %u\n",
> > +                       ret);
> > +     }
> > +
> > +     /* Set charge finishing mode to analog */
> > +     regmap_write_bits(rk808->regmap, RK817_PMIC_CHRG_TERM,
> > +                       RK817_CHRG_TERM_ANA_DIG, (0x0 << 2));
> > +
> > +     /* Set charge finish current, warn if value not in range and keep
> > +      * default.
> > +      */
> > +     chg_term_ma = bat_info->charge_term_current_ua / 1000;
> > +     if (chg_term_ma < 150 || chg_term_ma > 400) {
> > +             dev_warn(charger->dev,
> > +                      "Invalid charge termination value %u, keeping default\n",
> > +                      chg_term_ma * 1000);
> > +             chg_term_ma = 200;
> > +     }
> > +
> > +     /* Values of 150ma, 200ma, 300ma, and 400ma correspond to 00, 01, 10,
> > +      * and 11.
> > +      */
> > +     chg_term_i_reg = (chg_term_ma - 100) / 100;
> > +     regmap_write_bits(rk808->regmap, RK817_PMIC_CHRG_TERM,
> > +                       RK817_CHRG_TERM_ANA_SEL, chg_term_i_reg);
> > +
> > +     ret = rk817_read_or_set_full_charge_on_boot(charger, bat_info);
> > +     if (ret < 0)
> > +             return ret;
> > +
> > +     /* Set minimum USB input voltage to 4.5v and enable USB voltage input
> > +      * limit.
> > +      */
> > +     regmap_write_bits(rk808->regmap, RK817_PMIC_CHRG_IN,
> > +                       RK817_USB_VLIM_SEL, (0x05 << 4));
> > +     regmap_write_bits(rk808->regmap, RK817_PMIC_CHRG_IN, RK817_USB_VLIM_EN,
> > +                       (0x01 << 7));
> > +
> > +     /* Set average USB input current limit to 1.5A and enable USB current
> > +      * input limit.
> > +      */
> > +     regmap_write_bits(rk808->regmap, RK817_PMIC_CHRG_IN,
> > +                       RK817_USB_ILIM_SEL, 0x03);
> > +     regmap_write_bits(rk808->regmap, RK817_PMIC_CHRG_IN, RK817_USB_ILIM_EN,
> > +                       (0x01 << 3));
> > +
> > +     return 0;
> > +}
> > +
> > +static int rk817_charger_probe(struct platform_device *pdev)
> > +{
> > +     struct rk808 *rk808 = dev_get_drvdata(pdev->dev.parent);
> > +     struct rk817_charger *charger;
> > +     struct device_node *node;
> > +     struct power_supply_battery_info bat_info = { };
> > +     struct device *dev = &pdev->dev;
> > +     struct power_supply_config pscfg = {};
> > +     int plugin_irq, plugout_irq;
> > +     int of_value;
> > +     int ret;
> > +
> > +     node = of_get_child_by_name(dev->parent->of_node, "battery");
> > +     if (!node)
> > +             return -ENODEV;
> > +
> > +     charger = devm_kzalloc(&pdev->dev, sizeof(*charger), GFP_KERNEL);
> > +     if (!charger)
> > +             return -ENOMEM;
> > +
> > +     charger->rk808 = rk808;
> > +
> > +     charger->dev = &pdev->dev;
> > +     platform_set_drvdata(pdev, charger);
> > +
> > +     rk817_bat_calib_vol(charger);
> > +
> > +     pscfg.drv_data = charger;
> > +     pscfg.of_node = node;
> > +
> > +     /* Get sample resistor value. Note only values of 10000 or 20000
> > +      * microohms are allowed. Schematic for my test implementation (an
> > +      * Odroid Go Advance) shows a 10 milliohm resistor for reference.
> > +      */
> > +     ret = of_property_read_u32(node, "rockchip,resistor-sense-micro-ohms",
> > +                                &of_value);
> > +     if (ret < 0) {
> > +             return dev_err_probe(dev, ret,
> > +                                  "Error reading sample resistor value\n");
> > +     }
> > +     /* Store as a 1 or a 2, since all we really use the value for is as a
> > +      * divisor in some calculations.
> > +      */
> > +     charger->res_div = (of_value == 20000) ? 2 : 1;
> > +
> > +     /* Get sleep enter current value. Not sure what this value is for
> > +      * other than to help calibrate the relax threshold.
> > +      */
> > +     ret = of_property_read_u32(node,
> > +                                "rockchip,sleep-enter-current-microamp",
> > +                                &of_value);
> > +     if (ret < 0) {
> > +             return dev_err_probe(dev, ret,
> > +                                  "Error reading sleep enter cur value\n");
> > +     }
> > +     charger->sleep_enter_current = of_value;
> > +
> > +     /* Get sleep filter current value */
> > +     ret = of_property_read_u32(node,
> > +                                "rockchip,sleep-filter-current-microamp",
> > +                                &of_value);
> > +     if (ret < 0) {
> > +             return dev_err_probe(dev, ret,
> > +                                  "Error reading sleep filter cur value\n");
> > +     }
> > +
> > +     charger->sleep_filter_current = of_value;
> > +
> > +     charger->bat_ps = devm_power_supply_register(&pdev->dev,
> > +                                                  &rk817_bat_desc, &pscfg);
> > +
> > +     charger->chg_ps = devm_power_supply_register(&pdev->dev,
> > +                                                  &rk817_chg_desc, &pscfg);
> > +
> > +     if (IS_ERR(charger->chg_ps))
> > +             return dev_err_probe(dev, -EINVAL,
> > +                                  "Battery failed to probe\n");
> > +
> > +     if (IS_ERR(charger->chg_ps))
> > +             return dev_err_probe(dev, -EINVAL,
> > +                                  "Charger failed to probe\n");
> > +
> > +     ret = power_supply_get_battery_info(charger->bat_ps,
> > +                                         &bat_info);
> > +     if (ret) {
> > +             return dev_err_probe(dev, ret,
> > +                                  "Unable to get battery info: %d\n", ret);
> > +     }
> > +
> > +     if ((!bat_info.charge_full_design_uah) ||
> > +         (!bat_info.voltage_min_design_uv) ||
> > +         (!bat_info.voltage_max_design_uv) ||
> > +         (!bat_info.constant_charge_voltage_max_uv) ||
> > +         (!bat_info.constant_charge_current_max_ua) ||
> > +         (!bat_info.charge_term_current_ua)) {
> > +             return dev_err_probe(dev, -EINVAL,
> > +                                  "Required battery info missing.\n");
> > +     }
> > +
> > +     charger->bat_charge_full_design_uah = bat_info.charge_full_design_uah;
> > +     charger->bat_voltage_min_design_uv = bat_info.voltage_min_design_uv;
> > +     charger->bat_voltage_max_design_uv = bat_info.voltage_max_design_uv;
> > +
> > +     /* Has to run after power_supply_get_battery_info as it depends on some
> > +      * values discovered from that routine.
> > +      */
> > +     ret = rk817_battery_init(charger, &bat_info);
> > +     if (ret)
> > +             return ret;
> > +
> > +     power_supply_put_battery_info(charger->bat_ps, &bat_info);
> > +
> > +     plugin_irq = platform_get_irq(pdev, 0);
> > +     if (plugin_irq < 0)
> > +             return plugin_irq;
> > +
> > +     plugout_irq = platform_get_irq(pdev, 1);
> > +     if (plugout_irq < 0)
> > +             return plugout_irq;
> > +
> > +     ret = devm_request_threaded_irq(charger->dev, plugin_irq, NULL,
> > +                                     rk817_plug_in_isr,
> > +                                     IRQF_TRIGGER_RISING | IRQF_ONESHOT,
> > +                                     "rk817_plug_in", charger);
> > +     if (ret) {
> > +             return dev_err_probe(&pdev->dev, ret,
> > +                                   "plug_in_irq request failed!\n");
> > +     }
> > +
> > +     ret = devm_request_threaded_irq(charger->dev, plugout_irq, NULL,
> > +                                     rk817_plug_out_isr,
> > +                                     IRQF_TRIGGER_RISING | IRQF_ONESHOT,
> > +                                     "rk817_plug_out", charger);
> > +     if (ret) {
> > +             return dev_err_probe(&pdev->dev, ret,
> > +                                  "plug_out_irq request failed!\n");
> > +     }
> > +
> > +     return 0;
> > +}
> > +
> > +
> > +static struct platform_driver rk817_charger_driver = {
> > +     .probe    = rk817_charger_probe,
> > +     .driver   = {
> > +             .name  = "rk817-charger",
> > +     },
> > +};
> > +module_platform_driver(rk817_charger_driver);
> > +
> > +MODULE_DESCRIPTION("Battery power supply driver for RK817 PMIC");
> > +MODULE_AUTHOR("Maya Matuszczyk <maccraft123mc@gmail.com>");
> > +MODULE_LICENSE("GPL");
> > --
> > 2.25.1
> >
> _______________________________________________
> Linux-rockchip mailing list
> Linux-rockchip@lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/linux-rockchip