Skip to contentSkip to conversation

Enhance twl4030_charger functionality. - V2

PATCH Applied NeilBrown 5 participants 24 messages Mar 22, 2015 – Jul 29, 2015

Subsystems (4)
lore
Share & tools
lore

NeilBrown

author
#0 lore raw

cover Enhance twl4030_charger functionality. - V2

Hi Sebastian,
here is V2 of my patches to improve the twl4030 charger. They include improved documentation and removal of changes to the CVENAC bit which I didn't properly understand.

One patch needs to make a change in drivers/mfd, and that has been acked by Lee Jones.

Summary from before:
These patches make a number of improvements to twl4030_charger.

Some are just internal cleanups (e.g. use of devres). Others allow better control of charging through both manual and automatic means.

- the maximum current can be configured via sysfs.
- the charger will only draw that current if it can do so without the voltage dropping too much
- a 'continuous' mode is available which ignores voltage and just takes what it can (to be used with caution, but very useful in some circumstances).
- 'ac' and 'usb' power sources can be configured separately.

Thanks,
NeilBrown

---

NeilBrown (14):
twl4030_charger: use devm_request_threaded_irq
twl4030_charger: use devres for power_supply_register and kzalloc.
twl4030_charger: use runtime_pm to keep usb phy active while charging.
twl4030_charger: trust phy to determine when USB power is available.
twl4030_charger: split uA calculation into a function.
twl4030_charger: allow fine control of charger current.
twl4030_charger: distinguish between USB current and 'AC' current
twl4030_charger: allow max_current to be managed via sysfs.
twl4030_charger: only draw USB current as negotiated with host.
twl4030_charger: enable manual enable/disable of usb charging.
twl4030_charger: add software controlled linear charging mode.
twl4030_charger: add ac/mode to match usb/mode
twl4030_charger: Increase current carefully while watching voltage.
twl4030_charger: assume a 'charger' can supply maximum current.

 .../ABI/testing/sysfs-class-power-twl4030          |   45 ++
 drivers/mfd/twl-core.c                             |    9 
 drivers/power/twl4030_charger.c                    |  573 +++++++++++++++++---
 3 files changed, 536 insertions(+), 91 deletions(-)
 create mode 100644 Documentation/ABI/testing/sysfs-class-power-twl4030

--
Signature

author
#1 lore raw

PATCH 1/14 twl4030_charger: use devm_request_threaded_irq

From: NeilBrown <[email protected]>

This simplifies the error paths.

---
 drivers/power/twl4030_charger.c |   18 ++++++------------
 1 file changed, 6 insertions(+), 12 deletions(-)
drivers/power/twl4030_charger.c +6 -12
index b07f4e2f2dde..58996d252ecf 100644
@@ -610,21 +610,21 @@ static int __init twl4030_bci_probe(struct platform_device *pdev)
 		goto fail_register_usb;
 	}
 
-	ret = request_threaded_irq(bci->irq_chg, NULL,
+	ret = devm_request_threaded_irq(&pdev->dev, bci->irq_chg, NULL,
 			twl4030_charger_interrupt, IRQF_ONESHOT, pdev->name,
 			bci);
 	if (ret < 0) {
 		dev_err(&pdev->dev, "could not request irq %d, status %d\n",
 			bci->irq_chg, ret);
-		goto fail_chg_irq;
+		goto fail;
 	}
 
-	ret = request_threaded_irq(bci->irq_bci, NULL,
+	ret = devm_request_threaded_irq(&pdev->dev, bci->irq_bci, NULL,
 			twl4030_bci_interrupt, IRQF_ONESHOT, pdev->name, bci);
 	if (ret < 0) {
 		dev_err(&pdev->dev, "could not request irq %d, status %d\n",
 			bci->irq_bci, ret);
-		goto fail_bci_irq;
+		goto fail;
 	}
 
 	INIT_WORK(&bci->work, twl4030_bci_usb_work);
@@ -647,7 +647,7 @@ static int __init twl4030_bci_probe(struct platform_device *pdev)
 			       TWL4030_INTERRUPTS_BCIIMR1A);
 	if (ret < 0) {
 		dev_err(&pdev->dev, "failed to unmask interrupts: %d\n", ret);
-		goto fail_unmask_interrupts;
+		goto fail;
 	}
 
 	reg = ~(u32)(TWL4030_VBATOV | TWL4030_VBUSOV | TWL4030_ACCHGOV);
@@ -666,11 +666,7 @@ static int __init twl4030_bci_probe(struct platform_device *pdev)
 
 	return 0;
 
-fail_unmask_interrupts:
-	free_irq(bci->irq_bci, bci);
-fail_bci_irq:
-	free_irq(bci->irq_chg, bci);
-fail_chg_irq:
+fail:
 	power_supply_unregister(&bci->usb);
 fail_register_usb:
 	power_supply_unregister(&bci->ac);
@@ -695,8 +691,6 @@ static int __exit twl4030_bci_remove(struct platform_device *pdev)
 	twl_i2c_write_u8(TWL4030_MODULE_INTERRUPTS, 0xff,
 			 TWL4030_INTERRUPTS_BCIIMR2A);
 
-	free_irq(bci->irq_bci, bci);
-	free_irq(bci->irq_chg, bci);
 	power_supply_unregister(&bci->usb);
 	power_supply_unregister(&bci->ac);
 	kfree(bci);
author
#3 lore raw

PATCH 2/14 twl4030_charger: use devres for power_supply_register and kzalloc.

In reply to NeilBrown

From: NeilBrown <[email protected]>

Final allocations/registrations are now managed by devres.

---
 drivers/power/twl4030_charger.c |   32 +++++++++-----------------------
 1 file changed, 9 insertions(+), 23 deletions(-)
drivers/power/twl4030_charger.c +9 -23
index 58996d252ecf..7b6c4000cd30 100644
@@ -565,7 +565,7 @@ static int __init twl4030_bci_probe(struct platform_device *pdev)
 	int ret;
 	u32 reg;
 
-	bci = kzalloc(sizeof(*bci), GFP_KERNEL);
+	bci = devm_kzalloc(&pdev->dev, sizeof(*bci), GFP_KERNEL);
 	if (bci == NULL)
 		return -ENOMEM;
 
@@ -580,7 +580,7 @@ static int __init twl4030_bci_probe(struct platform_device *pdev)
 	ret = twl4030_is_battery_present(bci);
 	if  (ret) {
 		dev_crit(&pdev->dev, "Battery was not detected:%d\n", ret);
-		goto fail_no_battery;
+		return ret;
 	}
 
 	platform_set_drvdata(pdev, bci);
@@ -590,10 +590,10 @@ static int __init twl4030_bci_probe(struct platform_device *pdev)
 	bci->ac.num_properties = ARRAY_SIZE(twl4030_charger_props);
 	bci->ac.get_property = twl4030_bci_get_property;
 
-	ret = power_supply_register(&pdev->dev, &bci->ac);
+	ret = devm_power_supply_register(&pdev->dev, &bci->ac);
 	if (ret) {
 		dev_err(&pdev->dev, "failed to register ac: %d\n", ret);
-		goto fail_register_ac;
+		return ret;
 	}
 
 	bci->usb.name = "twl4030_usb";
@@ -604,10 +604,10 @@ static int __init twl4030_bci_probe(struct platform_device *pdev)
 
 	bci->usb_reg = regulator_get(bci->dev, "bci3v1");
 
-	ret = power_supply_register(&pdev->dev, &bci->usb);
+	ret = devm_power_supply_register(&pdev->dev, &bci->usb);
 	if (ret) {
 		dev_err(&pdev->dev, "failed to register usb: %d\n", ret);
-		goto fail_register_usb;
+		return ret;
 	}
 
 	ret = devm_request_threaded_irq(&pdev->dev, bci->irq_chg, NULL,
@@ -616,7 +616,7 @@ static int __init twl4030_bci_probe(struct platform_device *pdev)
 	if (ret < 0) {
 		dev_err(&pdev->dev, "could not request irq %d, status %d\n",
 			bci->irq_chg, ret);
-		goto fail;
+		return ret;
 	}
 
 	ret = devm_request_threaded_irq(&pdev->dev, bci->irq_bci, NULL,
@@ -624,7 +624,7 @@ static int __init twl4030_bci_probe(struct platform_device *pdev)
 	if (ret < 0) {
 		dev_err(&pdev->dev, "could not request irq %d, status %d\n",
 			bci->irq_bci, ret);
-		goto fail;
+		return ret;
 	}
 
 	INIT_WORK(&bci->work, twl4030_bci_usb_work);
@@ -647,7 +647,7 @@ static int __init twl4030_bci_probe(struct platform_device *pdev)
 			       TWL4030_INTERRUPTS_BCIIMR1A);
 	if (ret < 0) {
 		dev_err(&pdev->dev, "failed to unmask interrupts: %d\n", ret);
-		goto fail;
+		return ret;
 	}
 
 	reg = ~(u32)(TWL4030_VBATOV | TWL4030_VBUSOV | TWL4030_ACCHGOV);
@@ -665,16 +665,6 @@ static int __init twl4030_bci_probe(struct platform_device *pdev)
 		twl4030_charger_enable_backup(0, 0);
 
 	return 0;
-
-fail:
-	power_supply_unregister(&bci->usb);
-fail_register_usb:
-	power_supply_unregister(&bci->ac);
-fail_register_ac:
-fail_no_battery:
-	kfree(bci);
-
-	return ret;
 }
 
 static int __exit twl4030_bci_remove(struct platform_device *pdev)
@@ -691,10 +681,6 @@ static int __exit twl4030_bci_remove(struct platform_device *pdev)
 	twl_i2c_write_u8(TWL4030_MODULE_INTERRUPTS, 0xff,
 			 TWL4030_INTERRUPTS_BCIIMR2A);
 
-	power_supply_unregister(&bci->usb);
-	power_supply_unregister(&bci->ac);
-	kfree(bci);
-
 	return 0;
 }
 
