Files
android/drivers/power/supply/mediatek/charger/ln8000_charger.c
T
threeeye bc1d038eac REVVL 6 Pro kernel bring-up: full working tree snapshot (2026-09-12)
Squashed backup commit (no shallow-clone history issues) of
xiaomi-mt6833-dev/kernel_xiaomi_mt6833 (lineage-23.2 branch, upstream
commit 9eece662e) plus all fixes applied to get everpal_defconfig building
clean against a modern (GCC 15.2.0) toolchain -- see the previous commit
message on the lineage-23.2 branch (also in this repo history is not
available here since this is a squashed snapshot; full incremental history
lives in the local DL380 checkout and the upstream GitHub remote).

Upstream: https://github.com/xiaomi-mt6833-dev/kernel_xiaomi_mt6833 (lineage-23.2)
2026-09-12 13:36:46 +00:00

1742 lines
63 KiB
C

/*
* ln8000-charger.c - Charger driver for LIONSEMI LN8000
*
* Copyright (C) 2021 Lion Semiconductor Inc.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
*/
#include <linux/err.h>
#include <linux/kernel.h>
#include <linux/version.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/mutex.h>
#include <linux/delay.h>
#include <linux/of_irq.h>
#include <linux/of_gpio.h>
#include <linux/of_device.h>
#include <linux/pm_runtime.h>
#include <linux/pm_wakeup.h>
#include <linux/power_supply.h>
#include <linux/gpio/consumer.h>
#include <linux/i2c.h>
#include <linux/regmap.h>
#include <linux/sysfs.h>
#include <linux/debugfs.h>
#include <linux/types.h>
#include "ln8000_charger.h"
#include <linux/hardware_info.h>
static const char *ln8000_dev_name[] = {
"ln8000-standalone",
"ln8000-master",
"ln8000-slave",
};
#define ln_err(fmt, ...) \
do { \
if (info->dev_role == LN_ROLE_STANDALONE) \
printk(KERN_ERR "ln8000-standalone: %s: " fmt, __func__, ##__VA_ARGS__); \
else if (info->dev_role == LN_ROLE_MASTER) \
printk(KERN_ERR "ln8000-master: %s: " fmt, __func__, ##__VA_ARGS__); \
else \
printk(KERN_ERR "ln8000-slave: %s: " fmt, __func__, ##__VA_ARGS__); \
} while (0);
#define ln_info(fmt, ...) \
do { \
if (info->dev_role == LN_ROLE_STANDALONE) \
printk(KERN_INFO "ln8000-standalone: %s: " fmt, __func__, ##__VA_ARGS__); \
else if (info->dev_role == LN_ROLE_MASTER) \
printk(KERN_INFO "ln8000-master: %s: " fmt, __func__, ##__VA_ARGS__); \
else \
printk(KERN_INFO "ln8000-slave: %s: " fmt, __func__, ##__VA_ARGS__); \
} while (0);
#define ln_dbg(fmt, ...) \
do { \
if (info->dev_role == LN_ROLE_STANDALONE) \
printk(KERN_DEBUG "ln8000-standalone: %s: " fmt, __func__, ##__VA_ARGS__); \
else if (info->dev_role == LN_ROLE_MASTER) \
printk(KERN_DEBUG "ln8000-master: %s: " fmt, __func__, ##__VA_ARGS__); \
else \
printk(KERN_DEBUG "ln8000-slave: %s: " fmt, __func__, ##__VA_ARGS__); \
} while (0);
#define LN8000_REG_PRINT(reg_addr, val) \
do { \
ln_info(" --> [%-20s] 0x%02X : 0x%02X\n",\
#reg_addr, LN8000_REG_##reg_addr, (val) & 0xFF); \
} while (0);
#define LN8000_PARSE_PROP(ret, pdata, field, prop, default_prop)\
do { \
if (ret) { \
ln_info("%s = %d (set to default)\n", #field, default_prop); \
pdata->field = default_prop; \
} else { \
ln_info("%s = %d\n", #field, prop); \
pdata->field = prop; \
} \
} while (0);
#define LN8000_BIT_CHECK(val, idx, desc) if(val & (1<<idx)) ln_info("-> %s\n", desc)
#define LN8000_USE_GPIO(pdata) ((pdata != NULL) && (!IS_ERR_OR_NULL(pdata->irq_gpio)))
#define LN8000_STATUS(val, mask) ((val & mask) ? true : false)
enum {
VBUS_ERROR_NONE,
VBUS_ERROR_LOW,
VBUS_ERROR_HIGHT,
};
/*static int hv_charge_enable = 1;
static int bq_charge_done;
*/
/**
* I2C control functions : when occurred I2C tranfer fault, we
* will retry to it. (default count:3)
*/
#define I2C_RETRY_CNT 3
static int ln8000_read_reg(struct ln8000_info *info, u8 addr, u8 *data)
{
int i, ret = 0;
mutex_lock(&info->i2c_lock);
for (i=0; i < I2C_RETRY_CNT; ++i) {
ret = i2c_smbus_read_byte_data(info->client, addr);
if (IS_ERR_VALUE((unsigned long)ret)) {
ln_info("failed-read, reg(0x%02X), ret(%d)\n", addr, ret);
} else {
*data = (u8)ret;
}
}
mutex_unlock(&info->i2c_lock);
return ret;
}
static int ln8000_bulk_read_reg(struct ln8000_info *info, u8 addr, void *data, int count)
{
int i, ret = 0;
mutex_lock(&info->i2c_lock);
for (i=0; i < I2C_RETRY_CNT; ++i) {
ret = i2c_smbus_read_i2c_block_data(info->client, addr, count, data);
if (IS_ERR_VALUE((unsigned long)ret)) {
ln_info("failed-bulk-read, reg(0x%02X, %d bytes), ret(%d)\n", addr, count, ret);
} else {
break;
}
}
mutex_unlock(&info->i2c_lock);
return ret;
}
static int ln8000_write_reg(struct ln8000_info *info, u8 addr, u8 data)
{
int i, ret = 0;
mutex_lock(&info->i2c_lock);
for (i=0; i < I2C_RETRY_CNT; ++i) {
ret = i2c_smbus_write_byte_data(info->client, addr, data);
if (IS_ERR_VALUE((unsigned long)ret)) {
ln_info("failed-write, reg(0x%02X), ret(%d)\n", addr, ret);
} else {
break;
}
}
mutex_unlock(&info->i2c_lock);
return ret;
}
static int ln8000_update_reg(struct ln8000_info *info, u8 addr, u8 mask, u8 data)
{
int i, ret;
u8 old_val, new_val;
mutex_lock(&info->i2c_lock);
for (i=0; i < I2C_RETRY_CNT; ++i) {
ret = i2c_smbus_read_byte_data(info->client, addr);
if (ret < 0) {
ln_err("failed-update, reg(0x%02X), ret(%d)\n", addr, ret);
} else {
old_val = ret & 0xff;
new_val = (data & mask) | (old_val & ~(mask));
ret = i2c_smbus_write_byte_data(info->client, addr,
new_val);
if (ret < 0) {
ln_err("failed-update, reg(0x%02X), ret(%d)\n",
addr, ret);
} else {
break;
}
}
}
mutex_unlock(&info->i2c_lock);
return ret;
}
/**
* Register control functions
*/
static int ln8000_set_sw_freq(struct ln8000_info *info, u8 fsw_cfg)
{
return ln8000_update_reg(info, LN8000_REG_SYS_CTRL, 0xF << 4, fsw_cfg << 4);
}
static int ln8000_set_vac_ovp(struct ln8000_info *info, unsigned int ovp_th)
{
u8 cfg;
if (ovp_th <= 6500000) {
cfg = LN8000_VAC_OVP_6P5V;
} else if (ovp_th <= 11000000) {
cfg = LN8000_VAC_OVP_11V;
} else if (ovp_th <= 12000000) {
cfg = LN8000_VAC_OVP_12V;
} else {
cfg = LN8000_VAC_OVP_13V;
}
return ln8000_update_reg(info, LN8000_REG_GLITCH_CTRL, 0x3 << 2, cfg << 2);
}
/* battery float voltage */
static int ln8000_set_vbat_float(struct ln8000_info *info, unsigned int cfg)
{
u8 val;
if (cfg < LN8000_VBAT_FLOAT_MIN)
val = 0x00;
else if (cfg > LN8000_VBAT_FLOAT_MAX)
val = 0xFF;
else
val = (cfg - LN8000_VBAT_FLOAT_MIN) / LN8000_VBAT_FLOAT_LSB;
return ln8000_write_reg(info, LN8000_REG_V_FLOAT_CTRL, val);
}
static int ln8000_set_iin_limit(struct ln8000_info *info, unsigned int cfg)
{
u8 val = cfg / LN8000_IIN_CFG_LSB;
ln_info("iin_limit=%dmV(iin_ctrl=0x%x)\n", cfg / 1000, val);
return ln8000_update_reg(info, LN8000_REG_IIN_CTRL, 0x7F, val);
}
static int ln8000_set_ntc_alarm(struct ln8000_info *info, unsigned int cfg)
{
int ret;
/* update lower bits */
ret = ln8000_write_reg(info, LN8000_REG_NTC_CTRL, (cfg & 0xFF));
if (ret < 0)
return ret;
/* update upper bits */
ret = ln8000_update_reg(info, LN8000_REG_ADC_CTRL, 0x3, (cfg >> 8));
return ret;
}
/* battery voltage OV protection */
static int ln8000_enable_vbat_ovp(struct ln8000_info *info, bool enable)
{
u8 val;
val = (enable) ? 0 : 1;//disable
val <<= LN8000_BIT_DISABLE_VBAT_OV;
return ln8000_update_reg(info, LN8000_REG_FAULT_CTRL, BIT(LN8000_BIT_DISABLE_VBAT_OV), val);
}
static int ln8000_enable_vbat_regulation(struct ln8000_info *info, bool enable)
{
return ln8000_update_reg(info, LN8000_REG_REGULATION_CTRL,
0x1 << LN8000_BIT_DISABLE_VFLOAT_LOOP,
!(enable) << LN8000_BIT_DISABLE_VFLOAT_LOOP);
}
static int ln8000_enable_vbat_loop_int(struct ln8000_info *info, bool enable)
{
return ln8000_update_reg(info, LN8000_REG_REGULATION_CTRL,
0x1 << LN8000_BIT_ENABLE_VFLOAT_LOOP_INT,
enable << LN8000_BIT_ENABLE_VFLOAT_LOOP_INT);
}
/* input current OC protection */
static int ln8000_enable_iin_ocp(struct ln8000_info *info, bool enable)
{
return ln8000_update_reg(info, LN8000_REG_FAULT_CTRL,
0x1 << LN8000_BIT_DISABLE_IIN_OCP,
!(enable) << LN8000_BIT_DISABLE_IIN_OCP);
}
static int ln8000_enable_iin_regulation(struct ln8000_info *info, bool enable)
{
return ln8000_update_reg(info, LN8000_REG_REGULATION_CTRL,
0x1 << LN8000_BIT_DISABLE_IIN_LOOP,
!(enable) << LN8000_BIT_DISABLE_IIN_LOOP);
}
static int ln8000_enable_iin_loop_int(struct ln8000_info *info, bool enable)
{
return ln8000_update_reg(info, LN8000_REG_REGULATION_CTRL,
0x1 << LN8000_BIT_ENABLE_IIN_LOOP_INT,
enable << LN8000_BIT_ENABLE_IIN_LOOP_INT);
}
static int ln8000_enable_tdie_prot(struct ln8000_info *info, bool enable)
{
return ln8000_update_reg(info, LN8000_REG_REGULATION_CTRL,
0x1 << LN8000_BIT_TEMP_MAX_EN, enable << LN8000_BIT_TEMP_MAX_EN);
}
static int ln8000_enable_tdie_regulation(struct ln8000_info *info, bool enable)
{
return ln8000_update_reg(info, LN8000_REG_REGULATION_CTRL,
0x1 << LN8000_BIT_TEMP_REG_EN, enable << LN8000_BIT_TEMP_REG_EN);
}
/* ADC channel enable */
static int ln8000_set_adc_ch(struct ln8000_info *info, unsigned int ch, bool enable)
{
u8 mask;
u8 val;
int ret;
if ((ch > LN8000_ADC_CH_ALL) || (ch < 1))
return -EINVAL;
if (ch == LN8000_ADC_CH_ALL) {
// update all channels
val = (enable) ? 0x3E : 0x00;
ret = ln8000_write_reg(info, LN8000_REG_ADC_CFG, val);
} else {
// update selected channel
mask = 1<<(ch-1);
val = (enable) ? 1 : 0;
val <<= (ch-1);
ret = ln8000_update_reg(info, LN8000_REG_ADC_CFG, mask, val);
}
return ret;
}
/* BUS temperature monitoring (protection+alarm) */
static int ln8000_enable_tbus_monitor(struct ln8000_info *info, bool enable)
{
int ret;
/* enable BUS monitoring */
ret = ln8000_update_reg(info, LN8000_REG_RECOVERY_CTRL, 0x1 << 1, enable << 1);
if (ret < 0)
return ret;
/* enable BUS ADC channel */
if (enable) {
ret = ln8000_set_adc_ch(info, LN8000_ADC_CH_TSBUS, true);
}
return ret;
}
/* BAT temperature monitoring (protection+alarm) */
static int ln8000_enable_tbat_monitor(struct ln8000_info *info, bool enable)
{
int ret;
/* enable BAT monitoring */
ret = ln8000_update_reg(info, LN8000_REG_RECOVERY_CTRL, 0x1 << 0, enable << 0);
if (ret < 0)
return ret;
/* enable BAT ADC channel */
if (enable) {
ret = ln8000_set_adc_ch(info, LN8000_ADC_CH_TSBAT, true);
}
return ret;
}
/* watchdog timer */
static int ln8000_enable_wdt(struct ln8000_info *info, bool enable)
{
return ln8000_update_reg(info, LN8000_REG_TIMER_CTRL, 0x1 << 7, enable << 7);
}
#if 0
static int ln8000_set_wdt(struct ln8000_info *info, unsigned int cfg)
{
if (cfg >= LN8000_WATCHDOG_MAX) {
cfg = LN8000_WATCHDOG_40SEC;
}
return ln8000_update_reg(info, LN8000_REG_TIMER_CTRL, 0x3 << 5, cfg << 5);
}
#endif
/* unplug / reverse-current detection */
static int ln8000_enable_rcp(struct ln8000_info *info, bool enable)
{
info->rcp_en = enable;
return ln8000_update_reg(info, LN8000_REG_SYS_CTRL,
BIT(LN8000_BIT_REV_IIN_DET),
enable << LN8000_BIT_REV_IIN_DET);
}
/* auto-recovery */
static int ln8000_enable_auto_recovery(struct ln8000_info *info, bool enable)
{
return ln8000_update_reg(info, LN8000_REG_RECOVERY_CTRL, 0xF << 4, ((0xF << 4) * enable));
}
static int ln8000_set_adc_mode(struct ln8000_info *info, unsigned int cfg)
{
return ln8000_update_reg(info, LN8000_REG_ADC_CTRL, 0x7 << 5, cfg << 5);
}
static int ln8000_set_adc_hib_delay(struct ln8000_info *info, unsigned int cfg)
{
return ln8000_update_reg(info, LN8000_REG_ADC_CTRL, 0x3 << 3, cfg << 3);
}
/* grab programmed battery float voltage (uV) */
static int ln8000_get_vbat_float(struct ln8000_info *info)
{
int ret;
u8 val;
ret = ln8000_read_reg(info, LN8000_REG_V_FLOAT_CTRL, &val);
if (ret < 0)
return ret;
return ((val & 0xFF) * LN8000_VBAT_FLOAT_LSB + LN8000_VBAT_FLOAT_MIN);//uV
}
/* grab programmed input current limit (uA) */
static int ln8000_get_iin_limit(struct ln8000_info *info)
{
int ret, iin;
u8 val;
ret = ln8000_read_reg(info, LN8000_REG_IIN_CTRL, &val);
if (ret < 0)
return ret;
iin = ((val & 0x7F) * LN8000_IIN_CFG_LSB);
if (iin < LN8000_IIN_CFG_MIN) {
iin = LN8000_IIN_CFG_MIN;
}
return iin;
}
/* enable/disable STANDBY */
static inline void ln8000_sw_standby(struct ln8000_info *info, bool standby)
{
u8 val = (standby) ? BIT(LN8000_BIT_STANDBY_EN) : 0x00;
ln8000_update_reg(info, LN8000_REG_SYS_CTRL, BIT(LN8000_BIT_STANDBY_EN), val);
}
/* Convert Raw ADC Code */
static void ln8000_convert_adc_code(struct ln8000_info *info, unsigned int ch, u8 *sts, int *result)
{
int adc_raw; // raw ADC value
int adc_final; // final (converted) ADC value
switch (ch) {
case LN8000_ADC_CH_VOUT:
adc_raw = ((sts[1] & 0xFF)<<2) | ((sts[0] & 0xC0)>>6);
adc_final = adc_raw * LN8000_ADC_VOUT_STEP;//uV
break;
case LN8000_ADC_CH_VIN:
adc_raw = ((sts[1] & 0x3F)<<4) | ((sts[0] & 0xF0)>>4);
adc_final = adc_raw * LN8000_ADC_VIN_STEP;//uV
break;
case LN8000_ADC_CH_VBAT:
adc_raw = ((sts[1] & 0x03)<<8) | (sts[0] & 0xFF);
adc_final = adc_raw * LN8000_ADC_VBAT_STEP;//uV
break;
case LN8000_ADC_CH_VAC:
adc_raw = (((sts[1] & 0x0F)<<6) | ((sts[0] & 0xFC)>>2)) + LN8000_ADC_VAC_OS;
adc_final = adc_raw * LN8000_ADC_VAC_STEP;//uV
break;
case LN8000_ADC_CH_IIN:
adc_raw = ((sts[1] & 0x03)<<8) | (sts[0] & 0xFF);
adc_final = adc_raw * LN8000_ADC_IIN_STEP;//uA
break;
case LN8000_ADC_CH_DIETEMP:
adc_raw = ((sts[1] & 0x0F)<<6) | ((sts[0] & 0xFC)>>2);
adc_final = (935 - adc_raw) * LN8000_ADC_DIETEMP_STEP / LN8000_ADC_DIETEMP_DENOM;//dC
if (adc_final > LN8000_ADC_DIETEMP_MAX)
adc_final = LN8000_ADC_DIETEMP_MAX;
else if (adc_final < LN8000_ADC_DIETEMP_MIN)
adc_final = LN8000_ADC_DIETEMP_MIN;
break;
case LN8000_ADC_CH_TSBAT:
adc_raw = ((sts[1] & 0x3F)<<4) | ((sts[0] & 0xF0)>>4);
adc_final = adc_raw * LN8000_ADC_NTCV_STEP;//(NTC) uV
break;
case LN8000_ADC_CH_TSBUS:
adc_raw = ((sts[1] & 0xFF)<<2) | ((sts[0] & 0xC0)>>6);
adc_final = adc_raw * LN8000_ADC_NTCV_STEP;//(NTC) uV
break;
default:
adc_raw = -EINVAL;
adc_final = -EINVAL;
break;
}
*result = adc_final;
return;
}
static void ln8000_print_regmap(struct ln8000_info *info)
{
const u8 print_reg_num = (LN8000_REG_CHARGE_CTRL - LN8000_REG_INT1_MSK) + 1;
u8 regs[64] = {0x0, };
char temp_buf[128] = {0,};
int i, ret;
for (i = 0; i < print_reg_num; ++i) {
ret = ln8000_read_reg(info, LN8000_REG_INT1_MSK + i, &regs[i]);
if (IS_ERR_VALUE((unsigned long)ret)) {
ln_err("fail to read reg for print_regmap[%d]\n", i);
regs[i] = 0xFF;
}
sprintf(temp_buf + strlen(temp_buf), "0x%02X[0x%02X],", LN8000_REG_INT1_MSK + i, regs[i]);
if (((i+1) % 10 == 0) || ((i+1) == print_reg_num)) {
ln_info("%s\n", temp_buf);
memset(temp_buf, 0x0, sizeof(temp_buf));
}
}
#if defined(CONFIG_LION_BENCH_TEST)
ln8000_read_reg(info, 0x41, &regs[0]);
ln8000_read_reg(info, 0x31, &regs[1]);
ln8000_read_reg(info, 0x4A, &regs[2]);
ln_info("dual-config:BC_OP_1[0x41]=0x%x, PRODUCT_ID[0x31]=0x%x, [0x4A0=0x%x\n", regs[0], regs[1], regs[2]);
#endif
}
/**
* LN8000 device driver control routines
*/
static int ln8000_check_status(struct ln8000_info *info)
{
u8 val[4];
if (ln8000_bulk_read_reg(info, LN8000_REG_SYS_STS, val, 4) < 0) {
return -EINVAL;
}
mutex_lock(&info->data_lock);
info->vbat_regulated = LN8000_STATUS(val[0], LN8000_MASK_VFLOAT_LOOP_STS);
info->iin_regulated = LN8000_STATUS(val[0], LN8000_MASK_IIN_LOOP_STS);
info->pwr_status = val[0] & (LN8000_MASK_BYPASS_ENABLED | LN8000_MASK_SWITCHING_ENABLED | \
LN8000_MASK_STANDBY_STS | LN8000_MASK_SHUTDOWN_STS);
info->tdie_fault = LN8000_STATUS(val[1], LN8000_MASK_TEMP_MAX_STS);
info->tdie_alarm = LN8000_STATUS(val[1], LN8000_MASK_TEMP_REGULATION_STS);
if (!info->pdata->tbat_mon_disable || !info->pdata->tbus_mon_disable) {
info->tbus_tbat_fault = LN8000_STATUS(val[1], LN8000_MASK_NTC_SHUTDOWN_STS); //tbus or tbat
info->tbus_tbat_alarm = LN8000_STATUS(val[1], LN8000_MASK_NTC_ALARM_STS);//tbus or tbat
}
info->iin_rc = LN8000_STATUS(val[1], LN8000_MASK_REV_IIN_STS);
info->wdt_fault = LN8000_STATUS(val[2], LN8000_MASK_WATCHDOG_TIMER_STS);
info->vbat_ov = LN8000_STATUS(val[2], LN8000_MASK_VBAT_OV_STS);
info->vac_unplug = LN8000_STATUS(val[2], LN8000_MASK_VAC_UNPLUG_STS);
info->vac_ov = LN8000_STATUS(val[2], LN8000_MASK_VAC_OV_STS);
info->vbus_ov = LN8000_STATUS(val[2], LN8000_MASK_VIN_OV_STS);
info->volt_qual = !(LN8000_STATUS(val[2], 0x7F));
if (info->volt_qual == 1 && info->chg_en == 1) {
info->volt_qual = !(LN8000_STATUS(val[3], 1 << 5));
if (info->volt_qual == 0) {
ln_info("volt_fault_detected (volt_qual=%d\n", info->volt_qual);
}
}
info->iin_oc = LN8000_STATUS(val[3], LN8000_MASK_IIN_OC_DETECTED);
mutex_unlock(&info->data_lock);
return 0;
}
static void ln8000_irq_sleep(struct ln8000_info *info, int suspend)
{
if (info->client->irq <= 0)
return;
if (suspend) {
ln_info("disable/suspend IRQ\n");
disable_irq(info->client->irq);
} else {
ln_info("enable/resume IRQ\n");
enable_irq(info->client->irq);
}
}
static void ln8000_soft_reset(struct ln8000_info *info)
{
ln8000_write_reg(info, LN8000_REG_LION_CTRL, 0xC6);
ln8000_irq_sleep(info, 1);
ln_info("Trigger soft-reset\n");
ln8000_update_reg(info, LN8000_REG_BC_OP_2, 0x1 << 0, 0x1 << 0);
msleep(5 * 2); /* ln8000 min wait time 5ms (after POR) */
ln8000_irq_sleep(info, 0);
}
static void ln8000_update_opmode(struct ln8000_info *info)
{
unsigned int op_mode;
u8 val;
/* chack mode status */
ln8000_read_reg(info, LN8000_REG_SYS_STS, &val);
if (val & LN8000_MASK_SHUTDOWN_STS) {
op_mode = LN8000_OPMODE_STANDBY;
} else if (val & LN8000_MASK_STANDBY_STS) {
op_mode = LN8000_OPMODE_STANDBY;
} else if (val & LN8000_MASK_SWITCHING_ENABLED) {
op_mode = LN8000_OPMODE_SWITCHING;
} else if (val & LN8000_MASK_BYPASS_ENABLED) {
op_mode = LN8000_OPMODE_BYPASS;
} else {
op_mode = LN8000_OPMODE_UNKNOWN;
}
if (op_mode != info->op_mode) {
/* IC already has been entered standby_mode, need to trigger standbt_en bit */
if (op_mode == LN8000_OPMODE_STANDBY) {
ln8000_update_reg(info, LN8000_REG_SYS_CTRL, 1 << LN8000_BIT_STANDBY_EN, 1 << LN8000_BIT_STANDBY_EN);
ln_info("forced trigger standby_en\n");
}
ln_info("op_mode has been changed [%d]->[%d] (sys_st=0x%x)\n", info->op_mode, op_mode, val);
info->op_mode = op_mode;
}
return;
}
static int ln8000_change_opmode(struct ln8000_info *info, unsigned int target_mode)
{
int ret = 0;
u8 val, msk = (0x1 << LN8000_BIT_STANDBY_EN | 0x1 << LN8000_BIT_EN_1TO1);
switch(target_mode) {
case LN8000_OPMODE_STANDBY:
val = (1 << LN8000_BIT_STANDBY_EN);
break;
case LN8000_OPMODE_BYPASS:
val = (0 << LN8000_BIT_STANDBY_EN) | (1 << LN8000_BIT_EN_1TO1);
break;
case LN8000_OPMODE_SWITCHING:
val = (0 << LN8000_BIT_STANDBY_EN) | (0 << LN8000_BIT_EN_1TO1);
break;
default:
ln_err("invalid index (target_mode=%d)\n", target_mode);
return -EINVAL;
}
ret = ln8000_update_reg(info, LN8000_REG_SYS_CTRL, msk, val);
if (IS_ERR_VALUE((unsigned long)ret)) {
return -EINVAL;
}
ln_info("changed opmode [%d] -> [%d]\n", info->op_mode, target_mode);
info->op_mode = target_mode;
return 0;
}
static int ln8000_init_device(struct ln8000_info *info)
{
unsigned int vbat_float;
/* config default charging paramter by dt */
vbat_float = info->pdata->bat_ovp_th * 100 / 102; /* ovp thershold = v_float x 1.02 */
vbat_float = (vbat_float /1000) * 1000;
ln_info("bat_ovp_th=%d, vbat_float=%d\n", info->pdata->bat_ovp_th, vbat_float);
ln8000_set_vbat_float(info, vbat_float);
info->vbat_ovp_alarm_th = info->pdata->bat_ovp_alarm_th;
ln8000_set_vac_ovp(info, info->pdata->bus_ovp_th);
info->vin_ovp_alarm_th = info->pdata->bus_ovp_alarm_th;
ln8000_set_iin_limit(info, info->pdata->bus_ocp_th - 700000);
info->iin_ocp_alarm_th = info->pdata->bus_ocp_alarm_th;
ln8000_set_ntc_alarm(info, info->pdata->ntc_alarm_cfg);
ln8000_update_reg(info, LN8000_REG_REGULATION_CTRL, 0x3 << 2, LN8000_NTC_SHUTDOWN_CFG);
ln8000_enable_auto_recovery(info, 0);
/* config charging protection */
ln8000_enable_vbat_ovp(info, !info->pdata->vbat_ovp_disable);
ln8000_enable_vbat_regulation(info, !info->pdata->vbat_reg_disable);
ln8000_enable_vbat_loop_int(info, !info->pdata->vbat_reg_disable);
ln8000_enable_iin_ocp(info, !info->pdata->iin_ocp_disable);
ln8000_enable_iin_regulation(info, !info->pdata->iin_reg_disable);
ln8000_enable_iin_loop_int(info, !info->pdata->iin_reg_disable);
ln8000_enable_tdie_regulation(info, !info->pdata->tdie_reg_disable);
ln8000_enable_tdie_prot(info, !info->pdata->tdie_prot_disable);
ln8000_enable_rcp(info, !info->pdata->revcurr_prot_disable);
ln8000_change_opmode(info, LN8000_OPMODE_STANDBY);
/* wdt : disable, adc : shutdown mode */
ln8000_enable_wdt(info, false);
ln8000_set_adc_mode(info, ADC_SHUTDOWN_MODE);//disable before updating
ln8000_set_adc_hib_delay(info, ADC_HIBERNATE_4S);
ln8000_set_adc_ch(info, LN8000_ADC_CH_ALL, true);
ln8000_enable_tbus_monitor(info, !info->pdata->tbus_mon_disable);//+enables ADC ch
ln8000_enable_tbat_monitor(info, !info->pdata->tbat_mon_disable);//+enables ADC ch
ln8000_set_adc_mode(info, ADC_AUTO_HIB_MODE);
/* mark sw initialized (used CHARGE_CTRL bit:7) */
ln8000_update_reg(info, LN8000_REG_CHARGE_CTRL, 0x1 << 7, 0x1 << 7);
ln8000_write_reg(info, LN8000_REG_THRESHOLD_CTRL, 0x0E);
/* dual mode initialized */
if (info->dev_role == LN_ROLE_MASTER) {
ln8000_write_reg(info, LN8000_REG_LION_CTRL, 0xAA);
// dual_function_en=1, dual_cfg=0, dual_lockout_en=1
ln8000_update_reg(info, 0x41, 0x7, 0x5);
// OVPFETDR_HIGH_IMP=0
ln8000_update_reg(info, 0x31, 0x1 << 5, 0x0 << 5);
ln8000_write_reg(info, LN8000_REG_LION_CTRL, 0x00);
} else if (info->dev_role == LN_ROLE_SLAVE) {
ln8000_write_reg(info, LN8000_REG_LION_CTRL, 0xAA);
// dual_function_en=1, dual_cfg=1, dual_lockout_en=1
ln8000_update_reg(info, 0x41, 0x7, 0x7);
// OVPFETDR_HIGH_IMP=1
ln8000_update_reg(info, 0x31, 0x1 << 5, 0x1 << 5);
ln8000_write_reg(info, LN8000_REG_LION_CTRL, 0x00);
}
ln8000_set_sw_freq(info, 0x2); /* default switching frequency 890kHz */
ln8000_print_regmap(info);
ln_info(" done.\n");
return 0;
}
/**
* Support power_supply platform for charger block.
* propertis are compatible by Xiaomi platform
*/
static int ln8000_get_adc_data(struct ln8000_info *info, unsigned int ch, int *result)
{
int ret;
u8 sts[2];
/* pause adc update */
ret = ln8000_update_reg(info, LN8000_REG_TIMER_CTRL, 0x1 << 1, 0x1 << 1);
if (ret < 0) {
ln_err("fail to update bit PAUSE_ADC_UPDATE:1 (ret=%d)\n", ret);
return ret;
}
switch (ch) {
case LN8000_ADC_CH_VOUT:
ret = ln8000_bulk_read_reg(info, LN8000_REG_ADC04_STS, sts, 2);
break;
case LN8000_ADC_CH_VIN:
ret = ln8000_bulk_read_reg(info, LN8000_REG_ADC03_STS, sts, 2);
break;
case LN8000_ADC_CH_VBAT:
ret = ln8000_bulk_read_reg(info, LN8000_REG_ADC06_STS, sts, 2);
break;
case LN8000_ADC_CH_VAC:
ret = ln8000_bulk_read_reg(info, LN8000_REG_ADC02_STS, sts, 2);
break;
case LN8000_ADC_CH_IIN:
ret = ln8000_bulk_read_reg(info, LN8000_REG_ADC01_STS, sts, 2);
break;
case LN8000_ADC_CH_DIETEMP:
ret = ln8000_bulk_read_reg(info, LN8000_REG_ADC07_STS, sts, 2);
break;
case LN8000_ADC_CH_TSBAT:
ret = ln8000_bulk_read_reg(info, LN8000_REG_ADC08_STS, sts, 2);
break;
case LN8000_ADC_CH_TSBUS:
ret = ln8000_bulk_read_reg(info, LN8000_REG_ADC09_STS, sts, 2);
break;
default:
ln_err("invalid ch(%d)\n", ch);
ret = -EINVAL;
break;
}
/* resume adc update */
ln8000_update_reg(info, LN8000_REG_TIMER_CTRL, 0x1 << 1, 0x0 << 1);
if (IS_ERR_VALUE((unsigned long)ret) == false) {
ln8000_convert_adc_code(info, ch, sts, result);
}
return ret;
}
static int psy_chg_get_charging_enabled(struct ln8000_info *info)
{
int enabled = 0; /* disabled */
if (info->chg_en) {
ln8000_update_opmode(info);
if (info->op_mode >= LN8000_OPMODE_BYPASS) {
enabled = 1;
}
}
return enabled;
}
static int psy_chg_get_ti_alarm_status(struct ln8000_info *info)
{
int alarm;
unsigned int v_offset;
bool bus_ovp, bus_ocp, bat_ovp;
u8 val[4];
ln8000_check_status(info);
ln8000_get_adc_data(info, LN8000_ADC_CH_VIN, &info->vbus_uV);
ln8000_get_adc_data(info, LN8000_ADC_CH_IIN, &info->iin_uA);
ln8000_get_adc_data(info, LN8000_ADC_CH_VBAT, &info->vbat_uV);
bus_ovp = (info->vbus_uV > info->vin_ovp_alarm_th) ? 1 : 0;
bus_ocp = (info->iin_uA > info->iin_ocp_alarm_th) ? 1 : 0;
bat_ovp = (info->vbat_uV > info->vbat_ovp_alarm_th) ? 1 : 0;
/* BAT alarm status not support (ovp/ocp/ucp) */
alarm = ((bus_ovp << BUS_OVP_ALARM_SHIFT) |
(bus_ocp << BUS_OCP_ALARM_SHIFT) |
(bat_ovp << BAT_OVP_ALARM_SHIFT) |
(info->tbus_tbat_alarm << BAT_THERM_ALARM_SHIFT) |
(info->tbus_tbat_alarm << BUS_THERM_ALARM_SHIFT) |
(info->tdie_alarm << DIE_THERM_ALARM_SHIFT));
v_offset = info->vbus_uV - (info->vbat_uV * 2);
ln8000_bulk_read_reg(info, LN8000_REG_SYS_STS, val, 4);
ln_info("st:0x%x:0x%x:0x%x:0x%x alarm=0x%x, adc_vin=%d, adc_iin=%d, adc_vbat=%d, v_offset=%d\n",
val[0], val[1], val[2], val[3], alarm, info->vbus_uV/1000, info->iin_uA/1000,
info->vbat_uV/10000, v_offset/1000);
return alarm;
}
static int psy_chg_get_it_bus_error_status(struct ln8000_info *info)
{
ln8000_get_adc_data(info, LN8000_ADC_CH_VIN, &info->vbus_uV);
if (info->vbus_uV < 6000000) {
return VBUS_ERROR_LOW;
} else if (info->vbus_uV > 13000000) {
return VBUS_ERROR_HIGHT;
}
return VBUS_ERROR_NONE;
}
static int psy_chg_get_ti_fault_status(struct ln8000_info *info)
{
int fault;
ln8000_check_status(info);
/* BAT ocp fault status not suppport */
fault = ((info->vbat_ov << BAT_OVP_FAULT_SHIFT) |
(info->vbus_ov << BUS_OVP_FAULT_SHIFT) |
(info->iin_oc << BUS_OCP_FAULT_SHIFT) |
(info->tbus_tbat_fault << BAT_THERM_FAULT_SHIFT) |
(info->tbus_tbat_fault << BUS_THERM_FAULT_SHIFT) |
(info->tdie_fault << DIE_THERM_FAULT_SHIFT));
if (info->volt_qual == 0) {
fault |= ((1 << BAT_OVP_FAULT_SHIFT) |
(1 << BUS_OVP_FAULT_SHIFT));
}
if (fault) {
ln_info("fault=0x%x\n", fault);
}
return fault;
}
static int ln8000_charger_get_property(struct power_supply *psy,
enum power_supply_property prop,
union power_supply_propval *val)
{
struct ln8000_info *info = power_supply_get_drvdata(psy);
switch (prop) {
case POWER_SUPPLY_PROP_CHARGING_ENABLED:
val->intval = psy_chg_get_charging_enabled(info);
break;
case POWER_SUPPLY_PROP_STATUS:
val->intval = 0;
break;
case POWER_SUPPLY_PROP_PRESENT:
val->intval = info->usb_present;
break;
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
val->intval = ln8000_get_vbat_float(info);
break;
case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
val->intval = ln8000_get_iin_limit(info);
break;
case POWER_SUPPLY_PROP_SC_BATTERY_PRESENT:
ln8000_get_adc_data(info, LN8000_ADC_CH_VBAT, &info->vbat_uV);
if (info->vbat_uV > LN8000_ADC_VBAT_MIN) {
val->intval = 1; /* detected battery */
} else {
val->intval = 0; /* non-detected battery */
}
break;
case POWER_SUPPLY_PROP_SC_VBUS_PRESENT:
ln8000_check_status(info);
val->intval = !(info->vac_unplug);
break;
case POWER_SUPPLY_PROP_SC_BATTERY_VOLTAGE:
ln8000_get_adc_data(info, LN8000_ADC_CH_VBAT, &info->vbat_uV);
val->intval = info->vbat_uV/1000;
break;
case POWER_SUPPLY_PROP_SC_BATTERY_CURRENT: /* ln8000 not support IBAT_ADC */
ln8000_get_adc_data(info, LN8000_ADC_CH_IIN, &info->iin_uA);
val->intval = (info->iin_uA * 2)/1000; /* return to IBUS_ADC x 2 */
break;
case POWER_SUPPLY_PROP_SC_BATTERY_TEMPERATURE:
if (info->pdata->tbat_mon_disable) {
val->intval = 0;
} else {
ln8000_get_adc_data(info, LN8000_ADC_CH_TSBAT, &info->tbat_uV);
val->intval = info->tbat_uV;
ln_info("ti_battery_temperature: adc_tbat=%d\n", val->intval);
}
break;
case POWER_SUPPLY_PROP_SC_BUS_VOLTAGE:
ln8000_get_adc_data(info, LN8000_ADC_CH_VIN, &info->vbus_uV);
val->intval = info->vbus_uV/1000;
break;
case POWER_SUPPLY_PROP_SC_BUS_CURRENT:
ln8000_get_adc_data(info, LN8000_ADC_CH_IIN, &info->iin_uA);
val->intval = info->iin_uA/1000;
break;
case POWER_SUPPLY_PROP_SC_BUS_TEMPERATURE:
if (info->pdata->tbus_mon_disable) {
val->intval = 0;
} else {
ln8000_get_adc_data(info, LN8000_ADC_CH_TSBUS, &info->tbus_uV);
val->intval = info->tbus_uV;
ln_info("ti_bus_temperature: adc_tbus=%d\n", val->intval);
}
break;
case POWER_SUPPLY_PROP_SC_DIE_TEMPERATURE:
ln8000_get_adc_data(info, LN8000_ADC_CH_DIETEMP, &info->tdie_dC);
val->intval = info->tdie_dC;
ln_info("ti_die_temperature: adc_tdie=%d\n", val->intval);
break;
case POWER_SUPPLY_PROP_SC_ALARM_STATUS:
val->intval = psy_chg_get_ti_alarm_status(info);
break;
case POWER_SUPPLY_PROP_SC_FAULT_STATUS:
val->intval = psy_chg_get_ti_fault_status(info);
break;
#if 0
case POWER_SUPPLY_PROP_SC_REG_STATUS:
ln8000_check_status(info);
val->intval = ((info->vbat_regulated << VBAT_REG_STATUS_SHIFT) |
/* ln8000 not support ibat_reg, we are can be ibus_reg */
(info->iin_regulated << IBAT_REG_STATUS_SHIFT));
if (val->intval) {
ln_info("ti_reg_status: intavl=0x%x\n", val->intval);
}
break;
case POWER_SUPPLY_PROP_BQ_CHARGE_DONE:
val->intval = bq_charge_done;
break;
case POWER_SUPPLY_PROP_HV_CHARGE_ENABLED:
val->intval = hv_charge_enable;
break;
#endif
case POWER_SUPPLY_PROP_SC_VBUS_ERROR_STATUS:
val->intval = psy_chg_get_it_bus_error_status(info);
break;
#if 0
case POWER_SUPPLY_PROP_SC_CHARGE_MODE:
if (info->op_mode == LN8000_OPMODE_SWITCHING) {
val->intval = 0; /* DIV2 */
} else {
val->intval = -1; /* NOT_SUPPORT */
}
break;
case POWER_SUPPLY_PROP_SC_BYPASS_MODE_ENABLED:
val->intval = 0;
break;
#endif
case POWER_SUPPLY_PROP_MODEL_NAME:
val->strval = ln8000_dev_name[info->dev_role];
break;
#if defined(CONFIG_LION_BENCH_TEST)
case POWER_SUPPLY_PROP_LION_ADC_VALUES:
val->intval = psy_chg_get_ti_alarm_status(info);
break;
case POWER_SUPPLY_PROP_LION_CHG_STATUS:
psy_chg_get_ti_alarm_status(info);
ln8000_update_opmode(info);
if (info->op_mode == LN8000_OPMODE_STANDBY) {
val->intval = 0;
} else {
val->intval = 1;
}
break;
case POWER_SUPPLY_PROP_LION_REG_DUMP:
ln8000_print_regmap(info);
break;
#endif
default:
return -EINVAL;
}
return 0;
}
static int psy_chg_set_charging_enable(struct ln8000_info *info, int val)
{
int op_mode;
/* ignore duplicate request command*/
if (info->chg_en == val)
return 0;
if (val) {
ln_info("start charging\n");
op_mode = LN8000_OPMODE_SWITCHING;
} else {
ln_info("stop charging\n");
op_mode = LN8000_OPMODE_STANDBY;
}
ln8000_change_opmode(info, op_mode);
msleep(10);
ln8000_update_opmode(info);
info->chg_en = val;
if (val) {
ln8000_print_regmap(info);
}
ln_info("op_mode=%d\n", info->op_mode);
return 0;
}
static int psy_chg_set_present(struct ln8000_info *info, int val)
{
bool usb_present = (bool)val;
if (usb_present != info->usb_present) {
ln_info("changed usb_present [%d] -> [%d]\n", info->usb_present, usb_present);
if (usb_present) {
ln8000_init_device(info);
}
info->usb_present = usb_present;
}
return 0;
}
#if 0
static int psy_chg_set_bus_protection_for_qc3(struct ln8000_info *info, int hvdcp3_type)
{
ln_info("hvdcp3_type: %d\n", hvdcp3_type);
if (hvdcp3_type == HVDCP3_CLASSA_18W) {
ln8000_set_vac_ovp(info, BUS_OVP_FOR_QC);
info->vin_ovp_alarm_th = BUS_OVP_ALARM_FOR_QC;
ln8000_set_iin_limit(info, BUS_OCP_FOR_QC_CLASS_A - 700000);
info->iin_ocp_alarm_th = BUS_OCP_ALARM_FOR_QC_CLASS_A;
} else if (hvdcp3_type == HVDCP3_CLASSB_27W) {
ln8000_set_vac_ovp(info, BUS_OVP_FOR_QC);
info->vin_ovp_alarm_th = BUS_OVP_ALARM_FOR_QC;
ln8000_set_iin_limit(info, BUS_OCP_FOR_QC_CLASS_B - 700000);
info->iin_ocp_alarm_th = BUS_OCP_ALARM_FOR_QC_CLASS_B;
} else if (hvdcp3_type == HVDCP3_P_CLASSA_18W) {
ln8000_set_vac_ovp(info, BUS_OVP_FOR_QC35);
info->vin_ovp_alarm_th = BUS_OVP_ALARM_FOR_QC35;
ln8000_set_iin_limit(info, BUS_OCP_FOR_QC35_CLASS_A_P - 700000);
info->iin_ocp_alarm_th = BUS_OCP_ALARM_FOR_QC35_CLASS_A_P;
} else if (hvdcp3_type == HVDCP3_P_CLASSB_27W) {
ln8000_set_vac_ovp(info, BUS_OVP_FOR_QC35);
info->vin_ovp_alarm_th = BUS_OVP_ALARM_FOR_QC35;
ln8000_set_iin_limit(info, BUS_OCP_FOR_QC35_CLASS_A_P - 700000);
info->iin_ocp_alarm_th = BUS_OCP_ALARM_FOR_QC35_CLASS_A_P;
} else {
ln8000_set_vac_ovp(info, info->pdata->bus_ovp_th);
info->vin_ovp_alarm_th = info->pdata->bus_ovp_alarm_th;
ln8000_set_iin_limit(info, info->pdata->bus_ocp_th - 700000);
info->iin_ocp_alarm_th = info->pdata->bus_ocp_alarm_th;
}
ln8000_print_regmap(info);
return 0;
}
#endif
static int ln8000_charger_set_property(struct power_supply *psy,
enum power_supply_property prop,
const union power_supply_propval *val)
{
struct ln8000_info *info = power_supply_get_drvdata(psy);
int ret = 0;
switch (prop) {
case POWER_SUPPLY_PROP_CHARGING_ENABLED:
ret = psy_chg_set_charging_enable(info, val->intval);
break;
case POWER_SUPPLY_PROP_PRESENT:
ret = psy_chg_set_present(info, val->intval);
break;
#if 0
case POWER_SUPPLY_PROP_SC_SET_BUS_PROTECTION_FOR_QC3:
ret = psy_chg_set_bus_protection_for_qc3(info, val->intval);
break;
case POWER_SUPPLY_PROP_BQ_CHARGE_DONE:
bq_charge_done = val->intval;
break;
case POWER_SUPPLY_PROP_HV_CHARGE_ENABLED:
hv_charge_enable = val->intval;
break;
case POWER_SUPPLY_PROP_SC_CHARGE_MODE:
ln_info("not support ti_charge_mode yet\n");
break;
case POWER_SUPPLY_PROP_SC_BYPASS_MODE_ENABLED:
ln_info("not support ti_bypass_mode_enabled yet\n");
break;
#endif
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
ret = ln8000_set_vbat_float(info, val->intval);
break;
case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
ret = ln8000_set_iin_limit(info, val->intval);
break;
default:
ret = -EINVAL;
break;
}
return ret;
}
static int ln8000_charger_is_writeable(struct power_supply *psy,
enum power_supply_property prop) {
int ret;
switch (prop) {
case POWER_SUPPLY_PROP_PRESENT:
case POWER_SUPPLY_PROP_CHARGING_ENABLED:
#if 0
case POWER_SUPPLY_PROP_HV_CHARGE_ENABLED:
case POWER_SUPPLY_PROP_BQ_CHARGE_DONE:
case POWER_SUPPLY_PROP_SC_CHARGE_MODE:
case POWER_SUPPLY_PROP_SC_BYPASS_MODE_ENABLED:
#endif
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
//case POWER_SUPPLY_PROP_SC_SET_BUS_PROTECTION_FOR_QC3:
ret = 1;
break;
default:
ret = 0;
break;
}
return ret;
}
static enum power_supply_property ln8000_charger_props[] = {
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_CHARGING_ENABLED,
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE,
POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT,
/* support TI extended propertis */
POWER_SUPPLY_PROP_SC_VBUS_ERROR_STATUS,
#if 0
POWER_SUPPLY_PROP_SC_CHARGE_MODE,
POWER_SUPPLY_PROP_SC_BYPASS_MODE_ENABLED,
#endif
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,
#if 0
POWER_SUPPLY_PROP_SC_REG_STATUS,
POWER_SUPPLY_PROP_SC_SET_BUS_PROTECTION_FOR_QC3,
POWER_SUPPLY_PROP_BQ_CHARGE_DONE,
POWER_SUPPLY_PROP_HV_CHARGE_ENABLED,
#endif
POWER_SUPPLY_PROP_MODEL_NAME,
#if defined(CONFIG_LION_BENCH_TEST)
POWER_SUPPLY_PROP_LION_ADC_VALUES,
POWER_SUPPLY_PROP_LION_CHG_STATUS,
POWER_SUPPLY_PROP_LION_REG_DUMP,
#endif
};
static int read_reg(void *data, u64 *val)
{
struct ln8000_info *info = data;
int ret;
u8 temp;
ret = ln8000_read_reg(info, info->debug_address, &temp);
if (ret) {
ln_err("Unable to read reg(0x%02X), ret=%d\n", info->debug_address, ret);
return -EAGAIN;
}
*val = (u64)temp;
return 0;
}
static int write_reg(void *data, u64 val)
{
struct ln8000_info *info = data;
int ret;
u8 temp = (u8) val;
ret = ln8000_write_reg(info, info->debug_address, temp);
if (ret) {
ln_err("Unable to write reg(0x%02X), data(0x%02X), ret=%d\n",
info->debug_address, temp, ret);
return -EAGAIN;
}
return 0;
}
DEFINE_SIMPLE_ATTRIBUTE(register_debug_ops, read_reg, write_reg, "0x%02llX\n");
static int ln8000_create_debugfs_entries(struct ln8000_info *info)
{
struct dentry *ent;
info->debug_root = debugfs_create_dir(ln8000_dev_name[info->dev_role], NULL);
if (!info->debug_root) {
ln_err("unable to create debug dir\n");
return -ENOENT;
} else {
ent = debugfs_create_x32("address", S_IFREG | S_IWUSR | S_IRUGO, info->debug_root, &(info->debug_address));
if (!ent) {
ln_err("unable to create address debug file\n");
return -ENOENT;
}
ent = debugfs_create_file("data", S_IFREG | S_IWUSR | S_IRUGO, info->debug_root, info, &register_debug_ops);
if (!ent) {
ln_err("unable to create data debug file\n");
return -ENOENT;
}
}
return 0;
}
/**
* Support IRQ interface
*/
static int ln8000_read_int_value(struct ln8000_info *info, u8 *reg_val)
{
int ret;
/* pause INT updates */
ln8000_update_reg(info, LN8000_REG_TIMER_CTRL, 0x1, 0x1);
mdelay(1);
ret = ln8000_read_reg(info, LN8000_REG_INT1, reg_val);
/* resume INT updates */
ln8000_update_reg(info, LN8000_REG_TIMER_CTRL, 0x1, 0x0);
return ret;
}
static irqreturn_t ln8000_interrupt_handler(int irq, void *data)
{
struct ln8000_info *info = data;
u8 int_reg, int_msk;
u8 masked_int;
int ret;
ln_err("ln8000_interrupt_handler enter!\n");
ret = ln8000_read_int_value(info, &int_reg);
if (IS_ERR_VALUE((unsigned long)ret)) {
ln_err("fail to read INT reg (ret=%d)\n", ret);
return IRQ_NONE;
}
ln8000_read_reg(info, LN8000_REG_INT1_MSK, &int_msk);
masked_int = int_reg & ~int_msk;
ln_info("int_reg=0x%x, int_msk=0x%x, masked_int=0x%x\n", int_reg, int_msk, masked_int);
ln8000_print_regmap(info);
LN8000_BIT_CHECK(masked_int, 7, "(INT) FAULT_INT");
LN8000_BIT_CHECK(masked_int, 6, "(INT) NTC_PROT_INT");
LN8000_BIT_CHECK(masked_int, 5, "(INT) CHARGE_PHASE_INT");
LN8000_BIT_CHECK(masked_int, 4, "(INT) MODE_INT");
LN8000_BIT_CHECK(masked_int, 3, "(INT) REV_CURR_INT");
LN8000_BIT_CHECK(masked_int, 2, "(INT) TEMP_INT");
LN8000_BIT_CHECK(masked_int, 1, "(INT) ADC_DONE_INT");
LN8000_BIT_CHECK(masked_int, 0, "(INT) TIMER_INT");
ln8000_check_status(info);
if (masked_int & LN8000_MASK_FAULT_INT) { /* FAULT_INT */
if (info->volt_qual) {
ln_info("connected to power_supplier\n");
} else {
ln_info("FAULT_INT has occurred\n");
}
}
if (masked_int & LN8000_MASK_NTC_PROT_INT) { /* NTC_PROT_INT */
ln_info("NTC_PROT_INT has occurred(ntc_fault=%d, ntc_alarm=%d)\n",
info->tbus_tbat_fault, info->tbus_tbat_alarm);
}
if (masked_int & LN8000_MASK_CHARGE_PHASE_INT) { /* CHARGE_PHASE_INT */
if (info->vbat_regulated) {
ln_info("CHARGE_PHASE_INT: VFLOAT regulated\n");
} else if (info->iin_regulated) {
ln_info("CHARGE_PHASE_INT: IIN regulated\n");
}
}
if (masked_int & LN8000_MASK_MODE_INT) { /* MODE_INT */
switch(info->pwr_status) {
case LN8000_MASK_BYPASS_ENABLED:
ln_info("MODE_INT: device in BYPASS mode\n");
break;
case LN8000_MASK_SWITCHING_ENABLED:
ln_info("MODE_INT: device in SWITCHING mode\n");
break;
case LN8000_MASK_STANDBY_STS:
ln_info("MODE_INT: device in STANDBY mode\n");
break;
case LN8000_MASK_SHUTDOWN_STS:
ln_info("MODE_INT: device in SHUTDOWN mode\n");
break;
default:
ln_info("MODE_INT: device in unknown mode\n");
break;
}
}
if (masked_int & LN8000_MASK_TEMP_INT) { /* TEMP_INT */
ln_info("TEMP_INT has occurred(tdie_fault=%d, tdie_alarm=%d)\n",
info->tdie_fault, info->tdie_alarm);
}
if (masked_int & LN8000_MASK_TIMER_INT) { /* TIMER_INT */
ln_info("Watchdog timer has expired(wdt_fault=%d)\n", info->wdt_fault);
}
return IRQ_HANDLED;
}
static int ln8000_irq_init(struct ln8000_info *info)
{
const struct ln8000_platform_data *pdata = info->pdata;
int ret;
u8 mask, int_reg;
if (info->pdata->irq_gpio) {
info->client->irq = gpiod_to_irq(pdata->irq_gpio);
if (info->client->irq < 0) {
ln_err("fail to get irq from gpio(irq_gpio=%p)\n", pdata->irq_gpio)
info->client->irq = 0;
return -EINVAL;
}
ln_info("mapped GPIO to irq (%d)\n", info->client->irq);
}
/* interrupt mask setting */
mask = LN8000_MASK_ADC_DONE_INT | LN8000_MASK_TIMER_INT | LN8000_MASK_MODE_INT | LN8000_MASK_REV_CURR_INT;
if (info->pdata->tdie_prot_disable && info->pdata->tdie_reg_disable)
mask |= LN8000_MASK_TEMP_INT;
if (info->pdata->iin_reg_disable && info->pdata->vbat_reg_disable)
mask |= LN8000_MASK_CHARGE_PHASE_INT;
if (info->pdata->tbat_mon_disable && info->pdata->tbus_mon_disable)
mask |= LN8000_MASK_NTC_PROT_INT;
ln8000_write_reg(info, LN8000_REG_INT1_MSK, mask);
/* read clear int_reg */
ret = ln8000_read_int_value(info, &int_reg);
if (IS_ERR_VALUE((unsigned long)ret)) {
ln_err("fail to read INT reg (ret=%d)\n", ret);
return IRQ_NONE;
}
ln_info("int1_msk=0x%x\n", mask);
return 0;
}
static void determine_initial_status(struct ln8000_info *info)
{
if (info->client->irq)
ln8000_interrupt_handler(info->client->irq, info);
}
static const struct of_device_id ln8000_dt_match[] = {
{
.compatible = "lionsemi,ln8000",
.data = (void *) LN_ROLE_STANDALONE
},
{
.compatible = "lionsemi,ln8000-master",
.data = (void *) LN_ROLE_MASTER
},
{
.compatible = "lionsemi,ln8000-slave",
.data = (void *) LN_ROLE_SLAVE
},
{ },
};
MODULE_DEVICE_TABLE(of, ln8000_dt_match);
static const struct i2c_device_id ln8000_id[] = {
{ "ln8000", LN_ROLE_STANDALONE },
{ "ln8000-master", LN_ROLE_MASTER },
{ "ln8000-slave", LN_ROLE_SLAVE },
{ }
};
static int ln8000_get_dev_role(struct i2c_client *client)
{
const struct of_device_id *of_id;
of_id = of_match_device(of_match_ptr(ln8000_dt_match), &client->dev);
if (of_id == NULL) {
dev_err(&client->dev,"%s: fail to matched of_device_id\n", __func__);
return -EINVAL;
}
dev_info(&client->dev,"%s: matched to %s\n", __func__, of_id->compatible);
return (ptrdiff_t)of_id->data;
}
static int ln8000_parse_dt(struct ln8000_info *info)
{
struct device *dev = info->dev;
struct ln8000_platform_data *pdata = info->pdata;
struct device_node *np = dev->of_node;
u32 prop;
int ret;
if (np == NULL)
return -EINVAL;
/* first we try to used client->irq */
if (info->client->irq == 0) {
/* sencond we try to used irq_index info */
ret = of_property_read_u32(np, "ln8000_charger,irq_index", &prop);
if (ret < 0) {
ln_err("can't get irq_index(ret=%d)\n", ret);
prop = 0;
}
info->client->irq = prop;
}
ln_info("info->client->irq=%d\n", info->client->irq);
if (!info->client->irq) {
pdata->irq_gpio = gpiod_get(dev, "irqb", GPIOD_IN);
if (LN8000_USE_GPIO(pdata)) {
ln_info("[gpio] found valid GPIO/IRQ descriptor\n");
} else {
ln_info("[gpio] unspecified or invalid GPIO descriptor\n");
pdata->irq_gpio = 0;
}
}
/* device configuration */
ret = of_property_read_u32(np, "ln8000_charger,bat-ovp-threshold", &prop);
LN8000_PARSE_PROP(ret, pdata, bat_ovp_th, (prop*1000/*uV*/), LN8000_BAT_OVP_DEFAULT);
ret = of_property_read_u32(np, "ln8000_charger,bat-ovp-alarm-threshold", &prop);
LN8000_PARSE_PROP(ret, pdata, bat_ovp_alarm_th, (prop*1000/*uV*/), 0);
ret = of_property_read_u32(np, "ln8000_charger,bus-ovp-threshold", &prop);
LN8000_PARSE_PROP(ret, pdata, bus_ovp_th, (prop*1000/*uV*/), LN8000_BUS_OVP_DEFAULT);
ret = of_property_read_u32(np, "ln8000_charger,bus-ovp-alarm-threshold", &prop);
LN8000_PARSE_PROP(ret, pdata, bus_ovp_alarm_th, (prop*1000/*uA*/), 0);
ret = of_property_read_u32(np, "ln8000_charger,bus-ocp-threshold", &prop);
LN8000_PARSE_PROP(ret, pdata, bus_ocp_th, (prop*1000/*uA*/), LN8000_BUS_OCP_DEFAULT);
ret = of_property_read_u32(np, "ln8000_charger,bus-ocp-alarm-threshold", &prop);
LN8000_PARSE_PROP(ret, pdata, bus_ocp_alarm_th, (prop*1000/*uA*/), 0);
ret = of_property_read_u32(np, "ln8000_charger,ntc-alarm-cfg", &prop);
LN8000_PARSE_PROP(ret, pdata, ntc_alarm_cfg, prop, LN8000_NTC_ALARM_CFG_DEFAULT);
/* protection/alarm disable (defaults to enable) */
pdata->vbat_ovp_disable = of_property_read_bool(np, "ln8000_charger,vbat-ovp-disable");
pdata->vbat_reg_disable = of_property_read_bool(np, "ln8000_charger,vbat-reg-disable");
pdata->iin_ocp_disable = of_property_read_bool(np, "ln8000_charger,iin-ocp-disable");
pdata->iin_reg_disable = of_property_read_bool(np, "ln8000_charger,iin-reg-disable");
pdata->tbus_mon_disable = of_property_read_bool(np, "ln8000_charger,tbus-mon-disable");
pdata->tbat_mon_disable = of_property_read_bool(np, "ln8000_charger,tbat-mon-disable");
pdata->tdie_prot_disable = of_property_read_bool(np, "ln8000_charger,tdie-prot-disable");
pdata->tdie_reg_disable = of_property_read_bool(np, "ln8000_charger,tdie-reg-disable");
pdata->revcurr_prot_disable = of_property_read_bool(np, "ln8000_charger,revcurr-prot-disable");
/* override device tree */
if (info->dev_role == LN_ROLE_MASTER) {
ln_info("disable TS_BAT monitor for primary device on dual-mode\n");
pdata->tbat_mon_disable = true;
} else if (info->dev_role == LN_ROLE_SLAVE) {
ln_info("disable VBAT_OVP and TS_BAT for secondary device on dual-mode\n");
pdata->vbat_ovp_disable = true;
pdata->tbat_mon_disable = true;
}
ln_info("vbat_ovp_disable = %d\n", pdata->vbat_ovp_disable);
ln_info("vbat_reg_disable = %d\n", pdata->vbat_reg_disable);
ln_info("iin_ocp_disable = %d\n", pdata->iin_ocp_disable);
ln_info("iin_reg_disable = %d\n", pdata->iin_reg_disable);
ln_info("tbus_mon_disable = %d\n", pdata->tbus_mon_disable);
ln_info("tbat_mon_disable = %d\n", pdata->tbat_mon_disable);
ln_info("tdie_prot_disable = %d\n", pdata->tdie_prot_disable);
ln_info("tdie_reg_disable = %d\n", pdata->tdie_reg_disable);
ln_info("revcurr_prot_disable = %d\n", pdata->revcurr_prot_disable);
return 0;
}
static int ln8000_psy_register(struct ln8000_info *info)
{
info->psy_cfg.drv_data = info;
info->psy_cfg.of_node = info->client->dev.of_node;
info->psy_desc.name = ln8000_dev_name[info->dev_role];
info->psy_desc.type = POWER_SUPPLY_TYPE_MAINS;
info->psy_desc.properties = ln8000_charger_props;
info->psy_desc.num_properties = ARRAY_SIZE(ln8000_charger_props);
info->psy_desc.get_property = ln8000_charger_get_property;
info->psy_desc.set_property = ln8000_charger_set_property;
info->psy_desc.property_is_writeable = ln8000_charger_is_writeable;
info->psy_chg = power_supply_register(&info->client->dev, &info->psy_desc, &info->psy_cfg);
if (IS_ERR(info->psy_chg)) {
ln_err("failed to register power supply\n");
return PTR_ERR(info->psy_chg);
}
ln_info("successfully registered power supply(psy->desc->name=%s)\n", info->psy_chg->desc->name);
return 0;
}
static int ln8000_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
struct ln8000_info *info;
int ret = 0;
/* detect device on connected i2c bus */
ret = i2c_smbus_read_byte_data(client, LN8000_REG_DEVICE_ID);
if (IS_ERR_VALUE((unsigned long)ret)) {
dev_err(&client->dev, "fail to detect ln8000 on i2c_bus(addr=0x%x)\n", client->addr);
return -ENODEV;
}
dev_info(&client->dev, "device id=0x%x\n", ret);
info = devm_kzalloc(&client->dev, sizeof(struct ln8000_info), GFP_KERNEL);
if (info == NULL) {
dev_err(&client->dev, "%s: fail to alloc devm for ln8000_info\n", __func__);
return -ENOMEM;
}
info->dev_role = ln8000_get_dev_role(client);
if (IS_ERR_VALUE((unsigned long)info->dev_role)) {
kfree(info);
return -EINVAL;
}
info->pdata = devm_kzalloc(&client->dev, sizeof(struct ln8000_platform_data), GFP_KERNEL);
if (info->pdata == NULL) {
ln_err("fail to alloc devm for ln8000_platform_data\n");
kfree(info);
return -ENOMEM;
}
info->dev = &client->dev;
info->client = client;
ret = ln8000_parse_dt(info);
if (IS_ERR_VALUE((unsigned long)ret)) {
ln_err("fail to parsed dt\n");
goto err_devmem;
}
mutex_init(&info->data_lock);
mutex_init(&info->i2c_lock);
mutex_init(&info->irq_lock);
i2c_set_clientdata(client, info);
ln8000_soft_reset(info);
ln8000_init_device(info);
ret = ln8000_psy_register(info);
if (ret) {
goto err_cleanup;
}
ret = ln8000_irq_init(info);
if (ret < 0) {
goto err_psy;
}
if (client->irq) {
ret = devm_request_threaded_irq(&client->dev, client->irq,
NULL, ln8000_interrupt_handler,
IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
"ln8000-charger-irq", info);
if (ret < 0) {
ln_err("request irq for irq=%d failed, ret =%d\n",
client->irq, ret);
goto err_wakeup;
}
enable_irq_wake(client->irq);
} else {
ln_info("don't support isr(irq=%d)\n", info->client->irq);
}
device_init_wakeup(info->dev, 1);
ret = ln8000_create_debugfs_entries(info);
if (IS_ERR_VALUE((unsigned long)ret)) {
goto err_wakeup;
}
determine_initial_status(info);
hardwareinfo_set_prop(HARDWARE_SUB_CHARGER_IC_INFO, "LN8000_CHARGER");
return 0;
err_wakeup:
if (client->irq) {
free_irq(client->irq, info);
if (info->pdata->irq_gpio) {
gpiod_put(info->pdata->irq_gpio);
}
}
err_psy:
power_supply_unregister(info->psy_chg);
err_cleanup:
i2c_set_clientdata(client, NULL);
mutex_destroy(&info->data_lock);
mutex_destroy(&info->i2c_lock);
mutex_destroy(&info->irq_lock);
err_devmem:
kfree(info->pdata);
kfree(info);
return ret;
}
static int ln8000_remove(struct i2c_client *client)
{
struct ln8000_info *info = i2c_get_clientdata(client);
ln8000_change_opmode(info, LN8000_OPMODE_STANDBY);
debugfs_remove_recursive(info->debug_root);
if (client->irq) {
free_irq(client->irq, info);
if (info->pdata->irq_gpio) {
gpiod_put(info->pdata->irq_gpio);
}
}
power_supply_unregister(info->psy_chg);
i2c_set_clientdata(info->client, NULL);
mutex_destroy(&info->data_lock);
mutex_destroy(&info->i2c_lock);
mutex_destroy(&info->irq_lock);
kfree(info->pdata);
kfree(info);
return 0;
}
static void ln8000_shutdown(struct i2c_client *client)
{
struct ln8000_info *info = i2c_get_clientdata(client);
ln8000_change_opmode(info, LN8000_OPMODE_STANDBY);
}
#if defined(CONFIG_PM)
static int ln8000_suspend(struct device *dev)
{
struct ln8000_info *info = dev_get_drvdata(dev);
if (device_may_wakeup(dev) && info->client->irq)
enable_irq_wake(info->client->irq);
ln8000_irq_sleep(info, 1);
return 0;
}
static int ln8000_resume(struct device *dev)
{
struct ln8000_info *info = dev_get_drvdata(dev);
if (device_may_wakeup(dev) && info->client->irq)
disable_irq_wake(info->client->irq);
ln8000_irq_sleep(info, 0);
return 0;
}
static const struct dev_pm_ops ln8000_pm_ops = {
.suspend = ln8000_suspend,
.resume = ln8000_resume,
};
#endif
static struct i2c_driver ln8000_driver = {
.driver = {
.name = "ln8000_charger",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(ln8000_dt_match),
#if defined(CONFIG_PM)
.pm = &ln8000_pm_ops,
#endif
},
.probe = ln8000_probe,
.remove = ln8000_remove,
.shutdown = ln8000_shutdown,
.id_table = ln8000_id,
};
module_i2c_driver(ln8000_driver);
MODULE_AUTHOR("sungdae choi<sungdae@lionsemi.com>");
MODULE_DESCRIPTION("LIONSEMI LN8000 charger driver");
MODULE_LICENSE("GPL v2");
MODULE_VERSION("0.3.0");