cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

ug3105_battery.c (13566B)


      1// SPDX-License-Identifier: GPL-2.0+
      2/*
      3 * Battery monitor driver for the uPI uG3105 battery monitor
      4 *
      5 * Note the uG3105 is not a full-featured autonomous fuel-gauge. Instead it is
      6 * expected to be use in combination with some always on microcontroller reading
      7 * its coulomb-counter before it can wrap (must be read every 400 seconds!).
      8 *
      9 * Since Linux does not monitor coulomb-counter changes while the device
     10 * is off or suspended, the coulomb counter is not used atm.
     11 *
     12 * Possible improvements:
     13 * 1. Activate commented out total_coulomb_count code
     14 * 2. Reset total_coulomb_count val to 0 when the battery is as good as empty
     15 *    and remember that we did this (and clear the flag for this on susp/resume)
     16 * 3. When the battery is full check if the flag that we set total_coulomb_count
     17 *    to when the battery was empty is set. If so we now know the capacity,
     18 *    not the design, but actual capacity, of the battery
     19 * 4. Add some mechanism (needs userspace help, or maybe use efivar?) to remember
     20 *    the actual capacity of the battery over reboots
     21 * 5. When we know the actual capacity at probe time, add energy_now and
     22 *    energy_full attributes. Guess boot + resume energy_now value based on ocv
     23 *    and then use total_coulomb_count to report energy_now over time, resetting
     24 *    things to adjust for drift when empty/full. This should give more accurate
     25 *    readings, esp. in the 30-70% range and allow userspace to estimate time
     26 *    remaining till empty/full
     27 * 6. Maybe unregister + reregister the psy device when we learn the actual
     28 *    capacity during run-time ?
     29 *
     30 * The above will also require some sort of mwh_per_unit calculation. Testing
     31 * has shown that an estimated 7404mWh increase of the battery's energy results
     32 * in a total_coulomb_count increase of 3277 units with a 5 milli-ohm sense R.
     33 *
     34 * Copyright (C) 2021 Hans de Goede <hdegoede@redhat.com>
     35 */
     36
     37#include <linux/devm-helpers.h>
     38#include <linux/module.h>
     39#include <linux/mutex.h>
     40#include <linux/slab.h>
     41#include <linux/i2c.h>
     42#include <linux/mod_devicetable.h>
     43#include <linux/power_supply.h>
     44#include <linux/workqueue.h>
     45
     46#define UG3105_MOV_AVG_WINDOW					8
     47#define UG3105_INIT_POLL_TIME					(5 * HZ)
     48#define UG3105_POLL_TIME					(30 * HZ)
     49#define UG3105_SETTLE_TIME					(1 * HZ)
     50
     51#define UG3105_INIT_POLL_COUNT					30
     52
     53#define UG3105_REG_MODE						0x00
     54#define UG3105_REG_CTRL1					0x01
     55#define UG3105_REG_COULOMB_CNT					0x02
     56#define UG3105_REG_BAT_VOLT					0x08
     57#define UG3105_REG_BAT_CURR					0x0c
     58
     59#define UG3105_MODE_STANDBY					0x00
     60#define UG3105_MODE_RUN						0x10
     61
     62#define UG3105_CTRL1_RESET_COULOMB_CNT				0x03
     63
     64#define UG3105_CURR_HYST_UA					65000
     65
     66#define UG3105_LOW_BAT_UV					3700000
     67#define UG3105_FULL_BAT_HYST_UV					38000
     68
     69struct ug3105_chip {
     70	struct i2c_client *client;
     71	struct power_supply *psy;
     72	struct power_supply_battery_info *info;
     73	struct delayed_work work;
     74	struct mutex lock;
     75	int ocv[UG3105_MOV_AVG_WINDOW];		/* micro-volt */
     76	int intern_res[UG3105_MOV_AVG_WINDOW];	/* milli-ohm */
     77	int poll_count;
     78	int ocv_avg_index;
     79	int ocv_avg;				/* micro-volt */
     80	int intern_res_poll_count;
     81	int intern_res_avg_index;
     82	int intern_res_avg;			/* milli-ohm */
     83	int volt;				/* micro-volt */
     84	int curr;				/* micro-ampere */
     85	int total_coulomb_count;
     86	int uv_per_unit;
     87	int ua_per_unit;
     88	int status;
     89	int capacity;
     90	bool supplied;
     91};
     92
     93static int ug3105_read_word(struct i2c_client *client, u8 reg)
     94{
     95	int val;
     96
     97	val = i2c_smbus_read_word_data(client, reg);
     98	if (val < 0)
     99		dev_err(&client->dev, "Error reading reg 0x%02x\n", reg);
    100
    101	return val;
    102}
    103
    104static int ug3105_get_status(struct ug3105_chip *chip)
    105{
    106	int full = chip->info->constant_charge_voltage_max_uv - UG3105_FULL_BAT_HYST_UV;
    107
    108	if (chip->curr > UG3105_CURR_HYST_UA)
    109		return POWER_SUPPLY_STATUS_CHARGING;
    110
    111	if (chip->curr < -UG3105_CURR_HYST_UA)
    112		return POWER_SUPPLY_STATUS_DISCHARGING;
    113
    114	if (chip->supplied && chip->ocv_avg > full)
    115		return POWER_SUPPLY_STATUS_FULL;
    116
    117	return POWER_SUPPLY_STATUS_NOT_CHARGING;
    118}
    119
    120static int ug3105_get_capacity(struct ug3105_chip *chip)
    121{
    122	/*
    123	 * OCV voltages in uV for 0-110% in 5% increments, the 100-110% is
    124	 * for LiPo HV (High-Voltage) bateries which can go up to 4.35V
    125	 * instead of the usual 4.2V.
    126	 */
    127	static const int ocv_capacity_tbl[23] = {
    128		3350000,
    129		3610000,
    130		3690000,
    131		3710000,
    132		3730000,
    133		3750000,
    134		3770000,
    135		3786667,
    136		3803333,
    137		3820000,
    138		3836667,
    139		3853333,
    140		3870000,
    141		3907500,
    142		3945000,
    143		3982500,
    144		4020000,
    145		4075000,
    146		4110000,
    147		4150000,
    148		4200000,
    149		4250000,
    150		4300000,
    151	};
    152	int i, ocv_diff, ocv_step;
    153
    154	if (chip->ocv_avg < ocv_capacity_tbl[0])
    155		return 0;
    156
    157	if (chip->status == POWER_SUPPLY_STATUS_FULL)
    158		return 100;
    159
    160	for (i = 1; i < ARRAY_SIZE(ocv_capacity_tbl); i++) {
    161		if (chip->ocv_avg > ocv_capacity_tbl[i])
    162			continue;
    163
    164		ocv_diff = ocv_capacity_tbl[i] - chip->ocv_avg;
    165		ocv_step = ocv_capacity_tbl[i] - ocv_capacity_tbl[i - 1];
    166		/* scale 0-110% down to 0-100% for LiPo HV */
    167		if (chip->info->constant_charge_voltage_max_uv >= 4300000)
    168			return (i * 500 - ocv_diff * 500 / ocv_step) / 110;
    169		else
    170			return i * 5 - ocv_diff * 5 / ocv_step;
    171	}
    172
    173	return 100;
    174}
    175
    176static void ug3105_work(struct work_struct *work)
    177{
    178	struct ug3105_chip *chip = container_of(work, struct ug3105_chip,
    179						work.work);
    180	int i, val, curr_diff, volt_diff, res, win_size;
    181	bool prev_supplied = chip->supplied;
    182	int prev_status = chip->status;
    183	int prev_volt = chip->volt;
    184	int prev_curr = chip->curr;
    185	struct power_supply *psy;
    186
    187	mutex_lock(&chip->lock);
    188
    189	psy = chip->psy;
    190	if (!psy)
    191		goto out;
    192
    193	val = ug3105_read_word(chip->client, UG3105_REG_BAT_VOLT);
    194	if (val < 0)
    195		goto out;
    196	chip->volt = val * chip->uv_per_unit;
    197
    198	val = ug3105_read_word(chip->client, UG3105_REG_BAT_CURR);
    199	if (val < 0)
    200		goto out;
    201	chip->curr = (s16)val * chip->ua_per_unit;
    202
    203	chip->ocv[chip->ocv_avg_index] =
    204		chip->volt - chip->curr * chip->intern_res_avg / 1000;
    205	chip->ocv_avg_index = (chip->ocv_avg_index + 1) % UG3105_MOV_AVG_WINDOW;
    206	chip->poll_count++;
    207
    208	/*
    209	 * See possible improvements comment above.
    210	 *
    211	 * Read + reset coulomb counter every 10 polls (every 300 seconds)
    212	 * if ((chip->poll_count % 10) == 0) {
    213	 *	val = ug3105_read_word(chip->client, UG3105_REG_COULOMB_CNT);
    214	 *	if (val < 0)
    215	 *		goto out;
    216	 *
    217	 *	i2c_smbus_write_byte_data(chip->client, UG3105_REG_CTRL1,
    218	 *				  UG3105_CTRL1_RESET_COULOMB_CNT);
    219	 *
    220	 *	chip->total_coulomb_count += (s16)val;
    221	 *	dev_dbg(&chip->client->dev, "coulomb count %d total %d\n",
    222	 *		(s16)val, chip->total_coulomb_count);
    223	 * }
    224	 */
    225
    226	chip->ocv_avg = 0;
    227	win_size = min(chip->poll_count, UG3105_MOV_AVG_WINDOW);
    228	for (i = 0; i < win_size; i++)
    229		chip->ocv_avg += chip->ocv[i];
    230	chip->ocv_avg /= win_size;
    231
    232	chip->supplied = power_supply_am_i_supplied(psy);
    233	chip->status = ug3105_get_status(chip);
    234	chip->capacity = ug3105_get_capacity(chip);
    235
    236	/*
    237	 * Skip internal resistance calc on charger [un]plug and
    238	 * when the battery is almost empty (voltage low).
    239	 */
    240	if (chip->supplied != prev_supplied ||
    241	    chip->volt < UG3105_LOW_BAT_UV ||
    242	    chip->poll_count < 2)
    243		goto out;
    244
    245	/*
    246	 * Assuming that the OCV voltage does not change significantly
    247	 * between 2 polls, then we can calculate the internal resistance
    248	 * on a significant current change by attributing all voltage
    249	 * change between the 2 readings to the internal resistance.
    250	 */
    251	curr_diff = abs(chip->curr - prev_curr);
    252	if (curr_diff < UG3105_CURR_HYST_UA)
    253		goto out;
    254
    255	volt_diff = abs(chip->volt - prev_volt);
    256	res = volt_diff * 1000 / curr_diff;
    257
    258	if ((res < (chip->intern_res_avg * 2 / 3)) ||
    259	    (res > (chip->intern_res_avg * 4 / 3))) {
    260		dev_dbg(&chip->client->dev, "Ignoring outlier internal resistance %d mOhm\n", res);
    261		goto out;
    262	}
    263
    264	dev_dbg(&chip->client->dev, "Internal resistance %d mOhm\n", res);
    265
    266	chip->intern_res[chip->intern_res_avg_index] = res;
    267	chip->intern_res_avg_index = (chip->intern_res_avg_index + 1) % UG3105_MOV_AVG_WINDOW;
    268	chip->intern_res_poll_count++;
    269
    270	chip->intern_res_avg = 0;
    271	win_size = min(chip->intern_res_poll_count, UG3105_MOV_AVG_WINDOW);
    272	for (i = 0; i < win_size; i++)
    273		chip->intern_res_avg += chip->intern_res[i];
    274	chip->intern_res_avg /= win_size;
    275
    276out:
    277	mutex_unlock(&chip->lock);
    278
    279	queue_delayed_work(system_wq, &chip->work,
    280			   (chip->poll_count <= UG3105_INIT_POLL_COUNT) ?
    281					UG3105_INIT_POLL_TIME : UG3105_POLL_TIME);
    282
    283	if (chip->status != prev_status && psy)
    284		power_supply_changed(psy);
    285}
    286
    287static enum power_supply_property ug3105_battery_props[] = {
    288	POWER_SUPPLY_PROP_STATUS,
    289	POWER_SUPPLY_PROP_PRESENT,
    290	POWER_SUPPLY_PROP_TECHNOLOGY,
    291	POWER_SUPPLY_PROP_SCOPE,
    292	POWER_SUPPLY_PROP_VOLTAGE_NOW,
    293	POWER_SUPPLY_PROP_VOLTAGE_OCV,
    294	POWER_SUPPLY_PROP_CURRENT_NOW,
    295	POWER_SUPPLY_PROP_CAPACITY,
    296};
    297
    298static int ug3105_get_property(struct power_supply *psy,
    299			       enum power_supply_property psp,
    300			       union power_supply_propval *val)
    301{
    302	struct ug3105_chip *chip = power_supply_get_drvdata(psy);
    303	int ret = 0;
    304
    305	mutex_lock(&chip->lock);
    306
    307	if (!chip->psy) {
    308		ret = -EAGAIN;
    309		goto out;
    310	}
    311
    312	switch (psp) {
    313	case POWER_SUPPLY_PROP_STATUS:
    314		val->intval = chip->status;
    315		break;
    316	case POWER_SUPPLY_PROP_PRESENT:
    317		val->intval = 1;
    318		break;
    319	case POWER_SUPPLY_PROP_TECHNOLOGY:
    320		val->intval = chip->info->technology;
    321		break;
    322	case POWER_SUPPLY_PROP_SCOPE:
    323		val->intval = POWER_SUPPLY_SCOPE_SYSTEM;
    324		break;
    325	case POWER_SUPPLY_PROP_VOLTAGE_NOW:
    326		ret = ug3105_read_word(chip->client, UG3105_REG_BAT_VOLT);
    327		if (ret < 0)
    328			break;
    329		val->intval = ret * chip->uv_per_unit;
    330		ret = 0;
    331		break;
    332	case POWER_SUPPLY_PROP_VOLTAGE_OCV:
    333		val->intval = chip->ocv_avg;
    334		break;
    335	case POWER_SUPPLY_PROP_CURRENT_NOW:
    336		ret = ug3105_read_word(chip->client, UG3105_REG_BAT_CURR);
    337		if (ret < 0)
    338			break;
    339		val->intval = (s16)ret * chip->ua_per_unit;
    340		ret = 0;
    341		break;
    342	case POWER_SUPPLY_PROP_CAPACITY:
    343		val->intval = chip->capacity;
    344		break;
    345	default:
    346		ret = -EINVAL;
    347	}
    348
    349out:
    350	mutex_unlock(&chip->lock);
    351	return ret;
    352}
    353
    354static void ug3105_external_power_changed(struct power_supply *psy)
    355{
    356	struct ug3105_chip *chip = power_supply_get_drvdata(psy);
    357
    358	dev_dbg(&chip->client->dev, "external power changed\n");
    359	mod_delayed_work(system_wq, &chip->work, UG3105_SETTLE_TIME);
    360}
    361
    362static const struct power_supply_desc ug3105_psy_desc = {
    363	.name		= "ug3105_battery",
    364	.type		= POWER_SUPPLY_TYPE_BATTERY,
    365	.get_property	= ug3105_get_property,
    366	.external_power_changed	= ug3105_external_power_changed,
    367	.properties	= ug3105_battery_props,
    368	.num_properties	= ARRAY_SIZE(ug3105_battery_props),
    369};
    370
    371static void ug3105_init(struct ug3105_chip *chip)
    372{
    373	chip->poll_count = 0;
    374	chip->ocv_avg_index = 0;
    375	chip->total_coulomb_count = 0;
    376	i2c_smbus_write_byte_data(chip->client, UG3105_REG_MODE,
    377				  UG3105_MODE_RUN);
    378	i2c_smbus_write_byte_data(chip->client, UG3105_REG_CTRL1,
    379				  UG3105_CTRL1_RESET_COULOMB_CNT);
    380	queue_delayed_work(system_wq, &chip->work, 0);
    381	flush_delayed_work(&chip->work);
    382}
    383
    384static int ug3105_probe(struct i2c_client *client)
    385{
    386	struct power_supply_config psy_cfg = {};
    387	struct device *dev = &client->dev;
    388	u32 curr_sense_res_uohm = 10000;
    389	struct power_supply *psy;
    390	struct ug3105_chip *chip;
    391	int ret;
    392
    393	chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
    394	if (!chip)
    395		return -ENOMEM;
    396
    397	chip->client = client;
    398	mutex_init(&chip->lock);
    399	ret = devm_delayed_work_autocancel(dev, &chip->work, ug3105_work);
    400	if (ret)
    401		return ret;
    402
    403	psy_cfg.drv_data = chip;
    404	psy = devm_power_supply_register(dev, &ug3105_psy_desc, &psy_cfg);
    405	if (IS_ERR(psy))
    406		return PTR_ERR(psy);
    407
    408	ret = power_supply_get_battery_info(psy, &chip->info);
    409	if (ret)
    410		return ret;
    411
    412	if (chip->info->factory_internal_resistance_uohm == -EINVAL ||
    413	    chip->info->constant_charge_voltage_max_uv == -EINVAL) {
    414		dev_err(dev, "error required properties are missing\n");
    415		return -ENODEV;
    416	}
    417
    418	device_property_read_u32(dev, "upisemi,rsns-microohm", &curr_sense_res_uohm);
    419
    420	/*
    421	 * DAC maximum is 4.5V divided by 65536 steps + an unknown factor of 10
    422	 * coming from somewhere for some reason (verified with a volt-meter).
    423	 */
    424	chip->uv_per_unit = 45000000/65536;
    425	/* Datasheet says 8.1 uV per unit for the current ADC */
    426	chip->ua_per_unit = 8100000 / curr_sense_res_uohm;
    427
    428	/* Use provided internal resistance as start point (in milli-ohm) */
    429	chip->intern_res_avg = chip->info->factory_internal_resistance_uohm / 1000;
    430	/* Also add it to the internal resistance moving average window */
    431	chip->intern_res[0] = chip->intern_res_avg;
    432	chip->intern_res_avg_index = 1;
    433	chip->intern_res_poll_count = 1;
    434
    435	mutex_lock(&chip->lock);
    436	chip->psy = psy;
    437	mutex_unlock(&chip->lock);
    438
    439	ug3105_init(chip);
    440
    441	i2c_set_clientdata(client, chip);
    442	return 0;
    443}
    444
    445static int __maybe_unused ug3105_suspend(struct device *dev)
    446{
    447	struct ug3105_chip *chip = dev_get_drvdata(dev);
    448
    449	cancel_delayed_work_sync(&chip->work);
    450	i2c_smbus_write_byte_data(chip->client, UG3105_REG_MODE,
    451				  UG3105_MODE_STANDBY);
    452
    453	return 0;
    454}
    455
    456static int __maybe_unused ug3105_resume(struct device *dev)
    457{
    458	struct ug3105_chip *chip = dev_get_drvdata(dev);
    459
    460	ug3105_init(chip);
    461
    462	return 0;
    463}
    464
    465static SIMPLE_DEV_PM_OPS(ug3105_pm_ops, ug3105_suspend,
    466			ug3105_resume);
    467
    468static const struct i2c_device_id ug3105_id[] = {
    469	{ "ug3105" },
    470	{ }
    471};
    472MODULE_DEVICE_TABLE(i2c, ug3105_id);
    473
    474static struct i2c_driver ug3105_i2c_driver = {
    475	.driver	= {
    476		.name = "ug3105",
    477		.pm = &ug3105_pm_ops,
    478	},
    479	.probe_new = ug3105_probe,
    480	.id_table = ug3105_id,
    481};
    482module_i2c_driver(ug3105_i2c_driver);
    483
    484MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com");
    485MODULE_DESCRIPTION("uPI uG3105 battery monitor driver");
    486MODULE_LICENSE("GPL");