13d0407baSopenharmony_ci// SPDX-License-Identifier: GPL-2.0 23d0407baSopenharmony_ci/* 33d0407baSopenharmony_ci * Fuel gauge driver for CellWise 2017 43d0407baSopenharmony_ci * 53d0407baSopenharmony_ci * Copyright (C) 2012, RockChip 63d0407baSopenharmony_ci * 73d0407baSopenharmony_ci * Authors: Shunqing Chen <csq@rock-chips.com> 83d0407baSopenharmony_ci */ 93d0407baSopenharmony_ci 103d0407baSopenharmony_ci#include <linux/bits.h> 113d0407baSopenharmony_ci#include <linux/delay.h> 123d0407baSopenharmony_ci#include <linux/i2c.h> 133d0407baSopenharmony_ci#include <linux/gfp.h> 143d0407baSopenharmony_ci#include <linux/gpio/consumer.h> 153d0407baSopenharmony_ci#include <linux/kernel.h> 163d0407baSopenharmony_ci#include <linux/module.h> 173d0407baSopenharmony_ci#include <linux/power_supply.h> 183d0407baSopenharmony_ci#include <linux/property.h> 193d0407baSopenharmony_ci#include <linux/regmap.h> 203d0407baSopenharmony_ci#include <linux/time.h> 213d0407baSopenharmony_ci#include <linux/workqueue.h> 223d0407baSopenharmony_ci 233d0407baSopenharmony_ci#define CW2017_SIZE_BATINFO 80 243d0407baSopenharmony_ci 253d0407baSopenharmony_ci#define CW2017_REG_VERSION 0x00 263d0407baSopenharmony_ci#define CW2017_REG_VCELL_H 0x02 273d0407baSopenharmony_ci#define CW2017_REG_VCELL_L 0x03 283d0407baSopenharmony_ci#define CW2017_REG_SOC_INT 0x04 293d0407baSopenharmony_ci#define CW2017_REG_SOC_DECIMAL 0x05 303d0407baSopenharmony_ci#define CW2017_REG_TEMP 0x06 313d0407baSopenharmony_ci#define CW2017_REG_MODE_CONFIG 0x08 323d0407baSopenharmony_ci#define CW2017_REG_INT_CONFIG 0x0A 333d0407baSopenharmony_ci#define CW2017_REG_SOC_ALERT 0x0B 343d0407baSopenharmony_ci#define CW2017_REG_TEMP_MAX 0x0C 353d0407baSopenharmony_ci#define CW2017_REG_TEMP_MIN 0x0D 363d0407baSopenharmony_ci#define CW2017_REG_VOLT_ID_H 0x0E 373d0407baSopenharmony_ci#define CW2017_REG_VOLT_ID_L 0x0F 383d0407baSopenharmony_ci#define CW2017_REG_BATINFO 0x10 393d0407baSopenharmony_ci 403d0407baSopenharmony_ci#define CW2017_MODE_SLEEP 0x30 413d0407baSopenharmony_ci#define CW2017_MODE_NORMAL 0x00 423d0407baSopenharmony_ci#define CW2017_MODE_DEFAULT 0xF0 433d0407baSopenharmony_ci 443d0407baSopenharmony_ci#define CW2017_CONFIG_UPDATE_FLG 0x80 453d0407baSopenharmony_ci#define NO_START_VERSION 160 463d0407baSopenharmony_ci 473d0407baSopenharmony_ci#define TEMP_MAX_ALERT 0xFFFF 483d0407baSopenharmony_ci#define TEMP_MIN_ALERT 0xFFFF 493d0407baSopenharmony_ci#define TEMP_ALERT_DISABLE 0xFFFF 503d0407baSopenharmony_ci 513d0407baSopenharmony_ci#define INT_CONFIG_MIN_TEMP_MARK BIT(4) 523d0407baSopenharmony_ci#define INT_CONFIG_MAX_TEMP_MARK BIT(5) 533d0407baSopenharmony_ci#define INT_CONFIG_SOC_CHANGE_MARK BIT(6) 543d0407baSopenharmony_ci 553d0407baSopenharmony_ci#define DEF_DESIGN_CAPACITY 4000 563d0407baSopenharmony_ci 573d0407baSopenharmony_ci#define CW2017_MASK_ATHD GENMASK(7, 0) 583d0407baSopenharmony_ci 593d0407baSopenharmony_ci/* reset gauge of no valid state of charge could be polled for 40s */ 603d0407baSopenharmony_ci#define CW2017_BAT_SOC_ERROR_MS (40 * MSEC_PER_SEC) 613d0407baSopenharmony_ci/* reset gauge if state of charge stuck for half an hour during charging */ 623d0407baSopenharmony_ci#define CW2017_BAT_CHARGING_STUCK_MS (1800 * MSEC_PER_SEC) 633d0407baSopenharmony_ci 643d0407baSopenharmony_ci/* poll interval from CellWise GPL Android driver example */ 653d0407baSopenharmony_ci#define CW2017_DEFAULT_POLL_INTERVAL_MS 8000 663d0407baSopenharmony_ci 673d0407baSopenharmony_cistruct cw_battery { 683d0407baSopenharmony_ci struct device *dev; 693d0407baSopenharmony_ci struct workqueue_struct *battery_workqueue; 703d0407baSopenharmony_ci struct delayed_work battery_delay_work; 713d0407baSopenharmony_ci struct regmap *regmap; 723d0407baSopenharmony_ci struct power_supply *rk_bat; 733d0407baSopenharmony_ci struct power_supply_battery_info battery; 743d0407baSopenharmony_ci u8 *bat_profile; 753d0407baSopenharmony_ci 763d0407baSopenharmony_ci bool charger_attached; 773d0407baSopenharmony_ci bool battery_changed; 783d0407baSopenharmony_ci 793d0407baSopenharmony_ci int soc; 803d0407baSopenharmony_ci int voltage_mv; 813d0407baSopenharmony_ci int status; 823d0407baSopenharmony_ci int charge_count; 833d0407baSopenharmony_ci int design_capacity; 843d0407baSopenharmony_ci 853d0407baSopenharmony_ci u32 poll_interval_ms; 863d0407baSopenharmony_ci u32 alert_level; 873d0407baSopenharmony_ci int temp_max; 883d0407baSopenharmony_ci int temp_min; 893d0407baSopenharmony_ci int temp; 903d0407baSopenharmony_ci 913d0407baSopenharmony_ci bool dual_cell; 923d0407baSopenharmony_ci 933d0407baSopenharmony_ci unsigned int read_errors; 943d0407baSopenharmony_ci unsigned int charge_stuck_cnt; 953d0407baSopenharmony_ci}; 963d0407baSopenharmony_ci 973d0407baSopenharmony_cistatic int cw_read_word(struct cw_battery *cw_bat, u8 reg, u16 *val) 983d0407baSopenharmony_ci{ 993d0407baSopenharmony_ci __be16 value; 1003d0407baSopenharmony_ci int ret; 1013d0407baSopenharmony_ci 1023d0407baSopenharmony_ci ret = regmap_bulk_read(cw_bat->regmap, reg, &value, sizeof(value)); 1033d0407baSopenharmony_ci if (ret) 1043d0407baSopenharmony_ci return ret; 1053d0407baSopenharmony_ci 1063d0407baSopenharmony_ci *val = be16_to_cpu(value); 1073d0407baSopenharmony_ci return 0; 1083d0407baSopenharmony_ci} 1093d0407baSopenharmony_ci 1103d0407baSopenharmony_cistatic void cw2017_enable(struct cw_battery *cw_bat) 1113d0407baSopenharmony_ci{ 1123d0407baSopenharmony_ci unsigned char reg_val = CW2017_MODE_DEFAULT; 1133d0407baSopenharmony_ci 1143d0407baSopenharmony_ci regmap_write(cw_bat->regmap, CW2017_REG_MODE_CONFIG, reg_val); 1153d0407baSopenharmony_ci msleep(20); 1163d0407baSopenharmony_ci reg_val = CW2017_MODE_SLEEP; 1173d0407baSopenharmony_ci regmap_write(cw_bat->regmap, CW2017_REG_MODE_CONFIG, reg_val); 1183d0407baSopenharmony_ci msleep(20); 1193d0407baSopenharmony_ci reg_val = CW2017_MODE_NORMAL; 1203d0407baSopenharmony_ci regmap_write(cw_bat->regmap, CW2017_REG_MODE_CONFIG, reg_val); 1213d0407baSopenharmony_ci msleep(20); 1223d0407baSopenharmony_ci} 1233d0407baSopenharmony_ci 1243d0407baSopenharmony_cistatic int cw_update_profile(struct cw_battery *cw_bat) 1253d0407baSopenharmony_ci{ 1263d0407baSopenharmony_ci int ret; 1273d0407baSopenharmony_ci unsigned int reg_val = 0; 1283d0407baSopenharmony_ci unsigned char int_mask = 0; 1293d0407baSopenharmony_ci 1303d0407baSopenharmony_ci /* write new battery info */ 1313d0407baSopenharmony_ci ret = regmap_raw_write(cw_bat->regmap, CW2017_REG_BATINFO, 1323d0407baSopenharmony_ci cw_bat->bat_profile, 1333d0407baSopenharmony_ci CW2017_SIZE_BATINFO); 1343d0407baSopenharmony_ci if (ret) 1353d0407baSopenharmony_ci return ret; 1363d0407baSopenharmony_ci 1373d0407baSopenharmony_ci /* set config update flag */ 1383d0407baSopenharmony_ci reg_val |= CW2017_CONFIG_UPDATE_FLG; 1393d0407baSopenharmony_ci reg_val &= ~CW2017_MASK_ATHD; 1403d0407baSopenharmony_ci reg_val |= cw_bat->alert_level; 1413d0407baSopenharmony_ci regmap_write(cw_bat->regmap, CW2017_REG_SOC_ALERT, reg_val); 1423d0407baSopenharmony_ci 1433d0407baSopenharmony_ci if (cw_bat->alert_level) 1443d0407baSopenharmony_ci int_mask |= INT_CONFIG_SOC_CHANGE_MARK; 1453d0407baSopenharmony_ci 1463d0407baSopenharmony_ci cw_bat->temp_max = TEMP_MAX_ALERT; 1473d0407baSopenharmony_ci cw_bat->temp_min = TEMP_MIN_ALERT; 1483d0407baSopenharmony_ci if (cw_bat->temp_max != TEMP_ALERT_DISABLE) { 1493d0407baSopenharmony_ci int_mask |= INT_CONFIG_MAX_TEMP_MARK; 1503d0407baSopenharmony_ci reg_val = (cw_bat->temp_max + 40) * 2; 1513d0407baSopenharmony_ci regmap_write(cw_bat->regmap, CW2017_REG_TEMP_MAX, reg_val); 1523d0407baSopenharmony_ci } 1533d0407baSopenharmony_ci if (cw_bat->temp_min != TEMP_ALERT_DISABLE) { 1543d0407baSopenharmony_ci int_mask |= INT_CONFIG_MIN_TEMP_MARK; 1553d0407baSopenharmony_ci reg_val = (cw_bat->temp_min + 40) * 2; 1563d0407baSopenharmony_ci regmap_write(cw_bat->regmap, CW2017_REG_TEMP_MIN, reg_val); 1573d0407baSopenharmony_ci } 1583d0407baSopenharmony_ci regmap_write(cw_bat->regmap, CW2017_REG_INT_CONFIG, int_mask); 1593d0407baSopenharmony_ci 1603d0407baSopenharmony_ci /* wait for gauge to reset */ 1613d0407baSopenharmony_ci msleep(20); 1623d0407baSopenharmony_ci 1633d0407baSopenharmony_ci /* wait for gauge to become ready */ 1643d0407baSopenharmony_ci ret = regmap_read_poll_timeout(cw_bat->regmap, CW2017_REG_SOC_INT, 1653d0407baSopenharmony_ci reg_val, reg_val <= 100, 1663d0407baSopenharmony_ci 10 * USEC_PER_MSEC, 10 * USEC_PER_SEC); 1673d0407baSopenharmony_ci if (ret) 1683d0407baSopenharmony_ci dev_err(cw_bat->dev, 1693d0407baSopenharmony_ci "Gauge did not become ready after profile upload\n"); 1703d0407baSopenharmony_ci else 1713d0407baSopenharmony_ci dev_dbg(cw_bat->dev, "Battery profile updated\n"); 1723d0407baSopenharmony_ci 1733d0407baSopenharmony_ci cw2017_enable(cw_bat); 1743d0407baSopenharmony_ci dev_dbg(cw_bat->dev, "Battery profile configured\n"); 1753d0407baSopenharmony_ci 1763d0407baSopenharmony_ci return ret; 1773d0407baSopenharmony_ci} 1783d0407baSopenharmony_ci 1793d0407baSopenharmony_cistatic int cw_init(struct cw_battery *cw_bat) 1803d0407baSopenharmony_ci{ 1813d0407baSopenharmony_ci int ret; 1823d0407baSopenharmony_ci unsigned int reg_val; 1833d0407baSopenharmony_ci unsigned int config_flg; 1843d0407baSopenharmony_ci 1853d0407baSopenharmony_ci regmap_read(cw_bat->regmap, CW2017_REG_MODE_CONFIG, ®_val); 1863d0407baSopenharmony_ci regmap_read(cw_bat->regmap, CW2017_REG_SOC_ALERT, &config_flg); 1873d0407baSopenharmony_ci 1883d0407baSopenharmony_ci if (reg_val != CW2017_MODE_NORMAL || !(config_flg & CW2017_CONFIG_UPDATE_FLG)) { 1893d0407baSopenharmony_ci dev_dbg(cw_bat->dev, 1903d0407baSopenharmony_ci "Battery profile not present, uploading battery profile\n"); 1913d0407baSopenharmony_ci if (cw_bat->bat_profile) { 1923d0407baSopenharmony_ci ret = cw_update_profile(cw_bat); 1933d0407baSopenharmony_ci if (ret) { 1943d0407baSopenharmony_ci dev_err(cw_bat->dev, 1953d0407baSopenharmony_ci "Failed to upload battery profile\n"); 1963d0407baSopenharmony_ci return ret; 1973d0407baSopenharmony_ci } 1983d0407baSopenharmony_ci } else { 1993d0407baSopenharmony_ci dev_warn(cw_bat->dev, 2003d0407baSopenharmony_ci "No profile specified, continuing without profile\n"); 2013d0407baSopenharmony_ci } 2023d0407baSopenharmony_ci } else if (cw_bat->bat_profile) { 2033d0407baSopenharmony_ci u8 bat_info[CW2017_SIZE_BATINFO]; 2043d0407baSopenharmony_ci 2053d0407baSopenharmony_ci ret = regmap_raw_read(cw_bat->regmap, CW2017_REG_BATINFO, 2063d0407baSopenharmony_ci bat_info, CW2017_SIZE_BATINFO); 2073d0407baSopenharmony_ci if (ret) { 2083d0407baSopenharmony_ci dev_err(cw_bat->dev, 2093d0407baSopenharmony_ci "Failed to read stored battery profile\n"); 2103d0407baSopenharmony_ci return ret; 2113d0407baSopenharmony_ci } 2123d0407baSopenharmony_ci 2133d0407baSopenharmony_ci if (memcmp(bat_info, cw_bat->bat_profile, CW2017_SIZE_BATINFO)) { 2143d0407baSopenharmony_ci dev_warn(cw_bat->dev, "Replacing stored battery profile\n"); 2153d0407baSopenharmony_ci ret = cw_update_profile(cw_bat); 2163d0407baSopenharmony_ci if (ret) 2173d0407baSopenharmony_ci return ret; 2183d0407baSopenharmony_ci } 2193d0407baSopenharmony_ci } else { 2203d0407baSopenharmony_ci dev_warn(cw_bat->dev, 2213d0407baSopenharmony_ci "Can't check current battery profile, no profile provided\n"); 2223d0407baSopenharmony_ci } 2233d0407baSopenharmony_ci 2243d0407baSopenharmony_ci return 0; 2253d0407baSopenharmony_ci} 2263d0407baSopenharmony_ci 2273d0407baSopenharmony_ci#define HYSTERESIS(current, previous, up, down) \ 2283d0407baSopenharmony_ci (((current) < (previous) + (up)) && ((current) > (previous) - (down))) 2293d0407baSopenharmony_ci 2303d0407baSopenharmony_cistatic int cw_get_soc(struct cw_battery *cw_bat) 2313d0407baSopenharmony_ci{ 2323d0407baSopenharmony_ci unsigned int soc; 2333d0407baSopenharmony_ci 2343d0407baSopenharmony_ci regmap_read(cw_bat->regmap, CW2017_REG_SOC_INT, &soc); 2353d0407baSopenharmony_ci if (soc > 100) { 2363d0407baSopenharmony_ci int max_error_cycles = 2373d0407baSopenharmony_ci CW2017_BAT_SOC_ERROR_MS / cw_bat->poll_interval_ms; 2383d0407baSopenharmony_ci 2393d0407baSopenharmony_ci dev_err(cw_bat->dev, "Invalid SoC %d%%\n", soc); 2403d0407baSopenharmony_ci cw_bat->read_errors++; 2413d0407baSopenharmony_ci if (cw_bat->read_errors > max_error_cycles) { 2423d0407baSopenharmony_ci dev_warn(cw_bat->dev, 2433d0407baSopenharmony_ci "Too many invalid SoC reports, resetting gauge\n"); 2443d0407baSopenharmony_ci cw_bat->read_errors = 0; 2453d0407baSopenharmony_ci } 2463d0407baSopenharmony_ci return cw_bat->soc; 2473d0407baSopenharmony_ci } 2483d0407baSopenharmony_ci cw_bat->read_errors = 0; 2493d0407baSopenharmony_ci 2503d0407baSopenharmony_ci /* Reset gauge if stuck while charging */ 2513d0407baSopenharmony_ci if (cw_bat->status == POWER_SUPPLY_STATUS_CHARGING && soc == cw_bat->soc) { 2523d0407baSopenharmony_ci int max_stuck_cycles = 2533d0407baSopenharmony_ci CW2017_BAT_CHARGING_STUCK_MS / cw_bat->poll_interval_ms; 2543d0407baSopenharmony_ci 2553d0407baSopenharmony_ci cw_bat->charge_stuck_cnt++; 2563d0407baSopenharmony_ci if (cw_bat->charge_stuck_cnt > max_stuck_cycles) { 2573d0407baSopenharmony_ci dev_warn(cw_bat->dev, 2583d0407baSopenharmony_ci "SoC stuck @%u%%, resetting gauge\n", soc); 2593d0407baSopenharmony_ci cw_bat->charge_stuck_cnt = 0; 2603d0407baSopenharmony_ci } 2613d0407baSopenharmony_ci } else { 2623d0407baSopenharmony_ci cw_bat->charge_stuck_cnt = 0; 2633d0407baSopenharmony_ci } 2643d0407baSopenharmony_ci 2653d0407baSopenharmony_ci /* Ignore voltage dips during charge */ 2663d0407baSopenharmony_ci if (cw_bat->charger_attached && HYSTERESIS(soc, cw_bat->soc, 0, 3)) 2673d0407baSopenharmony_ci soc = cw_bat->soc; 2683d0407baSopenharmony_ci 2693d0407baSopenharmony_ci /* Ignore voltage spikes during discharge */ 2703d0407baSopenharmony_ci if (!cw_bat->charger_attached && HYSTERESIS(soc, cw_bat->soc, 3, 0)) 2713d0407baSopenharmony_ci soc = cw_bat->soc; 2723d0407baSopenharmony_ci 2733d0407baSopenharmony_ci return soc; 2743d0407baSopenharmony_ci} 2753d0407baSopenharmony_ci 2763d0407baSopenharmony_cistatic int cw_get_voltage(struct cw_battery *cw_bat) 2773d0407baSopenharmony_ci{ 2783d0407baSopenharmony_ci int voltage_mv; 2793d0407baSopenharmony_ci u16 reg_val = 0; 2803d0407baSopenharmony_ci 2813d0407baSopenharmony_ci cw_read_word(cw_bat, CW2017_REG_VCELL_H, ®_val); 2823d0407baSopenharmony_ci reg_val &= 0x3fff; 2833d0407baSopenharmony_ci voltage_mv = reg_val * 5 / 16; 2843d0407baSopenharmony_ci if (cw_bat->dual_cell) 2853d0407baSopenharmony_ci voltage_mv *= 2; 2863d0407baSopenharmony_ci 2873d0407baSopenharmony_ci dev_dbg(cw_bat->dev, "Read voltage: %d mV, raw=0x%04x\n", 2883d0407baSopenharmony_ci voltage_mv, reg_val); 2893d0407baSopenharmony_ci 2903d0407baSopenharmony_ci return voltage_mv; 2913d0407baSopenharmony_ci} 2923d0407baSopenharmony_ci 2933d0407baSopenharmony_cistatic void cw_update_charge_status(struct cw_battery *cw_bat) 2943d0407baSopenharmony_ci{ 2953d0407baSopenharmony_ci int ret; 2963d0407baSopenharmony_ci 2973d0407baSopenharmony_ci ret = power_supply_am_i_supplied(cw_bat->rk_bat); 2983d0407baSopenharmony_ci if (ret < 0) { 2993d0407baSopenharmony_ci dev_warn(cw_bat->dev, "Failed to get supply state: %d\n", ret); 3003d0407baSopenharmony_ci } else { 3013d0407baSopenharmony_ci bool charger_attached; 3023d0407baSopenharmony_ci 3033d0407baSopenharmony_ci charger_attached = !!ret; 3043d0407baSopenharmony_ci if (cw_bat->charger_attached != charger_attached) { 3053d0407baSopenharmony_ci cw_bat->battery_changed = true; 3063d0407baSopenharmony_ci if (charger_attached) 3073d0407baSopenharmony_ci cw_bat->charge_count++; 3083d0407baSopenharmony_ci } 3093d0407baSopenharmony_ci cw_bat->charger_attached = charger_attached; 3103d0407baSopenharmony_ci } 3113d0407baSopenharmony_ci} 3123d0407baSopenharmony_ci 3133d0407baSopenharmony_cistatic void cw_update_soc(struct cw_battery *cw_bat) 3143d0407baSopenharmony_ci{ 3153d0407baSopenharmony_ci int soc; 3163d0407baSopenharmony_ci 3173d0407baSopenharmony_ci soc = cw_get_soc(cw_bat); 3183d0407baSopenharmony_ci if (soc < 0) 3193d0407baSopenharmony_ci dev_err(cw_bat->dev, "Failed to get SoC from gauge: %d\n", soc); 3203d0407baSopenharmony_ci else if (cw_bat->soc != soc) { 3213d0407baSopenharmony_ci cw_bat->soc = soc; 3223d0407baSopenharmony_ci cw_bat->battery_changed = true; 3233d0407baSopenharmony_ci } 3243d0407baSopenharmony_ci} 3253d0407baSopenharmony_ci 3263d0407baSopenharmony_cistatic void cw_update_voltage(struct cw_battery *cw_bat) 3273d0407baSopenharmony_ci{ 3283d0407baSopenharmony_ci int voltage_mv; 3293d0407baSopenharmony_ci 3303d0407baSopenharmony_ci voltage_mv = cw_get_voltage(cw_bat); 3313d0407baSopenharmony_ci if (voltage_mv < 0) 3323d0407baSopenharmony_ci dev_err(cw_bat->dev, "Failed to get voltage from gauge: %d\n", 3333d0407baSopenharmony_ci voltage_mv); 3343d0407baSopenharmony_ci else 3353d0407baSopenharmony_ci cw_bat->voltage_mv = voltage_mv; 3363d0407baSopenharmony_ci} 3373d0407baSopenharmony_ci 3383d0407baSopenharmony_cistatic int cw_update_temp(struct cw_battery *cw_bat) 3393d0407baSopenharmony_ci{ 3403d0407baSopenharmony_ci unsigned int val = 0; 3413d0407baSopenharmony_ci 3423d0407baSopenharmony_ci regmap_read(cw_bat->regmap, CW2017_REG_TEMP, &val); 3433d0407baSopenharmony_ci cw_bat->temp = val * 10 / 2 - 400; 3443d0407baSopenharmony_ci 3453d0407baSopenharmony_ci return cw_bat->temp; 3463d0407baSopenharmony_ci} 3473d0407baSopenharmony_ci 3483d0407baSopenharmony_cistatic void cw_update_status(struct cw_battery *cw_bat) 3493d0407baSopenharmony_ci{ 3503d0407baSopenharmony_ci int status = POWER_SUPPLY_STATUS_DISCHARGING; 3513d0407baSopenharmony_ci 3523d0407baSopenharmony_ci if (cw_bat->charger_attached) { 3533d0407baSopenharmony_ci if (cw_bat->soc >= 100) 3543d0407baSopenharmony_ci status = POWER_SUPPLY_STATUS_FULL; 3553d0407baSopenharmony_ci else 3563d0407baSopenharmony_ci status = POWER_SUPPLY_STATUS_CHARGING; 3573d0407baSopenharmony_ci } 3583d0407baSopenharmony_ci 3593d0407baSopenharmony_ci if (cw_bat->status != status) 3603d0407baSopenharmony_ci cw_bat->battery_changed = true; 3613d0407baSopenharmony_ci cw_bat->status = status; 3623d0407baSopenharmony_ci} 3633d0407baSopenharmony_ci 3643d0407baSopenharmony_cistatic void cw_bat_work(struct work_struct *work) 3653d0407baSopenharmony_ci{ 3663d0407baSopenharmony_ci struct delayed_work *delay_work; 3673d0407baSopenharmony_ci struct cw_battery *cw_bat; 3683d0407baSopenharmony_ci 3693d0407baSopenharmony_ci delay_work = to_delayed_work(work); 3703d0407baSopenharmony_ci cw_bat = container_of(delay_work, struct cw_battery, battery_delay_work); 3713d0407baSopenharmony_ci 3723d0407baSopenharmony_ci cw_update_soc(cw_bat); 3733d0407baSopenharmony_ci cw_update_voltage(cw_bat); 3743d0407baSopenharmony_ci cw_update_charge_status(cw_bat); 3753d0407baSopenharmony_ci cw_update_temp(cw_bat); 3763d0407baSopenharmony_ci cw_update_status(cw_bat); 3773d0407baSopenharmony_ci 3783d0407baSopenharmony_ci dev_dbg(cw_bat->dev, "charger_attached = %d\n", cw_bat->charger_attached); 3793d0407baSopenharmony_ci dev_dbg(cw_bat->dev, "status = %d\n", cw_bat->status); 3803d0407baSopenharmony_ci dev_dbg(cw_bat->dev, "soc = %d%%\n", cw_bat->soc); 3813d0407baSopenharmony_ci dev_dbg(cw_bat->dev, "voltage = %dmV\n", cw_bat->voltage_mv); 3823d0407baSopenharmony_ci 3833d0407baSopenharmony_ci if (cw_bat->battery_changed) 3843d0407baSopenharmony_ci power_supply_changed(cw_bat->rk_bat); 3853d0407baSopenharmony_ci cw_bat->battery_changed = false; 3863d0407baSopenharmony_ci 3873d0407baSopenharmony_ci queue_delayed_work(cw_bat->battery_workqueue, 3883d0407baSopenharmony_ci &cw_bat->battery_delay_work, 3893d0407baSopenharmony_ci msecs_to_jiffies(cw_bat->poll_interval_ms)); 3903d0407baSopenharmony_ci} 3913d0407baSopenharmony_ci 3923d0407baSopenharmony_cistatic int cw_battery_get_property(struct power_supply *psy, 3933d0407baSopenharmony_ci enum power_supply_property psp, 3943d0407baSopenharmony_ci union power_supply_propval *val) 3953d0407baSopenharmony_ci{ 3963d0407baSopenharmony_ci struct cw_battery *cw_bat; 3973d0407baSopenharmony_ci 3983d0407baSopenharmony_ci cw_bat = power_supply_get_drvdata(psy); 3993d0407baSopenharmony_ci switch (psp) { 4003d0407baSopenharmony_ci case POWER_SUPPLY_PROP_CAPACITY: 4013d0407baSopenharmony_ci val->intval = cw_bat->soc; 4023d0407baSopenharmony_ci break; 4033d0407baSopenharmony_ci 4043d0407baSopenharmony_ci case POWER_SUPPLY_PROP_STATUS: 4053d0407baSopenharmony_ci val->intval = cw_bat->status; 4063d0407baSopenharmony_ci break; 4073d0407baSopenharmony_ci 4083d0407baSopenharmony_ci case POWER_SUPPLY_PROP_PRESENT: 4093d0407baSopenharmony_ci val->intval = !!cw_bat->voltage_mv; 4103d0407baSopenharmony_ci break; 4113d0407baSopenharmony_ci 4123d0407baSopenharmony_ci case POWER_SUPPLY_PROP_VOLTAGE_NOW: 4133d0407baSopenharmony_ci val->intval = cw_bat->voltage_mv * 1000; 4143d0407baSopenharmony_ci break; 4153d0407baSopenharmony_ci 4163d0407baSopenharmony_ci case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW: 4173d0407baSopenharmony_ci val->intval = 0; 4183d0407baSopenharmony_ci break; 4193d0407baSopenharmony_ci 4203d0407baSopenharmony_ci case POWER_SUPPLY_PROP_TECHNOLOGY: 4213d0407baSopenharmony_ci val->intval = POWER_SUPPLY_TECHNOLOGY_LION; 4223d0407baSopenharmony_ci break; 4233d0407baSopenharmony_ci 4243d0407baSopenharmony_ci case POWER_SUPPLY_PROP_CHARGE_COUNTER: 4253d0407baSopenharmony_ci val->intval = cw_bat->charge_count; 4263d0407baSopenharmony_ci break; 4273d0407baSopenharmony_ci 4283d0407baSopenharmony_ci case POWER_SUPPLY_PROP_TEMP: 4293d0407baSopenharmony_ci val->intval = cw_bat->temp; 4303d0407baSopenharmony_ci break; 4313d0407baSopenharmony_ci 4323d0407baSopenharmony_ci case POWER_SUPPLY_PROP_CHARGE_FULL: 4333d0407baSopenharmony_ci case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: 4343d0407baSopenharmony_ci if (cw_bat->battery.charge_full_design_uah > 0) 4353d0407baSopenharmony_ci val->intval = cw_bat->battery.charge_full_design_uah; 4363d0407baSopenharmony_ci else 4373d0407baSopenharmony_ci val->intval = cw_bat->design_capacity * 1000; 4383d0407baSopenharmony_ci break; 4393d0407baSopenharmony_ci 4403d0407baSopenharmony_ci case POWER_SUPPLY_PROP_CURRENT_NOW: 4413d0407baSopenharmony_ci val->intval = 0; 4423d0407baSopenharmony_ci break; 4433d0407baSopenharmony_ci 4443d0407baSopenharmony_ci default: 4453d0407baSopenharmony_ci break; 4463d0407baSopenharmony_ci } 4473d0407baSopenharmony_ci return 0; 4483d0407baSopenharmony_ci} 4493d0407baSopenharmony_ci 4503d0407baSopenharmony_cistatic enum power_supply_property cw_battery_properties[] = { 4513d0407baSopenharmony_ci POWER_SUPPLY_PROP_CAPACITY, 4523d0407baSopenharmony_ci POWER_SUPPLY_PROP_STATUS, 4533d0407baSopenharmony_ci POWER_SUPPLY_PROP_PRESENT, 4543d0407baSopenharmony_ci POWER_SUPPLY_PROP_VOLTAGE_NOW, 4553d0407baSopenharmony_ci POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW, 4563d0407baSopenharmony_ci POWER_SUPPLY_PROP_TECHNOLOGY, 4573d0407baSopenharmony_ci POWER_SUPPLY_PROP_CHARGE_COUNTER, 4583d0407baSopenharmony_ci POWER_SUPPLY_PROP_TEMP, 4593d0407baSopenharmony_ci POWER_SUPPLY_PROP_CHARGE_FULL, 4603d0407baSopenharmony_ci POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, 4613d0407baSopenharmony_ci POWER_SUPPLY_PROP_CURRENT_NOW, 4623d0407baSopenharmony_ci}; 4633d0407baSopenharmony_ci 4643d0407baSopenharmony_cistatic const struct power_supply_desc cw2017_bat_desc = { 4653d0407baSopenharmony_ci .name = "cw2017-battery", 4663d0407baSopenharmony_ci .type = POWER_SUPPLY_TYPE_BATTERY, 4673d0407baSopenharmony_ci .properties = cw_battery_properties, 4683d0407baSopenharmony_ci .num_properties = ARRAY_SIZE(cw_battery_properties), 4693d0407baSopenharmony_ci .get_property = cw_battery_get_property, 4703d0407baSopenharmony_ci}; 4713d0407baSopenharmony_ci 4723d0407baSopenharmony_cistatic int cw2017_parse_properties(struct cw_battery *cw_bat) 4733d0407baSopenharmony_ci{ 4743d0407baSopenharmony_ci struct device *dev = cw_bat->dev; 4753d0407baSopenharmony_ci int length; 4763d0407baSopenharmony_ci int ret; 4773d0407baSopenharmony_ci 4783d0407baSopenharmony_ci length = device_property_count_u8(dev, "cellwise,battery-profile"); 4793d0407baSopenharmony_ci if (length < 0) { 4803d0407baSopenharmony_ci dev_warn(cw_bat->dev, 4813d0407baSopenharmony_ci "No battery-profile found, using current flash contents\n"); 4823d0407baSopenharmony_ci } else if (length != CW2017_SIZE_BATINFO) { 4833d0407baSopenharmony_ci dev_err(cw_bat->dev, "battery-profile must be %d bytes\n", 4843d0407baSopenharmony_ci CW2017_SIZE_BATINFO); 4853d0407baSopenharmony_ci return -EINVAL; 4863d0407baSopenharmony_ci } 4873d0407baSopenharmony_ci 4883d0407baSopenharmony_ci cw_bat->bat_profile = devm_kzalloc(dev, length, GFP_KERNEL); 4893d0407baSopenharmony_ci if (!cw_bat->bat_profile) 4903d0407baSopenharmony_ci return -ENOMEM; 4913d0407baSopenharmony_ci 4923d0407baSopenharmony_ci ret = device_property_read_u8_array(dev, 4933d0407baSopenharmony_ci "cellwise,battery-profile", 4943d0407baSopenharmony_ci cw_bat->bat_profile, 4953d0407baSopenharmony_ci length); 4963d0407baSopenharmony_ci if (ret) 4973d0407baSopenharmony_ci return ret; 4983d0407baSopenharmony_ci 4993d0407baSopenharmony_ci ret = device_property_read_u32(dev, "cellwise,design-capacity-amh", 5003d0407baSopenharmony_ci &cw_bat->design_capacity); 5013d0407baSopenharmony_ci if (ret) { 5023d0407baSopenharmony_ci dev_info(cw_bat->dev, "Missing design capacity\n"); 5033d0407baSopenharmony_ci cw_bat->design_capacity = DEF_DESIGN_CAPACITY; 5043d0407baSopenharmony_ci } 5053d0407baSopenharmony_ci 5063d0407baSopenharmony_ci device_property_read_u32(dev, "cellwise,alert-level", 5073d0407baSopenharmony_ci &cw_bat->alert_level); 5083d0407baSopenharmony_ci 5093d0407baSopenharmony_ci cw_bat->dual_cell = device_property_read_bool(dev, "cellwise,dual-cell"); 5103d0407baSopenharmony_ci 5113d0407baSopenharmony_ci ret = device_property_read_u32(dev, "cellwise,monitor-interval-ms", 5123d0407baSopenharmony_ci &cw_bat->poll_interval_ms); 5133d0407baSopenharmony_ci if (ret) { 5143d0407baSopenharmony_ci dev_dbg(cw_bat->dev, "Using default poll interval\n"); 5153d0407baSopenharmony_ci cw_bat->poll_interval_ms = CW2017_DEFAULT_POLL_INTERVAL_MS; 5163d0407baSopenharmony_ci } 5173d0407baSopenharmony_ci 5183d0407baSopenharmony_ci return 0; 5193d0407baSopenharmony_ci} 5203d0407baSopenharmony_ci 5213d0407baSopenharmony_cistatic const struct regmap_range regmap_ranges_rd_yes[] = { 5223d0407baSopenharmony_ci regmap_reg_range(CW2017_REG_VERSION, CW2017_REG_VERSION), 5233d0407baSopenharmony_ci regmap_reg_range(CW2017_REG_VCELL_H, CW2017_REG_TEMP), 5243d0407baSopenharmony_ci regmap_reg_range(CW2017_REG_MODE_CONFIG, CW2017_REG_MODE_CONFIG), 5253d0407baSopenharmony_ci regmap_reg_range(CW2017_REG_INT_CONFIG, 5263d0407baSopenharmony_ci CW2017_REG_BATINFO + CW2017_SIZE_BATINFO - 1), 5273d0407baSopenharmony_ci}; 5283d0407baSopenharmony_ci 5293d0407baSopenharmony_cistatic const struct regmap_access_table regmap_rd_table = { 5303d0407baSopenharmony_ci .yes_ranges = regmap_ranges_rd_yes, 5313d0407baSopenharmony_ci .n_yes_ranges = ARRAY_SIZE(regmap_ranges_rd_yes), 5323d0407baSopenharmony_ci}; 5333d0407baSopenharmony_ci 5343d0407baSopenharmony_cistatic const struct regmap_range regmap_ranges_wr_yes[] = { 5353d0407baSopenharmony_ci regmap_reg_range(CW2017_REG_MODE_CONFIG, CW2017_REG_MODE_CONFIG), 5363d0407baSopenharmony_ci regmap_reg_range(CW2017_REG_INT_CONFIG, CW2017_REG_TEMP_MIN), 5373d0407baSopenharmony_ci regmap_reg_range(CW2017_REG_BATINFO, 5383d0407baSopenharmony_ci CW2017_REG_BATINFO + CW2017_SIZE_BATINFO - 1), 5393d0407baSopenharmony_ci}; 5403d0407baSopenharmony_ci 5413d0407baSopenharmony_cistatic const struct regmap_access_table regmap_wr_table = { 5423d0407baSopenharmony_ci .yes_ranges = regmap_ranges_wr_yes, 5433d0407baSopenharmony_ci .n_yes_ranges = ARRAY_SIZE(regmap_ranges_wr_yes), 5443d0407baSopenharmony_ci}; 5453d0407baSopenharmony_ci 5463d0407baSopenharmony_cistatic const struct regmap_range regmap_ranges_vol_yes[] = { 5473d0407baSopenharmony_ci regmap_reg_range(CW2017_REG_VCELL_H, CW2017_REG_TEMP), 5483d0407baSopenharmony_ci}; 5493d0407baSopenharmony_ci 5503d0407baSopenharmony_cistatic const struct regmap_access_table regmap_vol_table = { 5513d0407baSopenharmony_ci .yes_ranges = regmap_ranges_vol_yes, 5523d0407baSopenharmony_ci .n_yes_ranges = ARRAY_SIZE(regmap_ranges_vol_yes), 5533d0407baSopenharmony_ci}; 5543d0407baSopenharmony_ci 5553d0407baSopenharmony_cistatic const struct regmap_config cw2017_regmap_config = { 5563d0407baSopenharmony_ci .reg_bits = 8, 5573d0407baSopenharmony_ci .val_bits = 8, 5583d0407baSopenharmony_ci .rd_table = ®map_rd_table, 5593d0407baSopenharmony_ci .wr_table = ®map_wr_table, 5603d0407baSopenharmony_ci .volatile_table = ®map_vol_table, 5613d0407baSopenharmony_ci .max_register = CW2017_REG_BATINFO + CW2017_SIZE_BATINFO - 1, 5623d0407baSopenharmony_ci}; 5633d0407baSopenharmony_ci 5643d0407baSopenharmony_cistatic int cw_bat_probe(struct i2c_client *client) 5653d0407baSopenharmony_ci{ 5663d0407baSopenharmony_ci int ret; 5673d0407baSopenharmony_ci struct cw_battery *cw_bat; 5683d0407baSopenharmony_ci struct power_supply_config psy_cfg = { 0 }; 5693d0407baSopenharmony_ci 5703d0407baSopenharmony_ci cw_bat = devm_kzalloc(&client->dev, sizeof(*cw_bat), GFP_KERNEL); 5713d0407baSopenharmony_ci if (!cw_bat) 5723d0407baSopenharmony_ci return -ENOMEM; 5733d0407baSopenharmony_ci 5743d0407baSopenharmony_ci i2c_set_clientdata(client, cw_bat); 5753d0407baSopenharmony_ci cw_bat->dev = &client->dev; 5763d0407baSopenharmony_ci cw_bat->soc = 1; 5773d0407baSopenharmony_ci 5783d0407baSopenharmony_ci ret = cw2017_parse_properties(cw_bat); 5793d0407baSopenharmony_ci if (ret) { 5803d0407baSopenharmony_ci dev_err(cw_bat->dev, "Failed to parse cw2017 properties\n"); 5813d0407baSopenharmony_ci return ret; 5823d0407baSopenharmony_ci } 5833d0407baSopenharmony_ci 5843d0407baSopenharmony_ci cw_bat->regmap = devm_regmap_init_i2c(client, &cw2017_regmap_config); 5853d0407baSopenharmony_ci if (IS_ERR(cw_bat->regmap)) { 5863d0407baSopenharmony_ci dev_err(cw_bat->dev, "Failed to allocate regmap: %ld\n", 5873d0407baSopenharmony_ci PTR_ERR(cw_bat->regmap)); 5883d0407baSopenharmony_ci return PTR_ERR(cw_bat->regmap); 5893d0407baSopenharmony_ci } 5903d0407baSopenharmony_ci 5913d0407baSopenharmony_ci ret = cw_init(cw_bat); 5923d0407baSopenharmony_ci if (ret) { 5933d0407baSopenharmony_ci dev_err(cw_bat->dev, "Init failed: %d\n", ret); 5943d0407baSopenharmony_ci return ret; 5953d0407baSopenharmony_ci } 5963d0407baSopenharmony_ci 5973d0407baSopenharmony_ci psy_cfg.drv_data = cw_bat; 5983d0407baSopenharmony_ci psy_cfg.fwnode = dev_fwnode(cw_bat->dev); 5993d0407baSopenharmony_ci 6003d0407baSopenharmony_ci cw_bat->rk_bat = devm_power_supply_register(&client->dev, 6013d0407baSopenharmony_ci &cw2017_bat_desc, 6023d0407baSopenharmony_ci &psy_cfg); 6033d0407baSopenharmony_ci if (IS_ERR(cw_bat->rk_bat)) { 6043d0407baSopenharmony_ci dev_err(cw_bat->dev, "Failed to register power supply\n"); 6053d0407baSopenharmony_ci return PTR_ERR(cw_bat->rk_bat); 6063d0407baSopenharmony_ci } 6073d0407baSopenharmony_ci 6083d0407baSopenharmony_ci ret = power_supply_get_battery_info(cw_bat->rk_bat, &cw_bat->battery); 6093d0407baSopenharmony_ci if (ret) { 6103d0407baSopenharmony_ci dev_warn(cw_bat->dev, 6113d0407baSopenharmony_ci "No monitored battery, some properties will be missing\n"); 6123d0407baSopenharmony_ci } 6133d0407baSopenharmony_ci 6143d0407baSopenharmony_ci cw_bat->battery_workqueue = create_singlethread_workqueue("rk_battery"); 6153d0407baSopenharmony_ci INIT_DELAYED_WORK(&cw_bat->battery_delay_work, cw_bat_work); 6163d0407baSopenharmony_ci queue_delayed_work(cw_bat->battery_workqueue, 6173d0407baSopenharmony_ci &cw_bat->battery_delay_work, msecs_to_jiffies(10)); 6183d0407baSopenharmony_ci 6193d0407baSopenharmony_ci return 0; 6203d0407baSopenharmony_ci} 6213d0407baSopenharmony_ci 6223d0407baSopenharmony_cistatic int __maybe_unused cw_bat_suspend(struct device *dev) 6233d0407baSopenharmony_ci{ 6243d0407baSopenharmony_ci struct i2c_client *client = to_i2c_client(dev); 6253d0407baSopenharmony_ci struct cw_battery *cw_bat = i2c_get_clientdata(client); 6263d0407baSopenharmony_ci 6273d0407baSopenharmony_ci cancel_delayed_work_sync(&cw_bat->battery_delay_work); 6283d0407baSopenharmony_ci return 0; 6293d0407baSopenharmony_ci} 6303d0407baSopenharmony_ci 6313d0407baSopenharmony_cistatic int __maybe_unused cw_bat_resume(struct device *dev) 6323d0407baSopenharmony_ci{ 6333d0407baSopenharmony_ci struct i2c_client *client = to_i2c_client(dev); 6343d0407baSopenharmony_ci struct cw_battery *cw_bat = i2c_get_clientdata(client); 6353d0407baSopenharmony_ci 6363d0407baSopenharmony_ci queue_delayed_work(cw_bat->battery_workqueue, 6373d0407baSopenharmony_ci &cw_bat->battery_delay_work, 0); 6383d0407baSopenharmony_ci return 0; 6393d0407baSopenharmony_ci} 6403d0407baSopenharmony_ci 6413d0407baSopenharmony_cistatic SIMPLE_DEV_PM_OPS(cw_bat_pm_ops, cw_bat_suspend, cw_bat_resume); 6423d0407baSopenharmony_ci 6433d0407baSopenharmony_cistatic int cw_bat_remove(struct i2c_client *client) 6443d0407baSopenharmony_ci{ 6453d0407baSopenharmony_ci struct cw_battery *cw_bat = i2c_get_clientdata(client); 6463d0407baSopenharmony_ci 6473d0407baSopenharmony_ci cancel_delayed_work_sync(&cw_bat->battery_delay_work); 6483d0407baSopenharmony_ci power_supply_put_battery_info(cw_bat->rk_bat, &cw_bat->battery); 6493d0407baSopenharmony_ci return 0; 6503d0407baSopenharmony_ci} 6513d0407baSopenharmony_ci 6523d0407baSopenharmony_cistatic const struct i2c_device_id cw_bat_id_table[] = { 6533d0407baSopenharmony_ci { "cw2017", 0 }, 6543d0407baSopenharmony_ci { } 6553d0407baSopenharmony_ci}; 6563d0407baSopenharmony_ci 6573d0407baSopenharmony_cistatic const struct of_device_id cw2017_of_match[] = { 6583d0407baSopenharmony_ci { .compatible = "cellwise,cw2017" }, 6593d0407baSopenharmony_ci { } 6603d0407baSopenharmony_ci}; 6613d0407baSopenharmony_ciMODULE_DEVICE_TABLE(of, cw2017_of_match); 6623d0407baSopenharmony_ci 6633d0407baSopenharmony_cistatic struct i2c_driver cw_bat_driver = { 6643d0407baSopenharmony_ci .driver = { 6653d0407baSopenharmony_ci .name = "cw2017", 6663d0407baSopenharmony_ci .of_match_table = cw2017_of_match, 6673d0407baSopenharmony_ci .pm = &cw_bat_pm_ops, 6683d0407baSopenharmony_ci }, 6693d0407baSopenharmony_ci .probe_new = cw_bat_probe, 6703d0407baSopenharmony_ci .remove = cw_bat_remove, 6713d0407baSopenharmony_ci .id_table = cw_bat_id_table, 6723d0407baSopenharmony_ci}; 6733d0407baSopenharmony_ci 6743d0407baSopenharmony_cimodule_i2c_driver(cw_bat_driver); 6753d0407baSopenharmony_ci 6763d0407baSopenharmony_ciMODULE_AUTHOR("Shunqing Chen<csq@rock-chips.com>"); 6773d0407baSopenharmony_ciMODULE_DESCRIPTION("cw2017 battery driver"); 6783d0407baSopenharmony_ciMODULE_LICENSE("GPL"); 679