#4 lore raw

Re: [PATCH 02/14] twl4030_charger: use devres for power_supply_register and kzalloc.

Quoted 5 lines · NeilBrown

From: NeilBrown <[email protected]>

Final allocations/registrations are now managed by devres.

Patches 1,2: Acked-by: Pavel Machek <[email protected]>

Signature · 2 lines
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html
#5 lore raw

Re: [PATCH 02/14] twl4030_charger: use devres for power_supply_register and kzalloc.

In reply to NeilBrown

Hi,

Quoted 5 lines · NeilBrown

From: NeilBrown <[email protected]>

Final allocations/registrations are now managed by devres.

Thanks for the patch, rebased to current master and queued with Pavel's ACK:

http://git.infradead.org/battery-2.6.git/commit/325b50aa5d1c7dae57d1e44defdbacd1e2bcabde

-- Sebastian

author
#6 lore raw

PATCH 3/14 twl4030_charger: use runtime_pm to keep usb phy active while charging.

In reply to NeilBrown

From: NeilBrown <[email protected]>

The twl4030 usb phy needs to be active while we are using the USB VBUS as a current source for charging.
In particular, the usb3v1 regulator must be enabled and the PHY_PWR_PHYPWD bit must be set to keep the phy powered.

commit ab37813f4093a5f59cb8e083cde277289dc72ed3
twl4030_charger: Allow charger to control the regulator that feeds it

Gave the charger control over the regulator, but didn't resolve the PHY_PWR_PHYPWD issue.

Now that both of these are controlled by runtime_pm in phy-twl4030-usb, we can simply take a runtime_pm reference to the USB phy whenever the charger wants to use it as a current source.

So this patch reverts the above commit, and adds the necessary runtime_pm calls.

---
 drivers/mfd/twl-core.c          |    9 ++++-----
 drivers/power/twl4030_charger.c |   18 +++++-------------
 2 files changed, 9 insertions(+), 18 deletions(-)
