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, &reg_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, &reg_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 = &regmap_rd_table,
5593d0407baSopenharmony_ci	.wr_table = &regmap_wr_table,
5603d0407baSopenharmony_ci	.volatile_table = &regmap_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