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

Commit e58e59cd authored by Ram Chandrasekar's avatar Ram Chandrasekar Committed by Manaf Meethalavalappu Pallikunhi
Browse files

drivers: thermal: Snapshot of bcl driver from msm-4.14



Add a snapshot of bcl driver changes from msm-4.14.
This is snapshot as of 'commit <aee9cc3c530e> ("drivers: bcl_pmic5: Fix
Ibat related warnings if not enabled")'.

Change-Id: Ie9ff5809312f584cb8c8234c418c6dcea7fb5210
Signed-off-by: default avatarRam Chandrasekar <rkumbako@codeaurora.org>
parent 94164801
Loading
Loading
Loading
Loading
+22 −0
Original line number Diff line number Diff line
@@ -9,3 +9,25 @@ config QCOM_TSENS
	  thermal zone device via the mode file results in disabling the sensor.
	  Also able to set threshold temperature for both hot and cold and update
	  when a threshold is reached.

config QTI_BCL_PMIC5
	bool "BCL driver for BCL peripherals in PMIC5"
	depends on SPMI && THERMAL_OF
	help
	  Say Y here to enable this BCL driver for PMIC5. 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_BCL_SOC_DRIVER
	bool "QTI Battery state of charge sensor driver"
	depends on THERMAL_OF
	help
	  This driver registers battery state of charge as a sensor with
	  thermal zone. This sensor can monitor for state of charge
	  threshold and notify the thermal framework.

	  If you want this support, you should say Y here.
+2 −0
Original line number Diff line number Diff line
obj-$(CONFIG_QCOM_TSENS)	+= qcom_tsens.o
qcom_tsens-y			+= tsens.o tsens-common.o tsens-8916.o tsens-8974.o tsens-8960.o tsens-v2.o
obj-$(CONFIG_QTI_BCL_PMIC5) += bcl_pmic5.o
obj-$(CONFIG_QTI_BCL_SOC_DRIVER) += bcl_soc.o
+670 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (c) 2018, 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/thermal.h>

#include "../thermal_core.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_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;
	long			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 regmap			*regmap;
	uint16_t			fg_bcl_addr;
	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_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_ibat_to_adc_val(int *val)
{
	/*
	 * Threshold register is bit shifted from ADC MSB.
	 * So the scaling factor is half.
	 */
	*val = (*val * 2000) / BCL_IBAT_SCALING_UA;
}

static void convert_adc_to_ibat_val(int *val)
{
	*val = (*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)
{
	struct bcl_peripheral_data *bat_data =
		(struct bcl_peripheral_data *)data;

	mutex_lock(&bat_data->state_trans_lock);

	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);
	}

	/*
	 * Vbat threshold's are pre-configured and cant be
	 * programmed.
	 */
	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);

	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,
					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;
	}

	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);
	}

	return 0;
}

static int bcl_probe(struct platform_device *pdev)
{
	struct bcl_device *bcl_perph = NULL;

	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->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);
+183 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (c) 2018, The Linux Foundation. All rights reserved.
 */

#define pr_fmt(fmt) "%s:%s " fmt, KBUILD_MODNAME, __func__

#include <linux/module.h>
#include <linux/workqueue.h>
#include <linux/kernel.h>
#include <linux/io.h>
#include <linux/err.h>
#include <linux/of.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_soc_peripheral"

struct bcl_device {
	struct notifier_block			psy_nb;
	struct work_struct			soc_eval_work;
	long					trip_temp;
	int					trip_val;
	struct mutex				state_trans_lock;
	bool					irq_enabled;
	struct thermal_zone_device		*tz_dev;
	struct thermal_zone_of_device_ops	ops;
};

static struct bcl_device *bcl_perph;

static int bcl_set_soc(void *data, int low, int high)
{
	if (low == bcl_perph->trip_temp)
		return 0;

	mutex_lock(&bcl_perph->state_trans_lock);
	pr_debug("low soc threshold:%d\n", low);
	bcl_perph->trip_temp = low;
	if (low == INT_MIN) {
		bcl_perph->irq_enabled = false;
		goto unlock_and_exit;
	}
	bcl_perph->irq_enabled = true;
	schedule_work(&bcl_perph->soc_eval_work);

unlock_and_exit:
	mutex_unlock(&bcl_perph->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;

	if (bcl_read_soc(NULL, &battery_percentage))
		return;

	mutex_lock(&bcl_perph->state_trans_lock);
	if (!bcl_perph->irq_enabled)
		goto eval_exit;
	if (battery_percentage > bcl_perph->trip_temp)
		goto eval_exit;

	bcl_perph->trip_val = battery_percentage;
	mutex_unlock(&bcl_perph->state_trans_lock);
	of_thermal_handle_trip(bcl_perph->tz_dev);

	return;
eval_exit:
	mutex_unlock(&bcl_perph->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 int bcl_soc_remove(struct platform_device *pdev)
{
	power_supply_unreg_notifier(&bcl_perph->psy_nb);
	flush_work(&bcl_perph->soc_eval_work);
	if (bcl_perph->tz_dev)
		thermal_zone_of_sensor_unregister(&pdev->dev,
				bcl_perph->tz_dev);

	return 0;
}

static int bcl_soc_probe(struct platform_device *pdev)
{
	int ret = 0;

	bcl_perph = devm_kzalloc(&pdev->dev, sizeof(*bcl_perph), GFP_KERNEL);
	if (!bcl_perph)
		return -ENOMEM;

	mutex_init(&bcl_perph->state_trans_lock);
	bcl_perph->ops.get_temp = bcl_read_soc;
	bcl_perph->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("soc notifier registration error. defer. err:%d\n",
			ret);
		ret = -EPROBE_DEFER;
		goto bcl_soc_probe_exit;
	}
	bcl_perph->tz_dev = thermal_zone_of_sensor_register(&pdev->dev,
				0, bcl_perph, &bcl_perph->ops);
	if (IS_ERR(bcl_perph->tz_dev)) {
		pr_err("soc TZ register failed. err:%ld\n",
				PTR_ERR(bcl_perph->tz_dev));
		ret = PTR_ERR(bcl_perph->tz_dev);
		bcl_perph->tz_dev = NULL;
		goto bcl_soc_probe_exit;
	}
	thermal_zone_device_update(bcl_perph->tz_dev, THERMAL_DEVICE_UP);
	schedule_work(&bcl_perph->soc_eval_work);

	dev_set_drvdata(&pdev->dev, bcl_perph);

	return 0;

bcl_soc_probe_exit:
	bcl_soc_remove(pdev);
	return ret;
}

static const struct of_device_id bcl_match[] = {
	{
		.compatible = "qcom,msm-bcl-soc",
	},
	{},
};

static struct platform_driver bcl_driver = {
	.probe  = bcl_soc_probe,
	.remove = bcl_soc_remove,
	.driver = {
		.name           = BCL_DRIVER_NAME,
		.owner          = THIS_MODULE,
		.of_match_table = bcl_match,
	},
};

builtin_platform_driver(bcl_driver);