2161 lines
54 KiB
C
Executable file
2161 lines
54 KiB
C
Executable file
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (c) 2022 Southchip Semiconductor Technology(Shanghai) Co., Ltd.
|
|
*/
|
|
|
|
#define pr_fmt(fmt) "[sc854x] %s: " fmt, __func__
|
|
|
|
#include <linux/gpio.h>
|
|
#include <linux/i2c.h>
|
|
#include <linux/init.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/module.h>
|
|
#include <linux/power_supply.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/kthread.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_device.h>
|
|
#include <linux/of_gpio.h>
|
|
#include <linux/err.h>
|
|
#include <linux/regulator/driver.h>
|
|
#include <linux/regulator/of_regulator.h>
|
|
#include <linux/regulator/machine.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/bitops.h>
|
|
#include <linux/math64.h>
|
|
|
|
//#include "mtk_charger_intf.h"
|
|
#include "sc854x_reg.h"
|
|
#include "mtk_charger.h"
|
|
|
|
//prize add by lipengpeng 20220913 start
|
|
#if IS_ENABLED(CONFIG_FACTORY_CHARGE)&&IS_ENABLED(CONFIG_SECOND_CHARGER_SUPPORT)
|
|
extern int is_chg2_exist;
|
|
#endif /* CONFIG_FACTORY_CHARGE CONFIG_SECOND_CHARGER_SUPPORT*/
|
|
//prize add by lipengpeng 20220913 end
|
|
|
|
enum {
|
|
SC8545_ID,
|
|
SC8546_ID,
|
|
SC8547_ID,
|
|
SC8549_ID,
|
|
};
|
|
|
|
static int id_data[] = {
|
|
[SC8545_ID] = 0x66,
|
|
[SC8546_ID] = 0x67,
|
|
[SC8547_ID] = 0x66,
|
|
[SC8549_ID] = 0x49,//drv add by liuruiqian for chip_id,20240311
|
|
};
|
|
|
|
enum {
|
|
SC8545_STANDALONG = 0,
|
|
SC8545_MASTER,
|
|
SC8545_SLAVE,
|
|
SC8546_STANDALONG,
|
|
SC8546_MASTER,
|
|
SC8546_SLAVE,
|
|
SC8547_STANDALONG,
|
|
SC8547_MASTER,
|
|
SC8547_SLAVE,
|
|
SC8549_STANDALONG,
|
|
SC8549_MASTER,
|
|
SC8549_SLAVE,
|
|
};
|
|
|
|
static int sc854x_mode_data[] = {
|
|
[SC8545_STANDALONG] = SC8545_STANDALONG,
|
|
[SC8545_MASTER] = SC8545_MASTER,
|
|
[SC8545_SLAVE] = SC8545_SLAVE,
|
|
[SC8546_STANDALONG] = SC8546_STANDALONG,
|
|
[SC8546_MASTER] = SC8546_MASTER,
|
|
[SC8546_SLAVE] = SC8546_SLAVE,
|
|
[SC8547_STANDALONG] = SC8547_STANDALONG,
|
|
[SC8547_MASTER] = SC8547_MASTER,
|
|
[SC8547_SLAVE] = SC8547_SLAVE,
|
|
[SC8549_STANDALONG] = SC8549_STANDALONG,
|
|
[SC8549_MASTER] = SC8549_MASTER,
|
|
[SC8549_SLAVE] = SC8549_SLAVE,
|
|
};
|
|
|
|
typedef enum{
|
|
STANDALONE = 0,
|
|
MASTER,
|
|
SLAVE,
|
|
}WORK_MODE;
|
|
|
|
enum SC854X_ADC_CH{
|
|
ADC_IBUS,
|
|
ADC_VBUS,
|
|
ADC_VAC,
|
|
ADC_VOUT,
|
|
ADC_VBAT,
|
|
ADC_IBAT,
|
|
ADC_TDIE,
|
|
ADC_MAX_NUM,
|
|
};
|
|
|
|
static const u32 sc854x_adc_accuracy_tbl[ADC_MAX_NUM] = {
|
|
150000,/* IBUS */
|
|
35000,/* VBUS */
|
|
35000,/* VAC */
|
|
20000,/* VOUT */
|
|
20000,/* VBAT */
|
|
200000,/* IBAT */
|
|
4/* TDIE */
|
|
};
|
|
|
|
#define sc_err(fmt, ...) \
|
|
do { \
|
|
if (sc->mode == STANDALONE) \
|
|
printk(KERN_ERR "[sc854x-STANDALONE]:%s:" fmt, __func__, ##__VA_ARGS__);\
|
|
else if (sc->mode == MASTER) \
|
|
printk(KERN_ERR "[sc854x-MASTER]:%s:" fmt, __func__, ##__VA_ARGS__);\
|
|
else \
|
|
printk(KERN_ERR "[sc854x-SLAVE]:%s:" fmt, __func__, ##__VA_ARGS__);\
|
|
} while(0);
|
|
|
|
#define sc_info(fmt, ...) \
|
|
do { \
|
|
if (sc->mode == STANDALONE) \
|
|
printk(KERN_INFO "[sc854x-STANDALONE]:%s:" fmt, __func__, ##__VA_ARGS__);\
|
|
else if (sc->mode == MASTER) \
|
|
printk(KERN_INFO "[sc854x-MASTER]:%s:" fmt, __func__, ##__VA_ARGS__);\
|
|
else \
|
|
printk(KERN_INFO "[sc854x-SLAVE]:%s:" fmt, __func__, ##__VA_ARGS__);\
|
|
} while(0);
|
|
|
|
#define sc_dbg(fmt, ...) \
|
|
do { \
|
|
if (sc->mode == STANDALONE) \
|
|
printk(KERN_DEBUG "[sc854x-STANDALONE]:%s:" fmt, __func__, ##__VA_ARGS__);\
|
|
else if (sc->mode == MASTER) \
|
|
printk(KERN_DEBUG "[sc854x-MASTER]:%s:" fmt, __func__, ##__VA_ARGS__);\
|
|
else \
|
|
printk(KERN_DEBUG "[sc854x-MASTER]:%s:" fmt, __func__, ##__VA_ARGS__);\
|
|
} while(0);
|
|
|
|
|
|
struct sc854x_cfg {
|
|
const char *chg_name;
|
|
bool bat_ovp_disable;
|
|
bool bat_ocp_disable;
|
|
bool vdrop_ovp_disable;
|
|
|
|
int bat_ovp_th;
|
|
int bat_ocp_th;
|
|
|
|
bool bus_ovp_disable;
|
|
bool bus_ocp_disable;
|
|
bool bus_ucp_disable;
|
|
|
|
int bus_ovp_th;
|
|
int bus_ocp_th;
|
|
|
|
int ac_ovp_th;
|
|
|
|
int sense_r_mohm;
|
|
};
|
|
|
|
struct sc854x {
|
|
struct device *dev;
|
|
struct i2c_client *client;
|
|
|
|
int mode;
|
|
|
|
int irq_gpio;
|
|
int irq;
|
|
|
|
struct mutex data_lock;
|
|
struct mutex i2c_rw_lock;
|
|
struct mutex irq_complete;
|
|
|
|
bool irq_waiting;
|
|
bool irq_disabled;
|
|
bool resume_completed;
|
|
|
|
bool usb_present;
|
|
bool charge_enabled; /* Register bit status */
|
|
|
|
int vbus_error;
|
|
|
|
/* ADC reading */
|
|
int vbat_volt;
|
|
int vbus_volt;
|
|
int vout_volt;
|
|
int vac_volt;
|
|
|
|
int ibat_curr;
|
|
int ibus_curr;
|
|
|
|
int die_temp;
|
|
|
|
struct sc854x_cfg *cfg;
|
|
|
|
int skip_writes;
|
|
int skip_reads;
|
|
|
|
struct sc854x_platform_data *platform_data;
|
|
|
|
struct charger_device *chg_dev;
|
|
struct charger_properties chg_prop;
|
|
|
|
struct power_supply_desc psy_desc;
|
|
struct power_supply_config psy_cfg;
|
|
struct power_supply *fc2_psy;
|
|
struct power_supply *cw_bat;
|
|
struct power_supply *charge_psy;
|
|
bool sc_adc_disabled;
|
|
};
|
|
|
|
/************************************************************************/
|
|
static int __sc854x_read_word(struct sc854x *sc, u8 reg, u16 *data)
|
|
{
|
|
s32 ret;
|
|
|
|
ret = i2c_smbus_read_word_data(sc->client, reg);
|
|
if (ret < 0) {
|
|
sc_err("i2c read fail: can't read from reg 0x%02X\n", reg);
|
|
return ret;
|
|
}
|
|
|
|
*data = (u16) ret;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int __sc854x_read_byte(struct sc854x *sc, u8 reg, u8 *data)
|
|
{
|
|
s32 ret;
|
|
|
|
ret = i2c_smbus_read_byte_data(sc->client, reg);
|
|
if (ret < 0) {
|
|
sc_err("i2c read fail: can't read from reg 0x%02X\n", reg);
|
|
return ret;
|
|
}
|
|
|
|
*data = (u8) ret;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int __sc854x_write_byte(struct sc854x *sc, int reg, u8 val)
|
|
{
|
|
s32 ret;
|
|
|
|
ret = i2c_smbus_write_byte_data(sc->client, reg, val);
|
|
if (ret < 0) {
|
|
sc_err("i2c write fail: can't write 0x%02X to reg 0x%02X: %d\n",
|
|
val, reg, ret);
|
|
return ret;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int sc854x_read_byte(struct sc854x *sc, u8 reg, u8 *data)
|
|
{
|
|
int ret;
|
|
|
|
if (sc->skip_reads) {
|
|
*data = 0;
|
|
return 0;
|
|
}
|
|
|
|
mutex_lock(&sc->i2c_rw_lock);
|
|
ret = __sc854x_read_byte(sc, reg, data);
|
|
mutex_unlock(&sc->i2c_rw_lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int sc854x_write_byte(struct sc854x *sc, u8 reg, u8 data)
|
|
{
|
|
int ret;
|
|
|
|
if (sc->skip_writes)
|
|
return 0;
|
|
|
|
mutex_lock(&sc->i2c_rw_lock);
|
|
ret = __sc854x_write_byte(sc, reg, data);
|
|
mutex_unlock(&sc->i2c_rw_lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int sc854x_read_word(struct sc854x *sc, u8 reg, u16 *data)
|
|
{
|
|
int ret;
|
|
|
|
if (sc->skip_reads) {
|
|
*data = 0;
|
|
return 0;
|
|
}
|
|
|
|
mutex_lock(&sc->i2c_rw_lock);
|
|
ret = __sc854x_read_word(sc, reg, data);
|
|
mutex_unlock(&sc->i2c_rw_lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int sc854x_update_bits(struct sc854x*sc, u8 reg,
|
|
u8 mask, u8 data)
|
|
{
|
|
int ret;
|
|
u8 tmp;
|
|
|
|
if (sc->skip_reads || sc->skip_writes)
|
|
return 0;
|
|
|
|
mutex_lock(&sc->i2c_rw_lock);
|
|
ret = __sc854x_read_byte(sc, reg, &tmp);
|
|
if (ret) {
|
|
sc_err("Failed: reg=%02X, ret=%d\n", reg, ret);
|
|
goto out;
|
|
}
|
|
|
|
tmp &= ~mask;
|
|
tmp |= data & mask;
|
|
|
|
ret = __sc854x_write_byte(sc, reg, tmp);
|
|
if (ret)
|
|
sc_err("Failed: reg=%02X, ret=%d\n", reg, ret);
|
|
|
|
out:
|
|
mutex_unlock(&sc->i2c_rw_lock);
|
|
return ret;
|
|
}
|
|
|
|
/*********************************************************************/
|
|
static int sc854x_enable_batovp(struct sc854x *sc, bool enable)
|
|
{
|
|
u8 val;
|
|
|
|
if (enable)
|
|
val = SC854X_BAT_OVP_ENABLE;
|
|
else
|
|
val = SC854X_BAT_OVP_DISABLE;
|
|
|
|
val <<= SC854X_BAT_OVP_DIS_SHIFT;
|
|
|
|
return sc854x_update_bits(sc, SC854X_REG_00,
|
|
SC854X_BAT_OVP_DIS_MASK, val);
|
|
}
|
|
|
|
static int sc854x_set_batovp_th(struct sc854x *sc, int threshold)
|
|
{
|
|
u8 val;
|
|
|
|
if (threshold < SC854X_BAT_OVP_BASE)
|
|
threshold = SC854X_BAT_OVP_BASE;
|
|
|
|
val = (threshold - SC854X_BAT_OVP_BASE) / SC854X_BAT_OVP_LSB;
|
|
|
|
val <<= SC854X_BAT_OVP_SHIFT;
|
|
|
|
return sc854x_update_bits(sc, SC854X_REG_00,
|
|
SC854X_BAT_OVP_MASK, val);
|
|
}
|
|
|
|
static int sc854x_enable_batocp(struct sc854x *sc, bool enable)
|
|
{
|
|
u8 val;
|
|
|
|
if (enable)
|
|
val = SC854X_BAT_OCP_ENABLE;
|
|
else
|
|
val = SC854X_BAT_OCP_DISABLE;
|
|
|
|
val <<= SC854X_BAT_OCP_DIS_SHIFT;
|
|
|
|
return sc854x_update_bits(sc, SC854X_REG_01,
|
|
SC854X_BAT_OCP_DIS_MASK, val);
|
|
}
|
|
|
|
static int sc854x_set_batocp_th(struct sc854x *sc, int threshold)
|
|
{
|
|
u8 val;
|
|
|
|
if (threshold < SC854X_BAT_OCP_BASE)
|
|
threshold = SC854X_BAT_OCP_BASE;
|
|
|
|
val = (threshold - SC854X_BAT_OCP_BASE) / SC854X_BAT_OCP_LSB;
|
|
|
|
val <<= SC854X_BAT_OCP_SHIFT;
|
|
|
|
return sc854x_update_bits(sc, SC854X_REG_01,
|
|
SC854X_BAT_OCP_MASK, val);
|
|
}
|
|
|
|
static int sc854x_set_acovp_th(struct sc854x *sc, int threshold)
|
|
{
|
|
u8 val;
|
|
|
|
if (threshold < SC854X_AC_OVP_BASE)
|
|
threshold = SC854X_AC_OVP_BASE;
|
|
|
|
if (threshold == SC854X_AC_OVP_6P5V)
|
|
val = 0x07;
|
|
else
|
|
val = (threshold - SC854X_AC_OVP_BASE) / SC854X_AC_OVP_LSB;
|
|
|
|
val <<= SC854X_AC_OVP_SHIFT;
|
|
|
|
return sc854x_update_bits(sc, SC854X_REG_02,
|
|
SC854X_AC_OVP_MASK, val);
|
|
}
|
|
|
|
static int sc854x_enable_dropovp(struct sc854x *sc, bool enable)
|
|
{
|
|
u8 val;
|
|
|
|
if (enable)
|
|
val = SC854X_VDROP_OVP_ENABLE;
|
|
else
|
|
val = SC854X_VDROP_OVP_DISABLE;
|
|
|
|
val <<= SC854X_VDROP_OVP_DIS_SHIFT;
|
|
|
|
return sc854x_update_bits(sc, SC854X_REG_03,
|
|
SC854X_VDROP_OVP_DIS_MASK, val);
|
|
}
|
|
|
|
static int sc854x_set_vdrop_ovp_th(struct sc854x *sc, int threshold)
|
|
{
|
|
u8 val;
|
|
|
|
if (threshold == 400)
|
|
val = SC854X_VDROP_OVP_THRESHOLD_400MV;
|
|
else
|
|
val = SC854X_VDROP_OVP_THRESHOLD_300MV;
|
|
|
|
val <<= SC854X_VDROP_OVP_THRESHOLD_SHIFT;
|
|
|
|
return sc854x_update_bits(sc, SC854X_REG_03,
|
|
SC854X_VDROP_OVP_THRESHOLD_MASK, val);
|
|
}
|
|
|
|
static int sc854x_set_vdrop_deglitch(struct sc854x *sc, int deglitch)
|
|
{
|
|
u8 val;
|
|
|
|
if (deglitch == 5000)
|
|
val = SC854X_VDROP_DEGLITCH_SET_5MS;
|
|
else
|
|
val = SC854X_VDROP_DEGLITCH_SET_8US;
|
|
|
|
val <<= SC854X_VDROP_DEGLITCH_SET_SHIFT;
|
|
|
|
return sc854x_update_bits(sc, SC854X_REG_03,
|
|
SC854X_VDROP_DEGLITCH_SET_MASK, val);
|
|
}
|
|
|
|
static int sc854x_enable_busovp(struct sc854x *sc, bool enable)
|
|
{
|
|
u8 val;
|
|
|
|
if (enable)
|
|
val = SC854X_VBUS_OVP_ENABLE;
|
|
else
|
|
val = SC854X_VBUS_OVP_DISABLE;
|
|
|
|
val <<= SC854X_VBUS_OVP_DIS_SHIFT;
|
|
|
|
return sc854x_update_bits(sc, SC854X_REG_04,
|
|
SC854X_VBUS_OVP_DIS_MASK, val);
|
|
}
|
|
|
|
static int sc854x_set_busovp_th(struct sc854x *sc, int threshold)
|
|
{
|
|
u8 val;
|
|
|
|
if (threshold < SC854X_VBUS_OVP_BASE)
|
|
threshold = SC854X_VBUS_OVP_BASE;
|
|
|
|
val = (threshold - SC854X_VBUS_OVP_BASE) / SC854X_VBUS_OVP_LSB;
|
|
|
|
val <<= SC854X_VBUS_OVP_SHIFT;
|
|
|
|
return sc854x_update_bits(sc, SC854X_REG_04,
|
|
SC854X_VBUS_OVP_MASK, val);
|
|
}
|
|
|
|
static int sc854x_enable_busucp(struct sc854x *sc, bool enable)
|
|
{
|
|
u8 val;
|
|
|
|
if (enable)
|
|
val = SC854X_IBUS_UCP_ENABLE;
|
|
else
|
|
val = SC854X_IBUS_UCP_DISABLE;
|
|
|
|
val <<= SC854X_IBUS_UCP_DIS_SHIFT;
|
|
|
|
return sc854x_update_bits(sc, SC854X_REG_05,
|
|
SC854X_IBUS_UCP_DIS_MASK, val);
|
|
}
|
|
|
|
static int sc854x_enable_busocp(struct sc854x *sc, bool enable)
|
|
{
|
|
u8 val;
|
|
|
|
if (enable)
|
|
val = SC854X_IBUS_OCP_ENABLE;
|
|
else
|
|
val = SC854X_IBUS_OCP_DISABLE;
|
|
|
|
val <<= SC854X_IBUS_OCP_DIS_SHIFT;
|
|
|
|
return sc854x_update_bits(sc, SC854X_REG_05,
|
|
SC854X_IBUS_OCP_DIS_MASK, val);
|
|
}
|
|
|
|
static int sc854x_set_busocp_th(struct sc854x *sc, int threshold)
|
|
{
|
|
u8 val;
|
|
|
|
if (threshold < SC854X_IBUS_OCP_BASE)
|
|
threshold = SC854X_IBUS_OCP_BASE;
|
|
|
|
val = (threshold - SC854X_IBUS_OCP_BASE) / SC854X_IBUS_OCP_LSB;
|
|
|
|
val += 1;
|
|
|
|
val <<= SC854X_IBUS_OCP_SHIFT;
|
|
|
|
return sc854x_update_bits(sc, SC854X_REG_05,
|
|
SC854X_IBUS_OCP_MASK, val);
|
|
}
|
|
|
|
static int sc854x_check_vbus_error_status(struct sc854x *sc, int *result)
|
|
{
|
|
u8 ret;
|
|
u8 reg;
|
|
ret = sc854x_read_byte(sc, SC854X_REG_06, ®);
|
|
|
|
*result = (int)reg;
|
|
return ret;
|
|
}
|
|
|
|
static int sc854x_enable_charge(struct sc854x *sc, bool enable)
|
|
{
|
|
u8 val;
|
|
|
|
if (enable)
|
|
val = SC854X_CHG_ENABLE;
|
|
else
|
|
val = SC854X_CHG_DISABLE;
|
|
|
|
val <<= SC854X_CHG_EN_SHIFT;
|
|
|
|
sc_err("sc854x charger %s\n", enable == false ? "disable" : "enable");
|
|
return sc854x_update_bits(sc, SC854X_REG_07,
|
|
SC854X_CHG_EN_MASK, val);
|
|
}
|
|
|
|
static int sc854x_check_charge_enabled(struct sc854x *sc, bool *enabled)
|
|
{
|
|
int ret;
|
|
u8 val;
|
|
|
|
ret = sc854x_read_byte(sc, SC854X_REG_07, &val);
|
|
if (!ret) {
|
|
sc_info(">>>reg [0x07] = 0x%02x\n", val);
|
|
if (val & SC854X_CHG_EN_MASK) {
|
|
ret = sc854x_read_byte(sc, SC854X_REG_06, &val);
|
|
if (!ret) {
|
|
sc_info(">>>reg [0x06] = 0x%02x\n", val);
|
|
if (val & SC854X_CP_SWITCHING_STAT_MASK) {
|
|
*enabled = true;
|
|
return ret;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
*enabled = false;
|
|
return ret;
|
|
}
|
|
|
|
static int sc854x_reg_reset(struct sc854x *sc, bool enable)
|
|
{
|
|
u8 val;
|
|
|
|
if (enable)
|
|
val = SC854X_RESET_REG;
|
|
else
|
|
val = SC854X_NO_REG_RESET;
|
|
|
|
val <<= SC854X_REG_RESET_SHIFT;
|
|
|
|
return sc854x_update_bits(sc, SC854X_REG_07,
|
|
SC854X_REG_RESET_MASK, val);
|
|
}
|
|
|
|
static int sc854x_set_ss_timeout(struct sc854x *sc, int timeout)
|
|
{
|
|
u8 val;
|
|
|
|
switch (timeout) {
|
|
case 0:
|
|
val = SC854X_SS_TIMEOUT_DISABLE;
|
|
break;
|
|
case 40:
|
|
val = SC854X_SS_TIMEOUT_40MS;
|
|
break;
|
|
case 80:
|
|
val = SC854X_SS_TIMEOUT_80MS;
|
|
break;
|
|
case 320:
|
|
val = SC854X_SS_TIMEOUT_320MS;
|
|
break;
|
|
case 1280:
|
|
val = SC854X_SS_TIMEOUT_1280MS;
|
|
break;
|
|
case 5120:
|
|
val = SC854X_SS_TIMEOUT_5120MS;
|
|
break;
|
|
case 20480:
|
|
val = SC854X_SS_TIMEOUT_20480MS;
|
|
break;
|
|
case 81920:
|
|
val = SC854X_SS_TIMEOUT_81920MS;
|
|
break;
|
|
default:
|
|
val = SC854X_SS_TIMEOUT_DISABLE;
|
|
break;
|
|
}
|
|
|
|
val <<= SC854X_SS_TIMEOUT_SET_SHIFT;
|
|
|
|
return sc854x_update_bits(sc, SC854X_REG_08,
|
|
SC854X_SS_TIMEOUT_SET_MASK,
|
|
val);
|
|
}
|
|
|
|
static int sc854x_reg_timeout_enabled(struct sc854x *sc, bool enable)
|
|
{
|
|
u8 val;
|
|
|
|
if (enable)
|
|
val = SC854X_650MS_REG_TIMEOUT_ENABLE;
|
|
else
|
|
val = SC854X_650MS_REG_TIMEOUT_DISABLE;
|
|
|
|
val <<= SC854X_REG_TIMEOUT_DIS_SHIFT;
|
|
|
|
return sc854x_update_bits(sc, SC854X_REG_08,
|
|
SC854X_REG_TIMEOUT_DIS_MASK, val);
|
|
}
|
|
|
|
static int sc854x_voutovp_enabled(struct sc854x *sc, bool enable)
|
|
{
|
|
u8 val;
|
|
|
|
if (enable)
|
|
val = SC854X_VOUT_OVP_ENABLE;
|
|
else
|
|
val = SC854X_VOUT_OVP_DISABLE;
|
|
|
|
val <<= SC854X_VOUT_OVP_DIS_SHIFT;
|
|
|
|
return sc854x_update_bits(sc, SC854X_REG_08,
|
|
SC854X_VOUT_OVP_DIS_MASK, val);
|
|
}
|
|
|
|
static int sc854x_set_sense_resistor(struct sc854x *sc, int r_mohm)
|
|
{
|
|
u8 val;
|
|
|
|
if (r_mohm == 2)
|
|
val = SC854X_SET_IBAT_SNS_RES_2MHM;
|
|
else if (r_mohm == 5)
|
|
val = SC854X_SET_IBAT_SNS_RES_5MHM;
|
|
else
|
|
return -EINVAL;
|
|
|
|
val <<= SC854X_SET_IBAT_SNS_RES_SHIFT;
|
|
|
|
return sc854x_update_bits(sc, SC854X_REG_08,
|
|
SC854X_SET_IBAT_SNS_RES_MASK,
|
|
val);
|
|
}
|
|
|
|
static int sc854x_vbus_pd_enabled(struct sc854x *sc, bool enable)
|
|
{
|
|
u8 val;
|
|
|
|
if (enable)
|
|
val = SC854X_VBUS_PD_ENABLE;
|
|
else
|
|
val = SC854X_VBUS_PD_DISABLE;
|
|
|
|
val <<= SC854X_VBUS_PD_EN_SHIFT;
|
|
|
|
return sc854x_update_bits(sc, SC854X_REG_08,
|
|
SC854X_VBUS_PD_EN_MASK, val);
|
|
}
|
|
/*
|
|
static int sc854x_set_charge_mode(struct sc854x *sc, u8 charge_mode)
|
|
{
|
|
u8 val;
|
|
|
|
if(charge_mode)
|
|
val = SC854X_CHARGE_MODE_1_1;
|
|
else
|
|
val = SC854X_CHARGE_MODE_2_1;
|
|
|
|
val <<= SC854X_CHARGE_MODE_SHIFT;
|
|
|
|
return sc854x_update_bits(sc, SC854X_REG_09,
|
|
SC854X_CHARGE_MODE_MASK, val);
|
|
}
|
|
|
|
static int sc854x_get_charge_mode(struct sc854x *sc, int *result)
|
|
{
|
|
int ret;
|
|
u8 val;
|
|
|
|
ret = sc854x_read_byte(sc, SC854X_REG_09, &val);
|
|
if(ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
*result = (val >> SC854X_CHARGE_MODE_SHIFT) &
|
|
SC854X_CHARGE_MODE_MASK;
|
|
|
|
return ret;
|
|
}*/
|
|
|
|
static int sc854x_set_wdt(struct sc854x *sc, int ms)
|
|
{
|
|
u8 val;
|
|
|
|
if (ms == 0)
|
|
val = SC854X_WATCHDOG_DIS;
|
|
else if (ms == 200)
|
|
val = SC854X_WATCHDOG_200MS;
|
|
else if (ms == 500)
|
|
val = SC854X_WATCHDOG_500MS;
|
|
else if (ms == 1000)
|
|
val = SC854X_WATCHDOG_1S;
|
|
else if (ms == 5000)
|
|
val = SC854X_WATCHDOG_5S;
|
|
else if (ms == 30000)
|
|
val = SC854X_WATCHDOG_30S;
|
|
else
|
|
val = SC854X_WATCHDOG_DIS;
|
|
|
|
val <<= SC854X_WATCHDOG_SHIFT;
|
|
|
|
return sc854x_update_bits(sc, SC854X_REG_09,
|
|
SC854X_WATCHDOG_MASK, val);
|
|
}
|
|
|
|
static int sc854x_vbat_regulation_enabled(struct sc854x *sc, bool enable)
|
|
{
|
|
u8 val;
|
|
|
|
if (enable)
|
|
val = SC854X_VBAT_REG_ENABLE;
|
|
else
|
|
val = SC854X_VBAT_REG_DISABLE;
|
|
|
|
val <<= SC854X_VBAT_REG_EN_SHIFT;
|
|
|
|
return sc854x_update_bits(sc, SC854X_REG_0A,
|
|
SC854X_VBAT_REG_EN_MASK, val);
|
|
}
|
|
|
|
static int sc854x_set_vbat_regulation(struct sc854x *sc, int mv)
|
|
{
|
|
u8 val;
|
|
|
|
if (mv == 50)
|
|
val = SC854X_SET_VBATREG_50MV;
|
|
else if (mv == 100)
|
|
val = SC854X_SET_VBATREG_100MV;
|
|
else if (mv == 150)
|
|
val = SC854X_SET_VBATREG_150MV;
|
|
else if (mv == 200)
|
|
val = SC854X_SET_VBATREG_200MV;
|
|
else
|
|
val = SC854X_SET_VBATREG_50MV;
|
|
|
|
val <<= SC854X_SET_VBATREG_SHIFT;
|
|
|
|
return sc854x_update_bits(sc, SC854X_REG_0A,
|
|
SC854X_SET_VBATREG_MASK, val);
|
|
}
|
|
|
|
static int sc854x_ibat_regulation_enabled(struct sc854x *sc, bool enable)
|
|
{
|
|
u8 val;
|
|
|
|
if (enable)
|
|
val = SC854X_IBAT_REG_ENABLE;
|
|
else
|
|
val = SC854X_IBAT_REG_DISABLE;
|
|
|
|
val <<= SC854X_IBAT_REG_EN_SHIFT;
|
|
|
|
return sc854x_update_bits(sc, SC854X_REG_0B,
|
|
SC854X_IBAT_REG_EN_MASK, val);
|
|
}
|
|
|
|
static int sc854x_set_ibat_regulation(struct sc854x *sc, int ma)
|
|
{
|
|
u8 val;
|
|
|
|
if (ma == 200)
|
|
val = SC854X_SET_IBATREG_200MA;
|
|
else if (ma == 300)
|
|
val = SC854X_SET_IBATREG_300MA;
|
|
else if (ma == 450)
|
|
val = SC854X_SET_IBATREG_400MA;
|
|
else if (ma == 500)
|
|
val = SC854X_SET_IBATREG_500MA;
|
|
else
|
|
val = SC854X_SET_IBATREG_200MA;
|
|
|
|
val <<= SC854X_SET_VBATREG_SHIFT;
|
|
|
|
return sc854x_update_bits(sc, SC854X_REG_0B,
|
|
SC854X_SET_VBATREG_MASK, val);
|
|
}
|
|
|
|
static int sc854x_ibus_regulation_enabled(struct sc854x *sc, bool enable)
|
|
{
|
|
u8 val;
|
|
|
|
if (enable)
|
|
val = SC854X_IBUS_REG_ENABLE;
|
|
else
|
|
val = SC854X_IBUS_REG_DISABLE;
|
|
|
|
val <<= SC854X_IBUS_REG_EN_SHIFT;
|
|
|
|
return sc854x_update_bits(sc, SC854X_REG_0C,
|
|
SC854X_IBUS_REG_EN_MASK, val);
|
|
}
|
|
|
|
static int sc854x_set_ibus_regulation(struct sc854x *sc, int threshold)
|
|
{
|
|
u8 val;
|
|
|
|
if (threshold < SC854X_SET_IBUSREG_BASE)
|
|
threshold = SC854X_SET_IBUSREG_BASE;
|
|
|
|
val = (threshold - SC854X_SET_IBUSREG_BASE) / SC854X_SET_IBUSREG_LSB;
|
|
|
|
val <<= SC854X_SET_IBUSREG_SHIFT;
|
|
|
|
return sc854x_update_bits(sc, SC854X_REG_0C,
|
|
SC854X_SET_IBUSREG_MASK, val);
|
|
}
|
|
|
|
static int sc854x_set_pmid2out_uvp(struct sc854x *sc, int mv)
|
|
{
|
|
u8 val;
|
|
|
|
if (mv == -50)
|
|
val = SC854X_PMID2OUT_UVP_U_50MV;
|
|
else if (mv == -100)
|
|
val = SC854X_PMID2OUT_UVP_U_100MV;
|
|
else if (mv == -150)
|
|
val = SC854X_PMID2OUT_UVP_U_150MV;
|
|
else if (mv == -200)
|
|
val = SC854X_PMID2OUT_UVP_U_200MV;
|
|
else
|
|
val = SC854X_PMID2OUT_UVP_U_50MV;
|
|
|
|
val <<= SC854X_PMID2OUT_UVP_SHIFT;
|
|
|
|
return sc854x_update_bits(sc, SC854X_REG_0D,
|
|
SC854X_PMID2OUT_UVP_MASK, val);
|
|
}
|
|
|
|
static int sc854x_set_pmid2out_ovp(struct sc854x *sc, int mv)
|
|
{
|
|
u8 val;
|
|
|
|
if (mv == 200)
|
|
val = SC854X_PMID2OUT_OVP_200MV;
|
|
else if (mv == 300)
|
|
val = SC854X_PMID2OUT_OVP_300MV;
|
|
else if (mv == 400)
|
|
val = SC854X_PMID2OUT_OVP_400MV;
|
|
else if (mv == 500)
|
|
val = SC854X_PMID2OUT_OVP_500MV;
|
|
else
|
|
val = SC854X_PMID2OUT_OVP_200MV;
|
|
|
|
val <<= SC854X_PMID2OUT_OVP_SHIFT;
|
|
|
|
return sc854x_update_bits(sc, SC854X_REG_0D,
|
|
SC854X_PMID2OUT_OVP_MASK, val);
|
|
}
|
|
|
|
static int sc854x_enable_adc(struct sc854x *sc, bool enable)
|
|
{
|
|
u8 val;
|
|
|
|
if (enable)
|
|
val = SC854X_ADC_ENABLE;
|
|
else
|
|
val = SC854X_ADC_DISABLE;
|
|
|
|
val <<= SC854X_ADC_EN_SHIFT;
|
|
|
|
return sc854x_update_bits(sc, SC854X_REG_11,
|
|
SC854X_ADC_EN_MASK, val);
|
|
}
|
|
|
|
static int sc854x_set_adc_scanrate(struct sc854x *sc, bool oneshot)
|
|
{
|
|
u8 val;
|
|
|
|
if (oneshot)
|
|
val = SC854X_ADC_RATE_ONESHOT;
|
|
else
|
|
val = SC854X_ADC_RATE_CONTINOUS;
|
|
|
|
val <<= SC854X_ADC_RATE_SHIFT;
|
|
|
|
return sc854x_update_bits(sc, SC854X_REG_11,
|
|
SC854X_ADC_EN_MASK, val);
|
|
}
|
|
|
|
static int sc854x_set_adc_scan(struct sc854x *sc, int channel, bool enable)
|
|
{
|
|
u8 mask;
|
|
u8 shift;
|
|
u8 val;
|
|
|
|
if (channel > ADC_MAX_NUM)
|
|
return -EINVAL;
|
|
|
|
shift = SC854X_ADC_DIS_SHIFT - channel;
|
|
mask = SC854X_ADC_DIS_MASK << shift;
|
|
|
|
if (enable)
|
|
val = SC854X_ADC_CHANNEL_ENABLE << shift;
|
|
else
|
|
val = SC854X_ADC_CHANNEL_DISABLE << shift;
|
|
|
|
return sc854x_update_bits(sc, SC854X_REG_12, mask, val);
|
|
}
|
|
|
|
static int sc854x_get_adc_data(struct sc854x *sc, int channel, int *result)
|
|
{
|
|
int ret;
|
|
u16 val;
|
|
u16 t;
|
|
union power_supply_propval prop;
|
|
|
|
if(channel >= ADC_MAX_NUM)return 0;
|
|
|
|
usleep_range(15000, 17000);
|
|
|
|
if(channel == ADC_IBAT){
|
|
if(!sc->cw_bat){
|
|
sc->cw_bat = power_supply_get_by_name("cw-bat");
|
|
if(!sc->cw_bat){
|
|
sc_err("gezi failed to get cw_bat too!\n");
|
|
}
|
|
else{
|
|
ret = power_supply_get_property(sc->cw_bat,POWER_SUPPLY_PROP_CURRENT_NOW, &prop);
|
|
*result = prop.intval;
|
|
return 0;
|
|
}
|
|
}
|
|
else{
|
|
ret = power_supply_get_property(sc->cw_bat,POWER_SUPPLY_PROP_CURRENT_NOW, &prop);
|
|
*result = prop.intval;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
ret = sc854x_read_word(sc, SC854X_REG_13 + (channel << 1), &val);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
t = val & 0xFF;
|
|
t <<= 8;
|
|
t |= (val >> 8) & 0xFF;
|
|
val = t;
|
|
switch (channel)
|
|
{
|
|
case ADC_IBUS:
|
|
*result = val * SC854X_IBUS_ADC_LSB;
|
|
break;
|
|
case ADC_VBUS:
|
|
*result = val * SC854X_VBUS_ADC_LSB;
|
|
break;
|
|
case ADC_VAC:
|
|
*result = val * SC854X_VAC_ADC_LSB;
|
|
break;
|
|
case ADC_VOUT:
|
|
*result = val * SC854X_VOUT_ADC_LSB;
|
|
break;
|
|
case ADC_VBAT:
|
|
*result = val * SC854X_VBAT_ADC_LSB;
|
|
break;
|
|
case ADC_IBAT:
|
|
*result = val * SC854X_IBAT_ADC_LSB;
|
|
break;
|
|
case ADC_TDIE:
|
|
*result = val * SC854X_TDIE_ADC_LSB;
|
|
break;
|
|
default:
|
|
sc_err("Not find adc channel : %d\n", channel);
|
|
break;
|
|
}
|
|
sc_err("%s:---->chan:%d, val:%d, result:%d\n", __func__, channel, val, *result);
|
|
return 0;
|
|
}
|
|
|
|
static int sc854x_disable_vbus_range(struct sc854x *sc)
|
|
{
|
|
return sc854x_update_bits(sc, SC854X_REG_3C,
|
|
SC854X_VBUS_IN_RANGE_DIS_MASK,
|
|
SC854X_VBUS_EN_RANGE_DISABLE << SC854X_VBUS_IN_RANGE_DIS_SHIFT);
|
|
}
|
|
|
|
//*********************************************************************
|
|
|
|
/*
|
|
* interrupt does nothing, just info event chagne, other module could get info
|
|
* through power supply interface
|
|
*/
|
|
static void sc854x_check_fault_status(struct sc854x *sc);
|
|
static irqreturn_t sc854x_charger_interrupt(int irq, void *dev_id)
|
|
{
|
|
struct sc854x *sc = dev_id;
|
|
|
|
sc_err("INT OCCURED\n");
|
|
#if 1
|
|
mutex_lock(&sc->irq_complete);
|
|
sc->irq_waiting = true;
|
|
if (!sc->resume_completed) {
|
|
dev_dbg(sc->dev, "IRQ triggered before device-resume\n");
|
|
if (!sc->irq_disabled) {
|
|
disable_irq_nosync(irq);
|
|
sc->irq_disabled = true;
|
|
}
|
|
mutex_unlock(&sc->irq_complete);
|
|
return IRQ_HANDLED;
|
|
}
|
|
sc->irq_waiting = false;
|
|
#if 1
|
|
/* TODO */
|
|
sc854x_check_fault_status(sc);
|
|
#endif
|
|
|
|
#if 0
|
|
sc854x_dump_reg(sc);
|
|
#endif
|
|
mutex_unlock(&sc->irq_complete);
|
|
#endif
|
|
power_supply_changed(sc->fc2_psy);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static int sc854x_set_work_mode(struct sc854x *sc, int mode)
|
|
{
|
|
switch (mode)
|
|
{
|
|
case SC8545_STANDALONG:
|
|
case SC8546_STANDALONG:
|
|
case SC8547_STANDALONG:
|
|
case SC8549_STANDALONG:
|
|
sc->mode = STANDALONE;
|
|
break;
|
|
case SC8545_MASTER:
|
|
case SC8546_MASTER:
|
|
case SC8547_MASTER:
|
|
case SC8549_MASTER:
|
|
sc->mode = MASTER;
|
|
break;
|
|
case SC8545_SLAVE:
|
|
case SC8546_SLAVE:
|
|
case SC8547_SLAVE:
|
|
case SC8549_SLAVE:
|
|
sc->mode = SLAVE;
|
|
break;
|
|
default:
|
|
sc_err("Not find work mode\n");
|
|
return -ENODEV;
|
|
}
|
|
sc_err("[%s] work mode is %s\n", mode <= SC8545_SLAVE ?
|
|
"SC8545" : (mode <= SC8546_SLAVE ? "SC8546" : (mode <=
|
|
SC8547_SLAVE ? "SC8547" : "SC8549")), sc->mode == STANDALONE
|
|
? "standalone" : (sc->mode == MASTER ? "master" : "slave"));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sc854x_detect_device(struct sc854x *sc)
|
|
{
|
|
int ret;
|
|
u8 data;
|
|
|
|
ret = sc854x_read_byte(sc, SC854X_REG_36, &data);
|
|
if (ret == 0) {
|
|
if(data != id_data[SC8545_ID] && data != id_data[SC8546_ID]
|
|
&& data != id_data[SC8547_ID] && data != id_data[SC8549_ID]) {
|
|
return -ENOMEM;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int sc854x_parse_dt(struct sc854x *sc, struct device *dev)
|
|
{
|
|
int ret;
|
|
struct device_node *np = dev->of_node;
|
|
|
|
sc->cfg = devm_kzalloc(dev, sizeof(struct sc854x_cfg),
|
|
GFP_KERNEL);
|
|
|
|
if (!sc->cfg)
|
|
return -ENOMEM;
|
|
|
|
if (of_property_read_string(np, "chg_name", &sc->cfg->chg_name) < 0) {
|
|
sc_err("%s no chg name, use default\n", __func__);
|
|
sc->cfg->chg_name = "primary_dvchg";//divider_charger
|
|
}
|
|
|
|
sc->cfg->bat_ovp_disable = of_property_read_bool(np,
|
|
"sc,sc854x,bat-ovp-disable");
|
|
sc->cfg->bat_ocp_disable = of_property_read_bool(np,
|
|
"sc,sc854x,bat-ocp-disable");
|
|
sc->cfg->vdrop_ovp_disable = of_property_read_bool(np,
|
|
"sc,sc854x,vdrop-ovp-disable");
|
|
sc->cfg->bus_ovp_disable = of_property_read_bool(np,
|
|
"sc,sc854x,bus-ovp-disable");
|
|
sc->cfg->bus_ucp_disable = of_property_read_bool(np,
|
|
"sc,sc854x,bus-ucp-disable");
|
|
sc->cfg->bus_ocp_disable = of_property_read_bool(np,
|
|
"sc,sc854x,bus-ocp-disable");
|
|
|
|
ret = of_property_read_u32(np, "sc,sc854x,bat-ovp-threshold",
|
|
&sc->cfg->bat_ovp_th);
|
|
if (ret) {
|
|
sc_err("failed to read bat-ovp-threshold\n");
|
|
return ret;
|
|
}
|
|
ret = of_property_read_u32(np, "sc,sc854x,bat-ocp-threshold",
|
|
&sc->cfg->bat_ocp_th);
|
|
if (ret) {
|
|
sc_err("failed to read bat-ocp-threshold\n");
|
|
return ret;
|
|
}
|
|
ret = of_property_read_u32(np, "sc,sc854x,ac-ovp-threshold",
|
|
&sc->cfg->ac_ovp_th);
|
|
if (ret) {
|
|
sc_err("failed to read ac-ovp-threshold\n");
|
|
return ret;
|
|
}
|
|
ret = of_property_read_u32(np, "sc,sc854x,bus-ovp-threshold",
|
|
&sc->cfg->bus_ovp_th);
|
|
if (ret) {
|
|
sc_err("failed to read bus-ovp-threshold\n");
|
|
return ret;
|
|
}
|
|
ret = of_property_read_u32(np, "sc,sc854x,bus-ocp-threshold",
|
|
&sc->cfg->bus_ocp_th);
|
|
if (ret) {
|
|
sc_err("failed to read bus-ocp-threshold\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = of_property_read_u32(np, "sc,sc854x,sense-resistor-mohm",
|
|
&sc->cfg->sense_r_mohm);
|
|
if (ret) {
|
|
sc_err("failed to read sense-resistor-mohm\n");
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sc854x_init_protection(struct sc854x *sc)
|
|
{
|
|
int ret;
|
|
|
|
ret = sc854x_enable_batovp(sc, !sc->cfg->bat_ovp_disable);
|
|
sc_info("%s bat ovp %s\n",
|
|
sc->cfg->bat_ovp_disable ? "disable" : "enable",
|
|
!ret ? "successfullly" : "failed");
|
|
|
|
ret = sc854x_enable_batocp(sc, !sc->cfg->bat_ocp_disable);
|
|
sc_info("%s bat ocp %s\n",
|
|
sc->cfg->bat_ocp_disable ? "disable" : "enable",
|
|
!ret ? "successfullly" : "failed");
|
|
|
|
ret = sc854x_enable_dropovp(sc, !sc->cfg->vdrop_ovp_disable);
|
|
sc_info("%s drop ovp %s\n",
|
|
sc->cfg->vdrop_ovp_disable ? "disable" : "enable",
|
|
!ret ? "successfullly" : "failed");
|
|
|
|
ret = sc854x_enable_busovp(sc, !sc->cfg->bus_ovp_disable);
|
|
sc_info("%s bus ovp %s\n",
|
|
sc->cfg->bus_ovp_disable ? "disable" : "enable",
|
|
!ret ? "successfullly" : "failed");
|
|
|
|
ret = sc854x_enable_busucp(sc, !sc->cfg->bus_ucp_disable);
|
|
sc_info("%s bus ucp %s\n",
|
|
sc->cfg->bus_ucp_disable ? "disable" : "enable",
|
|
!ret ? "successfullly" : "failed");
|
|
|
|
ret = sc854x_enable_busocp(sc, !sc->cfg->bus_ocp_disable);
|
|
sc_info("%s bus ocp %s\n",
|
|
sc->cfg->bus_ocp_disable ? "disable" : "enable",
|
|
!ret ? "successfullly" : "failed");
|
|
|
|
sc854x_voutovp_enabled(sc, true);
|
|
|
|
ret = sc854x_set_batovp_th(sc, sc->cfg->bat_ovp_th);
|
|
sc_info("set bat ovp th %d %s\n", sc->cfg->bat_ovp_th,
|
|
!ret ? "successfully" : "failed");
|
|
|
|
ret = sc854x_set_batocp_th(sc, sc->cfg->bat_ocp_th);
|
|
sc_info("set bat ocp threshold %d %s\n", sc->cfg->bat_ocp_th,
|
|
!ret ? "successfully" : "failed");
|
|
|
|
ret = sc854x_set_acovp_th(sc, sc->cfg->ac_ovp_th);
|
|
sc_info("set ac ovp threshold %d %s\n", sc->cfg->ac_ovp_th,
|
|
!ret ? "successfully" : "failed");
|
|
|
|
ret = sc854x_set_busovp_th(sc, sc->cfg->bus_ovp_th);
|
|
sc_info("set bus ovp threshold %d %s\n", sc->cfg->bus_ovp_th,
|
|
!ret ? "successfully" : "failed");
|
|
|
|
ret = sc854x_set_busocp_th(sc, sc->cfg->bus_ocp_th);
|
|
sc_info("set bus ocp threshold %d %s\n", sc->cfg->bus_ocp_th,
|
|
!ret ? "successfully" : "failed");
|
|
|
|
ret = sc854x_set_sense_resistor(sc, sc->cfg->sense_r_mohm);
|
|
sc_info("set sense r mohm %d %s\n", sc->cfg->sense_r_mohm,
|
|
!ret ? "successfully" : "failed");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sc854x_init_adc(struct sc854x *sc)
|
|
{
|
|
|
|
sc854x_set_adc_scanrate(sc, false);
|
|
sc854x_set_adc_scan(sc, ADC_IBUS, true);
|
|
sc854x_set_adc_scan(sc, ADC_VBUS, true);
|
|
sc854x_set_adc_scan(sc, ADC_VOUT, true);
|
|
sc854x_set_adc_scan(sc, ADC_VBAT, true);
|
|
sc854x_set_adc_scan(sc, ADC_IBAT, true);
|
|
sc854x_set_adc_scan(sc, ADC_TDIE, true);
|
|
sc854x_set_adc_scan(sc, ADC_VAC, true);
|
|
|
|
sc854x_enable_adc(sc, true);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sc854x_init_regulation(struct sc854x *sc)
|
|
{
|
|
sc854x_set_ibat_regulation(sc, 300);
|
|
sc854x_set_vbat_regulation(sc, 50);
|
|
sc854x_set_ibus_regulation(sc, 2400);
|
|
|
|
sc854x_reg_timeout_enabled(sc, true);
|
|
|
|
sc854x_set_vdrop_deglitch(sc, 5000);
|
|
sc854x_set_vdrop_ovp_th(sc, 400);
|
|
|
|
sc854x_ibat_regulation_enabled(sc, false);
|
|
sc854x_vbat_regulation_enabled(sc, false);
|
|
sc854x_ibus_regulation_enabled(sc, false);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sc854x_init_device(struct sc854x *sc)
|
|
{
|
|
sc854x_reg_reset(sc, true);
|
|
|
|
sc854x_set_ss_timeout(sc, 5120);
|
|
sc854x_set_sense_resistor(sc, sc->cfg->sense_r_mohm);
|
|
|
|
sc854x_init_protection(sc);
|
|
sc854x_init_adc(sc);
|
|
|
|
sc854x_set_wdt(sc, 0);//disable WTD
|
|
sc854x_vbus_pd_enabled(sc, false);
|
|
|
|
sc854x_set_pmid2out_uvp(sc, -50);
|
|
sc854x_set_pmid2out_ovp(sc, 500);
|
|
|
|
sc854x_init_regulation(sc);
|
|
|
|
if (sc->mode == SLAVE) {
|
|
sc854x_disable_vbus_range(sc);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/***********************MTK CHARGER OPS API************************/
|
|
static int sc854x_enable_chg(struct charger_device *chg_dev, bool en)
|
|
{
|
|
struct sc854x *sc = charger_get_data(chg_dev);
|
|
|
|
sc_info("%s: %s\n", __func__, en ? "enable" : "disable");
|
|
|
|
return sc854x_enable_charge(sc, !!en);
|
|
}
|
|
|
|
static int sc854x_is_chg_enabled(struct charger_device *chg_dev, bool *en)
|
|
{
|
|
struct sc854x *sc = charger_get_data(chg_dev);
|
|
|
|
return sc854x_check_charge_enabled(sc, en);
|
|
}
|
|
|
|
static inline enum SC854X_ADC_CH to_sc854x_adc(enum adc_channel chan)
|
|
{
|
|
switch (chan) {
|
|
case ADC_CHANNEL_VBUS:
|
|
return ADC_VBUS;
|
|
case ADC_CHANNEL_VBAT:
|
|
return ADC_VBAT;
|
|
case ADC_CHANNEL_IBUS:
|
|
return ADC_IBUS;
|
|
case ADC_CHANNEL_IBAT:
|
|
return ADC_IBAT;
|
|
case ADC_CHANNEL_TEMP_JC:
|
|
return ADC_TDIE;
|
|
case ADC_CHANNEL_VOUT:
|
|
return ADC_VOUT;
|
|
default:
|
|
break;
|
|
}
|
|
return ADC_MAX_NUM;
|
|
}
|
|
|
|
static int sc854x_get_adc(struct charger_device *chg_dev, enum adc_channel chan,
|
|
int *min, int *max)
|
|
{
|
|
int ret;
|
|
struct sc854x *sc = charger_get_data(chg_dev);
|
|
enum SC854X_ADC_CH _chan = to_sc854x_adc(chan);
|
|
|
|
if (_chan == ADC_MAX_NUM)
|
|
return -EINVAL;
|
|
|
|
ret = sc854x_get_adc_data(sc, _chan, max);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
if (min != max)
|
|
*min = *max;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int sc854x_get_adc_accuracy(struct charger_device *chg_dev,
|
|
enum adc_channel chan, int *min, int *max)
|
|
{
|
|
enum SC854X_ADC_CH _chan = to_sc854x_adc(chan);
|
|
|
|
if (_chan == ADC_MAX_NUM)
|
|
return -EINVAL;
|
|
|
|
*min = *max = sc854x_adc_accuracy_tbl[_chan];
|
|
return 0;
|
|
}
|
|
|
|
static int sc854x_set_vbusovp(struct charger_device *chg_dev, u32 uV)
|
|
{
|
|
struct sc854x *sc = charger_get_data(chg_dev);
|
|
|
|
sc_info("%s: %d uV\n", __func__, uV)
|
|
|
|
return sc854x_set_busovp_th(sc, uV / 1000);
|
|
}
|
|
|
|
static int sc854x_set_ibusocp(struct charger_device *chg_dev, u32 uA)
|
|
{
|
|
struct sc854x *sc = charger_get_data(chg_dev);
|
|
|
|
sc_info("%s: %d uA\n", __func__, uA)
|
|
|
|
return sc854x_set_busocp_th(sc, uA / 1000);
|
|
}
|
|
|
|
static int sc854x_set_vbatovp(struct charger_device *chg_dev, u32 uV)
|
|
{
|
|
struct sc854x *sc = charger_get_data(chg_dev);
|
|
|
|
sc_info("%s: %d uV\n", __func__, uV)
|
|
|
|
return sc854x_set_batovp_th(sc, uV / 1000);
|
|
}
|
|
|
|
static int sc854x_set_ibatocp(struct charger_device *chg_dev, u32 uA)
|
|
{
|
|
struct sc854x *sc = charger_get_data(chg_dev);
|
|
|
|
sc_info("%s: %d uA\n", __func__, uA)
|
|
|
|
return sc854x_set_batocp_th(sc, uA / 1000);
|
|
}
|
|
|
|
static int sc854x_init_chip(struct charger_device *chg_dev)
|
|
{
|
|
struct sc854x *sc = charger_get_data(chg_dev);
|
|
|
|
return sc854x_init_device(sc);
|
|
}
|
|
|
|
static int sc854x_is_vbuslowerr(struct charger_device *chg_dev, bool *err)
|
|
{
|
|
int ret, reg_val;
|
|
struct sc854x *sc = charger_get_data(chg_dev);
|
|
|
|
ret = sc854x_check_vbus_error_status(sc, ®_val);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
sc_info("%s: reg val = 0x%02x\n", __func__, reg_val);
|
|
|
|
*err = !!(reg_val & SC854X_VBUS_ERRORLO_STAT_MASK);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int sc854x_set_vbatovp_alarm(struct charger_device *chg_dev, u32 uV)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int sc854x_reset_vbatovp_alarm(struct charger_device *chg_dev)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int sc854x_set_vbusovp_alarm(struct charger_device *chg_dev, u32 uV)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int sc854x_reset_vbusovp_alarm(struct charger_device *chg_dev)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static const struct charger_ops sc854x_chg_ops = {
|
|
.enable = sc854x_enable_chg,
|
|
.is_enabled = sc854x_is_chg_enabled,
|
|
.get_adc = sc854x_get_adc,
|
|
.set_vbusovp = sc854x_set_vbusovp,
|
|
.set_ibusocp = sc854x_set_ibusocp,
|
|
.set_vbatovp = sc854x_set_vbatovp,
|
|
.set_ibatocp = sc854x_set_ibatocp,
|
|
.init_chip = sc854x_init_chip,
|
|
.set_vbatovp_alarm = sc854x_set_vbatovp_alarm,
|
|
.reset_vbatovp_alarm = sc854x_reset_vbatovp_alarm,
|
|
.set_vbusovp_alarm = sc854x_set_vbusovp_alarm,
|
|
.reset_vbusovp_alarm = sc854x_reset_vbusovp_alarm,
|
|
.is_vbuslowerr = sc854x_is_vbuslowerr,
|
|
.get_adc_accuracy = sc854x_get_adc_accuracy,
|
|
};
|
|
|
|
static int sc854x_register_chgdev(struct sc854x *sc)
|
|
{
|
|
sc_info("%s: chg name : %s\n", __func__, sc->cfg->chg_name);
|
|
|
|
sc->chg_prop.alias_name = sc->cfg->chg_name;
|
|
sc->chg_dev = charger_device_register(sc->cfg->chg_name, sc->dev,
|
|
sc, &sc854x_chg_ops,
|
|
&sc->chg_prop);
|
|
if (!sc->chg_dev)
|
|
return -EINVAL;
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int sc854x_set_present(struct sc854x *sc, bool present)
|
|
{
|
|
sc->usb_present = present;
|
|
|
|
if (present)
|
|
sc854x_init_device(sc);
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t sc854x_show_registers(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct sc854x *sc = dev_get_drvdata(dev);
|
|
u8 addr;
|
|
u8 val;
|
|
u8 tmpbuf[300];
|
|
int len;
|
|
int idx = 0;
|
|
int ret;
|
|
|
|
idx = snprintf(buf, PAGE_SIZE, "%s:\n", "sc854x");
|
|
for (addr = 0x0; addr <= 0x3C; addr++) {
|
|
if((addr < 0x24) || (addr > 0x2B && addr < 0x33)
|
|
|| addr == 0x36 || addr == 0x3C) {
|
|
ret = sc854x_read_byte(sc, addr, &val);
|
|
if (ret == 0) {
|
|
len = snprintf(tmpbuf, PAGE_SIZE - idx,
|
|
"Reg[%.2X] = 0x%.2x\n", addr, val);
|
|
memcpy(&buf[idx], tmpbuf, len);
|
|
idx += len;
|
|
}
|
|
}
|
|
}
|
|
|
|
return idx;
|
|
}
|
|
|
|
static ssize_t sc854x_store_register(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct sc854x *sc = dev_get_drvdata(dev);
|
|
int ret;
|
|
unsigned int reg;
|
|
unsigned int val;
|
|
|
|
ret = sscanf(buf, "%x %x", ®, &val);
|
|
if (ret == 2 && reg <= 0x3C)
|
|
sc854x_write_byte(sc, (unsigned char)reg, (unsigned char)val);
|
|
|
|
return count;
|
|
}
|
|
|
|
static DEVICE_ATTR(registers, 0660, sc854x_show_registers, sc854x_store_register);
|
|
|
|
static void sc854x_create_device_node(struct device *dev)
|
|
{
|
|
device_create_file(dev, &dev_attr_registers);
|
|
}
|
|
|
|
static enum power_supply_property sc854x_charger_props[] = {
|
|
POWER_SUPPLY_PROP_PRESENT,
|
|
POWER_SUPPLY_PROP_ONLINE,
|
|
POWER_SUPPLY_PROP_VOLTAGE_NOW,
|
|
POWER_SUPPLY_PROP_CURRENT_NOW,
|
|
POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE,
|
|
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT,
|
|
POWER_SUPPLY_PROP_TEMP,
|
|
POWER_SUPPLY_PROP_CHARGE_COUNTER,
|
|
POWER_SUPPLY_PROP_CHARGE_TYPE,
|
|
};
|
|
|
|
|
|
static int sc854x_charger_get_property(struct power_supply *psy,
|
|
enum power_supply_property psp,
|
|
union power_supply_propval *val)
|
|
{
|
|
struct sc854x *sc = power_supply_get_drvdata(psy);
|
|
int result;
|
|
int ret;
|
|
|
|
sc_dbg(">>>>>psp = %d\n", psp);
|
|
|
|
switch (psp) {
|
|
case POWER_SUPPLY_PROP_ONLINE:
|
|
sc854x_check_charge_enabled(sc, &sc->charge_enabled);
|
|
val->intval = sc->charge_enabled;
|
|
break;
|
|
case POWER_SUPPLY_PROP_PRESENT:
|
|
val->intval = sc->usb_present;
|
|
break;
|
|
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
|
|
ret = sc854x_get_adc_data(sc, ADC_VBUS, &result);
|
|
if (!ret)
|
|
sc->vbus_volt = result;
|
|
|
|
val->intval = sc->vbus_volt;
|
|
break;
|
|
case POWER_SUPPLY_PROP_CURRENT_NOW:
|
|
ret = sc854x_get_adc_data(sc, ADC_IBUS, &result);
|
|
if (!ret)
|
|
sc->ibus_curr = result;
|
|
|
|
val->intval = sc->ibus_curr;
|
|
break;
|
|
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
|
|
ret = sc854x_get_adc_data(sc, ADC_VBAT, &result);
|
|
if (!ret)
|
|
sc->vbat_volt = result;
|
|
|
|
val->intval = sc->vbat_volt;
|
|
break;
|
|
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
|
|
ret = sc854x_get_adc_data(sc, ADC_IBAT, &result);
|
|
if (!ret)
|
|
sc->ibat_curr = result;
|
|
val->intval = sc->ibat_curr;
|
|
break;
|
|
case POWER_SUPPLY_PROP_TEMP:
|
|
ret = sc854x_get_adc_data(sc, ADC_TDIE, &result);
|
|
if (!ret)
|
|
sc->die_temp = result;
|
|
|
|
val->intval = sc->die_temp;
|
|
break;
|
|
case POWER_SUPPLY_PROP_CHARGE_COUNTER:
|
|
ret = sc854x_check_vbus_error_status(sc, &result);
|
|
if (!ret)
|
|
sc->vbus_error = result;
|
|
|
|
val->intval = sc->vbus_error;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int sc854x_charger_set_property(struct power_supply *psy,
|
|
enum power_supply_property prop,
|
|
const union power_supply_propval *val)
|
|
{
|
|
struct sc854x *sc = power_supply_get_drvdata(psy);
|
|
|
|
sc_err("prop = %d\n", prop);
|
|
switch (prop) {
|
|
case POWER_SUPPLY_PROP_ONLINE:
|
|
sc854x_enable_charge(sc, val->intval);
|
|
sc854x_check_charge_enabled(sc, &sc->charge_enabled);
|
|
sc_info("POWER_SUPPLY_PROP_ONLINE: %s\n",
|
|
val->intval ? "enable" : "disable");
|
|
break;
|
|
case POWER_SUPPLY_PROP_PRESENT:
|
|
sc854x_set_present(sc, val->intval);
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int sc854x_charger_is_writeable(struct power_supply *psy,
|
|
enum power_supply_property prop)
|
|
{
|
|
int ret;
|
|
|
|
switch (prop) {
|
|
case POWER_SUPPLY_PROP_ONLINE:
|
|
ret = 1;
|
|
break;
|
|
default:
|
|
ret = 0;
|
|
break;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static int sc854x_psy_register(struct sc854x *sc)
|
|
{
|
|
sc->psy_cfg.drv_data = sc;
|
|
sc->psy_cfg.of_node = sc->dev->of_node;
|
|
|
|
if (sc->mode == STANDALONE) {
|
|
sc->psy_desc.name = "sc854x-standalone";
|
|
} else if (sc->mode == MASTER) {
|
|
sc->psy_desc.name = "sc854x-master";
|
|
} else {
|
|
sc->psy_desc.name = "sc854x-slave";
|
|
}
|
|
|
|
sc->psy_desc.type = POWER_SUPPLY_TYPE_MAINS;
|
|
sc->psy_desc.properties = sc854x_charger_props;
|
|
sc->psy_desc.num_properties = ARRAY_SIZE(sc854x_charger_props);
|
|
sc->psy_desc.get_property = sc854x_charger_get_property;
|
|
sc->psy_desc.set_property = sc854x_charger_set_property;
|
|
sc->psy_desc.property_is_writeable = sc854x_charger_is_writeable;
|
|
|
|
|
|
sc->fc2_psy = devm_power_supply_register(sc->dev,
|
|
&sc->psy_desc, &sc->psy_cfg);
|
|
if (IS_ERR(sc->fc2_psy)) {
|
|
sc_err("failed to register fc2_psy\n");
|
|
return PTR_ERR(sc->fc2_psy);
|
|
}
|
|
|
|
sc->cw_bat = power_supply_get_by_name("cw-bat");
|
|
if(!sc->cw_bat){
|
|
sc_err("gezi failed to get cw_bat\n");
|
|
}
|
|
|
|
sc->charge_psy = power_supply_get_by_name("charger");
|
|
|
|
if(!sc->charge_psy){
|
|
sc_err("gezi failed to get charge_psy\n");
|
|
}
|
|
|
|
sc_info("%s power supply register successfully\n", sc->psy_desc.name);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sc854x_irq_register(struct sc854x *sc)
|
|
{
|
|
int ret;
|
|
struct device_node *node = sc->dev->of_node;
|
|
|
|
if (!node) {
|
|
sc_err("device tree node missing\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
sc->irq_gpio = of_get_named_gpio(node, "sc,sc854x,irq-gpio", 0);
|
|
if (!gpio_is_valid(sc->irq_gpio)) {
|
|
sc_err("fail to valid gpio : %d\n", sc->irq_gpio);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (gpio_is_valid(sc->irq_gpio)) {
|
|
ret = gpio_request_one(sc->irq_gpio, GPIOF_DIR_IN,"sc854x_irq");
|
|
if (ret) {
|
|
sc_err("failed to request sc854x_irq\n");
|
|
return -EINVAL;
|
|
}
|
|
sc->irq = gpio_to_irq(sc->irq_gpio);
|
|
if (sc->irq < 0) {
|
|
sc_err("failed to gpio_to_irq\n");
|
|
return -EINVAL;
|
|
}
|
|
} else {
|
|
sc_err("irq gpio not provided\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (sc->irq) {
|
|
if (sc->mode == STANDALONE) {
|
|
ret = devm_request_threaded_irq(&sc->client->dev, sc->irq,
|
|
NULL, sc854x_charger_interrupt,
|
|
IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
|
|
"sc854x standalone irq", sc);
|
|
} else if (sc->mode == MASTER) {
|
|
ret = devm_request_threaded_irq(&sc->client->dev, sc->irq,
|
|
NULL, sc854x_charger_interrupt,
|
|
IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
|
|
"sc854x master irq", sc);
|
|
} else {
|
|
ret = devm_request_threaded_irq(&sc->client->dev, sc->irq,
|
|
NULL, sc854x_charger_interrupt,
|
|
IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
|
|
"sc854x slave irq", sc);
|
|
}
|
|
if (ret < 0) {
|
|
sc_err("request irq for irq=%d failed, ret =%d\n",
|
|
sc->irq, ret);
|
|
return ret;
|
|
}
|
|
enable_irq_wake(sc->irq);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void sc854x_check_fault_status(struct sc854x *sc)
|
|
{
|
|
int ret;
|
|
u8 flag = 0;
|
|
|
|
mutex_lock(&sc->data_lock);
|
|
|
|
ret = sc854x_read_byte(sc, SC854X_REG_02, &flag);
|
|
if (!ret && (flag & SC854X_AC_OVP_FLAG_MASK))
|
|
sc_err("irq VAC OVP FLAG\n");
|
|
|
|
ret = sc854x_read_byte(sc, SC854X_REG_03, &flag);
|
|
if (!ret && (flag & SC854X_VDROP_OVP_FLAG_MASK))
|
|
sc_err("irq CDROP OVP FLAG\n");
|
|
|
|
ret = sc854x_read_byte(sc, SC854X_REG_06, &flag);
|
|
if (!ret && flag) {
|
|
if (flag & SC854X_TSHUT_FLAG_MASK)
|
|
sc_err("irq TSHUT FLAG\n");
|
|
if (flag & SC854X_SS_TIMEOUT_FLAG_MASK)
|
|
sc_err("irq SS TIMEOUT FLAG\n");
|
|
if (flag & SC854X_REG_TIMEOUT_FLAG_MASK)
|
|
sc_err("irq REG TIMEOUT FLAG\n");
|
|
if (flag & SC854X_PIN_DIAG_FALL_FLAG_MASK)
|
|
sc_err("irq PIN DIAG FALL FLAG\n");
|
|
}
|
|
|
|
ret = sc854x_read_byte(sc, SC854X_REG_09, &flag);
|
|
if (!ret && flag) {
|
|
if (flag & SC854X_POR_FLAG_MASK) {
|
|
sc_err("irq POR FLAG\n");
|
|
sc854x_set_present(sc, true);
|
|
}
|
|
if (flag & SC854X_IBUS_UCP_RISE_FLAG_MASK)
|
|
sc_err("irq IBUS UCP RIST FLAG");
|
|
if (flag & SC854X_WD_TIMEOUT_FLAG_MASK)
|
|
sc_err("irq WD TIMEOUT FLAG\n");
|
|
}
|
|
|
|
ret = sc854x_read_byte(sc, SC854X_REG_0A, &flag);
|
|
if (!ret && (flag & SC854X_VBATREG_ACTIVE_FLAG_MASK))
|
|
sc_err("irq VBATREG ACTIVE FLAG\n");
|
|
|
|
ret = sc854x_read_byte(sc, SC854X_REG_0B, &flag);
|
|
if (!ret && (flag & SC854X_IBATREG_ACTIVE_FLAG_MASK))
|
|
sc_err("irq IBATREG ACTIVE FLAG\n");
|
|
|
|
ret = sc854x_read_byte(sc, SC854X_REG_0C, &flag);
|
|
if (!ret && (flag & SC854X_IBUSREG_ACTIVE_FLAG_MASK))
|
|
sc_err("irq IBUSREG ACTIVE FLAG\n");
|
|
|
|
ret = sc854x_read_byte(sc, SC854X_REG_0D, &flag);
|
|
if (!ret && flag) {
|
|
if (flag & SC854X_PMID2OUT_UVP_FLAG_MASK)
|
|
sc_err("irq PMID2OUT UVP FLAG\n");
|
|
if (flag & SC854X_PMID2OUT_OVP_FLAG_MASK)
|
|
sc_err("irq PMID2OUT OVP FLAG\n");
|
|
}
|
|
|
|
ret = sc854x_read_byte(sc, SC854X_REG_0F, &flag);
|
|
if (!ret && flag) {
|
|
if (flag & SC854X_VOUT_OVP_FLAG_MASK)
|
|
sc_err("irq VOUT OVP FLAG\n");
|
|
if (flag & SC854X_VBAT_OVP_FLAG_MASK)
|
|
sc_err("irq VBAT OVP FLAG\n");
|
|
if (flag & SC854X_IBAT_OCP_FLAG_MASK)
|
|
sc_err("irq IBAT OCP FLAG\n");
|
|
if (flag & SC854X_VBUS_OVP_FLAG_MASK)
|
|
sc_err("irq VBUS OVP FLAG\n");
|
|
if (flag & SC854X_IBUS_OCP_FLAG_MASK)
|
|
sc_err("irq IBUS OCP FLAG\n");
|
|
if (flag & SC854X_IBUS_UCP_FALL_FLAG_MASK)
|
|
sc_err("irq IBUS UCP FALL FLAG\n");
|
|
if (flag & SC854X_ADAPTER_INSERT_FLAG_MASK)
|
|
sc_err("irq ADAPTER INSERT FLAG\n");
|
|
if (flag & SC854X_VBAT_INSERT_FLAG_MASK)
|
|
sc_err("irq VBAT INSERT FLAG\n");
|
|
}
|
|
|
|
mutex_unlock(&sc->data_lock);
|
|
}
|
|
|
|
static void determine_initial_status(struct sc854x *sc)
|
|
{
|
|
if (sc->client->irq)
|
|
sc854x_charger_interrupt(sc->client->irq, sc);
|
|
}
|
|
|
|
static struct of_device_id sc854x_charger_match_table[] = {
|
|
{ .compatible = "sc,sc8545-standalone",
|
|
.data = &sc854x_mode_data[SC8545_STANDALONG],},
|
|
{ .compatible = "sc,sc8545-master",
|
|
.data = &sc854x_mode_data[SC8545_MASTER],},
|
|
{ .compatible = "sc,sc8545-slave",
|
|
.data = &sc854x_mode_data[SC8545_SLAVE],},
|
|
{ .compatible = "sc,sc8546-standalone",
|
|
.data = &sc854x_mode_data[SC8546_STANDALONG],},
|
|
{ .compatible = "sc,sc8546-master",
|
|
.data = &sc854x_mode_data[SC8546_MASTER],},
|
|
{ .compatible = "sc,sc8546-slave",
|
|
.data = &sc854x_mode_data[SC8546_SLAVE],},
|
|
{ .compatible = "sc,sc8547-standalone",
|
|
.data = &sc854x_mode_data[SC8547_STANDALONG],},
|
|
{ .compatible = "sc,sc8547-master",
|
|
.data = &sc854x_mode_data[SC8547_MASTER],},
|
|
{ .compatible = "sc,sc8547-slave",
|
|
.data = &sc854x_mode_data[SC8547_SLAVE],},
|
|
{ .compatible = "sc,sc8549-standalone",
|
|
.data = &sc854x_mode_data[SC8549_STANDALONG],},
|
|
{ .compatible = "sc,sc8549-master",
|
|
.data = &sc854x_mode_data[SC8549_MASTER],},
|
|
{ .compatible = "sc,sc8549-slave",
|
|
.data = &sc854x_mode_data[SC8549_SLAVE],},
|
|
{},
|
|
};
|
|
|
|
static int sc854x_charger_probe(struct i2c_client *client,
|
|
const struct i2c_device_id *id)
|
|
{
|
|
struct sc854x *sc;
|
|
const struct of_device_id *match;
|
|
struct device_node *node = client->dev.of_node;
|
|
int ret;
|
|
|
|
sc = devm_kzalloc(&client->dev, sizeof(struct sc854x), GFP_KERNEL);
|
|
if (!sc)
|
|
return -ENOMEM;
|
|
|
|
sc->dev = &client->dev;
|
|
|
|
sc->client = client;
|
|
|
|
mutex_init(&sc->i2c_rw_lock);
|
|
mutex_init(&sc->data_lock);
|
|
mutex_init(&sc->irq_complete);
|
|
|
|
sc->resume_completed = true;
|
|
sc->irq_waiting = false;
|
|
sc->sc_adc_disabled = false;
|
|
|
|
ret = sc854x_detect_device(sc);
|
|
if (ret) {
|
|
sc_err("No sc854x device found!\n");
|
|
//prize add by lipengpeng 20220913 start
|
|
#if IS_ENABLED(CONFIG_FACTORY_CHARGE)&&IS_ENABLED(CONFIG_SECOND_CHARGER_SUPPORT)
|
|
is_chg2_exist=0;
|
|
#endif
|
|
//prize add by lipengpeng 20220913 end
|
|
goto err_1;
|
|
}
|
|
//prize add by lipengpeng 20220913 start
|
|
else{
|
|
sc_err("sc854x device found!\n");
|
|
#if IS_ENABLED(CONFIG_FACTORY_CHARGE)&&IS_ENABLED(CONFIG_SECOND_CHARGER_SUPPORT)
|
|
is_chg2_exist=1;
|
|
#endif
|
|
}
|
|
//prize add by lipengpeng 20220913 end
|
|
|
|
i2c_set_clientdata(client, sc);
|
|
sc854x_create_device_node(&(client->dev));
|
|
|
|
match = of_match_node(sc854x_charger_match_table, node);
|
|
if (match == NULL) {
|
|
sc_err("device tree match not found!\n");
|
|
goto err_1;
|
|
}
|
|
|
|
sc854x_set_work_mode(sc, *(int *)match->data);
|
|
if (ret) {
|
|
goto err_1;
|
|
}
|
|
|
|
ret = sc854x_parse_dt(sc, &client->dev);
|
|
if (ret) {
|
|
goto err_1;
|
|
}
|
|
|
|
ret = sc854x_init_device(sc);
|
|
if (ret) {
|
|
sc_err("Failed to init device\n");
|
|
goto err_1;
|
|
}
|
|
|
|
ret = sc854x_psy_register(sc);
|
|
if (ret) {
|
|
goto err_2;
|
|
}
|
|
|
|
ret = sc854x_irq_register(sc);
|
|
if (ret < 0) {
|
|
goto err_2;
|
|
}
|
|
|
|
ret = sc854x_register_chgdev(sc);
|
|
if (ret < 0) {
|
|
sc_err("%s reg chgdev fail(%d)\n", __func__, ret);
|
|
goto err_2;
|
|
}
|
|
|
|
device_init_wakeup(sc->dev, 1);
|
|
|
|
determine_initial_status(sc);
|
|
|
|
sc_info("sc854x probe successfully!\n");
|
|
|
|
return 0;
|
|
|
|
err_2:
|
|
power_supply_unregister(sc->fc2_psy);
|
|
err_1:
|
|
mutex_destroy(&sc->i2c_rw_lock);
|
|
mutex_destroy(&sc->data_lock);
|
|
mutex_destroy(&sc->irq_complete);
|
|
sc_info("sc854x probe fail\n");
|
|
devm_kfree(&client->dev, sc);
|
|
return ret;
|
|
}
|
|
|
|
static inline bool is_device_suspended(struct sc854x *sc)
|
|
{
|
|
return !sc->resume_completed;
|
|
}
|
|
|
|
|
|
static int sc854x_suspend_disable_adc(struct sc854x *sc)
|
|
{
|
|
union power_supply_propval prop;
|
|
int ret = 0;
|
|
|
|
if(!sc){
|
|
pr_err("gezi------%s---sc null----%d\n",__func__,__LINE__);
|
|
return -1;
|
|
}
|
|
if(!sc->charge_psy){
|
|
sc->charge_psy = power_supply_get_by_name("charger");
|
|
if(!sc->charge_psy){
|
|
pr_err("gezi failed to get charge_psy\n");
|
|
return -1;
|
|
}
|
|
else{
|
|
ret = power_supply_get_property(sc->charge_psy,POWER_SUPPLY_PROP_ONLINE, &prop);
|
|
pr_err("gezi------%s----online:%d dis:%d\n",__func__,prop.intval,sc->sc_adc_disabled);
|
|
if((!prop.intval) && (!sc->sc_adc_disabled)){
|
|
sc->sc_adc_disabled = true;
|
|
sc854x_enable_adc(sc, false);
|
|
}
|
|
}
|
|
}
|
|
else{
|
|
ret = power_supply_get_property(sc->charge_psy,POWER_SUPPLY_PROP_ONLINE, &prop);
|
|
pr_err("gezi------%s----online:%d dis:%d\n",__func__,prop.intval,sc->sc_adc_disabled);
|
|
if((!prop.intval) && (!sc->sc_adc_disabled)){
|
|
sc->sc_adc_disabled = true;
|
|
sc854x_enable_adc(sc, false);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int sc854x_resume_enable_adc(struct sc854x *sc)
|
|
{
|
|
//union power_supply_propval prop;
|
|
//int ret = 0;
|
|
|
|
if(!sc){
|
|
pr_err("gezi------%s---sc null----%d\n",__func__,__LINE__);
|
|
return -1;
|
|
}
|
|
|
|
pr_err("gezi------%s----dis:%d\n",__func__,sc->sc_adc_disabled);
|
|
|
|
if(sc->sc_adc_disabled){
|
|
sc854x_enable_adc(sc, true);
|
|
sc->sc_adc_disabled = false;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int sc854x_suspend(struct device *dev)
|
|
{
|
|
struct i2c_client *client = to_i2c_client(dev);
|
|
struct sc854x *sc = i2c_get_clientdata(client);
|
|
|
|
pr_err("gezi------%s-------%d\n",__func__,__LINE__);
|
|
|
|
mutex_lock(&sc->irq_complete);
|
|
sc854x_suspend_disable_adc(sc);
|
|
sc->resume_completed = false;
|
|
mutex_unlock(&sc->irq_complete);
|
|
sc_err("Suspend successfully!");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sc854x_suspend_noirq(struct device *dev)
|
|
{
|
|
struct i2c_client *client = to_i2c_client(dev);
|
|
struct sc854x *sc = i2c_get_clientdata(client);
|
|
|
|
if (sc->irq_waiting) {
|
|
pr_err_ratelimited("Aborting suspend, an interrupt was detected while suspending\n");
|
|
return -EBUSY;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int sc854x_resume(struct device *dev)
|
|
{
|
|
struct i2c_client *client = to_i2c_client(dev);
|
|
struct sc854x *sc = i2c_get_clientdata(client);
|
|
|
|
pr_err("gezi------%s-------%d\n",__func__,__LINE__);
|
|
|
|
mutex_lock(&sc->irq_complete);
|
|
|
|
sc854x_resume_enable_adc(sc);
|
|
|
|
sc->resume_completed = true;
|
|
if (sc->irq_waiting) {
|
|
sc->irq_disabled = false;
|
|
enable_irq(client->irq);
|
|
mutex_unlock(&sc->irq_complete);
|
|
sc854x_charger_interrupt(client->irq, sc);
|
|
} else {
|
|
mutex_unlock(&sc->irq_complete);
|
|
}
|
|
|
|
power_supply_changed(sc->fc2_psy);
|
|
sc_err("Resume successfully!");
|
|
|
|
return 0;
|
|
}
|
|
static int sc854x_charger_remove(struct i2c_client *client)
|
|
{
|
|
struct sc854x *sc = i2c_get_clientdata(client);
|
|
|
|
pr_err("gezi------%s-------%d\n",__func__,__LINE__);
|
|
|
|
sc854x_enable_adc(sc, false);
|
|
charger_device_unregister(sc->chg_dev);
|
|
power_supply_unregister(sc->fc2_psy);
|
|
mutex_destroy(&sc->data_lock);
|
|
mutex_destroy(&sc->i2c_rw_lock);
|
|
mutex_destroy(&sc->irq_complete);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void sc854x_charger_shutdown(struct i2c_client *client)
|
|
{
|
|
//pr_err("gezi------%s-------%d\n",__func__,__LINE__);
|
|
|
|
struct sc854x *sc = i2c_get_clientdata(client);
|
|
|
|
if(!sc){
|
|
pr_err("gezi------%s---sc null----%d\n",__func__,__LINE__);
|
|
return;
|
|
}
|
|
sc854x_enable_adc(sc, false);
|
|
}
|
|
|
|
static const struct dev_pm_ops sc854x_pm_ops = {
|
|
.resume = sc854x_resume,
|
|
.suspend_noirq = sc854x_suspend_noirq,
|
|
.suspend = sc854x_suspend,
|
|
};
|
|
|
|
static struct i2c_driver sc854x_charger_driver = {
|
|
.driver = {
|
|
.name = "sc854x-charger",
|
|
.owner = THIS_MODULE,
|
|
.of_match_table = sc854x_charger_match_table,
|
|
.pm = &sc854x_pm_ops,
|
|
},
|
|
.probe = sc854x_charger_probe,
|
|
.remove = sc854x_charger_remove,
|
|
.shutdown = sc854x_charger_shutdown,
|
|
};
|
|
|
|
module_i2c_driver(sc854x_charger_driver);
|
|
|
|
MODULE_DESCRIPTION("SC SC854X Charge Pump Driver");
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_AUTHOR("South Chip <Aiden-yu@southchip.com>");
|