root/drivers/power/supply/bq2515x_charger.c
// SPDX-License-Identifier: GPL-2.0
// BQ2515X Battery Charger Driver
// Copyright (C) 2020 Texas Instruments Incorporated - https://www.ti.com/

#include <linux/err.h>
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/gpio/consumer.h>
#include <linux/power_supply.h>
#include <linux/regmap.h>
#include <linux/types.h>

#define BQ2515X_MANUFACTURER "Texas Instruments"

#define BQ2515X_STAT0           0x00
#define BQ2515X_STAT1           0x01
#define BQ2515X_STAT2           0x02
#define BQ2515X_FLAG0           0x03
#define BQ2515X_FLAG1           0x04
#define BQ2515X_FLAG2           0x05
#define BQ2515X_FLAG3           0x06
#define BQ2515X_MASK0           0x07
#define BQ2515X_MASK1           0x08
#define BQ2515X_MASK2           0x09
#define BQ2515X_MASK3           0x0a
#define BQ2515X_VBAT_CTRL       0x12
#define BQ2515X_ICHG_CTRL       0x13
#define BQ2515X_PCHRGCTRL       0x14
#define BQ2515X_TERMCTRL        0x15
#define BQ2515X_BUVLO           0x16
#define BQ2515X_CHARGERCTRL0    0x17
#define BQ2515X_CHARGERCTRL1    0x18
#define BQ2515X_ILIMCTRL        0x19
#define BQ2515X_LDOCTRL         0x1d
#define BQ2515X_MRCTRL          0x30
#define BQ2515X_ICCTRL0         0x35
#define BQ2515X_ICCTRL1         0x36
#define BQ2515X_ICCTRL2         0x37
#define BQ2515X_ADCCTRL0        0x40
#define BQ2515X_ADCCTRL1        0x41
#define BQ2515X_ADC_VBAT_M      0x42
#define BQ2515X_ADC_VBAT_L      0x43
#define BQ2515X_ADC_TS_M        0x44
#define BQ2515X_ADC_TS_L        0x45
#define BQ2515X_ADC_ICHG_M      0x46
#define BQ2515X_ADC_ICHG_L      0x47
#define BQ2515X_ADC_ADCIN_M     0x48
#define BQ2515X_ADC_ADCIN_L     0x49
#define BQ2515X_ADC_VIN_M       0x4a
#define BQ2515X_ADC_VIN_L       0x4b
#define BQ2515X_ADC_PMID_M      0x4c
#define BQ2515X_ADC_PMID_L      0x4d
#define BQ2515X_ADC_IIN_M       0x4e
#define BQ2515X_ADC_IIN_L       0x4f
#define BQ2515X_ADC_COMP1_M     0x52
#define BQ2515X_ADC_COMP1_L     0X53
#define BQ2515X_ADC_COMP2_M     0X54
#define BQ2515X_ADC_COMP2_L     0x55
#define BQ2515X_ADC_COMP3_M     0x56
#define BQ2515X_ADC_COMP3_L     0x57
#define BQ2515X_ADC_READ_EN     0x58
#define BQ2515X_TS_FASTCHGCTRL  0x61
#define BQ2515X_TS_COLD         0x62
#define BQ2515X_TS_COOL         0x63
#define BQ2515X_TS_WARM         0x64
#define BQ2515X_TS_HOT          0x65
#define BQ2515X_DEVICE_ID       0x6f

#define BQ2515X_DEFAULT_ICHG_UA         10000
#define BQ25150_DEFAULT_ILIM_UA         100000
#define BQ25155_DEFAULT_ILIM_UA         500000
#define BQ2515X_DEFAULT_VBAT_REG_UV     4200000
#define BQ2515X_DEFAULT_IPRECHARGE_UA   2500

#define BQ2515X_DIVISOR                         65536
#define BQ2515X_VBAT_BASE_VOLT                  3600000
#define BQ2515X_VBAT_REG_MAX                    4600000
#define BQ2515X_VBAT_REG_MIN                    3600000
#define BQ2515X_VBAT_STEP_UV                    10000
#define BQ2515X_UV_FACTOR                       1000000
#define BQ2515X_VBAT_MULTIPLIER                 6
#define BQ2515X_ICHG_DIVISOR                    52429
#define BQ2515X_ICHG_CURR_STEP_THRESH_UA        318750
#define BQ2515X_ICHG_MIN_UA                     0
#define BQ2515X_ICHG_MAX_UA                     500000
#define BQ2515X_ICHG_RNG_1B0_UA                 1250
#define BQ2515X_ICHG_RNG_1B1_UA                 2500
#define BQ2515X_VLOWV_SEL_1B0_UV                3000000
#define BQ2515X_VLOWV_SEL_1B1_UV                2800000
#define BQ2515X_PRECHRG_ICHRG_RNGE_1875_UA      18750
#define BQ2515X_PRECHRG_ICHRG_RNGE_3750_UA      37500
#define BQ2515X_TWAKE2_MIN_US                   1700000
#define BQ2515X_TWAKE2_MAX_US                   2300000

#define BQ2515X_ILIM_150MA      0x2
#define BQ2515X_ILIM_MASK       0x7
#define BQ2515X_ILIM_MIN        50000
#define BQ2515X_ILIM_MAX        600000
#define BQ2515X_HEALTH_MASK     0xf
#define BQ2515X_ICHGRNG_MASK    0x80
#define BQ2515X_STAT0_MASK      0x0f
#define BQ2515X_STAT1_MASK      0x1f
#define BQ2515X_PRECHARGE_MASK  0x1f