drivers/mfd/twl-core.c +4 -5
index 489674a2497e..831696ee2472 100644
@@ -788,9 +788,8 @@ add_children(struct twl4030_platform_data *pdata, unsigned irq_base,
 		static struct regulator_consumer_supply usb1v8 = {
 			.supply =	"usb1v8",
 		};
-		static struct regulator_consumer_supply usb3v1[] = {
-			{ .supply =	"usb3v1" },
-			{ .supply =	"bci3v1" },
+		static struct regulator_consumer_supply usb3v1 = {
+			.supply =	"usb3v1",
 		};
 
 	/* First add the regulators so that they can be used by transceiver */
@@ -818,7 +817,7 @@ add_children(struct twl4030_platform_data *pdata, unsigned irq_base,
 				return PTR_ERR(child);
 
 			child = add_regulator_linked(TWL4030_REG_VUSB3V1,
-						      &usb_fixed, usb3v1, 2,
+						      &usb_fixed, &usb3v1, 1,
 						      features);
 			if (IS_ERR(child))
 				return PTR_ERR(child);
@@ -838,7 +837,7 @@ add_children(struct twl4030_platform_data *pdata, unsigned irq_base,
 		if (IS_ENABLED(CONFIG_REGULATOR_TWL4030) && child) {
 			usb1v5.dev_name = dev_name(child);
 			usb1v8.dev_name = dev_name(child);
-			usb3v1[0].dev_name = dev_name(child);
+			usb3v1.dev_name = dev_name(child);
 		}
 	}
 
drivers/power/twl4030_charger.c +5 -13
index 7b6c4000cd30..94c7a4d9400a 100644
@@ -22,7 +22,6 @@
 #include <linux/power_supply.h>
 #include <linux/notifier.h>
 #include <linux/usb/otg.h>
-#include <linux/regulator/machine.h>
 
 #define TWL4030_BCIMSTATEC	0x02
 #define TWL4030_BCIICHG		0x08
@@ -94,7 +93,6 @@ struct twl4030_bci {
 	struct work_struct	work;
 	int			irq_chg;
 	int			irq_bci;
-	struct regulator	*usb_reg;
 	int			usb_enabled;
 
 	unsigned long		event;
@@ -208,7 +206,7 @@ static int twl4030_charger_enable_usb(struct twl4030_bci *bci, bool enable)
 {
 	int ret;
 
-	if (enable) {
+	if (enable && !IS_ERR_OR_NULL(bci->transceiver)) {
 		/* Check for USB charger connected */
 		if (!twl4030_bci_have_vbus(bci))
 			return -ENODEV;
@@ -222,14 +220,9 @@ static int twl4030_charger_enable_usb(struct twl4030_bci *bci, bool enable)
 			return -EACCES;
 		}
 
-		/* Need to keep regulator on */
+		/* Need to keep phy powered */
 		if (!bci->usb_enabled) {
-			ret = regulator_enable(bci->usb_reg);
-			if (ret) {
-				dev_err(bci->dev,
-					"Failed to enable regulator\n");
-				return ret;
-			}
+			pm_runtime_get_sync(bci->transceiver->dev);
 			bci->usb_enabled = 1;
 		}
 
@@ -244,7 +237,8 @@ static int twl4030_charger_enable_usb(struct twl4030_bci *bci, bool enable)
 	} else {
 		ret = twl4030_clear_set_boot_bci(TWL4030_BCIAUTOUSB, 0);
 		if (bci->usb_enabled) {
-			regulator_disable(bci->usb_reg);
+			pm_runtime_mark_last_busy(bci->transceiver->dev);
+			pm_runtime_put_autosuspend(bci->transceiver->dev);
 			bci->usb_enabled = 0;
 		}
 	}
@@ -602,8 +596,6 @@ static int __init twl4030_bci_probe(struct platform_device *pdev)
 	bci->usb.num_properties = ARRAY_SIZE(twl4030_charger_props);
 	bci->usb.get_property = twl4030_bci_get_property;
 
-	bci->usb_reg = regulator_get(bci->dev, "bci3v1");
-
 	ret = devm_power_supply_register(&pdev->dev, &bci->usb);
 	if (ret) {
 		dev_err(&pdev->dev, "failed to register usb: %d\n", ret);
author
#7 lore raw

PATCH 4/14 twl4030_charger: trust phy to determine when USB power is available.

In reply to NeilBrown

From: NeilBrown <[email protected]>

The usb phy driver already determines when VBUS is available, so repeating the test in the charger driver is pointless duplication.

On probe, process the last event from the phy, and from then on, do whatever the phy tells us without double-checking.

---
 drivers/power/twl4030_charger.c |   33 ++++++---------------------------
 1 file changed, 6 insertions(+), 27 deletions(-)
drivers/power/twl4030_charger.c +6 -27
index 94c7a4d9400a..0e62a5ae56a3 100644
@@ -178,28 +178,6 @@ static int twl4030_is_battery_present(struct twl4030_bci *bci)
 }
 
 /*
- * Check if VBUS power is present
- */
-static int twl4030_bci_have_vbus(struct twl4030_bci *bci)
-{
-	int ret;
-	u8 hwsts;
-
-	ret = twl_i2c_read_u8(TWL_MODULE_PM_MASTER, &hwsts,
-			      TWL4030_PM_MASTER_STS_HW_CONDITIONS);
-	if (ret < 0)
-		return 0;
-
-	dev_dbg(bci->dev, "check_vbus: HW_CONDITIONS %02x\n", hwsts);
-
-	/* in case we also have STS_USB_ID, VBUS is driven by TWL itself */
-	if ((hwsts & TWL4030_STS_VBUS) && !(hwsts & TWL4030_STS_USB_ID))
-		return 1;
-
-	return 0;
-}
-
-/*
  * Enable/Disable USB Charge functionality.
  */
 static int twl4030_charger_enable_usb(struct twl4030_bci *bci, bool enable)
@@ -207,10 +185,6 @@ static int twl4030_charger_enable_usb(struct twl4030_bci *bci, bool enable)
 	int ret;
 
 	if (enable && !IS_ERR_OR_NULL(bci->transceiver)) {
-		/* Check for USB charger connected */
-		if (!twl4030_bci_have_vbus(bci))
-			return -ENODEV;
-
 		/*
 		 * Until we can find out what current the device can provide,
 		 * require a module param to enable USB charging.
@@ -649,7 +623,12 @@ static int __init twl4030_bci_probe(struct platform_device *pdev)
 		dev_warn(&pdev->dev, "failed to unmask interrupts: %d\n", ret);
 
 	twl4030_charger_enable_ac(true);
-	twl4030_charger_enable_usb(bci, true);
+	if (!IS_ERR_OR_NULL(bci->transceiver))
+		twl4030_bci_usb_ncb(&bci->usb_nb,
+				    bci->transceiver->last_event,
+				    NULL);
+	else
+		twl4030_charger_enable_usb(bci, false);
 	if (pdata)
 		twl4030_charger_enable_backup(pdata->bb_uvolt,
 					      pdata->bb_uamp);
author
#8 lore raw

PATCH 5/14 twl4030_charger: split uA calculation into a function.

In reply to NeilBrown

From: NeilBrown <[email protected]>

We will need this calculation in other places, so create functions to map between register value and uA value.

---
 drivers/power/twl4030_charger.c |   48 ++++++++++++++++++++++++++++-----------
 1 file changed, 35 insertions(+), 13 deletions(-)
drivers/power/twl4030_charger.c +35 -13
index 0e62a5ae56a3..c9a7efbb9141 100644
@@ -178,6 +178,40 @@ static int twl4030_is_battery_present(struct twl4030_bci *bci)
 }
 
 /*
+ * TI provided formulas:
+ * CGAIN == 0: ICHG = (BCIICHG * 1.7) / (2^10 - 1) - 0.85
+ * CGAIN == 1: ICHG = (BCIICHG * 3.4) / (2^10 - 1) - 1.7
+ * Here we use integer approximation of:
+ * CGAIN == 0: val * 1.6618 - 0.85 * 1000
+ * CGAIN == 1: (val * 1.6618 - 0.85 * 1000) * 2
+ */
+/*
+ * convert twl register value for currents into uA
+ */
+static int regval2ua(int regval, bool cgain)
+{
+	if (cgain)
+		return (regval * 16618 - 8500 * 1000) / 5;
+	else
+		return (regval * 16618 - 8500 * 1000) / 10;
+}
+
+/*
+ * convert uA currents into twl register value
+ */
+static int ua2regval(int ua, bool cgain)
+{
+	int ret;
+	if (cgain)
+		ua /= 2;
+	ret = (ua * 10 + 8500 * 1000) / 16618;
+	/* rounding problems */
+	if (ret < 512)
+		ret = 512;
+	return ret;
+}
+
+/*
  * Enable/Disable USB Charge functionality.
  */
 static int twl4030_charger_enable_usb(struct twl4030_bci *bci, bool enable)
@@ -366,14 +400,6 @@ static int twl4030_bci_usb_ncb(struct notifier_block *nb, unsigned long val,
 	return NOTIFY_OK;
 }
 
-/*
- * TI provided formulas:
- * CGAIN == 0: ICHG = (BCIICHG * 1.7) / (2^10 - 1) - 0.85
- * CGAIN == 1: ICHG = (BCIICHG * 3.4) / (2^10 - 1) - 1.7
- * Here we use integer approximation of:
- * CGAIN == 0: val * 1.6618 - 0.85
- * CGAIN == 1: (val * 1.6618 - 0.85) * 2
- */
 static int twl4030_charger_get_current(void)
 {
 	int curr;
@@ -388,11 +414,7 @@ static int twl4030_charger_get_current(void)
 	if (ret)
 		return ret;
 
-	ret = (curr * 16618 - 850 * 10000) / 10;
-	if (bcictl1 & TWL4030_CGAIN)
-		ret *= 2;
-
-	return ret;
+	return regval2ua(curr, bcictl1 & TWL4030_CGAIN);
 }
 
 /*
author
#9 lore raw

PATCH 6/14 twl4030_charger: allow fine control of charger current.

In reply to NeilBrown

From: NeilBrown <[email protected]>

The twl4030 allows control of the incoming current. Part of this control is a 'CGAIN' setting which doubles the range for half the precision. This control affects several different current setting, so all need to be updated at once when CGAIN is changed.

With this patch, all of these current setting are managed by the driver, but most are left at their default settings.

The current drawn is set to 500mA if the allow_usb module parameter is set, and to 100mA otherwise.
More fine control will appear in later patches.

---
 drivers/power/twl4030_charger.c |  168 +++++++++++++++++++++++++++++++++++++--
 1 file changed, 160 insertions(+), 8 deletions(-)
drivers/power/twl4030_charger.c +160 -8
index c9a7efbb9141..02d677ef63e2 100644
@@ -31,6 +31,11 @@
 #define TWL4030_BCIMFSTS4	0x10
 #define TWL4030_BCICTL1		0x23
 #define TWL4030_BB_CFG		0x12
+#define TWL4030_BCIIREF1	0x27
+#define TWL4030_BCIIREF2	0x28
+#define TWL4030_BCIMFKEY	0x11
+#define TWL4030_BCIMFTH8	0x1d
+#define TWL4030_BCIMFTH9	0x1e
 
 #define TWL4030_BCIMFSTS1	0x01
 
@@ -95,6 +100,12 @@ struct twl4030_bci {
 	int			irq_bci;
 	int			usb_enabled;
 
+	/*
+	 * ichg values in uA. If any are 'large', we set CGAIN to
+	 * '1' which doubles the range for half the precision.
+	 */
+	unsigned int		ichg_eoc, ichg_lo, ichg_hi, cur;
+
 	unsigned long		event;
 };
 
@@ -211,6 +222,146 @@ static int ua2regval(int ua, bool cgain)
 	return ret;
 }
 
+static int twl4030_charger_update_current(struct twl4030_bci *bci)
+{
+	int status;
+	unsigned reg, cur_reg;
+	u8 bcictl1, oldreg, fullreg;
+	bool cgain = false;
+	u8 boot_bci;
+
+	/* First, check thresholds and see if cgain is needed */
+	if (bci->ichg_eoc >= 200000)
+		cgain = true;
+	if (bci->ichg_lo >= 400000)
+		cgain = true;
+	if (bci->ichg_hi >= 820000)
+		cgain = true;
+	if (bci->cur > 852000)
+		cgain = true;
+
+	status = twl4030_bci_read(TWL4030_BCICTL1, &bcictl1);
+	if (status < 0)
+		return status;
+	if (twl_i2c_read_u8(TWL_MODULE_PM_MASTER, &boot_bci,
+			    TWL4030_PM_MASTER_BOOT_BCI) < 0)
+		boot_bci = 0;
+	boot_bci &= 7;
+
+	if ((!!cgain) != !!(bcictl1 & TWL4030_CGAIN))
+		/* Need to turn for charging while we change the
+		 * CGAIN bit.  Leave it off while everything is
+		 * updated.
+		 */
+		twl4030_clear_set_boot_bci(boot_bci, 0);
+
+	/*
+	 * For ichg_eoc, the hardware only supports reg values matching
+	 * 100XXXX000, and requires the XXXX be stored in the high nibble
+	 * of TWL4030_BCIMFTH8
+	 */
+	reg = ua2regval(bci->ichg_eoc, cgain);
+	if (reg > 0x278)
+		reg = 0x278;
+	if (reg < 0x200)
+		reg = 0x200;
+	reg = (reg >> 3) & 0xf;
+	fullreg = reg << 4;
+
+	/*
+	 * For ichg_lo, reg value must match 10XXXX0000.
+	 * XXXX is stored in low nibble of TWL4030_BCIMFTH8
+	 */
+	reg = ua2regval(bci->ichg_lo, cgain);
+	if (reg > 0x2F0)
+		reg = 0x2F0;
+	if (reg < 0x200)
+		reg = 0x200;
+	reg = (reg >> 4) & 0xf;
+	fullreg |= reg;
+
+	/* ichg_eoc and ichg_lo live in same register */
+	status = twl4030_bci_read(TWL4030_BCIMFTH8, &oldreg);
+	if (status < 0)
+		return status;
+	if (oldreg != fullreg) {
+		status = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0xF4,
+					  TWL4030_BCIMFKEY);
+		if (status < 0)
+			return status;
+		twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE,
+				 fullreg, TWL4030_BCIMFTH8);
+	}
+
+	/* ichg_hi threshold must be 1XXXX01100 (I think) */
+	reg = ua2regval(bci->ichg_hi, cgain);
+	if (reg > 0x3E0)
+		reg = 0x3E0;
+	if (reg < 0x200)
+		reg = 0x200;
+	fullreg = (reg >> 5) & 0xF;
+	fullreg <<= 4;
+	status = twl4030_bci_read(TWL4030_BCIMFTH9, &oldreg);
+	if (status < 0)
+		return status;
+	if ((oldreg & 0xF0) != fullreg) {
+		fullreg |= (oldreg & 0x0F);
+		status = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0xE7,
+					  TWL4030_BCIMFKEY);
+		if (status < 0)
+			return status;
+		twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE,
+				 fullreg, TWL4030_BCIMFTH9);
+	}
+
+	/*
+	 * And finally, set the current.  This is stored in
+	 * two registers.
+	 */
+	reg = ua2regval(bci->cur, cgain);
+	/* we have only 10 bits */
+	if (reg > 0x3ff)
+		reg = 0x3ff;
+	status = twl4030_bci_read(TWL4030_BCIIREF1, &oldreg);
+	if (status < 0)
+		return status;
+	cur_reg = oldreg;
+	status = twl4030_bci_read(TWL4030_BCIIREF2, &oldreg);
+	if (status < 0)
+		return status;
+	cur_reg |= oldreg << 8;
+	if (reg != oldreg) {
+		/* disable write protection for one write access for
+		 * BCIIREF */
+		status = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0xE7,
+					  TWL4030_BCIMFKEY);
+		if (status < 0)
+			return status;
+		status = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE,
+					  (reg & 0x100) ? 3 : 2,
+					  TWL4030_BCIIREF2);
+		if (status < 0)
+			return status;
+		/* disable write protection for one write access for
+		 * BCIIREF */
+		status = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0xE7,
+					  TWL4030_BCIMFKEY);
+		if (status < 0)
+			return status;
+		status = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE,
+					  reg & 0xff,
+					  TWL4030_BCIIREF1);
+	}
+	if ((!!cgain) != !!(bcictl1 & TWL4030_CGAIN)) {
+		/* Flip CGAIN and re-enable charging */
+		bcictl1 ^= TWL4030_CGAIN;
+		twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE,
+				 bcictl1, TWL4030_BCICTL1);
+		twl4030_clear_set_boot_bci(0, boot_bci);
+	}
+	return 0;
+}
+
 /*
  * Enable/Disable USB Charge functionality.
  */
