diff --git a/Documentation/devicetree/bindings/arm/msm/lmh-dcvs.txt b/Documentation/devicetree/bindings/arm/msm/lmh-dcvs.txt index 26d6e65dfee9..6337065c0d2b 100644 --- a/Documentation/devicetree/bindings/arm/msm/lmh-dcvs.txt +++ b/Documentation/devicetree/bindings/arm/msm/lmh-dcvs.txt @@ -15,10 +15,18 @@ Properties: Usage: required Value type: Definition: shall be "qcom,msm-hw-limits" +- interrupts: + Usage: required + Value 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 = ; }; diff --git a/drivers/thermal/msm_lmh_dcvs.c b/drivers/thermal/msm_lmh_dcvs.c index 4a35743d6f51..123c8cd245cc 100644 --- a/drivers/thermal/msm_lmh_dcvs.c +++ b/drivers/thermal/msm_lmh_dcvs.c @@ -17,7 +17,13 @@ #include #include #include +#include #include +#include +#include +#include +#include +#include #include #include @@ -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; }