kernel-brax3-ubuntu-touch/drivers/power/supply/sc858x_charger.c
erascape f319b992b1 kernel-5.15: Initial import brax3 UT kernel
* halium configs enabled

Signed-off-by: erascape <erascape@proton.me>
2025-09-23 15:17:10 +00:00

1710 lines
46 KiB
C
Executable file

// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2022 Southchip Semiconductor Technology(Shanghai) Co., Ltd.
*/
#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 <linux/regmap.h>
#define CONFIG_MTK_CLASS
#define CONFIG_MTK_CHARGER_V5P10
#ifdef CONFIG_MTK_CLASS
#include "charger_class.h"
#ifdef CONFIG_MTK_CHARGER_V4P19
#include "mtk_charger_intf.h"
#endif /*CONFIG_MTK_CHARGER_V4P19*/
#endif /*CONFIG_MTK_CLASS*/
#ifdef CONFIG_SOUTHCHIP_DVCHG_CLASS
#include "dvchg_class.h"
#endif /*CONFIG_SOUTHCHIP_DVCHG_CLASS*/
#define SC858X_DRV_VERSION "1.1.0_G"
enum {
SC858X_STANDALONG = 0,
SC858X_MASTER,
SC858X_SLAVE,
};
static const char* sc858x_psy_name[] = {
[SC858X_STANDALONG] = "sc-cp-standalone",
[SC858X_MASTER] = "sc-cp-master",
[SC858X_SLAVE] = "sc-cp-slave",
};
static const char* sc858x_irq_name[] = {
[SC858X_STANDALONG] = "sc858x-standalone-irq",
[SC858X_MASTER] = "sc858x-master-irq",
[SC858X_SLAVE] = "sc858x-slave-irq",
};
static int sc858x_mode_data[] = {
[SC858X_STANDALONG] = SC858X_STANDALONG,
[SC858X_MASTER] = SC858X_MASTER,
[SC858X_SLAVE] = SC858X_SLAVE,
};
enum {
ADC_IBUS,
ADC_VBUS,
ADC_VUSB,
ADC_VWPC,
ADC_VOUT,
ADC_VBAT,
ADC_IBAT,
RESERVED,
ADC_TDIE,
ADC_MAX_NUM,
}SC_858X_ADC_CH;
static const u32 sc858x_adc_accuracy_tbl[ADC_MAX_NUM] = {
150000, /* IBUS */
35000, /* VBUS */
35000, /* VUSB */
35000, /* VWPC */
20000, /* VOUT */
20000, /* VBAT */
200000, /* IBAT */
0, /* RESERVED */
4, /* TDIE */
};
static const int sc858x_adc_m[] =
{15625, 625, 625, 625, 125, 125, 375, 10, 5};
static const int sc858x_adc_l[] =
{10000, 100, 100, 100, 100, 100, 100, 10, 10};
enum sc858x_notify {
SC858X_NOTIFY_OTHER = 0,
SC858X_NOTIFY_IBUSOCP,
SC858X_NOTIFY_VBUSOVP,
SC858X_NOTIFY_IBATOCP,
SC858X_NOTIFY_VBATOVP,
SC858X_NOTIFY_VOUTOVP,
};
enum sc858x_error_stata {
ERROR_VBUS_HIGH = 0,
ERROR_VBUS_LOW,
ERROR_VBUS_OVP,
ERROR_IBUS_OCP,
ERROR_VBAT_OVP,
ERROR_IBAT_OCP,
};
struct flag_bit {
int notify;
int mask;
char *name;
};
struct intr_flag {
int reg;
int len;
struct flag_bit bit[8];
};
static struct intr_flag cp_intr_flag[] = {
{ .reg = 0x01, .len = 1, .bit = {
{.mask = BIT(5), .name = "vbat ovp flag", .notify = SC858X_NOTIFY_VBATOVP},
},
},
{ .reg = 0x02, .len = 1, .bit = {
{.mask = BIT(4), .name = "ibat ocp flag", .notify = SC858X_NOTIFY_IBATOCP},
},
},
{ .reg = 0x03, .len = 1, .bit = {
{.mask = BIT(5), .name = "vusb ovp flag", .notify = SC858X_NOTIFY_OTHER},
},
},
{ .reg = 0x04, .len = 1, .bit = {
{.mask = BIT(5), .name = "vwpc ovp flag", .notify = SC858X_NOTIFY_OTHER},
},
},
{ .reg = 0x06, .len = 1, .bit = {
{.mask = BIT(5), .name = "ibus ocp flag", .notify = SC858X_NOTIFY_IBUSOCP},
},
},
{ .reg = 0x07, .len = 2, .bit = {
{.mask = BIT(2), .name = "ibus ucp rise flag", .notify = SC858X_NOTIFY_OTHER},
{.mask = BIT(0), .name = "ibus ucp fall flag", .notify = SC858X_NOTIFY_OTHER},
},
},
{ .reg = 0x08, .len = 1, .bit = {
{.mask = BIT(3), .name = "pmid2out ovp flag", .notify = SC858X_NOTIFY_OTHER},
},
},
{ .reg = 0x09, .len = 1, .bit = {
{.mask = BIT(3), .name = "pmid2out uvp flag", .notify = SC858X_NOTIFY_OTHER},
},
},
{ .reg = 0x0a, .len = 2, .bit = {
{.mask = BIT(7), .name = "por flag", .notify = SC858X_NOTIFY_OTHER},
{.mask = BIT(0), .name = "pin diag fall flag", .notify = SC858X_NOTIFY_OTHER},
},
},
{ .reg = 0x11, .len = 7, .bit = {
{.mask = BIT(0), .name = "vusb insert flag", .notify = SC858X_NOTIFY_OTHER},
{.mask = BIT(1), .name = "vwpc insert flag", .notify = SC858X_NOTIFY_OTHER},
{.mask = BIT(2), .name = "vbus present flag", .notify = SC858X_NOTIFY_OTHER},
{.mask = BIT(3), .name = "vout insert flag", .notify = SC858X_NOTIFY_OTHER},
{.mask = BIT(4), .name = "vout ok chg flag", .notify = SC858X_NOTIFY_OTHER},
{.mask = BIT(5), .name = "vout ok rev flag", .notify = SC858X_NOTIFY_OTHER},
{.mask = BIT(6), .name = "vout ok sw regn flag", .notify = SC858X_NOTIFY_OTHER},
},
},
{ .reg = 0x13, .len = 7, .bit = {
{.mask = BIT(0), .name = "vout ovp flag", .notify = SC858X_NOTIFY_OTHER},
{.mask = BIT(1), .name = "vbus ovp flag", .notify = SC858X_NOTIFY_OTHER},
{.mask = BIT(2), .name = "ss fail flag", .notify = SC858X_NOTIFY_OTHER},
{.mask = BIT(3), .name = "conv ocp flag", .notify = SC858X_NOTIFY_OTHER},
{.mask = BIT(4), .name = "wd timeout flag", .notify = SC858X_NOTIFY_OTHER},
{.mask = BIT(5), .name = "ss timeout flag", .notify = SC858X_NOTIFY_OTHER},
{.mask = BIT(6), .name = "tshut flag", .notify = SC858X_NOTIFY_OTHER},
},
},
};
/************************************************************************/
#define SC858X_DEVICE_ID 0x81
#define SC8566_DEVICE_ID 0x2D
#define SC858X_REG17 0x17
#define SC858X_REGMAX 0x7F
#define SC858X_REG7C 0x7C
#define SC858X_PRIVATE_CODE 0x01
enum sc858x_reg_range {
SC858X_VBAT_OVP,
SC858X_IBAT_OCP,
SC858X_VBUS_OVP,
SC858X_IBUS_OCP,
};
struct reg_range {
u32 min;
u32 max;
u32 step;
u32 offset;
const u32 *table;
u16 num_table;
bool round_up;
};
#define SC858X_CHG_RANGE(_min, _max, _step, _offset, _ru) \
{ \
.min = _min, \
.max = _max, \
.step = _step, \
.offset = _offset, \
.round_up = _ru, \
}
#define SC858X_CHG_RANGE_T(_table, _ru) \
{ .table = _table, .num_table = ARRAY_SIZE(_table), .round_up = _ru, }
static const struct reg_range sc858x_reg_range[] = {
[SC858X_VBAT_OVP] = SC858X_CHG_RANGE(4450, 5225, 25, 4450, false),
[SC858X_IBAT_OCP] = SC858X_CHG_RANGE(8000, 15500, 500, 8000, false),
[SC858X_VBUS_OVP] = SC858X_CHG_RANGE(7000, 13300, 100, 7000, false),
[SC858X_IBUS_OCP] = SC858X_CHG_RANGE(2500, 6375, 125, 2500, false),
};
enum sc858x_fields {
DEVICE_VER,
VBAT_OVP_DIS, VBAT_OVP,
IBAT_OCP_DIS, IBAT_OCP,
VUSB_OVP,
VWPC_OVP,
VBUS_OVP, VOUT_OVP,
IBUS_OCP_DIS, IBUS_OCP,
IBUS_UCP_DIS, IBUS_UCP_FALL_DG_SET,
PMID2OUT_OVP_DIS, PMID2OUT_OVP,
PMID2OUT_UVP_DIS, PMID2OUT_UVP,
CP_SWITCHING_STAT,VBUS_ERRORHI_STAT,VBUS_ERRORLO_STAT, PIN_DIAG_FAIL,
CP_EN, QB_EN, ACDRV_MANUAL_EN, WPCGATE_EN, OVPGATE_EN, VBUS_PD_EN, VWPC_PD_EN, VUSB_PD_EN,
FSW_SET, FREQ_DITHER, ACDRV_HI_EN,
VBUS_INRANGE_DET_DIS, SS_TIMEOUT, WD_TIMEOUT,
VBAT_OVP_DG_SET, SET_IBAT_SNS_RES, REG_RST, MODE,
TSHUT_DIS, VWPC_OVP_DIS, VUSB_OVP_DIS, VBUS_OVP_DIS, VOUT_OVP_DIS,
ADC_EN, ADC_RATE,
DEVICE_ID,
F_MAX_FIELDS,
};
//REGISTER
static const struct reg_field sc858x_reg_fields[] = {
/*reg00*/
[DEVICE_VER] = REG_FIELD(0x00, 0, 7),
/*reg01*/
[VBAT_OVP_DIS] = REG_FIELD(0x01, 7, 7),
[VBAT_OVP] = REG_FIELD(0x01, 0, 4),
/*reg02*/
[IBAT_OCP_DIS] = REG_FIELD(0x02, 7, 7),
[IBAT_OCP] = REG_FIELD(0x02, 0, 3),
/*reg03*/
[VUSB_OVP] = REG_FIELD(0x03, 0, 3),
/*reg04*/
[VWPC_OVP] = REG_FIELD(0x04, 0, 3),
/*reg05*/
[VBUS_OVP] = REG_FIELD(0x05, 2, 7),
[VOUT_OVP] = REG_FIELD(0x05, 0, 1),
/*reg06*/
[IBUS_OCP_DIS] = REG_FIELD(0x06, 7, 7),
[IBUS_OCP] = REG_FIELD(0x06, 0, 4),
/*reg07*/
[IBUS_UCP_DIS] = REG_FIELD(0x07, 7, 7),
[IBUS_UCP_FALL_DG_SET] = REG_FIELD(0x07, 4, 5),
/*reg08*/
[PMID2OUT_OVP_DIS] = REG_FIELD(0x08, 7, 7),
[PMID2OUT_OVP] = REG_FIELD(0x08, 0, 2),
/*reg09*/
[PMID2OUT_UVP_DIS] = REG_FIELD(0x09, 7, 7),
[PMID2OUT_UVP] = REG_FIELD(0x09, 0, 2),
/*reg0a*/
[VBUS_ERRORLO_STAT] = REG_FIELD(0x0a, 4, 4),
[VBUS_ERRORHI_STAT] = REG_FIELD(0x0a, 3, 3),
[CP_SWITCHING_STAT] = REG_FIELD(0x0a, 1, 1),
[PIN_DIAG_FAIL] = REG_FIELD(0x0a, 0, 0),
/*reg0b*/
[CP_EN] = REG_FIELD(0x0b, 7, 7),
[QB_EN] = REG_FIELD(0x0b, 6, 6),
[ACDRV_MANUAL_EN] = REG_FIELD(0x0b, 5, 5),
[WPCGATE_EN] = REG_FIELD(0x0b, 4, 4),
[OVPGATE_EN] = REG_FIELD(0x0b, 3, 3),
[VBUS_PD_EN] = REG_FIELD(0x0b, 2, 2),
[VWPC_PD_EN] = REG_FIELD(0x0b, 1, 1),
[VUSB_PD_EN] = REG_FIELD(0x0b, 0, 0),
/*reg0c*/
[FSW_SET] = REG_FIELD(0x0c, 3, 7),
[FREQ_DITHER] = REG_FIELD(0x0c, 1, 1),
[ACDRV_HI_EN] = REG_FIELD(0x0c, 0, 0),
/*reg0d*/
[VBUS_INRANGE_DET_DIS] = REG_FIELD(0x0d, 7, 7),
[SS_TIMEOUT] = REG_FIELD(0x0d, 3, 5),
[WD_TIMEOUT] = REG_FIELD(0x0d, 0, 2),
/*reg0e*/
[VBAT_OVP_DG_SET] = REG_FIELD(0x0e, 5, 5),
[SET_IBAT_SNS_RES] = REG_FIELD(0x0e, 4, 4),
[REG_RST] = REG_FIELD(0x0e, 3, 3),
[MODE] = REG_FIELD(0x0e, 0, 2),
/*reg0f*/
[TSHUT_DIS] = REG_FIELD(0x0f, 4, 4),
[VWPC_OVP_DIS] = REG_FIELD(0x0f, 3, 3),
[VUSB_OVP_DIS] = REG_FIELD(0x0f, 2, 2),
[VBUS_OVP_DIS] = REG_FIELD(0x0f, 1, 1),
[VOUT_OVP_DIS] = REG_FIELD(0x0f, 0, 0),
/*reg15*/
[ADC_EN] = REG_FIELD(0x15, 7, 7),
[ADC_RATE] = REG_FIELD(0x15, 6, 6),
/*reg6e*/
[DEVICE_ID] = REG_FIELD(0x6e, 0, 7),
};
static const struct regmap_config sc858x_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
.max_register = SC858X_REGMAX,
};
/************************************************************************/
struct sc858x_cfg_e {
int vbat_ovp_dis;
int vbat_ovp;
int ibat_ocp_dis;
int ibat_ocp;
int vusb_ovp_dis;
int vusb_ovp;
int vwpc_ovp_dis;
int vwpc_ovp;
int vbus_ovp_dis;
int vbus_ovp;
int vout_ovp_dis;
int vout_ovp;
int ibus_ocp_dis;
int ibus_ocp;
int ibus_ucp_fall_dis;
int ibus_ucp_fall;
int pmid2out_uvp_dis;
int pmid2out_uvp;
int pmid2out_ovp_dis;
int pmid2out_ovp;
int fsw_set;
int ss_timeout;
int wd_timeout;
int ibat_sns_r;
int mode;
int tshut_dis;
};
struct sc858x_chip {
struct device *dev;
struct i2c_client *client;
struct regmap *regmap;
struct regmap_field *rmap_fields[F_MAX_FIELDS];
struct sc858x_cfg_e cfg;
int irq_gpio;
int irq;
int mode;
bool charge_enabled;
int usb_present;
int vbus_volt;
int ibus_curr;
int vbat_volt;
int ibat_curr;
int die_temp;
#ifdef CONFIG_MTK_CLASS
struct charger_device *chg_dev;
#endif /*CONFIG_MTK_CLASS*/
#ifdef CONFIG_SOUTHCHIP_DVCHG_CLASS
struct dvchg_dev *charger_pump;
#endif /*CONFIG_SOUTHCHIP_DVCHG_CLASS*/
const char *chg_dev_name;
struct power_supply_desc psy_desc;
struct power_supply_config psy_cfg;
struct power_supply *psy;
struct power_supply *bms_psy;
};
#ifdef CONFIG_MTK_CLASS
static const struct charger_properties sc858x_chg_props = {
.alias_name = "sc858x_chg",
};
#endif /*CONFIG_MTK_CLASS*/
/********************COMMON API***********************/
__maybe_unused static u8 val2reg(enum sc858x_reg_range id, u32 val)
{
int i;
u8 reg;
const struct reg_range *range= &sc858x_reg_range[id];
if (!range)
return val;
if (range->table) {
if (val <= range->table[0])
return 0;
for (i = 1; i < range->num_table - 1; i++) {
if (val == range->table[i])
return i;
if (val > range->table[i] &&
val < range->table[i + 1])
return range->round_up ? i + 1 : i;
}
return range->num_table - 1;
}
if (val <= range->min)
reg = 0;
else if (val >= range->max)
reg = (range->max - range->offset) / range->step;
else if (range->round_up)
reg = (val - range->offset) / range->step + 1;
else
reg = (val - range->offset) / range->step;
return reg;
}
__maybe_unused static u32 reg2val(enum sc858x_reg_range id, u8 reg)
{
const struct reg_range *range= &sc858x_reg_range[id];
if (!range)
return reg;
return range->table ? range->table[reg] :
range->offset + range->step * reg;
}
/*********************************************************/
static int sc858x_field_read(struct sc858x_chip *sc,
enum sc858x_fields field_id, int *val)
{
int ret;
ret = regmap_field_read(sc->rmap_fields[field_id], val);
if (ret < 0) {
dev_err(sc->dev, "sc858x read field %d fail: %d\n", field_id, ret);
}
return ret;
}
static int sc858x_field_write(struct sc858x_chip *sc,
enum sc858x_fields field_id, int val)
{
int ret;
ret = regmap_field_write(sc->rmap_fields[field_id], val);
if (ret < 0) {
dev_err(sc->dev, "sc858x read field %d fail: %d\n", field_id, ret);
}
return ret;
}
static int sc858x_read_block(struct sc858x_chip *sc,
int reg, uint8_t *val, int len)
{
int ret;
ret = regmap_bulk_read(sc->regmap, reg, val, len);
if (ret < 0) {
dev_err(sc->dev, "sc858x read %02x block failed %d\n", reg, ret);
}
return ret;
}
/*******************************************************/
__maybe_unused static int sc858x_detect_device(struct sc858x_chip *sc)
{
int ret;
int val;
ret = sc858x_field_read(sc, DEVICE_ID, &val);
if (ret < 0) {
dev_err(sc->dev, "%s fail(%d)\n", __func__, ret);
return ret;
}
if (val != SC858X_DEVICE_ID && val !=SC8566_DEVICE_ID) {
dev_err(sc->dev, "%s not find SC858X, ID = 0x%02x\n", __func__, val);
return -EINVAL;
}
return ret;
}
__maybe_unused static int sc858x_reg_reset(struct sc858x_chip *sc)
{
return sc858x_field_write(sc, REG_RST, 1);
}
__maybe_unused static int sc858x_dump_reg(struct sc858x_chip *sc)
{
int ret;
int i;
int val;
for (i = 0; i <= 0X28; i++) {
ret = regmap_read(sc->regmap, i, &val);
dev_err(sc->dev, "%s reg[0x%02x] = 0x%02x\n",
__func__, i, val);
}
return ret;
}
__maybe_unused static int sc858x_check_charge_enabled(struct sc858x_chip *sc, bool *enabled)
{
int ret, val;
ret = sc858x_field_read(sc, CP_SWITCHING_STAT, &val);
*enabled = (bool)val;
dev_info(sc->dev,"%s:%d", __func__, val);
return ret;
}
__maybe_unused static int sc858x_get_status(struct sc858x_chip *sc, uint32_t *status)
{
int ret, val;
*status = 0;
ret = sc858x_field_read(sc, VBUS_ERRORHI_STAT, &val);
if (ret < 0) {
dev_err(sc->dev, "%s fail to read VBUS_ERRORHI_STAT(%d)\n", __func__, ret);
return ret;
}
if (val != 0)
*status |= BIT(ERROR_VBUS_HIGH);
ret = sc858x_field_read(sc, VBUS_ERRORLO_STAT, &val);
if (ret < 0) {
dev_err(sc->dev, "%s fail to read VBUS_ERRORLO_STAT(%d)\n", __func__, ret);
return ret;
}
if (val != 0)
*status |= BIT(ERROR_VBUS_LOW);
return ret;
}
__maybe_unused static int sc858x_enable_adc(struct sc858x_chip *sc, bool en)
{
dev_info(sc->dev,"%s:%d", __func__, en);
return sc858x_field_write(sc, ADC_EN, !!en);
}
__maybe_unused static int sc858x_set_adc_scanrate(struct sc858x_chip *sc, bool oneshot)
{
dev_info(sc->dev,"%s:%d",__func__,oneshot);
return sc858x_field_write(sc, ADC_RATE, !!oneshot);
}
static int sc858x_get_adc_data(struct sc858x_chip *sc,
int channel, int *result)
{
uint8_t val[2] = {0};
int ret;
union power_supply_propval prop;
if(channel >= ADC_MAX_NUM){
return -EINVAL;
}
// sc858x_enable_adc(sc, true);
// msleep(50);
dev_info(sc->dev,"%s %d\n", __func__, channel);
if(channel == ADC_IBAT){
if(IS_ERR_OR_NULL(sc->bms_psy)){
sc->bms_psy = power_supply_get_by_name("cw-bat");
if (IS_ERR_OR_NULL(sc->bms_psy)) {
pr_err("%s Couldn't get bms_psy\n", __func__);
*result = 1;
return 0;
}
else{
goto to;
}
}
else{
to:
ret = power_supply_get_property(sc->bms_psy,POWER_SUPPLY_PROP_CURRENT_NOW, &prop);
ret = prop.intval / 1000;
dev_info(sc->dev,"[%s][%d]ibat:%d\n", __func__, __LINE__,ret);
if(ret <= 0){
//dev_info(sc->dev,"[%s][%d]ibat:%d\n", __func__, __LINE__,ret);
ret = 1;
//dev_info(sc->dev,"[%s][%d]ibat:%d\n", __func__, __LINE__,ret);
}
*result = ret;
return ret;
}
}
ret = sc858x_read_block(sc, SC858X_REG17 + (channel << 1), val, 2);
if (ret < 0) {
return ret;
}
*result = (val[1] | (val[0] << 8)) *
sc858x_adc_m[channel] / sc858x_adc_l[channel];
dev_info(sc->dev,"%s %d %d", __func__, channel, *result);
//sc858x_enable_adc(sc, false);
return ret;
}
static int sc858x_set_private_code(struct sc858x_chip *sc)
{
return regmap_write(sc->regmap, SC858X_REG7C, SC858X_PRIVATE_CODE);
}
__maybe_unused static int sc858x_set_busovp_th(struct sc858x_chip *sc, int threshold)
{
int reg_val;
int temp_threshold = 0;
int ret;
int val;
ret = sc858x_field_read(sc, MODE, &val);
if (ret < 0) {
dev_err(sc->dev, "%s fail to read MODE(%d)\n", __func__, ret);
return ret;
}
if (val == 0) {
temp_threshold = threshold / 2;
} else if (val == 2)
{
temp_threshold = threshold * 2;
} else if (val == 1)
{
temp_threshold = threshold;
} else {
return -1;
}
reg_val = val2reg(SC858X_VBUS_OVP, temp_threshold);
dev_info(sc->dev,"%s:%d-%d", __func__, threshold, reg_val);
return sc858x_field_write(sc, VBUS_OVP, reg_val);
}
__maybe_unused static int sc858x_set_busocp_th(struct sc858x_chip *sc, int threshold)
{
int reg_val = val2reg(SC858X_IBUS_OCP, threshold);
dev_info(sc->dev,"%s:%d-%d", __func__, threshold, reg_val);
return sc858x_field_write(sc, IBUS_OCP, reg_val);
}
__maybe_unused static int sc858x_set_batovp_th(struct sc858x_chip *sc, int threshold)
{
int reg_val = val2reg(SC858X_VBAT_OVP, threshold);
dev_info(sc->dev,"%s:%d-%d", __func__, threshold, reg_val);
return sc858x_field_write(sc, VBAT_OVP, reg_val);
}
__maybe_unused static int sc858x_set_batocp_th(struct sc858x_chip *sc, int threshold)
{
int reg_val = val2reg(SC858X_IBAT_OCP, threshold);
dev_info(sc->dev,"%s:%d-%d", __func__, threshold, reg_val);
return sc858x_field_write(sc, IBAT_OCP, reg_val);
}
__maybe_unused static int sc858x_set_vbusovp_alarm(struct sc858x_chip *sc, int threshold)
{
dev_info(sc->dev,"%s:%d", __func__, threshold);
return 0;
}
__maybe_unused static int sc858x_set_vbatovp_alarm(struct sc858x_chip *sc, int threshold)
{
dev_info(sc->dev,"%s:%d", __func__, threshold);
return 0;
}
__maybe_unused static int sc858x_is_vbuslowerr(struct sc858x_chip *sc, bool *err)
{
int ret;
int val;
ret = sc858x_field_read(sc, VBUS_ERRORLO_STAT, &val);
if(ret < 0) {
return ret;
}
dev_info(sc->dev,"%s:%d",__func__,val);
*err = (bool)val;
return ret;
}
__maybe_unused static int sc858x_enable_charge(struct sc858x_chip *sc, bool en)
{
int ret = 0;
int vbus_value = 0, vout_value = 0, value = 0;
int vbus_hi = 0, vbus_low = 0;
dev_info(sc->dev,"%s:%d",__func__,en);
if (!en) {
ret |= sc858x_field_write(sc, CP_EN, !!en);
return ret;
} else {
ret = sc858x_get_adc_data(sc, ADC_VBUS, &vbus_value);
ret |= sc858x_get_adc_data(sc, ADC_VOUT, &vout_value);
dev_info(sc->dev,"%s: vbus/vout:%d / %d = %d \r\n", __func__, vbus_value, vout_value, vbus_value*100/vout_value);
ret |= sc858x_field_read(sc, MODE, &value);
dev_info(sc->dev,"%s: mode:%d %s \r\n", __func__, value, (value == 0 ?"4:1":(value == 1 ?"2:1":"else")));
ret |= sc858x_field_read(sc, VBUS_ERRORLO_STAT, &vbus_low);
ret |= sc858x_field_read(sc, VBUS_ERRORHI_STAT, &vbus_hi);
dev_info(sc->dev,"%s: high:%d low:%d \r\n", __func__, vbus_hi, vbus_low);
ret |= sc858x_field_write(sc, CP_EN, !!en);
disable_irq(sc->irq);
mdelay(300);
ret |= sc858x_field_read(sc, PIN_DIAG_FAIL, &value);
dev_info(sc->dev,"%s: pin diag fail:%d \r\n", __func__, value);
ret |= sc858x_field_read(sc, CP_SWITCHING_STAT, &value);
if (!value) {
dev_info(sc->dev,"%s:enable fail \r\n", __func__);
sc858x_dump_reg(sc);
} else {
dev_info(sc->dev,"%s:enable success \r\n", __func__);
}
enable_irq(sc->irq);
}
return ret;
}
__maybe_unused static int sc858x_init_device(struct sc858x_chip *sc)
{
int ret = 0;
int i;
struct {
enum sc858x_fields field_id;
int conv_data;
} props[] = {
{VBAT_OVP_DIS, sc->cfg.vbat_ovp_dis},
{VBAT_OVP, sc->cfg.vbat_ovp},
{IBAT_OCP_DIS, sc->cfg.ibat_ocp_dis},
{IBAT_OCP, sc->cfg.ibat_ocp},
{VUSB_OVP_DIS, sc->cfg.vusb_ovp_dis},
{VUSB_OVP, sc->cfg.vusb_ovp},
{VWPC_OVP_DIS, sc->cfg.vwpc_ovp_dis},
{VWPC_OVP, sc->cfg.vwpc_ovp},
{VBUS_OVP_DIS, sc->cfg.vbus_ovp_dis},
{VBUS_OVP, sc->cfg.vbus_ovp},
{VOUT_OVP_DIS, sc->cfg.vout_ovp_dis},
{VOUT_OVP, sc->cfg.vout_ovp},
{IBUS_OCP_DIS, sc->cfg.ibus_ocp_dis},
{IBUS_OCP, sc->cfg.ibus_ocp},
{IBUS_UCP_DIS, sc->cfg.ibus_ucp_fall_dis},
{IBUS_UCP_FALL_DG_SET, sc->cfg.ibus_ucp_fall},
{PMID2OUT_UVP_DIS, sc->cfg.pmid2out_uvp_dis},
{PMID2OUT_UVP, sc->cfg.pmid2out_uvp},
{PMID2OUT_OVP_DIS, sc->cfg.pmid2out_ovp_dis},
{PMID2OUT_OVP, sc->cfg.pmid2out_ovp},
{FSW_SET, sc->cfg.fsw_set},
{WD_TIMEOUT, sc->cfg.wd_timeout},
{SET_IBAT_SNS_RES, sc->cfg.ibat_sns_r},
{MODE, sc->cfg.mode},
{TSHUT_DIS, sc->cfg.tshut_dis},
};
ret = sc858x_reg_reset(sc);
if (ret < 0) {
dev_err(sc->dev, "%s Failed to reset registers(%d)\n", __func__, ret);
}
msleep(10);
for (i = 0; i < ARRAY_SIZE(props); i++) {
ret = sc858x_field_write(sc, props[i].field_id, props[i].conv_data);
}
if (sc->mode == SC858X_SLAVE) {
ret = sc858x_field_write(sc, VBUS_INRANGE_DET_DIS, 1);
if (ret < 0) {
dev_err(sc->dev, "%s Failed to set vbus in range(%d)\n", __func__, ret);
}
}
sc858x_enable_adc(sc, true);
sc858x_set_private_code(sc);
sc858x_dump_reg(sc);
return ret;
}
/*********************mtk charger interface start**********************************/
#ifdef CONFIG_MTK_CLASS
static inline int to_sc858x_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 mtk_sc858x_is_chg_enabled(struct charger_device *chg_dev, bool *en)
{
struct sc858x_chip *sc = charger_get_data(chg_dev);
int ret;
ret = sc858x_check_charge_enabled(sc, en);
return ret;
}
static int mtk_sc858x_enable_chg(struct charger_device *chg_dev, bool en)
{
struct sc858x_chip *sc = charger_get_data(chg_dev);
int ret;
ret = sc858x_enable_charge(sc,en);
return ret;
}
static int mtk_sc858x_set_vbusovp(struct charger_device *chg_dev, u32 uV)
{
struct sc858x_chip *sc = charger_get_data(chg_dev);
int mv;
mv = uV / 1000;
return sc858x_set_busovp_th(sc, mv);
}
static int mtk_sc858x_set_ibusocp(struct charger_device *chg_dev, u32 uA)
{
struct sc858x_chip *sc = charger_get_data(chg_dev);
int ma;
ma = uA / 1000;
return sc858x_set_busocp_th(sc, ma);
}
static int mtk_sc858x_set_vbatovp(struct charger_device *chg_dev, u32 uV)
{
struct sc858x_chip *sc = charger_get_data(chg_dev);
int ret;
ret = sc858x_set_batovp_th(sc, uV/1000);
if (ret < 0)
return ret;
return ret;
}
static int mtk_sc858x_set_ibatocp(struct charger_device *chg_dev, u32 uA)
{
struct sc858x_chip *sc = charger_get_data(chg_dev);
int ret;
ret = sc858x_set_batocp_th(sc, uA/1000);
if (ret < 0)
return ret;
return ret;
}
static int mtk_sc858x_get_adc(struct charger_device *chg_dev, enum adc_channel chan,
int *min, int *max)
{
struct sc858x_chip *sc = charger_get_data(chg_dev);
sc858x_get_adc_data(sc, to_sc858x_adc(chan), max);
if(chan != ADC_CHANNEL_TEMP_JC)
*max = *max * 1000;
if (min != max)
*min = *max;
return 0;
}
static int mtk_sc858x_get_adc_accuracy(struct charger_device *chg_dev,
enum adc_channel chan, int *min, int *max)
{
*min = *max = sc858x_adc_accuracy_tbl[to_sc858x_adc(chan)];
return 0;
}
static int mtk_sc858x_is_vbuslowerr(struct charger_device *chg_dev, bool *err)
{
struct sc858x_chip *sc = charger_get_data(chg_dev);
return sc858x_is_vbuslowerr(sc,err);
}
static int mtk_sc858x_set_vbatovp_alarm(struct charger_device *chg_dev, u32 uV)
{
struct sc858x_chip *sc = charger_get_data(chg_dev);
int ret;
ret = sc858x_set_vbatovp_alarm(sc, uV/1000);
if (ret < 0)
return ret;
return ret;
}
static int mtk_sc858x_reset_vbatovp_alarm(struct charger_device *chg_dev)
{
struct sc858x_chip *sc = charger_get_data(chg_dev);
dev_err(sc->dev,"%s",__func__);
return 0;
}
static int mtk_sc858x_set_vbusovp_alarm(struct charger_device *chg_dev, u32 uV)
{
struct sc858x_chip *sc = charger_get_data(chg_dev);
int ret;
ret = sc858x_set_vbusovp_alarm(sc, uV/1000);
if (ret < 0)
return ret;
return ret;
}
static int mtk_sc858x_reset_vbusovp_alarm(struct charger_device *chg_dev)
{
struct sc858x_chip *sc = charger_get_data(chg_dev);
dev_err(sc->dev,"%s",__func__);
return 0;
}
/*
mode: 0 -> 4:1 1-> 2:1
*/
static int mtk_sc858x_set_mode(struct charger_device *chg_dev, u32 mode)
{
struct sc858x_chip *sc = charger_get_data(chg_dev);
int val = 0,ret = 0;
ret = sc858x_field_read(sc, MODE, &val);
dev_err(sc->dev,"%s,set mode:%d,cur reg:0x%x\n",__func__,mode,val);
if (ret < 0) {
dev_err(sc->dev, "%s fail to read MODE(%d)\n", __func__, ret);
return ret;
}
if(mode){
val |= 0x01;
}
else{
val &= 0xf8;
}
dev_err(sc->dev,"%s,set:0x%x\n",__func__,val);
ret = sc858x_field_write(sc, MODE, val);
if (ret < 0) {
dev_err(sc->dev, "%s fail to write MODE(%d)\n", __func__, ret);
return ret;
}
return 0;
}
static int mtk_sc858x_get_mode(struct charger_device *chg_dev, u32 *mode)
{
int val = 0,ret = 0;
struct sc858x_chip *sc = charger_get_data(chg_dev);
ret = sc858x_field_read(sc, MODE, &val);
if (ret < 0) {
dev_err(sc->dev, "%s fail to read MODE(%d)\n", __func__, ret);
return ret;
}
dev_err(sc->dev,"%s,mode:%d\n",__func__,val);
*mode = val;
return 0;
}
static int mtk_sc858x_init_chip(struct charger_device *chg_dev)
{
struct sc858x_chip *sc = charger_get_data(chg_dev);
return sc858x_init_device(sc);
}
static const struct charger_ops sc858x_chg_ops = {
.enable = mtk_sc858x_enable_chg,
.is_enabled = mtk_sc858x_is_chg_enabled,
.get_adc = mtk_sc858x_get_adc,
.get_adc_accuracy = mtk_sc858x_get_adc_accuracy,
.set_vbusovp = mtk_sc858x_set_vbusovp,
.set_ibusocp = mtk_sc858x_set_ibusocp,
.set_vbatovp = mtk_sc858x_set_vbatovp,
.set_ibatocp = mtk_sc858x_set_ibatocp,
.init_chip = mtk_sc858x_init_chip,
.is_vbuslowerr = mtk_sc858x_is_vbuslowerr,
.set_vbatovp_alarm = mtk_sc858x_set_vbatovp_alarm,
.reset_vbatovp_alarm = mtk_sc858x_reset_vbatovp_alarm,
.set_vbusovp_alarm = mtk_sc858x_set_vbusovp_alarm,
.reset_vbusovp_alarm = mtk_sc858x_reset_vbusovp_alarm,
.get_mivr = mtk_sc858x_get_mode,
.set_mivr = mtk_sc858x_set_mode,
};
#endif /*CONFIG_MTK_CLASS*/
/********************mtk charger interface end*************************************************/
#ifdef CONFIG_SOUTHCHIP_DVCHG_CLASS
static inline int sc_to_sc858x_adc(enum sc_adc_channel chan)
{
switch (chan) {
case SC_ADC_VBUS:
return ADC_VBUS;
case SC_ADC_VBAT:
return ADC_VBAT;
case SC_ADC_IBUS:
return ADC_IBUS;
case SC_ADC_IBAT:
return ADC_IBAT;
case SC_ADC_TDIE:
return ADC_TDIE;
default:
break;
}
return ADC_MAX_NUM;
}
static int sc_sc858x_set_enable(struct dvchg_dev *charger_pump, bool enable)
{
struct sc858x_chip *sc = dvchg_get_private(charger_pump);
int ret;
ret = sc858x_enable_charge(sc,enable);
return ret;
}
static int sc_sc858x_get_is_enable(struct dvchg_dev *charger_pump, bool *enable)
{
struct sc858x_chip *sc = dvchg_get_private(charger_pump);
int ret;
ret = sc858x_check_charge_enabled(sc, enable);
return ret;
}
static int sc_sc858x_get_status(struct dvchg_dev *charger_pump, uint32_t *status)
{
struct sc858x_chip *sc = dvchg_get_private(charger_pump);
int ret = 0;
ret = sc858x_get_status(sc, status);
return ret;
}
static int sc_sc858x_get_adc_value(struct dvchg_dev *charger_pump, enum sc_adc_channel ch, int *value)
{
struct sc858x_chip *sc = dvchg_get_private(charger_pump);
int ret = 0;
ret = sc858x_get_adc_data(sc, sc_to_sc858x_adc(ch), value);
return ret;
}
static struct dvchg_ops sc_sc858x_dvchg_ops = {
.set_enable = sc_sc858x_set_enable,
.get_status = sc_sc858x_get_status,
.get_is_enable = sc_sc858x_get_is_enable,
.get_adc_value = sc_sc858x_get_adc_value,
};
#endif /*CONFIG_SOUTHCHIP_DVCHG_CLASS*/
/********************creat devices note start*************************************************/
static ssize_t sc858x_show_registers(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct sc858x_chip *sc = dev_get_drvdata(dev);
u8 addr;
int val;
u8 tmpbuf[300];
int len;
int idx = 0;
int ret;
idx = snprintf(buf, PAGE_SIZE, "%s:\n", "sc858x");
for (addr = 0x0; addr <= SC858X_REGMAX; addr++) {
ret = regmap_read(sc->regmap, 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 sc858x_store_register(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct sc858x_chip *sc = dev_get_drvdata(dev);
int ret;
unsigned int reg;
unsigned int val;
ret = sscanf(buf, "%x %x", &reg, &val);
if (ret == 2 && reg <= SC858X_REGMAX)
regmap_write(sc->regmap, (unsigned char)reg, (unsigned char)val);
return count;
}
static DEVICE_ATTR(registers, 0660, sc858x_show_registers, sc858x_store_register);
static void sc858x_create_device_node(struct device *dev)
{
device_create_file(dev, &dev_attr_registers);
}
/********************creat devices note end*************************************************/
/*
* interrupt does nothing, just info event chagne, other module could get info
* through power supply interface
*/
#ifdef CONFIG_MTK_CLASS
static inline int status_reg_to_charger(enum sc858x_notify notify)
{
switch (notify) {
case SC858X_NOTIFY_IBUSOCP:
return CHARGER_DEV_NOTIFY_IBUSOCP;
case SC858X_NOTIFY_VBUSOVP:
return CHARGER_DEV_NOTIFY_VBUS_OVP;
case SC858X_NOTIFY_IBATOCP:
return CHARGER_DEV_NOTIFY_IBATOCP;
case SC858X_NOTIFY_VBATOVP:
return CHARGER_DEV_NOTIFY_BAT_OVP;
case SC858X_NOTIFY_VOUTOVP:
return CHARGER_DEV_NOTIFY_VOUTOVP;
default:
return -EINVAL;
break;
}
return -EINVAL;
}
#endif /*CONFIG_MTK_CLASS*/
__maybe_unused
static void sc858x_dump_check_fault_status(struct sc858x_chip *sc)
{
int ret;
u8 flag = 0;
int i,j,k;
#ifdef CONFIG_MTK_CLASS
int noti;
#endif /*CONFIG_MTK_CLASS*/
for (i = 0; i <= 0X28; i++) {
ret = sc858x_read_block(sc, i, &flag, 1);
dev_err(sc->dev, "%s reg[0x%02x] = 0x%02x\n", __func__, i, flag);
for (k=0; k < ARRAY_SIZE(cp_intr_flag); k++) {
if (cp_intr_flag[k].reg == i){
for (j=0; j < cp_intr_flag[k].len; j++) {
if (flag & cp_intr_flag[k].bit[j].mask) {
dev_err(sc->dev,"trigger :%s\n",cp_intr_flag[k].bit[j].name);
#ifdef CONFIG_MTK_CLASS
noti = status_reg_to_charger(cp_intr_flag[k].bit[j].notify);
if(noti >= 0) {
charger_dev_notify(sc->chg_dev, noti);
}
#endif /*CONFIG_MTK_CLASS*/
}
}
}
}
}
}
static irqreturn_t sc858x_irq_handler(int irq, void *data)
{
struct sc858x_chip *sc = data;
dev_err(sc->dev,"INT OCCURED\n");
//sc858x_dump_reg(sc);
sc858x_dump_check_fault_status(sc);
power_supply_changed(sc->psy);
return IRQ_HANDLED;
}
static int sc858x_register_interrupt(struct sc858x_chip *sc)
{
int ret;
if (gpio_is_valid(sc->irq_gpio)) {
ret = gpio_request_one(sc->irq_gpio, GPIOF_DIR_IN,"sc858x_irq");
if (ret) {
dev_err(sc->dev,"failed to request sc858x_irq\n");
return -EINVAL;
}
sc->irq = gpio_to_irq(sc->irq_gpio);
if (sc->irq < 0) {
dev_err(sc->dev,"failed to gpio_to_irq\n");
return -EINVAL;
}
} else {
dev_err(sc->dev,"irq gpio not provided\n");
return -EINVAL;
}
if (sc->irq) {
ret = devm_request_threaded_irq(&sc->client->dev, sc->irq,
NULL, sc858x_irq_handler,
IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
sc858x_irq_name[sc->mode], sc);
if (ret < 0) {
dev_err(sc->dev,"request irq for irq=%d failed, ret =%d\n",
sc->irq, ret);
return ret;
}
enable_irq_wake(sc->irq);
}
return ret;
}
/********************interrupte end*************************************************/
/************************psy start**************************************/
static enum power_supply_property sc858x_charger_props[] = {
POWER_SUPPLY_PROP_ONLINE,
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
POWER_SUPPLY_PROP_CURRENT_NOW,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE,
POWER_SUPPLY_PROP_TEMP,
};
static int sc858x_charger_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct sc858x_chip *sc = power_supply_get_drvdata(psy);
int result;
int ret;
switch (psp) {
case POWER_SUPPLY_PROP_ONLINE:
sc858x_check_charge_enabled(sc, &sc->charge_enabled);
val->intval = sc->charge_enabled;
break;
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
ret = sc858x_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 = sc858x_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 = sc858x_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 = sc858x_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 = sc858x_get_adc_data(sc, ADC_TDIE, &result);
if (!ret)
sc->die_temp = result;
val->intval = sc->die_temp;
break;
default:
return -EINVAL;
}
return 0;
}
static int sc858x_charger_set_property(struct power_supply *psy,
enum power_supply_property prop,
const union power_supply_propval *val)
{
struct sc858x_chip *sc = power_supply_get_drvdata(psy);
switch (prop) {
case POWER_SUPPLY_PROP_ONLINE:
sc858x_enable_charge(sc, val->intval);
dev_info(sc->dev, "POWER_SUPPLY_PROP_ONLINE: %s\n",
val->intval ? "enable" : "disable");
break;
default:
return -EINVAL;
}
return 0;
}
static int sc858x_charger_is_writeable(struct power_supply *psy,
enum power_supply_property prop)
{
return 0;
}
static int sc858x_psy_register(struct sc858x_chip *sc)
{
sc->psy_cfg.drv_data = sc;
sc->psy_cfg.of_node = sc->dev->of_node;
sc->psy_desc.name = sc858x_psy_name[sc->mode];
sc->psy_desc.type = POWER_SUPPLY_TYPE_MAINS;
sc->psy_desc.properties = sc858x_charger_props;
sc->psy_desc.num_properties = ARRAY_SIZE(sc858x_charger_props);
sc->psy_desc.get_property = sc858x_charger_get_property;
sc->psy_desc.set_property = sc858x_charger_set_property;
sc->psy_desc.property_is_writeable = sc858x_charger_is_writeable;
sc->psy = devm_power_supply_register(sc->dev,
&sc->psy_desc, &sc->psy_cfg);
if (IS_ERR(sc->psy)) {
dev_err(sc->dev, "%s failed to register psy\n", __func__);
return PTR_ERR(sc->psy);
}
dev_info(sc->dev, "%s power supply register successfully\n", sc->psy_desc.name);
return 0;
}
/************************psy end**************************************/
static int sc858x_set_work_mode(struct sc858x_chip *sc, int mode)
{
sc->mode = mode;
dev_err(sc->dev,"work mode is %s\n", sc->mode == SC858X_STANDALONG
? "standalone" : (sc->mode == SC858X_MASTER ? "master" : "slave"));
return 0;
}
static int sc858x_parse_dt(struct sc858x_chip *sc, struct device *dev)
{
struct device_node *np = dev->of_node;
int i;
int ret;
struct {
char *name;
int *conv_data;
} props[] = {
{"sc,sc858x,vbat-ovp-dis", &(sc->cfg.vbat_ovp_dis)},
{"sc,sc858x,vbat-ovp", &(sc->cfg.vbat_ovp)},
{"sc,sc858x,ibat-ocp-dis", &(sc->cfg.ibat_ocp_dis)},
{"sc,sc858x,ibat-ocp", &(sc->cfg.ibat_ocp)},
{"sc,sc858x,vusb-ovp-dis", &(sc->cfg.vusb_ovp_dis)},
{"sc,sc858x,vusb-ovp", &(sc->cfg.vusb_ovp)},
{"sc,sc858x,vwpc-ovp-dis", &(sc->cfg.vwpc_ovp_dis)},
{"sc,sc858x,vwpc-ovp", &(sc->cfg.vwpc_ovp)},
{"sc,sc858x,vbus-ovp-dis", &(sc->cfg.vbus_ovp_dis)},
{"sc,sc858x,vbus-ovp", &(sc->cfg.vbus_ovp)},
{"sc,sc858x,vout-ovp-dis", &(sc->cfg.vout_ovp_dis)},
{"sc,sc858x,vout-ovp", &(sc->cfg.vout_ovp)},
{"sc,sc858x,ibus-ocp-dis", &(sc->cfg.ibus_ocp_dis)},
{"sc,sc858x,ibus-ocp", &(sc->cfg.ibus_ocp)},
{"sc,sc858x,ibus-ucp-fall-dis", &(sc->cfg.ibus_ucp_fall_dis)},
{"sc,sc858x,ibus-ucp-fall", &(sc->cfg.ibus_ucp_fall)},
{"sc,sc858x,pmid2out-ovp-dis", &(sc->cfg.pmid2out_ovp_dis)},
{"sc,sc858x,pmid2out-ovp", &(sc->cfg.pmid2out_ovp)},
{"sc,sc858x,pmid2out-uvp-dis", &(sc->cfg.pmid2out_uvp_dis)},
{"sc,sc858x,pmid2out-uvp", &(sc->cfg.pmid2out_uvp)},
{"sc,sc858x,fsw-set", &(sc->cfg.fsw_set)},
{"sc,sc858x,ss-timeout", &(sc->cfg.ss_timeout)},
{"sc,sc858x,wd-timeout", &(sc->cfg.wd_timeout)},
{"sc,sc858x,ibat-sns-r", &(sc->cfg.ibat_sns_r)},
{"sc,sc858x,mode", &(sc->cfg.mode)},
{"sc,sc858x,tshut-dis", &(sc->cfg.tshut_dis)},
};
/* initialize data for optional properties */
for (i = 0; i < ARRAY_SIZE(props); i++) {
ret = of_property_read_u32(np, props[i].name,
props[i].conv_data);
if (ret < 0) {
dev_err(sc->dev, "can not read %s \n", props[i].name);
return ret;
}
}
sc->irq_gpio = of_get_named_gpio(np, "sc858x,intr_gpio", 0);
if (!gpio_is_valid(sc->irq_gpio)) {
dev_err(sc->dev,"fail to valid gpio : %d\n", sc->irq_gpio);
return -EINVAL;
}
#ifdef CONFIG_MTK_CHARGER_V5P10
if (of_property_read_string(np, "charger_name", &sc->chg_dev_name) < 0) {
sc->chg_dev_name = "charger";
dev_err(sc->dev,"no charger name\n");
}
#elif defined(CONFIG_MTK_CHARGER_V4P19)
if (of_property_read_string(np, "charger_name_v4_19", &sc->chg_dev_name) < 0) {
sc->chg_dev_name = "charger";
dev_err(sc->dev,"no charger name\n");
}
#endif /*CONFIG_MTK_CHARGER_V4P19*/
return 0;
}
static struct of_device_id sc858x_charger_match_table[] = {
{ .compatible = "sc,sc858x-standalone",
.data = &sc858x_mode_data[SC858X_STANDALONG], },
{ .compatible = "sc,sc858x-master",
.data = &sc858x_mode_data[SC858X_MASTER], },
{ .compatible = "sc,sc858x-slave",
.data = &sc858x_mode_data[SC858X_SLAVE], },
};
static int sc858x_charger_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct sc858x_chip *sc;
const struct of_device_id *match;
struct device_node *node = client->dev.of_node;
int ret, i;
dev_err(&client->dev, "%s (%s)\n", __func__, SC858X_DRV_VERSION);
sc = devm_kzalloc(&client->dev, sizeof(struct sc858x_chip), GFP_KERNEL);
if (!sc) {
ret = -ENOMEM;
goto err_kzalloc;
}
sc->dev = &client->dev;
sc->client = client;
sc->regmap = devm_regmap_init_i2c(client,
&sc858x_regmap_config);
if (IS_ERR(sc->regmap)) {
dev_err(sc->dev, "Failed to initialize regmap\n");
ret = PTR_ERR(sc->regmap);
goto err_regmap_init;
}
for (i = 0; i < ARRAY_SIZE(sc858x_reg_fields); i++) {
const struct reg_field *reg_fields = sc858x_reg_fields;
sc->rmap_fields[i] =
devm_regmap_field_alloc(sc->dev,
sc->regmap,
reg_fields[i]);
if (IS_ERR(sc->rmap_fields[i])) {
dev_err(sc->dev, "cannot allocate regmap field\n");
ret = PTR_ERR(sc->rmap_fields[i]);
goto err_regmap_field;
}
}
ret = sc858x_detect_device(sc);
if (ret < 0) {
dev_err(sc->dev, "%s detect device fail\n", __func__);
goto err_detect_dev;
}
i2c_set_clientdata(client, sc);
sc858x_create_device_node(&(client->dev));
match = of_match_node(sc858x_charger_match_table, node);
if (match == NULL) {
dev_err(sc->dev, "device tree match not found!\n");
goto err_match_node;
}
sc858x_set_work_mode(sc, *(int *)match->data);
if (ret) {
dev_err(sc->dev,"Fail to set work mode!\n");
goto err_set_mode;
}
ret = sc858x_parse_dt(sc, &client->dev);
if (ret < 0) {
dev_err(sc->dev, "%s parse dt failed(%d)\n", __func__, ret);
goto err_parse_dt;
}
ret = sc858x_init_device(sc);
if (ret < 0) {
dev_err(sc->dev, "%s init device failed(%d)\n", __func__, ret);
goto err_init_device;
}
ret = sc858x_psy_register(sc);
if (ret < 0) {
dev_err(sc->dev, "%s psy register failed(%d)\n", __func__, ret);
goto err_register_psy;
}
ret = sc858x_register_interrupt(sc);
if (ret < 0) {
dev_err(sc->dev, "%s register irq fail(%d)\n",
__func__, ret);
goto err_register_irq;
}
#ifdef CONFIG_MTK_CLASS
sc->chg_dev = charger_device_register(sc->chg_dev_name,
&client->dev, sc,
&sc858x_chg_ops,
&sc858x_chg_props);
if (IS_ERR_OR_NULL(sc->chg_dev)) {
ret = PTR_ERR(sc->chg_dev);
dev_err(sc->dev,"Fail to register charger!\n");
goto err_register_mtk_charger;
}
#endif /*CONFIG_MTK_CLASS*/
#ifdef CONFIG_SOUTHCHIP_DVCHG_CLASS
sc->charger_pump = dvchg_register("sc_dvchg",
sc->dev, &sc_sc858x_dvchg_ops, sc);
if (IS_ERR_OR_NULL(sc->charger_pump)) {
ret = PTR_ERR(sc->charger_pump);
dev_err(sc->dev,"Fail to register charger!\n");
goto err_register_sc_charger;
}
#endif /* CONFIG_SOUTHCHIP_DVCHG_CLASS */
dev_err(sc->dev, "sc858x[%s] probe successfully!\n",
sc->mode == SC858X_MASTER ? "master" : "slave");
return 0;
err_register_psy:
err_register_irq:
#ifdef CONFIG_MTK_CLASS
err_register_mtk_charger:
#endif /*CONFIG_MTK_CLASS*/
#ifdef CONFIG_SOUTHCHIP_DVCHG_CLASS
err_register_sc_charger:
#endif /*CONFIG_SOUTHCHIP_DVCHG_CLASS*/
err_init_device:
power_supply_unregister(sc->psy);
err_detect_dev:
err_match_node:
err_set_mode:
err_parse_dt:
err_regmap_init:
err_regmap_field:
devm_kfree(&client->dev, sc);
err_kzalloc:
dev_err(&client->dev,"sc858x probe fail\n");
return ret;
}
static int sc858x_charger_remove(struct i2c_client *client)
{
struct sc858x_chip *sc = i2c_get_clientdata(client);
power_supply_unregister(sc->psy);
devm_kfree(&client->dev, sc);
return 0;
}
#ifdef CONFIG_PM_SLEEP
static int sc858x_suspend(struct device *dev)
{
struct sc858x_chip *sc = dev_get_drvdata(dev);
dev_info(sc->dev, "Suspend successfully!");
if (device_may_wakeup(dev))
enable_irq_wake(sc->irq);
disable_irq(sc->irq);
return 0;
}
static int sc858x_resume(struct device *dev)
{
struct sc858x_chip *sc = dev_get_drvdata(dev);
dev_info(sc->dev, "Resume successfully!");
if (device_may_wakeup(dev))
disable_irq_wake(sc->irq);
enable_irq(sc->irq);
return 0;
}
static const struct dev_pm_ops sc858x_pm = {
SET_SYSTEM_SLEEP_PM_OPS(sc858x_suspend, sc858x_resume)
};
#endif
static struct i2c_driver sc858x_charger_driver = {
.driver = {
.name = "sc858x",
.owner = THIS_MODULE,
.of_match_table = sc858x_charger_match_table,
#ifdef CONFIG_PM_SLEEP
.pm = &sc858x_pm,
#endif
},
.probe = sc858x_charger_probe,
.remove = sc858x_charger_remove,
};
module_i2c_driver(sc858x_charger_driver);
MODULE_DESCRIPTION("SC SC858X Driver");
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("South Chip <Aiden-yu@southchip.com>");