From f2aa1569957b77301da8902f7ab993b1b703430b Mon Sep 17 00:00:00 2001
From: Ondrej Jirman <megi@xff.cz>
Date: Mon, 17 Mar 2025 10:38:54 +0100
Subject: [PATCH 357/484] power: supply: rk818-battery: Add code/docs for the
 HW reverse engineering

This code helps documenting behavior of the PMIC in order to be able
to implement a new driver for it.

Signed-off-by: Ondrej Jirman <megi@xff.cz>
---
 drivers/power/supply/rk818_battery.c | 282 ++++++++++++++++++++++++++-
 1 file changed, 279 insertions(+), 3 deletions(-)

diff --git a/drivers/power/supply/rk818_battery.c b/drivers/power/supply/rk818_battery.c
index b2a2a4b105c5..3e9152e1178a 100644
--- a/drivers/power/supply/rk818_battery.c
+++ b/drivers/power/supply/rk818_battery.c
@@ -61,6 +61,7 @@
 #define BAT_CON			BIT(4)
 #define RELAX_VOL1_UPD		BIT(3)
 #define RELAX_VOL2_UPD		BIT(2)
+#define RELAX_VOL_STS		BIT(1)
 #define RELAX_VOL12_UPD_MSK	(RELAX_VOL1_UPD | RELAX_VOL2_UPD)
 
 /* RK818_SUP_STS_REG */
@@ -293,7 +294,13 @@ struct battery_platform_data {
 	u32 ntc_factor;
 };
 
