summaryrefslogtreecommitdiff
path: root/drivers/power/bcl_peripheral.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/power/bcl_peripheral.c')
-rw-r--r--drivers/power/bcl_peripheral.c1153
1 files changed, 1153 insertions, 0 deletions
diff --git a/drivers/power/bcl_peripheral.c b/drivers/power/bcl_peripheral.c
new file mode 100644
index 000000000000..64a648c2be5b
--- /dev/null
+++ b/drivers/power/bcl_peripheral.c
@@ -0,0 +1,1153 @@
+/*
+ * 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);
+