[RFC Patch 2/4] mfd: AXP20x: Add power supply sub-driver

Bruno Prémont
2014-10-20T20:33:20Z

Add driver for the power supply features of AXP20x PMIC.

Covered features:
- backup / RTC battery
- VBUS/OTG power input
- AC power input
- LIon battery charger
---
drivers/mfd/axp20x.c | 106 +-
drivers/power/Kconfig | 9 +
drivers/power/Makefile | 1 +
drivers/power/axp20x_power.c | 1530 ++++++++++++++++++++++
include/linux/mfd/axp20x.h | 5 +
5 files changed, 1650 insertions(+), 1 deletion(-)
create mode 100644 drivers/power/axp20x_power.c

diff --git a/drivers/mfd/axp20x.c b/drivers/mfd/axp20x.c
index dee6539..1322489 100644
--- a/drivers/mfd/axp20x.c
+++ b/drivers/mfd/axp20x.c
@@ -31,10 +31,16 @@
static const struct regmap_range axp20x_writeable_ranges[] = {
regmap_reg_range(AXP20X_DATACACHE(0), AXP20X_IRQ5_STATE),
regmap_reg_range(AXP20X_DCDC_MODE, AXP20X_FG_RES),
+ regmap_reg_range(AXP20X_OCV(0), AXP20X_OCV(15)),
};

static const struct regmap_range axp20x_volatile_ranges[] = {
+ regmap_reg_range(AXP20X_PWR_INPUT_STATUS, AXP20X_USB_OTG_STATUS),
+ regmap_reg_range(AXP20X_CHRG_CTRL1, AXP20X_CHRG_CTRL2),
regmap_reg_range(AXP20X_IRQ1_EN, AXP20X_IRQ5_STATE),
+ regmap_reg_range(AXP20X_ACIN_V_ADC_H, AXP20X_IPSOUT_V_HIGH_L),
+ regmap_reg_range(AXP20X_GPIO20_SS, AXP20X_GPIO3_CTRL),
+ regmap_reg_range(AXP20X_FG_RES, AXP20X_RDC_L),
};

static const struct regmap_access_table axp20x_writeable_table = {
@@ -61,12 +67,106 @@ static struct resource axp20x_pek_resources[] = {
},
};

+static struct resource axp20x_power_resources[] = {
+ {
+ .name = "ACIN_OVER_V",
+ .start = AXP20X_IRQ_ACIN_OVER_V,
+ .end = AXP20X_IRQ_ACIN_OVER_V,
+ .flags = IORESOURCE_IRQ,
+ }, {
+ .name = "ACIN_PLUGIN",
+ .start = AXP20X_IRQ_ACIN_PLUGIN,
+ .end = AXP20X_IRQ_ACIN_PLUGIN,
+ .flags = IORESOURCE_IRQ,
+ }, {
+ .name = "ACIN_REMOVAL",
+ .start = AXP20X_IRQ_ACIN_REMOVAL,
+ .end = AXP20X_IRQ_ACIN_REMOVAL,
+ .flags = IORESOURCE_IRQ,
+ }, {
+ .name = "VBUS_OVER_V",
+ .start = AXP20X_IRQ_VBUS_OVER_V,
+ .end = AXP20X_IRQ_VBUS_OVER_V,
+ .flags = IORESOURCE_IRQ,
+ }, {
+ .name = "VBUS_PLUGIN",
+ .start = AXP20X_IRQ_VBUS_PLUGIN,
+ .end = AXP20X_IRQ_VBUS_PLUGIN,
+ .flags = IORESOURCE_IRQ,
+ }, {
+ .name = "VBUS_REMOVAL",
+ .start = AXP20X_IRQ_VBUS_REMOVAL,
+ .end = AXP20X_IRQ_VBUS_REMOVAL,
+ .flags = IORESOURCE_IRQ,
+ }, {
+ .name = "VBUS_V_LOW",
+ .start = AXP20X_IRQ_VBUS_V_LOW,
+ .end = AXP20X_IRQ_VBUS_V_LOW,
+ .flags = IORESOURCE_IRQ,
+ }, {
+ .name = "BATT_PLUGIN",
+ .start = AXP20X_IRQ_BATT_PLUGIN,
+ .end = AXP20X_IRQ_BATT_PLUGIN,
+ .flags = IORESOURCE_IRQ,
+ }, {
+ .name = "BATT_REMOVAL",
+ .start = AXP20X_IRQ_BATT_REMOVAL,
+ .end = AXP20X_IRQ_BATT_REMOVAL,
+ .flags = IORESOURCE_IRQ,
+ }, {
+ .name = "BATT_ACTIVATE",
+ .start = AXP20X_IRQ_BATT_ENT_ACT_MODE,
+ .end = AXP20X_IRQ_BATT_ENT_ACT_MODE,
+ .flags = IORESOURCE_IRQ,
+ }, {
+ .name = "BATT_ACTIVATED",
+ .start = AXP20X_IRQ_BATT_EXIT_ACT_MODE,
+ .end = AXP20X_IRQ_BATT_EXIT_ACT_MODE,
+ .flags = IORESOURCE_IRQ,
+ }, {
+ .name = "BATT_CHARGING",
+ .start = AXP20X_IRQ_CHARG,
+ .end = AXP20X_IRQ_CHARG,
+ .flags = IORESOURCE_IRQ,
+ }, {
+ .name = "BATT_CHARGED",
+ .start = AXP20X_IRQ_CHARG_DONE,
+ .end = AXP20X_IRQ_CHARG_DONE,
+ .flags = IORESOURCE_IRQ,
+ }, {
+ .name = "BATT_HOT",
+ .start = AXP20X_IRQ_BATT_TEMP_HIGH,
+ .end = AXP20X_IRQ_BATT_TEMP_HIGH,
+ .flags = IORESOURCE_IRQ,
+ }, {
+ .name = "BATT_COLD",
+ .start = AXP20X_IRQ_BATT_TEMP_LOW,
+ .end = AXP20X_IRQ_BATT_TEMP_LOW,
+ .flags = IORESOURCE_IRQ,
+ }, {
+ .name = "BATT_CHG_CURR_LOW",
+ .start = AXP20X_IRQ_CHARG_I_LOW,
+ .end = AXP20X_IRQ_CHARG_I_LOW,
+ .flags = IORESOURCE_IRQ,
+ }, {
+ .name = "POWER_LOW_WARN",
+ .start = AXP20X_IRQ_LOW_PWR_LVL1,
+ .end = AXP20X_IRQ_LOW_PWR_LVL1,
+ .flags = IORESOURCE_IRQ,
+ }, {
+ .name = "POWER_LOW_CRIT",
+ .start = AXP20X_IRQ_LOW_PWR_LVL2,
+ .end = AXP20X_IRQ_LOW_PWR_LVL2,
+ .flags = IORESOURCE_IRQ,
+ },
+};
+
static const struct regmap_config axp20x_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
.wr_table = &axp20x_writeable_table,
.volatile_table = &axp20x_volatile_table,
- .max_register = AXP20X_FG_RES,
+ .max_register = AXP20X_OCV(15),
.cache_type = REGCACHE_RBTREE,
};

@@ -158,6 +258,10 @@ static struct mfd_cell axp20x_cells[] = {
.name = "axp20x-regulator",
.parent_supplies = axp20x_supplies,
.num_parent_supplies = ARRAY_SIZE(axp20x_supplies),
+ }, {
+ .name = "axp20x-power",
+ .num_resources = ARRAY_SIZE(axp20x_power_resources),
+ .resources = axp20x_power_resources,
},
};

diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig
index 73cfcdf..209d677 100644
--- a/drivers/power/Kconfig
+++ b/drivers/power/Kconfig
@@ -396,6 +396,15 @@ config BATTERY_GOLDFISH
Say Y to enable support for the battery and AC power in the
Goldfish emulator.

+config AXP20X_POWER
+ tristate "AXP20x power supply driver"
+ depends on MFD_AXP20X
+ help
+ This driver provides support for the power supply features of
+ AXP20x PMIC.
+ Included features are: AC-power, USB-power, Battery charger
+ (RTC backup-battery and Lithium main bettery).
+
source "drivers/power/reset/Kconfig"

