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

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

Merge "defconfig: arm64: Enable SDPM clock monitor for Lahaina"

parents 0a1e1596 ea6921b4
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -119,6 +119,8 @@ CONFIG_QTI_ADC_TM=y
CONFIG_QTI_CPU_ISOLATE_COOLING_DEVICE=y
CONFIG_QTI_THERMAL_LIMITS_DCVS=y
CONFIG_QTI_CPU_VOLTAGE_COOLING_DEVICE=y
CONFIG_QTI_POLICY_ENGINE_SENSOR=y
CONFIG_QTI_SDPM_CLOCK_MONITOR=y
CONFIG_SPS=y
# CONFIG_SPS_SUPPORT_BAMDMA is not set
CONFIG_SPS_SUPPORT_NDP_BAM=y
+18 −0
Original line number Diff line number Diff line
@@ -100,3 +100,21 @@ config QTI_CPU_VOLTAGE_COOLING_DEVICE
	    cluster to be mitigated together based on the voltages. This will
	    decrease or increase the voltages in a cluster based on thermal
	    conditions.

config QTI_POLICY_ENGINE_SENSOR
	tristate "QTI Policy Engine Sensor device"
	depends on THERMAL_OF && QTI_THERMAL
	help
	    This enables the QTI Policy Engine sensor device. This driver will
	    register the policy engine recommendation with thermal framework as
	    a sensor. This will enable to provide configuration to mitigate
	    cooling devices when a recommendation is sent from hardware.

config QTI_SDPM_CLOCK_MONITOR
	tristate "QTI SDPM Clock Monitor"
	depends on COMMON_CLK && CPU_FREQ
	help
	    This enables the QTI SDPM Clock Monitor. This driver can register
	    for different clock rate change notifications and write the clock
	    rate into the SDPM CSR register. This driver will receive the clock
	    list and the CSR details from devicetree.
+2 −0
Original line number Diff line number Diff line
@@ -17,3 +17,5 @@ obj-$(CONFIG_QTI_BCL_PMIC5) += bcl_pmic5.o
obj-$(CONFIG_QTI_BCL_SOC_DRIVER) += bcl_soc.o
obj-$(CONFIG_QTI_THERMAL_LIMITS_DCVS) += msm_lmh_dcvs.o
obj-$(CONFIG_QTI_CPU_VOLTAGE_COOLING_DEVICE) += cpu_voltage_cooling.o
obj-$(CONFIG_QTI_POLICY_ENGINE_SENSOR) += policy_engine.o
obj-${CONFIG_QTI_SDPM_CLOCK_MONITOR} += sdpm_clk.o
+184 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (c) 2020, The Linux Foundation. All rights reserved.
 */

#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/thermal.h>
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/interrupt.h>

#include "../thermal_core.h"

#define PE_SENS_DRIVER		"policy-engine-sensor"
#define PE_INT_ENABLE_OFFSET	0x530
#define PE_STATUS_OFFSET	0x590
#define PE_INT_STATUS_OFFSET	0x620
#define PE_INTR_CFG		0x11000
#define PE_INTR_CLEAR		0x11111
#define PE_READ_MITIGATION_IDX(val) ((val >> 16) & 0x1F)

struct pe_sensor_data {
	struct device			*dev;
	struct thermal_zone_device	*tz_dev;
	int32_t				high_thresh;
	int32_t				low_thresh;
	int32_t				irq_num;
	void __iomem			*regmap;
	struct mutex			mutex;
};

static int fetch_mitigation_table_idx(struct pe_sensor_data *pe_sens, int *temp)
{
	u32 data = 0;

	data = readl_relaxed(pe_sens->regmap + PE_STATUS_OFFSET);
	*temp = PE_READ_MITIGATION_IDX(data);
	dev_dbg(pe_sens->dev, "PE data:%d index:%d\n", data, *temp);

	return 0;
}

static int pe_sensor_read(void *data, int *temp)
{
	struct pe_sensor_data *pe_sens = (struct pe_sensor_data *)data;

	return fetch_mitigation_table_idx(pe_sens, temp);
}

