Skip to content

Commit

Permalink
power: supply: add driver for LT8491
Browse files Browse the repository at this point in the history
LT8491 High Voltage Buck-Boost Battery Charge Controller with I2C

Signed-off-by: John Erasmus Mari Geronimo <[email protected]>
  • Loading branch information
jemfgeronimo committed Oct 30, 2024
1 parent da81f92 commit b597332
Show file tree
Hide file tree
Showing 3 changed files with 370 additions and 1 deletion.
9 changes: 9 additions & 0 deletions drivers/power/supply/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,15 @@ config CHARGER_LT3651
Say Y to include support for the Analog Devices (Linear Technology)
LT3651 battery charger which reports its status via GPIO lines.

config CHARGER_LT8491
tristate "Analog Devices LT8491 charger"
depends on I2C
help
Say Y to include support for the Analog Devices (Linear Technology)
LT8491 battery charge controller connected to I2C. The LT8491 is a
high voltage buck-boost switching regulator battery charger
controller.

config CHARGER_LTC4162L
tristate "LTC4162-L charger"
depends on I2C
Expand Down
3 changes: 2 additions & 1 deletion drivers/power/supply/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ obj-$(CONFIG_CHARGER_LP8788) += lp8788-charger.o
obj-$(CONFIG_CHARGER_GPIO) += gpio-charger.o
obj-$(CONFIG_CHARGER_MANAGER) += charger-manager.o
obj-$(CONFIG_CHARGER_LT3651) += lt3651-charger.o
obj-$(CONFIG_CHARGER_LT8491) += lt8491_charger.o
obj-$(CONFIG_CHARGER_LTC4162L) += ltc4162-l-charger.o
obj-$(CONFIG_CHARGER_MAX14577) += max14577_charger.o
obj-$(CONFIG_CHARGER_DETECTOR_MAX14656) += max14656_charger_detector.o
Expand Down Expand Up @@ -109,4 +110,4 @@ obj-$(CONFIG_RN5T618_POWER) += rn5t618_power.o
obj-$(CONFIG_BATTERY_ACER_A500) += acer_a500_battery.o
obj-$(CONFIG_BATTERY_SURFACE) += surface_battery.o
obj-$(CONFIG_CHARGER_SURFACE) += surface_charger.o
obj-$(CONFIG_BATTERY_UG3105) += ug3105_battery.o
obj-$(CONFIG_BATTERY_UG3105) += ug3105_battery.o
359 changes: 359 additions & 0 deletions drivers/power/supply/lt8491_charger.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,359 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Analog Devices LT8491 Battery Charger
*
* Copyright 2024 Analog Devices Inc.
*/

#include <linux/bitfield.h>
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/i2c.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/power_supply.h>

#define LT8491_TELE_TBAT_REG 0x0
#define LT8491_TELE_POUT_REG 0x2
#define LT8491_TELE_PIN_REG 0x4
#define LT8491_TELE_EFF_REG 0x6
#define LT8491_TELE_IOUT_REG 0x8
#define LT8491_TELE_IIN_REG 0xA
#define LT8491_TELE_VBAT_REG 0xC
#define LT8491_TELE_VIN_REG 0xE
#define LT8491_TELE_VINR_REG 0x10
#define LT8491_STAT_CHARGER_REG 0x12
#define LT8491_CTRL_UPDATE_TELEM_REG 0x26

#define LT8491_CFG_RSENSE1_REG 0x28
#define LT8491_CFG_RIMON_OUT_REG 0x2A
#define LT8491_CFG_RSENSE2_REG 0x2C
#define LT8491_CFG_RDACO_REG 0x2E
#define LT8491_CFG_RFBOUT1_REG 0x30
#define LT8491_CFG_RFBOUT2_REG 0x32
#define LT8491_CFG_RDACI_REG 0x34
#define LT8491_CFG_RFBIN2_REG 0x36
#define LT8491_CFG_RFBIN1_REG 0x38
#define LT8491_CFG_TBAT_MIN_REG 0x40
#define LT8491_CFG_TBAT_MAX_REG 0x41
#define LT8491_MFR_DATA1_LSB_REG 0x5C

#define LT8491_CHARGING_MASK BIT(2)

#define LT8491_MFR_DATA_LEN 0x3

struct lt8491_info {
struct i2c_client *client;
struct power_supply *psp;
};

static s32 lt8491_update_telemetry(struct lt8491_info *info)
{
return i2c_smbus_write_byte_data(info->client, LT8491_CTRL_UPDATE_TELEM_REG, 0xAA);
}

