root/drivers/power/supply/adp5061.c
// SPDX-License-Identifier: GPL-2.0-only
/*
 * ADP5061 I2C Programmable Linear Battery Charger
 *
 * Copyright 2018 Analog Devices Inc.
 */

#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/i2c.h>
#include <linux/delay.h>
#include <linux/pm.h>
#include <linux/mod_devicetable.h>
#include <linux/power_supply.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/regmap.h>

/* ADP5061 registers definition */
#define ADP5061_ID                      0x00
#define ADP5061_REV                     0x01
#define ADP5061_VINX_SET                0x02
#define ADP5061_TERM_SET                0x03
#define ADP5061_CHG_CURR                0x04
#define ADP5061_VOLTAGE_TH              0x05
#define ADP5061_TIMER_SET               0x06
#define ADP5061_FUNC_SET_1              0x07
#define ADP5061_FUNC_SET_2              0x08
#define ADP5061_INT_EN                  0x09
#define ADP5061_INT_ACT                 0x0A
#define ADP5061_CHG_STATUS_1            0x0B
#define ADP5061_CHG_STATUS_2            0x0C
#define ADP5061_FAULT                   0x0D
#define ADP5061_BATTERY_SHORT           0x10
#define ADP5061_IEND                    0x11

/* ADP5061_VINX_SET */
#define ADP5061_VINX_SET_ILIM_MSK               GENMASK(3, 0)
#define ADP5061_VINX_SET_ILIM_MODE(x)           (((x) & 0x0F) << 0)

/* ADP5061_TERM_SET */
#define ADP5061_TERM_SET_VTRM_MSK               GENMASK(7, 2)
#define ADP5061_TERM_SET_VTRM_MODE(x)           (((x) & 0x3F) << 2)
#define ADP5061_TERM_SET_CHG_VLIM_MSK           GENMASK(1, 0)
#define ADP5061_TERM_SET_CHG_VLIM_MODE(x)       (((x) & 0x03) << 0)

/* ADP5061_CHG_CURR */
#define ADP5061_CHG_CURR_ICHG_MSK               GENMASK(6, 2)
#define ADP5061_CHG_CURR_ICHG_MODE(x)           (((x) & 0x1F) << 2)
#define ADP5061_CHG_CURR_ITRK_DEAD_MSK          GENMASK(1, 0)
#define ADP5061_CHG_CURR_ITRK_DEAD_MODE(x)      (((x) & 0x03) << 0)

/* ADP5061_VOLTAGE_TH */
#define ADP5061_VOLTAGE_TH_DIS_RCH_MSK          BIT(7)
#define ADP5061_VOLTAGE_TH_DIS_RCH_MODE(x)      (((x) & 0x01) << 7)
#define ADP5061_VOLTAGE_TH_VRCH_MSK             GENMASK(6, 5)
#define ADP5061_VOLTAGE_TH_VRCH_MODE(x)         (((x) & 0x03) << 5)
#define ADP5061_VOLTAGE_TH_VTRK_DEAD_MSK        GENMASK(4, 3)
#define ADP5061_VOLTAGE_TH_VTRK_DEAD_MODE(x)    (((x) & 0x03) << 3)
#define ADP5061_VOLTAGE_TH_VWEAK_MSK            GENMASK(2, 0)
#define ADP5061_VOLTAGE_TH_VWEAK_MODE(x)        (((x) & 0x07) << 0)

/* ADP5061_CHG_STATUS_1 */
#define ADP5061_CHG_STATUS_1_VIN_OV(x)          (((x) >> 7) & 0x1)
#define ADP5061_CHG_STATUS_1_VIN_OK(x)          (((x) >> 6) & 0x1)
#define ADP5061_CHG_STATUS_1_VIN_ILIM(x)        (((x) >> 5) & 0x1)
#define ADP5061_CHG_STATUS_1_THERM_LIM(x)       (((x) >> 4) & 0x1)
#define ADP5061_CHG_STATUS_1_CHDONE(x)          (((x) >> 3) & 0x1)
#define ADP5061_CHG_STATUS_1_CHG_STATUS(x)      (((x) >> 0) & 0x7)

/* ADP5061_CHG_STATUS_2 */
#define ADP5061_CHG_STATUS_2_THR_STATUS(x)      (((x) >> 5) & 0x7)
#define ADP5061_CHG_STATUS_2_RCH_LIM_INFO(x)    (((x) >> 3) & 0x1)
#define ADP5061_CHG_STATUS_2_BAT_STATUS(x)      (((x) >> 0) & 0x7)