#define BQ2515X_TS_HOT_STAT             BIT(0)
#define BQ2515X_TS_WARM_STAT            BIT(1)
#define BQ2515X_TS_COOL_STAT            BIT(2)
#define BQ2515X_TS_COLD_STAT            BIT(3)
#define BQ2515X_SAFETY_TIMER_EXP        BIT(5)

#define BQ2515X_EN_VBAT_READ            BIT(3)
#define BQ2515X_EN_ICHG_READ            BIT(5)

#define BQ2515X_VIN_GOOD                BIT(0)
#define BQ2515X_CHRG_DONE               BIT(5)
#define BQ2515X_CV_CHRG_MODE            BIT(6)

#define BQ2515X_VIN_OVP_FAULT_STAT      BIT(7)

#define BQ2515X_WATCHDOG_DISABLE        BIT(4)

#define BQ2515X_ICHARGE_RANGE           BIT(7)

#define BQ2515X_VLOWV_SEL               BIT(5)

#define BQ2515X_CHARGER_DISABLE         BIT(0)

#define BQ2515X_HWRESET_14S_WD          BIT(1)

static const int bq2515x_ilim_lvl_values[] = {
        50000, 100000, 150000, 200000, 300000, 400000, 500000, 600000
};

/**
 * struct bq2515x_init_data -
 * @ilim: input current limit
 * @ichg: fast charge current
 * @vbatreg: battery regulation voltage
 * @iprechg: precharge current
 */
struct bq2515x_init_data {
        int ilim;
        int ichg;
        int vbatreg;
        int iprechg;
};

/**
 * struct bq2515x_info -
 * @regmap_config: register map config
 * @ilim: input current limit
 */
struct bq2515x_info {
        const struct regmap_config *regmap_config;
        int ilim;
};

/**
 * struct bq2515x_device -
 * @mains: mains properties
 * @battery: battery properties
 * @regmap: register map structure
 * @dev: device structure
 *
 * @reset_gpio: manual reset (MR) pin
 * @powerdown_gpio: low power mode pin
 * @ac_detect_gpio: power good (PG) pin
 * @ce_gpio: charge enable (CE) pin
 *
 * @info: device info
 * @model_name: string value describing device model
 * @mains_online: boolean value indicating power supply online
 *
 * @init_data: charger initialization data structure
 */
struct bq2515x_device {
        struct power_supply *mains;
        struct power_supply *battery;
        struct regmap *regmap;
        struct device *dev;

        struct gpio_desc *reset_gpio;
        struct gpio_desc *powerdown_gpio;
        struct gpio_desc *ac_detect_gpio;
        struct gpio_desc *ce_gpio;

        const struct bq2515x_info *info;
        char model_name[I2C_NAME_SIZE];
        bool mains_online;

        struct bq2515x_init_data init_data;
};

static const struct reg_default bq25150_reg_defaults[] = {
        {BQ2515X_FLAG0, 0x0},
        {BQ2515X_FLAG1, 0x0},
        {BQ2515X_FLAG2, 0x0},
        {BQ2515X_FLAG3, 0x0},
        {BQ2515X_MASK0, 0x0},
        {BQ2515X_MASK1, 0x0},
        {BQ2515X_MASK2, 0x71},
        {BQ2515X_MASK3, 0x0},
        {BQ2515X_VBAT_CTRL, 0x3C},
        {BQ2515X_ICHG_CTRL, 0x8},
        {BQ2515X_PCHRGCTRL, 0x2},
        {BQ2515X_TERMCTRL, 0x14},
        {BQ2515X_BUVLO, 0x0},
        {BQ2515X_CHARGERCTRL0, 0x82},
        {BQ2515X_CHARGERCTRL1, 0x42},
        {BQ2515X_ILIMCTRL, 0x1},
        {BQ2515X_LDOCTRL, 0xB0},
        {BQ2515X_MRCTRL, 0x2A},
        {BQ2515X_ICCTRL0, 0x10},
        {BQ2515X_ICCTRL1, 0x0},
        {BQ2515X_ICCTRL2, 0x0},
        {BQ2515X_ADCCTRL0, 0x2},
        {BQ2515X_ADCCTRL1, 0x40},
        {BQ2515X_ADC_COMP1_M, 0x23},
        {BQ2515X_ADC_COMP1_L, 0x20},
        {BQ2515X_ADC_COMP2_M, 0x38},
        {BQ2515X_ADC_COMP2_L, 0x90},
        {BQ2515X_ADC_COMP3_M, 0x0},
        {BQ2515X_ADC_COMP3_L, 0x0},
        {BQ2515X_ADC_READ_EN, 0x0},
        {BQ2515X_TS_FASTCHGCTRL, 0x34},
        {BQ2515X_TS_COLD, 0x7C},
        {BQ2515X_TS_COOL, 0x6D},
        {BQ2515X_TS_WARM, 0x38},
        {BQ2515X_TS_HOT, 0x27},
        {BQ2515X_DEVICE_ID, 0x20},
};

