forked from 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.
1227 lines
32 KiB
1227 lines
32 KiB
// SPDX-License-Identifier: GPL-2.0+ |
|
// |
|
// Fuel gauge driver for Maxim 17042 / 8966 / 8997 |
|
// Note that Maxim 8966 and 8997 are mfd and this is its subdevice. |
|
// |
|
// Copyright (C) 2011 Samsung Electronics |
|
// MyungJoo Ham <[email protected]> |
|
// |
|
// This driver is based on max17040_battery.c |
|
|
|
#include <linux/acpi.h> |
|
#include <linux/init.h> |
|
#include <linux/module.h> |
|
#include <linux/slab.h> |
|
#include <linux/i2c.h> |
|
#include <linux/delay.h> |
|
#include <linux/interrupt.h> |
|
#include <linux/pm.h> |
|
#include <linux/mod_devicetable.h> |
|
#include <linux/power_supply.h> |
|
#include <linux/power/max17042_battery.h> |
|
#include <linux/of.h> |
|
#include <linux/regmap.h> |
|
|
|
/* Status register bits */ |
|
#define STATUS_POR_BIT (1 << 1) |
|
#define STATUS_BST_BIT (1 << 3) |
|
#define STATUS_VMN_BIT (1 << 8) |
|
#define STATUS_TMN_BIT (1 << 9) |
|
#define STATUS_SMN_BIT (1 << 10) |
|
#define STATUS_BI_BIT (1 << 11) |
|
#define STATUS_VMX_BIT (1 << 12) |
|
#define STATUS_TMX_BIT (1 << 13) |
|
#define STATUS_SMX_BIT (1 << 14) |
|
#define STATUS_BR_BIT (1 << 15) |
|
|
|
/* Interrupt mask bits */ |
|
#define CONFIG_ALRT_BIT_ENBL (1 << 2) |
|
#define STATUS_INTR_SOCMIN_BIT (1 << 10) |
|
#define STATUS_INTR_SOCMAX_BIT (1 << 14) |
|
|
|
#define VFSOC0_LOCK 0x0000 |
|
#define VFSOC0_UNLOCK 0x0080 |
|
#define MODEL_UNLOCK1 0X0059 |
|
#define MODEL_UNLOCK2 0X00C4 |
|
#define MODEL_LOCK1 0X0000 |
|
#define MODEL_LOCK2 0X0000 |
|
|
|
#define dQ_ACC_DIV 0x4 |
|
#define dP_ACC_100 0x1900 |
|
#define dP_ACC_200 0x3200 |
|
|
|
#define MAX17042_VMAX_TOLERANCE 50 /* 50 mV */ |
|
|
|
struct max17042_chip { |
|
struct i2c_client *client; |
|
struct regmap *regmap; |
|
struct power_supply *battery; |
|
enum max170xx_chip_type chip_type; |
|
struct max17042_platform_data *pdata; |
|
struct work_struct work; |
|
int init_complete; |
|
}; |
|
|
|
static enum power_supply_property max17042_battery_props[] = { |
|
POWER_SUPPLY_PROP_STATUS, |
|
POWER_SUPPLY_PROP_PRESENT, |
|
POWER_SUPPLY_PROP_TECHNOLOGY, |
|
POWER_SUPPLY_PROP_CYCLE_COUNT, |
|
POWER_SUPPLY_PROP_VOLTAGE_MAX, |
|
POWER_SUPPLY_PROP_VOLTAGE_MIN, |
|
POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, |
|
POWER_SUPPLY_PROP_VOLTAGE_NOW, |
|
POWER_SUPPLY_PROP_VOLTAGE_AVG, |
|
POWER_SUPPLY_PROP_VOLTAGE_OCV, |
|
POWER_SUPPLY_PROP_CAPACITY, |
|
POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, |
|
POWER_SUPPLY_PROP_CHARGE_FULL, |
|
POWER_SUPPLY_PROP_CHARGE_NOW, |
|
POWER_SUPPLY_PROP_CHARGE_COUNTER, |
|
POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT, |
|
POWER_SUPPLY_PROP_TEMP, |
|
POWER_SUPPLY_PROP_TEMP_ALERT_MIN, |
|
POWER_SUPPLY_PROP_TEMP_ALERT_MAX, |
|
POWER_SUPPLY_PROP_TEMP_MIN, |
|
POWER_SUPPLY_PROP_TEMP_MAX, |
|
POWER_SUPPLY_PROP_HEALTH, |
|
POWER_SUPPLY_PROP_SCOPE, |
|
POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW, |
|
// these two have to be at the end on the list |
|
POWER_SUPPLY_PROP_CURRENT_NOW, |
|
POWER_SUPPLY_PROP_CURRENT_AVG, |
|
}; |
|
|
|
static int max17042_get_temperature(struct max17042_chip *chip, int *temp) |
|
{ |
|
int ret; |
|
u32 data; |
|
struct regmap *map = chip->regmap; |
|
|
|
ret = regmap_read(map, MAX17042_TEMP, &data); |
|
if (ret < 0) |
|
return ret; |
|
|
|
*temp = sign_extend32(data, 15); |
|
/* The value is converted into deci-centigrade scale */ |
|
/* Units of LSB = 1 / 256 degree Celsius */ |
|
*temp = *temp * 10 / 256; |
|
return 0; |
|
} |
|
|
|
static int max17042_get_status(struct max17042_chip *chip, int *status) |
|
{ |
|
int ret, charge_full, charge_now; |
|
int avg_current; |
|
u32 data; |
|
|
|
ret = power_supply_am_i_supplied(chip->battery); |
|
if (ret < 0) { |
|
*status = POWER_SUPPLY_STATUS_UNKNOWN; |
|
return 0; |
|
} |
|
if (ret == 0) { |
|
*status = POWER_SUPPLY_STATUS_DISCHARGING; |
|
return 0; |
|
} |
|
|
|
/* |
|
* The MAX170xx has builtin end-of-charge detection and will update |
|
* FullCAP to match RepCap when it detects end of charging. |
|
* |
|
* When this cycle the battery gets charged to a higher (calculated) |
|
* capacity then the previous cycle then FullCAP will get updated |
|
* contineously once end-of-charge detection kicks in, so allow the |
|
* 2 to differ a bit. |
|
*/ |
|
|
|
ret = regmap_read(chip->regmap, MAX17042_FullCAP, &charge_full); |
|
if (ret < 0) |
|
return ret; |
|
|
|
ret = regmap_read(chip->regmap, MAX17042_RepCap, &charge_now); |
|
if (ret < 0) |
|
return ret; |
|
|
|
if ((charge_full - charge_now) <= MAX17042_FULL_THRESHOLD) { |
|
*status = POWER_SUPPLY_STATUS_FULL; |
|
return 0; |
|
} |
|
|
|
/* |
|
* Even though we are supplied, we may still be discharging if the |
|
* supply is e.g. only delivering 5V 0.5A. Check current if available. |
|
*/ |
|
if (!chip->pdata->enable_current_sense) { |
|
*status = POWER_SUPPLY_STATUS_CHARGING; |
|
return 0; |
|
} |
|
|
|
ret = regmap_read(chip->regmap, MAX17042_AvgCurrent, &data); |
|
if (ret < 0) |
|
return ret; |
|
|
|
avg_current = sign_extend32(data, 15); |
|
avg_current *= 1562500 / chip->pdata->r_sns; |
|
|
|
if (avg_current > 0) |
|
*status = POWER_SUPPLY_STATUS_CHARGING; |
|
else |
|
*status = POWER_SUPPLY_STATUS_DISCHARGING; |
|
|
|
return 0; |
|
} |
|
|
|
static int max17042_get_battery_health(struct max17042_chip *chip, int *health) |
|
{ |
|
int temp, vavg, vbatt, ret; |
|
u32 val; |
|
|
|
ret = regmap_read(chip->regmap, MAX17042_AvgVCELL, &val); |
|
if (ret < 0) |
|
goto health_error; |
|
|
|
/* bits [0-3] unused */ |
|
vavg = val * 625 / 8; |
|
/* Convert to millivolts */ |
|
vavg /= 1000; |
|
|
|
ret = regmap_read(chip->regmap, MAX17042_VCELL, &val); |
|
if (ret < 0) |
|
goto health_error; |
|
|
|
/* bits [0-3] unused */ |
|
vbatt = val * 625 / 8; |
|
/* Convert to millivolts */ |
|
vbatt /= 1000; |
|
|
|
if (vavg < chip->pdata->vmin) { |
|
*health = POWER_SUPPLY_HEALTH_DEAD; |
|
goto out; |
|
} |
|
|
|
if (vbatt > chip->pdata->vmax + MAX17042_VMAX_TOLERANCE) { |
|
*health = POWER_SUPPLY_HEALTH_OVERVOLTAGE; |
|
goto out; |
|
} |
|
|
|
ret = max17042_get_temperature(chip, &temp); |
|
if (ret < 0) |
|
goto health_error; |
|
|
|
if (temp < chip->pdata->temp_min) { |
|
*health = POWER_SUPPLY_HEALTH_COLD; |
|
goto out; |
|
} |
|
|
|
if (temp > chip->pdata->temp_max) { |
|
*health = POWER_SUPPLY_HEALTH_OVERHEAT; |
|
goto out; |
|
} |
|
|
|
*health = POWER_SUPPLY_HEALTH_GOOD; |
|
|
|
out: |
|
return 0; |
|
|
|
health_error: |
|
return ret; |
|
} |
|
|
|
static int max17042_get_property(struct power_supply *psy, |
|
enum power_supply_property psp, |
|
union power_supply_propval *val) |
|
{ |
|
struct max17042_chip *chip = power_supply_get_drvdata(psy); |
|
struct regmap *map = chip->regmap; |
|
int ret; |
|
u32 data; |
|
u64 data64; |
|
|
|
if (!chip->init_complete) |
|
return -EAGAIN; |
|
|
|
switch (psp) { |
|
case POWER_SUPPLY_PROP_STATUS: |
|
ret = max17042_get_status(chip, &val->intval); |
|
if (ret < 0) |
|
return ret; |
|
break; |
|
case POWER_SUPPLY_PROP_PRESENT: |
|
ret = regmap_read(map, MAX17042_STATUS, &data); |
|
if (ret < 0) |
|
return ret; |
|
|
|
if (data & MAX17042_STATUS_BattAbsent) |
|
val->intval = 0; |
|
else |
|
val->intval = 1; |
|
break; |
|
case POWER_SUPPLY_PROP_TECHNOLOGY: |
|
val->intval = POWER_SUPPLY_TECHNOLOGY_LION; |
|
break; |
|
case POWER_SUPPLY_PROP_CYCLE_COUNT: |
|
ret = regmap_read(map, MAX17042_Cycles, &data); |
|
if (ret < 0) |
|
return ret; |
|
|
|
val->intval = data; |
|
break; |
|
case POWER_SUPPLY_PROP_VOLTAGE_MAX: |
|
ret = regmap_read(map, MAX17042_MinMaxVolt, &data); |
|
if (ret < 0) |
|
return ret; |
|
|
|
val->intval = data >> 8; |
|
val->intval *= 20000; /* Units of LSB = 20mV */ |
|
break; |
|
case POWER_SUPPLY_PROP_VOLTAGE_MIN: |
|
ret = regmap_read(map, MAX17042_MinMaxVolt, &data); |
|
if (ret < 0) |
|
return ret; |
|
|
|
val->intval = (data & 0xff) * 20000; /* Units of 20mV */ |
|
break; |
|
case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: |
|
if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17042) |
|
ret = regmap_read(map, MAX17042_V_empty, &data); |
|
else if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17055) |
|
ret = regmap_read(map, MAX17055_V_empty, &data); |
|
else |
|
ret = regmap_read(map, MAX17047_V_empty, &data); |
|
if (ret < 0) |
|
return ret; |
|
|
|
val->intval = data >> 7; |
|
val->intval *= 10000; /* Units of LSB = 10mV */ |
|
break; |
|
case POWER_SUPPLY_PROP_VOLTAGE_NOW: |
|
ret = regmap_read(map, MAX17042_VCELL, &data); |
|
if (ret < 0) |
|
return ret; |
|
|
|
val->intval = data * 625 / 8; |
|
break; |
|
case POWER_SUPPLY_PROP_VOLTAGE_AVG: |
|
ret = regmap_read(map, MAX17042_AvgVCELL, &data); |
|
if (ret < 0) |
|
return ret; |
|
|
|
val->intval = data * 625 / 8; |
|
break; |
|
case POWER_SUPPLY_PROP_VOLTAGE_OCV: |
|
ret = regmap_read(map, MAX17042_OCVInternal, &data); |
|
if (ret < 0) |
|
return ret; |
|
|
|
val->intval = data * 625 / 8; |
|
break; |
|
case POWER_SUPPLY_PROP_CAPACITY: |
|
ret = regmap_read(map, MAX17042_RepSOC, &data); |
|
if (ret < 0) |
|
return ret; |
|
|
|
val->intval = data >> 8; |
|
break; |
|
case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: |
|
ret = regmap_read(map, MAX17042_DesignCap, &data); |
|
if (ret < 0) |
|
return ret; |
|
|
|
data64 = data * 5000000ll; |
|
do_div(data64, chip->pdata->r_sns); |
|
val->intval = data64; |
|
break; |
|
case POWER_SUPPLY_PROP_CHARGE_FULL: |
|
ret = regmap_read(map, MAX17042_FullCAP, &data); |
|
if (ret < 0) |
|
return ret; |
|
|
|
data64 = data * 5000000ll; |
|
do_div(data64, chip->pdata->r_sns); |
|
val->intval = data64; |
|
break; |
|
case POWER_SUPPLY_PROP_CHARGE_NOW: |
|
ret = regmap_read(map, MAX17042_RepCap, &data); |
|
if (ret < 0) |
|
return ret; |
|
|
|
data64 = data * 5000000ll; |
|
do_div(data64, chip->pdata->r_sns); |
|
val->intval = data64; |
|
break; |
|
case POWER_SUPPLY_PROP_CHARGE_COUNTER: |
|
ret = regmap_read(map, MAX17042_QH, &data); |
|
if (ret < 0) |
|
return ret; |
|
|
|
data64 = sign_extend64(data, 15) * 5000000ll; |
|
val->intval = div_s64(data64, chip->pdata->r_sns); |
|
break; |
|
case POWER_SUPPLY_PROP_TEMP: |
|
ret = max17042_get_temperature(chip, &val->intval); |
|
if (ret < 0) |
|
return ret; |
|
break; |
|
case POWER_SUPPLY_PROP_TEMP_ALERT_MIN: |
|
ret = regmap_read(map, MAX17042_TALRT_Th, &data); |
|
if (ret < 0) |
|
return ret; |
|
/* LSB is Alert Minimum. In deci-centigrade */ |
|
val->intval = sign_extend32(data & 0xff, 7) * 10; |
|
break; |
|
case POWER_SUPPLY_PROP_TEMP_ALERT_MAX: |
|
ret = regmap_read(map, MAX17042_TALRT_Th, &data); |
|
if (ret < 0) |
|
return ret; |
|
/* MSB is Alert Maximum. In deci-centigrade */ |
|
val->intval = sign_extend32(data >> 8, 7) * 10; |
|
break; |
|
case POWER_SUPPLY_PROP_TEMP_MIN: |
|
val->intval = chip->pdata->temp_min; |
|
break; |
|
case POWER_SUPPLY_PROP_TEMP_MAX: |
|
val->intval = chip->pdata->temp_max; |
|
break; |
|
case POWER_SUPPLY_PROP_HEALTH: |
|
ret = max17042_get_battery_health(chip, &val->intval); |
|
if (ret < 0) |
|
return ret; |
|
break; |
|
case POWER_SUPPLY_PROP_SCOPE: |
|
val->intval = POWER_SUPPLY_SCOPE_SYSTEM; |
|
break; |
|
case POWER_SUPPLY_PROP_CURRENT_NOW: |
|
if (chip->pdata->enable_current_sense) { |
|
ret = regmap_read(map, MAX17042_Current, &data); |
|
if (ret < 0) |
|
return ret; |
|
|
|
data64 = sign_extend64(data, 15) * 1562500ll; |
|
val->intval = div_s64(data64, chip->pdata->r_sns); |
|
} else { |
|
return -EINVAL; |
|
} |
|
break; |
|
case POWER_SUPPLY_PROP_CURRENT_AVG: |
|
if (chip->pdata->enable_current_sense) { |
|
ret = regmap_read(map, MAX17042_AvgCurrent, &data); |
|
if (ret < 0) |
|
return ret; |
|
|
|
data64 = sign_extend64(data, 15) * 1562500ll; |
|
val->intval = div_s64(data64, chip->pdata->r_sns); |
|
} else { |
|
return -EINVAL; |
|
} |
|
break; |
|
case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT: |
|
ret = regmap_read(map, MAX17042_ICHGTerm, &data); |
|
if (ret < 0) |
|
return ret; |
|
|
|
data64 = data * 1562500ll; |
|
val->intval = div_s64(data64, chip->pdata->r_sns); |
|
break; |
|
case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW: |
|
ret = regmap_read(map, MAX17042_TTE, &data); |
|
if (ret < 0) |
|
return ret; |
|
|
|
val->intval = data * 5625 / 1000; |
|
break; |
|
default: |
|
return -EINVAL; |
|
} |
|
return 0; |
|
} |
|
|
|
static int max17042_set_property(struct power_supply *psy, |
|
enum power_supply_property psp, |
|
const union power_supply_propval *val) |
|
{ |
|
struct max17042_chip *chip = power_supply_get_drvdata(psy); |
|
struct regmap *map = chip->regmap; |
|
int ret = 0; |
|
u32 data; |
|
int8_t temp; |
|
|
|
switch (psp) { |
|
case POWER_SUPPLY_PROP_TEMP_ALERT_MIN: |
|
ret = regmap_read(map, MAX17042_TALRT_Th, &data); |
|
if (ret < 0) |
|
return ret; |
|
|
|
/* Input in deci-centigrade, convert to centigrade */ |
|
temp = val->intval / 10; |
|
/* force min < max */ |
|
if (temp >= (int8_t)(data >> 8)) |
|
temp = (int8_t)(data >> 8) - 1; |
|
/* Write both MAX and MIN ALERT */ |
|
data = (data & 0xff00) + temp; |
|
ret = regmap_write(map, MAX17042_TALRT_Th, data); |
|
break; |
|
case POWER_SUPPLY_PROP_TEMP_ALERT_MAX: |
|
ret = regmap_read(map, MAX17042_TALRT_Th, &data); |
|
if (ret < 0) |
|
return ret; |
|
|
|
/* Input in Deci-Centigrade, convert to centigrade */ |
|
temp = val->intval / 10; |
|
/* force max > min */ |
|
if (temp <= (int8_t)(data & 0xff)) |
|
temp = (int8_t)(data & 0xff) + 1; |
|
/* Write both MAX and MIN ALERT */ |
|
data = (data & 0xff) + (temp << 8); |
|
ret = regmap_write(map, MAX17042_TALRT_Th, data); |
|
break; |
|
default: |
|
ret = -EINVAL; |
|
} |
|
|
|
return ret; |
|
} |
|
|
|
static int max17042_property_is_writeable(struct power_supply *psy, |
|
enum power_supply_property psp) |
|
{ |
|
int ret; |
|
|
|
switch (psp) { |
|
case POWER_SUPPLY_PROP_TEMP_ALERT_MIN: |
|
case POWER_SUPPLY_PROP_TEMP_ALERT_MAX: |
|
ret = 1; |
|
break; |
|
default: |
|
ret = 0; |
|
} |
|
|
|
return ret; |
|
} |
|
|
|
static void max17042_external_power_changed(struct power_supply *psy) |
|
{ |
|
power_supply_changed(psy); |
|
} |
|
|
|
static int max17042_write_verify_reg(struct regmap *map, u8 reg, u32 value) |
|
{ |
|
int retries = 8; |
|
int ret; |
|
u32 read_value; |
|
|
|
do { |
|
ret = regmap_write(map, reg, value); |
|
regmap_read(map, reg, &read_value); |
|
if (read_value != value) { |
|
ret = -EIO; |
|
retries--; |
|
} |
|
} while (retries && read_value != value); |
|
|
|
if (ret < 0) |
|
pr_err("%s: err %d\n", __func__, ret); |
|
|
|
return ret; |
|
} |
|
|
|
static inline void max17042_override_por(struct regmap *map, |
|
u8 reg, u16 value) |
|
{ |
|
if (value) |
|
regmap_write(map, reg, value); |
|
} |
|
|
|
static inline void max17042_unlock_model(struct max17042_chip *chip) |
|
{ |
|
struct regmap *map = chip->regmap; |
|
|
|
regmap_write(map, MAX17042_MLOCKReg1, MODEL_UNLOCK1); |
|
regmap_write(map, MAX17042_MLOCKReg2, MODEL_UNLOCK2); |
|
} |
|
|
|
static inline void max17042_lock_model(struct max17042_chip *chip) |
|
{ |
|
struct regmap *map = chip->regmap; |
|
|
|
regmap_write(map, MAX17042_MLOCKReg1, MODEL_LOCK1); |
|
regmap_write(map, MAX17042_MLOCKReg2, MODEL_LOCK2); |
|
} |
|
|
|
static inline void max17042_write_model_data(struct max17042_chip *chip, |
|
u8 addr, int size) |
|
{ |
|
struct regmap *map = chip->regmap; |
|
int i; |
|
|
|
for (i = 0; i < size; i++) |
|
regmap_write(map, addr + i, |
|
chip->pdata->config_data->cell_char_tbl[i]); |
|
} |
|
|
|
static inline void max17042_read_model_data(struct max17042_chip *chip, |
|
u8 addr, u16 *data, int size) |
|
{ |
|
struct regmap *map = chip->regmap; |
|
int i; |
|
u32 tmp; |
|
|
|
for (i = 0; i < size; i++) { |
|
regmap_read(map, addr + i, &tmp); |
|
data[i] = (u16)tmp; |
|
} |
|
} |
|
|
|
static inline int max17042_model_data_compare(struct max17042_chip *chip, |
|
u16 *data1, u16 *data2, int size) |
|
{ |
|
int i; |
|
|
|
if (memcmp(data1, data2, size)) { |
|
dev_err(&chip->client->dev, "%s compare failed\n", __func__); |
|
for (i = 0; i < size; i++) |
|
dev_info(&chip->client->dev, "0x%x, 0x%x", |
|
data1[i], data2[i]); |
|
dev_info(&chip->client->dev, "\n"); |
|
return -EINVAL; |
|
} |
|
return 0; |
|
} |
|
|
|
static int max17042_init_model(struct max17042_chip *chip) |
|
{ |
|
int ret; |
|
int table_size = ARRAY_SIZE(chip->pdata->config_data->cell_char_tbl); |
|
u16 *temp_data; |
|
|
|
temp_data = kcalloc(table_size, sizeof(*temp_data), GFP_KERNEL); |
|
if (!temp_data) |
|
return -ENOMEM; |
|
|
|
max17042_unlock_model(chip); |
|
max17042_write_model_data(chip, MAX17042_MODELChrTbl, |
|
table_size); |
|
max17042_read_model_data(chip, MAX17042_MODELChrTbl, temp_data, |
|
table_size); |
|
|
|
ret = max17042_model_data_compare( |
|
chip, |
|
chip->pdata->config_data->cell_char_tbl, |
|
temp_data, |
|
table_size); |
|
|
|
max17042_lock_model(chip); |
|
kfree(temp_data); |
|
|
|
return ret; |
|
} |
|
|
|
static int max17042_verify_model_lock(struct max17042_chip *chip) |
|
{ |
|
int i; |
|
int table_size = ARRAY_SIZE(chip->pdata->config_data->cell_char_tbl); |
|
u16 *temp_data; |
|
int ret = 0; |
|
|
|
temp_data = kcalloc(table_size, sizeof(*temp_data), GFP_KERNEL); |
|
if (!temp_data) |
|
return -ENOMEM; |
|
|
|
max17042_read_model_data(chip, MAX17042_MODELChrTbl, temp_data, |
|
table_size); |
|
for (i = 0; i < table_size; i++) |
|
if (temp_data[i]) |
|
ret = -EINVAL; |
|
|
|
kfree(temp_data); |
|
return ret; |
|
} |
|
|
|
static void max17042_write_config_regs(struct max17042_chip *chip) |
|
{ |
|
struct max17042_config_data *config = chip->pdata->config_data; |
|
struct regmap *map = chip->regmap; |
|
|
|
regmap_write(map, MAX17042_CONFIG, config->config); |
|
regmap_write(map, MAX17042_LearnCFG, config->learn_cfg); |
|
regmap_write(map, MAX17042_FilterCFG, |
|
config->filter_cfg); |
|
regmap_write(map, MAX17042_RelaxCFG, config->relax_cfg); |
|
if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17047 || |
|
chip->chip_type == MAXIM_DEVICE_TYPE_MAX17050 || |
|
chip->chip_type == MAXIM_DEVICE_TYPE_MAX17055) |
|
regmap_write(map, MAX17047_FullSOCThr, |
|
config->full_soc_thresh); |
|
} |
|
|
|
static void max17042_write_custom_regs(struct max17042_chip *chip) |
|
{ |
|
struct max17042_config_data *config = chip->pdata->config_data; |
|
struct regmap *map = chip->regmap; |
|
|
|
max17042_write_verify_reg(map, MAX17042_RCOMP0, config->rcomp0); |
|
max17042_write_verify_reg(map, MAX17042_TempCo, config->tcompc0); |
|
max17042_write_verify_reg(map, MAX17042_ICHGTerm, config->ichgt_term); |
|
if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17042) { |
|
regmap_write(map, MAX17042_EmptyTempCo, config->empty_tempco); |
|
max17042_write_verify_reg(map, MAX17042_K_empty0, |
|
config->kempty0); |
|
} else { |
|
max17042_write_verify_reg(map, MAX17047_QRTbl00, |
|
config->qrtbl00); |
|
max17042_write_verify_reg(map, MAX17047_QRTbl10, |
|
config->qrtbl10); |
|
max17042_write_verify_reg(map, MAX17047_QRTbl20, |
|
config->qrtbl20); |
|
max17042_write_verify_reg(map, MAX17047_QRTbl30, |
|
config->qrtbl30); |
|
} |
|
} |
|
|
|
static void max17042_update_capacity_regs(struct max17042_chip *chip) |
|
{ |
|
struct max17042_config_data *config = chip->pdata->config_data; |
|
struct regmap *map = chip->regmap; |
|
|
|
max17042_write_verify_reg(map, MAX17042_FullCAP, |
|
config->fullcap); |
|
regmap_write(map, MAX17042_DesignCap, config->design_cap); |
|
max17042_write_verify_reg(map, MAX17042_FullCAPNom, |
|
config->fullcapnom); |
|
} |
|
|
|
static void max17042_reset_vfsoc0_reg(struct max17042_chip *chip) |
|
{ |
|
unsigned int vfSoc; |
|
struct regmap *map = chip->regmap; |
|
|
|
regmap_read(map, MAX17042_VFSOC, &vfSoc); |
|
regmap_write(map, MAX17042_VFSOC0Enable, VFSOC0_UNLOCK); |
|
max17042_write_verify_reg(map, MAX17042_VFSOC0, vfSoc); |
|
regmap_write(map, MAX17042_VFSOC0Enable, VFSOC0_LOCK); |
|
} |
|
|
|
static void max17042_load_new_capacity_params(struct max17042_chip *chip) |
|
{ |
|
u32 full_cap0, rep_cap, dq_acc, vfSoc; |
|
u32 rem_cap; |
|
|
|
struct max17042_config_data *config = chip->pdata->config_data; |
|
struct regmap *map = chip->regmap; |
|
|
|
regmap_read(map, MAX17042_FullCAP0, &full_cap0); |
|
regmap_read(map, MAX17042_VFSOC, &vfSoc); |
|
|
|
/* fg_vfSoc needs to shifted by 8 bits to get the |
|
* perc in 1% accuracy, to get the right rem_cap multiply |
|
* full_cap0, fg_vfSoc and devide by 100 |
|
*/ |
|
rem_cap = ((vfSoc >> 8) * full_cap0) / 100; |
|
max17042_write_verify_reg(map, MAX17042_RemCap, rem_cap); |
|
|
|
rep_cap = rem_cap; |
|
max17042_write_verify_reg(map, MAX17042_RepCap, rep_cap); |
|
|
|
/* Write dQ_acc to 200% of Capacity and dP_acc to 200% */ |
|
dq_acc = config->fullcap / dQ_ACC_DIV; |
|
max17042_write_verify_reg(map, MAX17042_dQacc, dq_acc); |
|
max17042_write_verify_reg(map, MAX17042_dPacc, dP_ACC_200); |
|
|
|
max17042_write_verify_reg(map, MAX17042_FullCAP, |
|
config->fullcap); |
|
regmap_write(map, MAX17042_DesignCap, |
|
config->design_cap); |
|
max17042_write_verify_reg(map, MAX17042_FullCAPNom, |
|
config->fullcapnom); |
|
/* Update SOC register with new SOC */ |
|
regmap_write(map, MAX17042_RepSOC, vfSoc); |
|
} |
|
|
|
/* |
|
* Block write all the override values coming from platform data. |
|
* This function MUST be called before the POR initialization proceedure |
|
* specified by maxim. |
|
*/ |
|
static inline void max17042_override_por_values(struct max17042_chip *chip) |
|
{ |
|
struct regmap *map = chip->regmap; |
|
struct max17042_config_data *config = chip->pdata->config_data; |
|
|
|
max17042_override_por(map, MAX17042_TGAIN, config->tgain); |
|
max17042_override_por(map, MAx17042_TOFF, config->toff); |
|
max17042_override_por(map, MAX17042_CGAIN, config->cgain); |
|
max17042_override_por(map, MAX17042_COFF, config->coff); |
|
|
|
max17042_override_por(map, MAX17042_VALRT_Th, config->valrt_thresh); |
|
max17042_override_por(map, MAX17042_TALRT_Th, config->talrt_thresh); |
|
max17042_override_por(map, MAX17042_SALRT_Th, |
|
config->soc_alrt_thresh); |
|
max17042_override_por(map, MAX17042_CONFIG, config->config); |
|
max17042_override_por(map, MAX17042_SHDNTIMER, config->shdntimer); |
|
|
|
max17042_override_por(map, MAX17042_DesignCap, config->design_cap); |
|
max17042_override_por(map, MAX17042_ICHGTerm, config->ichgt_term); |
|
|
|
max17042_override_por(map, MAX17042_AtRate, config->at_rate); |
|
max17042_override_por(map, MAX17042_LearnCFG, config->learn_cfg); |
|
max17042_override_por(map, MAX17042_FilterCFG, config->filter_cfg); |
|
max17042_override_por(map, MAX17042_RelaxCFG, config->relax_cfg); |
|
max17042_override_por(map, MAX17042_MiscCFG, config->misc_cfg); |
|
max17042_override_por(map, MAX17042_MaskSOC, config->masksoc); |
|
|
|
max17042_override_por(map, MAX17042_FullCAP, config->fullcap); |
|
max17042_override_por(map, MAX17042_FullCAPNom, config->fullcapnom); |
|
if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17042) |
|
max17042_override_por(map, MAX17042_SOC_empty, |
|
config->socempty); |
|
max17042_override_por(map, MAX17042_LAvg_empty, config->lavg_empty); |
|
max17042_override_por(map, MAX17042_dQacc, config->dqacc); |
|
max17042_override_por(map, MAX17042_dPacc, config->dpacc); |
|
|
|
if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17042) |
|
max17042_override_por(map, MAX17042_V_empty, config->vempty); |
|
if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17055) |
|
max17042_override_por(map, MAX17055_V_empty, config->vempty); |
|
else |
|
max17042_override_por(map, MAX17047_V_empty, config->vempty); |
|
max17042_override_por(map, MAX17042_TempNom, config->temp_nom); |
|
max17042_override_por(map, MAX17042_TempLim, config->temp_lim); |
|
max17042_override_por(map, MAX17042_FCTC, config->fctc); |
|
max17042_override_por(map, MAX17042_RCOMP0, config->rcomp0); |
|
max17042_override_por(map, MAX17042_TempCo, config->tcompc0); |
|
if (chip->chip_type && |
|
((chip->chip_type == MAXIM_DEVICE_TYPE_MAX17042) || |
|
(chip->chip_type == MAXIM_DEVICE_TYPE_MAX17047) || |
|
(chip->chip_type == MAXIM_DEVICE_TYPE_MAX17050))) { |
|
max17042_override_por(map, MAX17042_EmptyTempCo, |
|
config->empty_tempco); |
|
max17042_override_por(map, MAX17042_K_empty0, |
|
config->kempty0); |
|
} |
|
} |
|
|
|
static int max17042_init_chip(struct max17042_chip *chip) |
|
{ |
|
struct regmap *map = chip->regmap; |
|
int ret; |
|
|
|
max17042_override_por_values(chip); |
|
/* After Power up, the MAX17042 requires 500mS in order |
|
* to perform signal debouncing and initial SOC reporting |
|
*/ |
|
msleep(500); |
|
|
|
/* Initialize configaration */ |
|
max17042_write_config_regs(chip); |
|
|
|
/* write cell characterization data */ |
|
ret = max17042_init_model(chip); |
|
if (ret) { |
|
dev_err(&chip->client->dev, "%s init failed\n", |
|
__func__); |
|
return -EIO; |
|
} |
|
|
|
ret = max17042_verify_model_lock(chip); |
|
if (ret) { |
|
dev_err(&chip->client->dev, "%s lock verify failed\n", |
|
__func__); |
|
return -EIO; |
|
} |
|
/* write custom parameters */ |
|
max17042_write_custom_regs(chip); |
|
|
|
/* update capacity params */ |
|
max17042_update_capacity_regs(chip); |
|
|
|
/* delay must be atleast 350mS to allow VFSOC |
|
* to be calculated from the new configuration |
|
*/ |
|
msleep(350); |
|
|
|
/* reset vfsoc0 reg */ |
|
max17042_reset_vfsoc0_reg(chip); |
|
|
|
/* load new capacity params */ |
|
max17042_load_new_capacity_params(chip); |
|
|
|
/* Init complete, Clear the POR bit */ |
|
regmap_update_bits(map, MAX17042_STATUS, STATUS_POR_BIT, 0x0); |
|
return 0; |
|
} |
|
|
|
static void max17042_set_soc_threshold(struct max17042_chip *chip, u16 off) |
|
{ |
|
struct regmap *map = chip->regmap; |
|
u32 soc, soc_tr; |
|
|
|
/* program interrupt thesholds such that we should |
|
* get interrupt for every 'off' perc change in the soc |
|
*/ |
|
regmap_read(map, MAX17042_RepSOC, &soc); |
|
soc >>= 8; |
|
soc_tr = (soc + off) << 8; |
|
soc_tr |= (soc - off); |
|
regmap_write(map, MAX17042_SALRT_Th, soc_tr); |
|
} |
|
|
|
static irqreturn_t max17042_thread_handler(int id, void *dev) |
|
{ |
|
struct max17042_chip *chip = dev; |
|
u32 val; |
|
|
|
regmap_read(chip->regmap, MAX17042_STATUS, &val); |
|
if ((val & STATUS_INTR_SOCMIN_BIT) || |
|
(val & STATUS_INTR_SOCMAX_BIT)) { |
|
dev_info(&chip->client->dev, "SOC threshold INTR\n"); |
|
max17042_set_soc_threshold(chip, 1); |
|
} |
|
|
|
power_supply_changed(chip->battery); |
|
return IRQ_HANDLED; |
|
} |
|
|
|
static void max17042_init_worker(struct work_struct *work) |
|
{ |
|
struct max17042_chip *chip = container_of(work, |
|
struct max17042_chip, work); |
|
int ret; |
|
|
|
/* Initialize registers according to values from the platform data */ |
|
if (chip->pdata->enable_por_init && chip->pdata->config_data) { |
|
ret = max17042_init_chip(chip); |
|
if (ret) |
|
return; |
|
} |
|
|
|
chip->init_complete = 1; |
|
} |
|
|
|
#ifdef CONFIG_OF |
|
static struct max17042_platform_data * |
|
max17042_get_of_pdata(struct max17042_chip *chip) |
|
{ |
|
struct device *dev = &chip->client->dev; |
|
struct device_node *np = dev->of_node; |
|
u32 prop; |
|
struct max17042_platform_data *pdata; |
|
|
|
pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); |
|
if (!pdata) |
|
return NULL; |
|
|
|
/* |
|
* Require current sense resistor value to be specified for |
|
* current-sense functionality to be enabled at all. |
|
*/ |
|
if (of_property_read_u32(np, "maxim,rsns-microohm", &prop) == 0) { |
|
pdata->r_sns = prop; |
|
pdata->enable_current_sense = true; |
|
} |
|
|
|
if (of_property_read_s32(np, "maxim,cold-temp", &pdata->temp_min)) |
|
pdata->temp_min = INT_MIN; |
|
if (of_property_read_s32(np, "maxim,over-heat-temp", &pdata->temp_max)) |
|
pdata->temp_max = INT_MAX; |
|
if (of_property_read_s32(np, "maxim,dead-volt", &pdata->vmin)) |
|
pdata->vmin = INT_MIN; |
|
if (of_property_read_s32(np, "maxim,over-volt", &pdata->vmax)) |
|
pdata->vmax = INT_MAX; |
|
|
|
return pdata; |
|
} |
|
#endif |
|
|
|
static struct max17042_reg_data max17047_default_pdata_init_regs[] = { |
|
/* |
|
* Some firmwares do not set FullSOCThr, Enable End-of-Charge Detection |
|
* when the voltage FG reports 95%, as recommended in the datasheet. |
|
*/ |
|
{ MAX17047_FullSOCThr, MAX17042_BATTERY_FULL << 8 }, |
|
}; |
|
|
|
static struct max17042_platform_data * |
|
max17042_get_default_pdata(struct max17042_chip *chip) |
|
{ |
|
struct device *dev = &chip->client->dev; |
|
struct max17042_platform_data *pdata; |
|
int ret, misc_cfg; |
|
|
|
/* |
|
* The MAX17047 gets used on x86 where we might not have pdata, assume |
|
* the firmware will already have initialized the fuel-gauge and provide |
|
* default values for the non init bits to make things work. |
|
*/ |
|
pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); |
|
if (!pdata) |
|
return pdata; |
|
|
|
if ((chip->chip_type == MAXIM_DEVICE_TYPE_MAX17047) || |
|
(chip->chip_type == MAXIM_DEVICE_TYPE_MAX17050)) { |
|
pdata->init_data = max17047_default_pdata_init_regs; |
|
pdata->num_init_data = |
|
ARRAY_SIZE(max17047_default_pdata_init_regs); |
|
} |
|
|
|
ret = regmap_read(chip->regmap, MAX17042_MiscCFG, &misc_cfg); |
|
if (ret < 0) |
|
return NULL; |
|
|
|
/* If bits 0-1 are set to 3 then only Voltage readings are used */ |
|
if ((misc_cfg & 0x3) == 0x3) |
|
pdata->enable_current_sense = false; |
|
else |
|
pdata->enable_current_sense = true; |
|
|
|
pdata->vmin = MAX17042_DEFAULT_VMIN; |
|
pdata->vmax = MAX17042_DEFAULT_VMAX; |
|
pdata->temp_min = MAX17042_DEFAULT_TEMP_MIN; |
|
pdata->temp_max = MAX17042_DEFAULT_TEMP_MAX; |
|
|
|
return pdata; |
|
} |
|
|
|
static struct max17042_platform_data * |
|
max17042_get_pdata(struct max17042_chip *chip) |
|
{ |
|
struct device *dev = &chip->client->dev; |
|
|
|
#ifdef CONFIG_OF |
|
if (dev->of_node) |
|
return max17042_get_of_pdata(chip); |
|
#endif |
|
if (dev->platform_data) |
|
return dev->platform_data; |
|
|
|
return max17042_get_default_pdata(chip); |
|
} |
|
|
|
static const struct regmap_config max17042_regmap_config = { |
|
.reg_bits = 8, |
|
.val_bits = 16, |
|
.val_format_endian = REGMAP_ENDIAN_NATIVE, |
|
}; |
|
|
|
static const struct power_supply_desc max17042_psy_desc = { |
|
.name = "max170xx_battery", |
|
.type = POWER_SUPPLY_TYPE_BATTERY, |
|
.get_property = max17042_get_property, |
|
.set_property = max17042_set_property, |
|
.property_is_writeable = max17042_property_is_writeable, |
|
.external_power_changed = max17042_external_power_changed, |
|
.properties = max17042_battery_props, |
|
.num_properties = ARRAY_SIZE(max17042_battery_props), |
|
}; |
|
|
|
static const struct power_supply_desc max17042_no_current_sense_psy_desc = { |
|
.name = "max170xx_battery", |
|
.type = POWER_SUPPLY_TYPE_BATTERY, |
|
.get_property = max17042_get_property, |
|
.set_property = max17042_set_property, |
|
.property_is_writeable = max17042_property_is_writeable, |
|
.properties = max17042_battery_props, |
|
.num_properties = ARRAY_SIZE(max17042_battery_props) - 2, |
|
}; |
|
|
|
static void max17042_stop_work(void *data) |
|
{ |
|
struct max17042_chip *chip = data; |
|
|
|
cancel_work_sync(&chip->work); |
|
} |
|
|
|
static int max17042_probe(struct i2c_client *client, |
|
const struct i2c_device_id *id) |
|
{ |
|
struct i2c_adapter *adapter = client->adapter; |
|
const struct power_supply_desc *max17042_desc = &max17042_psy_desc; |
|
struct power_supply_config psy_cfg = {}; |
|
const struct acpi_device_id *acpi_id = NULL; |
|
struct device *dev = &client->dev; |
|
struct max17042_chip *chip; |
|
int ret; |
|
int i; |
|
u32 val; |
|
|
|
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA)) |
|
return -EIO; |
|
|
|
chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL); |
|
if (!chip) |
|
return -ENOMEM; |
|
|
|
chip->client = client; |
|
if (id) { |
|
chip->chip_type = id->driver_data; |
|
} else { |
|
acpi_id = acpi_match_device(dev->driver->acpi_match_table, dev); |
|
if (!acpi_id) |
|
return -ENODEV; |
|
|
|
chip->chip_type = acpi_id->driver_data; |
|
} |
|
chip->regmap = devm_regmap_init_i2c(client, &max17042_regmap_config); |
|
if (IS_ERR(chip->regmap)) { |
|
dev_err(&client->dev, "Failed to initialize regmap\n"); |
|
return -EINVAL; |
|
} |
|
|
|
chip->pdata = max17042_get_pdata(chip); |
|
if (!chip->pdata) { |
|
dev_err(&client->dev, "no platform data provided\n"); |
|
return -EINVAL; |
|
} |
|
|
|
i2c_set_clientdata(client, chip); |
|
psy_cfg.drv_data = chip; |
|
psy_cfg.of_node = dev->of_node; |
|
|
|
/* When current is not measured, |
|
* CURRENT_NOW and CURRENT_AVG properties should be invisible. */ |
|
if (!chip->pdata->enable_current_sense) |
|
max17042_desc = &max17042_no_current_sense_psy_desc; |
|
|
|
if (chip->pdata->r_sns == 0) |
|
chip->pdata->r_sns = MAX17042_DEFAULT_SNS_RESISTOR; |
|
|
|
if (chip->pdata->init_data) |
|
for (i = 0; i < chip->pdata->num_init_data; i++) |
|
regmap_write(chip->regmap, |
|
chip->pdata->init_data[i].addr, |
|
chip->pdata->init_data[i].data); |
|
|
|
if (!chip->pdata->enable_current_sense) { |
|
regmap_write(chip->regmap, MAX17042_CGAIN, 0x0000); |
|
regmap_write(chip->regmap, MAX17042_MiscCFG, 0x0003); |
|
regmap_write(chip->regmap, MAX17042_LearnCFG, 0x0007); |
|
} |
|
|
|
chip->battery = devm_power_supply_register(&client->dev, max17042_desc, |
|
&psy_cfg); |
|
if (IS_ERR(chip->battery)) { |
|
dev_err(&client->dev, "failed: power supply register\n"); |
|
return PTR_ERR(chip->battery); |
|
} |
|
|
|
if (client->irq) { |
|
unsigned int flags = IRQF_TRIGGER_FALLING | IRQF_ONESHOT; |
|
|
|
/* |
|
* On ACPI systems the IRQ may be handled by ACPI-event code, |
|
* so we need to share (if the ACPI code is willing to share). |
|
*/ |
|
if (acpi_id) |
|
flags |= IRQF_SHARED | IRQF_PROBE_SHARED; |
|
|
|
ret = devm_request_threaded_irq(&client->dev, client->irq, |
|
NULL, |
|
max17042_thread_handler, flags, |
|
chip->battery->desc->name, |
|
chip); |
|
if (!ret) { |
|
regmap_update_bits(chip->regmap, MAX17042_CONFIG, |
|
CONFIG_ALRT_BIT_ENBL, |
|
CONFIG_ALRT_BIT_ENBL); |
|
max17042_set_soc_threshold(chip, 1); |
|
} else { |
|
client->irq = 0; |
|
if (ret != -EBUSY) |
|
dev_err(&client->dev, "Failed to get IRQ\n"); |
|
} |
|
} |
|
/* Not able to update the charge threshold when exceeded? -> disable */ |
|
if (!client->irq) |
|
regmap_write(chip->regmap, MAX17042_SALRT_Th, 0xff00); |
|
|
|
regmap_read(chip->regmap, MAX17042_STATUS, &val); |
|
if (val & STATUS_POR_BIT) { |
|
INIT_WORK(&chip->work, max17042_init_worker); |
|
ret = devm_add_action(&client->dev, max17042_stop_work, chip); |
|
if (ret) |
|
return ret; |
|
schedule_work(&chip->work); |
|
} else { |
|
chip->init_complete = 1; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
#ifdef CONFIG_PM_SLEEP |
|
static int max17042_suspend(struct device *dev) |
|
{ |
|
struct max17042_chip *chip = dev_get_drvdata(dev); |
|
|
|
/* |
|
* disable the irq and enable irq_wake |
|
* capability to the interrupt line. |
|
*/ |
|
if (chip->client->irq) { |
|
disable_irq(chip->client->irq); |
|
enable_irq_wake(chip->client->irq); |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static int max17042_resume(struct device *dev) |
|
{ |
|
struct max17042_chip *chip = dev_get_drvdata(dev); |
|
|
|
if (chip->client->irq) { |
|
disable_irq_wake(chip->client->irq); |
|
enable_irq(chip->client->irq); |
|
/* re-program the SOC thresholds to 1% change */ |
|
max17042_set_soc_threshold(chip, 1); |
|
} |
|
|
|
return 0; |
|
} |
|
#endif |
|
|
|
static SIMPLE_DEV_PM_OPS(max17042_pm_ops, max17042_suspend, |
|
max17042_resume); |
|
|
|
#ifdef CONFIG_ACPI |
|
static const struct acpi_device_id max17042_acpi_match[] = { |
|
{ "MAX17047", MAXIM_DEVICE_TYPE_MAX17047 }, |
|
{ } |
|
}; |
|
MODULE_DEVICE_TABLE(acpi, max17042_acpi_match); |
|
#endif |
|
|
|
#ifdef CONFIG_OF |
|
static const struct of_device_id max17042_dt_match[] = { |
|
{ .compatible = "maxim,max17042" }, |
|
{ .compatible = "maxim,max17047" }, |
|
{ .compatible = "maxim,max17050" }, |
|
{ .compatible = "maxim,max17055" }, |
|
{ }, |
|
}; |
|
MODULE_DEVICE_TABLE(of, max17042_dt_match); |
|
#endif |
|
|
|
static const struct i2c_device_id max17042_id[] = { |
|
{ "max17042", MAXIM_DEVICE_TYPE_MAX17042 }, |
|
{ "max17047", MAXIM_DEVICE_TYPE_MAX17047 }, |
|
{ "max17050", MAXIM_DEVICE_TYPE_MAX17050 }, |
|
{ "max17055", MAXIM_DEVICE_TYPE_MAX17055 }, |
|
{ } |
|
}; |
|
MODULE_DEVICE_TABLE(i2c, max17042_id); |
|
|
|
static struct i2c_driver max17042_i2c_driver = { |
|
.driver = { |
|
.name = "max17042", |
|
.acpi_match_table = ACPI_PTR(max17042_acpi_match), |
|
.of_match_table = of_match_ptr(max17042_dt_match), |
|
.pm = &max17042_pm_ops, |
|
}, |
|
.probe = max17042_probe, |
|
.id_table = max17042_id, |
|
}; |
|
module_i2c_driver(max17042_i2c_driver); |
|
|
|
MODULE_AUTHOR("MyungJoo Ham <[email protected]>"); |
|
MODULE_DESCRIPTION("MAX17042 Fuel Gauge"); |
|
MODULE_LICENSE("GPL");
|
|
|