endif # POWER_SUPPLY
diff --git a/drivers/power/Makefile b/drivers/power/Makefile
index dfa8942..ab2324f 100644
--- a/drivers/power/Makefile
+++ b/drivers/power/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_power.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_power.c b/drivers/power/axp20x_power.c
new file mode 100644
index 0000000..9d6b8bc
--- /dev/null
+++ b/drivers/power/axp20x_power.c
@@ -0,0 +1,1530 @@
+/*
+ * AC power input driver for X-Powers AXP20x PMICs
+ *
+ * Copyright 2014 Bruno Pr?mont <bonbons@linux-vserver.org>
+ *
+ * This file is subject to the terms and conditions of the GNU General
+ * Public License. See the file "COPYING" in the main directory of this
+ * archive for more details.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/time.h>
+#include <linux/mfd/axp20x.h>
+
+struct axp20x_power {
+ struct axp20x_dev *axp20x;
+ /* RTC / Backup battery */
+ struct power_supply backup;
+ char backup_name[24];
+ /* ACIN power supply */
+ struct power_supply ac;
+ char ac_name[24];
+ /* VBUS/OTG power supply */
+ struct power_supply vbus;
+ char vbus_name[24];
+ /* Battery charger */
+ struct power_supply battery;
+ char battery_name[24];
+ char *battery_supplies[2];
+ /* AXP state tracking */
+ struct work_struct work;
+ spinlock_t lock;
+ struct timespec next_check;
+ uint8_t status1;
+ uint8_t status2;
+ uint8_t vbusmgt;
+ int vvbus;
+ int ivbus;
+ int vac;
+ int iac;
+ int vbatt;
+ int ibatt;
+ int pbatt;
+ int tbatt;
+ int tbatt_min;
+ int tbatt_max;
+ int batt_percent;
+ int batt_capacity;
+ int batt_health;
+ int batt_user_imax;
+};
+
+/* Fields of AXP20X_PWR_INPUT_STATUS */
+#define AXP20X_PWR_STATUS_AC_PRESENT (1 << 7)
+#define AXP20X_PWR_STATUS_AC_AVAILABLE (1 << 6)
+#define AXP20X_PWR_STATUS_VBUS_PRESENT (1 << 5)
+#define AXP20X_PWR_STATUS_VBUS_AVAILABLE (1 << 4)
+#define AXP20X_PWR_STATUS_VBUS_VHOLD (1 << 3)
+#define AXP20X_PWR_STATUS_BAT_CHARGING (1 << 2)
+#define AXP20X_PWR_STATUS_AC_VBUS_SHORT (1 << 1)
+#define AXP20X_PWR_STATUS_AC_VBUS_SEL (1 << 0)
+
+/* Fields of AXP20X_PWR_OP_MODE */
+#define AXP20X_PWR_OP_OVERTEMP (1 << 7)
+#define AXP20X_PWR_OP_CHARGING (1 << 6)
+#define AXP20X_PWR_OP_BATT_PRESENT (1 << 5)
+#define AXP20X_PWR_OP_BATT_ACTIVATED (1 << 3)
+#define AXP20X_PWR_OP_BATT_CHG_CURRENT_LOW (1 << 2)
+
+/* Fields of AXP20X_ADC_EN1 */
+#define AXP20X_ADC_EN1_BATT_V (1 << 7)
+#define AXP20X_ADC_EN1_BATT_C (1 << 6)
+#define AXP20X_ADC_EN1_ACIN_V (1 << 5)
+#define AXP20X_ADC_EN1_ACIN_C (1 << 4)
+#define AXP20X_ADC_EN1_VBUS_V (1 << 3)
+#define AXP20X_ADC_EN1_VBUS_C (1 << 2)
+#define AXP20X_ADC_EN1_APS_V (1 << 1)
+#define AXP20X_ADC_EN1_TEMP (1 << 0)
+
+/* Fields of AXP20X_ADC_RATE */
+#define AXP20X_ADR_RATE_MASK (3 << 6)
+#define AXP20X_ADR_RATE_25Hz (0 << 6)
+#define AXP20X_ADR_RATE_50Hz (1 << 6)
+#define AXP20X_ADR_RATE_100Hz (2 << 6)
+#define AXP20X_ADR_RATE_200Hz (3 << 6)
+#define AXP20X_ADR_TS_CURR_MASK (3 << 4)
+#define AXP20X_ADR_TS_CURR_20uA (0 << 4)
+#define AXP20X_ADR_TS_CURR_40uA (1 << 4)
+#define AXP20X_ADR_TS_CURR_60uA (2 << 4)
+#define AXP20X_ADR_TS_CURR_80uA (3 << 4)
+#define AXP20X_ADR_TS_UNRELATED (1 << 2)
+#define AXP20X_ADR_TS_WHEN_MASK (3 << 0)
+#define AXP20X_ADR_TS_WHEN_OFF (0 << 0)
+#define AXP20X_ADR_TS_WHEN_CHG (1 << 0)
+#define AXP20X_ADR_TS_WHEN_ADC (2 << 0)
+#define AXP20X_ADR_TS_WHEN_ON (3 << 0)
+
+/* Fields of AXP20X_VBUS_IPSOUT_MGMT */
+#define AXP20X_VBUS_VHOLD_MASK (7 << 3)
+#define AXP20X_VBUS_VHOLD_mV(b) (4000000 + (((b) >> 3) & 7) * 100000)
+#define AXP20X_VBUS_CLIMIT_MASK (3)
+#define AXP20X_VBUC_CLIMIT_900mA (0)
+#define AXP20X_VBUC_CLIMIT_500mA (1)
+#define AXP20X_VBUC_CLIMIT_100mA (2)
+#define AXP20X_VBUC_CLIMIT_NONE (3)
+
+/* Fields of AXP20X_OFF_CTRL */
+#define AXP20X_OFF_CTRL_BATT_MON (1 << 6)
+#define AXP20X_OFF_CTRL_CHGLED_MASK (3 << 4)
+#define AXP20X_OFF_CTRL_CHGLED_HR (0 << 4)
+#define AXP20X_OFF_CTRL_CHGLED_1Hz (1 << 4)
+#define AXP20X_OFF_CTRL_CHGLED_4Hz (2 << 4)
+#define AXP20X_OFF_CTRL_CHGLED_LOW (3 << 4)
+#define AXP20X_OFF_CTRL_CHGLED_FIX (1 << 3)
+/* Fields of AXP20X_CHRG_CTRL1 */
+#define AXP20X_CHRG_CTRL1_ENABLE (1 << 7)
+#define AXP20X_CHRG_CTRL1_TGT_VOLT (3 << 5)
+#define AXP20X_CHRG_CTRL1_TGT_4_1V (0 << 5)
+#define AXP20X_CHRG_CTRL1_TGT_4_15V (1 << 5)
+#define AXP20X_CHRG_CTRL1_TGT_4_2V (2 << 5)
+#define AXP20X_CHRG_CTRL1_TGT_4_36V (3 << 5)
+#define AXP20X_CHRG_CTRL1_END_CURR (1 << 4)
+#define AXP20X_CHRG_CTRL1_TGT_CURR 0x0f
+/* Fields of AXP20X_CHRG_CTRL2 */
+#define AXP20X_CHRG_CTRL2_PRE_MASK (3 << 6)
+#define AXP20X_CHRG_CTRL2_PRE_40MIN (0 << 6)
+#define AXP20X_CHRG_CTRL2_PRE_50MIN (1 << 6)
+#define AXP20X_CHRG_CTRL2_PRE_60MIN (2 << 6)
+#define AXP20X_CHRG_CTRL2_PRE_70MIN (3 << 6)
+#define AXP20X_CHRG_CTRL2_CHGLED_FL (1 << 4)
+#define AXP20X_CHRG_CTRL2_CHG_MASK (0 << 6)
+#define AXP20X_CHRG_CTRL2_CHG_6H (0 << 0)
+#define AXP20X_CHRG_CTRL2_CHG_8H (1 << 0)
+#define AXP20X_CHRG_CTRL2_CHG_10H (2 << 6)
+#define AXP20X_CHRG_CTRL2_CHG_12H (3 << 0)
+/* Fields of AXP20X_FG_RES */
+#define AXP20X_FG_ENABLE (1 << 7)
+#define AXP20X_FG_PERCENT (0x7f)
+
+static int axp20x_power_poll(struct axp20x_power *devdata, int init)
+{
+ struct axp20x_dev *axp20x = devdata->axp20x;
+ struct timespec ts;
+ int ret, status1, status2, vbusmgt, adc_cfg, bpercent;
+ uint8_t adc[19];
+
+ getnstimeofday(&ts);
+ /* only query hardware if our data is stale */
+ spin_lock(&devdata->lock);
+ if (!init && !(ts.tv_sec > devdata->next_check.tv_sec ||
+ ts.tv_nsec > devdata->next_check.tv_sec)) {
+ spin_unlock(&devdata->lock);
+ return 0;
+ }
+ spin_unlock(&devdata->lock);
+
+ ret = regmap_read(axp20x->regmap, AXP20X_PWR_INPUT_STATUS, &status1);
+ if (ret)
+ return ret;
+ ret = regmap_read(axp20x->regmap, AXP20X_PWR_OP_MODE, &status2);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(axp20x->regmap, AXP20X_ADC_RATE, &adc_cfg);
+ if (ret)
+ return ret;
+
+ if (init == 2) {
+ int reg = AXP20X_ADC_EN1_VBUS_V | AXP20X_ADC_EN1_VBUS_C;
+
+ if (!(status1 & AXP20X_PWR_STATUS_AC_VBUS_SHORT))
+ reg |= AXP20X_ADC_EN1_ACIN_V | AXP20X_ADC_EN1_ACIN_C;
+ if (devdata->battery_name[0])
+ reg |= AXP20X_ADC_EN1_BATT_V | AXP20X_ADC_EN1_BATT_C;
+ if (devdata->battery_name[0] &&
+ !(adc_cfg & AXP20X_ADR_TS_UNRELATED))
+ reg |= AXP20X_ADC_EN1_TEMP;
+
+ regmap_update_bits(axp20x->regmap, AXP20X_ADC_EN1,
+ AXP20X_ADC_EN1_ACIN_V | AXP20X_ADC_EN1_ACIN_C |
+ AXP20X_ADC_EN1_VBUS_V | AXP20X_ADC_EN1_VBUS_C |
+ AXP20X_ADC_EN1_BATT_V | AXP20X_ADC_EN1_BATT_C |
+ AXP20X_ADC_EN1_TEMP, reg);
+ }
+
+ ret = regmap_read(axp20x->regmap, AXP20X_VBUS_IPSOUT_MGMT, &vbusmgt);
+ if (ret)
+ return ret;
+
+ ret = regmap_bulk_read(axp20x->regmap, AXP20X_ACIN_V_ADC_H, adc, 8);
+ if (ret)
+ return ret;
+ if (devdata->battery_name[0] && !(adc_cfg & AXP20X_ADR_TS_UNRELATED)) {
+ ret = regmap_bulk_read(axp20x->regmap, AXP20X_TS_IN_H, adc+8, 2);
+ if (ret)
+ return ret;
+ }
+ if (devdata->battery_name[0]) {
+ ret = regmap_bulk_read(axp20x->regmap, AXP20X_PWR_BATT_H, adc+10, 3);
+ if (ret)
+ return ret;
+ ret = regmap_bulk_read(axp20x->regmap, AXP20X_BATT_V_H, adc+13, 6);
+ if (ret)
+ return ret;
+ ret = regmap_read(axp20x->regmap, AXP20X_FG_RES, &bpercent);
+ if (ret)
+ return ret;
+ }
+
+ switch (adc_cfg & AXP20X_ADR_RATE_MASK) {
+ case AXP20X_ADR_RATE_200Hz:
+ timespec_add_ns(&ts, 5000000); break;
+ case AXP20X_ADR_RATE_100Hz:
+ timespec_add_ns(&ts, 10000000); break;
+ case AXP20X_ADR_RATE_50Hz:
+ timespec_add_ns(&ts, 20000000); break;
+ case AXP20X_ADR_RATE_25Hz:
+ default:
+ timespec_add_ns(&ts, 40000000);
+ }
+
+ ret = devdata->status1 | (devdata->status2 << 8) |
+ ((devdata->batt_percent & 0x7f) << 16);
+ if (init == 2)
+ timespec_add_ns(&ts, 200000000);
+ spin_lock(&devdata->lock);
+ devdata->vac = ((adc[0] << 4) | (adc[1] & 0x0f)) * 1700;
+ devdata->iac = ((adc[2] << 4) | (adc[3] & 0x0f)) * 625;
+ devdata->vvbus = ((adc[4] << 4) | (adc[5] & 0x0f)) * 1700;
+ devdata->ivbus = ((adc[6] << 4) | (adc[7] & 0x0f)) * 375;
+ devdata->next_check = ts;
+ devdata->vbusmgt = vbusmgt;
+ devdata->status1 = status1;
+ devdata->status2 = status2;
+ if (devdata->battery_name[0] && !(adc_cfg & AXP20X_ADR_TS_UNRELATED))
+ devdata->tbatt = ((adc[8] << 4) | (adc[9] & 0x0f)) * 800;
+ if (devdata->battery_name[0]) {
+ devdata->vbatt = ((adc[13] << 4) | (adc[14] & 0x0f)) * 1100;
+ if (status1 & AXP20X_PWR_STATUS_BAT_CHARGING)
+ devdata->ibatt = ((adc[15] << 4) | (adc[16] & 0x0f));
+ else
+ devdata->ibatt = ((adc[17] << 4) | (adc[18] & 0x0f));
+ devdata->ibatt *= 500;
+ devdata->pbatt = ((adc[10] << 16) | (adc[11] << 8) | adc[12]) *
+ 55 / 100;
+ devdata->batt_percent = bpercent & 0x7f;
+ }
+ spin_unlock(&devdata->lock);
+
+ if (init == 2 || init == 0)
+ return 0;
+
+ if ((ret ^ status1) & (AXP20X_PWR_STATUS_VBUS_PRESENT |
+ AXP20X_PWR_STATUS_VBUS_AVAILABLE))
+ power_supply_changed(&devdata->vbus);
+ if (devdata->ac_name[0]) {
+ } else if ((ret ^ status1) & (AXP20X_PWR_STATUS_AC_PRESENT |
+ AXP20X_PWR_STATUS_AC_AVAILABLE))
+ power_supply_changed(&devdata->ac);
+ if (!devdata->battery_name[0]) {
+ } else if ((ret ^ status1) & AXP20X_PWR_STATUS_BAT_CHARGING) {
+ power_supply_changed(&devdata->battery);
+ } else if (((ret >> 8) ^ status2) & (AXP20X_PWR_OP_CHARGING |
+ AXP20X_PWR_OP_BATT_PRESENT | AXP20X_PWR_OP_BATT_ACTIVATED |
+ AXP20X_PWR_OP_BATT_CHG_CURRENT_LOW)) {
+ power_supply_changed(&devdata->battery);
+ } else if (((ret >> 16) & 0x7f) != (bpercent & 0x7f)) {
+ power_supply_changed(&devdata->battery);
+ }
+ return 0;
+}
+
+static void axp20x_power_monitor(struct work_struct *work)
+{
+ struct axp20x_power *devdata = container_of(work,
+ struct axp20x_power, work);
+
+ axp20x_power_poll(devdata, 1);
+
+ /* TODO: check status for consitency
+ * adjust battery charging parameters as needed
+ */
+}
+
+/* ********************************************** *
+ * *** RTC / Backup battery charger *** *
+ * ********************************************** */
+
+/* Fields of AXP20X_CHRG_BAK_CTRL */
+#define AXP20X_BACKUP_ENABLE (0x01 << 7)
+#define AXP20X_BACKUP_VOLTAGE_MASK (0x03 << 5)
+#define AXP20X_BACKUP_VOLTAGE_3_1V (0x00 << 5)
+#define AXP20X_BACKUP_VOLTAGE_3_0V (0x01 << 5)
+#define AXP20X_BACKUP_VOLTAGE_3_6V (0x02 << 5)
+#define AXP20X_BACKUP_VOLTAGE_2_5V (0x03 << 5)
+#define AXP20X_BACKUP_CURRENT_MASK 0x03
+#define AXP20X_BACKUP_CURRENT_50uA 0x00
+#define AXP20X_BACKUP_CURRENT_100uA 0x01
+#define AXP20X_BACKUP_CURRENT_200uA 0x02
+#define AXP20X_BACKUP_CURRENT_400uA 0x03
+
+static int axp20x_backup_config(struct platform_device *pdev,
+ struct axp20x_dev *axp20x)
+{
+ struct device_node *np;
+ int ret = 0, reg, new_reg = 0;
+ u32 lim[2];
+
+ ret = regmap_read(axp20x->regmap, AXP20X_CHRG_BAK_CTRL, &reg);
+ if (ret)
+ return ret;
+
+ np = of_node_get(axp20x->dev->of_node);
+ if (!np)
+ return -ENODEV;
+
+ ret = of_property_read_u32_array(np, "backup", lim, 2);
+ if (ret != 0)
+ goto err;
+
+ switch (lim[0]) {
+ case 2500000:
+ new_reg |= AXP20X_BACKUP_VOLTAGE_2_5V;
+ break;
+ case 3000000:
+ new_reg |= AXP20X_BACKUP_VOLTAGE_3_0V;
+ break;
+ case 3100000:
+ new_reg |= AXP20X_BACKUP_VOLTAGE_3_1V;
+ break;
+ case 3600000:
+ new_reg |= AXP20X_BACKUP_VOLTAGE_3_6V;
+ break;
+ default:
+ dev_warn(&pdev->dev, "Invalid backup DT voltage limit %u\n", lim[0]);
+ ret = -EINVAL;
+ goto err;
+ }
+ switch (lim[1]) {
+ case 50:
+ new_reg |= AXP20X_BACKUP_CURRENT_50uA;
+ break;
+ case 100:
+ new_reg |= AXP20X_BACKUP_CURRENT_100uA;
+ break;
+ case 200:
+ new_reg |= AXP20X_BACKUP_CURRENT_200uA;
+ break;
+ case 400:
+ new_reg |= AXP20X_BACKUP_CURRENT_400uA;
+ break;
+ default:
+ dev_warn(&pdev->dev, "Invalid backup DT current limit %u\n", lim[1]);
+ ret = -EINVAL;
+ goto err;
+ }
+ new_reg |= AXP20X_BACKUP_ENABLE;
+
+ ret = regmap_update_bits(axp20x->regmap, AXP20X_CHRG_BAK_CTRL,
+ AXP20X_BACKUP_ENABLE | AXP20X_BACKUP_VOLTAGE_MASK |
+ AXP20X_BACKUP_CURRENT_MASK, new_reg);
+ if (ret)
+ dev_warn(&pdev->dev, "Failed to adjust backup battery settings: %d\n", ret);
+
+err:
+ of_node_put(np);
+ return ret;
+}
+
+static int axp20x_backup_get_prop(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct axp20x_power *devdata = dev_get_drvdata(psy->dev->parent);
+ int ret = 0, reg;
+
+ ret = regmap_read(devdata->axp20x->regmap, AXP20X_CHRG_BAK_CTRL, &reg);
+ if (ret < 0)
+ return ret;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ if ((reg & AXP20X_BACKUP_ENABLE))
+ val->intval = POWER_SUPPLY_STATUS_CHARGING;
+ else
+ val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ break;
+
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
+ switch ((reg & AXP20X_BACKUP_VOLTAGE_MASK)) {
+ case AXP20X_BACKUP_VOLTAGE_2_5V:
+ val->intval = 2500000; break;
+ case AXP20X_BACKUP_VOLTAGE_3_0V:
+ val->intval = 3000000; break;
+ case AXP20X_BACKUP_VOLTAGE_3_1V:
+ val->intval = 3100000; break;
+ case AXP20X_BACKUP_VOLTAGE_3_6V:
+ val->intval = 3600000; break;
+ default:
+ val->intval = 0;
+ }
+ break;
+
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
+ switch ((reg & AXP20X_BACKUP_CURRENT_MASK)) {
+ case AXP20X_BACKUP_CURRENT_50uA:
+ val->intval = 50; break;
+ case AXP20X_BACKUP_CURRENT_100uA:
+ val->intval = 100; break;
+ case AXP20X_BACKUP_CURRENT_200uA:
+ val->intval = 200; break;
+ case AXP20X_BACKUP_CURRENT_400uA:
+ val->intval = 400; break;
+ default:
+ val->intval = 0;
+ }
+ break;
+
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static int axp20x_backup_set_prop(struct power_supply *psy,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ struct axp20x_power *devdata = dev_get_drvdata(psy->dev->parent);
+ int ret;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ if (val->intval == POWER_SUPPLY_STATUS_CHARGING)
+ ret = regmap_update_bits(devdata->axp20x->regmap,
+ AXP20X_CHRG_BAK_CTRL,
+ AXP20X_BACKUP_ENABLE,
+ AXP20X_BACKUP_ENABLE);
+ else if (val->intval == POWER_SUPPLY_STATUS_NOT_CHARGING)
+ ret = regmap_update_bits(devdata->axp20x->regmap,
+ AXP20X_CHRG_BAK_CTRL,
+ AXP20X_BACKUP_ENABLE, 0);
+ else
+ ret = -EINVAL;
+ break;
+
+ default:
+ ret = -EINVAL;
+ }
+ return ret;
+}
+
+static int axp20x_backup_prop_writeable(struct power_supply *psy,
+ enum power_supply_property psp)
+{
+ return psp == POWER_SUPPLY_PROP_STATUS;
+}
+
+static enum power_supply_property axp20x_backup_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT,
+};
+
+/* ********************************************** *
+ * *** ACIN power supply *** *
+ * ********************************************** */
+
+static int axp20x_ac_get_prop(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct axp20x_power *devdata = dev_get_drvdata(psy->dev->parent);
+ int ret;
+
+ ret = axp20x_power_poll(devdata, 0);
+ if (ret)
+ return ret;
+
+ spin_lock(&devdata->lock);
+ switch (psp) {
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = !!(devdata->status1 & AXP20X_PWR_STATUS_AC_PRESENT);
+ break;
+
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = !!(devdata->status1 & AXP20X_PWR_STATUS_AC_AVAILABLE);
+ break;
+
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ val->intval = devdata->vac;
+ break;
+
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ val->intval = devdata->iac;
+ break;
+
+ default:
+ ret = -EINVAL;
+ }
+ spin_unlock(&devdata->lock);
+
+ return ret;
+}
+
+static enum power_supply_property axp20x_ac_props[] = {
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+};
+
+/* ********************************************** *
+ * *** VBUS power supply *** *
+ * ********************************************** */
+
+static int axp20x_vbus_get_prop(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct axp20x_power *devdata = dev_get_drvdata(psy->dev->parent);
+ int ret;
+
+ ret = axp20x_power_poll(devdata, 0);
+ if (ret)
+ return ret;
+
+ spin_lock(&devdata->lock);
+ switch (psp) {
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = !!(devdata->status1 & AXP20X_PWR_STATUS_VBUS_PRESENT);
+ break;
+
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = !!(devdata->status1 & AXP20X_PWR_STATUS_VBUS_AVAILABLE);
+ break;
+
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ val->intval = devdata->vvbus;
+ break;
+
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ val->intval = devdata->ivbus;
+ break;
+
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ switch (devdata->vbusmgt & AXP20X_VBUS_CLIMIT_MASK) {
+ case AXP20X_VBUC_CLIMIT_100mA:
+ val->intval = 100000; break;
+ case AXP20X_VBUC_CLIMIT_500mA:
+ val->intval = 500000; break;
+ case AXP20X_VBUC_CLIMIT_900mA:
+ val->intval = 900000; break;
+ case AXP20X_VBUC_CLIMIT_NONE:
+ default:
+ val->intval = -1;
+ }
+ break;
+
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN:
+ val->intval = AXP20X_VBUS_VHOLD_mV(devdata->vbusmgt);
+ break;
+
+ default:
+ ret = -EINVAL;
+ }
+ spin_unlock(&devdata->lock);
+
+ return ret;
+}
+
+static int axp20x_vbus_set_prop(struct power_supply *psy,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ struct axp20x_power *devdata = dev_get_drvdata(psy->dev->parent);
+ int ret, reg;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ if (val->intval == 100000)
+ reg = AXP20X_VBUC_CLIMIT_100mA;
+ else if (val->intval == 500000)
+ reg = AXP20X_VBUC_CLIMIT_500mA;
+ else if (val->intval == 900000)
+ reg = AXP20X_VBUC_CLIMIT_900mA;
+ else if (val->intval == -1)
+ reg = AXP20X_VBUC_CLIMIT_NONE;
+ else {
+ ret = -EINVAL;
+ break;
+ }
+ regmap_update_bits(devdata->axp20x->regmap,
+ AXP20X_VBUS_IPSOUT_MGMT,
+ AXP20X_VBUS_CLIMIT_MASK, reg);
+ spin_lock(&devdata->lock);
+ devdata->vbusmgt = (devdata->vbusmgt & ~AXP20X_VBUS_CLIMIT_MASK) |
+ (reg & AXP20X_VBUS_CLIMIT_MASK);
+ spin_unlock(&devdata->lock);
+ ret = 0;
+ break;
+
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN:
+ if (val->intval < 4000000) {
+ ret = -EINVAL;
+ break;
+ } else
+ reg = val->intval / 100000;
+ if ((reg & 7) != reg) {
+ ret = -EINVAL;
+ break;
+ } else
+ reg = reg << 3;
+ regmap_update_bits(devdata->axp20x->regmap,
+ AXP20X_VBUS_IPSOUT_MGMT,
+ AXP20X_VBUS_VHOLD_MASK, reg);
+ spin_lock(&devdata->lock);
+ devdata->vbusmgt = (devdata->vbusmgt & ~AXP20X_VBUS_VHOLD_MASK) |
+ (reg & AXP20X_VBUS_VHOLD_MASK);
+ spin_unlock(&devdata->lock);
+ ret = 0;
+ break;
+
+ default:
+ ret = -EINVAL;
+ }
+ return ret;
+}
+
+static enum power_supply_property axp20x_vbus_props[] = {
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ POWER_SUPPLY_PROP_VOLTAGE_MIN,
+ POWER_SUPPLY_PROP_CURRENT_MAX,
+};
+
+static int axp20x_vbus_prop_writeable(struct power_supply *psy,
+ enum power_supply_property psp)
+{
+ return psp == POWER_SUPPLY_PROP_VOLTAGE_MIN ||
+ psp == POWER_SUPPLY_PROP_CURRENT_MAX;
+}
+
+
+/* ********************************************** *
+ * *** main battery charger *** *
+ * ********************************************** */
+
+static void axp20x_battery_chg_reconfig(struct power_supply *psy);
+
+static int axp20x_battery_config(struct platform_device *pdev,
+ struct axp20x_power *devdata,
+ struct axp20x_dev *axp20x)
+{
+ struct device_node *np;
+ int i, ret = 0, reg, new_reg = 0;
+ u32 ocv[16], temp[3], rdc, capa;
+
+ ret = regmap_read(axp20x->regmap, AXP20X_PWR_OP_MODE, &reg);
+ if (ret)
+ return ret;
+
+ np = of_node_get(axp20x->dev->of_node);
+ if (!np)
+ return -ENODEV;
+
+ ret = of_property_read_u32_array(np, "battery.ocv", ocv, 16);
+ for (i = 0; ret == 0 && i < ARRAY_SIZE(ocv); i++)
+ if (ocv[i] > 100) {
+ dev_warn(&pdev->dev, "OCV[%d] %u > 100\n", i, ocv[i]);
+ ret = -EINVAL;
+ goto err;
+ }
+
+ ret = of_property_read_u32_array(np, "battery.resistance", &rdc, 1);
+ if (ret != 0)
+ rdc = 100;
+
+ ret = of_property_read_u32_array(np, "battery.capacity", &capa, 1);
+ if (ret != 0)
+ capa = 0;
+
+ ret = of_property_read_u32_array(np, "battery.temp_sensor", temp, 3);
+ if (ret != 0)
+ memset(temp, 0, sizeof(temp));
+ else if (temp[0] != 20 && temp[0] != 40 && temp[0] != 60 &&
+ temp[0] != 80) {
+ dev_warn(&pdev->dev, "Invalid battery temperature sensor current setting\n");
+ ret = -EINVAL;
+ memset(temp, 0, sizeof(temp));
+ }
+
+ dev_info(&pdev->dev, "FDT settings: capacity=%d, resistance=%d, temp_sensor=<%d %d %d>\n", capa, rdc, temp[0], temp[1], temp[2]);
+ /* apply settings */
+ devdata->batt_health = POWER_SUPPLY_HEALTH_UNKNOWN;
+ regmap_update_bits(axp20x->regmap, AXP20X_FG_RES, AXP20X_FG_ENABLE, 0x00);
+ regmap_update_bits(axp20x->regmap, AXP20X_RDC_H, 0x80, 0x00);
+ regmap_update_bits(axp20x->regmap, AXP20X_RDC_L, 0xff, (rdc * 10000 + 5371) / 10742);
+ regmap_update_bits(axp20x->regmap, AXP20X_RDC_H, 0x1f, ((rdc * 10000 + 5371) / 10742) >> 8);
+ if (of_find_property(np, "battery.ocv", NULL))
+ for (i = 0; i < ARRAY_SIZE(ocv); i++) {
+ ret = regmap_update_bits(axp20x->regmap, AXP20X_OCV(i),
+ 0xff, ocv[i]);
+ if (ret)
+ dev_warn(&pdev->dev,
+ "Failed to store OCV[%d] setting: %d\n",
+ i, ret);
+ }
+ regmap_update_bits(axp20x->regmap, AXP20X_FG_RES, AXP20X_FG_ENABLE, AXP20X_FG_ENABLE);
+
+ if (capa == 0 && !(reg & AXP20X_PWR_OP_BATT_PRESENT)) {
+ /* No battery present or configured -> disable */
+ regmap_update_bits(axp20x->regmap, AXP20X_CHRG_CTRL1, AXP20X_CHRG_CTRL1_ENABLE, 0x00);
+ regmap_update_bits(axp20x->regmap, AXP20X_OFF_CTRL, AXP20X_OFF_CTRL_BATT_MON, 0x00);
+ dev_info(&pdev->dev, "No battery, disabling charger\n");
+ ret = -ENODEV;
+ goto err;
+ }
+
+ if (temp[0] == 0) {
+ regmap_update_bits(axp20x->regmap, AXP20X_ADC_RATE,
+ AXP20X_ADR_TS_WHEN_MASK |
+ AXP20X_ADR_TS_UNRELATED,
+ AXP20X_ADR_TS_UNRELATED |
+ AXP20X_ADR_TS_WHEN_OFF);
+ } else {
+ devdata->tbatt_min = temp[1];
+ devdata->tbatt_max = temp[2];
+ switch (temp[0]) {
+ case 20:
+ regmap_update_bits(axp20x->regmap, AXP20X_ADC_RATE,
+ AXP20X_ADR_TS_CURR_MASK |
+ AXP20X_ADR_TS_WHEN_MASK |
+ AXP20X_ADR_TS_UNRELATED,
+ AXP20X_ADR_TS_CURR_20uA |
+ AXP20X_ADR_TS_WHEN_ADC);
+ break;
+ case 40:
+ regmap_update_bits(axp20x->regmap, AXP20X_ADC_RATE,
+ AXP20X_ADR_TS_CURR_MASK |
+ AXP20X_ADR_TS_WHEN_MASK |
+ AXP20X_ADR_TS_UNRELATED,
+ AXP20X_ADR_TS_CURR_40uA |
+ AXP20X_ADR_TS_WHEN_ADC);
+ break;
+ case 60:
+ regmap_update_bits(axp20x->regmap, AXP20X_ADC_RATE,
+ AXP20X_ADR_TS_CURR_MASK |
+ AXP20X_ADR_TS_WHEN_MASK |
+ AXP20X_ADR_TS_UNRELATED,
+ AXP20X_ADR_TS_CURR_60uA |
+ AXP20X_ADR_TS_WHEN_ADC);
+ break;
+ case 80:
+ regmap_update_bits(axp20x->regmap, AXP20X_ADC_RATE,
+ AXP20X_ADR_TS_CURR_MASK |
+ AXP20X_ADR_TS_WHEN_MASK |
+ AXP20X_ADR_TS_UNRELATED,
+ AXP20X_ADR_TS_CURR_80uA |
+ AXP20X_ADR_TS_WHEN_ADC);
+ break;
+ }
+ new_reg = temp[1] / (0x10 * 800);
+ regmap_update_bits(axp20x->regmap, AXP20X_V_HTF_CHRG, 0xff,
+ new_reg);
+ regmap_update_bits(axp20x->regmap, AXP20X_V_HTF_DISCHRG, 0xff,
+ new_reg);
+ new_reg = temp[2] / (0x10 * 800);
+ regmap_update_bits(axp20x->regmap, AXP20X_V_LTF_CHRG, 0xff,
+ new_reg);
+ regmap_update_bits(axp20x->regmap, AXP20X_V_LTF_DISCHRG, 0xff,
+ new_reg);
+ }
+ devdata->batt_capacity = capa * 1000;
+ devdata->batt_user_imax = (capa < 300 ? 300 : capa) * 1000;
+ /* Prefer longer battery life over longer runtime. */
+ regmap_update_bits(devdata->axp20x->regmap, AXP20X_CHRG_CTRL1,
+ AXP20X_CHRG_CTRL1_TGT_VOLT,
+ AXP20X_CHRG_CTRL1_TGT_4_15V);
+
+ /* TODO: configure CHGLED? */
+
+ /* Default to about 5% capacity, about 3.5V */
+ regmap_update_bits(axp20x->regmap, AXP20X_APS_WARN_L1, 0xff,
+ (3500000 - 2867200) / 4 / 1400);
+ regmap_update_bits(axp20x->regmap, AXP20X_APS_WARN_L2, 0xff,
+ (3304000 - 2867200) / 4 / 1400);
+ /* RDC - disable capacity monitor, reconfigure, re-enable */
+ regmap_update_bits(axp20x->regmap, AXP20X_FG_RES, 0x80, 0x80);
+ regmap_update_bits(axp20x->regmap, AXP20X_RDC_H, 0x80, 0x00);
+ regmap_update_bits(axp20x->regmap, AXP20X_RDC_H, 0x1f, ((rdc * 10000 + 5371) / 10742) >> 8);
+ regmap_update_bits(axp20x->regmap, AXP20X_RDC_L, 0xff, (rdc * 10000 + 5371) / 10742);
+ regmap_update_bits(axp20x->regmap, AXP20X_FG_RES, 0x80, 0x00);
+ regmap_update_bits(axp20x->regmap, AXP20X_OFF_CTRL, AXP20X_OFF_CTRL_BATT_MON, AXP20X_OFF_CTRL_BATT_MON);
+ axp20x_battery_chg_reconfig(&devdata->battery);
+ ret = 0;
+
+err:
+ of_node_put(np);
+ return ret;
+}
+
+static int axp20x_battery_uv_to_temp(struct axp20x_power *devdata, int uv)
+{
+ /* TODO: convert ?V to ?C */
+ return uv;
+}
+
+static int axp20x_battery_get_prop(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct axp20x_power *devdata = dev_get_drvdata(psy->dev->parent);
+ int ret, reg;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ ret = regmap_read(devdata->axp20x->regmap, AXP20X_CHRG_CTRL1,
+ &reg);
+ if (ret)
+ return ret;
+ val->intval = (reg & AXP20X_CHRG_CTRL1_TGT_CURR) * 100000 +
+ 300000;
+ return 0;
+
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
+ ret = regmap_read(devdata->axp20x->regmap, AXP20X_CHRG_CTRL1,
+ &reg);
+ if (ret)
+ return ret;
+ switch (reg & AXP20X_CHRG_CTRL1_TGT_VOLT) {
+ case AXP20X_CHRG_CTRL1_TGT_4_1V:
+ val->intval = 4100000;
+ break;
+ case AXP20X_CHRG_CTRL1_TGT_4_15V:
+ val->intval = 4150000;
+ break;
+ case AXP20X_CHRG_CTRL1_TGT_4_2V:
+ val->intval = 4200000;
+ break;
+ case AXP20X_CHRG_CTRL1_TGT_4_36V:
+ val->intval = 4360000;
+ break;
+ default:
+ ret = -EINVAL;
+ }
+ return 0;
+
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
+ ret = regmap_read(devdata->axp20x->regmap, AXP20X_APS_WARN_L2,
+ &reg);
+ if (ret)
+ return ret;
+ val->intval = 2867200 + 1400 * reg * 4;
+ return 0;
+
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
+ val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
+ return 0;
+
+ default:
+ break;
+ }
+
+ ret = axp20x_power_poll(devdata, 0);
+ if (ret)
+ return ret;
+
+ spin_lock(&devdata->lock);
+ switch (psp) {
+ case POWER_SUPPLY_PROP_PRESENT:
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = !!(devdata->status2 & AXP20X_PWR_OP_BATT_PRESENT);
+ break;
+
+ case POWER_SUPPLY_PROP_STATUS:
+ if (devdata->status1 & AXP20X_PWR_STATUS_BAT_CHARGING)
+ val->intval = POWER_SUPPLY_STATUS_CHARGING;
+ else if (devdata->ibatt == 0 && devdata->batt_percent == 100)
+ val->intval = POWER_SUPPLY_STATUS_FULL;
+ else if (devdata->ibatt == 0)
+ val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ else
+ val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+ break;
+
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ val->intval = devdata->ibatt;
+ break;
+
+ case POWER_SUPPLY_PROP_HEALTH:
+ val->intval = POWER_SUPPLY_HEALTH_UNKNOWN;
+ // POWER_SUPPLY_HEALTH_GOOD, POWER_SUPPLY_HEALTH_OVERHEAT, POWER_SUPPLY_HEALTH_DEAD, POWER_SUPPLY_HEALTH_OVERVOLTAGE, POWER_SUPPLY_HEALTH_UNSPEC_FAILURE, POWER_SUPPLY_HEALTH_COLD, POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE
+ break;
+
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ val->intval = devdata->vbatt;
+ break;
+
+ case POWER_SUPPLY_PROP_POWER_NOW:
+ val->intval = devdata->pbatt;
+ break;
+
+ case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
+ val->intval = devdata->batt_capacity;
+ break;
+
+ case POWER_SUPPLY_PROP_CHARGE_NOW:
+ /* TODO */
+ val->intval = 12345;
+ break;
+
+ case POWER_SUPPLY_PROP_CAPACITY:
+ val->intval = devdata->batt_percent;
+ break;
+
+ case POWER_SUPPLY_PROP_TEMP:
+ val->intval = axp20x_battery_uv_to_temp(devdata,
+ devdata->tbatt);
+ break;
+
+ case POWER_SUPPLY_PROP_TEMP_ALERT_MIN:
+ val->intval = axp20x_battery_uv_to_temp(devdata,
+ devdata->tbatt_min);
+ break;
+
+ case POWER_SUPPLY_PROP_TEMP_ALERT_MAX:
+ val->intval = axp20x_battery_uv_to_temp(devdata,
+ devdata->tbatt_max);
+ break;
+
+ default:
+ ret = -EINVAL;
+ }
+ spin_unlock(&devdata->lock);
+
+ return ret;
+}
+
+static int axp20x_battery_max_chg_current(struct axp20x_power *devdata)
+{
+ if ((devdata->status1 & AXP20X_PWR_STATUS_AC_PRESENT) &&
+ (devdata->status1 & AXP20X_PWR_STATUS_AC_AVAILABLE)) {
+ /* AC available - unrestricted power */
+ return devdata->batt_capacity / 2;
+ } else if ((devdata->status1 & AXP20X_PWR_STATUS_VBUS_PRESENT) &&
+ (devdata->status1 & AXP20X_PWR_STATUS_VBUS_AVAILABLE)) {
+ /* VBUS available - limited power */
+ switch (devdata->vbusmgt & AXP20X_VBUS_CLIMIT_MASK) {
+ case AXP20X_VBUC_CLIMIT_100mA:
+ return 0;
+ case AXP20X_VBUC_CLIMIT_500mA:
+ return 300000;
+ case AXP20X_VBUC_CLIMIT_900mA:
+ return 600000;
+ case AXP20X_VBUC_CLIMIT_NONE:
+ return devdata->batt_capacity / 2;
+ default:
+ return 0;
+ }
+ } else {
+ /* on-battery */
+ return 0;
+ }
+}
+
+static int axp20x_battery_set_prop(struct power_supply *psy,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ struct axp20x_power *devdata = dev_get_drvdata(psy->dev->parent);
+ int ret;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ if (val->intval == POWER_SUPPLY_STATUS_CHARGING) {
+ ret = axp20x_battery_max_chg_current(devdata);
+ if (ret == 0) {
+ ret = -EBUSY;
+ break;
+ }
+ ret = regmap_update_bits(devdata->axp20x->regmap,
+ AXP20X_PWR_OP_MODE,
+ AXP20X_PWR_OP_CHARGING,
+ AXP20X_PWR_OP_CHARGING);
+ if (ret == 0)
+ axp20x_battery_chg_reconfig(&devdata->battery);
+ } else if (val->intval == POWER_SUPPLY_STATUS_NOT_CHARGING) {
+ ret = regmap_update_bits(devdata->axp20x->regmap,
+ AXP20X_PWR_OP_MODE,
+ AXP20X_PWR_OP_CHARGING, 0);
+ } else
+ ret = -EINVAL;
+ break;
+
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
+ /* TODO: adjust AXP20X_APS_WARN_L1 and AXP20X_APS_WARN_L2 accordingly */
+ ret = -EINVAL;
+ break;
+
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
+ switch (val->intval) {
+ case 4100000:
+ ret = regmap_update_bits(devdata->axp20x->regmap,
+ AXP20X_CHRG_CTRL1,
+ AXP20X_CHRG_CTRL1_TGT_VOLT,
+ AXP20X_CHRG_CTRL1_TGT_4_1V);
+ break;
+ case 4150000:
+ ret = regmap_update_bits(devdata->axp20x->regmap,
+ AXP20X_CHRG_CTRL1,
+ AXP20X_CHRG_CTRL1_TGT_VOLT,
+ AXP20X_CHRG_CTRL1_TGT_4_15V);
+ break;
+ case 4200000:
+ ret = regmap_update_bits(devdata->axp20x->regmap,
+ AXP20X_CHRG_CTRL1,
+ AXP20X_CHRG_CTRL1_TGT_VOLT,
+ AXP20X_CHRG_CTRL1_TGT_4_2V);
+ break;
+ case 4360000:
+ /* refuse this as it's too much for Li-ion! */
+ default:
+ ret = -EINVAL;
+ }
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ if (((val->intval - 300000) / 100000) > 0x0f)
+ ret = -EINVAL;
+ else if (val->intval < 300000)
+ ret = -EINVAL;
+ else {
+ devdata->batt_user_imax = val->intval;
+ axp20x_battery_chg_reconfig(&devdata->battery);
+ ret = 0;
+ }
+ break;
+
+ default:
+ ret = -EINVAL;
+ }
+ return ret;
+}
+
+static enum power_supply_property axp20x_battery_props[] = {
+ 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_prop_writeable(struct power_supply *psy,
+ enum power_supply_property psp)
+{
+ return psp == POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN ||
+ psp == POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN ||
+ psp == POWER_SUPPLY_PROP_CURRENT_MAX ||
+ psp == POWER_SUPPLY_PROP_STATUS;
+}
+
+static void axp20x_battery_chg_reconfig(struct power_supply *psy)
+{
+ struct axp20x_power *devdata = container_of(psy,
+ struct axp20x_power, battery);
+ int charge_max, ret;
+
+ ret = axp20x_power_poll(devdata, 0);
+ if (ret)
+ return;
+
+ charge_max = axp20x_battery_max_chg_current(devdata);
+
+ if (charge_max == 0) {
+ ret = regmap_update_bits(devdata->axp20x->regmap,
+ AXP20X_PWR_OP_MODE,
+ AXP20X_PWR_OP_CHARGING, 0);
+ } else {
+ if (devdata->batt_user_imax < charge_max)
+ charge_max = devdata->batt_user_imax;
+ if (((charge_max - 300000) / 100000) > 0x0f)
+ charge_max = 300000 + 0x0f * 100000;
+ ret = regmap_update_bits(devdata->axp20x->regmap,
+ AXP20X_CHRG_CTRL1,
+ AXP20X_CHRG_CTRL1_TGT_CURR,
+ (charge_max - 300000) / 100000);
+ ret = regmap_update_bits(devdata->axp20x->regmap,
+ AXP20X_PWR_OP_MODE,
+ AXP20X_PWR_OP_CHARGING,
+ AXP20X_PWR_OP_CHARGING);
+ }
+}
+
+
+
+/* ********************************************** *
+ * *** IRQ handlers *** *
+ * ********************************************** */
+
+static irqreturn_t axp20x_irq_ac_over_v(int irq, void *pwr)
+{
+ struct platform_device *pdev = pwr;
+ struct axp20x_power *devdata = platform_get_drvdata(pdev);
+
+ dev_warn(&pdev->dev, "IRQ#%d AC over voltage\n", irq);
+ schedule_work(&devdata->work);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t axp20x_irq_ac_plugin(int irq, void *pwr)
+{
+ struct platform_device *pdev = pwr;
+ struct axp20x_power *devdata = platform_get_drvdata(pdev);
+
+ dev_info(&pdev->dev, "IRQ#%d AC connected\n", irq);
+ schedule_work(&devdata->work);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t axp20x_irq_ac_removal(int irq, void *pwr)
+{
+ struct platform_device *pdev = pwr;
+ struct axp20x_power *devdata = platform_get_drvdata(pdev);
+
+ dev_info(&pdev->dev, "IRQ#%d AC disconnected\n", irq);
+ schedule_work(&devdata->work);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t axp20x_irq_vbus_over_v(int irq, void *pwr)
+{
+ struct platform_device *pdev = pwr;
+ struct axp20x_power *devdata = platform_get_drvdata(pdev);
+
+ dev_warn(&pdev->dev, "IRQ#%d VBUS over voltage\n", irq);
+ schedule_work(&devdata->work);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t axp20x_irq_vbus_plugin(int irq, void *pwr)
+{
+ struct platform_device *pdev = pwr;
+ struct axp20x_power *devdata = platform_get_drvdata(pdev);
+
+ dev_info(&pdev->dev, "IRQ#%d VBUS connected\n", irq);
+ schedule_work(&devdata->work);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t axp20x_irq_vbus_removal(int irq, void *pwr)
+{
+ struct platform_device *pdev = pwr;
+ struct axp20x_power *devdata = platform_get_drvdata(pdev);
+
+ dev_info(&pdev->dev, "IRQ#%d VBUS disconnected\n", irq);
+ schedule_work(&devdata->work);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t axp20x_irq_vbus_v_low(int irq, void *pwr)
+{
+ struct platform_device *pdev = pwr;
+ struct axp20x_power *devdata = platform_get_drvdata(pdev);
+
+ dev_warn(&pdev->dev, "IRQ#%d VBUS low voltage\n", irq);
+ schedule_work(&devdata->work);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t axp20x_irq_batt_plugin(int irq, void *pwr)
+{
+ struct platform_device *pdev = pwr;
+ struct axp20x_power *devdata = platform_get_drvdata(pdev);
+
+ dev_info(&pdev->dev, "IRQ#%d Battery connected\n", irq);
+ schedule_work(&devdata->work);
+ return IRQ_HANDLED;
+}
+static irqreturn_t axp20x_irq_batt_removal(int irq, void *pwr)
+{
+ struct platform_device *pdev = pwr;
+ struct axp20x_power *devdata = platform_get_drvdata(pdev);
+
+ dev_info(&pdev->dev, "IRQ#%d Battery disconnected\n", irq);
+ schedule_work(&devdata->work);
+ return IRQ_HANDLED;
+}
+static irqreturn_t axp20x_irq_batt_activation(int irq, void *pwr)
+{
+ struct platform_device *pdev = pwr;
+ struct axp20x_power *devdata = platform_get_drvdata(pdev);
+
+ dev_info(&pdev->dev, "IRQ#%d Battery activation started\n", irq);
+ schedule_work(&devdata->work);
+ return IRQ_HANDLED;
+}
+static irqreturn_t axp20x_irq_batt_activated(int irq, void *pwr)
+{
+ struct platform_device *pdev = pwr;
+ struct axp20x_power *devdata = platform_get_drvdata(pdev);
+
+ dev_info(&pdev->dev, "IRQ#%d Battery activation completed\n", irq);
+ schedule_work(&devdata->work);
+ return IRQ_HANDLED;
+}
+static irqreturn_t axp20x_irq_batt_charging(int irq, void *pwr)
+{
+ struct platform_device *pdev = pwr;
+ struct axp20x_power *devdata = platform_get_drvdata(pdev);
+
+ dev_info(&pdev->dev, "IRQ#%d Battery charging\n", irq);
+ schedule_work(&devdata->work);
+ return IRQ_HANDLED;
+}
+static irqreturn_t axp20x_irq_batt_charged(int irq, void *pwr)
+{
+ struct platform_device *pdev = pwr;
+ struct axp20x_power *devdata = platform_get_drvdata(pdev);
+
+ dev_info(&pdev->dev, "IRQ#%d Battery charged\n", irq);
+ schedule_work(&devdata->work);
+ return IRQ_HANDLED;
+}
+static irqreturn_t axp20x_irq_batt_high_temp(int irq, void *pwr)
+{
+ struct platform_device *pdev = pwr;
+ struct axp20x_power *devdata = platform_get_drvdata(pdev);
+
+ dev_warn(&pdev->dev, "IRQ#%d Battery temperature high\n", irq);
+ schedule_work(&devdata->work);
+ return IRQ_HANDLED;
+}
+static irqreturn_t axp20x_irq_batt_low_temp(int irq, void *pwr)
+{
+ struct platform_device *pdev = pwr;
+ struct axp20x_power *devdata = platform_get_drvdata(pdev);
+
+ dev_warn(&pdev->dev, "IRQ#%d Battery temperature low\n", irq);
+ schedule_work(&devdata->work);
+ return IRQ_HANDLED;
+}
+static irqreturn_t axp20x_irq_batt_chg_curr_low(int irq, void *pwr)
+{
+ struct platform_device *pdev = pwr;
+ struct axp20x_power *devdata = platform_get_drvdata(pdev);
+
+ dev_warn(&pdev->dev, "IRQ#%d External power too weak for target charging current!\n", irq);
+ schedule_work(&devdata->work);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t axp20x_irq_power_low(int irq, void *pwr)
+{
+ struct platform_device *pdev = pwr;
+ struct axp20x_power *devdata = platform_get_drvdata(pdev);
+
+ dev_warn(&pdev->dev, "IRQ#%d System power running out soon\n", irq);
+ schedule_work(&devdata->work);
+ return IRQ_HANDLED;
+}
+static irqreturn_t axp20x_irq_power_low_crit(int irq, void *pwr)
+{
+ struct platform_device *pdev = pwr;
+ struct axp20x_power *devdata = platform_get_drvdata(pdev);
+
+ dev_crit(&pdev->dev, "IRQ#%d System power running out now!\n", irq);
+ schedule_work(&devdata->work);
+ return IRQ_HANDLED;
+}
+
+/* ********************************************** *
+ * *** Platform driver code *** *
+ * ********************************************** */
+
+static int axp20x_init_irq(struct platform_device *pdev,
+ struct axp20x_dev *axp20x, const char *irq_name,
+ const char *dev_name, irq_handler_t handler)
+{
+ int irq = platform_get_irq_byname(pdev, irq_name);
+ int ret;
+
+ if (irq < 0) {
+ dev_warn(&pdev->dev, "No IRQ for %s: %d\n", irq_name, irq);
+ return irq;
+ }
+ irq = regmap_irq_get_virq(axp20x->regmap_irqc, irq);
+
+ ret = devm_request_any_context_irq(&pdev->dev, irq, handler, 0,
+ dev_name, pdev);
+ if (ret < 0)
+ dev_warn(&pdev->dev, "Failed to request %s IRQ#%d: %d\n", irq_name, irq, ret);
+ return ret;
+}
+
+static int axp20x_power_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ struct axp20x_power *devdata = platform_get_drvdata(pdev);
+
+ cancel_work_sync(&devdata->work);
+ return 0;
+}
+
+static int axp20x_power_resume(struct platform_device *pdev)
+{
+ struct axp20x_power *devdata = platform_get_drvdata(pdev);
+
+ axp20x_power_poll(devdata, 1);
+ return 0;
+}
+
+static void axp20x_power_shutdown(struct platform_device *pdev)
+{
+ struct axp20x_power *devdata = platform_get_drvdata(pdev);
+
+ cancel_work_sync(&devdata->work);
+}
+
+static int axp20x_power_probe(struct platform_device *pdev)
+{
+ struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent);
+ struct axp20x_power *devdata;
+ struct power_supply *ac, *vbus, *backup, *battery;
+ int ret;
+
+ devdata = devm_kzalloc(&pdev->dev, sizeof(struct axp20x_power),
+ GFP_KERNEL);
+ if (devdata == NULL)
+ return -ENOMEM;
+
+ spin_lock_init(&devdata->lock);
+ devdata->axp20x = axp20x;
+ platform_set_drvdata(pdev, devdata);
+
+ backup = &devdata->backup;
+ snprintf(devdata->backup_name, sizeof(devdata->backup_name), "axp20x-backup");
+ backup->name = devdata->backup_name;
+ backup->type = POWER_SUPPLY_TYPE_BATTERY;
+ backup->properties = axp20x_backup_props;
+ backup->num_properties = ARRAY_SIZE(axp20x_backup_props);
+ backup->property_is_writeable = axp20x_backup_prop_writeable;
+ backup->get_property = axp20x_backup_get_prop;
+ backup->set_property = axp20x_backup_set_prop;
+
+ ac = &devdata->ac;
+ snprintf(devdata->ac_name, sizeof(devdata->ac_name), "axp20x-ac");
+ ac->name = devdata->ac_name;
+ ac->type = POWER_SUPPLY_TYPE_MAINS;
+ ac->properties = axp20x_ac_props;
+ ac->num_properties = ARRAY_SIZE(axp20x_ac_props);
+ ac->get_property = axp20x_ac_get_prop;
+
+ vbus = &devdata->vbus;
+ snprintf(devdata->vbus_name, sizeof(devdata->vbus_name), "axp20x-usb");
+ vbus->name = devdata->vbus_name;
+ vbus->type = POWER_SUPPLY_TYPE_USB;
+ vbus->properties = axp20x_vbus_props;
+ vbus->num_properties = ARRAY_SIZE(axp20x_vbus_props);
+ vbus->property_is_writeable = axp20x_vbus_prop_writeable;
+ vbus->get_property = axp20x_vbus_get_prop;
+ vbus->set_property = axp20x_vbus_set_prop;
+
+ devdata->battery_supplies[0] = devdata->vbus_name;
+ devdata->battery_supplies[1] = devdata->ac_name;
+ battery = &devdata->battery;
+ snprintf(devdata->battery_name, sizeof(devdata->battery_name), "axp20x-battery");
+ battery->name = devdata->battery_name;
+ battery->type = POWER_SUPPLY_TYPE_BATTERY;
+ battery->properties = axp20x_battery_props;
+ battery->num_properties = ARRAY_SIZE(axp20x_battery_props);
+ battery->property_is_writeable = axp20x_battery_prop_writeable;
+ battery->get_property = axp20x_battery_get_prop;
+ battery->set_property = axp20x_battery_set_prop;
+ battery->supplied_from = devdata->battery_supplies;
+ battery->num_supplies = 1;
+ battery->external_power_changed = axp20x_battery_chg_reconfig;
+
+ /* configure hardware and check FDT params */
+ regmap_update_bits(axp20x->regmap, AXP20X_ADC_RATE,
+ AXP20X_ADR_RATE_MASK, AXP20X_ADR_RATE_50Hz);
+
+ ret = axp20x_backup_config(pdev, axp20x);
+ if (ret)
+ devdata->backup_name[0] = '\0';
+
+ ret = axp20x_battery_config(pdev, devdata, axp20x);
+ if (ret)
+ devdata->battery_name[0] = '\0';
+ else if (devdata->tbatt_min == 0 && devdata->tbatt_max == 0)
+ battery->num_properties -= 3;
+
+ ret = axp20x_power_poll(devdata, 2);
+ if (ret)
+ return ret;
+
+ if (devdata->status1 & AXP20X_PWR_STATUS_AC_VBUS_SHORT)
+ devdata->ac_name[0] = '\0';
+ else
+ battery->num_supplies = 2;
+
+ /* register present supplies */
+ ret = power_supply_register(&pdev->dev, backup);
+ if (ret)
+ return ret;
+
+ ret = power_supply_register(&pdev->dev, vbus);
+ if (ret)
+ goto err_unreg_backup;
+ power_supply_changed(&devdata->vbus);
+
+ if (devdata->ac_name[0]) {
+ ret = power_supply_register(&pdev->dev, ac);
+ if (ret)
+ goto err_unreg_vbus;
+ power_supply_changed(&devdata->ac);
+ }
+
+ if (devdata->battery_name[0]) {
+ ret = power_supply_register(&pdev->dev, battery);
+ if (ret)
+ goto err_unreg_ac;
+ power_supply_changed(&devdata->battery);
+ }
+
+ INIT_WORK(&devdata->work, axp20x_power_monitor);
+
+ /* configure interrupts */
+ axp20x_init_irq(pdev, axp20x, "VBUS_OVER_V", vbus->name, axp20x_irq_vbus_over_v);
+ axp20x_init_irq(pdev, axp20x, "VBUS_PLUGIN", vbus->name, axp20x_irq_vbus_plugin);
+ axp20x_init_irq(pdev, axp20x, "VBUS_REMOVAL", vbus->name, axp20x_irq_vbus_removal);
+ axp20x_init_irq(pdev, axp20x, "VBUS_V_LOW", vbus->name, axp20x_irq_vbus_v_low);
+
+ if (devdata->ac_name[0]) {
+ axp20x_init_irq(pdev, axp20x, "ACIN_OVER_V", ac->name, axp20x_irq_ac_over_v);
+ axp20x_init_irq(pdev, axp20x, "ACIN_PLUGIN", ac->name, axp20x_irq_ac_plugin);
+ axp20x_init_irq(pdev, axp20x, "ACIN_REMOVAL", ac->name, axp20x_irq_ac_removal);
+ }
+ if (devdata->battery_name[0]) {
+ axp20x_init_irq(pdev, axp20x, "BATT_PLUGIN", battery->name, axp20x_irq_batt_plugin);
+ axp20x_init_irq(pdev, axp20x, "BATT_REMOVAL", battery->name, axp20x_irq_batt_removal);
+ axp20x_init_irq(pdev, axp20x, "BATT_ACTIVATE", battery->name, axp20x_irq_batt_activation);
+ axp20x_init_irq(pdev, axp20x, "BATT_ACTIVATED", battery->name, axp20x_irq_batt_activated);
+ axp20x_init_irq(pdev, axp20x, "BATT_CHARGING", battery->name, axp20x_irq_batt_charging);
+ axp20x_init_irq(pdev, axp20x, "BATT_CHARGED", battery->name, axp20x_irq_batt_charged);
+ if (devdata->tbatt_min != 0 || devdata->tbatt_max != 0) {
+ axp20x_init_irq(pdev, axp20x, "BATT_HOT", battery->name, axp20x_irq_batt_high_temp);
+ axp20x_init_irq(pdev, axp20x, "BATT_COLD", battery->name, axp20x_irq_batt_low_temp);
+ }
+ axp20x_init_irq(pdev, axp20x, "BATT_CHG_CURR_LOW", battery->name, axp20x_irq_batt_chg_curr_low);
+
+ axp20x_init_irq(pdev, axp20x, "POWER_LOW_WARN", battery->name, axp20x_irq_power_low);
+ axp20x_init_irq(pdev, axp20x, "POWER_LOW_CRIT", battery->name, axp20x_irq_power_low_crit);
+ }
+
+ return 0;
+
+err_unreg_ac:
+ if (devdata->ac_name[0])
+ power_supply_unregister(&devdata->ac);
+err_unreg_vbus:
+ power_supply_unregister(&devdata->vbus);
+err_unreg_backup:
+ power_supply_unregister(&devdata->backup);
+
+ return ret;
+}
+
+static int axp20x_power_remove(struct platform_device *pdev)
+{
+ struct axp20x_power *devdata = platform_get_drvdata(pdev);
+
+ cancel_work_sync(&devdata->work);
+ if (devdata->battery_name[0])
+ power_supply_unregister(&devdata->battery);
+ if (devdata->ac_name[0])
+ power_supply_unregister(&devdata->ac);
+ power_supply_unregister(&devdata->vbus);
+ if (devdata->backup_name[0])
+ power_supply_unregister(&devdata->backup);
+
+ return 0;
+}
+
+static struct platform_driver axp20x_power_driver = {
+ .probe = axp20x_power_probe,
+ .remove = axp20x_power_remove,
+ .suspend = axp20x_power_suspend,
+ .resume = axp20x_power_resume,
+ .shutdown = axp20x_power_shutdown,
+ .driver = {
+ .name = "axp20x-power",
+ .owner = THIS_MODULE,
+ },
+};
+
+module_platform_driver(axp20x_power_driver);
+
+MODULE_DESCRIPTION("Power supply driver for AXP20x PMICs");
+MODULE_AUTHOR("Bruno Pr?mont <bonbons@linux-vserver.org>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:axp20x-power");
diff --git a/include/linux/mfd/axp20x.h b/include/linux/mfd/axp20x.h
index d0e31a2..eaf1f7b 100644
--- a/include/linux/mfd/axp20x.h
+++ b/include/linux/mfd/axp20x.h
@@ -116,6 +116,11 @@ enum {
#define AXP20X_CC_CTRL 0xb8
#define AXP20X_FG_RES 0xb9

+/* OCV */
+#define AXP20X_RDC_H 0xba
+#define AXP20X_RDC_L 0xbb
+#define AXP20X_OCV(m) (0xc0 + (m))
+
/* Regulators IDs */
enum {
AXP20X_LDO1 = 0,
--
2.0.4