static int pe_sensor_set_trips(void *data, int low, int high)
{
	struct pe_sensor_data *pe_sens = (struct pe_sensor_data *)data;

	mutex_lock(&pe_sens->mutex);
	if (pe_sens->high_thresh == high &&
		pe_sens->low_thresh == low)
		goto unlock_exit;
	pe_sens->high_thresh = high;
	pe_sens->low_thresh = low;
	dev_dbg(pe_sens->dev, "PE rail set trip. high:%d low:%d\n",
				high, low);

unlock_exit:
	mutex_unlock(&pe_sens->mutex);
	return 0;
}

static struct thermal_zone_of_device_ops pe_sensor_ops = {
	.get_temp = pe_sensor_read,
	.set_trips = pe_sensor_set_trips,
};

static irqreturn_t pe_handle_irq(int irq, void *data)
{
	struct pe_sensor_data *pe_sens = (struct pe_sensor_data *)data;
	int val = 0, ret = 0;

	ret = fetch_mitigation_table_idx(pe_sens, &val);
	if (ret)
		return IRQ_HANDLED;

	mutex_lock(&pe_sens->mutex);
	dev_dbg(pe_sens->dev, "Policy Engine interrupt fired value:%d\n", val);
	if (pe_sens->tz_dev && (val >= pe_sens->high_thresh ||
			val <= pe_sens->low_thresh)) {
		mutex_unlock(&pe_sens->mutex);
		of_thermal_handle_trip_temp(pe_sens->dev, pe_sens->tz_dev, val);
	} else
		mutex_unlock(&pe_sens->mutex);
	writel_relaxed(PE_INTR_CLEAR, pe_sens->regmap + PE_INT_STATUS_OFFSET);

	return IRQ_HANDLED;
}

static int pe_sens_device_probe(struct platform_device *pdev)
{
	struct device *dev = &pdev->dev;
	int ret = 0;
	struct pe_sensor_data *pe_sens;
	struct resource *res;

	pe_sens = devm_kzalloc(dev, sizeof(*pe_sens), GFP_KERNEL);
	if (!pe_sens)
		return -ENOMEM;
	pe_sens->dev = dev;
	pe_sens->high_thresh = INT_MAX;
	pe_sens->low_thresh = INT_MIN;
	mutex_init(&pe_sens->mutex);

	dev_set_drvdata(dev, pe_sens);
	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (!res) {
		dev_err(dev, "Couldn't get MEM resource\n");
		return -EINVAL;
	}
	dev_dbg(dev, "pe@0x%x size:%d\n", res->start,
			resource_size(res));

	pe_sens->regmap = devm_ioremap_resource(dev, res);
	if (!pe_sens->regmap) {
		dev_err(dev, "Couldn't get regmap\n");
		return -EINVAL;
	}

	pe_sens->irq_num = platform_get_irq(pdev, 0);
	if (pe_sens->irq_num < 0) {
		dev_err(dev, "Couldn't get irq number\n");
		return pe_sens->irq_num;
	}
	pe_sens->tz_dev = devm_thermal_zone_of_sensor_register(
				dev, 0, pe_sens, &pe_sensor_ops);
	if (IS_ERR_OR_NULL(pe_sens->tz_dev)) {
		ret = PTR_ERR(pe_sens->tz_dev);
		if (ret != -ENODEV)
			dev_err(dev, "sensor register failed. ret:%d\n", ret);
		pe_sens->tz_dev = NULL;
		return ret;
	}
	writel_relaxed(PE_INTR_CFG, pe_sens->regmap + PE_INT_ENABLE_OFFSET);
	writel_relaxed(PE_INTR_CLEAR, pe_sens->regmap + PE_INT_STATUS_OFFSET);
	ret = devm_request_threaded_irq(dev, pe_sens->irq_num, NULL,
				pe_handle_irq,
				IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
				dev_name(dev), pe_sens);
	if (ret) {
		dev_err(dev, "Couldn't get irq registered\n");
		thermal_zone_of_sensor_unregister(pe_sens->dev,
				pe_sens->tz_dev);
		return ret;
	}
	dev_dbg(dev, "PE sensor register success\n");

	return 0;
}