static int lt8491_read_serial_number(struct lt8491_info *info, char *strval)
{
int i, ret;
u32 serial_number[LT8491_MFR_DATA_LEN];

for (i = 0; i < LT8491_MFR_DATA_LEN; i++) {
serial_number[i] = i2c_smbus_read_word_data(info->client, LT8491_MFR_DATA1_LSB_REG + i * 2);
if (serial_number[i] < 0)
return serial_number[i];
}

ret = sprintf(strval, "%04x%04x%04x", serial_number[0], serial_number[1], serial_number[2]);
if (ret < 0)
return ret;

return 0;
}

static int lt8491_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct lt8491_info *info = power_supply_get_drvdata(psy);
char *strval;
s16 ret;

switch (psp) {
case POWER_SUPPLY_PROP_STATUS:
ret = i2c_smbus_read_byte_data(info->client, LT8491_STAT_CHARGER_REG);
if (ret < 0)
return ret;

val->intval = FIELD_GET(LT8491_CHARGING_MASK, ret) ?
POWER_SUPPLY_STATUS_CHARGING :
POWER_SUPPLY_STATUS_NOT_CHARGING;

return 0;
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
ret = lt8491_update_telemetry(info);
if (ret)
return ret;

ret = i2c_smbus_read_word_data(info->client, LT8491_TELE_VBAT_REG);
if (ret < 0)
return ret;

val->intval = ret * 10000;

return 0;
case POWER_SUPPLY_PROP_CURRENT_NOW:
ret = lt8491_update_telemetry(info);
if (ret)
return ret;

ret = i2c_smbus_read_word_data(info->client, LT8491_TELE_IOUT_REG);
if (ret < 0)
return ret;

val->intval = ret;

return 0;
case POWER_SUPPLY_PROP_POWER_NOW:
ret = lt8491_update_telemetry(info);
if (ret)
return ret;

ret = i2c_smbus_read_word_data(info->client, LT8491_TELE_POUT_REG);
if (ret < 0)
return ret;

val->intval = ret * 10000;

return 0;
case POWER_SUPPLY_PROP_TEMP:
ret = lt8491_update_telemetry(info);
if (ret)
return ret;

ret = i2c_smbus_read_word_data(info->client, LT8491_TELE_TBAT_REG);
if (ret < 0)
return ret;

val->intval = ret;

return 0;
case POWER_SUPPLY_PROP_TEMP_ALERT_MIN:
ret = i2c_smbus_read_byte_data(info->client, LT8491_CFG_TBAT_MIN_REG);
if (ret < 0)
return ret;

val->intval = ret * 10;

return 0;
case POWER_SUPPLY_PROP_TEMP_ALERT_MAX:
ret = i2c_smbus_read_byte_data(info->client, LT8491_CFG_TBAT_MAX_REG);
if (ret < 0)
return ret;

val->intval = ret * 10;

return 0;
case POWER_SUPPLY_PROP_MODEL_NAME:
val->strval = "lt8491";

return 0;
case POWER_SUPPLY_PROP_MANUFACTURER:
val->strval = "Linear Technology Corporation";

return 0;
case POWER_SUPPLY_PROP_SERIAL_NUMBER:
ret = lt8491_read_serial_number(info, strval);
if (ret)
return ret;

val->strval = strval;

return 0;
default:
return -EINVAL;
}
}

static int lt8491_set_property(struct power_supply *psy,
enum power_supply_property psp,
const union power_supply_propval *val)
{
struct lt8491_info *info = power_supply_get_drvdata(psy);

switch (psp) {
case POWER_SUPPLY_PROP_TEMP_ALERT_MIN:
return i2c_smbus_write_byte_data(info->client,
LT8491_CFG_TBAT_MIN_REG,
val->intval / 10);
case POWER_SUPPLY_PROP_TEMP_ALERT_MAX:
return i2c_smbus_write_byte_data(info->client,
LT8491_CFG_TBAT_MAX_REG,
val->intval / 10);
default:
return -EINVAL;
}
}

static int lt8491_property_is_writeable(struct power_supply *psy,
enum power_supply_property psp)
{
switch (psp) {
case POWER_SUPPLY_PROP_TEMP_ALERT_MIN:
case POWER_SUPPLY_PROP_TEMP_ALERT_MAX:
return 1;
default:
return 0;
}
}