static const struct reg_default bq25155_reg_defaults[] = {
        {BQ2515X_FLAG0, 0x0},
        {BQ2515X_FLAG1, 0x0},
        {BQ2515X_FLAG2, 0x0},
        {BQ2515X_FLAG3, 0x0},
        {BQ2515X_MASK0, 0x0},
        {BQ2515X_MASK1, 0x0},
        {BQ2515X_MASK2, 0x71},
        {BQ2515X_MASK3, 0x0},
        {BQ2515X_VBAT_CTRL, 0x3C},
        {BQ2515X_ICHG_CTRL, 0x8},
        {BQ2515X_PCHRGCTRL, 0x2},
        {BQ2515X_TERMCTRL, 0x14},
        {BQ2515X_BUVLO, 0x0},
        {BQ2515X_CHARGERCTRL0, 0x82},
        {BQ2515X_CHARGERCTRL1, 0xC2},
        {BQ2515X_ILIMCTRL, 0x6},
        {BQ2515X_LDOCTRL, 0xB0},
        {BQ2515X_MRCTRL, 0x2A},
        {BQ2515X_ICCTRL0, 0x10},
        {BQ2515X_ICCTRL1, 0x0},
        {BQ2515X_ICCTRL2, 0x40},
        {BQ2515X_ADCCTRL0, 0x2},
        {BQ2515X_ADCCTRL1, 0x40},
        {BQ2515X_ADC_COMP1_M, 0x23},
        {BQ2515X_ADC_COMP1_L, 0x20},
        {BQ2515X_ADC_COMP2_M, 0x38},
        {BQ2515X_ADC_COMP2_L, 0x90},
        {BQ2515X_ADC_COMP3_M, 0x0},
        {BQ2515X_ADC_COMP3_L, 0x0},
        {BQ2515X_ADC_READ_EN, 0x0},
        {BQ2515X_TS_FASTCHGCTRL, 0x34},
        {BQ2515X_TS_COLD, 0x7C},
        {BQ2515X_TS_COOL, 0x6D},
        {BQ2515X_TS_WARM, 0x38},
        {BQ2515X_TS_HOT, 0x27},
        {BQ2515X_DEVICE_ID, 0x35},
};

static int bq2515x_wake_up(struct bq2515x_device *bq2515x)
{
        int ret;
        int val;

        /* Read the STAT register if we can read it then the device is out
         * of ship mode.  If the register cannot be read then attempt to wake
         * it up and enable the ADC.
         */
        ret = regmap_read(bq2515x->regmap, BQ2515X_STAT0, &val);
        if (ret)
                return ret;

        /* Need to toggle LP and bring device out of ship mode. The device
         * will exit the ship mode when the MR pin is held low for at least
         * t_WAKE2 as shown in section 8.3.7.1 of the datasheet.
         */
        gpiod_set_value_cansleep(bq2515x->powerdown_gpio, 0);

        gpiod_set_value_cansleep(bq2515x->reset_gpio, 0);
        usleep_range(BQ2515X_TWAKE2_MIN_US, BQ2515X_TWAKE2_MAX_US);
        gpiod_set_value_cansleep(bq2515x->reset_gpio, 1);

        return regmap_write(bq2515x->regmap, BQ2515X_ADC_READ_EN,
                                (BQ2515X_EN_VBAT_READ | BQ2515X_EN_ICHG_READ));
}

static int bq2515x_update_ps_status(struct bq2515x_device *bq2515x)
{
        bool dc = false;
        unsigned int val;
        int ret;

        if (bq2515x->ac_detect_gpio)
                val = gpiod_get_value_cansleep(bq2515x->ac_detect_gpio);
        else {
                ret = regmap_read(bq2515x->regmap, BQ2515X_STAT0, &val);
                if (ret)
                        return ret;
        }

        dc = val & BQ2515X_VIN_GOOD;

        ret = bq2515x->mains_online != dc;

        bq2515x->mains_online = dc;

        return ret;
}

static int bq2515x_disable_watchdog_timers(struct bq2515x_device *bq2515x)
{
        int ret;

        ret = regmap_update_bits(bq2515x->regmap, BQ2515X_CHARGERCTRL0,
                        BQ2515X_WATCHDOG_DISABLE, BQ2515X_WATCHDOG_DISABLE);
        if (ret)
                return ret;

        return regmap_update_bits(bq2515x->regmap, BQ2515X_ICCTRL2,
                                                BQ2515X_HWRESET_14S_WD, 0);
}

static int bq2515x_get_battery_voltage_now(struct bq2515x_device *bq2515x)
{
        int ret;
        int vbat_msb;
        int vbat_lsb;
        uint32_t vbat_measurement;

        if (!bq2515x->mains_online)
                bq2515x_wake_up(bq2515x);

        ret = regmap_read(bq2515x->regmap, BQ2515X_ADC_VBAT_M, &vbat_msb);
        if (ret)
                return ret;

        ret = regmap_read(bq2515x->regmap, BQ2515X_ADC_VBAT_L, &vbat_lsb);
        if (ret)
                return ret;

        vbat_measurement = (vbat_msb << 8) | vbat_lsb;

        return vbat_measurement * (BQ2515X_UV_FACTOR / BQ2515X_DIVISOR) *
                                                BQ2515X_VBAT_MULTIPLIER;
}

