1154 lines
32 KiB
C
1154 lines
32 KiB
C
|
/*
|
||
|
* Copyright (c) 2014-2015, 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; \
|
||
|
} \
|
||
|
} 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);
|
||
|
|