drivers: thermal: bcl: Add BCL peripheral driver

Add snapshot of BCL driver from kernel 4.14 as of 'commit
<2a82a82> ("Merge "msm: ais: fix soc resource start
sequence")'.

Change-Id: I903fdec14b1657e3da762e254de6a391ef8e630a
Signed-off-by: Ram Chandrasekar <rkumbako@codeaurora.org>
Signed-off-by: Asha Magadi Venkateshamurthy <amagad@codeaurora.org>
This commit is contained in:
Ram Chandrasekar 2018-01-10 12:01:58 -07:00 committed by Asha Magadi Venkateshamurthy
parent 9f6b4236db
commit c91e08d2c7
3 changed files with 791 additions and 0 deletions

View file

@ -102,6 +102,17 @@ config REGULATOR_COOLING_DEVICE
If you want this support, you should say Y here.
config MSM_BCL_PERIPHERAL_CTL
bool "BCL driver to control the PMIC BCL peripheral"
depends on SPMI && THERMAL_OF
help
Say Y here to enable this BCL PMIC peripheral driver. This driver
provides routines to configure and monitor the BCL
PMIC peripheral. This driver registers the battery current and
voltage sensors with the thermal core framework and can take
threshold input and notify the thermal core when the threshold is
reached.
config QTI_CPU_ISOLATE_COOLING_DEVICE
bool "QTI CPU Isolate cooling devices"
depends on THERMAL_OF

View file

@ -9,6 +9,7 @@ obj-$(CONFIG_QTI_QMI_COOLING_DEVICE) += thermal_mitigation_device_service_v01.o
obj-$(CONFIG_QTI_THERMAL_LIMITS_DCVS) += msm_lmh_dcvs.o lmh_dbg.o
obj-$(CONFIG_QTI_AOP_REG_COOLING_DEVICE) += regulator_aop_cdev.o
obj-$(CONFIG_REGULATOR_COOLING_DEVICE) += regulator_cdev.o
obj-$(CONFIG_MSM_BCL_PERIPHERAL_CTL) += bcl_peripheral.o
obj-$(CONFIG_QTI_CPU_ISOLATE_COOLING_DEVICE) += cpu_isolate.o
obj-$(CONFIG_QTI_LMH_CPU_VDD_COOLING_DEVICE) += lmh_cpu_vdd_cdev.o
obj-$(CONFIG_QTI_LIMITS_ISENSE_CDSP) += msm_isense_cdsp.o

View file

@ -0,0 +1,779 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2014-2020, The Linux Foundation. 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/power_supply.h>
#include <linux/thermal.h>
#include "../thermal_core.h"
#define BCL_DRIVER_NAME "bcl_peripheral"
#define BCL_VBAT_INT "bcl-low-vbat"
#define BCL_VLOW_VBAT_INT "bcl-very-low-vbat"
#define BCL_CLOW_VBAT_INT "bcl-crit-low-vbat"
#define BCL_IBAT_INT "bcl-high-ibat"
#define BCL_VHIGH_IBAT_INT "bcl-very-high-ibat"
#define BCL_MONITOR_EN 0x46
#define BCL_VBAT_MIN 0x5C
#define BCL_IBAT_MAX 0x5D
#define BCL_MAX_MIN_CLR 0x48
#define BCL_IBAT_MAX_CLR 3
#define BCL_VBAT_MIN_CLR 2
#define BCL_VBAT_ADC_LOW 0x72
#define BCL_VBAT_COMP_LOW 0x75
#define BCL_VBAT_COMP_TLOW 0x76
#define BCL_IBAT_HIGH 0x78
#define BCL_IBAT_TOO_HIGH 0x79
#define BCL_LMH_CFG 0xA3
#define BCL_CFG 0x6A
#define LMH_INT_POL_HIGH 0x12
#define LMH_INT_EN 0x15
#define BCL_VBAT_SCALING 39000
#define BCL_IBAT_SCALING 80
#define BCL_LMH_CFG_VAL 0x3
#define BCL_CFG_VAL 0x81
#define LMH_INT_VAL 0x7
#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 BCL_STD_VBAT_NR 9
#define BCL_VBAT_NO_READING 127
enum bcl_dev_type {
BCL_HIGH_IBAT,
BCL_VHIGH_IBAT,
BCL_LOW_VBAT,
BCL_VLOW_VBAT,
BCL_CLOW_VBAT,
BCL_SOC_MONITOR,
BCL_TYPE_MAX,
};
struct bcl_peripheral_data {
int irq_num;
long trip_temp;
int trip_val;
int last_val;
struct mutex state_trans_lock;
bool irq_enabled;
struct thermal_zone_of_device_ops ops;
struct thermal_zone_device *tz_dev;
};
struct bcl_device {
struct regmap *regmap;
uint16_t fg_bcl_addr;
uint16_t fg_lmh_addr;
struct notifier_block psy_nb;
struct work_struct soc_eval_work;
struct bcl_peripheral_data param[BCL_TYPE_MAX];
};
static struct bcl_device *bcl_perph;
static int vbat_low[BCL_STD_VBAT_NR] = {
2400, 2500, 2600, 2700, 2800, 2900,
3000, 3100, 3200};
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 = regmap_bulk_read(bcl_perph->regmap,
(bcl_perph->fg_bcl_addr + reg_offset),
data, len);
if (ret < 0) {
pr_err("Error reading register %d. err:%d\n", reg_offset, ret);
return ret;
}
return ret;
}
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 = regmap_write(bcl_perph->regmap, (base + reg_offset), *write_buf);
if (ret < 0) {
pr_err("Error reading register %d. err:%d\n", 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->fg_bcl_addr, data);
}
static void convert_vbat_to_adc_val(int *val)
{
*val = (*val * 1000) / BCL_VBAT_SCALING;
}
static void convert_adc_to_vbat_val(int *val)
{
*val = *val * BCL_VBAT_SCALING / 1000;
}
static void convert_ibat_to_adc_val(int *val)
{
*val = *val / BCL_IBAT_SCALING;
}
static void convert_adc_to_ibat_val(int *val)
{
*val = *val * BCL_IBAT_SCALING;
}
static int bcl_set_ibat(void *data, 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 *)data;
thresh_value = high;
if (bat_data->trip_temp == thresh_value)
return 0;
mutex_lock(&bat_data->state_trans_lock);
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_temp = thresh_value;
goto set_trip_exit;
}
ibat_ua = thresh_value;
convert_ibat_to_adc_val(&thresh_value);
val = (int8_t)thresh_value;
if (&bcl_perph->param[BCL_HIGH_IBAT] == bat_data) {
addr = BCL_IBAT_HIGH;
pr_debug("ibat high threshold:%d mA ADC:0x%02x\n",
ibat_ua, val);
} else if (&bcl_perph->param[BCL_VHIGH_IBAT] == bat_data) {
addr = BCL_IBAT_TOO_HIGH;
pr_debug("ibat too high threshold:%d mA ADC:0x%02x\n",
ibat_ua, val);
} else {
goto set_trip_exit;
}
ret = bcl_write_register(addr, val);
if (ret) {
pr_err("Error accessing BCL peripheral. err:%d\n", ret);
goto set_trip_exit;
}
bat_data->trip_temp = 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_set_vbat(void *data, int low, int high)
{
int ret = 0, vbat_uv, vbat_idx, thresh_value;
int8_t val = 0;
struct bcl_peripheral_data *bat_data =
(struct bcl_peripheral_data *)data;
uint16_t addr;
thresh_value = low;
if (bat_data->trip_temp == thresh_value)
return 0;
mutex_lock(&bat_data->state_trans_lock);
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_MIN) {
bat_data->trip_temp = thresh_value;
goto set_trip_exit;
}
vbat_uv = thresh_value;
convert_vbat_to_adc_val(&thresh_value);
val = (int8_t)thresh_value;
/*
* very low and critical low trip can support only standard
* trip thresholds
*/
if (&bcl_perph->param[BCL_LOW_VBAT] == bat_data) {
addr = BCL_VBAT_ADC_LOW;
pr_debug("vbat low threshold:%d mv ADC:0x%02x\n",
vbat_uv, val);
} else if (&bcl_perph->param[BCL_VLOW_VBAT] == bat_data) {
/*
* Scan the standard voltage table, sorted in ascending order
* and find the closest threshold that is lower or equal to
* the requested value. Passive trip supports thresholds
* indexed from 1...BCL_STD_VBAT_NR in the voltage table.
*/
for (vbat_idx = 2; vbat_idx < BCL_STD_VBAT_NR;
vbat_idx++) {
if (vbat_uv >= vbat_low[vbat_idx])
continue;
break;
}
addr = BCL_VBAT_COMP_LOW;
val = vbat_idx - 2;
vbat_uv = vbat_low[vbat_idx - 1];
pr_debug("vbat too low threshold:%d mv ADC:0x%02x\n",
vbat_uv, val);
} else if (&bcl_perph->param[BCL_CLOW_VBAT] == bat_data) {
/* Hot trip supports thresholds indexed from
* 0...BCL_STD_VBAT_NR-1 in the voltage table.
*/
for (vbat_idx = 1; vbat_idx < (BCL_STD_VBAT_NR - 1);
vbat_idx++) {
if (vbat_uv >= vbat_low[vbat_idx])
continue;
break;
}
addr = BCL_VBAT_COMP_TLOW;
val = vbat_idx - 1;
vbat_uv = vbat_low[vbat_idx - 1];
pr_debug("vbat critic low threshold:%d mv ADC:0x%02x\n",
vbat_uv, val);
} else {
goto set_trip_exit;
}
ret = bcl_write_register(addr, val);
if (ret) {
pr_err("Error accessing BCL peripheral. err:%d\n", ret);
goto set_trip_exit;
}
bat_data->trip_temp = vbat_uv;
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_clear_vbat_min(void)
{
int ret = 0;
ret = bcl_write_register(BCL_MAX_MIN_CLR,
BIT(BCL_VBAT_MIN_CLR));
if (ret)
pr_err("Error in clearing vbat min reg. err:%d\n", ret);
return ret;
}
static int bcl_clear_ibat_max(void)
{
int ret = 0;
ret = bcl_write_register(BCL_MAX_MIN_CLR,
BIT(BCL_IBAT_MAX_CLR));
if (ret)
pr_err("Error in clearing ibat max reg. err:%d\n", ret);
return ret;
}
static int bcl_read_ibat(void *data, int *adc_value)
{
int ret = 0, timeout = 0;
int8_t val[VAL_CP_REG_BUF_LEN] = {0};
struct bcl_peripheral_data *bat_data =
(struct bcl_peripheral_data *)data;
*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;
*adc_value = bat_data->last_val;
goto bcl_read_exit;
}
*adc_value = (int)val[VAL_REG_BUF_OFFSET];
if (*adc_value == 0) {
/*
* The sensor sometime can read a value 0 if there is
* consequtive reads
*/
*adc_value = bat_data->last_val;
} else {
convert_adc_to_ibat_val(adc_value);
bat_data->last_val = *adc_value;
}
pr_debug("ibat:%d mA\n", bat_data->last_val);
bcl_read_exit:
return ret;
}
static int bcl_read_ibat_and_clear(void *data, int *adc_value)
{
int ret = 0;
ret = bcl_read_ibat(data, adc_value);
if (ret)
return ret;
return bcl_clear_ibat_max();
}
static int bcl_read_vbat(void *data, int *adc_value)
{
int ret = 0, timeout = 0;
int8_t val[VAL_CP_REG_BUF_LEN] = {0};
struct bcl_peripheral_data *bat_data =
(struct bcl_peripheral_data *)data;
*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];
if (*adc_value == BCL_VBAT_NO_READING) {
*adc_value = bat_data->last_val;
} else {
convert_adc_to_vbat_val(adc_value);
bat_data->last_val = *adc_value;
}
pr_debug("vbat:%d mv\n", bat_data->last_val);
bcl_read_exit:
return ret;
}
static int bcl_read_vbat_and_clear(void *data, int *adc_value)
{
int ret;
ret = bcl_read_vbat(data, adc_value);
if (ret)
return ret;
return bcl_clear_vbat_min();
}
static irqreturn_t bcl_handle_ibat(int irq, void *data)
{
struct bcl_peripheral_data *perph_data =
(struct bcl_peripheral_data *)data;
mutex_lock(&perph_data->state_trans_lock);
if (!perph_data->irq_enabled) {
WARN_ON(1);
disable_irq_nosync(irq);
perph_data->irq_enabled = false;
goto exit_intr;
}
mutex_unlock(&perph_data->state_trans_lock);
of_thermal_handle_trip(perph_data->tz_dev);
return IRQ_HANDLED;
exit_intr:
mutex_unlock(&perph_data->state_trans_lock);
return IRQ_HANDLED;
}
static irqreturn_t bcl_handle_vbat(int irq, void *data)
{
struct bcl_peripheral_data *perph_data =
(struct bcl_peripheral_data *)data;
mutex_lock(&perph_data->state_trans_lock);
if (!perph_data->irq_enabled) {
WARN_ON(1);
disable_irq_nosync(irq);
perph_data->irq_enabled = false;
goto exit_intr;
}
mutex_unlock(&perph_data->state_trans_lock);
of_thermal_handle_trip(perph_data->tz_dev);
return IRQ_HANDLED;
exit_intr:
mutex_unlock(&perph_data->state_trans_lock);
return IRQ_HANDLED;
}
static int bcl_get_devicetree_data(struct platform_device *pdev)
{
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_user_adc@%04x\n", bcl_perph->fg_bcl_addr);
} else {
dev_err(&pdev->dev, "No fg_user_adc registers found\n");
return -ENODEV;
}
prop = of_get_address(dev_node, 1, NULL, NULL);
if (prop) {
bcl_perph->fg_lmh_addr = be32_to_cpu(*prop);
pr_debug("fg_lmh@%04x\n", bcl_perph->fg_lmh_addr);
} else {
dev_err(&pdev->dev, "No fg_lmh registers found\n");
return -ENODEV;
}
return ret;
}
static int bcl_set_soc(void *data, int low, int high)
{
struct bcl_peripheral_data *bat_data =
(struct bcl_peripheral_data *)data;
if (low == bat_data->trip_temp)
return 0;
mutex_lock(&bat_data->state_trans_lock);
pr_debug("low soc threshold:%d\n", low);
bat_data->trip_temp = low;
if (low == INT_MIN) {
bat_data->irq_enabled = false;
goto unlock_and_exit;
}
bat_data->irq_enabled = true;
schedule_work(&bcl_perph->soc_eval_work);
unlock_and_exit:
mutex_unlock(&bat_data->state_trans_lock);
return 0;
}
static int bcl_read_soc(void *data, int *val)
{
static struct power_supply *batt_psy;
union power_supply_propval ret = {0,};
int err = 0;
*val = 100;
if (!batt_psy)
batt_psy = power_supply_get_by_name("battery");
if (batt_psy) {
err = power_supply_get_property(batt_psy,
POWER_SUPPLY_PROP_CAPACITY, &ret);
if (err) {
pr_err("battery percentage read error:%d\n",
err);
return err;
}
*val = ret.intval;
}
pr_debug("soc:%d\n", *val);
return err;
}
static void bcl_evaluate_soc(struct work_struct *work)
{
int battery_percentage;
struct bcl_peripheral_data *perph_data =
&bcl_perph->param[BCL_SOC_MONITOR];
if (bcl_read_soc((void *)perph_data, &battery_percentage))
return;
mutex_lock(&perph_data->state_trans_lock);
if (!perph_data->irq_enabled)
goto eval_exit;
if (battery_percentage > perph_data->trip_temp)
goto eval_exit;
perph_data->trip_val = battery_percentage;
mutex_unlock(&perph_data->state_trans_lock);
of_thermal_handle_trip(perph_data->tz_dev);
return;
eval_exit:
mutex_unlock(&perph_data->state_trans_lock);
}
static int battery_supply_callback(struct notifier_block *nb,
unsigned long event, void *data)
{
struct power_supply *psy = data;
if (strcmp(psy->desc->name, "battery"))
return NOTIFY_OK;
schedule_work(&bcl_perph->soc_eval_work);
return NOTIFY_OK;
}
static void bcl_fetch_trip(struct platform_device *pdev, const char *int_name,
struct bcl_peripheral_data *data,
irqreturn_t (*handle)(int, void *))
{
int ret = 0, irq_num = 0;
/*
* Allow flexibility for the HLOS to set the trip temperature for
* all the thresholds but handle the interrupt for only one vbat
* and ibat interrupt. The LMH-DCVSh will handle and mitigate for the
* rest of the ibat/vbat interrupts.
*/
if (!handle) {
mutex_lock(&data->state_trans_lock);
data->irq_num = 0;
data->irq_enabled = false;
mutex_unlock(&data->state_trans_lock);
return;
}
irq_num = platform_get_irq_byname(pdev, int_name);
if (irq_num) {
mutex_lock(&data->state_trans_lock);
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;
data->irq_enabled = false;
mutex_unlock(&data->state_trans_lock);
}
}
static void bcl_probe_soc(struct platform_device *pdev)
{
int ret = 0;
struct bcl_peripheral_data *soc_data;
soc_data = &bcl_perph->param[BCL_SOC_MONITOR];
mutex_init(&soc_data->state_trans_lock);
soc_data->ops.get_temp = bcl_read_soc;
soc_data->ops.set_trips = bcl_set_soc;
INIT_WORK(&bcl_perph->soc_eval_work, bcl_evaluate_soc);
bcl_perph->psy_nb.notifier_call = battery_supply_callback;
ret = power_supply_reg_notifier(&bcl_perph->psy_nb);
if (ret < 0) {
pr_err("Unable to register soc notifier. err:%d\n", ret);
return;
}
soc_data->tz_dev = thermal_zone_of_sensor_register(&pdev->dev,
BCL_SOC_MONITOR, soc_data, &soc_data->ops);
if (IS_ERR(soc_data->tz_dev)) {
pr_err("vbat register failed. err:%ld\n",
PTR_ERR(soc_data->tz_dev));
return;
}
thermal_zone_device_update(soc_data->tz_dev, THERMAL_DEVICE_UP);
schedule_work(&bcl_perph->soc_eval_work);
}
static void bcl_vbat_init(struct platform_device *pdev,
struct bcl_peripheral_data *vbat, enum bcl_dev_type type)
{
mutex_init(&vbat->state_trans_lock);
switch (type) {
case BCL_LOW_VBAT:
bcl_fetch_trip(pdev, BCL_VBAT_INT, vbat, bcl_handle_vbat);
break;
case BCL_VLOW_VBAT:
bcl_fetch_trip(pdev, BCL_VLOW_VBAT_INT, vbat, NULL);
break;
case BCL_CLOW_VBAT:
bcl_fetch_trip(pdev, BCL_CLOW_VBAT_INT, vbat, NULL);
break;
default:
return;
}
vbat->ops.get_temp = bcl_read_vbat_and_clear;
vbat->ops.set_trips = bcl_set_vbat;
vbat->tz_dev = thermal_zone_of_sensor_register(&pdev->dev,
type, vbat, &vbat->ops);
if (IS_ERR(vbat->tz_dev)) {
pr_err("vbat register failed. err:%ld\n",
PTR_ERR(vbat->tz_dev));
return;
}
thermal_zone_device_update(vbat->tz_dev, THERMAL_DEVICE_UP);
}
static void bcl_probe_vbat(struct platform_device *pdev)
{
bcl_vbat_init(pdev, &bcl_perph->param[BCL_LOW_VBAT], BCL_LOW_VBAT);
bcl_vbat_init(pdev, &bcl_perph->param[BCL_VLOW_VBAT], BCL_VLOW_VBAT);
bcl_vbat_init(pdev, &bcl_perph->param[BCL_CLOW_VBAT], BCL_CLOW_VBAT);
}
static void bcl_ibat_init(struct platform_device *pdev,
struct bcl_peripheral_data *ibat, enum bcl_dev_type type)
{
mutex_init(&ibat->state_trans_lock);
if (type == BCL_HIGH_IBAT)
bcl_fetch_trip(pdev, BCL_IBAT_INT, ibat, bcl_handle_ibat);
else
bcl_fetch_trip(pdev, BCL_VHIGH_IBAT_INT, ibat, NULL);
ibat->ops.get_temp = bcl_read_ibat_and_clear;
ibat->ops.set_trips = bcl_set_ibat;
ibat->tz_dev = thermal_zone_of_sensor_register(&pdev->dev,
type, ibat, &ibat->ops);
if (IS_ERR(ibat->tz_dev)) {
pr_err("ibat register failed. err:%ld\n",
PTR_ERR(ibat->tz_dev));
return;
}
thermal_zone_device_update(ibat->tz_dev, THERMAL_DEVICE_UP);
}
static void bcl_probe_ibat(struct platform_device *pdev)
{
bcl_ibat_init(pdev, &bcl_perph->param[BCL_HIGH_IBAT], BCL_HIGH_IBAT);
bcl_ibat_init(pdev, &bcl_perph->param[BCL_VHIGH_IBAT], BCL_VHIGH_IBAT);
}
static void bcl_configure_lmh_peripheral(void)
{
bcl_write_register(BCL_LMH_CFG, BCL_LMH_CFG_VAL);
bcl_write_register(BCL_CFG, BCL_CFG_VAL);
bcl_write_general_register(LMH_INT_POL_HIGH,
bcl_perph->fg_lmh_addr, LMH_INT_VAL);
bcl_write_general_register(LMH_INT_EN,
bcl_perph->fg_lmh_addr, LMH_INT_VAL);
}
static int bcl_remove(struct platform_device *pdev)
{
int i = 0;
for (; i < BCL_TYPE_MAX; i++) {
if (!bcl_perph->param[i].tz_dev)
continue;
if (i == BCL_SOC_MONITOR) {
power_supply_unreg_notifier(&bcl_perph->psy_nb);
flush_work(&bcl_perph->soc_eval_work);
}
thermal_zone_of_sensor_unregister(&pdev->dev,
bcl_perph->param[i].tz_dev);
}
bcl_perph = NULL;
return 0;
}
static int bcl_probe(struct platform_device *pdev)
{
int ret = 0;
bcl_perph = devm_kzalloc(&pdev->dev, sizeof(*bcl_perph), GFP_KERNEL);
if (!bcl_perph)
return -ENOMEM;
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_get_devicetree_data(pdev);
bcl_probe_ibat(pdev);
bcl_probe_vbat(pdev);
bcl_probe_soc(pdev);
bcl_configure_lmh_peripheral();
dev_set_drvdata(&pdev->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_remove(pdev);
return ret;
}
static const struct of_device_id bcl_match[] = {
{
.compatible = "qcom,msm-bcl-lmh",
},
{},
};
static struct platform_driver bcl_driver = {
.probe = bcl_probe,
.remove = bcl_remove,
.driver = {
.name = BCL_DRIVER_NAME,
.owner = THIS_MODULE,
.of_match_table = bcl_match,
},
};
builtin_platform_driver(bcl_driver);