static int bq2515x_get_battery_current_now(struct bq2515x_device *bq2515x)
{
        int ret;
        int ichg_msb;
        int ichg_lsb;
        uint32_t ichg_measurement;
        u16 ichg_multiplier = BQ2515X_ICHG_RNG_1B0_UA;
        unsigned int ichg_reg_code, reg_code;
        unsigned int icharge_range = 0, pchrgctrl;
        unsigned int buvlo, vlowv_sel, vlowv = BQ2515X_VLOWV_SEL_1B0_UV;

        if (!bq2515x->mains_online)
                return -ENODATA;

        ret = regmap_read(bq2515x->regmap, BQ2515X_ADC_ICHG_M, &ichg_msb);
        if (ret)
                return ret;

        ret = regmap_read(bq2515x->regmap, BQ2515X_ADC_ICHG_L, &ichg_lsb);
        if (ret)
                return ret;

        ichg_measurement = (ichg_msb << 8) | ichg_lsb;

        ret = regmap_read(bq2515x->regmap, BQ2515X_BUVLO, &buvlo);
        if (ret)
                return ret;

        vlowv_sel = buvlo & BQ2515X_VLOWV_SEL;

        if (vlowv_sel)
                vlowv = BQ2515X_VLOWV_SEL_1B1_UV;

        if (bq2515x_get_battery_voltage_now(bq2515x) < vlowv) {
                ret = regmap_read(bq2515x->regmap, BQ2515X_PCHRGCTRL,
                                                                &pchrgctrl);
                if (ret)
                        return ret;

                reg_code = pchrgctrl & BQ2515X_PRECHARGE_MASK;
        } else {
                ret = regmap_read(bq2515x->regmap, BQ2515X_ICHG_CTRL,
                                                        &ichg_reg_code);
                if (ret)
                        return ret;

                reg_code = ichg_reg_code;
        }

        ret = regmap_read(bq2515x->regmap, BQ2515X_PCHRGCTRL, &pchrgctrl);
        if (ret)
                return ret;

        icharge_range = pchrgctrl & BQ2515X_ICHARGE_RANGE;

        if (icharge_range)
                ichg_multiplier = BQ2515X_ICHG_RNG_1B1_UA;

        return reg_code * (ichg_multiplier * ichg_measurement /
                                                        BQ2515X_ICHG_DIVISOR);
}

static bool bq2515x_get_charge_disable(struct bq2515x_device *bq2515x)
{
        int ret;
        int ce_pin;
        int icctrl2;
        int charger_disable;

        ce_pin = gpiod_get_value_cansleep(bq2515x->ce_gpio);

        ret = regmap_read(bq2515x->regmap, BQ2515X_ICCTRL2, &icctrl2);
        if (ret)
                return ret;

        charger_disable = icctrl2 & BQ2515X_CHARGER_DISABLE;

        if (charger_disable || ce_pin)
                return true;

        return false;
}

static int bq2515x_set_charge_disable(struct bq2515x_device *bq2515x, int val)
{
        gpiod_set_value_cansleep(bq2515x->ce_gpio, val);

        return regmap_update_bits(bq2515x->regmap, BQ2515X_ICCTRL2,
                                        BQ2515X_CHARGER_DISABLE, val);
}

static int bq2515x_get_const_charge_current(struct bq2515x_device *bq2515x)
{
        int ret;
        u16 ichg_multiplier = BQ2515X_ICHG_RNG_1B0_UA;
        unsigned int ichg_reg_code;
        unsigned int pchrgctrl;
        unsigned int icharge_range;

        ret = regmap_read(bq2515x->regmap, BQ2515X_ICHG_CTRL, &ichg_reg_code);
        if (ret)
                return ret;

        ret = regmap_read(bq2515x->regmap, BQ2515X_PCHRGCTRL, &pchrgctrl);
        if (ret)
                return ret;

        icharge_range = pchrgctrl & BQ2515X_ICHARGE_RANGE;

        if (icharge_range)
                ichg_multiplier = BQ2515X_ICHG_RNG_1B1_UA;

        return ichg_reg_code * ichg_multiplier;
}

static int bq2515x_set_const_charge_current(struct bq2515x_device *bq2515x,
                                                                int val)
{
        int ret;
        unsigned int ichg_reg_code;
        u16 ichg_multiplier = BQ2515X_ICHG_RNG_1B0_UA;
        unsigned int icharge_range = 0;

        if (val > BQ2515X_ICHG_MAX_UA || val < BQ2515X_ICHG_MIN_UA)
                return -EINVAL;

        if (val > BQ2515X_ICHG_CURR_STEP_THRESH_UA) {
                ichg_multiplier = BQ2515X_ICHG_RNG_1B1_UA;
                icharge_range = BQ2515X_ICHARGE_RANGE;
        }

        bq2515x_set_charge_disable(bq2515x, 1);

        ret = regmap_update_bits(bq2515x->regmap, BQ2515X_PCHRGCTRL,
                                        BQ2515X_ICHARGE_RANGE, icharge_range);
        if (ret)
                return ret;

        ichg_reg_code = val / ichg_multiplier;

        ret = regmap_write(bq2515x->regmap, BQ2515X_ICHG_CTRL, ichg_reg_code);
        if (ret)
                return ret;

        return bq2515x_set_charge_disable(bq2515x, 0);
}

static int bq2515x_get_precharge_current(struct bq2515x_device *bq2515x)
{
        int ret;
        unsigned int pchrgctrl;
        unsigned int icharge_range;
        u16 precharge_multiplier = BQ2515X_ICHG_RNG_1B0_UA;
        unsigned int precharge_reg_code;

        ret = regmap_read(bq2515x->regmap, BQ2515X_PCHRGCTRL, &pchrgctrl);
        if (ret)
                return ret;

        icharge_range = pchrgctrl & BQ2515X_ICHARGE_RANGE;

        if (icharge_range)
                precharge_multiplier = BQ2515X_ICHG_RNG_1B1_UA;

        precharge_reg_code = pchrgctrl & BQ2515X_PRECHARGE_MASK;

        return precharge_reg_code * precharge_multiplier;
}

