From 33a18232661b4ed90e130cb32be14c12488c014b Mon Sep 17 00:00:00 2001
From: Ondrej Jirman <megous@megous.com>
Date: Sat, 22 Jan 2022 00:03:11 +0100
Subject: [PATCH 310/446] power: supply: ip5xxx: Retry charger initialization
 if it fails

It is normal and expected that access to the charger may fail at
any time. When the charger is asleep, it does not react on the
I2C bus. We simply poll and wait until the user enables the charger
via a physical button.

Signed-off-by: Ondrej Jirman <megous@megous.com>
---
 drivers/power/supply/ip5xxx_power.c | 70 +++++++++++++++++++++++------
 1 file changed, 57 insertions(+), 13 deletions(-)

diff --git a/drivers/power/supply/ip5xxx_power.c b/drivers/power/supply/ip5xxx_power.c
index 82df0fb82..7f727f5be 100644
--- a/drivers/power/supply/ip5xxx_power.c
+++ b/drivers/power/supply/ip5xxx_power.c
@@ -75,7 +75,10 @@
 #define IP5XXX_BATOCV_DAT1		0xa9
 
 struct ip5xxx {
+	struct device *dev;
 	struct regmap *regmap;
+	struct delayed_work wd_work;
+	bool initialized;
 };
 
 static const enum power_supply_property ip5xxx_boost_properties[] = {
@@ -480,18 +483,18 @@ static const struct regmap_config ip5xxx_regmap_config = {
 	.max_register		= IP5XXX_BATOCV_DAT1,
 };
 
-int ip5xxx_power_probe_with_regmap(struct device* dev, struct regmap *regmap)
+static void ip5xxx_power_wd_work(struct work_struct *work)
 {
-	struct power_supply_config psy_cfg = {};
-	struct power_supply *psy;
-	struct ip5xxx *ip5xxx;
+	struct ip5xxx *ip5xxx = container_of(work, struct ip5xxx, wd_work.work);
 	int ret;
 
-	ip5xxx = devm_kzalloc(dev, sizeof(*ip5xxx), GFP_KERNEL);
-	if (!ip5xxx)
-		return -ENOMEM;
-
-	ip5xxx->regmap = regmap;
+	if (ip5xxx->initialized) {
+		//XXX: perform some checks?
+		//- charger may reset some of its state when it's powered down
+		//  by the user and not accessible
+		schedule_delayed_work(&ip5xxx->wd_work, msecs_to_jiffies(5000));
+		return;
+	}
 
 	/*
 	 * Disable shutdown under light load (it turns off the keyboard).
@@ -501,7 +504,7 @@ int ip5xxx_power_probe_with_regmap(struct device* dev, struct regmap *regmap)
 				 IP5XXX_SYS_CTL1_LIGHT_SHDN_EN |
 				 IP5XXX_SYS_CTL1_LOAD_PWRUP_EN, 0);
 	if (ret)
-		return ret;
+		goto err;
 
 	/*
 	 * Enable shutdown after two short button presses within 1 second.
@@ -510,7 +513,7 @@ int ip5xxx_power_probe_with_regmap(struct device* dev, struct regmap *regmap)
 				 IP5XXX_SYS_CTL3_BTN_SHDN_EN,
 				 IP5XXX_SYS_CTL3_BTN_SHDN_EN);
 	if (ret)
-		return ret;
+		goto err;
 
 	/*
 	 * Disable power on when VIN is removed (wait until the phone is on).
@@ -518,7 +521,7 @@ int ip5xxx_power_probe_with_regmap(struct device* dev, struct regmap *regmap)
 	ret = regmap_update_bits(ip5xxx->regmap, IP5XXX_SYS_CTL4,
 				 IP5XXX_SYS_CTL4_VIN_PULLOUT_BOOST_EN, 0);
 	if (ret)
-		return ret;
+		goto err;
 
 	/*
 	 * Enable the NTC.
@@ -529,7 +532,39 @@ int ip5xxx_power_probe_with_regmap(struct device* dev, struct regmap *regmap)
 				 IP5XXX_SYS_CTL5_WLED_MODE_SEL |
 				 IP5XXX_SYS_CTL5_BTN_SHDN_SEL, 0);
 	if (ret)
-		return ret;
+		goto err;
+
+	ip5xxx->initialized = true;
+	schedule_delayed_work(&ip5xxx->wd_work, msecs_to_jiffies(5000));
+	dev_info(ip5xxx->dev, "Charger is initialized\n");
+	return;
+
+err:
+	dev_err(ip5xxx->dev, "Failed to initialize the charger\n");
+	schedule_delayed_work(&ip5xxx->wd_work, msecs_to_jiffies(5000));
+}
+
+static void ip5xxx_power_cleanup(void* data)
+{
+	struct ip5xxx *ip5xxx = data;
+
+	cancel_delayed_work_sync(&ip5xxx->wd_work);
+}
+
+int ip5xxx_power_probe_with_regmap(struct device* dev, struct regmap *regmap)
+{
+	struct power_supply_config psy_cfg = {};
+	struct power_supply *psy;
+	struct ip5xxx *ip5xxx;
+	int ret;
+
+	ip5xxx = devm_kzalloc(dev, sizeof(*ip5xxx), GFP_KERNEL);
+	if (!ip5xxx)
+		return -ENOMEM;
+
+	ip5xxx->dev = dev;
+	ip5xxx->regmap = regmap;
+	INIT_DELAYED_WORK(&ip5xxx->wd_work, ip5xxx_power_wd_work);
 
 	psy_cfg.of_node = dev->of_node;
 	psy_cfg.drv_data = ip5xxx;
@@ -542,6 +577,15 @@ int ip5xxx_power_probe_with_regmap(struct device* dev, struct regmap *regmap)
 	if (IS_ERR(psy))
 		return PTR_ERR(psy);
 
+	ret = devm_add_action(dev, ip5xxx_power_cleanup, ip5xxx);
+	if (ret) {
+		ip5xxx_power_cleanup(ip5xxx);
+		dev_err(dev, "Failed to add cleanup action (%d)\n", ret);
+		return ret;
+	}
+
+	schedule_delayed_work(&ip5xxx->wd_work, msecs_to_jiffies(500));
+
 	return 0;
 }
 EXPORT_SYMBOL_GPL(ip5xxx_power_probe_with_regmap);
-- 
2.31.1

