Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f75836aa authored by qctecmdr's avatar qctecmdr Committed by Gerrit - the friendly Code Review server
Browse files

Merge "drivers: thermal: bcl: Add BCL peripheral driver"

parents a9dc695e c91e08d2
Loading
Loading
Loading
Loading
+11 −0
Original line number Diff line number Diff line
@@ -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
+1 −0
Original line number Diff line number Diff line
@@ -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
+779 −0
Original line number Diff line number Diff line
// 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);