drivers: thermal: Read the mitigation frequency and notify scheduler

LMH DCVSh hardware can send interrupts whenever the hardware mitigation
blocks make a new frequency mitigation vote. scheduler is not aware of
any of the hardware mitigation.

Add support to listen for this interrupt, read the aggregated mitigation
frequency from all LMH DCVSh block and notify scheduler. After receiving
interrupt, poll the hardware periodically for the mitigation frequency
till the mitigation is cleared by hardware. Once the mitigation is
cleared, interrupt is re-enabled.

Change-Id: I38bc0c80710038f135289420d6b20c1ff0ab06eb
Signed-off-by: Ram Chandrasekar <rkumbako@codeaurora.org>
This commit is contained in:
Ram Chandrasekar 2016-05-17 15:18:52 -06:00 committed by Kyle Yan
parent 57e62c7ca5
commit 1884f6ccf9
2 changed files with 143 additions and 9 deletions

View file

@ -15,10 +15,18 @@ Properties:
Usage: required
Value type: <string>
Definition: shall be "qcom,msm-hw-limits"
- interrupts:
Usage: required
Value type: <interrupt_type interrupt_number interrupt_trigger_type>
Definition: Should specify interrupt information about the debug
interrupt generated by the LMH DCVSh hardware. LMH
DCVSh hardware will generate this interrupt whenever
it makes a new cpu DCVS decision.
Example:
lmh_dcvs0: qcom,limits-dcvs@0 {
compatible = "qcom,msm-hw-limits";
interrupts = <GIC_SPI 38 IRQ_TYPE_LEVEL_HIGH>;
};

View file

