diff mbox

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

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

Commit Message

Icenowy Zheng July 1, 2016, 9:29 a.m. UTC
This driver is the battery power supply driver of axp20x PMIC. Currently
it supports only AXP22x variants.

Signed-off-by: Icenowy Zheng <icenowy@aosc.xyz>
---
 drivers/mfd/axp20x.c           |  14 +++
 drivers/power/Makefile         |   1 +
 drivers/power/axp20x_battery.c | 254 +++++++++++++++++++++++++++++++++++++++++
 3 files changed, 269 insertions(+)
 create mode 100644 drivers/power/axp20x_battery.c

Comments

Michael Haas July 5, 2016, 5:26 a.m. UTC | #1
Hi,

nice work! Is this in any way related to Bruno Prémonts driver for the 
axp20x?

I've got a reworked version of that lying around, but it's not quite 
ready for submission. Do you know what pieces are missing in your driver 
for axp20x support - as opposed to axp22x, which is already working?

Michael

On 01.07.2016 11:29, Icenowy Zheng wrote:
> This driver is the battery power supply driver of axp20x PMIC. Currently
> it supports only AXP22x variants.
>
> Signed-off-by: Icenowy Zheng <icenowy@aosc.xyz>
> ---
>   drivers/mfd/axp20x.c           |  14 +++
>   drivers/power/Makefile         |   1 +
>   drivers/power/axp20x_battery.c | 254 +++++++++++++++++++++++++++++++++++++++++
>   3 files changed, 269 insertions(+)
>   create mode 100644 drivers/power/axp20x_battery.c
>
> diff --git a/drivers/mfd/axp20x.c b/drivers/mfd/axp20x.c
> index cee5288..064e5015 100644
> --- a/drivers/mfd/axp20x.c
> +++ b/drivers/mfd/axp20x.c
> @@ -166,6 +166,15 @@ static struct resource axp22x_usb_power_supply_resources[] = {
>   	DEFINE_RES_IRQ_NAMED(AXP22X_IRQ_VBUS_REMOVAL, "VBUS_REMOVAL"),
>   };
>   
> +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_pek_resources[] = {
>   	{
>   		.name   = "PEK_DBR",
> @@ -538,6 +547,11 @@ static struct mfd_cell axp22x_cells[] = {
>   		.of_compatible	= "x-powers,axp202-usb-power-supply",
>   		.num_resources	= ARRAY_SIZE(axp22x_usb_power_supply_resources),
>   		.resources	= axp22x_usb_power_supply_resources,
> +	}, {
> +		.name		= "axp20x-battery",
> +		.of_compatible	= "x-powers,axp202-battery",
> +		.num_resources	= ARRAY_SIZE(axp22x_battery_resources),
> +		.resources	= axp22x_battery_resources,
>   	},
>   };
>   
> diff --git a/drivers/power/Makefile b/drivers/power/Makefile
> index e46b75d..1452d23 100644
> --- a/drivers/power/Makefile
> +++ b/drivers/power/Makefile
> @@ -10,6 +10,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_usb_power.o
> +obj-$(CONFIG_AXP20X_POWER)	+= axp20x_battery.o
>   obj-$(CONFIG_MAX8925_POWER)	+= max8925_power.o
>   obj-$(CONFIG_WM831X_BACKUP)	+= wm831x_backup.o
>   obj-$(CONFIG_WM831X_POWER)	+= wm831x_power.o
> diff --git a/drivers/power/axp20x_battery.c b/drivers/power/axp20x_battery.c
> new file mode 100644
> index 0000000..8fac2cd
> --- /dev/null
> +++ b/drivers/power/axp20x_battery.c
> @@ -0,0 +1,254 @@
> +/*
> + * 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 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 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 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");
Icenowy Zheng July 5, 2016, 8:33 a.m. UTC | #2
05.07.2016, 13:26, "Michael Haas" <michael.haas@mailbox.org>:
> Hi,
>
> nice work! Is this in any way related to Bruno Prémonts driver for the
> axp20x?
>
> I've got a reworked version of that lying around, but it's not quite
> ready for submission. Do you know what pieces are missing in your driver
> for axp20x support - as opposed to axp22x, which is already working?
>
> Michael
>
> On 01.07.2016 11:29, Icenowy Zheng wrote:
>>  This driver is the battery power supply driver of axp20x PMIC. Currently
>>  it supports only AXP22x variants.
>>
>>  Signed-off-by: Icenowy Zheng <icenowy@aosc.xyz>
>>  ---
>>    drivers/mfd/axp20x.c | 14 +++
>>    drivers/power/Makefile | 1 +
>>    drivers/power/axp20x_battery.c | 254 +++++++++++++++++++++++++++++++++++++++++
>>    3 files changed, 269 insertions(+)
>>    create mode 100644 drivers/power/axp20x_battery.c
>>
>>  diff --git a/drivers/mfd/axp20x.c b/drivers/mfd/axp20x.c
>>  index cee5288..064e5015 100644
>>  --- a/drivers/mfd/axp20x.c
>>  +++ b/drivers/mfd/axp20x.c
>>  @@ -166,6 +166,15 @@ static struct resource axp22x_usb_power_supply_resources[] = {
>>            DEFINE_RES_IRQ_NAMED(AXP22X_IRQ_VBUS_REMOVAL, "VBUS_REMOVAL"),
>>    };
>>
>>  +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_pek_resources[] = {
>>            {
>>                    .name = "PEK_DBR",
>>  @@ -538,6 +547,11 @@ static struct mfd_cell axp22x_cells[] = {
>>                    .of_compatible = "x-powers,axp202-usb-power-supply",
>>                    .num_resources = ARRAY_SIZE(axp22x_usb_power_supply_resources),
>>                    .resources = axp22x_usb_power_supply_resources,
>>  + }, {
>>  + .name = "axp20x-battery",
>>  + .of_compatible = "x-powers,axp202-battery",
>>  + .num_resources = ARRAY_SIZE(axp22x_battery_resources),
>>  + .resources = axp22x_battery_resources,
>>            },
>>    };
>>
>>  diff --git a/drivers/power/Makefile b/drivers/power/Makefile
>>  index e46b75d..1452d23 100644
>>  --- a/drivers/power/Makefile
>>  +++ b/drivers/power/Makefile
>>  @@ -10,6 +10,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_usb_power.o
>>  +obj-$(CONFIG_AXP20X_POWER) += axp20x_battery.o
>>    obj-$(CONFIG_MAX8925_POWER) += max8925_power.o
>>    obj-$(CONFIG_WM831X_BACKUP) += wm831x_backup.o
>>    obj-$(CONFIG_WM831X_POWER) += wm831x_power.o
>>  diff --git a/drivers/power/axp20x_battery.c b/drivers/power/axp20x_battery.c
>>  new file mode 100644
>>  index 0000000..8fac2cd
>>  --- /dev/null
>>  +++ b/drivers/power/axp20x_battery.c
>>  @@ -0,0 +1,254 @@
>>  +/*
>>  + * 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 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 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 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");

Therotically, it can run on axp20x. However, I have currently no test device. (Still waiting for my C.H.I.P.)
So I can only promise it to run on axp22x.
Icenowy Zheng July 5, 2016, 8:47 a.m. UTC | #3
05.07.2016, 13:26, "Michael Haas" <michael.haas@mailbox.org>:
> Hi,
>
> nice work! Is this in any way related to Bruno Prémonts driver for the
> axp20x?
>
> I've got a reworked version of that lying around, but it's not quite
> ready for submission. Do you know what pieces are missing in your driver
> for axp20x support - as opposed to axp22x, which is already working?
>
> Michael
>
> On 01.07.2016 11:29, Icenowy Zheng wrote:
>>  This driver is the battery power supply driver of axp20x PMIC. Currently
>>  it supports only AXP22x variants.
>>
>>  Signed-off-by: Icenowy Zheng <icenowy@aosc.xyz>
>>  ---
>>    drivers/mfd/axp20x.c | 14 +++
>>    drivers/power/Makefile | 1 +
>>    drivers/power/axp20x_battery.c | 254 +++++++++++++++++++++++++++++++++++++++++
>>    3 files changed, 269 insertions(+)
>>    create mode 100644 drivers/power/axp20x_battery.c
>>
>>  diff --git a/drivers/mfd/axp20x.c b/drivers/mfd/axp20x.c
>>  index cee5288..064e5015 100644
>>  --- a/drivers/mfd/axp20x.c
>>  +++ b/drivers/mfd/axp20x.c
>>  @@ -166,6 +166,15 @@ static struct resource axp22x_usb_power_supply_resources[] = {
>>            DEFINE_RES_IRQ_NAMED(AXP22X_IRQ_VBUS_REMOVAL, "VBUS_REMOVAL"),
>>    };
>>
>>  +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_pek_resources[] = {
>>            {
>>                    .name = "PEK_DBR",
>>  @@ -538,6 +547,11 @@ static struct mfd_cell axp22x_cells[] = {
>>                    .of_compatible = "x-powers,axp202-usb-power-supply",
>>                    .num_resources = ARRAY_SIZE(axp22x_usb_power_supply_resources),
>>                    .resources = axp22x_usb_power_supply_resources,
>>  + }, {
>>  + .name = "axp20x-battery",
>>  + .of_compatible = "x-powers,axp202-battery",
>>  + .num_resources = ARRAY_SIZE(axp22x_battery_resources),
>>  + .resources = axp22x_battery_resources,
>>            },
>>    };
>>
>>  diff --git a/drivers/power/Makefile b/drivers/power/Makefile
>>  index e46b75d..1452d23 100644
>>  --- a/drivers/power/Makefile
>>  +++ b/drivers/power/Makefile
>>  @@ -10,6 +10,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_usb_power.o
>>  +obj-$(CONFIG_AXP20X_POWER) += axp20x_battery.o
>>    obj-$(CONFIG_MAX8925_POWER) += max8925_power.o
>>    obj-$(CONFIG_WM831X_BACKUP) += wm831x_backup.o
>>    obj-$(CONFIG_WM831X_POWER) += wm831x_power.o
>>  diff --git a/drivers/power/axp20x_battery.c b/drivers/power/axp20x_battery.c
>>  new file mode 100644
>>  index 0000000..8fac2cd
>>  --- /dev/null
>>  +++ b/drivers/power/axp20x_battery.c
>>  @@ -0,0 +1,254 @@
>>  +/*
>>  + * 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 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 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 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");

I read the datasheet of axp20x, and then found that this driver does not support backup RTC battery. 
(But maybe backup battery do not need a driver -- at least on IBM PC it has no driver)

And I don't know whether the axp20x has default Li-ion/LiPo battery OCV parameter. (axp22x seems to be have a set of default OCV)

You can test this driver on AXP20x. (I think I didn't access to AXP22x-specified registers in the power supply code)
Maxime Ripard July 5, 2016, 9:25 a.m. UTC | #4
On Tue, Jul 05, 2016 at 04:33:31PM +0800, Icenowy Zheng wrote:
> 
> 
> 05.07.2016, 13:26, "Michael Haas" <michael.haas@mailbox.org>:
> > Hi,
> >
> > nice work! Is this in any way related to Bruno Prémonts driver for the
> > axp20x?
> >
> > I've got a reworked version of that lying around, but it's not quite
> > ready for submission. Do you know what pieces are missing in your driver
> > for axp20x support - as opposed to axp22x, which is already working?
> 
> Therotically, it can run on axp20x. However, I have currently no
> test device. (Still waiting for my C.H.I.P.)  So I can only promise
> it to run on axp22x.

Still, it looks like you just took Bruno's driver, stripped out the
axp20x parts and added the one to deal with axp22x.

This makes the driver look weird, by being called axp20x, without
actually supporting those PMICs, but having some axp22x variables
right in the middle. And that's awfully inconsistent.

I'd rather have you add the AXP20x driver directly, and then add the
needed stuff for the AXP22x.

Maxime
Icenowy Zheng July 5, 2016, 10:09 a.m. UTC | #5
05.07.2016, 17:25, "maxime.ripard@free-electrons.com" <maxime.ripard@free-electrons.com>:
> On Tue, Jul 05, 2016 at 04:33:31PM +0800, Icenowy Zheng wrote:
>>  05.07.2016, 13:26, "Michael Haas" <michael.haas@mailbox.org>:
>>  > Hi,
>>  >
>>  > nice work! Is this in any way related to Bruno Prémonts driver for the
>>  > axp20x?
>>  >
>>  > I've got a reworked version of that lying around, but it's not quite
>>  > ready for submission. Do you know what pieces are missing in your driver
>>  > for axp20x support - as opposed to axp22x, which is already working?
>>
>>  Therotically, it can run on axp20x. However, I have currently no
>>  test device. (Still waiting for my C.H.I.P.) So I can only promise
>>  it to run on axp22x.
>
> Still, it looks like you just took Bruno's driver, stripped out the
> axp20x parts and added the one to deal with axp22x.
>
> This makes the driver look weird, by being called axp20x, without
> actually supporting those PMICs, but having some axp22x variables
> right in the middle. And that's awfully inconsistent.
>
> I'd rather have you add the AXP20x driver directly, and then add the
> needed stuff for the AXP22x.
>
> Maxime
>
> --
> Maxime Ripard, Free Electrons
> Embedded Linux, Kernel and Android engineering
> http://free-electrons.com

I modified axp20x_usb_power.c to suite the battery.

No axp22x-specified register is used in this driver.
I think this driver may now also work properly on axp20x, however
I have currently no device with axp20x.
(I didn't enter linux-sunxi when A10/13/20 is popular... So I'm waiting for
my CHIP)

This driver didn't implement OCV value tweak.

In fact, OCV value tweaking is even not documented in the datasheet
of both axp20x and axp22x.

Can you test it with axp20x? (It seems that the shipment of CHIP is so slow)
Bruno Prémont July 5, 2016, 12:43 p.m. UTC | #6
On Tue, 05 Jul 2016 16:47:38 +0800 Icenowy Zheng wrote:
> I read the datasheet of axp20x, and then found that this driver does
> not support backup RTC battery.
> (But maybe backup battery do not need a driver -- at least on IBM PC
> it has no driver)

A driver is needed to enable/disable the RTC battery charging (unless
uboot does it).
However all the driver can do according to datasheet is configure the
charge voltage/current or disable charging completely.
Monitoring RTC battery is not possible, neither is presence detection.

So driver should just apply configuration it finds in device-tree and
eventually share that information via power supply class.

> And I don't know whether the axp20x has default Li-ion/LiPo battery
> OCV parameter. (axp22x seems to be have a set of default OCV)

It seems to have default OCV parameters as well. With empty RTC
battery OCV values are present.

> You can test this driver on AXP20x. (I think I didn't access to
> AXP22x-specified registers in the power supply code)

Bruno
Bruno Prémont July 5, 2016, 12:45 p.m. UTC | #7
On Tue, 5 Jul 2016 11:25:10 +0200 Maxime Ripard wrote:
> On Tue, Jul 05, 2016 at 04:33:31PM +0800, Icenowy Zheng wrote:
> > 
> > 
> > 05.07.2016, 13:26, "Michael Haas" <michael.haas@mailbox.org>:  
> > > Hi,
> > >
> > > nice work! Is this in any way related to Bruno Prémonts driver for the
> > > axp20x?
> > >
> > > I've got a reworked version of that lying around, but it's not quite
> > > ready for submission. Do you know what pieces are missing in your driver
> > > for axp20x support - as opposed to axp22x, which is already working?  
> > 
> > Therotically, it can run on axp20x. However, I have currently no
> > test device. (Still waiting for my C.H.I.P.)  So I can only promise
> > it to run on axp22x.  
> 
> Still, it looks like you just took Bruno's driver, stripped out the
> axp20x parts and added the one to deal with axp22x.
> 
> This makes the driver look weird, by being called axp20x, without
> actually supporting those PMICs, but having some axp22x variables
> right in the middle. And that's awfully inconsistent.
> 
> I'd rather have you add the AXP20x driver directly, and then add the
> needed stuff for the AXP22x.

From a very quick look at the patch it looks like only basic
"monitoring" features have been retained, and yes, claiming the driver
to be for axp22x and having the functions named axp20x feels weird.

Missing parts are OCV, charge control (be it as interaction with
USB-power where charge current must be limited to not over-drain
USB power source or manual control when user knows more about the USB
power plug than the device).

Proper capacity measurement is a completely different story which I
did not implement (it exists in 3.4 sunxi kernel but is not
straight-forward and makes use of extra registers for data keeping).

Bruno
Michael Haas July 6, 2016, 4:34 a.m. UTC | #8
Hi There,

On 05.07.2016 10:33, Icenowy Zheng wrote:
> On 01.07.2016 11:29, Icenowy Zheng wrote:

>>>   +
>>>   +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,
>>>   +};

Here's what Bruno's driver supports:

static enum power_supply_property axp20x_battery_power_properties[] = {
     POWER_SUPPLY_PROP_PRESENT,
     POWER_SUPPLY_PROP_ONLINE,
     POWER_SUPPLY_PROP_STATUS,
     POWER_SUPPLY_PROP_VOLTAGE_NOW,
     POWER_SUPPLY_PROP_CURRENT_NOW,
     POWER_SUPPLY_PROP_CURRENT_MAX,
     POWER_SUPPLY_PROP_HEALTH,
     POWER_SUPPLY_PROP_TECHNOLOGY,
     POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
     POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
     /* POWER_SUPPLY_PROP_POWER_NOW, */
     POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
     /* POWER_SUPPLY_PROP_CHARGE_NOW, */
     POWER_SUPPLY_PROP_CAPACITY,
     POWER_SUPPLY_PROP_TEMP,
     POWER_SUPPLY_PROP_TEMP_ALERT_MIN,
     POWER_SUPPLY_PROP_TEMP_ALERT_MAX,
};