static int bq2515x_set_precharge_current(struct bq2515x_device *bq2515x,
                                        int val)
{
        int ret;
        unsigned int pchrgctrl;
        unsigned int icharge_range;
        unsigned int precharge_reg_code;
        unsigned int precharge_multiplier = BQ2515X_ICHG_RNG_1B0_UA;
        unsigned int precharge_max_ua = BQ2515X_PRECHRG_ICHRG_RNGE_1875_UA;

        ret = regmap_read(bq2515x->regmap, BQ2515X_PCHRGCTRL, &pchrgctrl);
        if (ret)
                return ret;

        icharge_range = pchrgctrl & BQ2515X_ICHARGE_RANGE;

        if (icharge_range) {
                precharge_max_ua = BQ2515X_PRECHRG_ICHRG_RNGE_3750_UA;
                precharge_multiplier = BQ2515X_ICHG_RNG_1B1_UA;
        } else {
                precharge_max_ua = BQ2515X_PRECHRG_ICHRG_RNGE_1875_UA;
                precharge_multiplier = BQ2515X_ICHG_RNG_1B0_UA;
        }
        if (val > precharge_max_ua || val < BQ2515X_ICHG_MIN_UA)
                return -EINVAL;

        precharge_reg_code = val / precharge_multiplier;

        ret = bq2515x_set_charge_disable(bq2515x, 1);
        if (ret)
                return ret;

        ret = regmap_update_bits(bq2515x->regmap, BQ2515X_PCHRGCTRL,
                                BQ2515X_PRECHARGE_MASK, precharge_reg_code);
        if (ret)
                return ret;

        return bq2515x_set_charge_disable(bq2515x, 0);
}

static int bq2515x_charging_status(struct bq2515x_device *bq2515x,
                                   union power_supply_propval *val)
{
        bool status0_no_fault;
        bool status1_no_fault;
        bool ce_status;
        bool charge_done;
        unsigned int status;
        int ret;

        if (!bq2515x->mains_online) {
                val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
                return 0;
        }

        ret = regmap_read(bq2515x->regmap, BQ2515X_STAT0, &status);
        if (ret)
                return ret;

        /*
         * The code block below is used to determine if any faults from the
         * STAT0 register are disbaling charging or if the charge has completed
         * according to the CHARGE_DONE_STAT bit.
         */
        if (((status & BQ2515X_STAT0_MASK) == true) &
                        ((status & BQ2515X_CHRG_DONE) == false)) {
                status0_no_fault = true;
                charge_done = false;
        } else if (status & BQ2515X_CHRG_DONE) {
                charge_done = true;
                status0_no_fault = false;
        } else {
                status0_no_fault = false;
                charge_done = false;
        }

        ret = regmap_read(bq2515x->regmap, BQ2515X_STAT1, &status);
        if (ret)
                return ret;
        /*
         * The code block below is used to determine if any faults from the
         * STAT1 register are disbaling charging
         */
        if ((status & BQ2515X_STAT1_MASK) == false)
                status1_no_fault = true;
        else
                status1_no_fault = false;

        ce_status = (!bq2515x_get_charge_disable(bq2515x));

        /*
         * If there are no faults and charging is enabled, then status is
         * charging. Otherwise, if charging is complete, then status is full.
         * Otherwise, if a fault exists or charging is disabled, then status is
         * not charging
         */
        if (status0_no_fault & status1_no_fault & ce_status)
                val->intval = POWER_SUPPLY_STATUS_CHARGING;
        else if (charge_done)
                val->intval = POWER_SUPPLY_STATUS_FULL;
        else if (!(status0_no_fault & status1_no_fault & ce_status))
                val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;

        return 0;
}

static int bq2515x_get_batt_reg(struct bq2515x_device *bq2515x)
{
        int vbat_reg_code;
        int ret;

        ret = regmap_read(bq2515x->regmap, BQ2515X_VBAT_CTRL, &vbat_reg_code);
        if (ret)
                return ret;

        return BQ2515X_VBAT_BASE_VOLT + vbat_reg_code * BQ2515X_VBAT_STEP_UV;
}

static int bq2515x_set_batt_reg(struct bq2515x_device *bq2515x, int val)
{
        int vbat_reg_code;

        if (val > BQ2515X_VBAT_REG_MAX || val < BQ2515X_VBAT_REG_MIN)
                return -EINVAL;

        vbat_reg_code = (val - BQ2515X_VBAT_BASE_VOLT) / BQ2515X_VBAT_STEP_UV;

        return regmap_write(bq2515x->regmap, BQ2515X_VBAT_CTRL, vbat_reg_code);
}

static int bq2515x_get_ilim_lvl(struct bq2515x_device *bq2515x)
{
        int ret;
        int ilimctrl;

        ret = regmap_read(bq2515x->regmap, BQ2515X_ILIMCTRL, &ilimctrl);
        if (ret)
                return ret;

        return bq2515x_ilim_lvl_values[ilimctrl & BQ2515X_ILIM_MASK];
}

