| /* |
| * 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 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; |
| |
| 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; |
| } |
| |
| 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); |
| |
| 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 * 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: |
| 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: |
| 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) |
| { |
| return bcl_access_monitor_enable(true); |
| } |
| |
| static int bcl_monitor_disable(void) |
| { |
| 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]); |
| |
| 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]); |
| |
| 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]); |
| |
| 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]); |
| |
| 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]; |
| |
| 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); |
| 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); |
| 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)); |
| 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]; |
| |
| 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); |
| 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); |
| 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)); |
| 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; |
| |
| 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); |
| goto exit_intr; |
| } |
| pr_debug("Ibat reached high trip. ibat:%d\n", |
| perph_data->trip_val); |
| disable_irq_nosync(perph_data->irq_num); |
| perph_data->state = BCL_PARAM_POLLING; |
| 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"); |
| } |
| |
| 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; |
| |
| 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); |
| goto exit_intr; |
| } |
| pr_debug("Vbat reached Low trip. vbat:%d\n", |
| perph_data->trip_val); |
| disable_irq_nosync(perph_data->irq_num); |
| perph_data->state = BCL_PARAM_POLLING; |
| 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"); |
| } |
| |
| 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; |
| 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); |
| |