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:
parent
57e62c7ca5
commit
1884f6ccf9
2 changed files with 143 additions and 9 deletions
|
@ -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>;
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue