2212 lines
50 KiB
C
2212 lines
50 KiB
C
|
|
// SPDX-License-Identifier: GPL-2.0
|
||
|
|
/*
|
||
|
|
* Copyright (c) 2022 Southchip Semiconductor Technology(Shanghai) Co., Ltd.
|
||
|
|
*/
|
||
|
|
|
||
|
|
#define pr_fmt(fmt) "[sc8551] %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 "sc8551.h"
|
||
|
|
|
||
|
|
enum {
|
||
|
|
ADC_IBUS,
|
||
|
|
ADC_VBUS,
|
||
|
|
ADC_VAC,
|
||
|
|
ADC_VOUT,
|
||
|
|
ADC_VBAT,
|
||
|
|
ADC_IBAT,
|
||
|
|
ADC_TBUS,
|
||
|
|
ADC_TBAT,
|
||
|
|
ADC_TDIE,
|
||
|
|
ADC_MAX_NUM,
|
||
|
|
};
|
||
|
|
|
||
|
|
#define SC8551_ROLE_STDALONE 0
|
||
|
|
#define SC8551_ROLE_SLAVE 1
|
||
|
|
#define SC8551_ROLE_MASTER 2
|
||
|
|
|
||
|
|
enum {
|
||
|
|
SC8551_STDALONE,
|
||
|
|
SC8551_SLAVE,
|
||
|
|
SC8551_MASTER,
|
||
|
|
};
|
||
|
|
|
||
|
|
static int sc8551_mode_data[] = {
|
||
|
|
[SC8551_STDALONE] = SC8551_STDALONE,
|
||
|
|
[SC8551_MASTER] = SC8551_ROLE_MASTER,
|
||
|
|
[SC8551_SLAVE] = SC8551_ROLE_SLAVE,
|
||
|
|
};
|
||
|
|
|
||
|
|
#define BAT_OVP_ALARM BIT(7)
|
||
|
|
#define BAT_OCP_ALARM BIT(6)
|
||
|
|
#define BUS_OVP_ALARM BIT(5)
|
||
|
|
#define BUS_OCP_ALARM BIT(4)
|
||
|
|
#define BAT_UCP_ALARM BIT(3)
|
||
|
|
#define VBUS_INSERT BIT(2)
|
||
|
|
#define VBAT_INSERT BIT(1)
|
||
|
|
#define ADC_DONE BIT(0)
|
||
|
|
|
||
|
|
#define BAT_OVP_FAULT BIT(7)
|
||
|
|
#define BAT_OCP_FAULT BIT(6)
|
||
|
|
#define BUS_OVP_FAULT BIT(5)
|
||
|
|
#define BUS_OCP_FAULT BIT(4)
|
||
|
|
#define TBUS_TBAT_ALARM BIT(3)
|
||
|
|
#define TS_BAT_FAULT BIT(2)
|
||
|
|
#define TS_BUS_FAULT BIT(1)
|
||
|
|
#define TS_DIE_FAULT BIT(0)
|
||
|
|
|
||
|
|
/*below used for comm with other module*/
|
||
|
|
#define BAT_OVP_FAULT_SHIFT 0
|
||
|
|
#define BAT_OCP_FAULT_SHIFT 1
|
||
|
|
#define BUS_OVP_FAULT_SHIFT 2
|
||
|
|
#define BUS_OCP_FAULT_SHIFT 3
|
||
|
|
#define BAT_THERM_FAULT_SHIFT 4
|
||
|
|
#define BUS_THERM_FAULT_SHIFT 5
|
||
|
|
#define DIE_THERM_FAULT_SHIFT 6
|
||
|
|
|
||
|
|
#define BAT_OVP_FAULT_MASK (1 << BAT_OVP_FAULT_SHIFT)
|
||
|
|
#define BAT_OCP_FAULT_MASK (1 << BAT_OCP_FAULT_SHIFT)
|
||
|
|
#define BUS_OVP_FAULT_MASK (1 << BUS_OVP_FAULT_SHIFT)
|
||
|
|
#define BUS_OCP_FAULT_MASK (1 << BUS_OCP_FAULT_SHIFT)
|
||
|
|
#define BAT_THERM_FAULT_MASK (1 << BAT_THERM_FAULT_SHIFT)
|
||
|
|
#define BUS_THERM_FAULT_MASK (1 << BUS_THERM_FAULT_SHIFT)
|
||
|
|
#define DIE_THERM_FAULT_MASK (1 << DIE_THERM_FAULT_SHIFT)
|
||
|
|
|
||
|
|
#define BAT_OVP_ALARM_SHIFT 0
|
||
|
|
#define BAT_OCP_ALARM_SHIFT 1
|
||
|
|
#define BUS_OVP_ALARM_SHIFT 2
|
||
|
|
#define BUS_OCP_ALARM_SHIFT 3
|
||
|
|
#define BAT_THERM_ALARM_SHIFT 4
|
||
|
|
#define BUS_THERM_ALARM_SHIFT 5
|
||
|
|
#define DIE_THERM_ALARM_SHIFT 6
|
||
|
|
#define BAT_UCP_ALARM_SHIFT 7
|
||
|
|
|
||
|
|
#define BAT_OVP_ALARM_MASK (1 << BAT_OVP_ALARM_SHIFT)
|
||
|
|
#define BAT_OCP_ALARM_MASK (1 << BAT_OCP_ALARM_SHIFT)
|
||
|
|
#define BUS_OVP_ALARM_MASK (1 << BUS_OVP_ALARM_SHIFT)
|
||
|
|
#define BUS_OCP_ALARM_MASK (1 << BUS_OCP_ALARM_SHIFT)
|
||
|
|
#define BAT_THERM_ALARM_MASK (1 << BAT_THERM_ALARM_SHIFT)
|
||
|
|
#define BUS_THERM_ALARM_MASK (1 << BUS_THERM_ALARM_SHIFT)
|
||
|
|
#define DIE_THERM_ALARM_MASK (1 << DIE_THERM_ALARM_SHIFT)
|
||
|
|
#define BAT_UCP_ALARM_MASK (1 << BAT_UCP_ALARM_SHIFT)
|
||
|
|
|
||
|
|
#define VBAT_REG_STATUS_SHIFT 0
|
||
|
|
#define IBAT_REG_STATUS_SHIFT 1
|
||
|
|
|
||
|
|
#define VBAT_REG_STATUS_MASK (1 << VBAT_REG_STATUS_SHIFT)
|
||
|
|
#define IBAT_REG_STATUS_MASK (1 << VBAT_REG_STATUS_SHIFT)
|
||
|
|
|
||
|
|
struct sc8551_cfg {
|
||
|
|
bool bat_ovp_disable;
|
||
|
|
bool bat_ocp_disable;
|
||
|
|
bool bat_ovp_alm_disable;
|
||
|
|
bool bat_ocp_alm_disable;
|
||
|
|
|
||
|
|
int bat_ovp_th;
|
||
|
|
int bat_ovp_alm_th;
|
||
|
|
int bat_ocp_th;
|
||
|
|
int bat_ocp_alm_th;
|
||
|
|
|
||
|
|
bool bus_ovp_alm_disable;
|
||
|
|
bool bus_ocp_disable;
|
||
|
|
bool bus_ocp_alm_disable;
|
||
|
|
|
||
|
|
int bus_ovp_th;
|
||
|
|
int bus_ovp_alm_th;
|
||
|
|
int bus_ocp_th;
|
||
|
|
int bus_ocp_alm_th;
|
||
|
|
|
||
|
|
bool bat_ucp_alm_disable;
|
||
|
|
|
||
|
|
int bat_ucp_alm_th;
|
||
|
|
int ac_ovp_th;
|
||
|
|
|
||
|
|
bool bat_therm_disable;
|
||
|
|
bool bus_therm_disable;
|
||
|
|
bool die_therm_disable;
|
||
|
|
|
||
|
|
int bat_therm_th; /*in %*/
|
||
|
|
int bus_therm_th; /*in %*/
|
||
|
|
int die_therm_th; /*in degC*/
|
||
|
|
|
||
|
|
int sense_r_mohm;
|
||
|
|
};
|
||
|
|
|
||
|
|
struct sc8551 {
|
||
|
|
struct device *dev;
|
||
|
|
struct i2c_client *client;
|
||
|
|
|
||
|
|
int part_no;
|
||
|
|
int revision;
|
||
|
|
|
||
|
|
int mode;
|
||
|
|
|
||
|
|
struct mutex data_lock;
|
||
|
|
struct mutex i2c_rw_lock;
|
||
|
|
struct mutex charging_disable_lock;
|
||
|
|
struct mutex irq_complete;
|
||
|
|
|
||
|
|
bool irq_waiting;
|
||
|
|
bool irq_disabled;
|
||
|
|
bool resume_completed;
|
||
|
|
|
||
|
|
int irq_gpio;
|
||
|
|
int irq;
|
||
|
|
|
||
|
|
bool batt_present;
|
||
|
|
bool vbus_present;
|
||
|
|
|
||
|
|
bool usb_present;
|
||
|
|
bool charge_enabled; /* Register bit status */
|
||
|
|
|
||
|
|
bool is_sc8551;
|
||
|
|
int vbus_error;
|
||
|
|
|
||
|
|
/* ADC reading */
|
||
|
|
int vbat_volt;
|
||
|
|
int vbus_volt;
|
||
|
|
int vout_volt;
|
||
|
|
int vac_volt;
|
||
|
|
|
||
|
|
int ibat_curr;
|
||
|
|
int ibus_curr;
|
||
|
|
|
||
|
|
int bat_temp;
|
||
|
|
int bus_temp;
|
||
|
|
int die_temp;
|
||
|
|
|
||
|
|
/* alarm/fault status */
|
||
|
|
bool bat_ovp_fault;
|
||
|
|
bool bat_ocp_fault;
|
||
|
|
bool bus_ovp_fault;
|
||
|
|
bool bus_ocp_fault;
|
||
|
|
|
||
|
|
bool bat_ovp_alarm;
|
||
|
|
bool bat_ocp_alarm;
|
||
|
|
bool bus_ovp_alarm;
|
||
|
|
bool bus_ocp_alarm;
|
||
|
|
|
||
|
|
bool bat_ucp_alarm;
|
||
|
|
|
||
|
|
bool bat_therm_alarm;
|
||
|
|
bool bus_therm_alarm;
|
||
|
|
bool die_therm_alarm;
|
||
|
|
|
||
|
|
bool bat_therm_fault;
|
||
|
|
bool bus_therm_fault;
|
||
|
|
bool die_therm_fault;
|
||
|
|
|
||
|
|
bool therm_shutdown_flag;
|
||
|
|
bool therm_shutdown_stat;
|
||
|
|
|
||
|
|
bool vbat_reg;
|
||
|
|
bool ibat_reg;
|
||
|
|
|
||
|
|
int prev_alarm;
|
||
|
|
int prev_fault;
|
||
|
|
|
||
|
|
int chg_ma;
|
||
|
|
int chg_mv;
|
||
|
|
|
||
|
|
int charge_state;
|
||
|
|
|
||
|
|
struct sc8551_cfg *cfg;
|
||
|
|
|
||
|
|
int skip_writes;
|
||
|
|
int skip_reads;
|
||
|
|
|
||
|
|
struct sc8551_platform_data *platform_data;
|
||
|
|
|
||
|
|
struct delayed_work monitor_work;
|
||
|
|
|
||
|
|
struct dentry *debug_root;
|
||
|
|
|
||
|
|
struct power_supply_desc psy_desc;
|
||
|
|
struct power_supply_config psy_cfg;
|
||
|
|
struct power_supply *fc2_psy;
|
||
|
|
};
|
||
|
|
|
||
|
|
/************************************************************************/
|
||
|
|
static int __sc8551_read_byte(struct sc8551 *sc, u8 reg, u8 *data)
|
||
|
|
{
|
||
|
|
s32 ret;
|
||
|
|
|
||
|
|
ret = i2c_smbus_read_byte_data(sc->client, reg);
|
||
|
|
if (ret < 0) {
|
||
|
|
pr_info("i2c read fail: can't read from reg 0x%02X\n", reg);
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
*data = (u8) ret;
|
||
|
|
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int __sc8551_write_byte(struct sc8551 *sc, int reg, u8 val)
|
||
|
|
{
|
||
|
|
s32 ret;
|
||
|
|
|
||
|
|
ret = i2c_smbus_write_byte_data(sc->client, reg, val);
|
||
|
|
if (ret < 0) {
|
||
|
|
pr_info("i2c write fail: can't write 0x%02X to reg 0x%02X: %d\n",
|
||
|
|
val, reg, ret);
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int sc8551_read_byte(struct sc8551 *sc, u8 reg, u8 *data)
|
||
|
|
{
|
||
|
|
int ret;
|
||
|
|
|
||
|
|
if (sc->skip_reads) {
|
||
|
|
*data = 0;
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
mutex_lock(&sc->i2c_rw_lock);
|
||
|
|
ret = __sc8551_read_byte(sc, reg, data);
|
||
|
|
mutex_unlock(&sc->i2c_rw_lock);
|
||
|
|
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int sc8551_write_byte(struct sc8551 *sc, u8 reg, u8 data)
|
||
|
|
{
|
||
|
|
int ret;
|
||
|
|
|
||
|
|
if (sc->skip_writes)
|
||
|
|
return 0;
|
||
|
|
|
||
|
|
mutex_lock(&sc->i2c_rw_lock);
|
||
|
|
ret = __sc8551_write_byte(sc, reg, data);
|
||
|
|
mutex_unlock(&sc->i2c_rw_lock);
|
||
|
|
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int sc8551_update_bits(struct sc8551 *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 = __sc8551_read_byte(sc, reg, &tmp);
|
||
|
|
if (ret) {
|
||
|
|
pr_info("Failed: reg=%02X, ret=%d\n", reg, ret);
|
||
|
|
goto out;
|
||
|
|
}
|
||
|
|
|
||
|
|
tmp &= ~mask;
|
||
|
|
tmp |= data & mask;
|
||
|
|
|
||
|
|
ret = __sc8551_write_byte(sc, reg, tmp);
|
||
|
|
if (ret)
|
||
|
|
pr_info("Failed: reg=%02X, ret=%d\n", reg, ret);
|
||
|
|
|
||
|
|
out:
|
||
|
|
mutex_unlock(&sc->i2c_rw_lock);
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
/*********************************************************************/
|
||
|
|
|
||
|
|
static int sc8551_enable_charge(struct sc8551 *sc, bool enable)
|
||
|
|
{
|
||
|
|
int ret;
|
||
|
|
u8 val;
|
||
|
|
|
||
|
|
if (enable)
|
||
|
|
val = SC8551_CHG_ENABLE;
|
||
|
|
else
|
||
|
|
val = SC8551_CHG_DISABLE;
|
||
|
|
|
||
|
|
val <<= SC8551_CHG_EN_SHIFT;
|
||
|
|
|
||
|
|
pr_info("sc8551 charger %s\n", enable == false ? "disable" : "enable");
|
||
|
|
ret = sc8551_update_bits(sc, SC8551_REG_0C,
|
||
|
|
SC8551_CHG_EN_MASK, val);
|
||
|
|
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
EXPORT_SYMBOL_GPL(sc8551_enable_charge);
|
||
|
|
|
||
|
|
static int sc8551_check_charge_enabled(struct sc8551 *sc, bool *enabled)
|
||
|
|
{
|
||
|
|
int ret;
|
||
|
|
u8 val;
|
||
|
|
|
||
|
|
ret = sc8551_read_byte(sc, SC8551_REG_0C, &val);
|
||
|
|
pr_info(">>>reg [0x0c] = 0x%02x\n", val);
|
||
|
|
if (!ret)
|
||
|
|
*enabled = !!(val & SC8551_CHG_EN_MASK);
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int sc8551_enable_wdt(struct sc8551 *sc, bool enable)
|
||
|
|
{
|
||
|
|
int ret;
|
||
|
|
u8 val;
|
||
|
|
|
||
|
|
if (enable)
|
||
|
|
val = SC8551_WATCHDOG_ENABLE;
|
||
|
|
else
|
||
|
|
val = SC8551_WATCHDOG_DISABLE;
|
||
|
|
|
||
|
|
val <<= SC8551_WATCHDOG_DIS_SHIFT;
|
||
|
|
|
||
|
|
ret = sc8551_update_bits(sc, SC8551_REG_0B,
|
||
|
|
SC8551_WATCHDOG_DIS_MASK, val);
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
EXPORT_SYMBOL_GPL(sc8551_enable_wdt);
|
||
|
|
|
||
|
|
static int sc8551_set_wdt(struct sc8551 *sc, int ms)
|
||
|
|
{
|
||
|
|
int ret;
|
||
|
|
u8 val;
|
||
|
|
|
||
|
|
if (ms == 500)
|
||
|
|
val = SC8551_WATCHDOG_0P5S;
|
||
|
|
else if (ms == 1000)
|
||
|
|
val = SC8551_WATCHDOG_1S;
|
||
|
|
else if (ms == 5000)
|
||
|
|
val = SC8551_WATCHDOG_5S;
|
||
|
|
else if (ms == 30000)
|
||
|
|
val = SC8551_WATCHDOG_30S;
|
||
|
|
else
|
||
|
|
val = SC8551_WATCHDOG_30S;
|
||
|
|
|
||
|
|
val <<= SC8551_WATCHDOG_SHIFT;
|
||
|
|
|
||
|
|
ret = sc8551_update_bits(sc, SC8551_REG_0B,
|
||
|
|
SC8551_WATCHDOG_MASK, val);
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
EXPORT_SYMBOL_GPL(sc8551_set_wdt);
|
||
|
|
|
||
|
|
|
||
|
|
//
|
||
|
|
|
||
|
|
static int sc8551_enable_batovp(struct sc8551 *sc, bool enable)
|
||
|
|
{
|
||
|
|
int ret;
|
||
|
|
u8 val;
|
||
|
|
|
||
|
|
if (enable)
|
||
|
|
val = SC8551_BAT_OVP_ENABLE;
|
||
|
|
else
|
||
|
|
val = SC8551_BAT_OVP_DISABLE;
|
||
|
|
|
||
|
|
val <<= SC8551_BAT_OVP_DIS_SHIFT;
|
||
|
|
|
||
|
|
ret = sc8551_update_bits(sc, SC8551_REG_00,
|
||
|
|
SC8551_BAT_OVP_DIS_MASK, val);
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
EXPORT_SYMBOL_GPL(sc8551_enable_batovp);
|
||
|
|
|
||
|
|
static int sc8551_set_batovp_th(struct sc8551 *sc, int threshold)
|
||
|
|
{
|
||
|
|
int ret;
|
||
|
|
u8 val;
|
||
|
|
|
||
|
|
if (threshold < SC8551_BAT_OVP_BASE)
|
||
|
|
threshold = SC8551_BAT_OVP_BASE;
|
||
|
|
|
||
|
|
val = (threshold - SC8551_BAT_OVP_BASE) / SC8551_BAT_OVP_LSB;
|
||
|
|
|
||
|
|
val <<= SC8551_BAT_OVP_SHIFT;
|
||
|
|
|
||
|
|
ret = sc8551_update_bits(sc, SC8551_REG_00,
|
||
|
|
SC8551_BAT_OVP_MASK, val);
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
EXPORT_SYMBOL_GPL(sc8551_set_batovp_th);
|
||
|
|
|
||
|
|
static int sc8551_enable_batovp_alarm(struct sc8551 *sc, bool enable)
|
||
|
|
{
|
||
|
|
int ret;
|
||
|
|
u8 val;
|
||
|
|
|
||
|
|
if (enable)
|
||
|
|
val = SC8551_BAT_OVP_ALM_ENABLE;
|
||
|
|
else
|
||
|
|
val = SC8551_BAT_OVP_ALM_DISABLE;
|
||
|
|
|
||
|
|
val <<= SC8551_BAT_OVP_ALM_DIS_SHIFT;
|
||
|
|
|
||
|
|
ret = sc8551_update_bits(sc, SC8551_REG_01,
|
||
|
|
SC8551_BAT_OVP_ALM_DIS_MASK, val);
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
EXPORT_SYMBOL_GPL(sc8551_enable_batovp_alarm);
|
||
|
|
|
||
|
|
static int sc8551_set_batovp_alarm_th(struct sc8551 *sc, int threshold)
|
||
|
|
{
|
||
|
|
int ret;
|
||
|
|
u8 val;
|
||
|
|
|
||
|
|
if (threshold < SC8551_BAT_OVP_ALM_BASE)
|
||
|
|
threshold = SC8551_BAT_OVP_ALM_BASE;
|
||
|
|
|
||
|
|
val = (threshold - SC8551_BAT_OVP_ALM_BASE) / SC8551_BAT_OVP_ALM_LSB;
|
||
|
|
|
||
|
|
val <<= SC8551_BAT_OVP_ALM_SHIFT;
|
||
|
|
|
||
|
|
ret = sc8551_update_bits(sc, SC8551_REG_01,
|
||
|
|
SC8551_BAT_OVP_ALM_MASK, val);
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
EXPORT_SYMBOL_GPL(sc8551_set_batovp_alarm_th);
|
||
|
|
|
||
|
|
static int sc8551_enable_batocp(struct sc8551 *sc, bool enable)
|
||
|
|
{
|
||
|
|
int ret;
|
||
|
|
u8 val;
|
||
|
|
|
||
|
|
if (enable)
|
||
|
|
val = SC8551_BAT_OCP_ENABLE;
|
||
|
|
else
|
||
|
|
val = SC8551_BAT_OCP_DISABLE;
|
||
|
|
|
||
|
|
val <<= SC8551_BAT_OCP_DIS_SHIFT;
|
||
|
|
|
||
|
|
ret = sc8551_update_bits(sc, SC8551_REG_02,
|
||
|
|
SC8551_BAT_OCP_DIS_MASK, val);
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
EXPORT_SYMBOL_GPL(sc8551_enable_batocp);
|
||
|
|
|
||
|
|
static int sc8551_set_batocp_th(struct sc8551 *sc, int threshold)
|
||
|
|
{
|
||
|
|
int ret;
|
||
|
|
u8 val;
|
||
|
|
|
||
|
|
if (threshold < SC8551_BAT_OCP_BASE)
|
||
|
|
threshold = SC8551_BAT_OCP_BASE;
|
||
|
|
|
||
|
|
val = (threshold - SC8551_BAT_OCP_BASE) / SC8551_BAT_OCP_LSB;
|
||
|
|
|
||
|
|
val <<= SC8551_BAT_OCP_SHIFT;
|
||
|
|
|
||
|
|
ret = sc8551_update_bits(sc, SC8551_REG_02,
|
||
|
|
SC8551_BAT_OCP_MASK, val);
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
EXPORT_SYMBOL_GPL(sc8551_set_batocp_th);
|
||
|
|
|
||
|
|
static int sc8551_enable_batocp_alarm(struct sc8551 *sc, bool enable)
|
||
|
|
{
|
||
|
|
int ret;
|
||
|
|
u8 val;
|
||
|
|
|
||
|
|
if (enable)
|
||
|
|
val = SC8551_BAT_OCP_ALM_ENABLE;
|
||
|
|
else
|
||
|
|
val = SC8551_BAT_OCP_ALM_DISABLE;
|
||
|
|
|
||
|
|
val <<= SC8551_BAT_OCP_ALM_DIS_SHIFT;
|
||
|
|
|
||
|
|
ret = sc8551_update_bits(sc, SC8551_REG_03,
|
||
|
|
SC8551_BAT_OCP_ALM_DIS_MASK, val);
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
EXPORT_SYMBOL_GPL(sc8551_enable_batocp_alarm);
|
||
|
|
|
||
|
|
static int sc8551_set_batocp_alarm_th(struct sc8551 *sc, int threshold)
|
||
|
|
{
|
||
|
|
int ret;
|
||
|
|
u8 val;
|
||
|
|
|
||
|
|
if (threshold < SC8551_BAT_OCP_ALM_BASE)
|
||
|
|
threshold = SC8551_BAT_OCP_ALM_BASE;
|
||
|
|
|
||
|
|
val = (threshold - SC8551_BAT_OCP_ALM_BASE) / SC8551_BAT_OCP_ALM_LSB;
|
||
|
|
|
||
|
|
val <<= SC8551_BAT_OCP_ALM_SHIFT;
|
||
|
|
|
||
|
|
ret = sc8551_update_bits(sc, SC8551_REG_03,
|
||
|
|
SC8551_BAT_OCP_ALM_MASK, val);
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
EXPORT_SYMBOL_GPL(sc8551_set_batocp_alarm_th);
|
||
|
|
|
||
|
|
|
||
|
|
static int sc8551_set_busovp_th(struct sc8551 *sc, int threshold)
|
||
|
|
{
|
||
|
|
int ret;
|
||
|
|
u8 val;
|
||
|
|
|
||
|
|
if (threshold < SC8551_BUS_OVP_BASE)
|
||
|
|
threshold = SC8551_BUS_OVP_BASE;
|
||
|
|
|
||
|
|
val = (threshold - SC8551_BUS_OVP_BASE) / SC8551_BUS_OVP_LSB;
|
||
|
|
|
||
|
|
val <<= SC8551_BUS_OVP_SHIFT;
|
||
|
|
|
||
|
|
ret = sc8551_update_bits(sc, SC8551_REG_06,
|
||
|
|
SC8551_BUS_OVP_MASK, val);
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
EXPORT_SYMBOL_GPL(sc8551_set_busovp_th);
|
||
|
|
|
||
|
|
static int sc8551_enable_busovp_alarm(struct sc8551 *sc, bool enable)
|
||
|
|
{
|
||
|
|
int ret;
|
||
|
|
u8 val;
|
||
|
|
|
||
|
|
if (enable)
|
||
|
|
val = SC8551_BUS_OVP_ALM_ENABLE;
|
||
|
|
else
|
||
|
|
val = SC8551_BUS_OVP_ALM_DISABLE;
|
||
|
|
|
||
|
|
val <<= SC8551_BUS_OVP_ALM_DIS_SHIFT;
|
||
|
|
|
||
|
|
ret = sc8551_update_bits(sc, SC8551_REG_07,
|
||
|
|
SC8551_BUS_OVP_ALM_DIS_MASK, val);
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
EXPORT_SYMBOL_GPL(sc8551_enable_busovp_alarm);
|
||
|
|
|
||
|
|
static int sc8551_set_busovp_alarm_th(struct sc8551 *sc, int threshold)
|
||
|
|
{
|
||
|
|
int ret;
|
||
|
|
u8 val;
|
||
|
|
|
||
|
|
if (threshold < SC8551_BUS_OVP_ALM_BASE)
|
||
|
|
threshold = SC8551_BUS_OVP_ALM_BASE;
|
||
|
|
|
||
|
|
val = (threshold - SC8551_BUS_OVP_ALM_BASE) / SC8551_BUS_OVP_ALM_LSB;
|
||
|
|
|
||
|
|
val <<= SC8551_BUS_OVP_ALM_SHIFT;
|
||
|
|
|
||
|
|
ret = sc8551_update_bits(sc, SC8551_REG_07,
|
||
|
|
SC8551_BUS_OVP_ALM_MASK, val);
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
EXPORT_SYMBOL_GPL(sc8551_set_busovp_alarm_th);
|
||
|
|
|
||
|
|
static int sc8551_enable_busocp(struct sc8551 *sc, bool enable)
|
||
|
|
{
|
||
|
|
int ret;
|
||
|
|
u8 val;
|
||
|
|
|
||
|
|
if (enable)
|
||
|
|
val = SC8551_BUS_OCP_ENABLE;
|
||
|
|
else
|
||
|
|
val = SC8551_BUS_OCP_DISABLE;
|
||
|
|
|
||
|
|
val <<= SC8551_BUS_OCP_DIS_SHIFT;
|
||
|
|
|
||
|
|
ret = sc8551_update_bits(sc, SC8551_REG_08,
|
||
|
|
SC8551_BUS_OCP_DIS_MASK, val);
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
EXPORT_SYMBOL_GPL(sc8551_enable_busocp);
|
||
|
|
|
||
|
|
|
||
|
|
static int sc8551_set_busocp_th(struct sc8551 *sc, int threshold)
|
||
|
|
{
|
||
|
|
int ret;
|
||
|
|
u8 val;
|
||
|
|
|
||
|
|
if (threshold < SC8551_BUS_OCP_BASE)
|
||
|
|
threshold = SC8551_BUS_OCP_BASE;
|
||
|
|
|
||
|
|
val = (threshold - SC8551_BUS_OCP_BASE) / SC8551_BUS_OCP_LSB;
|
||
|
|
|
||
|
|
val <<= SC8551_BUS_OCP_SHIFT;
|
||
|
|
|
||
|
|
ret = sc8551_update_bits(sc, SC8551_REG_08,
|
||
|
|
SC8551_BUS_OCP_MASK, val);
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
EXPORT_SYMBOL_GPL(sc8551_set_busocp_th);
|
||
|
|
|
||
|
|
static int sc8551_enable_busocp_alarm(struct sc8551 *sc, bool enable)
|
||
|
|
{
|
||
|
|
int ret;
|
||
|
|
u8 val;
|
||
|
|
|
||
|
|
if (enable)
|
||
|
|
val = SC8551_BUS_OCP_ALM_ENABLE;
|
||
|
|
else
|
||
|
|
val = SC8551_BUS_OCP_ALM_DISABLE;
|
||
|
|
|
||
|
|
val <<= SC8551_BUS_OCP_ALM_DIS_SHIFT;
|
||
|
|
|
||
|
|
ret = sc8551_update_bits(sc, SC8551_REG_09,
|
||
|
|
SC8551_BUS_OCP_ALM_DIS_MASK, val);
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
EXPORT_SYMBOL_GPL(sc8551_enable_busocp_alarm);
|
||
|
|
|
||
|
|
static int sc8551_set_busocp_alarm_th(struct sc8551 *sc, int threshold)
|
||
|
|
{
|
||
|
|
int ret;
|
||
|
|
u8 val;
|
||
|
|
|
||
|
|
if (threshold < SC8551_BUS_OCP_ALM_BASE)
|
||
|
|
threshold = SC8551_BUS_OCP_ALM_BASE;
|
||
|
|
|
||
|
|
val = (threshold - SC8551_BUS_OCP_ALM_BASE) / SC8551_BUS_OCP_ALM_LSB;
|
||
|
|
|
||
|
|
val <<= SC8551_BUS_OCP_ALM_SHIFT;
|
||
|
|
|
||
|
|
ret = sc8551_update_bits(sc, SC8551_REG_09,
|
||
|
|
SC8551_BUS_OCP_ALM_MASK, val);
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
EXPORT_SYMBOL_GPL(sc8551_set_busocp_alarm_th);
|
||
|
|
|
||
|
|
static int sc8551_enable_batucp_alarm(struct sc8551 *sc, bool enable)
|
||
|
|
{
|
||
|
|
int ret;
|
||
|
|
u8 val;
|
||
|
|
|
||
|
|
if (enable)
|
||
|
|
val = SC8551_BAT_UCP_ALM_ENABLE;
|
||
|
|
else
|
||
|
|
val = SC8551_BAT_UCP_ALM_DISABLE;
|
||
|
|
|
||
|
|
val <<= SC8551_BAT_UCP_ALM_DIS_SHIFT;
|
||
|
|
|
||
|
|
ret = sc8551_update_bits(sc, SC8551_REG_04,
|
||
|
|
SC8551_BAT_UCP_ALM_DIS_MASK, val);
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
EXPORT_SYMBOL_GPL(sc8551_enable_batucp_alarm);
|
||
|
|
|
||
|
|
static int sc8551_set_batucp_alarm_th(struct sc8551 *sc, int threshold)
|
||
|
|
{
|
||
|
|
int ret;
|
||
|
|
u8 val;
|
||
|
|
|
||
|
|
if (threshold < SC8551_BAT_UCP_ALM_BASE)
|
||
|
|
threshold = SC8551_BAT_UCP_ALM_BASE;
|
||
|
|
|
||
|
|
val = (threshold - SC8551_BAT_UCP_ALM_BASE) / SC8551_BAT_UCP_ALM_LSB;
|
||
|
|
|
||
|
|
val <<= SC8551_BAT_UCP_ALM_SHIFT;
|
||
|
|
|
||
|
|
ret = sc8551_update_bits(sc, SC8551_REG_04,
|
||
|
|
SC8551_BAT_UCP_ALM_MASK, val);
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
EXPORT_SYMBOL_GPL(sc8551_set_batucp_alarm_th);
|
||
|
|
|
||
|
|
static int sc8551_set_acovp_th(struct sc8551 *sc, int threshold)
|
||
|
|
{
|
||
|
|
int ret;
|
||
|
|
u8 val;
|
||
|
|
|
||
|
|
if (threshold < SC8551_AC_OVP_BASE)
|
||
|
|
threshold = SC8551_AC_OVP_BASE;
|
||
|
|
|
||
|
|
if (threshold == SC8551_AC_OVP_6P5V)
|
||
|
|
val = 0x07;
|
||
|
|
else
|
||
|
|
val = (threshold - SC8551_AC_OVP_BASE) / SC8551_AC_OVP_LSB;
|
||
|
|
|
||
|
|
val <<= SC8551_AC_OVP_SHIFT;
|
||
|
|
|
||
|
|
ret = sc8551_update_bits(sc, SC8551_REG_05,
|
||
|
|
SC8551_AC_OVP_MASK, val);
|
||
|
|
|
||
|
|
return ret;
|
||
|
|
|
||
|
|
}
|
||
|
|
EXPORT_SYMBOL_GPL(sc8551_set_acovp_th);
|
||
|
|
|
||
|
|
static int sc8551_set_vdrop_th(struct sc8551 *sc, int threshold)
|
||
|
|
{
|
||
|
|
int ret;
|
||
|
|
u8 val;
|
||
|
|
|
||
|
|
if (threshold == 300)
|
||
|
|
val = SC8551_VDROP_THRESHOLD_300MV;
|
||
|
|
else
|
||
|
|
val = SC8551_VDROP_THRESHOLD_400MV;
|
||
|
|
|
||
|
|
val <<= SC8551_VDROP_THRESHOLD_SET_SHIFT;
|
||
|
|
|
||
|
|
ret = sc8551_update_bits(sc, SC8551_REG_05,
|
||
|
|
SC8551_VDROP_THRESHOLD_SET_MASK,
|
||
|
|
val);
|
||
|
|
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int sc8551_set_vdrop_deglitch(struct sc8551 *sc, int us)
|
||
|
|
{
|
||
|
|
int ret;
|
||
|
|
u8 val;
|
||
|
|
|
||
|
|
if (us == 8)
|
||
|
|
val = SC8551_VDROP_DEGLITCH_8US;
|
||
|
|
else
|
||
|
|
val = SC8551_VDROP_DEGLITCH_5MS;
|
||
|
|
|
||
|
|
val <<= SC8551_VDROP_DEGLITCH_SET_SHIFT;
|
||
|
|
|
||
|
|
ret = sc8551_update_bits(sc, SC8551_REG_05,
|
||
|
|
SC8551_VDROP_DEGLITCH_SET_MASK,
|
||
|
|
val);
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int sc8551_enable_bat_therm(struct sc8551 *sc, bool enable)
|
||
|
|
{
|
||
|
|
int ret;
|
||
|
|
u8 val;
|
||
|
|
|
||
|
|
if (enable)
|
||
|
|
val = SC8551_TSBAT_ENABLE;
|
||
|
|
else
|
||
|
|
val = SC8551_TSBAT_DISABLE;
|
||
|
|
|
||
|
|
val <<= SC8551_TSBAT_DIS_SHIFT;
|
||
|
|
|
||
|
|
ret = sc8551_update_bits(sc, SC8551_REG_0C,
|
||
|
|
SC8551_TSBAT_DIS_MASK, val);
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
EXPORT_SYMBOL_GPL(sc8551_enable_bat_therm);
|
||
|
|
|
||
|
|
/*
|
||
|
|
* the input threshold is the raw value that would write to register directly.
|
||
|
|
*/
|
||
|
|
static int sc8551_set_bat_therm_th(struct sc8551 *sc, u8 threshold)
|
||
|
|
{
|
||
|
|
int ret;
|
||
|
|
|
||
|
|
ret = sc8551_write_byte(sc, SC8551_REG_29, threshold);
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
EXPORT_SYMBOL_GPL(sc8551_set_bat_therm_th);
|
||
|
|
|
||
|
|
static int sc8551_enable_bus_therm(struct sc8551 *sc, bool enable)
|
||
|
|
{
|
||
|
|
int ret;
|
||
|
|
u8 val;
|
||
|
|
|
||
|
|
if (enable)
|
||
|
|
val = SC8551_TSBUS_ENABLE;
|
||
|
|
else
|
||
|
|
val = SC8551_TSBUS_DISABLE;
|
||
|
|
|
||
|
|
val <<= SC8551_TSBUS_DIS_SHIFT;
|
||
|
|
|
||
|
|
ret = sc8551_update_bits(sc, SC8551_REG_0C,
|
||
|
|
SC8551_TSBUS_DIS_MASK, val);
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
EXPORT_SYMBOL_GPL(sc8551_enable_bus_therm);
|
||
|
|
|
||
|
|
/*
|
||
|
|
* the input threshold is the raw value that would write to register directly.
|
||
|
|
*/
|
||
|
|
static int sc8551_set_bus_therm_th(struct sc8551 *sc, u8 threshold)
|
||
|
|
{
|
||
|
|
int ret;
|
||
|
|
|
||
|
|
ret = sc8551_write_byte(sc, SC8551_REG_28, threshold);
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
EXPORT_SYMBOL_GPL(sc8551_set_bus_therm_th);
|
||
|
|
|
||
|
|
/*
|
||
|
|
* please be noted that the unit here is degC
|
||
|
|
*/
|
||
|
|
static int sc8551_set_die_therm_th(struct sc8551 *sc, u8 threshold)
|
||
|
|
{
|
||
|
|
int ret;
|
||
|
|
u8 val;
|
||
|
|
|
||
|
|
/*BE careful, LSB is here is 1/LSB, so we use multiply here*/
|
||
|
|
val = (threshold - SC8551_TDIE_ALM_BASE) * 10/SC8551_TDIE_ALM_LSB;
|
||
|
|
val <<= SC8551_TDIE_ALM_SHIFT;
|
||
|
|
|
||
|
|
ret = sc8551_update_bits(sc, SC8551_REG_2A,
|
||
|
|
SC8551_TDIE_ALM_MASK, val);
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
EXPORT_SYMBOL_GPL(sc8551_set_die_therm_th);
|
||
|
|
|
||
|
|
static int sc8551_enable_adc(struct sc8551 *sc, bool enable)
|
||
|
|
{
|
||
|
|
int ret;
|
||
|
|
u8 val;
|
||
|
|
|
||
|
|
if (enable)
|
||
|
|
val = SC8551_ADC_ENABLE;
|
||
|
|
else
|
||
|
|
val = SC8551_ADC_DISABLE;
|
||
|
|
|
||
|
|
val <<= SC8551_ADC_EN_SHIFT;
|
||
|
|
|
||
|
|
ret = sc8551_update_bits(sc, SC8551_REG_14,
|
||
|
|
SC8551_ADC_EN_MASK, val);
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
EXPORT_SYMBOL_GPL(sc8551_enable_adc);
|
||
|
|
|
||
|
|
|
||
|
|
static int sc8551_set_adc_scanrate(struct sc8551 *sc, bool oneshot)
|
||
|
|
{
|
||
|
|
int ret;
|
||
|
|
u8 val;
|
||
|
|
|
||
|
|
if (oneshot)
|
||
|
|
val = SC8551_ADC_RATE_ONESHOT;
|
||
|
|
else
|
||
|
|
val = SC8551_ADC_RATE_CONTINUOUS;
|
||
|
|
|
||
|
|
val <<= SC8551_ADC_RATE_SHIFT;
|
||
|
|
|
||
|
|
ret = sc8551_update_bits(sc, SC8551_REG_14,
|
||
|
|
SC8551_ADC_EN_MASK, val);
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
EXPORT_SYMBOL_GPL(sc8551_set_adc_scanrate);
|
||
|
|
|
||
|
|
|
||
|
|
#define ADC_REG_BASE SC8551_REG_16
|
||
|
|
static int sc8551_get_adc_data(struct sc8551 *sc, int channel, int *result)
|
||
|
|
{
|
||
|
|
int ret;
|
||
|
|
u8 val_l, val_h;
|
||
|
|
u16 val;
|
||
|
|
|
||
|
|
if (channel >= ADC_MAX_NUM)
|
||
|
|
return 0;
|
||
|
|
|
||
|
|
sc8551_enable_adc(sc, true);
|
||
|
|
msleep(20);
|
||
|
|
|
||
|
|
ret = sc8551_read_byte(sc, ADC_REG_BASE + (channel << 1), &val_h);
|
||
|
|
ret = sc8551_read_byte(sc, ADC_REG_BASE + (channel << 1) + 1, &val_l);
|
||
|
|
|
||
|
|
if (ret < 0)
|
||
|
|
return ret;
|
||
|
|
val = (val_h << 8) | val_l;
|
||
|
|
|
||
|
|
if (sc->is_sc8551) {
|
||
|
|
if (channel == ADC_IBUS)
|
||
|
|
val = val * SC8551_IBUS_ADC_LSB;
|
||
|
|
else if (channel == ADC_VBUS)
|
||
|
|
val = val * SC8551_VBUS_ADC_LSB;
|
||
|
|
else if (channel == ADC_VAC)
|
||
|
|
val = val * SC8551_VAC_ADC_LSB;
|
||
|
|
else if (channel == ADC_VOUT)
|
||
|
|
val = val * SC8551_VOUT_ADC_LSB;
|
||
|
|
else if (channel == ADC_VBAT)
|
||
|
|
val = val * SC8551_VBAT_ADC_LSB;
|
||
|
|
else if (channel == ADC_IBAT)
|
||
|
|
val = val * SC8551_IBAT_ADC_LSB;
|
||
|
|
else if (channel == ADC_TDIE)
|
||
|
|
val = val * SC8551_TDIE_ADC_LSB;
|
||
|
|
}
|
||
|
|
|
||
|
|
*result = val;
|
||
|
|
|
||
|
|
sc8551_enable_adc(sc, false);
|
||
|
|
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
EXPORT_SYMBOL_GPL(sc8551_get_adc_data);
|
||
|
|
|
||
|
|
static int sc8551_set_adc_scan(struct sc8551 *sc, int channel, bool enable)
|
||
|
|
{
|
||
|
|
int ret;
|
||
|
|
u8 reg;
|
||
|
|
u8 mask;
|
||
|
|
u8 shift;
|
||
|
|
u8 val;
|
||
|
|
|
||
|
|
if (channel > ADC_MAX_NUM)
|
||
|
|
return -EINVAL;
|
||
|
|
|
||
|
|
if (channel == ADC_IBUS) {
|
||
|
|
reg = SC8551_REG_14;
|
||
|
|
shift = SC8551_IBUS_ADC_DIS_SHIFT;
|
||
|
|
mask = SC8551_IBUS_ADC_DIS_MASK;
|
||
|
|
} else {
|
||
|
|
reg = SC8551_REG_15;
|
||
|
|
shift = 8 - channel;
|
||
|
|
mask = 1 << shift;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (enable)
|
||
|
|
val = 0 << shift;
|
||
|
|
else
|
||
|
|
val = 1 << shift;
|
||
|
|
|
||
|
|
ret = sc8551_update_bits(sc, reg, mask, val);
|
||
|
|
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int sc8551_set_alarm_int_mask(struct sc8551 *sc, u8 mask)
|
||
|
|
{
|
||
|
|
int ret;
|
||
|
|
u8 val;
|
||
|
|
|
||
|
|
ret = sc8551_read_byte(sc, SC8551_REG_0F, &val);
|
||
|
|
if (ret)
|
||
|
|
return ret;
|
||
|
|
|
||
|
|
val |= mask;
|
||
|
|
|
||
|
|
ret = sc8551_write_byte(sc, SC8551_REG_0F, val);
|
||
|
|
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
EXPORT_SYMBOL_GPL(sc8551_set_alarm_int_mask);
|
||
|
|
|
||
|
|
static int sc8551_clear_alarm_int_mask(struct sc8551 *sc, u8 mask)
|
||
|
|
{
|
||
|
|
int ret;
|
||
|
|
u8 val;
|
||
|
|
|
||
|
|
ret = sc8551_read_byte(sc, SC8551_REG_0F, &val);
|
||
|
|
if (ret)
|
||
|
|
return ret;
|
||
|
|
|
||
|
|
val &= ~mask;
|
||
|
|
|
||
|
|
ret = sc8551_write_byte(sc, SC8551_REG_0F, val);
|
||
|
|
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
EXPORT_SYMBOL_GPL(sc8551_clear_alarm_int_mask);
|
||
|
|
|
||
|
|
static int sc8551_set_fault_int_mask(struct sc8551 *sc, u8 mask)
|
||
|
|
{
|
||
|
|
int ret;
|
||
|
|
u8 val;
|
||
|
|
|
||
|
|
ret = sc8551_read_byte(sc, SC8551_REG_12, &val);
|
||
|
|
if (ret)
|
||
|
|
return ret;
|
||
|
|
|
||
|
|
val |= mask;
|
||
|
|
|
||
|
|
ret = sc8551_write_byte(sc, SC8551_REG_12, val);
|
||
|
|
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
EXPORT_SYMBOL_GPL(sc8551_set_fault_int_mask);
|
||
|
|
|
||
|
|
static int sc8551_clear_fault_int_mask(struct sc8551 *sc, u8 mask)
|
||
|
|
{
|
||
|
|
int ret;
|
||
|
|
u8 val;
|
||
|
|
|
||
|
|
ret = sc8551_read_byte(sc, SC8551_REG_12, &val);
|
||
|
|
if (ret)
|
||
|
|
return ret;
|
||
|
|
|
||
|
|
val &= ~mask;
|
||
|
|
|
||
|
|
ret = sc8551_write_byte(sc, SC8551_REG_12, val);
|
||
|
|
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
EXPORT_SYMBOL_GPL(sc8551_clear_fault_int_mask);
|
||
|
|
|
||
|
|
static int sc8551_set_sense_resistor(struct sc8551 *sc, int r_mohm)
|
||
|
|
{
|
||
|
|
int ret;
|
||
|
|
u8 val;
|
||
|
|
|
||
|
|
if (r_mohm == 2)
|
||
|
|
val = SC8551_SET_IBAT_SNS_RES_2MHM;
|
||
|
|
else if (r_mohm == 5)
|
||
|
|
val = SC8551_SET_IBAT_SNS_RES_5MHM;
|
||
|
|
else
|
||
|
|
return -EINVAL;
|
||
|
|
|
||
|
|
val <<= SC8551_SET_IBAT_SNS_RES_SHIFT;
|
||
|
|
|
||
|
|
ret = sc8551_update_bits(sc, SC8551_REG_2B,
|
||
|
|
SC8551_SET_IBAT_SNS_RES_MASK,
|
||
|
|
val);
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int sc8551_enable_regulation(struct sc8551 *sc, bool enable)
|
||
|
|
{
|
||
|
|
int ret;
|
||
|
|
u8 val;
|
||
|
|
|
||
|
|
if (enable)
|
||
|
|
val = SC8551_EN_REGULATION_ENABLE;
|
||
|
|
else
|
||
|
|
val = SC8551_EN_REGULATION_DISABLE;
|
||
|
|
|
||
|
|
val <<= SC8551_EN_REGULATION_SHIFT;
|
||
|
|
|
||
|
|
ret = sc8551_update_bits(sc, SC8551_REG_2B,
|
||
|
|
SC8551_EN_REGULATION_MASK,
|
||
|
|
val);
|
||
|
|
|
||
|
|
return ret;
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
static int sc8551_set_ss_timeout(struct sc8551 *sc, int timeout)
|
||
|
|
{
|
||
|
|
int ret;
|
||
|
|
u8 val;
|
||
|
|
|
||
|
|
switch (timeout) {
|
||
|
|
case 0:
|
||
|
|
val = SC8551_SS_TIMEOUT_DISABLE;
|
||
|
|
break;
|
||
|
|
case 12:
|
||
|
|
val = SC8551_SS_TIMEOUT_12P5MS;
|
||
|
|
break;
|
||
|
|
case 25:
|
||
|
|
val = SC8551_SS_TIMEOUT_25MS;
|
||
|
|
break;
|
||
|
|
case 50:
|
||
|
|
val = SC8551_SS_TIMEOUT_50MS;
|
||
|
|
break;
|
||
|
|
case 100:
|
||
|
|
val = SC8551_SS_TIMEOUT_100MS;
|
||
|
|
break;
|
||
|
|
case 400:
|
||
|
|
val = SC8551_SS_TIMEOUT_400MS;
|
||
|
|
break;
|
||
|
|
case 1500:
|
||
|
|
val = SC8551_SS_TIMEOUT_1500MS;
|
||
|
|
break;
|
||
|
|
case 100000:
|
||
|
|
val = SC8551_SS_TIMEOUT_100000MS;
|
||
|
|
break;
|
||
|
|
default:
|
||
|
|
val = SC8551_SS_TIMEOUT_DISABLE;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
val <<= SC8551_SS_TIMEOUT_SET_SHIFT;
|
||
|
|
|
||
|
|
ret = sc8551_update_bits(sc, SC8551_REG_2B,
|
||
|
|
SC8551_SS_TIMEOUT_SET_MASK,
|
||
|
|
val);
|
||
|
|
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int sc8551_set_ibat_reg_th(struct sc8551 *sc, int th_ma)
|
||
|
|
{
|
||
|
|
int ret;
|
||
|
|
u8 val;
|
||
|
|
|
||
|
|
if (th_ma == 200)
|
||
|
|
val = SC8551_IBAT_REG_200MA;
|
||
|
|
else if (th_ma == 300)
|
||
|
|
val = SC8551_IBAT_REG_300MA;
|
||
|
|
else if (th_ma == 400)
|
||
|
|
val = SC8551_IBAT_REG_400MA;
|
||
|
|
else if (th_ma == 500)
|
||
|
|
val = SC8551_IBAT_REG_500MA;
|
||
|
|
else
|
||
|
|
val = SC8551_IBAT_REG_500MA;
|
||
|
|
|
||
|
|
val <<= SC8551_IBAT_REG_SHIFT;
|
||
|
|
ret = sc8551_update_bits(sc, SC8551_REG_2C,
|
||
|
|
SC8551_IBAT_REG_MASK,
|
||
|
|
val);
|
||
|
|
|
||
|
|
return ret;
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
static int sc8551_set_vbat_reg_th(struct sc8551 *sc, int th_mv)
|
||
|
|
{
|
||
|
|
int ret;
|
||
|
|
u8 val;
|
||
|
|
|
||
|
|
if (th_mv == 50)
|
||
|
|
val = SC8551_VBAT_REG_50MV;
|
||
|
|
else if (th_mv == 100)
|
||
|
|
val = SC8551_VBAT_REG_100MV;
|
||
|
|
else if (th_mv == 150)
|
||
|
|
val = SC8551_VBAT_REG_150MV;
|
||
|
|
else
|
||
|
|
val = SC8551_VBAT_REG_200MV;
|
||
|
|
|
||
|
|
val <<= SC8551_VBAT_REG_SHIFT;
|
||
|
|
|
||
|
|
ret = sc8551_update_bits(sc, SC8551_REG_2C,
|
||
|
|
SC8551_VBAT_REG_MASK,
|
||
|
|
val);
|
||
|
|
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
static int sc8551_get_work_mode(struct sc8551 *sc, int *mode)
|
||
|
|
{
|
||
|
|
int ret;
|
||
|
|
u8 val;
|
||
|
|
|
||
|
|
ret = sc8551_read_byte(sc, SC8551_REG_0C, &val);
|
||
|
|
|
||
|
|
if (ret) {
|
||
|
|
pr_info("Failed to read operation mode register\n");
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
val = (val & SC8551_MS_MASK) >> SC8551_MS_SHIFT;
|
||
|
|
if (val == SC8551_MS_MASTER)
|
||
|
|
*mode = SC8551_ROLE_MASTER;
|
||
|
|
else if (val == SC8551_MS_SLAVE)
|
||
|
|
*mode = SC8551_ROLE_SLAVE;
|
||
|
|
else
|
||
|
|
*mode = SC8551_ROLE_STDALONE;
|
||
|
|
|
||
|
|
pr_info("work mode:%s\n", *mode == SC8551_ROLE_STDALONE ? "Standalone" :
|
||
|
|
(*mode == SC8551_ROLE_SLAVE ? "Slave" : "Master"));
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int sc8551_check_vbus_error_status(struct sc8551 *sc)
|
||
|
|
{
|
||
|
|
int ret;
|
||
|
|
u8 data;
|
||
|
|
|
||
|
|
ret = sc8551_read_byte(sc, SC8551_REG_0A, &data);
|
||
|
|
if (ret == 0) {
|
||
|
|
pr_info("vbus error >>>>%02x\n", data);
|
||
|
|
sc->vbus_error = data;
|
||
|
|
}
|
||
|
|
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int sc8551_detect_device(struct sc8551 *sc)
|
||
|
|
{
|
||
|
|
int ret;
|
||
|
|
u8 data;
|
||
|
|
|
||
|
|
ret = sc8551_read_byte(sc, SC8551_REG_13, &data);
|
||
|
|
if (ret == 0) {
|
||
|
|
sc->part_no = (data & SC8551_DEV_ID_MASK);
|
||
|
|
sc->part_no >>= SC8551_DEV_ID_SHIFT;
|
||
|
|
}
|
||
|
|
pr_info("%s:chip ID[%d]\n", __func__, data);
|
||
|
|
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int sc8551_parse_dt(struct sc8551 *sc, struct device *dev)
|
||
|
|
{
|
||
|
|
int ret;
|
||
|
|
struct device_node *np = dev->of_node;
|
||
|
|
|
||
|
|
sc->cfg = devm_kzalloc(dev, sizeof(struct sc8551_cfg),
|
||
|
|
GFP_KERNEL);
|
||
|
|
|
||
|
|
if (!sc->cfg)
|
||
|
|
return -ENOMEM;
|
||
|
|
|
||
|
|
sc->cfg->bat_ovp_disable = of_property_read_bool(np,
|
||
|
|
"sc,sc8551,bat-ovp-disable");
|
||
|
|
sc->cfg->bat_ocp_disable = of_property_read_bool(np,
|
||
|
|
"sc,sc8551,bat-ocp-disable");
|
||
|
|
sc->cfg->bat_ovp_alm_disable = of_property_read_bool(np,
|
||
|
|
"sc,sc8551,bat-ovp-alarm-disable");
|
||
|
|
sc->cfg->bat_ocp_alm_disable = of_property_read_bool(np,
|
||
|
|
"sc,sc8551,bat-ocp-alarm-disable");
|
||
|
|
sc->cfg->bus_ocp_disable = of_property_read_bool(np,
|
||
|
|
"sc,sc8551,bus-ocp-disable");
|
||
|
|
sc->cfg->bus_ovp_alm_disable = of_property_read_bool(np,
|
||
|
|
"sc,sc8551,bus-ovp-alarm-disable");
|
||
|
|
sc->cfg->bus_ocp_alm_disable = of_property_read_bool(np,
|
||
|
|
"sc,sc8551,bus-ocp-alarm-disable");
|
||
|
|
sc->cfg->bat_ucp_alm_disable = of_property_read_bool(np,
|
||
|
|
"sc,sc8551,bat-ucp-alarm-disable");
|
||
|
|
sc->cfg->bat_therm_disable = of_property_read_bool(np,
|
||
|
|
"sc,sc8551,bat-therm-disable");
|
||
|
|
sc->cfg->bus_therm_disable = of_property_read_bool(np,
|
||
|
|
"sc,sc8551,bus-therm-disable");
|
||
|
|
|
||
|
|
ret = of_property_read_u32(np, "sc,sc8551,bat-ovp-threshold",
|
||
|
|
&sc->cfg->bat_ovp_th);
|
||
|
|
if (ret) {
|
||
|
|
pr_info("failed to read bat-ovp-threshold\n");
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
ret = of_property_read_u32(np, "sc,sc8551,bat-ovp-alarm-threshold",
|
||
|
|
&sc->cfg->bat_ovp_alm_th);
|
||
|
|
if (ret) {
|
||
|
|
pr_info("failed to read bat-ovp-alarm-threshold\n");
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
ret = of_property_read_u32(np, "sc,sc8551,bat-ocp-threshold",
|
||
|
|
&sc->cfg->bat_ocp_th);
|
||
|
|
if (ret) {
|
||
|
|
pr_info("failed to read bat-ocp-threshold\n");
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
ret = of_property_read_u32(np, "sc,sc8551,bat-ocp-alarm-threshold",
|
||
|
|
&sc->cfg->bat_ocp_alm_th);
|
||
|
|
if (ret) {
|
||
|
|
pr_info("failed to read bat-ocp-alarm-threshold\n");
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
ret = of_property_read_u32(np, "sc,sc8551,bus-ovp-threshold",
|
||
|
|
&sc->cfg->bus_ovp_th);
|
||
|
|
if (ret) {
|
||
|
|
pr_info("failed to read bus-ovp-threshold\n");
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
ret = of_property_read_u32(np, "sc,sc8551,bus-ovp-alarm-threshold",
|
||
|
|
&sc->cfg->bus_ovp_alm_th);
|
||
|
|
if (ret) {
|
||
|
|
pr_info("failed to read bus-ovp-alarm-threshold\n");
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
ret = of_property_read_u32(np, "sc,sc8551,bus-ocp-threshold",
|
||
|
|
&sc->cfg->bus_ocp_th);
|
||
|
|
if (ret) {
|
||
|
|
pr_info("failed to read bus-ocp-threshold\n");
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
ret = of_property_read_u32(np, "sc,sc8551,bus-ocp-alarm-threshold",
|
||
|
|
&sc->cfg->bus_ocp_alm_th);
|
||
|
|
if (ret) {
|
||
|
|
pr_info("failed to read bus-ocp-alarm-threshold\n");
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
ret = of_property_read_u32(np, "sc,sc8551,bat-ucp-alarm-threshold",
|
||
|
|
&sc->cfg->bat_ucp_alm_th);
|
||
|
|
if (ret) {
|
||
|
|
pr_info("failed to read bat-ucp-alarm-threshold\n");
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
ret = of_property_read_u32(np, "sc,sc8551,bat-therm-threshold",
|
||
|
|
&sc->cfg->bat_therm_th);
|
||
|
|
if (ret) {
|
||
|
|
pr_info("failed to read bat-therm-threshold\n");
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
ret = of_property_read_u32(np, "sc,sc8551,bus-therm-threshold",
|
||
|
|
&sc->cfg->bus_therm_th);
|
||
|
|
if (ret) {
|
||
|
|
pr_info("failed to read bus-therm-threshold\n");
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
ret = of_property_read_u32(np, "sc,sc8551,die-therm-threshold",
|
||
|
|
&sc->cfg->die_therm_th);
|
||
|
|
if (ret) {
|
||
|
|
pr_info("failed to read die-therm-threshold\n");
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
ret = of_property_read_u32(np, "sc,sc8551,ac-ovp-threshold",
|
||
|
|
&sc->cfg->ac_ovp_th);
|
||
|
|
if (ret) {
|
||
|
|
pr_info("failed to read ac-ovp-threshold\n");
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
ret = of_property_read_u32(np, "sc,sc8551,sense-resistor-mohm",
|
||
|
|
&sc->cfg->sense_r_mohm);
|
||
|
|
if (ret) {
|
||
|
|
pr_info("failed to read sense-resistor-mohm\n");
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
#if defined(SC8551_CUSTOMER_SUPPORT)
|
||
|
|
sc->irq_gpio = devm_gpiod_get(sc->dev, "sc,sc8551,interrupt_gpios", GPIOD_IN);
|
||
|
|
if (IS_ERR(sc->irq_gpio))
|
||
|
|
return PTR_ERR(sc->irq_gpio);
|
||
|
|
#endif
|
||
|
|
ret = of_get_named_gpio(np, "sc,sc8551,interrupt_gpios", 0);
|
||
|
|
if (ret < 0) {
|
||
|
|
pr_info("no intr_gpio info\n");
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
sc->irq_gpio = ret;
|
||
|
|
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
static int sc8551_init_protection(struct sc8551 *sc)
|
||
|
|
{
|
||
|
|
int ret;
|
||
|
|
|
||
|
|
ret = sc8551_enable_batovp(sc, !sc->cfg->bat_ovp_disable);
|
||
|
|
pr_info("%s bat ovp %s\n",
|
||
|
|
sc->cfg->bat_ovp_disable ? "disable" : "enable",
|
||
|
|
!ret ? "successfullly" : "failed");
|
||
|
|
|
||
|
|
ret = sc8551_enable_batocp(sc, !sc->cfg->bat_ocp_disable);
|
||
|
|
pr_info("%s bat ocp %s\n",
|
||
|
|
sc->cfg->bat_ocp_disable ? "disable" : "enable",
|
||
|
|
!ret ? "successfullly" : "failed");
|
||
|
|
|
||
|
|
ret = sc8551_enable_batovp_alarm(sc, !sc->cfg->bat_ovp_alm_disable);
|
||
|
|
pr_info("%s bat ovp alarm %s\n",
|
||
|
|
sc->cfg->bat_ovp_alm_disable ? "disable" : "enable",
|
||
|
|
!ret ? "successfullly" : "failed");
|
||
|
|
|
||
|
|
ret = sc8551_enable_batocp_alarm(sc, !sc->cfg->bat_ocp_alm_disable);
|
||
|
|
pr_info("%s bat ocp alarm %s\n",
|
||
|
|
sc->cfg->bat_ocp_alm_disable ? "disable" : "enable",
|
||
|
|
!ret ? "successfullly" : "failed");
|
||
|
|
|
||
|
|
ret = sc8551_enable_batucp_alarm(sc, !sc->cfg->bat_ucp_alm_disable);
|
||
|
|
pr_info("%s bat ocp alarm %s\n",
|
||
|
|
sc->cfg->bat_ucp_alm_disable ? "disable" : "enable",
|
||
|
|
!ret ? "successfullly" : "failed");
|
||
|
|
|
||
|
|
ret = sc8551_enable_busovp_alarm(sc, !sc->cfg->bus_ovp_alm_disable);
|
||
|
|
pr_info("%s bus ovp alarm %s\n",
|
||
|
|
sc->cfg->bus_ovp_alm_disable ? "disable" : "enable",
|
||
|
|
!ret ? "successfullly" : "failed");
|
||
|
|
|
||
|
|
ret = sc8551_enable_busocp(sc, !sc->cfg->bus_ocp_disable);
|
||
|
|
pr_info("%s bus ocp %s\n",
|
||
|
|
sc->cfg->bus_ocp_disable ? "disable" : "enable",
|
||
|
|
!ret ? "successfullly" : "failed");
|
||
|
|
|
||
|
|
ret = sc8551_enable_busocp_alarm(sc, !sc->cfg->bus_ocp_alm_disable);
|
||
|
|
pr_info("%s bus ocp alarm %s\n",
|
||
|
|
sc->cfg->bus_ocp_alm_disable ? "disable" : "enable",
|
||
|
|
!ret ? "successfullly" : "failed");
|
||
|
|
|
||
|
|
ret = sc8551_enable_bat_therm(sc, !sc->cfg->bat_therm_disable);
|
||
|
|
pr_info("%s bat therm %s\n",
|
||
|
|
sc->cfg->bat_therm_disable ? "disable" : "enable",
|
||
|
|
!ret ? "successfullly" : "failed");
|
||
|
|
|
||
|
|
ret = sc8551_enable_bus_therm(sc, !sc->cfg->bus_therm_disable);
|
||
|
|
pr_info("%s bus therm %s\n",
|
||
|
|
sc->cfg->bus_therm_disable ? "disable" : "enable",
|
||
|
|
!ret ? "successfullly" : "failed");
|
||
|
|
|
||
|
|
ret = sc8551_set_batovp_th(sc, sc->cfg->bat_ovp_th);
|
||
|
|
pr_info("set bat ovp th %d %s\n", sc->cfg->bat_ovp_th,
|
||
|
|
!ret ? "successfully" : "failed");
|
||
|
|
|
||
|
|
ret = sc8551_set_batovp_alarm_th(sc, sc->cfg->bat_ovp_alm_th);
|
||
|
|
pr_info("set bat ovp alarm threshold %d %s\n", sc->cfg->bat_ovp_alm_th,
|
||
|
|
!ret ? "successfully" : "failed");
|
||
|
|
|
||
|
|
ret = sc8551_set_batocp_th(sc, sc->cfg->bat_ocp_th);
|
||
|
|
pr_info("set bat ocp threshold %d %s\n", sc->cfg->bat_ocp_th,
|
||
|
|
!ret ? "successfully" : "failed");
|
||
|
|
|
||
|
|
ret = sc8551_set_batocp_alarm_th(sc, sc->cfg->bat_ocp_alm_th);
|
||
|
|
pr_info("set bat ocp alarm threshold %d %s\n", sc->cfg->bat_ocp_alm_th,
|
||
|
|
!ret ? "successfully" : "failed");
|
||
|
|
|
||
|
|
ret = sc8551_set_busovp_th(sc, sc->cfg->bus_ovp_th);
|
||
|
|
pr_info("set bus ovp threshold %d %s\n", sc->cfg->bus_ovp_th,
|
||
|
|
!ret ? "successfully" : "failed");
|
||
|
|
|
||
|
|
ret = sc8551_set_busovp_alarm_th(sc, sc->cfg->bus_ovp_alm_th);
|
||
|
|
pr_info("set bus ovp alarm threshold %d %s\n", sc->cfg->bus_ovp_alm_th,
|
||
|
|
!ret ? "successfully" : "failed");
|
||
|
|
|
||
|
|
ret = sc8551_set_busocp_th(sc, sc->cfg->bus_ocp_th);
|
||
|
|
pr_info("set bus ocp threshold %d %s\n", sc->cfg->bus_ocp_th,
|
||
|
|
!ret ? "successfully" : "failed");
|
||
|
|
|
||
|
|
ret = sc8551_set_busocp_alarm_th(sc, sc->cfg->bus_ocp_alm_th);
|
||
|
|
pr_info("set bus ocp alarm th %d %s\n", sc->cfg->bus_ocp_alm_th,
|
||
|
|
!ret ? "successfully" : "failed");
|
||
|
|
|
||
|
|
ret = sc8551_set_batucp_alarm_th(sc, sc->cfg->bat_ucp_alm_th);
|
||
|
|
pr_info("set bat ucp threshold %d %s\n", sc->cfg->bat_ucp_alm_th,
|
||
|
|
!ret ? "successfully" : "failed");
|
||
|
|
|
||
|
|
ret = sc8551_set_bat_therm_th(sc, sc->cfg->bat_therm_th);
|
||
|
|
pr_info("set die therm threshold %d %s\n", sc->cfg->bat_therm_th,
|
||
|
|
!ret ? "successfully" : "failed");
|
||
|
|
ret = sc8551_set_bus_therm_th(sc, sc->cfg->bus_therm_th);
|
||
|
|
pr_info("set bus therm threshold %d %s\n", sc->cfg->bus_therm_th,
|
||
|
|
!ret ? "successfully" : "failed");
|
||
|
|
ret = sc8551_set_die_therm_th(sc, sc->cfg->die_therm_th);
|
||
|
|
pr_info("set die therm threshold %d %s\n", sc->cfg->die_therm_th,
|
||
|
|
!ret ? "successfully" : "failed");
|
||
|
|
|
||
|
|
ret = sc8551_set_acovp_th(sc, sc->cfg->ac_ovp_th);
|
||
|
|
pr_info("set ac ovp threshold %d %s\n", sc->cfg->ac_ovp_th,
|
||
|
|
!ret ? "successfully" : "failed");
|
||
|
|
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int sc8551_init_adc(struct sc8551 *sc)
|
||
|
|
{
|
||
|
|
|
||
|
|
sc8551_set_adc_scanrate(sc, false);
|
||
|
|
sc8551_set_adc_scan(sc, ADC_IBUS, true);
|
||
|
|
sc8551_set_adc_scan(sc, ADC_VBUS, true);
|
||
|
|
sc8551_set_adc_scan(sc, ADC_VOUT, true);
|
||
|
|
sc8551_set_adc_scan(sc, ADC_VBAT, true);
|
||
|
|
sc8551_set_adc_scan(sc, ADC_IBAT, true);
|
||
|
|
sc8551_set_adc_scan(sc, ADC_TBUS, true);
|
||
|
|
sc8551_set_adc_scan(sc, ADC_TBAT, true);
|
||
|
|
sc8551_set_adc_scan(sc, ADC_TDIE, true);
|
||
|
|
sc8551_set_adc_scan(sc, ADC_VAC, true);
|
||
|
|
|
||
|
|
sc8551_enable_adc(sc, false);
|
||
|
|
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int sc8551_init_int_src(struct sc8551 *sc)
|
||
|
|
{
|
||
|
|
int ret;
|
||
|
|
/*TODO:be careful ts bus and ts bat alarm bit mask is in
|
||
|
|
* fault mask register, so you need call
|
||
|
|
* sc8551_set_fault_int_mask for tsbus and tsbat alarm
|
||
|
|
*/
|
||
|
|
ret = sc8551_set_alarm_int_mask(sc, ADC_DONE
|
||
|
|
/* | BAT_UCP_ALARM */
|
||
|
|
| BAT_OVP_ALARM);
|
||
|
|
if (ret) {
|
||
|
|
pr_info("failed to set alarm mask:%d\n", ret);
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
#if defined(SC8551_CUSTOMER_SUPPORT)
|
||
|
|
ret = sc8551_set_fault_int_mask(sc, TS_BUS_FAULT);
|
||
|
|
if (ret) {
|
||
|
|
pr_info("failed to set fault mask:%d\n", ret);
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
#endif
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int sc8551_init_regulation(struct sc8551 *sc)
|
||
|
|
{
|
||
|
|
sc8551_set_ibat_reg_th(sc, 300);
|
||
|
|
sc8551_set_vbat_reg_th(sc, 100);
|
||
|
|
|
||
|
|
sc8551_set_vdrop_deglitch(sc, 5000);
|
||
|
|
sc8551_set_vdrop_th(sc, 400);
|
||
|
|
|
||
|
|
sc8551_enable_regulation(sc, false);
|
||
|
|
|
||
|
|
sc8551_write_byte(sc, SC8551_REG_2E, 0x08);
|
||
|
|
sc8551_update_bits(sc, SC8551_REG_34, 0x01, 0x01);
|
||
|
|
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int sc8551_init_device(struct sc8551 *sc)
|
||
|
|
{
|
||
|
|
sc8551_update_bits(sc, SC8551_REG_0B, 0x80, 0x80);
|
||
|
|
sc8551_enable_wdt(sc, false);
|
||
|
|
|
||
|
|
sc8551_set_ss_timeout(sc, 100000);
|
||
|
|
sc8551_set_sense_resistor(sc, sc->cfg->sense_r_mohm);
|
||
|
|
|
||
|
|
sc8551_init_protection(sc);
|
||
|
|
sc8551_init_adc(sc);
|
||
|
|
sc8551_init_int_src(sc);
|
||
|
|
|
||
|
|
sc8551_init_regulation(sc);
|
||
|
|
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
static int sc8551_set_present(struct sc8551 *sc, bool present)
|
||
|
|
{
|
||
|
|
sc->usb_present = present;
|
||
|
|
|
||
|
|
if (present)
|
||
|
|
sc8551_init_device(sc);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
#if defined(SC8551_CUSTOMER_SUPPORT)
|
||
|
|
static void sc8551_create_device_node(struct device *dev)
|
||
|
|
{
|
||
|
|
device_create_file(dev, &dev_attr_registers);
|
||
|
|
}
|
||
|
|
#endif
|
||
|
|
|
||
|
|
static enum power_supply_property sc8551_charger_props[] = {
|
||
|
|
POWER_SUPPLY_PROP_PRESENT,
|
||
|
|
POWER_SUPPLY_PROP_CHARGING_ENABLED,
|
||
|
|
POWER_SUPPLY_PROP_STATUS,
|
||
|
|
|
||
|
|
POWER_SUPPLY_PROP_SC_BATTERY_PRESENT,
|
||
|
|
POWER_SUPPLY_PROP_SC_VBUS_PRESENT,
|
||
|
|
POWER_SUPPLY_PROP_SC_BATTERY_VOLTAGE,
|
||
|
|
POWER_SUPPLY_PROP_SC_BATTERY_CURRENT,
|
||
|
|
POWER_SUPPLY_PROP_SC_BATTERY_TEMPERATURE,
|
||
|
|
POWER_SUPPLY_PROP_SC_BUS_VOLTAGE,
|
||
|
|
POWER_SUPPLY_PROP_SC_BUS_CURRENT,
|
||
|
|
POWER_SUPPLY_PROP_SC_BUS_TEMPERATURE,
|
||
|
|
POWER_SUPPLY_PROP_SC_DIE_TEMPERATURE,
|
||
|
|
POWER_SUPPLY_PROP_SC_ALARM_STATUS,
|
||
|
|
POWER_SUPPLY_PROP_SC_FAULT_STATUS,
|
||
|
|
POWER_SUPPLY_PROP_SC_VBUS_ERROR_STATUS,
|
||
|
|
|
||
|
|
};
|
||
|
|
|
||
|
|
static void sc8551_check_alarm_status(struct sc8551 *sc);
|
||
|
|
static void sc8551_check_fault_status(struct sc8551 *sc);
|
||
|
|
|
||
|
|
static int sc8551_charger_get_property(struct power_supply *psy,
|
||
|
|
enum power_supply_property psp,
|
||
|
|
union power_supply_propval *val)
|
||
|
|
{
|
||
|
|
struct sc8551 *sc = power_supply_get_drvdata(psy);
|
||
|
|
int result;
|
||
|
|
int ret;
|
||
|
|
u8 reg_val;
|
||
|
|
|
||
|
|
pr_debug(">>>>>psp = %d\n", psp);
|
||
|
|
|
||
|
|
switch (psp) {
|
||
|
|
case POWER_SUPPLY_PROP_CHARGING_ENABLED:
|
||
|
|
pr_debug("POWER_SUPPLY_PROP_CHARGING_ENABLED >>>>>psp = %d\n", psp);
|
||
|
|
sc8551_check_charge_enabled(sc, &sc->charge_enabled);
|
||
|
|
val->intval = sc->charge_enabled;
|
||
|
|
break;
|
||
|
|
case POWER_SUPPLY_PROP_STATUS:
|
||
|
|
pr_debug("POWER_SUPPLY_PROP_STATUS >>>>>psp = %d\n", psp);
|
||
|
|
val->intval = 0;
|
||
|
|
break;
|
||
|
|
case POWER_SUPPLY_PROP_PRESENT:
|
||
|
|
pr_debug("POWER_SUPPLY_PROP_PRESENT >>>>>psp = %d\n", psp);
|
||
|
|
val->intval = sc->usb_present;
|
||
|
|
break;
|
||
|
|
case POWER_SUPPLY_PROP_SC_BATTERY_PRESENT:
|
||
|
|
pr_debug("POWER_SUPPLY_PROP_SC_BATTERY_PRESENT >>>>>psp = %d\n", psp);
|
||
|
|
ret = sc8551_read_byte(sc, SC8551_REG_0D, ®_val);
|
||
|
|
if (!ret)
|
||
|
|
sc->batt_present = !!(reg_val & VBAT_INSERT);
|
||
|
|
val->intval = sc->batt_present;
|
||
|
|
break;
|
||
|
|
case POWER_SUPPLY_PROP_SC_VBUS_PRESENT:
|
||
|
|
pr_debug("POWER_SUPPLY_PROP_SC_VBUS_PRESENT >>>>>psp = %d\n", psp);
|
||
|
|
ret = sc8551_read_byte(sc, SC8551_REG_0D, ®_val);
|
||
|
|
if (!ret)
|
||
|
|
sc->vbus_present = !!(reg_val & VBUS_INSERT);
|
||
|
|
val->intval = sc->vbus_present;
|
||
|
|
break;
|
||
|
|
case POWER_SUPPLY_PROP_SC_BATTERY_VOLTAGE:
|
||
|
|
pr_debug("POWER_SUPPLY_PROP_SC_BATTERY_VOLTAGE >>>>>psp = %d\n", psp);
|
||
|
|
ret = sc8551_get_adc_data(sc, ADC_VOUT, &result);
|
||
|
|
if (!ret)
|
||
|
|
sc->vbat_volt = result;
|
||
|
|
|
||
|
|
val->intval = sc->vbat_volt;
|
||
|
|
break;
|
||
|
|
case POWER_SUPPLY_PROP_SC_BATTERY_CURRENT:
|
||
|
|
pr_debug("POWER_SUPPLY_PROP_SC_BATTERY_CURRENT >>>>>psp = %d\n", psp);
|
||
|
|
ret = sc8551_get_adc_data(sc, ADC_IBAT, &result);
|
||
|
|
if (!ret)
|
||
|
|
sc->ibat_curr = result;
|
||
|
|
|
||
|
|
val->intval = sc->ibat_curr;
|
||
|
|
break;
|
||
|
|
case POWER_SUPPLY_PROP_SC_BATTERY_TEMPERATURE:
|
||
|
|
pr_debug("POWER_SUPPLY_PROP_SC_BATTERY_TEMPERATURE >>>>>psp = %d\n", psp);
|
||
|
|
ret = sc8551_get_adc_data(sc, ADC_TBAT, &result);
|
||
|
|
if (!ret)
|
||
|
|
sc->bat_temp = result;
|
||
|
|
|
||
|
|
val->intval = sc->bat_temp;
|
||
|
|
break;
|
||
|
|
case POWER_SUPPLY_PROP_SC_BUS_VOLTAGE:
|
||
|
|
pr_debug("POWER_SUPPLY_PROP_SC_BUS_VOLTAGE >>>>>psp = %d\n", psp);
|
||
|
|
ret = sc8551_get_adc_data(sc, ADC_VBUS, &result);
|
||
|
|
if (!ret)
|
||
|
|
sc->vbus_volt = result;
|
||
|
|
|
||
|
|
val->intval = sc->vbus_volt;
|
||
|
|
break;
|
||
|
|
case POWER_SUPPLY_PROP_SC_BUS_CURRENT:
|
||
|
|
pr_debug("POWER_SUPPLY_PROP_SC_BUS_CURRENT >>>>>psp = %d\n", psp);
|
||
|
|
ret = sc8551_get_adc_data(sc, ADC_IBUS, &result);
|
||
|
|
if (!ret)
|
||
|
|
sc->ibus_curr = result;
|
||
|
|
|
||
|
|
val->intval = sc->ibus_curr;
|
||
|
|
break;
|
||
|
|
case POWER_SUPPLY_PROP_SC_BUS_TEMPERATURE:
|
||
|
|
pr_debug("POWER_SUPPLY_PROP_SC_BUS_TEMPERATURE >>>>>psp = %d\n", psp);
|
||
|
|
ret = sc8551_get_adc_data(sc, ADC_TBUS, &result);
|
||
|
|
if (!ret)
|
||
|
|
sc->bus_temp = result;
|
||
|
|
|
||
|
|
val->intval = sc->bus_temp;
|
||
|
|
|
||
|
|
break;
|
||
|
|
case POWER_SUPPLY_PROP_SC_DIE_TEMPERATURE:
|
||
|
|
pr_debug("POWER_SUPPLY_PROP_SC_DIE_TEMPERATURE >>>>>psp = %d\n", psp);
|
||
|
|
ret = sc8551_get_adc_data(sc, ADC_TDIE, &result);
|
||
|
|
if (!ret)
|
||
|
|
sc->die_temp = result;
|
||
|
|
|
||
|
|
val->intval = sc->die_temp;
|
||
|
|
break;
|
||
|
|
case POWER_SUPPLY_PROP_SC_ALARM_STATUS:
|
||
|
|
pr_debug("POWER_SUPPLY_PROP_SC_ALARM_STATUS >>>>>psp = %d\n", psp);
|
||
|
|
sc8551_check_alarm_status(sc);
|
||
|
|
val->intval = ((sc->bat_ovp_alarm << BAT_OVP_ALARM_SHIFT)
|
||
|
|
| (sc->bat_ocp_alarm << BAT_OCP_ALARM_SHIFT)
|
||
|
|
| (sc->bat_ucp_alarm << BAT_UCP_ALARM_SHIFT)
|
||
|
|
| (sc->bus_ovp_alarm << BUS_OVP_ALARM_SHIFT)
|
||
|
|
| (sc->bus_ocp_alarm << BUS_OCP_ALARM_SHIFT)
|
||
|
|
| (sc->bat_therm_alarm << BAT_THERM_ALARM_SHIFT)
|
||
|
|
| (sc->bus_therm_alarm << BUS_THERM_ALARM_SHIFT)
|
||
|
|
| (sc->die_therm_alarm << DIE_THERM_ALARM_SHIFT));
|
||
|
|
break;
|
||
|
|
|
||
|
|
case POWER_SUPPLY_PROP_SC_FAULT_STATUS:
|
||
|
|
pr_debug("POWER_SUPPLY_PROP_SC_FAULT_STATUS >>>>>psp = %d\n", psp);
|
||
|
|
sc8551_check_fault_status(sc);
|
||
|
|
val->intval = ((sc->bat_ovp_fault << BAT_OVP_FAULT_SHIFT)
|
||
|
|
| (sc->bat_ocp_fault << BAT_OCP_FAULT_SHIFT)
|
||
|
|
| (sc->bus_ovp_fault << BUS_OVP_FAULT_SHIFT)
|
||
|
|
| (sc->bus_ocp_fault << BUS_OCP_FAULT_SHIFT)
|
||
|
|
| (sc->bat_therm_fault << BAT_THERM_FAULT_SHIFT)
|
||
|
|
| (sc->bus_therm_fault << BUS_THERM_FAULT_SHIFT)
|
||
|
|
| (sc->die_therm_fault << DIE_THERM_FAULT_SHIFT));
|
||
|
|
break;
|
||
|
|
|
||
|
|
case POWER_SUPPLY_PROP_SC_VBUS_ERROR_STATUS:
|
||
|
|
pr_debug("POWER_SUPPLY_PROP_SC_VBUS_ERROR_STATUS >>>>>psp = %d\n", psp);
|
||
|
|
sc8551_check_vbus_error_status(sc);
|
||
|
|
val->intval = sc->vbus_error;
|
||
|
|
|
||
|
|
break;
|
||
|
|
default:
|
||
|
|
return -EINVAL;
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
static int sc8551_charger_set_property(struct power_supply *psy,
|
||
|
|
enum power_supply_property prop,
|
||
|
|
const union power_supply_propval *val)
|
||
|
|
{
|
||
|
|
struct sc8551 *sc = power_supply_get_drvdata(psy);
|
||
|
|
|
||
|
|
switch (prop) {
|
||
|
|
case POWER_SUPPLY_PROP_CHARGING_ENABLED:
|
||
|
|
pr_debug("POWER_SUPPLY_PROP_CHARGING_ENABLED >>>>>prop = %d\n", prop);
|
||
|
|
sc8551_enable_charge(sc, val->intval);
|
||
|
|
sc8551_check_charge_enabled(sc, &sc->charge_enabled);
|
||
|
|
pr_info("POWER_SUPPLY_PROP_CHARGING_ENABLED: %s\n",
|
||
|
|
val->intval ? "enable" : "disable");
|
||
|
|
break;
|
||
|
|
case POWER_SUPPLY_PROP_PRESENT:
|
||
|
|
pr_debug("POWER_SUPPLY_PROP_PRESENT >>>>>prop = %d\n", prop);
|
||
|
|
sc8551_set_present(sc, !!val->intval);
|
||
|
|
break;
|
||
|
|
default:
|
||
|
|
return -EINVAL;
|
||
|
|
}
|
||
|
|
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
static int sc8551_charger_is_writeable(struct power_supply *psy,
|
||
|
|
enum power_supply_property prop)
|
||
|
|
{
|
||
|
|
int ret;
|
||
|
|
|
||
|
|
switch (prop) {
|
||
|
|
case POWER_SUPPLY_PROP_CHARGING_ENABLED:
|
||
|
|
ret = 1;
|
||
|
|
break;
|
||
|
|
default:
|
||
|
|
ret = 0;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int sc8551_psy_register(struct sc8551 *sc)
|
||
|
|
{
|
||
|
|
int ret;
|
||
|
|
|
||
|
|
sc->psy_cfg.drv_data = sc;
|
||
|
|
sc->psy_cfg.of_node = sc->dev->of_node;
|
||
|
|
|
||
|
|
|
||
|
|
if (sc->mode == SC8551_ROLE_MASTER)
|
||
|
|
sc->psy_desc.name = "sc8551-master";
|
||
|
|
else if (sc->mode == SC8551_ROLE_SLAVE)
|
||
|
|
sc->psy_desc.name = "sc8551-slave";
|
||
|
|
else
|
||
|
|
sc->psy_desc.name = "sc8551-standalone";
|
||
|
|
|
||
|
|
sc->psy_desc.type = POWER_SUPPLY_TYPE_MAINS;
|
||
|
|
sc->psy_desc.properties = sc8551_charger_props;
|
||
|
|
sc->psy_desc.num_properties = ARRAY_SIZE(sc8551_charger_props);
|
||
|
|
sc->psy_desc.get_property = sc8551_charger_get_property;
|
||
|
|
sc->psy_desc.set_property = sc8551_charger_set_property;
|
||
|
|
sc->psy_desc.property_is_writeable = sc8551_charger_is_writeable;
|
||
|
|
|
||
|
|
|
||
|
|
sc->fc2_psy = devm_power_supply_register(sc->dev,
|
||
|
|
&sc->psy_desc, &sc->psy_cfg);
|
||
|
|
if (IS_ERR(sc->fc2_psy)) {
|
||
|
|
pr_info("failed to register fc2_psy:%d\n", ret);
|
||
|
|
return PTR_ERR(sc->fc2_psy);
|
||
|
|
}
|
||
|
|
|
||
|
|
pr_info("%s power supply register successfully\n", sc->psy_desc.name);
|
||
|
|
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
static irqreturn_t sc8551_charger_interrupt(int irq, void *dev_id);
|
||
|
|
static int sc8551_init_irq(struct sc8551 *sc)
|
||
|
|
{
|
||
|
|
int ret;
|
||
|
|
|
||
|
|
gpio_free(sc->irq_gpio);
|
||
|
|
|
||
|
|
pr_info(">>>>>>>>>>>>%d\n", sc->irq_gpio);
|
||
|
|
ret = gpio_request(sc->irq_gpio, "sc8551");
|
||
|
|
if (ret < 0) {
|
||
|
|
pr_info("fail to request GPIO(%d) %d\n", sc->irq_gpio, ret);
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
ret = gpio_direction_input(sc->irq_gpio);
|
||
|
|
if (ret < 0) {
|
||
|
|
pr_info("fail to set GPIO%d as input pin(%d)\n", sc->irq_gpio, ret);
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
sc->irq = gpio_to_irq(sc->irq_gpio);
|
||
|
|
if (sc->irq <= 0) {
|
||
|
|
pr_info("irq mapping fail\n");
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
pr_info("irq : %d\n", sc->irq);
|
||
|
|
|
||
|
|
if (sc->mode == SC8551_ROLE_MASTER) {
|
||
|
|
ret = devm_request_threaded_irq(sc->dev, sc->irq,
|
||
|
|
NULL, sc8551_charger_interrupt,
|
||
|
|
IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
|
||
|
|
"sc8551 master irq", sc);
|
||
|
|
if (ret < 0) {
|
||
|
|
pr_info("request irq for irq=%d failed, ret =%d\n",
|
||
|
|
sc->irq, ret);
|
||
|
|
}
|
||
|
|
} else if (sc->mode == SC8551_ROLE_SLAVE) {
|
||
|
|
ret = devm_request_threaded_irq(sc->dev, sc->irq,
|
||
|
|
NULL, sc8551_charger_interrupt,
|
||
|
|
IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
|
||
|
|
"sc8551 slave irq", sc);
|
||
|
|
if (ret < 0) {
|
||
|
|
pr_info("request irq for isrq=%d failed, ret =%d\n",
|
||
|
|
sc->irq, ret);
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
ret = devm_request_threaded_irq(sc->dev, sc->irq,
|
||
|
|
NULL, sc8551_charger_interrupt,
|
||
|
|
IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
|
||
|
|
"sc8551 standalone irq", sc);
|
||
|
|
if (ret < 0) {
|
||
|
|
pr_info("request irq for irq=%d failed, ret =%d\n",
|
||
|
|
sc->irq, ret);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
enable_irq_wake(sc->irq);
|
||
|
|
|
||
|
|
device_init_wakeup(sc->dev, 1);
|
||
|
|
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void sc8551_check_alarm_status(struct sc8551 *sc)
|
||
|
|
{
|
||
|
|
int ret;
|
||
|
|
u8 flag = 0;
|
||
|
|
u8 stat = 0;
|
||
|
|
|
||
|
|
mutex_lock(&sc->data_lock);
|
||
|
|
|
||
|
|
ret = sc8551_read_byte(sc, SC8551_REG_08, &flag);
|
||
|
|
if (!ret && (flag & SC8551_IBUS_UCP_FALL_FLAG_MASK))
|
||
|
|
pr_debug("UCP_FLAG =0x%02X\n",
|
||
|
|
!!(flag & SC8551_IBUS_UCP_FALL_FLAG_MASK));
|
||
|
|
|
||
|
|
ret = sc8551_read_byte(sc, SC8551_REG_2D, &flag);
|
||
|
|
if (!ret && (flag & SC8551_VDROP_OVP_FLAG_MASK))
|
||
|
|
pr_debug("VDROP_OVP_FLAG =0x%02X\n",
|
||
|
|
!!(flag & SC8551_VDROP_OVP_FLAG_MASK));
|
||
|
|
|
||
|
|
/*read to clear alarm flag*/
|
||
|
|
ret = sc8551_read_byte(sc, SC8551_REG_0E, &flag);
|
||
|
|
if (!ret && flag)
|
||
|
|
pr_debug("INT_FLAG =0x%02X\n", flag);
|
||
|
|
|
||
|
|
ret = sc8551_read_byte(sc, SC8551_REG_0D, &stat);
|
||
|
|
if (!ret && stat != sc->prev_alarm) {
|
||
|
|
pr_debug("INT_STAT = 0X%02x\n", stat);
|
||
|
|
sc->prev_alarm = stat;
|
||
|
|
sc->bat_ovp_alarm = !!(stat & BAT_OVP_ALARM);
|
||
|
|
sc->bat_ocp_alarm = !!(stat & BAT_OCP_ALARM);
|
||
|
|
sc->bus_ovp_alarm = !!(stat & BUS_OVP_ALARM);
|
||
|
|
sc->bus_ocp_alarm = !!(stat & BUS_OCP_ALARM);
|
||
|
|
sc->batt_present = !!(stat & VBAT_INSERT);
|
||
|
|
sc->vbus_present = !!(stat & VBUS_INSERT);
|
||
|
|
sc->bat_ucp_alarm = !!(stat & BAT_UCP_ALARM);
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
ret = sc8551_read_byte(sc, SC8551_REG_08, &stat);
|
||
|
|
if (!ret && (stat & 0x50))
|
||
|
|
pr_info("Reg[05]BUS_UCPOVP = 0x%02X\n", stat);
|
||
|
|
|
||
|
|
ret = sc8551_read_byte(sc, SC8551_REG_0A, &stat);
|
||
|
|
if (!ret && (stat & 0x02))
|
||
|
|
pr_info("Reg[0A]CONV_OCP = 0x%02X\n", stat);
|
||
|
|
|
||
|
|
mutex_unlock(&sc->data_lock);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void sc8551_check_fault_status(struct sc8551 *sc)
|
||
|
|
{
|
||
|
|
int ret;
|
||
|
|
u8 flag = 0;
|
||
|
|
u8 stat = 0;
|
||
|
|
bool changed = false;
|
||
|
|
|
||
|
|
mutex_lock(&sc->data_lock);
|
||
|
|
|
||
|
|
ret = sc8551_read_byte(sc, SC8551_REG_10, &stat);
|
||
|
|
if (!ret && stat)
|
||
|
|
pr_info("FAULT_STAT = 0x%02X\n", stat);
|
||
|
|
|
||
|
|
ret = sc8551_read_byte(sc, SC8551_REG_11, &flag);
|
||
|
|
if (!ret && flag)
|
||
|
|
pr_info("FAULT_FLAG = 0x%02X\n", flag);
|
||
|
|
|
||
|
|
if (!ret && flag != sc->prev_fault) {
|
||
|
|
changed = true;
|
||
|
|
sc->prev_fault = flag;
|
||
|
|
sc->bat_ovp_fault = !!(flag & BAT_OVP_FAULT);
|
||
|
|
sc->bat_ocp_fault = !!(flag & BAT_OCP_FAULT);
|
||
|
|
sc->bus_ovp_fault = !!(flag & BUS_OVP_FAULT);
|
||
|
|
sc->bus_ocp_fault = !!(flag & BUS_OCP_FAULT);
|
||
|
|
sc->bat_therm_fault = !!(flag & TS_BAT_FAULT);
|
||
|
|
sc->bus_therm_fault = !!(flag & TS_BUS_FAULT);
|
||
|
|
|
||
|
|
sc->bat_therm_alarm = !!(flag & TBUS_TBAT_ALARM);
|
||
|
|
sc->bus_therm_alarm = !!(flag & TBUS_TBAT_ALARM);
|
||
|
|
}
|
||
|
|
|
||
|
|
mutex_unlock(&sc->data_lock);
|
||
|
|
}
|
||
|
|
|
||
|
|
#if defined(SC8551_CUSTOMER_SUPPORT)
|
||
|
|
static int sc8551_check_reg_status(struct sc8551 *sc)
|
||
|
|
{
|
||
|
|
int ret;
|
||
|
|
u8 val;
|
||
|
|
|
||
|
|
ret = sc8551_read_byte(sc, SC8551_REG_2C, &val);
|
||
|
|
if (!ret) {
|
||
|
|
sc->vbat_reg = !!(val & SC8551_VBAT_REG_ACTIVE_STAT_MASK);
|
||
|
|
sc->ibat_reg = !!(val & SC8551_IBAT_REG_ACTIVE_STAT_MASK);
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
#endif
|
||
|
|
|
||
|
|
/*
|
||
|
|
* interrupt does nothing, just info event chagne, other module could get info
|
||
|
|
* through power supply interface
|
||
|
|
*/
|
||
|
|
static irqreturn_t sc8551_charger_interrupt(int irq, void *dev_id)
|
||
|
|
{
|
||
|
|
#if defined(SC8551_CUSTOMER_SUPPORT)
|
||
|
|
struct sc8551 *sc = dev_id;
|
||
|
|
|
||
|
|
pr_debug("INT OCCURRED\n");
|
||
|
|
|
||
|
|
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 defined(SC8551_CUSTOMER_SUPPORT)
|
||
|
|
/* TODO */
|
||
|
|
sc8551_check_alarm_status(sc);
|
||
|
|
sc8551_check_fault_status(sc);
|
||
|
|
#endif
|
||
|
|
|
||
|
|
#if defined(SC8551_CUSTOMER_SUPPORT)
|
||
|
|
sc8551_dump_reg(sc);
|
||
|
|
#endif
|
||
|
|
mutex_unlock(&sc->irq_complete);
|
||
|
|
|
||
|
|
power_supply_changed(sc->fc2_psy);
|
||
|
|
#endif
|
||
|
|
|
||
|
|
return IRQ_HANDLED;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void determine_initial_status(struct sc8551 *sc)
|
||
|
|
{
|
||
|
|
if (sc->client->irq)
|
||
|
|
sc8551_charger_interrupt(sc->client->irq, sc);
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
static const struct of_device_id sc8551_charger_match_table[] = {
|
||
|
|
{
|
||
|
|
.compatible = "sc,sc8551-standalone",
|
||
|
|
.data = &sc8551_mode_data[SC8551_STDALONE],
|
||
|
|
},
|
||
|
|
{
|
||
|
|
.compatible = "sc,sc8551-master",
|
||
|
|
.data = &sc8551_mode_data[SC8551_MASTER],
|
||
|
|
},
|
||
|
|
|
||
|
|
{
|
||
|
|
.compatible = "sc,sc8551-slave",
|
||
|
|
.data = &sc8551_mode_data[SC8551_SLAVE],
|
||
|
|
},
|
||
|
|
{},
|
||
|
|
};
|
||
|
|
|
||
|
|
static int sc8551_charger_probe(struct i2c_client *client,
|
||
|
|
const struct i2c_device_id *id)
|
||
|
|
{
|
||
|
|
struct sc8551 *sc;
|
||
|
|
const struct of_device_id *match;
|
||
|
|
struct device_node *node = client->dev.of_node;
|
||
|
|
int ret;
|
||
|
|
|
||
|
|
sc = devm_kzalloc(&client->dev, sizeof(struct sc8551), 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->charging_disable_lock);
|
||
|
|
mutex_init(&sc->irq_complete);
|
||
|
|
|
||
|
|
sc->resume_completed = true;
|
||
|
|
sc->irq_waiting = false;
|
||
|
|
sc->is_sc8551 = true;
|
||
|
|
|
||
|
|
ret = sc8551_detect_device(sc);
|
||
|
|
if (ret) {
|
||
|
|
pr_info("No sc8551 device found!\n");
|
||
|
|
return -ENODEV;
|
||
|
|
}
|
||
|
|
i2c_set_clientdata(client, sc);
|
||
|
|
#if defined(SC8551_CUSTOMER_SUPPORT)
|
||
|
|
sc8551_create_device_node(&(client->dev));
|
||
|
|
#endif
|
||
|
|
match = of_match_node(sc8551_charger_match_table, node);
|
||
|
|
if (match == NULL) {
|
||
|
|
pr_info("device tree match not found!\n");
|
||
|
|
return -ENODEV;
|
||
|
|
}
|
||
|
|
|
||
|
|
sc8551_get_work_mode(sc, &sc->mode);
|
||
|
|
|
||
|
|
if (sc->mode != *(int *)match->data) {
|
||
|
|
pr_info("device operation mode mismatch with dts configuration\n");
|
||
|
|
return -EINVAL;
|
||
|
|
}
|
||
|
|
|
||
|
|
ret = sc8551_parse_dt(sc, &client->dev);
|
||
|
|
if (ret)
|
||
|
|
return -EIO;
|
||
|
|
|
||
|
|
ret = sc8551_init_device(sc);
|
||
|
|
if (ret) {
|
||
|
|
pr_info("Failed to init device\n");
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
#if defined(SC8551_CUSTOMER_SUPPORT)
|
||
|
|
ret = sc8551_psy_register(sc);
|
||
|
|
if (ret)
|
||
|
|
goto err_1;
|
||
|
|
|
||
|
|
ret = sc8551_init_irq(sc);
|
||
|
|
if (ret)
|
||
|
|
goto err_1;
|
||
|
|
|
||
|
|
determine_initial_status(sc);
|
||
|
|
#endif
|
||
|
|
pr_info("sc8551 probe successfully, Part Num:%d\n!",
|
||
|
|
sc->part_no);
|
||
|
|
|
||
|
|
return 0;
|
||
|
|
|
||
|
|
err_1:
|
||
|
|
power_supply_unregister(sc->fc2_psy);
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
static inline bool is_device_suspended(struct sc8551 *sc)
|
||
|
|
{
|
||
|
|
return !sc->resume_completed;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int sc8551_suspend(struct device *dev)
|
||
|
|
{
|
||
|
|
struct i2c_client *client = to_i2c_client(dev);
|
||
|
|
struct sc8551 *sc = i2c_get_clientdata(client);
|
||
|
|
|
||
|
|
mutex_lock(&sc->irq_complete);
|
||
|
|
sc->resume_completed = false;
|
||
|
|
mutex_unlock(&sc->irq_complete);
|
||
|
|
pr_info("Suspend successfully!");
|
||
|
|
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int sc8551_suspend_noirq(struct device *dev)
|
||
|
|
{
|
||
|
|
struct i2c_client *client = to_i2c_client(dev);
|
||
|
|
struct sc8551 *sc = i2c_get_clientdata(client);
|
||
|
|
|
||
|
|
if (sc->irq_waiting) {
|
||
|
|
pr_info_ratelimited("Aborting suspend, an interrupt was detected while suspending\n");
|
||
|
|
return -EBUSY;
|
||
|
|
}
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int sc8551_resume(struct device *dev)
|
||
|
|
{
|
||
|
|
struct i2c_client *client = to_i2c_client(dev);
|
||
|
|
struct sc8551 *sc = i2c_get_clientdata(client);
|
||
|
|
|
||
|
|
|
||
|
|
mutex_lock(&sc->irq_complete);
|
||
|
|
sc->resume_completed = true;
|
||
|
|
if (sc->irq_waiting) {
|
||
|
|
sc->irq_disabled = false;
|
||
|
|
enable_irq(client->irq);
|
||
|
|
mutex_unlock(&sc->irq_complete);
|
||
|
|
sc8551_charger_interrupt(client->irq, sc);
|
||
|
|
} else {
|
||
|
|
mutex_unlock(&sc->irq_complete);
|
||
|
|
}
|
||
|
|
|
||
|
|
//power_supply_changed(sc->fc2_psy);
|
||
|
|
pr_info("Resume successfully!");
|
||
|
|
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
static int sc8551_charger_remove(struct i2c_client *client)
|
||
|
|
{
|
||
|
|
struct sc8551 *sc = i2c_get_clientdata(client);
|
||
|
|
|
||
|
|
pr_debug("%s:enter!\n", __func__);
|
||
|
|
sc8551_enable_adc(sc, false);
|
||
|
|
|
||
|
|
power_supply_unregister(sc->fc2_psy);
|
||
|
|
|
||
|
|
mutex_destroy(&sc->charging_disable_lock);
|
||
|
|
mutex_destroy(&sc->data_lock);
|
||
|
|
mutex_destroy(&sc->i2c_rw_lock);
|
||
|
|
mutex_destroy(&sc->irq_complete);
|
||
|
|
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
static void sc8551_charger_shutdown(struct i2c_client *client)
|
||
|
|
{
|
||
|
|
struct sc8551 *sc = i2c_get_clientdata(client);
|
||
|
|
|
||
|
|
pr_debug("%s:enter!\n", __func__);
|
||
|
|
|
||
|
|
sc8551_enable_adc(sc, false);
|
||
|
|
}
|
||
|
|
|
||
|
|
static const struct dev_pm_ops sc8551_pm_ops = {
|
||
|
|
.resume = sc8551_resume,
|
||
|
|
.suspend_noirq = sc8551_suspend_noirq,
|
||
|
|
.suspend = sc8551_suspend,
|
||
|
|
};
|
||
|
|
|
||
|
|
static const struct i2c_device_id sc8551_charger_id[] = {
|
||
|
|
{"sc8551-standalone", SC8551_ROLE_STDALONE},
|
||
|
|
{},
|
||
|
|
};
|
||
|
|
|
||
|
|
static struct i2c_driver sc8551_charger_driver = {
|
||
|
|
.driver = {
|
||
|
|
.name = "sc8551-charger",
|
||
|
|
.owner = THIS_MODULE,
|
||
|
|
.of_match_table = sc8551_charger_match_table,
|
||
|
|
.pm = &sc8551_pm_ops,
|
||
|
|
},
|
||
|
|
.id_table = sc8551_charger_id,
|
||
|
|
|
||
|
|
.probe = sc8551_charger_probe,
|
||
|
|
.remove = sc8551_charger_remove,
|
||
|
|
.shutdown = sc8551_charger_shutdown,
|
||
|
|
};
|
||
|
|
|
||
|
|
module_i2c_driver(sc8551_charger_driver);
|
||
|
|
|
||
|
|
MODULE_DESCRIPTION("SC SC8551 Charge Pump Driver");
|
||
|
|
MODULE_LICENSE("GPL v2");
|
||
|
|
MODULE_AUTHOR("Aiden-yu@southchip.com");
|
||
|
|
|