blob: ed543c12b8dbdb8415a8182251be06dc66f89dfa [file] [log] [blame]
/*
* dc_xpwr_battery.c - Dollar Cove(X-power) Battery driver
*
* Copyright (C) 2014 Intel Corporation
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* 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; version 2 of the License.
*
* 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.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Author: Ramakrishna Pallala <ramakrishna.pallala@intel.com>
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/err.h>
#include <linux/param.h>
#include <linux/jiffies.h>
#include <linux/interrupt.h>
#include <linux/device.h>
#include <linux/workqueue.h>
#include <linux/gpio.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/power_supply.h>
#include <linux/idr.h>
#include <linux/i2c.h>
#include <linux/slab.h>
#include <asm/unaligned.h>
#include <linux/notifier.h>
#include <linux/acpi.h>
#include <linux/acpi_gpio.h>
#include <linux/iio/consumer.h>
#include <linux/mfd/intel_mid_pmic.h>
#include <linux/power/dc_xpwr_battery.h>
#include <linux/intel_fg_helper.h>
#define DC_PS_STAT_REG 0x00
#define PS_STAT_VBUS_TRIGGER (1 << 0)
#define PS_STAT_BAT_CHRG_DIR (1 << 2)
#define PS_STAT_VBUS_ABOVE_VHOLD (1 << 3)
#define PS_STAT_VBUS_VALID (1 << 4)
#define PS_STAT_VBUS_PRESENT (1 << 5)
#define DC_CHRG_STAT_REG 0x01
#define CHRG_STAT_BAT_SAFE_MODE (1 << 3)
#define CHRG_STAT_BAT_VALID (1 << 4)
#define CHRG_STAT_BAT_PRESENT (1 << 5)
#define CHRG_STAT_CHARGING (1 << 6)
#define CHRG_STAT_PMIC_OTP (1 << 7)
#define DC_CHRG_CCCV_REG 0x33
#define CHRG_CCCV_CC_MASK 0xf /* 4 bits */
#define CHRG_CCCV_CC_BIT_POS 0
#define CHRG_CCCV_CC_OFFSET 200 /* 200mA */
#define CHRG_CCCV_CC_LSB_RES 200 /* 200mA */
#define CHRG_CCCV_ITERM_20P (1 << 4) /* 20% of CC */
#define CHRG_CCCV_CV_MASK 0x60 /* 2 bits */
#define CHRG_CCCV_CV_BIT_POS 5
#define CHRG_CCCV_CV_4100MV 0x0 /* 4.10V */
#define CHRG_CCCV_CV_4150MV 0x1 /* 4.15V */
#define CHRG_CCCV_CV_4200MV 0x2 /* 4.20V */
#define CHRG_CCCV_CV_4350MV 0x3 /* 4.35V */
#define CHRG_CCCV_CHG_EN (1 << 7)
#define CV_4100 4100 /* 4100mV */
#define CV_4150 4150 /* 4150mV */
#define CV_4200 4200 /* 4200mV */
#define CV_4350 4350 /* 4350mV */
#define DC_FG_VLTFW_REG 0x3C
#define FG_VLTFW_0C 0xA5 /* 0 DegC */
#define DC_FG_VHTFW_REG 0x3D
#define FG_VHTFW_56C 0x1E /* 45 DegC */
#define DC_TEMP_IRQ_CFG_REG 0x42
#define TEMP_IRQ_CFG_QWBTU (1 << 0)
#define TEMP_IRQ_CFG_WBTU (1 << 1)
#define TEMP_IRQ_CFG_QWBTO (1 << 2)
#define TEMP_IRQ_CFG_WBTO (1 << 3)
#define TEMP_IRQ_CFG_MASK 0xf
#define DC_FG_IRQ_CFG_REG 0x43
#define FG_IRQ_CFG_LOWBATT_WL2 (1 << 0)
#define FG_IRQ_CFG_LOWBATT_WL1 (1 << 1)
#define FG_IRQ_CFG_LOWBATT_MASK 0x1
#define DC_LOWBAT_IRQ_STAT_REG 0x4B
#define LOWBAT_IRQ_STAT_LOWBATT_WL2 (1 << 0)
#define LOWBAT_IRQ_STAT_LOWBATT_WL1 (1 << 1)
#define DC_FG_CNTL_REG 0xB8
#define FG_CNTL_OCV_ADJ_STAT (1 << 2)
#define FG_CNTL_OCV_ADJ_EN (1 << 3)
#define FG_CNTL_CAP_ADJ_STAT (1 << 4)
#define FG_CNTL_CAP_ADJ_EN (1 << 5)
#define FG_CNTL_CC_EN (1 << 6)
#define FG_CNTL_GAUGE_EN (1 << 7)
#define FG_CNTL_DEF_EN_MASK 0xff
#define DC_FG_REP_CAP_REG 0xB9
#define FG_REP_CAP_VALID (1 << 7)
#define FG_REP_CAP_VAL_MASK 0x7F
#define DC_FG_RDC1_REG 0xBA
#define DC_FG_RDC0_REG 0xBB
#define DC_FG_OCVH_REG 0xBC
#define DC_FG_OCVL_REG 0xBD
#define DC_FG_OCV_CURVE_REG 0xC0
#define DC_FG_DES_CAP1_REG 0xE0
#define FG_DES_CAP1_VALID (1 << 7)
#define FG_DES_CAP1_VAL_MASK 0x7F
#define DC_FG_DES_CAP0_REG 0xE1
#define FG_DES_CAP0_VAL_MASK 0xFF
#define FG_DES_CAP_RES_LSB 1456 /* 1.456mAhr */
#define DC_FG_CC_MTR1_REG 0xE2
#define FG_CC_MTR1_VALID (1 << 7)
#define FG_CC_MTR1_VAL_MASK 0x7F
#define DC_FG_CC_MTR0_REG 0xE3
#define FG_CC_MTR0_VAL_MASK 0xFF
#define FG_DES_CC_RES_LSB 1456 /* 1.456mAhr */
#define DC_FG_OCV_CAP_REG 0xE4
#define FG_OCV_CAP_VALID (1 << 7)
#define FG_OCV_CAP_VAL_MASK 0x7F
#define DC_FG_CC_CAP_REG 0xE5
#define FG_CC_CAP_VALID (1 << 7)
#define FG_CC_CAP_VAL_MASK 0x7F
#define DC_FG_LOW_CAP_REG 0xE6
#define FG_LOW_CAP_THR1_MASK 0xf0 /* 5% tp 20% */
#define FG_LOW_CAP_THR1_VAL 0xa0 /* 15 perc */
#define FG_LOW_CAP_THR2_MASK 0x0f /* 0% to 15% */
#define FG_LOW_CAP_WARN1_THR 15 /* 15 perc */
#define FG_LOW_CAP_WARN2_THR 10 /* 10 perc */
#define FG_LOW_CAP_CRIT_THR 5 /* 5 perc */
#define FG_LOW_CAP_SHDN_THR 0 /* 0 perc */
#define DC_FG_TUNING_CNTL0 0xE8
#define DC_FG_TUNING_CNTL1 0xE9
#define DC_FG_TUNING_CNTL2 0xEA
#define DC_FG_TUNING_CNTL3 0xEB
#define DC_FG_TUNING_CNTL4 0xEC
#define DC_FG_TUNING_CNTL5 0xED
/* each LSB is equal to 1.1mV */
#define ADC_TO_VBATT(a) ((a * 11) / 10)
/* each LSB is equal to 1mA */
#define ADC_TO_BATCUR(a) (a)
/* each LSB is equal to 1mA */
#define ADC_TO_PMICTEMP(a) (a - 267)
#define STATUS_MON_DELAY_JIFFIES (HZ * 60) /*60 sec */
#define DC_FG_INTR_NUM 6
#define THERM_CURVE_MAX_SAMPLES 18
#define THERM_CURVE_MAX_VALUES 4
/* No of times we should retry on -EAGAIN error */
#define NR_RETRY_CNT 3
#define DEV_NAME "dollar_cove_battery"
#define BATT_OVP_OFFSET 50 /* 50mV */
enum {
QWBTU_IRQ = 0,
WBTU_IRQ,
QWBTO_IRQ,
WBTO_IRQ,
WL2_IRQ,
WL1_IRQ,
};
struct pmic_fg_info {
struct platform_device *pdev;
struct dollarcove_fg_pdata *pdata;
int irq[DC_FG_INTR_NUM];
struct power_supply bat;
struct mutex lock;
struct dc_xpwr_fg_cfg *cfg;
bool fg_init_done;
int status;
int btemp;
int health;
int ext_set_cap;
/* Worker to monitor status and faults */
struct delayed_work status_monitor;
};
static struct pmic_fg_info *info_ptr;
static enum power_supply_property pmic_fg_props[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_HEALTH,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
POWER_SUPPLY_PROP_VOLTAGE_OCV,
POWER_SUPPLY_PROP_CURRENT_NOW,
POWER_SUPPLY_PROP_CAPACITY,
POWER_SUPPLY_PROP_TEMP,
POWER_SUPPLY_PROP_TECHNOLOGY,
POWER_SUPPLY_PROP_CHARGE_FULL,
POWER_SUPPLY_PROP_CHARGE_NOW,
POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
POWER_SUPPLY_PROP_MODEL_NAME,
};
/*
* This array represents the Battery Pack thermistor
* temperature and corresponding ADC value limits
*/
static int const therm_curve_data[THERM_CURVE_MAX_SAMPLES]
[THERM_CURVE_MAX_VALUES] = {
/* {temp_max, temp_min, adc_max, adc_min} */
{-15, -20, 682, 536},
{-10, -15, 536, 425},
{-5, -10, 425, 338},
{0, -5, 338, 272},
{5, 0, 272, 220},
{10, 5, 220, 179},
{15, 10, 179, 146},
{20, 15, 146, 120},
{25, 20, 120, 100},
{30, 25, 100, 83},
{35, 30, 83, 69},
{40, 35, 69, 58},
{45, 40, 58, 49},
{50, 45, 49, 41},
{55, 50, 41, 35},
{60, 55, 35, 30},
{65, 60, 30, 25},
{70, 65, 25, 22},
};
static int pmic_fg_reg_setb(struct pmic_fg_info *info, int reg, u8 mask)
{
int ret;
ret = intel_mid_pmic_setb(reg, mask);
if (ret < 0)
dev_err(&info->pdev->dev, "pmic reg set mask err:%d\n", ret);
return ret;
}
static int pmic_fg_reg_clearb(struct pmic_fg_info *info, int reg, u8 mask)
{
int ret;
ret = intel_mid_pmic_clearb(reg, mask);
if (ret < 0)
dev_err(&info->pdev->dev, "pmic reg set mask err:%d\n", ret);
return ret;
}
static int pmic_fg_reg_readb(struct pmic_fg_info *info, int reg)
{
int ret, i;
for (i = 0; i < NR_RETRY_CNT; i++) {
ret = intel_mid_pmic_readb(reg);
if (ret == -EAGAIN || ret == -ETIMEDOUT)
continue;
else
break;
}
if (ret < 0)
dev_err(&info->pdev->dev, "pmic reg read err:%d\n", ret);
return ret;
}
static int pmic_fg_reg_writeb(struct pmic_fg_info *info, int reg, u8 val)
{
int ret, i;
for (i = 0; i < NR_RETRY_CNT; i++) {
ret = intel_mid_pmic_writeb(reg, val);
if (ret == -EAGAIN || ret == -ETIMEDOUT)
continue;
else
break;
}
if (ret < 0)
dev_err(&info->pdev->dev, "pmic reg write err:%d\n", ret);
return ret;
}
static void pmic_fg_dump_init_regs(struct pmic_fg_info *info)
{
int i;
dev_info(&info->pdev->dev, "reg:%x, val:%x\n",
DC_CHRG_CCCV_REG,
pmic_fg_reg_readb(info, DC_CHRG_CCCV_REG));
for (i = 0; i < BAT_CURVE_SIZE; i++) {
dev_info(&info->pdev->dev, "reg:%x, val:%x\n",
DC_FG_OCV_CURVE_REG + i,
pmic_fg_reg_readb(info, DC_FG_OCV_CURVE_REG + i));
}
dev_info(&info->pdev->dev, "reg:%x, val:%x\n",
DC_FG_CNTL_REG,
pmic_fg_reg_readb(info, DC_FG_CNTL_REG));
dev_info(&info->pdev->dev, "reg:%x, val:%x\n",
DC_FG_DES_CAP1_REG,
pmic_fg_reg_readb(info, DC_FG_DES_CAP1_REG));
dev_info(&info->pdev->dev, "reg:%x, val:%x\n",
DC_FG_DES_CAP0_REG,
pmic_fg_reg_readb(info, DC_FG_DES_CAP0_REG));
dev_info(&info->pdev->dev, "reg:%x, val:%x\n",
DC_FG_RDC1_REG,
pmic_fg_reg_readb(info, DC_FG_RDC1_REG));
dev_info(&info->pdev->dev, "reg:%x, val:%x\n",
DC_FG_RDC0_REG,
pmic_fg_reg_readb(info, DC_FG_RDC0_REG));
}
static int conv_adc_temp(int adc_val, int adc_max, int adc_diff, int temp_diff)
{
int ret;
ret = (adc_max - adc_val) * temp_diff;
return ret / adc_diff;
}
static bool is_valid_temp_adc_range(int val, int min, int max)
{
if (val > min && val <= max)
return true;
else
return false;
}
static int dc_xpwr_get_batt_temp(int adc_val, int *temp)
{
int i;
for (i = 0; i < THERM_CURVE_MAX_SAMPLES; i++) {
/* linear approximation for battery pack temperature */
if (is_valid_temp_adc_range(adc_val, therm_curve_data[i][3],
therm_curve_data[i][2])) {
*temp = conv_adc_temp(adc_val, therm_curve_data[i][2],
therm_curve_data[i][2] -
therm_curve_data[i][3],
therm_curve_data[i][0] -
therm_curve_data[i][1]);
*temp += therm_curve_data[i][1];
break;
}
}
if (i >= THERM_CURVE_MAX_SAMPLES)
return -ERANGE;
return 0;
}
/**
* pmic_read_adc_val - read ADC value of specified sensors
* @channel: channel of the sensor to be sampled
* @sensor_val: pointer to the charger property to hold sampled value
* @chc : battery info pointer
*
* Returns 0 if success
*/
static int pmic_read_adc_val(const char *map, const char *name,
int *raw_val, struct pmic_fg_info *info)
{
int ret, val;
struct iio_channel *indio_chan;
indio_chan = iio_channel_get(NULL, name);
if (IS_ERR_OR_NULL(indio_chan)) {
ret = PTR_ERR(indio_chan);
goto exit;
}
ret = iio_read_channel_raw(indio_chan, &val);
if (ret) {
dev_err(&info->pdev->dev, "IIO channel read error\n");
goto err_exit;
}
dev_dbg(&info->pdev->dev, "adc raw val=%x\n", val);
*raw_val = val;
err_exit:
iio_channel_release(indio_chan);
exit:
return ret;
}
static int pmic_fg_get_vbatt(struct pmic_fg_info *info, int *vbatt)
{
int ret, raw_val;
ret = pmic_read_adc_val("VIBAT", "VBAT", &raw_val, info);
if (ret < 0)
goto vbatt_read_fail;
*vbatt = ADC_TO_VBATT(raw_val);
vbatt_read_fail:
return ret;
}
static int pmic_fg_get_current(struct pmic_fg_info *info, int *cur)
{
int ret = 0, raw_val, sign;
int pwr_stat;
pwr_stat = pmic_fg_reg_readb(info, DC_PS_STAT_REG);
if (pwr_stat < 0) {
dev_err(&info->pdev->dev, "PWR STAT read failed:%d\n", pwr_stat);
return pwr_stat;
}
if (pwr_stat & PS_STAT_BAT_CHRG_DIR) {
sign = 1;
ret = pmic_read_adc_val("CURRENT", "BATCCUR", &raw_val, info);
} else {
sign = -1;
ret = pmic_read_adc_val("CURRENT", "BATDCUR", &raw_val, info);
}
if (ret < 0)
goto cur_read_fail;
*cur = raw_val * sign;
cur_read_fail:
return ret;
}
static int pmic_fg_get_btemp(struct pmic_fg_info *info, int *btemp)
{
int ret, raw_val;
ret = pmic_read_adc_val("THERMAL", "BATTEMP", &raw_val, info);
if (ret < 0)
goto btemp_read_fail;
/*
* Convert the TS pin ADC codes in to 10's of Kohms
* by deviding the ADC code with 10 and pass it to
* the Thermistor look up function.
*/
ret = dc_xpwr_get_batt_temp(raw_val / 10, btemp);
if (ret < 0)
dev_warn(&info->pdev->dev, "ADC conversion error%d\n", ret);
else
dev_dbg(&info->pdev->dev,
"ADC code:%d, TEMP:%d\n", raw_val, *btemp);
btemp_read_fail:
return ret;
}
static int pmic_fg_get_vocv(struct pmic_fg_info *info, int *vocv)
{
int ret, value;
/*
* OCV readings are 12-bit length. So Read
* the MSB first left-shift by 4 bits and
* read the lower nibble.
*/
ret = pmic_fg_reg_readb(info, DC_FG_OCVH_REG);
if (ret < 0)
goto vocv_read_fail;
value = ret << 4;
ret = pmic_fg_reg_readb(info, DC_FG_OCVL_REG);
if (ret < 0)
goto vocv_read_fail;
value |= (ret & 0xf);
*vocv = ADC_TO_VBATT(value);
vocv_read_fail:
return ret;
}
static int pmic_fg_get_capacity(struct pmic_fg_info *info)
{
int ret, value, cap, cc_cap;
ret = pmic_fg_get_vocv(info, &value);
if (ret < 0)
return ret;
/* do Vocv min threshold check */
if (value < info->pdata->design_min_volt)
return 0;
ret = pmic_fg_reg_readb(info, DC_FG_REP_CAP_REG);
if (ret < 0)
return ret;
if (!(ret & FG_REP_CAP_VALID))
dev_err(&info->pdev->dev,
"capacity measurement not valid\n");
/*
* read the coulomb meter capacity to report SOC
* when the FULL is detected, as RepSoC is not
* hitting 100% in some cases.
*/
if (info->status == POWER_SUPPLY_STATUS_FULL) {
cc_cap = pmic_fg_reg_readb(info, DC_FG_CC_CAP_REG);
if (cc_cap < 0)
return cc_cap;
cap = (cc_cap & FG_CC_CAP_VAL_MASK);
} else {
cap = (ret & FG_REP_CAP_VAL_MASK);
}
return cap;
}
static int pmic_fg_battery_health(struct pmic_fg_info *info)
{
int temp, vocv;
int ret, health = POWER_SUPPLY_HEALTH_UNKNOWN;
if (info->pdata->technology != POWER_SUPPLY_TECHNOLOGY_LION) {
dev_err(&info->pdev->dev, "Invalid battery detected");
return POWER_SUPPLY_HEALTH_UNKNOWN;
}
ret = pmic_fg_get_btemp(info, &temp);
if (ret < 0)
goto health_read_fail;
info->btemp = temp;
ret = pmic_fg_get_vocv(info, &vocv);
if (ret < 0)
goto health_read_fail;
if (vocv > (info->pdata->design_max_volt + BATT_OVP_OFFSET))
health = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
else if (temp >= info->pdata->max_temp ||
temp <= info->pdata->min_temp)
health = POWER_SUPPLY_HEALTH_OVERHEAT;
else if (vocv < info->pdata->design_min_volt)
health = POWER_SUPPLY_HEALTH_DEAD;
else
health = POWER_SUPPLY_HEALTH_GOOD;
health_read_fail:
return health;
}
static int pmic_fg_get_charge_now(struct pmic_fg_info *info, int *value)
{
int ret;
ret = pmic_fg_reg_readb(info, DC_FG_CC_MTR1_REG);
if (ret < 0)
goto pmic_fg_cnow_err;
*value = (ret & FG_CC_MTR1_VAL_MASK) << 8;
/*
* higher byte and lower byte reads should be
* back to back to get successful lower byte result.
*/
pmic_fg_reg_readb(info, DC_FG_CC_MTR1_REG);
ret = pmic_fg_reg_readb(info, DC_FG_CC_MTR0_REG);
if (ret < 0)
goto pmic_fg_cnow_err;
*value |= (ret & FG_CC_MTR0_VAL_MASK);
*value *= FG_DES_CAP_RES_LSB;
return 0;
pmic_fg_cnow_err:
return ret;
}
static int pmic_fg_get_charge_full(struct pmic_fg_info *info, int *value)
{
int ret;
ret = pmic_fg_reg_readb(info, DC_FG_DES_CAP1_REG);
if (ret < 0)
goto pmic_fg_cfull_err;
*value = (ret & FG_DES_CAP1_VAL_MASK) << 8;
/*
* higher byte and lower byte reads should be
* back to back to get successful lower byte result.
*/
pmic_fg_reg_readb(info, DC_FG_DES_CAP1_REG);
ret = pmic_fg_reg_readb(info, DC_FG_DES_CAP0_REG);
if (ret < 0)
goto pmic_fg_cfull_err;
*value |= (ret & FG_DES_CAP0_VAL_MASK);
*value *= FG_DES_CAP_RES_LSB;
return 0;
pmic_fg_cfull_err:
return ret;
}
static int pmic_fg_get_battery_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct pmic_fg_info *info = container_of(psy,
struct pmic_fg_info, bat);
int ret = 0, value, cc_cap;
mutex_lock(&info->lock);
switch (psp) {
case POWER_SUPPLY_PROP_STATUS:
val->intval = info->status;
break;
case POWER_SUPPLY_PROP_HEALTH:
val->intval = pmic_fg_battery_health(info);
info->health = val->intval;
break;
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
ret = pmic_fg_get_vbatt(info, &value);
if (ret < 0)
goto pmic_fg_read_err;
val->intval = value * 1000;
break;
case POWER_SUPPLY_PROP_VOLTAGE_OCV:
ret = pmic_fg_get_vocv(info, &value);
if (ret < 0)
goto pmic_fg_read_err;
val->intval = value * 1000;
break;
case POWER_SUPPLY_PROP_CURRENT_NOW:
ret = pmic_fg_get_current(info, &value);
if (ret < 0)
goto pmic_fg_read_err;
val->intval = value * 1000;
break;
case POWER_SUPPLY_PROP_PRESENT:
ret = pmic_fg_reg_readb(info, DC_CHRG_STAT_REG);
if (ret < 0)
goto pmic_fg_read_err;
if (ret & CHRG_STAT_BAT_PRESENT)
val->intval = 1;
else
val->intval = 0;
break;
case POWER_SUPPLY_PROP_CAPACITY:
/*
* Check whether the capacity is set externally or not. If the
* capacity value is set externally, use same as the SOC value
* for the battery level usage.
*/
if ((info->ext_set_cap) >= 0 && (info->ext_set_cap <= 100)) {
val->intval = info->ext_set_cap;
break;
}
ret = pmic_fg_get_capacity(info);
if (ret < 0)
goto pmic_fg_read_err;
val->intval = ret;
break;
case POWER_SUPPLY_PROP_TEMP:
ret = pmic_fg_get_btemp(info, &value);
if (ret < 0)
goto pmic_fg_read_err;
val->intval = value * 10;
break;
case POWER_SUPPLY_PROP_TECHNOLOGY:
val->intval = info->pdata->technology;
break;
case POWER_SUPPLY_PROP_CHARGE_NOW:
ret = pmic_fg_get_charge_now(info, &value);
if (ret < 0)
goto pmic_fg_read_err;
val->intval = value;
break;
case POWER_SUPPLY_PROP_CHARGE_FULL:
ret = pmic_fg_get_charge_full(info, &value);
if (ret < 0)
goto pmic_fg_read_err;
val->intval = value;
break;
case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
val->intval = info->pdata->design_cap * 1000;
break;
case POWER_SUPPLY_PROP_MODEL_NAME:
val->strval = info->pdata->battid;
break;
default:
mutex_unlock(&info->lock);
return -EINVAL;
}
mutex_unlock(&info->lock);
return 0;
pmic_fg_read_err:
mutex_unlock(&info->lock);
return ret;
}
static int pmic_fg_set_battery_property(struct power_supply *psy,
enum power_supply_property psp,
const union power_supply_propval *val)
{
struct pmic_fg_info *info = container_of(psy,
struct pmic_fg_info, bat);
int ret = 0;
mutex_lock(&info->lock);
switch (psp) {
case POWER_SUPPLY_PROP_STATUS:
info->status = val->intval;
break;
case POWER_SUPPLY_PROP_CAPACITY:
if ((val->intval >= 0) && (val->intval <= 100))
info->ext_set_cap = val->intval;
break;
default:
ret = -EINVAL;
break;
}
mutex_unlock(&info->lock);
return ret;
}
static int pmic_fg_update_config_params(struct pmic_fg_info *info)
{
int ret, i;
ret = pmic_fg_reg_readb(info, DC_FG_DES_CAP1_REG);
if (ret < 0)
goto fg_svae_cfg_fail;
info->cfg->cap1 = ret;
/*
* higher byte and lower byte reads should be
* back to back to get successful lower byte result.
*/
pmic_fg_reg_readb(info, DC_FG_DES_CAP1_REG);
ret = pmic_fg_reg_readb(info, DC_FG_DES_CAP0_REG);
if (ret < 0)
goto fg_svae_cfg_fail;
else
info->cfg->cap0 = ret;
ret = pmic_fg_reg_readb(info, DC_FG_RDC1_REG);
if (ret < 0)
goto fg_svae_cfg_fail;
else
info->cfg->rdc1 = ret;
/*
* higher byte and lower byte reads should be
* back to back to get successful lower byte result.
*/
pmic_fg_reg_readb(info, DC_FG_RDC1_REG);
ret = pmic_fg_reg_readb(info, DC_FG_RDC0_REG);
if (ret < 0)
goto fg_svae_cfg_fail;
else
info->cfg->rdc0 = ret;
for (i = 0; i < BAT_CURVE_SIZE; i++) {
ret = pmic_fg_reg_readb(info, DC_FG_OCV_CURVE_REG + i);
if (ret < 0)
goto fg_svae_cfg_fail;
else
info->cfg->bat_curve[i] = ret;
}
return 0;
fg_svae_cfg_fail:
return ret;
}
static void pmic_fg_status_monitor(struct work_struct *work)
{
struct pmic_fg_info *info = container_of(work,
struct pmic_fg_info, status_monitor.work);
static int cache_cap = -1, cache_health = POWER_SUPPLY_HEALTH_UNKNOWN
, cache_temp = INT_MAX;
int present_cap, present_health;
mutex_lock(&info->lock);
present_cap = pmic_fg_get_capacity(info);
if (present_cap < 0) {
mutex_unlock(&info->lock);
goto end_stat_mon;
}
/*
*temp and ocv values are read here.
*/
present_health = pmic_fg_battery_health(info);
mutex_unlock(&info->lock);
/*
*PSY change event is sent only upon change in
*health,cap,temp.
*/
if ((cache_health != present_health)
|| (cache_cap != present_cap)
|| (info->btemp != cache_temp)) {
power_supply_changed(&info->bat);
cache_cap = present_cap;
cache_health = present_health;
cache_temp = info->btemp;
}
end_stat_mon:
schedule_delayed_work(&info->status_monitor, STATUS_MON_DELAY_JIFFIES);
}
static irqreturn_t pmic_fg_thread_handler(int irq, void *dev)
{
struct pmic_fg_info *info = dev;
int i;
for (i = 0; i < DC_FG_INTR_NUM; i++) {
if (info->irq[i] == irq)
break;
}
if (i >= DC_FG_INTR_NUM) {
dev_warn(&info->pdev->dev, "spurious interrupt!!\n");
return IRQ_NONE;
}
switch (i) {
case QWBTU_IRQ:
dev_info(&info->pdev->dev,
"Quit Battery Under Temperature(DISCHRG) INTR\n");
break;
case WBTU_IRQ:
dev_info(&info->pdev->dev,
"Hit Battery Over Temperature(DISCHRG) INTR\n");
break;
case QWBTO_IRQ:
dev_info(&info->pdev->dev,
"Quit Battery Over Temperature(DISCHRG) INTR\n");
break;
case WBTO_IRQ:
dev_info(&info->pdev->dev,
"Hit Battery Over Temperature(DISCHRG) INTR\n");
break;
case WL2_IRQ:
dev_info(&info->pdev->dev, "Low Batt Warning(2) INTR\n");
break;
case WL1_IRQ:
dev_info(&info->pdev->dev, "Low Batt Warning(1) INTR\n");
break;
default:
dev_warn(&info->pdev->dev, "Spurious Interrupt!!!\n");
}
power_supply_changed(&info->bat);
return IRQ_HANDLED;
}
static void pmic_fg_external_power_changed(struct power_supply *psy)
{
struct pmic_fg_info *info = container_of(psy,
struct pmic_fg_info, bat);
power_supply_changed(&info->bat);
}
static int pmic_fg_set_lowbatt_thresholds(struct pmic_fg_info *info)
{
int ret;
u8 reg_val, reg_thr;
ret = pmic_fg_reg_readb(info, DC_FG_REP_CAP_REG);
if (ret < 0) {
dev_err(&info->pdev->dev, "%s:read err:%d\n", __func__, ret);
return ret;
}
ret = (ret & FG_REP_CAP_VAL_MASK);
if (ret > FG_LOW_CAP_WARN1_THR)
reg_val = FG_LOW_CAP_WARN1_THR;
else if (ret > FG_LOW_CAP_WARN2_THR)
reg_val = FG_LOW_CAP_WARN2_THR;
else if (ret > FG_LOW_CAP_CRIT_THR)
reg_val = FG_LOW_CAP_CRIT_THR;
/*set low battery alert, which is 14% for the first level,
* 4% and 0% for the sencond level, the value will impact PMIC auto calibration
* this default value is recommended by PMIC vendor*/
if (ret > FG_LOW_CAP_CRIT_THR) {
ret = pmic_fg_reg_readb(info, DC_FG_TUNING_CNTL4);
if (ret < 0) {
dev_err(&info->pdev->dev, "%s:read err:%d\n", __func__, ret);
return ret;
}
reg_thr = ret & 0xF8;
reg_thr |= 0x04;
ret = pmic_fg_reg_writeb(info, DC_FG_TUNING_CNTL4, reg_thr);
if (ret < 0) {
dev_err(&info->pdev->dev, "%s:write err:%d\n", __func__, ret);
return ret;
}
} else {
reg_val = FG_LOW_CAP_SHDN_THR;
ret = pmic_fg_reg_readb(info, DC_FG_TUNING_CNTL4);
if (ret < 0) {
dev_err(&info->pdev->dev, "%s:read err:%d\n", __func__, ret);
return ret;
}
reg_thr = ret & 0xF8;
ret = pmic_fg_reg_writeb(info, DC_FG_TUNING_CNTL4, reg_thr);
if (ret < 0) {
dev_err(&info->pdev->dev, "%s:write err:%d\n", __func__, ret);
return ret;
}
}
reg_val |= 0x90;
ret = pmic_fg_reg_writeb(info, DC_FG_LOW_CAP_REG, reg_val);
if (ret < 0)
dev_err(&info->pdev->dev, "%s:write err:%d\n", __func__, ret);
return ret;
}
static int pmic_fg_program_vbatt_full(struct pmic_fg_info *info)
{
int ret;
u8 val;
ret = pmic_fg_reg_readb(info, DC_CHRG_CCCV_REG);
if (ret < 0)
goto fg_prog_ocv_fail;
else
val = (ret & ~CHRG_CCCV_CV_MASK);
switch (info->pdata->design_max_volt) {
case CV_4100:
val |= (CHRG_CCCV_CV_4100MV << CHRG_CCCV_CV_BIT_POS);
break;
case CV_4150:
val |= (CHRG_CCCV_CV_4150MV << CHRG_CCCV_CV_BIT_POS);
break;
case CV_4200:
val |= (CHRG_CCCV_CV_4200MV << CHRG_CCCV_CV_BIT_POS);
break;
case CV_4350:
val |= (CHRG_CCCV_CV_4350MV << CHRG_CCCV_CV_BIT_POS);
break;
default:
val |= (CHRG_CCCV_CV_4200MV << CHRG_CCCV_CV_BIT_POS);
break;
}
ret = pmic_fg_reg_writeb(info, DC_CHRG_CCCV_REG, val);
fg_prog_ocv_fail:
return ret;
}
static int pmic_fg_program_design_cap(struct pmic_fg_info *info)
{
int ret = 0;
int cap1, cap0;
/*
ret = pmic_fg_reg_writeb(info, DC_FG_DES_CAP1_REG, info->pdata->cap1);
if (ret < 0)
goto fg_prog_descap_fail;
*/
cap1 = pmic_fg_reg_readb(info, DC_FG_DES_CAP1_REG);
if (cap1 < 0) {
dev_warn(&info->pdev->dev, "CAP1 reg read err!!\n");
return ret;
}
cap0 = pmic_fg_reg_readb(info, DC_FG_DES_CAP0_REG);
if (cap1 == info->cfg->cap1 && cap0 == info->cfg->cap0) {
dev_info(&info->pdev->dev, "FG data is already initialized\n");
return 0;
} else {
dev_info(&info->pdev->dev, "FG data need to be initialized\n");
}
/*Disable coulomb meter*/
ret = pmic_fg_reg_clearb(info, DC_FG_CNTL_REG, FG_CNTL_CC_EN);
ret = pmic_fg_reg_writeb(info, DC_FG_DES_CAP1_REG, info->cfg->cap1);
ret = pmic_fg_reg_writeb(info, DC_FG_DES_CAP0_REG, info->cfg->cap0);
ret = pmic_fg_reg_setb(info, DC_FG_CNTL_REG, FG_CNTL_CC_EN);
return 0;
}
static int pmic_fg_program_ocv_curve(struct pmic_fg_info *info)
{
int ret, i;
for (i = 0; i < BAT_CURVE_SIZE; i++) {
ret = pmic_fg_reg_writeb(info,
DC_FG_OCV_CURVE_REG + i, info->cfg->bat_curve[i]);
if (ret < 0)
goto fg_prog_ocv_fail;
}
fg_prog_ocv_fail:
return ret;
}
static int pmic_fg_program_rdc_vals(struct pmic_fg_info *info)
{
int ret = 0;
int rdc1, rdc0;
rdc1 = pmic_fg_reg_readb(info, DC_FG_RDC1_REG);
if (rdc1 < 0) {
dev_warn(&info->pdev->dev, "RDC1 reg read err!!\n");
return ret;
}
rdc0 = pmic_fg_reg_readb(info, DC_FG_RDC1_REG);
if (rdc1 == info->cfg->rdc1 && rdc0 == info->cfg->rdc0) {
dev_info(&info->pdev->dev, "RDC is already initialized\n");
return 0;
} else {
dev_info(&info->pdev->dev, "RDC need to be initialized\n");
}
ret = pmic_fg_reg_writeb(info, DC_FG_RDC1_REG, info->cfg->rdc1);
ret = pmic_fg_reg_writeb(info, DC_FG_RDC0_REG, info->cfg->rdc0);
ret = pmic_fg_reg_clearb(info, DC_FG_TUNING_CNTL4, (1<<3));
ret = pmic_fg_reg_setb(info, DC_FG_TUNING_CNTL4, (1<<4));
return ret;
}
static void pmic_fg_init_config_regs(struct pmic_fg_info *info)
{
int ret;
/*
* check if the config data is already
* programmed and if so just return.
*/
ret = pmic_fg_reg_readb(info, DC_FG_CNTL_REG);
if (ret < 0) {
dev_warn(&info->pdev->dev, "FG CNTL reg read err!!\n");
} else if ((ret & FG_CNTL_OCV_ADJ_EN) && (ret & FG_CNTL_CAP_ADJ_EN)) {
dev_info(&info->pdev->dev,
"FG data except the OCV curve is initialized\n");
/*
* ocv curve will be set to default values
* at every boot, so it is needed to explicitly write
* the ocv curve data for each boot
*/
ret = pmic_fg_program_ocv_curve(info);
if (ret < 0)
dev_err(&info->pdev->dev,
"set ocv curve fail:%d\n", ret);
info->fg_init_done = true;
pmic_fg_dump_init_regs(info);
return;
} else {
dev_info(&info->pdev->dev, "FG data need to be initialized\n");
}
ret = pmic_fg_program_ocv_curve(info);
if (ret < 0)
dev_err(&info->pdev->dev, "set ocv curve fail:%d\n", ret);
ret = pmic_fg_program_rdc_vals(info);
if (ret < 0)
dev_err(&info->pdev->dev, "set rdc fail:%d\n", ret);
ret = pmic_fg_program_design_cap(info);
if (ret < 0)
dev_err(&info->pdev->dev, "set design cap fail:%d\n", ret);
ret = pmic_fg_program_vbatt_full(info);
if (ret < 0)
dev_err(&info->pdev->dev, "set vbatt full fail:%d\n", ret);
ret = pmic_fg_set_lowbatt_thresholds(info);
if (ret < 0)
dev_err(&info->pdev->dev, "lowbatt thr set fail:%d\n", ret);
ret = pmic_fg_reg_writeb(info, DC_FG_CNTL_REG, 0xff);
if (ret < 0)
dev_err(&info->pdev->dev, "gauge cntl set fail:%d\n", ret);
info->fg_init_done = true;
pmic_fg_dump_init_regs(info);
}
static void pmic_fg_init_irq(struct pmic_fg_info *info)
{
int ret, i;
for (i = 0; i < DC_FG_INTR_NUM; i++) {
info->irq[i] = platform_get_irq(info->pdev, i);
ret = request_threaded_irq(info->irq[i],
NULL, pmic_fg_thread_handler,
IRQF_ONESHOT, DEV_NAME, info);
if (ret) {
dev_warn(&info->pdev->dev,
"cannot get IRQ:%d\n", info->irq[i]);
info->irq[i] = -1;
goto intr_failed;
} else {
dev_info(&info->pdev->dev, "IRQ No:%d\n", info->irq[i]);
}
}
return;
intr_failed:
for (; i > 0; i--) {
free_irq(info->irq[i - 1], info);
info->irq[i - 1] = -1;
}
}
static int pmic_fg_save_fg_params(struct dc_xpwr_fg_cfg *cfg, int len)
{
int ret;
if (!info_ptr || !info_ptr->cfg || !info_ptr->fg_init_done)
return -ENODEV;
mutex_lock(&info_ptr->lock);
ret = pmic_fg_update_config_params(info_ptr);
mutex_unlock(&info_ptr->lock);
if (ret < 0)
return ret;
memcpy(cfg, info_ptr->cfg, sizeof(struct dc_xpwr_fg_cfg));
return 0;
}
static int pmic_fg_set_config_params(struct dc_xpwr_fg_cfg *cfg, int len)
{
if (!info_ptr)
return -ENODEV;
info_ptr->cfg = kmalloc(sizeof(struct dc_xpwr_fg_cfg), GFP_KERNEL);
if (!info_ptr->cfg)
return -ENOMEM;
memcpy(info_ptr->cfg, cfg, sizeof(struct dc_xpwr_fg_cfg));
mutex_lock(&info_ptr->lock);
pmic_fg_init_config_regs(info_ptr);
mutex_unlock(&info_ptr->lock);
return 0;
}
static void pmic_fg_init_hw_regs(struct pmic_fg_info *info)
{
/* program temperature thresholds */
intel_mid_pmic_writeb(DC_FG_VLTFW_REG, FG_VLTFW_0C);
intel_mid_pmic_writeb(DC_FG_VHTFW_REG, FG_VHTFW_56C);
/* enable interrupts */
intel_mid_pmic_setb(DC_TEMP_IRQ_CFG_REG, TEMP_IRQ_CFG_MASK);
intel_mid_pmic_setb(DC_FG_IRQ_CFG_REG, FG_IRQ_CFG_LOWBATT_MASK);
}
static void pmic_fg_init_psy(struct pmic_fg_info *info)
{
info->status = POWER_SUPPLY_STATUS_DISCHARGING;
info->ext_set_cap = -EINVAL;
}
static int pmic_fg_probe(struct platform_device *pdev)
{
struct pmic_fg_info *info;
int ret;
info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
if (!info) {
dev_err(&pdev->dev, "mem alloc failed\n");
return -ENOMEM;
}
info->pdev = pdev;
info->pdata = pdev->dev.platform_data;
if (!info->pdata)
return -ENODEV;
platform_set_drvdata(pdev, info);
mutex_init(&info->lock);
INIT_DELAYED_WORK(&info->status_monitor, pmic_fg_status_monitor);
pmic_fg_init_psy(info);
intel_fg_set_store_fn(pmic_fg_save_fg_params);
intel_fg_set_restore_fn(pmic_fg_set_config_params);
info_ptr = info;
info->bat.name = DEV_NAME;
info->bat.type = POWER_SUPPLY_TYPE_BATTERY;
info->bat.properties = pmic_fg_props;
info->bat.num_properties = ARRAY_SIZE(pmic_fg_props);
info->bat.get_property = pmic_fg_get_battery_property;
info->bat.set_property = pmic_fg_set_battery_property;
info->bat.external_power_changed = pmic_fg_external_power_changed;
ret = power_supply_register(&pdev->dev, &info->bat);
if (ret) {
dev_err(&pdev->dev, "failed to register battery: %d\n", ret);
return ret;
}
/* register fuel gauge interrupts */
pmic_fg_init_irq(info);
pmic_fg_init_hw_regs(info);
schedule_delayed_work(&info->status_monitor, STATUS_MON_DELAY_JIFFIES);
return 0;
}
static int pmic_fg_remove(struct platform_device *pdev)
{
struct pmic_fg_info *info = platform_get_drvdata(pdev);
int i;
cancel_delayed_work_sync(&info->status_monitor);
for (i = 0; i < DC_FG_INTR_NUM && info->irq[i] != -1; i++)
free_irq(info->irq[i], info);
kfree(info->cfg);
power_supply_unregister(&info->bat);
return 0;
}
static int pmic_fg_suspend(struct device *dev)
{
struct pmic_fg_info *info = dev_get_drvdata(dev);
dev_dbg(dev, "%s called\n", __func__);
/*
* set lowbatt thresholds to
* wake the platform from S3.
*/
pmic_fg_set_lowbatt_thresholds(info);
cancel_delayed_work_sync(&info->status_monitor);
return 0;
}
static int pmic_fg_resume(struct device *dev)
{
struct pmic_fg_info *info = dev_get_drvdata(dev);
dev_dbg(dev, "%s called\n", __func__);
schedule_delayed_work(&info->status_monitor, 0);
return 0;
}
static int pmic_fg_runtime_suspend(struct device *dev)
{
dev_dbg(dev, "%s called\n", __func__);
return 0;
}
static int pmic_fg_runtime_resume(struct device *dev)
{
dev_dbg(dev, "%s called\n", __func__);
return 0;
}
static int pmic_fg_runtime_idle(struct device *dev)
{
dev_dbg(dev, "%s called\n", __func__);
return 0;
}
static const struct dev_pm_ops pmic_fg_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(pmic_fg_suspend,
pmic_fg_resume)
SET_RUNTIME_PM_OPS(pmic_fg_runtime_suspend,
pmic_fg_runtime_resume,
pmic_fg_runtime_idle)
};
static struct platform_driver pmic_fg_driver = {
.driver = {
.name = DEV_NAME,
.owner = THIS_MODULE,
.pm = &pmic_fg_pm_ops,
},
.probe = pmic_fg_probe,
.remove = pmic_fg_remove,
};
static int __init dc_pmic_fg_init(void)
{
return platform_driver_register(&pmic_fg_driver);
}
device_initcall(dc_pmic_fg_init);
static void __exit dc_pmic_fg_exit(void)
{
platform_driver_unregister(&pmic_fg_driver);
}
module_exit(dc_pmic_fg_exit);
MODULE_AUTHOR("Ramakrishna Pallala <ramakrishna.pallala@intel.com>");
MODULE_DESCRIPTION("Dollar Cove(X-power) PMIC Battery Driver");
MODULE_LICENSE("GPL");