/* ADP5061_IEND */
#define ADP5061_IEND_IEND_MSK                   GENMASK(7, 5)
#define ADP5061_IEND_IEND_MODE(x)               (((x) & 0x07) << 5)

#define ADP5061_NO_BATTERY      0x01
#define ADP5061_ICHG_MAX        1300 // mA

enum adp5061_chg_status {
        ADP5061_CHG_OFF,
        ADP5061_CHG_TRICKLE,
        ADP5061_CHG_FAST_CC,
        ADP5061_CHG_FAST_CV,
        ADP5061_CHG_COMPLETE,
        ADP5061_CHG_LDO_MODE,
        ADP5061_CHG_TIMER_EXP,
        ADP5061_CHG_BAT_DET,
};

static const int adp5061_chg_type[4] = {
        [ADP5061_CHG_OFF] = POWER_SUPPLY_CHARGE_TYPE_NONE,
        [ADP5061_CHG_TRICKLE] = POWER_SUPPLY_CHARGE_TYPE_TRICKLE,
        [ADP5061_CHG_FAST_CC] = POWER_SUPPLY_CHARGE_TYPE_FAST,
        [ADP5061_CHG_FAST_CV] = POWER_SUPPLY_CHARGE_TYPE_FAST,
};

static const int adp5061_vweak_th[8] = {
        2700, 2800, 2900, 3000, 3100, 3200, 3300, 3400,
};

static const int adp5061_prechg_current[4] = {
        5, 10, 20, 80,
};

static const int adp5061_vmin[4] = {
        2000, 2500, 2600, 2900,
};

static const int adp5061_const_chg_vmax[4] = {
        3200, 3400, 3700, 3800,
};

static const int adp5061_const_ichg[24] = {
        50, 100, 150, 200, 250, 300, 350, 400, 450, 500, 550, 600, 650,
        700, 750, 800, 850, 900, 950, 1000, 1050, 1100, 1200, 1300,
};

static const int adp5061_vmax[36] = {
        3800, 3820, 3840, 3860, 3880, 3900, 3920, 3940, 3960, 3980,
        4000, 4020, 4040, 4060, 4080, 4100, 4120, 4140, 4160, 4180,
        4200, 4220, 4240, 4260, 4280, 4300, 4320, 4340, 4360, 4380,
        4400, 4420, 4440, 4460, 4480, 4500,
};

static const int adp5061_in_current_lim[16] = {
        100, 150, 200, 250, 300, 400, 500, 600, 700,
        800, 900, 1000, 1200, 1500, 1800, 2100,
};

static const int adp5061_iend[8] = {
        12500, 32500, 52500, 72500, 92500, 117500, 142500, 170000,
};

struct adp5061_state {
        struct i2c_client               *client;
        struct regmap                   *regmap;
        struct power_supply             *psy;
};

static int adp5061_get_array_index(const int *array, u8 size, int val)
{
        int i;

        for (i = 1; i < size; i++) {
                if (val < array[i])
                        break;
        }

        return i-1;
}

static int adp5061_get_status(struct adp5061_state *st,
                              u8 *status1, u8 *status2)
{
        u8 buf[2];
        int ret;

        /* CHG_STATUS1 and CHG_STATUS2 are adjacent regs */
        ret = regmap_bulk_read(st->regmap, ADP5061_CHG_STATUS_1,
                               &buf[0], 2);
        if (ret < 0)
                return ret;

        *status1 = buf[0];
        *status2 = buf[1];

        return ret;
}

static int adp5061_get_input_current_limit(struct adp5061_state *st,
                union power_supply_propval *val)
{
        unsigned int regval;
        int mode, ret;

        ret = regmap_read(st->regmap, ADP5061_VINX_SET, &regval);
        if (ret < 0)
                return ret;

        mode = ADP5061_VINX_SET_ILIM_MODE(regval);
        val->intval = adp5061_in_current_lim[mode] * 1000;

        return ret;
}