>>>   +
>>>   +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 axp22x_irq_names[] = {
>>>   + "BATT_PLUGIN", "BATT_REMOVAL", "BATT_ENT_ACT_MODE",
>>>   + "BATT_EXIT_ACT_MODE", "CHARG", "CHARG_DONE", NULL };


And here are the interrupts handled:

static const char * const irq_names[] = { "BATT_HOT", "BATT_COLD",
         "BATT_PLUGIN", "BATT_REMOVAL", "BATT_ACTIVATE",
         "BATT_ACTIVATED", "BATT_CHARGING", "BATT_CHARGED",
         "BATT_CHG_CURR_LOW", "BATT_POWER_LOW_WARN",
         "BATT_POWER_LOW_CRIT" };



There are a couple of issues with the version of Bruno's driver that I have:

* power management is disabled (in the driver, not in the charger) - 
think suspend/resume
* the temperature sensor data is not turned into a temperature value 
correctly
* the IRQ handlers need to be cleaned up (remove logging)
* device tree binding documentation is missing

Other than that, it's basically working and I have been using it at 
least for some testing.

The device tree bindings support:
* OCV curve support
* battery resistance
* battery capacity
* temp sensor settings

You can find my copy of Bruno's driver here:
https://github.com/mhaas/linux-sunxi/blob/axp209-charger/drivers/power/axp20x_fuel_gauge.c

