diff mbox

[v2,2/4] power: add axp20x-battery driver

Message ID 20161009062714.5085-2-icenowy@aosc.xyz
State New
Headers show

Commit Message

Icenowy Zheng Oct. 9, 2016, 6:27 a.m. UTC
This driver is the battery power supply driver of axp20x PMIC.

This driver currently contains only capacity and current properties (for
axp20x only capacity is supported now) , however it's easy to implement more
properties.

Signed-off-by: Icenowy Zheng <icenowy@aosc.xyz>
---
Changes since v1:
- Add initial support for AXP20x.

 drivers/mfd/axp20x.c                  |  28 ++++
 drivers/power/supply/Makefile         |   1 +
 drivers/power/supply/axp20x_battery.c | 277 ++++++++++++++++++++++++++++++++++
 3 files changed, 306 insertions(+)
 create mode 100644 drivers/power/supply/axp20x_battery.c

Comments

Maxime Ripard Oct. 10, 2016, 12:57 p.m. UTC | #1
Hi Icenowy,

On Sun, Oct 09, 2016 at 02:27:12PM +0800, Icenowy Zheng wrote:
> +	case POWER_SUPPLY_PROP_CURRENT_NOW:
> +		ret = regmap_read(power->regmap, AXP20X_BATT_DISCHRG_I_H, &dh);
> +		if (ret)
> +			return ret;
> +		ret = regmap_read(power->regmap, AXP20X_BATT_DISCHRG_I_L, &dl);
> +		if (ret)
> +			return ret;
> +		/* it's a 12 bit integer, high 8-bit is stored in dh */
> +		val->intval = dh << 4 | dl >> 4;
> +		break;
> +	case POWER_SUPPLY_PROP_VOLTAGE_NOW:
> +		ret = regmap_read(power->regmap, AXP20X_BATT_V_H, &dh);
> +		if (ret)
> +			return ret;
> +		ret = regmap_read(power->regmap, AXP20X_BATT_V_L, &dl);
> +		if (ret)
> +			return ret;
> +		/* it's a 12 bit integer, high 8-bit is stored in dh */
> +		val->intval = dh << 4 | dl >> 4;
> +		/* The formula below is from axp22_vbat_to_mV function
> +		 * of Allwinner 3.4 kernel.
> +		 */
> +		val->intval = val->intval * 1100 / 1000;
> +		break;

I really feel that this should be implemented through a IIO driver
(like the AXP288). This is especially true for the AXP209 and its
multiple GPIOs that can be muxed to a general purpose ADC, but it's
also true for the AXP221 / 223 TS pin that can also be used as an ADC.

Quentin has been working on this lately for the AXP209, feel free to
sync with him to support the AXP22*

Maxime
Maxime Ripard Oct. 10, 2016, 1:12 p.m. UTC | #2
Adding the real Quentin address

On Mon, Oct 10, 2016 at 02:57:26PM +0200, Maxime Ripard wrote:
> Hi Icenowy,
> 
> On Sun, Oct 09, 2016 at 02:27:12PM +0800, Icenowy Zheng wrote:
> > +	case POWER_SUPPLY_PROP_CURRENT_NOW:
> > +		ret = regmap_read(power->regmap, AXP20X_BATT_DISCHRG_I_H, &dh);
> > +		if (ret)
> > +			return ret;
> > +		ret = regmap_read(power->regmap, AXP20X_BATT_DISCHRG_I_L, &dl);
> > +		if (ret)
> > +			return ret;
> > +		/* it's a 12 bit integer, high 8-bit is stored in dh */
> > +		val->intval = dh << 4 | dl >> 4;
> > +		break;
> > +	case POWER_SUPPLY_PROP_VOLTAGE_NOW:
> > +		ret = regmap_read(power->regmap, AXP20X_BATT_V_H, &dh);
> > +		if (ret)
> > +			return ret;
> > +		ret = regmap_read(power->regmap, AXP20X_BATT_V_L, &dl);
> > +		if (ret)
> > +			return ret;
> > +		/* it's a 12 bit integer, high 8-bit is stored in dh */
> > +		val->intval = dh << 4 | dl >> 4;
> > +		/* The formula below is from axp22_vbat_to_mV function
> > +		 * of Allwinner 3.4 kernel.
> > +		 */
> > +		val->intval = val->intval * 1100 / 1000;
> > +		break;
> 
> I really feel that this should be implemented through a IIO driver
> (like the AXP288). This is especially true for the AXP209 and its
> multiple GPIOs that can be muxed to a general purpose ADC, but it's
> also true for the AXP221 / 223 TS pin that can also be used as an ADC.
> 
> Quentin has been working on this lately for the AXP209, feel free to
> sync with him to support the AXP22*
> 
> Maxime
> 
> -- 
> Maxime Ripard, Free Electrons
> Embedded Linux and Kernel engineering
> http://free-electrons.com
diff mbox