static int pe_sens_device_remove(struct platform_device *pdev)
{
	struct pe_sensor_data *pe_sens =
		(struct pe_sensor_data *)dev_get_drvdata(&pdev->dev);

	thermal_zone_of_sensor_unregister(pe_sens->dev, pe_sens->tz_dev);

	return 0;
}

static const struct of_device_id pe_sens_device_match[] = {
	{.compatible = "qcom,policy-engine"},
	{}
};

static struct platform_driver pe_sens_device_driver = {
	.probe          = pe_sens_device_probe,
	.remove         = pe_sens_device_remove,
	.driver         = {
		.name   = PE_SENS_DRIVER,
		.of_match_table = pe_sens_device_match,
	},
};

module_platform_driver(pe_sens_device_driver);
MODULE_LICENSE("GPL v2");
+284 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (c) 2020, The Linux Foundation. All rights reserved.
 */

#include <linux/module.h>
#include <linux/clk.h>
#include <linux/platform_device.h>
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/cpufreq.h>
#include <linux/pm_opp.h>
#include <linux/cpu.h>

#define SDPM_DRIVER		"sdpm-clk-notify"
#define CSR_MAX_VAL		7
#define CSR_OFFSET		0xF00
#define FREQ_HZ_TO_MHZ(f)	((f) / 1000000)
#define FREQ_KHZ_TO_MHZ(f)	((f) / 1000)

struct sdpm_clk_instance;
struct sdpm_clk_data {
	struct notifier_block		clk_rate_nb;
	struct clk			*clk;
	const char			*clock_name;
	uint32_t			cpu_id;
	uint32_t			csr_id;
	unsigned long			last_freq;
	struct sdpm_clk_instance	*sdpm_inst;
};
struct sdpm_clk_instance {
	struct device			*dev;
	void __iomem			*regmap;
	uint32_t			clk_ct;
	struct sdpm_clk_data		*clk_data;
};

static void sdpm_csr_write(struct sdpm_clk_data *sdpm_data,
				unsigned long clk_rate)
{
	struct sdpm_clk_instance *sdpm_inst = sdpm_data->sdpm_inst;
	uint32_t val = clk_rate;

	sdpm_data->last_freq = clk_rate;

	dev_dbg(sdpm_inst->dev, "clock:%s offset:0x%x frequency:%u\n",
			(sdpm_data->clock_name) ? sdpm_data->clock_name :
			"cpu", CSR_OFFSET + sdpm_data->csr_id * 4,
			val);
	writel_relaxed(val,
		sdpm_inst->regmap + CSR_OFFSET + sdpm_data->csr_id * 4);
}

static int sdpm_cpu_notifier(struct notifier_block *nb,
					unsigned long event, void *data)
{
	struct cpufreq_freqs *freq_data = (struct cpufreq_freqs *)data;
	struct sdpm_clk_data *sdpm_data = container_of(nb,
				struct sdpm_clk_data, clk_rate_nb);

	if (freq_data->policy->cpu != sdpm_data->cpu_id)
		return NOTIFY_DONE;
	dev_dbg(sdpm_data->sdpm_inst->dev, "CPU%d event:%lu\n",
			freq_data->policy->cpu, event);
	switch (event) {
	case CPUFREQ_PRECHANGE:
		if (freq_data->new > freq_data->old)
			sdpm_csr_write(sdpm_data,
					FREQ_KHZ_TO_MHZ(freq_data->new));
		return NOTIFY_DONE;
	case CPUFREQ_POSTCHANGE:
		if (freq_data->new < freq_data->old)
			sdpm_csr_write(sdpm_data,
					FREQ_KHZ_TO_MHZ(freq_data->new));
		return NOTIFY_DONE;
	default:
		return NOTIFY_DONE;
	}
	return NOTIFY_DONE;
}