Thanks,

Michael
Icenowy Zheng July 6, 2016, 7:48 a.m. UTC | #9
06.07.2016, 12:35, "Michael Haas" <haas@computerlinguist.org>:
> Hi There,
>
> On 05.07.2016 10:33, Icenowy Zheng wrote:
>>  On 01.07.2016 11:29, Icenowy Zheng wrote:
>
>>>>    +
>>>>    +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,
>>>>    +};
>
> Here's what Bruno's driver supports:
>
> static enum power_supply_property axp20x_battery_power_properties[] = {
>      POWER_SUPPLY_PROP_PRESENT,
>      POWER_SUPPLY_PROP_ONLINE,
>      POWER_SUPPLY_PROP_STATUS,
>      POWER_SUPPLY_PROP_VOLTAGE_NOW,
>      POWER_SUPPLY_PROP_CURRENT_NOW,
>      POWER_SUPPLY_PROP_CURRENT_MAX,
>      POWER_SUPPLY_PROP_HEALTH,
>      POWER_SUPPLY_PROP_TECHNOLOGY,
>      POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
>      POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
>      /* POWER_SUPPLY_PROP_POWER_NOW, */
>      POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
>      /* POWER_SUPPLY_PROP_CHARGE_NOW, */
>      POWER_SUPPLY_PROP_CAPACITY,
>      POWER_SUPPLY_PROP_TEMP,
>      POWER_SUPPLY_PROP_TEMP_ALERT_MIN,
>      POWER_SUPPLY_PROP_TEMP_ALERT_MAX,
> };
>
>>>>    +
>>>>    +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 axp22x_irq_names[] = {
>>>>    + "BATT_PLUGIN", "BATT_REMOVAL", "BATT_ENT_ACT_MODE",
>>>>    + "BATT_EXIT_ACT_MODE", "CHARG", "CHARG_DONE", NULL };
>
> And here are the interrupts handled:
>
> static const char * const irq_names[] = { "BATT_HOT", "BATT_COLD",
>          "BATT_PLUGIN", "BATT_REMOVAL", "BATT_ACTIVATE",
>          "BATT_ACTIVATED", "BATT_CHARGING", "BATT_CHARGED",
>          "BATT_CHG_CURR_LOW", "BATT_POWER_LOW_WARN",
>          "BATT_POWER_LOW_CRIT" };
>
> There are a couple of issues with the version of Bruno's driver that I have:
>
> * power management is disabled (in the driver, not in the charger) -
> think suspend/resume
> * the temperature sensor data is not turned into a temperature value
> correctly
> * the IRQ handlers need to be cleaned up (remove logging)
> * device tree binding documentation is missing
>
> Other than that, it's basically working and I have been using it at
> least for some testing.
>
> The device tree bindings support:
> * OCV curve support
> * battery resistance
> * battery capacity
> * temp sensor settings
>
> You can find my copy of Bruno's driver here:
> https://github.com/mhaas/linux-sunxi/blob/axp209-charger/drivers/power/axp20x_fuel_gauge.c
>
> Thanks,
>
> Michael

