blob: 5740cbfa94985254eea93fc2ae02e92d8f60f72b [file] [log] [blame]
/*
* Copyright 2022 Google, Inc
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#define BMS_DEV_NAME "sw5100_bms"
#define pr_fmt(fmt) BMS_DEV_NAME": " fmt
#include <linux/of.h>
#include <linux/of_platform.h>
//#include <linux/of_batterydata.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/power_supply.h>
#include <linux/pmic-voter.h>
#include <linux/regmap.h>
#include <linux/bitops.h>
#include <linux/iio/consumer.h>
#include <linux/regulator/consumer.h>
#include "google_bms.h"
/* hackaroo... */
#include "battery-profile-loader.h"
#include "smblite-reg.h"
#include "smblite-lib.h"
#include "smb5-iio.h"
#define CAPACITY_OFFSET 5
#define BIAS_STS_READY BIT(0)
#define CHARGE_DISABLE_VOTER "charge_disable"
#define USBIN_DISABLE_VOTER "USBIN_DISABLE"
struct bms_dev {
struct device *dev;
struct power_supply *psy;
struct regmap *pmic_regmap;
struct votable *fv_votable;
struct votable *fcc_votable;
struct votable *dc_suspend_votable;
struct votable *icl_votable;
struct notifier_block nb;
int batt_id_ohms;
u32 rradc_base;
int chg_term_voltage;
int chg_term_voltage_debounce;
/* Amount of SOC percentage points to offset to 0% UI SOC. */
int soc_shutdown_offset;
struct iio_channel *batt_therm_chan;
struct iio_channel *batt_id_chan;
struct iio_channel **iio_chan_list_qg;
};
struct bias_config {
u16 status_reg;
u16 lsb_reg;
int bias_kohms;
};
//CHARGER_STATUS_1
#define CHGR_BATTERY_CHARGER_STATUS_REG 0x2606
#define CHG_ERR_STATUS_SFT_EXPIRE BIT(5)
#define CHG_ERR_STATUS_BAT_OV BIT(4)
//CHARGER_STATUS_2
#define CHGR_BATTERY_CHARGER_EN_STATUS_REG 0x2607
#define ENABLE_TRICKLE_BIT BIT(1)
#define ENABLE_PRE_CHARGING_BIT BIT(2)
#define ENABLE_FULLON_MODE_BIT BIT(3)
//CHARGER_STATUS_7
#define BATIF_BAT_TEMP_STATUS_REG 0x280D
#define BAT_TEMP_WARM BIT(3)
#define BAT_TEMP_COOL BIT(2)
#define BAT_TEMP_TOO_HOT BIT(4)
#define BAT_TEMP_TOO_COLD BIT(1)
#define CHGR_FLOAT_VOLTAGE_NOW 0x260B
#define CHGR_CHG_EN 0x2646
#define CHARGING_ENABLE_CMD_BIT BIT(0)
#define CHGR_CHARGING_PAUSE_CMD 0x2646
#define CHARGING_PAUSE_CMD_BIT BIT(4)
#define CHGR_FAST_CHARGE_CURRENT_SETTING 0x2654
#define CHGR_ADC_ITERM_UP_THD_MSB 0x2664
#define CHGR_FLOAT_VOLTAGE_SETTING 0x2658
#define CHGR_CHG_TERM_CFG_REG 0x2660
#define CHGR_ITERM_USE_ANALOG_BIT BIT(3)
#define DCDC_ICL_STATUS_REG 0x2709
#define DCDC_AICL_ICL_STATUS_REG 0x2707
#define DCDC_AICL_STATUS_REG 0x2C06
#define DCDC_SOFT_ILIMIT_BIT BIT(6)
#define DCDC_POWER_PATH_STATUS_REG 0x270B
#define USE_USBIN_BIT BIT(5)
#define USE_DCIN_BIT BIT(4)
#define VALID_INPUT_POWER_SOURCE_STS_BIT BIT(7)
#define MISC_AICL_CMD_REG 0x2C50
#define BATIF_INT_RT_STS 0x2810
#define BATIF_THERM_OR_ID_MISSING_RT_STS_BIT BIT(1)
#define CHGR_BATTERY_CHARGER_STATUS_MASK GENMASK(2, 0)
#define CHGR_USB_SUSPEND 0x2954
#define USBIN_SUSPEND BIT(0)
#define SUSPEND_ON_COLLAPSE_USBIN BIT(7)
#define QBG_MAIN_QBG_STATE_FORCE_CMD 0x4F41
#define FORCE_HIGH_POWER_SHIFT 2
#define CHGR_FLOAT_VOLTAGE_BASE 3600000
#define CHGR_CHARGE_CURRENT_STEP 25000
#define CHG_TERM_VOLTAGE 4350
#define CHG_TERM_VOLT_DEBOUNCE 200
#define PM5100_ADC_CHG_ITERM_MULT 16384
/* sync from google_battery.c */
#define DEFAULT_BATT_DRV_RL_SOC_THRESHOLD 97
enum sw5100_chg_status {
SW5100_INHIBIT_CHARGE = 0,
SW5100_TRICKLE_CHARGE = 1,
SW5100_PRE_CHARGE = 2,
SW5100_FULLON_CHARGE = 3,
SW5100_TAPER_CHARGE = 4,
SW5100_TERMINATE_CHARGE = 5,
SW5100_PAUSE_CHARGE = 6,
SW5100_DISABLE_CHARGE = 7,
};
static int sw5100_read(struct regmap *pmic_regmap, int addr, u8 *val, int len)
{
int rc;
rc = regmap_bulk_read(pmic_regmap, addr, val, len);
if (rc < 0) {
pr_err("Failed regmap_read for address %04x rc=%d\n", addr, rc);
return rc;
}
return 0;
}
static int sw5100_write(struct regmap *pmic_regmap, int addr, u8 *val, int len)
{
int rc;
rc = regmap_bulk_write(pmic_regmap, addr, val, len);
if (rc < 0) {
pr_err("Failed regmap_write for address %04x rc=%d\n",
addr, rc);
return rc;
}
return 0;
}
static int sw5100_masked_write(struct regmap *pmic_regmap,
u16 addr, u8 mask, u8 val)
{
return regmap_update_bits(pmic_regmap, addr, mask, val);
}
static int sw5100_rd8(struct regmap *pmic_regmap, int addr, u8 *val)
{
return sw5100_read(pmic_regmap, addr, val, 1);
}
/* ------------------------------------------------------------------------- */
enum sw5100_qbg_iio_channels {
SW5100_QBG_DEBUG_BATTERY,
SW5100_QBG_CAPACITY,
SW5100_QBG_REAL_CAPACITY,
SW5100_QBG_CURRENT_NOW,
SW5100_QBG_VOLTAGE_NOW,
SW5100_QBG_VOLTAGE_MAX,
SW5100_QBG_CHARGE_FULL,
SW5100_QBG_RESISTANCE_ID,
SW5100_QBG_TEMP,
SW5100_QBG_CHARGE_COUNTER,
SW5100_QBG_CYCLE_COUNT,
SW5100_QBG_CHARGE_FULL_DESIGN,
SW5100_QBG_TIME_TO_FULL_NOW,
SW5100_QBG_TIME_TO_EMPTY_AVG,
SW5100_QBG_VOLTAGE_AVG,
SW5100_QBG_VOLTAGE_OCV,
SW5100_QBG_MAX,
};
/* QBG/FG channels */
static const char * const sw5100_qbg_ext_iio_chan[] = {
[SW5100_QBG_DEBUG_BATTERY] = "debug_battery",
[SW5100_QBG_CAPACITY] = "capacity",
[SW5100_QBG_REAL_CAPACITY] = "real_capacity",
[SW5100_QBG_CURRENT_NOW] = "current_now",
[SW5100_QBG_VOLTAGE_NOW] = "voltage_now",
[SW5100_QBG_VOLTAGE_MAX] = "voltage_max",
[SW5100_QBG_CHARGE_FULL] = "charge_full",
[SW5100_QBG_RESISTANCE_ID] = "resistance_id",
[SW5100_QBG_TEMP] = "temp",
[SW5100_QBG_CHARGE_COUNTER] = "charge_counter",
[SW5100_QBG_CYCLE_COUNT] = "cycle_count",
[SW5100_QBG_CHARGE_FULL_DESIGN] = "charge_full_design",
[SW5100_QBG_TIME_TO_FULL_NOW] = "time_to_full_now",
[SW5100_QBG_TIME_TO_EMPTY_AVG] = "time_to_empty_avg",
[SW5100_QBG_VOLTAGE_AVG] = "voltage_avg",
[SW5100_QBG_VOLTAGE_OCV] = "voltage_ocv",
};
static int sw5100_get_prop_from_bms(struct bms_dev *bms, int channel, int *val)
{
int rc;
if (IS_ERR_OR_NULL(bms->iio_chan_list_qg))
return -ENODEV;
rc = iio_read_channel_processed(bms->iio_chan_list_qg[channel],
val);
return rc < 0 ? rc : 0;
}
static struct iio_channel **sw5100_get_ext_channels(struct device *dev,
const char *const *channel_map, int size)
{
int i, rc = 0;
struct iio_channel **iio_ch_ext;
iio_ch_ext = devm_kcalloc(dev, size, sizeof(*iio_ch_ext), GFP_KERNEL);
if (!iio_ch_ext)
return ERR_PTR(-ENOMEM);
for (i = 0; i < size; i++) {
iio_ch_ext[i] = devm_iio_channel_get(dev, channel_map[i]);
if (IS_ERR(iio_ch_ext[i])) {
rc = PTR_ERR(iio_ch_ext[i]);
if (rc != -EPROBE_DEFER)
dev_err(dev, "%s channel unavailable, %d\n", channel_map[i], rc);
return ERR_PTR(rc);
}
}
return iio_ch_ext;
}
/* ------------------------------------------------------------------------- */
static irqreturn_t sw5100_chg_state_change_irq_handler(int irq, void *data)
{
struct smb_irq_data *irq_data = data;
struct bms_dev *chg = irq_data->parent_data;
u8 stat;
int rc;
u8 val;
dev_dbg(chg->dev, "IRQ: %s\n", irq_data->name);
rc = sw5100_read(chg->pmic_regmap, CHGR_BATTERY_CHARGER_STATUS_REG, &stat, 1);
if (rc < 0) {
dev_err(chg->dev, "Couldn't read BATTERY_CHARGER_STATUS rc=%d\n", rc);
return IRQ_HANDLED;
}
power_supply_changed(chg->psy);
/* Modify QBG update rate for different charge states. */
switch (stat & CHGR_BATTERY_CHARGER_STATUS_MASK) {
case SW5100_FULLON_CHARGE:
case SW5100_TAPER_CHARGE:
/* Force fuel gauge update interval to high power mode. */
val = (1 << FORCE_HIGH_POWER_SHIFT);
rc = sw5100_write(chg->pmic_regmap, QBG_MAIN_QBG_STATE_FORCE_CMD, &val, 1);
if (rc < 0) {
dev_err(chg->dev, "Failure to force QBG HPM rc=%d\n", rc);
}
break;
case SW5100_TERMINATE_CHARGE:
case SW5100_PAUSE_CHARGE:
case SW5100_DISABLE_CHARGE:
case SW5100_INHIBIT_CHARGE:
/* Return to dynamic fuel gauge update interval. */
val = 0;
rc = sw5100_write(chg->pmic_regmap, QBG_MAIN_QBG_STATE_FORCE_CMD, &val, 1);
if (rc < 0) {
dev_err(chg->dev, "Failure to restore dynamic QBG update rc=%d\n", rc);
}
break;
}
/* Monitor soc discrepancies at charge termination. */
if ((stat & CHGR_BATTERY_CHARGER_STATUS_MASK) == SW5100_TERMINATE_CHARGE) {
int sys_soc = 100;
rc = sw5100_get_prop_from_bms(chg, SW5100_QBG_REAL_CAPACITY, &sys_soc);
if (rc != 0) {
dev_err(chg->dev, "Failed to read system soc, rc=%d\n", rc);
} else if (sys_soc != 100) {
dev_err(chg->dev, "Battery full charge termination, sys_soc of %d != 100%%\n",
sys_soc);
}
}
return IRQ_HANDLED;
}
static irqreturn_t sw5100_batt_temp_changed_irq_handler(int irq, void *data)
{
struct smb_irq_data *irq_data = data;
struct bms_dev *chg = irq_data->parent_data;
dev_dbg(chg->dev, "IRQ: %s\n", irq_data->name);
power_supply_changed(chg->psy);
return IRQ_HANDLED;
}
static irqreturn_t sw5100_batt_psy_changed_irq_handler(int irq, void *data)
{
struct smb_irq_data *irq_data = data;
struct bms_dev *chg = irq_data->parent_data;
dev_dbg(chg->dev, "IRQ: %s\n", irq_data->name);
power_supply_changed(chg->psy);
return IRQ_HANDLED;
}
static irqreturn_t sw5100_default_irq_handler(int irq, void *data)
{
struct smb_irq_data *irq_data = data;
struct smb_charger *chg = irq_data->parent_data;
dev_dbg(chg->dev, "IRQ: %s\n", irq_data->name);
return IRQ_HANDLED;
}
/* TODO: sparse, consider adding .irqno */
static struct smb_irq_info sw5100_bms_irqs[] = {
/* CHARGER IRQs */
[CHGR_ERROR_IRQ] = {
.name = "chgr-error",
.handler = sw5100_default_irq_handler,
},
[CHG_STATE_CHANGE_IRQ] = {
.name = "chg-state-change",
.handler = sw5100_chg_state_change_irq_handler,
.wake = true,
},
[VPH_OV_IRQ] = {
.name = "vph-ov",
},
[BUCK_OC_IRQ] = {
.name = "buck-oc",
},
/* BATTERY IRQs */
[BAT_TEMP_IRQ] = {
.name = "bat-temp",
.handler = sw5100_batt_temp_changed_irq_handler,
.wake = true,
},
[BAT_THERM_OR_ID_MISSING_IRQ] = {
.name = "bat-therm-or-id-missing",
.handler = sw5100_batt_psy_changed_irq_handler,
},
[BAT_LOW_IRQ] = {
.name = "bat-low",
.handler = sw5100_batt_psy_changed_irq_handler,
},
[BAT_OV_IRQ] = {
.name = "bat-ov",
.handler = sw5100_batt_psy_changed_irq_handler,
},
[BSM_ACTIVE_IRQ] = {
.name = "bsm-active",
},
};
static int sw5100_get_irq_index_byname(const char *irq_name)
{
int i;
for (i = 0; i < ARRAY_SIZE(sw5100_bms_irqs); i++) {
if (!sw5100_bms_irqs[i].name)
continue;
if (strcmp(sw5100_bms_irqs[i].name, irq_name) == 0)
return i;
}
return -ENOENT;
}
static int sw5100_request_interrupt(struct bms_dev *bms,
struct device_node *node,
const char *irq_name)
{
int rc, irq, irq_index;
struct smb_irq_data *irq_data;
irq = of_irq_get_byname(node, irq_name);
if (irq < 0) {
pr_err("Couldn't get irq %s byname\n", irq_name);
return irq;
}
irq_index = sw5100_get_irq_index_byname(irq_name);
if (irq_index < 0) {
pr_err("%s is not a defined irq\n", irq_name);
return irq_index;
}
if (!sw5100_bms_irqs[irq_index].handler)
return 0;
irq_data = devm_kzalloc(bms->dev, sizeof(*irq_data), GFP_KERNEL);
if (!irq_data)
return -ENOMEM;
irq_data->parent_data = bms;
irq_data->name = irq_name;
irq_data->storm_data = sw5100_bms_irqs[irq_index].storm_data;
mutex_init(&irq_data->storm_data.storm_lock);
rc = devm_request_threaded_irq(bms->dev, irq, NULL,
sw5100_bms_irqs[irq_index].handler,
IRQF_ONESHOT, irq_name, irq_data);
if (rc < 0) {
pr_err("Couldn't request irq %d\n", irq);
return rc;
}
sw5100_bms_irqs[irq_index].irq = irq;
sw5100_bms_irqs[irq_index].irq_data = irq_data;
if (sw5100_bms_irqs[irq_index].wake)
enable_irq_wake(irq);
return rc;
}
static void sw5100_free_interrupts(struct bms_dev *bms)
{
int i;
for (i = 0; i < ARRAY_SIZE(sw5100_bms_irqs); i++) {
if (sw5100_bms_irqs[i].irq > 0) {
if (sw5100_bms_irqs[i].wake)
disable_irq_wake(sw5100_bms_irqs[i].irq);
devm_free_irq(bms->dev, sw5100_bms_irqs[i].irq, sw5100_bms_irqs[i].irq_data);
}
}
}
static void sw5100_disable_interrupts(struct bms_dev *bms)
{
int i;
for (i = 0; i < ARRAY_SIZE(sw5100_bms_irqs); i++) {
if (sw5100_bms_irqs[i].irq > 0)
disable_irq(sw5100_bms_irqs[i].irq);
}
}
static int sw5100_request_interrupts(struct bms_dev *bms)
{
struct device_node *node = bms->dev->of_node;
struct device_node *child;
int rc = 0;
const char *name;
struct property *prop;
for_each_available_child_of_node(node, child) {
of_property_for_each_string(child, "interrupt-names", prop, name) {
rc = sw5100_request_interrupt(bms, child, name);
if (rc < 0)
return rc;
}
}
return 0;
}
#define BID_RPULL_OHM 100000
#define BID_VREF_MV 1875
static void sw5100_get_batt_id(const struct bms_dev *bms, int *batt_id_ohm)
{
int rc, batt_id_mv;
/* Read battery-id */
rc = iio_read_channel_processed(bms->batt_id_chan, &batt_id_mv);
if (rc < 0) {
pr_err("Failed to read BATT_ID over ADC, rc=%d\n", rc);
return;
}
batt_id_mv = div_s64(batt_id_mv, 1000);
if (batt_id_mv == 0) {
pr_info("batt_id_mv = 0 from ADC\n");
return;
}
*batt_id_ohm = (u32)batt_id_mv;
pr_info("batt_id = %d\n", *batt_id_ohm);
}
#define QBG_MAIN_LAST_BURST_AVG_ACC2_DATA0 0xA4
#define IBATT_10A_LSB 6103
#define ICHG_FS_10A 1
#define TEN_NANO_TO_MICRO 100
static int sw5100_get_battery_current(const struct bms_dev *bms, int *val)
{
int rc = 0;
unsigned short acc2_data;
u8 buf[2];
const unsigned long addr = bms->rradc_base + QBG_MAIN_LAST_BURST_AVG_ACC2_DATA0;
rc = sw5100_read(bms->pmic_regmap, addr, buf, 2);
if (rc < 0) {
pr_err("Failed to read LAST_BURST_AVG_I reg, rc=%d\n", rc);
return rc;
}
acc2_data = buf[0] | (buf[1] << 8);
*val = ((int16_t)acc2_data) * IBATT_10A_LSB * ICHG_FS_10A;
*val = *val / TEN_NANO_TO_MICRO;
return rc;
}
#define QBG_MAIN_LAST_BURST_AVG_ACC0_DATA0 0xA0
#define VBATT_1S_LSB 19463
static int sw5100_get_battery_voltage(const struct bms_dev *bms, int *val)
{
int rc = 0;
unsigned short acc0_data;
u8 buf[2];
const unsigned long addr = bms->rradc_base + QBG_MAIN_LAST_BURST_AVG_ACC0_DATA0;
rc = sw5100_read(bms->pmic_regmap, addr, buf, 2);
if (rc < 0) {
pr_err("Failed to read LAST_ADV_V reg, rc=%d\n", rc);
return rc;
}
acc0_data = buf[0] | (buf[1] << 8);
*val = acc0_data * VBATT_1S_LSB;
*val = *val / TEN_NANO_TO_MICRO;
return rc;
}
static int sw5100_get_battery_temp(const struct bms_dev *bms, int *val)
{
int rc = 0;
if (!bms->rradc_base)
return -EIO;
rc = iio_read_channel_processed(bms->batt_therm_chan, val);
if (rc < 0) {
pr_err("Failed reading BAT_TEMP over ADC rc=%d\n", rc);
return rc;
}
return rc;
}
#define sw5100_IS_ONLINE(stat) \
(((stat) & (USE_DCIN_BIT | USE_USBIN_BIT)) && \
((stat) & VALID_INPUT_POWER_SOURCE_STS_BIT))
/* charger online when connected */
static bool sw5100_is_online(const struct bms_dev *bms)
{
u8 stat;
const int rc = sw5100_read(bms->pmic_regmap, DCDC_POWER_PATH_STATUS_REG, &stat, 1);
return (rc == 0) && sw5100_IS_ONLINE(stat);
}
static int sw5100_is_limited(const struct bms_dev *bms)
{
int rc;
u8 val;
rc = sw5100_read(bms->pmic_regmap, DCDC_AICL_STATUS_REG, &val, 1);
return (rc < 0) ? -EIO : ((val & DCDC_SOFT_ILIMIT_BIT) != 0);
}
static int sw5100_get_chg_type(const struct bms_dev *bms)
{
u8 val;
int chg_type, rc;
rc = sw5100_read(bms->pmic_regmap, CHGR_BATTERY_CHARGER_STATUS_REG, &val, 1);
if (rc < 0)
return POWER_SUPPLY_CHARGE_TYPE_UNKNOWN;
switch (val & CHGR_BATTERY_CHARGER_STATUS_MASK) {
case SW5100_TRICKLE_CHARGE:
case SW5100_PRE_CHARGE:
chg_type = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
break;
case SW5100_FULLON_CHARGE:
chg_type = POWER_SUPPLY_CHARGE_TYPE_FAST;
break;
case SW5100_TAPER_CHARGE:
chg_type = POWER_SUPPLY_CHARGE_TYPE_TAPER_EXT;
break;
default:
chg_type = POWER_SUPPLY_CHARGE_TYPE_NONE;
break;
}
return chg_type;
}
static int sw5100_get_chg_status(const struct bms_dev *bms,
bool *dc_valid, bool *usb_valid)
{
bool plugged, valid;
int rc, ret;
int vchrg = 0;
int vlimit = bms->chg_term_voltage;
u8 pstat, stat1, stat2;
u8 suspend;
rc = sw5100_rd8(bms->pmic_regmap, DCDC_POWER_PATH_STATUS_REG, &pstat);
if (rc < 0)
return POWER_SUPPLY_STATUS_UNKNOWN;
valid = (pstat & VALID_INPUT_POWER_SOURCE_STS_BIT);
plugged = (pstat & USE_DCIN_BIT) || (pstat & USE_USBIN_BIT);
*dc_valid = valid && (pstat & USE_DCIN_BIT);
*usb_valid = valid && (pstat & USE_USBIN_BIT);
rc = sw5100_rd8(bms->pmic_regmap, CHGR_BATTERY_CHARGER_STATUS_REG, &stat1);
if (rc < 0)
return POWER_SUPPLY_STATUS_UNKNOWN;
rc = sw5100_rd8(bms->pmic_regmap, CHGR_BATTERY_CHARGER_EN_STATUS_REG, &stat2);
if (rc < 0)
return POWER_SUPPLY_STATUS_UNKNOWN;
pr_debug("pmic: pstat=%x stat=%x enstat=%x\n",
pstat, stat1, stat2);
stat1 = stat1 & CHGR_BATTERY_CHARGER_STATUS_MASK;
if (!plugged)
return POWER_SUPPLY_STATUS_DISCHARGING;
switch (stat1) {
case SW5100_TRICKLE_CHARGE:
case SW5100_PRE_CHARGE:
case SW5100_FULLON_CHARGE:
case SW5100_TAPER_CHARGE:
ret = POWER_SUPPLY_STATUS_CHARGING;
break;
/* pause on FCC=0, JEITA, USB/DC suspend or on INPUT UV/OV */
case SW5100_PAUSE_CHARGE:
case SW5100_INHIBIT_CHARGE:
ret = POWER_SUPPLY_STATUS_NOT_CHARGING;
break;
case SW5100_TERMINATE_CHARGE:
/* flag full only at the correct voltage */
rc = sw5100_get_battery_voltage(bms, &vchrg);
if (rc == 0)
vchrg = (vchrg / 1000);
if (stat1 == SW5100_TERMINATE_CHARGE)
vlimit -= bms->chg_term_voltage_debounce;
if (vchrg < vlimit)
ret = POWER_SUPPLY_STATUS_NOT_CHARGING;
else
ret = POWER_SUPPLY_STATUS_FULL;
break;
/* disabled disconnect */
case SW5100_DISABLE_CHARGE:
rc = sw5100_rd8(bms->pmic_regmap, CHGR_USB_SUSPEND, &suspend);
if (rc < 0)
return POWER_SUPPLY_STATUS_UNKNOWN;
if ((suspend & USBIN_SUSPEND) == USBIN_SUSPEND) {
ret = POWER_SUPPLY_STATUS_DISCHARGING;
} else {
ret = POWER_SUPPLY_STATUS_NOT_CHARGING;
}
break;
default:
ret = POWER_SUPPLY_STATUS_UNKNOWN;
break;
}
if (ret != POWER_SUPPLY_STATUS_CHARGING)
return ret;
if (valid) {
u8 stat;
rc = sw5100_rd8(bms->pmic_regmap,
CHGR_BATTERY_CHARGER_EN_STATUS_REG,
&stat);
if (rc < 0)
return POWER_SUPPLY_STATUS_UNKNOWN;
stat &= ENABLE_TRICKLE_BIT | ENABLE_PRE_CHARGING_BIT |
ENABLE_FULLON_MODE_BIT;
if (stat)
return POWER_SUPPLY_STATUS_CHARGING;
}
return POWER_SUPPLY_STATUS_NOT_CHARGING;
}
static int sw5100_get_chg_chgr_state(const struct bms_dev *bms,
union gbms_charger_state *chg_state)
{
int vchrg, rc;
bool usb_valid, dc_valid;
u8 icl = 0;
chg_state->v = 0;
chg_state->f.chg_status = sw5100_get_chg_status(bms, &dc_valid, &usb_valid);
chg_state->f.chg_type = sw5100_get_chg_type(bms);
chg_state->f.flags = gbms_gen_chg_flags(chg_state->f.chg_status, chg_state->f.chg_type);
rc = sw5100_is_limited(bms);
if (rc > 0)
chg_state->f.flags |= GBMS_CS_FLAG_ILIM;
rc = sw5100_get_battery_voltage(bms, &vchrg);
if (rc == 0)
chg_state->f.vchrg = (vchrg / 1000);
if (usb_valid) {
(void)sw5100_rd8(bms->pmic_regmap, DCDC_ICL_STATUS_REG, &icl);
}
chg_state->f.icl = (icl * 100);
pr_info("MSC_PCS chg_state=%lx [0x%x:%d:%d:%d:%d] chg=%c\n",
(unsigned long)chg_state->v,
chg_state->f.flags,
chg_state->f.chg_type,
chg_state->f.chg_status,
chg_state->f.vchrg,
chg_state->f.icl,
usb_valid ? 'u' : dc_valid ? 'w' : ' ');
return 0;
}
static int sw5100_get_batt_health(struct bms_dev *bms)
{
int vchrg, effective_fv_uv, rc, ret;
u8 stat;
rc = sw5100_rd8(bms->pmic_regmap, CHGR_BATTERY_CHARGER_STATUS_REG, &stat);
if (rc < 0) {
pr_err("Couldn't read CHGR_BATTERY_CHARGER_STATUS_REG rc=%d\n", rc);
return POWER_SUPPLY_HEALTH_UNKNOWN;
}
if (stat & CHG_ERR_STATUS_BAT_OV) {
rc = sw5100_get_battery_voltage(bms, &vchrg);
if (rc == 0) {
/*
* If Vbatt is within 40mV above Vfloat, then don't
* treat it as overvoltage.
*/
if (!bms->fv_votable)
bms->fv_votable = find_votable(VOTABLE_MSC_FV);
if (bms->fv_votable) {
effective_fv_uv = get_effective_result(bms->fv_votable);
if (vchrg >= effective_fv_uv + 40000) {
ret = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
pr_err("battery over-voltage vbat_fg = %duV, fv = %duV\n",
vchrg, effective_fv_uv);
goto done;
}
}
}
}
rc = sw5100_rd8(bms->pmic_regmap, BATIF_BAT_TEMP_STATUS_REG, &stat);
if (rc < 0) {
pr_err("Couldn't read BATIF_BAT_TEMP_STATUS_REG rc=%d\n", rc);
return POWER_SUPPLY_HEALTH_UNKNOWN;
}
if (stat & BAT_TEMP_TOO_COLD)
ret = POWER_SUPPLY_HEALTH_COLD;
else if (stat & BAT_TEMP_TOO_HOT)
ret = POWER_SUPPLY_HEALTH_OVERHEAT;
else if (stat & BAT_TEMP_COOL)
ret = POWER_SUPPLY_HEALTH_COOL;
else if (stat & BAT_TEMP_WARM)
ret = POWER_SUPPLY_HEALTH_WARM;
else
ret = POWER_SUPPLY_HEALTH_GOOD;
done:
return ret;
}
static int sw5100_get_batt_iterm(struct bms_dev *bms)
{
int rc, temp;
u8 stat, buf[2];
rc = sw5100_rd8(bms->pmic_regmap, CHGR_CHG_TERM_CFG_REG, &stat);
if (rc < 0) {
pr_err("Couldn't read CHGR_CHG_TERM_CFG_REG rc=%d\n", rc);
return rc;
}
if (stat & CHGR_ITERM_USE_ANALOG_BIT)
return -EINVAL;
rc = sw5100_read(bms->pmic_regmap, CHGR_ADC_ITERM_UP_THD_MSB, buf, 2);
if (rc < 0) {
pr_err("Couldn't read CHGR_ADC_ITERM_UP_THD_MSB rc=%d\n", rc);
return rc;
}
temp = buf[1] | (buf[0] << 8);
temp = sign_extend32(temp, 15);
temp = DIV_ROUND_CLOSEST(temp * 1000, PM5100_ADC_CHG_ITERM_MULT);
return temp;
}
static int sw5100_get_batt_present(struct bms_dev *bms)
{
int rc, ret;
u8 stat;
rc = sw5100_rd8(bms->pmic_regmap,
BATIF_INT_RT_STS,
&stat);
if (rc < 0) {
pr_err("Couldn't read BATIF_INT_RT_STS rc=%d\n", rc);
return rc;
}
ret = !(stat & (BATIF_THERM_OR_ID_MISSING_RT_STS_BIT));
return ret;
}
/** Given a SOC percentage aka capacity we're going to scale 5-100 to 0-100. */
static int scale_capacity(struct bms_dev const *bms, int capacity)
{
if ((bms->soc_shutdown_offset > 0) && (bms->soc_shutdown_offset < 100)) {
if (capacity >= 100) {
return 100;
} else if (capacity >= bms->soc_shutdown_offset) {
return DIV_ROUND_UP(((capacity - bms->soc_shutdown_offset) * 100),
(100 - bms->soc_shutdown_offset));
} else {
return 0;
}
} else {
return capacity;
}
}
static int sw5100_psy_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *pval)
{
struct bms_dev *bms = (struct bms_dev *)power_supply_get_drvdata(psy);
union gbms_charger_state chg_state;
u8 val;
int ivalue = 0;
int rc = 0;
bool usb_valid, dc_valid;
if (!bms->psy) {
pr_err("failed to register power supply\n");
return -EAGAIN;
}
switch ((int) psp) {
/*
* called from power_supply_update_leds(), not using it on this
* platform. Could return the state of the charge buck (BUCKEN)
*/
case POWER_SUPPLY_PROP_ONLINE:
pval->intval = sw5100_is_online(bms);
break;
case POWER_SUPPLY_PROP_STATUS:
pval->intval = sw5100_get_chg_status(bms, &dc_valid, &usb_valid);
break;
/* pixel battery management subsystem */
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
/*
* CHGR_FAST_CHARGE_CURRENT_SETTING, 0x2654
* 7 : 0 => FAST_CHARGE_CURRENT_SETTING:
* Fast Charge Current = DATA x 25mA
*/
rc = sw5100_read(bms->pmic_regmap, CHGR_FAST_CHARGE_CURRENT_SETTING, &val, 1);
if (rc == 0)
pval->intval = val * CHGR_CHARGE_CURRENT_STEP;
break;
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
/*
* CHGR_FLOAT_VOLTAGE_SETTING 0x2658
* 7 : 0 => FLOAT_VOLTAGE_SETTING:
* Float voltage setting = 3.6V + (DATA x 10mV)
*/
rc = sw5100_read(bms->pmic_regmap, CHGR_FLOAT_VOLTAGE_SETTING, &val, 1);
if (rc == 0)
pval->intval = val * 10000 + CHGR_FLOAT_VOLTAGE_BASE;
break;
case GBMS_PROP_CHARGE_CHARGER_STATE:
rc = sw5100_get_chg_chgr_state(bms, &chg_state);
if (rc == 0)
gbms_propval_int64val(pval) = chg_state.v;
break;
case POWER_SUPPLY_PROP_CHARGE_TYPE:
pval->intval = sw5100_get_chg_type(bms);
break;
case POWER_SUPPLY_PROP_CURRENT_NOW:
rc = sw5100_get_battery_current(bms, &ivalue);
if (rc == 0)
pval->intval = ivalue;
break;
case GBMS_PROP_INPUT_CURRENT_LIMITED:
rc = sw5100_is_limited(bms);
if (rc < 0)
break;
pval->intval = (rc > 0);
break;
case POWER_SUPPLY_PROP_TEMP:
rc = sw5100_get_battery_temp(bms, &ivalue);
if (rc < 0)
break;
pval->intval = ivalue;
break;
case POWER_SUPPLY_PROP_VOLTAGE_MAX:
/*
* CHGR_FLOAT_VOLTAGE_NOW 0x260B
* 7 : 0 => FLOAT_VOLTAGE:
* Float voltage after JEITA compensation
*/
rc = sw5100_read(bms->pmic_regmap, CHGR_FLOAT_VOLTAGE_NOW,
&val, 1);
if (rc == 0)
pval->intval = val * 10000 + CHGR_FLOAT_VOLTAGE_BASE;
break;
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
rc = sw5100_get_battery_voltage(bms, &ivalue);
if (!rc)
pval->intval = ivalue;
break;
case GBMS_PROP_CHARGE_DISABLE:
rc = sw5100_read(bms->pmic_regmap, CHGR_CHG_EN, &val, 1);
if (rc == 0)
pval->intval = !(val & CHARGING_ENABLE_CMD_BIT);
break;
case POWER_SUPPLY_PROP_HEALTH:
pval->intval = sw5100_get_batt_health(bms);
break;
case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT:
pval->intval = sw5100_get_batt_iterm(bms);
break;
case POWER_SUPPLY_PROP_PRESENT:
pval->intval = sw5100_get_batt_present(bms);
break;
case GBMS_PROP_CAPACITY_RAW:
// First query for monotonic SOC value.
rc = sw5100_get_prop_from_bms(bms, SW5100_QBG_CAPACITY, &ivalue);
if (rc == 0) {
// By default we use the monotonic SOC; this prevents
// any erroneous 0 SOC values due to any of the
// following calls failing.
pval->intval = (scale_capacity(bms, ivalue) << 8);
if (ivalue == 100) {
// monotonic SOC is 100%, now check if
// battery offline and idling
rc = sw5100_get_prop_from_bms(bms, SW5100_QBG_CURRENT_NOW, &ivalue);
if (rc == 0 && ivalue == 0) {
// use sys_soc as our SOC for
// recharge tracking.
rc = sw5100_get_prop_from_bms(bms, SW5100_QBG_REAL_CAPACITY, &ivalue);
if (rc == 0) {
pval->intval = (scale_capacity(bms, ivalue) << 8);
}
}
}
}
break;
case POWER_SUPPLY_PROP_CAPACITY:
rc = sw5100_get_prop_from_bms(bms, SW5100_QBG_CAPACITY, &ivalue);
if (rc == 0)
pval->intval = scale_capacity(bms, ivalue);
break;
case POWER_SUPPLY_PROP_CYCLE_COUNT:
rc = sw5100_get_prop_from_bms(bms, SW5100_QBG_CYCLE_COUNT, &ivalue);
if (rc == 0)
pval->intval = ivalue;
/* TODO(b/243407602): fix cycle count, but for now set it to 0 as a workaround */
if (pval->intval < 0)
pval->intval = 0;
break;
case POWER_SUPPLY_PROP_VOLTAGE_AVG:
rc = sw5100_get_prop_from_bms(bms, SW5100_QBG_VOLTAGE_AVG, &ivalue);
if (rc == 0)
pval->intval = ivalue;
break;
case POWER_SUPPLY_PROP_VOLTAGE_OCV:
rc = sw5100_get_prop_from_bms(bms, SW5100_QBG_VOLTAGE_OCV, &ivalue);
if (rc == 0)
pval->intval = ivalue;
break;
case POWER_SUPPLY_PROP_CHARGE_FULL:
rc = sw5100_get_prop_from_bms(bms, SW5100_QBG_CHARGE_FULL, &ivalue);
if (rc == 0)
pval->intval = ivalue;
break;
case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
rc = sw5100_get_prop_from_bms(bms, SW5100_QBG_CHARGE_FULL_DESIGN, &ivalue);
if (rc == 0)
pval->intval = ivalue;
break;
case POWER_SUPPLY_PROP_CHARGE_COUNTER:
rc = sw5100_get_prop_from_bms(bms, SW5100_QBG_CHARGE_COUNTER, &ivalue);
if (rc == 0)
pval->intval = ivalue;
break;
case POWER_SUPPLY_PROP_TIME_TO_FULL_NOW:
rc = sw5100_get_prop_from_bms(bms, SW5100_QBG_TIME_TO_FULL_NOW, &ivalue);
if (rc == 0)
pval->intval = ivalue;
break;
case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG:
rc = sw5100_get_prop_from_bms(bms, SW5100_QBG_TIME_TO_EMPTY_AVG, &ivalue);
if (rc == 0)
pval->intval = ivalue;
break;
case POWER_SUPPLY_PROP_CURRENT_AVG:
rc = sw5100_get_battery_current(bms, &ivalue);
if (rc == 0)
pval->intval = ivalue;
break;
case POWER_SUPPLY_PROP_TECHNOLOGY:
pval->intval = POWER_SUPPLY_TECHNOLOGY_LION;
break;
case POWER_SUPPLY_PROP_SERIAL_NUMBER:
pval->strval = "";
break;
case GBMS_PROP_HEALTH_ACT_IMPEDANCE:
pval->intval = -EINVAL;
break;
case GBMS_PROP_RESISTANCE:
pval->intval = 0;
break;
default:
pr_debug("getting unsupported property: %d\n", psp);
return -EINVAL;
}
if (rc < 0)
return -ENODATA;
return 0;
}
static int sw5100_usbin_disable(struct bms_dev *bms, bool disable)
{
int rc;
if (!bms->icl_votable) {
bms->icl_votable = find_votable("USB_ICL");
if (bms->icl_votable == NULL) {
pr_err("USBIN_DISABLE: disable failed\n");
return -EINVAL;
}
}
rc = vote(bms->icl_votable, USBIN_DISABLE_VOTER, disable, 0);
pr_debug("USBIN_DISABLE : disable=%d, rc=%d)\n", disable, rc);
if (rc > 0) {
/* vote returns positive number on success */
rc = 0;
}
return rc;
}
static int sw5100_charge_disable(struct bms_dev *bms, bool disable)
{
const u8 val = disable ? 0 : CHARGING_ENABLE_CMD_BIT;
int rc;
rc = sw5100_masked_write(bms->pmic_regmap, CHGR_CHG_EN, CHARGING_ENABLE_CMD_BIT, val);
if (!disable) {
/* Make sure charging is restarted by toggling usbin */
if (rc == 0)
rc = sw5100_usbin_disable(bms, true);
if (rc == 0)
rc = sw5100_usbin_disable(bms, false);
}
pr_debug("CHARGE_DISABLE : disable=%d -> val=%d (%d)\n", disable, val, rc);
return rc;
}
static int sw5100_charge_pause(struct bms_dev *bms, bool pause)
{
const u8 val = pause ? CHARGING_PAUSE_CMD_BIT : 0;
int rc;
rc = sw5100_masked_write(bms->pmic_regmap, CHGR_CHARGING_PAUSE_CMD, CHARGING_PAUSE_CMD_BIT, val);
pr_debug("CHARGE_PAUSE : pause=%d -> val=%d (%d)\n",
pause, val, rc);
return rc;
}
static ssize_t soc_shutdown_offset_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct power_supply *psy = container_of(dev, struct power_supply, dev);
struct bms_dev *bms = power_supply_get_drvdata(psy);
sscanf(buf, "%d", &bms->soc_shutdown_offset);
return count;
}
static ssize_t soc_shutdown_offset_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct power_supply *psy = container_of(dev, struct power_supply, dev);
struct bms_dev *bms = power_supply_get_drvdata(psy);
return scnprintf(buf, PAGE_SIZE, "%d\n", bms->soc_shutdown_offset);
}
static const DEVICE_ATTR_RW(soc_shutdown_offset);
static int sw5100_psy_set_property(struct power_supply *psy,
enum power_supply_property psp,
const union power_supply_propval *pval)
{
struct bms_dev *bms = (struct bms_dev *)power_supply_get_drvdata(psy);
u8 val;
int ivalue = 0;
int rc = 0;
switch ((int) psp) {
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
/*
* CHGR_FAST_CHARGE_CURRENT_SETTING, 0x2654
* 7 : 0 => FAST_CHARGE_CURRENT_SETTING:
* Fast Charge Current = DATA x 25mA
*/
ivalue = pval->intval;
if (ivalue < CHGR_CHARGE_CURRENT_STEP)
val = 0;
else
val = ivalue / CHGR_CHARGE_CURRENT_STEP;
if (ivalue == 0)
rc = sw5100_charge_pause(bms, true);
rc = sw5100_write(bms->pmic_regmap, CHGR_FAST_CHARGE_CURRENT_SETTING, &val, 1);
if (ivalue != 0) {
u8 paused;
rc = sw5100_read(bms->pmic_regmap, CHGR_CHARGING_PAUSE_CMD, &paused, 1);
if (rc == 0 && (paused & CHARGING_PAUSE_CMD_BIT)) {
rc = sw5100_charge_pause(bms, false);
/* make sure charging restart */
if (rc == 0)
rc = sw5100_charge_disable(bms, true);
if (rc == 0)
rc = sw5100_charge_disable(bms, false);
if (rc < 0)
pr_err("Failed to toggle charging during charging restart\n");
}
}
pr_debug("CONSTANT_CHARGE_CURRENT_MAX : ivalue=%d, val=%d pause=%d (%d)\n",
ivalue, val, ivalue == 0, rc);
break;
case POWER_SUPPLY_PROP_VOLTAGE_MAX:
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
/*
* CHGR_FLOAT_VOLTAGE_SETTING 0x2658
* 7 : 0 => FLOAT_VOLTAGE_SETTING:
* Float voltage setting = 3.6V + (DATA x 10mV)
*/
ivalue = pval->intval;
if (ivalue < CHGR_FLOAT_VOLTAGE_BASE) {
pr_err("CONSTANT_CHARGE_VOLTAGE_MAX : %d (ivalue) < %d (base). Ignoring\n",
ivalue, CHGR_FLOAT_VOLTAGE_BASE);
rc = -EINVAL;
break;
}
else
val = (ivalue - CHGR_FLOAT_VOLTAGE_BASE) / 10000;
rc = sw5100_write(bms->pmic_regmap, CHGR_FLOAT_VOLTAGE_SETTING, &val, 1);
pr_debug("CONSTANT_CHARGE_VOLTAGE_MAX : ivalue=%d, val=%d (%d)\n",
ivalue, val, rc);
break;
case GBMS_PROP_CHARGE_DISABLE:
if (!bms->fcc_votable)
bms->fcc_votable = find_votable(VOTABLE_MSC_FCC);
if (bms->fcc_votable)
vote(bms->fcc_votable, CHARGE_DISABLE_VOTER,
pval->intval, 0);
rc = sw5100_charge_disable(bms, pval->intval != 0);
break;
default:
pr_debug("setting unsupported property: %d\n", psp);
break;
}
return rc;
}
static int sw5100_property_is_writeable(struct power_supply *psy,
enum power_supply_property psp)
{
switch ((int) psp) {
case POWER_SUPPLY_PROP_VOLTAGE_MAX:
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
case GBMS_PROP_CHARGE_DISABLE:
return 1;
default:
break;
}
return 0;
}
static enum power_supply_property sw5100_psy_props[] = {
POWER_SUPPLY_PROP_STATUS,
/* pixel battery management subsystem */
POWER_SUPPLY_PROP_HEALTH,
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX,
POWER_SUPPLY_PROP_CHARGE_TYPE,
POWER_SUPPLY_PROP_CURRENT_NOW,
POWER_SUPPLY_PROP_ONLINE,
POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT,
POWER_SUPPLY_PROP_TEMP,
POWER_SUPPLY_PROP_VOLTAGE_MAX, /* compat */
POWER_SUPPLY_PROP_VOLTAGE_AVG,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
POWER_SUPPLY_PROP_VOLTAGE_OCV,
POWER_SUPPLY_PROP_CAPACITY,
POWER_SUPPLY_PROP_CYCLE_COUNT,
POWER_SUPPLY_PROP_CHARGE_FULL,
POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
POWER_SUPPLY_PROP_CHARGE_COUNTER,
POWER_SUPPLY_PROP_CURRENT_AVG,
POWER_SUPPLY_PROP_TIME_TO_FULL_NOW,
POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG,
POWER_SUPPLY_PROP_TECHNOLOGY,
POWER_SUPPLY_PROP_SERIAL_NUMBER,
};
static struct power_supply_desc sw5100_psy_desc = {
.name = "sw5100_bms",
.type = POWER_SUPPLY_TYPE_UNKNOWN,
.properties = sw5100_psy_props,
.num_properties = ARRAY_SIZE(sw5100_psy_props),
.get_property = sw5100_psy_get_property,
.set_property = sw5100_psy_set_property,
.property_is_writeable = sw5100_property_is_writeable,
};
/* All callback functions below */
static int sw5100_notifier_cb(struct notifier_block *nb,
unsigned long event, void *data)
{
if (event != PSY_EVENT_PROP_CHANGED)
return NOTIFY_OK;
/* TBD: notification?*/
return NOTIFY_OK;
}
static int sw5100_dc_suspend_vote_callback(struct votable *votable, void *data,
int disable, const char *client)
{
/* DC suspend isn't supported but function is needed for compatibility */
return 0;
}
/* All init functions below this */
#define PERPH_TYPE_REG 0x04
#define QG_TYPE 0x0D
static int sw5100_parse_dt_fg(struct bms_dev *bms, struct device_node *node)
{
int rc = 0;
u32 val;
rc = of_property_read_u32(node, "reg", &val);
if (rc < 0) {
pr_err("Failed to get base address for QBG, rc = %d\n", rc);
return rc;
}
bms->rradc_base = val;
return rc;
}
static int sw5100_parse_dt(struct bms_dev *bms)
{
struct device_node *fg_node, *node = bms->dev->of_node;
const char *psy_name = NULL;
int ret;
if (!node) {
pr_err("device tree node missing\n");
return -ENXIO;
}
fg_node = of_get_parent(node);
if (fg_node)
fg_node = of_get_child_by_name(fg_node, "qpnp,qbg");
if (fg_node)
sw5100_parse_dt_fg(bms, fg_node);
else
pr_err("cannot find qpnp,qbg, rradc not available\n");
ret = of_property_read_u32(node, "google,chg-term-voltage", &bms->chg_term_voltage);
if (ret < 0)
bms->chg_term_voltage = CHG_TERM_VOLTAGE;
ret = of_property_read_u32(node, "google,chg-term-voltage-debounce", &bms->chg_term_voltage_debounce);
if (ret < 0)
bms->chg_term_voltage_debounce = CHG_TERM_VOLT_DEBOUNCE;
ret = of_property_read_string(node, "google,psy-name", &psy_name);
if (ret == 0)
sw5100_psy_desc.name =
devm_kstrdup(bms->dev, psy_name, GFP_KERNEL);
ret = of_property_read_u32(node, "google,soc_shutdown_offset", &bms->soc_shutdown_offset);
if (ret < 0)
bms->soc_shutdown_offset = 0;
if (sw5100_psy_desc.name == NULL)
return -EINVAL;
return 0;
}
static int bms_probe(struct platform_device *pdev)
{
struct bms_dev *bms;
struct power_supply_config bms_psy_cfg = {};
struct iio_channel **iio_list;
int rc = 0;
bms = devm_kzalloc(&pdev->dev, sizeof(*bms), GFP_KERNEL);
if (!bms) {
pr_info("kalloc error\n");
return -ENOMEM;
}
bms->dev = &pdev->dev;
bms->batt_id_ohms = -EINVAL;
bms->pmic_regmap = dev_get_regmap(bms->dev->parent, NULL);
if (!bms->pmic_regmap) {
pr_err("Parent regmap is unavailable\n");
} else {
/* ADC for BID & THERM */
bms->batt_id_chan = iio_channel_get(&pdev->dev, "batt-id");
if (IS_ERR(bms->batt_id_chan)) {
rc = PTR_ERR(bms->batt_id_chan);
if (rc != -EPROBE_DEFER)
pr_err("batt-id channel unavailable, rc=%d\n", rc);
bms->batt_id_chan = NULL;
return rc;
}
bms->batt_therm_chan = iio_channel_get(&pdev->dev, "batt-therm");
if (IS_ERR(bms->batt_therm_chan)) {
rc = PTR_ERR(bms->batt_therm_chan);
if (rc != -EPROBE_DEFER)
pr_err("batt-therm channel unavailable, rc=%d\n", rc);
bms->batt_therm_chan = NULL;
return rc;
}
sw5100_get_batt_id(bms, &bms->batt_id_ohms);
/*
* If AICL collapses, do not "latch-off" (i.e. do not require
* that a user take the device off-charger and place it back
* on-charger in order to attempt charging again).
*/
rc = sw5100_masked_write(bms->pmic_regmap, CHGR_USB_SUSPEND,
SUSPEND_ON_COLLAPSE_USBIN, 0);
if (rc != 0) {
dev_err(bms->dev,
"Could not disable USBIN latch-off, rc=%d\n", rc);
}
}
rc = sw5100_parse_dt(bms);
if (rc < 0) {
pr_err("Parse the device tree fail. rc = %d\n", rc);
goto exit;
}
/* Register the power supply */
bms_psy_cfg.drv_data = bms;
bms_psy_cfg.of_node = bms->dev->of_node;
bms_psy_cfg.supplied_to = NULL;
bms_psy_cfg.num_supplicants = 0;
bms->psy = devm_power_supply_register(bms->dev, &sw5100_psy_desc,
&bms_psy_cfg);
if (IS_ERR(bms->psy)) {
pr_err("failed to register psy rc = %ld\n", PTR_ERR(bms->psy));
goto exit;
}
rc = device_create_file(&bms->psy->dev, &dev_attr_soc_shutdown_offset);
if (rc < 0)
dev_err(&bms->psy->dev, "Failed to create soc scaling offset for shutdown\n");
iio_list = sw5100_get_ext_channels(bms->dev, sw5100_qbg_ext_iio_chan,
ARRAY_SIZE(sw5100_qbg_ext_iio_chan));
if (!IS_ERR(iio_list))
bms->iio_chan_list_qg = iio_list;
bms->nb.notifier_call = sw5100_notifier_cb;
rc = power_supply_reg_notifier(&bms->nb);
if (rc < 0) {
pr_err("Couldn't register psy notifier rc = %d\n", rc);
goto exit;
}
rc = sw5100_request_interrupts(bms);
if (rc < 0) {
pr_err("Couldn't register the interrupts rc = %d\n", rc);
goto exit;
}
bms->dc_suspend_votable = create_votable("DC_SUSPEND", VOTE_SET_ANY,
sw5100_dc_suspend_vote_callback,
bms);
if (IS_ERR(bms->dc_suspend_votable)) {
rc = PTR_ERR(bms->dc_suspend_votable);
bms->dc_suspend_votable = NULL;
return rc;
}
pr_info("SW5100 BMS driver probed successfully\n");
return 0;
exit:
return rc;
}
static int bms_remove(struct platform_device *pdev)
{
struct bms_dev *bms = platform_get_drvdata(pdev);
sw5100_free_interrupts(bms);
platform_set_drvdata(pdev, NULL);
return 0;
}
static void bms_shutdown(struct platform_device *pdev)
{
struct bms_dev *bms = platform_get_drvdata(pdev);
/* disable all interrupts */
sw5100_disable_interrupts(bms);
}
static const struct of_device_id bms_of_match[] = {
{.compatible = "google,sw5100_bms"},
{},
};
static struct platform_driver sw5100_bms_driver = {
.driver = {
.name = BMS_DEV_NAME,
.owner = THIS_MODULE,
.of_match_table = bms_of_match,
},
.probe = bms_probe,
.remove = bms_remove,
.shutdown = bms_shutdown,
};
module_platform_driver(sw5100_bms_driver);
MODULE_DESCRIPTION("sw5100 BMS driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:" BMS_DEV_NAME);
MODULE_AUTHOR("Alice Sheng <[email protected]>");