static int sdpm_clock_notifier(struct notifier_block *nb,
					unsigned long event, void *data)
{
	struct clk_notifier_data *ndata = data;
	struct sdpm_clk_data *sdpm_data = container_of(nb,
				struct sdpm_clk_data, clk_rate_nb);

	dev_dbg(sdpm_data->sdpm_inst->dev, "clock:%s event:%lu\n",
			sdpm_data->clock_name, event);
	switch (event) {
	case PRE_RATE_CHANGE:
		if (ndata->new_rate > ndata->old_rate)
			sdpm_csr_write(sdpm_data,
					FREQ_HZ_TO_MHZ(ndata->new_rate));
		return NOTIFY_DONE;
	case POST_RATE_CHANGE:
		if (ndata->new_rate < ndata->old_rate)
			sdpm_csr_write(sdpm_data,
					FREQ_HZ_TO_MHZ(ndata->new_rate));
		return NOTIFY_DONE;
	case ABORT_RATE_CHANGE:
		if (ndata->new_rate > ndata->old_rate)
			sdpm_csr_write(sdpm_data,
					FREQ_HZ_TO_MHZ(ndata->old_rate));
		return NOTIFY_DONE;
	default:
		return NOTIFY_DONE;
	}
}

static int sdpm_clk_device_probe(struct platform_device *pdev)
{
	struct device *dev = &pdev->dev;
	int ret = 0, idx = 0, clk_ct = 0, csr = 0, csr_ct = 0, cpu = 0;
	struct sdpm_clk_instance *sdpm_clk;
	struct device_node *dev_node = dev->of_node;
	struct device_node *cpu_phandle = NULL;
	struct device *cpu_dev;
	struct dev_pm_opp *opp = NULL;
	unsigned long freq;
	struct resource *res;

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

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (!res) {
		dev_err(dev, "Couldn't get MEM resource\n");
		return -EINVAL;
	}
	dev_dbg(dev, "sdpm@0x%x size:%d\n", res->start,
			resource_size(res));
	dev_set_drvdata(dev, sdpm_clk);

	sdpm_clk->regmap = devm_ioremap_resource(dev, res);
	if (!sdpm_clk->regmap) {
		dev_err(dev, "Couldn't get regmap\n");
		return -EINVAL;
	}

	ret = of_property_count_strings(dev_node, "clock-names");
	if (ret < 0) {
		dev_err(dev, "Couldn't get clock names. %d\n", ret);
		return ret;
	}
	clk_ct = ret;
	ret = of_property_count_u32_elems(dev_node, "csr-id");
	if (ret <= 0) {
		dev_err(dev, "Couldn't get csr ID array. %d\n", ret);
		return ret;
	}
	csr_ct = ret;
	if (clk_ct > csr_ct || (csr_ct - clk_ct > 1)) {
		dev_err(dev, "Invalid csr:%d and clk:%d count.\n", csr_ct,
				clk_ct);
		return -EINVAL;
	}
	sdpm_clk->clk_ct = clk_ct;
	sdpm_clk->clk_data = devm_kcalloc(dev, clk_ct + 1,
				sizeof(*sdpm_clk->clk_data), GFP_KERNEL);
	if (!sdpm_clk->clk_data)
		return -ENOMEM;

	for (idx = 0; idx < sdpm_clk->clk_ct; idx++) {
		ret = of_property_read_string_index(dev_node, "clock-names",
				idx, &sdpm_clk->clk_data[idx].clock_name);
		if (ret < 0) {
			dev_err(dev, "Couldn't get clk name index:%d. %d\n",
					idx, ret);
			return ret;
		}

		sdpm_clk->clk_data[idx].clk = devm_clk_get(dev,
				sdpm_clk->clk_data[idx].clock_name);
		if (IS_ERR(sdpm_clk->clk_data[idx].clk))
			return PTR_ERR(sdpm_clk->clk_data[idx].clk);

		ret = of_property_read_u32_index(dev_node, "csr-id", idx, &csr);
		if (ret < 0) {
			dev_err(dev, "Couldn't get CSR for index:%d. %d\n",
					idx, ret);
			return ret;
		}
		if (ret > CSR_MAX_VAL) {
			dev_err(dev, "Invalid CSR %d\n", csr);
			return -EINVAL;
		}
		dev_dbg(dev, "SDPM clock:%s csr:%d initialized\n",
				sdpm_clk->clk_data[idx].clock_name, csr);
		sdpm_clk->clk_data[idx].csr_id = csr;
		sdpm_clk->clk_data[idx].sdpm_inst = sdpm_clk;
		sdpm_clk->clk_data[idx].clk_rate_nb.notifier_call =
			sdpm_clock_notifier;
		sdpm_clk->clk_data[idx].last_freq = FREQ_HZ_TO_MHZ(
				clk_get_rate(sdpm_clk->clk_data[idx].clk));
		sdpm_csr_write(&sdpm_clk->clk_data[idx],
				sdpm_clk->clk_data[idx].last_freq);
		clk_notifier_register(sdpm_clk->clk_data[idx].clk,
					&sdpm_clk->clk_data[idx].clk_rate_nb);
	}
	cpu_phandle = of_parse_phandle(dev_node, "cpu", 0);
	if (!cpu_phandle)
		return 0;

	for_each_possible_cpu(cpu) {
		cpu_dev = get_cpu_device(cpu);
		if (!cpu_dev || cpu_dev->of_node != cpu_phandle)
			continue;
		sdpm_clk->clk_data[idx].clk = NULL;
		sdpm_clk->clk_data[idx].clock_name = NULL;
		ret = of_property_read_u32_index(dev_node, "csr-id", idx,
							&csr);
		if (ret < 0) {
			dev_err(dev, "Couldn't get CSR for index:%d. %d\n",
					idx, ret);
			return ret;
		}
		if (ret > CSR_MAX_VAL) {
			dev_err(dev, "Invalid CSR %d\n", csr);
			return -EINVAL;
		}
		dev_dbg(dev, "CPU%d clock csr:%d initialized\n",
				cpu, csr);
		sdpm_clk->clk_data[idx].csr_id = csr;
		sdpm_clk->clk_data[idx].cpu_id = cpu;
		sdpm_clk->clk_data[idx].sdpm_inst = sdpm_clk;
		sdpm_clk->clk_data[idx].clk_rate_nb.notifier_call =
			sdpm_cpu_notifier;
		freq = UINT_MAX;
		opp = dev_pm_opp_find_freq_floor(cpu_dev, &freq);
		if (IS_ERR(opp)) {
			dev_err(dev, "OPP for CPU%d fetch error:%d\n", cpu,
					PTR_ERR(opp));
			return PTR_ERR(opp);
		}
		sdpm_clk->clk_data[idx].last_freq = FREQ_HZ_TO_MHZ(freq);
		sdpm_csr_write(&sdpm_clk->clk_data[idx],
				sdpm_clk->clk_data[idx].last_freq);
		dev_pm_opp_put(opp);
		cpufreq_register_notifier(&sdpm_clk->clk_data[idx].clk_rate_nb,
					CPUFREQ_TRANSITION_NOTIFIER);
		break;
	}

	return 0;
}

static int sdpm_clk_device_remove(struct platform_device *pdev)
{
	struct sdpm_clk_instance *sdpm_clk =
		(struct sdpm_clk_instance *)dev_get_drvdata(&pdev->dev);
	int idx = 0;

	for (idx = 0; idx < sdpm_clk->clk_ct; idx++) {
		clk_notifier_unregister(sdpm_clk->clk_data[idx].clk,
					&sdpm_clk->clk_data[idx].clk_rate_nb);
	}
	cpufreq_unregister_notifier(&sdpm_clk->clk_data[idx].clk_rate_nb,
					CPUFREQ_TRANSITION_NOTIFIER);

	return 0;
}

static const struct of_device_id sdpm_clk_device_match[] = {
	{.compatible = "qcom,sdpm"},
	{}
};

static struct platform_driver sdpm_clk_device_driver = {
	.probe          = sdpm_clk_device_probe,
	.remove         = sdpm_clk_device_remove,
	.driver         = {
		.name   = SDPM_DRIVER,
		.of_match_table = sdpm_clk_device_match,
	},
};

module_platform_driver(sdpm_clk_device_driver);
MODULE_LICENSE("GPL v2");