blob: ca7488519a793034d128e84f0cc1ef6e27f2ea5e [file] [log] [blame]
/*
* Copyright (c) 2014-2016, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* 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.
*/
#define pr_fmt(fmt) "%s:%s " fmt, KBUILD_MODNAME, __func__
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <linux/workqueue.h>
#include <linux/kernel.h>
#include <linux/io.h>
#include <linux/err.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/spmi.h>
#include <linux/mutex.h>
#include <linux/msm_bcl.h>
#include <linux/power_supply.h>
#define CREATE_TRACE_POINTS
#define _BCL_HW_TRACE
#include <trace/trace_thermal.h>
#define BCL_DRIVER_NAME "bcl_peripheral"
#define BCL_VBAT_INT_NAME "bcl-low-vbat-int"
#define BCL_IBAT_INT_NAME "bcl-high-ibat-int"
#define BCL_PARAM_MAX_ATTR 3
#define BCL_INT_EN 0x15
#define BCL_MONITOR_EN 0x46
#define BCL_VBAT_VALUE 0x54
#define BCL_IBAT_VALUE 0x55
#define BCL_VBAT_CP_VALUE 0x56
#define BCL_IBAT_CP_VALUE 0x57
#define BCL_VBAT_MIN 0x58
#define BCL_IBAT_MAX 0x59
#define BCL_VBAT_MIN_CP 0x5A
#define BCL_IBAT_MAX_CP 0x5B
#define BCL_V_GAIN_BAT 0x60
#define BCL_I_GAIN_RSENSE 0x61
#define BCL_I_OFFSET_RSENSE 0x62
#define BCL_I_GAIN_BATFET 0x63
#define BCL_I_OFFSET_BATFET 0x64
#define BCL_I_SENSE_SRC 0x65
#define BCL_VBAT_MIN_CLR 0x66
#define BCL_IBAT_MAX_CLR 0x67
#define BCL_VBAT_TRIP 0x68
#define BCL_IBAT_TRIP 0x69
#define BCL_CONSTANT_NUM 32
#define BCL_READ_RETRY_LIMIT 3
#define VAL_CP_REG_BUF_LEN 3
#define VAL_REG_BUF_OFFSET 0
#define VAL_CP_REG_BUF_OFFSET 2
#define PON_SPARE_FULL_CURRENT 0x0
#define PON_SPARE_DERATED_CURRENT 0x1
#define READ_CONV_FACTOR(_node, _key, _val, _ret, _dest) do { \
_ret = of_property_read_u32(_node, _key, &_val); \
if (_ret) { \
pr_err("Error reading key:%s. err:%d\n", _key, _ret); \
goto bcl_dev_exit; \
} \
_dest = _val; \
} while (0)
#define READ_OPTIONAL_PROP(_node, _key, _val, _ret, _dest) do { \
_ret = of_property_read_u32(_node, _key, &_val); \
if (_ret && _ret != -EINVAL) { \
pr_err("Error reading key:%s. err:%d\n", _key, _ret); \
goto bcl_dev_exit; \
} else if (!_ret) { \
_dest = _val; \
} else { \
_ret = 0; \
} \
} while (0)
enum bcl_monitor_state {
BCL_PARAM_INACTIVE,
BCL_PARAM_MONITOR,
BCL_PARAM_POLLING,
};
struct bcl_peripheral_data {
struct bcl_param_data *param_data;
struct bcl_driver_ops ops;
enum bcl_monitor_state state;
struct delayed_work poll_work;
int irq_num;
int high_trip;
int low_trip;
int trip_val;
int scaling_factor;
int offset_factor_num;
int offset_factor_den;
int offset;
int gain_factor_num;
int gain_factor_den;
int gain;
uint32_t polling_delay_ms;
int inhibit_derating_ua;
int (*read_max) (int *adc_value);
int (*clear_max) (void);
struct mutex state_trans_lock;
};
struct bcl_device {
bool enabled;
struct device *dev;
struct spmi_device *spmi;
uint16_t base_addr;
uint16_t pon_spare_addr;
uint8_t slave_id;
int i_src;
struct bcl_peripheral_data param[BCL_PARAM_MAX];
};
static struct bcl_device *bcl_perph;
static struct power_supply bcl_psy;
static const char bcl_psy_name[] = "fg_adc";
static bool calibration_done;
static DEFINE_MUTEX(bcl_access_mutex);
static DEFINE_MUTEX(bcl_enable_mutex);
static int bcl_read_multi_register(int16_t reg_offset, uint8_t *data, int len)
{
int ret = 0, trace_len = 0;
if (!bcl_perph) {
pr_err("BCL device not initialized\n");
return -EINVAL;
}
ret = spmi_ext_register_readl(bcl_perph->spmi->ctrl,
bcl_perph->slave_id, (bcl_perph->base_addr + reg_offset),
data, len);
if (ret < 0) {
pr_err("Error reading register %d. err:%d", reg_offset, ret);
return ret;
}
while (trace_len < len) {
trace_bcl_hw_reg_access("Read",
bcl_perph->base_addr + reg_offset + trace_len,
data[trace_len]);
trace_len++;
}
return ret;
}
static int bcl_read_register(int16_t reg_offset, uint8_t *data)
{
return bcl_read_multi_register(reg_offset, data, 1);
}
static int bcl_write_general_register(int16_t reg_offset,
uint16_t base, uint8_t data)
{
int ret = 0;
uint8_t *write_buf = &data;
if (!bcl_perph) {
pr_err("BCL device not initialized\n");
return -EINVAL;
}
ret = spmi_ext_register_writel(bcl_perph->spmi->ctrl,
bcl_perph->slave_id, (base + reg_offset),
write_buf, 1);
if (ret < 0) {
pr_err("Error reading register %d. err:%d", reg_offset, ret);
return ret;
}
pr_debug("wrote 0x%02x to 0x%04x\n", data, base + reg_offset);
trace_bcl_hw_reg_access("write", base + reg_offset, data);
return ret;
}
static int bcl_write_register(int16_t reg_offset, uint8_t data)
{
return bcl_write_general_register(reg_offset,
bcl_perph->base_addr, data);
}
static void convert_vbat_to_adc_val(int *val)
{
struct bcl_peripheral_data *perph_data = NULL;
if (!bcl_perph)
return;
perph_data = &bcl_perph->param[BCL_PARAM_VOLTAGE];
*val = (*val * 100
/ (100 + (perph_data->gain_factor_num * perph_data->gain)
* BCL_CONSTANT_NUM
/ perph_data->gain_factor_den))
/ perph_data->scaling_factor;
return;
}
static void convert_adc_to_vbat_val(int *val)
{
struct bcl_peripheral_data *perph_data = NULL;
if (!bcl_perph)
return;
perph_data = &bcl_perph->param[BCL_PARAM_VOLTAGE];
*val = ((*val + 2) * perph_data->scaling_factor)
* (100 + (perph_data->gain_factor_num * perph_data->gain)
* BCL_CONSTANT_NUM / perph_data->gain_factor_den)
/ 100;
return;
}
static void convert_ibat_to_adc_val(int *val)
{
struct bcl_peripheral_data *perph_data = NULL;
if (!bcl_perph)
return;
perph_data = &bcl_perph->param[BCL_PARAM_CURRENT];
*val = (*val * 100
/ (100 + (perph_data->gain_factor_num * perph_data->gain)
* BCL_CONSTANT_NUM / perph_data->gain_factor_den)
- (perph_data->offset_factor_num * perph_data->offset)
/ perph_data->offset_factor_den)
/ perph_data->scaling_factor;
return;
}
static void convert_adc_to_ibat_val(int *val)
{
struct bcl_peripheral_data *perph_data = NULL;
if (!bcl_perph)
return;
perph_data = &bcl_perph->param[BCL_PARAM_CURRENT];
*val = (*val * perph_data->scaling_factor
+ (perph_data->offset_factor_num * perph_data->offset)
/ perph_data->offset_factor_den)
* (100 + (perph_data->gain_factor_num * perph_data->gain)
* BCL_CONSTANT_NUM / perph_data->gain_factor_den) / 100;
return;
}
static int bcl_set_high_vbat(int thresh_value)
{
bcl_perph->param[BCL_PARAM_VOLTAGE].high_trip = thresh_value;
return 0;
}
static int bcl_set_low_ibat(int thresh_value)
{
bcl_perph->param[BCL_PARAM_CURRENT].low_trip = thresh_value;
return 0;
}
static int bcl_set_high_ibat(int thresh_value)
{
int ret = 0, ibat_ua;
int8_t val = 0;
ibat_ua = thresh_value;
convert_ibat_to_adc_val(&thresh_value);
pr_debug("Setting Ibat high trip:%d. ADC_val:%d\n", ibat_ua,
thresh_value);
val = (int8_t)thresh_value;
ret = bcl_write_register(BCL_IBAT_TRIP, val);
if (ret) {
pr_err("Error accessing BCL peripheral. err:%d\n", ret);
return ret;
}
bcl_perph->param[BCL_PARAM_CURRENT].high_trip = thresh_value;
if (bcl_perph->param[BCL_PARAM_CURRENT].inhibit_derating_ua == 0
|| bcl_perph->pon_spare_addr == 0)
return ret;
ret = bcl_write_general_register(bcl_perph->pon_spare_addr,
PON_SPARE_FULL_CURRENT, val);
if (ret) {
pr_debug("Error accessing PON register. err:%d\n", ret);
return ret;
}
thresh_value = ibat_ua
- bcl_perph->param[BCL_PARAM_CURRENT].inhibit_derating_ua;
convert_ibat_to_adc_val(&thresh_value);
val = (int8_t)thresh_value;
ret = bcl_write_general_register(bcl_perph->pon_spare_addr,
PON_SPARE_DERATED_CURRENT, val);
if (ret) {
pr_debug("Error accessing PON register. err:%d\n", ret);
return ret;
}
return ret;
}
static int bcl_set_low_vbat(int thresh_value)
{
int ret = 0, vbat_uv;
int8_t val = 0;
vbat_uv = thresh_value;
convert_vbat_to_adc_val(&thresh_value);
pr_debug("Setting Vbat low trip:%d. ADC_val:%d\n", vbat_uv,
thresh_value);
val = (int8_t)thresh_value;
ret = bcl_write_register(BCL_VBAT_TRIP, val);
if (ret) {
pr_err("Error accessing BCL peripheral. err:%d\n", ret);
return ret;
}
bcl_perph->param[BCL_PARAM_VOLTAGE].low_trip = thresh_value;
return ret;
}
static int bcl_access_monitor_enable(bool enable)
{
int ret = 0, i = 0;
struct bcl_peripheral_data *perph_data = NULL;
mutex_lock(&bcl_enable_mutex);
if (enable == bcl_perph->enabled)
goto access_exit;
for (; i < BCL_PARAM_MAX; i++) {
perph_data = &bcl_perph->param[i];
mutex_lock(&perph_data->state_trans_lock);
if (enable) {
switch (perph_data->state) {
case BCL_PARAM_INACTIVE:
trace_bcl_hw_state_event(
(i == BCL_PARAM_VOLTAGE)
? "Voltage Inactive to Monitor"
: "Current Inactive to Monitor",
0);
enable_irq(perph_data->irq_num);
break;
case BCL_PARAM_POLLING:
case BCL_PARAM_MONITOR:
default:
break;
}
perph_data->state = BCL_PARAM_MONITOR;
} else {
switch (perph_data->state) {
case BCL_PARAM_MONITOR:
trace_bcl_hw_state_event(
(i == BCL_PARAM_VOLTAGE)
? "Voltage Monitor to Inactive"
: "Current Monitor to Inactive",
0);
disable_irq_nosync(perph_data->irq_num);
/* Fall through to clear the poll work */
case BCL_PARAM_INACTIVE:
case BCL_PARAM_POLLING:
cancel_delayed_work_sync(
&perph_data->poll_work);
break;
default:
break;
}
perph_data->state = BCL_PARAM_INACTIVE;
}
mutex_unlock(&perph_data->state_trans_lock);
}
bcl_perph->enabled = enable;
access_exit:
mutex_unlock(&bcl_enable_mutex);
return ret;
}
static int bcl_monitor_enable(void)
{
trace_bcl_hw_event("BCL Enable");
return bcl_access_monitor_enable(true);
}
static int bcl_monitor_disable(void)
{
trace_bcl_hw_event("BCL Disable");
return bcl_access_monitor_enable(false);
}
static int bcl_read_ibat_high_trip(int *thresh_value)
{
int ret = 0;
int8_t val = 0;
*thresh_value = (int)val;
ret = bcl_read_register(BCL_IBAT_TRIP, &val);
if (ret) {
pr_err("BCL register read error. err:%d\n", ret);
ret = 0;
val = bcl_perph->param[BCL_PARAM_CURRENT].high_trip;
*thresh_value = (int)val;
} else {
*thresh_value = (int)val;
convert_adc_to_ibat_val(thresh_value);
pr_debug("Reading Ibat high trip:%d. ADC_val:%d\n",
*thresh_value, val);
}
return ret;
}
static int bcl_read_ibat_low_trip(int *thresh_value)
{
*thresh_value = bcl_perph->param[BCL_PARAM_CURRENT].low_trip;
return 0;
}
static int bcl_read_vbat_low_trip(int *thresh_value)
{
int ret = 0;
int8_t val = 0;
*thresh_value = (int)val;
ret = bcl_read_register(BCL_VBAT_TRIP, &val);
if (ret) {
pr_err("BCL register read error. err:%d\n", ret);
ret = 0;
*thresh_value = bcl_perph->param[BCL_PARAM_VOLTAGE].low_trip;
} else {
*thresh_value = (int)val;
convert_adc_to_vbat_val(thresh_value);
pr_debug("Reading Ibat high trip:%d. ADC_val:%d\n",
*thresh_value, val);
}
return ret;
}
static int bcl_read_vbat_high_trip(int *thresh_value)
{
*thresh_value = bcl_perph->param[BCL_PARAM_VOLTAGE].high_trip;
return 0;
}
static int bcl_clear_vbat_min(void)
{
int ret = 0;
ret = bcl_write_register(BCL_VBAT_MIN_CLR, BIT(7));
if (ret)
pr_err("Error in clearing vbat min reg. err:%d", ret);
return ret;
}
static int bcl_clear_ibat_max(void)
{
int ret = 0;
ret = bcl_write_register(BCL_IBAT_MAX_CLR, BIT(7));
if (ret)
pr_err("Error in clearing ibat max reg. err:%d", ret);
return ret;
}
static int bcl_read_ibat_max(int *adc_value)
{
int ret = 0, timeout = 0;
int8_t val[VAL_CP_REG_BUF_LEN] = {0};
*adc_value = (int)val[VAL_REG_BUF_OFFSET];
do {
ret = bcl_read_multi_register(BCL_IBAT_MAX, val,
VAL_CP_REG_BUF_LEN);
if (ret) {
pr_err("BCL register read error. err:%d\n", ret);
goto bcl_read_exit;
}
} while (val[VAL_REG_BUF_OFFSET] != val[VAL_CP_REG_BUF_OFFSET]
&& timeout++ < BCL_READ_RETRY_LIMIT);
if (val[VAL_REG_BUF_OFFSET] != val[VAL_CP_REG_BUF_OFFSET]) {
ret = -ENODEV;
goto bcl_read_exit;
}
*adc_value = (int)val[VAL_REG_BUF_OFFSET];
convert_adc_to_ibat_val(adc_value);
pr_debug("Ibat Max:%d. ADC_val:%d\n", *adc_value,
val[VAL_REG_BUF_OFFSET]);
trace_bcl_hw_sensor_reading("Ibat Max[uA]", *adc_value);
bcl_read_exit:
return ret;
}
static int bcl_read_vbat_min(int *adc_value)
{
int ret = 0, timeout = 0;
int8_t val[VAL_CP_REG_BUF_LEN] = {0};
*adc_value = (int)val[VAL_REG_BUF_OFFSET];
do {
ret = bcl_read_multi_register(BCL_VBAT_MIN, val,
VAL_CP_REG_BUF_LEN);
if (ret) {
pr_err("BCL register read error. err:%d\n", ret);
goto bcl_read_exit;
}
} while (val[VAL_REG_BUF_OFFSET] != val[VAL_CP_REG_BUF_OFFSET]
&& timeout++ < BCL_READ_RETRY_LIMIT);
if (val[VAL_REG_BUF_OFFSET] != val[VAL_CP_REG_BUF_OFFSET]) {
ret = -ENODEV;
goto bcl_read_exit;
}
*adc_value = (int)val[VAL_REG_BUF_OFFSET];
convert_adc_to_vbat_val(adc_value);
pr_debug("Vbat Min:%d. ADC_val:%d\n", *adc_value,
val[VAL_REG_BUF_OFFSET]);
trace_bcl_hw_sensor_reading("vbat Min[uV]", *adc_value);
bcl_read_exit:
return ret;
}
static int bcl_read_ibat(int *adc_value)
{
int ret = 0, timeout = 0;
int8_t val[VAL_CP_REG_BUF_LEN] = {0};
*adc_value = (int)val[VAL_REG_BUF_OFFSET];
do {
ret = bcl_read_multi_register(BCL_IBAT_VALUE, val,
VAL_CP_REG_BUF_LEN);
if (ret) {
pr_err("BCL register read error. err:%d\n", ret);
goto bcl_read_exit;
}
} while (val[VAL_REG_BUF_OFFSET] != val[VAL_CP_REG_BUF_OFFSET]
&& timeout++ < BCL_READ_RETRY_LIMIT);
if (val[VAL_REG_BUF_OFFSET] != val[VAL_CP_REG_BUF_OFFSET]) {
ret = -ENODEV;
goto bcl_read_exit;
}
*adc_value = (int)val[VAL_REG_BUF_OFFSET];
convert_adc_to_ibat_val(adc_value);
pr_debug("Read Ibat:%d. ADC_val:%d\n", *adc_value,
val[VAL_REG_BUF_OFFSET]);
trace_bcl_hw_sensor_reading("ibat[uA]", *adc_value);
bcl_read_exit:
return ret;
}
static int bcl_read_vbat(int *adc_value)
{
int ret = 0, timeout = 0;
int8_t val[VAL_CP_REG_BUF_LEN] = {0};
*adc_value = (int)val[VAL_REG_BUF_OFFSET];
do {
ret = bcl_read_multi_register(BCL_VBAT_VALUE, val,
VAL_CP_REG_BUF_LEN);
if (ret) {
pr_err("BCL register read error. err:%d\n", ret);
goto bcl_read_exit;
}
} while (val[VAL_REG_BUF_OFFSET] != val[VAL_CP_REG_BUF_OFFSET]
&& timeout++ < BCL_READ_RETRY_LIMIT);
if (val[VAL_REG_BUF_OFFSET] != val[VAL_CP_REG_BUF_OFFSET]) {
ret = -ENODEV;
goto bcl_read_exit;
}
*adc_value = (int)val[VAL_REG_BUF_OFFSET];
convert_adc_to_vbat_val(adc_value);
pr_debug("Read Vbat:%d. ADC_val:%d\n", *adc_value,
val[VAL_REG_BUF_OFFSET]);
trace_bcl_hw_sensor_reading("vbat[uV]", *adc_value);
bcl_read_exit:
return ret;
}
static void bcl_poll_ibat_low(struct work_struct *work)
{
int ret = 0, val = 0;
struct bcl_peripheral_data *perph_data =
&bcl_perph->param[BCL_PARAM_CURRENT];
trace_bcl_hw_event("ibat poll low. Enter");
mutex_lock(&perph_data->state_trans_lock);
if (perph_data->state != BCL_PARAM_POLLING) {
pr_err("Invalid ibat state %d\n", perph_data->state);
goto exit_ibat;
}
ret = perph_data->read_max(&val);
if (ret) {
pr_err("Error in reading ibat. err:%d", ret);
goto reschedule_ibat;
}
ret = perph_data->clear_max();
if (ret)
pr_err("Error clearing max ibat reg. err:%d\n", ret);
if (val <= perph_data->low_trip) {
pr_debug("Ibat reached low clear trip. ibat:%d\n", val);
trace_bcl_hw_state_event("Polling to Monitor. Ibat[uA]:", val);
trace_bcl_hw_mitigation("Ibat low trip. Ibat[uA]", val);
perph_data->ops.notify(perph_data->param_data, val,
BCL_LOW_TRIP);
perph_data->state = BCL_PARAM_MONITOR;
enable_irq(perph_data->irq_num);
} else {
goto reschedule_ibat;
}
exit_ibat:
mutex_unlock(&perph_data->state_trans_lock);
trace_bcl_hw_event("ibat poll low. Exit");
return;
reschedule_ibat:
mutex_unlock(&perph_data->state_trans_lock);
schedule_delayed_work(&perph_data->poll_work,
msecs_to_jiffies(perph_data->polling_delay_ms));
trace_bcl_hw_event("ibat poll low. Exit");
return;
}
static void bcl_poll_vbat_high(struct work_struct *work)
{
int ret = 0, val = 0;
struct bcl_peripheral_data *perph_data =
&bcl_perph->param[BCL_PARAM_VOLTAGE];
trace_bcl_hw_event("vbat poll high. Enter");
mutex_lock(&perph_data->state_trans_lock);
if (perph_data->state != BCL_PARAM_POLLING) {
pr_err("Invalid vbat state %d\n", perph_data->state);
goto exit_vbat;
}
ret = perph_data->read_max(&val);
if (ret) {
pr_err("Error in reading vbat. err:%d", ret);
goto reschedule_vbat;
}
ret = perph_data->clear_max();
if (ret)
pr_err("Error clearing min vbat reg. err:%d\n", ret);
if (val >= perph_data->high_trip) {
pr_debug("Vbat reached high clear trip. vbat:%d\n", val);
trace_bcl_hw_state_event("Polling to Monitor. vbat[uV]:", val);
trace_bcl_hw_mitigation("vbat high trip. vbat[uV]", val);
perph_data->ops.notify(perph_data->param_data, val,
BCL_HIGH_TRIP);
perph_data->state = BCL_PARAM_MONITOR;
enable_irq(perph_data->irq_num);
} else {
goto reschedule_vbat;
}
exit_vbat:
mutex_unlock(&perph_data->state_trans_lock);
trace_bcl_hw_event("vbat poll high. Exit");
return;
reschedule_vbat:
mutex_unlock(&perph_data->state_trans_lock);
schedule_delayed_work(&perph_data->poll_work,
msecs_to_jiffies(perph_data->polling_delay_ms));
trace_bcl_hw_event("vbat poll high. Exit");
return;
}
static irqreturn_t bcl_handle_ibat(int irq, void *data)
{
int thresh_value = 0, ret = 0;
struct bcl_peripheral_data *perph_data =
(struct bcl_peripheral_data *)data;
trace_bcl_hw_mitigation_event("Ibat interrupted");
mutex_lock(&perph_data->state_trans_lock);
if (perph_data->state == BCL_PARAM_MONITOR) {
ret = perph_data->read_max(&perph_data->trip_val);
if (ret) {
pr_err("Error reading max/min reg. err:%d\n", ret);
goto exit_intr;
}
ret = perph_data->clear_max();
if (ret)
pr_err("Error clearing max/min reg. err:%d\n", ret);
thresh_value = perph_data->high_trip;
convert_adc_to_ibat_val(&thresh_value);
/* Account threshold trip from PBS threshold for dead time */
thresh_value -= perph_data->inhibit_derating_ua;
if (perph_data->trip_val < thresh_value) {
pr_debug("False Ibat high trip. ibat:%d ibat_thresh_val:%d\n",
perph_data->trip_val, thresh_value);
trace_bcl_hw_event("Ibat invalid interrupt");
goto exit_intr;
}
pr_debug("Ibat reached high trip. ibat:%d\n",
perph_data->trip_val);
trace_bcl_hw_state_event("Monitor to Polling. ibat[uA]:",
perph_data->trip_val);
disable_irq_nosync(perph_data->irq_num);
perph_data->state = BCL_PARAM_POLLING;
trace_bcl_hw_mitigation("ibat high trip. ibat[uA]",
perph_data->trip_val);
perph_data->ops.notify(perph_data->param_data,
perph_data->trip_val, BCL_HIGH_TRIP);
schedule_delayed_work(&perph_data->poll_work,
msecs_to_jiffies(perph_data->polling_delay_ms));
} else {
pr_debug("Ignoring interrupt\n");
trace_bcl_hw_event("Ibat Ignoring interrupt");
}
exit_intr:
mutex_unlock(&perph_data->state_trans_lock);
return IRQ_HANDLED;
}
static irqreturn_t bcl_handle_vbat(int irq, void *data)
{
int thresh_value = 0, ret = 0;
struct bcl_peripheral_data *perph_data =
(struct bcl_peripheral_data *)data;
trace_bcl_hw_mitigation_event("Vbat Interrupted");
mutex_lock(&perph_data->state_trans_lock);
if (perph_data->state == BCL_PARAM_MONITOR) {
ret = perph_data->read_max(&perph_data->trip_val);
if (ret) {
pr_err("Error reading max/min reg. err:%d\n", ret);
goto exit_intr;
}
ret = perph_data->clear_max();
if (ret)
pr_err("Error clearing max/min reg. err:%d\n", ret);
thresh_value = perph_data->low_trip;
convert_adc_to_vbat_val(&thresh_value);
if (perph_data->trip_val > thresh_value) {
pr_debug("False vbat min trip. vbat:%d vbat_thresh_val:%d\n",
perph_data->trip_val, thresh_value);
trace_bcl_hw_event("Vbat Invalid interrupt");
goto exit_intr;
}
pr_debug("Vbat reached Low trip. vbat:%d\n",
perph_data->trip_val);
trace_bcl_hw_state_event("Monitor to Polling. vbat[uV]:",
perph_data->trip_val);
disable_irq_nosync(perph_data->irq_num);
perph_data->state = BCL_PARAM_POLLING;
trace_bcl_hw_mitigation("vbat low trip. vbat[uV]",
perph_data->trip_val);
perph_data->ops.notify(perph_data->param_data,
perph_data->trip_val, BCL_LOW_TRIP);
schedule_delayed_work(&perph_data->poll_work,
msecs_to_jiffies(perph_data->polling_delay_ms));
} else {
pr_debug("Ignoring interrupt\n");
trace_bcl_hw_event("Vbat Ignoring interrupt");
}
exit_intr:
mutex_unlock(&perph_data->state_trans_lock);
return IRQ_HANDLED;
}
static int bcl_get_devicetree_data(struct spmi_device *spmi)
{
int ret = 0, irq_num = 0, temp_val = 0;
struct resource *resource = NULL;
char *key = NULL;
const __be32 *prop = NULL;
struct device_node *dev_node = spmi->dev.of_node;
/* Get SPMI peripheral address */
resource = spmi_get_resource(spmi, NULL, IORESOURCE_MEM, 0);
if (!resource) {
pr_err("No base address defined\n");
return -EINVAL;
}
bcl_perph->slave_id = spmi->sid;
prop = of_get_address_by_name(dev_node,
"fg_user_adc", 0, 0);
if (prop) {
bcl_perph->base_addr = be32_to_cpu(*prop);
pr_debug("fg_user_adc@%04x\n", bcl_perph->base_addr);
} else {
dev_err(&spmi->dev, "No fg_user_adc registers found\n");
return -EINVAL;
}
prop = of_get_address_by_name(dev_node,
"pon_spare", 0, 0);
if (prop) {
bcl_perph->pon_spare_addr = be32_to_cpu(*prop);
pr_debug("pon_spare@%04x\n", bcl_perph->pon_spare_addr);
}
/* Register SPMI peripheral interrupt */
irq_num = spmi_get_irq_byname(spmi, NULL,
BCL_VBAT_INT_NAME);
if (irq_num < 0) {
pr_err("Invalid vbat IRQ\n");
ret = -ENXIO;
goto bcl_dev_exit;
}
bcl_perph->param[BCL_PARAM_VOLTAGE].irq_num = irq_num;
irq_num = spmi_get_irq_byname(spmi, NULL,
BCL_IBAT_INT_NAME);
if (irq_num < 0) {
pr_err("Invalid ibat IRQ\n");
ret = -ENXIO;
goto bcl_dev_exit;
}
bcl_perph->param[BCL_PARAM_CURRENT].irq_num = irq_num;
/* Get VADC and IADC scaling factor */
key = "qcom,vbat-scaling-factor";
READ_CONV_FACTOR(dev_node, key, temp_val, ret,
bcl_perph->param[BCL_PARAM_VOLTAGE].scaling_factor);
key = "qcom,vbat-gain-numerator";
READ_CONV_FACTOR(dev_node, key, temp_val, ret,
bcl_perph->param[BCL_PARAM_VOLTAGE].gain_factor_num);
key = "qcom,vbat-gain-denominator";
READ_CONV_FACTOR(dev_node, key, temp_val, ret,
bcl_perph->param[BCL_PARAM_VOLTAGE].gain_factor_den);
key = "qcom,ibat-scaling-factor";
READ_CONV_FACTOR(dev_node, key, temp_val, ret,
bcl_perph->param[BCL_PARAM_CURRENT].scaling_factor);
key = "qcom,ibat-offset-numerator";
READ_CONV_FACTOR(dev_node, key, temp_val, ret,
bcl_perph->param[BCL_PARAM_CURRENT].offset_factor_num);
key = "qcom,ibat-offset-denominator";
READ_CONV_FACTOR(dev_node, key, temp_val, ret,
bcl_perph->param[BCL_PARAM_CURRENT].offset_factor_den);
key = "qcom,ibat-gain-numerator";
READ_CONV_FACTOR(dev_node, key, temp_val, ret,
bcl_perph->param[BCL_PARAM_CURRENT].gain_factor_num);
key = "qcom,ibat-gain-denominator";
READ_CONV_FACTOR(dev_node, key, temp_val, ret,
bcl_perph->param[BCL_PARAM_CURRENT].gain_factor_den);
key = "qcom,vbat-polling-delay-ms";
READ_CONV_FACTOR(dev_node, key, temp_val, ret,
bcl_perph->param[BCL_PARAM_VOLTAGE].polling_delay_ms);
key = "qcom,ibat-polling-delay-ms";
READ_CONV_FACTOR(dev_node, key, temp_val, ret,
bcl_perph->param[BCL_PARAM_CURRENT].polling_delay_ms);
key = "qcom,inhibit-derating-ua";
READ_OPTIONAL_PROP(dev_node, key, temp_val, ret,
bcl_perph->param[BCL_PARAM_CURRENT].inhibit_derating_ua);
bcl_dev_exit:
return ret;
}
static int bcl_calibrate(void)
{
int ret = 0;
int8_t i_src = 0, val = 0;
ret = bcl_read_register(BCL_I_SENSE_SRC, &i_src);
if (ret) {
pr_err("Error reading current sense reg. err:%d\n", ret);
goto bcl_cal_exit;
}
ret = bcl_read_register((i_src & 0x01) ? BCL_I_GAIN_RSENSE
: BCL_I_GAIN_BATFET, &val);
if (ret) {
pr_err("Error reading %s current gain. err:%d\n",
(i_src & 0x01) ? "rsense" : "batfet", ret);
goto bcl_cal_exit;
}
bcl_perph->param[BCL_PARAM_CURRENT].gain = val;
ret = bcl_read_register((i_src & 0x01) ? BCL_I_OFFSET_RSENSE
: BCL_I_OFFSET_BATFET, &val);
if (ret) {
pr_err("Error reading %s current offset. err:%d\n",
(i_src & 0x01) ? "rsense" : "batfet", ret);
goto bcl_cal_exit;
}
bcl_perph->param[BCL_PARAM_CURRENT].offset = val;
ret = bcl_read_register(BCL_V_GAIN_BAT, &val);
if (ret) {
pr_err("Error reading vbat offset. err:%d\n", ret);
goto bcl_cal_exit;
}
bcl_perph->param[BCL_PARAM_VOLTAGE].gain = val;
if (((i_src & 0x01) != bcl_perph->i_src)
&& (bcl_perph->enabled)) {
bcl_set_low_vbat(bcl_perph->param[BCL_PARAM_VOLTAGE]
.low_trip);
bcl_set_high_ibat(bcl_perph->param[BCL_PARAM_CURRENT]
.high_trip);
bcl_perph->i_src = i_src;
}
bcl_cal_exit:
return ret;
}
static void power_supply_callback(struct power_supply *psy)
{
static struct power_supply *bms_psy;
int ret = 0;
if (calibration_done)
return;
if (!bms_psy)
bms_psy = power_supply_get_by_name("bms");
if (bms_psy) {
calibration_done = true;
trace_bcl_hw_event("Recalibrate callback");
ret = bcl_calibrate();
if (ret)
pr_err("Could not read calibration values. err:%d",
ret);
}
}
static int bcl_psy_get_property(struct power_supply *psy,
enum power_supply_property prop,
union power_supply_propval *val)
{
return 0;
}
static int bcl_psy_set_property(struct power_supply *psy,
enum power_supply_property prop,
const union power_supply_propval *val)
{
return -EINVAL;
}
static int bcl_update_data(void)
{
int ret = 0;
bcl_perph->param[BCL_PARAM_VOLTAGE].ops.read = bcl_read_vbat;
bcl_perph->param[BCL_PARAM_VOLTAGE].ops.get_high_trip
= bcl_read_vbat_high_trip;
bcl_perph->param[BCL_PARAM_VOLTAGE].ops.get_low_trip
= bcl_read_vbat_low_trip;
bcl_perph->param[BCL_PARAM_VOLTAGE].ops.set_high_trip
= bcl_set_high_vbat;
bcl_perph->param[BCL_PARAM_VOLTAGE].ops.set_low_trip
= bcl_set_low_vbat;
bcl_perph->param[BCL_PARAM_VOLTAGE].ops.enable
= bcl_monitor_enable;
bcl_perph->param[BCL_PARAM_VOLTAGE].ops.disable
= bcl_monitor_disable;
bcl_perph->param[BCL_PARAM_VOLTAGE].read_max
= bcl_read_vbat_min;
bcl_perph->param[BCL_PARAM_VOLTAGE].clear_max
= bcl_clear_vbat_min;
bcl_perph->param[BCL_PARAM_CURRENT].ops.read = bcl_read_ibat;
bcl_perph->param[BCL_PARAM_CURRENT].ops.get_high_trip
= bcl_read_ibat_high_trip;
bcl_perph->param[BCL_PARAM_CURRENT].ops.get_low_trip
= bcl_read_ibat_low_trip;
bcl_perph->param[BCL_PARAM_CURRENT].ops.set_high_trip
= bcl_set_high_ibat;
bcl_perph->param[BCL_PARAM_CURRENT].ops.set_low_trip
= bcl_set_low_ibat;
bcl_perph->param[BCL_PARAM_CURRENT].ops.enable
= bcl_monitor_enable;
bcl_perph->param[BCL_PARAM_CURRENT].ops.disable
= bcl_monitor_disable;
bcl_perph->param[BCL_PARAM_CURRENT].read_max
= bcl_read_ibat_max;
bcl_perph->param[BCL_PARAM_CURRENT].clear_max
= bcl_clear_ibat_max;
bcl_perph->param[BCL_PARAM_VOLTAGE].param_data = msm_bcl_register_param(
BCL_PARAM_VOLTAGE, &bcl_perph->param[BCL_PARAM_VOLTAGE].ops,
"vbat");
if (!bcl_perph->param[BCL_PARAM_VOLTAGE].param_data) {
pr_err("register Vbat failed.\n");
ret = -ENODEV;
goto update_data_exit;
}
bcl_perph->param[BCL_PARAM_CURRENT].param_data = msm_bcl_register_param(
BCL_PARAM_CURRENT, &bcl_perph->param[BCL_PARAM_CURRENT].ops,
"ibat");
if (!bcl_perph->param[BCL_PARAM_CURRENT].param_data) {
pr_err("register Ibat failed.\n");
ret = -ENODEV;
goto update_data_exit;
}
INIT_DELAYED_WORK(&bcl_perph->param[BCL_PARAM_VOLTAGE].poll_work,
bcl_poll_vbat_high);
INIT_DELAYED_WORK(&bcl_perph->param[BCL_PARAM_CURRENT].poll_work,
bcl_poll_ibat_low);
mutex_init(&bcl_perph->param[BCL_PARAM_CURRENT].state_trans_lock);
mutex_init(&bcl_perph->param[BCL_PARAM_VOLTAGE].state_trans_lock);
update_data_exit:
return ret;
}
static int bcl_probe(struct spmi_device *spmi)
{
int ret = 0;
bcl_perph = devm_kzalloc(&spmi->dev, sizeof(struct bcl_device),
GFP_KERNEL);
if (!bcl_perph) {
pr_err("Memory alloc failed\n");
return -ENOMEM;
}
memset(bcl_perph, 0, sizeof(struct bcl_device));
bcl_perph->spmi = spmi;
bcl_perph->dev = &(spmi->dev);
ret = bcl_get_devicetree_data(spmi);
if (ret) {
pr_err("Device tree data fetch error. err:%d", ret);
goto bcl_probe_exit;
}
ret = bcl_calibrate();
if (ret) {
pr_debug("Could not read calibration values. err:%d", ret);
goto bcl_probe_exit;
}
bcl_psy.name = bcl_psy_name;
bcl_psy.type = POWER_SUPPLY_TYPE_BMS;
bcl_psy.get_property = bcl_psy_get_property;
bcl_psy.set_property = bcl_psy_set_property;
bcl_psy.num_properties = 0;
bcl_psy.external_power_changed = power_supply_callback;
ret = power_supply_register(&spmi->dev, &bcl_psy);
if (ret < 0) {
pr_err("Unable to register bcl_psy rc = %d\n", ret);
return ret;
}
ret = bcl_update_data();
if (ret) {
pr_err("Update data failed. err:%d", ret);
goto bcl_probe_exit;
}
mutex_lock(&bcl_perph->param[BCL_PARAM_VOLTAGE].state_trans_lock);
ret = devm_request_threaded_irq(&spmi->dev,
bcl_perph->param[BCL_PARAM_VOLTAGE].irq_num,
NULL, bcl_handle_vbat,
IRQF_TRIGGER_RISING | IRQF_ONESHOT,
"bcl_vbat_interrupt",
&bcl_perph->param[BCL_PARAM_VOLTAGE]);
if (ret) {
dev_err(&spmi->dev, "Error requesting VBAT irq. err:%d", ret);
mutex_unlock(
&bcl_perph->param[BCL_PARAM_VOLTAGE].state_trans_lock);
goto bcl_probe_exit;
}
/*
* BCL is enabled by default in hardware.
* Disable BCL monitoring till a valid threshold is set by APPS
*/
disable_irq_nosync(bcl_perph->param[BCL_PARAM_VOLTAGE].irq_num);
mutex_unlock(&bcl_perph->param[BCL_PARAM_VOLTAGE].state_trans_lock);
mutex_lock(&bcl_perph->param[BCL_PARAM_CURRENT].state_trans_lock);
ret = devm_request_threaded_irq(&spmi->dev,
bcl_perph->param[BCL_PARAM_CURRENT].irq_num,
NULL, bcl_handle_ibat,
IRQF_TRIGGER_RISING | IRQF_ONESHOT,
"bcl_ibat_interrupt",
&bcl_perph->param[BCL_PARAM_CURRENT]);
if (ret) {
dev_err(&spmi->dev, "Error requesting IBAT irq. err:%d", ret);
mutex_unlock(
&bcl_perph->param[BCL_PARAM_CURRENT].state_trans_lock);
goto bcl_probe_exit;
}
disable_irq_nosync(bcl_perph->param[BCL_PARAM_CURRENT].irq_num);
mutex_unlock(&bcl_perph->param[BCL_PARAM_CURRENT].state_trans_lock);
dev_set_drvdata(&spmi->dev, bcl_perph);
ret = bcl_write_register(BCL_MONITOR_EN, BIT(7));
if (ret) {
pr_err("Error accessing BCL peripheral. err:%d\n", ret);
goto bcl_probe_exit;
}
return 0;
bcl_probe_exit:
bcl_perph = NULL;
return ret;
}
static int bcl_remove(struct spmi_device *spmi)
{
int ret = 0, i = 0;
ret = bcl_monitor_disable();
if (ret)
pr_err("Error disabling BCL. err:%d\n", ret);
for (; i < BCL_PARAM_MAX; i++) {
if (!bcl_perph->param[i].param_data)
continue;
ret = msm_bcl_unregister_param(bcl_perph->param[i].param_data);
if (ret)
pr_err("Error unregistering with Framework. err:%d\n",
ret);
}
return 0;
}
static struct of_device_id bcl_match[] = {
{ .compatible = "qcom,msm-bcl",
},
{},
};
static struct spmi_driver bcl_driver = {
.probe = bcl_probe,
.remove = bcl_remove,
.driver = {
.name = BCL_DRIVER_NAME,
.owner = THIS_MODULE,
.of_match_table = bcl_match,
},
};
static int __init bcl_perph_init(void)
{
pr_info("BCL Initialized\n");
return spmi_driver_register(&bcl_driver);
}
static void __exit bcl_perph_exit(void)
{
spmi_driver_unregister(&bcl_driver);
}
fs_initcall(bcl_perph_init);
module_exit(bcl_perph_exit);
MODULE_ALIAS("platform:" BCL_DRIVER_NAME);