+struct rk818_v2 {
+	int v;
+};
+
 struct rk818_battery {
+	int				ver;
+	struct rk818_v2			v2;
 	struct platform_device		*pdev;
 	struct rk808			*rk818;
 	struct regmap			*regmap;
@@ -314,12 +321,17 @@ struct rk818_battery {
 	int				voltage_avg;
 	int				voltage_ocv;
 	int				voltage_relax;
-	int				voltage_k;
-	int				voltage_b;
+
+					// stored in vcalib0/1
+					// k = (4200 - 3000) * 1000 / DIV(vcalib1 - vcalib0);
+					// b = 4200 - (di->voltage_k * vcalib1) / 1000;
+	int				voltage_k; // correction multiplier
+	int				voltage_b; // correction offset  (vol = val * k / 1000 + b)
+
 	int				remain_cap;
 	int				design_cap;
 	int				nac;
-	int				fcc;
+	int				fcc; // fully charged capacity
 	int				qmax;
 	int				dsoc;
 	int				rsoc;
@@ -3270,6 +3282,9 @@ static int rk818_battery_suspend(struct platform_device *dev,
 
 	cancel_delayed_work_sync(&di->bat_delay_work);
 
+	if (di->ver == 2)
+		return 0;
+
 	di->s2r = false;
 	di->sleep_chrg_online = rk818_bat_chrg_online(di);
 	di->sleep_chrg_status = rk818_bat_get_chrg_status(di);
@@ -3323,6 +3338,12 @@ static int rk818_battery_resume(struct platform_device *dev)
 	int interval_sec, time_step, pwroff_vol;
 	u8 st;
 
+	if (di->ver == 2) {
+		queue_delayed_work(di->bat_monitor_wq, &di->bat_delay_work,
+				   msecs_to_jiffies(2000));
+		return 0;
+	}
+
 	di->s2r = true;
 	di->current_avg = rk818_bat_get_avg_current(di);
 	di->voltage_relax = rk818_bat_get_relax_voltage(di);
@@ -3371,6 +3392,252 @@ static int rk818_battery_resume(struct platform_device *dev)
 	return 0;
 }
 
+// }}}
+// {{{ V2
+
+static unsigned rk818_bat_read16(struct rk818_battery *di, unsigned regh,
+				 unsigned mul, unsigned off)
+{
+	unsigned val;
+
+	val  = rk818_bat_read(di, regh + 1);
+	val |= rk818_bat_read(di, regh) << 8;
+
+	return val * mul / 1000 + off;
+}
+
+static int rk818_bat_read16s(struct rk818_battery *di, unsigned regh,
+			     int mul)
+{
+	int val;
+
+	val  = rk818_bat_read(di, regh + 1);
+	val |= rk818_bat_read(di, regh) << 8;
+
+	if (val & 0x800)
+		val -= 4096;
+
+	return val * mul / 1000;
+}
+
+static void rk818_bat_write16(struct rk818_battery *di, unsigned regh, int val,
+				 unsigned mul, unsigned off)
+{
+	unsigned r = (val - off) * 1000 / mul;
+
+	rk818_bat_write(di, regh + 1, r & 0xff);
+	rk818_bat_write(di, regh, (r >> 8) & 0xff);
+}
+
+static int rk818_v2_bat_get_coulomb_cap(struct rk818_battery *di)
+{
+	int val = 0;
+
+	val |= rk818_bat_read(di, RK818_GASCNT3_REG) << 24;
+	val |= rk818_bat_read(di, RK818_GASCNT2_REG) << 16;
+	val |= rk818_bat_read(di, RK818_GASCNT1_REG) << 8;
+	val |= rk818_bat_read(di, RK818_GASCNT0_REG) << 0;
+
+	return val;
+	//return val / 1195;
+}
+
+static void rk818_v2_bat_set_coulomb_cap(struct rk818_battery *di, u32 capacity)
+{
+	//capacity *= 1195;
+
+	rk818_bat_write(di, RK818_GASCNT_CAL_REG3, (capacity >> 24) & 0xff);
+	rk818_bat_write(di, RK818_GASCNT_CAL_REG2, (capacity >> 16) & 0xff);
+	rk818_bat_write(di, RK818_GASCNT_CAL_REG1, (capacity >> 8) & 0xff);
+	rk818_bat_write(di, RK818_GASCNT_CAL_REG0, (capacity >> 0) & 0xff);
+}
+
+/*
+ * ADC Voltage readout calibration
+ * -------------------------------
+ *
+ * Two values are stored in VCALIB registers:
+ *
+ * vcalib0 = adc register value at 3.0V
+ * vcalib1 = adc register value at 4.2V
+ *
+ * k = (4200 - 3000) * 1000 / (vcalib1 - vcalib0);
+ * b = 4200 - (k * vcalib1) / 1000;
+ * vol = reg_val * k / 1000 + b
+ *
+ * Max range for ADC seems to be ~4.4V (reg val is approx. 4095)
+ *
+ *
+ * ADC Current sensing calibration
+ * -------------------------------
+ *
+ * For current sensing, we need to eliminate offset at 0A. PMIC stores the value
+ * read out from ADC when the current should be 0 (eg. when the system is off and
+ * not charging) to IOFFSET register.
+ *
+ * We can use this value to calculate current offset and write it to CAL_OFFSET
+ * register during driver initialization. Valid range is 0x780 - 0x980
+ *
+ * This is usually not enough, so the driver has to perform its own offset removal,
+ * by waiting for a suitable state of the device (after charging is finished or
+ * when charging is blocked, while the device is connected to external power
+ * source) and then assume there's no current flow to/from the battery and cancel
+ * the offset based on reading out the BAT_CUR_AVG code word and adding it to
+ * CAL_OFFSET, until BAT_CUR_AVG becomes 0 over several iterations.
+ *
+ * General algorithm:
+ *
+ * while (abs(BAT_CUR_AVG) > 2) {
+ *	CAL_OFFSET = CAL_OFFSET + BAT_CUR_AVG
+ *      wait 2s
+ * }
+ *
+ * // save a difference of CAL_OFFSET from IOFFSET for reuse later
+ * POFFSET = CAL_OFFSET - IOFFSET
+ *
+ * POFFSET is used after re-boot to initialize COFFSET to POFFSET + IOFFSET.
+ *
+ * BAT_CUR_AVG is an averaged ADC reading for current sensing resistor compensated
+ * by subtracting CAL_OFFSET from the actual ADC value.
+ *
+ * BAT_CUR_AVG range is 0-4095, format 12bit 2's complement, representing values
+ * -2048 - 2047.
+ *
+ * Current sensing resistor can be arbitrary value, but two values are supported
+ * by the original driver:
+ *
+ * 20 mOhm - ADC code word needs to be multiplied by 1.506 to get current
+ * 10 mOhm - ADC code word needs to be multiplied by 3.012 to get current (PPP)
+ *
+ * Differential voltage range on sensing resistor is +- 61.65mV
+ *
+ * That means that full range is 2047 * 3.012 = +-6.165 A and LSB is 3mA
+ *
+ * Reasonalbe COFFSET initial value for my PPP is 0x81b (when IOFFSET is 0x804)
+ *
+ *
+ * Relaxed voltage measurement
+ * ---------------------------
+ *
+ * This is a voltage measured on battery after current decreases below
+ * RELAX_ENTRY_THRES or increases above RELAX_EXIT_THRES.
+ *
+ * To measure the voltage, configure the current thresholds and clear
+ * bits 2 and 3 in GGSTS, and wait until they're set again.
+ *
+ * After COFFSET is calibrated, it's possible to detect low current scenario.
+ * After 8 minutes of BAT_CUR_AVG being < RELAX_ENTRY_THRES, BAT_VOL is
+ * written to RELAX_VOL1 and RELAX_VOL1 is written to RELAX_VOL2. This happens
+ * every 8 minutes the condition is satisfied, whithout any need to trigger
+ * this behavior. It's a 2 level queue, where RELAX_VOL1 is last item, and
+ * RELAX_VOL2 is previous one.
+ *
+ * GGSTS.RELAX_VOL1_UPD and GGSTS.RELAX_VOL2_UPD are flags indicating validity
+ * of data in RELAX_VOL# registers. They can be cleared by the driver and are
+ * set by HW when registers are updated.
+ *
+ * GGCON.OCV_SAMPL_INTERV controls the update period?
+ *
+ *
+ * Automatic switch to lower sampling frequency when current is low
+ * ----------------------------------------------------------------
+ *
+ * This is a "sleep" mode, and supposed to be used in suspend to ram state, when
+ * current is very stable, there doesn't need to be high sampling frequency for
+ * coulomb meter.
+ *
+ * AUTO_SLP_CUR_THR - current threshold for switching to sleep mode
+ * FRAME_SMP_INTERV - sampling interval in sleep mode
+ * AUTO_SLP_EN - enable bit
+ * NON_ACT_TIMER_CNT - sleep off counter? (minutes)
+ *
+ *
+ * Coulomb meter
+ * -------------
+ *
+ * TS_CTRL.GG_EN - enable coulomb meter (default on)
+ * ADC_CUR_VOL_MODE - bit switching between voltage/0 and current/1 mode
+ * GASCNT_CAL - sets value directly to GASCNT
+ * GASCNT - accumulates values from ADC_RES_MODE every second when in current mode
+ *
+ *
+ * OCV and Rint sensing
+ * --------------------
+ *
+ * BAT_OCV is also updated at this time.
+ *
+ * ADC_RES_MODE - enable flag
+ * RES_CUR_AVG_SEL - amount of ripple used for Rint sensing
+ * IV_AVG_UPD_STS - flag when sensing was successful, when set?
+ * BAT_VOL_R_CALC - stable voltage for Rint calc
+ * BAT_CUR_R_CALC - stable current for Rint calc
+ * BAT_OCV - voltage?
+ *
+ */
+
+static void rk818_v2_work(struct work_struct *work)
+{
+	struct rk818_battery *di =
+		container_of(work, struct rk818_battery, bat_delay_work.work);
+
+	unsigned vcalib0 = rk818_bat_read16(di, RK818_VCALIB0_REGH, 1000, 0);
+	unsigned vcalib1 = rk818_bat_read16(di, RK818_VCALIB1_REGH, 1000, 0);
+	unsigned ioffset = rk818_bat_read16(di, RK818_IOFFSET_REGH, 1000, 0);
+
+	unsigned k = (4200 - 3000) * 1000 / (vcalib1 - vcalib0);
+	unsigned b = 4200 - (k * vcalib1) / 1000;
+	int csens = 3012; // conductance of sensing resistor
+
+	unsigned vol = rk818_bat_read16(di, RK818_BAT_VOL_REGH, k, b);
+	unsigned ocv = rk818_bat_read16(di, RK818_BAT_OCV_REGH, k, b);
+	unsigned rv1 = rk818_bat_read16(di, RK818_RELAX_VOL1_REGH, k, b);
+	unsigned rv2 = rk818_bat_read16(di, RK818_RELAX_VOL2_REGH, k, b);
+
+	unsigned sts = rk818_bat_read(di, RK818_GGSTS_REG);
+	unsigned rv1s = !!(sts & RELAX_VOL1_UPD);
+	unsigned rv2s = !!(sts & RELAX_VOL2_UPD);
+	unsigned rs = !!(sts & RELAX_VOL_STS);
+
+	unsigned vol_r = rk818_bat_read16(di, RK818_BAT_VOL_R_CALC_REGH, k, b);
+	int cur_r = rk818_bat_read16s(di, RK818_BAT_CUR_R_CALC_REGH, csens);
+
+	unsigned caloff = rk818_bat_read16(di, RK818_CAL_OFFSET_REGH, 1000, 0);
+	int cur_raw = rk818_bat_read16s(di, RK818_BAT_CUR_AVG_REGH, 1000);
+	int cur = rk818_bat_read16s(di, RK818_BAT_CUR_AVG_REGH, csens);
+
+	int cap = rk818_v2_bat_get_coulomb_cap(di);
+
+	pr_err("bat: cap=%d cur=%d cur_raw=%d caloff=%u ioffset=%u vol=%u rv1=%u(%u) rv2=%u(%u) rs=%u ocv=%u cur_r=%d vol_r=%u\n",
+	       cap, cur, cur_raw, caloff, ioffset, vol, rv1, rv1s, rv2, rv2s, rs, ocv, cur_r, vol_r);
+
+	queue_delayed_work(di->bat_monitor_wq, &di->bat_delay_work,
+			   msecs_to_jiffies(250));
+}
+
+static void rk818_v2_init(struct rk818_battery *di)
+{
+	int csens = 3012; // conductance of sensing resistor
+
+	rk818_v2_bat_set_coulomb_cap(di, 0);
+
+	// 100mA thresholds
+	rk818_bat_write16(di, RK818_RELAX_ENTRY_THRES_REGH, 100, csens, 0);
+	rk818_bat_write16(di, RK818_RELAX_EXIT_THRES_REGH, 100, csens, 0);
+
+	unsigned sts = rk818_bat_read(di, RK818_GGSTS_REG);
+	rk818_bat_write(di, RK818_GGSTS_REG, sts & ~RELAX_VOL12_UPD_MSK);
+
+	di->bat_monitor_wq =
+		alloc_ordered_workqueue("%s", WQ_MEM_RECLAIM | WQ_FREEZABLE,
+					"rk818-v2-bat-work");
+
+	INIT_DELAYED_WORK(&di->bat_delay_work, rk818_v2_work);
+	queue_delayed_work(di->bat_monitor_wq, &di->bat_delay_work,
+			   msecs_to_jiffies(2000));
+
+	dev_info(di->dev, "v2 driver ready\n");
+}
+
 // }}}
 // {{{ Probe/shutdown
 
@@ -3398,6 +3665,12 @@ static int rk818_battery_probe(struct platform_device *pdev)
 	di->regmap = rk818->regmap;
 	platform_set_drvdata(pdev, di);
 
+	di->ver = device_property_read_bool(di->dev, "v2") ? 2 : 1;
+	if (di->ver == 2) {
+		rk818_v2_init(di);
+		return 0;
+	}
+
 	ret = rk818_bat_parse_dt(di);
 	if (ret < 0) {
 		dev_err(di->dev, "rk818 battery parse dt failed!\n");
@@ -3432,6 +3705,9 @@ static void rk818_battery_shutdown(struct platform_device *dev)
 	u8 cnt = 0;
 	struct rk818_battery *di = platform_get_drvdata(dev);
 
+	if (di->ver == 2)
+		return;
+
 	cancel_delayed_work_sync(&di->bat_delay_work);
 	cancel_delayed_work_sync(&di->calib_delay_work);
 	timer_delete(&di->caltimer);
-- 
2.49.0

