android_kernel_modules_leno.../drivers/power/bq24261_charger.c

1966 lines
49 KiB
C

/*
* bq24261_charger.c - BQ24261 Charger I2C client driver
*
* Copyright (C) 2011 Intel Corporation
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* 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.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Author: Jenny TC <jenny.tc@intel.com>
*/
#include <linux/version.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/err.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/device.h>
#include <linux/i2c.h>
#include <linux/power_supply.h>
#include <linux/pm_runtime.h>
#include <linux/io.h>
#include <linux/sched.h>
#include <linux/delay.h>
#include <linux/usb/otg.h>
#include <linux/power/bq24261_charger.h>
#include <linux/seq_file.h>
#include <linux/debugfs.h>
#include <linux/wakelock.h>
#include <asm/intel_scu_ipc.h>
#define NR_RETRY_CNT 3
#define DEV_NAME "bq24261_charger"
#define DEV_MANUFACTURER "TI"
#define MODEL_NAME_SIZE 8
#define DEV_MANUFACTURER_NAME_SIZE 4
#define CHRG_TERM_WORKER_DELAY (30 * HZ)
#define EXCEPTION_MONITOR_DELAY (60 * HZ)
#define WDT_RESET_DELAY (5 * HZ)
/* BQ24261 registers */
#define BQ24261_STAT_CTRL0_ADDR 0x00
#define BQ24261_CTRL_ADDR 0x01
#define BQ24261_BATT_VOL_CTRL_ADDR 0x02
#define BQ24261_VENDOR_REV_ADDR 0x03
#define BQ24261_TERM_FCC_ADDR 0x04
#define BQ24261_VINDPM_STAT_ADDR 0x05
#define BQ24261_ST_NTC_MON_ADDR 0x06
#define BQ24261_RESET_MASK (0x01 << 7)
#define BQ24261_RESET_ENABLE (0x01 << 7)
#define BQ24261_FAULT_MASK 0x07
#define BQ24261_STAT_MASK (0x03 << 4)
#define BQ24261_BOOST_MASK (0x01 << 6)
#define BQ24261_TMR_RST_MASK (0x01 << 7)
#define BQ24261_TMR_RST (0x01 << 7)
#define BQ24261_ENABLE_BOOST (0x01 << 6)
#define BQ24261_VOVP 0x01
#define BQ24261_LOW_SUPPLY 0x02
#define BQ24261_THERMAL_SHUTDOWN 0x03
#define BQ24261_BATT_TEMP_FAULT 0x04
#define BQ24261_TIMER_FAULT 0x05
#define BQ24261_BATT_OVP 0x06
#define BQ24261_NO_BATTERY 0x07
#define BQ24261_STAT_READY 0x00
#define BQ24261_STAT_CHRG_PRGRSS (0x01 << 4)
#define BQ24261_STAT_CHRG_DONE (0x02 << 4)
#define BQ24261_STAT_FAULT (0x03 << 4)
#define BQ24261_CE_MASK (0x01 << 1)
#define BQ24261_CE_DISABLE (0x01 << 1)
#define BQ24261_HZ_MASK (0x01)
#define BQ24261_HZ_ENABLE (0x01)
#define BQ24261_ICHRG_MASK (0x1F << 3)
#define BQ24261_ITERM_MASK (0x03)
#define BQ24261_MIN_ITERM 50 /* 50 mA */
#define BQ24261_MAX_ITERM 300 /* 300 mA */
#define BQ24261_VBREG_MASK (0x3F << 2)
#define BQ24261_INLMT_MASK (0x03 << 4)
#define BQ24261_INLMT_100 0x00
#define BQ24261_INLMT_150 (0x01 << 4)
#define BQ24261_INLMT_500 (0x02 << 4)
#define BQ24261_INLMT_900 (0x03 << 4)
#define BQ24261_INLMT_1500 (0x04 << 4)
#define BQ24261_INLMT_2000 (0x05 << 4)
#define BQ24261_INLMT_2500 (0x06 << 4)
#define BQ24261_TE_MASK (0x01 << 2)
#define BQ24261_TE_ENABLE (0x01 << 2)
#define BQ24261_STAT_ENABLE_MASK (0x01 << 3)
#define BQ24261_STAT_ENABLE (0x01 << 3)
#define BQ24261_VENDOR_MASK (0x07 << 5)
#define BQ24261_VENDOR (0x02 << 5)
#define BQ24261_REV_MASK (0x07)
#define BQ24261_2_3_REV (0x06)
#define BQ24261_REV (0x02)
#define BQ24260_REV (0x01)
#define BQ24261_TS_MASK (0x01 << 3)
#define BQ24261_TS_ENABLED (0x01 << 3)
#define BQ24261_BOOST_ILIM_MASK (0x01 << 4)
#define BQ24261_BOOST_ILIM_500ma (0x0)
#define BQ24261_BOOST_ILIM_1A (0x01 << 4)
#define BQ24261_VINDPM_OFF_MASK (0x01 << 0)
#define BQ24261_VINDPM_OFF_5V (0x0)
#define BQ24261_VINDPM_OFF_12V (0x01 << 0)
#define BQ24261_SAFETY_TIMER_MASK (0x03 << 5)
#define BQ24261_SAFETY_TIMER_40MIN 0x00
#define BQ24261_SAFETY_TIMER_6HR (0x01 << 5)
#define BQ24261_SAFETY_TIMER_9HR (0x02 << 5)
#define BQ24261_SAFETY_TIMER_DISABLED (0x03 << 5)
/* 1% above voltage max design to report over voltage */
#define BQ24261_OVP_MULTIPLIER 1010
#define BQ24261_OVP_RECOVER_MULTIPLIER 990
#define BQ24261_DEF_BAT_VOLT_MAX_DESIGN 4200000
/* Settings for Voltage / DPPM Register (05) */
#define BQ24261_VBATT_LEVEL1 3700000
#define BQ24261_VBATT_LEVEL2 3960000
#define BQ24261_VINDPM_MASK (0x07)
#define BQ24261_VINDPM_320MV (0x01 << 2)
#define BQ24261_VINDPM_160MV (0x01 << 1)
#define BQ24261_VINDPM_80MV (0x01 << 0)
#define BQ24261_CD_STATUS_MASK (0x01 << 3)
#define BQ24261_DPM_EN_MASK (0x01 << 4)
#define BQ24261_DPM_EN_FORCE (0x01 << 4)
#define BQ24261_LOW_CHG_MASK (0x01 << 5)
#define BQ24261_LOW_CHG_EN (0x01 << 5)
#define BQ24261_LOW_CHG_DIS (~BQ24261_LOW_CHG_EN)
#define BQ24261_DPM_STAT_MASK (0x01 << 6)
#define BQ24261_MINSYS_STAT_MASK (0x01 << 7)
#define BQ24261_MIN_CC 500 /* 500mA */
#define BQ24261_MAX_CC 3000 /* 3A */
#define BQ24261_INT_COUNTER "bq24261_irq_counter"
u16 bq24261_sfty_tmr[][2] = {
{0, BQ24261_SAFETY_TIMER_DISABLED}
,
{40, BQ24261_SAFETY_TIMER_40MIN}
,
{360, BQ24261_SAFETY_TIMER_6HR}
,
{540, BQ24261_SAFETY_TIMER_9HR}
,
};
u16 bq24261_inlmt[][2] = {
{100, BQ24261_INLMT_100}
,
{150, BQ24261_INLMT_150}
,
{500, BQ24261_INLMT_500}
,
{900, BQ24261_INLMT_900}
,
{1500, BQ24261_INLMT_1500}
,
{2000, BQ24261_INLMT_2000}
,
{2500, BQ24261_INLMT_2500}
,
};
#define BQ24261_MIN_CV 3500
#define BQ24261_MAX_CV 4440
#define BQ24261_CV_DIV 20
#define BQ24261_CV_BIT_POS 2
static enum power_supply_property bq24261_usb_props[] = {
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_ONLINE,
POWER_SUPPLY_PROP_TYPE,
POWER_SUPPLY_PROP_HEALTH,
POWER_SUPPLY_PROP_MAX_CHARGE_CURRENT,
POWER_SUPPLY_PROP_MAX_CHARGE_VOLTAGE,
POWER_SUPPLY_PROP_CHARGE_CURRENT,
POWER_SUPPLY_PROP_CHARGE_VOLTAGE,
POWER_SUPPLY_PROP_INLMT,
POWER_SUPPLY_PROP_ENABLE_CHARGING,
POWER_SUPPLY_PROP_ENABLE_CHARGER,
POWER_SUPPLY_PROP_CHARGE_TERM_CUR,
POWER_SUPPLY_PROP_CABLE_TYPE,
POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT,
POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX,
POWER_SUPPLY_PROP_MODEL_NAME,
POWER_SUPPLY_PROP_MANUFACTURER,
POWER_SUPPLY_PROP_MAX_TEMP,
POWER_SUPPLY_PROP_MIN_TEMP,
};
enum bq24261_chrgr_stat {
BQ24261_CHRGR_STAT_UNKNOWN,
BQ24261_CHRGR_STAT_READY,
BQ24261_CHRGR_STAT_CHARGING,
BQ24261_CHRGR_STAT_BAT_FULL,
BQ24261_CHRGR_STAT_FAULT,
};
struct bq24261_otg_event {
struct list_head node;
bool is_enable;
};
struct bq24261_charger {
struct mutex stat_lock;
struct i2c_client *client;
struct bq24261_plat_data *pdata;
struct power_supply psy_usb;
struct delayed_work sw_term_work;
struct delayed_work wdt_work;
struct delayed_work low_supply_fault_work;
struct delayed_work exception_mon_work;
struct notifier_block otg_nb;
struct usb_phy *transceiver;
struct work_struct otg_work;
struct work_struct irq_work;
struct list_head otg_queue;
struct list_head irq_queue;
wait_queue_head_t wait_ready;
spinlock_t otg_queue_lock;
void __iomem *irq_iomap;
int chrgr_health;
int bat_health;
int cc;
int cv;
int inlmt;
int max_cc;
int max_cv;
int iterm;
int cable_type;
int cntl_state;
int max_temp;
int min_temp;
int revision;
int cc_limit_max;
enum bq24261_chrgr_stat chrgr_stat;
bool online;
bool present;
bool is_charging_enabled;
bool is_charger_enabled;
bool is_vsys_on;
bool boost_mode;
bool is_hw_chrg_term;
char model_name[MODEL_NAME_SIZE];
char manufacturer[DEV_MANUFACTURER_NAME_SIZE];
struct wake_lock chrgr_en_wakelock;
u32 irq_counter;
};
enum bq2426x_model_num {
BQ2426X = 0,
BQ24260,
BQ24261,
};
struct bq2426x_model {
char model_name[MODEL_NAME_SIZE];
enum bq2426x_model_num model;
};
static struct bq2426x_model bq24261_model_name[] = {
{ "bq2426x", BQ2426X },
{ "bq24260", BQ24260 },
{ "bq24261", BQ24261 },
};
struct i2c_client *bq24261_client;
static inline int get_battery_voltage(int *volt);
static inline int get_battery_current(int *cur);
static int bq24261_handle_irq(struct bq24261_charger *chip, u8 stat_reg);
static inline int bq24261_set_iterm(struct bq24261_charger *chip, int iterm);
enum power_supply_type get_power_supply_type(
enum power_supply_charger_cable_type cable)
{
switch (cable) {
case POWER_SUPPLY_CHARGER_TYPE_USB_DCP:
return POWER_SUPPLY_TYPE_USB_DCP;
case POWER_SUPPLY_CHARGER_TYPE_USB_CDP:
return POWER_SUPPLY_TYPE_USB_CDP;
case POWER_SUPPLY_CHARGER_TYPE_USB_ACA:
case POWER_SUPPLY_CHARGER_TYPE_ACA_DOCK:
return POWER_SUPPLY_TYPE_USB_ACA;
case POWER_SUPPLY_CHARGER_TYPE_AC:
return POWER_SUPPLY_TYPE_MAINS;
case POWER_SUPPLY_CHARGER_TYPE_SE1:
return POWER_SUPPLY_TYPE_USB_DCP;
case POWER_SUPPLY_CHARGER_TYPE_NONE:
case POWER_SUPPLY_CHARGER_TYPE_USB_SDP:
default:
return POWER_SUPPLY_TYPE_USB;
}
return POWER_SUPPLY_TYPE_USB;
}
static void lookup_regval(u16 tbl[][2], size_t size, u16 in_val, u8 *out_val)
{
int i;
for (i = 1; i < size; ++i)
if (in_val < tbl[i][0])
break;
*out_val = (u8) tbl[i - 1][1];
}
void bq24261_cc_to_reg(int cc, u8 *reg_val)
{
/* Ichrg bits are B3-B7
* Icharge = 500mA + IchrgCode * 100mA
*/
cc = clamp_t(int, cc, BQ24261_MIN_CC, BQ24261_MAX_CC);
cc = cc - BQ24261_MIN_CC;
*reg_val = (cc / 100) << 3;
}
void bq24261_cv_to_reg(int cv, u8 *reg_val)
{
int val;
val = clamp_t(int, cv, BQ24261_MIN_CV, BQ24261_MAX_CV);
*reg_val =
(((val - BQ24261_MIN_CV) / BQ24261_CV_DIV)
<< BQ24261_CV_BIT_POS);
}
void bq24261_inlmt_to_reg(int inlmt, u8 *regval)
{
return lookup_regval(bq24261_inlmt, ARRAY_SIZE(bq24261_inlmt),
inlmt, regval);
}
static inline void bq24261_iterm_to_reg(int iterm, u8 *regval)
{
/* Iterm bits are B0-B2
* Icharge = 50mA + ItermCode * 50mA
*/
iterm = clamp_t(int, iterm, BQ24261_MIN_ITERM, BQ24261_MAX_ITERM);
iterm = iterm - BQ24261_MIN_ITERM;
*regval = iterm / 50;
}
static inline void bq24261_sfty_tmr_to_reg(int tmr, u8 *regval)
{
return lookup_regval(bq24261_sfty_tmr, ARRAY_SIZE(bq24261_sfty_tmr),
tmr, regval);
}
static inline int bq24261_read_reg(struct i2c_client *client, u8 reg)
{
int ret, i;
for (i = 0; i < NR_RETRY_CNT; i++) {
ret = i2c_smbus_read_byte_data(client, reg);
if (ret == -EAGAIN || ret == -ETIMEDOUT)
continue;
else
break;
}
if (ret < 0)
dev_err(&client->dev, "Error(%d) in reading reg %d\n", ret,
reg);
return ret;
}
static inline void bq24261_dump_regs(bool dump_master)
{
int i;
int ret;
int bat_cur, bat_volt;
struct bq24261_charger *chip;
char buf[1024] = {0};
int used = 0;
if (!bq24261_client)
return;
chip = i2c_get_clientdata(bq24261_client);
dev_info(&bq24261_client->dev, "*======================*\n");
ret = get_battery_current(&bat_cur);
if (ret)
dev_err(&bq24261_client->dev,
"%s: Error in getting battery current", __func__);
else
dev_info(&bq24261_client->dev, "Battery Current=%dma\n",
(bat_cur/1000));
ret = get_battery_voltage(&bat_volt);
if (ret)
dev_err(&bq24261_client->dev,
"%s: Error in getting battery voltage", __func__);
else
dev_info(&bq24261_client->dev, "Battery VOlatge=%dmV\n",
(bat_volt/1000));
dev_info(&bq24261_client->dev, "BQ24261 Register dump:\n");
for (i = 0; i < 7; ++i) {
ret = bq24261_read_reg(bq24261_client, i);
if (ret < 0)
dev_err(&bq24261_client->dev,
"Error in reading REG 0x%X\n", i);
else
used += snprintf(buf + used, sizeof(buf) - used,
" 0x%X=0x%X,", i, ret);
}
dev_info(&bq24261_client->dev, "%s\n", buf);
dev_info(&bq24261_client->dev, "*======================*\n");
if (chip->pdata->dump_master_regs && dump_master)
chip->pdata->dump_master_regs();
}
#ifdef CONFIG_DEBUG_FS
static int bq24261_reg_show(struct seq_file *seq, void *unused)
{
int val;
u8 reg;
reg = *((u8 *)seq->private);
val = bq24261_read_reg(bq24261_client, reg);
seq_printf(seq, "0x%02x\n", val);
return 0;
}
static int bq24261_dbgfs_open(struct inode *inode, struct file *file)
{
return single_open(file, bq24261_reg_show, inode->i_private);
}
static u32 bq24261_register_set[] = {
BQ24261_STAT_CTRL0_ADDR,
BQ24261_CTRL_ADDR,
BQ24261_BATT_VOL_CTRL_ADDR,
BQ24261_VENDOR_REV_ADDR,
BQ24261_TERM_FCC_ADDR,
BQ24261_VINDPM_STAT_ADDR,
BQ24261_ST_NTC_MON_ADDR,
};
static struct dentry *bq24261_dbgfs_dir;
static const struct file_operations bq24261_dbg_fops = {
.open = bq24261_dbgfs_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release
};
static void bq24261_debugfs_init(void)
{
struct dentry *fentry;
u32 count = ARRAY_SIZE(bq24261_register_set);
struct bq24261_charger *chip = i2c_get_clientdata(bq24261_client);
u32 i;
char name[6] = {0};
bq24261_dbgfs_dir = debugfs_create_dir(DEV_NAME, NULL);
if (bq24261_dbgfs_dir == NULL)
goto debugfs_root_exit;
for (i = 0; i < count; i++) {
snprintf(name, 6, "%02x", bq24261_register_set[i]);
fentry = debugfs_create_file(name, S_IRUGO,
bq24261_dbgfs_dir,
&bq24261_register_set[i],
&bq24261_dbg_fops);
if (fentry == NULL)
goto debugfs_err_exit;
}
fentry = debugfs_create_u32(BQ24261_INT_COUNTER, S_IRUGO,
bq24261_dbgfs_dir, &chip->irq_counter);
if (fentry == NULL)
goto debugfs_err_exit;
dev_err(&bq24261_client->dev, "Debugfs created successfully!!\n");
return;
debugfs_err_exit:
debugfs_remove_recursive(bq24261_dbgfs_dir);
debugfs_root_exit:
dev_err(&bq24261_client->dev, "Error Creating debugfs!!\n");
return;
}
static void bq24261_debugfs_exit(void)
{
if (bq24261_dbgfs_dir)
debugfs_remove_recursive(bq24261_dbgfs_dir);
return;
}
#else
static void bq24261_debugfs_init(void)
{
return;
}
static void bq24261_debugfs_exit(void)
{
return;
}
#endif
static inline int bq24261_write_reg(struct i2c_client *client, u8 reg, u8 data)
{
int ret, i;
for (i = 0; i < NR_RETRY_CNT; i++) {
ret = i2c_smbus_write_byte_data(client, reg, data);
if (ret == -EAGAIN || ret == -ETIMEDOUT)
continue;
else
break;
}
if (ret < 0)
dev_err(&client->dev, "Error(%d) in writing %d to reg %d\n",
ret, data, reg);
return ret;
}
static inline int bq24261_read_modify_reg(struct i2c_client *client, u8 reg,
u8 mask, u8 val)
{
int ret;
ret = bq24261_read_reg(client, reg);
if (ret < 0)
return ret;
ret = (ret & ~mask) | (mask & val);
return bq24261_write_reg(client, reg, ret);
}
static inline int bq24261_tmr_ntc_init(struct bq24261_charger *chip)
{
u8 reg_val;
int ret;
bq24261_sfty_tmr_to_reg(chip->pdata->safety_timer, &reg_val);
if (chip->pdata->is_ts_enabled)
reg_val |= BQ24261_TS_ENABLED;
/* Check if boost mode current configuration is above 1A*/
if (chip->pdata->boost_mode_ma >= 1000)
reg_val |= BQ24261_BOOST_ILIM_1A;
ret = bq24261_read_modify_reg(chip->client, BQ24261_ST_NTC_MON_ADDR,
BQ24261_TS_MASK|BQ24261_SAFETY_TIMER_MASK|
BQ24261_BOOST_ILIM_MASK, reg_val);
return ret;
}
static inline int bq24261_enable_charging(
struct bq24261_charger *chip, bool val)
{
int ret;
u8 reg_val;
bool is_ready;
dev_dbg(&chip->client->dev, "%s=%d\n", __func__, val);
ret = bq24261_read_reg(chip->client,
BQ24261_STAT_CTRL0_ADDR);
if (ret < 0) {
dev_err(&chip->client->dev,
"Error(%d) in reading BQ24261_STAT_CTRL0_ADDR\n", ret);
}
is_ready = (ret & BQ24261_STAT_MASK) != BQ24261_STAT_FAULT;
/* If status is fault, wait for READY before enabling the charging */
if (!is_ready && val) {
ret = wait_event_timeout(chip->wait_ready,
(chip->chrgr_stat == BQ24261_CHRGR_STAT_READY),
HZ);
dev_info(&chip->client->dev,
"chrgr_stat=%x\n", chip->chrgr_stat);
if (ret == 0) {
dev_err(&chip->client->dev,
"ChgrReady timeout, enable charging anyway\n");
}
}
if (chip->pdata->enable_charging) {
ret = chip->pdata->enable_charging(val);
if (ret) {
dev_err(&chip->client->dev,
"Error(%d) in master enable-charging\n", ret);
}
}
if (val) {
reg_val = (~BQ24261_CE_DISABLE & BQ24261_CE_MASK);
if (chip->is_hw_chrg_term)
reg_val |= BQ24261_TE_ENABLE;
} else {
reg_val = BQ24261_CE_DISABLE;
}
reg_val |= BQ24261_STAT_ENABLE;
ret = bq24261_read_modify_reg(chip->client, BQ24261_CTRL_ADDR,
BQ24261_STAT_ENABLE_MASK|BQ24261_RESET_MASK|
BQ24261_CE_MASK|BQ24261_TE_MASK,
reg_val);
if (ret || !val)
return ret;
bq24261_set_iterm(chip, chip->iterm);
ret = bq24261_tmr_ntc_init(chip);
if (ret) {
dev_err(&chip->client->dev,
"Error(%d) in tmr_ntc_init\n", ret);
}
dev_info(&chip->client->dev, "Completed %s=%d\n", __func__, val);
bq24261_dump_regs(false);
return ret;
}
static inline int bq24261_reset_timer(struct bq24261_charger *chip)
{
return bq24261_read_modify_reg(chip->client, BQ24261_STAT_CTRL0_ADDR,
BQ24261_TMR_RST_MASK, BQ24261_TMR_RST);
}
static inline int bq24261_enable_charger(
struct bq24261_charger *chip, int val)
{
/* TODO: Implement enable/disable HiZ mode to enable/
* disable charger
*/
u8 reg_val;
int ret;
dev_dbg(&chip->client->dev, "%s=%d\n", __func__, val);
reg_val = val ? (~BQ24261_HZ_ENABLE & BQ24261_HZ_MASK) :
BQ24261_HZ_ENABLE;
ret = bq24261_read_modify_reg(chip->client, BQ24261_CTRL_ADDR,
BQ24261_HZ_MASK|BQ24261_RESET_MASK, reg_val);
if (ret)
return ret;
return bq24261_reset_timer(chip);
}
static inline int bq24261_set_cc(struct bq24261_charger *chip, int cc)
{
u8 reg_val;
int ret;
dev_dbg(&chip->client->dev, "%s=%d\n", __func__, cc);
if (chip->pdata->set_cc) {
ret = chip->pdata->set_cc(cc);
if (unlikely(ret))
return ret;
}
if (cc && (cc < BQ24261_MIN_CC)) {
dev_dbg(&chip->client->dev, "Set LOW_CHG bit\n");
reg_val = BQ24261_LOW_CHG_EN;
ret = bq24261_read_modify_reg(chip->client,
BQ24261_VINDPM_STAT_ADDR,
BQ24261_LOW_CHG_MASK, reg_val);
} else {
dev_dbg(&chip->client->dev, "Clear LOW_CHG bit\n");
reg_val = BQ24261_LOW_CHG_DIS;
ret = bq24261_read_modify_reg(chip->client,
BQ24261_VINDPM_STAT_ADDR,
BQ24261_LOW_CHG_MASK, reg_val);
}
/* cc setting will be done by platform specific hardware
* but, in case of error-conditions or if the setting fails,
* the following will be a fail-safe mechanism.
*/
bq24261_cc_to_reg(cc, &reg_val);
return bq24261_read_modify_reg(chip->client, BQ24261_TERM_FCC_ADDR,
BQ24261_ICHRG_MASK, reg_val);
}
static inline int bq24261_set_cv(struct bq24261_charger *chip, int cv)
{
int bat_volt;
int ret;
u8 reg_val;
u8 vindpm_val = 0x0;
dev_dbg(&chip->client->dev, "%s=%d\n", __func__, cv);
/*
* Setting VINDPM value as per the battery voltage
* VBatt Vindpm Register Setting
* < 3.7v 4.2v 0x0 (default)
* 3.71v - 3.96v 4.36v 0x2
* > 3.96v 4.6v 0x5
*/
ret = get_battery_voltage(&bat_volt);
if (ret) {
dev_err(&chip->client->dev,
"Error getting battery voltage!!\n");
} else {
if (bat_volt > BQ24261_VBATT_LEVEL2)
vindpm_val =
(BQ24261_VINDPM_320MV | BQ24261_VINDPM_80MV);
else if (bat_volt > BQ24261_VBATT_LEVEL1)
vindpm_val = BQ24261_VINDPM_160MV;
}
ret = bq24261_read_modify_reg(chip->client,
BQ24261_VINDPM_STAT_ADDR,
BQ24261_VINDPM_MASK,
vindpm_val);
if (ret) {
dev_err(&chip->client->dev,
"Error setting VINDPM setting!!\n");
return ret;
}
if (chip->pdata->set_cv)
chip->pdata->set_cv(cv);
/* cv setting will be done by platform specific hardware
* but, in case of error-conditions or if the setting fails,
* the following will be a fail-safe mechanism.
*/
bq24261_cv_to_reg(cv, &reg_val);
return bq24261_read_modify_reg(chip->client, BQ24261_BATT_VOL_CTRL_ADDR,
BQ24261_VBREG_MASK, reg_val);
}
static inline int bq24261_set_inlmt(struct bq24261_charger *chip, int inlmt)
{
u8 reg_val;
dev_dbg(&chip->client->dev, "%s=%d\n", __func__, inlmt);
if (chip->pdata->set_inlmt)
return chip->pdata->set_inlmt(inlmt);
bq24261_inlmt_to_reg(inlmt, &reg_val);
return bq24261_read_modify_reg(chip->client, BQ24261_CTRL_ADDR,
BQ24261_RESET_MASK|BQ24261_INLMT_MASK, reg_val);
}
static inline void resume_charging(struct bq24261_charger *chip)
{
if (chip->is_charger_enabled)
bq24261_enable_charger(chip, true);
if (chip->inlmt)
bq24261_set_inlmt(chip, chip->inlmt);
if (chip->cc)
bq24261_set_cc(chip, chip->cc);
if (chip->cv)
bq24261_set_cv(chip, chip->cv);
if (chip->is_charging_enabled)
bq24261_enable_charging(chip, true);
}
static inline int bq24261_set_iterm(struct bq24261_charger *chip, int iterm)
{
u8 reg_val;
if (chip->pdata->set_iterm)
return chip->pdata->set_iterm(iterm);
bq24261_iterm_to_reg(iterm, &reg_val);
return bq24261_read_modify_reg(chip->client, BQ24261_TERM_FCC_ADDR,
BQ24261_ITERM_MASK, reg_val);
}
static inline int bq24261_enable_hw_charge_term(
struct bq24261_charger *chip, bool val)
{
u8 data;
int ret;
data = val ? BQ24261_TE_ENABLE : (~BQ24261_TE_ENABLE & BQ24261_TE_MASK);
ret = bq24261_read_modify_reg(chip->client, BQ24261_CTRL_ADDR,
BQ24261_RESET_MASK|BQ24261_TE_MASK, data);
if (ret)
return ret;
chip->is_hw_chrg_term = val ? true : false;
return ret;
}
static inline int bq24261_enable_boost_mode(
struct bq24261_charger *chip, int val)
{
int ret = 0;
if (val) {
if (((chip->revision & BQ24261_REV_MASK) == BQ24261_REV) ||
chip->pdata->is_wdt_kick_needed) {
if (chip->pdata->enable_vbus)
chip->pdata->enable_vbus(true);
}
if (chip->pdata->handle_otgmode)
chip->pdata->handle_otgmode(true);
/* TODO: Support different Host Mode Current limits */
bq24261_enable_charger(chip, true);
ret =
bq24261_read_modify_reg(chip->client,
BQ24261_STAT_CTRL0_ADDR,
BQ24261_BOOST_MASK,
BQ24261_ENABLE_BOOST);
if (unlikely(ret))
return ret;
ret = bq24261_tmr_ntc_init(chip);
if (unlikely(ret))
return ret;
chip->boost_mode = true;
if (((chip->revision & BQ24261_REV_MASK) == BQ24261_REV) ||
chip->pdata->is_wdt_kick_needed)
schedule_delayed_work(&chip->wdt_work, 0);
dev_info(&chip->client->dev, "Boost Mode enabled\n");
} else {
ret =
bq24261_read_modify_reg(chip->client,
BQ24261_STAT_CTRL0_ADDR,
BQ24261_BOOST_MASK,
~BQ24261_ENABLE_BOOST);
if (unlikely(ret))
return ret;
/* if charging need not to be enabled, disable
* the charger else keep the charger on
*/
if (!chip->is_charging_enabled)
bq24261_enable_charger(chip, false);
chip->boost_mode = false;
dev_info(&chip->client->dev, "Boost Mode disabled\n");
if (((chip->revision & BQ24261_REV_MASK) == BQ24261_REV) ||
chip->pdata->is_wdt_kick_needed) {
cancel_delayed_work_sync(&chip->wdt_work);
if (chip->pdata->enable_vbus)
chip->pdata->enable_vbus(false);
}
if (chip->pdata->handle_otgmode)
chip->pdata->handle_otgmode(false);
/* Notify power supply subsystem to enable charging
* if needed. Eg. if DC adapter is connected
*/
power_supply_changed(&chip->psy_usb);
}
return ret;
}
static inline bool bq24261_is_vsys_on(struct bq24261_charger *chip)
{
int ret;
struct i2c_client *client = chip->client;
ret = bq24261_read_reg(client, BQ24261_CTRL_ADDR);
if (ret < 0) {
dev_err(&client->dev,
"Error(%d) in reading BQ24261_CTRL_ADDR\n", ret);
return false;
}
if (((ret & BQ24261_HZ_MASK) == BQ24261_HZ_ENABLE) &&
chip->is_charger_enabled) {
dev_err(&client->dev, "Charger in Hi Z Mode\n");
bq24261_dump_regs(true);
return false;
}
ret = bq24261_read_reg(client, BQ24261_VINDPM_STAT_ADDR);
if (ret < 0) {
dev_err(&client->dev,
"Error(%d) in reading BQ24261_VINDPM_STAT_ADDR\n", ret);
return false;
}
if (ret & BQ24261_CD_STATUS_MASK) {
dev_err(&client->dev, "CD line asserted\n");
bq24261_dump_regs(true);
return false;
}
return true;
}
static inline bool bq24261_is_online(struct bq24261_charger *chip)
{
if (chip->cable_type == POWER_SUPPLY_CHARGER_TYPE_NONE)
return false;
else if (!chip->is_charger_enabled)
return false;
/* BQ24261 gives interrupt only on stop/resume charging.
* If charging is already stopped, we need to query the hardware
* to see charger is still active and can supply vsys or not.
*/
else if ((chip->chrgr_stat == BQ24261_CHRGR_STAT_FAULT) ||
(!chip->is_charging_enabled))
return bq24261_is_vsys_on(chip);
else
return chip->is_vsys_on;
}
static int bq24261_usb_set_property(struct power_supply *psy,
enum power_supply_property psp,
const union power_supply_propval *val)
{
struct bq24261_charger *chip = container_of(psy,
struct bq24261_charger,
psy_usb);
int ret = 0;
mutex_lock(&chip->stat_lock);
switch (psp) {
case POWER_SUPPLY_PROP_PRESENT:
chip->present = val->intval;
/*If charging capable cable is present, then
hold the charger wakelock so that the target
does not enter suspend mode when charging is
in progress.
If charging cable has been removed, then
unlock the wakelock to allow the target to
enter the sleep mode*/
if (!wake_lock_active(&chip->chrgr_en_wakelock) &&
val->intval)
wake_lock(&chip->chrgr_en_wakelock);
else if (wake_lock_active(&chip->chrgr_en_wakelock) &&
!val->intval)
wake_unlock(&chip->chrgr_en_wakelock);
break;
case POWER_SUPPLY_PROP_ONLINE:
chip->online = val->intval;
break;
case POWER_SUPPLY_PROP_ENABLE_CHARGING:
/* Reset charging to avoid issues of not starting
* charging when we're recovering from fault-cases.
*/
if (val->intval) {
dev_info(&chip->client->dev, "Charging reset");
ret = bq24261_enable_charging(chip, false);
if (ret)
dev_err(&chip->client->dev,
"Error(%d) in charging reset", ret);
}
ret = bq24261_enable_charging(chip, val->intval);
if (ret)
dev_err(&chip->client->dev,
"Error(%d) in %s charging", ret,
(val->intval ? "enable" : "disable"));
else
chip->is_charging_enabled = val->intval;
if (val->intval)
bq24261_enable_hw_charge_term(chip, true);
else
cancel_delayed_work_sync(&chip->sw_term_work);
break;
case POWER_SUPPLY_PROP_ENABLE_CHARGER:
/* Don't enable the charger unless overvoltage is recovered */
if (chip->bat_health != POWER_SUPPLY_HEALTH_OVERVOLTAGE) {
ret = bq24261_enable_charger(chip, val->intval);
if (ret)
dev_err(&chip->client->dev,
"Error(%d) in %s charger", ret,
(val->intval ? "enable" : "disable"));
else
chip->is_charger_enabled = val->intval;
} else {
dev_info(&chip->client->dev, "Battery Over Voltage. Charger will be disabled\n");
}
break;
case POWER_SUPPLY_PROP_CHARGE_CURRENT:
ret = bq24261_set_cc(chip, val->intval);
if (!ret)
chip->cc = val->intval;
break;
case POWER_SUPPLY_PROP_CHARGE_VOLTAGE:
ret = bq24261_set_cv(chip, val->intval);
if (!ret)
chip->cv = val->intval;
break;
case POWER_SUPPLY_PROP_MAX_CHARGE_CURRENT:
chip->max_cc = val->intval;
break;
case POWER_SUPPLY_PROP_MAX_CHARGE_VOLTAGE:
chip->max_cv = val->intval;
break;
case POWER_SUPPLY_PROP_CHARGE_TERM_CUR:
ret = bq24261_set_iterm(chip, val->intval);
if (!ret)
chip->iterm = val->intval;
break;
case POWER_SUPPLY_PROP_CABLE_TYPE:
chip->cable_type = val->intval;
chip->psy_usb.type = get_power_supply_type(chip->cable_type);
if (chip->cable_type != POWER_SUPPLY_CHARGER_TYPE_NONE) {
chip->chrgr_health = POWER_SUPPLY_HEALTH_GOOD;
chip->chrgr_stat = BQ24261_CHRGR_STAT_UNKNOWN;
/* Adding this processing in order to check
for any faults during connect */
ret = bq24261_read_reg(chip->client,
BQ24261_STAT_CTRL0_ADDR);
if (ret < 0)
dev_err(&chip->client->dev,
"Error (%d) in reading status register(0x00)\n",
ret);
else
bq24261_handle_irq(chip, ret);
} else {
chip->chrgr_stat = BQ24261_CHRGR_STAT_UNKNOWN;
chip->chrgr_health = POWER_SUPPLY_HEALTH_UNKNOWN;
cancel_delayed_work_sync(&chip->low_supply_fault_work);
}
break;
case POWER_SUPPLY_PROP_INLMT:
ret = bq24261_set_inlmt(chip, val->intval);
if (!ret)
chip->inlmt = val->intval;
break;
case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT:
chip->cntl_state = val->intval;
break;
case POWER_SUPPLY_PROP_MAX_TEMP:
chip->max_temp = val->intval;
break;
case POWER_SUPPLY_PROP_MIN_TEMP:
chip->min_temp = val->intval;
break;
case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX:
chip->cc_limit_max = val->intval;
break;
default:
ret = -ENODATA;
}
mutex_unlock(&chip->stat_lock);
return ret;
}
static int bq24261_usb_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct bq24261_charger *chip = container_of(psy,
struct bq24261_charger,
psy_usb);
mutex_lock(&chip->stat_lock);
switch (psp) {
case POWER_SUPPLY_PROP_PRESENT:
val->intval = chip->present;
break;
case POWER_SUPPLY_PROP_ONLINE:
val->intval = chip->online;
break;
case POWER_SUPPLY_PROP_HEALTH:
val->intval = chip->chrgr_health;
break;
case POWER_SUPPLY_PROP_MAX_CHARGE_CURRENT:
val->intval = chip->max_cc;
break;
case POWER_SUPPLY_PROP_MAX_CHARGE_VOLTAGE:
val->intval = chip->max_cv;
break;
case POWER_SUPPLY_PROP_CHARGE_CURRENT:
val->intval = chip->cc;
break;
case POWER_SUPPLY_PROP_CHARGE_VOLTAGE:
val->intval = chip->cv;
break;
case POWER_SUPPLY_PROP_INLMT:
val->intval = chip->inlmt;
break;
case POWER_SUPPLY_PROP_CHARGE_TERM_CUR:
val->intval = chip->iterm;
break;
case POWER_SUPPLY_PROP_CABLE_TYPE:
val->intval = chip->cable_type;
break;
case POWER_SUPPLY_PROP_ENABLE_CHARGING:
if (chip->boost_mode)
val->intval = false;
else
val->intval = (chip->is_charging_enabled &&
(chip->chrgr_stat == BQ24261_CHRGR_STAT_CHARGING));
break;
case POWER_SUPPLY_PROP_ENABLE_CHARGER:
val->intval = bq24261_is_online(chip);
break;
case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT:
val->intval = chip->cntl_state;
break;
case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX:
val->intval = chip->cc_limit_max;
break;
case POWER_SUPPLY_PROP_MODEL_NAME:
val->strval = chip->model_name;
break;
case POWER_SUPPLY_PROP_MANUFACTURER:
val->strval = chip->manufacturer;
break;
case POWER_SUPPLY_PROP_MAX_TEMP:
val->intval = chip->max_temp;
break;
case POWER_SUPPLY_PROP_MIN_TEMP:
val->intval = chip->min_temp;
break;
default:
mutex_unlock(&chip->stat_lock);
return -EINVAL;
}
mutex_unlock(&chip->stat_lock);
return 0;
}
static inline struct power_supply *get_psy_battery(void)
{
struct class_dev_iter iter;
struct device *dev;
static struct power_supply *pst;
class_dev_iter_init(&iter, power_supply_class, NULL, NULL);
while ((dev = class_dev_iter_next(&iter))) {
pst = (struct power_supply *)dev_get_drvdata(dev);
if (pst->type == POWER_SUPPLY_TYPE_BATTERY) {
class_dev_iter_exit(&iter);
return pst;
}
}
class_dev_iter_exit(&iter);
return NULL;
}
static inline int get_battery_voltage(int *volt)
{
struct power_supply *psy;
union power_supply_propval val;
int ret;
psy = get_psy_battery();
if (!psy)
return -EINVAL;
ret = psy->get_property(psy, POWER_SUPPLY_PROP_VOLTAGE_NOW, &val);
if (!ret)
*volt = (val.intval);
return ret;
}
static inline int get_battery_volt_max_design(int *volt)
{
struct power_supply *psy;
union power_supply_propval val;
int ret;
psy = get_psy_battery();
if (!psy)
return -EINVAL;
ret = psy->get_property(psy,
POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, &val);
if (!ret)
(*volt = val.intval);
return ret;
}
static inline int get_battery_current(int *cur)
{
struct power_supply *psy;
union power_supply_propval val;
int ret;
psy = get_psy_battery();
if (!psy)
return -EINVAL;
ret = psy->get_property(psy, POWER_SUPPLY_PROP_CURRENT_NOW, &val);
if (!ret)
*cur = val.intval;
return ret;
}
static void bq24261_wdt_reset_worker(struct work_struct *work)
{
struct bq24261_charger *chip = container_of(work,
struct bq24261_charger, wdt_work.work);
int ret;
ret = bq24261_reset_timer(chip);
if (ret)
dev_err(&chip->client->dev, "Error (%d) in WDT reset\n", ret);
else
dev_info(&chip->client->dev, "WDT reset\n");
schedule_delayed_work(&chip->wdt_work, WDT_RESET_DELAY);
}
static void bq24261_sw_charge_term_worker(struct work_struct *work)
{
struct bq24261_charger *chip = container_of(work,
struct bq24261_charger,
sw_term_work.work);
power_supply_changed(NULL);
schedule_delayed_work(&chip->sw_term_work,
CHRG_TERM_WORKER_DELAY);
}
int bq24261_get_bat_health(void)
{
struct bq24261_charger *chip;
if (!bq24261_client)
return -ENODEV;
chip = i2c_get_clientdata(bq24261_client);
return chip->bat_health;
}
static void bq24261_low_supply_fault_work(struct work_struct *work)
{
struct bq24261_charger *chip = container_of(work,
struct bq24261_charger,
low_supply_fault_work.work);
if (chip->chrgr_stat == BQ24261_CHRGR_STAT_FAULT) {
dev_err(&chip->client->dev, "Low Supply Fault detected!!\n");
chip->chrgr_health = POWER_SUPPLY_HEALTH_DEAD;
power_supply_changed(&chip->psy_usb);
schedule_delayed_work(&chip->exception_mon_work,
EXCEPTION_MONITOR_DELAY);
bq24261_dump_regs(true);
}
return;
}
/* is_bat_over_voltage: check battery is over voltage or not
* @chip: bq24261_charger context
*
* This function is used to verify the over voltage condition.
* In some scenarios, HW generates Over Voltage exceptions when
* battery voltage is normal. This function uses the over voltage
* condition (voltage_max_design * 1.01) to verify battery is really
* over charged or not.
*/
static bool is_bat_over_voltage(struct bq24261_charger *chip,
bool verify_recovery)
{
int bat_volt, bat_volt_max_des, ret;
ret = get_battery_voltage(&bat_volt);
if (ret)
return verify_recovery ? false : true;
ret = get_battery_volt_max_design(&bat_volt_max_des);
if (ret)
bat_volt_max_des = BQ24261_DEF_BAT_VOLT_MAX_DESIGN;
dev_info(&chip->client->dev, "bat_volt=%d Voltage Max Design=%d OVP_VOLT=%d OVP recover volt=%d\n",
bat_volt, bat_volt_max_des,
(bat_volt_max_des/1000 * BQ24261_OVP_MULTIPLIER),
(bat_volt_max_des/1000 *
BQ24261_OVP_RECOVER_MULTIPLIER));
if (verify_recovery) {
if ((bat_volt) <= (bat_volt_max_des / 1000 *
BQ24261_OVP_RECOVER_MULTIPLIER))
return true;
else
return false;
} else {
if ((bat_volt) >= (bat_volt_max_des / 1000 *
BQ24261_OVP_MULTIPLIER))
return true;
else
return false;
}
return false;
}
#define IS_BATTERY_OVER_VOLTAGE(chip) \
is_bat_over_voltage(chip , false)
#define IS_BATTERY_OVER_VOLTAGE_RECOVERED(chip) \
is_bat_over_voltage(chip , true)
static void handle_battery_over_voltage(struct bq24261_charger *chip)
{
/* Set Health to Over Voltage. Disable charger to discharge
* battery to reduce the battery voltage.
*/
chip->bat_health = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
bq24261_enable_charger(chip, false);
chip->is_charger_enabled = false;
cancel_delayed_work_sync(&chip->exception_mon_work);
schedule_delayed_work(&chip->exception_mon_work,
EXCEPTION_MONITOR_DELAY);
}
static void bq24261_exception_mon_work(struct work_struct *work)
{
struct bq24261_charger *chip = container_of(work,
struct bq24261_charger,
exception_mon_work.work);
int ret;
if (chip->bat_health == POWER_SUPPLY_HEALTH_OVERVOLTAGE) {
if (IS_BATTERY_OVER_VOLTAGE_RECOVERED(chip)) {
dev_info(&chip->client->dev,
"Battery OVP Exception Recovered\n");
chip->bat_health = POWER_SUPPLY_HEALTH_GOOD;
bq24261_enable_charger(chip, true);
chip->is_charger_enabled = true;
power_supply_changed(&chip->psy_usb);
} else {
schedule_delayed_work(&chip->exception_mon_work,
EXCEPTION_MONITOR_DELAY);
}
}
if ((chip->chrgr_health == POWER_SUPPLY_HEALTH_OVERVOLTAGE) ||
(chip->chrgr_health == POWER_SUPPLY_HEALTH_DEAD)) {
ret = bq24261_read_reg(chip->client, BQ24261_STAT_CTRL0_ADDR);
if (ret < 0) {
dev_err(&chip->client->dev, "Error reading reg %x\n",
BQ24261_STAT_CTRL0_ADDR);
} else {
mutex_lock(&chip->stat_lock);
bq24261_handle_irq(chip, ret);
mutex_unlock(&chip->stat_lock);
if ((ret & BQ24261_STAT_MASK) == BQ24261_STAT_READY) {
dev_info(&chip->client->dev,
"Charger OVP/Low Supply Exception recovered\n");
power_supply_changed(&chip->psy_usb);
}
}
}
}
static int bq24261_handle_irq(struct bq24261_charger *chip, u8 stat_reg)
{
struct i2c_client *client = chip->client;
bool notify = true;
dev_info(&client->dev, "%s:%d stat=0x%x\n",
__func__, __LINE__, stat_reg);
switch (stat_reg & BQ24261_STAT_MASK) {
case BQ24261_STAT_READY:
chip->chrgr_stat = BQ24261_CHRGR_STAT_READY;
chip->chrgr_health = POWER_SUPPLY_HEALTH_GOOD;
chip->bat_health = POWER_SUPPLY_HEALTH_GOOD;
dev_info(&client->dev, "Charger Status: Ready\n");
notify = false;
break;
case BQ24261_STAT_CHRG_PRGRSS:
chip->chrgr_stat = BQ24261_CHRGR_STAT_CHARGING;
chip->chrgr_health = POWER_SUPPLY_HEALTH_GOOD;
chip->bat_health = POWER_SUPPLY_HEALTH_GOOD;
dev_info(&client->dev, "Charger Status: Charge Progress\n");
bq24261_dump_regs(false);
break;
case BQ24261_STAT_CHRG_DONE:
chip->chrgr_health = POWER_SUPPLY_HEALTH_GOOD;
chip->bat_health = POWER_SUPPLY_HEALTH_GOOD;
dev_info(&client->dev, "Charger Status: Charge Done\n");
bq24261_enable_hw_charge_term(chip, false);
resume_charging(chip);
schedule_delayed_work(&chip->sw_term_work, 0);
break;
case BQ24261_STAT_FAULT:
break;
}
if (stat_reg & BQ24261_BOOST_MASK)
dev_info(&client->dev, "Boost Mode\n");
if ((stat_reg & BQ24261_STAT_MASK) == BQ24261_STAT_FAULT) {
bool dump_master = true;
chip->chrgr_stat = BQ24261_CHRGR_STAT_FAULT;
switch (stat_reg & BQ24261_FAULT_MASK) {
case BQ24261_VOVP:
chip->chrgr_health = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
chip->is_charger_enabled = false;
schedule_delayed_work(&chip->exception_mon_work,
EXCEPTION_MONITOR_DELAY);
dev_err(&client->dev, "Charger OVP Fault\n");
break;
case BQ24261_LOW_SUPPLY:
notify = false;
if (chip->pdata->handle_low_supply)
chip->pdata->handle_low_supply();
if (chip->cable_type !=
POWER_SUPPLY_CHARGER_TYPE_NONE) {
schedule_delayed_work
(&chip->low_supply_fault_work,
5*HZ);
dev_dbg(&client->dev,
"Schedule Low Supply Fault work!!\n");
}
break;
case BQ24261_THERMAL_SHUTDOWN:
chip->chrgr_health = POWER_SUPPLY_HEALTH_OVERHEAT;
dev_err(&client->dev, "Charger Thermal Fault\n");
break;
case BQ24261_BATT_TEMP_FAULT:
chip->bat_health = POWER_SUPPLY_HEALTH_OVERHEAT;
dev_err(&client->dev, "Battery Temperature Fault\n");
break;
case BQ24261_TIMER_FAULT:
chip->bat_health = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
chip->chrgr_health = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
dev_err(&client->dev, "Charger Timer Fault\n");
break;
case BQ24261_BATT_OVP:
notify = false;
if (chip->bat_health !=
POWER_SUPPLY_HEALTH_OVERVOLTAGE) {
if (!IS_BATTERY_OVER_VOLTAGE(chip)) {
chip->chrgr_stat =
BQ24261_CHRGR_STAT_UNKNOWN;
resume_charging(chip);
} else {
dev_err(&client->dev, "Battery Over Voltage Fault\n");
handle_battery_over_voltage(chip);
notify = true;
}
}
break;
case BQ24261_NO_BATTERY:
dev_err(&client->dev, "No Battery Connected\n");
break;
}
if (chip->chrgr_stat == BQ24261_CHRGR_STAT_FAULT && notify)
bq24261_dump_regs(dump_master);
}
wake_up(&chip->wait_ready);
chip->is_vsys_on = bq24261_is_vsys_on(chip);
if (notify)
power_supply_changed(&chip->psy_usb);
return 0;
}
static void bq24261_irq_worker(struct work_struct *work)
{
struct bq24261_charger *chip =
container_of(work, struct bq24261_charger, irq_work);
int ret;
/*Lock to ensure that interrupt register readings are done
* and processed sequentially. The interrupt Fault registers
* are read on clear and without sequential processing double
* fault interrupts or fault recovery cannot be handlled propely
*/
mutex_lock(&chip->stat_lock);
dev_dbg(&chip->client->dev, "%s\n", __func__);
ret = bq24261_read_reg(chip->client, BQ24261_STAT_CTRL0_ADDR);
if (ret < 0) {
dev_err(&chip->client->dev,
"Error (%d) in reading BQ24261_STAT_CTRL0_ADDR\n", ret);
} else {
bq24261_handle_irq(chip, ret);
#ifdef CONFIG_DEBUG_FS
chip->irq_counter++;
#endif
}
mutex_unlock(&chip->stat_lock);
}
static irqreturn_t bq24261_thread_handler(int id, void *data)
{
struct bq24261_charger *chip = (struct bq24261_charger *)data;
queue_work(system_nrt_wq, &chip->irq_work);
return IRQ_HANDLED;
}
static irqreturn_t bq24261_irq_handler(int irq, void *data)
{
struct bq24261_charger *chip = (struct bq24261_charger *)data;
u8 intr_stat;
if (chip->irq_iomap) {
intr_stat = ioread8(chip->irq_iomap);
if ((intr_stat & chip->pdata->irq_mask)) {
dev_dbg(&chip->client->dev, "%s\n", __func__);
return IRQ_WAKE_THREAD;
}
}
return IRQ_NONE;
}
static void bq24261_boostmode_worker(struct work_struct *work)
{
struct bq24261_charger *chip =
container_of(work, struct bq24261_charger, otg_work);
struct bq24261_otg_event *evt, *tmp;
unsigned long flags;
spin_lock_irqsave(&chip->otg_queue_lock, flags);
list_for_each_entry_safe(evt, tmp, &chip->otg_queue, node) {
list_del(&evt->node);
spin_unlock_irqrestore(&chip->otg_queue_lock, flags);
dev_info(&chip->client->dev,
"%s:%d state=%d\n", __FILE__, __LINE__,
evt->is_enable);
mutex_lock(&chip->stat_lock);
if (evt->is_enable)
bq24261_enable_boost_mode(chip, 1);
else
bq24261_enable_boost_mode(chip, 0);
mutex_unlock(&chip->stat_lock);
spin_lock_irqsave(&chip->otg_queue_lock, flags);
kfree(evt);
}
spin_unlock_irqrestore(&chip->otg_queue_lock, flags);
}
static int otg_handle_notification(struct notifier_block *nb,
unsigned long event, void *param)
{
struct bq24261_charger *chip =
container_of(nb, struct bq24261_charger, otg_nb);
struct bq24261_otg_event *evt;
dev_dbg(&chip->client->dev, "OTG notification: %lu\n", event);
if (!param || event != USB_EVENT_DRIVE_VBUS)
return NOTIFY_DONE;
evt = kzalloc(sizeof(*evt), GFP_ATOMIC);
if (!evt) {
dev_err(&chip->client->dev,
"failed to allocate memory for OTG event\n");
return NOTIFY_DONE;
}
evt->is_enable = *(int *)param;
INIT_LIST_HEAD(&evt->node);
spin_lock(&chip->otg_queue_lock);
list_add_tail(&evt->node, &chip->otg_queue);
spin_unlock(&chip->otg_queue_lock);
queue_work(system_nrt_wq, &chip->otg_work);
return NOTIFY_OK;
}
static inline int register_otg_notifications(struct bq24261_charger *chip)
{
int retval;
INIT_LIST_HEAD(&chip->otg_queue);
INIT_WORK(&chip->otg_work, bq24261_boostmode_worker);
spin_lock_init(&chip->otg_queue_lock);
chip->otg_nb.notifier_call = otg_handle_notification;
#if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 10, 0))
chip->transceiver = usb_get_transceiver();
#else
chip->transceiver = usb_get_phy(USB_PHY_TYPE_USB2);
#endif
if (!chip->transceiver || IS_ERR(chip->transceiver)) {
dev_err(&chip->client->dev, "failed to get otg transceiver\n");
return -EINVAL;
}
retval = usb_register_notifier(chip->transceiver, &chip->otg_nb);
if (retval) {
dev_err(&chip->client->dev,
"failed to register otg notifier\n");
return -EINVAL;
}
return 0;
}
static enum bq2426x_model_num bq24261_get_model(int bq24261_rev_reg)
{
switch (bq24261_rev_reg & BQ24261_REV_MASK) {
case BQ24260_REV:
return BQ24260;
case BQ24261_REV:
case BQ24261_2_3_REV:
return BQ24261;
default:
return BQ2426X;
}
}
static int bq24261_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct i2c_adapter *adapter;
struct bq24261_charger *chip;
int ret;
int bq2426x_rev;
enum bq2426x_model_num bq24261_rev_index;
adapter = to_i2c_adapter(client->dev.parent);
if (!client->dev.platform_data) {
dev_err(&client->dev, "platform data is null");
return -EFAULT;
}
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
dev_err(&client->dev,
"I2C adapter %s doesn'tsupport BYTE DATA transfer\n",
adapter->name);
return -EIO;
}
bq2426x_rev = bq24261_read_reg(client, BQ24261_VENDOR_REV_ADDR);
if (bq2426x_rev < 0) {
dev_err(&client->dev,
"Error (%d) in reading BQ24261_VENDOR_REV_ADDR\n", bq2426x_rev);
return bq2426x_rev;
}
dev_info(&client->dev, "bq2426x revision: 0x%x found!!\n", bq2426x_rev);
bq24261_rev_index = bq24261_get_model(bq2426x_rev);
if ((bq2426x_rev & BQ24261_VENDOR_MASK) != BQ24261_VENDOR) {
dev_err(&client->dev,
"Invalid Vendor/Revision number in BQ24261_VENDOR_REV_ADDR: %d",
bq2426x_rev);
return -ENODEV;
}
chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
if (!chip) {
dev_err(&client->dev, "mem alloc failed\n");
return -ENOMEM;
}
init_waitqueue_head(&chip->wait_ready);
i2c_set_clientdata(client, chip);
chip->pdata = client->dev.platform_data;
/* Remap IRQ map address to read the IRQ status */
if ((chip->pdata->irq_map) && (chip->pdata->irq_mask)) {
chip->irq_iomap = ioremap_nocache(chip->pdata->irq_map, 8);
if (!chip->irq_iomap) {
dev_err(&client->dev, "Failed: ioremap_nocache\n");
return -EFAULT;
}
}
chip->client = client;
chip->pdata = client->dev.platform_data;
chip->psy_usb.name = DEV_NAME;
chip->psy_usb.type = POWER_SUPPLY_TYPE_USB;
chip->psy_usb.properties = bq24261_usb_props;
chip->psy_usb.num_properties = ARRAY_SIZE(bq24261_usb_props);
chip->psy_usb.get_property = bq24261_usb_get_property;
chip->psy_usb.set_property = bq24261_usb_set_property;
chip->psy_usb.supplied_to = chip->pdata->supplied_to;
chip->psy_usb.num_supplicants = chip->pdata->num_supplicants;
chip->psy_usb.throttle_states = chip->pdata->throttle_states;
chip->psy_usb.num_throttle_states = chip->pdata->num_throttle_states;
chip->psy_usb.supported_cables = POWER_SUPPLY_CHARGER_TYPE_USB;
chip->max_cc = chip->pdata->max_cc;
chip->max_cv = 4350;
chip->chrgr_stat = BQ24261_CHRGR_STAT_UNKNOWN;
chip->chrgr_health = POWER_SUPPLY_HEALTH_UNKNOWN;
chip->revision = bq2426x_rev;
strncpy(chip->model_name,
bq24261_model_name[bq24261_rev_index].model_name,
MODEL_NAME_SIZE);
strncpy(chip->manufacturer, DEV_MANUFACTURER,
DEV_MANUFACTURER_NAME_SIZE);
mutex_init(&chip->stat_lock);
wake_lock_init(&chip->chrgr_en_wakelock,
WAKE_LOCK_SUSPEND, "chrgr_en_wakelock");
ret = power_supply_register(&client->dev, &chip->psy_usb);
if (ret) {
dev_err(&client->dev, "Failed: power supply register (%d)\n",
ret);
iounmap(chip->irq_iomap);
return ret;
}
INIT_DELAYED_WORK(&chip->sw_term_work, bq24261_sw_charge_term_worker);
INIT_DELAYED_WORK(&chip->low_supply_fault_work,
bq24261_low_supply_fault_work);
INIT_DELAYED_WORK(&chip->exception_mon_work,
bq24261_exception_mon_work);
if (((chip->revision & BQ24261_REV_MASK) == BQ24261_REV) ||
chip->pdata->is_wdt_kick_needed) {
INIT_DELAYED_WORK(&chip->wdt_work,
bq24261_wdt_reset_worker);
}
INIT_WORK(&chip->irq_work, bq24261_irq_worker);
if (chip->client->irq) {
ret = request_threaded_irq(chip->client->irq,
bq24261_irq_handler,
bq24261_thread_handler,
IRQF_SHARED|IRQF_NO_SUSPEND,
DEV_NAME, chip);
if (ret) {
dev_err(&client->dev, "Failed: request_irq (%d)\n",
ret);
iounmap(chip->irq_iomap);
power_supply_unregister(&chip->psy_usb);
return ret;
}
}
if (IS_BATTERY_OVER_VOLTAGE(chip))
handle_battery_over_voltage(chip);
else
chip->bat_health = POWER_SUPPLY_HEALTH_GOOD;
if (register_otg_notifications(chip))
dev_err(&client->dev, "Error in registering OTG notifications. Unable to supply power to Host\n");
bq24261_client = client;
power_supply_changed(&chip->psy_usb);
bq24261_debugfs_init();
return 0;
}
static int bq24261_remove(struct i2c_client *client)
{
struct bq24261_charger *chip = i2c_get_clientdata(client);
if (client->irq)
free_irq(client->irq, chip);
flush_scheduled_work();
wake_lock_destroy(&chip->chrgr_en_wakelock);
if (chip->irq_iomap)
iounmap(chip->irq_iomap);
if (chip->transceiver)
usb_unregister_notifier(chip->transceiver, &chip->otg_nb);
power_supply_unregister(&chip->psy_usb);
bq24261_debugfs_exit();
return 0;
}
static int bq24261_suspend(struct device *dev)
{
struct bq24261_charger *chip = dev_get_drvdata(dev);
if (((chip->revision & BQ24261_REV_MASK) == BQ24261_REV) ||
chip->pdata->is_wdt_kick_needed) {
if (chip->boost_mode)
cancel_delayed_work_sync(&chip->wdt_work);
}
dev_dbg(&chip->client->dev, "bq24261 suspend\n");
return 0;
}
static int bq24261_resume(struct device *dev)
{
struct bq24261_charger *chip = dev_get_drvdata(dev);
if (((chip->revision & BQ24261_REV_MASK) == BQ24261_REV) ||
chip->pdata->is_wdt_kick_needed) {
if (chip->boost_mode)
bq24261_enable_boost_mode(chip, 1);
}
dev_dbg(&chip->client->dev, "bq24261 resume\n");
return 0;
}
static int bq24261_runtime_suspend(struct device *dev)
{
dev_dbg(dev, "%s called\n", __func__);
return 0;
}
static int bq24261_runtime_resume(struct device *dev)
{
dev_dbg(dev, "%s called\n", __func__);
return 0;
}
static int bq24261_runtime_idle(struct device *dev)
{
dev_dbg(dev, "%s called\n", __func__);
return 0;
}
static const struct dev_pm_ops bq24261_pm_ops = {
.suspend = bq24261_suspend,
.resume = bq24261_resume,
.runtime_suspend = bq24261_runtime_suspend,
.runtime_resume = bq24261_runtime_resume,
.runtime_idle = bq24261_runtime_idle,
};
static const struct i2c_device_id bq24261_id[] = {
{DEV_NAME, 0},
{},
};
MODULE_DEVICE_TABLE(i2c, bq24261_id);
static struct i2c_driver bq24261_driver = {
.driver = {
.name = DEV_NAME,
.pm = &bq24261_pm_ops,
},
.probe = bq24261_probe,
.remove = bq24261_remove,
.id_table = bq24261_id,
};
static int __init bq24261_init(void)
{
return i2c_add_driver(&bq24261_driver);
}
module_init(bq24261_init);
static void __exit bq24261_exit(void)
{
i2c_del_driver(&bq24261_driver);
}
module_exit(bq24261_exit);
MODULE_AUTHOR("Jenny TC <jenny.tc@intel.com>");
MODULE_DESCRIPTION("BQ24261 Charger Driver");
MODULE_LICENSE("GPL");