static int adp5061_set_input_current_limit(struct adp5061_state *st, int val)
{
        int index;

        /* Convert from uA to mA */
        val /= 1000;
        index = adp5061_get_array_index(adp5061_in_current_lim,
                                        ARRAY_SIZE(adp5061_in_current_lim),
                                        val);
        if (index < 0)
                return index;

        return regmap_update_bits(st->regmap, ADP5061_VINX_SET,
                                  ADP5061_VINX_SET_ILIM_MSK,
                                  ADP5061_VINX_SET_ILIM_MODE(index));
}

static int adp5061_set_min_voltage(struct adp5061_state *st, int val)
{
        int index;

        /* Convert from uV to mV */
        val /= 1000;
        index = adp5061_get_array_index(adp5061_vmin,
                                        ARRAY_SIZE(adp5061_vmin),
                                        val);
        if (index < 0)
                return index;

        return regmap_update_bits(st->regmap, ADP5061_VOLTAGE_TH,
                                  ADP5061_VOLTAGE_TH_VTRK_DEAD_MSK,
                                  ADP5061_VOLTAGE_TH_VTRK_DEAD_MODE(index));
}

static int adp5061_get_min_voltage(struct adp5061_state *st,
                                   union power_supply_propval *val)
{
        unsigned int regval;
        int ret;

        ret = regmap_read(st->regmap, ADP5061_VOLTAGE_TH, &regval);
        if (ret < 0)
                return ret;

        regval = ((regval & ADP5061_VOLTAGE_TH_VTRK_DEAD_MSK) >> 3);
        val->intval = adp5061_vmin[regval] * 1000;

        return ret;
}

static int adp5061_get_chg_volt_lim(struct adp5061_state *st,
                                    union power_supply_propval *val)
{
        unsigned int regval;
        int mode, ret;

        ret = regmap_read(st->regmap, ADP5061_TERM_SET, &regval);
        if (ret < 0)
                return ret;

        mode = ADP5061_TERM_SET_CHG_VLIM_MODE(regval);
        val->intval = adp5061_const_chg_vmax[mode] * 1000;

        return ret;
}

static int adp5061_get_max_voltage(struct adp5061_state *st,
                                   union power_supply_propval *val)
{
        unsigned int regval;
        int ret;

        ret = regmap_read(st->regmap, ADP5061_TERM_SET, &regval);
        if (ret < 0)
                return ret;

        regval = ((regval & ADP5061_TERM_SET_VTRM_MSK) >> 2) - 0x0F;
        if (regval >= ARRAY_SIZE(adp5061_vmax))
                regval = ARRAY_SIZE(adp5061_vmax) - 1;

        val->intval = adp5061_vmax[regval] * 1000;

        return ret;
}

static int adp5061_set_max_voltage(struct adp5061_state *st, int val)
{
        int vmax_index;

        /* Convert from uV to mV */
        val /= 1000;
        if (val > 4500)
                val = 4500;

        vmax_index = adp5061_get_array_index(adp5061_vmax,
                                             ARRAY_SIZE(adp5061_vmax), val);
        if (vmax_index < 0)
                return vmax_index;

        vmax_index += 0x0F;

        return regmap_update_bits(st->regmap, ADP5061_TERM_SET,
                                  ADP5061_TERM_SET_VTRM_MSK,
                                  ADP5061_TERM_SET_VTRM_MODE(vmax_index));
}

static int adp5061_set_const_chg_vmax(struct adp5061_state *st, int val)
{
        int index;

        /* Convert from uV to mV */
        val /= 1000;
        index = adp5061_get_array_index(adp5061_const_chg_vmax,
                                        ARRAY_SIZE(adp5061_const_chg_vmax),
                                        val);
        if (index < 0)
                return index;

        return regmap_update_bits(st->regmap, ADP5061_TERM_SET,
                                  ADP5061_TERM_SET_CHG_VLIM_MSK,
                                  ADP5061_TERM_SET_CHG_VLIM_MODE(index));
}

static int adp5061_set_const_chg_current(struct adp5061_state *st, int val)
{

        int index;

        /* Convert from uA to mA */
        val /= 1000;
        if (val > ADP5061_ICHG_MAX)
                val = ADP5061_ICHG_MAX;

        index = adp5061_get_array_index(adp5061_const_ichg,
                                        ARRAY_SIZE(adp5061_const_ichg),
                                        val);
        if (index < 0)
                return index;

        return regmap_update_bits(st->regmap, ADP5061_CHG_CURR,
                                  ADP5061_CHG_CURR_ICHG_MSK,
                                  ADP5061_CHG_CURR_ICHG_MODE(index));
}

