18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci// 38c2ecf20Sopenharmony_ci// max17040_battery.c 48c2ecf20Sopenharmony_ci// fuel-gauge systems for lithium-ion (Li+) batteries 58c2ecf20Sopenharmony_ci// 68c2ecf20Sopenharmony_ci// Copyright (C) 2009 Samsung Electronics 78c2ecf20Sopenharmony_ci// Minkyu Kang <mk7.kang@samsung.com> 88c2ecf20Sopenharmony_ci 98c2ecf20Sopenharmony_ci#include <linux/module.h> 108c2ecf20Sopenharmony_ci#include <linux/init.h> 118c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 128c2ecf20Sopenharmony_ci#include <linux/mutex.h> 138c2ecf20Sopenharmony_ci#include <linux/err.h> 148c2ecf20Sopenharmony_ci#include <linux/i2c.h> 158c2ecf20Sopenharmony_ci#include <linux/delay.h> 168c2ecf20Sopenharmony_ci#include <linux/interrupt.h> 178c2ecf20Sopenharmony_ci#include <linux/power_supply.h> 188c2ecf20Sopenharmony_ci#include <linux/of_device.h> 198c2ecf20Sopenharmony_ci#include <linux/max17040_battery.h> 208c2ecf20Sopenharmony_ci#include <linux/regmap.h> 218c2ecf20Sopenharmony_ci#include <linux/slab.h> 228c2ecf20Sopenharmony_ci 238c2ecf20Sopenharmony_ci#define MAX17040_VCELL 0x02 248c2ecf20Sopenharmony_ci#define MAX17040_SOC 0x04 258c2ecf20Sopenharmony_ci#define MAX17040_MODE 0x06 268c2ecf20Sopenharmony_ci#define MAX17040_VER 0x08 278c2ecf20Sopenharmony_ci#define MAX17040_CONFIG 0x0C 288c2ecf20Sopenharmony_ci#define MAX17040_STATUS 0x1A 298c2ecf20Sopenharmony_ci#define MAX17040_CMD 0xFE 308c2ecf20Sopenharmony_ci 318c2ecf20Sopenharmony_ci 328c2ecf20Sopenharmony_ci#define MAX17040_DELAY 1000 338c2ecf20Sopenharmony_ci#define MAX17040_BATTERY_FULL 95 348c2ecf20Sopenharmony_ci#define MAX17040_RCOMP_DEFAULT 0x9700 358c2ecf20Sopenharmony_ci 368c2ecf20Sopenharmony_ci#define MAX17040_ATHD_MASK 0x3f 378c2ecf20Sopenharmony_ci#define MAX17040_ALSC_MASK 0x40 388c2ecf20Sopenharmony_ci#define MAX17040_ATHD_DEFAULT_POWER_UP 4 398c2ecf20Sopenharmony_ci#define MAX17040_STATUS_HD_MASK 0x1000 408c2ecf20Sopenharmony_ci#define MAX17040_STATUS_SC_MASK 0x2000 418c2ecf20Sopenharmony_ci#define MAX17040_CFG_RCOMP_MASK 0xff00 428c2ecf20Sopenharmony_ci 438c2ecf20Sopenharmony_cienum chip_id { 448c2ecf20Sopenharmony_ci ID_MAX17040, 458c2ecf20Sopenharmony_ci ID_MAX17041, 468c2ecf20Sopenharmony_ci ID_MAX17043, 478c2ecf20Sopenharmony_ci ID_MAX17044, 488c2ecf20Sopenharmony_ci ID_MAX17048, 498c2ecf20Sopenharmony_ci ID_MAX17049, 508c2ecf20Sopenharmony_ci ID_MAX17058, 518c2ecf20Sopenharmony_ci ID_MAX17059, 528c2ecf20Sopenharmony_ci}; 538c2ecf20Sopenharmony_ci 548c2ecf20Sopenharmony_ci/* values that differ by chip_id */ 558c2ecf20Sopenharmony_cistruct chip_data { 568c2ecf20Sopenharmony_ci u16 reset_val; 578c2ecf20Sopenharmony_ci u16 vcell_shift; 588c2ecf20Sopenharmony_ci u16 vcell_mul; 598c2ecf20Sopenharmony_ci u16 vcell_div; 608c2ecf20Sopenharmony_ci u8 has_low_soc_alert; 618c2ecf20Sopenharmony_ci u8 rcomp_bytes; 628c2ecf20Sopenharmony_ci u8 has_soc_alert; 638c2ecf20Sopenharmony_ci}; 648c2ecf20Sopenharmony_ci 658c2ecf20Sopenharmony_cistatic struct chip_data max17040_family[] = { 668c2ecf20Sopenharmony_ci [ID_MAX17040] = { 678c2ecf20Sopenharmony_ci .reset_val = 0x0054, 688c2ecf20Sopenharmony_ci .vcell_shift = 4, 698c2ecf20Sopenharmony_ci .vcell_mul = 1250, 708c2ecf20Sopenharmony_ci .vcell_div = 1, 718c2ecf20Sopenharmony_ci .has_low_soc_alert = 0, 728c2ecf20Sopenharmony_ci .rcomp_bytes = 2, 738c2ecf20Sopenharmony_ci .has_soc_alert = 0, 748c2ecf20Sopenharmony_ci }, 758c2ecf20Sopenharmony_ci [ID_MAX17041] = { 768c2ecf20Sopenharmony_ci .reset_val = 0x0054, 778c2ecf20Sopenharmony_ci .vcell_shift = 4, 788c2ecf20Sopenharmony_ci .vcell_mul = 2500, 798c2ecf20Sopenharmony_ci .vcell_div = 1, 808c2ecf20Sopenharmony_ci .has_low_soc_alert = 0, 818c2ecf20Sopenharmony_ci .rcomp_bytes = 2, 828c2ecf20Sopenharmony_ci .has_soc_alert = 0, 838c2ecf20Sopenharmony_ci }, 848c2ecf20Sopenharmony_ci [ID_MAX17043] = { 858c2ecf20Sopenharmony_ci .reset_val = 0x0054, 868c2ecf20Sopenharmony_ci .vcell_shift = 4, 878c2ecf20Sopenharmony_ci .vcell_mul = 1250, 888c2ecf20Sopenharmony_ci .vcell_div = 1, 898c2ecf20Sopenharmony_ci .has_low_soc_alert = 1, 908c2ecf20Sopenharmony_ci .rcomp_bytes = 1, 918c2ecf20Sopenharmony_ci .has_soc_alert = 0, 928c2ecf20Sopenharmony_ci }, 938c2ecf20Sopenharmony_ci [ID_MAX17044] = { 948c2ecf20Sopenharmony_ci .reset_val = 0x0054, 958c2ecf20Sopenharmony_ci .vcell_shift = 4, 968c2ecf20Sopenharmony_ci .vcell_mul = 2500, 978c2ecf20Sopenharmony_ci .vcell_div = 1, 988c2ecf20Sopenharmony_ci .has_low_soc_alert = 1, 998c2ecf20Sopenharmony_ci .rcomp_bytes = 1, 1008c2ecf20Sopenharmony_ci .has_soc_alert = 0, 1018c2ecf20Sopenharmony_ci }, 1028c2ecf20Sopenharmony_ci [ID_MAX17048] = { 1038c2ecf20Sopenharmony_ci .reset_val = 0x5400, 1048c2ecf20Sopenharmony_ci .vcell_shift = 0, 1058c2ecf20Sopenharmony_ci .vcell_mul = 625, 1068c2ecf20Sopenharmony_ci .vcell_div = 8, 1078c2ecf20Sopenharmony_ci .has_low_soc_alert = 1, 1088c2ecf20Sopenharmony_ci .rcomp_bytes = 1, 1098c2ecf20Sopenharmony_ci .has_soc_alert = 1, 1108c2ecf20Sopenharmony_ci }, 1118c2ecf20Sopenharmony_ci [ID_MAX17049] = { 1128c2ecf20Sopenharmony_ci .reset_val = 0x5400, 1138c2ecf20Sopenharmony_ci .vcell_shift = 0, 1148c2ecf20Sopenharmony_ci .vcell_mul = 625, 1158c2ecf20Sopenharmony_ci .vcell_div = 4, 1168c2ecf20Sopenharmony_ci .has_low_soc_alert = 1, 1178c2ecf20Sopenharmony_ci .rcomp_bytes = 1, 1188c2ecf20Sopenharmony_ci .has_soc_alert = 1, 1198c2ecf20Sopenharmony_ci }, 1208c2ecf20Sopenharmony_ci [ID_MAX17058] = { 1218c2ecf20Sopenharmony_ci .reset_val = 0x5400, 1228c2ecf20Sopenharmony_ci .vcell_shift = 0, 1238c2ecf20Sopenharmony_ci .vcell_mul = 625, 1248c2ecf20Sopenharmony_ci .vcell_div = 8, 1258c2ecf20Sopenharmony_ci .has_low_soc_alert = 1, 1268c2ecf20Sopenharmony_ci .rcomp_bytes = 1, 1278c2ecf20Sopenharmony_ci .has_soc_alert = 0, 1288c2ecf20Sopenharmony_ci }, 1298c2ecf20Sopenharmony_ci [ID_MAX17059] = { 1308c2ecf20Sopenharmony_ci .reset_val = 0x5400, 1318c2ecf20Sopenharmony_ci .vcell_shift = 0, 1328c2ecf20Sopenharmony_ci .vcell_mul = 625, 1338c2ecf20Sopenharmony_ci .vcell_div = 4, 1348c2ecf20Sopenharmony_ci .has_low_soc_alert = 1, 1358c2ecf20Sopenharmony_ci .rcomp_bytes = 1, 1368c2ecf20Sopenharmony_ci .has_soc_alert = 0, 1378c2ecf20Sopenharmony_ci }, 1388c2ecf20Sopenharmony_ci}; 1398c2ecf20Sopenharmony_ci 1408c2ecf20Sopenharmony_cistruct max17040_chip { 1418c2ecf20Sopenharmony_ci struct i2c_client *client; 1428c2ecf20Sopenharmony_ci struct regmap *regmap; 1438c2ecf20Sopenharmony_ci struct delayed_work work; 1448c2ecf20Sopenharmony_ci struct power_supply *battery; 1458c2ecf20Sopenharmony_ci struct max17040_platform_data *pdata; 1468c2ecf20Sopenharmony_ci struct chip_data data; 1478c2ecf20Sopenharmony_ci 1488c2ecf20Sopenharmony_ci /* battery capacity */ 1498c2ecf20Sopenharmony_ci int soc; 1508c2ecf20Sopenharmony_ci /* State Of Charge */ 1518c2ecf20Sopenharmony_ci int status; 1528c2ecf20Sopenharmony_ci /* Low alert threshold from 32% to 1% of the State of Charge */ 1538c2ecf20Sopenharmony_ci u32 low_soc_alert; 1548c2ecf20Sopenharmony_ci /* some devices return twice the capacity */ 1558c2ecf20Sopenharmony_ci bool quirk_double_soc; 1568c2ecf20Sopenharmony_ci /* higher 8 bits for 17043+, 16 bits for 17040,41 */ 1578c2ecf20Sopenharmony_ci u16 rcomp; 1588c2ecf20Sopenharmony_ci}; 1598c2ecf20Sopenharmony_ci 1608c2ecf20Sopenharmony_cistatic int max17040_reset(struct max17040_chip *chip) 1618c2ecf20Sopenharmony_ci{ 1628c2ecf20Sopenharmony_ci return regmap_write(chip->regmap, MAX17040_CMD, chip->data.reset_val); 1638c2ecf20Sopenharmony_ci} 1648c2ecf20Sopenharmony_ci 1658c2ecf20Sopenharmony_cistatic int max17040_set_low_soc_alert(struct max17040_chip *chip, u32 level) 1668c2ecf20Sopenharmony_ci{ 1678c2ecf20Sopenharmony_ci level = 32 - level * (chip->quirk_double_soc ? 2 : 1); 1688c2ecf20Sopenharmony_ci return regmap_update_bits(chip->regmap, MAX17040_CONFIG, 1698c2ecf20Sopenharmony_ci MAX17040_ATHD_MASK, level); 1708c2ecf20Sopenharmony_ci} 1718c2ecf20Sopenharmony_ci 1728c2ecf20Sopenharmony_cistatic int max17040_set_soc_alert(struct max17040_chip *chip, bool enable) 1738c2ecf20Sopenharmony_ci{ 1748c2ecf20Sopenharmony_ci return regmap_update_bits(chip->regmap, MAX17040_CONFIG, 1758c2ecf20Sopenharmony_ci MAX17040_ALSC_MASK, enable ? MAX17040_ALSC_MASK : 0); 1768c2ecf20Sopenharmony_ci} 1778c2ecf20Sopenharmony_ci 1788c2ecf20Sopenharmony_cistatic int max17040_set_rcomp(struct max17040_chip *chip, u16 rcomp) 1798c2ecf20Sopenharmony_ci{ 1808c2ecf20Sopenharmony_ci u16 mask = chip->data.rcomp_bytes == 2 ? 1818c2ecf20Sopenharmony_ci 0xffff : MAX17040_CFG_RCOMP_MASK; 1828c2ecf20Sopenharmony_ci 1838c2ecf20Sopenharmony_ci return regmap_update_bits(chip->regmap, MAX17040_CONFIG, mask, rcomp); 1848c2ecf20Sopenharmony_ci} 1858c2ecf20Sopenharmony_ci 1868c2ecf20Sopenharmony_cistatic int max17040_raw_vcell_to_uvolts(struct max17040_chip *chip, u16 vcell) 1878c2ecf20Sopenharmony_ci{ 1888c2ecf20Sopenharmony_ci struct chip_data *d = &chip->data; 1898c2ecf20Sopenharmony_ci 1908c2ecf20Sopenharmony_ci return (vcell >> d->vcell_shift) * d->vcell_mul / d->vcell_div; 1918c2ecf20Sopenharmony_ci} 1928c2ecf20Sopenharmony_ci 1938c2ecf20Sopenharmony_ci 1948c2ecf20Sopenharmony_cistatic int max17040_get_vcell(struct max17040_chip *chip) 1958c2ecf20Sopenharmony_ci{ 1968c2ecf20Sopenharmony_ci u32 vcell; 1978c2ecf20Sopenharmony_ci 1988c2ecf20Sopenharmony_ci regmap_read(chip->regmap, MAX17040_VCELL, &vcell); 1998c2ecf20Sopenharmony_ci 2008c2ecf20Sopenharmony_ci return max17040_raw_vcell_to_uvolts(chip, vcell); 2018c2ecf20Sopenharmony_ci} 2028c2ecf20Sopenharmony_ci 2038c2ecf20Sopenharmony_cistatic int max17040_get_soc(struct max17040_chip *chip) 2048c2ecf20Sopenharmony_ci{ 2058c2ecf20Sopenharmony_ci u32 soc; 2068c2ecf20Sopenharmony_ci 2078c2ecf20Sopenharmony_ci regmap_read(chip->regmap, MAX17040_SOC, &soc); 2088c2ecf20Sopenharmony_ci 2098c2ecf20Sopenharmony_ci return soc >> (chip->quirk_double_soc ? 9 : 8); 2108c2ecf20Sopenharmony_ci} 2118c2ecf20Sopenharmony_ci 2128c2ecf20Sopenharmony_cistatic int max17040_get_version(struct max17040_chip *chip) 2138c2ecf20Sopenharmony_ci{ 2148c2ecf20Sopenharmony_ci int ret; 2158c2ecf20Sopenharmony_ci u32 version; 2168c2ecf20Sopenharmony_ci 2178c2ecf20Sopenharmony_ci ret = regmap_read(chip->regmap, MAX17040_VER, &version); 2188c2ecf20Sopenharmony_ci 2198c2ecf20Sopenharmony_ci return ret ? ret : version; 2208c2ecf20Sopenharmony_ci} 2218c2ecf20Sopenharmony_ci 2228c2ecf20Sopenharmony_cistatic int max17040_get_online(struct max17040_chip *chip) 2238c2ecf20Sopenharmony_ci{ 2248c2ecf20Sopenharmony_ci return chip->pdata && chip->pdata->battery_online ? 2258c2ecf20Sopenharmony_ci chip->pdata->battery_online() : 1; 2268c2ecf20Sopenharmony_ci} 2278c2ecf20Sopenharmony_ci 2288c2ecf20Sopenharmony_cistatic int max17040_get_status(struct max17040_chip *chip) 2298c2ecf20Sopenharmony_ci{ 2308c2ecf20Sopenharmony_ci if (!chip->pdata || !chip->pdata->charger_online 2318c2ecf20Sopenharmony_ci || !chip->pdata->charger_enable) 2328c2ecf20Sopenharmony_ci return POWER_SUPPLY_STATUS_UNKNOWN; 2338c2ecf20Sopenharmony_ci 2348c2ecf20Sopenharmony_ci if (max17040_get_soc(chip) > MAX17040_BATTERY_FULL) 2358c2ecf20Sopenharmony_ci return POWER_SUPPLY_STATUS_FULL; 2368c2ecf20Sopenharmony_ci 2378c2ecf20Sopenharmony_ci if (chip->pdata->charger_online()) 2388c2ecf20Sopenharmony_ci if (chip->pdata->charger_enable()) 2398c2ecf20Sopenharmony_ci return POWER_SUPPLY_STATUS_CHARGING; 2408c2ecf20Sopenharmony_ci else 2418c2ecf20Sopenharmony_ci return POWER_SUPPLY_STATUS_NOT_CHARGING; 2428c2ecf20Sopenharmony_ci else 2438c2ecf20Sopenharmony_ci return POWER_SUPPLY_STATUS_DISCHARGING; 2448c2ecf20Sopenharmony_ci} 2458c2ecf20Sopenharmony_ci 2468c2ecf20Sopenharmony_cistatic int max17040_get_of_data(struct max17040_chip *chip) 2478c2ecf20Sopenharmony_ci{ 2488c2ecf20Sopenharmony_ci struct device *dev = &chip->client->dev; 2498c2ecf20Sopenharmony_ci struct chip_data *data = &max17040_family[ 2508c2ecf20Sopenharmony_ci (uintptr_t) of_device_get_match_data(dev)]; 2518c2ecf20Sopenharmony_ci int rcomp_len; 2528c2ecf20Sopenharmony_ci u8 rcomp[2]; 2538c2ecf20Sopenharmony_ci 2548c2ecf20Sopenharmony_ci chip->quirk_double_soc = device_property_read_bool(dev, 2558c2ecf20Sopenharmony_ci "maxim,double-soc"); 2568c2ecf20Sopenharmony_ci 2578c2ecf20Sopenharmony_ci chip->low_soc_alert = MAX17040_ATHD_DEFAULT_POWER_UP; 2588c2ecf20Sopenharmony_ci device_property_read_u32(dev, 2598c2ecf20Sopenharmony_ci "maxim,alert-low-soc-level", 2608c2ecf20Sopenharmony_ci &chip->low_soc_alert); 2618c2ecf20Sopenharmony_ci 2628c2ecf20Sopenharmony_ci if (chip->low_soc_alert <= 0 || 2638c2ecf20Sopenharmony_ci chip->low_soc_alert > (chip->quirk_double_soc ? 16 : 32)) { 2648c2ecf20Sopenharmony_ci dev_err(dev, "maxim,alert-low-soc-level out of bounds\n"); 2658c2ecf20Sopenharmony_ci return -EINVAL; 2668c2ecf20Sopenharmony_ci } 2678c2ecf20Sopenharmony_ci 2688c2ecf20Sopenharmony_ci rcomp_len = device_property_count_u8(dev, "maxim,rcomp"); 2698c2ecf20Sopenharmony_ci chip->rcomp = MAX17040_RCOMP_DEFAULT; 2708c2ecf20Sopenharmony_ci if (rcomp_len == data->rcomp_bytes) { 2718c2ecf20Sopenharmony_ci device_property_read_u8_array(dev, "maxim,rcomp", 2728c2ecf20Sopenharmony_ci rcomp, rcomp_len); 2738c2ecf20Sopenharmony_ci chip->rcomp = rcomp_len == 2 ? 2748c2ecf20Sopenharmony_ci rcomp[0] << 8 | rcomp[1] : 2758c2ecf20Sopenharmony_ci rcomp[0] << 8; 2768c2ecf20Sopenharmony_ci } else if (rcomp_len > 0) { 2778c2ecf20Sopenharmony_ci dev_err(dev, "maxim,rcomp has incorrect length\n"); 2788c2ecf20Sopenharmony_ci return -EINVAL; 2798c2ecf20Sopenharmony_ci } 2808c2ecf20Sopenharmony_ci 2818c2ecf20Sopenharmony_ci return 0; 2828c2ecf20Sopenharmony_ci} 2838c2ecf20Sopenharmony_ci 2848c2ecf20Sopenharmony_cistatic void max17040_check_changes(struct max17040_chip *chip) 2858c2ecf20Sopenharmony_ci{ 2868c2ecf20Sopenharmony_ci chip->soc = max17040_get_soc(chip); 2878c2ecf20Sopenharmony_ci chip->status = max17040_get_status(chip); 2888c2ecf20Sopenharmony_ci} 2898c2ecf20Sopenharmony_ci 2908c2ecf20Sopenharmony_cistatic void max17040_queue_work(struct max17040_chip *chip) 2918c2ecf20Sopenharmony_ci{ 2928c2ecf20Sopenharmony_ci queue_delayed_work(system_power_efficient_wq, &chip->work, 2938c2ecf20Sopenharmony_ci MAX17040_DELAY); 2948c2ecf20Sopenharmony_ci} 2958c2ecf20Sopenharmony_ci 2968c2ecf20Sopenharmony_cistatic void max17040_stop_work(void *data) 2978c2ecf20Sopenharmony_ci{ 2988c2ecf20Sopenharmony_ci struct max17040_chip *chip = data; 2998c2ecf20Sopenharmony_ci 3008c2ecf20Sopenharmony_ci cancel_delayed_work_sync(&chip->work); 3018c2ecf20Sopenharmony_ci} 3028c2ecf20Sopenharmony_ci 3038c2ecf20Sopenharmony_cistatic void max17040_work(struct work_struct *work) 3048c2ecf20Sopenharmony_ci{ 3058c2ecf20Sopenharmony_ci struct max17040_chip *chip; 3068c2ecf20Sopenharmony_ci int last_soc, last_status; 3078c2ecf20Sopenharmony_ci 3088c2ecf20Sopenharmony_ci chip = container_of(work, struct max17040_chip, work.work); 3098c2ecf20Sopenharmony_ci 3108c2ecf20Sopenharmony_ci /* store SOC and status to check changes */ 3118c2ecf20Sopenharmony_ci last_soc = chip->soc; 3128c2ecf20Sopenharmony_ci last_status = chip->status; 3138c2ecf20Sopenharmony_ci max17040_check_changes(chip); 3148c2ecf20Sopenharmony_ci 3158c2ecf20Sopenharmony_ci /* check changes and send uevent */ 3168c2ecf20Sopenharmony_ci if (last_soc != chip->soc || last_status != chip->status) 3178c2ecf20Sopenharmony_ci power_supply_changed(chip->battery); 3188c2ecf20Sopenharmony_ci 3198c2ecf20Sopenharmony_ci max17040_queue_work(chip); 3208c2ecf20Sopenharmony_ci} 3218c2ecf20Sopenharmony_ci 3228c2ecf20Sopenharmony_ci/* Returns true if alert cause was SOC change, not low SOC */ 3238c2ecf20Sopenharmony_cistatic bool max17040_handle_soc_alert(struct max17040_chip *chip) 3248c2ecf20Sopenharmony_ci{ 3258c2ecf20Sopenharmony_ci bool ret = true; 3268c2ecf20Sopenharmony_ci u32 data; 3278c2ecf20Sopenharmony_ci 3288c2ecf20Sopenharmony_ci regmap_read(chip->regmap, MAX17040_STATUS, &data); 3298c2ecf20Sopenharmony_ci 3308c2ecf20Sopenharmony_ci if (data & MAX17040_STATUS_HD_MASK) { 3318c2ecf20Sopenharmony_ci // this alert was caused by low soc 3328c2ecf20Sopenharmony_ci ret = false; 3338c2ecf20Sopenharmony_ci } 3348c2ecf20Sopenharmony_ci if (data & MAX17040_STATUS_SC_MASK) { 3358c2ecf20Sopenharmony_ci // soc change bit -- deassert to mark as handled 3368c2ecf20Sopenharmony_ci regmap_write(chip->regmap, MAX17040_STATUS, 3378c2ecf20Sopenharmony_ci data & ~MAX17040_STATUS_SC_MASK); 3388c2ecf20Sopenharmony_ci } 3398c2ecf20Sopenharmony_ci 3408c2ecf20Sopenharmony_ci return ret; 3418c2ecf20Sopenharmony_ci} 3428c2ecf20Sopenharmony_ci 3438c2ecf20Sopenharmony_cistatic irqreturn_t max17040_thread_handler(int id, void *dev) 3448c2ecf20Sopenharmony_ci{ 3458c2ecf20Sopenharmony_ci struct max17040_chip *chip = dev; 3468c2ecf20Sopenharmony_ci 3478c2ecf20Sopenharmony_ci if (!(chip->data.has_soc_alert && max17040_handle_soc_alert(chip))) 3488c2ecf20Sopenharmony_ci dev_warn(&chip->client->dev, "IRQ: Alert battery low level\n"); 3498c2ecf20Sopenharmony_ci 3508c2ecf20Sopenharmony_ci /* read registers */ 3518c2ecf20Sopenharmony_ci max17040_check_changes(chip); 3528c2ecf20Sopenharmony_ci 3538c2ecf20Sopenharmony_ci /* send uevent */ 3548c2ecf20Sopenharmony_ci power_supply_changed(chip->battery); 3558c2ecf20Sopenharmony_ci 3568c2ecf20Sopenharmony_ci /* reset alert bit */ 3578c2ecf20Sopenharmony_ci max17040_set_low_soc_alert(chip, chip->low_soc_alert); 3588c2ecf20Sopenharmony_ci 3598c2ecf20Sopenharmony_ci return IRQ_HANDLED; 3608c2ecf20Sopenharmony_ci} 3618c2ecf20Sopenharmony_ci 3628c2ecf20Sopenharmony_cistatic int max17040_enable_alert_irq(struct max17040_chip *chip) 3638c2ecf20Sopenharmony_ci{ 3648c2ecf20Sopenharmony_ci struct i2c_client *client = chip->client; 3658c2ecf20Sopenharmony_ci unsigned int flags; 3668c2ecf20Sopenharmony_ci int ret; 3678c2ecf20Sopenharmony_ci 3688c2ecf20Sopenharmony_ci flags = IRQF_TRIGGER_FALLING | IRQF_ONESHOT; 3698c2ecf20Sopenharmony_ci ret = devm_request_threaded_irq(&client->dev, client->irq, NULL, 3708c2ecf20Sopenharmony_ci max17040_thread_handler, flags, 3718c2ecf20Sopenharmony_ci chip->battery->desc->name, chip); 3728c2ecf20Sopenharmony_ci 3738c2ecf20Sopenharmony_ci return ret; 3748c2ecf20Sopenharmony_ci} 3758c2ecf20Sopenharmony_ci 3768c2ecf20Sopenharmony_cistatic int max17040_prop_writeable(struct power_supply *psy, 3778c2ecf20Sopenharmony_ci enum power_supply_property psp) 3788c2ecf20Sopenharmony_ci{ 3798c2ecf20Sopenharmony_ci switch (psp) { 3808c2ecf20Sopenharmony_ci case POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN: 3818c2ecf20Sopenharmony_ci return 1; 3828c2ecf20Sopenharmony_ci default: 3838c2ecf20Sopenharmony_ci return 0; 3848c2ecf20Sopenharmony_ci } 3858c2ecf20Sopenharmony_ci} 3868c2ecf20Sopenharmony_ci 3878c2ecf20Sopenharmony_cistatic int max17040_set_property(struct power_supply *psy, 3888c2ecf20Sopenharmony_ci enum power_supply_property psp, 3898c2ecf20Sopenharmony_ci const union power_supply_propval *val) 3908c2ecf20Sopenharmony_ci{ 3918c2ecf20Sopenharmony_ci struct max17040_chip *chip = power_supply_get_drvdata(psy); 3928c2ecf20Sopenharmony_ci int ret; 3938c2ecf20Sopenharmony_ci 3948c2ecf20Sopenharmony_ci switch (psp) { 3958c2ecf20Sopenharmony_ci case POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN: 3968c2ecf20Sopenharmony_ci /* alert threshold can be programmed from 1% up to 16/32% */ 3978c2ecf20Sopenharmony_ci if ((val->intval < 1) || 3988c2ecf20Sopenharmony_ci (val->intval > (chip->quirk_double_soc ? 16 : 32))) { 3998c2ecf20Sopenharmony_ci ret = -EINVAL; 4008c2ecf20Sopenharmony_ci break; 4018c2ecf20Sopenharmony_ci } 4028c2ecf20Sopenharmony_ci ret = max17040_set_low_soc_alert(chip, val->intval); 4038c2ecf20Sopenharmony_ci chip->low_soc_alert = val->intval; 4048c2ecf20Sopenharmony_ci break; 4058c2ecf20Sopenharmony_ci default: 4068c2ecf20Sopenharmony_ci ret = -EINVAL; 4078c2ecf20Sopenharmony_ci } 4088c2ecf20Sopenharmony_ci 4098c2ecf20Sopenharmony_ci return ret; 4108c2ecf20Sopenharmony_ci} 4118c2ecf20Sopenharmony_ci 4128c2ecf20Sopenharmony_cistatic int max17040_get_property(struct power_supply *psy, 4138c2ecf20Sopenharmony_ci enum power_supply_property psp, 4148c2ecf20Sopenharmony_ci union power_supply_propval *val) 4158c2ecf20Sopenharmony_ci{ 4168c2ecf20Sopenharmony_ci struct max17040_chip *chip = power_supply_get_drvdata(psy); 4178c2ecf20Sopenharmony_ci 4188c2ecf20Sopenharmony_ci switch (psp) { 4198c2ecf20Sopenharmony_ci case POWER_SUPPLY_PROP_STATUS: 4208c2ecf20Sopenharmony_ci val->intval = max17040_get_status(chip); 4218c2ecf20Sopenharmony_ci break; 4228c2ecf20Sopenharmony_ci case POWER_SUPPLY_PROP_ONLINE: 4238c2ecf20Sopenharmony_ci val->intval = max17040_get_online(chip); 4248c2ecf20Sopenharmony_ci break; 4258c2ecf20Sopenharmony_ci case POWER_SUPPLY_PROP_VOLTAGE_NOW: 4268c2ecf20Sopenharmony_ci val->intval = max17040_get_vcell(chip); 4278c2ecf20Sopenharmony_ci break; 4288c2ecf20Sopenharmony_ci case POWER_SUPPLY_PROP_CAPACITY: 4298c2ecf20Sopenharmony_ci val->intval = max17040_get_soc(chip); 4308c2ecf20Sopenharmony_ci break; 4318c2ecf20Sopenharmony_ci case POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN: 4328c2ecf20Sopenharmony_ci val->intval = chip->low_soc_alert; 4338c2ecf20Sopenharmony_ci break; 4348c2ecf20Sopenharmony_ci default: 4358c2ecf20Sopenharmony_ci return -EINVAL; 4368c2ecf20Sopenharmony_ci } 4378c2ecf20Sopenharmony_ci return 0; 4388c2ecf20Sopenharmony_ci} 4398c2ecf20Sopenharmony_ci 4408c2ecf20Sopenharmony_cistatic const struct regmap_config max17040_regmap = { 4418c2ecf20Sopenharmony_ci .reg_bits = 8, 4428c2ecf20Sopenharmony_ci .reg_stride = 2, 4438c2ecf20Sopenharmony_ci .val_bits = 16, 4448c2ecf20Sopenharmony_ci .val_format_endian = REGMAP_ENDIAN_BIG, 4458c2ecf20Sopenharmony_ci}; 4468c2ecf20Sopenharmony_ci 4478c2ecf20Sopenharmony_cistatic enum power_supply_property max17040_battery_props[] = { 4488c2ecf20Sopenharmony_ci POWER_SUPPLY_PROP_STATUS, 4498c2ecf20Sopenharmony_ci POWER_SUPPLY_PROP_ONLINE, 4508c2ecf20Sopenharmony_ci POWER_SUPPLY_PROP_VOLTAGE_NOW, 4518c2ecf20Sopenharmony_ci POWER_SUPPLY_PROP_CAPACITY, 4528c2ecf20Sopenharmony_ci POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN, 4538c2ecf20Sopenharmony_ci}; 4548c2ecf20Sopenharmony_ci 4558c2ecf20Sopenharmony_cistatic const struct power_supply_desc max17040_battery_desc = { 4568c2ecf20Sopenharmony_ci .name = "battery", 4578c2ecf20Sopenharmony_ci .type = POWER_SUPPLY_TYPE_BATTERY, 4588c2ecf20Sopenharmony_ci .get_property = max17040_get_property, 4598c2ecf20Sopenharmony_ci .set_property = max17040_set_property, 4608c2ecf20Sopenharmony_ci .property_is_writeable = max17040_prop_writeable, 4618c2ecf20Sopenharmony_ci .properties = max17040_battery_props, 4628c2ecf20Sopenharmony_ci .num_properties = ARRAY_SIZE(max17040_battery_props), 4638c2ecf20Sopenharmony_ci}; 4648c2ecf20Sopenharmony_ci 4658c2ecf20Sopenharmony_cistatic int max17040_probe(struct i2c_client *client, 4668c2ecf20Sopenharmony_ci const struct i2c_device_id *id) 4678c2ecf20Sopenharmony_ci{ 4688c2ecf20Sopenharmony_ci struct i2c_adapter *adapter = client->adapter; 4698c2ecf20Sopenharmony_ci struct power_supply_config psy_cfg = {}; 4708c2ecf20Sopenharmony_ci struct max17040_chip *chip; 4718c2ecf20Sopenharmony_ci enum chip_id chip_id; 4728c2ecf20Sopenharmony_ci bool enable_irq = false; 4738c2ecf20Sopenharmony_ci int ret; 4748c2ecf20Sopenharmony_ci 4758c2ecf20Sopenharmony_ci if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE)) 4768c2ecf20Sopenharmony_ci return -EIO; 4778c2ecf20Sopenharmony_ci 4788c2ecf20Sopenharmony_ci chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL); 4798c2ecf20Sopenharmony_ci if (!chip) 4808c2ecf20Sopenharmony_ci return -ENOMEM; 4818c2ecf20Sopenharmony_ci 4828c2ecf20Sopenharmony_ci chip->client = client; 4838c2ecf20Sopenharmony_ci chip->regmap = devm_regmap_init_i2c(client, &max17040_regmap); 4848c2ecf20Sopenharmony_ci chip->pdata = client->dev.platform_data; 4858c2ecf20Sopenharmony_ci if (IS_ERR(chip->regmap)) 4868c2ecf20Sopenharmony_ci return PTR_ERR(chip->regmap); 4878c2ecf20Sopenharmony_ci chip_id = (enum chip_id) id->driver_data; 4888c2ecf20Sopenharmony_ci if (client->dev.of_node) { 4898c2ecf20Sopenharmony_ci ret = max17040_get_of_data(chip); 4908c2ecf20Sopenharmony_ci if (ret) 4918c2ecf20Sopenharmony_ci return ret; 4928c2ecf20Sopenharmony_ci chip_id = (enum chip_id) (uintptr_t) 4938c2ecf20Sopenharmony_ci of_device_get_match_data(&client->dev); 4948c2ecf20Sopenharmony_ci } 4958c2ecf20Sopenharmony_ci chip->data = max17040_family[chip_id]; 4968c2ecf20Sopenharmony_ci 4978c2ecf20Sopenharmony_ci i2c_set_clientdata(client, chip); 4988c2ecf20Sopenharmony_ci psy_cfg.drv_data = chip; 4998c2ecf20Sopenharmony_ci 5008c2ecf20Sopenharmony_ci chip->battery = devm_power_supply_register(&client->dev, 5018c2ecf20Sopenharmony_ci &max17040_battery_desc, &psy_cfg); 5028c2ecf20Sopenharmony_ci if (IS_ERR(chip->battery)) { 5038c2ecf20Sopenharmony_ci dev_err(&client->dev, "failed: power supply register\n"); 5048c2ecf20Sopenharmony_ci return PTR_ERR(chip->battery); 5058c2ecf20Sopenharmony_ci } 5068c2ecf20Sopenharmony_ci 5078c2ecf20Sopenharmony_ci ret = max17040_get_version(chip); 5088c2ecf20Sopenharmony_ci if (ret < 0) 5098c2ecf20Sopenharmony_ci return ret; 5108c2ecf20Sopenharmony_ci dev_dbg(&chip->client->dev, "MAX17040 Fuel-Gauge Ver 0x%x\n", ret); 5118c2ecf20Sopenharmony_ci 5128c2ecf20Sopenharmony_ci if (chip_id == ID_MAX17040 || chip_id == ID_MAX17041) 5138c2ecf20Sopenharmony_ci max17040_reset(chip); 5148c2ecf20Sopenharmony_ci 5158c2ecf20Sopenharmony_ci max17040_set_rcomp(chip, chip->rcomp); 5168c2ecf20Sopenharmony_ci 5178c2ecf20Sopenharmony_ci /* check interrupt */ 5188c2ecf20Sopenharmony_ci if (client->irq && chip->data.has_low_soc_alert) { 5198c2ecf20Sopenharmony_ci ret = max17040_set_low_soc_alert(chip, chip->low_soc_alert); 5208c2ecf20Sopenharmony_ci if (ret) { 5218c2ecf20Sopenharmony_ci dev_err(&client->dev, 5228c2ecf20Sopenharmony_ci "Failed to set low SOC alert: err %d\n", ret); 5238c2ecf20Sopenharmony_ci return ret; 5248c2ecf20Sopenharmony_ci } 5258c2ecf20Sopenharmony_ci 5268c2ecf20Sopenharmony_ci enable_irq = true; 5278c2ecf20Sopenharmony_ci } 5288c2ecf20Sopenharmony_ci 5298c2ecf20Sopenharmony_ci if (client->irq && chip->data.has_soc_alert) { 5308c2ecf20Sopenharmony_ci ret = max17040_set_soc_alert(chip, 1); 5318c2ecf20Sopenharmony_ci if (ret) { 5328c2ecf20Sopenharmony_ci dev_err(&client->dev, 5338c2ecf20Sopenharmony_ci "Failed to set SOC alert: err %d\n", ret); 5348c2ecf20Sopenharmony_ci return ret; 5358c2ecf20Sopenharmony_ci } 5368c2ecf20Sopenharmony_ci enable_irq = true; 5378c2ecf20Sopenharmony_ci } else { 5388c2ecf20Sopenharmony_ci /* soc alerts negate the need for polling */ 5398c2ecf20Sopenharmony_ci INIT_DEFERRABLE_WORK(&chip->work, max17040_work); 5408c2ecf20Sopenharmony_ci ret = devm_add_action(&client->dev, max17040_stop_work, chip); 5418c2ecf20Sopenharmony_ci if (ret) 5428c2ecf20Sopenharmony_ci return ret; 5438c2ecf20Sopenharmony_ci max17040_queue_work(chip); 5448c2ecf20Sopenharmony_ci } 5458c2ecf20Sopenharmony_ci 5468c2ecf20Sopenharmony_ci if (enable_irq) { 5478c2ecf20Sopenharmony_ci ret = max17040_enable_alert_irq(chip); 5488c2ecf20Sopenharmony_ci if (ret) { 5498c2ecf20Sopenharmony_ci client->irq = 0; 5508c2ecf20Sopenharmony_ci dev_warn(&client->dev, 5518c2ecf20Sopenharmony_ci "Failed to get IRQ err %d\n", ret); 5528c2ecf20Sopenharmony_ci } 5538c2ecf20Sopenharmony_ci } 5548c2ecf20Sopenharmony_ci 5558c2ecf20Sopenharmony_ci return 0; 5568c2ecf20Sopenharmony_ci} 5578c2ecf20Sopenharmony_ci 5588c2ecf20Sopenharmony_ci#ifdef CONFIG_PM_SLEEP 5598c2ecf20Sopenharmony_ci 5608c2ecf20Sopenharmony_cistatic int max17040_suspend(struct device *dev) 5618c2ecf20Sopenharmony_ci{ 5628c2ecf20Sopenharmony_ci struct i2c_client *client = to_i2c_client(dev); 5638c2ecf20Sopenharmony_ci struct max17040_chip *chip = i2c_get_clientdata(client); 5648c2ecf20Sopenharmony_ci 5658c2ecf20Sopenharmony_ci if (client->irq && chip->data.has_soc_alert) 5668c2ecf20Sopenharmony_ci // disable soc alert to prevent wakeup 5678c2ecf20Sopenharmony_ci max17040_set_soc_alert(chip, 0); 5688c2ecf20Sopenharmony_ci else 5698c2ecf20Sopenharmony_ci cancel_delayed_work(&chip->work); 5708c2ecf20Sopenharmony_ci 5718c2ecf20Sopenharmony_ci if (client->irq && device_may_wakeup(dev)) 5728c2ecf20Sopenharmony_ci enable_irq_wake(client->irq); 5738c2ecf20Sopenharmony_ci 5748c2ecf20Sopenharmony_ci return 0; 5758c2ecf20Sopenharmony_ci} 5768c2ecf20Sopenharmony_ci 5778c2ecf20Sopenharmony_cistatic int max17040_resume(struct device *dev) 5788c2ecf20Sopenharmony_ci{ 5798c2ecf20Sopenharmony_ci struct i2c_client *client = to_i2c_client(dev); 5808c2ecf20Sopenharmony_ci struct max17040_chip *chip = i2c_get_clientdata(client); 5818c2ecf20Sopenharmony_ci 5828c2ecf20Sopenharmony_ci if (client->irq && device_may_wakeup(dev)) 5838c2ecf20Sopenharmony_ci disable_irq_wake(client->irq); 5848c2ecf20Sopenharmony_ci 5858c2ecf20Sopenharmony_ci if (client->irq && chip->data.has_soc_alert) 5868c2ecf20Sopenharmony_ci max17040_set_soc_alert(chip, 1); 5878c2ecf20Sopenharmony_ci else 5888c2ecf20Sopenharmony_ci max17040_queue_work(chip); 5898c2ecf20Sopenharmony_ci 5908c2ecf20Sopenharmony_ci return 0; 5918c2ecf20Sopenharmony_ci} 5928c2ecf20Sopenharmony_ci 5938c2ecf20Sopenharmony_cistatic SIMPLE_DEV_PM_OPS(max17040_pm_ops, max17040_suspend, max17040_resume); 5948c2ecf20Sopenharmony_ci#define MAX17040_PM_OPS (&max17040_pm_ops) 5958c2ecf20Sopenharmony_ci 5968c2ecf20Sopenharmony_ci#else 5978c2ecf20Sopenharmony_ci 5988c2ecf20Sopenharmony_ci#define MAX17040_PM_OPS NULL 5998c2ecf20Sopenharmony_ci 6008c2ecf20Sopenharmony_ci#endif /* CONFIG_PM_SLEEP */ 6018c2ecf20Sopenharmony_ci 6028c2ecf20Sopenharmony_cistatic const struct i2c_device_id max17040_id[] = { 6038c2ecf20Sopenharmony_ci { "max17040", ID_MAX17040 }, 6048c2ecf20Sopenharmony_ci { "max17041", ID_MAX17041 }, 6058c2ecf20Sopenharmony_ci { "max17043", ID_MAX17043 }, 6068c2ecf20Sopenharmony_ci { "max77836-battery", ID_MAX17043 }, 6078c2ecf20Sopenharmony_ci { "max17044", ID_MAX17044 }, 6088c2ecf20Sopenharmony_ci { "max17048", ID_MAX17048 }, 6098c2ecf20Sopenharmony_ci { "max17049", ID_MAX17049 }, 6108c2ecf20Sopenharmony_ci { "max17058", ID_MAX17058 }, 6118c2ecf20Sopenharmony_ci { "max17059", ID_MAX17059 }, 6128c2ecf20Sopenharmony_ci { /* sentinel */ } 6138c2ecf20Sopenharmony_ci}; 6148c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(i2c, max17040_id); 6158c2ecf20Sopenharmony_ci 6168c2ecf20Sopenharmony_cistatic const struct of_device_id max17040_of_match[] = { 6178c2ecf20Sopenharmony_ci { .compatible = "maxim,max17040", .data = (void *) ID_MAX17040 }, 6188c2ecf20Sopenharmony_ci { .compatible = "maxim,max17041", .data = (void *) ID_MAX17041 }, 6198c2ecf20Sopenharmony_ci { .compatible = "maxim,max17043", .data = (void *) ID_MAX17043 }, 6208c2ecf20Sopenharmony_ci { .compatible = "maxim,max77836-battery", .data = (void *) ID_MAX17043 }, 6218c2ecf20Sopenharmony_ci { .compatible = "maxim,max17044", .data = (void *) ID_MAX17044 }, 6228c2ecf20Sopenharmony_ci { .compatible = "maxim,max17048", .data = (void *) ID_MAX17048 }, 6238c2ecf20Sopenharmony_ci { .compatible = "maxim,max17049", .data = (void *) ID_MAX17049 }, 6248c2ecf20Sopenharmony_ci { .compatible = "maxim,max17058", .data = (void *) ID_MAX17058 }, 6258c2ecf20Sopenharmony_ci { .compatible = "maxim,max17059", .data = (void *) ID_MAX17059 }, 6268c2ecf20Sopenharmony_ci { /* sentinel */ }, 6278c2ecf20Sopenharmony_ci}; 6288c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, max17040_of_match); 6298c2ecf20Sopenharmony_ci 6308c2ecf20Sopenharmony_cistatic struct i2c_driver max17040_i2c_driver = { 6318c2ecf20Sopenharmony_ci .driver = { 6328c2ecf20Sopenharmony_ci .name = "max17040", 6338c2ecf20Sopenharmony_ci .of_match_table = max17040_of_match, 6348c2ecf20Sopenharmony_ci .pm = MAX17040_PM_OPS, 6358c2ecf20Sopenharmony_ci }, 6368c2ecf20Sopenharmony_ci .probe = max17040_probe, 6378c2ecf20Sopenharmony_ci .id_table = max17040_id, 6388c2ecf20Sopenharmony_ci}; 6398c2ecf20Sopenharmony_cimodule_i2c_driver(max17040_i2c_driver); 6408c2ecf20Sopenharmony_ci 6418c2ecf20Sopenharmony_ciMODULE_AUTHOR("Minkyu Kang <mk7.kang@samsung.com>"); 6428c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("MAX17040 Fuel Gauge"); 6438c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 644