linux-pm.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v4 RESEND 0/4] power: supply: Add Support for RK817 Charger
@ 2021-09-16 19:42 Chris Morgan
  2021-09-16 19:42 ` [PATCH v4 RESEND 1/4] dt-bindings: Add Rockchip rk817 battery charger support Chris Morgan
                   ` (4 more replies)
  0 siblings, 5 replies; 11+ messages in thread
From: Chris Morgan @ 2021-09-16 19:42 UTC (permalink / raw)
  To: linux-rockchip
  Cc: lee.jones, robh+dt, heiko, sre, maccraft123mc, devicetree,
	linux-pm, Chris Morgan

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

-- 
2.25.1


^ permalink raw reply	[flat|nested] 11+ messages in thread

* [PATCH v4 RESEND 1/4] dt-bindings: Add Rockchip rk817 battery charger support
  2021-09-16 19:42 [PATCH v4 RESEND 0/4] power: supply: Add Support for RK817 Charger Chris Morgan
@ 2021-09-16 19:42 ` Chris Morgan
  2021-09-22 19:19   ` Rob Herring
  2021-09-16 19:42 ` [PATCH v4 RESEND 2/4] mfd: " Chris Morgan
                   ` (3 subsequent siblings)
  4 siblings, 1 reply; 11+ messages in thread
From: Chris Morgan @ 2021-09-16 19:42 UTC (permalink / raw)
  To: linux-rockchip
  Cc: lee.jones, robh+dt, heiko, sre, maccraft123mc, devicetree,
	linux-pm, Chris Morgan

From: Chris Morgan <macromorgan@hotmail.com>

Create dt-binding documentation to document rk817 battery and charger
usage. New device-tree properties have been added.

- rockchip,resistor-sense-micro-ohms: The value in microohms of the
                                      sample resistor.
- rockchip,sleep-enter-current-microamp: The value in microamps of the
                                         sleep enter current.
- rockchip,sleep-filter-current: The value in microamps of the sleep
                                 filter current.

Signed-off-by: Chris Morgan <macromorgan@hotmail.com>
Signed-off-by: Maya Matuszczyk <maccraft123mc@gmail.com>
---
 .../devicetree/bindings/mfd/rk808.txt         | 38 +++++++++++++++++++
 1 file changed, 38 insertions(+)

diff --git a/Documentation/devicetree/bindings/mfd/rk808.txt b/Documentation/devicetree/bindings/mfd/rk808.txt
index 23a17a6663ec..6e1b9fb1f64a 100644
--- a/Documentation/devicetree/bindings/mfd/rk808.txt
+++ b/Documentation/devicetree/bindings/mfd/rk808.txt
@@ -77,6 +77,37 @@ Optional RK817 properties:
 - rockchip,mic-in-differential: Telling if the microphone uses differential
 				mode. Should be under the codec child node.
 
+- battery:	The child node for the charger to hold additional properties.
+		If a battery is not in use, this node can be omitted. If a
+		battery node is used, the following values are required in the
+		battery node itself:
+		rockchip,resistor-sense-micro-ohms,
+		rockchip,sleep-enter-current-microamp,
+		rockchip,sleep-filter-current-microamp,
+		Additionally, a phandle to a monitored-battery node that
+		contains the following is also required:
+		charge-full-design-microamp-hours,
+		charge-term-current-microamp,
+		constant-charge-current-max-microamp,
+		constant-charge-voltage-max-microvolt,
+		voltage-max-design-microvolt,
+		voltage-min-design-microvolt,
+		and a valid ocv-capacity table.
+- rockchip,resistor-sense-micro-ohms: Value in microohms of the battery sense
+				      resistor. The PMIC only supports values
+				      of either 10000 or 20000. This value is
+				      used by the driver to set the correct
+				      divisor value to translate ADC readings
+				      into the proper units of measure.
+- rockchip,sleep-enter-current-microamp: Value in microamps of the sleep enter
+					 current for the charger. Value is used
+					 by the driver to calibrate the relax
+					 threshold.
+- rockchip,sleep-filter-current-microamp: Value in microamps of the sleep
+					  filter current for the charger.
+					  Value is used by the driver to derive
+					  the sleep sample current.
+
 Optional RK818 properties:
 - vcc1-supply:  The input supply for DCDC_REG1
 - vcc2-supply:  The input supply for DCDC_REG2