static int adp5061_get_const_chg_current(struct adp5061_state *st,
                union power_supply_propval *val)
{
        unsigned int regval;
        int ret;

        ret = regmap_read(st->regmap, ADP5061_CHG_CURR, &regval);
        if (ret < 0)
                return ret;

        regval = ((regval & ADP5061_CHG_CURR_ICHG_MSK) >> 2);
        if (regval >= ARRAY_SIZE(adp5061_const_ichg))
                regval = ARRAY_SIZE(adp5061_const_ichg) - 1;

        val->intval = adp5061_const_ichg[regval] * 1000;

        return ret;
}

static int adp5061_get_prechg_current(struct adp5061_state *st,
                                      union power_supply_propval *val)
{
        unsigned int regval;
        int ret;

        ret = regmap_read(st->regmap, ADP5061_CHG_CURR, &regval);
        if (ret < 0)
                return ret;

        regval &= ADP5061_CHG_CURR_ITRK_DEAD_MSK;
        val->intval = adp5061_prechg_current[regval] * 1000;

        return ret;
}

static int adp5061_set_prechg_current(struct adp5061_state *st, int val)
{
        int index;

        /* Convert from uA to mA */
        val /= 1000;
        index = adp5061_get_array_index(adp5061_prechg_current,
                                        ARRAY_SIZE(adp5061_prechg_current),
                                        val);
        if (index < 0)
                return index;

        return regmap_update_bits(st->regmap, ADP5061_CHG_CURR,
                                  ADP5061_CHG_CURR_ITRK_DEAD_MSK,
                                  ADP5061_CHG_CURR_ITRK_DEAD_MODE(index));
}

static int adp5061_get_vweak_th(struct adp5061_state *st,
                                union power_supply_propval *val)
{
        unsigned int regval;
        int ret;

        ret = regmap_read(st->regmap, ADP5061_VOLTAGE_TH, &regval);
        if (ret < 0)
                return ret;

        regval &= ADP5061_VOLTAGE_TH_VWEAK_MSK;
        val->intval = adp5061_vweak_th[regval] * 1000;

        return ret;
}

static int adp5061_set_vweak_th(struct adp5061_state *st, int val)
{
        int index;

        /* Convert from uV to mV */
        val /= 1000;
        index = adp5061_get_array_index(adp5061_vweak_th,
                                        ARRAY_SIZE(adp5061_vweak_th),
                                        val);
        if (index < 0)
                return index;

        return regmap_update_bits(st->regmap, ADP5061_VOLTAGE_TH,
                                  ADP5061_VOLTAGE_TH_VWEAK_MSK,
                                  ADP5061_VOLTAGE_TH_VWEAK_MODE(index));
}

static int adp5061_get_chg_type(struct adp5061_state *st,
                                union power_supply_propval *val)
{
        u8 status1, status2;
        int chg_type, ret;

        ret = adp5061_get_status(st, &status1, &status2);
        if (ret < 0)
                return ret;

        chg_type = ADP5061_CHG_STATUS_1_CHG_STATUS(status1);
        if (chg_type >= ARRAY_SIZE(adp5061_chg_type))
                val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
        else
                val->intval = adp5061_chg_type[chg_type];

        return ret;
}

static int adp5061_get_charger_status(struct adp5061_state *st,
                                      union power_supply_propval *val)
{
        u8 status1, status2;
        int ret;

        ret = adp5061_get_status(st, &status1, &status2);
        if (ret < 0)
                return ret;

        switch (ADP5061_CHG_STATUS_1_CHG_STATUS(status1)) {
        case ADP5061_CHG_OFF:
                val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
                break;
        case ADP5061_CHG_TRICKLE:
        case ADP5061_CHG_FAST_CC:
        case ADP5061_CHG_FAST_CV:
                val->intval = POWER_SUPPLY_STATUS_CHARGING;
                break;
        case ADP5061_CHG_COMPLETE:
                val->intval = POWER_SUPPLY_STATUS_FULL;
                break;
        case ADP5061_CHG_TIMER_EXP:
                /* The battery must be discharging if there is a charge fault */
                val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
                break;
        default:
                val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
        }

        return ret;
}

static int adp5061_get_battery_status(struct adp5061_state *st,
                                      union power_supply_propval *val)
{
        u8 status1, status2;
        int ret;

