blob: 5a4f427868dc378b08a0486dec9fa47305f9b90f [file] [log] [blame]
/* Copyright (c) 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-dual %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"
#define LOW_CHARGE_DELAY_MS 2000
#define PERIODIC_DELAY_MS 120000
#define LOW_CHARGE_CURRENT_MA 200
#define SMB_RECHARGE_THRESHOLD_MV 50
#define DELAY_IRQ_LEVEL_MS 20
enum wake_reason {
PM_SMB349_IRQ_HANDLING = BIT(0),
PM_SMB349_DC_INSERTED = BIT(1),
};
struct smb349_dual_charger {
struct i2c_client *client;
struct device *dev;
enum wake_reason wake_reasons;
bool recharge_disabled;
int recharge_mv;
bool iterm_disabled;
int iterm_ma;
int vfloat_mv;
int chg_stat_gpio;
int chg_present;
const char *ext_psy_name;
bool charging_disabled;
int fastchg_current_max_ma;
struct delayed_work irq_handler_dwork;
struct delayed_work periodic_charge_handler_dwork;
struct power_supply *ext_psy;
struct power_supply cradle_psy;
struct mutex irq_complete;
struct mutex pm_lock;
struct dentry *debug_root;
u32 peek_poke_address;
};
struct smb_irq_info {
const char *name;
int (*smb_irq)(struct smb349_dual_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 smb349_read_reg(struct smb349_dual_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_dual_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_dual_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_dual_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_dual_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_dual_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_hw_init(struct smb349_dual_charger *chip)
{
int rc;
u8 reg = 0;
rc = smb349_enable_volatile_writes(chip);
if (rc) {
dev_err(chip->dev, "Couldn't configure volatile writes rc=%d\n",
rc);
return rc;
}
/* overwrite OTP defaults */
rc = smb349_masked_write(chip, CHG_CURRENT_CTRL_REG, 0xFF, 0x5F);
if (rc) {
dev_err(chip->dev, "Couldn't set current control rc=%d\n",
rc);
return rc;
}
rc = smb349_masked_write(chip, THERM_A_CTRL_REG, 0xFF, 0xCF);
if (rc) {
dev_err(chip->dev, "Couldn't set thermal control rc=%d\n",
rc);
return rc;
}
rc = smb349_masked_write(chip, OTG_TLIM_THERM_CTRL_REG, 0xFF, 0x5B);
if (rc) {
dev_err(chip->dev, "Couldn't set otg thermal limits rc=%d\n",
rc);
return rc;
}
rc = smb349_masked_write(chip, HARD_SOFT_TLIM_CTRL_REG, 0xFF, 0x23);
if (rc) {
dev_err(chip->dev, "Couldn't set thermal limits 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 > SMB_RECHARGE_THRESHOLD_MV)
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_cradle_properties[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_ONLINE,
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_CHARGING_ENABLED,
POWER_SUPPLY_PROP_CHARGE_TYPE,
POWER_SUPPLY_PROP_TECHNOLOGY,
POWER_SUPPLY_PROP_MODEL_NAME,
};
static int smb349_get_prop_cradle_status(struct smb349_dual_charger *chip)
{
int rc;
u8 reg = 0;
if (chip->chg_present && !chip->charging_disabled) {
rc = smb349_read_reg(chip, STATUS_C_REG, &reg);
if (rc) {
dev_dbg(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_CHARGING_MASK) &&
!(reg & STATUS_C_CHG_ERR_STATUS_BIT))
return POWER_SUPPLY_STATUS_CHARGING;
}
return POWER_SUPPLY_STATUS_DISCHARGING;
}
static int smb349_get_prop_cradle_present(struct smb349_dual_charger *chip)
{
return chip->chg_present && !chip->charging_disabled;
}
static int smb349_get_prop_charge_type(struct smb349_dual_charger *chip)
{
int rc;
u8 reg = 0;
if (chip->chg_present && !chip->charging_disabled) {
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;
}
return POWER_SUPPLY_CHARGE_TYPE_NONE;
}
static int smb349_get_charging_status(struct smb349_dual_charger *chip)
{
int rc;
u8 reg = 0;
if (chip->chg_present && !chip->charging_disabled) {
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;
}
return 0;
}
static int smb349_charging(struct smb349_dual_charger *chip, int enable)
{
int rc = 0;
dev_dbg(chip->dev, "%s: charging enable = %d\n", __func__, enable);
if (chip->chg_present) {
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);
dev_dbg(chip->dev, "cradle psy changed\n");
power_supply_changed(&chip->cradle_psy);
}
chip->charging_disabled = !enable;
return rc;
}
static int
smb349_cradle_property_is_writeable(struct power_supply *psy,
enum power_supply_property psp)
{
switch (psp) {
case POWER_SUPPLY_PROP_CHARGING_ENABLED:
return 1;
default:
break;
}
return 0;
}
static int smb349_cradle_set_property(struct power_supply *psy,
enum power_supply_property prop,
const union power_supply_propval *val)
{
struct smb349_dual_charger *chip = container_of(psy,
struct smb349_dual_charger, cradle_psy);
switch (prop) {
case POWER_SUPPLY_PROP_CHARGING_ENABLED:
smb349_charging(chip, val->intval);
break;
default:
return -EINVAL;
}
return 0;
}
static int smb349_cradle_get_property(struct power_supply *psy,
enum power_supply_property prop,
union power_supply_propval *val)
{
struct smb349_dual_charger *chip = container_of(psy,
struct smb349_dual_charger, cradle_psy);
switch (prop) {
case POWER_SUPPLY_PROP_STATUS:
val->intval = smb349_get_prop_cradle_status(chip);
break;
case POWER_SUPPLY_PROP_ONLINE:
val->intval = smb349_get_prop_cradle_present(chip);
break;
case POWER_SUPPLY_PROP_PRESENT:
val->intval = smb349_get_prop_cradle_present(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_TECHNOLOGY:
val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
break;
case POWER_SUPPLY_PROP_MODEL_NAME:
val->strval = "SMB349 Dual Charger";
break;
default:
return -EINVAL;
}
return 0;
}
static int fast_chg(struct smb349_dual_charger *chip, u8 status)
{
dev_dbg(chip->dev, "%s\n", __func__);
return 0;
}
static int chg_term(struct smb349_dual_charger *chip, u8 status)
{
dev_dbg(chip->dev, "%s\n", __func__);
return 0;
}
static int chg_type_start(struct smb349_dual_charger *chip, u8 status)
{
dev_dbg(chip->dev, "%s status=%d\n", __func__, status);
return 0;
}
static int chg_type_taper(struct smb349_dual_charger *chip, u8 status)
{
dev_dbg(chip->dev, "%s status=%d\n", __func__, status);
return 0;
}
static int chg_type_recharge(struct smb349_dual_charger *chip, u8 status)
{
dev_dbg(chip->dev, "%s status=%d\n", __func__, status);
return 0;
}
static struct irq_handler_info handlers[] = {
[0] = {
.stat_reg = IRQ_A_REG,
.val = 0,
.prev_val = 0,
.irq_info = {
{
.name = "cold_soft",
},
{
.name = "hot_soft",
},
{
.name = "cold_hard",
},
{
.name = "hot_hard",
},
},
},
[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",
},
{
.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",
.smb_irq = chg_type_taper,
},
{
.name = "recharge",
.smb_irq = chg_type_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",
.smb_irq = chg_type_start,
},
{
.name = "src_detect",
},
},
},
[4] = {
.stat_reg = IRQ_E_REG,
.val = 0,
.prev_val = 0,
.irq_info = {
{
.name = "dcin_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 void smb349_pm_stay_awake(struct smb349_dual_charger *chip, int reason)
{
int reasons;
mutex_lock(&chip->pm_lock);
reasons = chip->wake_reasons | reason;
if (reasons != 0 && chip->wake_reasons == 0) {
dev_dbg(chip->dev, "staying awake: 0x%02x (bit %d)\n",
reasons, reason);
pm_stay_awake(chip->dev);
}
chip->wake_reasons = reasons;
mutex_unlock(&chip->pm_lock);
}
static void smb349_pm_relax(struct smb349_dual_charger *chip, int reason)
{
int reasons;
mutex_lock(&chip->pm_lock);
reasons = chip->wake_reasons & (~reason);
if (reasons == 0 && chip->wake_reasons != 0) {
dev_dbg(chip->dev, "relaxing: 0x%02x (bit %d)\n",
reasons, reason);
pm_relax(chip->dev);
}
chip->wake_reasons = reasons;
mutex_unlock(&chip->pm_lock);
}
static irqreturn_t smb349_chg_stat_handler(int irq, void *dev_id)
{
struct smb349_dual_charger *chip = dev_id;
smb349_pm_stay_awake(chip, PM_SMB349_IRQ_HANDLING);
schedule_delayed_work(&chip->irq_handler_dwork,
round_jiffies_relative(msecs_to_jiffies(DELAY_IRQ_LEVEL_MS)));
return IRQ_HANDLED;
}
static void handle_stat_irqs(struct smb349_dual_charger *chip)
{
int i, j;
u8 triggered;
u8 changed;
u8 rt_stat, prev_rt_stat;
int rc;
int handler_count = 0;
dev_dbg(chip->dev, "%s\n", __func__);
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("cradle psy changed\n");
power_supply_changed(&chip->cradle_psy);
}
}
static int smb_parse_dt(struct smb349_dual_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");
rc = of_property_read_string(node, "qcom,ext-psy-name",
&chip->ext_psy_name);
if (rc) {
dev_err(chip->dev, "Invalid qcom,ext-psy-name, rc = %d\n", rc);
return -EINVAL;
}
chip->chg_stat_gpio = of_get_named_gpio_flags(node,
"qcom,chg-stat-gpio", 0, &gpio_flags);
if (!gpio_is_valid(chip->chg_stat_gpio)) {
dev_err(chip->dev, "Invalid qcom,chg-stat-gpio, rc = %d\n",
rc);
return -EINVAL;
}
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 void notify_external_charger(struct smb349_dual_charger *chip, int on)
{
union power_supply_propval prop = {on,};
chip->ext_psy->set_property(chip->ext_psy,
POWER_SUPPLY_PROP_CHARGING_ENABLED, &prop);
}
static inline void handle_dc_inout(struct smb349_dual_charger *chip,
int present)
{
if (chip->chg_present != present) {
chip->chg_present = present;
power_supply_changed(&chip->cradle_psy);
if (present) {
dev_dbg(chip->dev, "DC IN\n");
smb349_hw_init(chip);
smb349_pm_stay_awake(chip, PM_SMB349_DC_INSERTED);
schedule_delayed_work(
&chip->periodic_charge_handler_dwork,
round_jiffies_relative(
msecs_to_jiffies(PERIODIC_DELAY_MS)));
notify_external_charger(chip, 0);
dev_dbg(chip->dev, "Periodic work started\n");
} else {
dev_dbg(chip->dev, "DC OUT\n");
cancel_delayed_work(
&chip->periodic_charge_handler_dwork);
smb349_pm_relax(chip, PM_SMB349_DC_INSERTED);
notify_external_charger(chip, 1);
dev_dbg(chip->dev, "Periodic work stopped\n");
}
}
}
static int determine_initial_state(struct smb349_dual_charger *chip)
{
u8 level;
if (gpio_is_valid(chip->chg_stat_gpio))
level = gpio_get_value_cansleep(chip->chg_stat_gpio);
else
return -EINVAL;
handle_dc_inout(chip, !level);
return 0;
}
static void stat_irq_work(struct work_struct *work)
{
struct smb349_dual_charger *chip = container_of(work,
struct smb349_dual_charger,
irq_handler_dwork.work);
int level;
mutex_lock(&chip->irq_complete);
level = gpio_get_value_cansleep(chip->chg_stat_gpio);
if (level < 0) {
dev_err(chip->dev, "Couldn't read chg_valid gpio=%d\n",
chip->chg_stat_gpio);
mutex_unlock(&chip->irq_complete);
return;
}
dev_dbg(chip->dev, "%s level=%d\n", __func__, level);
if (chip->chg_present == !level)
handle_stat_irqs(chip);
else
handle_dc_inout(chip, !level);
smb349_pm_relax(chip, PM_SMB349_IRQ_HANDLING);
mutex_unlock(&chip->irq_complete);
}
/*
* There is a PMI Fuel Gauge requirement to lower
* the Fast Charge current for 2 seconds each 2 minutes by at least 200mA
*/
static void periodic_charge_work(struct work_struct *work)
{
int save_current, rc;
struct smb349_dual_charger *chip = container_of(work,
struct smb349_dual_charger,
periodic_charge_handler_dwork.work);
rc = smb349_enable_volatile_writes(chip);
if (rc) {
dev_dbg(chip->dev, "Couldn't configure volatile writes rc=%d\n",
rc);
goto resched;
}
save_current = chip->fastchg_current_max_ma;
chip->fastchg_current_max_ma -= LOW_CHARGE_CURRENT_MA;
/* lower the fast charge current limit to allow PMIC FG metering */
rc = smb349_fastchg_current_set(chip);
if (rc) {
dev_dbg(chip->dev, "Couldn't set fastchg current rc=%d\n", rc);
goto resched;
}
/* The required delay to ensure PMI FG detects the transient */
msleep(LOW_CHARGE_DELAY_MS);
chip->fastchg_current_max_ma = save_current;
/* set the fast charge current limit */
rc = smb349_fastchg_current_set(chip);
if (rc) {
dev_dbg(chip->dev, "Couldn't set fastchg current rc=%d\n", rc);
goto resched;
}
resched:
schedule_delayed_work(&chip->periodic_charge_handler_dwork,
round_jiffies_relative(msecs_to_jiffies(PERIODIC_DELAY_MS)));
return;
}
static int smb349_dual_charger_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
int rc, irq;
struct smb349_dual_charger *chip;
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->chg_present = -EINVAL;
INIT_DELAYED_WORK(&chip->irq_handler_dwork, stat_irq_work);
INIT_DELAYED_WORK(&chip->periodic_charge_handler_dwork,
periodic_charge_work);
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->cradle_psy.name = "cradle-charger";
chip->cradle_psy.type = POWER_SUPPLY_TYPE_MAINS;
chip->cradle_psy.get_property = smb349_cradle_get_property;
chip->cradle_psy.set_property = smb349_cradle_set_property;
chip->cradle_psy.property_is_writeable =
smb349_cradle_property_is_writeable;
chip->cradle_psy.properties = smb349_cradle_properties;
chip->cradle_psy.num_properties = ARRAY_SIZE(smb349_cradle_properties);
chip->ext_psy = power_supply_get_by_name((char *)chip->ext_psy_name);
if (!chip->ext_psy) {
dev_err(chip->dev,
"Waiting for '%s' psy to become available\n",
(char *)chip->ext_psy_name);
return -EPROBE_DEFER;
}
mutex_init(&chip->irq_complete);
mutex_init(&chip->pm_lock);
rc = power_supply_register(chip->dev, &chip->cradle_psy);
if (rc < 0) {
dev_err(&client->dev, "Couldn't register '%s' psy rc=%d\n",
chip->cradle_psy.name, rc);
return rc;
}
rc = gpio_request(chip->chg_stat_gpio, "smb349_chg_stat");
if (rc) {
dev_err(&client->dev,
"gpio_request for %d failed rc=%d\n",
chip->chg_stat_gpio, rc);
goto fail_chg_stat_irq;
}
irq = gpio_to_irq(chip->chg_stat_gpio);
if (irq < 0) {
dev_err(&client->dev,
"Invalid chg_stat irq = %d\n", irq);
goto fail_chg_stat_irq;
}
rc = devm_request_threaded_irq(&client->dev, irq,
NULL, smb349_chg_stat_handler,
IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING | IRQF_ONESHOT,
"smb349_chg_stat_irq", chip);
if (rc) {
dev_err(&client->dev,
"Failed request_irq irq=%d, gpio=%d rc=%d\n",
irq, chip->chg_stat_gpio, rc);
goto fail_chg_stat_irq;
}
determine_initial_state(chip);
dev_info(chip->dev, "SMB349 successfully initialized in STAT driven mode. charger=%d\n",
chip->chg_present);
return 0;
fail_chg_stat_irq:
if (gpio_is_valid(chip->chg_stat_gpio))
gpio_free(chip->chg_stat_gpio);
power_supply_unregister(&chip->cradle_psy);
return rc;
}
static int smb349_dual_charger_remove(struct i2c_client *client)
{
struct smb349_dual_charger *chip = i2c_get_clientdata(client);
power_supply_unregister(&chip->cradle_psy);
if (gpio_is_valid(chip->chg_stat_gpio))
gpio_free(chip->chg_stat_gpio);
mutex_destroy(&chip->pm_lock);
mutex_destroy(&chip->irq_complete);
return 0;
}
static struct of_device_id smb349_match_table[] = {
{ .compatible = "qcom,smb349-dual-charger",},
{},
};
static const struct i2c_device_id smb349_dual_charger_id[] = {
{"smb349-dual-charger", 0},
{},
};
MODULE_DEVICE_TABLE(i2c, smb349_dual_charger_id);
static struct i2c_driver smb349_dual_charger_driver = {
.driver = {
.name = "smb349-dual-charger",
.owner = THIS_MODULE,
.of_match_table = smb349_match_table,
},
.probe = smb349_dual_charger_probe,
.remove = smb349_dual_charger_remove,
.id_table = smb349_dual_charger_id,
};
module_i2c_driver(smb349_dual_charger_driver);
MODULE_DESCRIPTION("SMB349 Dual Charger");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("i2c:smb349-dual-charger");