static enum power_supply_property lt8491_properties[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
POWER_SUPPLY_PROP_CURRENT_NOW,
POWER_SUPPLY_PROP_POWER_NOW,
POWER_SUPPLY_PROP_TEMP,
POWER_SUPPLY_PROP_TEMP_ALERT_MIN,
POWER_SUPPLY_PROP_TEMP_ALERT_MAX,
POWER_SUPPLY_PROP_MODEL_NAME,
POWER_SUPPLY_PROP_MANUFACTURER,
POWER_SUPPLY_PROP_SERIAL_NUMBER,
};

static const struct power_supply_desc lt8491_desc = {
.name = "lt8491",
.type = POWER_SUPPLY_TYPE_BATTERY,
.properties = lt8491_properties,
.num_properties = ARRAY_SIZE(lt8491_properties),
.get_property = lt8491_get_property,
.set_property = lt8491_set_property,
.property_is_writeable = lt8491_property_is_writeable,
};

static int lt8491_configure_resistor(struct i2c_client *client,
const char *propname, int divider,
unsigned int reg)
{
struct lt8491_info *info = i2c_get_clientdata(client);
struct device *dev = &client->dev;
int ret;
u32 val;

ret = device_property_read_u32(dev, propname, &val);
if (ret)
return dev_err_probe(dev, ret, "Missing %s property.\n", propname);

ret = i2c_smbus_write_word_data(info->client, reg, val / divider);
if (ret)
return ret;

return val;
}

static int lt8491_configure_telemetry(struct i2c_client *client)
{
int ret;

ret = lt8491_configure_resistor(client, "adi,rsense1-micro-ohms", 10,
LT8491_CFG_RSENSE1_REG);
if (ret < 0)
return ret;

ret = lt8491_configure_resistor(client, "adi,rimon-out-ohms", 10,
LT8491_CFG_RIMON_OUT_REG);
if (ret < 0)
return ret;

ret = lt8491_configure_resistor(client, "adi,rsense2-micro-ohms", 10,
LT8491_CFG_RSENSE2_REG);
if (ret < 0)
return ret;

ret = lt8491_configure_resistor(client, "adi,rdaco-ohms", 10,
LT8491_CFG_RDACO_REG);
if (ret < 0)
return ret;

ret = lt8491_configure_resistor(client, "adi,rfbout1-ohms", 100,
LT8491_CFG_RFBOUT1_REG);
if (ret < 0)
return ret;

ret = lt8491_configure_resistor(client, "adi,rfbout2-ohms", 10,
LT8491_CFG_RFBOUT2_REG);
if (ret < 0)
return ret;

ret = lt8491_configure_resistor(client, "adi,rdaci-ohms", 10,
LT8491_CFG_RDACI_REG);
if (ret < 0)
return ret;

ret = lt8491_configure_resistor(client, "adi,rfbin2-ohms", 10,
LT8491_CFG_RFBIN2_REG);
if (ret < 0)
return ret;

ret = lt8491_configure_resistor(client, "adi,rfbin1-ohms", 100,
LT8491_CFG_RFBIN1_REG);
if (ret < 0)
return ret;

return 0;
}

static int lt8491_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
struct lt8491_info *info;
struct power_supply_config psy_cfg = {};
int ret;

if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA |
I2C_FUNC_SMBUS_READ_WORD_DATA))
return -EOPNOTSUPP;

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

i2c_set_clientdata(client, info);
info->client = client;
psy_cfg.drv_data = info;

ret = lt8491_configure_telemetry(client);
if (ret)
return ret;

info->psp = power_supply_register(dev, &lt8491_desc, &psy_cfg);
if (IS_ERR(info->psp))
return dev_err_probe(dev, PTR_ERR(info->psp),
"Failed to register power supply.\n");

return 0;
}

static const struct i2c_device_id lt8491_id[] = {
{ "lt8491", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, lt8491_id);

static const struct of_device_id lt8491_of_match[] = {
{ .compatible = "adi,lt8491" },
{ }
};
MODULE_DEVICE_TABLE(of, lt8491_of_match);

static struct i2c_driver lt8491_driver = {
.driver = {
.name = "lt8491",
.of_match_table = lt8491_of_match,
},
.probe_new = lt8491_probe,
.id_table = lt8491_id,
};
module_i2c_driver(lt8491_driver);

MODULE_AUTHOR("John Erasmus Mari Geronimo <[email protected]");
MODULE_DESCRIPTION("LT8491 battery charger");
MODULE_LICENSE("GPL");

0 comments on commit b597332

Please sign in to comment.