        ret = adp5061_get_status(st, &status1, &status2);
        if (ret < 0)
                return ret;

        switch (ADP5061_CHG_STATUS_2_BAT_STATUS(status2)) {
        case 0x0: /* Battery monitor off */
        case 0x1: /* No battery */
                val->intval = POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN;
                break;
        case 0x2: /* VBAT < VTRK */
                val->intval = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
                break;
        case 0x3: /* VTRK < VBAT_SNS < VWEAK */
                val->intval = POWER_SUPPLY_CAPACITY_LEVEL_LOW;
                break;
        case 0x4: /* VBAT_SNS > VWEAK */
                val->intval = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
                break;
        default:
                val->intval = POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN;
                break;
        }

        return ret;
}

static int adp5061_get_termination_current(struct adp5061_state *st,
                                           union power_supply_propval *val)
{
        unsigned int regval;
        int ret;

        ret = regmap_read(st->regmap, ADP5061_IEND, &regval);
        if (ret < 0)
                return ret;

        regval = (regval & ADP5061_IEND_IEND_MSK) >> 5;
        val->intval = adp5061_iend[regval];

        return ret;
}

static int adp5061_set_termination_current(struct adp5061_state *st, int val)
{
        int index;

        index = adp5061_get_array_index(adp5061_iend,
                                        ARRAY_SIZE(adp5061_iend),
                                        val);
        if (index < 0)
                return index;

        return regmap_update_bits(st->regmap, ADP5061_IEND,
                                  ADP5061_IEND_IEND_MSK,
                                  ADP5061_IEND_IEND_MODE(index));
}

static int adp5061_get_property(struct power_supply *psy,
                                enum power_supply_property psp,
                                union power_supply_propval *val)
{
        struct adp5061_state *st = power_supply_get_drvdata(psy);
        u8 status1, status2;
        int mode, ret;

        switch (psp) {
        case POWER_SUPPLY_PROP_PRESENT:
                ret = adp5061_get_status(st, &status1, &status2);
                if (ret < 0)
                        return ret;

                mode = ADP5061_CHG_STATUS_2_BAT_STATUS(status2);
                if (mode == ADP5061_NO_BATTERY)
                        val->intval = 0;
                else
                        val->intval = 1;
                break;
        case POWER_SUPPLY_PROP_CHARGE_TYPE:
                return adp5061_get_chg_type(st, val);
        case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
                /* This property is used to indicate the input current
                 * limit into VINx (ILIM)
                 */
                return adp5061_get_input_current_limit(st, val);
        case POWER_SUPPLY_PROP_VOLTAGE_MAX:
                /* This property is used to indicate the termination
                 * voltage (VTRM)
                 */
                return adp5061_get_max_voltage(st, val);
        case POWER_SUPPLY_PROP_VOLTAGE_MIN:
                /*
                 * This property is used to indicate the trickle to fast
                 * charge threshold (VTRK_DEAD)
                 */
                return adp5061_get_min_voltage(st, val);
        case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
                /* This property is used to indicate the charging
                 * voltage limit (CHG_VLIM)
                 */
                return adp5061_get_chg_volt_lim(st, val);
        case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
                /*
                 * This property is used to indicate the value of the constant
                 * current charge (ICHG)
                 */
                return adp5061_get_const_chg_current(st, val);
        case POWER_SUPPLY_PROP_PRECHARGE_CURRENT:
                /*
                 * This property is used to indicate the value of the trickle
                 * and weak charge currents (ITRK_DEAD)
                 */
                return adp5061_get_prechg_current(st, val);
        case POWER_SUPPLY_PROP_VOLTAGE_AVG:
                /*
                 * This property is used to set the VWEAK threshold
                 * below this value, weak charge mode is entered
                 * above this value, fast chargerge mode is entered
                 */
                return adp5061_get_vweak_th(st, val);
        case POWER_SUPPLY_PROP_STATUS:
                /*
                 * Indicate the charger status in relation to power
                 * supply status property
                 */
                return adp5061_get_charger_status(st, val);
        case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
                /*
                 * Indicate the battery status in relation to power
                 * supply capacity level property
                 */
                return adp5061_get_battery_status(st, val);
        case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT:
                /* Indicate the values of the termination current */
                return adp5061_get_termination_current(st, val);
        default:
                return -EINVAL;
        }

        return 0;
}