@ -17,7 +17,13 @@
#include <linux/thermal.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_irq.h>
#include <linux/platform_device.h>
#include <linux/sched.h>
#include <linux/io.h>
#include <linux/interrupt.h>
#include <linux/timer.h>
#include <linux/pm_opp.h>
#include <asm/smp_plat.h>
#include <asm/cacheflush.h>
@ -39,6 +45,16 @@
#define MSM_LIMITS_CLUSTER_0 0x6370302D
#define MSM_LIMITS_CLUSTER_1 0x6370312D
#define MSM_LIMITS_POLLING_DELAY_MS 10
#define MSM_LIMITS_CLUSTER_0_REQ 0x179C1B04
#define MSM_LIMITS_CLUSTER_1_REQ 0x179C3B04
#define MSM_LIMITS_CLUSTER_0_INT_CLR 0x179CE808
#define MSM_LIMITS_CLUSTER_1_INT_CLR 0x179CC808
#define dcvsh_get_frequency(_val, _max) do { \
_max = (_val) & 0x3FF; \
_max *= 19200; \
} while (0)
enum lmh_hw_trips {
LIMITS_TRIP_LO,
LIMITS_TRIP_HI,
@ -49,8 +65,73 @@ struct msm_lmh_dcvs_hw {
uint32_t affinity;
uint32_t temp_limits[LIMITS_TRIP_MAX];
struct sensor_threshold default_lo, default_hi;
int irq_num;
void *osm_hw_reg;
void *int_clr_reg;
cpumask_t core_map;
struct timer_list poll_timer;
uint32_t max_freq;
};
static void msm_lmh_dcvs_get_max_freq(uint32_t cpu, uint32_t *max_freq)
{
unsigned long freq_ceil = UINT_MAX;
struct device *cpu_dev = NULL;
cpu_dev = get_cpu_device(cpu);
if (!cpu_dev) {
pr_err("Error in get CPU%d device\n", cpu);
return;
}
rcu_read_lock();
dev_pm_opp_find_freq_floor(cpu_dev, &freq_ceil);
rcu_read_unlock();
*max_freq = freq_ceil/1000;
}
static uint32_t msm_lmh_mitigation_notify(struct msm_lmh_dcvs_hw *hw)
{
uint32_t max_limit = 0, val = 0;
val = readl_relaxed(hw->osm_hw_reg);
dcvsh_get_frequency(val, max_limit);
sched_update_cpu_freq_min_max(&hw->core_map, 0, max_limit);
return max_limit;
}
static void msm_lmh_dcvs_poll(unsigned long data)
{
uint32_t max_limit = 0;
struct msm_lmh_dcvs_hw *hw = (struct msm_lmh_dcvs_hw *)data;
if (hw->max_freq == UINT_MAX)
msm_lmh_dcvs_get_max_freq(cpumask_first(&hw->core_map),
&hw->max_freq);
max_limit = msm_lmh_mitigation_notify(hw);
if (max_limit >= hw->max_freq) {
del_timer(&hw->poll_timer);
writel_relaxed(0xFF, hw->int_clr_reg);
enable_irq(hw->irq_num);
} else {
mod_timer(&hw->poll_timer, jiffies + msecs_to_jiffies(
MSM_LIMITS_POLLING_DELAY_MS));
}
}
static irqreturn_t lmh_dcvs_handle_isr(int irq, void *data)
{
struct msm_lmh_dcvs_hw *hw = data;
disable_irq_nosync(irq);
msm_lmh_mitigation_notify(hw);
mod_timer(&hw->poll_timer, jiffies + msecs_to_jiffies(
MSM_LIMITS_POLLING_DELAY_MS));
return IRQ_HANDLED;
}
static int msm_lmh_dcvs_write(uint32_t node_id, uint32_t fn,
uint32_t setting, uint32_t val)
{
@ -165,42 +246,44 @@ static int trip_notify(enum thermal_trip_type type, int temp, void *data)
static int msm_lmh_dcvs_probe(struct platform_device *pdev)
{
int ret;
uint32_t affinity;
int affinity = -1;
struct msm_lmh_dcvs_hw *hw;
char sensor_name[] = "limits_sensor-00";
struct thermal_zone_device *tzdev;
struct device_node *dn = pdev->dev.of_node;
struct device_node *cpu_node, *lmh_node;
uint32_t id;
uint32_t id, max_freq, request_reg, clear_reg;
int cpu;
bool found = false;
cpumask_t mask = { CPU_BITS_NONE };
for_each_possible_cpu(cpu) {
cpu_node = of_cpu_device_node_get(cpu);
if (!cpu_node)
continue;
lmh_node = of_parse_phandle(cpu_node, "qcom,lmh-dcvs", 0);
found = (lmh_node == dn);
of_node_put(cpu_node);
of_node_put(lmh_node);
if (found) {
if (lmh_node == dn) {
affinity = MPIDR_AFFINITY_LEVEL(
cpu_logical_map(cpu), 1);
break;
/*set the cpumask*/
cpumask_set_cpu(cpu, &(mask));
}
of_node_put(cpu_node);
of_node_put(lmh_node);
}
/*
* We return error if none of the CPUs have
* reference to our LMH node
*/
if (!found)
if (affinity == -1)
return -EINVAL;
msm_lmh_dcvs_get_max_freq(cpumask_first(&mask), &max_freq);
hw = devm_kzalloc(&pdev->dev, sizeof(*hw), GFP_KERNEL);
if (!hw)
return -ENOMEM;
cpumask_copy(&hw->core_map, &mask);
switch (affinity) {
case 0:
hw->affinity = MSM_LIMITS_CLUSTER_0;
@ -266,6 +349,49 @@ static int msm_lmh_dcvs_probe(struct platform_device *pdev)
return ret;
}
hw->max_freq = max_freq;
switch (affinity) {
case 0:
request_reg = MSM_LIMITS_CLUSTER_0_REQ;
clear_reg = MSM_LIMITS_CLUSTER_0_INT_CLR;
break;
case 1:
request_reg = MSM_LIMITS_CLUSTER_1_REQ;
clear_reg = MSM_LIMITS_CLUSTER_1_INT_CLR;
break;
default:
return -EINVAL;
};
hw->osm_hw_reg = devm_ioremap(&pdev->dev, request_reg, 0x4);
if (!hw->osm_hw_reg) {
pr_err("register remap failed\n");
return -ENOMEM;
}
hw->int_clr_reg = devm_ioremap(&pdev->dev, clear_reg, 0x4);
if (!hw->int_clr_reg) {
pr_err("interrupt clear reg remap failed\n");
return -ENOMEM;
}
init_timer_deferrable(&hw->poll_timer);
hw->poll_timer.data = (unsigned long)hw;
hw->poll_timer.function = msm_lmh_dcvs_poll;
hw->irq_num = of_irq_get(pdev->dev.of_node, 0);
if (hw->irq_num < 0) {
ret = hw->irq_num;
pr_err("Error getting IRQ number. err:%d\n", ret);
return ret;
}
ret = devm_request_threaded_irq(&pdev->dev, hw->irq_num, NULL,
lmh_dcvs_handle_isr, IRQF_TRIGGER_HIGH | IRQF_ONESHOT
| IRQF_NO_SUSPEND, sensor_name, hw);
if (ret) {
pr_err("Error registering for irq. err:%d\n", ret);
return ret;
}
return ret;
}