blob: f88b9409621b1712502e6e7557dedb3d64fc44ec [file] [log] [blame]
/*
* Copyright (c) 2018-2019, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#define pr_fmt(fmt) "%s:%s " fmt, KBUILD_MODNAME, __func__
#include <linux/module.h>
#include <linux/interrupt.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 "../thermal_core.h"
#include "qti_virtual_sensor.h"
#define BCL_DRIVER_NAME "bcl_pmic5"
#define BCL_MONITOR_EN 0x46
#define BCL_IRQ_STATUS 0x09
#define BCL_IBAT_HIGH 0x4B
#define BCL_IBAT_TOO_HIGH 0x4C
#define BCL_IBAT_READ 0x86
#define BCL_IBAT_SCALING_UA 78127
#define BCL_VBAT_SCALING 25
#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_IRQ_VCMP_L0 0x1
#define BCL_IRQ_VCMP_L1 0x2
#define BCL_IRQ_VCMP_L2 0x4
#define BCL_IRQ_IBAT_L0 0x10
#define BCL_IRQ_IBAT_L1 0x20
#define BCL_IRQ_IBAT_L2 0x40
#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 MAX_PERPH_COUNT 2
enum bcl_dev_type {
BCL_IBAT_LVL0,
BCL_IBAT_LVL1,
BCL_VBAT_LVL0,
BCL_VBAT_LVL1,
BCL_VBAT_LVL2,
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",
};
struct bcl_device;
struct bcl_peripheral_data {
int irq_num;
int status_bit_idx;
long int trip_thresh;
int last_val;
struct mutex state_trans_lock;
bool irq_enabled;
enum bcl_dev_type type;
struct thermal_zone_of_device_ops ops;
struct thermal_zone_device *tz_dev;
struct bcl_device *dev;
};
struct bcl_device {
struct device *device;
struct regmap *regmap;
uint16_t fg_bcl_addr;
struct bcl_peripheral_data param[BCL_TYPE_MAX];
struct thermal_zone_device
*virtual_tz_dev[THERMAL_MAX_VIRT_SENSORS];
int num_count;
};
static struct bcl_device *bcl_devices[MAX_PERPH_COUNT];
static int bcl_device_ct;
static bool ibat_use_qg_adc;
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(int *val)
{
/*
* Threshold register is bit shifted from ADC MSB.
* So the scaling factor is half.
*/
*val = (*val * BCL_VBAT_SCALING_UV) / 2000;
}
static void convert_adc_to_vbat_val(int *val)
{
*val = (*val * BCL_VBAT_SCALING_UV) / 1000;
}
static void convert_vbat_to_adc_val(int *val)
{
*val = *val / BCL_VBAT_SCALING + 1;
}
static void convert_ibat_to_adc_val(int *val)
{
/*
* Threshold register is bit shifted from ADC MSB.
* So the scaling factor is half.
*/
if (ibat_use_qg_adc)
*val = (int)div_s64(*val * 2000 * 2, BCL_IBAT_SCALING_UA);
else
*val = (int)div_s64(*val * 2000, BCL_IBAT_SCALING_UA);
}
static void convert_adc_to_ibat_val(int *val)
{
/* Scaling factor will be half if ibat_use_qg_adc is true */
if (ibat_use_qg_adc)
*val = (int)div_s64(*val * BCL_IBAT_SCALING_UA, 2 * 1000);
else
*val = (int)div_s64(*val * BCL_IBAT_SCALING_UA, 1000);
}
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;
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;
convert_ibat_to_adc_val(&thresh_value);
val = (int8_t)thresh_value;
switch (bat_data->type) {
case BCL_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:
addr = BCL_IBAT_TOO_HIGH;
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(void *data, int *adc_value)
{
int ret = 0;
unsigned int val = 0;
struct bcl_peripheral_data *bat_data =
(struct bcl_peripheral_data *)data;
*adc_value = val;
ret = bcl_read_register(bat_data->dev, BCL_IBAT_READ, &val);
if (ret)
goto bcl_read_exit;
/* IBat ADC reading is in 2's compliment form */
*adc_value = sign_extend32(val, 7);
if (val == 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 ADC:0x%02x\n", bat_data->last_val, val);
bcl_read_exit:
return ret;
}
static int bcl_get_vbat_trip(void *data, int type, int *trip)
{
int ret = 0;
unsigned int val = 0;
struct bcl_peripheral_data *bat_data =
(struct bcl_peripheral_data *)data;
int16_t addr;
*trip = 0;
switch (bat_data->type) {
case BCL_VBAT_LVL0:
addr = BCL_VBAT_ADC_LOW;
break;
case BCL_VBAT_LVL1:
addr = BCL_VBAT_COMP_LOW;
break;
case BCL_VBAT_LVL2:
addr = BCL_VBAT_COMP_TLOW;
break;
default:
return -ENODEV;
}
ret = bcl_read_register(bat_data->dev, addr, &val);
if (ret)
return ret;
if (addr == BCL_VBAT_ADC_LOW) {
*trip = val;
convert_adc_to_vbat_thresh_val(trip);
pr_debug("vbat trip: %d mV ADC:0x%02x\n", *trip, val);
} else {
*trip = 2250 + 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 == BCL_VBAT_COMP_LOW) ?
"too" : "critical",
*trip, val);
}
return 0;
}
static int bcl_set_vbat(void *data, int low, int high)
{
int thresh_value;
int16_t addr;
int8_t val = 0;
struct bcl_peripheral_data *bat_data =
(struct bcl_peripheral_data *)data;
mutex_lock(&bat_data->state_trans_lock);
thresh_value = low;
if (low == INT_MIN &&
bat_data->irq_num && bat_data->irq_enabled) {
disable_irq_nosync(bat_data->irq_num);
bat_data->irq_enabled = false;
pr_debug("vbat: disable irq:%d\n", bat_data->irq_num);
} else if (low != INT_MIN &&
bat_data->irq_num && !bat_data->irq_enabled) {
enable_irq(bat_data->irq_num);
bat_data->irq_enabled = true;
pr_debug("vbat: enable irq:%d\n", bat_data->irq_num);
}
switch (bat_data->type) {
case BCL_VBAT_LVL0:
addr = BCL_VBAT_ADC_LOW;
break;
case BCL_VBAT_LVL1:
addr = BCL_VBAT_COMP_LOW;
thresh_value = thresh_value - 2250;
break;
default:
addr = BCL_VBAT_COMP_TLOW;
thresh_value = thresh_value - 2250;
break;
};
convert_vbat_to_adc_val(&thresh_value);
val = (int8_t)thresh_value;
bcl_write_register(bat_data->dev, addr, val);
mutex_unlock(&bat_data->state_trans_lock);
return 0;
}
static int bcl_read_vbat(void *data, int *adc_value)
{
int ret = 0;
unsigned int val = 0;
struct bcl_peripheral_data *bat_data =
(struct bcl_peripheral_data *)data;
*adc_value = val;
ret = bcl_read_register(bat_data->dev, BCL_VBAT_READ, &val);
if (ret)
goto bcl_read_exit;
*adc_value = val;
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 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 ret;
bool notify = false;
struct bcl_device *bcl_perph;
mutex_lock(&perph_data->state_trans_lock);
if (!perph_data->irq_enabled) {
pr_err("irq:%d not in expected state\n", irq);
disable_irq_nosync(irq);
perph_data->irq_enabled = false;
goto exit_intr;
}
mutex_unlock(&perph_data->state_trans_lock);
bcl_perph = perph_data->dev;
ret = bcl_read_register(bcl_perph, BCL_IRQ_STATUS, &irq_status);
if (ret) {
disable_irq_nosync(irq);
perph_data->irq_enabled = false;
return IRQ_HANDLED;
}
pr_debug("Irq:%d triggered for bcl type:%d. status:%u\n", irq,
perph_data->type, irq_status);
switch (perph_data->type) {
case BCL_VBAT_LVL0: /* BCL L0 interrupt */
if ((irq_status & BCL_IRQ_VCMP_L0) &&
(bcl_perph->param[BCL_VBAT_LVL0].tz_dev)) {
of_thermal_handle_trip(
bcl_perph->param[BCL_VBAT_LVL0].tz_dev);
notify = true;
}
if ((irq_status & BCL_IRQ_IBAT_L0) &&
(bcl_perph->param[BCL_IBAT_LVL0].tz_dev)) {
of_thermal_handle_trip(
bcl_perph->param[BCL_IBAT_LVL0].tz_dev);
notify = true;
}
break;
case BCL_VBAT_LVL1: /* BCL L1 interrupt */
if ((irq_status & BCL_IRQ_VCMP_L1) &&
(bcl_perph->param[BCL_VBAT_LVL1].tz_dev)) {
of_thermal_handle_trip(
bcl_perph->param[BCL_VBAT_LVL1].tz_dev);
/*
* There is a possibility that just vbat_l1 be the
* source of bcl event. So trigger Vbat_l0 in
* those case.
*/
if (!(irq_status & BCL_IRQ_VCMP_L0) &&
(bcl_perph->param[BCL_VBAT_LVL0].tz_dev))
of_thermal_handle_trip(
bcl_perph->param[BCL_VBAT_LVL0].tz_dev);
notify = true;
}
if ((irq_status & BCL_IRQ_IBAT_L1) &&
(bcl_perph->param[BCL_IBAT_LVL1].tz_dev)) {
of_thermal_handle_trip(
bcl_perph->param[BCL_IBAT_LVL1].tz_dev);
/*
* There is a possibility that just ibat_l1 be the
* source of bcl event. So trigger ibat_l0 in
* those case.
*/
if (!(irq_status & BCL_IRQ_IBAT_L0) &&
(bcl_perph->param[BCL_IBAT_LVL0].tz_dev))
of_thermal_handle_trip(
bcl_perph->param[BCL_IBAT_LVL0].tz_dev);
notify = true;
}
break;
case BCL_VBAT_LVL2: /* BCL L2 interrupt */
if ((irq_status & BCL_IRQ_VCMP_L2) &&
(bcl_perph->param[BCL_VBAT_LVL2].tz_dev)) {
of_thermal_handle_trip(
bcl_perph->param[BCL_VBAT_LVL2].tz_dev);
/*
* There is a possibility that just vbat_l2 be the
* source of bcl event. So trigger Vbat_l0 and vbat_l1
* in those case.
*/
if (!(irq_status & BCL_IRQ_VCMP_L1) &&
(bcl_perph->param[BCL_VBAT_LVL1].tz_dev))
of_thermal_handle_trip(
bcl_perph->param[BCL_VBAT_LVL1].tz_dev);
if (!(irq_status & BCL_IRQ_VCMP_L0) &&
(bcl_perph->param[BCL_VBAT_LVL0].tz_dev))
of_thermal_handle_trip(
bcl_perph->param[BCL_VBAT_LVL0].tz_dev);
notify = true;
}
break;
default:
pr_err("Invalid type%d for interrupt:%d\n", perph_data->type,
irq);
break;
}
if (!notify)
pr_err_ratelimited("Irq:%d triggered. status:%u\n", irq,
irq_status);
bcl_perph = perph_data->dev;
bcl_read_register(bcl_perph, BCL_IRQ_STATUS, &irq_status);
if (irq_status & perph_data->status_bit_idx) {
pr_debug("Irq:%d triggered for bcl type:%s. status:%u\n",
irq, bcl_int_names[perph_data->type],
irq_status);
of_thermal_handle_trip_temp(perph_data->tz_dev,
perph_data->status_bit_idx);
}
return IRQ_HANDLED;
exit_intr:
mutex_unlock(&perph_data->state_trans_lock);
return IRQ_HANDLED;
}
static int bcl_parse_devicetree_virtual_data(struct bcl_device *bcl_perph)
{
struct device_node *bcl_virtual_parent, *child;
const char *dt_sensor_names[THERMAL_MAX_VIRT_SENSORS];
struct thermal_zone_device *tzd;
struct virtual_sensor_data virtual_sensor = {0};
int rc = 0, i, count = 0;
if (!bcl_perph)
return -EINVAL;
bcl_perph->num_count = 0;
bcl_virtual_parent = of_find_node_by_name(NULL, "bcl-virtual-sensor");
if (!bcl_virtual_parent) {
pr_err("No BCL Virtual sensor defined\n");
return -EINVAL;
}
for_each_available_child_of_node(bcl_virtual_parent, child) {
strlcpy(virtual_sensor.virt_zone_name, child->name,
THERMAL_NAME_LENGTH);
virtual_sensor.logic = VIRT_COUNT_THRESHOLD;
virtual_sensor.avg_offset = 0;
virtual_sensor.avg_denominator = 1;
rc = of_property_read_u32(child, "num_sensors",
&(virtual_sensor.num_sensors));
if (rc < 0) {
pr_err("Cannot read virtual sensor number\n");
goto free_child;
}
virtual_sensor.coefficient_ct = virtual_sensor.num_sensors;
rc = of_property_read_u32_array(child, "coefficients",
virtual_sensor.coefficients,
virtual_sensor.num_sensors);
if (rc < 0) {
pr_err("Cannot read virtual sensor coefficients\n");
goto free_child;
}
rc = of_property_read_string_array(child, "sensor-names",
dt_sensor_names,
virtual_sensor.num_sensors);
if (rc < 0) {
pr_err("Cannot read virtual sensor names\n");
goto free_child;
}
for (i = 0; i < virtual_sensor.num_sensors; i++) {
tzd = thermal_zone_get_zone_by_name(
dt_sensor_names[i]);
if (IS_ERR(tzd)) {
pr_err("Thermal zone not available yet\n");
rc = -EPROBE_DEFER;
goto free_child;
}
}
for (i = 0; i < virtual_sensor.num_sensors; i++) {
virtual_sensor.sensor_names[i] =
devm_kzalloc(bcl_perph->device,
THERMAL_NAME_LENGTH,
GFP_KERNEL);
if (!virtual_sensor.sensor_names[i]) {
pr_err("Cannot alloc sensor names:%d\n", i);
rc = -ENOMEM;
goto free_devm;
}
strlcpy(virtual_sensor.sensor_names[i],
dt_sensor_names[i], THERMAL_NAME_LENGTH);
}
bcl_perph->virtual_tz_dev[count] =
devm_thermal_of_virtual_sensor_register(
bcl_perph->device,
&virtual_sensor);
if (IS_ERR(bcl_perph->virtual_tz_dev[count])) {
dev_err(bcl_perph->device,
"Couldn't register virtual sensor\n");
rc = -EINVAL;
goto free_devm;
}
thermal_zone_device_update(
bcl_perph->virtual_tz_dev[count],
THERMAL_DEVICE_UP);
count += 1;
if (count > THERMAL_MAX_VIRT_SENSORS) {
pr_err("Reached maximum number of virtual sensors");
rc = -EINVAL;
goto free_devm;
}
bcl_perph->num_count = count;
}
free_devm:
for (i = 0; i < virtual_sensor.num_sensors; i++) {
if (virtual_sensor.sensor_names[i])
devm_kfree(bcl_perph->device,
virtual_sensor.sensor_names[i]);
}
free_child:
of_node_put(child);
of_node_put(bcl_virtual_parent);
return rc;
}
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;
}
ibat_use_qg_adc = of_property_read_bool(dev_node,
"qcom,ibat-use-qg-adc-5a");
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];
irqreturn_t (*handle)(int, void *) = bcl_handle_irq;
mutex_init(&vbat->state_trans_lock);
vbat->type = type;
vbat->dev = bcl_perph;
bcl_fetch_trip(pdev, type, vbat, handle);
vbat->ops.get_temp = bcl_read_vbat;
vbat->ops.set_trips = bcl_set_vbat;
vbat->ops.get_trip_temp = bcl_get_vbat_trip;
vbat->tz_dev = thermal_zone_of_sensor_register(&pdev->dev,
type, vbat, &vbat->ops);
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;
}
thermal_zone_device_update(vbat->tz_dev, THERMAL_DEVICE_UP);
}
static void bcl_probe_vbat(struct platform_device *pdev,
struct bcl_device *bcl_perph)
{
bcl_vbat_init(pdev, BCL_VBAT_LVL0, bcl_perph);
bcl_vbat_init(pdev, BCL_VBAT_LVL1, bcl_perph);
bcl_vbat_init(pdev, BCL_VBAT_LVL2, 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;
bcl_fetch_trip(pdev, type, ibat, NULL);
if (ibat->irq_num <= 0)
return;
ibat->ops.get_temp = bcl_read_ibat;
ibat->ops.set_trips = bcl_set_ibat;
switch (type) {
case BCL_IBAT_LVL0:
if (!bcl_perph->param[BCL_VBAT_LVL0].irq_num ||
ibat->irq_num != bcl_perph->param[BCL_VBAT_LVL0].irq_num) {
pr_err("ibat[%d]: irq %d mismatch\n", type,
ibat->irq_num);
return;
}
break;
case BCL_IBAT_LVL1:
if (!bcl_perph->param[BCL_VBAT_LVL1].irq_num ||
ibat->irq_num != bcl_perph->param[BCL_VBAT_LVL1].irq_num) {
pr_err("ibat[%d]: irq %d mismatch\n", type,
ibat->irq_num);
return;
}
break;
default:
return;
}
ibat->tz_dev = thermal_zone_of_sensor_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 void bcl_probe_ibat(struct platform_device *pdev,
struct bcl_device *bcl_perph)
{
bcl_ibat_init(pdev, BCL_IBAT_LVL0, bcl_perph);
bcl_ibat_init(pdev, BCL_IBAT_LVL1, bcl_perph);
}
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;
thermal_zone_of_sensor_unregister(&pdev->dev,
bcl_perph->param[i].tz_dev);
}
for (i = 0; i < bcl_perph->num_count; i++) {
if (bcl_perph->virtual_tz_dev[i])
thermal_zone_of_sensor_unregister(&pdev->dev,
bcl_perph->virtual_tz_dev[i]);
}
return 0;
}
static int bcl_probe(struct platform_device *pdev)
{
struct bcl_device *bcl_perph = NULL;
int rc = 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->device = &pdev->dev;
if (bcl_device_ct == 0) {
rc = bcl_parse_devicetree_virtual_data(bcl_perph);
if (rc < 0)
return rc;
}
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++;
bcl_get_devicetree_data(pdev, bcl_perph);
bcl_probe_vbat(pdev, bcl_perph);
bcl_probe_ibat(pdev, bcl_perph);
bcl_configure_bcl_peripheral(bcl_perph);
dev_set_drvdata(&pdev->dev, bcl_perph);
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,
.owner = THIS_MODULE,
.of_match_table = bcl_match,
},
};
builtin_platform_driver(bcl_driver);