static int adp5061_set_property(struct power_supply *psy,
                                enum power_supply_property psp,
                                const union power_supply_propval *val)
{
        struct adp5061_state *st = power_supply_get_drvdata(psy);

        switch (psp) {
        case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
                return adp5061_set_input_current_limit(st, val->intval);
        case POWER_SUPPLY_PROP_VOLTAGE_MAX:
                return adp5061_set_max_voltage(st, val->intval);
        case POWER_SUPPLY_PROP_VOLTAGE_MIN:
                return adp5061_set_min_voltage(st, val->intval);
        case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
                return adp5061_set_const_chg_vmax(st, val->intval);
        case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
                return adp5061_set_const_chg_current(st, val->intval);
        case POWER_SUPPLY_PROP_PRECHARGE_CURRENT:
                return adp5061_set_prechg_current(st, val->intval);
        case POWER_SUPPLY_PROP_VOLTAGE_AVG:
                return adp5061_set_vweak_th(st, val->intval);
        case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT:
                return adp5061_set_termination_current(st, val->intval);
        default:
                return -EINVAL;
        }

        return 0;
}

static int adp5061_prop_writeable(struct power_supply *psy,
                                  enum power_supply_property psp)
{
        switch (psp) {
        case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
        case POWER_SUPPLY_PROP_VOLTAGE_MAX:
        case POWER_SUPPLY_PROP_VOLTAGE_MIN:
        case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
        case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
        case POWER_SUPPLY_PROP_PRECHARGE_CURRENT:
        case POWER_SUPPLY_PROP_VOLTAGE_AVG:
        case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT:
                return 1;
        default:
                return 0;
        }
}

static enum power_supply_property adp5061_props[] = {
        POWER_SUPPLY_PROP_PRESENT,
        POWER_SUPPLY_PROP_CHARGE_TYPE,
        POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT,
        POWER_SUPPLY_PROP_VOLTAGE_MAX,
        POWER_SUPPLY_PROP_VOLTAGE_MIN,
        POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX,
        POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT,
        POWER_SUPPLY_PROP_PRECHARGE_CURRENT,
        POWER_SUPPLY_PROP_VOLTAGE_AVG,
        POWER_SUPPLY_PROP_STATUS,
        POWER_SUPPLY_PROP_CAPACITY_LEVEL,
        POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT,
};

static const struct regmap_config adp5061_regmap_config = {
        .reg_bits = 8,
        .val_bits = 8,
};

static const struct power_supply_desc adp5061_desc = {
        .name                   = "adp5061",
        .type                   = POWER_SUPPLY_TYPE_USB,
        .get_property           = adp5061_get_property,
        .set_property           = adp5061_set_property,
        .property_is_writeable  = adp5061_prop_writeable,
        .properties             = adp5061_props,
        .num_properties         = ARRAY_SIZE(adp5061_props),
};

static int adp5061_probe(struct i2c_client *client)
{
        struct power_supply_config psy_cfg = {};
        struct adp5061_state *st;

        st = devm_kzalloc(&client->dev, sizeof(*st), GFP_KERNEL);
        if (!st)
                return -ENOMEM;

        st->client = client;
        st->regmap = devm_regmap_init_i2c(client,
                                          &adp5061_regmap_config);
        if (IS_ERR(st->regmap)) {
                dev_err(&client->dev, "Failed to initialize register map\n");
                return -EINVAL;
        }

        i2c_set_clientdata(client, st);
        psy_cfg.drv_data = st;

        st->psy = devm_power_supply_register(&client->dev,
                                             &adp5061_desc,
                                             &psy_cfg);

        if (IS_ERR(st->psy)) {
                dev_err(&client->dev, "Failed to register power supply\n");
                return PTR_ERR(st->psy);
        }

        return 0;
}

static const struct i2c_device_id adp5061_id[] = {
        { "adp5061" },
        { }
};
MODULE_DEVICE_TABLE(i2c, adp5061_id);

static struct i2c_driver adp5061_driver = {
        .driver = {
                .name = KBUILD_MODNAME,
        },
        .probe = adp5061_probe,
        .id_table = adp5061_id,
};
module_i2c_driver(adp5061_driver);

MODULE_DESCRIPTION("Analog Devices adp5061 battery charger driver");
MODULE_AUTHOR("Stefan Popa <stefan.popa@analog.com>");
MODULE_LICENSE("GPL v2");