static int bq2515x_set_ilim_lvl(struct bq2515x_device *bq2515x, int val)
{
        int i = 0;
        unsigned int array_size = ARRAY_SIZE(bq2515x_ilim_lvl_values);

        for (i = array_size - 1; i > 0; i--) {
                if (val >= bq2515x_ilim_lvl_values[i])
                        break;
        }
        return regmap_write(bq2515x->regmap, BQ2515X_ILIMCTRL, i);
}

static int bq2515x_power_supply_property_is_writeable(struct power_supply *psy,
                                        enum power_supply_property prop)
{
        switch (prop) {
        case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
        case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
        case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
        case POWER_SUPPLY_PROP_PRECHARGE_CURRENT:
                return true;
        default:
                return false;
        }
}

static int bq2515x_charger_get_health(struct bq2515x_device *bq2515x,
                                      union power_supply_propval *val)
{
        int health = POWER_SUPPLY_HEALTH_GOOD;
        int ret;
        unsigned int stat1;
        unsigned int flag3;

        if (!bq2515x->mains_online)
                bq2515x_wake_up(bq2515x);

        ret = regmap_read(bq2515x->regmap, BQ2515X_FLAG3, &flag3);
        if (ret)
                return ret;

        ret = regmap_read(bq2515x->regmap, BQ2515X_STAT1, &stat1);
        if (ret)
                return ret;

        if (stat1 & BQ2515X_HEALTH_MASK) {
                switch (stat1 & BQ2515X_HEALTH_MASK) {
                case BQ2515X_TS_HOT_STAT:
                        health = POWER_SUPPLY_HEALTH_HOT;
                        break;
                case BQ2515X_TS_WARM_STAT:
                        health = POWER_SUPPLY_HEALTH_WARM;
                        break;
                case BQ2515X_TS_COOL_STAT:
                        health = POWER_SUPPLY_HEALTH_COOL;
                        break;
                case BQ2515X_TS_COLD_STAT:
                        health = POWER_SUPPLY_HEALTH_COLD;
                        break;
                default:
                        health = POWER_SUPPLY_HEALTH_UNKNOWN;
                        break;
                }
        }

        if (stat1 & BQ2515X_VIN_OVP_FAULT_STAT)
                health = POWER_SUPPLY_HEALTH_OVERVOLTAGE;

        if (flag3 & BQ2515X_SAFETY_TIMER_EXP)
                health = POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE;

        val->intval = health;
        return 0;
}

static int bq2515x_mains_set_property(struct power_supply *psy,
                enum power_supply_property prop,
                const union power_supply_propval *val)
{
        struct bq2515x_device *bq2515x = power_supply_get_drvdata(psy);
        int ret;

        switch (prop) {
        case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
                ret = bq2515x_set_batt_reg(bq2515x, val->intval);
                break;

        case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
                ret = bq2515x_set_const_charge_current(bq2515x, val->intval);
                break;

        case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
                ret = bq2515x_set_ilim_lvl(bq2515x, val->intval);
                break;

        case POWER_SUPPLY_PROP_PRECHARGE_CURRENT:
                ret = bq2515x_set_precharge_current(bq2515x, val->intval);
                break;

        default:
                return -EINVAL;
        }

        return ret;
}

static int bq2515x_mains_get_property(struct power_supply *psy,
                                     enum power_supply_property prop,
                                     union power_supply_propval *val)
{
        struct bq2515x_device *bq2515x = power_supply_get_drvdata(psy);
        int ret = 0;

        switch (prop) {

        case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
                ret = bq2515x_get_const_charge_current(bq2515x);
                if (ret < 0)
                        return ret;

                val->intval = ret;
                break;

        case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
                ret = bq2515x_get_batt_reg(bq2515x);
                if (ret < 0)
                        return ret;
                val->intval = ret;
                break;

        case POWER_SUPPLY_PROP_PRECHARGE_CURRENT:
                ret = bq2515x_get_precharge_current(bq2515x);
                if (ret < 0)
                        return ret;
                val->intval = ret;
                break;

        case POWER_SUPPLY_PROP_ONLINE:
                val->intval = bq2515x->mains_online;
                break;

        case POWER_SUPPLY_PROP_HEALTH:
                ret = bq2515x_charger_get_health(bq2515x, val);
                if (ret)
                        val->intval = POWER_SUPPLY_HEALTH_UNKNOWN;
                break;

        case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
                ret = bq2515x_get_ilim_lvl(bq2515x);
                if (ret < 0)
                        return ret;
                val->intval = ret;
                break;

        case POWER_SUPPLY_PROP_MODEL_NAME:
                val->strval = bq2515x->model_name;
                break;

        case POWER_SUPPLY_PROP_MANUFACTURER:
                val->strval = BQ2515X_MANUFACTURER;
                break;

        case POWER_SUPPLY_PROP_STATUS:
                ret = bq2515x_charging_status(bq2515x, val);
                if (ret)
                        return ret;
                break;

        default:
                return -EINVAL;
        }

        return ret;
}

static int bq2515x_battery_get_property(struct power_supply *psy,
                                       enum power_supply_property prop,
                                       union power_supply_propval *val)
{
        struct bq2515x_device *bq2515x = power_supply_get_drvdata(psy);
        int ret;

        ret = bq2515x_update_ps_status(bq2515x);
        if (ret)
                return ret;

