1063 lines
28 KiB
C
Executable File
1063 lines
28 KiB
C
Executable File
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright (c) 2018-2021, The Linux Foundation. All rights reserved.
|
|
* Copyright (c) 2021-2024, Qualcomm Innovation Center, Inc. All rights reserved.
|
|
*/
|
|
|
|
#define pr_fmt(fmt) "%s:%s " fmt, KBUILD_MODNAME, __func__
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/workqueue.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/regmap.h>
|
|
#include <linux/io.h>
|
|
#include <linux/err.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_address.h>
|
|
#include <linux/spmi.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/thermal.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/nvmem-consumer.h>
|
|
#include <linux/ipc_logging.h>
|
|
#include "thermal_zone_internal.h"
|
|
#if IS_ENABLED(CONFIG_SEC_PM_LOG)
|
|
#include <linux/sec_pm_log.h>
|
|
#endif
|
|
|
|
#define BCL_DRIVER_NAME "bcl_pmic5"
|
|
#define BCL_MONITOR_EN 0x46
|
|
#define BCL_IRQ_STATUS 0x08
|
|
#define BCL_REVISION1 0x0
|
|
#define BCL_REVISION2 0x01
|
|
#define BCL_PARAM_1 0x0e
|
|
#define BCL_PARAM_2 0x0f
|
|
|
|
#define BCL_IBAT_HIGH 0x4B
|
|
#define BCL_IBAT_TOO_HIGH 0x4C
|
|
#define BCL_IBAT_TOO_HIGH_REV4 0x4D
|
|
#define BCL_IBAT_READ 0x86
|
|
#define BCL_IBAT_SCALING_UA 78127
|
|
#define BCL_IBAT_CCM_SCALING_UA 15625
|
|
#define BCL_IBAT_SCALING_REV4_UA 93753
|
|
|
|
#define BCL_VBAT_READ 0x76
|
|
#define BCL_VBAT_ADC_LOW 0x48
|
|
#define BCL_VBAT_COMP_LOW 0x49
|
|
#define BCL_VBAT_COMP_TLOW 0x4A
|
|
#define BCL_VBAT_CONV_REQ 0x72
|
|
|
|
#define BCL_GEN3_MAJOR_REV 4
|
|
#define BCL_PARAM_HAS_ADC BIT(0)
|
|
#define BCL_PARAM_HAS_IBAT_ADC BIT(2)
|
|
|
|
#define BCL_IRQ_L0 0x1
|
|
#define BCL_IRQ_L1 0x2
|
|
#define BCL_IRQ_L2 0x4
|
|
|
|
/*
|
|
* 49827 = 64.879uV (one bit value) * 3 (voltage divider)
|
|
* * 256 (8 bit shift for MSB)
|
|
*/
|
|
#define BCL_VBAT_SCALING_UV 49827
|
|
#define BCL_VBAT_NO_READING 127
|
|
#define BCL_VBAT_BASE_MV 2000
|
|
#define BCL_VBAT_INC_MV 25
|
|
#define BCL_VBAT_MAX_MV 3600
|
|
#define BCL_VBAT_THRESH_BASE 0x8CA
|
|
|
|
#define BCL_IBAT_CCM_OFFSET 800
|
|
#define BCL_IBAT_CCM_LSB 100
|
|
#define BCL_IBAT_CCM_MAX_VAL 14
|
|
|
|
#define BCL_VBAT_4G_NO_READING 0x7fff
|
|
#define BCL_GEN4_MAJOR_REV 5
|
|
#define BCL_VBAT_SCALING_REV5_NV 194637 /* 64.879uV (one bit) * 3 VD */
|
|
#define BCL_IBAT_SCALING_REV5_NA 61037
|
|
#define BCL_IBAT_THRESH_SCALING_REV5_UA 156255 /* 610.37uA * 256 */
|
|
#define BCL_VBAT_TRIP_CNT 3
|
|
|
|
#define MAX_PERPH_COUNT 2
|
|
#define IPC_LOGPAGES 2
|
|
|
|
#define BCL_IPC(dev, msg, args...) do { \
|
|
if ((dev) && (dev)->ipc_log) { \
|
|
ipc_log_string((dev)->ipc_log, \
|
|
"[%s]: %s: " msg, \
|
|
current->comm, __func__, args); \
|
|
} \
|
|
} while (0)
|
|
|
|
enum bcl_dev_type {
|
|
BCL_IBAT_LVL0,
|
|
BCL_IBAT_LVL1,
|
|
BCL_VBAT_LVL0,
|
|
BCL_VBAT_LVL1,
|
|
BCL_VBAT_LVL2,
|
|
BCL_LVL0,
|
|
BCL_LVL1,
|
|
BCL_LVL2,
|
|
BCL_2S_IBAT_LVL0,
|
|
BCL_2S_IBAT_LVL1,
|
|
BCL_TYPE_MAX,
|
|
};
|
|
|
|
static char bcl_int_names[BCL_TYPE_MAX][25] = {
|
|
"bcl-ibat-lvl0",
|
|
"bcl-ibat-lvl1",
|
|
"bcl-vbat-lvl0",
|
|
"bcl-vbat-lvl1",
|
|
"bcl-vbat-lvl2",
|
|
"bcl-lvl0",
|
|
"bcl-lvl1",
|
|
"bcl-lvl2",
|
|
"bcl-2s-ibat-lvl0",
|
|
"bcl-2s-ibat-lvl1",
|
|
};
|
|
|
|
enum bcl_ibat_ext_range_type {
|
|
BCL_IBAT_RANGE_LVL0,
|
|
BCL_IBAT_RANGE_LVL1,
|
|
BCL_IBAT_RANGE_LVL2,
|
|
BCL_IBAT_RANGE_MAX,
|
|
};
|
|
|
|
static uint32_t bcl_ibat_ext_ranges[BCL_IBAT_RANGE_MAX] = {
|
|
10, /* default range factor */
|
|
20,
|
|
25
|
|
};
|
|
|
|
struct bcl_device;
|
|
|
|
struct bcl_peripheral_data {
|
|
int irq_num;
|
|
int status_bit_idx;
|
|
long trip_thresh;
|
|
int last_val;
|
|
struct mutex state_trans_lock;
|
|
bool irq_enabled;
|
|
enum bcl_dev_type type;
|
|
struct thermal_zone_device_ops ops;
|
|
struct thermal_zone_device *tz_dev;
|
|
struct bcl_device *dev;
|
|
};
|
|
|
|
struct bcl_device {
|
|
struct device *dev;
|
|
struct regmap *regmap;
|
|
uint16_t fg_bcl_addr;
|
|
uint8_t dig_major;
|
|
uint8_t dig_minor;
|
|
uint8_t bcl_param_1;
|
|
uint8_t bcl_type;
|
|
void *ipc_log;
|
|
bool ibat_ccm_enabled;
|
|
bool ibat_use_qg_adc;
|
|
bool no_bit_shift;
|
|
uint32_t ibat_ext_range_factor;
|
|
struct bcl_peripheral_data param[BCL_TYPE_MAX];
|
|
};
|
|
|
|
static struct bcl_device *bcl_devices[MAX_PERPH_COUNT];
|
|
static int bcl_device_ct;
|
|
|
|
static int bcl_read_multi_register(struct bcl_device *bcl_perph, int16_t reg_offset,
|
|
unsigned int *data, size_t len)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (!bcl_perph) {
|
|
pr_err("BCL device not initialized\n");
|
|
return -EINVAL;
|
|
}
|
|
ret = regmap_bulk_read(bcl_perph->regmap,
|
|
(bcl_perph->fg_bcl_addr + reg_offset),
|
|
data, len);
|
|
if (ret < 0)
|
|
pr_err("Error reading reg base:0x%04x len:%ld err:%d\n",
|
|
bcl_perph->fg_bcl_addr + reg_offset, len, ret);
|
|
else
|
|
pr_debug("Read register:0x%04x value:0x%02x len:%ld\n",
|
|
bcl_perph->fg_bcl_addr + reg_offset,
|
|
*data, len);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int bcl_read_register(struct bcl_device *bcl_perph, int16_t reg_offset,
|
|
unsigned int *data)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (!bcl_perph) {
|
|
pr_err("BCL device not initialized\n");
|
|
return -EINVAL;
|
|
}
|
|
ret = regmap_read(bcl_perph->regmap,
|
|
(bcl_perph->fg_bcl_addr + reg_offset),
|
|
data);
|
|
if (ret < 0)
|
|
pr_err("Error reading register 0x%04x err:%d\n",
|
|
bcl_perph->fg_bcl_addr + reg_offset, ret);
|
|
else
|
|
pr_debug("Read register:0x%04x value:0x%02x\n",
|
|
bcl_perph->fg_bcl_addr + reg_offset,
|
|
*data);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int bcl_write_register(struct bcl_device *bcl_perph,
|
|
int16_t reg_offset, uint8_t data)
|
|
{
|
|
int ret = 0;
|
|
uint8_t *write_buf = &data;
|
|
uint16_t base;
|
|
|
|
if (!bcl_perph) {
|
|
pr_err("BCL device not initialized\n");
|
|
return -EINVAL;
|
|
}
|
|
base = bcl_perph->fg_bcl_addr;
|
|
ret = regmap_write(bcl_perph->regmap, (base + reg_offset), *write_buf);
|
|
if (ret < 0) {
|
|
pr_err("Error reading register:0x%04x val:0x%02x err:%d\n",
|
|
base + reg_offset, data, ret);
|
|
return ret;
|
|
}
|
|
pr_debug("wrote 0x%02x to 0x%04x\n", data, base + reg_offset);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void convert_adc_to_vbat_thresh_val(struct bcl_device *bcl_perph, int *val)
|
|
{
|
|
/*
|
|
* Threshold register can be bit shifted from ADC MSB.
|
|
* So the scaling factor is half in those cases.
|
|
*/
|
|
if (bcl_perph->no_bit_shift)
|
|
*val = (*val * BCL_VBAT_SCALING_UV) / 1000;
|
|
else
|
|
*val = (*val * BCL_VBAT_SCALING_UV) / 2000;
|
|
}
|
|
|
|
/* Common helper to convert nano unit to milli unit */
|
|
static void convert_adc_nu_to_mu_val(unsigned int *val, unsigned int scaling_factor)
|
|
{
|
|
*val = div_s64(*val * scaling_factor, 1000000);
|
|
}
|
|
|
|
static void convert_adc_to_vbat_val(int *val)
|
|
{
|
|
*val = (*val * BCL_VBAT_SCALING_UV) / 1000;
|
|
}
|
|
|
|
static void convert_ibat_to_adc_val(struct bcl_device *bcl_perph, int *val, int scaling_factor)
|
|
{
|
|
/*
|
|
* Threshold register can be bit shifted from ADC MSB.
|
|
* So the scaling factor is half in those cases.
|
|
*/
|
|
if (bcl_perph->ibat_use_qg_adc)
|
|
*val = (int)div_s64(*val * 2000 * 2, scaling_factor);
|
|
else if (bcl_perph->no_bit_shift)
|
|
*val = (int)div_s64(*val * 1000 * bcl_ibat_ext_ranges[BCL_IBAT_RANGE_LVL0],
|
|
scaling_factor);
|
|
else
|
|
*val = (int)div_s64(*val * 2000 * bcl_ibat_ext_ranges[BCL_IBAT_RANGE_LVL0],
|
|
scaling_factor);
|
|
|
|
}
|
|
|
|
static void convert_adc_to_ibat_val(struct bcl_device *bcl_perph, int *val, int scaling_factor)
|
|
{
|
|
/* Scaling factor will be half if ibat_use_qg_adc is true */
|
|
if (bcl_perph->ibat_use_qg_adc)
|
|
*val = (int)div_s64(*val * scaling_factor, 2 * 1000);
|
|
else
|
|
*val = (int)div_s64(*val * scaling_factor,
|
|
1000 * bcl_ibat_ext_ranges[BCL_IBAT_RANGE_LVL0]);
|
|
}
|
|
|
|
static int8_t convert_ibat_to_ccm_val(int ibat)
|
|
{
|
|
int8_t val = BCL_IBAT_CCM_MAX_VAL;
|
|
|
|
val = (int8_t)((ibat - BCL_IBAT_CCM_OFFSET) / BCL_IBAT_CCM_LSB);
|
|
|
|
if (val > BCL_IBAT_CCM_MAX_VAL) {
|
|
pr_err(
|
|
"CCM thresh:%d is invalid, use MAX supported threshold\n",
|
|
ibat);
|
|
val = BCL_IBAT_CCM_MAX_VAL;
|
|
}
|
|
|
|
return val;
|
|
}
|
|
|
|
static int bcl_set_ibat(struct thermal_zone_device *tz, int low, int high)
|
|
{
|
|
int ret = 0, ibat_ua, thresh_value;
|
|
int8_t val = 0;
|
|
int16_t addr;
|
|
struct bcl_peripheral_data *bat_data =
|
|
(struct bcl_peripheral_data *)tz->devdata;
|
|
|
|
mutex_lock(&bat_data->state_trans_lock);
|
|
thresh_value = high;
|
|
if (bat_data->trip_thresh == thresh_value)
|
|
goto set_trip_exit;
|
|
|
|
if (bat_data->irq_num && bat_data->irq_enabled) {
|
|
disable_irq_nosync(bat_data->irq_num);
|
|
bat_data->irq_enabled = false;
|
|
}
|
|
if (thresh_value == INT_MAX) {
|
|
bat_data->trip_thresh = thresh_value;
|
|
goto set_trip_exit;
|
|
}
|
|
|
|
ibat_ua = thresh_value;
|
|
if (bat_data->dev->ibat_ccm_enabled)
|
|
convert_ibat_to_adc_val(bat_data->dev, &thresh_value,
|
|
BCL_IBAT_CCM_SCALING_UA *
|
|
bat_data->dev->ibat_ext_range_factor);
|
|
else if (bat_data->dev->dig_major >= BCL_GEN4_MAJOR_REV)
|
|
convert_ibat_to_adc_val(bat_data->dev, &thresh_value,
|
|
BCL_IBAT_THRESH_SCALING_REV5_UA *
|
|
bat_data->dev->ibat_ext_range_factor);
|
|
else if (bat_data->dev->dig_major >= BCL_GEN3_MAJOR_REV)
|
|
convert_ibat_to_adc_val(bat_data->dev, &thresh_value,
|
|
BCL_IBAT_SCALING_REV4_UA *
|
|
bat_data->dev->ibat_ext_range_factor);
|
|
else
|
|
convert_ibat_to_adc_val(bat_data->dev, &thresh_value,
|
|
BCL_IBAT_SCALING_UA *
|
|
bat_data->dev->ibat_ext_range_factor);
|
|
val = (int8_t)thresh_value;
|
|
switch (bat_data->type) {
|
|
case BCL_IBAT_LVL0:
|
|
case BCL_2S_IBAT_LVL0:
|
|
addr = BCL_IBAT_HIGH;
|
|
pr_debug("ibat high threshold:%d mA ADC:0x%02x\n",
|
|
ibat_ua, val);
|
|
break;
|
|
case BCL_IBAT_LVL1:
|
|
case BCL_2S_IBAT_LVL1:
|
|
addr = BCL_IBAT_TOO_HIGH;
|
|
if (bat_data->dev->dig_major >= BCL_GEN3_MAJOR_REV &&
|
|
bat_data->dev->bcl_param_1 & BCL_PARAM_HAS_IBAT_ADC)
|
|
addr = BCL_IBAT_TOO_HIGH_REV4;
|
|
if (bat_data->dev->ibat_ccm_enabled)
|
|
val = convert_ibat_to_ccm_val(ibat_ua);
|
|
pr_debug("ibat too high threshold:%d mA ADC:0x%02x\n",
|
|
ibat_ua, val);
|
|
break;
|
|
default:
|
|
goto set_trip_exit;
|
|
}
|
|
ret = bcl_write_register(bat_data->dev, addr, val);
|
|
if (ret)
|
|
goto set_trip_exit;
|
|
bat_data->trip_thresh = ibat_ua;
|
|
|
|
if (bat_data->irq_num && !bat_data->irq_enabled) {
|
|
enable_irq(bat_data->irq_num);
|
|
bat_data->irq_enabled = true;
|
|
}
|
|
|
|
set_trip_exit:
|
|
mutex_unlock(&bat_data->state_trans_lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int bcl_read_ibat(struct thermal_zone_device *tz, int *adc_value)
|
|
{
|
|
int ret = 0;
|
|
unsigned int val = 0;
|
|
struct bcl_peripheral_data *bat_data =
|
|
(struct bcl_peripheral_data *)tz->devdata;
|
|
|
|
*adc_value = val;
|
|
if (bat_data->dev->dig_major < BCL_GEN4_MAJOR_REV)
|
|
ret = bcl_read_register(bat_data->dev, BCL_IBAT_READ, &val);
|
|
else
|
|
ret = bcl_read_multi_register(bat_data->dev, BCL_IBAT_READ, &val, 2);
|
|
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* IBat ADC reading is in 2's compliment form */
|
|
if (bat_data->dev->dig_major < BCL_GEN4_MAJOR_REV)
|
|
*adc_value = sign_extend32(val, 7);
|
|
else
|
|
*adc_value = sign_extend32(val, 15);
|
|
if (val == 0) {
|
|
/*
|
|
* The sensor sometime can read a value 0 if there is
|
|
* consequtive reads
|
|
*/
|
|
*adc_value = bat_data->last_val;
|
|
} else {
|
|
if (bat_data->dev->ibat_ccm_enabled)
|
|
convert_adc_to_ibat_val(bat_data->dev, adc_value,
|
|
BCL_IBAT_CCM_SCALING_UA *
|
|
bat_data->dev->ibat_ext_range_factor);
|
|
else if (bat_data->dev->dig_major >= BCL_GEN4_MAJOR_REV)
|
|
convert_adc_nu_to_mu_val(adc_value,
|
|
BCL_IBAT_SCALING_REV5_NA);
|
|
else if (bat_data->dev->dig_major >= BCL_GEN3_MAJOR_REV)
|
|
convert_adc_to_ibat_val(bat_data->dev, adc_value,
|
|
BCL_IBAT_SCALING_REV4_UA *
|
|
bat_data->dev->ibat_ext_range_factor);
|
|
else
|
|
convert_adc_to_ibat_val(bat_data->dev, adc_value,
|
|
BCL_IBAT_SCALING_UA *
|
|
bat_data->dev->ibat_ext_range_factor);
|
|
bat_data->last_val = *adc_value;
|
|
}
|
|
pr_debug("ibat:%d mA ADC:0x%02x\n", bat_data->last_val, val);
|
|
BCL_IPC(bat_data->dev, "ibat:%d mA ADC:0x%02x\n",
|
|
bat_data->last_val, val);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static struct thermal_trip *bcl_get_vbat_trip(struct bcl_peripheral_data *bat_data)
|
|
{
|
|
int ret = 0, i;
|
|
unsigned int val = 0;
|
|
int16_t addr[BCL_VBAT_TRIP_CNT] = {BCL_VBAT_ADC_LOW, BCL_VBAT_COMP_LOW,
|
|
BCL_VBAT_COMP_TLOW};
|
|
struct thermal_trip *zone_trips;
|
|
int trip;
|
|
struct bcl_device *bcl_perph = bat_data->dev;
|
|
|
|
zone_trips = devm_kzalloc(bcl_perph->dev,
|
|
(sizeof(*zone_trips) * BCL_VBAT_TRIP_CNT), GFP_KERNEL);
|
|
if (!zone_trips) {
|
|
ret = -ENOMEM;
|
|
return NULL;
|
|
}
|
|
|
|
for (i = 0; i < BCL_VBAT_TRIP_CNT; i++) {
|
|
ret = bcl_read_register(bat_data->dev, addr[i], &val);
|
|
if (ret)
|
|
return NULL;
|
|
|
|
if (addr[i] == BCL_VBAT_ADC_LOW) {
|
|
trip = val;
|
|
convert_adc_to_vbat_thresh_val(bat_data->dev, &trip);
|
|
pr_debug("vbat trip: %d mV ADC:0x%02x\n", trip, val);
|
|
} else {
|
|
trip = BCL_VBAT_THRESH_BASE + val * 25;
|
|
if (trip > BCL_VBAT_MAX_MV)
|
|
trip = BCL_VBAT_MAX_MV;
|
|
pr_debug("vbat-%s-low trip: %d mV ADC:0x%02x\n",
|
|
(addr[i] == BCL_VBAT_COMP_LOW) ?
|
|
"too" : "critical",
|
|
trip, val);
|
|
}
|
|
zone_trips[i].temperature = trip;
|
|
zone_trips[i].type = THERMAL_TRIP_PASSIVE;
|
|
}
|
|
|
|
return zone_trips;
|
|
}
|
|
|
|
static int bcl_read_vbat_tz(struct thermal_zone_device *tzd, int *adc_value)
|
|
{
|
|
int ret = 0;
|
|
unsigned int val = 0;
|
|
struct bcl_peripheral_data *bat_data =
|
|
(struct bcl_peripheral_data *)tzd->devdata;
|
|
|
|
*adc_value = val;
|
|
if (bat_data->dev->dig_major < BCL_GEN4_MAJOR_REV)
|
|
ret = bcl_read_register(bat_data->dev, BCL_VBAT_READ, &val);
|
|
else
|
|
ret = bcl_read_multi_register(bat_data->dev, BCL_VBAT_READ,
|
|
&val, 2);
|
|
|
|
if (ret)
|
|
return ret;
|
|
|
|
*adc_value = val;
|
|
if ((bat_data->dev->dig_major < BCL_GEN4_MAJOR_REV &&
|
|
*adc_value == BCL_VBAT_NO_READING) ||
|
|
(bat_data->dev->dig_major >= BCL_GEN4_MAJOR_REV &&
|
|
*adc_value == BCL_VBAT_4G_NO_READING)) {
|
|
*adc_value = bat_data->last_val;
|
|
} else {
|
|
if (bat_data->dev->dig_major < BCL_GEN4_MAJOR_REV)
|
|
convert_adc_to_vbat_val(adc_value);
|
|
else
|
|
convert_adc_nu_to_mu_val(adc_value,
|
|
BCL_VBAT_SCALING_REV5_NV);
|
|
bat_data->last_val = *adc_value;
|
|
}
|
|
pr_debug("vbat:%d mv\n", bat_data->last_val);
|
|
BCL_IPC(bat_data->dev, "vbat:%d mv ADC:0x%02x\n",
|
|
bat_data->last_val, val);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static struct thermal_zone_device_ops vbat_tzd_ops = {
|
|
.get_temp = bcl_read_vbat_tz,
|
|
};
|
|
|
|
static struct thermal_zone_params vbat_tzp = {
|
|
.governor_name = "step_wise",
|
|
.no_hwmon = true,
|
|
.sustainable_power = 0,
|
|
.k_po = 0,
|
|
.k_pu = 0,
|
|
.k_i = 0,
|
|
.k_d = 0,
|
|
.integral_cutoff = 0,
|
|
.slope = 1,
|
|
.offset = 0
|
|
};
|
|
|
|
static int bcl_get_trend(struct thermal_zone_device *tz, const struct thermal_trip *trips,
|
|
enum thermal_trend *trend)
|
|
{
|
|
struct bcl_peripheral_data *bat_data =
|
|
(struct bcl_peripheral_data *)tz->devdata;
|
|
|
|
mutex_lock(&bat_data->state_trans_lock);
|
|
if (!bat_data->last_val)
|
|
*trend = THERMAL_TREND_DROPPING;
|
|
else
|
|
*trend = THERMAL_TREND_RAISING;
|
|
mutex_unlock(&bat_data->state_trans_lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int bcl_set_lbat(struct thermal_zone_device *tz, int low, int high)
|
|
{
|
|
struct bcl_peripheral_data *bat_data =
|
|
(struct bcl_peripheral_data *)tz->devdata;
|
|
|
|
mutex_lock(&bat_data->state_trans_lock);
|
|
|
|
if (high == INT_MAX &&
|
|
bat_data->irq_num && bat_data->irq_enabled) {
|
|
disable_irq_nosync(bat_data->irq_num);
|
|
disable_irq_wake(bat_data->irq_num);
|
|
bat_data->irq_enabled = false;
|
|
pr_debug("lbat[%d]: disable irq:%d\n",
|
|
bat_data->type,
|
|
bat_data->irq_num);
|
|
} else if (high != INT_MAX &&
|
|
bat_data->irq_num && !bat_data->irq_enabled) {
|
|
enable_irq(bat_data->irq_num);
|
|
enable_irq_wake(bat_data->irq_num);
|
|
bat_data->irq_enabled = true;
|
|
pr_debug("lbat[%d]: enable irq:%d\n",
|
|
bat_data->type,
|
|
bat_data->irq_num);
|
|
}
|
|
|
|
mutex_unlock(&bat_data->state_trans_lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int bcl_read_lbat(struct thermal_zone_device *tz, int *adc_value)
|
|
{
|
|
int ret = 0;
|
|
int ibat = 0, vbat = 0;
|
|
unsigned int val = 0;
|
|
struct bcl_peripheral_data *bat_data =
|
|
(struct bcl_peripheral_data *)tz->devdata;
|
|
struct bcl_device *bcl_perph = bat_data->dev;
|
|
|
|
*adc_value = val;
|
|
ret = bcl_read_register(bcl_perph, BCL_IRQ_STATUS, &val);
|
|
if (ret)
|
|
goto bcl_read_exit;
|
|
switch (bat_data->type) {
|
|
case BCL_LVL0:
|
|
*adc_value = val & BCL_IRQ_L0;
|
|
break;
|
|
case BCL_LVL1:
|
|
*adc_value = val & BCL_IRQ_L1;
|
|
break;
|
|
case BCL_LVL2:
|
|
*adc_value = val & BCL_IRQ_L2;
|
|
break;
|
|
default:
|
|
pr_err("Invalid sensor type:%d\n", bat_data->type);
|
|
ret = -ENODEV;
|
|
goto bcl_read_exit;
|
|
}
|
|
bat_data->last_val = *adc_value;
|
|
pr_debug("lbat:%d val:%d\n", bat_data->type,
|
|
bat_data->last_val);
|
|
if (bcl_perph->param[BCL_IBAT_LVL0].tz_dev)
|
|
bcl_read_ibat(bcl_perph->param[BCL_IBAT_LVL0].tz_dev, &ibat);
|
|
else if (bcl_perph->param[BCL_2S_IBAT_LVL0].tz_dev)
|
|
bcl_read_ibat(bcl_perph->param[BCL_2S_IBAT_LVL0].tz_dev, &ibat);
|
|
if (bcl_perph->param[BCL_VBAT_LVL0].tz_dev)
|
|
bcl_read_vbat_tz(bcl_perph->param[BCL_VBAT_LVL0].tz_dev, &vbat);
|
|
BCL_IPC(bcl_perph, "LVLbat:%d val:%d\n", bat_data->type,
|
|
bat_data->last_val);
|
|
|
|
bcl_read_exit:
|
|
return ret;
|
|
}
|
|
|
|
static int panic_lvl = BCL_TYPE_MAX;
|
|
module_param(panic_lvl, int, 0644);
|
|
|
|
static irqreturn_t bcl_handle_irq(int irq, void *data)
|
|
{
|
|
struct bcl_peripheral_data *perph_data =
|
|
(struct bcl_peripheral_data *)data;
|
|
unsigned int irq_status = 0;
|
|
int ibat = 0, vbat = 0;
|
|
struct bcl_device *bcl_perph;
|
|
|
|
if (!perph_data->tz_dev)
|
|
return IRQ_HANDLED;
|
|
bcl_perph = perph_data->dev;
|
|
bcl_read_register(bcl_perph, BCL_IRQ_STATUS, &irq_status);
|
|
if (bcl_perph->param[BCL_IBAT_LVL0].tz_dev)
|
|
bcl_read_ibat(bcl_perph->param[BCL_IBAT_LVL0].tz_dev, &ibat);
|
|
else if (bcl_perph->param[BCL_2S_IBAT_LVL0].tz_dev)
|
|
bcl_read_ibat(bcl_perph->param[BCL_2S_IBAT_LVL0].tz_dev, &ibat);
|
|
if (bcl_perph->param[BCL_VBAT_LVL0].tz_dev)
|
|
bcl_read_vbat_tz(bcl_perph->param[BCL_VBAT_LVL0].tz_dev, &vbat);
|
|
|
|
if (irq_status & perph_data->status_bit_idx) {
|
|
pr_info(
|
|
"Irq:%d triggered for bcl type:%s. status:%u ibat=%d vbat=%d\n",
|
|
irq, bcl_int_names[perph_data->type],
|
|
irq_status, ibat, vbat);
|
|
|
|
/* trigger panic on given panic level */
|
|
if (perph_data->type >= BCL_LVL0 && perph_data->type <= BCL_LVL2
|
|
&& perph_data->type == BCL_LVL0 + panic_lvl) {
|
|
panic("bcl type:%s forced panic", bcl_int_names[perph_data->type]);
|
|
}
|
|
|
|
BCL_IPC(bcl_perph,
|
|
"Irq:%d triggered for bcl type:%s. status:%u ibat=%d vbat=%d\n",
|
|
irq, bcl_int_names[perph_data->type],
|
|
irq_status, ibat, vbat);
|
|
|
|
#if IS_ENABLED(CONFIG_SEC_PM_LOG)
|
|
ss_thermal_print("BCL: %s, %u\n", bcl_int_names[perph_data->type], irq_status);
|
|
#endif
|
|
thermal_zone_device_update(perph_data->tz_dev,
|
|
THERMAL_TRIP_VIOLATED);
|
|
}
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static int bcl_get_ibat_ext_range_factor(struct platform_device *pdev,
|
|
uint32_t *ibat_range_factor)
|
|
{
|
|
int ret = 0;
|
|
const char *name;
|
|
struct nvmem_cell *cell;
|
|
size_t len;
|
|
char *buf;
|
|
uint32_t ext_range_index = 0;
|
|
|
|
ret = of_property_read_string(pdev->dev.of_node, "nvmem-cell-names", &name);
|
|
if (ret) {
|
|
*ibat_range_factor = bcl_ibat_ext_ranges[BCL_IBAT_RANGE_LVL0];
|
|
pr_debug("Default ibat range factor enabled %u\n", *ibat_range_factor);
|
|
return 0;
|
|
}
|
|
|
|
cell = nvmem_cell_get(&pdev->dev, name);
|
|
if (IS_ERR(cell)) {
|
|
dev_err(&pdev->dev, "failed to get nvmem cell %s\n", name);
|
|
return PTR_ERR(cell);
|
|
}
|
|
|
|
buf = nvmem_cell_read(cell, &len);
|
|
nvmem_cell_put(cell);
|
|
if (IS_ERR_OR_NULL(buf)) {
|
|
dev_err(&pdev->dev, "failed to read nvmem cell %s\n", name);
|
|
return PTR_ERR(buf);
|
|
}
|
|
|
|
if (len <= 0 || len > sizeof(uint32_t)) {
|
|
dev_err(&pdev->dev, "nvmem cell length out of range %zu\n", len);
|
|
kfree(buf);
|
|
return -EINVAL;
|
|
}
|
|
memcpy(&ext_range_index, buf, min(len, sizeof(ext_range_index)));
|
|
kfree(buf);
|
|
|
|
if (ext_range_index >= BCL_IBAT_RANGE_MAX) {
|
|
dev_err(&pdev->dev, "invalid BCL ibat scaling factor %d\n", ext_range_index);
|
|
return -EINVAL;
|
|
}
|
|
|
|
*ibat_range_factor = bcl_ibat_ext_ranges[ext_range_index];
|
|
pr_debug("ext_range_index %u, ibat range factor %u\n",
|
|
ext_range_index, *ibat_range_factor);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int bcl_get_devicetree_data(struct platform_device *pdev,
|
|
struct bcl_device *bcl_perph)
|
|
{
|
|
int ret = 0;
|
|
const __be32 *prop = NULL;
|
|
struct device_node *dev_node = pdev->dev.of_node;
|
|
|
|
prop = of_get_address(dev_node, 0, NULL, NULL);
|
|
if (prop) {
|
|
bcl_perph->fg_bcl_addr = be32_to_cpu(*prop);
|
|
pr_debug("fg_bcl@%04x\n", bcl_perph->fg_bcl_addr);
|
|
} else {
|
|
dev_err(&pdev->dev, "No fg_bcl registers found\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
bcl_perph->ibat_use_qg_adc = of_property_read_bool(dev_node,
|
|
"qcom,ibat-use-qg-adc-5a");
|
|
bcl_perph->no_bit_shift = of_property_read_bool(dev_node,
|
|
"qcom,pmic7-threshold");
|
|
bcl_perph->ibat_ccm_enabled = of_property_read_bool(dev_node,
|
|
"qcom,ibat-ccm-hw-support");
|
|
|
|
ret = bcl_get_ibat_ext_range_factor(pdev,
|
|
&bcl_perph->ibat_ext_range_factor);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void bcl_fetch_trip(struct platform_device *pdev, enum bcl_dev_type type,
|
|
struct bcl_peripheral_data *data,
|
|
irqreturn_t (*handle)(int, void *))
|
|
{
|
|
int ret = 0, irq_num = 0;
|
|
char *int_name = bcl_int_names[type];
|
|
|
|
mutex_lock(&data->state_trans_lock);
|
|
data->irq_num = 0;
|
|
data->irq_enabled = false;
|
|
irq_num = platform_get_irq_byname(pdev, int_name);
|
|
if (irq_num > 0 && handle) {
|
|
ret = devm_request_threaded_irq(&pdev->dev,
|
|
irq_num, NULL, handle,
|
|
IRQF_TRIGGER_RISING | IRQF_ONESHOT,
|
|
int_name, data);
|
|
if (ret) {
|
|
dev_err(&pdev->dev,
|
|
"Error requesting trip irq. err:%d\n",
|
|
ret);
|
|
mutex_unlock(&data->state_trans_lock);
|
|
return;
|
|
}
|
|
disable_irq_nosync(irq_num);
|
|
data->irq_num = irq_num;
|
|
} else if (irq_num > 0 && !handle) {
|
|
disable_irq_nosync(irq_num);
|
|
data->irq_num = irq_num;
|
|
}
|
|
mutex_unlock(&data->state_trans_lock);
|
|
}
|
|
|
|
static void bcl_vbat_init(struct platform_device *pdev,
|
|
enum bcl_dev_type type, struct bcl_device *bcl_perph)
|
|
{
|
|
struct bcl_peripheral_data *vbat = &bcl_perph->param[type];
|
|
unsigned int val = 0;
|
|
int ret;
|
|
|
|
mutex_init(&vbat->state_trans_lock);
|
|
vbat->dev = bcl_perph;
|
|
vbat->irq_num = 0;
|
|
vbat->irq_enabled = false;
|
|
vbat->tz_dev = NULL;
|
|
|
|
/* If revision 4 or above && bcl support adc, then only enable vbat */
|
|
if (bcl_perph->dig_major >= BCL_GEN3_MAJOR_REV) {
|
|
if (!(bcl_perph->bcl_param_1 & BCL_PARAM_HAS_ADC))
|
|
return;
|
|
} else {
|
|
ret = bcl_read_register(bcl_perph, BCL_VBAT_CONV_REQ, &val);
|
|
if (ret || !val)
|
|
return;
|
|
}
|
|
vbat->tz_dev = thermal_zone_device_register_with_trips("vbat",
|
|
bcl_get_vbat_trip(vbat), 3, 0, vbat,
|
|
&vbat_tzd_ops, &vbat_tzp, 0, 0);
|
|
if (IS_ERR(vbat->tz_dev)) {
|
|
pr_debug("vbat[%s] register failed. err:%ld\n",
|
|
bcl_int_names[type],
|
|
PTR_ERR(vbat->tz_dev));
|
|
vbat->tz_dev = NULL;
|
|
return;
|
|
}
|
|
|
|
ret = thermal_zone_device_enable(vbat->tz_dev);
|
|
if (ret) {
|
|
thermal_zone_device_unregister(vbat->tz_dev);
|
|
vbat->tz_dev = NULL;
|
|
}
|
|
}
|
|
|
|
static void bcl_probe_vbat(struct platform_device *pdev,
|
|
struct bcl_device *bcl_perph)
|
|
{
|
|
bcl_vbat_init(pdev, BCL_VBAT_LVL0, bcl_perph);
|
|
}
|
|
|
|
static void bcl_ibat_init(struct platform_device *pdev,
|
|
enum bcl_dev_type type, struct bcl_device *bcl_perph)
|
|
{
|
|
struct bcl_peripheral_data *ibat = &bcl_perph->param[type];
|
|
|
|
mutex_init(&ibat->state_trans_lock);
|
|
ibat->type = type;
|
|
ibat->dev = bcl_perph;
|
|
ibat->irq_num = 0;
|
|
ibat->irq_enabled = false;
|
|
ibat->ops.get_temp = bcl_read_ibat;
|
|
ibat->ops.set_trips = bcl_set_ibat;
|
|
ibat->tz_dev = devm_thermal_of_zone_register(&pdev->dev,
|
|
type, ibat, &ibat->ops);
|
|
if (IS_ERR(ibat->tz_dev)) {
|
|
pr_debug("ibat:[%s] register failed. err:%ld\n",
|
|
bcl_int_names[type],
|
|
PTR_ERR(ibat->tz_dev));
|
|
ibat->tz_dev = NULL;
|
|
return;
|
|
}
|
|
thermal_zone_device_update(ibat->tz_dev, THERMAL_DEVICE_UP);
|
|
}
|
|
|
|
static int bcl_get_ibat_config(struct platform_device *pdev,
|
|
uint32_t *ibat_config)
|
|
{
|
|
int ret = 0;
|
|
const char *name;
|
|
struct nvmem_cell *cell;
|
|
size_t len;
|
|
char *buf;
|
|
|
|
ret = of_property_read_string(pdev->dev.of_node, "nvmem-cell-names", &name);
|
|
if (ret) {
|
|
*ibat_config = 0;
|
|
pr_debug("Default ibat config enabled %u\n", *ibat_config);
|
|
return 0;
|
|
}
|
|
|
|
cell = nvmem_cell_get(&pdev->dev, name);
|
|
if (IS_ERR(cell)) {
|
|
dev_err(&pdev->dev, "failed to get nvmem cell %s\n", name);
|
|
return PTR_ERR(cell);
|
|
}
|
|
|
|
buf = nvmem_cell_read(cell, &len);
|
|
nvmem_cell_put(cell);
|
|
if (IS_ERR_OR_NULL(buf)) {
|
|
dev_err(&pdev->dev, "failed to read nvmem cell %s\n", name);
|
|
return PTR_ERR(buf);
|
|
}
|
|
|
|
if (len <= 0 || len > sizeof(uint32_t)) {
|
|
dev_err(&pdev->dev, "nvmem cell length out of range %zu\n", len);
|
|
kfree(buf);
|
|
return -EINVAL;
|
|
}
|
|
memcpy(ibat_config, buf, min(len, sizeof(*ibat_config)));
|
|
kfree(buf);
|
|
|
|
return 0;
|
|
}
|
|
static void bcl_probe_ibat(struct platform_device *pdev,
|
|
struct bcl_device *bcl_perph)
|
|
{
|
|
uint32_t bcl_config = 0;
|
|
|
|
bcl_get_ibat_config(pdev, &bcl_config);
|
|
|
|
if (bcl_config == 1) {
|
|
bcl_ibat_init(pdev, BCL_2S_IBAT_LVL0, bcl_perph);
|
|
bcl_ibat_init(pdev, BCL_2S_IBAT_LVL1, bcl_perph);
|
|
} else {
|
|
bcl_ibat_init(pdev, BCL_IBAT_LVL0, bcl_perph);
|
|
bcl_ibat_init(pdev, BCL_IBAT_LVL1, bcl_perph);
|
|
}
|
|
}
|
|
|
|
static void bcl_lvl_init(struct platform_device *pdev,
|
|
enum bcl_dev_type type, int sts_bit_idx, struct bcl_device *bcl_perph)
|
|
{
|
|
struct bcl_peripheral_data *lbat = &bcl_perph->param[type];
|
|
|
|
mutex_init(&lbat->state_trans_lock);
|
|
lbat->type = type;
|
|
lbat->dev = bcl_perph;
|
|
lbat->status_bit_idx = sts_bit_idx;
|
|
bcl_fetch_trip(pdev, type, lbat, bcl_handle_irq);
|
|
if (lbat->irq_num <= 0)
|
|
return;
|
|
|
|
lbat->ops.get_temp = bcl_read_lbat;
|
|
lbat->ops.set_trips = bcl_set_lbat;
|
|
lbat->ops.get_trend = bcl_get_trend;
|
|
lbat->ops.change_mode = qti_tz_change_mode;
|
|
|
|
lbat->tz_dev = devm_thermal_of_zone_register(&pdev->dev,
|
|
type, lbat, &lbat->ops);
|
|
if (IS_ERR(lbat->tz_dev)) {
|
|
pr_debug("lbat:[%s] register failed. err:%ld\n",
|
|
bcl_int_names[type],
|
|
PTR_ERR(lbat->tz_dev));
|
|
lbat->tz_dev = NULL;
|
|
return;
|
|
}
|
|
thermal_zone_device_update(lbat->tz_dev, THERMAL_DEVICE_UP);
|
|
}
|
|
|
|
static void bcl_probe_lvls(struct platform_device *pdev,
|
|
struct bcl_device *bcl_perph)
|
|
{
|
|
bcl_lvl_init(pdev, BCL_LVL0, BCL_IRQ_L0, bcl_perph);
|
|
bcl_lvl_init(pdev, BCL_LVL1, BCL_IRQ_L1, bcl_perph);
|
|
bcl_lvl_init(pdev, BCL_LVL2, BCL_IRQ_L2, bcl_perph);
|
|
}
|
|
|
|
static int bcl_version_init(struct bcl_device *bcl_perph)
|
|
{
|
|
int ret = 0;
|
|
unsigned int val = 0;
|
|
|
|
ret = bcl_read_register(bcl_perph, BCL_REVISION2, &val);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
bcl_perph->dig_major = val;
|
|
ret = bcl_read_register(bcl_perph, BCL_REVISION1, &val);
|
|
if (ret >= 0)
|
|
bcl_perph->dig_minor = val;
|
|
|
|
if (bcl_perph->dig_major >= BCL_GEN3_MAJOR_REV) {
|
|
ret = bcl_read_register(bcl_perph, BCL_PARAM_1, &val);
|
|
if (ret < 0)
|
|
return ret;
|
|
bcl_perph->bcl_param_1 = val;
|
|
|
|
val = 0;
|
|
bcl_read_register(bcl_perph, BCL_PARAM_2, &val);
|
|
bcl_perph->bcl_type = val;
|
|
} else {
|
|
bcl_perph->bcl_param_1 = 0;
|
|
bcl_perph->bcl_type = 0;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void bcl_configure_bcl_peripheral(struct bcl_device *bcl_perph)
|
|
{
|
|
bcl_write_register(bcl_perph, BCL_MONITOR_EN, BIT(7));
|
|
}
|
|
|
|
static int bcl_remove(struct platform_device *pdev)
|
|
{
|
|
int i = 0;
|
|
struct bcl_device *bcl_perph =
|
|
(struct bcl_device *)dev_get_drvdata(&pdev->dev);
|
|
|
|
for (; i < BCL_TYPE_MAX; i++) {
|
|
if (!bcl_perph->param[i].tz_dev)
|
|
continue;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int bcl_probe(struct platform_device *pdev)
|
|
{
|
|
struct bcl_device *bcl_perph = NULL;
|
|
char bcl_name[40];
|
|
int err = 0;
|
|
|
|
if (bcl_device_ct >= MAX_PERPH_COUNT) {
|
|
dev_err(&pdev->dev, "Max bcl peripheral supported already.\n");
|
|
return -EINVAL;
|
|
}
|
|
bcl_devices[bcl_device_ct] = devm_kzalloc(&pdev->dev,
|
|
sizeof(*bcl_devices[0]), GFP_KERNEL);
|
|
if (!bcl_devices[bcl_device_ct])
|
|
return -ENOMEM;
|
|
bcl_perph = bcl_devices[bcl_device_ct];
|
|
bcl_perph->dev = &pdev->dev;
|
|
|
|
bcl_perph->regmap = dev_get_regmap(pdev->dev.parent, NULL);
|
|
if (!bcl_perph->regmap) {
|
|
dev_err(&pdev->dev, "Couldn't get parent's regmap\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
bcl_device_ct++;
|
|
err = bcl_get_devicetree_data(pdev, bcl_perph);
|
|
if (err) {
|
|
bcl_device_ct--;
|
|
return err;
|
|
}
|
|
err = bcl_version_init(bcl_perph);
|
|
if (err) {
|
|
bcl_device_ct--;
|
|
return err;
|
|
}
|
|
bcl_probe_vbat(pdev, bcl_perph);
|
|
bcl_probe_ibat(pdev, bcl_perph);
|
|
bcl_probe_lvls(pdev, bcl_perph);
|
|
bcl_configure_bcl_peripheral(bcl_perph);
|
|
|
|
dev_set_drvdata(&pdev->dev, bcl_perph);
|
|
|
|
snprintf(bcl_name, sizeof(bcl_name), "bcl_0x%04x_%d",
|
|
bcl_perph->fg_bcl_addr,
|
|
bcl_device_ct - 1);
|
|
|
|
bcl_perph->ipc_log = ipc_log_context_create(IPC_LOGPAGES,
|
|
bcl_name, 0);
|
|
if (!bcl_perph->ipc_log)
|
|
pr_err("%s: unable to create IPC Logging for %s\n",
|
|
__func__, bcl_name);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id bcl_match[] = {
|
|
{
|
|
.compatible = "qcom,bcl-v5",
|
|
},
|
|
{},
|
|
};
|
|
|
|
static struct platform_driver bcl_driver = {
|
|
.probe = bcl_probe,
|
|
.remove = bcl_remove,
|
|
.driver = {
|
|
.name = BCL_DRIVER_NAME,
|
|
.of_match_table = bcl_match,
|
|
},
|
|
};
|
|
|
|
module_platform_driver(bcl_driver);
|
|
MODULE_LICENSE("GPL");
|