12849dd8bSTaniya Das // SPDX-License-Identifier: GPL-2.0 22849dd8bSTaniya Das /* 32849dd8bSTaniya Das * Copyright (c) 2018, The Linux Foundation. All rights reserved. 42849dd8bSTaniya Das */ 52849dd8bSTaniya Das 62849dd8bSTaniya Das #include <linux/bitfield.h> 74370232cSManivannan Sadhasivam #include <linux/clk-provider.h> 82849dd8bSTaniya Das #include <linux/cpufreq.h> 92849dd8bSTaniya Das #include <linux/init.h> 1051c843cfSSibi Sankar #include <linux/interconnect.h> 11275157b3SThara Gopinath #include <linux/interrupt.h> 122849dd8bSTaniya Das #include <linux/kernel.h> 132849dd8bSTaniya Das #include <linux/module.h> 142849dd8bSTaniya Das #include <linux/of_address.h> 152849dd8bSTaniya Das #include <linux/of_platform.h> 1655538fbcSTaniya Das #include <linux/pm_opp.h> 17c4c0efb0SXuewen Yan #include <linux/pm_qos.h> 182849dd8bSTaniya Das #include <linux/slab.h> 19275157b3SThara Gopinath #include <linux/spinlock.h> 209de0d75bSYicong Yang #include <linux/units.h> 212849dd8bSTaniya Das 222849dd8bSTaniya Das #define LUT_MAX_ENTRIES 40U 232849dd8bSTaniya Das #define LUT_SRC GENMASK(31, 30) 242849dd8bSTaniya Das #define LUT_L_VAL GENMASK(7, 0) 252849dd8bSTaniya Das #define LUT_CORE_COUNT GENMASK(18, 16) 2655538fbcSTaniya Das #define LUT_VOLT GENMASK(11, 0) 272849dd8bSTaniya Das #define CLK_HW_DIV 2 280eae1e37SSibi Sankar #define LUT_TURBO_IND 1 292849dd8bSTaniya Das 30e4e64486SVladimir Zapolskiy #define GT_IRQ_STATUS BIT(2) 31e4e64486SVladimir Zapolskiy 32dcd1fd72SManivannan Sadhasivam struct qcom_cpufreq_soc_data { 33dcd1fd72SManivannan Sadhasivam u32 reg_enable; 34f84ccad5SVladimir Zapolskiy u32 reg_domain_state; 35c377d4baSBjorn Andersson u32 reg_dcvs_ctrl; 36dcd1fd72SManivannan Sadhasivam u32 reg_freq_lut; 37dcd1fd72SManivannan Sadhasivam u32 reg_volt_lut; 38e4e64486SVladimir Zapolskiy u32 reg_intr_clr; 39275157b3SThara Gopinath u32 reg_current_vote; 40dcd1fd72SManivannan Sadhasivam u32 reg_perf_state; 41dcd1fd72SManivannan Sadhasivam u8 lut_row_size; 42dcd1fd72SManivannan Sadhasivam }; 43dcd1fd72SManivannan Sadhasivam 44dcd1fd72SManivannan Sadhasivam struct qcom_cpufreq_data { 45dcd1fd72SManivannan Sadhasivam void __iomem *base; 4667fc209bSShawn Guo struct resource *res; 47275157b3SThara Gopinath 48275157b3SThara Gopinath /* 49275157b3SThara Gopinath * Mutex to synchronize between de-init sequence and re-starting LMh 50275157b3SThara Gopinath * polling/interrupts 51275157b3SThara Gopinath */ 52275157b3SThara Gopinath struct mutex throttle_lock; 53275157b3SThara Gopinath int throttle_irq; 54be6592edSArd Biesheuvel char irq_name[15]; 55275157b3SThara Gopinath bool cancel_throttle; 56275157b3SThara Gopinath struct delayed_work throttle_work; 57275157b3SThara Gopinath struct cpufreq_policy *policy; 584370232cSManivannan Sadhasivam struct clk_hw cpu_clk; 59c377d4baSBjorn Andersson 60c377d4baSBjorn Andersson bool per_core_dcvs; 61c4c0efb0SXuewen Yan 62c4c0efb0SXuewen Yan struct freq_qos_request throttle_freq_req; 63dcd1fd72SManivannan Sadhasivam }; 642849dd8bSTaniya Das 65054a3ef6SManivannan Sadhasivam static struct { 66054a3ef6SManivannan Sadhasivam struct qcom_cpufreq_data *data; 674f796170SManivannan Sadhasivam const struct qcom_cpufreq_soc_data *soc_data; 68054a3ef6SManivannan Sadhasivam } qcom_cpufreq; 69054a3ef6SManivannan Sadhasivam 702849dd8bSTaniya Das static unsigned long cpu_hw_rate, xo_rate; 7151c843cfSSibi Sankar static bool icc_scaling_enabled; 7251c843cfSSibi Sankar 7351c843cfSSibi Sankar static int qcom_cpufreq_set_bw(struct cpufreq_policy *policy, 7451c843cfSSibi Sankar unsigned long freq_khz) 7551c843cfSSibi Sankar { 7651c843cfSSibi Sankar unsigned long freq_hz = freq_khz * 1000; 7751c843cfSSibi Sankar struct dev_pm_opp *opp; 7851c843cfSSibi Sankar struct device *dev; 7951c843cfSSibi Sankar int ret; 8051c843cfSSibi Sankar 8151c843cfSSibi Sankar dev = get_cpu_device(policy->cpu); 8251c843cfSSibi Sankar if (!dev) 8351c843cfSSibi Sankar return -ENODEV; 8451c843cfSSibi Sankar 8551c843cfSSibi Sankar opp = dev_pm_opp_find_freq_exact(dev, freq_hz, true); 8651c843cfSSibi Sankar if (IS_ERR(opp)) 8751c843cfSSibi Sankar return PTR_ERR(opp); 8851c843cfSSibi Sankar 898d25157fSViresh Kumar ret = dev_pm_opp_set_opp(dev, opp); 9051c843cfSSibi Sankar dev_pm_opp_put(opp); 9151c843cfSSibi Sankar return ret; 9251c843cfSSibi Sankar } 9351c843cfSSibi Sankar 9451c843cfSSibi Sankar static int qcom_cpufreq_update_opp(struct device *cpu_dev, 9551c843cfSSibi Sankar unsigned long freq_khz, 9651c843cfSSibi Sankar unsigned long volt) 9751c843cfSSibi Sankar { 9851c843cfSSibi Sankar unsigned long freq_hz = freq_khz * 1000; 9951c843cfSSibi Sankar int ret; 10051c843cfSSibi Sankar 10151c843cfSSibi Sankar /* Skip voltage update if the opp table is not available */ 10251c843cfSSibi Sankar if (!icc_scaling_enabled) 10351c843cfSSibi Sankar return dev_pm_opp_add(cpu_dev, freq_hz, volt); 10451c843cfSSibi Sankar 10551c843cfSSibi Sankar ret = dev_pm_opp_adjust_voltage(cpu_dev, freq_hz, volt, volt, volt); 10651c843cfSSibi Sankar if (ret) { 10751c843cfSSibi Sankar dev_err(cpu_dev, "Voltage update failed freq=%ld\n", freq_khz); 10851c843cfSSibi Sankar return ret; 10951c843cfSSibi Sankar } 11051c843cfSSibi Sankar 11151c843cfSSibi Sankar return dev_pm_opp_enable(cpu_dev, freq_hz); 11251c843cfSSibi Sankar } 1132849dd8bSTaniya Das 1142849dd8bSTaniya Das static int qcom_cpufreq_hw_target_index(struct cpufreq_policy *policy, 1152849dd8bSTaniya Das unsigned int index) 1162849dd8bSTaniya Das { 117dcd1fd72SManivannan Sadhasivam struct qcom_cpufreq_data *data = policy->driver_data; 1184f796170SManivannan Sadhasivam const struct qcom_cpufreq_soc_data *soc_data = qcom_cpufreq.soc_data; 119ada54f35SDouglas RAILLARD unsigned long freq = policy->freq_table[index].frequency; 120c377d4baSBjorn Andersson unsigned int i; 1212849dd8bSTaniya Das 122dcd1fd72SManivannan Sadhasivam writel_relaxed(index, data->base + soc_data->reg_perf_state); 1232849dd8bSTaniya Das 124c377d4baSBjorn Andersson if (data->per_core_dcvs) 125c377d4baSBjorn Andersson for (i = 1; i < cpumask_weight(policy->related_cpus); i++) 126c377d4baSBjorn Andersson writel_relaxed(index, data->base + soc_data->reg_perf_state + i * 4); 127c377d4baSBjorn Andersson 12851c843cfSSibi Sankar if (icc_scaling_enabled) 12951c843cfSSibi Sankar qcom_cpufreq_set_bw(policy, freq); 13051c843cfSSibi Sankar 1312849dd8bSTaniya Das return 0; 1322849dd8bSTaniya Das } 1332849dd8bSTaniya Das 134c72cf0cbSManivannan Sadhasivam static unsigned long qcom_lmh_get_throttle_freq(struct qcom_cpufreq_data *data) 135c72cf0cbSManivannan Sadhasivam { 136c72cf0cbSManivannan Sadhasivam unsigned int lval; 137c72cf0cbSManivannan Sadhasivam 138c72cf0cbSManivannan Sadhasivam if (qcom_cpufreq.soc_data->reg_current_vote) 139c72cf0cbSManivannan Sadhasivam lval = readl_relaxed(data->base + qcom_cpufreq.soc_data->reg_current_vote) & 0x3ff; 140c72cf0cbSManivannan Sadhasivam else 141c72cf0cbSManivannan Sadhasivam lval = readl_relaxed(data->base + qcom_cpufreq.soc_data->reg_domain_state) & 0xff; 142c72cf0cbSManivannan Sadhasivam 143c72cf0cbSManivannan Sadhasivam return lval * xo_rate; 144c72cf0cbSManivannan Sadhasivam } 145c72cf0cbSManivannan Sadhasivam 146c72cf0cbSManivannan Sadhasivam /* Get the frequency requested by the cpufreq core for the CPU */ 147c72cf0cbSManivannan Sadhasivam static unsigned int qcom_cpufreq_get_freq(unsigned int cpu) 148c72cf0cbSManivannan Sadhasivam { 149c72cf0cbSManivannan Sadhasivam struct qcom_cpufreq_data *data; 150dcd1fd72SManivannan Sadhasivam const struct qcom_cpufreq_soc_data *soc_data; 1512849dd8bSTaniya Das struct cpufreq_policy *policy; 1522849dd8bSTaniya Das unsigned int index; 1532849dd8bSTaniya Das 1542849dd8bSTaniya Das policy = cpufreq_cpu_get_raw(cpu); 1552849dd8bSTaniya Das if (!policy) 1562849dd8bSTaniya Das return 0; 1572849dd8bSTaniya Das 158dcd1fd72SManivannan Sadhasivam data = policy->driver_data; 1594f796170SManivannan Sadhasivam soc_data = qcom_cpufreq.soc_data; 1602849dd8bSTaniya Das 161dcd1fd72SManivannan Sadhasivam index = readl_relaxed(data->base + soc_data->reg_perf_state); 1622849dd8bSTaniya Das index = min(index, LUT_MAX_ENTRIES - 1); 1632849dd8bSTaniya Das 1642849dd8bSTaniya Das return policy->freq_table[index].frequency; 1652849dd8bSTaniya Das } 1662849dd8bSTaniya Das 16751be2fffSDouglas Anderson static unsigned int qcom_cpufreq_hw_get(unsigned int cpu) 16851be2fffSDouglas Anderson { 16951be2fffSDouglas Anderson struct qcom_cpufreq_data *data; 17051be2fffSDouglas Anderson struct cpufreq_policy *policy; 17151be2fffSDouglas Anderson 17251be2fffSDouglas Anderson policy = cpufreq_cpu_get_raw(cpu); 17351be2fffSDouglas Anderson if (!policy) 17451be2fffSDouglas Anderson return 0; 17551be2fffSDouglas Anderson 17651be2fffSDouglas Anderson data = policy->driver_data; 17751be2fffSDouglas Anderson 17851be2fffSDouglas Anderson if (data->throttle_irq >= 0) 17951be2fffSDouglas Anderson return qcom_lmh_get_throttle_freq(data) / HZ_PER_KHZ; 18051be2fffSDouglas Anderson 18151be2fffSDouglas Anderson return qcom_cpufreq_get_freq(cpu); 18251be2fffSDouglas Anderson } 18351be2fffSDouglas Anderson 1842849dd8bSTaniya Das static unsigned int qcom_cpufreq_hw_fast_switch(struct cpufreq_policy *policy, 1852849dd8bSTaniya Das unsigned int target_freq) 1862849dd8bSTaniya Das { 187dcd1fd72SManivannan Sadhasivam struct qcom_cpufreq_data *data = policy->driver_data; 1884f796170SManivannan Sadhasivam const struct qcom_cpufreq_soc_data *soc_data = qcom_cpufreq.soc_data; 189292072c3SViresh Kumar unsigned int index; 190c377d4baSBjorn Andersson unsigned int i; 1912849dd8bSTaniya Das 1922849dd8bSTaniya Das index = policy->cached_resolved_idx; 193dcd1fd72SManivannan Sadhasivam writel_relaxed(index, data->base + soc_data->reg_perf_state); 1942849dd8bSTaniya Das 195c377d4baSBjorn Andersson if (data->per_core_dcvs) 196c377d4baSBjorn Andersson for (i = 1; i < cpumask_weight(policy->related_cpus); i++) 197c377d4baSBjorn Andersson writel_relaxed(index, data->base + soc_data->reg_perf_state + i * 4); 198c377d4baSBjorn Andersson 1991a0419b0SIonela Voinescu return policy->freq_table[index].frequency; 2002849dd8bSTaniya Das } 2012849dd8bSTaniya Das 20255538fbcSTaniya Das static int qcom_cpufreq_hw_read_lut(struct device *cpu_dev, 203dcd1fd72SManivannan Sadhasivam struct cpufreq_policy *policy) 2042849dd8bSTaniya Das { 2050eae1e37SSibi Sankar u32 data, src, lval, i, core_count, prev_freq = 0, freq; 20655538fbcSTaniya Das u32 volt; 2072849dd8bSTaniya Das struct cpufreq_frequency_table *table; 20851c843cfSSibi Sankar struct dev_pm_opp *opp; 20951c843cfSSibi Sankar unsigned long rate; 21051c843cfSSibi Sankar int ret; 211dcd1fd72SManivannan Sadhasivam struct qcom_cpufreq_data *drv_data = policy->driver_data; 2124f796170SManivannan Sadhasivam const struct qcom_cpufreq_soc_data *soc_data = qcom_cpufreq.soc_data; 2132849dd8bSTaniya Das 2142849dd8bSTaniya Das table = kcalloc(LUT_MAX_ENTRIES + 1, sizeof(*table), GFP_KERNEL); 2152849dd8bSTaniya Das if (!table) 2162849dd8bSTaniya Das return -ENOMEM; 2172849dd8bSTaniya Das 21851c843cfSSibi Sankar ret = dev_pm_opp_of_add_table(cpu_dev); 21951c843cfSSibi Sankar if (!ret) { 22051c843cfSSibi Sankar /* Disable all opps and cross-validate against LUT later */ 22151c843cfSSibi Sankar icc_scaling_enabled = true; 22251c843cfSSibi Sankar for (rate = 0; ; rate++) { 22351c843cfSSibi Sankar opp = dev_pm_opp_find_freq_ceil(cpu_dev, &rate); 22451c843cfSSibi Sankar if (IS_ERR(opp)) 22551c843cfSSibi Sankar break; 22651c843cfSSibi Sankar 22751c843cfSSibi Sankar dev_pm_opp_put(opp); 22851c843cfSSibi Sankar dev_pm_opp_disable(cpu_dev, rate); 22951c843cfSSibi Sankar } 23051c843cfSSibi Sankar } else if (ret != -ENODEV) { 23151c843cfSSibi Sankar dev_err(cpu_dev, "Invalid opp table in device tree\n"); 2329901c21bSChen Hui kfree(table); 23351c843cfSSibi Sankar return ret; 23451c843cfSSibi Sankar } else { 235afdb219bSSibi Sankar policy->fast_switch_possible = true; 23651c843cfSSibi Sankar icc_scaling_enabled = false; 23751c843cfSSibi Sankar } 23851c843cfSSibi Sankar 2392849dd8bSTaniya Das for (i = 0; i < LUT_MAX_ENTRIES; i++) { 240dcd1fd72SManivannan Sadhasivam data = readl_relaxed(drv_data->base + soc_data->reg_freq_lut + 241dcd1fd72SManivannan Sadhasivam i * soc_data->lut_row_size); 2422849dd8bSTaniya Das src = FIELD_GET(LUT_SRC, data); 2432849dd8bSTaniya Das lval = FIELD_GET(LUT_L_VAL, data); 2442849dd8bSTaniya Das core_count = FIELD_GET(LUT_CORE_COUNT, data); 2452849dd8bSTaniya Das 246dcd1fd72SManivannan Sadhasivam data = readl_relaxed(drv_data->base + soc_data->reg_volt_lut + 247dcd1fd72SManivannan Sadhasivam i * soc_data->lut_row_size); 24855538fbcSTaniya Das volt = FIELD_GET(LUT_VOLT, data) * 1000; 24955538fbcSTaniya Das 2502849dd8bSTaniya Das if (src) 2512849dd8bSTaniya Das freq = xo_rate * lval / 1000; 2522849dd8bSTaniya Das else 2532849dd8bSTaniya Das freq = cpu_hw_rate / 1000; 2542849dd8bSTaniya Das 2550eae1e37SSibi Sankar if (freq != prev_freq && core_count != LUT_TURBO_IND) { 256bc9b9c5aSMatthias Kaehlcke if (!qcom_cpufreq_update_opp(cpu_dev, freq, volt)) { 2572849dd8bSTaniya Das table[i].frequency = freq; 25855538fbcSTaniya Das dev_dbg(cpu_dev, "index=%d freq=%d, core_count %d\n", i, 2592849dd8bSTaniya Das freq, core_count); 260bc9b9c5aSMatthias Kaehlcke } else { 261bc9b9c5aSMatthias Kaehlcke dev_warn(cpu_dev, "failed to update OPP for freq=%d\n", freq); 262bc9b9c5aSMatthias Kaehlcke table[i].frequency = CPUFREQ_ENTRY_INVALID; 263bc9b9c5aSMatthias Kaehlcke } 264bc9b9c5aSMatthias Kaehlcke 2650eae1e37SSibi Sankar } else if (core_count == LUT_TURBO_IND) { 26655538fbcSTaniya Das table[i].frequency = CPUFREQ_ENTRY_INVALID; 2672849dd8bSTaniya Das } 2682849dd8bSTaniya Das 2692849dd8bSTaniya Das /* 2702849dd8bSTaniya Das * Two of the same frequencies with the same core counts means 2712849dd8bSTaniya Das * end of table 2722849dd8bSTaniya Das */ 2730eae1e37SSibi Sankar if (i > 0 && prev_freq == freq) { 2742849dd8bSTaniya Das struct cpufreq_frequency_table *prev = &table[i - 1]; 2752849dd8bSTaniya Das 2762849dd8bSTaniya Das /* 2772849dd8bSTaniya Das * Only treat the last frequency that might be a boost 2782849dd8bSTaniya Das * as the boost frequency 2792849dd8bSTaniya Das */ 2800eae1e37SSibi Sankar if (prev->frequency == CPUFREQ_ENTRY_INVALID) { 281bc9b9c5aSMatthias Kaehlcke if (!qcom_cpufreq_update_opp(cpu_dev, prev_freq, volt)) { 2822849dd8bSTaniya Das prev->frequency = prev_freq; 2832849dd8bSTaniya Das prev->flags = CPUFREQ_BOOST_FREQ; 284bc9b9c5aSMatthias Kaehlcke } else { 285bc9b9c5aSMatthias Kaehlcke dev_warn(cpu_dev, "failed to update OPP for freq=%d\n", 286bc9b9c5aSMatthias Kaehlcke freq); 287bc9b9c5aSMatthias Kaehlcke } 2882849dd8bSTaniya Das } 2892849dd8bSTaniya Das 2902849dd8bSTaniya Das break; 2912849dd8bSTaniya Das } 2922849dd8bSTaniya Das 2932849dd8bSTaniya Das prev_freq = freq; 2942849dd8bSTaniya Das } 2952849dd8bSTaniya Das 2962849dd8bSTaniya Das table[i].frequency = CPUFREQ_TABLE_END; 2972849dd8bSTaniya Das policy->freq_table = table; 29855538fbcSTaniya Das dev_pm_opp_set_sharing_cpus(cpu_dev, policy->cpus); 2992849dd8bSTaniya Das 3002849dd8bSTaniya Das return 0; 3012849dd8bSTaniya Das } 3022849dd8bSTaniya Das 3032849dd8bSTaniya Das static void qcom_get_related_cpus(int index, struct cpumask *m) 3042849dd8bSTaniya Das { 3052849dd8bSTaniya Das struct device_node *cpu_np; 3062849dd8bSTaniya Das struct of_phandle_args args; 3072849dd8bSTaniya Das int cpu, ret; 3082849dd8bSTaniya Das 3092849dd8bSTaniya Das for_each_possible_cpu(cpu) { 3102849dd8bSTaniya Das cpu_np = of_cpu_device_node_get(cpu); 3112849dd8bSTaniya Das if (!cpu_np) 3122849dd8bSTaniya Das continue; 3132849dd8bSTaniya Das 3142849dd8bSTaniya Das ret = of_parse_phandle_with_args(cpu_np, "qcom,freq-domain", 3152849dd8bSTaniya Das "#freq-domain-cells", 0, 3162849dd8bSTaniya Das &args); 3172849dd8bSTaniya Das of_node_put(cpu_np); 3182849dd8bSTaniya Das if (ret < 0) 3192849dd8bSTaniya Das continue; 3202849dd8bSTaniya Das 3212849dd8bSTaniya Das if (index == args.args[0]) 3222849dd8bSTaniya Das cpumask_set_cpu(cpu, m); 3232849dd8bSTaniya Das } 3242849dd8bSTaniya Das } 3252849dd8bSTaniya Das 326275157b3SThara Gopinath static void qcom_lmh_dcvs_notify(struct qcom_cpufreq_data *data) 327275157b3SThara Gopinath { 328275157b3SThara Gopinath struct cpufreq_policy *policy = data->policy; 3295e4f009dSDmitry Baryshkov int cpu = cpumask_first(policy->related_cpus); 330275157b3SThara Gopinath struct device *dev = get_cpu_device(cpu); 3310258cb19SLukasz Luba unsigned long freq_hz, throttled_freq; 332275157b3SThara Gopinath struct dev_pm_opp *opp; 333275157b3SThara Gopinath 334275157b3SThara Gopinath /* 335275157b3SThara Gopinath * Get the h/w throttled frequency, normalize it using the 336275157b3SThara Gopinath * registered opp table and use it to calculate thermal pressure. 337275157b3SThara Gopinath */ 338f84ccad5SVladimir Zapolskiy freq_hz = qcom_lmh_get_throttle_freq(data); 339275157b3SThara Gopinath 340275157b3SThara Gopinath opp = dev_pm_opp_find_freq_floor(dev, &freq_hz); 341275157b3SThara Gopinath if (IS_ERR(opp) && PTR_ERR(opp) == -ERANGE) 3426240aaadSDmitry Baryshkov opp = dev_pm_opp_find_freq_ceil(dev, &freq_hz); 343275157b3SThara Gopinath 3446240aaadSDmitry Baryshkov if (IS_ERR(opp)) { 3456240aaadSDmitry Baryshkov dev_warn(dev, "Can't find the OPP for throttling: %pe!\n", opp); 3466240aaadSDmitry Baryshkov } else { 34791dc90fdSViresh Kumar dev_pm_opp_put(opp); 34891dc90fdSViresh Kumar } 34991dc90fdSViresh Kumar 350275157b3SThara Gopinath throttled_freq = freq_hz / HZ_PER_KHZ; 351275157b3SThara Gopinath 352c4c0efb0SXuewen Yan freq_qos_update_request(&data->throttle_freq_req, throttled_freq); 353c4c0efb0SXuewen Yan 3540258cb19SLukasz Luba /* Update thermal pressure (the boost frequencies are accepted) */ 3550258cb19SLukasz Luba arch_update_thermal_pressure(policy->related_cpus, throttled_freq); 356275157b3SThara Gopinath 357275157b3SThara Gopinath /* 358275157b3SThara Gopinath * In the unlikely case policy is unregistered do not enable 359275157b3SThara Gopinath * polling or h/w interrupt 360275157b3SThara Gopinath */ 361275157b3SThara Gopinath mutex_lock(&data->throttle_lock); 362275157b3SThara Gopinath if (data->cancel_throttle) 363275157b3SThara Gopinath goto out; 364275157b3SThara Gopinath 365275157b3SThara Gopinath /* 366275157b3SThara Gopinath * If h/w throttled frequency is higher than what cpufreq has requested 367275157b3SThara Gopinath * for, then stop polling and switch back to interrupt mechanism. 368275157b3SThara Gopinath */ 369c72cf0cbSManivannan Sadhasivam if (throttled_freq >= qcom_cpufreq_get_freq(cpu)) 370275157b3SThara Gopinath enable_irq(data->throttle_irq); 371275157b3SThara Gopinath else 372275157b3SThara Gopinath mod_delayed_work(system_highpri_wq, &data->throttle_work, 373275157b3SThara Gopinath msecs_to_jiffies(10)); 374275157b3SThara Gopinath 375275157b3SThara Gopinath out: 376275157b3SThara Gopinath mutex_unlock(&data->throttle_lock); 377275157b3SThara Gopinath } 378275157b3SThara Gopinath 379275157b3SThara Gopinath static void qcom_lmh_dcvs_poll(struct work_struct *work) 380275157b3SThara Gopinath { 381275157b3SThara Gopinath struct qcom_cpufreq_data *data; 382275157b3SThara Gopinath 383275157b3SThara Gopinath data = container_of(work, struct qcom_cpufreq_data, throttle_work.work); 384275157b3SThara Gopinath qcom_lmh_dcvs_notify(data); 385275157b3SThara Gopinath } 386275157b3SThara Gopinath 387275157b3SThara Gopinath static irqreturn_t qcom_lmh_dcvs_handle_irq(int irq, void *data) 388275157b3SThara Gopinath { 389275157b3SThara Gopinath struct qcom_cpufreq_data *c_data = data; 390275157b3SThara Gopinath 391275157b3SThara Gopinath /* Disable interrupt and enable polling */ 392275157b3SThara Gopinath disable_irq_nosync(c_data->throttle_irq); 393e0e27c3dSVladimir Zapolskiy schedule_delayed_work(&c_data->throttle_work, 0); 394275157b3SThara Gopinath 3954f796170SManivannan Sadhasivam if (qcom_cpufreq.soc_data->reg_intr_clr) 396e4e64486SVladimir Zapolskiy writel_relaxed(GT_IRQ_STATUS, 3974f796170SManivannan Sadhasivam c_data->base + qcom_cpufreq.soc_data->reg_intr_clr); 398e4e64486SVladimir Zapolskiy 399e0e27c3dSVladimir Zapolskiy return IRQ_HANDLED; 400275157b3SThara Gopinath } 401275157b3SThara Gopinath 402dcd1fd72SManivannan Sadhasivam static const struct qcom_cpufreq_soc_data qcom_soc_data = { 403dcd1fd72SManivannan Sadhasivam .reg_enable = 0x0, 404c377d4baSBjorn Andersson .reg_dcvs_ctrl = 0xbc, 405dcd1fd72SManivannan Sadhasivam .reg_freq_lut = 0x110, 406dcd1fd72SManivannan Sadhasivam .reg_volt_lut = 0x114, 407275157b3SThara Gopinath .reg_current_vote = 0x704, 408dcd1fd72SManivannan Sadhasivam .reg_perf_state = 0x920, 409dcd1fd72SManivannan Sadhasivam .lut_row_size = 32, 410dcd1fd72SManivannan Sadhasivam }; 411dcd1fd72SManivannan Sadhasivam 41249b59f4cSManivannan Sadhasivam static const struct qcom_cpufreq_soc_data epss_soc_data = { 41349b59f4cSManivannan Sadhasivam .reg_enable = 0x0, 414f84ccad5SVladimir Zapolskiy .reg_domain_state = 0x20, 415c377d4baSBjorn Andersson .reg_dcvs_ctrl = 0xb0, 41649b59f4cSManivannan Sadhasivam .reg_freq_lut = 0x100, 41749b59f4cSManivannan Sadhasivam .reg_volt_lut = 0x200, 418e4e64486SVladimir Zapolskiy .reg_intr_clr = 0x308, 41949b59f4cSManivannan Sadhasivam .reg_perf_state = 0x320, 42049b59f4cSManivannan Sadhasivam .lut_row_size = 4, 42149b59f4cSManivannan Sadhasivam }; 42249b59f4cSManivannan Sadhasivam 423dcd1fd72SManivannan Sadhasivam static const struct of_device_id qcom_cpufreq_hw_match[] = { 424dcd1fd72SManivannan Sadhasivam { .compatible = "qcom,cpufreq-hw", .data = &qcom_soc_data }, 42549b59f4cSManivannan Sadhasivam { .compatible = "qcom,cpufreq-epss", .data = &epss_soc_data }, 426dcd1fd72SManivannan Sadhasivam {} 427dcd1fd72SManivannan Sadhasivam }; 428dcd1fd72SManivannan Sadhasivam MODULE_DEVICE_TABLE(of, qcom_cpufreq_hw_match); 429dcd1fd72SManivannan Sadhasivam 430275157b3SThara Gopinath static int qcom_cpufreq_hw_lmh_init(struct cpufreq_policy *policy, int index) 431275157b3SThara Gopinath { 432275157b3SThara Gopinath struct qcom_cpufreq_data *data = policy->driver_data; 433275157b3SThara Gopinath struct platform_device *pdev = cpufreq_get_driver_data(); 434275157b3SThara Gopinath int ret; 435275157b3SThara Gopinath 436275157b3SThara Gopinath /* 437275157b3SThara Gopinath * Look for LMh interrupt. If no interrupt line is specified / 438275157b3SThara Gopinath * if there is an error, allow cpufreq to be enabled as usual. 439275157b3SThara Gopinath */ 4408f5783adSStephen Boyd data->throttle_irq = platform_get_irq_optional(pdev, index); 4418f5783adSStephen Boyd if (data->throttle_irq == -ENXIO) 4428f5783adSStephen Boyd return 0; 4438f5783adSStephen Boyd if (data->throttle_irq < 0) 4448f5783adSStephen Boyd return data->throttle_irq; 445275157b3SThara Gopinath 446c4c0efb0SXuewen Yan ret = freq_qos_add_request(&policy->constraints, 447c4c0efb0SXuewen Yan &data->throttle_freq_req, FREQ_QOS_MAX, 448c4c0efb0SXuewen Yan FREQ_QOS_MAX_DEFAULT_VALUE); 449c4c0efb0SXuewen Yan if (ret < 0) { 450c4c0efb0SXuewen Yan dev_err(&pdev->dev, "Failed to add freq constraint (%d)\n", ret); 451c4c0efb0SXuewen Yan return ret; 452c4c0efb0SXuewen Yan } 453c4c0efb0SXuewen Yan 454275157b3SThara Gopinath data->cancel_throttle = false; 455275157b3SThara Gopinath data->policy = policy; 456275157b3SThara Gopinath 457275157b3SThara Gopinath mutex_init(&data->throttle_lock); 458275157b3SThara Gopinath INIT_DEFERRABLE_WORK(&data->throttle_work, qcom_lmh_dcvs_poll); 459275157b3SThara Gopinath 460be6592edSArd Biesheuvel snprintf(data->irq_name, sizeof(data->irq_name), "dcvsh-irq-%u", policy->cpu); 461275157b3SThara Gopinath ret = request_threaded_irq(data->throttle_irq, NULL, qcom_lmh_dcvs_handle_irq, 462ef8ee1cbSBjorn Andersson IRQF_ONESHOT | IRQF_NO_AUTOEN, data->irq_name, data); 463275157b3SThara Gopinath if (ret) { 464be6592edSArd Biesheuvel dev_err(&pdev->dev, "Error registering %s: %d\n", data->irq_name, ret); 465275157b3SThara Gopinath return 0; 466275157b3SThara Gopinath } 467275157b3SThara Gopinath 468f2b03dffSPierre Gondois ret = irq_set_affinity_and_hint(data->throttle_irq, policy->cpus); 4693ed6dfbdSVladimir Zapolskiy if (ret) 4703ed6dfbdSVladimir Zapolskiy dev_err(&pdev->dev, "Failed to set CPU affinity of %s[%d]\n", 4713ed6dfbdSVladimir Zapolskiy data->irq_name, data->throttle_irq); 4723ed6dfbdSVladimir Zapolskiy 473275157b3SThara Gopinath return 0; 474275157b3SThara Gopinath } 475275157b3SThara Gopinath 476a1eb080aSDmitry Baryshkov static int qcom_cpufreq_hw_cpu_online(struct cpufreq_policy *policy) 477275157b3SThara Gopinath { 478a1eb080aSDmitry Baryshkov struct qcom_cpufreq_data *data = policy->driver_data; 479a1eb080aSDmitry Baryshkov struct platform_device *pdev = cpufreq_get_driver_data(); 480a1eb080aSDmitry Baryshkov int ret; 481a1eb080aSDmitry Baryshkov 482668a7a12SStephen Boyd if (data->throttle_irq <= 0) 483668a7a12SStephen Boyd return 0; 484668a7a12SStephen Boyd 485cdcf8eb3SPierre Gondois mutex_lock(&data->throttle_lock); 486cdcf8eb3SPierre Gondois data->cancel_throttle = false; 487cdcf8eb3SPierre Gondois mutex_unlock(&data->throttle_lock); 488cdcf8eb3SPierre Gondois 489f2b03dffSPierre Gondois ret = irq_set_affinity_and_hint(data->throttle_irq, policy->cpus); 490a1eb080aSDmitry Baryshkov if (ret) 491a1eb080aSDmitry Baryshkov dev_err(&pdev->dev, "Failed to set CPU affinity of %s[%d]\n", 492a1eb080aSDmitry Baryshkov data->irq_name, data->throttle_irq); 493a1eb080aSDmitry Baryshkov 494a1eb080aSDmitry Baryshkov return ret; 495a1eb080aSDmitry Baryshkov } 496a1eb080aSDmitry Baryshkov 497a1eb080aSDmitry Baryshkov static int qcom_cpufreq_hw_cpu_offline(struct cpufreq_policy *policy) 498a1eb080aSDmitry Baryshkov { 499a1eb080aSDmitry Baryshkov struct qcom_cpufreq_data *data = policy->driver_data; 500a1eb080aSDmitry Baryshkov 501275157b3SThara Gopinath if (data->throttle_irq <= 0) 502a1eb080aSDmitry Baryshkov return 0; 503275157b3SThara Gopinath 504275157b3SThara Gopinath mutex_lock(&data->throttle_lock); 505275157b3SThara Gopinath data->cancel_throttle = true; 506275157b3SThara Gopinath mutex_unlock(&data->throttle_lock); 507275157b3SThara Gopinath 508275157b3SThara Gopinath cancel_delayed_work_sync(&data->throttle_work); 509f2b03dffSPierre Gondois irq_set_affinity_and_hint(data->throttle_irq, NULL); 510f7fca54aSPierre Gondois disable_irq_nosync(data->throttle_irq); 511a1eb080aSDmitry Baryshkov 512a1eb080aSDmitry Baryshkov return 0; 513a1eb080aSDmitry Baryshkov } 514a1eb080aSDmitry Baryshkov 515a1eb080aSDmitry Baryshkov static void qcom_cpufreq_hw_lmh_exit(struct qcom_cpufreq_data *data) 516a1eb080aSDmitry Baryshkov { 517668a7a12SStephen Boyd if (data->throttle_irq <= 0) 518668a7a12SStephen Boyd return; 519668a7a12SStephen Boyd 520c4c0efb0SXuewen Yan freq_qos_remove_request(&data->throttle_freq_req); 521275157b3SThara Gopinath free_irq(data->throttle_irq, data); 522275157b3SThara Gopinath } 523275157b3SThara Gopinath 5242849dd8bSTaniya Das static int qcom_cpufreq_hw_cpu_init(struct cpufreq_policy *policy) 5252849dd8bSTaniya Das { 526bd74e286SManivannan Sadhasivam struct platform_device *pdev = cpufreq_get_driver_data(); 527bd74e286SManivannan Sadhasivam struct device *dev = &pdev->dev; 5282849dd8bSTaniya Das struct of_phandle_args args; 5292849dd8bSTaniya Das struct device_node *cpu_np; 53055538fbcSTaniya Das struct device *cpu_dev; 531dcd1fd72SManivannan Sadhasivam struct qcom_cpufreq_data *data; 5322849dd8bSTaniya Das int ret, index; 5332849dd8bSTaniya Das 53455538fbcSTaniya Das cpu_dev = get_cpu_device(policy->cpu); 53555538fbcSTaniya Das if (!cpu_dev) { 53655538fbcSTaniya Das pr_err("%s: failed to get cpu%d device\n", __func__, 53755538fbcSTaniya Das policy->cpu); 53855538fbcSTaniya Das return -ENODEV; 53955538fbcSTaniya Das } 54055538fbcSTaniya Das 5412849dd8bSTaniya Das cpu_np = of_cpu_device_node_get(policy->cpu); 5422849dd8bSTaniya Das if (!cpu_np) 5432849dd8bSTaniya Das return -EINVAL; 5442849dd8bSTaniya Das 5452849dd8bSTaniya Das ret = of_parse_phandle_with_args(cpu_np, "qcom,freq-domain", 5462849dd8bSTaniya Das "#freq-domain-cells", 0, &args); 5472849dd8bSTaniya Das of_node_put(cpu_np); 5482849dd8bSTaniya Das if (ret) 5492849dd8bSTaniya Das return ret; 5502849dd8bSTaniya Das 5512849dd8bSTaniya Das index = args.args[0]; 552054a3ef6SManivannan Sadhasivam data = &qcom_cpufreq.data[index]; 5532849dd8bSTaniya Das 554054a3ef6SManivannan Sadhasivam /* HW should be in enabled state to proceed */ 5554f796170SManivannan Sadhasivam if (!(readl_relaxed(data->base + qcom_cpufreq.soc_data->reg_enable) & 0x1)) { 556054a3ef6SManivannan Sadhasivam dev_err(dev, "Domain-%d cpufreq hardware not enabled\n", index); 55767fc209bSShawn Guo return -ENODEV; 55867fc209bSShawn Guo } 5592849dd8bSTaniya Das 5604f796170SManivannan Sadhasivam if (readl_relaxed(data->base + qcom_cpufreq.soc_data->reg_dcvs_ctrl) & 0x1) 561c377d4baSBjorn Andersson data->per_core_dcvs = true; 562c377d4baSBjorn Andersson 5632849dd8bSTaniya Das qcom_get_related_cpus(index, policy->cpus); 5642849dd8bSTaniya Das 565dcd1fd72SManivannan Sadhasivam policy->driver_data = data; 566f0712aceSTaniya Das policy->dvfs_possible_from_any_cpu = true; 5672849dd8bSTaniya Das 568dcd1fd72SManivannan Sadhasivam ret = qcom_cpufreq_hw_read_lut(cpu_dev, policy); 5692849dd8bSTaniya Das if (ret) { 5702849dd8bSTaniya Das dev_err(dev, "Domain-%d failed to read LUT\n", index); 571054a3ef6SManivannan Sadhasivam return ret; 5722849dd8bSTaniya Das } 5732849dd8bSTaniya Das 57455538fbcSTaniya Das ret = dev_pm_opp_get_opp_count(cpu_dev); 57555538fbcSTaniya Das if (ret <= 0) { 57655538fbcSTaniya Das dev_err(cpu_dev, "Failed to add OPPs\n"); 577054a3ef6SManivannan Sadhasivam return -ENODEV; 57855538fbcSTaniya Das } 57955538fbcSTaniya Das 58026699172SShawn Guo if (policy_has_boost_freq(policy)) { 58126699172SShawn Guo ret = cpufreq_enable_boost_support(); 58226699172SShawn Guo if (ret) 58326699172SShawn Guo dev_warn(cpu_dev, "failed to enable boost: %d\n", ret); 58426699172SShawn Guo } 58526699172SShawn Guo 586054a3ef6SManivannan Sadhasivam return qcom_cpufreq_hw_lmh_init(policy, index); 5872849dd8bSTaniya Das } 5882849dd8bSTaniya Das 5892849dd8bSTaniya Das static int qcom_cpufreq_hw_cpu_exit(struct cpufreq_policy *policy) 5902849dd8bSTaniya Das { 59155538fbcSTaniya Das struct device *cpu_dev = get_cpu_device(policy->cpu); 592dcd1fd72SManivannan Sadhasivam struct qcom_cpufreq_data *data = policy->driver_data; 59367fc209bSShawn Guo struct resource *res = data->res; 59467fc209bSShawn Guo void __iomem *base = data->base; 5952849dd8bSTaniya Das 59655538fbcSTaniya Das dev_pm_opp_remove_all_dynamic(cpu_dev); 59751c843cfSSibi Sankar dev_pm_opp_of_cpumask_remove_table(policy->related_cpus); 598275157b3SThara Gopinath qcom_cpufreq_hw_lmh_exit(data); 5992849dd8bSTaniya Das kfree(policy->freq_table); 60067fc209bSShawn Guo kfree(data); 60167fc209bSShawn Guo iounmap(base); 60267fc209bSShawn Guo release_mem_region(res->start, resource_size(res)); 6032849dd8bSTaniya Das 6042849dd8bSTaniya Das return 0; 6052849dd8bSTaniya Das } 6062849dd8bSTaniya Das 607ef8ee1cbSBjorn Andersson static void qcom_cpufreq_ready(struct cpufreq_policy *policy) 608ef8ee1cbSBjorn Andersson { 609ef8ee1cbSBjorn Andersson struct qcom_cpufreq_data *data = policy->driver_data; 610ef8ee1cbSBjorn Andersson 611ef8ee1cbSBjorn Andersson if (data->throttle_irq >= 0) 612ef8ee1cbSBjorn Andersson enable_irq(data->throttle_irq); 613ef8ee1cbSBjorn Andersson } 614ef8ee1cbSBjorn Andersson 6152849dd8bSTaniya Das static struct freq_attr *qcom_cpufreq_hw_attr[] = { 6162849dd8bSTaniya Das &cpufreq_freq_attr_scaling_available_freqs, 6172849dd8bSTaniya Das &cpufreq_freq_attr_scaling_boost_freqs, 6182849dd8bSTaniya Das NULL 6192849dd8bSTaniya Das }; 6202849dd8bSTaniya Das 6212849dd8bSTaniya Das static struct cpufreq_driver cpufreq_qcom_hw_driver = { 6225ae4a4b4SViresh Kumar .flags = CPUFREQ_NEED_INITIAL_FREQ_CHECK | 6234c5ff1c8SAmit Kucheria CPUFREQ_HAVE_GOVERNOR_PER_POLICY | 6244c5ff1c8SAmit Kucheria CPUFREQ_IS_COOLING_DEV, 6252849dd8bSTaniya Das .verify = cpufreq_generic_frequency_table_verify, 6262849dd8bSTaniya Das .target_index = qcom_cpufreq_hw_target_index, 6272849dd8bSTaniya Das .get = qcom_cpufreq_hw_get, 6282849dd8bSTaniya Das .init = qcom_cpufreq_hw_cpu_init, 6292849dd8bSTaniya Das .exit = qcom_cpufreq_hw_cpu_exit, 630a1eb080aSDmitry Baryshkov .online = qcom_cpufreq_hw_cpu_online, 631a1eb080aSDmitry Baryshkov .offline = qcom_cpufreq_hw_cpu_offline, 632e96c2153SViresh Kumar .register_em = cpufreq_register_em_with_opp, 6332849dd8bSTaniya Das .fast_switch = qcom_cpufreq_hw_fast_switch, 6342849dd8bSTaniya Das .name = "qcom-cpufreq-hw", 6352849dd8bSTaniya Das .attr = qcom_cpufreq_hw_attr, 636ef8ee1cbSBjorn Andersson .ready = qcom_cpufreq_ready, 6372849dd8bSTaniya Das }; 6382849dd8bSTaniya Das 6394370232cSManivannan Sadhasivam static unsigned long qcom_cpufreq_hw_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) 6404370232cSManivannan Sadhasivam { 6414370232cSManivannan Sadhasivam struct qcom_cpufreq_data *data = container_of(hw, struct qcom_cpufreq_data, cpu_clk); 6424370232cSManivannan Sadhasivam 6434370232cSManivannan Sadhasivam return qcom_lmh_get_throttle_freq(data); 6444370232cSManivannan Sadhasivam } 6454370232cSManivannan Sadhasivam 6464370232cSManivannan Sadhasivam static const struct clk_ops qcom_cpufreq_hw_clk_ops = { 6474370232cSManivannan Sadhasivam .recalc_rate = qcom_cpufreq_hw_recalc_rate, 6484370232cSManivannan Sadhasivam }; 6494370232cSManivannan Sadhasivam 6502849dd8bSTaniya Das static int qcom_cpufreq_hw_driver_probe(struct platform_device *pdev) 6512849dd8bSTaniya Das { 6524370232cSManivannan Sadhasivam struct clk_hw_onecell_data *clk_data; 6537cfa8553SManivannan Sadhasivam struct device *dev = &pdev->dev; 6541a6a8b00SKonrad Dybcio struct device_node *soc_node; 65551c843cfSSibi Sankar struct device *cpu_dev; 6562849dd8bSTaniya Das struct clk *clk; 6571a6a8b00SKonrad Dybcio int ret, i, num_domains, reg_sz; 6582849dd8bSTaniya Das 6597cfa8553SManivannan Sadhasivam clk = clk_get(dev, "xo"); 6602849dd8bSTaniya Das if (IS_ERR(clk)) 6612849dd8bSTaniya Das return PTR_ERR(clk); 6622849dd8bSTaniya Das 6632849dd8bSTaniya Das xo_rate = clk_get_rate(clk); 6642849dd8bSTaniya Das clk_put(clk); 6652849dd8bSTaniya Das 6667cfa8553SManivannan Sadhasivam clk = clk_get(dev, "alternate"); 6672849dd8bSTaniya Das if (IS_ERR(clk)) 6682849dd8bSTaniya Das return PTR_ERR(clk); 6692849dd8bSTaniya Das 6702849dd8bSTaniya Das cpu_hw_rate = clk_get_rate(clk) / CLK_HW_DIV; 6712849dd8bSTaniya Das clk_put(clk); 6722849dd8bSTaniya Das 673bd74e286SManivannan Sadhasivam cpufreq_qcom_hw_driver.driver_data = pdev; 6742849dd8bSTaniya Das 67551c843cfSSibi Sankar /* Check for optional interconnect paths on CPU0 */ 67651c843cfSSibi Sankar cpu_dev = get_cpu_device(0); 67751c843cfSSibi Sankar if (!cpu_dev) 67851c843cfSSibi Sankar return -EPROBE_DEFER; 67951c843cfSSibi Sankar 68051c843cfSSibi Sankar ret = dev_pm_opp_of_find_icc_paths(cpu_dev, NULL); 68151c843cfSSibi Sankar if (ret) 68251c843cfSSibi Sankar return ret; 68351c843cfSSibi Sankar 684054a3ef6SManivannan Sadhasivam /* Allocate qcom_cpufreq_data based on the available frequency domains in DT */ 6851a6a8b00SKonrad Dybcio soc_node = of_get_parent(dev->of_node); 6861a6a8b00SKonrad Dybcio if (!soc_node) 6871a6a8b00SKonrad Dybcio return -EINVAL; 6881a6a8b00SKonrad Dybcio 6891a6a8b00SKonrad Dybcio ret = of_property_read_u32(soc_node, "#address-cells", ®_sz); 6901a6a8b00SKonrad Dybcio if (ret) 6911a6a8b00SKonrad Dybcio goto of_exit; 6921a6a8b00SKonrad Dybcio 6931a6a8b00SKonrad Dybcio ret = of_property_read_u32(soc_node, "#size-cells", &i); 6941a6a8b00SKonrad Dybcio if (ret) 6951a6a8b00SKonrad Dybcio goto of_exit; 6961a6a8b00SKonrad Dybcio 6971a6a8b00SKonrad Dybcio reg_sz += i; 6981a6a8b00SKonrad Dybcio 6991a6a8b00SKonrad Dybcio num_domains = of_property_count_elems_of_size(dev->of_node, "reg", sizeof(u32) * reg_sz); 700054a3ef6SManivannan Sadhasivam if (num_domains <= 0) 701054a3ef6SManivannan Sadhasivam return num_domains; 702054a3ef6SManivannan Sadhasivam 7037cfa8553SManivannan Sadhasivam qcom_cpufreq.data = devm_kzalloc(dev, sizeof(struct qcom_cpufreq_data) * num_domains, 704054a3ef6SManivannan Sadhasivam GFP_KERNEL); 705054a3ef6SManivannan Sadhasivam if (!qcom_cpufreq.data) 706054a3ef6SManivannan Sadhasivam return -ENOMEM; 707054a3ef6SManivannan Sadhasivam 7084f796170SManivannan Sadhasivam qcom_cpufreq.soc_data = of_device_get_match_data(dev); 709*6f098cdeSManivannan Sadhasivam if (!qcom_cpufreq.soc_data) 710*6f098cdeSManivannan Sadhasivam return -ENODEV; 7114f796170SManivannan Sadhasivam 7124370232cSManivannan Sadhasivam clk_data = devm_kzalloc(dev, struct_size(clk_data, hws, num_domains), GFP_KERNEL); 7134370232cSManivannan Sadhasivam if (!clk_data) 7144370232cSManivannan Sadhasivam return -ENOMEM; 7154370232cSManivannan Sadhasivam 7164370232cSManivannan Sadhasivam clk_data->num = num_domains; 7174370232cSManivannan Sadhasivam 718054a3ef6SManivannan Sadhasivam for (i = 0; i < num_domains; i++) { 719054a3ef6SManivannan Sadhasivam struct qcom_cpufreq_data *data = &qcom_cpufreq.data[i]; 7204370232cSManivannan Sadhasivam struct clk_init_data clk_init = {}; 721054a3ef6SManivannan Sadhasivam struct resource *res; 722054a3ef6SManivannan Sadhasivam void __iomem *base; 723054a3ef6SManivannan Sadhasivam 724054a3ef6SManivannan Sadhasivam base = devm_platform_get_and_ioremap_resource(pdev, i, &res); 725054a3ef6SManivannan Sadhasivam if (IS_ERR(base)) { 7267cfa8553SManivannan Sadhasivam dev_err(dev, "Failed to map resource %pR\n", res); 727054a3ef6SManivannan Sadhasivam return PTR_ERR(base); 728054a3ef6SManivannan Sadhasivam } 729054a3ef6SManivannan Sadhasivam 730054a3ef6SManivannan Sadhasivam data->base = base; 731054a3ef6SManivannan Sadhasivam data->res = res; 7324370232cSManivannan Sadhasivam 7334370232cSManivannan Sadhasivam /* Register CPU clock for each frequency domain */ 7344370232cSManivannan Sadhasivam clk_init.name = kasprintf(GFP_KERNEL, "qcom_cpufreq%d", i); 7354370232cSManivannan Sadhasivam if (!clk_init.name) 7364370232cSManivannan Sadhasivam return -ENOMEM; 7374370232cSManivannan Sadhasivam 7384370232cSManivannan Sadhasivam clk_init.flags = CLK_GET_RATE_NOCACHE; 7394370232cSManivannan Sadhasivam clk_init.ops = &qcom_cpufreq_hw_clk_ops; 7404370232cSManivannan Sadhasivam data->cpu_clk.init = &clk_init; 7414370232cSManivannan Sadhasivam 7424370232cSManivannan Sadhasivam ret = devm_clk_hw_register(dev, &data->cpu_clk); 7434370232cSManivannan Sadhasivam if (ret < 0) { 7444370232cSManivannan Sadhasivam dev_err(dev, "Failed to register clock %d: %d\n", i, ret); 7454370232cSManivannan Sadhasivam kfree(clk_init.name); 7464370232cSManivannan Sadhasivam return ret; 7474370232cSManivannan Sadhasivam } 7484370232cSManivannan Sadhasivam 7494370232cSManivannan Sadhasivam clk_data->hws[i] = &data->cpu_clk; 7504370232cSManivannan Sadhasivam kfree(clk_init.name); 7514370232cSManivannan Sadhasivam } 7524370232cSManivannan Sadhasivam 7534370232cSManivannan Sadhasivam ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get, clk_data); 7544370232cSManivannan Sadhasivam if (ret < 0) { 7554370232cSManivannan Sadhasivam dev_err(dev, "Failed to add clock provider\n"); 7564370232cSManivannan Sadhasivam return ret; 757054a3ef6SManivannan Sadhasivam } 758054a3ef6SManivannan Sadhasivam 7592849dd8bSTaniya Das ret = cpufreq_register_driver(&cpufreq_qcom_hw_driver); 7602849dd8bSTaniya Das if (ret) 7617cfa8553SManivannan Sadhasivam dev_err(dev, "CPUFreq HW driver failed to register\n"); 7622849dd8bSTaniya Das else 7637cfa8553SManivannan Sadhasivam dev_dbg(dev, "QCOM CPUFreq HW driver initialized\n"); 7642849dd8bSTaniya Das 7651a6a8b00SKonrad Dybcio of_exit: 7661a6a8b00SKonrad Dybcio of_node_put(soc_node); 7671a6a8b00SKonrad Dybcio 7682849dd8bSTaniya Das return ret; 7692849dd8bSTaniya Das } 7702849dd8bSTaniya Das 7712849dd8bSTaniya Das static int qcom_cpufreq_hw_driver_remove(struct platform_device *pdev) 7722849dd8bSTaniya Das { 7732849dd8bSTaniya Das return cpufreq_unregister_driver(&cpufreq_qcom_hw_driver); 7742849dd8bSTaniya Das } 7752849dd8bSTaniya Das 7762849dd8bSTaniya Das static struct platform_driver qcom_cpufreq_hw_driver = { 7772849dd8bSTaniya Das .probe = qcom_cpufreq_hw_driver_probe, 7782849dd8bSTaniya Das .remove = qcom_cpufreq_hw_driver_remove, 7792849dd8bSTaniya Das .driver = { 7802849dd8bSTaniya Das .name = "qcom-cpufreq-hw", 7812849dd8bSTaniya Das .of_match_table = qcom_cpufreq_hw_match, 7822849dd8bSTaniya Das }, 7832849dd8bSTaniya Das }; 7842849dd8bSTaniya Das 7852849dd8bSTaniya Das static int __init qcom_cpufreq_hw_init(void) 7862849dd8bSTaniya Das { 7872849dd8bSTaniya Das return platform_driver_register(&qcom_cpufreq_hw_driver); 7882849dd8bSTaniya Das } 78911ff4bddSAmit Kucheria postcore_initcall(qcom_cpufreq_hw_init); 7902849dd8bSTaniya Das 7912849dd8bSTaniya Das static void __exit qcom_cpufreq_hw_exit(void) 7922849dd8bSTaniya Das { 7932849dd8bSTaniya Das platform_driver_unregister(&qcom_cpufreq_hw_driver); 7942849dd8bSTaniya Das } 7952849dd8bSTaniya Das module_exit(qcom_cpufreq_hw_exit); 7962849dd8bSTaniya Das 7972849dd8bSTaniya Das MODULE_DESCRIPTION("QCOM CPUFREQ HW Driver"); 7982849dd8bSTaniya Das MODULE_LICENSE("GPL v2"); 799