        switch (prop) {

        case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
                ret = bq2515x->init_data.vbatreg;
                if (ret < 0)
                        return ret;
                val->intval = ret;
                break;

        case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
                ret = bq2515x->init_data.ichg;
                if (ret < 0)
                        return ret;
                val->intval = ret;
                break;

        case POWER_SUPPLY_PROP_VOLTAGE_NOW:
                ret = bq2515x_get_battery_voltage_now(bq2515x);
                if (ret < 0)
                        return ret;
                val->intval = ret;
                break;

        case POWER_SUPPLY_PROP_CURRENT_NOW:
                ret = bq2515x_get_battery_current_now(bq2515x);
                if (ret < 0)
                        return ret;
                val->intval = ret;
                break;

        default:
                return -EINVAL;
        }
        return 0;
}

static const enum power_supply_property bq2515x_battery_properties[] = {
        POWER_SUPPLY_PROP_VOLTAGE_NOW,
        POWER_SUPPLY_PROP_CURRENT_NOW,
        POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
        POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX,
};

static const enum power_supply_property bq2515x_mains_properties[] = {
        POWER_SUPPLY_PROP_ONLINE,
        POWER_SUPPLY_PROP_STATUS,
        POWER_SUPPLY_PROP_HEALTH,
        POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT,
        POWER_SUPPLY_PROP_MODEL_NAME,
        POWER_SUPPLY_PROP_MANUFACTURER,
        POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE,
        POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT,
        POWER_SUPPLY_PROP_PRECHARGE_CURRENT,
};

static const struct power_supply_desc bq2515x_mains_desc = {
        .name                   = "bq2515x-mains",
        .type                   = POWER_SUPPLY_TYPE_MAINS,
        .get_property           = bq2515x_mains_get_property,
        .set_property           = bq2515x_mains_set_property,
        .properties             = bq2515x_mains_properties,
        .num_properties         = ARRAY_SIZE(bq2515x_mains_properties),
        .property_is_writeable  = bq2515x_power_supply_property_is_writeable,
};

static const struct power_supply_desc bq2515x_battery_desc = {
        .name                   = "bq2515x-battery",
        .type                   = POWER_SUPPLY_TYPE_BATTERY,
        .get_property           = bq2515x_battery_get_property,
        .properties             = bq2515x_battery_properties,
        .num_properties         = ARRAY_SIZE(bq2515x_battery_properties),
        .property_is_writeable  = bq2515x_power_supply_property_is_writeable,
};

static int bq2515x_power_supply_register(struct bq2515x_device *bq2515x,
                struct device *dev, struct power_supply_config psy_cfg)
{
        bq2515x->mains = devm_power_supply_register(bq2515x->dev,
                                                    &bq2515x_mains_desc,
                                                    &psy_cfg);
        if (IS_ERR(bq2515x->mains))
                return -EINVAL;

        bq2515x->battery = devm_power_supply_register(bq2515x->dev,
                                                      &bq2515x_battery_desc,
                                                      &psy_cfg);
        if (IS_ERR(bq2515x->battery))
                return -EINVAL;

        return 0;
}

static int bq2515x_hw_init(struct bq2515x_device *bq2515x)
{
        int ret;
        struct power_supply_battery_info *bat_info;

        ret = bq2515x_disable_watchdog_timers(bq2515x);
        if (ret)
                return ret;

        if (bq2515x->init_data.ilim) {
                ret = bq2515x_set_ilim_lvl(bq2515x, bq2515x->init_data.ilim);
                if (ret)
                        return ret;
        }

        ret = power_supply_get_battery_info(bq2515x->mains, &bat_info);
        if (ret) {
                dev_warn(bq2515x->dev, "battery info missing, default values will be applied\n");

                bq2515x->init_data.ichg = BQ2515X_DEFAULT_ICHG_UA;

                bq2515x->init_data.vbatreg = BQ2515X_DEFAULT_VBAT_REG_UV;

                bq2515x->init_data.iprechg = BQ2515X_DEFAULT_IPRECHARGE_UA;

        } else {
                bq2515x->init_data.ichg =
                                bat_info->constant_charge_current_max_ua;

                bq2515x->init_data.vbatreg =
                                bat_info->constant_charge_voltage_max_uv;

                bq2515x->init_data.iprechg =
                                bat_info->precharge_current_ua;
        }

        ret = bq2515x_set_const_charge_current(bq2515x,
                                                bq2515x->init_data.ichg);
        if (ret)
                return ret;

        ret = bq2515x_set_batt_reg(bq2515x, bq2515x->init_data.vbatreg);
        if (ret)
                return ret;

        return bq2515x_set_precharge_current(bq2515x,
                                                bq2515x->init_data.iprechg);
}

