mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/chenhuacai/linux-loongson
synced 2025-09-04 02:25:58 +00:00

i2c-core updates (collected by Wolfram) - remove last user and unexport i2c_of_match_device() - irq usage cleanup from Jiri i2c-host updates (collected by Andi) Refactoring and cleanups - octeon, cadence, i801, pasemi, mlxbf, bcm-iproc: general refactorings - octeon: remove 10-bit address support Improvements - amd-asf: improved error handling - designware: use guard(mutex) - amd-asf, designware: update naming to follow latest specs - cadence: fix cleanup path in probe - i801: use MMIO and I/O mapping helpers to access registers - pxa: handle error after clk_prepare_enable New features - added i2c_10bit_addr_*_from_msg() and updated multiple drivers - omap: added multiplexer state handling - qcom-geni: update frequency configuration - qup: introduce DMA usage policy New hardware support - exynos: add support for Samsung exynos7870 - k1: add support for spacemit k1 (new driver) - imx: add support for i.mx94 lpi2c - rk3x: add support for rk3562 - designware: add support for Renesas RZ/N1D Multiplexers - ltc4306, reg: fix assignment in platform_driver structure at24 eeprom updates (collected by Bartosz) - add two new compatible entries to the DT binding document - drop of_match_ptr() and ACPI_PTR() macros -----BEGIN PGP SIGNATURE----- iQIzBAABCgAdFiEEOZGx6rniZ1Gk92RdFA3kzBSgKbYFAmfoBuwACgkQFA3kzBSg KbZ+xg/9EEJIae2OVU1o7Ea4kPeyQEdLd2c6wjdrBWRdKuMcBMXR5y16yNI73bWZ greZpQDM3AQDNDoJQRe0502pjY/IzVmG6cgDqEdkI0/9x09e2Tk4UJacJttWbcS1 eCvfvn+yIC2XFoDH/kQozoxSnADXF7yA4XFjF0ljosk+kbtTj6VYV4P0iOkyTaM6 yWiU55X7x6S6P4pLtY+KByTFEfwV5YLBIJZv4U7sGR+XghUkFfa5mg3ZfblKDIe3 7SU1XLwSpLbLDf5xn1GMJEHZoq+Glqvrg32TSwbPcV6Aqf67LGXcTvUd+lPxWDsk rrpylo4XCfSgBn26NuCJBVWXmtraa1QEKrKvNw+7jIakxJ32YuAo/Lvc2Jk3KMcy bdOLR3C5RXNJAE2QDgsIspp3VJSMk43aeLhJm/zATu4sEjOUc0tNwQueXeuVXbp2 X9wX9ErPBpwn9f2jCZ1+v4vRbQbmQZREo6xVzNUd/AJf3muthKBP7tLokxq9pNlx q/x6N4PGy+x9MFT8BIwNgPpP4i8bVtWh+RJL68jh3WQoqNigkDGD0qmO/MZkHUeW n+pdI1MpoMaB+BH3HmWuTCOMYGNo4hfdrrAhmdh3fqQRrikgEXxXp3jicRBjoih+ vSgjQ5Q4bwhHzYqRNbBWVIvgSayQKVbaLqkbWjoWdngJCOUU7Tw= =q/EK -----END PGP SIGNATURE----- Merge tag 'i2c-for-6.15-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/wsa/linux Pull i2c updates from Wolfram Sang: "i2c-core updates (collected by Wolfram): - remove last user and unexport i2c_of_match_device() - irq usage cleanup from Jiri i2c-host updates (collected by Andi): Refactoring and cleanups: - octeon, cadence, i801, pasemi, mlxbf, bcm-iproc: general refactorings - octeon: remove 10-bit address support Improvements: - amd-asf: improved error handling - designware: use guard(mutex) - amd-asf, designware: update naming to follow latest specs - cadence: fix cleanup path in probe - i801: use MMIO and I/O mapping helpers to access registers - pxa: handle error after clk_prepare_enable New features: - added i2c_10bit_addr_*_from_msg() and updated multiple drivers - omap: added multiplexer state handling - qcom-geni: update frequency configuration - qup: introduce DMA usage policy New hardware support: - exynos: add support for Samsung exynos7870 - k1: add support for spacemit k1 (new driver) - imx: add support for i.mx94 lpi2c - rk3x: add support for rk3562 - designware: add support for Renesas RZ/N1D Multiplexers: - ltc4306, reg: fix assignment in platform_driver structure at24 eeprom updates (collected by Bartosz): - add two new compatible entries to the DT binding document - drop of_match_ptr() and ACPI_PTR() macros" * tag 'i2c-for-6.15-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/wsa/linux: (50 commits) dt-bindings: i2c: snps,designware-i2c: describe Renesas RZ/N1D variant irqdomain: i2c: Switch to irq_find_mapping() i2c: iproc: Refactor prototype and remove redundant error checks i2c: qcom-geni: Update i2c frequency table to match hardware guidance i2c: mlxbf: Use readl_poll_timeout_atomic() for polling i2c: pasemi: Add registers bits and switch to BIT() i2c: k1: Initialize variable before use i2c: spacemit: add support for SpacemiT K1 SoC dt-bindings: i2c: spacemit: add support for K1 SoC i2c: omap: Add support for setting mux dt-bindings: i2c: omap: Add mux-states property i2c: octeon: remove 10-bit addressing support i2c: octeon: fix return commenting i2c: i801: Use MMIO if available i2c: i801: Switch to iomapped register access i2c: i801: Improve too small kill wait time in i801_check_post i2c: i801: Move i801_wait_intr and i801_wait_byte_done in the code i2c: i801: Cosmetic improvements i2c: cadence: Move reset_control_assert after pm_runtime_set_suspended in probe error path i2c: cadence: Simplify using devm_clk_get_enabled() ...
884 lines
25 KiB
C
884 lines
25 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_BAT_TYPE_4_2V 0x0
|
|
#define IP5XXX_BAT_TYPE_4_3V 0x1
|
|
#define IP5XXX_BAT_TYPE_4_35V 0x2
|
|
#define IP5XXX_BAT_TYPE_4_4V 0x3
|
|
#define IP5XXX_CHG_STAT_IDLE 0x0
|
|
#define IP5XXX_CHG_STAT_TRICKLE 0x1
|
|
#define IP5XXX_CHG_STAT_CONST_VOLT 0x2
|
|
#define IP5XXX_CHG_STAT_CONST_CUR 0x3
|
|
#define IP5XXX_CHG_STAT_CONST_VOLT_STOP 0x4
|
|
#define IP5XXX_CHG_STAT_FULL 0x5
|
|
#define IP5XXX_CHG_STAT_TIMEOUT 0x6
|
|
|
|
struct ip5xxx {
|
|
struct regmap *regmap;
|
|
bool initialized;
|
|
struct {
|
|
struct {
|
|
/* Charger enable */
|
|
struct regmap_field *enable;
|
|
/* Constant voltage value */
|
|
struct regmap_field *const_volt_sel;
|
|
/* Constant current value */
|
|
struct regmap_field *const_curr_sel;
|
|
/* Charger status */
|
|
struct regmap_field *status;
|
|
/* Charging ended flag */
|
|
struct regmap_field *chg_end;
|
|
/* Timeout flags (CV, charge, trickle) */
|
|
struct regmap_field *timeout;
|
|
/* Overvoltage limit */
|
|
struct regmap_field *vin_overvolt;
|
|
} charger;
|
|
struct {
|
|
/* Boost converter enable */
|
|
struct regmap_field *enable;
|
|
struct {
|
|
/* Light load shutdown enable */
|
|
struct regmap_field *enable;
|
|
/* Light load shutdown current limit */
|
|
struct regmap_field *i_limit;
|
|
} light_load_shutdown;
|
|
/* Automatic powerup on increased load */
|
|
struct regmap_field *load_powerup_en;
|
|
/* Automatic powerup on VIN pull-out */
|
|
struct regmap_field *vin_pullout_en;
|
|
/* Undervoltage limit */
|
|
struct regmap_field *undervolt_limit;
|
|
/* Light load status flag */
|
|
struct regmap_field *light_load_status;
|
|
} boost;
|
|
struct {
|
|
/* NTC disable */
|
|
struct regmap_field *ntc_dis;
|
|
/* Battery voltage type */
|
|
struct regmap_field *type;
|
|
/* Battery voltage autoset from Vset pin */
|
|
struct regmap_field *vset_en;
|
|
struct {
|
|
/* Battery measurement registers */
|
|
struct ip5xxx_battery_adc_regs {
|
|
struct regmap_field *low;
|
|
struct regmap_field *high;
|
|
} volt, curr, open_volt;
|
|
} adc;
|
|
} battery;
|
|
struct {
|
|
/* Double/long press shutdown enable */
|
|
struct regmap_field *shdn_enable;
|
|
/* WLED activation: double press or long press */
|
|
struct regmap_field *wled_mode;
|
|
/* Shutdown activation: double press or long press */
|
|
struct regmap_field *shdn_mode;
|
|
/* Long press time */
|
|
struct regmap_field *long_press_time;
|
|
/* Button pressed */
|
|
struct regmap_field *pressed;
|
|
/* Button long-pressed */
|
|
struct regmap_field *long_pressed;
|
|
/* Button short-pressed */
|
|
struct regmap_field *short_pressed;
|
|
} btn;
|
|
struct {
|
|
/* WLED enable */
|
|
struct regmap_field *enable;
|
|
/* WLED detect */
|
|
struct regmap_field *detect_en;
|
|
/* WLED present */
|
|
struct regmap_field *present;
|
|
} wled;
|
|
} regs;
|
|
|
|
/* Maximum supported battery voltage (via regs.battery.type) */
|
|
int vbat_max;
|
|
/* Scaling constants for regs.boost.undervolt_limit */
|
|
struct {
|
|
int setpoint;
|
|
int microvolts_per_bit;
|
|
} boost_undervolt;
|
|
/* Scaling constants for regs.charger.const_curr_sel */
|
|
struct {
|
|
int setpoint;
|
|
} const_curr;
|
|
/* Whether regs.charger.chg_end is inverted */
|
|
u8 chg_end_inverted;
|
|
};
|
|
|
|
#define REG_FIELD_UNSUPPORTED { .lsb = 1 }
|
|
/* Register fields layout. Unsupported registers marked as { .lsb = 1 } */
|
|
struct ip5xxx_regfield_config {
|
|
const struct reg_field charger_enable;
|
|
const struct reg_field charger_const_volt_sel;
|
|
const struct reg_field charger_const_curr_sel;
|
|
const struct reg_field charger_status;
|
|
const struct reg_field charger_chg_end;
|
|
const struct reg_field charger_timeout;
|
|
const struct reg_field charger_vin_overvolt;
|
|
const struct reg_field boost_enable;
|
|
const struct reg_field boost_llshdn_enable;
|
|
const struct reg_field boost_llshdn_i_limit;
|
|
const struct reg_field boost_load_powerup_en;
|
|
const struct reg_field boost_vin_pullout_en;
|
|
const struct reg_field boost_undervolt_limit;
|
|
const struct reg_field boost_light_load_status;
|
|
const struct reg_field battery_ntc_dis;
|
|
const struct reg_field battery_type;
|
|
const struct reg_field battery_vset_en;
|
|
const struct reg_field battery_adc_volt_low;
|
|
const struct reg_field battery_adc_volt_high;
|
|
const struct reg_field battery_adc_curr_low;
|
|
const struct reg_field battery_adc_curr_high;
|
|
const struct reg_field battery_adc_ovolt_low;
|
|
const struct reg_field battery_adc_ovolt_high;
|
|
const struct reg_field btn_shdn_enable;
|
|
const struct reg_field btn_wled_mode;
|
|
const struct reg_field btn_shdn_mode;
|
|
const struct reg_field btn_long_press_time;
|
|
const struct reg_field btn_pressed;
|
|
const struct reg_field btn_long_pressed;
|
|
const struct reg_field btn_short_pressed;
|
|
const struct reg_field wled_enable;
|
|
const struct reg_field wled_detect_en;
|
|
const struct reg_field wled_present;
|
|
|
|
int vbat_max;
|
|
int boost_undervolt_setpoint;
|
|
int boost_undervolt_uv_per_bit;
|
|
int const_curr_setpoint;
|
|
u8 chg_end_inverted;
|
|
};
|
|
|
|
/*
|
|
* 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, struct regmap_field *field,
|
|
unsigned int *val)
|
|
{
|
|
int ret;
|
|
|
|
if (!field)
|
|
return -EOPNOTSUPP;
|
|
|
|
ret = regmap_field_read(field, val);
|
|
if (ret)
|
|
ip5xxx->initialized = false;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int ip5xxx_write(struct ip5xxx *ip5xxx, struct regmap_field *field,
|
|
unsigned int val)
|
|
{
|
|
int ret;
|
|
|
|
if (!field)
|
|
return -EOPNOTSUPP;
|
|
|
|
ret = regmap_field_write(field, 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.
|
|
*/
|
|
if (ip5xxx->regs.boost.light_load_shutdown.enable) {
|
|
ret = ip5xxx_write(ip5xxx, ip5xxx->regs.boost.light_load_shutdown.enable, 0);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
ret = ip5xxx_write(ip5xxx, ip5xxx->regs.boost.load_powerup_en, 1);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/*
|
|
* Enable shutdown after a long button press (as configured below).
|
|
*/
|
|
ret = ip5xxx_write(ip5xxx, ip5xxx->regs.btn.shdn_enable, 1);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/*
|
|
* Power on automatically when VIN is removed.
|
|
*/
|
|
ret = ip5xxx_write(ip5xxx, ip5xxx->regs.boost.vin_pullout_en, 1);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/*
|
|
* Enable the NTC.
|
|
* Configure the button for two presses => LED, long press => shutdown.
|
|
*/
|
|
if (ip5xxx->regs.battery.ntc_dis) {
|
|
ret = ip5xxx_write(ip5xxx, ip5xxx->regs.battery.ntc_dis, 0);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
ret = ip5xxx_write(ip5xxx, ip5xxx->regs.btn.wled_mode, 1);
|
|
if (ret)
|
|
return ret;
|
|
ret = ip5xxx_write(ip5xxx, ip5xxx->regs.btn.shdn_mode, 1);
|
|
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;
|
|
|
|
if (!ip5xxx->regs.charger.status) {
|
|
// Fall-back to Charging Ended bit
|
|
ret = ip5xxx_read(ip5xxx, ip5xxx->regs.charger.chg_end, &rval);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (rval == ip5xxx->chg_end_inverted)
|
|
*val = POWER_SUPPLY_STATUS_CHARGING;
|
|
else
|
|
*val = POWER_SUPPLY_STATUS_NOT_CHARGING;
|
|
return 0;
|
|
}
|
|
|
|
ret = ip5xxx_read(ip5xxx, ip5xxx->regs.charger.status, &rval);
|
|
if (ret)
|
|
return ret;
|
|
|
|
switch (rval) {
|
|
case IP5XXX_CHG_STAT_IDLE:
|
|
*val = POWER_SUPPLY_STATUS_DISCHARGING;
|
|
break;
|
|
case IP5XXX_CHG_STAT_TRICKLE:
|
|
case IP5XXX_CHG_STAT_CONST_CUR:
|
|
case IP5XXX_CHG_STAT_CONST_VOLT:
|
|
*val = POWER_SUPPLY_STATUS_CHARGING;
|
|
break;
|
|
case IP5XXX_CHG_STAT_CONST_VOLT_STOP:
|
|
case IP5XXX_CHG_STAT_FULL:
|
|
*val = POWER_SUPPLY_STATUS_FULL;
|
|
break;
|
|
case IP5XXX_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->regs.charger.status, &rval);
|
|
if (ret)
|
|
return ret;
|
|
|
|
switch (rval) {
|
|
case IP5XXX_CHG_STAT_IDLE:
|
|
case IP5XXX_CHG_STAT_CONST_VOLT_STOP:
|
|
case IP5XXX_CHG_STAT_FULL:
|
|
case IP5XXX_CHG_STAT_TIMEOUT:
|
|
*val = POWER_SUPPLY_CHARGE_TYPE_NONE;
|
|
break;
|
|
case IP5XXX_CHG_STAT_TRICKLE:
|
|
*val = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
|
|
break;
|
|
case IP5XXX_CHG_STAT_CONST_CUR:
|
|
case IP5XXX_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->regs.charger.timeout, &rval);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (rval)
|
|
*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->regs.battery.type, &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) {
|
|
case IP5XXX_BAT_TYPE_4_2V:
|
|
*val = 4200000;
|
|
break;
|
|
case IP5XXX_BAT_TYPE_4_3V:
|
|
*val = 4300000;
|
|
break;
|
|
case IP5XXX_BAT_TYPE_4_35V:
|
|
*val = 4350000;
|
|
break;
|
|
case IP5XXX_BAT_TYPE_4_4V:
|
|
*val = 4400000;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ip5xxx_battery_read_adc(struct ip5xxx *ip5xxx,
|
|
struct ip5xxx_battery_adc_regs *regs, int *val)
|
|
{
|
|
unsigned int hi, lo;
|
|
int ret;
|
|
|
|
ret = ip5xxx_read(ip5xxx, regs->low, &lo);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = ip5xxx_read(ip5xxx, regs->high, &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->regs.battery.adc.volt, &raw);
|
|
if (ret)
|
|
return ret;
|
|
|
|
val->intval = 2600000 + DIV_ROUND_CLOSEST(raw * 26855, 100);
|
|
return 0;
|
|
|
|
case POWER_SUPPLY_PROP_VOLTAGE_OCV:
|
|
ret = ip5xxx_battery_read_adc(ip5xxx, &ip5xxx->regs.battery.adc.open_volt, &raw);
|
|
if (ret)
|
|
return ret;
|
|
|
|
val->intval = 2600000 + DIV_ROUND_CLOSEST(raw * 26855, 100);
|
|
return 0;
|
|
|
|
case POWER_SUPPLY_PROP_CURRENT_NOW:
|
|
ret = ip5xxx_battery_read_adc(ip5xxx, &ip5xxx->regs.battery.adc.curr, &raw);
|
|
if (ret)
|
|
return ret;
|
|
|
|
val->intval = DIV_ROUND_CLOSEST(raw * 149197, 200);
|
|
return 0;
|
|
|
|
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
|
|
ret = ip5xxx_read(ip5xxx, ip5xxx->regs.charger.const_curr_sel, &rval);
|
|
if (ret)
|
|
return ret;
|
|
|
|
val->intval = ip5xxx->const_curr.setpoint + 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->regs.charger.const_volt_sel, &rval);
|
|
if (ret)
|
|
return ret;
|
|
|
|
val->intval = vmax + 14000 * rval;
|
|
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;
|
|
|
|
if (val > ip5xxx->vbat_max)
|
|
return -EINVAL;
|
|
|
|
switch (val) {
|
|
case 4200000:
|
|
rval = IP5XXX_BAT_TYPE_4_2V;
|
|
break;
|
|
case 4300000:
|
|
rval = IP5XXX_BAT_TYPE_4_3V;
|
|
break;
|
|
case 4350000:
|
|
rval = IP5XXX_BAT_TYPE_4_35V;
|
|
break;
|
|
case 4400000:
|
|
rval = IP5XXX_BAT_TYPE_4_4V;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = ip5xxx_write(ip5xxx, ip5xxx->regs.battery.type, rval);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* Don't try to auto-detect battery type, even if the IC could */
|
|
if (ip5xxx->regs.battery.vset_en) {
|
|
ret = ip5xxx_write(ip5xxx, ip5xxx->regs.battery.vset_en, 1);
|
|
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 = 1;
|
|
break;
|
|
case POWER_SUPPLY_STATUS_DISCHARGING:
|
|
case POWER_SUPPLY_STATUS_NOT_CHARGING:
|
|
rval = 0;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
return ip5xxx_write(ip5xxx, ip5xxx->regs.charger.enable, 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 - ip5xxx->const_curr.setpoint) / 100000;
|
|
return ip5xxx_write(ip5xxx, ip5xxx->regs.charger.const_curr_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;
|
|
return ip5xxx_write(ip5xxx, ip5xxx->regs.charger.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->regs.boost.enable, &rval);
|
|
if (ret)
|
|
return ret;
|
|
|
|
val->intval = !!rval;
|
|
return 0;
|
|
|
|
case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
|
|
ret = ip5xxx_read(ip5xxx, ip5xxx->regs.boost.undervolt_limit, &rval);
|
|
if (ret)
|
|
return ret;
|
|
|
|
val->intval = ip5xxx->boost_undervolt.setpoint +
|
|
ip5xxx->boost_undervolt.microvolts_per_bit * rval;
|
|
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:
|
|
return ip5xxx_write(ip5xxx, ip5xxx->regs.boost.enable, !!val->intval);
|
|
|
|
case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
|
|
rval = (val->intval - ip5xxx->boost_undervolt.setpoint) /
|
|
ip5xxx->boost_undervolt.microvolts_per_bit;
|
|
return ip5xxx_write(ip5xxx, ip5xxx->regs.boost.undervolt_limit, 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 = 0xa9,
|
|
};
|
|
|
|
static struct ip5xxx_regfield_config ip51xx_fields = {
|
|
.charger_enable = REG_FIELD(0x01, 1, 1),
|
|
.charger_const_volt_sel = REG_FIELD(0x24, 1, 2),
|
|
.charger_const_curr_sel = REG_FIELD(0x25, 0, 4),
|
|
.charger_status = REG_FIELD(0x71, 5, 7),
|
|
.charger_chg_end = REG_FIELD(0x71, 3, 3),
|
|
.charger_timeout = REG_FIELD(0x71, 0, 2),
|
|
.charger_vin_overvolt = REG_FIELD(0x72, 5, 5),
|
|
.boost_enable = REG_FIELD(0x01, 2, 2),
|
|
.boost_llshdn_enable = REG_FIELD(0x02, 1, 1),
|
|
.boost_llshdn_i_limit = REG_FIELD(0x0c, 3, 7),
|
|
.boost_load_powerup_en = REG_FIELD(0x02, 0, 0),
|
|
.boost_vin_pullout_en = REG_FIELD(0x04, 5, 5),
|
|
.boost_undervolt_limit = REG_FIELD(0x22, 2, 3),
|
|
.boost_light_load_status = REG_FIELD(0x72, 6, 6),
|
|
.battery_ntc_dis = REG_FIELD(0x07, 6, 6),
|
|
.battery_type = REG_FIELD(0x24, 5, 6),
|
|
.battery_vset_en = REG_FIELD(0x26, 6, 6),
|
|
.battery_adc_volt_low = REG_FIELD(0xa2, 0, 7),
|
|
.battery_adc_volt_high = REG_FIELD(0xa3, 0, 5),
|
|
.battery_adc_curr_low = REG_FIELD(0xa4, 0, 7),
|
|
.battery_adc_curr_high = REG_FIELD(0xa5, 0, 5),
|
|
.battery_adc_ovolt_low = REG_FIELD(0xa8, 0, 7),
|
|
.battery_adc_ovolt_high = REG_FIELD(0xa9, 0, 5),
|
|
.btn_shdn_enable = REG_FIELD(0x03, 5, 5),
|
|
.btn_wled_mode = REG_FIELD(0x07, 1, 1),
|
|
.btn_shdn_mode = REG_FIELD(0x07, 0, 0),
|
|
.btn_long_press_time = REG_FIELD(0x03, 6, 7),
|
|
.btn_pressed = REG_FIELD(0x77, 3, 3),
|
|
.btn_long_pressed = REG_FIELD(0x77, 1, 1),
|
|
.btn_short_pressed = REG_FIELD(0x77, 0, 0),
|
|
.wled_enable = REG_FIELD(0x01, 3, 3),
|
|
.wled_detect_en = REG_FIELD(0x01, 4, 4),
|
|
.wled_present = REG_FIELD(0x72, 7, 7),
|
|
|
|
.vbat_max = 4350000,
|
|
.boost_undervolt_setpoint = 4530000,
|
|
.boost_undervolt_uv_per_bit = 100000,
|
|
};
|
|
|
|
static struct ip5xxx_regfield_config ip5306_fields = {
|
|
.charger_enable = REG_FIELD(0x00, 4, 4),
|
|
.charger_const_volt_sel = REG_FIELD(0x22, 0, 1),
|
|
.charger_const_curr_sel = REG_FIELD(0x24, 0, 4),
|
|
.charger_status = REG_FIELD_UNSUPPORTED, // other bits...
|
|
.charger_chg_end = REG_FIELD(0x71, 3, 3),
|
|
.charger_timeout = REG_FIELD_UNSUPPORTED,
|
|
.charger_vin_overvolt = REG_FIELD_UNSUPPORTED,
|
|
.boost_enable = REG_FIELD(0x00, 5, 5),
|
|
.boost_llshdn_enable = REG_FIELD_UNSUPPORTED,
|
|
.boost_llshdn_i_limit = REG_FIELD_UNSUPPORTED,
|
|
.boost_load_powerup_en = REG_FIELD(0x00, 2, 2),
|
|
.boost_vin_pullout_en = REG_FIELD(0x01, 2, 2),
|
|
.boost_undervolt_limit = REG_FIELD(0x21, 2, 4),
|
|
.boost_light_load_status = REG_FIELD(0x72, 2, 2),
|
|
.battery_ntc_dis = REG_FIELD_UNSUPPORTED,
|
|
.battery_type = REG_FIELD(0x22, 2, 3),
|
|
.battery_vset_en = REG_FIELD_UNSUPPORTED,
|
|
.battery_adc_volt_low = REG_FIELD_UNSUPPORTED,
|
|
.battery_adc_volt_high = REG_FIELD_UNSUPPORTED,
|
|
.battery_adc_curr_low = REG_FIELD_UNSUPPORTED,
|
|
.battery_adc_curr_high = REG_FIELD_UNSUPPORTED,
|
|
.battery_adc_ovolt_low = REG_FIELD_UNSUPPORTED,
|
|
.battery_adc_ovolt_high = REG_FIELD_UNSUPPORTED,
|
|
.btn_shdn_enable = REG_FIELD(0x00, 0, 0),
|
|
.btn_wled_mode = REG_FIELD(0x01, 6, 6),
|
|
.btn_shdn_mode = REG_FIELD(0x01, 7, 7),
|
|
.btn_long_press_time = REG_FIELD(0x02, 4, 4), // +1s
|
|
.btn_pressed = REG_FIELD_UNSUPPORTED,
|
|
/* TODO: double press */
|
|
.btn_long_pressed = REG_FIELD(0x77, 1, 1),
|
|
.btn_short_pressed = REG_FIELD(0x77, 0, 0),
|
|
.wled_enable = REG_FIELD_UNSUPPORTED,
|
|
.wled_detect_en = REG_FIELD_UNSUPPORTED,
|
|
.wled_present = REG_FIELD_UNSUPPORTED,
|
|
|
|
.vbat_max = 4400000,
|
|
.boost_undervolt_setpoint = 4450000,
|
|
.boost_undervolt_uv_per_bit = 50000,
|
|
.const_curr_setpoint = 50000,
|
|
.chg_end_inverted = 1,
|
|
};
|
|
|
|
#define ip5xxx_setup_reg(_field, _reg) \
|
|
do { \
|
|
if (likely(cfg->_field.lsb <= cfg->_field.msb)) { \
|
|
struct regmap_field *_tmp = devm_regmap_field_alloc(dev, \
|
|
ip5xxx->regmap, cfg->_field); \
|
|
if (!IS_ERR(_tmp)) \
|
|
ip5xxx->regs._reg = _tmp; \
|
|
} \
|
|
} while (0)
|
|
|
|
static void ip5xxx_setup_regs(struct device *dev, struct ip5xxx *ip5xxx,
|
|
const struct ip5xxx_regfield_config *cfg)
|
|
{
|
|
ip5xxx_setup_reg(charger_enable, charger.enable);
|
|
ip5xxx_setup_reg(charger_const_volt_sel, charger.const_volt_sel);
|
|
ip5xxx_setup_reg(charger_const_curr_sel, charger.const_curr_sel);
|
|
ip5xxx_setup_reg(charger_status, charger.status);
|
|
ip5xxx_setup_reg(charger_chg_end, charger.chg_end);
|
|
ip5xxx_setup_reg(charger_timeout, charger.timeout);
|
|
ip5xxx_setup_reg(charger_vin_overvolt, charger.vin_overvolt);
|
|
ip5xxx_setup_reg(boost_enable, boost.enable);
|
|
ip5xxx_setup_reg(boost_llshdn_enable, boost.light_load_shutdown.enable);
|
|
ip5xxx_setup_reg(boost_llshdn_i_limit, boost.light_load_shutdown.i_limit);
|
|
ip5xxx_setup_reg(boost_load_powerup_en, boost.load_powerup_en);
|
|
ip5xxx_setup_reg(boost_vin_pullout_en, boost.vin_pullout_en);
|
|
ip5xxx_setup_reg(boost_undervolt_limit, boost.undervolt_limit);
|
|
ip5xxx_setup_reg(boost_light_load_status, boost.light_load_status);
|
|
ip5xxx_setup_reg(battery_ntc_dis, battery.ntc_dis);
|
|
ip5xxx_setup_reg(battery_type, battery.type);
|
|
ip5xxx_setup_reg(battery_vset_en, battery.vset_en);
|
|
ip5xxx_setup_reg(battery_adc_volt_low, battery.adc.volt.low);
|
|
ip5xxx_setup_reg(battery_adc_volt_high, battery.adc.volt.high);
|
|
ip5xxx_setup_reg(battery_adc_curr_low, battery.adc.curr.low);
|
|
ip5xxx_setup_reg(battery_adc_curr_high, battery.adc.curr.high);
|
|
ip5xxx_setup_reg(battery_adc_ovolt_low, battery.adc.open_volt.low);
|
|
ip5xxx_setup_reg(battery_adc_ovolt_high, battery.adc.open_volt.high);
|
|
ip5xxx_setup_reg(btn_shdn_enable, btn.shdn_enable);
|
|
ip5xxx_setup_reg(btn_wled_mode, btn.wled_mode);
|
|
ip5xxx_setup_reg(btn_shdn_mode, btn.shdn_mode);
|
|
ip5xxx_setup_reg(btn_long_press_time, btn.long_press_time);
|
|
ip5xxx_setup_reg(btn_pressed, btn.pressed);
|
|
ip5xxx_setup_reg(btn_long_pressed, btn.long_pressed);
|
|
ip5xxx_setup_reg(btn_short_pressed, btn.short_pressed);
|
|
ip5xxx_setup_reg(wled_enable, wled.enable);
|
|
ip5xxx_setup_reg(wled_detect_en, wled.detect_en);
|
|
ip5xxx_setup_reg(wled_present, wled.present);
|
|
|
|
ip5xxx->vbat_max = cfg->vbat_max;
|
|
ip5xxx->boost_undervolt.setpoint = cfg->boost_undervolt_setpoint;
|
|
ip5xxx->boost_undervolt.microvolts_per_bit = cfg->boost_undervolt_uv_per_bit;
|
|
ip5xxx->const_curr.setpoint = cfg->const_curr_setpoint;
|
|
ip5xxx->chg_end_inverted = cfg->chg_end_inverted;
|
|
}
|
|
|
|
static int ip5xxx_power_probe(struct i2c_client *client)
|
|
{
|
|
const struct ip5xxx_regfield_config *fields;
|
|
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);
|
|
|
|
fields = i2c_get_match_data(client) ?: &ip51xx_fields;
|
|
ip5xxx_setup_regs(dev, ip5xxx, fields);
|
|
|
|
psy_cfg.fwnode = dev_fwnode(dev);
|
|
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", .data = &ip51xx_fields },
|
|
{ .compatible = "injoinic,ip5109", .data = &ip51xx_fields },
|
|
{ .compatible = "injoinic,ip5207", .data = &ip51xx_fields },
|
|
{ .compatible = "injoinic,ip5209", .data = &ip51xx_fields },
|
|
{ .compatible = "injoinic,ip5306", .data = &ip5306_fields },
|
|
{ }
|
|
};
|
|
MODULE_DEVICE_TABLE(of, ip5xxx_power_of_match);
|
|
|
|
static struct i2c_driver ip5xxx_power_driver = {
|
|
.probe = 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");
|