From c91e08d2c76db5a5e6ff0ccaf097b882dade6f55 Mon Sep 17 00:00:00 2001 From: Ram Chandrasekar Date: Wed, 10 Jan 2018 12:01:58 -0700 Subject: [PATCH] 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 Signed-off-by: Asha Magadi Venkateshamurthy --- drivers/thermal/qcom/Kconfig | 11 + drivers/thermal/qcom/Makefile | 1 + drivers/thermal/qcom/bcl_peripheral.c | 779 ++++++++++++++++++++++++++ 3 files changed, 791 insertions(+) create mode 100644 drivers/thermal/qcom/bcl_peripheral.c diff --git a/drivers/thermal/qcom/Kconfig b/drivers/thermal/qcom/Kconfig index 247162792d1b..cc78d3711bfe 100644 --- a/drivers/thermal/qcom/Kconfig +++ b/drivers/thermal/qcom/Kconfig @@ -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 diff --git a/drivers/thermal/qcom/Makefile b/drivers/thermal/qcom/Makefile index 71785f2fad39..8a0b6190ec9f 100644 --- a/drivers/thermal/qcom/Makefile +++ b/drivers/thermal/qcom/Makefile @@ -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 diff --git a/drivers/thermal/qcom/bcl_peripheral.c b/drivers/thermal/qcom/bcl_peripheral.c new file mode 100644 index 000000000000..a9cf4f5e1155 --- /dev/null +++ b/drivers/thermal/qcom/bcl_peripheral.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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);