18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * DA9150 Fuel-Gauge Driver 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (c) 2015 Dialog Semiconductor 68c2ecf20Sopenharmony_ci * 78c2ecf20Sopenharmony_ci * Author: Adam Thomson <Adam.Thomson.Opensource@diasemi.com> 88c2ecf20Sopenharmony_ci */ 98c2ecf20Sopenharmony_ci 108c2ecf20Sopenharmony_ci#include <linux/kernel.h> 118c2ecf20Sopenharmony_ci#include <linux/module.h> 128c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 138c2ecf20Sopenharmony_ci#include <linux/of.h> 148c2ecf20Sopenharmony_ci#include <linux/of_platform.h> 158c2ecf20Sopenharmony_ci#include <linux/slab.h> 168c2ecf20Sopenharmony_ci#include <linux/interrupt.h> 178c2ecf20Sopenharmony_ci#include <linux/delay.h> 188c2ecf20Sopenharmony_ci#include <linux/power_supply.h> 198c2ecf20Sopenharmony_ci#include <linux/list.h> 208c2ecf20Sopenharmony_ci#include <asm/div64.h> 218c2ecf20Sopenharmony_ci#include <linux/mfd/da9150/core.h> 228c2ecf20Sopenharmony_ci#include <linux/mfd/da9150/registers.h> 238c2ecf20Sopenharmony_ci 248c2ecf20Sopenharmony_ci/* Core2Wire */ 258c2ecf20Sopenharmony_ci#define DA9150_QIF_READ (0x0 << 7) 268c2ecf20Sopenharmony_ci#define DA9150_QIF_WRITE (0x1 << 7) 278c2ecf20Sopenharmony_ci#define DA9150_QIF_CODE_MASK 0x7F 288c2ecf20Sopenharmony_ci 298c2ecf20Sopenharmony_ci#define DA9150_QIF_BYTE_SIZE 8 308c2ecf20Sopenharmony_ci#define DA9150_QIF_BYTE_MASK 0xFF 318c2ecf20Sopenharmony_ci#define DA9150_QIF_SHORT_SIZE 2 328c2ecf20Sopenharmony_ci#define DA9150_QIF_LONG_SIZE 4 338c2ecf20Sopenharmony_ci 348c2ecf20Sopenharmony_ci/* QIF Codes */ 358c2ecf20Sopenharmony_ci#define DA9150_QIF_UAVG 6 368c2ecf20Sopenharmony_ci#define DA9150_QIF_UAVG_SIZE DA9150_QIF_LONG_SIZE 378c2ecf20Sopenharmony_ci#define DA9150_QIF_IAVG 8 388c2ecf20Sopenharmony_ci#define DA9150_QIF_IAVG_SIZE DA9150_QIF_LONG_SIZE 398c2ecf20Sopenharmony_ci#define DA9150_QIF_NTCAVG 12 408c2ecf20Sopenharmony_ci#define DA9150_QIF_NTCAVG_SIZE DA9150_QIF_LONG_SIZE 418c2ecf20Sopenharmony_ci#define DA9150_QIF_SHUNT_VAL 36 428c2ecf20Sopenharmony_ci#define DA9150_QIF_SHUNT_VAL_SIZE DA9150_QIF_SHORT_SIZE 438c2ecf20Sopenharmony_ci#define DA9150_QIF_SD_GAIN 38 448c2ecf20Sopenharmony_ci#define DA9150_QIF_SD_GAIN_SIZE DA9150_QIF_LONG_SIZE 458c2ecf20Sopenharmony_ci#define DA9150_QIF_FCC_MAH 40 468c2ecf20Sopenharmony_ci#define DA9150_QIF_FCC_MAH_SIZE DA9150_QIF_SHORT_SIZE 478c2ecf20Sopenharmony_ci#define DA9150_QIF_SOC_PCT 43 488c2ecf20Sopenharmony_ci#define DA9150_QIF_SOC_PCT_SIZE DA9150_QIF_SHORT_SIZE 498c2ecf20Sopenharmony_ci#define DA9150_QIF_CHARGE_LIMIT 44 508c2ecf20Sopenharmony_ci#define DA9150_QIF_CHARGE_LIMIT_SIZE DA9150_QIF_SHORT_SIZE 518c2ecf20Sopenharmony_ci#define DA9150_QIF_DISCHARGE_LIMIT 45 528c2ecf20Sopenharmony_ci#define DA9150_QIF_DISCHARGE_LIMIT_SIZE DA9150_QIF_SHORT_SIZE 538c2ecf20Sopenharmony_ci#define DA9150_QIF_FW_MAIN_VER 118 548c2ecf20Sopenharmony_ci#define DA9150_QIF_FW_MAIN_VER_SIZE DA9150_QIF_SHORT_SIZE 558c2ecf20Sopenharmony_ci#define DA9150_QIF_E_FG_STATUS 126 568c2ecf20Sopenharmony_ci#define DA9150_QIF_E_FG_STATUS_SIZE DA9150_QIF_SHORT_SIZE 578c2ecf20Sopenharmony_ci#define DA9150_QIF_SYNC 127 588c2ecf20Sopenharmony_ci#define DA9150_QIF_SYNC_SIZE DA9150_QIF_SHORT_SIZE 598c2ecf20Sopenharmony_ci#define DA9150_QIF_MAX_CODES 128 608c2ecf20Sopenharmony_ci 618c2ecf20Sopenharmony_ci/* QIF Sync Timeout */ 628c2ecf20Sopenharmony_ci#define DA9150_QIF_SYNC_TIMEOUT 1000 638c2ecf20Sopenharmony_ci#define DA9150_QIF_SYNC_RETRIES 10 648c2ecf20Sopenharmony_ci 658c2ecf20Sopenharmony_ci/* QIF E_FG_STATUS */ 668c2ecf20Sopenharmony_ci#define DA9150_FG_IRQ_LOW_SOC_MASK (1 << 0) 678c2ecf20Sopenharmony_ci#define DA9150_FG_IRQ_HIGH_SOC_MASK (1 << 1) 688c2ecf20Sopenharmony_ci#define DA9150_FG_IRQ_SOC_MASK \ 698c2ecf20Sopenharmony_ci (DA9150_FG_IRQ_LOW_SOC_MASK | DA9150_FG_IRQ_HIGH_SOC_MASK) 708c2ecf20Sopenharmony_ci 718c2ecf20Sopenharmony_ci/* Private data */ 728c2ecf20Sopenharmony_cistruct da9150_fg { 738c2ecf20Sopenharmony_ci struct da9150 *da9150; 748c2ecf20Sopenharmony_ci struct device *dev; 758c2ecf20Sopenharmony_ci 768c2ecf20Sopenharmony_ci struct mutex io_lock; 778c2ecf20Sopenharmony_ci 788c2ecf20Sopenharmony_ci struct power_supply *battery; 798c2ecf20Sopenharmony_ci struct delayed_work work; 808c2ecf20Sopenharmony_ci u32 interval; 818c2ecf20Sopenharmony_ci 828c2ecf20Sopenharmony_ci int warn_soc; 838c2ecf20Sopenharmony_ci int crit_soc; 848c2ecf20Sopenharmony_ci int soc; 858c2ecf20Sopenharmony_ci}; 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_ci/* Battery Properties */ 888c2ecf20Sopenharmony_cistatic u32 da9150_fg_read_attr(struct da9150_fg *fg, u8 code, u8 size) 898c2ecf20Sopenharmony_ci 908c2ecf20Sopenharmony_ci{ 918c2ecf20Sopenharmony_ci u8 buf[DA9150_QIF_LONG_SIZE]; 928c2ecf20Sopenharmony_ci u8 read_addr; 938c2ecf20Sopenharmony_ci u32 res = 0; 948c2ecf20Sopenharmony_ci int i; 958c2ecf20Sopenharmony_ci 968c2ecf20Sopenharmony_ci /* Set QIF code (READ mode) */ 978c2ecf20Sopenharmony_ci read_addr = (code & DA9150_QIF_CODE_MASK) | DA9150_QIF_READ; 988c2ecf20Sopenharmony_ci 998c2ecf20Sopenharmony_ci da9150_read_qif(fg->da9150, read_addr, size, buf); 1008c2ecf20Sopenharmony_ci for (i = 0; i < size; ++i) 1018c2ecf20Sopenharmony_ci res |= (buf[i] << (i * DA9150_QIF_BYTE_SIZE)); 1028c2ecf20Sopenharmony_ci 1038c2ecf20Sopenharmony_ci return res; 1048c2ecf20Sopenharmony_ci} 1058c2ecf20Sopenharmony_ci 1068c2ecf20Sopenharmony_cistatic void da9150_fg_write_attr(struct da9150_fg *fg, u8 code, u8 size, 1078c2ecf20Sopenharmony_ci u32 val) 1088c2ecf20Sopenharmony_ci 1098c2ecf20Sopenharmony_ci{ 1108c2ecf20Sopenharmony_ci u8 buf[DA9150_QIF_LONG_SIZE]; 1118c2ecf20Sopenharmony_ci u8 write_addr; 1128c2ecf20Sopenharmony_ci int i; 1138c2ecf20Sopenharmony_ci 1148c2ecf20Sopenharmony_ci /* Set QIF code (WRITE mode) */ 1158c2ecf20Sopenharmony_ci write_addr = (code & DA9150_QIF_CODE_MASK) | DA9150_QIF_WRITE; 1168c2ecf20Sopenharmony_ci 1178c2ecf20Sopenharmony_ci for (i = 0; i < size; ++i) { 1188c2ecf20Sopenharmony_ci buf[i] = (val >> (i * DA9150_QIF_BYTE_SIZE)) & 1198c2ecf20Sopenharmony_ci DA9150_QIF_BYTE_MASK; 1208c2ecf20Sopenharmony_ci } 1218c2ecf20Sopenharmony_ci da9150_write_qif(fg->da9150, write_addr, size, buf); 1228c2ecf20Sopenharmony_ci} 1238c2ecf20Sopenharmony_ci 1248c2ecf20Sopenharmony_ci/* Trigger QIF Sync to update QIF readable data */ 1258c2ecf20Sopenharmony_cistatic void da9150_fg_read_sync_start(struct da9150_fg *fg) 1268c2ecf20Sopenharmony_ci{ 1278c2ecf20Sopenharmony_ci int i = 0; 1288c2ecf20Sopenharmony_ci u32 res = 0; 1298c2ecf20Sopenharmony_ci 1308c2ecf20Sopenharmony_ci mutex_lock(&fg->io_lock); 1318c2ecf20Sopenharmony_ci 1328c2ecf20Sopenharmony_ci /* Check if QIF sync already requested, and write to sync if not */ 1338c2ecf20Sopenharmony_ci res = da9150_fg_read_attr(fg, DA9150_QIF_SYNC, 1348c2ecf20Sopenharmony_ci DA9150_QIF_SYNC_SIZE); 1358c2ecf20Sopenharmony_ci if (res > 0) 1368c2ecf20Sopenharmony_ci da9150_fg_write_attr(fg, DA9150_QIF_SYNC, 1378c2ecf20Sopenharmony_ci DA9150_QIF_SYNC_SIZE, 0); 1388c2ecf20Sopenharmony_ci 1398c2ecf20Sopenharmony_ci /* Wait for sync to complete */ 1408c2ecf20Sopenharmony_ci res = 0; 1418c2ecf20Sopenharmony_ci while ((res == 0) && (i++ < DA9150_QIF_SYNC_RETRIES)) { 1428c2ecf20Sopenharmony_ci usleep_range(DA9150_QIF_SYNC_TIMEOUT, 1438c2ecf20Sopenharmony_ci DA9150_QIF_SYNC_TIMEOUT * 2); 1448c2ecf20Sopenharmony_ci res = da9150_fg_read_attr(fg, DA9150_QIF_SYNC, 1458c2ecf20Sopenharmony_ci DA9150_QIF_SYNC_SIZE); 1468c2ecf20Sopenharmony_ci } 1478c2ecf20Sopenharmony_ci 1488c2ecf20Sopenharmony_ci /* Check if sync completed */ 1498c2ecf20Sopenharmony_ci if (res == 0) 1508c2ecf20Sopenharmony_ci dev_err(fg->dev, "Failed to perform QIF read sync!\n"); 1518c2ecf20Sopenharmony_ci} 1528c2ecf20Sopenharmony_ci 1538c2ecf20Sopenharmony_ci/* 1548c2ecf20Sopenharmony_ci * Should always be called after QIF sync read has been performed, and all 1558c2ecf20Sopenharmony_ci * attributes required have been accessed. 1568c2ecf20Sopenharmony_ci */ 1578c2ecf20Sopenharmony_cistatic inline void da9150_fg_read_sync_end(struct da9150_fg *fg) 1588c2ecf20Sopenharmony_ci{ 1598c2ecf20Sopenharmony_ci mutex_unlock(&fg->io_lock); 1608c2ecf20Sopenharmony_ci} 1618c2ecf20Sopenharmony_ci 1628c2ecf20Sopenharmony_ci/* Sync read of single QIF attribute */ 1638c2ecf20Sopenharmony_cistatic u32 da9150_fg_read_attr_sync(struct da9150_fg *fg, u8 code, u8 size) 1648c2ecf20Sopenharmony_ci{ 1658c2ecf20Sopenharmony_ci u32 val; 1668c2ecf20Sopenharmony_ci 1678c2ecf20Sopenharmony_ci da9150_fg_read_sync_start(fg); 1688c2ecf20Sopenharmony_ci val = da9150_fg_read_attr(fg, code, size); 1698c2ecf20Sopenharmony_ci da9150_fg_read_sync_end(fg); 1708c2ecf20Sopenharmony_ci 1718c2ecf20Sopenharmony_ci return val; 1728c2ecf20Sopenharmony_ci} 1738c2ecf20Sopenharmony_ci 1748c2ecf20Sopenharmony_ci/* Wait for QIF Sync, write QIF data and wait for ack */ 1758c2ecf20Sopenharmony_cistatic void da9150_fg_write_attr_sync(struct da9150_fg *fg, u8 code, u8 size, 1768c2ecf20Sopenharmony_ci u32 val) 1778c2ecf20Sopenharmony_ci{ 1788c2ecf20Sopenharmony_ci int i = 0; 1798c2ecf20Sopenharmony_ci u32 res = 0, sync_val; 1808c2ecf20Sopenharmony_ci 1818c2ecf20Sopenharmony_ci mutex_lock(&fg->io_lock); 1828c2ecf20Sopenharmony_ci 1838c2ecf20Sopenharmony_ci /* Check if QIF sync already requested */ 1848c2ecf20Sopenharmony_ci res = da9150_fg_read_attr(fg, DA9150_QIF_SYNC, 1858c2ecf20Sopenharmony_ci DA9150_QIF_SYNC_SIZE); 1868c2ecf20Sopenharmony_ci 1878c2ecf20Sopenharmony_ci /* Wait for an existing sync to complete */ 1888c2ecf20Sopenharmony_ci while ((res == 0) && (i++ < DA9150_QIF_SYNC_RETRIES)) { 1898c2ecf20Sopenharmony_ci usleep_range(DA9150_QIF_SYNC_TIMEOUT, 1908c2ecf20Sopenharmony_ci DA9150_QIF_SYNC_TIMEOUT * 2); 1918c2ecf20Sopenharmony_ci res = da9150_fg_read_attr(fg, DA9150_QIF_SYNC, 1928c2ecf20Sopenharmony_ci DA9150_QIF_SYNC_SIZE); 1938c2ecf20Sopenharmony_ci } 1948c2ecf20Sopenharmony_ci 1958c2ecf20Sopenharmony_ci if (res == 0) { 1968c2ecf20Sopenharmony_ci dev_err(fg->dev, "Timeout waiting for existing QIF sync!\n"); 1978c2ecf20Sopenharmony_ci mutex_unlock(&fg->io_lock); 1988c2ecf20Sopenharmony_ci return; 1998c2ecf20Sopenharmony_ci } 2008c2ecf20Sopenharmony_ci 2018c2ecf20Sopenharmony_ci /* Write value for QIF code */ 2028c2ecf20Sopenharmony_ci da9150_fg_write_attr(fg, code, size, val); 2038c2ecf20Sopenharmony_ci 2048c2ecf20Sopenharmony_ci /* Wait for write acknowledgment */ 2058c2ecf20Sopenharmony_ci i = 0; 2068c2ecf20Sopenharmony_ci sync_val = res; 2078c2ecf20Sopenharmony_ci while ((res == sync_val) && (i++ < DA9150_QIF_SYNC_RETRIES)) { 2088c2ecf20Sopenharmony_ci usleep_range(DA9150_QIF_SYNC_TIMEOUT, 2098c2ecf20Sopenharmony_ci DA9150_QIF_SYNC_TIMEOUT * 2); 2108c2ecf20Sopenharmony_ci res = da9150_fg_read_attr(fg, DA9150_QIF_SYNC, 2118c2ecf20Sopenharmony_ci DA9150_QIF_SYNC_SIZE); 2128c2ecf20Sopenharmony_ci } 2138c2ecf20Sopenharmony_ci 2148c2ecf20Sopenharmony_ci mutex_unlock(&fg->io_lock); 2158c2ecf20Sopenharmony_ci 2168c2ecf20Sopenharmony_ci /* Check write was actually successful */ 2178c2ecf20Sopenharmony_ci if (res != (sync_val + 1)) 2188c2ecf20Sopenharmony_ci dev_err(fg->dev, "Error performing QIF sync write for code %d\n", 2198c2ecf20Sopenharmony_ci code); 2208c2ecf20Sopenharmony_ci} 2218c2ecf20Sopenharmony_ci 2228c2ecf20Sopenharmony_ci/* Power Supply attributes */ 2238c2ecf20Sopenharmony_cistatic int da9150_fg_capacity(struct da9150_fg *fg, 2248c2ecf20Sopenharmony_ci union power_supply_propval *val) 2258c2ecf20Sopenharmony_ci{ 2268c2ecf20Sopenharmony_ci val->intval = da9150_fg_read_attr_sync(fg, DA9150_QIF_SOC_PCT, 2278c2ecf20Sopenharmony_ci DA9150_QIF_SOC_PCT_SIZE); 2288c2ecf20Sopenharmony_ci 2298c2ecf20Sopenharmony_ci if (val->intval > 100) 2308c2ecf20Sopenharmony_ci val->intval = 100; 2318c2ecf20Sopenharmony_ci 2328c2ecf20Sopenharmony_ci return 0; 2338c2ecf20Sopenharmony_ci} 2348c2ecf20Sopenharmony_ci 2358c2ecf20Sopenharmony_cistatic int da9150_fg_current_avg(struct da9150_fg *fg, 2368c2ecf20Sopenharmony_ci union power_supply_propval *val) 2378c2ecf20Sopenharmony_ci{ 2388c2ecf20Sopenharmony_ci u32 iavg, sd_gain, shunt_val; 2398c2ecf20Sopenharmony_ci u64 div, res; 2408c2ecf20Sopenharmony_ci 2418c2ecf20Sopenharmony_ci da9150_fg_read_sync_start(fg); 2428c2ecf20Sopenharmony_ci iavg = da9150_fg_read_attr(fg, DA9150_QIF_IAVG, 2438c2ecf20Sopenharmony_ci DA9150_QIF_IAVG_SIZE); 2448c2ecf20Sopenharmony_ci shunt_val = da9150_fg_read_attr(fg, DA9150_QIF_SHUNT_VAL, 2458c2ecf20Sopenharmony_ci DA9150_QIF_SHUNT_VAL_SIZE); 2468c2ecf20Sopenharmony_ci sd_gain = da9150_fg_read_attr(fg, DA9150_QIF_SD_GAIN, 2478c2ecf20Sopenharmony_ci DA9150_QIF_SD_GAIN_SIZE); 2488c2ecf20Sopenharmony_ci da9150_fg_read_sync_end(fg); 2498c2ecf20Sopenharmony_ci 2508c2ecf20Sopenharmony_ci div = (u64) (sd_gain * shunt_val * 65536ULL); 2518c2ecf20Sopenharmony_ci do_div(div, 1000000); 2528c2ecf20Sopenharmony_ci res = (u64) (iavg * 1000000ULL); 2538c2ecf20Sopenharmony_ci do_div(res, div); 2548c2ecf20Sopenharmony_ci 2558c2ecf20Sopenharmony_ci val->intval = (int) res; 2568c2ecf20Sopenharmony_ci 2578c2ecf20Sopenharmony_ci return 0; 2588c2ecf20Sopenharmony_ci} 2598c2ecf20Sopenharmony_ci 2608c2ecf20Sopenharmony_cistatic int da9150_fg_voltage_avg(struct da9150_fg *fg, 2618c2ecf20Sopenharmony_ci union power_supply_propval *val) 2628c2ecf20Sopenharmony_ci{ 2638c2ecf20Sopenharmony_ci u64 res; 2648c2ecf20Sopenharmony_ci 2658c2ecf20Sopenharmony_ci val->intval = da9150_fg_read_attr_sync(fg, DA9150_QIF_UAVG, 2668c2ecf20Sopenharmony_ci DA9150_QIF_UAVG_SIZE); 2678c2ecf20Sopenharmony_ci 2688c2ecf20Sopenharmony_ci res = (u64) (val->intval * 186ULL); 2698c2ecf20Sopenharmony_ci do_div(res, 10000); 2708c2ecf20Sopenharmony_ci val->intval = (int) res; 2718c2ecf20Sopenharmony_ci 2728c2ecf20Sopenharmony_ci return 0; 2738c2ecf20Sopenharmony_ci} 2748c2ecf20Sopenharmony_ci 2758c2ecf20Sopenharmony_cistatic int da9150_fg_charge_full(struct da9150_fg *fg, 2768c2ecf20Sopenharmony_ci union power_supply_propval *val) 2778c2ecf20Sopenharmony_ci{ 2788c2ecf20Sopenharmony_ci val->intval = da9150_fg_read_attr_sync(fg, DA9150_QIF_FCC_MAH, 2798c2ecf20Sopenharmony_ci DA9150_QIF_FCC_MAH_SIZE); 2808c2ecf20Sopenharmony_ci 2818c2ecf20Sopenharmony_ci val->intval = val->intval * 1000; 2828c2ecf20Sopenharmony_ci 2838c2ecf20Sopenharmony_ci return 0; 2848c2ecf20Sopenharmony_ci} 2858c2ecf20Sopenharmony_ci 2868c2ecf20Sopenharmony_ci/* 2878c2ecf20Sopenharmony_ci * Temperature reading from device is only valid if battery/system provides 2888c2ecf20Sopenharmony_ci * valid NTC to associated pin of DA9150 chip. 2898c2ecf20Sopenharmony_ci */ 2908c2ecf20Sopenharmony_cistatic int da9150_fg_temp(struct da9150_fg *fg, 2918c2ecf20Sopenharmony_ci union power_supply_propval *val) 2928c2ecf20Sopenharmony_ci{ 2938c2ecf20Sopenharmony_ci val->intval = da9150_fg_read_attr_sync(fg, DA9150_QIF_NTCAVG, 2948c2ecf20Sopenharmony_ci DA9150_QIF_NTCAVG_SIZE); 2958c2ecf20Sopenharmony_ci 2968c2ecf20Sopenharmony_ci val->intval = (val->intval * 10) / 1048576; 2978c2ecf20Sopenharmony_ci 2988c2ecf20Sopenharmony_ci return 0; 2998c2ecf20Sopenharmony_ci} 3008c2ecf20Sopenharmony_ci 3018c2ecf20Sopenharmony_cistatic enum power_supply_property da9150_fg_props[] = { 3028c2ecf20Sopenharmony_ci POWER_SUPPLY_PROP_CAPACITY, 3038c2ecf20Sopenharmony_ci POWER_SUPPLY_PROP_CURRENT_AVG, 3048c2ecf20Sopenharmony_ci POWER_SUPPLY_PROP_VOLTAGE_AVG, 3058c2ecf20Sopenharmony_ci POWER_SUPPLY_PROP_CHARGE_FULL, 3068c2ecf20Sopenharmony_ci POWER_SUPPLY_PROP_TEMP, 3078c2ecf20Sopenharmony_ci}; 3088c2ecf20Sopenharmony_ci 3098c2ecf20Sopenharmony_cistatic int da9150_fg_get_prop(struct power_supply *psy, 3108c2ecf20Sopenharmony_ci enum power_supply_property psp, 3118c2ecf20Sopenharmony_ci union power_supply_propval *val) 3128c2ecf20Sopenharmony_ci{ 3138c2ecf20Sopenharmony_ci struct da9150_fg *fg = dev_get_drvdata(psy->dev.parent); 3148c2ecf20Sopenharmony_ci int ret; 3158c2ecf20Sopenharmony_ci 3168c2ecf20Sopenharmony_ci switch (psp) { 3178c2ecf20Sopenharmony_ci case POWER_SUPPLY_PROP_CAPACITY: 3188c2ecf20Sopenharmony_ci ret = da9150_fg_capacity(fg, val); 3198c2ecf20Sopenharmony_ci break; 3208c2ecf20Sopenharmony_ci case POWER_SUPPLY_PROP_CURRENT_AVG: 3218c2ecf20Sopenharmony_ci ret = da9150_fg_current_avg(fg, val); 3228c2ecf20Sopenharmony_ci break; 3238c2ecf20Sopenharmony_ci case POWER_SUPPLY_PROP_VOLTAGE_AVG: 3248c2ecf20Sopenharmony_ci ret = da9150_fg_voltage_avg(fg, val); 3258c2ecf20Sopenharmony_ci break; 3268c2ecf20Sopenharmony_ci case POWER_SUPPLY_PROP_CHARGE_FULL: 3278c2ecf20Sopenharmony_ci ret = da9150_fg_charge_full(fg, val); 3288c2ecf20Sopenharmony_ci break; 3298c2ecf20Sopenharmony_ci case POWER_SUPPLY_PROP_TEMP: 3308c2ecf20Sopenharmony_ci ret = da9150_fg_temp(fg, val); 3318c2ecf20Sopenharmony_ci break; 3328c2ecf20Sopenharmony_ci default: 3338c2ecf20Sopenharmony_ci ret = -EINVAL; 3348c2ecf20Sopenharmony_ci break; 3358c2ecf20Sopenharmony_ci } 3368c2ecf20Sopenharmony_ci 3378c2ecf20Sopenharmony_ci return ret; 3388c2ecf20Sopenharmony_ci} 3398c2ecf20Sopenharmony_ci 3408c2ecf20Sopenharmony_ci/* Repeated SOC check */ 3418c2ecf20Sopenharmony_cistatic bool da9150_fg_soc_changed(struct da9150_fg *fg) 3428c2ecf20Sopenharmony_ci{ 3438c2ecf20Sopenharmony_ci union power_supply_propval val; 3448c2ecf20Sopenharmony_ci 3458c2ecf20Sopenharmony_ci da9150_fg_capacity(fg, &val); 3468c2ecf20Sopenharmony_ci if (val.intval != fg->soc) { 3478c2ecf20Sopenharmony_ci fg->soc = val.intval; 3488c2ecf20Sopenharmony_ci return true; 3498c2ecf20Sopenharmony_ci } 3508c2ecf20Sopenharmony_ci 3518c2ecf20Sopenharmony_ci return false; 3528c2ecf20Sopenharmony_ci} 3538c2ecf20Sopenharmony_ci 3548c2ecf20Sopenharmony_cistatic void da9150_fg_work(struct work_struct *work) 3558c2ecf20Sopenharmony_ci{ 3568c2ecf20Sopenharmony_ci struct da9150_fg *fg = container_of(work, struct da9150_fg, work.work); 3578c2ecf20Sopenharmony_ci 3588c2ecf20Sopenharmony_ci /* Report if SOC has changed */ 3598c2ecf20Sopenharmony_ci if (da9150_fg_soc_changed(fg)) 3608c2ecf20Sopenharmony_ci power_supply_changed(fg->battery); 3618c2ecf20Sopenharmony_ci 3628c2ecf20Sopenharmony_ci schedule_delayed_work(&fg->work, msecs_to_jiffies(fg->interval)); 3638c2ecf20Sopenharmony_ci} 3648c2ecf20Sopenharmony_ci 3658c2ecf20Sopenharmony_ci/* SOC level event configuration */ 3668c2ecf20Sopenharmony_cistatic void da9150_fg_soc_event_config(struct da9150_fg *fg) 3678c2ecf20Sopenharmony_ci{ 3688c2ecf20Sopenharmony_ci int soc; 3698c2ecf20Sopenharmony_ci 3708c2ecf20Sopenharmony_ci soc = da9150_fg_read_attr_sync(fg, DA9150_QIF_SOC_PCT, 3718c2ecf20Sopenharmony_ci DA9150_QIF_SOC_PCT_SIZE); 3728c2ecf20Sopenharmony_ci 3738c2ecf20Sopenharmony_ci if (soc > fg->warn_soc) { 3748c2ecf20Sopenharmony_ci /* If SOC > warn level, set discharge warn level event */ 3758c2ecf20Sopenharmony_ci da9150_fg_write_attr_sync(fg, DA9150_QIF_DISCHARGE_LIMIT, 3768c2ecf20Sopenharmony_ci DA9150_QIF_DISCHARGE_LIMIT_SIZE, 3778c2ecf20Sopenharmony_ci fg->warn_soc + 1); 3788c2ecf20Sopenharmony_ci } else if ((soc <= fg->warn_soc) && (soc > fg->crit_soc)) { 3798c2ecf20Sopenharmony_ci /* 3808c2ecf20Sopenharmony_ci * If SOC <= warn level, set discharge crit level event, 3818c2ecf20Sopenharmony_ci * and set charge warn level event. 3828c2ecf20Sopenharmony_ci */ 3838c2ecf20Sopenharmony_ci da9150_fg_write_attr_sync(fg, DA9150_QIF_DISCHARGE_LIMIT, 3848c2ecf20Sopenharmony_ci DA9150_QIF_DISCHARGE_LIMIT_SIZE, 3858c2ecf20Sopenharmony_ci fg->crit_soc + 1); 3868c2ecf20Sopenharmony_ci 3878c2ecf20Sopenharmony_ci da9150_fg_write_attr_sync(fg, DA9150_QIF_CHARGE_LIMIT, 3888c2ecf20Sopenharmony_ci DA9150_QIF_CHARGE_LIMIT_SIZE, 3898c2ecf20Sopenharmony_ci fg->warn_soc); 3908c2ecf20Sopenharmony_ci } else if (soc <= fg->crit_soc) { 3918c2ecf20Sopenharmony_ci /* If SOC <= crit level, set charge crit level event */ 3928c2ecf20Sopenharmony_ci da9150_fg_write_attr_sync(fg, DA9150_QIF_CHARGE_LIMIT, 3938c2ecf20Sopenharmony_ci DA9150_QIF_CHARGE_LIMIT_SIZE, 3948c2ecf20Sopenharmony_ci fg->crit_soc); 3958c2ecf20Sopenharmony_ci } 3968c2ecf20Sopenharmony_ci} 3978c2ecf20Sopenharmony_ci 3988c2ecf20Sopenharmony_cistatic irqreturn_t da9150_fg_irq(int irq, void *data) 3998c2ecf20Sopenharmony_ci{ 4008c2ecf20Sopenharmony_ci struct da9150_fg *fg = data; 4018c2ecf20Sopenharmony_ci u32 e_fg_status; 4028c2ecf20Sopenharmony_ci 4038c2ecf20Sopenharmony_ci /* Read FG IRQ status info */ 4048c2ecf20Sopenharmony_ci e_fg_status = da9150_fg_read_attr(fg, DA9150_QIF_E_FG_STATUS, 4058c2ecf20Sopenharmony_ci DA9150_QIF_E_FG_STATUS_SIZE); 4068c2ecf20Sopenharmony_ci 4078c2ecf20Sopenharmony_ci /* Handle warning/critical threhold events */ 4088c2ecf20Sopenharmony_ci if (e_fg_status & DA9150_FG_IRQ_SOC_MASK) 4098c2ecf20Sopenharmony_ci da9150_fg_soc_event_config(fg); 4108c2ecf20Sopenharmony_ci 4118c2ecf20Sopenharmony_ci /* Clear any FG IRQs */ 4128c2ecf20Sopenharmony_ci da9150_fg_write_attr(fg, DA9150_QIF_E_FG_STATUS, 4138c2ecf20Sopenharmony_ci DA9150_QIF_E_FG_STATUS_SIZE, e_fg_status); 4148c2ecf20Sopenharmony_ci 4158c2ecf20Sopenharmony_ci return IRQ_HANDLED; 4168c2ecf20Sopenharmony_ci} 4178c2ecf20Sopenharmony_ci 4188c2ecf20Sopenharmony_cistatic struct da9150_fg_pdata *da9150_fg_dt_pdata(struct device *dev) 4198c2ecf20Sopenharmony_ci{ 4208c2ecf20Sopenharmony_ci struct device_node *fg_node = dev->of_node; 4218c2ecf20Sopenharmony_ci struct da9150_fg_pdata *pdata; 4228c2ecf20Sopenharmony_ci 4238c2ecf20Sopenharmony_ci pdata = devm_kzalloc(dev, sizeof(struct da9150_fg_pdata), GFP_KERNEL); 4248c2ecf20Sopenharmony_ci if (!pdata) 4258c2ecf20Sopenharmony_ci return NULL; 4268c2ecf20Sopenharmony_ci 4278c2ecf20Sopenharmony_ci of_property_read_u32(fg_node, "dlg,update-interval", 4288c2ecf20Sopenharmony_ci &pdata->update_interval); 4298c2ecf20Sopenharmony_ci of_property_read_u8(fg_node, "dlg,warn-soc-level", 4308c2ecf20Sopenharmony_ci &pdata->warn_soc_lvl); 4318c2ecf20Sopenharmony_ci of_property_read_u8(fg_node, "dlg,crit-soc-level", 4328c2ecf20Sopenharmony_ci &pdata->crit_soc_lvl); 4338c2ecf20Sopenharmony_ci 4348c2ecf20Sopenharmony_ci return pdata; 4358c2ecf20Sopenharmony_ci} 4368c2ecf20Sopenharmony_ci 4378c2ecf20Sopenharmony_cistatic const struct power_supply_desc fg_desc = { 4388c2ecf20Sopenharmony_ci .name = "da9150-fg", 4398c2ecf20Sopenharmony_ci .type = POWER_SUPPLY_TYPE_BATTERY, 4408c2ecf20Sopenharmony_ci .properties = da9150_fg_props, 4418c2ecf20Sopenharmony_ci .num_properties = ARRAY_SIZE(da9150_fg_props), 4428c2ecf20Sopenharmony_ci .get_property = da9150_fg_get_prop, 4438c2ecf20Sopenharmony_ci}; 4448c2ecf20Sopenharmony_ci 4458c2ecf20Sopenharmony_cistatic int da9150_fg_probe(struct platform_device *pdev) 4468c2ecf20Sopenharmony_ci{ 4478c2ecf20Sopenharmony_ci struct device *dev = &pdev->dev; 4488c2ecf20Sopenharmony_ci struct da9150 *da9150 = dev_get_drvdata(dev->parent); 4498c2ecf20Sopenharmony_ci struct da9150_fg_pdata *fg_pdata = dev_get_platdata(dev); 4508c2ecf20Sopenharmony_ci struct da9150_fg *fg; 4518c2ecf20Sopenharmony_ci int ver, irq, ret = 0; 4528c2ecf20Sopenharmony_ci 4538c2ecf20Sopenharmony_ci fg = devm_kzalloc(dev, sizeof(*fg), GFP_KERNEL); 4548c2ecf20Sopenharmony_ci if (fg == NULL) 4558c2ecf20Sopenharmony_ci return -ENOMEM; 4568c2ecf20Sopenharmony_ci 4578c2ecf20Sopenharmony_ci platform_set_drvdata(pdev, fg); 4588c2ecf20Sopenharmony_ci fg->da9150 = da9150; 4598c2ecf20Sopenharmony_ci fg->dev = dev; 4608c2ecf20Sopenharmony_ci 4618c2ecf20Sopenharmony_ci mutex_init(&fg->io_lock); 4628c2ecf20Sopenharmony_ci 4638c2ecf20Sopenharmony_ci /* Enable QIF */ 4648c2ecf20Sopenharmony_ci da9150_set_bits(da9150, DA9150_CORE2WIRE_CTRL_A, DA9150_FG_QIF_EN_MASK, 4658c2ecf20Sopenharmony_ci DA9150_FG_QIF_EN_MASK); 4668c2ecf20Sopenharmony_ci 4678c2ecf20Sopenharmony_ci fg->battery = devm_power_supply_register(dev, &fg_desc, NULL); 4688c2ecf20Sopenharmony_ci if (IS_ERR(fg->battery)) { 4698c2ecf20Sopenharmony_ci ret = PTR_ERR(fg->battery); 4708c2ecf20Sopenharmony_ci return ret; 4718c2ecf20Sopenharmony_ci } 4728c2ecf20Sopenharmony_ci 4738c2ecf20Sopenharmony_ci ver = da9150_fg_read_attr(fg, DA9150_QIF_FW_MAIN_VER, 4748c2ecf20Sopenharmony_ci DA9150_QIF_FW_MAIN_VER_SIZE); 4758c2ecf20Sopenharmony_ci dev_info(dev, "Version: 0x%x\n", ver); 4768c2ecf20Sopenharmony_ci 4778c2ecf20Sopenharmony_ci /* Handle DT data if provided */ 4788c2ecf20Sopenharmony_ci if (dev->of_node) { 4798c2ecf20Sopenharmony_ci fg_pdata = da9150_fg_dt_pdata(dev); 4808c2ecf20Sopenharmony_ci dev->platform_data = fg_pdata; 4818c2ecf20Sopenharmony_ci } 4828c2ecf20Sopenharmony_ci 4838c2ecf20Sopenharmony_ci /* Handle any pdata provided */ 4848c2ecf20Sopenharmony_ci if (fg_pdata) { 4858c2ecf20Sopenharmony_ci fg->interval = fg_pdata->update_interval; 4868c2ecf20Sopenharmony_ci 4878c2ecf20Sopenharmony_ci if (fg_pdata->warn_soc_lvl > 100) 4888c2ecf20Sopenharmony_ci dev_warn(dev, "Invalid SOC warning level provided, Ignoring"); 4898c2ecf20Sopenharmony_ci else 4908c2ecf20Sopenharmony_ci fg->warn_soc = fg_pdata->warn_soc_lvl; 4918c2ecf20Sopenharmony_ci 4928c2ecf20Sopenharmony_ci if ((fg_pdata->crit_soc_lvl > 100) || 4938c2ecf20Sopenharmony_ci (fg_pdata->crit_soc_lvl >= fg_pdata->warn_soc_lvl)) 4948c2ecf20Sopenharmony_ci dev_warn(dev, "Invalid SOC critical level provided, Ignoring"); 4958c2ecf20Sopenharmony_ci else 4968c2ecf20Sopenharmony_ci fg->crit_soc = fg_pdata->crit_soc_lvl; 4978c2ecf20Sopenharmony_ci 4988c2ecf20Sopenharmony_ci 4998c2ecf20Sopenharmony_ci } 5008c2ecf20Sopenharmony_ci 5018c2ecf20Sopenharmony_ci /* Configure initial SOC level events */ 5028c2ecf20Sopenharmony_ci da9150_fg_soc_event_config(fg); 5038c2ecf20Sopenharmony_ci 5048c2ecf20Sopenharmony_ci /* 5058c2ecf20Sopenharmony_ci * If an interval period has been provided then setup repeating 5068c2ecf20Sopenharmony_ci * work for reporting data updates. 5078c2ecf20Sopenharmony_ci */ 5088c2ecf20Sopenharmony_ci if (fg->interval) { 5098c2ecf20Sopenharmony_ci INIT_DELAYED_WORK(&fg->work, da9150_fg_work); 5108c2ecf20Sopenharmony_ci schedule_delayed_work(&fg->work, 5118c2ecf20Sopenharmony_ci msecs_to_jiffies(fg->interval)); 5128c2ecf20Sopenharmony_ci } 5138c2ecf20Sopenharmony_ci 5148c2ecf20Sopenharmony_ci /* Register IRQ */ 5158c2ecf20Sopenharmony_ci irq = platform_get_irq_byname(pdev, "FG"); 5168c2ecf20Sopenharmony_ci if (irq < 0) { 5178c2ecf20Sopenharmony_ci dev_err(dev, "Failed to get IRQ FG: %d\n", irq); 5188c2ecf20Sopenharmony_ci ret = irq; 5198c2ecf20Sopenharmony_ci goto irq_fail; 5208c2ecf20Sopenharmony_ci } 5218c2ecf20Sopenharmony_ci 5228c2ecf20Sopenharmony_ci ret = devm_request_threaded_irq(dev, irq, NULL, da9150_fg_irq, 5238c2ecf20Sopenharmony_ci IRQF_ONESHOT, "FG", fg); 5248c2ecf20Sopenharmony_ci if (ret) { 5258c2ecf20Sopenharmony_ci dev_err(dev, "Failed to request IRQ %d: %d\n", irq, ret); 5268c2ecf20Sopenharmony_ci goto irq_fail; 5278c2ecf20Sopenharmony_ci } 5288c2ecf20Sopenharmony_ci 5298c2ecf20Sopenharmony_ci return 0; 5308c2ecf20Sopenharmony_ci 5318c2ecf20Sopenharmony_ciirq_fail: 5328c2ecf20Sopenharmony_ci if (fg->interval) 5338c2ecf20Sopenharmony_ci cancel_delayed_work(&fg->work); 5348c2ecf20Sopenharmony_ci 5358c2ecf20Sopenharmony_ci return ret; 5368c2ecf20Sopenharmony_ci} 5378c2ecf20Sopenharmony_ci 5388c2ecf20Sopenharmony_cistatic int da9150_fg_remove(struct platform_device *pdev) 5398c2ecf20Sopenharmony_ci{ 5408c2ecf20Sopenharmony_ci struct da9150_fg *fg = platform_get_drvdata(pdev); 5418c2ecf20Sopenharmony_ci 5428c2ecf20Sopenharmony_ci if (fg->interval) 5438c2ecf20Sopenharmony_ci cancel_delayed_work(&fg->work); 5448c2ecf20Sopenharmony_ci 5458c2ecf20Sopenharmony_ci return 0; 5468c2ecf20Sopenharmony_ci} 5478c2ecf20Sopenharmony_ci 5488c2ecf20Sopenharmony_cistatic int da9150_fg_resume(struct platform_device *pdev) 5498c2ecf20Sopenharmony_ci{ 5508c2ecf20Sopenharmony_ci struct da9150_fg *fg = platform_get_drvdata(pdev); 5518c2ecf20Sopenharmony_ci 5528c2ecf20Sopenharmony_ci /* 5538c2ecf20Sopenharmony_ci * Trigger SOC check to happen now so as to indicate any value change 5548c2ecf20Sopenharmony_ci * since last check before suspend. 5558c2ecf20Sopenharmony_ci */ 5568c2ecf20Sopenharmony_ci if (fg->interval) 5578c2ecf20Sopenharmony_ci flush_delayed_work(&fg->work); 5588c2ecf20Sopenharmony_ci 5598c2ecf20Sopenharmony_ci return 0; 5608c2ecf20Sopenharmony_ci} 5618c2ecf20Sopenharmony_ci 5628c2ecf20Sopenharmony_cistatic struct platform_driver da9150_fg_driver = { 5638c2ecf20Sopenharmony_ci .driver = { 5648c2ecf20Sopenharmony_ci .name = "da9150-fuel-gauge", 5658c2ecf20Sopenharmony_ci }, 5668c2ecf20Sopenharmony_ci .probe = da9150_fg_probe, 5678c2ecf20Sopenharmony_ci .remove = da9150_fg_remove, 5688c2ecf20Sopenharmony_ci .resume = da9150_fg_resume, 5698c2ecf20Sopenharmony_ci}; 5708c2ecf20Sopenharmony_ci 5718c2ecf20Sopenharmony_cimodule_platform_driver(da9150_fg_driver); 5728c2ecf20Sopenharmony_ci 5738c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Fuel-Gauge Driver for DA9150"); 5748c2ecf20Sopenharmony_ciMODULE_AUTHOR("Adam Thomson <Adam.Thomson.Opensource@diasemi.com>"); 5758c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 576