@@ -219,14 +370,6 @@ static int twl4030_charger_enable_usb(struct twl4030_bci *bci, bool enable)
 	int ret;
 
 	if (enable && !IS_ERR_OR_NULL(bci->transceiver)) {
-		/*
-		 * Until we can find out what current the device can provide,
-		 * require a module param to enable USB charging.
-		 */
-		if (!allow_usb) {
-			dev_warn(bci->dev, "USB charging is disabled.\n");
-			return -EACCES;
-		}
 
 		/* Need to keep phy powered */
 		if (!bci->usb_enabled) {
@@ -562,6 +705,14 @@ static int __init twl4030_bci_probe(struct platform_device *pdev)
 	if (!pdata)
 		pdata = twl4030_bci_parse_dt(&pdev->dev);
 
+	bci->ichg_eoc = 80100; /* Stop charging when current drops to here */
+	bci->ichg_lo = 241000; /* Low threshold */
+	bci->ichg_hi = 500000; /* High threshold */
+	if (allow_usb)
+		bci->cur = 500000;  /* 500mA */
+	else
+		bci->cur = 100000;  /* 100mA */
+
 	bci->dev = &pdev->dev;
 	bci->irq_chg = platform_get_irq(pdev, 0);
 	bci->irq_bci = platform_get_irq(pdev, 1);
@@ -644,6 +795,7 @@ static int __init twl4030_bci_probe(struct platform_device *pdev)
 	if (ret < 0)
 		dev_warn(&pdev->dev, "failed to unmask interrupts: %d\n", ret);
 
+	twl4030_charger_update_current(bci);
 	twl4030_charger_enable_ac(true);
 	if (!IS_ERR_OR_NULL(bci->transceiver))
 		twl4030_bci_usb_ncb(&bci->usb_nb,
author
#10 lore raw

PATCH 7/14 twl4030_charger: distinguish between USB current and 'AC' current

In reply to NeilBrown

From: NeilBrown <[email protected]>

The twl4030 charger has two current sources, 'USB' and 'AC' (presumably "Accessory Charger").

If 'AC' is providing current, we should set the current limit differently to when it isn't (and so USB is used).
So split 'cur' into 'usb_cur' and 'ac_cur' and use accordingly.

Now we must review the current setting on any interrupt or USB event which might indicate that the charger-source has changed.

---
 drivers/power/twl4030_charger.c |   36 +++++++++++++++++++++++++++++-------
 1 file changed, 29 insertions(+), 7 deletions(-)
drivers/power/twl4030_charger.c +29 -7
index 02d677ef63e2..37f2f40991ee 100644
@@ -22,6 +22,7 @@
 #include <linux/power_supply.h>
 #include <linux/notifier.h>
 #include <linux/usb/otg.h>
+#include <linux/i2c/twl4030-madc.h>
 
 #define TWL4030_BCIMSTATEC	0x02
 #define TWL4030_BCIICHG		0x08
@@ -101,10 +102,13 @@ struct twl4030_bci {
 	int			usb_enabled;
 
 	/*
-	 * ichg values in uA. If any are 'large', we set CGAIN to
-	 * '1' which doubles the range for half the precision.
+	 * ichg_* and *_cur values in uA. If any are 'large', we set
+	 * CGAIN to '1' which doubles the range for half the
+	 * precision.
 	 */
-	unsigned int		ichg_eoc, ichg_lo, ichg_hi, cur;
+	unsigned int		ichg_eoc, ichg_lo, ichg_hi;
+	unsigned int		usb_cur, ac_cur;
+	bool			ac_is_active;
 
 	unsigned long		event;
 };
@@ -225,11 +229,24 @@ static int ua2regval(int ua, bool cgain)
 static int twl4030_charger_update_current(struct twl4030_bci *bci)
 {
 	int status;
+	int cur;
 	unsigned reg, cur_reg;
 	u8 bcictl1, oldreg, fullreg;
 	bool cgain = false;
 	u8 boot_bci;
 
+	/*
+	 * If AC (Accessory Charger) voltage exceeds 4.5V (MADC 11)
+	 * and AC is enabled, set current for 'ac'
+	 */
+	if (twl4030_get_madc_conversion(11) > 4500) {
+		cur = bci->ac_cur;
+		bci->ac_is_active = true;
+	} else {
+		cur = bci->usb_cur;
+		bci->ac_is_active = false;
+	}
+
 	/* First, check thresholds and see if cgain is needed */
 	if (bci->ichg_eoc >= 200000)
 		cgain = true;
@@ -237,7 +254,7 @@ static int twl4030_charger_update_current(struct twl4030_bci *bci)
 		cgain = true;
 	if (bci->ichg_hi >= 820000)
 		cgain = true;
-	if (bci->cur > 852000)
+	if (cur > 852000)
 		cgain = true;
 
 	status = twl4030_bci_read(TWL4030_BCICTL1, &bcictl1);
@@ -318,7 +335,7 @@ static int twl4030_charger_update_current(struct twl4030_bci *bci)
 	 * And finally, set the current.  This is stored in
 	 * two registers.
 	 */
-	reg = ua2regval(bci->cur, cgain);
+	reg = ua2regval(cur, cgain);
 	/* we have only 10 bits */
 	if (reg > 0x3ff)
 		reg = 0x3ff;
@@ -371,6 +388,8 @@ static int twl4030_charger_enable_usb(struct twl4030_bci *bci, bool enable)
 
 	if (enable && !IS_ERR_OR_NULL(bci->transceiver)) {
 
+		twl4030_charger_update_current(bci);
+
 		/* Need to keep phy powered */
 		if (!bci->usb_enabled) {
 			pm_runtime_get_sync(bci->transceiver->dev);
@@ -463,6 +482,7 @@ static irqreturn_t twl4030_charger_interrupt(int irq, void *arg)
 	struct twl4030_bci *bci = arg;
 
 	dev_dbg(bci->dev, "CHG_PRES irq\n");
+	twl4030_charger_update_current(bci);
 	power_supply_changed(&bci->ac);
 	power_supply_changed(&bci->usb);
 
@@ -495,6 +515,7 @@ static irqreturn_t twl4030_bci_interrupt(int irq, void *arg)
 		power_supply_changed(&bci->ac);
 		power_supply_changed(&bci->usb);
 	}
+	twl4030_charger_update_current(bci);
 
 	/* various monitoring events, for now we just log them here */
 	if (irqs1 & (TWL4030_TBATOR2 | TWL4030_TBATOR1))
@@ -708,10 +729,11 @@ static int __init twl4030_bci_probe(struct platform_device *pdev)
 	bci->ichg_eoc = 80100; /* Stop charging when current drops to here */
 	bci->ichg_lo = 241000; /* Low threshold */
 	bci->ichg_hi = 500000; /* High threshold */
+	bci->ac_cur = 500000; /* 500mA */
 	if (allow_usb)
-		bci->cur = 500000;  /* 500mA */
+		bci->usb_cur = 500000;  /* 500mA */
 	else
-		bci->cur = 100000;  /* 100mA */
+		bci->usb_cur = 100000;  /* 100mA */
 
 	bci->dev = &pdev->dev;
 	bci->irq_chg = platform_get_irq(pdev, 0);
author
#11 lore raw

PATCH 8/14 twl4030_charger: allow max_current to be managed via sysfs.

In reply to NeilBrown

From: NeilBrown <[email protected]>

'max_current' sysfs attributes are created which allow the max to be set.
Whenever a current source changes, the default is restored. This will be followed by a uevent, so user-space can decide to update again.

---
 .../ABI/testing/sysfs-class-power-twl4030          |   15 ++++
 drivers/power/twl4030_charger.c                    |   72 ++++++++++++++++++++
 2 files changed, 87 insertions(+)
 create mode 100644 Documentation/ABI/testing/sysfs-class-power-twl4030
Documentation/ABI/testing/sysfs-class-power-twl4030 new file +15 -0
new file mode 100644
index 000000000000..06092209d851
@@ -0,0 +1,15 @@
+What: /sys/class/power_supply/twl4030_ac/max_current
+      /sys/class/power_supply/twl4030_usb/max_current
+Description:
+	Read/Write limit on current which which may
+	be drawn from the ac (Accessory Charger) or
+	USB port.
+
+	Value is in micro-Amps.
+
+	Value is set automatically to an appropriate
+	value when a cable is plugged on unplugged.
+
+	Value can the set by writing to the attribute.
+	The change will only persist until the next
+	plug event.  These event are reported via udev.
drivers/power/twl4030_charger.c +72 -0

102 lines not on this page · raw message

#12 lore raw

Re: [Gta04-owner] [PATCH 08/14] twl4030_charger: allow max_current to be managed via sysfs.

Hello Neil,

some suggestions:

Documentation/ABI/testing/sysfs-class-power-twl4030 line 4

Quoted 12 lines · NeilBrown

From: NeilBrown <[email protected]>

Documentation/ABI/testing/sysfs-class-power-twl4030 new file +4 -0
new file mode 100644
index 000000000000..06092209d851
@@ -0,0 +1,15 @@
+What: /sys/class/power_supply/twl4030_ac/max_current
+      /sys/class/power_supply/twl4030_usb/max_current
+Description:
+	Read/Write limit on current which which may

one less which ^^

Documentation/ABI/testing/sysfs-class-power-twl4030 line 11

Quoted 7 lines
Documentation/ABI/testing/sysfs-class-power-twl4030 new file +7 -0
+	be drawn from the ac (Accessory Charger) or
+	USB port.
+
+	Value is in micro-Amps.
+
+	Value is set automatically to an appropriate
+	value when a cable is plugged on unplugged.

s/on/or ^^

Documentation/ABI/testing/sysfs-class-power-twl4030 line 13

Quoted 2 lines
Documentation/ABI/testing/sysfs-class-power-twl4030 new file +2 -0
+
+	Value can the set by writing to the attribute.
                  ^^ be set?

Documentation/ABI/testing/sysfs-class-power-twl4030 line 15

Quoted 2 lines
Documentation/ABI/testing/sysfs-class-power-twl4030 new file +2 -0
+	The change will only persist until the next
+	plug event.  These event are reported via udev.

Regards
Jake

author
#13 lore raw

Re: [Gta04-owner] [PATCH 08/14] twl4030_charger: allow max_current to be managed via sysfs.

Quoted 26 lines · On Mon, 23 Mar 2015 13:14:50 +0100 jake42 <[email protected]>…

Hello Neil,

some suggestions:

Documentation/ABI/testing/sysfs-class-power-twl4030 line 4

Quoted 12 lines · NeilBrown

From: NeilBrown <[email protected]>

Documentation/ABI/testing/sysfs-class-power-twl4030 new file +4 -0
new file mode 100644
index 000000000000..06092209d851
@@ -0,0 +1,15 @@
+What: /sys/class/power_supply/twl4030_ac/max_current
+      /sys/class/power_supply/twl4030_usb/max_current
+Description:
+	Read/Write limit on current which which may

one less which ^^

Documentation/ABI/testing/sysfs-class-power-twl4030 line 11

Quoted 7 lines
Documentation/ABI/testing/sysfs-class-power-twl4030 new file +7 -0
+	be drawn from the ac (Accessory Charger) or
+	USB port.
+
+	Value is in micro-Amps.
+
+	Value is set automatically to an appropriate
+	value when a cable is plugged on unplugged.

s/on/or ^^

Quoted 9 lines

Documentation/ABI/testing/sysfs-class-power-twl4030 line 13

Quoted 2 lines
Documentation/ABI/testing/sysfs-class-power-twl4030 new file +2 -0
+
+	Value can the set by writing to the attribute.
                   ^^ be set?

Documentation/ABI/testing/sysfs-class-power-twl4030 line 15

Quoted 2 lines
Documentation/ABI/testing/sysfs-class-power-twl4030 new file +2 -0
+	The change will only persist until the next
+	plug event.  These event are reported via udev.

Regards
Jake

Thanks.
I've made those two changes.

NeilBrown

author
#14 lore raw

PATCH 9/14 twl4030_charger: only draw USB current as negotiated with host.

In reply to NeilBrown

From: NeilBrown <[email protected]>

If the phy has been told what current it can draw, it tells us and now we use that number.

Note that 'vbus_draw' is in mA, while usb_cur is in uA.

---
 drivers/power/twl4030_charger.c |    5 +++++
 1 file changed, 5 insertions(+)
drivers/power/twl4030_charger.c +5 -0
index df031b0123d0..2042e7619954 100644
@@ -602,6 +602,7 @@ static void twl4030_bci_usb_work(struct work_struct *data)
 	switch (bci->event) {
 	case USB_EVENT_VBUS:
 	case USB_EVENT_CHARGER:
+	case USB_EVENT_ENUMERATED:
 		twl4030_charger_enable_usb(bci, true);
 		break;
 	case USB_EVENT_NONE:
@@ -614,6 +615,7 @@ static int twl4030_bci_usb_ncb(struct notifier_block *nb, unsigned long val,
 			       void *priv)
 {
 	struct twl4030_bci *bci = container_of(nb, struct twl4030_bci, usb_nb);
+	unsigned int *vbus_draw = priv;
 
 	dev_dbg(bci->dev, "OTG notify %lu\n", val);
 
@@ -624,6 +626,9 @@ static int twl4030_bci_usb_ncb(struct notifier_block *nb, unsigned long val,
 		bci->usb_cur = 100000;
 
 	bci->event = val;
+	if (val == USB_EVENT_ENUMERATED && vbus_draw &&
+	    *vbus_draw * 1000 > bci->usb_cur)
+		bci->usb_cur = *vbus_draw * 1000;
 	schedule_work(&bci->work);
 
 	return NOTIFY_OK;
author
#15 lore raw

PATCH 10/14 twl4030_charger: enable manual enable/disable of usb charging.

In reply to NeilBrown

From: NeilBrown <[email protected]>

'off' or 'auto' to

/sys/class/power/twl4030_usb/mode

will now enable or disable charging from USB port. Normally this is enabled on 'plug' and disabled on 'unplug'.
Unplug will still disable charging. 'plug' will only enable it if 'auto' if selected.

---
 .../ABI/testing/sysfs-class-power-twl4030          |   11 ++++
 drivers/power/twl4030_charger.c                    |   59 ++++++++++++++++++++
 2 files changed, 70 insertions(+)
Documentation/ABI/testing/sysfs-class-power-twl4030 +11 -0
index 06092209d851..390db7a55231 100644
@@ -13,3 +13,14 @@ Description:
 	Value can the set by writing to the attribute.
 	The change will only persist until the next
 	plug event.  These event are reported via udev.
+
+
+What: /sys/class/power_supply/twl4030_usb/mode
+Description:
+	Changing mode for USB port.
+	Writing to this can disable charging.
+
+	Possible values are:
+		"auto" - draw power as appropriate for detected
+			 power source and battery status.
+		"off"  - do not draw any power.
drivers/power/twl4030_charger.c +59 -0

99 lines not on this page · raw message

author
#16 lore raw

PATCH 11/14 twl4030_charger: add software controlled linear charging mode.

In reply to NeilBrown

From: NeilBrown <[email protected]>

Add a 'continuous' option for usb charging which enables the "linear" charging mode of the twl4030.

Linear charging does a good job with not-so-reliable power sources. Auto mode does not work well as it switches off when voltage drops momentarily. Care must be taken not to over-charge.

It was used with a bike hub dynamo since a year or so. In that case there are automatically charging stops when the cyclist needs a break.

---
 .../ABI/testing/sysfs-class-power-twl4030          |    9 +++
 drivers/power/twl4030_charger.c                    |   55 ++++++++++++++++++--
 2 files changed, 59 insertions(+), 5 deletions(-)
Documentation/ABI/testing/sysfs-class-power-twl4030 +9 -0
index 390db7a55231..c27e8ca8c9db 100644
@@ -24,3 +24,12 @@ Description:
 		"auto" - draw power as appropriate for detected
 			 power source and battery status.
 		"off"  - do not draw any power.
+		"continuous"
+		       - activate mode described as "linear" in
+		         TWL data sheets.  This uses whatever
+			 current is available and doesn't switch off
+			 when voltage drops.
+
+			 This is useful for unstable power sources
+			 such as bicycle dynamo, but care should
+			 be taken that battery is not over-charged.
drivers/power/twl4030_charger.c +50 -5

109 lines not on this page · raw message

author
#17 lore raw

PATCH 12/14 twl4030_charger: add ac/mode to match usb/mode

In reply to NeilBrown

From: NeilBrown <[email protected]>

This allows AC charging to be turned off, much like usb charging. "continuous" mode is not available though.

---
 .../ABI/testing/sysfs-class-power-twl4030          |   10 ++++++
 drivers/power/twl4030_charger.c                    |   35 +++++++++++++++-----
 2 files changed, 37 insertions(+), 8 deletions(-)
Documentation/ABI/testing/sysfs-class-power-twl4030 +10 -0
index c27e8ca8c9db..35a211b9a172 100644
@@ -33,3 +33,13 @@ Description:
 			 This is useful for unstable power sources
 			 such as bicycle dynamo, but care should
 			 be taken that battery is not over-charged.
+
+What: /sys/class/power_supply/twl4030_ac/mode
+Description:
+	Changing mode for 'ac' port.
+	Writing to this can disable charging.
+
+	Possible values are:
+		"auto" - draw power as appropriate for detected
+			 power source and battery status.
+		"off"  - do not draw any power.
drivers/power/twl4030_charger.c +27 -8
index 2c8d85102def..73c7cd96ebc1 100644
@@ -114,7 +114,7 @@ struct twl4030_bci {
 	unsigned int		ichg_eoc, ichg_lo, ichg_hi;
 	unsigned int		usb_cur, ac_cur;
 	bool			ac_is_active;
-	int			usb_mode; /* charging mode requested */
+	int			usb_mode, ac_mode; /* charging mode requested */
 #define	CHARGE_OFF	0
 #define	CHARGE_AUTO	1
 #define	CHARGE_LINEAR	2
@@ -459,10 +459,13 @@ static int twl4030_charger_enable_usb(struct twl4030_bci *bci, bool enable)
 /*
  * Enable/Disable AC Charge funtionality.
  */
-static int twl4030_charger_enable_ac(bool enable)
+static int twl4030_charger_enable_ac(struct twl4030_bci *bci, bool enable)
 {
 	int ret;
 
+	if (bci->ac_mode == CHARGE_OFF)
+		enable = false;
+
 	if (enable)
 		ret = twl4030_clear_set_boot_bci(0, TWL4030_BCIAUTOAC);
 	else
@@ -693,9 +696,17 @@ twl4030_bci_mode_store(struct device *dev, struct device_attribute *attr,
 		mode = 2;
 	else
 		return -EINVAL;
-	twl4030_charger_enable_usb(bci, false);
-	bci->usb_mode = mode;
-	status = twl4030_charger_enable_usb(bci, true);
+	if (dev == bci->ac.dev) {
+		if (mode == 2)
+			return -EINVAL;
+		twl4030_charger_enable_ac(bci, false);
+		bci->ac_mode = mode;
+		status = twl4030_charger_enable_ac(bci, true);
+	} else {
+		twl4030_charger_enable_usb(bci, false);
+		bci->usb_mode = mode;
+		status = twl4030_charger_enable_usb(bci, true);
+	}
 	return (status == 0) ? n : status;
 }
 
@@ -709,9 +720,13 @@ twl4030_bci_mode_show(struct device *dev,
 	struct twl4030_bci *bci = dev_get_drvdata(dev->parent);
 	int len = 0;
 	int i;
+	int mode = bci->usb_mode;
+
+	if (dev == bci->ac.dev)
+		mode = bci->ac_mode;
 
 	for (i = 0; i < ARRAY_SIZE(modes); i++)
-		if (bci->usb_mode == i)
+		if (mode == i)
 			len += snprintf(buf+len, PAGE_SIZE-len,
 					"[%s] ", modes[i]);
 		else
@@ -905,6 +920,7 @@ static int __init twl4030_bci_probe(struct platform_device *pdev)
 	else
 		bci->usb_cur = 100000;  /* 100mA */
 	bci->usb_mode = CHARGE_AUTO;
+	bci->ac_mode = CHARGE_AUTO;
 
 	bci->dev = &pdev->dev;
 	bci->irq_chg = platform_get_irq(pdev, 0);
@@ -993,10 +1009,12 @@ static int __init twl4030_bci_probe(struct platform_device *pdev)
 		dev_warn(&pdev->dev, "could not create sysfs file\n");
 	if (device_create_file(bci->usb.dev, &dev_attr_mode))
 		dev_warn(&pdev->dev, "could not create sysfs file\n");
+	if (device_create_file(bci->ac.dev, &dev_attr_mode))
+		dev_warn(&pdev->dev, "could not create sysfs file\n");
 	if (device_create_file(bci->ac.dev, &dev_attr_max_current))
 		dev_warn(&pdev->dev, "could not create sysfs file\n");
 
-	twl4030_charger_enable_ac(true);
+	twl4030_charger_enable_ac(bci, true);
 	if (!IS_ERR_OR_NULL(bci->transceiver))
 		twl4030_bci_usb_ncb(&bci->usb_nb,
 				    bci->transceiver->last_event,
@@ -1016,13 +1034,14 @@ static int __exit twl4030_bci_remove(struct platform_device *pdev)
 {
 	struct twl4030_bci *bci = platform_get_drvdata(pdev);
 
-	twl4030_charger_enable_ac(false);
+	twl4030_charger_enable_ac(bci, false);
 	twl4030_charger_enable_usb(bci, false);
 	twl4030_charger_enable_backup(0, 0);
 
 	device_remove_file(bci->usb.dev, &dev_attr_max_current);
 	device_remove_file(bci->usb.dev, &dev_attr_mode);
 	device_remove_file(bci->ac.dev, &dev_attr_max_current);
+	device_remove_file(bci->ac.dev, &dev_attr_mode);
 	/* mask interrupts */
 	twl_i2c_write_u8(TWL4030_MODULE_INTERRUPTS, 0xff,
 			 TWL4030_INTERRUPTS_BCIIMR1A);
author
#18 lore raw

PATCH 13/14 twl4030_charger: Increase current carefully while watching voltage.

In reply to NeilBrown

From: NeilBrown <[email protected]>

The USB Battery Charging spec (BC1.2) suggests a dedicated charging port can deliver from 0.5 to 5.0A at between 4.75 and 5.25 volts.

To choose the "correct" current voltage setting requires a trial and error approach: try to draw current and see if the voltage drops too low.

Even with a configured Standard Downstream Port, it may not be possible to reliably pull 500mA - depending on cable quality and source quality I have reports of charging failure due to the voltage dropping too low.

To address both these concerns, this patch introduce incremental current setting.
The current pull from VBUS is increased in steps of 20mA every 100ms until the target is reached or until the measure voltage drops below 4.75V. If the voltage does go too low, the target current is reduced by 20mA and kept there.

This applies to currents selected automatically, or to values set via sysfs. So setting a large value will cause the maximum available to be used - up to the limit of 1.7A imposed by the hardware.

---
 drivers/power/twl4030_charger.c |   68 ++++++++++++++++++++++++++++++++++-----
 1 file changed, 60 insertions(+), 8 deletions(-)
drivers/power/twl4030_charger.c +60 -8
index 73c7cd96ebc1..d0199495dddc 100644
@@ -119,6 +119,18 @@ struct twl4030_bci {
 #define	CHARGE_AUTO	1
 #define	CHARGE_LINEAR	2
 
+	/* When setting the USB current we slowly increase the
+	 * requested current until target is reached or the voltage
+	 * drops below 4.75V.  In the latter case we step back one
+	 * step.
+	 */
+	unsigned int		usb_cur_target;
+	struct delayed_work	current_worker;
+#define	USB_CUR_STEP	20000	/* 20mA at a time */
+#define	USB_MIN_VOLT	4750000	/* 4.75V */
+#define	USB_CUR_DELAY	msecs_to_jiffies(100)
+#define	USB_MAX_CURRENT	1700000 /* TWL4030 caps at 1.7A */
+
 	unsigned long		event;
 };
 
@@ -257,6 +269,12 @@ static int twl4030_charger_update_current(struct twl4030_bci *bci)
 	} else {
 		cur = bci->usb_cur;
 		bci->ac_is_active = false;
+		if (cur > bci->usb_cur_target) {
+			cur = bci->usb_cur_target;
+			bci->usb_cur = cur;
+		}
+		if (cur < bci->usb_cur_target)
+			schedule_delayed_work(&bci->current_worker, USB_CUR_DELAY);
 	}
 
 	/* First, check thresholds and see if cgain is needed */
@@ -391,6 +409,38 @@ static int twl4030_charger_update_current(struct twl4030_bci *bci)
 	return 0;
 }
 
+static void twl4030_current_worker(struct work_struct *data)
+{
+	int v;
+	int res;
+	struct twl4030_bci *bci = container_of(data, struct twl4030_bci,
+					       current_worker.work);
+
+	res = twl4030bci_read_adc_val(TWL4030_BCIVBUS);
+	if (res < 0)
+		v = 0;
+	else
+		/* BCIVBUS uses ADCIN8, 7/1023 V/step */
+		v = res * 6843;
+
+	printk("v=%d cur=%d target=%d\n", v, bci->usb_cur,
+	       bci->usb_cur_target);
+
+	if (v < USB_MIN_VOLT) {
+		/* Back up and stop adjusting. */
+		bci->usb_cur -= USB_CUR_STEP;
+		bci->usb_cur_target = bci->usb_cur;
+	} else if (bci->usb_cur >= bci->usb_cur_target ||
+		   bci->usb_cur + USB_CUR_STEP > USB_MAX_CURRENT) {
+		/* Reached target and voltage is OK - stop */
+		return;
+	} else {
+		bci->usb_cur += USB_CUR_STEP;
+		schedule_delayed_work(&bci->current_worker, USB_CUR_DELAY);
+	}
+	twl4030_charger_update_current(bci);
+}
+
 /*
  * Enable/Disable USB Charge functionality.
  */
@@ -451,6 +501,7 @@ static int twl4030_charger_enable_usb(struct twl4030_bci *bci, bool enable)
 			pm_runtime_put_autosuspend(bci->transceiver->dev);
 			bci->usb_enabled = 0;
 		}
+		bci->usb_cur = 0;
 	}
 
 	return ret;
@@ -599,7 +650,7 @@ twl4030_bci_max_current_store(struct device *dev, struct device_attribute *attr,
 	if (dev == bci->ac.dev)
 		bci->ac_cur = cur;
 	else
-		bci->usb_cur = cur;
+		bci->usb_cur_target = cur;
 
 	twl4030_charger_update_current(bci);
 	return n;
@@ -621,7 +672,7 @@ static ssize_t twl4030_bci_max_current_show(struct device *dev,
 			cur = bci->ac_cur;
 	} else {
 		if (bci->ac_is_active)
-			cur = bci->usb_cur;
+			cur = bci->usb_cur_target;
 	}
 	if (cur < 0) {
 		cur = twl4030bci_read_adc_val(TWL4030_BCIIREF1);
@@ -664,14 +715,14 @@ static int twl4030_bci_usb_ncb(struct notifier_block *nb, unsigned long val,
 
 	/* reset current on each 'plug' event */
 	if (allow_usb)
-		bci->usb_cur = 500000;
+		bci->usb_cur_target = 500000;
 	else
-		bci->usb_cur = 100000;
+		bci->usb_cur_target = 100000;
 
 	bci->event = val;
 	if (val == USB_EVENT_ENUMERATED && vbus_draw &&
-	    *vbus_draw * 1000 > bci->usb_cur)
-		bci->usb_cur = *vbus_draw * 1000;
+	    *vbus_draw * 1000 > bci->usb_cur_target)
+		bci->usb_cur_target = *vbus_draw * 1000;
 	schedule_work(&bci->work);
 
 	return NOTIFY_OK;
@@ -916,9 +967,9 @@ static int __init twl4030_bci_probe(struct platform_device *pdev)
 	bci->ichg_hi = 500000; /* High threshold */
 	bci->ac_cur = 500000; /* 500mA */
 	if (allow_usb)
-		bci->usb_cur = 500000;  /* 500mA */
+		bci->usb_cur_target = 500000;  /* 500mA */
 	else
-		bci->usb_cur = 100000;  /* 100mA */
+		bci->usb_cur_target = 100000;  /* 100mA */
 	bci->usb_mode = CHARGE_AUTO;
 	bci->ac_mode = CHARGE_AUTO;
 
@@ -976,6 +1027,7 @@ static int __init twl4030_bci_probe(struct platform_device *pdev)
 	}
 
 	INIT_WORK(&bci->work, twl4030_bci_usb_work);
+	INIT_DELAYED_WORK(&bci->current_worker, twl4030_current_worker);
 
 	bci->usb_nb.notifier_call = twl4030_bci_usb_ncb;
 	if (bci->dev->of_node) {
#19 lore raw

Re: [PATCH 13/14] twl4030_charger: Increase current carefully while watching voltage.

Hi!

Quoted 13 lines

The USB Battery Charging spec (BC1.2) suggests a dedicated charging port can deliver from 0.5 to 5.0A at between 4.75 and 5.25 volts.

To choose the "correct" current voltage setting requires a trial and error approach: try to draw current and see if the voltage drops too low.

Even with a configured Standard Downstream Port, it may not be possible to reliably pull 500mA - depending on cable quality and source quality I have reports of charging failure due to the voltage dropping too low.

Quoted 6 lines

To address both these concerns, this patch introduce incremental current setting.
The current pull from VBUS is increased in steps of 20mA every 100ms until the target is reached or until the measure voltage drops below 4.75V. If the voltage does go too low, the target current is reduced by 20mA and kept there.

Still nervous. If it is possible to overheat the charger, without tripping internal fuse, then you'll do it.

drivers/power/twl4030_charger.c static int twl4030_charger_update_current(struct twl4030_bci *bci) · line 427

Quoted 8 lines

This applies to currents selected automatically, or to values set via sysfs. So setting a large value will cause the maximum available to be used - up to the limit of 1.7A imposed by the hardware.

drivers/power/twl4030_charger.c +2 -0
+	printk("v=%d cur=%d target=%d\n", v, bci->usb_cur,
+	       bci->usb_cur_target);

dev_info() and a bit better message, or drop it for production?

drivers/power/twl4030_charger.c static int twl4030_charger_update_current(struct twl4030_bci *bci) · line 432

Quoted 4 lines
drivers/power/twl4030_charger.c +4 -0
+	if (v < USB_MIN_VOLT) {
+		/* Back up and stop adjusting. */
+		bci->usb_cur -= USB_CUR_STEP;
+		bci->usb_cur_target = bci->usb_cur;

More importantly.... how does it work with device drawing power for operation, too?

Imagine device need 500mA with wifi hotspot, nearly nothing while idle.

Idle device. Code will find that it can charge using 1A, backs up to 0.9A. User starts hotspot. Now device will draw 1.4A, overloading the charger and not charging at all...?

Best regards,
Pavel

Signature · 2 lines
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html
#20 lore raw

Re: [PATCH 13/14] twl4030_charger: Increase current carefully while watching voltage.

Quoted 22 lines · On Mon, 23 Mar 2015 22:25:41 +0100 Pavel Machek…

Hi!

Quoted 13 lines

The USB Battery Charging spec (BC1.2) suggests a dedicated charging port can deliver from 0.5 to 5.0A at between 4.75 and 5.25 volts.

To choose the "correct" current voltage setting requires a trial and error approach: try to draw current and see if the voltage drops too low.

Even with a configured Standard Downstream Port, it may not be possible to reliably pull 500mA - depending on cable quality and source quality I have reports of charging failure due to the voltage dropping too low.

Quoted 6 lines

To address both these concerns, this patch introduce incremental current setting.
The current pull from VBUS is increased in steps of 20mA every 100ms until the target is reached or until the measure voltage drops below 4.75V. If the voltage does go too low, the target current is reduced by 20mA and kept there.

Quoted 2 lines

Still nervous. If it is possible to overheat the charger, without tripping internal fuse, then you'll do it.

If it is possible to overheat the charger without tripping an internal fuse, then sure the charger is mis-designed - is it not?

Can you suggest an algorithm for determining how much current can safely be pulled from a charger that would *not* make you nervous?

Quoted 11 lines

drivers/power/twl4030_charger.c static int twl4030_charger_update_current(struct twl4030_bci *bci) · line 427

Quoted 9 lines

This applies to currents selected automatically, or to values set via sysfs. So setting a large value will cause the maximum available to be used - up to the limit of 1.7A imposed by the hardware.

drivers/power/twl4030_charger.c +2 -0
+	printk("v=%d cur=%d target=%d\n", v, bci->usb_cur,
+	       bci->usb_cur_target);

dev_info() and a bit better message, or drop it for production?

Changed to dev_dbg() - thanks.

Quoted 9 lines

drivers/power/twl4030_charger.c static int twl4030_charger_update_current(struct twl4030_bci *bci) · line 432

Quoted 5 lines
drivers/power/twl4030_charger.c +4 -0
+	if (v < USB_MIN_VOLT) {
+		/* Back up and stop adjusting. */
+		bci->usb_cur -= USB_CUR_STEP;
+		bci->usb_cur_target = bci->usb_cur;

More importantly.... how does it work with device drawing power for operation, too?

Quoted 5 lines

Imagine device need 500mA with wifi hotspot, nearly nothing while idle.

Idle device. Code will find that it can charge using 1A, backs up to 0.9A. User starts hotspot. Now device will draw 1.4A, overloading the charger and not charging at all...?

The current being measured and controlled is the current flowing in from the USB VBUS, not flowing out to the battery.
So I the code choose 0.9A, that is all that will be drawn.

This is a possible issue similar to this though.
If the device is idle and the battery is fully charged, then it won't draw much current from USB even if we allow it too.
So the algorithm might decide it is OK to draw 1.7A because at that time the device cannot use more than 200mA, and that doesn't cause the voltage to drop.

Then later when user enabled wifi-hotspot, the current needed might go up above what the charger can provide.

Maybe I should only increase the limit while the actual current is also increasing. Maybe also revisit the setting when the battery starts charging.

NeilBrown

Quoted 3 lines

Best regards,
Pavel

#21 lore raw

Re: [PATCH 13/14] twl4030_charger: Increase current carefully while watching voltage.

Hi!

Quoted 20 lines
Quoted 20 lines
Quoted 19 lines

The USB Battery Charging spec (BC1.2) suggests a dedicated charging port can deliver from 0.5 to 5.0A at between 4.75 and 5.25 volts.

To choose the "correct" current voltage setting requires a trial and error approach: try to draw current and see if the voltage drops too low.

Even with a configured Standard Downstream Port, it may not be possible to reliably pull 500mA - depending on cable quality and source quality I have reports of charging failure due to the voltage dropping too low.

To address both these concerns, this patch introduce incremental current setting.
The current pull from VBUS is increased in steps of 20mA every 100ms until the target is reached or until the measure voltage drops below 4.75V. If the voltage does go too low, the target current is reduced by 20mA and kept there.

Quoted 8 lines
Quoted 2 lines

Still nervous. If it is possible to overheat the charger, without tripping internal fuse, then you'll do it.

If it is possible to overheat the charger without tripping an internal fuse, then sure the charger is mis-designed - is it not?

Can you suggest an algorithm for determining how much current can safely be pulled from a charger that would *not* make you nervous?

Not nervous? No.

Less nervous?

Run detection as you do, but then round down to "known reasonable" max charging currents?

If you detected 1.1A charger, you are probably overloading 1A charger. So idea would be to round down to 0.5A, 1A, 1.7A.

Quoted 14 lines
Quoted 3 lines

Idle device. Code will find that it can charge using 1A, backs up to 0.9A. User starts hotspot. Now device will draw 1.4A, overloading the charger and not charging at all...?

The current being measured and controlled is the current flowing in from the USB VBUS, not flowing out to the battery.
So I the code choose 0.9A, that is all that will be drawn.

This is a possible issue similar to this though.
If the device is idle and the battery is fully charged, then it won't draw much current from USB even if we allow it too.
So the algorithm might decide it is OK to draw 1.7A because at that time the device cannot use more than 200mA, and that doesn't cause the voltage to drop.

Quoted 5 lines

Then later when user enabled wifi-hotspot, the current needed might go up above what the charger can provide.

Maybe I should only increase the limit while the actual current is also increasing. Maybe also revisit the setting when the battery starts charging.

Yes, that sounds better.
Pavel

Signature · 2 lines
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html
author
#22 lore raw

PATCH 14/14 twl4030_charger: assume a 'charger' can supply maximum current.

In reply to NeilBrown

From: NeilBrown <[email protected]>

If it cannot, we will stop pulling more current when voltage drops.

---
 drivers/power/twl4030_charger.c |    4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)
drivers/power/twl4030_charger.c +3 -1
index d0199495dddc..6f0dc971d84d 100644
@@ -694,8 +694,10 @@ static void twl4030_bci_usb_work(struct work_struct *data)
 	struct twl4030_bci *bci = container_of(data, struct twl4030_bci, work);
 
 	switch (bci->event) {
-	case USB_EVENT_VBUS:
 	case USB_EVENT_CHARGER:
+		bci->usb_cur = USB_MAX_CURRENT;
+		/* FALL THROUGH */
+	case USB_EVENT_VBUS:
 	case USB_EVENT_ENUMERATED:
 		twl4030_charger_enable_usb(bci, true);
 		break;
#23 lore raw
In reply to NeilBrown

Hi Neil,

Quoted 3 lines · NeilBrown

here is V2 of my patches to improve the twl4030 charger. They include improved documentation and removal of changes to the CVENAC bit which I didn't properly understand.

I rebased and queued patches 1 and 2. Can you rebase the other patches and take care of the remaining comments?

-- Sebastian