Michael Haas:
I'm grateful for you providing your axp20x_fuel_gauge.c copy.

I will try to tidy it up and add support for axp22x.

Thanks,

Icenowy
diff mbox

Patch

diff --git a/drivers/mfd/axp20x.c b/drivers/mfd/axp20x.c
index cee5288..064e5015 100644
--- a/drivers/mfd/axp20x.c
+++ b/drivers/mfd/axp20x.c
@@ -166,6 +166,15 @@  static struct resource axp22x_usb_power_supply_resources[] = {
 	DEFINE_RES_IRQ_NAMED(AXP22X_IRQ_VBUS_REMOVAL, "VBUS_REMOVAL"),
 };
 
+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_pek_resources[] = {
 	{
 		.name   = "PEK_DBR",
@@ -538,6 +547,11 @@  static struct mfd_cell axp22x_cells[] = {
 		.of_compatible	= "x-powers,axp202-usb-power-supply",
 		.num_resources	= ARRAY_SIZE(axp22x_usb_power_supply_resources),
 		.resources	= axp22x_usb_power_supply_resources,
+	}, {
+		.name		= "axp20x-battery",
+		.of_compatible	= "x-powers,axp202-battery",
+		.num_resources	= ARRAY_SIZE(axp22x_battery_resources),
+		.resources	= axp22x_battery_resources,
 	},
 };
 
diff --git a/drivers/power/Makefile b/drivers/power/Makefile
index e46b75d..1452d23 100644
--- a/drivers/power/Makefile
+++ b/drivers/power/Makefile
@@ -10,6 +10,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_usb_power.o
+obj-$(CONFIG_AXP20X_POWER)	+= axp20x_battery.o
 obj-$(CONFIG_MAX8925_POWER)	+= max8925_power.o
 obj-$(CONFIG_WM831X_BACKUP)	+= wm831x_backup.o
 obj-$(CONFIG_WM831X_POWER)	+= wm831x_power.o
diff --git a/drivers/power/axp20x_battery.c b/drivers/power/axp20x_battery.c
new file mode 100644
index 0000000..8fac2cd
--- /dev/null
+++ b/drivers/power/axp20x_battery.c
@@ -0,0 +1,254 @@ 
+/*
+ * 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 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 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 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");