Patch

diff --git a/drivers/mfd/axp20x.c b/drivers/mfd/axp20x.c
index 17877b2..ebccb22 100644
--- a/drivers/mfd/axp20x.c
+++ b/drivers/mfd/axp20x.c
@@ -141,6 +141,15 @@  static struct resource axp20x_ac_power_supply_resources[] = {
 	DEFINE_RES_IRQ_NAMED(AXP20X_IRQ_ACIN_OVER_V, "ACIN_OVER_V"),
 };
 
+static struct resource axp20x_battery_resources[] = {
+	DEFINE_RES_IRQ_NAMED(AXP20X_IRQ_BATT_PLUGIN, "BATT_PLUGIN"),
+	DEFINE_RES_IRQ_NAMED(AXP20X_IRQ_BATT_REMOVAL, "BATT_REMOVAL"),
+	DEFINE_RES_IRQ_NAMED(AXP20X_IRQ_BATT_ENT_ACT_MODE, "BATT_ENT_ACT_MODE"),
+	DEFINE_RES_IRQ_NAMED(AXP20X_IRQ_BATT_EXIT_ACT_MODE, "BATT_EXIT_ACT_MODE"),
+	DEFINE_RES_IRQ_NAMED(AXP20X_IRQ_CHARG, "CHARG"),
+	DEFINE_RES_IRQ_NAMED(AXP20X_IRQ_CHARG_DONE, "CHARG_DONE"),
+};
+
 static struct resource axp20x_pek_resources[] = {
 	{
 		.name	= "PEK_DBR",
@@ -162,6 +171,15 @@  static struct resource axp20x_usb_power_supply_resources[] = {
 	DEFINE_RES_IRQ_NAMED(AXP20X_IRQ_VBUS_NOT_VALID, "VBUS_NOT_VALID"),
 };
 
+static struct resource axp22x_battery_resources[] = {
+	DEFINE_RES_IRQ_NAMED(AXP22X_IRQ_BATT_PLUGIN, "BATT_PLUGIN"),
+	DEFINE_RES_IRQ_NAMED(AXP22X_IRQ_BATT_REMOVAL, "BATT_REMOVAL"),
+	DEFINE_RES_IRQ_NAMED(AXP22X_IRQ_BATT_ENT_ACT_MODE, "BATT_ENT_ACT_MODE"),
+	DEFINE_RES_IRQ_NAMED(AXP22X_IRQ_BATT_EXIT_ACT_MODE, "BATT_EXIT_ACT_MODE"),
+	DEFINE_RES_IRQ_NAMED(AXP22X_IRQ_CHARG, "CHARG"),
+	DEFINE_RES_IRQ_NAMED(AXP22X_IRQ_CHARG_DONE, "CHARG_DONE"),
+};
+
 static struct resource axp22x_usb_power_supply_resources[] = {
 	DEFINE_RES_IRQ_NAMED(AXP22X_IRQ_VBUS_PLUGIN, "VBUS_PLUGIN"),
 	DEFINE_RES_IRQ_NAMED(AXP22X_IRQ_VBUS_REMOVAL, "VBUS_REMOVAL"),
@@ -520,6 +538,11 @@  static struct mfd_cell axp20x_cells[] = {
 		.num_resources	= ARRAY_SIZE(axp20x_ac_power_supply_resources),
 		.resources	= axp20x_ac_power_supply_resources,
 	}, {
+		.name		= "axp20x-battery",
+		.of_compatible	= "x-powers,axp202-battery",
+		.num_resources	= ARRAY_SIZE(axp20x_battery_resources),
+		.resources	= axp20x_battery_resources,
+	}, {
 		.name		= "axp20x-usb-power-supply",
 		.of_compatible	= "x-powers,axp202-usb-power-supply",
 		.num_resources	= ARRAY_SIZE(axp20x_usb_power_supply_resources),
@@ -535,6 +558,11 @@  static struct mfd_cell axp22x_cells[] = {
 	}, {
 		.name			= "axp20x-regulator",
 	}, {
+		.name		= "axp20x-battery",
+		.of_compatible	= "x-powers,axp221-battery",
+		.num_resources	= ARRAY_SIZE(axp22x_battery_resources),
+		.resources	= axp22x_battery_resources,
+	}, {
 		.name		= "axp20x-usb-power-supply",
 		.of_compatible	= "x-powers,axp221-usb-power-supply",
 		.num_resources	= ARRAY_SIZE(axp22x_usb_power_supply_resources),
diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile
index 36c599d..01271d3 100644
--- a/drivers/power/supply/Makefile
+++ b/drivers/power/supply/Makefile
@@ -9,6 +9,7 @@  obj-$(CONFIG_GENERIC_ADC_BATTERY)	+= generic-adc-battery.o
 
 obj-$(CONFIG_PDA_POWER)		+= pda_power.o
 obj-$(CONFIG_APM_POWER)		+= apm_power.o
+obj-$(CONFIG_AXP20X_POWER)	+= axp20x_battery.o
 obj-$(CONFIG_AXP20X_POWER)	+= axp20x_usb_power.o
 obj-$(CONFIG_MAX8925_POWER)	+= max8925_power.o
 obj-$(CONFIG_WM831X_BACKUP)	+= wm831x_backup.o
diff --git a/drivers/power/supply/axp20x_battery.c b/drivers/power/supply/axp20x_battery.c
new file mode 100644
index 0000000..67ee58f
--- /dev/null
+++ b/drivers/power/supply/axp20x_battery.c
@@ -0,0 +1,277 @@ 
+/*
+ * AXP20x PMIC battery status driver
+ *
+ * Copyright (C) 2016 Icenowy Zheng <icenowy@aosc.xyz>
+ * Copyright (C) 2015 Hans de Goede <hdegoede@redhat.com>
+ * Copyright (C) 2014 Bruno Prémont <bonbons@linux-vserver.org>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under  the terms of the GNU General  Public License as published by the
+ * Free Software Foundation;  either version 2 of the License, or (at your
+ * option) any later version.
+ */
+
+#include <linux/device.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/mfd/axp20x.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+
+#define DRVNAME "axp20x-battery"
+
+#define AXP20X_PWR_STATUS_ACIN_USED BIT(6)
+#define AXP20X_PWR_STATUS_VBUS_USED BIT(4)
+#define AXP20X_PWR_STATUS_BAT_DIRECTION BIT(2)
+
+#define AXP20X_OP_MODE_CHARGING BIT(6)
+#define AXP20X_OP_MODE_BATTERY_PRESENT BIT(5)
+#define AXP20X_OP_MODE_BATTERY_ACTIVE BIT(3)
+
+#define AXP20X_CAPACITY_CORRECT BIT(7)
+
+struct axp20x_battery {
+	struct axp20x_dev *axp20x;
+	struct regmap *regmap;
+	struct power_supply *supply;
+};
+
+static irqreturn_t axp20x_battery_irq(int irq, void *devid)
+{
+	struct axp20x_battery *power = devid;
+
+	power_supply_changed(power->supply);
+
+	return IRQ_HANDLED;
+}
+
+static int axp20x_battery_get_property(struct power_supply *psy,
+	enum power_supply_property psp, union power_supply_propval *val)
+{
+	struct axp20x_battery *power = power_supply_get_drvdata(psy);
+	unsigned int input, op_mode, capacity, dh, dl;
+	int ret, ext;
+
+	/* All the properties below need the input-status reg value */
+	ret = regmap_read(power->regmap, AXP20X_PWR_INPUT_STATUS, &input);
+	if (ret)
+		return ret;
+
+	ret = regmap_read(power->regmap, AXP20X_PWR_OP_MODE, &op_mode);
+	if (ret)
+		return ret;
+
+	switch (psp) {
+	case POWER_SUPPLY_PROP_HEALTH:
+		if (!(op_mode & AXP20X_OP_MODE_BATTERY_PRESENT)) {
+			val->intval = POWER_SUPPLY_HEALTH_UNKNOWN;
+			break;
+		}
+
+		val->intval = POWER_SUPPLY_HEALTH_GOOD;
+
+		if (op_mode & AXP20X_OP_MODE_BATTERY_ACTIVE) {
+			/* AXP20X is now trying to re-activate the battery */
+			val->intval = POWER_SUPPLY_HEALTH_DEAD;
+			break;
+		}
+		break;
+	case POWER_SUPPLY_PROP_PRESENT:
+		val->intval = !!(op_mode & AXP20X_OP_MODE_BATTERY_PRESENT);
+		break;
+	case POWER_SUPPLY_PROP_CAPACITY:
+		ret = regmap_read(power->regmap, AXP20X_FG_RES, &capacity);
+		if (ret)
+			return ret;
+		if (capacity & AXP20X_CAPACITY_CORRECT)
+			val->intval = capacity & (~AXP20X_CAPACITY_CORRECT);
+		else
+			return -EIO;
+		/* from axp_capchange function of Allwinner 3.4 driver */
+		if (val->intval == 127)
+			val->intval = 100;
+		break;
+	case POWER_SUPPLY_PROP_STATUS:
+		if (!(op_mode & AXP20X_OP_MODE_BATTERY_PRESENT)) {
+			val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
+			break;
+		}
+
+		ret = regmap_read(power->regmap, AXP20X_FG_RES, &capacity);
+		if (ret)
+			return ret;
+
+		ext = (input & AXP20X_PWR_STATUS_ACIN_USED) ||
+		      (input & AXP20X_PWR_STATUS_VBUS_USED);
+
+		if (op_mode & AXP20X_OP_MODE_CHARGING)
+			val->intval = POWER_SUPPLY_STATUS_CHARGING;
+		else if (!(input & AXP20X_PWR_STATUS_BAT_DIRECTION) && !ext)
+			val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+		else if (capacity == (100 | AXP20X_CAPACITY_CORRECT) ||
+			 capacity == (127 | AXP20X_CAPACITY_CORRECT))
+			val->intval = POWER_SUPPLY_STATUS_FULL;
+		else
+			val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
+		break;
+	case POWER_SUPPLY_PROP_CURRENT_NOW:
+		ret = regmap_read(power->regmap, AXP20X_BATT_DISCHRG_I_H, &dh);
+		if (ret)
+			return ret;
+		ret = regmap_read(power->regmap, AXP20X_BATT_DISCHRG_I_L, &dl);
+		if (ret)
+			return ret;
+		/* it's a 12 bit integer, high 8-bit is stored in dh */
+		val->intval = dh << 4 | dl >> 4;
+		break;
+	case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+		ret = regmap_read(power->regmap, AXP20X_BATT_V_H, &dh);
+		if (ret)
+			return ret;
+		ret = regmap_read(power->regmap, AXP20X_BATT_V_L, &dl);
+		if (ret)
+			return ret;
+		/* it's a 12 bit integer, high 8-bit is stored in dh */
+		val->intval = dh << 4 | dl >> 4;
+		/* The formula below is from axp22_vbat_to_mV function
+		 * of Allwinner 3.4 kernel.
+		 */
+		val->intval = val->intval * 1100 / 1000;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static enum power_supply_property axp20x_battery_properties[] = {
+	POWER_SUPPLY_PROP_CAPACITY,
+	POWER_SUPPLY_PROP_HEALTH,
+	POWER_SUPPLY_PROP_PRESENT,
+	POWER_SUPPLY_PROP_STATUS,
+};
+
+static const struct power_supply_desc axp20x_battery_desc = {
+	.name = "axp20x-battery",
+	.type = POWER_SUPPLY_TYPE_BATTERY,
+	.properties = axp20x_battery_properties,
+	.num_properties = ARRAY_SIZE(axp20x_battery_properties),
+	.get_property = axp20x_battery_get_property,
+};
+
+static enum power_supply_property axp22x_battery_properties[] = {
+	POWER_SUPPLY_PROP_CAPACITY,
+	POWER_SUPPLY_PROP_HEALTH,
+	POWER_SUPPLY_PROP_PRESENT,
+	POWER_SUPPLY_PROP_STATUS,
+	POWER_SUPPLY_PROP_CURRENT_NOW,
+	POWER_SUPPLY_PROP_VOLTAGE_NOW,
+};
+
+static const struct power_supply_desc axp22x_battery_desc = {
+	.name = "axp20x-battery",
+	.type = POWER_SUPPLY_TYPE_BATTERY,
+	.properties = axp22x_battery_properties,
+	.num_properties = ARRAY_SIZE(axp22x_battery_properties),
+	.get_property = axp20x_battery_get_property,
+};
+
+static int axp20x_battery_probe(struct platform_device *pdev)
+{
+	struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent);
+	struct power_supply_config psy_cfg = {};
+	struct axp20x_battery *power;
+	static const char * const axp20x_irq_names[] = {
+		"BATT_PLUGIN", "BATT_REMOVAL", "BATT_ENT_ACT_MODE",
+		"BATT_EXIT_ACT_MODE", "CHARG", "CHARG_DONE", NULL };
+	static const char * const axp22x_irq_names[] = {
+		"BATT_PLUGIN", "BATT_REMOVAL", "BATT_ENT_ACT_MODE",
+		"BATT_EXIT_ACT_MODE", "CHARG", "CHARG_DONE", NULL };
+	static const char * const *irq_names;
+	const struct power_supply_desc *battery_desc;
+	int i, irq, ret;
+
+	if (!of_device_is_available(pdev->dev.of_node))
+		return -ENODEV;
+
+	if (!axp20x) {
+		dev_err(&pdev->dev, "Parent drvdata not set\n");
+		return -EINVAL;
+	}
+
+	power = devm_kzalloc(&pdev->dev, sizeof(*power), GFP_KERNEL);
+	if (!power)
+		return -ENOMEM;
+
+	power->axp20x = axp20x;
+	power->regmap = axp20x->regmap;
+
+	switch (power->axp20x->variant) {
+	case AXP202_ID:
+	case AXP209_ID:
+		battery_desc = &axp20x_battery_desc;
+		irq_names = axp20x_irq_names;
+		break;
+	case AXP221_ID:
+	case AXP223_ID:
+		battery_desc = &axp22x_battery_desc;
+		irq_names = axp22x_irq_names;
+		break;
+	default:
+		dev_err(&pdev->dev, "Unsupported AXP variant: %ld\n",
+			axp20x->variant);
+		return -EINVAL;
+	}
+
+	psy_cfg.of_node = pdev->dev.of_node;
+	psy_cfg.drv_data = power;
+
+	power->supply = devm_power_supply_register(&pdev->dev, battery_desc,
+						   &psy_cfg);
+	if (IS_ERR(power->supply))
+		return PTR_ERR(power->supply);
+
+	/* Request irqs after registering, as irqs may trigger immediately */
+	for (i = 0; irq_names[i]; i++) {
+		irq = platform_get_irq_byname(pdev, irq_names[i]);
+		if (irq < 0) {
+			dev_warn(&pdev->dev, "No IRQ for %s: %d\n",
+				 irq_names[i], irq);
+			continue;
+		}
+		irq = regmap_irq_get_virq(axp20x->regmap_irqc, irq);
+		ret = devm_request_any_context_irq(&pdev->dev, irq,
+				axp20x_battery_irq, 0, DRVNAME, power);
+		if (ret < 0)
+			dev_warn(&pdev->dev, "Error requesting %s IRQ: %d\n",
+				 irq_names[i], ret);
+	}
+
+	return 0;
+}
+
+static const struct of_device_id axp20x_battery_match[] = {
+	{ .compatible = "x-powers,axp202-battery" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, axp20x_battery_match);
+
+static struct platform_driver axp20x_battery_driver = {
+	.probe = axp20x_battery_probe,
+	.driver = {
+		.name = DRVNAME,
+		.of_match_table = axp20x_battery_match,
+	},
+};
+
+module_platform_driver(axp20x_battery_driver);
+
+MODULE_AUTHOR("Icenowy Zheng <icenowy@aosc.xyz>");
+MODULE_DESCRIPTION("AXP20x PMIC battery status driver");
+MODULE_LICENSE("GPL");