@@ -459,6 +490,13 @@ Example:
 			};
 		};
 
+		rk817_battery: battery {
+			monitored-battery = <&battery_cell>;
+			rockchip,resistor-sense-micro-ohms = <10000>;
+			rockchip,sleep-enter-current-microamp = <300000>;
+			rockchip,sleep-filter-current-microamp = <100000>;
+		};
+
 		rk817_codec: codec {
 			rockchip,mic-in-differential;
 		};
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH v4 RESEND 2/4] mfd: Add Rockchip rk817 battery charger support
  2021-09-16 19:42 [PATCH v4 RESEND 0/4] power: supply: Add Support for RK817 Charger Chris Morgan
  2021-09-16 19:42 ` [PATCH v4 RESEND 1/4] dt-bindings: Add Rockchip rk817 battery charger support Chris Morgan
@ 2021-09-16 19:42 ` Chris Morgan
  2021-09-16 19:42 ` [PATCH v4 RESEND 3/4] power: supply: Add charger driver for Rockchip RK817 Chris Morgan
                   ` (2 subsequent siblings)
  4 siblings, 0 replies; 11+ messages in thread
From: Chris Morgan @ 2021-09-16 19:42 UTC (permalink / raw)
  To: linux-rockchip
  Cc: lee.jones, robh+dt, heiko, sre, maccraft123mc, devicetree,
	linux-pm, Chris Morgan

From: Chris Morgan <macromorgan@hotmail.com>

Add rk817 charger support cell to rk808 mfd driver.

Acked-for-MFD-by: Lee Jones <lee.jones@linaro.org>
Signed-off-by: Chris Morgan <macromorgan@hotmail.com>
Signed-off-by: Maya Matuszczyk <maccraft123mc@gmail.com>
---
 drivers/mfd/rk808.c       | 16 ++++++-
 include/linux/mfd/rk808.h | 87 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 102 insertions(+), 1 deletion(-)

diff --git a/drivers/mfd/rk808.c b/drivers/mfd/rk808.c
index 77ccd31ca1d9..edc779aee667 100644
--- a/drivers/mfd/rk808.c
+++ b/drivers/mfd/rk808.c
@@ -66,6 +66,10 @@ static bool rk817_is_volatile_reg(struct device *dev, unsigned int reg)
 	case RK817_SECONDS_REG ... RK817_WEEKS_REG:
 	case RK817_RTC_STATUS_REG:
 	case RK817_CODEC_DTOP_LPT_SRST:
+	case RK817_GAS_GAUGE_ADC_CONFIG0 ... RK817_GAS_GAUGE_CUR_ADC_K0:
+	case RK817_PMIC_CHRG_STS:
+	case RK817_PMIC_CHRG_OUT:
+	case RK817_PMIC_CHRG_IN:
 	case RK817_INT_STS_REG0:
 	case RK817_INT_STS_REG1:
 	case RK817_INT_STS_REG2:
@@ -73,7 +77,7 @@ static bool rk817_is_volatile_reg(struct device *dev, unsigned int reg)
 		return true;
 	}
 