static int bq2515x_read_properties(struct bq2515x_device *bq2515x)
{
        int ret;

        ret = device_property_read_u32(bq2515x->dev,
                                      "input-current-limit-microamp",
                                      &bq2515x->init_data.ilim);
        if (ret)
                bq2515x->init_data.ilim = bq2515x->info->ilim;

        bq2515x->ac_detect_gpio = devm_gpiod_get_optional(bq2515x->dev,
                                                   "ac-detect", GPIOD_IN);
        if (IS_ERR(bq2515x->ac_detect_gpio)) {
                ret = PTR_ERR(bq2515x->ac_detect_gpio);
                dev_err(bq2515x->dev, "Failed to get ac detect");
                return ret;
        }

        bq2515x->reset_gpio = devm_gpiod_get_optional(bq2515x->dev,
                                                   "reset", GPIOD_OUT_LOW);
        if (IS_ERR(bq2515x->reset_gpio)) {
                ret = PTR_ERR(bq2515x->reset_gpio);
                dev_err(bq2515x->dev, "Failed to get reset");
                return ret;
        }

        bq2515x->powerdown_gpio = devm_gpiod_get_optional(bq2515x->dev,
                                                "powerdown", GPIOD_OUT_LOW);
        if (IS_ERR(bq2515x->powerdown_gpio)) {
                ret = PTR_ERR(bq2515x->powerdown_gpio);
                dev_err(bq2515x->dev, "Failed to get powerdown");
                return ret;
        }

        bq2515x->ce_gpio = devm_gpiod_get_optional(bq2515x->dev,
                                                   "charge-enable",
                                                   GPIOD_OUT_LOW);
        if (IS_ERR(bq2515x->ce_gpio)) {
                ret = PTR_ERR(bq2515x->ce_gpio);
                dev_err(bq2515x->dev, "Failed to get ce");
                return ret;
        }

        return 0;
}

static bool bq2515x_volatile_register(struct device *dev, unsigned int reg)
{
        switch (reg) {
        case BQ2515X_STAT0 ... BQ2515X_FLAG3:
        case BQ2515X_ADC_VBAT_M ... BQ2515X_ADC_IIN_L:
                return true;
        default:
                return false;
        }
}

static const struct regmap_config bq25150_regmap_config = {
        .reg_bits = 8,
        .val_bits = 8,

        .max_register           = BQ2515X_DEVICE_ID,
        .reg_defaults           = bq25150_reg_defaults,
        .num_reg_defaults       = ARRAY_SIZE(bq25150_reg_defaults),
        .cache_type             = REGCACHE_MAPLE,
        .volatile_reg           = bq2515x_volatile_register,
};

static const struct regmap_config bq25155_regmap_config = {
        .reg_bits = 8,
        .val_bits = 8,

        .max_register           = BQ2515X_DEVICE_ID,
        .reg_defaults           = bq25155_reg_defaults,
        .num_reg_defaults       = ARRAY_SIZE(bq25155_reg_defaults),
        .cache_type             = REGCACHE_MAPLE,
        .volatile_reg           = bq2515x_volatile_register,
};

static int bq2515x_probe(struct i2c_client *client)
{
        const struct i2c_device_id *id = i2c_client_get_device_id(client);
        struct device *dev = &client->dev;
        struct bq2515x_device *bq2515x;
        struct power_supply_config charger_cfg = {};
        int ret;

        bq2515x = devm_kzalloc(dev, sizeof(*bq2515x), GFP_KERNEL);
        if (!bq2515x)
                return -ENOMEM;

        bq2515x->dev = dev;

        strscpy(bq2515x->model_name, id->name, sizeof(bq2515x->model_name));

        bq2515x->info = i2c_get_match_data(client);
        bq2515x->regmap = devm_regmap_init_i2c(client,
                                               bq2515x->info->regmap_config);
        if (IS_ERR(bq2515x->regmap)) {
                dev_err(dev, "failed to allocate register map\n");
                return PTR_ERR(bq2515x->regmap);
        }

        i2c_set_clientdata(client, bq2515x);

        charger_cfg.drv_data = bq2515x;
        charger_cfg.fwnode = dev_fwnode(dev);

        ret = bq2515x_read_properties(bq2515x);
        if (ret) {
                dev_err(dev, "Failed to read device tree properties %d\n",
                                                                        ret);
                return ret;
        }

        ret = bq2515x_power_supply_register(bq2515x, dev, charger_cfg);
        if (ret) {
                dev_err(dev, "failed to register power supply\n");
                return ret;
        }

        ret = bq2515x_hw_init(bq2515x);
        if (ret) {
                dev_err(dev, "Cannot initialize the chip\n");
                return ret;
        }

        return 0;
}

static const struct bq2515x_info bq25150 = {
        .regmap_config = &bq25150_regmap_config,
        .ilim = BQ25150_DEFAULT_ILIM_UA,
};

static const struct bq2515x_info bq25155 = {
        .regmap_config = &bq25155_regmap_config,
        .ilim = BQ25155_DEFAULT_ILIM_UA,
};

static const struct i2c_device_id bq2515x_i2c_ids[] = {
        { .name = "bq25150", .driver_data = (kernel_ulong_t)&bq25150 },
        { .name = "bq25155", .driver_data = (kernel_ulong_t)&bq25155 },
        { }
};
MODULE_DEVICE_TABLE(i2c, bq2515x_i2c_ids);

static const struct of_device_id bq2515x_of_match[] = {
        { .compatible = "ti,bq25150", .data = &bq25150 },
        { .compatible = "ti,bq25155", .data = &bq25155 },
        {}
};
MODULE_DEVICE_TABLE(of, bq2515x_of_match);

static struct i2c_driver bq2515x_driver = {
        .driver = {
                .name = "bq2515x-charger",
                .of_match_table = bq2515x_of_match,
        },
        .probe = bq2515x_probe,
        .id_table = bq2515x_i2c_ids,
};
module_i2c_driver(bq2515x_driver);

MODULE_AUTHOR("Dan Murphy <dmurphy@ti.com>");
MODULE_AUTHOR("Ricardo Rivera-Matos <r-rivera-matos@ti.com>");
MODULE_DESCRIPTION("BQ2515X charger driver");
MODULE_LICENSE("GPL v2");