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)
1742 lines
63 KiB
C
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, ®s[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, ®s[0]);
|
|
ln8000_read_reg(info, 0x31, ®s[1]);
|
|
ln8000_read_reg(info, 0x4A, ®s[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, ®ister_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");
|