-	return true;
+	return false;
 }
 
 static const struct regmap_config rk818_regmap_config = {
@@ -126,6 +130,11 @@ static const struct resource rk817_pwrkey_resources[] = {
 	DEFINE_RES_IRQ(RK817_IRQ_PWRON_FALL),
 };
 
+static const struct resource rk817_charger_resources[] = {
+	DEFINE_RES_IRQ(RK817_IRQ_PLUG_IN),
+	DEFINE_RES_IRQ(RK817_IRQ_PLUG_OUT),
+};
+
 static const struct mfd_cell rk805s[] = {
 	{ .name = "rk808-clkout", },
 	{ .name = "rk808-regulator", },
@@ -165,6 +174,11 @@ static const struct mfd_cell rk817s[] = {
 		.resources = &rk817_rtc_resources[0],
 	},
 	{ .name = "rk817-codec",},
+	{
+		.name = "rk817-charger",
+		.num_resources = ARRAY_SIZE(rk817_charger_resources),
+		.resources = &rk817_charger_resources[0],
+	},
 };
 
 static const struct mfd_cell rk818s[] = {
diff --git a/include/linux/mfd/rk808.h b/include/linux/mfd/rk808.h
index a96e6d43ca06..1390432c0222 100644
--- a/include/linux/mfd/rk808.h
+++ b/include/linux/mfd/rk808.h
@@ -518,6 +518,74 @@ enum rk809_reg_id {
 #define MIC_DIFF_DIS			(0x0 << 7)
 #define MIC_DIFF_EN			(0x1 << 7)
 
+/* RK817 Battery Registers */
+#define RK817_GAS_GAUGE_ADC_CONFIG0	0x50
+#define RK817_GG_EN			(0x1 << 7)
+#define RK817_SYS_VOL_ADC_EN		(0x1 << 6)
+#define RK817_TS_ADC_EN			(0x1 << 5)
+#define RK817_USB_VOL_ADC_EN		(0x1 << 4)
+#define RK817_BAT_VOL_ADC_EN		(0x1 << 3)
+#define RK817_BAT_CUR_ADC_EN		(0x1 << 2)
+
+#define RK817_GAS_GAUGE_ADC_CONFIG1	0x55
+
+#define RK817_CUR_CALIB_UPD		(0x1 << 7)
+#define RK817_VOL_CALIB_UPD		(0x1 << 6)
+
+#define RK817_GAS_GAUGE_GG_CON		0x56
+#define RK817_GAS_GAUGE_GG_STS		0x57
+
+#define RK817_RELAX_VOL_UPD		(0x3 << 2)
+#define RK817_RELAX_STS			(0x1 << 1)
+
+#define RK817_GAS_GAUGE_RELAX_THRE_H	0x58
+#define RK817_GAS_GAUGE_RELAX_THRE_L	0x59
+#define RK817_GAS_GAUGE_OCV_THRE_VOL	0x62
+#define RK817_GAS_GAUGE_OCV_VOL_H	0x63
+#define RK817_GAS_GAUGE_OCV_VOL_L	0x64
+#define RK817_GAS_GAUGE_PWRON_VOL_H	0x6b
+#define RK817_GAS_GAUGE_PWRON_VOL_L	0x6c
+#define RK817_GAS_GAUGE_PWRON_CUR_H	0x6d
+#define RK817_GAS_GAUGE_PWRON_CUR_L	0x6e
+#define RK817_GAS_GAUGE_OFF_CNT		0x6f
+#define RK817_GAS_GAUGE_Q_INIT_H3	0x70
+#define RK817_GAS_GAUGE_Q_INIT_H2	0x71
+#define RK817_GAS_GAUGE_Q_INIT_L1	0x72
+#define RK817_GAS_GAUGE_Q_INIT_L0	0x73
+#define RK817_GAS_GAUGE_Q_PRES_H3	0x74
+#define RK817_GAS_GAUGE_Q_PRES_H2	0x75
+#define RK817_GAS_GAUGE_Q_PRES_L1	0x76
+#define RK817_GAS_GAUGE_Q_PRES_L0	0x77
+#define RK817_GAS_GAUGE_BAT_VOL_H	0x78
+#define RK817_GAS_GAUGE_BAT_VOL_L	0x79
+#define RK817_GAS_GAUGE_BAT_CUR_H	0x7a
+#define RK817_GAS_GAUGE_BAT_CUR_L	0x7b
+#define RK817_GAS_GAUGE_USB_VOL_H	0x7e
+#define RK817_GAS_GAUGE_USB_VOL_L	0x7f
+#define RK817_GAS_GAUGE_SYS_VOL_H	0x80
+#define RK817_GAS_GAUGE_SYS_VOL_L	0x81
+#define RK817_GAS_GAUGE_Q_MAX_H3	0x82
+#define RK817_GAS_GAUGE_Q_MAX_H2	0x83
+#define RK817_GAS_GAUGE_Q_MAX_L1	0x84
+#define RK817_GAS_GAUGE_Q_MAX_L0	0x85
+#define RK817_GAS_GAUGE_SLEEP_CON_SAMP_CUR_H	0x8f
+#define RK817_GAS_GAUGE_SLEEP_CON_SAMP_CUR_L	0x90
+#define RK817_GAS_GAUGE_CAL_OFFSET_H	0x91
+#define RK817_GAS_GAUGE_CAL_OFFSET_L	0x92
+#define RK817_GAS_GAUGE_VCALIB0_H	0x93
+#define RK817_GAS_GAUGE_VCALIB0_L	0x94
+#define RK817_GAS_GAUGE_VCALIB1_H	0x95
+#define RK817_GAS_GAUGE_VCALIB1_L	0x96
+#define RK817_GAS_GAUGE_IOFFSET_H	0x97
+#define RK817_GAS_GAUGE_IOFFSET_L	0x98
+#define RK817_GAS_GAUGE_BAT_R1		0x9a
+#define RK817_GAS_GAUGE_BAT_R2		0x9b
+#define RK817_GAS_GAUGE_BAT_R3		0x9c
+#define RK817_GAS_GAUGE_DATA3		0xa0
+#define RK817_GAS_GAUGE_DATA4		0xa1
+#define RK817_GAS_GAUGE_DATA5		0xa2
+#define RK817_GAS_GAUGE_CUR_ADC_K0	0xb0
+
 #define RK817_POWER_EN_REG(i)		(0xb1 + (i))
 #define RK817_POWER_SLP_EN_REG(i)	(0xb5 + (i))
 
@@ -543,10 +611,29 @@ enum rk809_reg_id {
 #define RK817_LDO_ON_VSEL_REG(idx)	(0xcc + (idx) * 2)
 #define RK817_BOOST_OTG_CFG		(0xde)
 
+#define RK817_PMIC_CHRG_OUT		0xe4
+#define RK817_CHRG_VOL_SEL		(0x07 << 4)
+#define RK817_CHRG_CUR_SEL		(0x07 << 0)
+
+#define RK817_PMIC_CHRG_IN		0xe5
+#define RK817_USB_VLIM_EN		(0x01 << 7)
+#define RK817_USB_VLIM_SEL		(0x07 << 4)
+#define RK817_USB_ILIM_EN		(0x01 << 3)
+#define RK817_USB_ILIM_SEL		(0x07 << 0)
+#define RK817_PMIC_CHRG_TERM		0xe6
+#define RK817_CHRG_TERM_ANA_DIG		(0x01 << 2)
+#define RK817_CHRG_TERM_ANA_SEL		(0x03 << 0)
+#define RK817_CHRG_EN			(0x01 << 6)
+
+#define RK817_PMIC_CHRG_STS		0xeb
+#define RK817_CHG_STS			(0x07 << 4)
+
 #define RK817_ID_MSB			0xed
 #define RK817_ID_LSB			0xee
 
 #define RK817_SYS_STS			0xf0
+#define RK817_PLUG_IN_STS		(0x1 << 6)
+
 #define RK817_SYS_CFG(i)		(0xf1 + (i))
 
 #define RK817_ON_SOURCE_REG		0xf5
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH v4 RESEND 3/4] power: supply: Add charger driver for Rockchip RK817
  2021-09-16 19:42 [PATCH v4 RESEND 0/4] power: supply: Add Support for RK817 Charger Chris Morgan
  2021-09-16 19:42 ` [PATCH v4 RESEND 1/4] dt-bindings: Add Rockchip rk817 battery charger support Chris Morgan
  2021-09-16 19:42 ` [PATCH v4 RESEND 2/4] mfd: " Chris Morgan
@ 2021-09-16 19:42 ` Chris Morgan
  2021-10-13 17:40   ` Sebastian Reichel
  2021-09-16 19:42 ` [PATCH v4 RESEND 4/4] arm64: dts: rockchip: add rk817 charger to Odroid Go Advance Chris Morgan
  2021-11-30  2:03 ` [PATCH v4 RESEND 0/4] power: supply: Add Support for RK817 Charger Nicolas Frattaroli
  4 siblings, 1 reply; 11+ messages in thread
From: Chris Morgan @ 2021-09-16 19:42 UTC (permalink / raw)
  To: linux-rockchip
  Cc: lee.jones, robh+dt, heiko, sre, maccraft123mc, devicetree,
	linux-pm, Chris Morgan

From: Chris Morgan <macromorgan@hotmail.com>

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

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


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH v4 RESEND 4/4] arm64: dts: rockchip: add rk817 charger to Odroid Go Advance
  2021-09-16 19:42 [PATCH v4 RESEND 0/4] power: supply: Add Support for RK817 Charger Chris Morgan
                   ` (2 preceding siblings ...)
  2021-09-16 19:42 ` [PATCH v4 RESEND 3/4] power: supply: Add charger driver for Rockchip RK817 Chris Morgan
@ 2021-09-16 19:42 ` Chris Morgan
  2021-11-30  2:03 ` [PATCH v4 RESEND 0/4] power: supply: Add Support for RK817 Charger Nicolas Frattaroli
  4 siblings, 0 replies; 11+ messages in thread
From: Chris Morgan @ 2021-09-16 19:42 UTC (permalink / raw)
  To: linux-rockchip
  Cc: lee.jones, robh+dt, heiko, sre, maccraft123mc, devicetree,
	linux-pm, Chris Morgan

From: Chris Morgan <macromorgan@hotmail.com>

Add the new rk817 charger driver to the Odroid Go Advance. Create a
monitored battery node as well for the charger to use. All values
from monitored battery are gathered from the BSP kernel for the
Odroid Go Advance provided by HardKernel.

Signed-off-by: Chris Morgan <macromorgan@hotmail.com>
Signed-off-by: Maya Matuszczyk <maccraft123mc@gmail.com>
---
 .../boot/dts/rockchip/rk3326-odroid-go2.dts   | 26 +++++++++++++++++++
 1 file changed, 26 insertions(+)

diff --git a/arch/arm64/boot/dts/rockchip/rk3326-odroid-go2.dts b/arch/arm64/boot/dts/rockchip/rk3326-odroid-go2.dts
index 7fc674a99a6c..aff8d0768c5a 100644
--- a/arch/arm64/boot/dts/rockchip/rk3326-odroid-go2.dts
+++ b/arch/arm64/boot/dts/rockchip/rk3326-odroid-go2.dts
@@ -52,6 +52,25 @@ backlight: backlight {
 		pwms = <&pwm1 0 25000 0>;
 	};
 
+	battery_cell: battery-cell {
+		compatible = "simple-battery";
+		charge-full-design-microamp-hours = <3000000>;
+		charge-term-current-microamp = <300000>;
+		constant-charge-current-max-microamp = <2000000>;
+		constant-charge-voltage-max-microvolt = <4200000>;
+		factory-internal-resistance-micro-ohms = <180000>;
+		voltage-max-design-microvolt = <4100000>;
+		voltage-min-design-microvolt = <3500000>;
+
+		ocv-capacity-celsius = <20>;
+		ocv-capacity-table-0 =	<4106000 100>, <4071000 95>, <4018000 90>, <3975000 85>,
+					<3946000 80>, <3908000 75>, <3877000 70>, <3853000 65>,
+					<3834000 60>, <3816000 55>, <3802000 50>, <3788000 45>,
+					<3774000 40>, <3760000 35>, <3748000 30>, <3735000 25>,
+					<3718000 20>, <3697000 15>, <3685000 10>, <3625000 5>,
+					<3500000 0>;
+	};
+
 	gpio-keys {
 		compatible = "gpio-keys";
 		pinctrl-names = "default";
@@ -462,6 +481,13 @@ regulator-state-mem {
 			};
 		};
 
+		rk817_battery: battery {
+			monitored-battery = <&battery_cell>;
+			rockchip,resistor-sense-micro-ohms = <10000>;
+			rockchip,sleep-enter-current-microamp = <300000>;
+			rockchip,sleep-filter-current-microamp = <100000>;
+		};
+
 		rk817_codec: codec {
 			rockchip,mic-in-differential;
 		};
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* Re: [PATCH v4 RESEND 1/4] dt-bindings: Add Rockchip rk817 battery charger support
  2021-09-16 19:42 ` [PATCH v4 RESEND 1/4] dt-bindings: Add Rockchip rk817 battery charger support Chris Morgan
@ 2021-09-22 19:19   ` Rob Herring
  0 siblings, 0 replies; 11+ messages in thread
From: Rob Herring @ 2021-09-22 19:19 UTC (permalink / raw)
  To: Chris Morgan
  Cc: linux-rockchip, lee.jones, heiko, sre, maccraft123mc, devicetree,
	linux-pm, Chris Morgan

On Thu, Sep 16, 2021 at 02:42:05PM -0500, Chris Morgan wrote:
> From: Chris Morgan <macromorgan@hotmail.com>
> 
> Create dt-binding documentation to document rk817 battery and charger
> usage. New device-tree properties have been added.
> 
> - rockchip,resistor-sense-micro-ohms: The value in microohms of the
>                                       sample resistor.
> - rockchip,sleep-enter-current-microamp: The value in microamps of the
>                                          sleep enter current.
> - rockchip,sleep-filter-current: The value in microamps of the sleep
>                                  filter current.
> 
> Signed-off-by: Chris Morgan <macromorgan@hotmail.com>
> Signed-off-by: Maya Matuszczyk <maccraft123mc@gmail.com>
> ---
>  .../devicetree/bindings/mfd/rk808.txt         | 38 +++++++++++++++++++
>  1 file changed, 38 insertions(+)

You've also submitted converting this to schema. Please make the 
dependencies explicit. 

Rob


^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH v4 RESEND 3/4] power: supply: Add charger driver for Rockchip RK817
  2021-09-16 19:42 ` [PATCH v4 RESEND 3/4] power: supply: Add charger driver for Rockchip RK817 Chris Morgan
@ 2021-10-13 17:40   ` Sebastian Reichel
  2022-01-27 23:27     ` Peter Geis
  0 siblings, 1 reply; 11+ messages in thread
From: Sebastian Reichel @ 2021-10-13 17:40 UTC (permalink / raw)
  To: Chris Morgan
  Cc: linux-rockchip, lee.jones, robh+dt, heiko, maccraft123mc,
	devicetree, linux-pm, Chris Morgan

[-- Attachment #1: Type: text/plain, Size: 35008 bytes --]

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
> 

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH v4 RESEND 0/4] power: supply: Add Support for RK817 Charger
  2021-09-16 19:42 [PATCH v4 RESEND 0/4] power: supply: Add Support for RK817 Charger Chris Morgan
                   ` (3 preceding siblings ...)
  2021-09-16 19:42 ` [PATCH v4 RESEND 4/4] arm64: dts: rockchip: add rk817 charger to Odroid Go Advance Chris Morgan
@ 2021-11-30  2:03 ` Nicolas Frattaroli
       [not found]   ` <SN6PR06MB534222D7CA5732E689F5BA21A5679@SN6PR06MB5342.namprd06.prod.outlook.com>
  4 siblings, 1 reply; 11+ messages in thread
From: Nicolas Frattaroli @ 2021-11-30  2:03 UTC (permalink / raw)
  To: linux-rockchip
  Cc: lee.jones, robh+dt, heiko, sre, maccraft123mc, devicetree,
	linux-pm, Chris Morgan, Chris Morgan

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



^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH v4 RESEND 0/4] power: supply: Add Support for RK817 Charger
       [not found]   ` <SN6PR06MB534222D7CA5732E689F5BA21A5679@SN6PR06MB5342.namprd06.prod.outlook.com>
@ 2021-11-30 18:12     ` Nicolas Frattaroli
       [not found]       ` <SN6PR06MB5342DF2234B8F9599E1AF125A5679@SN6PR06MB5342.namprd06.prod.outlook.com>
  0 siblings, 1 reply; 11+ messages in thread
From: Nicolas Frattaroli @ 2021-11-30 18:12 UTC (permalink / raw)
  To: Chris Morgan
  Cc: linux-rockchip, lee.jones, robh+dt, heiko, sre, maccraft123mc,
	devicetree, linux-pm, Chris Morgan

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
> > 
> > 
> 





^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH v4 RESEND 0/4] power: supply: Add Support for RK817 Charger
       [not found]       ` <SN6PR06MB5342DF2234B8F9599E1AF125A5679@SN6PR06MB5342.namprd06.prod.outlook.com>
@ 2021-11-30 19:16         ` Nicolas Frattaroli
  0 siblings, 0 replies; 11+ messages in thread
