162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Battery monitor driver for the uPI uG3105 battery monitor 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Note the uG3105 is not a full-featured autonomous fuel-gauge. Instead it is 662306a36Sopenharmony_ci * expected to be use in combination with some always on microcontroller reading 762306a36Sopenharmony_ci * its coulomb-counter before it can wrap (must be read every 400 seconds!). 862306a36Sopenharmony_ci * 962306a36Sopenharmony_ci * Since Linux does not monitor coulomb-counter changes while the device 1062306a36Sopenharmony_ci * is off or suspended, the coulomb counter is not used atm. 1162306a36Sopenharmony_ci * 1262306a36Sopenharmony_ci * Possible improvements: 1362306a36Sopenharmony_ci * 1. Activate commented out total_coulomb_count code 1462306a36Sopenharmony_ci * 2. Reset total_coulomb_count val to 0 when the battery is as good as empty 1562306a36Sopenharmony_ci * and remember that we did this (and clear the flag for this on susp/resume) 1662306a36Sopenharmony_ci * 3. When the battery is full check if the flag that we set total_coulomb_count 1762306a36Sopenharmony_ci * to when the battery was empty is set. If so we now know the capacity, 1862306a36Sopenharmony_ci * not the design, but actual capacity, of the battery 1962306a36Sopenharmony_ci * 4. Add some mechanism (needs userspace help, or maybe use efivar?) to remember 2062306a36Sopenharmony_ci * the actual capacity of the battery over reboots 2162306a36Sopenharmony_ci * 5. When we know the actual capacity at probe time, add energy_now and 2262306a36Sopenharmony_ci * energy_full attributes. Guess boot + resume energy_now value based on ocv 2362306a36Sopenharmony_ci * and then use total_coulomb_count to report energy_now over time, resetting 2462306a36Sopenharmony_ci * things to adjust for drift when empty/full. This should give more accurate 2562306a36Sopenharmony_ci * readings, esp. in the 30-70% range and allow userspace to estimate time 2662306a36Sopenharmony_ci * remaining till empty/full 2762306a36Sopenharmony_ci * 6. Maybe unregister + reregister the psy device when we learn the actual 2862306a36Sopenharmony_ci * capacity during run-time ? 2962306a36Sopenharmony_ci * 3062306a36Sopenharmony_ci * The above will also require some sort of mwh_per_unit calculation. Testing 3162306a36Sopenharmony_ci * has shown that an estimated 7404mWh increase of the battery's energy results 3262306a36Sopenharmony_ci * in a total_coulomb_count increase of 3277 units with a 5 milli-ohm sense R. 3362306a36Sopenharmony_ci * 3462306a36Sopenharmony_ci * Copyright (C) 2021 Hans de Goede <hdegoede@redhat.com> 3562306a36Sopenharmony_ci */ 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_ci#include <linux/devm-helpers.h> 3862306a36Sopenharmony_ci#include <linux/module.h> 3962306a36Sopenharmony_ci#include <linux/mutex.h> 4062306a36Sopenharmony_ci#include <linux/slab.h> 4162306a36Sopenharmony_ci#include <linux/i2c.h> 4262306a36Sopenharmony_ci#include <linux/mod_devicetable.h> 4362306a36Sopenharmony_ci#include <linux/power_supply.h> 4462306a36Sopenharmony_ci#include <linux/workqueue.h> 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_ci#define UG3105_MOV_AVG_WINDOW 8 4762306a36Sopenharmony_ci#define UG3105_INIT_POLL_TIME (5 * HZ) 4862306a36Sopenharmony_ci#define UG3105_POLL_TIME (30 * HZ) 4962306a36Sopenharmony_ci#define UG3105_SETTLE_TIME (1 * HZ) 5062306a36Sopenharmony_ci 5162306a36Sopenharmony_ci#define UG3105_INIT_POLL_COUNT 30 5262306a36Sopenharmony_ci 5362306a36Sopenharmony_ci#define UG3105_REG_MODE 0x00 5462306a36Sopenharmony_ci#define UG3105_REG_CTRL1 0x01 5562306a36Sopenharmony_ci#define UG3105_REG_COULOMB_CNT 0x02 5662306a36Sopenharmony_ci#define UG3105_REG_BAT_VOLT 0x08 5762306a36Sopenharmony_ci#define UG3105_REG_BAT_CURR 0x0c 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_ci#define UG3105_MODE_STANDBY 0x00 6062306a36Sopenharmony_ci#define UG3105_MODE_RUN 0x10 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_ci#define UG3105_CTRL1_RESET_COULOMB_CNT 0x03 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_ci#define UG3105_CURR_HYST_UA 65000 6562306a36Sopenharmony_ci 6662306a36Sopenharmony_ci#define UG3105_LOW_BAT_UV 3700000 6762306a36Sopenharmony_ci#define UG3105_FULL_BAT_HYST_UV 38000 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_cistruct ug3105_chip { 7062306a36Sopenharmony_ci struct i2c_client *client; 7162306a36Sopenharmony_ci struct power_supply *psy; 7262306a36Sopenharmony_ci struct power_supply_battery_info *info; 7362306a36Sopenharmony_ci struct delayed_work work; 7462306a36Sopenharmony_ci struct mutex lock; 7562306a36Sopenharmony_ci int ocv[UG3105_MOV_AVG_WINDOW]; /* micro-volt */ 7662306a36Sopenharmony_ci int intern_res[UG3105_MOV_AVG_WINDOW]; /* milli-ohm */ 7762306a36Sopenharmony_ci int poll_count; 7862306a36Sopenharmony_ci int ocv_avg_index; 7962306a36Sopenharmony_ci int ocv_avg; /* micro-volt */ 8062306a36Sopenharmony_ci int intern_res_poll_count; 8162306a36Sopenharmony_ci int intern_res_avg_index; 8262306a36Sopenharmony_ci int intern_res_avg; /* milli-ohm */ 8362306a36Sopenharmony_ci int volt; /* micro-volt */ 8462306a36Sopenharmony_ci int curr; /* micro-ampere */ 8562306a36Sopenharmony_ci int total_coulomb_count; 8662306a36Sopenharmony_ci int uv_per_unit; 8762306a36Sopenharmony_ci int ua_per_unit; 8862306a36Sopenharmony_ci int status; 8962306a36Sopenharmony_ci int capacity; 9062306a36Sopenharmony_ci bool supplied; 9162306a36Sopenharmony_ci}; 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_cistatic int ug3105_read_word(struct i2c_client *client, u8 reg) 9462306a36Sopenharmony_ci{ 9562306a36Sopenharmony_ci int val; 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_ci val = i2c_smbus_read_word_data(client, reg); 9862306a36Sopenharmony_ci if (val < 0) 9962306a36Sopenharmony_ci dev_err(&client->dev, "Error reading reg 0x%02x\n", reg); 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_ci return val; 10262306a36Sopenharmony_ci} 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_cistatic int ug3105_get_status(struct ug3105_chip *chip) 10562306a36Sopenharmony_ci{ 10662306a36Sopenharmony_ci int full = chip->info->constant_charge_voltage_max_uv - UG3105_FULL_BAT_HYST_UV; 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_ci if (chip->curr > UG3105_CURR_HYST_UA) 10962306a36Sopenharmony_ci return POWER_SUPPLY_STATUS_CHARGING; 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_ci if (chip->curr < -UG3105_CURR_HYST_UA) 11262306a36Sopenharmony_ci return POWER_SUPPLY_STATUS_DISCHARGING; 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_ci if (chip->supplied && chip->ocv_avg > full) 11562306a36Sopenharmony_ci return POWER_SUPPLY_STATUS_FULL; 11662306a36Sopenharmony_ci 11762306a36Sopenharmony_ci return POWER_SUPPLY_STATUS_NOT_CHARGING; 11862306a36Sopenharmony_ci} 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_cistatic int ug3105_get_capacity(struct ug3105_chip *chip) 12162306a36Sopenharmony_ci{ 12262306a36Sopenharmony_ci /* 12362306a36Sopenharmony_ci * OCV voltages in uV for 0-110% in 5% increments, the 100-110% is 12462306a36Sopenharmony_ci * for LiPo HV (High-Voltage) bateries which can go up to 4.35V 12562306a36Sopenharmony_ci * instead of the usual 4.2V. 12662306a36Sopenharmony_ci */ 12762306a36Sopenharmony_ci static const int ocv_capacity_tbl[23] = { 12862306a36Sopenharmony_ci 3350000, 12962306a36Sopenharmony_ci 3610000, 13062306a36Sopenharmony_ci 3690000, 13162306a36Sopenharmony_ci 3710000, 13262306a36Sopenharmony_ci 3730000, 13362306a36Sopenharmony_ci 3750000, 13462306a36Sopenharmony_ci 3770000, 13562306a36Sopenharmony_ci 3786667, 13662306a36Sopenharmony_ci 3803333, 13762306a36Sopenharmony_ci 3820000, 13862306a36Sopenharmony_ci 3836667, 13962306a36Sopenharmony_ci 3853333, 14062306a36Sopenharmony_ci 3870000, 14162306a36Sopenharmony_ci 3907500, 14262306a36Sopenharmony_ci 3945000, 14362306a36Sopenharmony_ci 3982500, 14462306a36Sopenharmony_ci 4020000, 14562306a36Sopenharmony_ci 4075000, 14662306a36Sopenharmony_ci 4110000, 14762306a36Sopenharmony_ci 4150000, 14862306a36Sopenharmony_ci 4200000, 14962306a36Sopenharmony_ci 4250000, 15062306a36Sopenharmony_ci 4300000, 15162306a36Sopenharmony_ci }; 15262306a36Sopenharmony_ci int i, ocv_diff, ocv_step; 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_ci if (chip->ocv_avg < ocv_capacity_tbl[0]) 15562306a36Sopenharmony_ci return 0; 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_ci if (chip->status == POWER_SUPPLY_STATUS_FULL) 15862306a36Sopenharmony_ci return 100; 15962306a36Sopenharmony_ci 16062306a36Sopenharmony_ci for (i = 1; i < ARRAY_SIZE(ocv_capacity_tbl); i++) { 16162306a36Sopenharmony_ci if (chip->ocv_avg > ocv_capacity_tbl[i]) 16262306a36Sopenharmony_ci continue; 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_ci ocv_diff = ocv_capacity_tbl[i] - chip->ocv_avg; 16562306a36Sopenharmony_ci ocv_step = ocv_capacity_tbl[i] - ocv_capacity_tbl[i - 1]; 16662306a36Sopenharmony_ci /* scale 0-110% down to 0-100% for LiPo HV */ 16762306a36Sopenharmony_ci if (chip->info->constant_charge_voltage_max_uv >= 4300000) 16862306a36Sopenharmony_ci return (i * 500 - ocv_diff * 500 / ocv_step) / 110; 16962306a36Sopenharmony_ci else 17062306a36Sopenharmony_ci return i * 5 - ocv_diff * 5 / ocv_step; 17162306a36Sopenharmony_ci } 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_ci return 100; 17462306a36Sopenharmony_ci} 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_cistatic void ug3105_work(struct work_struct *work) 17762306a36Sopenharmony_ci{ 17862306a36Sopenharmony_ci struct ug3105_chip *chip = container_of(work, struct ug3105_chip, 17962306a36Sopenharmony_ci work.work); 18062306a36Sopenharmony_ci int i, val, curr_diff, volt_diff, res, win_size; 18162306a36Sopenharmony_ci bool prev_supplied = chip->supplied; 18262306a36Sopenharmony_ci int prev_status = chip->status; 18362306a36Sopenharmony_ci int prev_volt = chip->volt; 18462306a36Sopenharmony_ci int prev_curr = chip->curr; 18562306a36Sopenharmony_ci struct power_supply *psy; 18662306a36Sopenharmony_ci 18762306a36Sopenharmony_ci mutex_lock(&chip->lock); 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_ci psy = chip->psy; 19062306a36Sopenharmony_ci if (!psy) 19162306a36Sopenharmony_ci goto out; 19262306a36Sopenharmony_ci 19362306a36Sopenharmony_ci val = ug3105_read_word(chip->client, UG3105_REG_BAT_VOLT); 19462306a36Sopenharmony_ci if (val < 0) 19562306a36Sopenharmony_ci goto out; 19662306a36Sopenharmony_ci chip->volt = val * chip->uv_per_unit; 19762306a36Sopenharmony_ci 19862306a36Sopenharmony_ci val = ug3105_read_word(chip->client, UG3105_REG_BAT_CURR); 19962306a36Sopenharmony_ci if (val < 0) 20062306a36Sopenharmony_ci goto out; 20162306a36Sopenharmony_ci chip->curr = (s16)val * chip->ua_per_unit; 20262306a36Sopenharmony_ci 20362306a36Sopenharmony_ci chip->ocv[chip->ocv_avg_index] = 20462306a36Sopenharmony_ci chip->volt - chip->curr * chip->intern_res_avg / 1000; 20562306a36Sopenharmony_ci chip->ocv_avg_index = (chip->ocv_avg_index + 1) % UG3105_MOV_AVG_WINDOW; 20662306a36Sopenharmony_ci chip->poll_count++; 20762306a36Sopenharmony_ci 20862306a36Sopenharmony_ci /* 20962306a36Sopenharmony_ci * See possible improvements comment above. 21062306a36Sopenharmony_ci * 21162306a36Sopenharmony_ci * Read + reset coulomb counter every 10 polls (every 300 seconds) 21262306a36Sopenharmony_ci * if ((chip->poll_count % 10) == 0) { 21362306a36Sopenharmony_ci * val = ug3105_read_word(chip->client, UG3105_REG_COULOMB_CNT); 21462306a36Sopenharmony_ci * if (val < 0) 21562306a36Sopenharmony_ci * goto out; 21662306a36Sopenharmony_ci * 21762306a36Sopenharmony_ci * i2c_smbus_write_byte_data(chip->client, UG3105_REG_CTRL1, 21862306a36Sopenharmony_ci * UG3105_CTRL1_RESET_COULOMB_CNT); 21962306a36Sopenharmony_ci * 22062306a36Sopenharmony_ci * chip->total_coulomb_count += (s16)val; 22162306a36Sopenharmony_ci * dev_dbg(&chip->client->dev, "coulomb count %d total %d\n", 22262306a36Sopenharmony_ci * (s16)val, chip->total_coulomb_count); 22362306a36Sopenharmony_ci * } 22462306a36Sopenharmony_ci */ 22562306a36Sopenharmony_ci 22662306a36Sopenharmony_ci chip->ocv_avg = 0; 22762306a36Sopenharmony_ci win_size = min(chip->poll_count, UG3105_MOV_AVG_WINDOW); 22862306a36Sopenharmony_ci for (i = 0; i < win_size; i++) 22962306a36Sopenharmony_ci chip->ocv_avg += chip->ocv[i]; 23062306a36Sopenharmony_ci chip->ocv_avg /= win_size; 23162306a36Sopenharmony_ci 23262306a36Sopenharmony_ci chip->supplied = power_supply_am_i_supplied(psy); 23362306a36Sopenharmony_ci chip->status = ug3105_get_status(chip); 23462306a36Sopenharmony_ci chip->capacity = ug3105_get_capacity(chip); 23562306a36Sopenharmony_ci 23662306a36Sopenharmony_ci /* 23762306a36Sopenharmony_ci * Skip internal resistance calc on charger [un]plug and 23862306a36Sopenharmony_ci * when the battery is almost empty (voltage low). 23962306a36Sopenharmony_ci */ 24062306a36Sopenharmony_ci if (chip->supplied != prev_supplied || 24162306a36Sopenharmony_ci chip->volt < UG3105_LOW_BAT_UV || 24262306a36Sopenharmony_ci chip->poll_count < 2) 24362306a36Sopenharmony_ci goto out; 24462306a36Sopenharmony_ci 24562306a36Sopenharmony_ci /* 24662306a36Sopenharmony_ci * Assuming that the OCV voltage does not change significantly 24762306a36Sopenharmony_ci * between 2 polls, then we can calculate the internal resistance 24862306a36Sopenharmony_ci * on a significant current change by attributing all voltage 24962306a36Sopenharmony_ci * change between the 2 readings to the internal resistance. 25062306a36Sopenharmony_ci */ 25162306a36Sopenharmony_ci curr_diff = abs(chip->curr - prev_curr); 25262306a36Sopenharmony_ci if (curr_diff < UG3105_CURR_HYST_UA) 25362306a36Sopenharmony_ci goto out; 25462306a36Sopenharmony_ci 25562306a36Sopenharmony_ci volt_diff = abs(chip->volt - prev_volt); 25662306a36Sopenharmony_ci res = volt_diff * 1000 / curr_diff; 25762306a36Sopenharmony_ci 25862306a36Sopenharmony_ci if ((res < (chip->intern_res_avg * 2 / 3)) || 25962306a36Sopenharmony_ci (res > (chip->intern_res_avg * 4 / 3))) { 26062306a36Sopenharmony_ci dev_dbg(&chip->client->dev, "Ignoring outlier internal resistance %d mOhm\n", res); 26162306a36Sopenharmony_ci goto out; 26262306a36Sopenharmony_ci } 26362306a36Sopenharmony_ci 26462306a36Sopenharmony_ci dev_dbg(&chip->client->dev, "Internal resistance %d mOhm\n", res); 26562306a36Sopenharmony_ci 26662306a36Sopenharmony_ci chip->intern_res[chip->intern_res_avg_index] = res; 26762306a36Sopenharmony_ci chip->intern_res_avg_index = (chip->intern_res_avg_index + 1) % UG3105_MOV_AVG_WINDOW; 26862306a36Sopenharmony_ci chip->intern_res_poll_count++; 26962306a36Sopenharmony_ci 27062306a36Sopenharmony_ci chip->intern_res_avg = 0; 27162306a36Sopenharmony_ci win_size = min(chip->intern_res_poll_count, UG3105_MOV_AVG_WINDOW); 27262306a36Sopenharmony_ci for (i = 0; i < win_size; i++) 27362306a36Sopenharmony_ci chip->intern_res_avg += chip->intern_res[i]; 27462306a36Sopenharmony_ci chip->intern_res_avg /= win_size; 27562306a36Sopenharmony_ci 27662306a36Sopenharmony_ciout: 27762306a36Sopenharmony_ci mutex_unlock(&chip->lock); 27862306a36Sopenharmony_ci 27962306a36Sopenharmony_ci queue_delayed_work(system_wq, &chip->work, 28062306a36Sopenharmony_ci (chip->poll_count <= UG3105_INIT_POLL_COUNT) ? 28162306a36Sopenharmony_ci UG3105_INIT_POLL_TIME : UG3105_POLL_TIME); 28262306a36Sopenharmony_ci 28362306a36Sopenharmony_ci if (chip->status != prev_status && psy) 28462306a36Sopenharmony_ci power_supply_changed(psy); 28562306a36Sopenharmony_ci} 28662306a36Sopenharmony_ci 28762306a36Sopenharmony_cistatic enum power_supply_property ug3105_battery_props[] = { 28862306a36Sopenharmony_ci POWER_SUPPLY_PROP_STATUS, 28962306a36Sopenharmony_ci POWER_SUPPLY_PROP_PRESENT, 29062306a36Sopenharmony_ci POWER_SUPPLY_PROP_TECHNOLOGY, 29162306a36Sopenharmony_ci POWER_SUPPLY_PROP_SCOPE, 29262306a36Sopenharmony_ci POWER_SUPPLY_PROP_VOLTAGE_NOW, 29362306a36Sopenharmony_ci POWER_SUPPLY_PROP_VOLTAGE_OCV, 29462306a36Sopenharmony_ci POWER_SUPPLY_PROP_CURRENT_NOW, 29562306a36Sopenharmony_ci POWER_SUPPLY_PROP_CAPACITY, 29662306a36Sopenharmony_ci}; 29762306a36Sopenharmony_ci 29862306a36Sopenharmony_cistatic int ug3105_get_property(struct power_supply *psy, 29962306a36Sopenharmony_ci enum power_supply_property psp, 30062306a36Sopenharmony_ci union power_supply_propval *val) 30162306a36Sopenharmony_ci{ 30262306a36Sopenharmony_ci struct ug3105_chip *chip = power_supply_get_drvdata(psy); 30362306a36Sopenharmony_ci int ret = 0; 30462306a36Sopenharmony_ci 30562306a36Sopenharmony_ci mutex_lock(&chip->lock); 30662306a36Sopenharmony_ci 30762306a36Sopenharmony_ci if (!chip->psy) { 30862306a36Sopenharmony_ci ret = -EAGAIN; 30962306a36Sopenharmony_ci goto out; 31062306a36Sopenharmony_ci } 31162306a36Sopenharmony_ci 31262306a36Sopenharmony_ci switch (psp) { 31362306a36Sopenharmony_ci case POWER_SUPPLY_PROP_STATUS: 31462306a36Sopenharmony_ci val->intval = chip->status; 31562306a36Sopenharmony_ci break; 31662306a36Sopenharmony_ci case POWER_SUPPLY_PROP_PRESENT: 31762306a36Sopenharmony_ci val->intval = 1; 31862306a36Sopenharmony_ci break; 31962306a36Sopenharmony_ci case POWER_SUPPLY_PROP_TECHNOLOGY: 32062306a36Sopenharmony_ci val->intval = chip->info->technology; 32162306a36Sopenharmony_ci break; 32262306a36Sopenharmony_ci case POWER_SUPPLY_PROP_SCOPE: 32362306a36Sopenharmony_ci val->intval = POWER_SUPPLY_SCOPE_SYSTEM; 32462306a36Sopenharmony_ci break; 32562306a36Sopenharmony_ci case POWER_SUPPLY_PROP_VOLTAGE_NOW: 32662306a36Sopenharmony_ci ret = ug3105_read_word(chip->client, UG3105_REG_BAT_VOLT); 32762306a36Sopenharmony_ci if (ret < 0) 32862306a36Sopenharmony_ci break; 32962306a36Sopenharmony_ci val->intval = ret * chip->uv_per_unit; 33062306a36Sopenharmony_ci ret = 0; 33162306a36Sopenharmony_ci break; 33262306a36Sopenharmony_ci case POWER_SUPPLY_PROP_VOLTAGE_OCV: 33362306a36Sopenharmony_ci val->intval = chip->ocv_avg; 33462306a36Sopenharmony_ci break; 33562306a36Sopenharmony_ci case POWER_SUPPLY_PROP_CURRENT_NOW: 33662306a36Sopenharmony_ci ret = ug3105_read_word(chip->client, UG3105_REG_BAT_CURR); 33762306a36Sopenharmony_ci if (ret < 0) 33862306a36Sopenharmony_ci break; 33962306a36Sopenharmony_ci val->intval = (s16)ret * chip->ua_per_unit; 34062306a36Sopenharmony_ci ret = 0; 34162306a36Sopenharmony_ci break; 34262306a36Sopenharmony_ci case POWER_SUPPLY_PROP_CAPACITY: 34362306a36Sopenharmony_ci val->intval = chip->capacity; 34462306a36Sopenharmony_ci break; 34562306a36Sopenharmony_ci default: 34662306a36Sopenharmony_ci ret = -EINVAL; 34762306a36Sopenharmony_ci } 34862306a36Sopenharmony_ci 34962306a36Sopenharmony_ciout: 35062306a36Sopenharmony_ci mutex_unlock(&chip->lock); 35162306a36Sopenharmony_ci return ret; 35262306a36Sopenharmony_ci} 35362306a36Sopenharmony_ci 35462306a36Sopenharmony_cistatic void ug3105_external_power_changed(struct power_supply *psy) 35562306a36Sopenharmony_ci{ 35662306a36Sopenharmony_ci struct ug3105_chip *chip = power_supply_get_drvdata(psy); 35762306a36Sopenharmony_ci 35862306a36Sopenharmony_ci dev_dbg(&chip->client->dev, "external power changed\n"); 35962306a36Sopenharmony_ci mod_delayed_work(system_wq, &chip->work, UG3105_SETTLE_TIME); 36062306a36Sopenharmony_ci} 36162306a36Sopenharmony_ci 36262306a36Sopenharmony_cistatic const struct power_supply_desc ug3105_psy_desc = { 36362306a36Sopenharmony_ci .name = "ug3105_battery", 36462306a36Sopenharmony_ci .type = POWER_SUPPLY_TYPE_BATTERY, 36562306a36Sopenharmony_ci .get_property = ug3105_get_property, 36662306a36Sopenharmony_ci .external_power_changed = ug3105_external_power_changed, 36762306a36Sopenharmony_ci .properties = ug3105_battery_props, 36862306a36Sopenharmony_ci .num_properties = ARRAY_SIZE(ug3105_battery_props), 36962306a36Sopenharmony_ci}; 37062306a36Sopenharmony_ci 37162306a36Sopenharmony_cistatic void ug3105_init(struct ug3105_chip *chip) 37262306a36Sopenharmony_ci{ 37362306a36Sopenharmony_ci chip->poll_count = 0; 37462306a36Sopenharmony_ci chip->ocv_avg_index = 0; 37562306a36Sopenharmony_ci chip->total_coulomb_count = 0; 37662306a36Sopenharmony_ci i2c_smbus_write_byte_data(chip->client, UG3105_REG_MODE, 37762306a36Sopenharmony_ci UG3105_MODE_RUN); 37862306a36Sopenharmony_ci i2c_smbus_write_byte_data(chip->client, UG3105_REG_CTRL1, 37962306a36Sopenharmony_ci UG3105_CTRL1_RESET_COULOMB_CNT); 38062306a36Sopenharmony_ci queue_delayed_work(system_wq, &chip->work, 0); 38162306a36Sopenharmony_ci flush_delayed_work(&chip->work); 38262306a36Sopenharmony_ci} 38362306a36Sopenharmony_ci 38462306a36Sopenharmony_cistatic int ug3105_probe(struct i2c_client *client) 38562306a36Sopenharmony_ci{ 38662306a36Sopenharmony_ci struct power_supply_config psy_cfg = {}; 38762306a36Sopenharmony_ci struct device *dev = &client->dev; 38862306a36Sopenharmony_ci u32 curr_sense_res_uohm = 10000; 38962306a36Sopenharmony_ci struct power_supply *psy; 39062306a36Sopenharmony_ci struct ug3105_chip *chip; 39162306a36Sopenharmony_ci int ret; 39262306a36Sopenharmony_ci 39362306a36Sopenharmony_ci chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL); 39462306a36Sopenharmony_ci if (!chip) 39562306a36Sopenharmony_ci return -ENOMEM; 39662306a36Sopenharmony_ci 39762306a36Sopenharmony_ci chip->client = client; 39862306a36Sopenharmony_ci mutex_init(&chip->lock); 39962306a36Sopenharmony_ci ret = devm_delayed_work_autocancel(dev, &chip->work, ug3105_work); 40062306a36Sopenharmony_ci if (ret) 40162306a36Sopenharmony_ci return ret; 40262306a36Sopenharmony_ci 40362306a36Sopenharmony_ci psy_cfg.drv_data = chip; 40462306a36Sopenharmony_ci psy = devm_power_supply_register(dev, &ug3105_psy_desc, &psy_cfg); 40562306a36Sopenharmony_ci if (IS_ERR(psy)) 40662306a36Sopenharmony_ci return PTR_ERR(psy); 40762306a36Sopenharmony_ci 40862306a36Sopenharmony_ci ret = power_supply_get_battery_info(psy, &chip->info); 40962306a36Sopenharmony_ci if (ret) 41062306a36Sopenharmony_ci return ret; 41162306a36Sopenharmony_ci 41262306a36Sopenharmony_ci if (chip->info->factory_internal_resistance_uohm == -EINVAL || 41362306a36Sopenharmony_ci chip->info->constant_charge_voltage_max_uv == -EINVAL) { 41462306a36Sopenharmony_ci dev_err(dev, "error required properties are missing\n"); 41562306a36Sopenharmony_ci return -ENODEV; 41662306a36Sopenharmony_ci } 41762306a36Sopenharmony_ci 41862306a36Sopenharmony_ci device_property_read_u32(dev, "upisemi,rsns-microohm", &curr_sense_res_uohm); 41962306a36Sopenharmony_ci 42062306a36Sopenharmony_ci /* 42162306a36Sopenharmony_ci * DAC maximum is 4.5V divided by 65536 steps + an unknown factor of 10 42262306a36Sopenharmony_ci * coming from somewhere for some reason (verified with a volt-meter). 42362306a36Sopenharmony_ci */ 42462306a36Sopenharmony_ci chip->uv_per_unit = 45000000/65536; 42562306a36Sopenharmony_ci /* Datasheet says 8.1 uV per unit for the current ADC */ 42662306a36Sopenharmony_ci chip->ua_per_unit = 8100000 / curr_sense_res_uohm; 42762306a36Sopenharmony_ci 42862306a36Sopenharmony_ci /* Use provided internal resistance as start point (in milli-ohm) */ 42962306a36Sopenharmony_ci chip->intern_res_avg = chip->info->factory_internal_resistance_uohm / 1000; 43062306a36Sopenharmony_ci /* Also add it to the internal resistance moving average window */ 43162306a36Sopenharmony_ci chip->intern_res[0] = chip->intern_res_avg; 43262306a36Sopenharmony_ci chip->intern_res_avg_index = 1; 43362306a36Sopenharmony_ci chip->intern_res_poll_count = 1; 43462306a36Sopenharmony_ci 43562306a36Sopenharmony_ci mutex_lock(&chip->lock); 43662306a36Sopenharmony_ci chip->psy = psy; 43762306a36Sopenharmony_ci mutex_unlock(&chip->lock); 43862306a36Sopenharmony_ci 43962306a36Sopenharmony_ci ug3105_init(chip); 44062306a36Sopenharmony_ci 44162306a36Sopenharmony_ci i2c_set_clientdata(client, chip); 44262306a36Sopenharmony_ci return 0; 44362306a36Sopenharmony_ci} 44462306a36Sopenharmony_ci 44562306a36Sopenharmony_cistatic int __maybe_unused ug3105_suspend(struct device *dev) 44662306a36Sopenharmony_ci{ 44762306a36Sopenharmony_ci struct ug3105_chip *chip = dev_get_drvdata(dev); 44862306a36Sopenharmony_ci 44962306a36Sopenharmony_ci cancel_delayed_work_sync(&chip->work); 45062306a36Sopenharmony_ci i2c_smbus_write_byte_data(chip->client, UG3105_REG_MODE, 45162306a36Sopenharmony_ci UG3105_MODE_STANDBY); 45262306a36Sopenharmony_ci 45362306a36Sopenharmony_ci return 0; 45462306a36Sopenharmony_ci} 45562306a36Sopenharmony_ci 45662306a36Sopenharmony_cistatic int __maybe_unused ug3105_resume(struct device *dev) 45762306a36Sopenharmony_ci{ 45862306a36Sopenharmony_ci struct ug3105_chip *chip = dev_get_drvdata(dev); 45962306a36Sopenharmony_ci 46062306a36Sopenharmony_ci ug3105_init(chip); 46162306a36Sopenharmony_ci 46262306a36Sopenharmony_ci return 0; 46362306a36Sopenharmony_ci} 46462306a36Sopenharmony_ci 46562306a36Sopenharmony_cistatic SIMPLE_DEV_PM_OPS(ug3105_pm_ops, ug3105_suspend, 46662306a36Sopenharmony_ci ug3105_resume); 46762306a36Sopenharmony_ci 46862306a36Sopenharmony_cistatic const struct i2c_device_id ug3105_id[] = { 46962306a36Sopenharmony_ci { "ug3105" }, 47062306a36Sopenharmony_ci { } 47162306a36Sopenharmony_ci}; 47262306a36Sopenharmony_ciMODULE_DEVICE_TABLE(i2c, ug3105_id); 47362306a36Sopenharmony_ci 47462306a36Sopenharmony_cistatic struct i2c_driver ug3105_i2c_driver = { 47562306a36Sopenharmony_ci .driver = { 47662306a36Sopenharmony_ci .name = "ug3105", 47762306a36Sopenharmony_ci .pm = &ug3105_pm_ops, 47862306a36Sopenharmony_ci }, 47962306a36Sopenharmony_ci .probe = ug3105_probe, 48062306a36Sopenharmony_ci .id_table = ug3105_id, 48162306a36Sopenharmony_ci}; 48262306a36Sopenharmony_cimodule_i2c_driver(ug3105_i2c_driver); 48362306a36Sopenharmony_ci 48462306a36Sopenharmony_ciMODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com"); 48562306a36Sopenharmony_ciMODULE_DESCRIPTION("uPI uG3105 battery monitor driver"); 48662306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 487