1802 lines
43 KiB
C
1802 lines
43 KiB
C
/* Copyright (c) 2013-2014 The Linux Foundation. All rights reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 and
|
|
* only version 2 as published by the Free Software Foundation.
|
|
*
|
|
* 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 pr_fmt(fmt) "SMB349 %s: " fmt, __func__
|
|
#include <linux/i2c.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/gpio.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/module.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/power_supply.h>
|
|
#include <linux/regulator/driver.h>
|
|
#include <linux/regulator/of_regulator.h>
|
|
#include <linux/regulator/machine.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_gpio.h>
|
|
#include <linux/mutex.h>
|
|
#include "smb349-charger.h"
|
|
|
|
enum {
|
|
WRKARND_APSD_FAIL = BIT(0),
|
|
};
|
|
|
|
struct smb349_regulator {
|
|
struct regulator_desc rdesc;
|
|
struct regulator_dev *rdev;
|
|
};
|
|
|
|
struct smb349_charger {
|
|
struct i2c_client *client;
|
|
struct device *dev;
|
|
|
|
bool recharge_disabled;
|
|
int recharge_mv;
|
|
bool iterm_disabled;
|
|
int iterm_ma;
|
|
int vfloat_mv;
|
|
int chg_valid_gpio;
|
|
int chg_valid_act_low;
|
|
int chg_present;
|
|
int fake_battery_soc;
|
|
bool chg_autonomous_mode;
|
|
bool disable_apsd;
|
|
bool battery_missing;
|
|
const char *bms_psy_name;
|
|
struct completion resumed;
|
|
bool resume_completed;
|
|
bool irq_waiting;
|
|
|
|
/* status tracking */
|
|
bool batt_full;
|
|
bool batt_hot;
|
|
bool batt_cold;
|
|
bool batt_warm;
|
|
bool batt_cool;
|
|
|
|
int charging_disabled;
|
|
int fastchg_current_max_ma;
|
|
int workaround_flags;
|
|
|
|
struct power_supply *usb_psy;
|
|
struct power_supply *bms_psy;
|
|
struct power_supply batt_psy;
|
|
|
|
struct smb349_regulator otg_vreg;
|
|
struct mutex irq_complete;
|
|
|
|
struct dentry *debug_root;
|
|
u32 peek_poke_address;
|
|
};
|
|
|
|
struct smb_irq_info {
|
|
const char *name;
|
|
int (*smb_irq)(struct smb349_charger *chip,
|
|
u8 rt_stat);
|
|
int high;
|
|
int low;
|
|
};
|
|
|
|
struct irq_handler_info {
|
|
u8 stat_reg;
|
|
u8 val;
|
|
u8 prev_val;
|
|
struct smb_irq_info irq_info[4];
|
|
};
|
|
|
|
struct chg_current_map {
|
|
int chg_current_ma;
|
|
u8 value;
|
|
};
|
|
|
|
static int chg_current[] = {
|
|
500, 900, 1000, 1100, 1200, 1300, 1500, 1600,
|
|
1700, 1800, 2000, 2200, 2400, 2500, 3000, 3500,
|
|
};
|
|
|
|
static int smb349_read_reg(struct smb349_charger *chip, int reg, u8 *val)
|
|
{
|
|
s32 ret;
|
|
|
|
ret = i2c_smbus_read_byte_data(chip->client, reg);
|
|
if (ret < 0) {
|
|
dev_err(chip->dev,
|
|
"i2c read fail: can't read from %02x: %d\n", reg, ret);
|
|
return ret;
|
|
} else {
|
|
*val = ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int smb349_write_reg(struct smb349_charger *chip, int reg, u8 val)
|
|
{
|
|
s32 ret;
|
|
|
|
ret = i2c_smbus_write_byte_data(chip->client, reg, val);
|
|
if (ret < 0) {
|
|
dev_err(chip->dev,
|
|
"i2c write fail: can't write %02x to %02x: %d\n",
|
|
val, reg, ret);
|
|
return ret;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int smb349_masked_write(struct smb349_charger *chip, int reg,
|
|
u8 mask, u8 val)
|
|
{
|
|
s32 rc;
|
|
u8 temp;
|
|
|
|
rc = smb349_read_reg(chip, reg, &temp);
|
|
if (rc) {
|
|
dev_err(chip->dev,
|
|
"smb349_read_reg Failed: reg=%03X, rc=%d\n", reg, rc);
|
|
return rc;
|
|
}
|
|
temp &= ~mask;
|
|
temp |= val & mask;
|
|
rc = smb349_write_reg(chip, reg, temp);
|
|
if (rc) {
|
|
dev_err(chip->dev,
|
|
"smb349_write Failed: reg=%03X, rc=%d\n", reg, rc);
|
|
return rc;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int smb349_enable_volatile_writes(struct smb349_charger *chip)
|
|
{
|
|
int rc;
|
|
|
|
rc = smb349_masked_write(chip, CMD_A_REG, CMD_A_VOLATILE_W_PERM_BIT,
|
|
CMD_A_VOLATILE_W_PERM_BIT);
|
|
if (rc)
|
|
dev_err(chip->dev, "Couldn't write VOLATILE_W_PERM_BIT rc=%d\n",
|
|
rc);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int smb349_fastchg_current_set(struct smb349_charger *chip)
|
|
{
|
|
u8 temp;
|
|
|
|
if ((chip->fastchg_current_max_ma < SMB349_FAST_CHG_MIN_MA) ||
|
|
(chip->fastchg_current_max_ma > SMB349_FAST_CHG_MAX_MA)) {
|
|
dev_dbg(chip->dev, "bad fastchg current mA=%d asked to set\n",
|
|
chip->fastchg_current_max_ma);
|
|
return -EINVAL;
|
|
}
|
|
|
|
temp = (chip->fastchg_current_max_ma - SMB349_FAST_CHG_MIN_MA)
|
|
/ SMB349_FAST_CHG_STEP_MA;
|
|
|
|
temp = temp << SMB349_FAST_CHG_SHIFT;
|
|
dev_dbg(chip->dev, "fastchg limit=%d setting %02x\n",
|
|
chip->fastchg_current_max_ma, temp);
|
|
|
|
return smb349_masked_write(chip, CHG_CURRENT_CTRL_REG,
|
|
SMB_FAST_CHG_CURRENT_MASK, temp);
|
|
}
|
|
|
|
static int smb349_float_voltage_set(struct smb349_charger *chip, int vfloat_mv)
|
|
{
|
|
u8 temp;
|
|
|
|
if ((vfloat_mv < MIN_FLOAT_MV) || (vfloat_mv > MAX_FLOAT_MV)) {
|
|
dev_err(chip->dev, "bad float voltage mv =%d asked to set\n",
|
|
vfloat_mv);
|
|
return -EINVAL;
|
|
}
|
|
|
|
temp = (vfloat_mv - MIN_FLOAT_MV) / VFLOAT_STEP_MV;
|
|
|
|
return smb349_masked_write(chip, VFLOAT_REG, VFLOAT_MASK, temp);
|
|
}
|
|
|
|
static int smb349_chg_otg_regulator_enable(struct regulator_dev *rdev)
|
|
{
|
|
int rc = 0;
|
|
struct smb349_charger *chip = rdev_get_drvdata(rdev);
|
|
|
|
rc = smb349_masked_write(chip, CMD_A_REG, CMD_A_OTG_ENABLE_BIT,
|
|
CMD_A_OTG_ENABLE_BIT);
|
|
if (rc)
|
|
dev_err(chip->dev, "Couldn't enable OTG mode rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
static int smb349_chg_otg_regulator_disable(struct regulator_dev *rdev)
|
|
{
|
|
int rc = 0;
|
|
struct smb349_charger *chip = rdev_get_drvdata(rdev);
|
|
|
|
rc = smb349_masked_write(chip, CMD_A_REG, CMD_A_OTG_ENABLE_BIT, 0);
|
|
if (rc)
|
|
dev_err(chip->dev, "Couldn't disable OTG mode rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
static int smb349_chg_otg_regulator_is_enable(struct regulator_dev *rdev)
|
|
{
|
|
int rc = 0;
|
|
u8 reg = 0;
|
|
struct smb349_charger *chip = rdev_get_drvdata(rdev);
|
|
|
|
rc = smb349_read_reg(chip, CMD_A_REG, ®);
|
|
if (rc) {
|
|
dev_err(chip->dev,
|
|
"Couldn't read OTG enable bit rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
return (reg & CMD_A_OTG_ENABLE_BIT) ? 1 : 0;
|
|
}
|
|
|
|
struct regulator_ops smb349_chg_otg_reg_ops = {
|
|
.enable = smb349_chg_otg_regulator_enable,
|
|
.disable = smb349_chg_otg_regulator_disable,
|
|
.is_enabled = smb349_chg_otg_regulator_is_enable,
|
|
};
|
|
|
|
static int smb349_regulator_init(struct smb349_charger *chip)
|
|
{
|
|
int rc = 0;
|
|
struct regulator_init_data *init_data;
|
|
struct regulator_config cfg = {};
|
|
|
|
init_data = of_get_regulator_init_data(chip->dev, chip->dev->of_node);
|
|
if (!init_data) {
|
|
dev_err(chip->dev, "Unable to allocate memory\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
if (init_data->constraints.name) {
|
|
chip->otg_vreg.rdesc.owner = THIS_MODULE;
|
|
chip->otg_vreg.rdesc.type = REGULATOR_VOLTAGE;
|
|
chip->otg_vreg.rdesc.ops = &smb349_chg_otg_reg_ops;
|
|
chip->otg_vreg.rdesc.name = init_data->constraints.name;
|
|
|
|
cfg.dev = chip->dev;
|
|
cfg.init_data = init_data;
|
|
cfg.driver_data = chip;
|
|
cfg.of_node = chip->dev->of_node;
|
|
|
|
init_data->constraints.valid_ops_mask
|
|
|= REGULATOR_CHANGE_STATUS;
|
|
|
|
chip->otg_vreg.rdev = regulator_register(
|
|
&chip->otg_vreg.rdesc, &cfg);
|
|
if (IS_ERR(chip->otg_vreg.rdev)) {
|
|
rc = PTR_ERR(chip->otg_vreg.rdev);
|
|
chip->otg_vreg.rdev = NULL;
|
|
if (rc != -EPROBE_DEFER)
|
|
dev_err(chip->dev,
|
|
"OTG reg failed, rc=%d\n", rc);
|
|
}
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int smb349_hw_init(struct smb349_charger *chip)
|
|
{
|
|
int rc;
|
|
u8 reg = 0, mask = 0;
|
|
|
|
/*
|
|
* If the charger is pre-configured for autonomous operation,
|
|
* do not apply additonal settings
|
|
*/
|
|
if (chip->chg_autonomous_mode) {
|
|
dev_dbg(chip->dev, "Charger configured for autonomous mode\n");
|
|
return 0;
|
|
}
|
|
|
|
rc = smb349_read_reg(chip, CHG_REVISION_REG, ®);
|
|
if (rc) {
|
|
dev_err(chip->dev, "Couldn't read CHG_REVISION_REG rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
/*
|
|
* A4 silicon revision of SMB349 fails charger type detection
|
|
* (apsd) due to interference on the D+/- lines by the USB phy.
|
|
* Set the workaround flag to disable charger type reporting
|
|
* for this revision.
|
|
*/
|
|
if ((reg & SMB349_REV_MASK) == SMB349_REV_A4)
|
|
chip->workaround_flags |= WRKARND_APSD_FAIL;
|
|
|
|
pr_debug("workaround_flags = %x\n", chip->workaround_flags);
|
|
|
|
rc = smb349_enable_volatile_writes(chip);
|
|
if (rc) {
|
|
dev_err(chip->dev, "Couldn't configure volatile writes rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
/* setup defaults for CHG_CNTRL_REG */
|
|
reg = CHG_CTRL_BATT_MISSING_DET_THERM_IO;
|
|
mask = CHG_CTRL_BATT_MISSING_DET_MASK;
|
|
rc = smb349_masked_write(chip, CHG_CTRL_REG, mask, reg);
|
|
if (rc) {
|
|
dev_err(chip->dev, "Couldn't set CHG_CTRL_REG rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
/* setup defaults for PIN_CTRL_REG */
|
|
reg = CHG_PIN_CTRL_USBCS_REG_BIT | CHG_PIN_CTRL_CHG_EN_LOW_REG_BIT |
|
|
CHG_PIN_CTRL_APSD_IRQ_BIT | CHG_PIN_CTRL_CHG_ERR_IRQ_BIT;
|
|
mask = CHG_PIN_CTRL_CHG_EN_MASK | CHG_PIN_CTRL_USBCS_REG_MASK |
|
|
CHG_PIN_CTRL_APSD_IRQ_MASK | CHG_PIN_CTRL_CHG_ERR_IRQ_MASK;
|
|
rc = smb349_masked_write(chip, CHG_PIN_EN_CTRL_REG, mask, reg);
|
|
if (rc) {
|
|
dev_err(chip->dev, "Couldn't set CHG_PIN_EN_CTRL_REG rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
/* setup USB 2.0/3.0 detection mechanism */
|
|
rc = smb349_masked_write(chip, CHG_OTH_CURRENT_CTRL_REG,
|
|
CHG_OTH_CTRL_USB_2_3_PIN_REG_MASK,
|
|
CHG_OTH_CTRL_USB_2_3_REG_CTRL_BIT);
|
|
if (rc) {
|
|
dev_err(chip->dev, "Couldn't set USB_2_3_REG_CTRL_BIT rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
/* setup USB suspend and APSD */
|
|
reg = VARIOUS_FUNC_USB_SUSP_EN_REG_BIT;
|
|
if (!chip->disable_apsd)
|
|
reg |= VARIOUS_FUNC_APSD_EN_BIT;
|
|
mask = VARIOUS_FUNC_USB_SUSP_MASK | VARIOUS_FUNC_APSD_MASK;
|
|
rc = smb349_masked_write(chip, VARIOUS_FUNC_REG, mask, reg);
|
|
if (rc) {
|
|
dev_err(chip->dev, "Couldn't set VARIOUS_FUNC_REG rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
/* Fault and Status IRQ configuration */
|
|
reg = FAULT_INT_HOT_COLD_HARD_BIT | FAULT_INT_HOT_COLD_SOFT_BIT
|
|
| FAULT_INT_INPUT_UV_BIT | FAULT_INT_AICL_COMPLETE_BIT;
|
|
rc = smb349_write_reg(chip, FAULT_INT_REG, reg);
|
|
if (rc) {
|
|
dev_err(chip->dev, "Couldn't set FAULT_INT_REG rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
reg = STATUS_INT_CHG_TIMEOUT_BIT | STATUS_INT_OTG_DETECT_BIT |
|
|
STATUS_INT_BATT_OV_BIT | STATUS_INT_TERM_TAPER_BIT |
|
|
STATUS_INT_FAST_CHG_BIT | STATUS_INT_LOW_BATT_BIT |
|
|
STATUS_INT_MISSING_BATT_BIT;
|
|
rc = smb349_write_reg(chip, STATUS_INT_REG, reg);
|
|
if (rc) {
|
|
dev_err(chip->dev, "Couldn't set STATUS_INT_REG rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
/* setup THERM Monitor */
|
|
rc = smb349_masked_write(chip, THERM_A_CTRL_REG,
|
|
THERM_A_THERM_MONITOR_EN_MASK, THERM_A_THERM_MONITOR_EN_BIT);
|
|
if (rc) {
|
|
dev_err(chip->dev, "Couldn't set THERM_A_CTRL_REG rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
/* set the fast charge current limit */
|
|
rc = smb349_fastchg_current_set(chip);
|
|
if (rc) {
|
|
dev_err(chip->dev, "Couldn't set fastchg current rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
/* set the float voltage */
|
|
if (chip->vfloat_mv != -EINVAL) {
|
|
rc = smb349_float_voltage_set(chip, chip->vfloat_mv);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev,
|
|
"Couldn't set float voltage rc = %d\n", rc);
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
/* set iterm */
|
|
if (chip->iterm_ma != -EINVAL) {
|
|
if (chip->iterm_disabled) {
|
|
dev_err(chip->dev, "Error: Both iterm_disabled and iterm_ma set\n");
|
|
return -EINVAL;
|
|
} else {
|
|
if (chip->iterm_ma <= 100)
|
|
reg = CHG_ITERM_100MA;
|
|
else if (chip->iterm_ma <= 200)
|
|
reg = CHG_ITERM_200MA;
|
|
else if (chip->iterm_ma <= 300)
|
|
reg = CHG_ITERM_300MA;
|
|
else if (chip->iterm_ma <= 400)
|
|
reg = CHG_ITERM_400MA;
|
|
else if (chip->iterm_ma <= 500)
|
|
reg = CHG_ITERM_500MA;
|
|
else if (chip->iterm_ma <= 600)
|
|
reg = CHG_ITERM_600MA;
|
|
else
|
|
reg = CHG_ITERM_700MA;
|
|
|
|
rc = smb349_masked_write(chip, CHG_OTH_CURRENT_CTRL_REG,
|
|
CHG_ITERM_MASK, reg);
|
|
if (rc) {
|
|
dev_err(chip->dev,
|
|
"Couldn't set iterm rc = %d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
rc = smb349_masked_write(chip, CHG_CTRL_REG,
|
|
CHG_CTRL_CURR_TERM_END_MASK, 0);
|
|
if (rc) {
|
|
dev_err(chip->dev,
|
|
"Couldn't enable iterm rc = %d\n", rc);
|
|
return rc;
|
|
}
|
|
}
|
|
} else if (chip->iterm_disabled) {
|
|
rc = smb349_masked_write(chip, CHG_CTRL_REG,
|
|
CHG_CTRL_CURR_TERM_END_MASK,
|
|
CHG_CTRL_CURR_TERM_END_MASK);
|
|
if (rc) {
|
|
dev_err(chip->dev, "Couldn't set iterm rc = %d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
/* set recharge-threshold */
|
|
if (chip->recharge_mv != -EINVAL) {
|
|
if (chip->recharge_disabled) {
|
|
dev_err(chip->dev, "Error: Both recharge_disabled and recharge_mv set\n");
|
|
return -EINVAL;
|
|
} else {
|
|
reg = 0;
|
|
if (chip->recharge_mv > 50)
|
|
reg = CHG_CTRL_RECHG_100MV_BIT;
|
|
|
|
rc = smb349_masked_write(chip, CHG_CTRL_REG,
|
|
CHG_CTRL_RECHG_50_100_MASK |
|
|
CHG_CTRL_AUTO_RECHARGE_MASK, reg);
|
|
if (rc) {
|
|
dev_err(chip->dev,
|
|
"Couldn't set rechg-cfg rc = %d\n", rc);
|
|
return rc;
|
|
}
|
|
}
|
|
} else if (chip->recharge_disabled) {
|
|
rc = smb349_masked_write(chip, CHG_CTRL_REG,
|
|
CHG_CTRL_AUTO_RECHARGE_MASK,
|
|
CHG_CTRL_AUTO_RECHARGE_MASK);
|
|
if (rc) {
|
|
dev_err(chip->dev,
|
|
"Couldn't disable auto-rechg rc = %d\n", rc);
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
/* enable/disable charging */
|
|
rc = smb349_masked_write(chip, CMD_A_REG, CMD_A_CHG_ENABLE_BIT,
|
|
chip->charging_disabled ? 0 : CMD_A_CHG_ENABLE_BIT);
|
|
if (rc) {
|
|
dev_err(chip->dev, "Unable to %s charging. rc=%d\n",
|
|
chip->charging_disabled ? "disable" : "enable", rc);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static enum power_supply_property smb349_battery_properties[] = {
|
|
POWER_SUPPLY_PROP_STATUS,
|
|
POWER_SUPPLY_PROP_PRESENT,
|
|
POWER_SUPPLY_PROP_CHARGING_ENABLED,
|
|
POWER_SUPPLY_PROP_CHARGE_TYPE,
|
|
POWER_SUPPLY_PROP_CAPACITY,
|
|
POWER_SUPPLY_PROP_HEALTH,
|
|
POWER_SUPPLY_PROP_TECHNOLOGY,
|
|
POWER_SUPPLY_PROP_MODEL_NAME,
|
|
};
|
|
|
|
static int smb349_get_prop_batt_status(struct smb349_charger *chip)
|
|
{
|
|
int rc;
|
|
u8 reg = 0;
|
|
|
|
if (chip->batt_full)
|
|
return POWER_SUPPLY_STATUS_FULL;
|
|
|
|
rc = smb349_read_reg(chip, STATUS_C_REG, ®);
|
|
if (rc) {
|
|
dev_err(chip->dev, "Couldn't read STAT_C rc = %d\n", rc);
|
|
return POWER_SUPPLY_STATUS_UNKNOWN;
|
|
}
|
|
|
|
dev_dbg(chip->dev, "%s: STATUS_C_REG=%x\n", __func__, reg);
|
|
|
|
if (reg & STATUS_C_CHG_HOLD_OFF_BIT)
|
|
return POWER_SUPPLY_STATUS_NOT_CHARGING;
|
|
|
|
if ((reg & STATUS_C_CHARGING_MASK) &&
|
|
!(reg & STATUS_C_CHG_ERR_STATUS_BIT))
|
|
return POWER_SUPPLY_STATUS_CHARGING;
|
|
|
|
return POWER_SUPPLY_STATUS_DISCHARGING;
|
|
}
|
|
|
|
static int smb349_get_prop_batt_present(struct smb349_charger *chip)
|
|
{
|
|
return !chip->battery_missing;
|
|
}
|
|
|
|
static int smb349_get_prop_batt_capacity(struct smb349_charger *chip)
|
|
{
|
|
union power_supply_propval ret = {0, };
|
|
|
|
if (chip->fake_battery_soc >= 0)
|
|
return chip->fake_battery_soc;
|
|
|
|
if (chip->bms_psy) {
|
|
chip->bms_psy->get_property(chip->bms_psy,
|
|
POWER_SUPPLY_PROP_CAPACITY, &ret);
|
|
return ret.intval;
|
|
}
|
|
|
|
return SMB349_DEFAULT_BATT_CAPACITY;
|
|
}
|
|
|
|
static int smb349_get_prop_charge_type(struct smb349_charger *chip)
|
|
{
|
|
int rc;
|
|
u8 reg = 0;
|
|
|
|
rc = smb349_read_reg(chip, STATUS_C_REG, ®);
|
|
if (rc) {
|
|
dev_err(chip->dev, "Couldn't read STAT_C rc = %d\n", rc);
|
|
return POWER_SUPPLY_CHARGE_TYPE_UNKNOWN;
|
|
}
|
|
|
|
dev_dbg(chip->dev, "%s: STATUS_C_REG=%x\n", __func__, reg);
|
|
|
|
reg &= STATUS_C_CHARGING_MASK;
|
|
|
|
if ((reg == STATUS_C_FAST_CHARGING) || (reg == STATUS_C_TAPER_CHARGING))
|
|
return POWER_SUPPLY_CHARGE_TYPE_FAST;
|
|
else if (reg == STATUS_C_PRE_CHARGING)
|
|
return POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
|
|
else
|
|
return POWER_SUPPLY_CHARGE_TYPE_NONE;
|
|
}
|
|
|
|
static int smb349_get_prop_batt_health(struct smb349_charger *chip)
|
|
{
|
|
union power_supply_propval ret = {0, };
|
|
|
|
if (chip->batt_hot)
|
|
ret.intval = POWER_SUPPLY_HEALTH_OVERHEAT;
|
|
else if (chip->batt_cold)
|
|
ret.intval = POWER_SUPPLY_HEALTH_COLD;
|
|
else if (chip->batt_warm)
|
|
ret.intval = POWER_SUPPLY_HEALTH_WARM;
|
|
else if (chip->batt_cool)
|
|
ret.intval = POWER_SUPPLY_HEALTH_COOL;
|
|
else
|
|
ret.intval = POWER_SUPPLY_HEALTH_GOOD;
|
|
|
|
return ret.intval;
|
|
}
|
|
|
|
static int smb349_get_charging_status(struct smb349_charger *chip)
|
|
{
|
|
int rc;
|
|
u8 reg = 0;
|
|
|
|
rc = smb349_read_reg(chip, STATUS_C_REG, ®);
|
|
if (rc) {
|
|
dev_err(chip->dev, "Couldn't read STAT_C rc = %d\n", rc);
|
|
return 0;
|
|
}
|
|
|
|
return (reg & STATUS_C_CHG_ENABLE_STATUS_BIT) ? 1 : 0;
|
|
}
|
|
|
|
static int smb349_charging(struct smb349_charger *chip, int enable)
|
|
{
|
|
int rc = 0;
|
|
|
|
if (chip->chg_autonomous_mode) {
|
|
dev_dbg(chip->dev, "%s: Charger in autonmous mode\n", __func__);
|
|
return 0;
|
|
}
|
|
|
|
dev_dbg(chip->dev, "%s: charging enable = %d\n", __func__, enable);
|
|
|
|
rc = smb349_masked_write(chip, CMD_A_REG, CMD_A_CHG_ENABLE_BIT,
|
|
enable ? CMD_A_CHG_ENABLE_BIT : 0);
|
|
if (rc)
|
|
dev_err(chip->dev, "Couldn't enable = %d rc = %d\n",
|
|
enable, rc);
|
|
else
|
|
chip->charging_disabled = !enable;
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int smb349_set_usb_chg_current(struct smb349_charger *chip,
|
|
int current_ma)
|
|
{
|
|
int i, rc = 0;
|
|
u8 reg = 0, mask = 0;
|
|
|
|
dev_dbg(chip->dev, "%s: USB current_ma = %d\n", __func__, current_ma);
|
|
|
|
if (chip->chg_autonomous_mode) {
|
|
dev_dbg(chip->dev, "%s: Charger in autonmous mode\n", __func__);
|
|
return 0;
|
|
}
|
|
|
|
/* Only set suspend bit when chg present and current_ma = 2 */
|
|
if (current_ma == 2 && chip->chg_present) {
|
|
rc = smb349_masked_write(chip, CMD_A_REG,
|
|
CMD_A_CHG_SUSP_EN_MASK, CMD_A_CHG_SUSP_EN_BIT);
|
|
if (rc < 0)
|
|
dev_err(chip->dev, "Couldn't suspend rc = %d\n", rc);
|
|
|
|
return rc;
|
|
}
|
|
|
|
if (current_ma <= 2)
|
|
current_ma = USB2_MIN_CURRENT_MA;
|
|
|
|
if (current_ma == USB2_MIN_CURRENT_MA) {
|
|
/* USB 2.0 - 100mA */
|
|
reg &= ~CMD_B_CHG_USB3_ENABLE_BIT;
|
|
reg &= ~CMD_B_CHG_USB_500_900_ENABLE_BIT;
|
|
} else if (current_ma == USB2_MAX_CURRENT_MA) {
|
|
/* USB 2.0 - 500mA */
|
|
reg &= ~CMD_B_CHG_USB3_ENABLE_BIT;
|
|
reg |= CMD_B_CHG_USB_500_900_ENABLE_BIT;
|
|
} else if (current_ma == USB3_MAX_CURRENT_MA) {
|
|
/* USB 3.0 - 900mA */
|
|
reg |= CMD_B_CHG_USB3_ENABLE_BIT;
|
|
reg |= CMD_B_CHG_USB_500_900_ENABLE_BIT;
|
|
} else if (current_ma > USB2_MAX_CURRENT_MA) {
|
|
/* HC mode - if none of the above */
|
|
reg |= CMD_B_CHG_HC_ENABLE_BIT;
|
|
|
|
for (i = ARRAY_SIZE(chg_current) - 1; i >= 0; i--) {
|
|
if (chg_current[i] <= current_ma)
|
|
break;
|
|
}
|
|
if (i < 0) {
|
|
dev_err(chip->dev, "Cannot find %dmA\n", current_ma);
|
|
i = 0;
|
|
}
|
|
|
|
rc = smb349_masked_write(chip, CHG_CURRENT_CTRL_REG,
|
|
AC_CHG_CURRENT_MASK, i);
|
|
if (rc)
|
|
dev_err(chip->dev, "Couldn't set input mA rc=%d\n", rc);
|
|
}
|
|
|
|
mask = CMD_B_CHG_HC_ENABLE_BIT | CMD_B_CHG_USB3_ENABLE_BIT |
|
|
CMD_B_CHG_USB_500_900_ENABLE_BIT;
|
|
rc = smb349_masked_write(chip, CMD_B_REG, mask, reg);
|
|
if (rc < 0)
|
|
dev_err(chip->dev, "Couldn't set charging mode rc = %d\n", rc);
|
|
|
|
/* unset the susp bit here */
|
|
rc = smb349_masked_write(chip, CMD_A_REG, CMD_A_CHG_SUSP_EN_MASK, 0);
|
|
if (rc < 0)
|
|
dev_err(chip->dev, "Couldn't disable suspend rc = %d\n", rc);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int
|
|
smb349_batt_property_is_writeable(struct power_supply *psy,
|
|
enum power_supply_property psp)
|
|
{
|
|
switch (psp) {
|
|
case POWER_SUPPLY_PROP_CHARGING_ENABLED:
|
|
case POWER_SUPPLY_PROP_CAPACITY:
|
|
return 1;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int smb349_battery_set_property(struct power_supply *psy,
|
|
enum power_supply_property prop,
|
|
const union power_supply_propval *val)
|
|
{
|
|
struct smb349_charger *chip = container_of(psy,
|
|
struct smb349_charger, batt_psy);
|
|
|
|
switch (prop) {
|
|
case POWER_SUPPLY_PROP_CHARGING_ENABLED:
|
|
smb349_charging(chip, val->intval);
|
|
break;
|
|
case POWER_SUPPLY_PROP_CAPACITY:
|
|
chip->fake_battery_soc = val->intval;
|
|
power_supply_changed(&chip->batt_psy);
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int smb349_battery_get_property(struct power_supply *psy,
|
|
enum power_supply_property prop,
|
|
union power_supply_propval *val)
|
|
{
|
|
struct smb349_charger *chip = container_of(psy,
|
|
struct smb349_charger, batt_psy);
|
|
|
|
switch (prop) {
|
|
case POWER_SUPPLY_PROP_STATUS:
|
|
val->intval = smb349_get_prop_batt_status(chip);
|
|
break;
|
|
case POWER_SUPPLY_PROP_PRESENT:
|
|
val->intval = smb349_get_prop_batt_present(chip);
|
|
break;
|
|
case POWER_SUPPLY_PROP_CAPACITY:
|
|
val->intval = smb349_get_prop_batt_capacity(chip);
|
|
break;
|
|
case POWER_SUPPLY_PROP_CHARGING_ENABLED:
|
|
val->intval = smb349_get_charging_status(chip);
|
|
break;
|
|
case POWER_SUPPLY_PROP_CHARGE_TYPE:
|
|
val->intval = smb349_get_prop_charge_type(chip);
|
|
break;
|
|
case POWER_SUPPLY_PROP_HEALTH:
|
|
val->intval = smb349_get_prop_batt_health(chip);
|
|
break;
|
|
case POWER_SUPPLY_PROP_TECHNOLOGY:
|
|
val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
|
|
break;
|
|
case POWER_SUPPLY_PROP_MODEL_NAME:
|
|
val->strval = "SMB349";
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int apsd_complete(struct smb349_charger *chip, u8 status)
|
|
{
|
|
int rc;
|
|
u8 reg = 0;
|
|
enum power_supply_type type = POWER_SUPPLY_TYPE_UNKNOWN;
|
|
|
|
/*
|
|
* If apsd is disabled, charger detection is done by
|
|
* DCIN UV irq.
|
|
* status = ZERO - indicates charger removed, handled
|
|
* by DCIN UV irq
|
|
*/
|
|
if (chip->disable_apsd || status == 0) {
|
|
dev_dbg(chip->dev, "APSD %s, status = %d\n",
|
|
chip->disable_apsd ? "disabled" : "enabled", !!status);
|
|
return 0;
|
|
}
|
|
|
|
rc = smb349_read_reg(chip, STATUS_D_REG, ®);
|
|
if (rc) {
|
|
dev_err(chip->dev, "Couldn't read STATUS D rc = %d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
dev_dbg(chip->dev, "%s: STATUS_D_REG=%x\n", __func__, reg);
|
|
|
|
switch (reg) {
|
|
case STATUS_D_PORT_ACA_DOCK:
|
|
case STATUS_D_PORT_ACA_C:
|
|
case STATUS_D_PORT_ACA_B:
|
|
case STATUS_D_PORT_ACA_A:
|
|
type = POWER_SUPPLY_TYPE_USB_ACA;
|
|
break;
|
|
case STATUS_D_PORT_CDP:
|
|
type = POWER_SUPPLY_TYPE_USB_CDP;
|
|
break;
|
|
case STATUS_D_PORT_DCP:
|
|
type = POWER_SUPPLY_TYPE_USB_DCP;
|
|
break;
|
|
case STATUS_D_PORT_SDP:
|
|
type = POWER_SUPPLY_TYPE_USB;
|
|
break;
|
|
case STATUS_D_PORT_OTHER:
|
|
type = POWER_SUPPLY_TYPE_USB_DCP;
|
|
break;
|
|
default:
|
|
type = POWER_SUPPLY_TYPE_USB;
|
|
break;
|
|
}
|
|
|
|
chip->chg_present = !!status;
|
|
|
|
/*
|
|
* Report the charger type as UNKNOWN if the
|
|
* apsd-fail flag is set. This nofifies the USB driver
|
|
* to initiate a s/w based charger type detection.
|
|
*/
|
|
if (chip->workaround_flags & WRKARND_APSD_FAIL)
|
|
type = POWER_SUPPLY_TYPE_UNKNOWN;
|
|
|
|
dev_dbg(chip->dev, "APSD complete. USB type detected=%d chg_present=%d",
|
|
type, chip->chg_present);
|
|
|
|
power_supply_set_supply_type(chip->usb_psy, type);
|
|
|
|
/* SMB is now done sampling the D+/D- lines, indicate USB driver */
|
|
dev_dbg(chip->dev, "%s updating usb_psy present=%d", __func__,
|
|
chip->chg_present);
|
|
power_supply_set_present(chip->usb_psy, chip->chg_present);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int chg_uv(struct smb349_charger *chip, u8 status)
|
|
{
|
|
/* use this to detect USB insertion only if !apsd */
|
|
if (chip->disable_apsd && status == 0) {
|
|
chip->chg_present = true;
|
|
dev_dbg(chip->dev, "%s updating usb_psy present=%d",
|
|
__func__, chip->chg_present);
|
|
power_supply_set_supply_type(chip->usb_psy,
|
|
POWER_SUPPLY_TYPE_USB);
|
|
power_supply_set_present(chip->usb_psy, chip->chg_present);
|
|
}
|
|
|
|
if (status != 0) {
|
|
chip->chg_present = false;
|
|
dev_dbg(chip->dev, "%s updating usb_psy present=%d",
|
|
__func__, chip->chg_present);
|
|
power_supply_set_supply_type(chip->usb_psy,
|
|
POWER_SUPPLY_TYPE_UNKNOWN);
|
|
power_supply_set_present(chip->usb_psy, chip->chg_present);
|
|
}
|
|
|
|
dev_dbg(chip->dev, "chip->chg_present = %d\n", chip->chg_present);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int fast_chg(struct smb349_charger *chip, u8 status)
|
|
{
|
|
dev_dbg(chip->dev, "%s\n", __func__);
|
|
return 0;
|
|
}
|
|
|
|
static int chg_term(struct smb349_charger *chip, u8 status)
|
|
{
|
|
dev_dbg(chip->dev, "%s\n", __func__);
|
|
chip->batt_full = !!status;
|
|
return 0;
|
|
}
|
|
|
|
static int hot_hard_handler(struct smb349_charger *chip, u8 status)
|
|
{
|
|
pr_debug("status = 0x%02x\n", status);
|
|
chip->batt_hot = !!status;
|
|
return 0;
|
|
}
|
|
static int cold_hard_handler(struct smb349_charger *chip, u8 status)
|
|
{
|
|
pr_debug("status = 0x%02x\n", status);
|
|
chip->batt_cold = !!status;
|
|
return 0;
|
|
}
|
|
static int hot_soft_handler(struct smb349_charger *chip, u8 status)
|
|
{
|
|
pr_debug("status = 0x%02x\n", status);
|
|
chip->batt_warm = !!status;
|
|
return 0;
|
|
}
|
|
static int cold_soft_handler(struct smb349_charger *chip, u8 status)
|
|
{
|
|
pr_debug("status = 0x%02x\n", status);
|
|
chip->batt_cool = !!status;
|
|
return 0;
|
|
}
|
|
|
|
static int battery_missing(struct smb349_charger *chip, u8 status)
|
|
{
|
|
if (status)
|
|
chip->battery_missing = true;
|
|
else
|
|
chip->battery_missing = false;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct irq_handler_info handlers[] = {
|
|
[0] = {
|
|
.stat_reg = IRQ_A_REG,
|
|
.val = 0,
|
|
.prev_val = 0,
|
|
.irq_info = {
|
|
{
|
|
.name = "cold_soft",
|
|
.smb_irq = cold_soft_handler,
|
|
},
|
|
{
|
|
.name = "hot_soft",
|
|
.smb_irq = hot_soft_handler,
|
|
},
|
|
{
|
|
.name = "cold_hard",
|
|
.smb_irq = cold_hard_handler,
|
|
},
|
|
{
|
|
.name = "hot_hard",
|
|
.smb_irq = hot_hard_handler,
|
|
},
|
|
},
|
|
},
|
|
[1] = {
|
|
.stat_reg = IRQ_B_REG,
|
|
.val = 0,
|
|
.prev_val = 0,
|
|
.irq_info = {
|
|
{
|
|
.name = "fast_chg",
|
|
.smb_irq = fast_chg,
|
|
},
|
|
{
|
|
.name = "vbat_low",
|
|
},
|
|
{
|
|
.name = "battery_missing",
|
|
.smb_irq = battery_missing
|
|
},
|
|
{
|
|
.name = "battery_ov",
|
|
},
|
|
},
|
|
},
|
|
[2] = {
|
|
.stat_reg = IRQ_C_REG,
|
|
.val = 0,
|
|
.prev_val = 0,
|
|
.irq_info = {
|
|
{
|
|
.name = "chg_term",
|
|
.smb_irq = chg_term,
|
|
},
|
|
{
|
|
.name = "taper",
|
|
},
|
|
{
|
|
.name = "recharge",
|
|
},
|
|
{
|
|
.name = "chg_hot",
|
|
},
|
|
},
|
|
},
|
|
[3] = {
|
|
.stat_reg = IRQ_D_REG,
|
|
.val = 0,
|
|
.prev_val = 0,
|
|
.irq_info = {
|
|
{
|
|
.name = "prechg_timeout",
|
|
},
|
|
{
|
|
.name = "safety_timeout",
|
|
},
|
|
{
|
|
.name = "aicl_complete",
|
|
},
|
|
{
|
|
.name = "src_detect",
|
|
.smb_irq = apsd_complete,
|
|
},
|
|
},
|
|
},
|
|
[4] = {
|
|
.stat_reg = IRQ_E_REG,
|
|
.val = 0,
|
|
.prev_val = 0,
|
|
.irq_info = {
|
|
{
|
|
.name = "dcin_uv",
|
|
.smb_irq = chg_uv,
|
|
},
|
|
{
|
|
.name = "dcin_ov",
|
|
},
|
|
{
|
|
.name = "afvc_active",
|
|
},
|
|
{
|
|
.name = "unknown",
|
|
},
|
|
},
|
|
},
|
|
[5] = {
|
|
.stat_reg = IRQ_F_REG,
|
|
.val = 0,
|
|
.prev_val = 0,
|
|
.irq_info = {
|
|
{
|
|
.name = "power_ok",
|
|
},
|
|
{
|
|
.name = "otg_det",
|
|
},
|
|
{
|
|
.name = "otg_batt_uv",
|
|
},
|
|
{
|
|
.name = "otg_oc",
|
|
},
|
|
},
|
|
},
|
|
};
|
|
|
|
static irqreturn_t smb349_chg_stat_handler(int irq, void *dev_id)
|
|
{
|
|
struct smb349_charger *chip = dev_id;
|
|
int i, j;
|
|
u8 triggered;
|
|
u8 changed;
|
|
u8 rt_stat, prev_rt_stat;
|
|
int rc;
|
|
int handler_count = 0;
|
|
|
|
mutex_lock(&chip->irq_complete);
|
|
|
|
init_completion(&chip->resumed);
|
|
chip->irq_waiting = true;
|
|
if (!chip->resume_completed) {
|
|
dev_dbg(chip->dev, "IRQ triggered before device-resume\n");
|
|
wait_for_completion_interruptible(&chip->resumed);
|
|
}
|
|
chip->irq_waiting = false;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(handlers); i++) {
|
|
rc = smb349_read_reg(chip, handlers[i].stat_reg,
|
|
&handlers[i].val);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Couldn't read %d rc = %d\n",
|
|
handlers[i].stat_reg, rc);
|
|
continue;
|
|
}
|
|
|
|
for (j = 0; j < ARRAY_SIZE(handlers[i].irq_info); j++) {
|
|
triggered = handlers[i].val
|
|
& (IRQ_LATCHED_MASK << (j * BITS_PER_IRQ));
|
|
rt_stat = handlers[i].val
|
|
& (IRQ_STATUS_MASK << (j * BITS_PER_IRQ));
|
|
prev_rt_stat = handlers[i].prev_val
|
|
& (IRQ_STATUS_MASK << (j * BITS_PER_IRQ));
|
|
changed = prev_rt_stat ^ rt_stat;
|
|
|
|
if (triggered || changed)
|
|
rt_stat ? handlers[i].irq_info[j].high++ :
|
|
handlers[i].irq_info[j].low++;
|
|
|
|
if ((triggered || changed)
|
|
&& handlers[i].irq_info[j].smb_irq != NULL) {
|
|
handler_count++;
|
|
rc = handlers[i].irq_info[j].smb_irq(chip,
|
|
rt_stat);
|
|
if (rc < 0)
|
|
dev_err(chip->dev,
|
|
"Couldn't handle %d irq for reg 0x%02x rc = %d\n",
|
|
j, handlers[i].stat_reg, rc);
|
|
}
|
|
}
|
|
handlers[i].prev_val = handlers[i].val;
|
|
}
|
|
|
|
pr_debug("handler count = %d\n", handler_count);
|
|
if (handler_count) {
|
|
pr_debug("batt psy changed\n");
|
|
power_supply_changed(&chip->batt_psy);
|
|
}
|
|
|
|
mutex_unlock(&chip->irq_complete);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static irqreturn_t smb349_chg_valid_handler(int irq, void *dev_id)
|
|
{
|
|
struct smb349_charger *chip = dev_id;
|
|
int present;
|
|
|
|
present = gpio_get_value_cansleep(chip->chg_valid_gpio);
|
|
if (present < 0) {
|
|
dev_err(chip->dev, "Couldn't read chg_valid gpio=%d\n",
|
|
chip->chg_valid_gpio);
|
|
return IRQ_HANDLED;
|
|
}
|
|
present ^= chip->chg_valid_act_low;
|
|
|
|
dev_dbg(chip->dev, "%s: chg_present = %d\n", __func__, present);
|
|
|
|
if (present != chip->chg_present) {
|
|
chip->chg_present = present;
|
|
dev_dbg(chip->dev, "%s updating usb_psy present=%d",
|
|
__func__, chip->chg_present);
|
|
power_supply_set_present(chip->usb_psy, chip->chg_present);
|
|
}
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static void smb349_external_power_changed(struct power_supply *psy)
|
|
{
|
|
struct smb349_charger *chip = container_of(psy,
|
|
struct smb349_charger, batt_psy);
|
|
union power_supply_propval prop = {0,};
|
|
int rc, current_limit = 0, online = 0;
|
|
|
|
if (chip->bms_psy_name)
|
|
chip->bms_psy =
|
|
power_supply_get_by_name((char *)chip->bms_psy_name);
|
|
|
|
rc = chip->usb_psy->get_property(chip->usb_psy,
|
|
POWER_SUPPLY_PROP_ONLINE, &prop);
|
|
if (rc)
|
|
dev_err(chip->dev,
|
|
"Couldn't read USB online property, rc=%d\n", rc);
|
|
else
|
|
online = prop.intval;
|
|
|
|
rc = chip->usb_psy->get_property(chip->usb_psy,
|
|
POWER_SUPPLY_PROP_CURRENT_MAX, &prop);
|
|
if (rc)
|
|
dev_err(chip->dev,
|
|
"Couldn't read USB current_max property, rc=%d\n", rc);
|
|
else
|
|
current_limit = prop.intval / 1000;
|
|
|
|
dev_dbg(chip->dev, "online = %d, current_limit = %d\n",
|
|
online, current_limit);
|
|
|
|
smb349_enable_volatile_writes(chip);
|
|
smb349_set_usb_chg_current(chip, current_limit);
|
|
|
|
dev_dbg(chip->dev, "%s updating batt psy\n", __func__);
|
|
power_supply_changed(&chip->batt_psy);
|
|
}
|
|
|
|
static int show_cnfg_regs(struct seq_file *m, void *data)
|
|
{
|
|
struct smb349_charger *chip = m->private;
|
|
int rc;
|
|
u8 reg;
|
|
u8 addr;
|
|
|
|
for (addr = 0; addr <= LAST_CNFG_REG; addr++) {
|
|
rc = smb349_read_reg(chip, addr, ®);
|
|
if (!rc)
|
|
seq_printf(m, "0x%02x = 0x%02x\n", addr, reg);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cnfg_debugfs_open(struct inode *inode, struct file *file)
|
|
{
|
|
struct smb349_charger *chip = inode->i_private;
|
|
|
|
return single_open(file, show_cnfg_regs, chip);
|
|
}
|
|
|
|
static const struct file_operations cnfg_debugfs_ops = {
|
|
.owner = THIS_MODULE,
|
|
.open = cnfg_debugfs_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
static int show_cmd_regs(struct seq_file *m, void *data)
|
|
{
|
|
struct smb349_charger *chip = m->private;
|
|
int rc;
|
|
u8 reg;
|
|
u8 addr;
|
|
|
|
for (addr = FIRST_CMD_REG; addr <= LAST_CMD_REG; addr++) {
|
|
rc = smb349_read_reg(chip, addr, ®);
|
|
if (!rc)
|
|
seq_printf(m, "0x%02x = 0x%02x\n", addr, reg);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_debugfs_open(struct inode *inode, struct file *file)
|
|
{
|
|
struct smb349_charger *chip = inode->i_private;
|
|
|
|
return single_open(file, show_cmd_regs, chip);
|
|
}
|
|
|
|
static const struct file_operations cmd_debugfs_ops = {
|
|
.owner = THIS_MODULE,
|
|
.open = cmd_debugfs_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
static int show_status_regs(struct seq_file *m, void *data)
|
|
{
|
|
struct smb349_charger *chip = m->private;
|
|
int rc;
|
|
u8 reg;
|
|
u8 addr;
|
|
|
|
for (addr = FIRST_STATUS_REG; addr <= LAST_STATUS_REG; addr++) {
|
|
rc = smb349_read_reg(chip, addr, ®);
|
|
if (!rc)
|
|
seq_printf(m, "0x%02x = 0x%02x\n", addr, reg);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int status_debugfs_open(struct inode *inode, struct file *file)
|
|
{
|
|
struct smb349_charger *chip = inode->i_private;
|
|
|
|
return single_open(file, show_status_regs, chip);
|
|
}
|
|
|
|
static const struct file_operations status_debugfs_ops = {
|
|
.owner = THIS_MODULE,
|
|
.open = status_debugfs_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
static int show_irq_count(struct seq_file *m, void *data)
|
|
{
|
|
int i, j, total = 0;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(handlers); i++)
|
|
for (j = 0; j < 4; j++) {
|
|
seq_printf(m, "%s=%d\t(high=%d low=%d)\n",
|
|
handlers[i].irq_info[j].name,
|
|
handlers[i].irq_info[j].high
|
|
+ handlers[i].irq_info[j].low,
|
|
handlers[i].irq_info[j].high,
|
|
handlers[i].irq_info[j].low);
|
|
total += (handlers[i].irq_info[j].high
|
|
+ handlers[i].irq_info[j].low);
|
|
}
|
|
|
|
seq_printf(m, "\n\tTotal = %d\n", total);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int irq_count_debugfs_open(struct inode *inode, struct file *file)
|
|
{
|
|
struct smb349_charger *chip = inode->i_private;
|
|
|
|
return single_open(file, show_irq_count, chip);
|
|
}
|
|
|
|
static const struct file_operations irq_count_debugfs_ops = {
|
|
.owner = THIS_MODULE,
|
|
.open = irq_count_debugfs_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
static int get_reg(void *data, u64 *val)
|
|
{
|
|
struct smb349_charger *chip = data;
|
|
int rc;
|
|
u8 temp;
|
|
|
|
rc = smb349_read_reg(chip, chip->peek_poke_address, &temp);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev,
|
|
"Couldn't read reg %x rc = %d\n",
|
|
chip->peek_poke_address, rc);
|
|
return -EAGAIN;
|
|
}
|
|
*val = temp;
|
|
return 0;
|
|
}
|
|
|
|
static int set_reg(void *data, u64 val)
|
|
{
|
|
struct smb349_charger *chip = data;
|
|
int rc;
|
|
u8 temp;
|
|
|
|
temp = (u8) val;
|
|
rc = smb349_write_reg(chip, chip->peek_poke_address, temp);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev,
|
|
"Couldn't write 0x%02x to 0x%02x rc= %d\n",
|
|
chip->peek_poke_address, temp, rc);
|
|
return -EAGAIN;
|
|
}
|
|
return 0;
|
|
}
|
|
DEFINE_SIMPLE_ATTRIBUTE(poke_poke_debug_ops, get_reg, set_reg, "0x%02llx\n");
|
|
|
|
static int force_irq_set(void *data, u64 val)
|
|
{
|
|
struct smb349_charger *chip = data;
|
|
|
|
smb349_chg_stat_handler(chip->client->irq, data);
|
|
return 0;
|
|
}
|
|
DEFINE_SIMPLE_ATTRIBUTE(force_irq_ops, NULL, force_irq_set, "0x%02llx\n");
|
|
|
|
#ifdef DEBUG
|
|
static void dump_regs(struct smb349_charger *chip)
|
|
{
|
|
int rc;
|
|
u8 reg;
|
|
u8 addr;
|
|
|
|
for (addr = 0; addr <= LAST_CNFG_REG; addr++) {
|
|
rc = smb349_read_reg(chip, addr, ®);
|
|
if (rc)
|
|
dev_err(chip->dev, "Couldn't read 0x%02x rc = %d\n",
|
|
addr, rc);
|
|
else
|
|
pr_debug("0x%02x = 0x%02x\n", addr, reg);
|
|
}
|
|
|
|
for (addr = FIRST_STATUS_REG; addr <= LAST_STATUS_REG; addr++) {
|
|
rc = smb349_read_reg(chip, addr, ®);
|
|
if (rc)
|
|
dev_err(chip->dev, "Couldn't read 0x%02x rc = %d\n",
|
|
addr, rc);
|
|
else
|
|
pr_debug("0x%02x = 0x%02x\n", addr, reg);
|
|
}
|
|
|
|
for (addr = FIRST_CMD_REG; addr <= LAST_CMD_REG; addr++) {
|
|
rc = smb349_read_reg(chip, addr, ®);
|
|
if (rc)
|
|
dev_err(chip->dev, "Couldn't read 0x%02x rc = %d\n",
|
|
addr, rc);
|
|
else
|
|
pr_debug("0x%02x = 0x%02x\n", addr, reg);
|
|
}
|
|
}
|
|
#else
|
|
static void dump_regs(struct smb349_charger *chip)
|
|
{
|
|
}
|
|
#endif
|
|
|
|
static int smb_parse_dt(struct smb349_charger *chip)
|
|
{
|
|
int rc;
|
|
enum of_gpio_flags gpio_flags;
|
|
struct device_node *node = chip->dev->of_node;
|
|
|
|
if (!node) {
|
|
dev_err(chip->dev, "device tree info. missing\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
chip->charging_disabled = of_property_read_bool(node,
|
|
"qcom,charging-disabled");
|
|
|
|
chip->chg_autonomous_mode = of_property_read_bool(node,
|
|
"qcom,chg-autonomous-mode");
|
|
|
|
chip->disable_apsd = of_property_read_bool(node, "qcom,disable-apsd");
|
|
|
|
rc = of_property_read_string(node, "qcom,bms-psy-name",
|
|
&chip->bms_psy_name);
|
|
if (rc)
|
|
chip->bms_psy_name = NULL;
|
|
|
|
chip->chg_valid_gpio = of_get_named_gpio_flags(node,
|
|
"qcom,chg-valid-gpio", 0, &gpio_flags);
|
|
if (!gpio_is_valid(chip->chg_valid_gpio))
|
|
dev_dbg(chip->dev, "Invalid chg-valid-gpio");
|
|
else
|
|
chip->chg_valid_act_low = gpio_flags & OF_GPIO_ACTIVE_LOW;
|
|
|
|
rc = of_property_read_u32(node, "qcom,fastchg-current-max-ma",
|
|
&chip->fastchg_current_max_ma);
|
|
if (rc)
|
|
chip->fastchg_current_max_ma = SMB349_FAST_CHG_MAX_MA;
|
|
|
|
chip->iterm_disabled = of_property_read_bool(node,
|
|
"qcom,iterm-disabled");
|
|
|
|
rc = of_property_read_u32(node, "qcom,iterm-ma", &chip->iterm_ma);
|
|
if (rc < 0)
|
|
chip->iterm_ma = -EINVAL;
|
|
|
|
rc = of_property_read_u32(node, "qcom,float-voltage-mv",
|
|
&chip->vfloat_mv);
|
|
if (rc < 0)
|
|
chip->vfloat_mv = -EINVAL;
|
|
|
|
rc = of_property_read_u32(node, "qcom,recharge-mv",
|
|
&chip->recharge_mv);
|
|
if (rc < 0)
|
|
chip->recharge_mv = -EINVAL;
|
|
|
|
chip->recharge_disabled = of_property_read_bool(node,
|
|
"qcom,recharge-disabled");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int determine_initial_state(struct smb349_charger *chip)
|
|
{
|
|
int rc;
|
|
u8 reg = 0;
|
|
|
|
rc = smb349_read_reg(chip, IRQ_B_REG, ®);
|
|
if (rc) {
|
|
dev_err(chip->dev, "Couldn't read IRQ_B rc = %d\n", rc);
|
|
goto fail_init_status;
|
|
}
|
|
|
|
chip->battery_missing = (reg & IRQ_B_BATT_MISSING_BIT) ? true : false;
|
|
|
|
rc = smb349_read_reg(chip, IRQ_C_REG, ®);
|
|
if (rc) {
|
|
dev_err(chip->dev, "Couldn't read IRQ_C rc = %d\n", rc);
|
|
goto fail_init_status;
|
|
}
|
|
chip->batt_full = (reg & IRQ_C_TERM_BIT) ? true : false;
|
|
|
|
rc = smb349_read_reg(chip, IRQ_A_REG, ®);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Couldn't read irq A rc = %d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
if (reg & IRQ_A_HOT_HARD_BIT)
|
|
chip->batt_hot = true;
|
|
if (reg & IRQ_A_COLD_HARD_BIT)
|
|
chip->batt_cold = true;
|
|
if (reg & IRQ_A_HOT_SOFT_BIT)
|
|
chip->batt_warm = true;
|
|
if (reg & IRQ_A_COLD_SOFT_BIT)
|
|
chip->batt_cool = true;
|
|
|
|
rc = smb349_read_reg(chip, IRQ_E_REG, ®);
|
|
if (rc) {
|
|
dev_err(chip->dev, "Couldn't read IRQ_E rc = %d\n", rc);
|
|
goto fail_init_status;
|
|
}
|
|
|
|
if (reg & IRQ_E_INPUT_UV_BIT) {
|
|
chg_uv(chip, 1);
|
|
} else {
|
|
chg_uv(chip, 0);
|
|
apsd_complete(chip, 1);
|
|
}
|
|
|
|
return 0;
|
|
|
|
fail_init_status:
|
|
dev_err(chip->dev, "Couldn't determine intial status\n");
|
|
return rc;
|
|
}
|
|
|
|
static int smb349_charger_probe(struct i2c_client *client,
|
|
const struct i2c_device_id *id)
|
|
{
|
|
int rc, irq;
|
|
struct smb349_charger *chip;
|
|
struct power_supply *usb_psy;
|
|
u8 reg = 0;
|
|
|
|
usb_psy = power_supply_get_by_name("usb");
|
|
if (!usb_psy) {
|
|
dev_dbg(&client->dev, "USB psy not found; deferring probe\n");
|
|
return -EPROBE_DEFER;
|
|
}
|
|
|
|
chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
|
|
if (!chip) {
|
|
dev_err(&client->dev, "Couldn't allocate memory\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
chip->client = client;
|
|
chip->dev = &client->dev;
|
|
chip->usb_psy = usb_psy;
|
|
chip->fake_battery_soc = -EINVAL;
|
|
|
|
/* probe the device to check if its actually connected */
|
|
rc = smb349_read_reg(chip, CHG_OTH_CURRENT_CTRL_REG, ®);
|
|
if (rc) {
|
|
pr_err("Failed to detect SMB349, device may be absent\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
rc = smb_parse_dt(chip);
|
|
if (rc) {
|
|
dev_err(&client->dev, "Couldn't parse DT nodes rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
i2c_set_clientdata(client, chip);
|
|
|
|
chip->batt_psy.name = "battery";
|
|
chip->batt_psy.type = POWER_SUPPLY_TYPE_BATTERY;
|
|
chip->batt_psy.get_property = smb349_battery_get_property;
|
|
chip->batt_psy.set_property = smb349_battery_set_property;
|
|
chip->batt_psy.property_is_writeable =
|
|
smb349_batt_property_is_writeable;
|
|
chip->batt_psy.properties = smb349_battery_properties;
|
|
chip->batt_psy.num_properties = ARRAY_SIZE(smb349_battery_properties);
|
|
chip->batt_psy.external_power_changed = smb349_external_power_changed;
|
|
|
|
chip->resume_completed = true;
|
|
init_completion(&chip->resumed);
|
|
mutex_init(&chip->irq_complete);
|
|
|
|
rc = power_supply_register(chip->dev, &chip->batt_psy);
|
|
if (rc < 0) {
|
|
dev_err(&client->dev, "Couldn't register batt psy rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
dump_regs(chip);
|
|
|
|
rc = smb349_regulator_init(chip);
|
|
if (rc) {
|
|
dev_err(&client->dev,
|
|
"Couldn't initialize smb349 ragulator rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
rc = smb349_hw_init(chip);
|
|
if (rc) {
|
|
dev_err(&client->dev,
|
|
"Couldn't intialize hardware rc=%d\n", rc);
|
|
goto fail_smb349_hw_init;
|
|
}
|
|
|
|
rc = determine_initial_state(chip);
|
|
if (rc) {
|
|
dev_err(&client->dev,
|
|
"Couldn't determine initial state rc=%d\n", rc);
|
|
goto fail_smb349_hw_init;
|
|
}
|
|
|
|
if (gpio_is_valid(chip->chg_valid_gpio)) {
|
|
rc = gpio_request(chip->chg_valid_gpio, "smb349_chg_valid");
|
|
if (rc) {
|
|
dev_err(&client->dev,
|
|
"gpio_request for %d failed rc=%d\n",
|
|
chip->chg_valid_gpio, rc);
|
|
goto fail_smb349_hw_init;
|
|
}
|
|
irq = gpio_to_irq(chip->chg_valid_gpio);
|
|
if (irq < 0) {
|
|
dev_err(&client->dev,
|
|
"Invalid chg_valid irq = %d\n", irq);
|
|
goto fail_chg_valid_irq;
|
|
}
|
|
rc = devm_request_threaded_irq(&client->dev, irq,
|
|
NULL, smb349_chg_valid_handler,
|
|
IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
|
|
"smb349_chg_valid_irq", chip);
|
|
if (rc) {
|
|
dev_err(&client->dev,
|
|
"Failed request_irq irq=%d, gpio=%d rc=%d\n",
|
|
irq, chip->chg_valid_gpio, rc);
|
|
goto fail_chg_valid_irq;
|
|
}
|
|
smb349_chg_valid_handler(irq, chip);
|
|
enable_irq_wake(irq);
|
|
}
|
|
|
|
/* STAT irq configuration */
|
|
if (client->irq) {
|
|
rc = devm_request_threaded_irq(&client->dev, client->irq, NULL,
|
|
smb349_chg_stat_handler,
|
|
IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
|
|
"smb349_chg_stat_irq", chip);
|
|
if (rc) {
|
|
dev_err(&client->dev,
|
|
"Failed STAT irq=%d request rc = %d\n",
|
|
client->irq, rc);
|
|
goto fail_chg_valid_irq;
|
|
}
|
|
enable_irq_wake(client->irq);
|
|
}
|
|
|
|
chip->debug_root = debugfs_create_dir("smb349", NULL);
|
|
if (!chip->debug_root)
|
|
dev_err(chip->dev, "Couldn't create debug dir\n");
|
|
|
|
if (chip->debug_root) {
|
|
struct dentry *ent;
|
|
|
|
ent = debugfs_create_file("config_registers", S_IFREG | S_IRUGO,
|
|
chip->debug_root, chip,
|
|
&cnfg_debugfs_ops);
|
|
if (!ent)
|
|
dev_err(chip->dev,
|
|
"Couldn't create cnfg debug file rc = %d\n",
|
|
rc);
|
|
|
|
ent = debugfs_create_file("status_registers", S_IFREG | S_IRUGO,
|
|
chip->debug_root, chip,
|
|
&status_debugfs_ops);
|
|
if (!ent)
|
|
dev_err(chip->dev,
|
|
"Couldn't create status debug file rc = %d\n",
|
|
rc);
|
|
|
|
ent = debugfs_create_file("cmd_registers", S_IFREG | S_IRUGO,
|
|
chip->debug_root, chip,
|
|
&cmd_debugfs_ops);
|
|
if (!ent)
|
|
dev_err(chip->dev,
|
|
"Couldn't create cmd debug file rc = %d\n",
|
|
rc);
|
|
|
|
ent = debugfs_create_x32("address", S_IFREG | S_IWUSR | S_IRUGO,
|
|
chip->debug_root,
|
|
&(chip->peek_poke_address));
|
|
if (!ent)
|
|
dev_err(chip->dev,
|
|
"Couldn't create address debug file rc = %d\n",
|
|
rc);
|
|
|
|
ent = debugfs_create_file("data", S_IFREG | S_IWUSR | S_IRUGO,
|
|
chip->debug_root, chip,
|
|
&poke_poke_debug_ops);
|
|
if (!ent)
|
|
dev_err(chip->dev,
|
|
"Couldn't create data debug file rc = %d\n",
|
|
rc);
|
|
|
|
ent = debugfs_create_file("force_irq",
|
|
S_IFREG | S_IWUSR | S_IRUGO,
|
|
chip->debug_root, chip,
|
|
&force_irq_ops);
|
|
if (!ent)
|
|
dev_err(chip->dev,
|
|
"Couldn't create data debug file rc = %d\n",
|
|
rc);
|
|
|
|
ent = debugfs_create_file("irq_count", S_IFREG | S_IRUGO,
|
|
chip->debug_root, chip,
|
|
&irq_count_debugfs_ops);
|
|
if (!ent)
|
|
dev_err(chip->dev,
|
|
"Couldn't create count debug file rc = %d\n",
|
|
rc);
|
|
}
|
|
|
|
dump_regs(chip);
|
|
|
|
dev_info(chip->dev, "SMB349 successfully probed. charger=%d, batt=%d\n",
|
|
chip->chg_present, smb349_get_prop_batt_present(chip));
|
|
return 0;
|
|
|
|
fail_chg_valid_irq:
|
|
if (gpio_is_valid(chip->chg_valid_gpio))
|
|
gpio_free(chip->chg_valid_gpio);
|
|
fail_smb349_hw_init:
|
|
power_supply_unregister(&chip->batt_psy);
|
|
regulator_unregister(chip->otg_vreg.rdev);
|
|
return rc;
|
|
}
|
|
|
|
static int smb349_charger_remove(struct i2c_client *client)
|
|
{
|
|
struct smb349_charger *chip = i2c_get_clientdata(client);
|
|
|
|
power_supply_unregister(&chip->batt_psy);
|
|
if (gpio_is_valid(chip->chg_valid_gpio))
|
|
gpio_free(chip->chg_valid_gpio);
|
|
|
|
mutex_destroy(&chip->irq_complete);
|
|
debugfs_remove_recursive(chip->debug_root);
|
|
return 0;
|
|
}
|
|
|
|
static int smb349_suspend(struct device *dev)
|
|
{
|
|
struct i2c_client *client = to_i2c_client(dev);
|
|
struct smb349_charger *chip = i2c_get_clientdata(client);
|
|
|
|
mutex_lock(&chip->irq_complete);
|
|
chip->resume_completed = false;
|
|
mutex_unlock(&chip->irq_complete);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int smb349_suspend_noirq(struct device *dev)
|
|
{
|
|
struct i2c_client *client = to_i2c_client(dev);
|
|
struct smb349_charger *chip = i2c_get_clientdata(client);
|
|
|
|
if (chip->irq_waiting) {
|
|
pr_err_ratelimited("Aborting suspend, an interrupt was detected while suspending\n");
|
|
return -EBUSY;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int smb349_resume(struct device *dev)
|
|
{
|
|
struct i2c_client *client = to_i2c_client(dev);
|
|
struct smb349_charger *chip = i2c_get_clientdata(client);
|
|
|
|
chip->resume_completed = true;
|
|
complete(&chip->resumed);
|
|
return 0;
|
|
}
|
|
|
|
static const struct dev_pm_ops smb349_pm_ops = {
|
|
.suspend = smb349_suspend,
|
|
.suspend_noirq = smb349_suspend_noirq,
|
|
.resume = smb349_resume,
|
|
};
|
|
|
|
static struct of_device_id smb349_match_table[] = {
|
|
{ .compatible = "qcom,smb349-charger",},
|
|
{ },
|
|
};
|
|
|
|
static const struct i2c_device_id smb349_charger_id[] = {
|
|
{"smb349-charger", 0},
|
|
{},
|
|
};
|
|
MODULE_DEVICE_TABLE(i2c, smb349_charger_id);
|
|
|
|
static struct i2c_driver smb349_charger_driver = {
|
|
.driver = {
|
|
.name = "smb349-charger",
|
|
.owner = THIS_MODULE,
|
|
.of_match_table = smb349_match_table,
|
|
.pm = &smb349_pm_ops,
|
|
},
|
|
.probe = smb349_charger_probe,
|
|
.remove = smb349_charger_remove,
|
|
.id_table = smb349_charger_id,
|
|
};
|
|
|
|
module_i2c_driver(smb349_charger_driver);
|
|
|
|
MODULE_DESCRIPTION("SMB349 Charger");
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_ALIAS("i2c:smb349-charger");
|