android_kernel_samsung_msm8976/drivers/power/smb349-charger.c
Eugene Yasman c041a436ea power: smb349-dual: Add SMB349 Dual Charger driver
SMB349 Dual Charger is a single path switch-mode
battery charger based on SMB349 working in conjunction
with an external charger on the same path.
SMB349 HW to HW suspend signal is used to prevent
SMB349 from charging when external charging is used.

Change-Id: Ibc54e4af874d465c87b1f14f23d380a03c214e07
Signed-off-by: Eugene Yasman <eyasman@codeaurora.org>
2014-11-18 09:42:29 +02:00

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, &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, &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, &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, &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, &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, &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, &reg);
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, &reg);
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, &reg);
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, &reg);
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, &reg);
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, &reg);
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, &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, &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, &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, &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, &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");