Commit 4f7f8e87 authored by Iskren Chernev's avatar Iskren Chernev Committed by Sebastian Reichel

power: supply: max17040: Support compatible devices

The max17040 fuel gauge is part of a family of 8 chips that have very
similar mode of operations and registers.

This change adds:
- compatible strings for all supported devices and handling for the
  minor differences between them;
- handling for devices reporting double capacity via maxim,double-soc;

The datasheets of the supported devices are linked [0] [1] [2] [3].

[0] https://datasheets.maximintegrated.com/en/ds/MAX17040-MAX17041.pdf
[1] https://datasheets.maximintegrated.com/en/ds/MAX17043-MAX17044.pdf
[2] https://datasheets.maximintegrated.com/en/ds/MAX17048-MAX17049.pdf
[3] https://datasheets.maximintegrated.com/en/ds/MAX17058-MAX17059.pdfSigned-off-by: default avatarIskren Chernev <iskren.chernev@gmail.com>
Tested-by: default avatarJonathan Bakker <xc-racer2@live.ca>
Reported-by: default avatarkernel test robot <lkp@intel.com>
Signed-off-by: default avatarSebastian Reichel <sebastian.reichel@collabora.com>
parent 11a2bdc1
......@@ -369,9 +369,13 @@ config BATTERY_MAX17040
depends on I2C
select REGMAP_I2C
help
MAX17040 is fuel-gauge systems for lithium-ion (Li+) batteries
in handheld and portable equipment. The MAX17040 is configured
to operate with a single lithium cell
Maxim models with ModelGauge are fuel-gauge systems for lithium-ion
(Li+) batteries in handheld and portable equipment, including
max17040, max17041, max17043, max17044, max17048, max17049, max17058,
max17059. It is also included in some batteries like max77836.
Driver supports reporting SOC (State of Charge, i.e capacity),
voltage and configurable low-SOC wakeup interrupt.
config BATTERY_MAX17042
tristate "Maxim MAX17042/17047/17050/8997/8966 Fuel Gauge"
......
......@@ -15,6 +15,7 @@
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/power_supply.h>
#include <linux/of_device.h>
#include <linux/max17040_battery.h>
#include <linux/regmap.h>
#include <linux/slab.h>
......@@ -33,12 +34,92 @@
#define MAX17040_ATHD_MASK 0x3f
#define MAX17040_ATHD_DEFAULT_POWER_UP 4
enum chip_id {
ID_MAX17040,
ID_MAX17041,
ID_MAX17043,
ID_MAX17044,
ID_MAX17048,
ID_MAX17049,
ID_MAX17058,
ID_MAX17059,
};
/* values that differ by chip_id */
struct chip_data {
u16 reset_val;
u16 vcell_shift;
u16 vcell_mul;
u16 vcell_div;
u8 has_low_soc_alert;
};
static struct chip_data max17040_family[] = {
[ID_MAX17040] = {
.reset_val = 0x0054,
.vcell_shift = 4,
.vcell_mul = 1250,
.vcell_div = 1,
.has_low_soc_alert = 0,
},
[ID_MAX17041] = {
.reset_val = 0x0054,
.vcell_shift = 4,
.vcell_mul = 2500,
.vcell_div = 1,
.has_low_soc_alert = 0,
},
[ID_MAX17043] = {
.reset_val = 0x0054,
.vcell_shift = 4,
.vcell_mul = 1250,
.vcell_div = 1,
.has_low_soc_alert = 1,
},
[ID_MAX17044] = {
.reset_val = 0x0054,
.vcell_shift = 4,
.vcell_mul = 2500,
.vcell_div = 1,
.has_low_soc_alert = 1,
},
[ID_MAX17048] = {
.reset_val = 0x5400,
.vcell_shift = 0,
.vcell_mul = 625,
.vcell_div = 8,
.has_low_soc_alert = 1,
},
[ID_MAX17049] = {
.reset_val = 0x5400,
.vcell_shift = 0,
.vcell_mul = 625,
.vcell_div = 4,
.has_low_soc_alert = 1,
},
[ID_MAX17058] = {
.reset_val = 0x5400,
.vcell_shift = 0,
.vcell_mul = 625,
.vcell_div = 8,
.has_low_soc_alert = 1,
},
[ID_MAX17059] = {
.reset_val = 0x5400,
.vcell_shift = 0,
.vcell_mul = 625,
.vcell_div = 4,
.has_low_soc_alert = 1,
},
};
struct max17040_chip {
struct i2c_client *client;
struct regmap *regmap;
struct delayed_work work;
struct power_supply *battery;
struct max17040_platform_data *pdata;
struct chip_data data;
/* battery capacity */
int soc;
......@@ -46,27 +127,37 @@ struct max17040_chip {
int status;
/* Low alert threshold from 32% to 1% of the State of Charge */
u32 low_soc_alert;
/* some devices return twice the capacity */
bool quirk_double_soc;
};
static int max17040_reset(struct max17040_chip *chip)
{
return regmap_write(chip->regmap, MAX17040_CMD, 0x0054);
return regmap_write(chip->regmap, MAX17040_CMD, chip->data.reset_val);
}
static int max17040_set_low_soc_alert(struct max17040_chip *chip, u32 level)
{
level = 32 - level;
level = 32 - level * (chip->quirk_double_soc ? 2 : 1);
return regmap_update_bits(chip->regmap, MAX17040_CONFIG,
MAX17040_ATHD_MASK, level);
}
static int max17040_raw_vcell_to_uvolts(struct max17040_chip *chip, u16 vcell)
{
struct chip_data *d = &chip->data;
return (vcell >> d->vcell_shift) * d->vcell_mul / d->vcell_div;
}
static int max17040_get_vcell(struct max17040_chip *chip)
{
u32 vcell;
regmap_read(chip->regmap, MAX17040_VCELL, &vcell);
return (vcell >> 4) * 1250;
return max17040_raw_vcell_to_uvolts(chip, vcell);
}
static int max17040_get_soc(struct max17040_chip *chip)
......@@ -75,7 +166,7 @@ static int max17040_get_soc(struct max17040_chip *chip)
regmap_read(chip->regmap, MAX17040_SOC, &soc);
return soc >> 8;
return soc >> (chip->quirk_double_soc ? 9 : 8);
}
static int max17040_get_version(struct max17040_chip *chip)
......@@ -116,12 +207,16 @@ static int max17040_get_of_data(struct max17040_chip *chip)
{
struct device *dev = &chip->client->dev;
chip->quirk_double_soc = device_property_read_bool(dev,
"maxim,double-soc");
chip->low_soc_alert = MAX17040_ATHD_DEFAULT_POWER_UP;
device_property_read_u32(dev,
"maxim,alert-low-soc-level",
&chip->low_soc_alert);
if (chip->low_soc_alert <= 0 || chip->low_soc_alert >= 33) {
if (chip->low_soc_alert <= 0 ||
chip->low_soc_alert > (chip->quirk_double_soc ? 16 : 32)) {
dev_err(dev, "maxim,alert-low-soc-level out of bounds\n");
return -EINVAL;
}
......@@ -219,8 +314,9 @@ static int max17040_set_property(struct power_supply *psy,
switch (psp) {
case POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN:
/* alert threshold can be programmed from 1% up to 32% */
if ((val->intval < 1) || (val->intval > 32)) {
/* alert threshold can be programmed from 1% up to 16/32% */
if ((val->intval < 1) ||
(val->intval > (chip->quirk_double_soc ? 16 : 32))) {
ret = -EINVAL;
break;
}
......@@ -293,6 +389,7 @@ static int max17040_probe(struct i2c_client *client,
struct i2c_adapter *adapter = client->adapter;
struct power_supply_config psy_cfg = {};
struct max17040_chip *chip;
enum chip_id chip_id;
int ret;
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE))
......@@ -305,9 +402,15 @@ static int max17040_probe(struct i2c_client *client,
chip->client = client;
chip->regmap = devm_regmap_init_i2c(client, &max17040_regmap);
chip->pdata = client->dev.platform_data;
chip_id = (enum chip_id) id->driver_data;
if (client->dev.of_node) {
ret = max17040_get_of_data(chip);
if (ret)
return ret;
chip_id = (enum chip_id) (uintptr_t)
of_device_get_match_data(&client->dev);
}
chip->data = max17040_family[chip_id];
i2c_set_clientdata(client, chip);
psy_cfg.drv_data = chip;
......@@ -324,11 +427,11 @@ static int max17040_probe(struct i2c_client *client,
return ret;
dev_dbg(&chip->client->dev, "MAX17040 Fuel-Gauge Ver 0x%x\n", ret);
if (chip_id == ID_MAX17040 || chip_id == ID_MAX17041)
max17040_reset(chip);
/* check interrupt */
if (client->irq && of_device_is_compatible(client->dev.of_node,
"maxim,max77836-battery")) {
if (client->irq && chip->data.has_low_soc_alert) {
ret = max17040_set_low_soc_alert(chip, chip->low_soc_alert);
if (ret) {
dev_err(&client->dev,
......@@ -391,16 +494,30 @@ static SIMPLE_DEV_PM_OPS(max17040_pm_ops, max17040_suspend, max17040_resume);
#endif /* CONFIG_PM_SLEEP */
static const struct i2c_device_id max17040_id[] = {
{ "max17040" },
{ "max77836-battery" },
{ }
{ "max17040", ID_MAX17040 },
{ "max17041", ID_MAX17041 },
{ "max17043", ID_MAX17043 },
{ "max77836-battery", ID_MAX17043 },
{ "max17044", ID_MAX17044 },
{ "max17048", ID_MAX17048 },
{ "max17049", ID_MAX17049 },
{ "max17058", ID_MAX17058 },
{ "max17059", ID_MAX17059 },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(i2c, max17040_id);
static const struct of_device_id max17040_of_match[] = {
{ .compatible = "maxim,max17040" },
{ .compatible = "maxim,max77836-battery" },
{ },
{ .compatible = "maxim,max17040", .data = (void *) ID_MAX17040 },
{ .compatible = "maxim,max17041", .data = (void *) ID_MAX17041 },
{ .compatible = "maxim,max17043", .data = (void *) ID_MAX17043 },
{ .compatible = "maxim,max77836-battery", .data = (void *) ID_MAX17043 },
{ .compatible = "maxim,max17044", .data = (void *) ID_MAX17044 },
{ .compatible = "maxim,max17048", .data = (void *) ID_MAX17048 },
{ .compatible = "maxim,max17049", .data = (void *) ID_MAX17049 },
{ .compatible = "maxim,max17058", .data = (void *) ID_MAX17058 },
{ .compatible = "maxim,max17059", .data = (void *) ID_MAX17059 },
{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, max17040_of_match);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment