mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
931 lines
23 KiB
931 lines
23 KiB
// SPDX-License-Identifier: GPL-2.0-or-later |
|
/* |
|
* Driver for Analog Devices (Linear Technology) LTC4162-L charger IC. |
|
* Copyright (C) 2020, Topic Embedded Products |
|
*/ |
|
|
|
#include <linux/module.h> |
|
#include <linux/delay.h> |
|
#include <linux/of_device.h> |
|
#include <linux/pm_runtime.h> |
|
#include <linux/power_supply.h> |
|
#include <linux/i2c.h> |
|
#include <linux/regmap.h> |
|
|
|
/* Registers (names based on what datasheet uses) */ |
|
#define LTC4162L_EN_LIMIT_ALERTS_REG 0x0D |
|
#define LTC4162L_EN_CHARGER_STATE_ALERTS_REG 0x0E |
|
#define LTC4162L_EN_CHARGE_STATUS_ALERTS_REG 0x0F |
|
#define LTC4162L_CONFIG_BITS_REG 0x14 |
|
#define LTC4162L_IIN_LIMIT_TARGET 0x15 |
|
#define LTC4162L_ARM_SHIP_MODE 0x19 |
|
#define LTC4162L_CHARGE_CURRENT_SETTING 0X1A |
|
#define LTC4162L_VCHARGE_SETTING 0X1B |
|
#define LTC4162L_C_OVER_X_THRESHOLD 0x1C |
|
#define LTC4162L_MAX_CV_TIME 0X1D |
|
#define LTC4162L_MAX_CHARGE_TIME 0X1E |
|
#define LTC4162L_CHARGER_CONFIG_BITS 0x29 |
|
#define LTC4162L_CHARGER_STATE 0x34 |
|
#define LTC4162L_CHARGE_STATUS 0x35 |
|
#define LTC4162L_LIMIT_ALERTS_REG 0x36 |
|
#define LTC4162L_CHARGER_STATE_ALERTS_REG 0x37 |
|
#define LTC4162L_CHARGE_STATUS_ALERTS_REG 0x38 |
|
#define LTC4162L_SYSTEM_STATUS_REG 0x39 |
|
#define LTC4162L_VBAT 0x3A |
|
#define LTC4162L_VIN 0x3B |
|
#define LTC4162L_VOUT 0x3C |
|
#define LTC4162L_IBAT 0x3D |
|
#define LTC4162L_IIN 0x3E |
|
#define LTC4162L_DIE_TEMPERATURE 0x3F |
|
#define LTC4162L_THERMISTOR_VOLTAGE 0x40 |
|
#define LTC4162L_BSR 0x41 |
|
#define LTC4162L_JEITA_REGION 0x42 |
|
#define LTC4162L_CHEM_CELLS_REG 0x43 |
|
#define LTC4162L_ICHARGE_DAC 0x44 |
|
#define LTC4162L_VCHARGE_DAC 0x45 |
|
#define LTC4162L_IIN_LIMIT_DAC 0x46 |
|
#define LTC4162L_VBAT_FILT 0x47 |
|
#define LTC4162L_INPUT_UNDERVOLTAGE_DAC 0x4B |
|
|
|
/* Enumeration as in datasheet. Individual bits are mutually exclusive. */ |
|
enum ltc4162l_state { |
|
battery_detection = 2048, |
|
charger_suspended = 256, |
|
precharge = 128, /* trickle on low bat voltage */ |
|
cc_cv_charge = 64, /* normal charge */ |
|
ntc_pause = 32, |
|
timer_term = 16, |
|
c_over_x_term = 8, /* battery is full */ |
|
max_charge_time_fault = 4, |
|
bat_missing_fault = 2, |
|
bat_short_fault = 1 |
|
}; |
|
|
|
/* Individual bits are mutually exclusive. Only active in charging states.*/ |
|
enum ltc4162l_charge_status { |
|
ilim_reg_active = 32, |
|
thermal_reg_active = 16, |
|
vin_uvcl_active = 8, |
|
iin_limit_active = 4, |
|
constant_current = 2, |
|
constant_voltage = 1, |
|
charger_off = 0 |
|
}; |
|
|
|
/* Magic number to write to ARM_SHIP_MODE register */ |
|
#define LTC4162L_ARM_SHIP_MODE_MAGIC 21325 |
|
|
|
struct ltc4162l_info { |
|
struct i2c_client *client; |
|
struct regmap *regmap; |
|
struct power_supply *charger; |
|
u32 rsnsb; /* Series resistor that sets charge current, microOhm */ |
|
u32 rsnsi; /* Series resistor to measure input current, microOhm */ |
|
u8 cell_count; /* Number of connected cells, 0 while unknown */ |
|
}; |
|
|
|
static u8 ltc4162l_get_cell_count(struct ltc4162l_info *info) |
|
{ |
|
int ret; |
|
unsigned int val; |
|
|
|
/* Once read successfully */ |
|
if (info->cell_count) |
|
return info->cell_count; |
|
|
|
ret = regmap_read(info->regmap, LTC4162L_CHEM_CELLS_REG, &val); |
|
if (ret) |
|
return 0; |
|
|
|
/* Lower 4 bits is the cell count, or 0 if the chip doesn't know yet */ |
|
val &= 0x0f; |
|
if (!val) |
|
return 0; |
|
|
|
/* Once determined, keep the value */ |
|
info->cell_count = val; |
|
|
|
return val; |
|
}; |
|
|
|
/* Convert enum value to POWER_SUPPLY_STATUS value */ |
|
static int ltc4162l_state_decode(enum ltc4162l_state value) |
|
{ |
|
switch (value) { |
|
case precharge: |
|
case cc_cv_charge: |
|
return POWER_SUPPLY_STATUS_CHARGING; |
|
case c_over_x_term: |
|
return POWER_SUPPLY_STATUS_FULL; |
|
case bat_missing_fault: |
|
case bat_short_fault: |
|
return POWER_SUPPLY_STATUS_UNKNOWN; |
|
default: |
|
return POWER_SUPPLY_STATUS_NOT_CHARGING; |
|
} |
|
}; |
|
|
|
static int ltc4162l_get_status(struct ltc4162l_info *info, |
|
union power_supply_propval *val) |
|
{ |
|
unsigned int regval; |
|
int ret; |
|
|
|
ret = regmap_read(info->regmap, LTC4162L_CHARGER_STATE, ®val); |
|
if (ret) { |
|
dev_err(&info->client->dev, "Failed to read CHARGER_STATE\n"); |
|
return ret; |
|
} |
|
|
|
val->intval = ltc4162l_state_decode(regval); |
|
|
|
return 0; |
|
} |
|
|
|
static int ltc4162l_charge_status_decode(enum ltc4162l_charge_status value) |
|
{ |
|
if (!value) |
|
return POWER_SUPPLY_CHARGE_TYPE_NONE; |
|
|
|
/* constant voltage/current and input_current limit are "fast" modes */ |
|
if (value <= iin_limit_active) |
|
return POWER_SUPPLY_CHARGE_TYPE_FAST; |
|
|
|
/* Anything that's not fast we'll return as trickle */ |
|
return POWER_SUPPLY_CHARGE_TYPE_TRICKLE; |
|
} |
|
|
|
static int ltc4162l_get_charge_type(struct ltc4162l_info *info, |
|
union power_supply_propval *val) |
|
{ |
|
unsigned int regval; |
|
int ret; |
|
|
|
ret = regmap_read(info->regmap, LTC4162L_CHARGE_STATUS, ®val); |
|
if (ret) |
|
return ret; |
|
|
|
val->intval = ltc4162l_charge_status_decode(regval); |
|
|
|
return 0; |
|
} |
|
|
|
static int ltc4162l_state_to_health(enum ltc4162l_state value) |
|
{ |
|
switch (value) { |
|
case ntc_pause: |
|
return POWER_SUPPLY_HEALTH_OVERHEAT; |
|
case timer_term: |
|
return POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE; |
|
case max_charge_time_fault: |
|
return POWER_SUPPLY_HEALTH_WATCHDOG_TIMER_EXPIRE; |
|
case bat_missing_fault: |
|
return POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; |
|
case bat_short_fault: |
|
return POWER_SUPPLY_HEALTH_DEAD; |
|
default: |
|
return POWER_SUPPLY_HEALTH_GOOD; |
|
} |
|
} |
|
|
|
static int ltc4162l_get_health(struct ltc4162l_info *info, |
|
union power_supply_propval *val) |
|
{ |
|
unsigned int regval; |
|
int ret; |
|
|
|
ret = regmap_read(info->regmap, LTC4162L_CHARGER_STATE, ®val); |
|
if (ret) |
|
return ret; |
|
|
|
val->intval = ltc4162l_state_to_health(regval); |
|
|
|
return 0; |
|
} |
|
|
|
static int ltc4162l_get_online(struct ltc4162l_info *info, |
|
union power_supply_propval *val) |
|
{ |
|
unsigned int regval; |
|
int ret; |
|
|
|
ret = regmap_read(info->regmap, LTC4162L_SYSTEM_STATUS_REG, ®val); |
|
if (ret) |
|
return ret; |
|
|
|
/* BIT(2) indicates if input voltage is sufficient to charge */ |
|
val->intval = !!(regval & BIT(2)); |
|
|
|
return 0; |
|
} |
|
|
|
static int ltc4162l_get_vbat(struct ltc4162l_info *info, |
|
unsigned int reg, |
|
union power_supply_propval *val) |
|
{ |
|
unsigned int regval; |
|
int ret; |
|
|
|
ret = regmap_read(info->regmap, reg, ®val); |
|
if (ret) |
|
return ret; |
|
|
|
/* cell_count × 192.4μV/LSB */ |
|
regval *= 1924; |
|
regval *= ltc4162l_get_cell_count(info); |
|
regval /= 10; |
|
val->intval = regval; |
|
|
|
return 0; |
|
} |
|
|
|
static int ltc4162l_get_ibat(struct ltc4162l_info *info, |
|
union power_supply_propval *val) |
|
{ |
|
unsigned int regval; |
|
int ret; |
|
|
|
ret = regmap_read(info->regmap, LTC4162L_IBAT, ®val); |
|
if (ret) |
|
return ret; |
|
|
|
/* Signed 16-bit number, 1.466μV / RSNSB amperes/LSB. */ |
|
ret = (s16)(regval & 0xFFFF); |
|
val->intval = 100 * mult_frac(ret, 14660, (int)info->rsnsb); |
|
|
|
return 0; |
|
} |
|
|
|
|
|
static int ltc4162l_get_input_voltage(struct ltc4162l_info *info, |
|
union power_supply_propval *val) |
|
{ |
|
unsigned int regval; |
|
int ret; |
|
|
|
ret = regmap_read(info->regmap, LTC4162L_VIN, ®val); |
|
if (ret) |
|
return ret; |
|
|
|
/* 1.649mV/LSB */ |
|
val->intval = regval * 1694; |
|
|
|
return 0; |
|
} |
|
|
|
static int ltc4162l_get_input_current(struct ltc4162l_info *info, |
|
union power_supply_propval *val) |
|
{ |
|
unsigned int regval; |
|
int ret; |
|
|
|
ret = regmap_read(info->regmap, LTC4162L_IIN, ®val); |
|
if (ret) |
|
return ret; |
|
|
|
/* Signed 16-bit number, 1.466μV / RSNSI amperes/LSB. */ |
|
ret = (s16)(regval & 0xFFFF); |
|
ret *= 14660; |
|
ret /= info->rsnsi; |
|
ret *= 100; |
|
|
|
val->intval = ret; |
|
|
|
return 0; |
|
} |
|
|
|
static int ltc4162l_get_icharge(struct ltc4162l_info *info, |
|
unsigned int reg, |
|
union power_supply_propval *val) |
|
{ |
|
unsigned int regval; |
|
int ret; |
|
|
|
ret = regmap_read(info->regmap, reg, ®val); |
|
if (ret) |
|
return ret; |
|
|
|
regval &= BIT(6) - 1; /* Only the lower 5 bits */ |
|
|
|
/* The charge current servo level: (icharge_dac + 1) × 1mV/RSNSB */ |
|
++regval; |
|
val->intval = 10000u * mult_frac(regval, 100000u, info->rsnsb); |
|
|
|
return 0; |
|
} |
|
|
|
static int ltc4162l_set_icharge(struct ltc4162l_info *info, |
|
unsigned int reg, |
|
unsigned int value) |
|
{ |
|
value = mult_frac(value, info->rsnsb, 100000u); |
|
value /= 10000u; |
|
|
|
/* Round to lowest possible */ |
|
if (value) |
|
--value; |
|
|
|
if (value > 31) |
|
return -EINVAL; |
|
|
|
return regmap_write(info->regmap, reg, value); |
|
} |
|
|
|
|
|
static int ltc4162l_get_vcharge(struct ltc4162l_info *info, |
|
unsigned int reg, |
|
union power_supply_propval *val) |
|
{ |
|
unsigned int regval; |
|
int ret; |
|
u32 voltage; |
|
|
|
ret = regmap_read(info->regmap, reg, ®val); |
|
if (ret) |
|
return ret; |
|
|
|
regval &= BIT(6) - 1; /* Only the lower 5 bits */ |
|
|
|
/* |
|
* charge voltage setting can be computed from |
|
* cell_count × (vcharge_setting × 12.5mV + 3.8125V) |
|
* where vcharge_setting ranges from 0 to 31 (4.2V max). |
|
*/ |
|
voltage = 3812500 + (regval * 12500); |
|
voltage *= ltc4162l_get_cell_count(info); |
|
val->intval = voltage; |
|
|
|
return 0; |
|
} |
|
|
|
static int ltc4162l_set_vcharge(struct ltc4162l_info *info, |
|
unsigned int reg, |
|
unsigned int value) |
|
{ |
|
u8 cell_count = ltc4162l_get_cell_count(info); |
|
|
|
if (!cell_count) |
|
return -EBUSY; /* Not available yet, try again later */ |
|
|
|
value /= cell_count; |
|
|
|
if (value < 3812500) |
|
return -EINVAL; |
|
|
|
value -= 3812500; |
|
value /= 12500; |
|
|
|
if (value > 31) |
|
return -EINVAL; |
|
|
|
return regmap_write(info->regmap, reg, value); |
|
} |
|
|
|
static int ltc4162l_get_iin_limit_dac(struct ltc4162l_info *info, |
|
union power_supply_propval *val) |
|
{ |
|
unsigned int regval; |
|
int ret; |
|
|
|
ret = regmap_read(info->regmap, LTC4162L_IIN_LIMIT_DAC, ®val); |
|
if (ret) |
|
return ret; |
|
|
|
regval &= BIT(6) - 1; /* Only 6 bits */ |
|
|
|
/* (iin_limit_dac + 1) × 500μV / RSNSI */ |
|
++regval; |
|
regval *= 5000000u; |
|
regval /= info->rsnsi; |
|
val->intval = 100u * regval; |
|
|
|
return 0; |
|
} |
|
|
|
static int ltc4162l_set_iin_limit(struct ltc4162l_info *info, |
|
unsigned int value) |
|
{ |
|
unsigned int regval; |
|
|
|
regval = mult_frac(value, info->rsnsi, 50000u); |
|
regval /= 10000u; |
|
if (regval) |
|
--regval; |
|
if (regval > 63) |
|
regval = 63; |
|
|
|
return regmap_write(info->regmap, LTC4162L_IIN_LIMIT_TARGET, regval); |
|
} |
|
|
|
static int ltc4162l_get_die_temp(struct ltc4162l_info *info, |
|
union power_supply_propval *val) |
|
{ |
|
unsigned int regval; |
|
int ret; |
|
|
|
ret = regmap_read(info->regmap, LTC4162L_DIE_TEMPERATURE, ®val); |
|
if (ret) |
|
return ret; |
|
|
|
/* die_temp × 0.0215°C/LSB - 264.4°C */ |
|
ret = (s16)(regval & 0xFFFF); |
|
ret *= 215; |
|
ret /= 100; /* Centidegrees scale */ |
|
ret -= 26440; |
|
val->intval = ret; |
|
|
|
return 0; |
|
} |
|
|
|
static int ltc4162l_get_term_current(struct ltc4162l_info *info, |
|
union power_supply_propval *val) |
|
{ |
|
unsigned int regval; |
|
int ret; |
|
|
|
ret = regmap_read(info->regmap, LTC4162L_CHARGER_CONFIG_BITS, ®val); |
|
if (ret) |
|
return ret; |
|
|
|
/* Check if C_OVER_X_THRESHOLD is enabled */ |
|
if (!(regval & BIT(2))) { |
|
val->intval = 0; |
|
return 0; |
|
} |
|
|
|
ret = regmap_read(info->regmap, LTC4162L_C_OVER_X_THRESHOLD, ®val); |
|
if (ret) |
|
return ret; |
|
|
|
/* 1.466μV / RSNSB amperes/LSB */ |
|
regval *= 14660u; |
|
regval /= info->rsnsb; |
|
val->intval = 100 * regval; |
|
|
|
return 0; |
|
} |
|
|
|
static int ltc4162l_set_term_current(struct ltc4162l_info *info, |
|
unsigned int value) |
|
{ |
|
int ret; |
|
unsigned int regval; |
|
|
|
if (!value) { |
|
/* Disable en_c_over_x_term when set to zero */ |
|
return regmap_update_bits(info->regmap, |
|
LTC4162L_CHARGER_CONFIG_BITS, |
|
BIT(2), 0); |
|
} |
|
|
|
regval = mult_frac(value, info->rsnsb, 14660u); |
|
regval /= 100u; |
|
|
|
ret = regmap_write(info->regmap, LTC4162L_C_OVER_X_THRESHOLD, regval); |
|
if (ret) |
|
return ret; |
|
|
|
/* Set en_c_over_x_term after changing the threshold value */ |
|
return regmap_update_bits(info->regmap, LTC4162L_CHARGER_CONFIG_BITS, |
|
BIT(2), BIT(2)); |
|
} |
|
|
|
/* Custom properties */ |
|
static const char * const ltc4162l_charge_status_name[] = { |
|
"ilim_reg_active", /* 32 */ |
|
"thermal_reg_active", |
|
"vin_uvcl_active", |
|
"iin_limit_active", |
|
"constant_current", |
|
"constant_voltage", |
|
"charger_off" /* 0 */ |
|
}; |
|
|
|
static ssize_t charge_status_show(struct device *dev, |
|
struct device_attribute *attr, char *buf) |
|
{ |
|
struct power_supply *psy = to_power_supply(dev); |
|
struct ltc4162l_info *info = power_supply_get_drvdata(psy); |
|
const char *result = ltc4162l_charge_status_name[ |
|
ARRAY_SIZE(ltc4162l_charge_status_name) - 1]; |
|
unsigned int regval; |
|
unsigned int mask; |
|
unsigned int index; |
|
int ret; |
|
|
|
ret = regmap_read(info->regmap, LTC4162L_CHARGE_STATUS, ®val); |
|
if (ret) |
|
return ret; |
|
|
|
/* Only one bit is set according to datasheet, let's be safe here */ |
|
for (mask = 32, index = 0; mask != 0; mask >>= 1, ++index) { |
|
if (regval & mask) { |
|
result = ltc4162l_charge_status_name[index]; |
|
break; |
|
} |
|
} |
|
|
|
return sprintf(buf, "%s\n", result); |
|
} |
|
static DEVICE_ATTR_RO(charge_status); |
|
|
|
static ssize_t vbat_show(struct device *dev, |
|
struct device_attribute *attr, char *buf) |
|
{ |
|
struct power_supply *psy = to_power_supply(dev); |
|
struct ltc4162l_info *info = power_supply_get_drvdata(psy); |
|
union power_supply_propval val; |
|
int ret; |
|
|
|
ret = ltc4162l_get_vbat(info, LTC4162L_VBAT, &val); |
|
if (ret) |
|
return ret; |
|
|
|
return sprintf(buf, "%d\n", val.intval); |
|
} |
|
static DEVICE_ATTR_RO(vbat); |
|
|
|
static ssize_t vbat_avg_show(struct device *dev, |
|
struct device_attribute *attr, char *buf) |
|
{ |
|
struct power_supply *psy = to_power_supply(dev); |
|
struct ltc4162l_info *info = power_supply_get_drvdata(psy); |
|
union power_supply_propval val; |
|
int ret; |
|
|
|
ret = ltc4162l_get_vbat(info, LTC4162L_VBAT_FILT, &val); |
|
if (ret) |
|
return ret; |
|
|
|
return sprintf(buf, "%d\n", val.intval); |
|
} |
|
static DEVICE_ATTR_RO(vbat_avg); |
|
|
|
static ssize_t ibat_show(struct device *dev, |
|
struct device_attribute *attr, char *buf) |
|
{ |
|
struct power_supply *psy = to_power_supply(dev); |
|
struct ltc4162l_info *info = power_supply_get_drvdata(psy); |
|
union power_supply_propval val; |
|
int ret; |
|
|
|
ret = ltc4162l_get_ibat(info, &val); |
|
if (ret) |
|
return ret; |
|
|
|
return sprintf(buf, "%d\n", val.intval); |
|
} |
|
static DEVICE_ATTR_RO(ibat); |
|
|
|
static ssize_t force_telemetry_show(struct device *dev, |
|
struct device_attribute *attr, char *buf) |
|
{ |
|
struct power_supply *psy = to_power_supply(dev); |
|
struct ltc4162l_info *info = power_supply_get_drvdata(psy); |
|
unsigned int regval; |
|
int ret; |
|
|
|
ret = regmap_read(info->regmap, LTC4162L_CONFIG_BITS_REG, ®val); |
|
if (ret) |
|
return ret; |
|
|
|
return sprintf(buf, "%u\n", regval & BIT(2) ? 1 : 0); |
|
} |
|
|
|
static ssize_t force_telemetry_store(struct device *dev, |
|
struct device_attribute *attr, |
|
const char *buf, |
|
size_t count) |
|
{ |
|
struct power_supply *psy = to_power_supply(dev); |
|
struct ltc4162l_info *info = power_supply_get_drvdata(psy); |
|
int ret; |
|
unsigned int value; |
|
|
|
ret = kstrtouint(buf, 0, &value); |
|
if (ret < 0) |
|
return ret; |
|
|
|
ret = regmap_update_bits(info->regmap, LTC4162L_CONFIG_BITS_REG, |
|
BIT(2), value ? BIT(2) : 0); |
|
if (ret < 0) |
|
return ret; |
|
|
|
return count; |
|
} |
|
|
|
static DEVICE_ATTR_RW(force_telemetry); |
|
|
|
static ssize_t arm_ship_mode_show(struct device *dev, |
|
struct device_attribute *attr, char *buf) |
|
{ |
|
struct power_supply *psy = to_power_supply(dev); |
|
struct ltc4162l_info *info = power_supply_get_drvdata(psy); |
|
unsigned int regval; |
|
int ret; |
|
|
|
ret = regmap_read(info->regmap, LTC4162L_ARM_SHIP_MODE, ®val); |
|
if (ret) |
|
return ret; |
|
|
|
return sprintf(buf, "%u\n", |
|
regval == LTC4162L_ARM_SHIP_MODE_MAGIC ? 1 : 0); |
|
} |
|
|
|
static ssize_t arm_ship_mode_store(struct device *dev, |
|
struct device_attribute *attr, |
|
const char *buf, |
|
size_t count) |
|
{ |
|
struct power_supply *psy = to_power_supply(dev); |
|
struct ltc4162l_info *info = power_supply_get_drvdata(psy); |
|
int ret; |
|
unsigned int value; |
|
|
|
ret = kstrtouint(buf, 0, &value); |
|
if (ret < 0) |
|
return ret; |
|
|
|
ret = regmap_write(info->regmap, LTC4162L_ARM_SHIP_MODE, |
|
value ? LTC4162L_ARM_SHIP_MODE_MAGIC : 0); |
|
if (ret < 0) |
|
return ret; |
|
|
|
return count; |
|
} |
|
|
|
static DEVICE_ATTR_RW(arm_ship_mode); |
|
|
|
static struct attribute *ltc4162l_sysfs_entries[] = { |
|
&dev_attr_charge_status.attr, |
|
&dev_attr_ibat.attr, |
|
&dev_attr_vbat.attr, |
|
&dev_attr_vbat_avg.attr, |
|
&dev_attr_force_telemetry.attr, |
|
&dev_attr_arm_ship_mode.attr, |
|
NULL, |
|
}; |
|
|
|
static const struct attribute_group ltc4162l_attr_group = { |
|
.name = NULL, /* put in device directory */ |
|
.attrs = ltc4162l_sysfs_entries, |
|
}; |
|
|
|
static const struct attribute_group *ltc4162l_attr_groups[] = { |
|
<c4162l_attr_group, |
|
NULL, |
|
}; |
|
|
|
static int ltc4162l_get_property(struct power_supply *psy, |
|
enum power_supply_property psp, |
|
union power_supply_propval *val) |
|
{ |
|
struct ltc4162l_info *info = power_supply_get_drvdata(psy); |
|
|
|
switch (psp) { |
|
case POWER_SUPPLY_PROP_STATUS: |
|
return ltc4162l_get_status(info, val); |
|
case POWER_SUPPLY_PROP_CHARGE_TYPE: |
|
return ltc4162l_get_charge_type(info, val); |
|
case POWER_SUPPLY_PROP_HEALTH: |
|
return ltc4162l_get_health(info, val); |
|
case POWER_SUPPLY_PROP_ONLINE: |
|
return ltc4162l_get_online(info, val); |
|
case POWER_SUPPLY_PROP_VOLTAGE_NOW: |
|
return ltc4162l_get_input_voltage(info, val); |
|
case POWER_SUPPLY_PROP_CURRENT_NOW: |
|
return ltc4162l_get_input_current(info, val); |
|
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: |
|
return ltc4162l_get_icharge(info, |
|
LTC4162L_ICHARGE_DAC, val); |
|
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: |
|
return ltc4162l_get_icharge(info, |
|
LTC4162L_CHARGE_CURRENT_SETTING, val); |
|
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: |
|
return ltc4162l_get_vcharge(info, |
|
LTC4162L_VCHARGE_DAC, val); |
|
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: |
|
return ltc4162l_get_vcharge(info, |
|
LTC4162L_VCHARGE_SETTING, val); |
|
case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: |
|
return ltc4162l_get_iin_limit_dac(info, val); |
|
case POWER_SUPPLY_PROP_TEMP: |
|
return ltc4162l_get_die_temp(info, val); |
|
case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT: |
|
return ltc4162l_get_term_current(info, val); |
|
default: |
|
return -EINVAL; |
|
} |
|
} |
|
|
|
static int ltc4162l_set_property(struct power_supply *psy, |
|
enum power_supply_property psp, |
|
const union power_supply_propval *val) |
|
{ |
|
struct ltc4162l_info *info = power_supply_get_drvdata(psy); |
|
|
|
switch (psp) { |
|
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: |
|
return ltc4162l_set_icharge(info, |
|
LTC4162L_CHARGE_CURRENT_SETTING, val->intval); |
|
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: |
|
return ltc4162l_set_vcharge(info, |
|
LTC4162L_VCHARGE_SETTING, val->intval); |
|
case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: |
|
return ltc4162l_set_iin_limit(info, val->intval); |
|
case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT: |
|
return ltc4162l_set_term_current(info, val->intval); |
|
default: |
|
return -EINVAL; |
|
} |
|
} |
|
|
|
static int ltc4162l_property_is_writeable(struct power_supply *psy, |
|
enum power_supply_property psp) |
|
{ |
|
switch (psp) { |
|
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: |
|
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: |
|
case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: |
|
case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT: |
|
return 1; |
|
default: |
|
return 0; |
|
} |
|
} |
|
|
|
/* Charger power supply property routines */ |
|
static enum power_supply_property ltc4162l_properties[] = { |
|
POWER_SUPPLY_PROP_STATUS, |
|
POWER_SUPPLY_PROP_CHARGE_TYPE, |
|
POWER_SUPPLY_PROP_HEALTH, |
|
POWER_SUPPLY_PROP_ONLINE, |
|
POWER_SUPPLY_PROP_VOLTAGE_NOW, |
|
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, |
|
POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, |
|
POWER_SUPPLY_PROP_TEMP, |
|
POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT, |
|
}; |
|
|
|
static const struct power_supply_desc ltc4162l_desc = { |
|
.name = "ltc4162-l", |
|
.type = POWER_SUPPLY_TYPE_MAINS, |
|
.properties = ltc4162l_properties, |
|
.num_properties = ARRAY_SIZE(ltc4162l_properties), |
|
.get_property = ltc4162l_get_property, |
|
.set_property = ltc4162l_set_property, |
|
.property_is_writeable = ltc4162l_property_is_writeable, |
|
}; |
|
|
|
static bool ltc4162l_is_writeable_reg(struct device *dev, unsigned int reg) |
|
{ |
|
/* all registers up to this one are writeable */ |
|
if (reg <= LTC4162L_CHARGER_CONFIG_BITS) |
|
return true; |
|
|
|
/* The ALERTS registers can be written to clear alerts */ |
|
if (reg >= LTC4162L_LIMIT_ALERTS_REG && |
|
reg <= LTC4162L_CHARGE_STATUS_ALERTS_REG) |
|
return true; |
|
|
|
return false; |
|
} |
|
|
|
static bool ltc4162l_is_volatile_reg(struct device *dev, unsigned int reg) |
|
{ |
|
/* all registers after this one are read-only status registers */ |
|
return reg > LTC4162L_CHARGER_CONFIG_BITS; |
|
} |
|
|
|
static const struct regmap_config ltc4162l_regmap_config = { |
|
.reg_bits = 8, |
|
.val_bits = 16, |
|
.val_format_endian = REGMAP_ENDIAN_LITTLE, |
|
.writeable_reg = ltc4162l_is_writeable_reg, |
|
.volatile_reg = ltc4162l_is_volatile_reg, |
|
.max_register = LTC4162L_INPUT_UNDERVOLTAGE_DAC, |
|
.cache_type = REGCACHE_RBTREE, |
|
}; |
|
|
|
static void ltc4162l_clear_interrupts(struct ltc4162l_info *info) |
|
{ |
|
/* Acknowledge interrupt to chip by clearing all events */ |
|
regmap_write(info->regmap, LTC4162L_LIMIT_ALERTS_REG, 0); |
|
regmap_write(info->regmap, LTC4162L_CHARGER_STATE_ALERTS_REG, 0); |
|
regmap_write(info->regmap, LTC4162L_CHARGE_STATUS_ALERTS_REG, 0); |
|
} |
|
|
|
static int ltc4162l_probe(struct i2c_client *client, |
|
const struct i2c_device_id *id) |
|
{ |
|
struct i2c_adapter *adapter = client->adapter; |
|
struct device *dev = &client->dev; |
|
struct ltc4162l_info *info; |
|
struct power_supply_config ltc4162l_config = {}; |
|
u32 value; |
|
int ret; |
|
|
|
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA)) { |
|
dev_err(dev, "No support for SMBUS_WORD_DATA\n"); |
|
return -ENODEV; |
|
} |
|
info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL); |
|
if (!info) |
|
return -ENOMEM; |
|
|
|
info->client = client; |
|
i2c_set_clientdata(client, info); |
|
|
|
info->regmap = devm_regmap_init_i2c(client, <c4162l_regmap_config); |
|
if (IS_ERR(info->regmap)) { |
|
dev_err(dev, "Failed to initialize register map\n"); |
|
return PTR_ERR(info->regmap); |
|
} |
|
|
|
ret = device_property_read_u32(dev, "lltc,rsnsb-micro-ohms", |
|
&info->rsnsb); |
|
if (ret) { |
|
dev_err(dev, "Missing lltc,rsnsb-micro-ohms property\n"); |
|
return ret; |
|
} |
|
if (!info->rsnsb) |
|
return -EINVAL; |
|
|
|
ret = device_property_read_u32(dev, "lltc,rsnsi-micro-ohms", |
|
&info->rsnsi); |
|
if (ret) { |
|
dev_err(dev, "Missing lltc,rsnsi-micro-ohms property\n"); |
|
return ret; |
|
} |
|
if (!info->rsnsi) |
|
return -EINVAL; |
|
|
|
if (!device_property_read_u32(dev, "lltc,cell-count", &value)) |
|
info->cell_count = value; |
|
|
|
ltc4162l_config.of_node = dev->of_node; |
|
ltc4162l_config.drv_data = info; |
|
ltc4162l_config.attr_grp = ltc4162l_attr_groups; |
|
|
|
info->charger = devm_power_supply_register(dev, <c4162l_desc, |
|
<c4162l_config); |
|
if (IS_ERR(info->charger)) { |
|
dev_err(dev, "Failed to register charger\n"); |
|
return PTR_ERR(info->charger); |
|
} |
|
|
|
/* Disable the threshold alerts, we're not using them */ |
|
regmap_write(info->regmap, LTC4162L_EN_LIMIT_ALERTS_REG, 0); |
|
|
|
/* Enable interrupts on all status changes */ |
|
regmap_write(info->regmap, LTC4162L_EN_CHARGER_STATE_ALERTS_REG, |
|
0x1fff); |
|
regmap_write(info->regmap, LTC4162L_EN_CHARGE_STATUS_ALERTS_REG, 0x1f); |
|
|
|
ltc4162l_clear_interrupts(info); |
|
|
|
return 0; |
|
} |
|
|
|
static void ltc4162l_alert(struct i2c_client *client, |
|
enum i2c_alert_protocol type, unsigned int flag) |
|
{ |
|
struct ltc4162l_info *info = i2c_get_clientdata(client); |
|
|
|
if (type != I2C_PROTOCOL_SMBUS_ALERT) |
|
return; |
|
|
|
ltc4162l_clear_interrupts(info); |
|
power_supply_changed(info->charger); |
|
} |
|
|
|
static const struct i2c_device_id ltc4162l_i2c_id_table[] = { |
|
{ "ltc4162-l", 0 }, |
|
{ }, |
|
}; |
|
MODULE_DEVICE_TABLE(i2c, ltc4162l_i2c_id_table); |
|
|
|
static const struct of_device_id ltc4162l_of_match[] = { |
|
{ .compatible = "lltc,ltc4162-l", }, |
|
{ }, |
|
}; |
|
MODULE_DEVICE_TABLE(of, ltc4162l_of_match); |
|
|
|
static struct i2c_driver ltc4162l_driver = { |
|
.probe = ltc4162l_probe, |
|
.alert = ltc4162l_alert, |
|
.id_table = ltc4162l_i2c_id_table, |
|
.driver = { |
|
.name = "ltc4162-l-charger", |
|
.of_match_table = of_match_ptr(ltc4162l_of_match), |
|
}, |
|
}; |
|
module_i2c_driver(ltc4162l_driver); |
|
|
|
MODULE_LICENSE("GPL"); |
|
MODULE_AUTHOR("Mike Looijmans <[email protected]>"); |
|
MODULE_DESCRIPTION("LTC4162-L charger driver");
|
|
|