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

Commit b5dd98cd authored by Manaf Meethalavalappu Pallikunhi's avatar Manaf Meethalavalappu Pallikunhi
Browse files

drivers: thermal: qcom: Add driver to modify thermal zone based on efuse



Add driver to enable/disable pre-configured thermal zones
selectively at runtime based on efuse value. It uses QFPROM
nvmem cell interface to read efuse data. It supports multiple
efuse condtion and if any of them fails, driver just exit
with default enabled thermal zones.

Change-Id: I9bca61eb1ae9eeceff4ee0dd630c1ddb5967e2a5
Signed-off-by: default avatarManaf Meethalavalappu Pallikunhi <manafm@codeaurora.org>
parent 0a85a6fd
Loading
Loading
Loading
Loading
+11 −0
Original line number Diff line number Diff line
@@ -151,3 +151,14 @@ config QTI_CX_IPEAK_COOLING_DEVICE
	  on the CX rail.

	  If you want this support, you should say Y here.

config QTI_THERMAL_QFPROM
	tristate "Qualcomm Technologies Inc. thermal QFPROM driver"
	depends on THERMAL
	depends on QCOM_QFPROM
	help
	  This driver enables or disables pre-configured thermal zones
	  selectively at runtime based on QFPROM nvmem cell bit value is
	  set or not. It supports to check multiple nvmem cell value for
	  multiple condtion. In that case, if any of the nvmem-cell condition
	  fails, driver just exits with default enabled thermal zones.
+1 −0
Original line number Diff line number Diff line
@@ -14,3 +14,4 @@ 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
obj-$(CONFIG_QTI_CX_IPEAK_COOLING_DEVICE) += cx_ipeak_cdev.o
obj-$(CONFIG_QTI_THERMAL_QFPROM) += qti_thermal_qfprom.o
+173 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (c) 2021, The Linux Foundation. All rights reserved.
 */

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

#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/nvmem-consumer.h>
#include <linux/thermal.h>

#include "../thermal_core.h"

static int thermal_qfprom_read(struct platform_device *pdev,
			const char *cname, unsigned int *efuse_val)
{
	struct nvmem_cell *cell;
	size_t len;
	char *buf;

	cell = nvmem_cell_get(&pdev->dev, cname);
	if (IS_ERR(cell)) {
		dev_err(&pdev->dev, "failed to get nvmem cell %s\n", cname);
		return -EINVAL;
	}

	buf = nvmem_cell_read(cell, &len);
	nvmem_cell_put(cell);
	if (IS_ERR_OR_NULL(buf)) {
		dev_err(&pdev->dev, "failed to read nvmem cell %s\n", cname);
		return -EINVAL;
	}

	if (len <= 0 || len > sizeof(u32)) {
		dev_err(&pdev->dev, "nvmem cell length out of range:%d\n", len);
		kfree(buf);
		return -EINVAL;
	}
	memcpy(efuse_val, buf, min(len, sizeof(*efuse_val)));
	kfree(buf);

	return 0;
}

static int thermal_zone_set_mode(struct platform_device *pdev,
			enum thermal_device_mode mode)
{
	const char *name;
	struct property *prop = NULL;

	of_property_for_each_string(pdev->dev.of_node,
		mode == THERMAL_DEVICE_ENABLED ?
		"qcom,thermal-zone-enable-list" :
		"qcom,thermal-zone-disable-list", prop, name) {
		struct thermal_zone_device *zone;
		struct thermal_instance *pos;

		zone = thermal_zone_get_zone_by_name(name);
		if (IS_ERR(zone)) {
			dev_err(&pdev->dev,
				"could not find %s thermal zone\n", name);
			continue;
		}

		if (!(zone->ops && zone->ops->set_mode)) {
			dev_err(&pdev->dev,
				"thermal zone ops is not supported for %s\n",
				name);
			continue;
		}

		zone->ops->set_mode(zone, mode);
		if (mode == THERMAL_DEVICE_DISABLED) {
			/* Clear thermal zone device */
			mutex_lock(&zone->lock);
			zone->temperature = THERMAL_TEMP_INVALID;
			zone->passive = 0;
			list_for_each_entry(pos, &zone->thermal_instances,
				tz_node) {
				pos->initialized = false;
				pos->target = THERMAL_NO_TARGET;
				mutex_lock(&pos->cdev->lock);
				pos->cdev->updated = false;
				mutex_unlock(&pos->cdev->lock);
				thermal_cdev_update(pos->cdev);
			}
			mutex_unlock(&zone->lock);
		}
		dev_dbg(&pdev->dev, "thermal zone %s is %s\n", name,
			mode == THERMAL_DEVICE_ENABLED ?
			"enabled" : "disabled");
	}

	return 0;
}

static void update_thermal_zones(struct platform_device *pdev)
{
	thermal_zone_set_mode(pdev, THERMAL_DEVICE_ENABLED);
	thermal_zone_set_mode(pdev, THERMAL_DEVICE_DISABLED);
}

static int thermal_qfprom_probe(struct platform_device *pdev)
{
	int err = 0;
	const char *name;
	struct property *prop = NULL;
	u8 efuse_pass_cnt = 0;

	of_property_for_each_string(pdev->dev.of_node,
		"nvmem-cell-names", prop, name) {
		u32 efuse_val = 0, efuse_match_val = 0;

		err = thermal_qfprom_read(pdev, name, &efuse_val);
		if (err)
			return err;

		err = of_property_read_u32_index(pdev->dev.of_node,
			"qcom,thermal-qfprom-bit-values", efuse_pass_cnt,
			&efuse_match_val);
		if (err) {
			dev_err(&pdev->dev,
				"Invalid qfprom bit value for index %d\n",
				efuse_pass_cnt);
			return err;
		}

		dev_dbg(&pdev->dev, "efuse[%s] val:0x%x match val[%d]:0x%x\n",
				name, efuse_val, efuse_pass_cnt,
				efuse_match_val);

		/* if any of efuse condition fails, just exit */
		if (efuse_val != efuse_match_val)
			return 0;

		efuse_pass_cnt++;
	}

	if (efuse_pass_cnt)
		update_thermal_zones(pdev);

	return err;
}

static const struct of_device_id thermal_qfprom_match[] = {
	{ .compatible = "qcom,thermal-qfprom-device", },
	{},
};

static struct platform_driver thermal_qfprom_driver = {
	.probe = thermal_qfprom_probe,
	.driver = {
		.name = KBUILD_MODNAME,
		.of_match_table = thermal_qfprom_match,
	},
};

int __init thermal_qfprom_init(void)
{
	int err;

	err = platform_driver_register(&thermal_qfprom_driver);
	if (err)
		pr_err("Failed to register thermal qfprom platform driver:%d\n",
			err);
	return err;
}

late_initcall(thermal_qfprom_init);