1303 lines
34 KiB
C
1303 lines
34 KiB
C
/*
|
|
* max77693_charger.c
|
|
* Samsung max77693 Charger Driver
|
|
*
|
|
* Copyright (C) 2012 Samsung Electronics
|
|
*
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*/
|
|
|
|
#include <linux/mfd/max77693.h>
|
|
#include <linux/mfd/max77693-private.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/seq_file.h>
|
|
|
|
#define DEBUG
|
|
|
|
#define ENABLE 1
|
|
#define DISABLE 0
|
|
|
|
static struct dentry *max77693_dentry;
|
|
|
|
struct max77693_charger_data {
|
|
struct max77693_dev *max77693;
|
|
|
|
struct power_supply psy_chg;
|
|
|
|
struct workqueue_struct *wqueue;
|
|
struct work_struct chgin_work;
|
|
struct delayed_work isr_work;
|
|
|
|
/* mutex */
|
|
struct mutex irq_lock;
|
|
struct mutex ops_lock;
|
|
|
|
/* wakelock */
|
|
struct wake_lock update_wake_lock;
|
|
|
|
unsigned int is_charging;
|
|
unsigned int charging_type;
|
|
unsigned int battery_state;
|
|
unsigned int battery_present;
|
|
unsigned int cable_type;
|
|
unsigned int charging_current_max;
|
|
unsigned int charging_current;
|
|
unsigned int input_current_limit;
|
|
unsigned int vbus_state;
|
|
int status;
|
|
bool aicl_on;
|
|
int siop_level;
|
|
|
|
int irq_bypass;
|
|
int irq_therm;
|
|
int irq_battery;
|
|
int irq_chg;
|
|
int irq_chgin;
|
|
|
|
/* software regulation */
|
|
bool soft_reg_state;
|
|
int soft_reg_current;
|
|
|
|
/* unsufficient power */
|
|
bool reg_loop_deted;
|
|
|
|
#if defined(CONFIG_WIRELESS_CHARGING)
|
|
/* wireless charge, w(wpc), v(vbus) */
|
|
int wc_w_gpio;
|
|
int wc_w_irq;
|
|
int wc_w_state;
|
|
int wc_v_gpio;
|
|
int wc_v_irq;
|
|
int wc_v_state;
|
|
bool wc_pwr_det;
|
|
#endif
|
|
|
|
sec_battery_platform_data_t *pdata;
|
|
};
|
|
|
|
static enum power_supply_property sec_charger_props[] = {
|
|
POWER_SUPPLY_PROP_STATUS,
|
|
POWER_SUPPLY_PROP_CHARGE_TYPE,
|
|
POWER_SUPPLY_PROP_HEALTH,
|
|
POWER_SUPPLY_PROP_ONLINE,
|
|
POWER_SUPPLY_PROP_CURRENT_MAX,
|
|
POWER_SUPPLY_PROP_CURRENT_AVG,
|
|
POWER_SUPPLY_PROP_CURRENT_NOW,
|
|
};
|
|
|
|
/* static void MAX77693_charger_initialize(struct max77693_charger_data *charger); */
|
|
static int max77693_get_vbus_state(struct max77693_charger_data *charger);
|
|
|
|
static void max77693_dump_reg(struct max77693_charger_data *charger)
|
|
{
|
|
u8 reg_data;
|
|
u32 reg_addr;
|
|
pr_info("%s\n", __func__);
|
|
|
|
for (reg_addr = 0xB0; reg_addr <= 0xC5; reg_addr++) {
|
|
max77693_read_reg(charger->max77693->i2c, reg_addr, ®_data);
|
|
pr_info("max77693: c: 0x%02x(0x%02x)\n", reg_addr, reg_data);
|
|
}
|
|
}
|
|
|
|
static int max77693_get_battery_present(struct max77693_charger_data *charger)
|
|
{
|
|
u8 reg_data;
|
|
|
|
if (max77693_read_reg(charger->max77693->i2c,
|
|
MAX77693_CHG_REG_CHG_INT_OK, ®_data) < 0) {
|
|
/* Eventhough there is an error,
|
|
don't do power-off */
|
|
return 1;
|
|
}
|
|
|
|
pr_debug("%s: CHG_INT_OK(0x%02x)\n", __func__, reg_data);
|
|
|
|
reg_data = ((reg_data & MAX77693_BATP_OK) >> MAX77693_BATP_OK_SHIFT);
|
|
|
|
return reg_data;
|
|
}
|
|
|
|
static void max77693_set_charger_state(struct max77693_charger_data *charger,
|
|
int enable)
|
|
{
|
|
u8 reg_data;
|
|
|
|
max77693_read_reg(charger->max77693->i2c,
|
|
MAX77693_CHG_REG_CHG_CNFG_00, ®_data);
|
|
|
|
if (enable)
|
|
reg_data |= MAX77693_MODE_CHGR;
|
|
else
|
|
reg_data &= ~MAX77693_MODE_CHGR;
|
|
|
|
pr_info("%s: CHG_CNFG_00(0x%02x)\n", __func__, reg_data);
|
|
max77693_write_reg(charger->max77693->i2c,
|
|
MAX77693_CHG_REG_CHG_CNFG_00, reg_data);
|
|
}
|
|
|
|
static void max77693_set_buck(struct max77693_charger_data *charger,
|
|
int enable)
|
|
{
|
|
u8 reg_data;
|
|
|
|
max77693_read_reg(charger->max77693->i2c,
|
|
MAX77693_CHG_REG_CHG_CNFG_00, ®_data);
|
|
|
|
if (enable)
|
|
reg_data |= MAX77693_MODE_BUCK;
|
|
else
|
|
reg_data &= ~MAX77693_MODE_BUCK;
|
|
|
|
pr_debug("%s: CHG_CNFG_00(0x%02x)\n", __func__, reg_data);
|
|
max77693_write_reg(charger->max77693->i2c,
|
|
MAX77693_CHG_REG_CHG_CNFG_00, reg_data);
|
|
}
|
|
|
|
static void max77693_set_input_current(struct max77693_charger_data *charger,
|
|
int cur)
|
|
{
|
|
int set_current_reg, now_current_reg;
|
|
int vbus_state, curr_step, delay;
|
|
u8 set_reg, reg_data;
|
|
|
|
mutex_lock(&charger->ops_lock);
|
|
disable_irq(charger->irq_chgin);
|
|
|
|
if (charger->cable_type == POWER_SUPPLY_TYPE_WPC)
|
|
set_reg = MAX77693_CHG_REG_CHG_CNFG_10;
|
|
else
|
|
set_reg = MAX77693_CHG_REG_CHG_CNFG_09;
|
|
|
|
|
|
if (cur <= 0) {
|
|
max77693_write_reg(charger->max77693->i2c,
|
|
set_reg, 0);
|
|
// max77693_set_buck(charger, DISABLE);
|
|
enable_irq(charger->irq_chgin);
|
|
mutex_unlock(&charger->ops_lock);
|
|
return;
|
|
} else
|
|
max77693_set_buck(charger, ENABLE);
|
|
|
|
set_current_reg = cur / 20;
|
|
max77693_read_reg(charger->max77693->i2c,
|
|
set_reg, ®_data);
|
|
if (reg_data == set_current_reg) {
|
|
/* check uvlo */
|
|
while(1) {
|
|
vbus_state = max77693_get_vbus_state(charger);
|
|
if (((vbus_state == 0x00) || (vbus_state == 0x01)) &&
|
|
(charger->cable_type != POWER_SUPPLY_TYPE_WPC)) {
|
|
/* UVLO */
|
|
set_current_reg -= 5;
|
|
if (set_current_reg <= 0)
|
|
break;
|
|
max77693_write_reg(charger->max77693->i2c,
|
|
set_reg, set_current_reg);
|
|
pr_info("%s: reg_data(0x%02x)\n", __func__, set_current_reg);
|
|
/* under 1.3A, slow rate */
|
|
if (set_current_reg < (1300 / 20) &&
|
|
(charger->cable_type == POWER_SUPPLY_TYPE_MAINS))
|
|
charger->aicl_on = true;
|
|
msleep(50);
|
|
} else
|
|
break;
|
|
}
|
|
enable_irq(charger->irq_chgin);
|
|
mutex_unlock(&charger->ops_lock);
|
|
return;
|
|
}
|
|
|
|
if (reg_data == 0) {
|
|
now_current_reg = SOFT_CHG_START_CURR / 20;
|
|
max77693_write_reg(charger->max77693->i2c,
|
|
set_reg, now_current_reg);
|
|
msleep(SOFT_CHG_START_DUR);
|
|
} else
|
|
now_current_reg = reg_data;
|
|
|
|
if (cur < 500) {
|
|
curr_step = 1;
|
|
delay = 50;
|
|
} else {
|
|
curr_step = SOFT_CHG_CURR_STEP / 20;
|
|
delay = SOFT_CHG_STEP_DUR;
|
|
}
|
|
now_current_reg += (curr_step);
|
|
|
|
while (now_current_reg < set_current_reg &&
|
|
charger->cable_type != POWER_SUPPLY_TYPE_BATTERY)
|
|
{
|
|
now_current_reg = min(now_current_reg, set_current_reg);
|
|
max77693_write_reg(charger->max77693->i2c,
|
|
set_reg, now_current_reg);
|
|
msleep(delay);
|
|
|
|
vbus_state = max77693_get_vbus_state(charger);
|
|
if (((vbus_state == 0x00) || (vbus_state == 0x01)) &&
|
|
!(charger->cable_type == POWER_SUPPLY_TYPE_WPC)) {
|
|
/* UVLO */
|
|
now_current_reg -= (curr_step * 3);
|
|
curr_step /= 2;
|
|
max77693_write_reg(charger->max77693->i2c,
|
|
set_reg, now_current_reg);
|
|
pr_info("%s: reg_data(0x%02x)\n", __func__, now_current_reg);
|
|
if (curr_step < 5) {
|
|
/* under 1.3A, slow rate */
|
|
if (now_current_reg < (1300 / 20) &&
|
|
(charger->cable_type == POWER_SUPPLY_TYPE_MAINS))
|
|
charger->aicl_on = true;
|
|
enable_irq(charger->irq_chgin);
|
|
mutex_unlock(&charger->ops_lock);
|
|
return;
|
|
}
|
|
msleep(50);
|
|
} else
|
|
now_current_reg += (curr_step);
|
|
}
|
|
|
|
pr_info("%s: reg_data(0x%02x)\n", __func__, set_current_reg);
|
|
max77693_write_reg(charger->max77693->i2c,
|
|
set_reg, set_current_reg);
|
|
enable_irq(charger->irq_chgin);
|
|
mutex_unlock(&charger->ops_lock);
|
|
}
|
|
|
|
static int max77693_get_input_current(struct max77693_charger_data *charger)
|
|
{
|
|
u8 reg_data;
|
|
int get_current = 0;
|
|
|
|
if (charger->cable_type == POWER_SUPPLY_TYPE_WPC) {
|
|
max77693_read_reg(charger->max77693->i2c,
|
|
MAX77693_CHG_REG_CHG_CNFG_10, ®_data);
|
|
pr_info("%s: CHG_CNFG_10(0x%02x)\n", __func__, reg_data);
|
|
} else {
|
|
max77693_read_reg(charger->max77693->i2c,
|
|
MAX77693_CHG_REG_CHG_CNFG_09, ®_data);
|
|
pr_info("%s: CHG_CNFG_09(0x%02x)\n", __func__, reg_data);
|
|
}
|
|
|
|
get_current = reg_data * 20;
|
|
|
|
pr_debug("%s: get input current: %dmA\n", __func__, get_current);
|
|
return get_current;
|
|
}
|
|
|
|
static void max77693_set_topoff_current(struct max77693_charger_data *charger,
|
|
int cur, int timeout)
|
|
{
|
|
u8 reg_data;
|
|
|
|
if (cur >= 350)
|
|
reg_data = 0x07;
|
|
else if (cur >= 300)
|
|
reg_data = 0x06;
|
|
else if (cur >= 250)
|
|
reg_data = 0x05;
|
|
else if (cur >= 200)
|
|
reg_data = 0x04;
|
|
else if (cur >= 175)
|
|
reg_data = 0x03;
|
|
else if (cur >= 150)
|
|
reg_data = 0x02;
|
|
else if (cur >= 125)
|
|
reg_data = 0x01;
|
|
else
|
|
reg_data = 0x00;
|
|
|
|
/* the unit of timeout is second*/
|
|
timeout = timeout / 60;
|
|
reg_data |= ((timeout / 10) << 3);
|
|
pr_info("%s: reg_data(0x%02x), topoff(%d)\n", __func__, reg_data, cur);
|
|
|
|
max77693_write_reg(charger->max77693->i2c,
|
|
MAX77693_CHG_REG_CHG_CNFG_03, reg_data);
|
|
}
|
|
|
|
static void max77693_set_charge_current(struct max77693_charger_data *charger,
|
|
int cur)
|
|
{
|
|
u8 reg_data = 0;
|
|
|
|
pr_info("%s: set current value : %d\n", __func__, cur);
|
|
|
|
if (!cur) {
|
|
/* No charger */
|
|
max77693_write_reg(charger->max77693->i2c,
|
|
MAX77693_CHG_REG_CHG_CNFG_02, 0x0);
|
|
} else {
|
|
reg_data &= ~MAX77693_CHG_CC;
|
|
reg_data |= ((cur * 3 / 100) << 0);
|
|
// reg_data |= MAX77693_OTG_ILIM;
|
|
|
|
pr_info("%s: charge current %d mA, reg_data(0x%02x)\n",
|
|
__func__, cur, reg_data);
|
|
|
|
max77693_write_reg(charger->max77693->i2c,
|
|
MAX77693_CHG_REG_CHG_CNFG_02, reg_data);
|
|
}
|
|
}
|
|
|
|
static int max77693_get_charge_current(struct max77693_charger_data *charger)
|
|
{
|
|
u8 reg_data;
|
|
int get_current = 0;
|
|
|
|
max77693_read_reg(charger->max77693->i2c,
|
|
MAX77693_CHG_REG_CHG_CNFG_02, ®_data);
|
|
pr_debug("%s: CHG_CNFG_02(0x%02x)\n", __func__, reg_data);
|
|
|
|
reg_data &= MAX77693_CHG_CC;
|
|
get_current = reg_data * 333 / 10;
|
|
|
|
pr_debug("%s: get charge current: %dmA\n", __func__, get_current);
|
|
return get_current;
|
|
}
|
|
|
|
static void reduce_input_current(struct max77693_charger_data *charger, int cur)
|
|
{
|
|
u8 set_reg;
|
|
u8 set_value;
|
|
|
|
if ((!charger->is_charging) || mutex_is_locked(&charger->ops_lock) ||
|
|
(charger->cable_type == POWER_SUPPLY_TYPE_WPC))
|
|
return;
|
|
set_reg = MAX77693_CHG_REG_CHG_CNFG_09;
|
|
if (!max77693_read_reg(charger->max77693->i2c,
|
|
set_reg, &set_value)) {
|
|
if (set_value == 0)
|
|
return;
|
|
set_value -= (cur / 20);
|
|
set_value = (set_value < 10) ? 10 : set_value;
|
|
max77693_write_reg(charger->max77693->i2c,
|
|
set_reg, set_value);
|
|
pr_info("%s: set current: reg:(0x%x), val:(0x%x)\n",
|
|
__func__, set_reg, set_value);
|
|
/* under 1.3A, slow rate */
|
|
if (set_value < (1300 / 20) &&
|
|
(charger->cable_type == POWER_SUPPLY_TYPE_MAINS))
|
|
charger->aicl_on = true;
|
|
}
|
|
}
|
|
|
|
static int max77693_get_vbus_state(struct max77693_charger_data *charger)
|
|
{
|
|
u8 reg_data;
|
|
|
|
max77693_read_reg(charger->max77693->i2c,
|
|
MAX77693_CHG_REG_CHG_DTLS_00, ®_data);
|
|
if (charger->cable_type == POWER_SUPPLY_TYPE_WPC)
|
|
reg_data = ((reg_data & MAX77693_WCIN_DTLS) >>
|
|
MAX77693_WCIN_DTLS_SHIFT);
|
|
else
|
|
reg_data = ((reg_data & MAX77693_CHGIN_DTLS) >>
|
|
MAX77693_CHGIN_DTLS_SHIFT);
|
|
|
|
switch (reg_data) {
|
|
case 0x00:
|
|
pr_info("%s: VBUS is invalid. CHGIN < CHGIN_UVLO\n",
|
|
__func__);
|
|
break;
|
|
case 0x01:
|
|
pr_info("%s: VBUS is invalid. CHGIN < MBAT+CHGIN2SYS" \
|
|
"and CHGIN > CHGIN_UVLO\n", __func__);
|
|
break;
|
|
case 0x02:
|
|
pr_info("%s: VBUS is invalid. CHGIN > CHGIN_OVLO",
|
|
__func__);
|
|
break;
|
|
case 0x03:
|
|
pr_info("%s: VBUS is valid. CHGIN < CHGIN_OVLO", __func__);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return reg_data;
|
|
}
|
|
|
|
static int max77693_get_charger_state(struct max77693_charger_data *charger)
|
|
{
|
|
int state;
|
|
u8 reg_data;
|
|
|
|
max77693_read_reg(charger->max77693->i2c,
|
|
MAX77693_CHG_REG_CHG_DTLS_01, ®_data);
|
|
reg_data = ((reg_data & MAX77693_CHG_DTLS) >> MAX77693_CHG_DTLS_SHIFT);
|
|
pr_info("%s: CHG_DTLS : 0x%2x\n", __func__, reg_data);
|
|
|
|
switch (reg_data) {
|
|
case 0x0:
|
|
case 0x1:
|
|
case 0x2:
|
|
state = POWER_SUPPLY_STATUS_CHARGING;
|
|
break;
|
|
case 0x3:
|
|
case 0x4:
|
|
state = POWER_SUPPLY_STATUS_FULL;
|
|
break;
|
|
case 0x5:
|
|
case 0x6:
|
|
case 0x7:
|
|
state = POWER_SUPPLY_STATUS_NOT_CHARGING;
|
|
break;
|
|
case 0x8:
|
|
case 0xA:
|
|
case 0xB:
|
|
state = POWER_SUPPLY_STATUS_DISCHARGING;
|
|
break;
|
|
default:
|
|
state = POWER_SUPPLY_STATUS_UNKNOWN;
|
|
break;
|
|
}
|
|
|
|
return state;
|
|
}
|
|
|
|
static int max77693_get_health_state(struct max77693_charger_data *charger)
|
|
{
|
|
int state;
|
|
int vbus_state;
|
|
int chg_state;
|
|
u8 reg_data;
|
|
|
|
max77693_read_reg(charger->max77693->i2c,
|
|
MAX77693_CHG_REG_CHG_DTLS_01, ®_data);
|
|
reg_data = ((reg_data & MAX77693_BAT_DTLS) >> MAX77693_BAT_DTLS_SHIFT);
|
|
|
|
switch (reg_data) {
|
|
case 0x00:
|
|
pr_info("%s: No battery and the charger is suspended\n",
|
|
__func__);
|
|
state = POWER_SUPPLY_HEALTH_UNKNOWN;
|
|
break;
|
|
case 0x01:
|
|
pr_info("%s: battery unspec failure\n",
|
|
__func__);
|
|
state = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
|
|
break;
|
|
case 0x02:
|
|
pr_info("%s: battery dead\n", __func__);
|
|
state = POWER_SUPPLY_HEALTH_DEAD;
|
|
break;
|
|
case 0x03:
|
|
pr_info("%s: battery good\n", __func__);
|
|
state = POWER_SUPPLY_HEALTH_GOOD;
|
|
break;
|
|
case 0x04:
|
|
pr_info("%s: battery is okay" \
|
|
"but its voltage is low\n", __func__);
|
|
state = POWER_SUPPLY_HEALTH_GOOD;
|
|
break;
|
|
case 0x05:
|
|
pr_info("%s: battery ovp\n", __func__);
|
|
state = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
|
|
break;
|
|
default:
|
|
pr_info("%s: battery unknown : 0x%d\n", __func__, reg_data);
|
|
state = POWER_SUPPLY_HEALTH_UNKNOWN;
|
|
break;
|
|
}
|
|
|
|
if (state == POWER_SUPPLY_HEALTH_GOOD) {
|
|
/* VBUS OVP state return battery OVP state */
|
|
vbus_state = max77693_get_vbus_state(charger);
|
|
if ((vbus_state == 0x00) || (vbus_state == 0x01))
|
|
reduce_input_current(charger, 20);
|
|
/* read CHG_DTLS and detecting battery terminal error */
|
|
chg_state = max77693_get_charger_state(charger);
|
|
/* OVP is higher priority */
|
|
if (vbus_state == 0x02) { /* CHGIN_OVLO */
|
|
pr_info("%s: vbus ovp\n", __func__);
|
|
state = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
|
|
} else if (reg_data == 0x04 &&
|
|
chg_state == POWER_SUPPLY_STATUS_FULL) {
|
|
pr_info("%s: battery terminal error\n", __func__);
|
|
state = POWER_SUPPLY_HEALTH_UNDERVOLTAGE;
|
|
}
|
|
}
|
|
|
|
return state;
|
|
}
|
|
|
|
static bool max77693_charger_unlock(struct max77693_charger_data *chg_data)
|
|
{
|
|
struct i2c_client *i2c = chg_data->max77693->i2c;
|
|
u8 reg_data;
|
|
u8 chgprot;
|
|
int retry_cnt = 0;
|
|
bool need_init = false;
|
|
pr_debug("%s\n", __func__);
|
|
pr_info("%s: charger unlock enable \n", __func__);
|
|
|
|
max77693_read_reg(i2c, MAX77693_CHG_REG_CHG_CNFG_06, ®_data);
|
|
chgprot = ((reg_data & 0x0C) >> 2);
|
|
|
|
if (chgprot == 0x03) {
|
|
pr_info("%s: unlocked state, return\n", __func__);
|
|
need_init = false;
|
|
goto unlock_finish;
|
|
}
|
|
|
|
do {
|
|
max77693_write_reg(i2c, MAX77693_CHG_REG_CHG_CNFG_06,
|
|
(0x03 << 2));
|
|
|
|
max77693_read_reg(i2c, MAX77693_CHG_REG_CHG_CNFG_06, ®_data);
|
|
chgprot = ((reg_data & 0x0C) >> 2);
|
|
|
|
if (chgprot != 0x03) {
|
|
pr_err("%s: unlock err, chgprot(0x%x), retry(%d)\n",
|
|
__func__, chgprot, retry_cnt);
|
|
msleep(100);
|
|
} else {
|
|
pr_info("%s: unlock success, chgprot(0x%x)\n",
|
|
__func__, chgprot);
|
|
need_init = true;
|
|
break;
|
|
}
|
|
} while ((chgprot != 0x03) && (++retry_cnt < 10));
|
|
|
|
unlock_finish:
|
|
return need_init;
|
|
}
|
|
|
|
static void max77693_charger_initialize(struct max77693_charger_data *charger)
|
|
{
|
|
u8 reg_data, float_voltage = 0x1D;
|
|
pr_debug("%s\n", __func__);
|
|
|
|
max77693_set_buck(charger, ENABLE);
|
|
|
|
/* unlock charger setting protect */
|
|
reg_data = (0x03 << 2);
|
|
max77693_write_reg(charger->max77693->i2c,
|
|
MAX77693_CHG_REG_CHG_CNFG_06, reg_data);
|
|
|
|
/*
|
|
* fast charge timer disable
|
|
* restart threshold disable
|
|
* pre-qual charge enable(default)
|
|
*/
|
|
reg_data = (0x0 << 0) | (0x03 << 4);
|
|
max77693_write_reg(charger->max77693->i2c,
|
|
MAX77693_CHG_REG_CHG_CNFG_01, reg_data);
|
|
|
|
/*
|
|
* confirm whether TA is connected or NOT
|
|
*/
|
|
if (max77693_read_reg(charger->max77693->i2c,
|
|
MAX77693_CHG_REG_CHG_INT_OK, ®_data) == 0) {
|
|
/* Check the charging status*/
|
|
if ((reg_data & (MAX77693_CHGIN_OK|MAX77693_WCIN_OK)) == 0) {
|
|
max77693_read_reg(charger->max77693->i2c,
|
|
MAX77693_CHG_REG_CHG_CNFG_00, ®_data);
|
|
reg_data &= ~(CHG_CNFG_00_CHG_MASK
|
|
| CHG_CNFG_00_BUCK_MASK);
|
|
max77693_write_reg(charger->max77693->i2c,
|
|
MAX77693_CHG_REG_CHG_CNFG_00, reg_data);
|
|
}
|
|
}
|
|
/*
|
|
* charge current 466mA(default)
|
|
* otg current limit 900mA
|
|
*/
|
|
max77693_read_reg(charger->max77693->i2c,
|
|
MAX77693_CHG_REG_CHG_CNFG_02, ®_data);
|
|
reg_data |= (1 << 7);
|
|
max77693_write_reg(charger->max77693->i2c,
|
|
MAX77693_CHG_REG_CHG_CNFG_02, reg_data);
|
|
|
|
/*
|
|
* top off current 100mA
|
|
* top off timer 40min
|
|
*/
|
|
reg_data = (0x04 << 3);
|
|
max77693_write_reg(charger->max77693->i2c,
|
|
MAX77693_CHG_REG_CHG_CNFG_03, reg_data);
|
|
|
|
/*
|
|
* cv voltage 4.2V or 4.35V
|
|
* MINVSYS 3.6V(default)
|
|
*/
|
|
max77693_read_reg(charger->max77693->i2c,
|
|
MAX77693_CHG_REG_CHG_CNFG_04, ®_data);
|
|
|
|
reg_data &= ~0x1F;
|
|
if (charger->pdata->chg_float_voltage) {
|
|
float_voltage = charger->pdata->chg_float_voltage <= 4325 ?
|
|
(charger->pdata->chg_float_voltage - 3650) / 25 :
|
|
(charger->pdata->chg_float_voltage - 3650) / 25 + 1;
|
|
}
|
|
|
|
reg_data |= float_voltage;
|
|
|
|
/*
|
|
pr_info("%s: battery cv voltage %s, (sysrev %d)\n", __func__,
|
|
(((reg_data & max77693_CHG_PRM_MASK) == \
|
|
(0x1D << max77693_CHG_PRM_SHIFT)) ? "4.35V" : "4.2V"),
|
|
system_rev);
|
|
*/
|
|
max77693_write_reg(charger->max77693->i2c,
|
|
MAX77693_CHG_REG_CHG_CNFG_04, reg_data);
|
|
|
|
/* VBYPSET Default set 3V */
|
|
reg_data = 0x00;
|
|
max77693_write_reg(charger->max77693->i2c,
|
|
MAX77693_CHG_REG_CHG_CNFG_11, reg_data);
|
|
|
|
max77693_dump_reg(charger);
|
|
}
|
|
|
|
static void check_charger_unlock_state(struct max77693_charger_data *chg_data)
|
|
{
|
|
bool need_reg_init = false;
|
|
pr_debug("%s\n", __func__);
|
|
|
|
pr_info("%s: unlock state enable\n", __func__);
|
|
need_reg_init = max77693_charger_unlock(chg_data);
|
|
if (need_reg_init) {
|
|
pr_err("%s: charger locked state, reg init\n", __func__);
|
|
max77693_charger_initialize(chg_data);
|
|
}
|
|
}
|
|
|
|
static int sec_chg_get_property(struct power_supply *psy,
|
|
enum power_supply_property psp,
|
|
union power_supply_propval *val)
|
|
{
|
|
struct max77693_charger_data *charger =
|
|
container_of(psy, struct max77693_charger_data, psy_chg);
|
|
u8 reg_data;
|
|
|
|
switch (psp) {
|
|
case POWER_SUPPLY_PROP_ONLINE:
|
|
val->intval = POWER_SUPPLY_TYPE_BATTERY;
|
|
if (max77693_read_reg(charger->max77693->i2c,
|
|
MAX77693_CHG_REG_CHG_INT_OK, ®_data) == 0) {
|
|
if (reg_data & MAX77693_CHGIN_OK)
|
|
val->intval = POWER_SUPPLY_TYPE_MAINS;
|
|
else if (reg_data & MAX77693_WCIN_OK)
|
|
val->intval = POWER_SUPPLY_TYPE_WPC;
|
|
}
|
|
break;
|
|
case POWER_SUPPLY_PROP_STATUS:
|
|
val->intval = max77693_get_charger_state(charger);
|
|
break;
|
|
case POWER_SUPPLY_PROP_HEALTH:
|
|
val->intval = max77693_get_health_state(charger);
|
|
break;
|
|
case POWER_SUPPLY_PROP_CURRENT_MAX:
|
|
val->intval = charger->charging_current_max;
|
|
break;
|
|
case POWER_SUPPLY_PROP_CURRENT_AVG:
|
|
val->intval = charger->charging_current;
|
|
break;
|
|
case POWER_SUPPLY_PROP_CURRENT_NOW:
|
|
val->intval = min(max77693_get_input_current(charger),
|
|
max77693_get_charge_current(charger));
|
|
break;
|
|
case POWER_SUPPLY_PROP_CHARGE_TYPE:
|
|
if (!charger->is_charging)
|
|
val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE;
|
|
else if (charger->aicl_on)
|
|
val->intval = POWER_SUPPLY_CHARGE_TYPE_SLOW;
|
|
else
|
|
val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST;
|
|
break;
|
|
case POWER_SUPPLY_PROP_PRESENT:
|
|
val->intval = max77693_get_battery_present(charger);
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sec_chg_set_property(struct power_supply *psy,
|
|
enum power_supply_property psp,
|
|
const union power_supply_propval *val)
|
|
{
|
|
struct max77693_charger_data *charger =
|
|
container_of(psy, struct max77693_charger_data, psy_chg);
|
|
union power_supply_propval value;
|
|
int set_charging_current, set_charging_current_max;
|
|
const int usb_charging_current = charger->pdata->charging_current[
|
|
POWER_SUPPLY_TYPE_USB].fast_charging_current;
|
|
const int wpc_charging_current = charger->pdata->charging_current[
|
|
POWER_SUPPLY_TYPE_WPC].input_current_limit;
|
|
|
|
/* check and unlock */
|
|
check_charger_unlock_state(charger);
|
|
|
|
switch (psp) {
|
|
case POWER_SUPPLY_PROP_STATUS:
|
|
charger->status = val->intval;
|
|
break;
|
|
/* val->intval : type */
|
|
case POWER_SUPPLY_PROP_ONLINE:
|
|
charger->cable_type = val->intval;
|
|
psy_do_property("battery", get,
|
|
POWER_SUPPLY_PROP_HEALTH, value);
|
|
if (val->intval == POWER_SUPPLY_TYPE_BATTERY) {
|
|
charger->is_charging = false;
|
|
charger->aicl_on = false;
|
|
set_charging_current = 0;
|
|
set_charging_current_max = 0;
|
|
} else {
|
|
charger->is_charging = true;
|
|
/* decrease the charging current according to siop level */
|
|
set_charging_current =
|
|
charger->charging_current * charger->siop_level / 100;
|
|
if (set_charging_current > 0 &&
|
|
set_charging_current < usb_charging_current)
|
|
set_charging_current = usb_charging_current;
|
|
if (val->intval == POWER_SUPPLY_TYPE_WPC)
|
|
set_charging_current_max = wpc_charging_current;
|
|
else
|
|
set_charging_current_max =
|
|
charger->charging_current_max;
|
|
}
|
|
|
|
max77693_set_charger_state(charger, charger->is_charging);
|
|
/* if battery full, only disable charging */
|
|
if ((charger->status == POWER_SUPPLY_STATUS_CHARGING) ||
|
|
(charger->status == POWER_SUPPLY_STATUS_DISCHARGING) ||
|
|
(value.intval == POWER_SUPPLY_HEALTH_UNSPEC_FAILURE)) {
|
|
|
|
/* current setting */
|
|
max77693_set_charge_current(charger,
|
|
set_charging_current);
|
|
max77693_set_input_current(charger,
|
|
set_charging_current_max);
|
|
max77693_set_topoff_current(charger,
|
|
charger->pdata->charging_current[
|
|
val->intval].full_check_current_1st,
|
|
charger->pdata->charging_current[
|
|
val->intval].full_check_current_2nd);
|
|
}
|
|
break;
|
|
/* val->intval : input charging current */
|
|
case POWER_SUPPLY_PROP_CURRENT_MAX:
|
|
charger->charging_current_max = val->intval;
|
|
break;
|
|
/* val->intval : charging current */
|
|
case POWER_SUPPLY_PROP_CURRENT_AVG:
|
|
charger->charging_current = val->intval;
|
|
break;
|
|
case POWER_SUPPLY_PROP_CURRENT_NOW:
|
|
charger->siop_level = val->intval;
|
|
if (charger->is_charging) {
|
|
/* decrease the charging current according to siop level */
|
|
int current_now =
|
|
charger->charging_current * val->intval / 100;
|
|
if (current_now > 0 &&
|
|
current_now < usb_charging_current)
|
|
current_now = usb_charging_current;
|
|
max77693_set_charge_current(charger, current_now);
|
|
}
|
|
break;
|
|
case POWER_SUPPLY_PROP_POWER_NOW:
|
|
max77693_set_charge_current(charger,
|
|
val->intval);
|
|
max77693_set_input_current(charger,
|
|
val->intval);
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void sec_chg_isr_work(struct work_struct *work)
|
|
{
|
|
struct max77693_charger_data *charger =
|
|
container_of(work, struct max77693_charger_data, isr_work.work);
|
|
|
|
union power_supply_propval val;
|
|
|
|
if (charger->pdata->full_check_type ==
|
|
SEC_BATTERY_FULLCHARGED_CHGINT) {
|
|
|
|
val.intval = max77693_get_charger_state(charger);
|
|
|
|
switch (val.intval) {
|
|
case POWER_SUPPLY_STATUS_DISCHARGING:
|
|
pr_err("%s: Interrupted but Discharging\n", __func__);
|
|
break;
|
|
|
|
case POWER_SUPPLY_STATUS_NOT_CHARGING:
|
|
pr_err("%s: Interrupted but NOT Charging\n", __func__);
|
|
break;
|
|
|
|
case POWER_SUPPLY_STATUS_FULL:
|
|
pr_info("%s: Interrupted by Full\n", __func__);
|
|
psy_do_property("battery", set,
|
|
POWER_SUPPLY_PROP_STATUS, val);
|
|
break;
|
|
|
|
case POWER_SUPPLY_STATUS_CHARGING:
|
|
pr_err("%s: Interrupted but Charging\n", __func__);
|
|
break;
|
|
|
|
case POWER_SUPPLY_STATUS_UNKNOWN:
|
|
default:
|
|
pr_err("%s: Invalid Charger Status\n", __func__);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (charger->pdata->ovp_uvlo_check_type ==
|
|
SEC_BATTERY_OVP_UVLO_CHGINT) {
|
|
|
|
val.intval = max77693_get_health_state(charger);
|
|
|
|
switch (val.intval) {
|
|
case POWER_SUPPLY_HEALTH_OVERHEAT:
|
|
case POWER_SUPPLY_HEALTH_COLD:
|
|
pr_err("%s: Interrupted but Hot/Cold\n", __func__);
|
|
break;
|
|
|
|
case POWER_SUPPLY_HEALTH_DEAD:
|
|
pr_err("%s: Interrupted but Dead\n", __func__);
|
|
break;
|
|
|
|
case POWER_SUPPLY_HEALTH_OVERVOLTAGE:
|
|
case POWER_SUPPLY_HEALTH_UNDERVOLTAGE:
|
|
pr_info("%s: Interrupted by OVP/UVLO\n", __func__);
|
|
psy_do_property("battery", set,
|
|
POWER_SUPPLY_PROP_HEALTH, val);
|
|
break;
|
|
|
|
case POWER_SUPPLY_HEALTH_UNSPEC_FAILURE:
|
|
pr_err("%s: Interrupted but Unspec\n", __func__);
|
|
break;
|
|
|
|
case POWER_SUPPLY_HEALTH_GOOD:
|
|
pr_err("%s: Interrupted but Good\n", __func__);
|
|
break;
|
|
|
|
case POWER_SUPPLY_HEALTH_UNKNOWN:
|
|
default:
|
|
pr_err("%s: Invalid Charger Health\n", __func__);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static int max77693_debugfs_show(struct seq_file *s, void *data)
|
|
{
|
|
struct max77693_charger_data *charger = s->private;
|
|
u8 reg;
|
|
u8 reg_data;
|
|
|
|
seq_printf(s, "MAX77693 CHARGER IC :\n");
|
|
seq_printf(s, "==================\n");
|
|
for (reg = 0xB0; reg <= 0xC5; reg++) {
|
|
max77693_read_reg(charger->max77693->i2c, reg, ®_data);
|
|
seq_printf(s, "0x%02x:\t0x%02x\n", reg, reg_data);
|
|
}
|
|
|
|
seq_printf(s, "\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int max77693_debugfs_open(struct inode *inode, struct file *file)
|
|
{
|
|
return single_open(file, max77693_debugfs_show, inode->i_private);
|
|
}
|
|
|
|
static const struct file_operations max77693_debugfs_fops = {
|
|
.open = max77693_debugfs_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
static irqreturn_t sec_chg_irq_thread(int irq, void *irq_data)
|
|
{
|
|
struct max77693_charger_data *charger = irq_data;
|
|
|
|
pr_info("%s: Charger interrupt occured\n", __func__);
|
|
|
|
if ((charger->pdata->full_check_type ==
|
|
SEC_BATTERY_FULLCHARGED_CHGINT) ||
|
|
(charger->pdata->ovp_uvlo_check_type ==
|
|
SEC_BATTERY_OVP_UVLO_CHGINT))
|
|
schedule_delayed_work(&charger->isr_work, 0);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
#if defined(CONFIG_WIRELESS_CHARGING)
|
|
static irqreturn_t wpc_charger_irq(int irq, void *data)
|
|
{
|
|
struct max77693_charger_data *chg_data = data;
|
|
int wc_w_state;
|
|
union power_supply_propval value;
|
|
pr_info("%s: irq(%d)\n", __func__, irq);
|
|
|
|
/* check and unlock */
|
|
check_charger_unlock_state(chg_data);
|
|
|
|
wc_w_state = 0;
|
|
|
|
wc_w_state = !gpio_get_value(chg_data->wc_w_gpio);
|
|
if ((chg_data->wc_w_state == 0) && (wc_w_state == 1)) {
|
|
value.intval = POWER_SUPPLY_TYPE_WPC<<ONLINE_TYPE_MAIN_SHIFT;
|
|
psy_do_property("battery", set,
|
|
POWER_SUPPLY_PROP_ONLINE, value);
|
|
pr_info("%s: wpc activated, set V_INT as PN\n",
|
|
__func__);
|
|
} else if ((chg_data->wc_w_state == 1) && (wc_w_state == 0)) {
|
|
value.intval =
|
|
POWER_SUPPLY_TYPE_BATTERY<<ONLINE_TYPE_MAIN_SHIFT;
|
|
psy_do_property("battery", set,
|
|
POWER_SUPPLY_PROP_ONLINE, value);
|
|
pr_info("%s: wpc deactivated, set V_INT as PD\n",
|
|
__func__);
|
|
}
|
|
pr_info("%s: w(%d to %d)\n", __func__,
|
|
chg_data->wc_w_state, wc_w_state);
|
|
|
|
chg_data->wc_w_state = wc_w_state;
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
#endif
|
|
|
|
static irqreturn_t max77693_bypass_irq(int irq, void *data)
|
|
{
|
|
struct max77693_charger_data *chg_data = data;
|
|
u8 dtls_02;
|
|
u8 byp_dtls;
|
|
u8 chg_cnfg_00;
|
|
|
|
pr_info("%s: irq(%d)\n", __func__, irq);
|
|
|
|
/* check and unlock */
|
|
check_charger_unlock_state(chg_data);
|
|
|
|
max77693_read_reg(chg_data->max77693->i2c,
|
|
MAX77693_CHG_REG_CHG_DTLS_02,
|
|
&dtls_02);
|
|
|
|
byp_dtls = ((dtls_02 & MAX77693_BYP_DTLS) >>
|
|
MAX77693_BYP_DTLS_SHIFT);
|
|
pr_info("%s: BYP_DTLS(0x%02x)\n", __func__, byp_dtls);
|
|
|
|
if (byp_dtls & 0x1) {
|
|
pr_info("%s: bypass overcurrent limit\n", __func__);
|
|
#ifdef CONFIG_USB_HOST_NOTIFY
|
|
/* max77693_muic_host_notify_cb(0); */
|
|
#endif
|
|
/* disable the register values just related to OTG and
|
|
keep the values about the charging */
|
|
max77693_read_reg(chg_data->max77693->i2c,
|
|
MAX77693_CHG_REG_CHG_CNFG_00, &chg_cnfg_00);
|
|
chg_cnfg_00 &= ~(CHG_CNFG_00_OTG_MASK
|
|
| CHG_CNFG_00_BOOST_MASK
|
|
| CHG_CNFG_00_DIS_MUIC_CTRL_MASK);
|
|
max77693_write_reg(chg_data->max77693->i2c,
|
|
MAX77693_CHG_REG_CHG_CNFG_00,
|
|
chg_cnfg_00);
|
|
}
|
|
if (byp_dtls & 0x8)
|
|
reduce_input_current(chg_data, 100);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static void max77693_chgin_isr_work(struct work_struct *work)
|
|
{
|
|
struct max77693_charger_data *charger = container_of(work,
|
|
struct max77693_charger_data, chgin_work);
|
|
u8 chgin_dtls;
|
|
u8 prev_chgin_dtls = 0xff;
|
|
int battery_health;
|
|
union power_supply_propval value;
|
|
int stable_count = 0;
|
|
|
|
disable_irq(charger->irq_chgin);
|
|
|
|
while (1) {
|
|
psy_do_property("battery", get,
|
|
POWER_SUPPLY_PROP_HEALTH, value);
|
|
battery_health = value.intval;
|
|
|
|
max77693_read_reg(charger->max77693->i2c,
|
|
MAX77693_CHG_REG_CHG_DTLS_00,
|
|
&chgin_dtls);
|
|
chgin_dtls = ((chgin_dtls & MAX77693_CHGIN_DTLS) >>
|
|
MAX77693_CHGIN_DTLS_SHIFT);
|
|
if (prev_chgin_dtls == chgin_dtls)
|
|
stable_count++;
|
|
else
|
|
stable_count = 0;
|
|
if (stable_count > 10) {
|
|
pr_info("%s: irq(%d), chgin(0x%x), prev 0x%x\n",
|
|
__func__, charger->irq_chgin,
|
|
chgin_dtls, prev_chgin_dtls);
|
|
break;
|
|
}
|
|
|
|
if (charger->is_charging) {
|
|
if ((chgin_dtls == 0x02) && \
|
|
(battery_health == POWER_SUPPLY_HEALTH_GOOD)) {
|
|
pr_info("%s: charger is over voltage\n",
|
|
__func__);
|
|
value.intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
|
|
psy_do_property("battery", set,
|
|
POWER_SUPPLY_PROP_HEALTH, value);
|
|
} else if (battery_health == \
|
|
POWER_SUPPLY_HEALTH_OVERVOLTAGE) {
|
|
pr_info("%s: charger is good\n", __func__);
|
|
value.intval = POWER_SUPPLY_HEALTH_GOOD;
|
|
psy_do_property("battery", set,
|
|
POWER_SUPPLY_PROP_HEALTH, value);
|
|
}
|
|
if ((chgin_dtls == 0x0) || (chgin_dtls == 0x01))
|
|
reduce_input_current(charger, 20);
|
|
}
|
|
prev_chgin_dtls = chgin_dtls;
|
|
msleep(100);
|
|
}
|
|
enable_irq(charger->irq_chgin);
|
|
}
|
|
|
|
static irqreturn_t max77693_chgin_irq(int irq, void *data)
|
|
{
|
|
struct max77693_charger_data *charger = data;
|
|
queue_work(charger->wqueue, &charger->chgin_work);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static __devinit int max77693_charger_probe(struct platform_device *pdev)
|
|
{
|
|
struct max77693_dev *iodev = dev_get_drvdata(pdev->dev.parent);
|
|
struct max77693_platform_data *pdata = dev_get_platdata(iodev->dev);
|
|
struct max77693_charger_data *charger;
|
|
int ret = 0;
|
|
|
|
pr_info("%s: max77693 Charger driver probe\n", __func__);
|
|
|
|
charger = kzalloc(sizeof(*charger), GFP_KERNEL);
|
|
if (!charger)
|
|
return -ENOMEM;
|
|
|
|
charger->max77693 = iodev;
|
|
charger->pdata = pdata->charger_data;
|
|
charger->aicl_on = false;
|
|
charger->siop_level = 100;
|
|
|
|
platform_set_drvdata(pdev, charger);
|
|
|
|
charger->psy_chg.name = "sec-charger";
|
|
charger->psy_chg.type = POWER_SUPPLY_TYPE_UNKNOWN;
|
|
charger->psy_chg.get_property = sec_chg_get_property;
|
|
charger->psy_chg.set_property = sec_chg_set_property;
|
|
charger->psy_chg.properties = sec_charger_props;
|
|
charger->psy_chg.num_properties = ARRAY_SIZE(sec_charger_props);
|
|
|
|
mutex_init(&charger->ops_lock);
|
|
|
|
if (charger->pdata->chg_gpio_init) {
|
|
if (!charger->pdata->chg_gpio_init()) {
|
|
pr_err("%s: Failed to Initialize GPIO\n", __func__);
|
|
goto err_free;
|
|
}
|
|
}
|
|
|
|
max77693_charger_initialize(charger);
|
|
|
|
ret = power_supply_register(&pdev->dev, &charger->psy_chg);
|
|
if (ret) {
|
|
pr_err("%s: Failed to Register psy_chg\n", __func__);
|
|
goto err_free;
|
|
}
|
|
|
|
if (charger->pdata->chg_irq) {
|
|
INIT_DELAYED_WORK_DEFERRABLE(
|
|
&charger->isr_work, sec_chg_isr_work);
|
|
ret = request_threaded_irq(charger->pdata->chg_irq,
|
|
NULL, sec_chg_irq_thread,
|
|
charger->pdata->chg_irq_attr,
|
|
"charger-irq", charger);
|
|
if (ret) {
|
|
pr_err("%s: Failed to Reqeust IRQ\n", __func__);
|
|
goto err_irq;
|
|
}
|
|
}
|
|
|
|
#if defined(CONFIG_WIRELESS_CHARGING)
|
|
charger->wc_w_gpio = pdata->wc_irq_gpio;
|
|
if (charger->wc_w_gpio) {
|
|
charger->wc_w_irq = gpio_to_irq(charger->wc_w_gpio);
|
|
ret = gpio_request(charger->wc_w_gpio, "wpc_charger-irq");
|
|
if (ret < 0) {
|
|
pr_err("%s: failed requesting gpio %d\n", __func__,
|
|
charger->wc_w_gpio);
|
|
goto err_wc_irq;
|
|
}
|
|
ret = request_threaded_irq(charger->wc_w_irq,
|
|
NULL, wpc_charger_irq,
|
|
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING |
|
|
IRQF_ONESHOT,
|
|
"wpc-int", charger);
|
|
if (ret) {
|
|
pr_err("%s: Failed to Reqeust IRQ\n", __func__);
|
|
goto err_wc_irq;
|
|
}
|
|
enable_irq_wake(charger->wc_w_irq);
|
|
charger->wc_w_state = !gpio_get_value(charger->wc_w_gpio);
|
|
}
|
|
#endif
|
|
|
|
charger->wqueue =
|
|
create_singlethread_workqueue(dev_name(&pdev->dev));
|
|
INIT_WORK(&charger->chgin_work, max77693_chgin_isr_work);
|
|
if (!charger->wqueue) {
|
|
pr_err("%s: Fail to Create Workqueue\n", __func__);
|
|
goto err_workqueue;
|
|
}
|
|
|
|
charger->irq_chgin = pdata->irq_base + MAX77693_CHG_IRQ_CHGIN_I;
|
|
ret = request_threaded_irq(charger->irq_chgin, NULL,
|
|
max77693_chgin_irq, 0, "chgin-irq", charger);
|
|
if (ret < 0) {
|
|
pr_err("%s: fail to request chgin IRQ: %d: %d\n",
|
|
__func__, charger->irq_chgin, ret);
|
|
goto err_chgin_irq;
|
|
}
|
|
|
|
charger->irq_bypass = pdata->irq_base + MAX77693_CHG_IRQ_BYP_I;
|
|
ret = request_threaded_irq(charger->irq_bypass, NULL,
|
|
max77693_bypass_irq, 0, "bypass-irq", charger);
|
|
if (ret < 0)
|
|
pr_err("%s: fail to request bypass IRQ: %d: %d\n",
|
|
__func__, charger->irq_bypass, ret);
|
|
|
|
max77693_dentry = debugfs_create_file("max77693-regs",
|
|
S_IRUSR, NULL, charger, &max77693_debugfs_fops);
|
|
return 0;
|
|
|
|
err_chgin_irq:
|
|
destroy_workqueue(charger->wqueue);
|
|
err_workqueue:
|
|
#if defined(CONFIG_WIRELESS_CHARGING)
|
|
if (charger->wc_w_irq)
|
|
free_irq(charger->wc_w_irq, NULL);
|
|
err_wc_irq:
|
|
#endif
|
|
if (charger->pdata->chg_irq)
|
|
free_irq(charger->pdata->chg_irq, NULL);
|
|
err_irq:
|
|
power_supply_unregister(&charger->psy_chg);
|
|
err_free:
|
|
kfree(charger);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
static int __devexit max77693_charger_remove(struct platform_device *pdev)
|
|
{
|
|
struct max77693_charger_data *charger =
|
|
platform_get_drvdata(pdev);
|
|
|
|
if (!IS_ERR_OR_NULL(max77693_dentry))
|
|
debugfs_remove(max77693_dentry);
|
|
|
|
destroy_workqueue(charger->wqueue);
|
|
#if defined(CONFIG_WIRELESS_CHARGING)
|
|
if (charger->wc_w_irq)
|
|
free_irq(charger->wc_w_irq, NULL);
|
|
#endif
|
|
if (charger->pdata->chg_irq)
|
|
free_irq(charger->pdata->chg_irq, NULL);
|
|
|
|
power_supply_unregister(&charger->psy_chg);
|
|
kfree(charger);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#if defined CONFIG_PM
|
|
static int max77693_charger_suspend(struct device *dev)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int max77693_charger_resume(struct device *dev)
|
|
{
|
|
return 0;
|
|
}
|
|
#else
|
|
#define max77693_charger_suspend NULL
|
|
#define max77693_charger_resume NULL
|
|
#endif
|
|
|
|
static SIMPLE_DEV_PM_OPS(max77693_charger_pm_ops, max77693_charger_suspend,
|
|
max77693_charger_resume);
|
|
|
|
void max77693_charger_shutdown(struct device *dev)
|
|
{
|
|
struct max77693_charger_data *charger =
|
|
dev_get_drvdata(dev);
|
|
u8 reg_data;
|
|
|
|
if (!charger->max77693->i2c) {
|
|
pr_err("%s: no max77693 i2c client\n", __func__);
|
|
return;
|
|
}
|
|
reg_data = 0x04;
|
|
max77693_write_reg(charger->max77693->i2c,
|
|
MAX77693_CHG_REG_CHG_CNFG_00, reg_data);
|
|
reg_data = 0x19;
|
|
max77693_write_reg(charger->max77693->i2c,
|
|
MAX77693_CHG_REG_CHG_CNFG_09, reg_data);
|
|
reg_data = 0x19;
|
|
max77693_write_reg(charger->max77693->i2c,
|
|
MAX77693_CHG_REG_CHG_CNFG_10, reg_data);
|
|
pr_info("func:%s \n", __func__);
|
|
}
|
|
|
|
static struct platform_driver max77693_charger_driver = {
|
|
.driver = {
|
|
.name = "max77693-charger",
|
|
.owner = THIS_MODULE,
|
|
.pm = &max77693_charger_pm_ops,
|
|
.shutdown = max77693_charger_shutdown,
|
|
},
|
|
.probe = max77693_charger_probe,
|
|
.remove = __devexit_p(max77693_charger_remove),
|
|
};
|
|
|
|
static int __init max77693_charger_init(void)
|
|
{
|
|
pr_info("func:%s\n", __func__);
|
|
return platform_driver_register(&max77693_charger_driver);
|
|
}
|
|
module_init(max77693_charger_init);
|
|
|
|
static void __exit max77693_charger_exit(void)
|
|
{
|
|
platform_driver_register(&max77693_charger_driver);
|
|
}
|
|
|
|
module_exit(max77693_charger_exit);
|
|
|
|
MODULE_DESCRIPTION("max77693 charger driver");
|
|
MODULE_AUTHOR("Samsung Electronics");
|
|
MODULE_LICENSE("GPL");
|
|
|