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