639 lines
16 KiB
C
639 lines
16 KiB
C
|
// SPDX-License-Identifier: GPL-2.0
|
||
|
//
|
||
|
// Copyright (C) 2021 Samuel Holland <samuel@sholland.org>
|
||
|
|
||
|
#include <linux/i2c.h>
|
||
|
#include <linux/module.h>
|
||
|
#include <linux/power_supply.h>
|
||
|
#include <linux/regmap.h>
|
||
|
|
||
|
#define IP5XXX_SYS_CTL0 0x01
|
||
|
#define IP5XXX_SYS_CTL0_WLED_DET_EN BIT(4)
|
||
|
#define IP5XXX_SYS_CTL0_WLED_EN BIT(3)
|
||
|
#define IP5XXX_SYS_CTL0_BOOST_EN BIT(2)
|
||
|
#define IP5XXX_SYS_CTL0_CHARGER_EN BIT(1)
|
||
|
#define IP5XXX_SYS_CTL1 0x02
|
||
|
#define IP5XXX_SYS_CTL1_LIGHT_SHDN_EN BIT(1)
|
||
|
#define IP5XXX_SYS_CTL1_LOAD_PWRUP_EN BIT(0)
|
||
|
#define IP5XXX_SYS_CTL2 0x0c
|
||
|
#define IP5XXX_SYS_CTL2_LIGHT_SHDN_TH GENMASK(7, 3)
|
||
|
#define IP5XXX_SYS_CTL3 0x03
|
||
|
#define IP5XXX_SYS_CTL3_LONG_PRESS_TIME_SEL GENMASK(7, 6)
|
||
|
#define IP5XXX_SYS_CTL3_BTN_SHDN_EN BIT(5)
|
||
|
#define IP5XXX_SYS_CTL4 0x04
|
||
|
#define IP5XXX_SYS_CTL4_SHDN_TIME_SEL GENMASK(7, 6)
|
||
|
#define IP5XXX_SYS_CTL4_VIN_PULLOUT_BOOST_EN BIT(5)
|
||
|
#define IP5XXX_SYS_CTL5 0x07
|
||
|
#define IP5XXX_SYS_CTL5_NTC_DIS BIT(6)
|
||
|
#define IP5XXX_SYS_CTL5_WLED_MODE_SEL BIT(1)
|
||
|
#define IP5XXX_SYS_CTL5_BTN_SHDN_SEL BIT(0)
|
||
|
#define IP5XXX_CHG_CTL1 0x22
|
||
|
#define IP5XXX_CHG_CTL1_BOOST_UVP_SEL GENMASK(3, 2)
|
||
|
#define IP5XXX_CHG_CTL2 0x24
|
||
|
#define IP5XXX_CHG_CTL2_BAT_TYPE_SEL GENMASK(6, 5)
|
||
|
#define IP5XXX_CHG_CTL2_BAT_TYPE_SEL_4_2V (0x0 << 5)
|
||
|
#define IP5XXX_CHG_CTL2_BAT_TYPE_SEL_4_3V (0x1 << 5)
|
||
|
#define IP5XXX_CHG_CTL2_BAT_TYPE_SEL_4_35V (0x2 << 5)
|
||
|
#define IP5XXX_CHG_CTL2_CONST_VOLT_SEL GENMASK(2, 1)
|
||
|
#define IP5XXX_CHG_CTL4 0x26
|
||
|
#define IP5XXX_CHG_CTL4_BAT_TYPE_SEL_EN BIT(6)
|
||
|
#define IP5XXX_CHG_CTL4A 0x25
|
||
|
#define IP5XXX_CHG_CTL4A_CONST_CUR_SEL GENMASK(4, 0)
|
||
|
#define IP5XXX_MFP_CTL0 0x51
|
||
|
#define IP5XXX_MFP_CTL1 0x52
|
||
|
#define IP5XXX_GPIO_CTL2 0x53
|
||
|
#define IP5XXX_GPIO_CTL2A 0x54
|
||
|
#define IP5XXX_GPIO_CTL3 0x55
|
||
|
#define IP5XXX_READ0 0x71
|
||
|
#define IP5XXX_READ0_CHG_STAT GENMASK(7, 5)
|
||
|
#define IP5XXX_READ0_CHG_STAT_IDLE (0x0 << 5)
|
||
|
#define IP5XXX_READ0_CHG_STAT_TRICKLE (0x1 << 5)
|
||
|
#define IP5XXX_READ0_CHG_STAT_CONST_VOLT (0x2 << 5)
|
||
|
#define IP5XXX_READ0_CHG_STAT_CONST_CUR (0x3 << 5)
|
||
|
#define IP5XXX_READ0_CHG_STAT_CONST_VOLT_STOP (0x4 << 5)
|
||
|
#define IP5XXX_READ0_CHG_STAT_FULL (0x5 << 5)
|
||
|
#define IP5XXX_READ0_CHG_STAT_TIMEOUT (0x6 << 5)
|
||
|
#define IP5XXX_READ0_CHG_OP BIT(4)
|
||
|
#define IP5XXX_READ0_CHG_END BIT(3)
|
||
|
#define IP5XXX_READ0_CONST_VOLT_TIMEOUT BIT(2)
|
||
|
#define IP5XXX_READ0_CHG_TIMEOUT BIT(1)
|
||
|
#define IP5XXX_READ0_TRICKLE_TIMEOUT BIT(0)
|
||
|
#define IP5XXX_READ0_TIMEOUT GENMASK(2, 0)
|
||
|
#define IP5XXX_READ1 0x72
|
||
|
#define IP5XXX_READ1_WLED_PRESENT BIT(7)
|
||
|
#define IP5XXX_READ1_LIGHT_LOAD BIT(6)
|
||
|
#define IP5XXX_READ1_VIN_OVERVOLT BIT(5)
|
||
|
#define IP5XXX_READ2 0x77
|
||
|
#define IP5XXX_READ2_BTN_PRESS BIT(3)
|
||
|
#define IP5XXX_READ2_BTN_LONG_PRESS BIT(1)
|
||
|
#define IP5XXX_READ2_BTN_SHORT_PRESS BIT(0)
|
||
|
#define IP5XXX_BATVADC_DAT0 0xa2
|
||
|
#define IP5XXX_BATVADC_DAT1 0xa3
|
||
|
#define IP5XXX_BATIADC_DAT0 0xa4
|
||
|
#define IP5XXX_BATIADC_DAT1 0xa5
|
||
|
#define IP5XXX_BATOCV_DAT0 0xa8
|
||
|
#define IP5XXX_BATOCV_DAT1 0xa9
|
||
|
|
||
|
struct ip5xxx {
|
||
|
struct regmap *regmap;
|
||
|
bool initialized;
|
||
|
};
|
||
|
|
||
|
/*
|
||
|
* The IP5xxx charger only responds on I2C when it is "awake". The charger is
|
||
|
* generally only awake when VIN is powered or when its boost converter is
|
||
|
* enabled. Going into shutdown resets all register values. To handle this:
|
||
|
* 1) When any bus error occurs, assume the charger has gone into shutdown.
|
||
|
* 2) Attempt the initialization sequence on each subsequent register access
|
||
|
* until it succeeds.
|
||
|
*/
|
||
|
static int ip5xxx_read(struct ip5xxx *ip5xxx, unsigned int reg,
|
||
|
unsigned int *val)
|
||
|
{
|
||
|
int ret;
|
||
|
|
||
|
ret = regmap_read(ip5xxx->regmap, reg, val);
|
||
|
if (ret)
|
||
|
ip5xxx->initialized = false;
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int ip5xxx_update_bits(struct ip5xxx *ip5xxx, unsigned int reg,
|
||
|
unsigned int mask, unsigned int val)
|
||
|
{
|
||
|
int ret;
|
||
|
|
||
|
ret = regmap_update_bits(ip5xxx->regmap, reg, mask, val);
|
||
|
if (ret)
|
||
|
ip5xxx->initialized = false;
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int ip5xxx_initialize(struct power_supply *psy)
|
||
|
{
|
||
|
struct ip5xxx *ip5xxx = power_supply_get_drvdata(psy);
|
||
|
int ret;
|
||
|
|
||
|
if (ip5xxx->initialized)
|
||
|
return 0;
|
||
|
|
||
|
/*
|
||
|
* Disable shutdown under light load.
|
||
|
* Enable power on when under load.
|
||
|
*/
|
||
|
ret = ip5xxx_update_bits(ip5xxx, IP5XXX_SYS_CTL1,
|
||
|
IP5XXX_SYS_CTL1_LIGHT_SHDN_EN |
|
||
|
IP5XXX_SYS_CTL1_LOAD_PWRUP_EN,
|
||
|
IP5XXX_SYS_CTL1_LOAD_PWRUP_EN);
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
|
||
|
/*
|
||
|
* Enable shutdown after a long button press (as configured below).
|
||
|
*/
|
||
|
ret = ip5xxx_update_bits(ip5xxx, IP5XXX_SYS_CTL3,
|
||
|
IP5XXX_SYS_CTL3_BTN_SHDN_EN,
|
||
|
IP5XXX_SYS_CTL3_BTN_SHDN_EN);
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
|
||
|
/*
|
||
|
* Power on automatically when VIN is removed.
|
||
|
*/
|
||
|
ret = ip5xxx_update_bits(ip5xxx, IP5XXX_SYS_CTL4,
|
||
|
IP5XXX_SYS_CTL4_VIN_PULLOUT_BOOST_EN,
|
||
|
IP5XXX_SYS_CTL4_VIN_PULLOUT_BOOST_EN);
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
|
||
|
/*
|
||
|
* Enable the NTC.
|
||
|
* Configure the button for two presses => LED, long press => shutdown.
|
||
|
*/
|
||
|
ret = ip5xxx_update_bits(ip5xxx, IP5XXX_SYS_CTL5,
|
||
|
IP5XXX_SYS_CTL5_NTC_DIS |
|
||
|
IP5XXX_SYS_CTL5_WLED_MODE_SEL |
|
||
|
IP5XXX_SYS_CTL5_BTN_SHDN_SEL,
|
||
|
IP5XXX_SYS_CTL5_WLED_MODE_SEL |
|
||
|
IP5XXX_SYS_CTL5_BTN_SHDN_SEL);
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
|
||
|
ip5xxx->initialized = true;
|
||
|
dev_dbg(psy->dev.parent, "Initialized after power on\n");
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static const enum power_supply_property ip5xxx_battery_properties[] = {
|
||
|
POWER_SUPPLY_PROP_STATUS,
|
||
|
POWER_SUPPLY_PROP_CHARGE_TYPE,
|
||
|
POWER_SUPPLY_PROP_HEALTH,
|
||
|
POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
|
||
|
POWER_SUPPLY_PROP_VOLTAGE_NOW,
|
||
|
POWER_SUPPLY_PROP_VOLTAGE_OCV,
|
||
|
POWER_SUPPLY_PROP_CURRENT_NOW,
|
||
|
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT,
|
||
|
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
|
||
|
POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE,
|
||
|
POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX,
|
||
|
};
|
||
|
|
||
|
static int ip5xxx_battery_get_status(struct ip5xxx *ip5xxx, int *val)
|
||
|
{
|
||
|
unsigned int rval;
|
||
|
int ret;
|
||
|
|
||
|
ret = ip5xxx_read(ip5xxx, IP5XXX_READ0, &rval);
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
|
||
|
switch (rval & IP5XXX_READ0_CHG_STAT) {
|
||
|
case IP5XXX_READ0_CHG_STAT_IDLE:
|
||
|
*val = POWER_SUPPLY_STATUS_DISCHARGING;
|
||
|
break;
|
||
|
case IP5XXX_READ0_CHG_STAT_TRICKLE:
|
||
|
case IP5XXX_READ0_CHG_STAT_CONST_CUR:
|
||
|
case IP5XXX_READ0_CHG_STAT_CONST_VOLT:
|
||
|
*val = POWER_SUPPLY_STATUS_CHARGING;
|
||
|
break;
|
||
|
case IP5XXX_READ0_CHG_STAT_CONST_VOLT_STOP:
|
||
|
case IP5XXX_READ0_CHG_STAT_FULL:
|
||
|
*val = POWER_SUPPLY_STATUS_FULL;
|
||
|
break;
|
||
|
case IP5XXX_READ0_CHG_STAT_TIMEOUT:
|
||
|
*val = POWER_SUPPLY_STATUS_NOT_CHARGING;
|
||
|
break;
|
||
|
default:
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int ip5xxx_battery_get_charge_type(struct ip5xxx *ip5xxx, int *val)
|
||
|
{
|
||
|
unsigned int rval;
|
||
|
int ret;
|
||
|
|
||
|
ret = ip5xxx_read(ip5xxx, IP5XXX_READ0, &rval);
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
|
||
|
switch (rval & IP5XXX_READ0_CHG_STAT) {
|
||
|
case IP5XXX_READ0_CHG_STAT_IDLE:
|
||
|
case IP5XXX_READ0_CHG_STAT_CONST_VOLT_STOP:
|
||
|
case IP5XXX_READ0_CHG_STAT_FULL:
|
||
|
case IP5XXX_READ0_CHG_STAT_TIMEOUT:
|
||
|
*val = POWER_SUPPLY_CHARGE_TYPE_NONE;
|
||
|
break;
|
||
|
case IP5XXX_READ0_CHG_STAT_TRICKLE:
|
||
|
*val = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
|
||
|
break;
|
||
|
case IP5XXX_READ0_CHG_STAT_CONST_CUR:
|
||
|
case IP5XXX_READ0_CHG_STAT_CONST_VOLT:
|
||
|
*val = POWER_SUPPLY_CHARGE_TYPE_STANDARD;
|
||
|
break;
|
||
|
default:
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int ip5xxx_battery_get_health(struct ip5xxx *ip5xxx, int *val)
|
||
|
{
|
||
|
unsigned int rval;
|
||
|
int ret;
|
||
|
|
||
|
ret = ip5xxx_read(ip5xxx, IP5XXX_READ0, &rval);
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
|
||
|
if (rval & IP5XXX_READ0_TIMEOUT)
|
||
|
*val = POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE;
|
||
|
else
|
||
|
*val = POWER_SUPPLY_HEALTH_GOOD;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int ip5xxx_battery_get_voltage_max(struct ip5xxx *ip5xxx, int *val)
|
||
|
{
|
||
|
unsigned int rval;
|
||
|
int ret;
|
||
|
|
||
|
ret = ip5xxx_read(ip5xxx, IP5XXX_CHG_CTL2, &rval);
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
|
||
|
/*
|
||
|
* It is not clear what this will return if
|
||
|
* IP5XXX_CHG_CTL4_BAT_TYPE_SEL_EN is not set...
|
||
|
*/
|
||
|
switch (rval & IP5XXX_CHG_CTL2_BAT_TYPE_SEL) {
|
||
|
case IP5XXX_CHG_CTL2_BAT_TYPE_SEL_4_2V:
|
||
|
*val = 4200000;
|
||
|
break;
|
||
|
case IP5XXX_CHG_CTL2_BAT_TYPE_SEL_4_3V:
|
||
|
*val = 4300000;
|
||
|
break;
|
||
|
case IP5XXX_CHG_CTL2_BAT_TYPE_SEL_4_35V:
|
||
|
*val = 4350000;
|
||
|
break;
|
||
|
default:
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int ip5xxx_battery_read_adc(struct ip5xxx *ip5xxx,
|
||
|
u8 lo_reg, u8 hi_reg, int *val)
|
||
|
{
|
||
|
unsigned int hi, lo;
|
||
|
int ret;
|
||
|
|
||
|
ret = ip5xxx_read(ip5xxx, lo_reg, &lo);
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
|
||
|
ret = ip5xxx_read(ip5xxx, hi_reg, &hi);
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
|
||
|
*val = sign_extend32(hi << 8 | lo, 13);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int ip5xxx_battery_get_property(struct power_supply *psy,
|
||
|
enum power_supply_property psp,
|
||
|
union power_supply_propval *val)
|
||
|
{
|
||
|
struct ip5xxx *ip5xxx = power_supply_get_drvdata(psy);
|
||
|
int raw, ret, vmax;
|
||
|
unsigned int rval;
|
||
|
|
||
|
ret = ip5xxx_initialize(psy);
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
|
||
|
switch (psp) {
|
||
|
case POWER_SUPPLY_PROP_STATUS:
|
||
|
return ip5xxx_battery_get_status(ip5xxx, &val->intval);
|
||
|
|
||
|
case POWER_SUPPLY_PROP_CHARGE_TYPE:
|
||
|
return ip5xxx_battery_get_charge_type(ip5xxx, &val->intval);
|
||
|
|
||
|
case POWER_SUPPLY_PROP_HEALTH:
|
||
|
return ip5xxx_battery_get_health(ip5xxx, &val->intval);
|
||
|
|
||
|
case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
|
||
|
return ip5xxx_battery_get_voltage_max(ip5xxx, &val->intval);
|
||
|
|
||
|
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
|
||
|
ret = ip5xxx_battery_read_adc(ip5xxx, IP5XXX_BATVADC_DAT0,
|
||
|
IP5XXX_BATVADC_DAT1, &raw);
|
||
|
|
||
|
val->intval = 2600000 + DIV_ROUND_CLOSEST(raw * 26855, 100);
|
||
|
return 0;
|
||
|
|
||
|
case POWER_SUPPLY_PROP_VOLTAGE_OCV:
|
||
|
ret = ip5xxx_battery_read_adc(ip5xxx, IP5XXX_BATOCV_DAT0,
|
||
|
IP5XXX_BATOCV_DAT1, &raw);
|
||
|
|
||
|
val->intval = 2600000 + DIV_ROUND_CLOSEST(raw * 26855, 100);
|
||
|
return 0;
|
||
|
|
||
|
case POWER_SUPPLY_PROP_CURRENT_NOW:
|
||
|
ret = ip5xxx_battery_read_adc(ip5xxx, IP5XXX_BATIADC_DAT0,
|
||
|
IP5XXX_BATIADC_DAT1, &raw);
|
||
|
|
||
|
val->intval = DIV_ROUND_CLOSEST(raw * 149197, 200);
|
||
|
return 0;
|
||
|
|
||
|
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
|
||
|
ret = ip5xxx_read(ip5xxx, IP5XXX_CHG_CTL4A, &rval);
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
|
||
|
rval &= IP5XXX_CHG_CTL4A_CONST_CUR_SEL;
|
||
|
val->intval = 100000 * rval;
|
||
|
return 0;
|
||
|
|
||
|
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
|
||
|
val->intval = 100000 * 0x1f;
|
||
|
return 0;
|
||
|
|
||
|
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
|
||
|
ret = ip5xxx_battery_get_voltage_max(ip5xxx, &vmax);
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
|
||
|
ret = ip5xxx_read(ip5xxx, IP5XXX_CHG_CTL2, &rval);
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
|
||
|
rval &= IP5XXX_CHG_CTL2_CONST_VOLT_SEL;
|
||
|
val->intval = vmax + 14000 * (rval >> 1);
|
||
|
return 0;
|
||
|
|
||
|
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
|
||
|
ret = ip5xxx_battery_get_voltage_max(ip5xxx, &vmax);
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
|
||
|
val->intval = vmax + 14000 * 3;
|
||
|
return 0;
|
||
|
|
||
|
default:
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static int ip5xxx_battery_set_voltage_max(struct ip5xxx *ip5xxx, int val)
|
||
|
{
|
||
|
unsigned int rval;
|
||
|
int ret;
|
||
|
|
||
|
switch (val) {
|
||
|
case 4200000:
|
||
|
rval = IP5XXX_CHG_CTL2_BAT_TYPE_SEL_4_2V;
|
||
|
break;
|
||
|
case 4300000:
|
||
|
rval = IP5XXX_CHG_CTL2_BAT_TYPE_SEL_4_3V;
|
||
|
break;
|
||
|
case 4350000:
|
||
|
rval = IP5XXX_CHG_CTL2_BAT_TYPE_SEL_4_35V;
|
||
|
break;
|
||
|
default:
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
ret = ip5xxx_update_bits(ip5xxx, IP5XXX_CHG_CTL2,
|
||
|
IP5XXX_CHG_CTL2_BAT_TYPE_SEL, rval);
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
|
||
|
ret = ip5xxx_update_bits(ip5xxx, IP5XXX_CHG_CTL4,
|
||
|
IP5XXX_CHG_CTL4_BAT_TYPE_SEL_EN,
|
||
|
IP5XXX_CHG_CTL4_BAT_TYPE_SEL_EN);
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int ip5xxx_battery_set_property(struct power_supply *psy,
|
||
|
enum power_supply_property psp,
|
||
|
const union power_supply_propval *val)
|
||
|
{
|
||
|
struct ip5xxx *ip5xxx = power_supply_get_drvdata(psy);
|
||
|
unsigned int rval;
|
||
|
int ret, vmax;
|
||
|
|
||
|
ret = ip5xxx_initialize(psy);
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
|
||
|
switch (psp) {
|
||
|
case POWER_SUPPLY_PROP_STATUS:
|
||
|
switch (val->intval) {
|
||
|
case POWER_SUPPLY_STATUS_CHARGING:
|
||
|
rval = IP5XXX_SYS_CTL0_CHARGER_EN;
|
||
|
break;
|
||
|
case POWER_SUPPLY_STATUS_DISCHARGING:
|
||
|
case POWER_SUPPLY_STATUS_NOT_CHARGING:
|
||
|
rval = 0;
|
||
|
break;
|
||
|
default:
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
return ip5xxx_update_bits(ip5xxx, IP5XXX_SYS_CTL0,
|
||
|
IP5XXX_SYS_CTL0_CHARGER_EN, rval);
|
||
|
|
||
|
case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
|
||
|
return ip5xxx_battery_set_voltage_max(ip5xxx, val->intval);
|
||
|
|
||
|
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
|
||
|
rval = val->intval / 100000;
|
||
|
return ip5xxx_update_bits(ip5xxx, IP5XXX_CHG_CTL4A,
|
||
|
IP5XXX_CHG_CTL4A_CONST_CUR_SEL, rval);
|
||
|
|
||
|
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
|
||
|
ret = ip5xxx_battery_get_voltage_max(ip5xxx, &vmax);
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
|
||
|
rval = ((val->intval - vmax) / 14000) << 1;
|
||
|
return ip5xxx_update_bits(ip5xxx, IP5XXX_CHG_CTL2,
|
||
|
IP5XXX_CHG_CTL2_CONST_VOLT_SEL, rval);
|
||
|
|
||
|
default:
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static int ip5xxx_battery_property_is_writeable(struct power_supply *psy,
|
||
|
enum power_supply_property psp)
|
||
|
{
|
||
|
return psp == POWER_SUPPLY_PROP_STATUS ||
|
||
|
psp == POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN ||
|
||
|
psp == POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT ||
|
||
|
psp == POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE;
|
||
|
}
|
||
|
|
||
|
static const struct power_supply_desc ip5xxx_battery_desc = {
|
||
|
.name = "ip5xxx-battery",
|
||
|
.type = POWER_SUPPLY_TYPE_BATTERY,
|
||
|
.properties = ip5xxx_battery_properties,
|
||
|
.num_properties = ARRAY_SIZE(ip5xxx_battery_properties),
|
||
|
.get_property = ip5xxx_battery_get_property,
|
||
|
.set_property = ip5xxx_battery_set_property,
|
||
|
.property_is_writeable = ip5xxx_battery_property_is_writeable,
|
||
|
};
|
||
|
|
||
|
static const enum power_supply_property ip5xxx_boost_properties[] = {
|
||
|
POWER_SUPPLY_PROP_ONLINE,
|
||
|
POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
|
||
|
};
|
||
|
|
||
|
static int ip5xxx_boost_get_property(struct power_supply *psy,
|
||
|
enum power_supply_property psp,
|
||
|
union power_supply_propval *val)
|
||
|
{
|
||
|
struct ip5xxx *ip5xxx = power_supply_get_drvdata(psy);
|
||
|
unsigned int rval;
|
||
|
int ret;
|
||
|
|
||
|
ret = ip5xxx_initialize(psy);
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
|
||
|
switch (psp) {
|
||
|
case POWER_SUPPLY_PROP_ONLINE:
|
||
|
ret = ip5xxx_read(ip5xxx, IP5XXX_SYS_CTL0, &rval);
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
|
||
|
val->intval = !!(rval & IP5XXX_SYS_CTL0_BOOST_EN);
|
||
|
return 0;
|
||
|
|
||
|
case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
|
||
|
ret = ip5xxx_read(ip5xxx, IP5XXX_CHG_CTL1, &rval);
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
|
||
|
rval &= IP5XXX_CHG_CTL1_BOOST_UVP_SEL;
|
||
|
val->intval = 4530000 + 100000 * (rval >> 2);
|
||
|
return 0;
|
||
|
|
||
|
default:
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static int ip5xxx_boost_set_property(struct power_supply *psy,
|
||
|
enum power_supply_property psp,
|
||
|
const union power_supply_propval *val)
|
||
|
{
|
||
|
struct ip5xxx *ip5xxx = power_supply_get_drvdata(psy);
|
||
|
unsigned int rval;
|
||
|
int ret;
|
||
|
|
||
|
ret = ip5xxx_initialize(psy);
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
|
||
|
switch (psp) {
|
||
|
case POWER_SUPPLY_PROP_ONLINE:
|
||
|
rval = val->intval ? IP5XXX_SYS_CTL0_BOOST_EN : 0;
|
||
|
return ip5xxx_update_bits(ip5xxx, IP5XXX_SYS_CTL0,
|
||
|
IP5XXX_SYS_CTL0_BOOST_EN, rval);
|
||
|
|
||
|
case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
|
||
|
rval = ((val->intval - 4530000) / 100000) << 2;
|
||
|
return ip5xxx_update_bits(ip5xxx, IP5XXX_CHG_CTL1,
|
||
|
IP5XXX_CHG_CTL1_BOOST_UVP_SEL, rval);
|
||
|
|
||
|
default:
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static int ip5xxx_boost_property_is_writeable(struct power_supply *psy,
|
||
|
enum power_supply_property psp)
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
static const struct power_supply_desc ip5xxx_boost_desc = {
|
||
|
.name = "ip5xxx-boost",
|
||
|
.type = POWER_SUPPLY_TYPE_USB,
|
||
|
.properties = ip5xxx_boost_properties,
|
||
|
.num_properties = ARRAY_SIZE(ip5xxx_boost_properties),
|
||
|
.get_property = ip5xxx_boost_get_property,
|
||
|
.set_property = ip5xxx_boost_set_property,
|
||
|
.property_is_writeable = ip5xxx_boost_property_is_writeable,
|
||
|
};
|
||
|
|
||
|
static const struct regmap_config ip5xxx_regmap_config = {
|
||
|
.reg_bits = 8,
|
||
|
.val_bits = 8,
|
||
|
.max_register = IP5XXX_BATOCV_DAT1,
|
||
|
};
|
||
|
|
||
|
static int ip5xxx_power_probe(struct i2c_client *client)
|
||
|
{
|
||
|
struct power_supply_config psy_cfg = {};
|
||
|
struct device *dev = &client->dev;
|
||
|
struct power_supply *psy;
|
||
|
struct ip5xxx *ip5xxx;
|
||
|
|
||
|
ip5xxx = devm_kzalloc(dev, sizeof(*ip5xxx), GFP_KERNEL);
|
||
|
if (!ip5xxx)
|
||
|
return -ENOMEM;
|
||
|
|
||
|
ip5xxx->regmap = devm_regmap_init_i2c(client, &ip5xxx_regmap_config);
|
||
|
if (IS_ERR(ip5xxx->regmap))
|
||
|
return PTR_ERR(ip5xxx->regmap);
|
||
|
|
||
|
psy_cfg.of_node = dev->of_node;
|
||
|
psy_cfg.drv_data = ip5xxx;
|
||
|
|
||
|
psy = devm_power_supply_register(dev, &ip5xxx_battery_desc, &psy_cfg);
|
||
|
if (IS_ERR(psy))
|
||
|
return PTR_ERR(psy);
|
||
|
|
||
|
psy = devm_power_supply_register(dev, &ip5xxx_boost_desc, &psy_cfg);
|
||
|
if (IS_ERR(psy))
|
||
|
return PTR_ERR(psy);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static const struct of_device_id ip5xxx_power_of_match[] = {
|
||
|
{ .compatible = "injoinic,ip5108" },
|
||
|
{ .compatible = "injoinic,ip5109" },
|
||
|
{ .compatible = "injoinic,ip5207" },
|
||
|
{ .compatible = "injoinic,ip5209" },
|
||
|
{ }
|
||
|
};
|
||
|
MODULE_DEVICE_TABLE(of, ip5xxx_power_of_match);
|
||
|
|
||
|
static struct i2c_driver ip5xxx_power_driver = {
|
||
|
.probe_new = ip5xxx_power_probe,
|
||
|
.driver = {
|
||
|
.name = "ip5xxx-power",
|
||
|
.of_match_table = ip5xxx_power_of_match,
|
||
|
}
|
||
|
};
|
||
|
module_i2c_driver(ip5xxx_power_driver);
|
||
|
|
||
|
MODULE_AUTHOR("Samuel Holland <samuel@sholland.org>");
|
||
|
MODULE_DESCRIPTION("Injoinic IP5xxx power bank IC driver");
|
||
|
MODULE_LICENSE("GPL");
|