From: Nicolas Frattaroli @ 2021-11-30 19:16 UTC (permalink / raw)
  To: Chris Morgan
  Cc: linux-rockchip, lee.jones, robh+dt, heiko, sre, maccraft123mc,
	devicetree, linux-pm, Chris Morgan

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
> > > > 
> > > > 
> > > 
> > 
> > 
> > 
> > 
> 





^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH v4 RESEND 3/4] power: supply: Add charger driver for Rockchip RK817
  2021-10-13 17:40   ` Sebastian Reichel
@ 2022-01-27 23:27     ` Peter Geis
  0 siblings, 0 replies; 11+ messages in thread
From: Peter Geis @ 2022-01-27 23:27 UTC (permalink / raw)
  To: Sebastian Reichel
  Cc: Chris Morgan, open list:ARM/Rockchip SoC...,
	Lee Jones, Rob Herring, Heiko Stuebner, maccraft123mc,
	devicetree, linux-pm, Chris Morgan

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

^ permalink raw reply	[flat|nested] 11+ messages in thread

end of thread, other threads:[~2022-01-27 23:27 UTC | newest]

Thread overview: 11+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-09-16 19:42 [PATCH v4 RESEND 0/4] power: supply: Add Support for RK817 Charger Chris Morgan
2021-09-16 19:42 ` [PATCH v4 RESEND 1/4] dt-bindings: Add Rockchip rk817 battery charger support Chris Morgan
2021-09-22 19:19   ` Rob Herring
2021-09-16 19:42 ` [PATCH v4 RESEND 2/4] mfd: " Chris Morgan
2021-09-16 19:42 ` [PATCH v4 RESEND 3/4] power: supply: Add charger driver for Rockchip RK817 Chris Morgan
2021-10-13 17:40   ` Sebastian Reichel
2022-01-27 23:27     ` Peter Geis
2021-09-16 19:42 ` [PATCH v4 RESEND 4/4] arm64: dts: rockchip: add rk817 charger to Odroid Go Advance Chris Morgan
2021-11-30  2:03 ` [PATCH v4 RESEND 0/4] power: supply: Add Support for RK817 Charger Nicolas Frattaroli
     [not found]   ` <SN6PR06MB534222D7CA5732E689F5BA21A5679@SN6PR06MB5342.namprd06.prod.outlook.com>
2021-11-30 18:12     ` Nicolas Frattaroli
     [not found]       ` <SN6PR06MB5342DF2234B8F9599E1AF125A5679@SN6PR06MB5342.namprd06.prod.outlook.com>
2021-11-30 19:16         ` Nicolas Frattaroli

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).