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> 72849dd8bSTaniya Das #include <linux/cpufreq.h> 82849dd8bSTaniya Das #include <linux/init.h> 951c843cfSSibi Sankar #include <linux/interconnect.h> 10275157b3SThara Gopinath #include <linux/interrupt.h> 112849dd8bSTaniya Das #include <linux/kernel.h> 122849dd8bSTaniya Das #include <linux/module.h> 132849dd8bSTaniya Das #include <linux/of_address.h> 142849dd8bSTaniya Das #include <linux/of_platform.h> 1555538fbcSTaniya Das #include <linux/pm_opp.h> 162849dd8bSTaniya Das #include <linux/slab.h> 17275157b3SThara Gopinath #include <linux/spinlock.h> 182849dd8bSTaniya Das 192849dd8bSTaniya Das #define LUT_MAX_ENTRIES 40U 202849dd8bSTaniya Das #define LUT_SRC GENMASK(31, 30) 212849dd8bSTaniya Das #define LUT_L_VAL GENMASK(7, 0) 222849dd8bSTaniya Das #define LUT_CORE_COUNT GENMASK(18, 16) 2355538fbcSTaniya Das #define LUT_VOLT GENMASK(11, 0) 242849dd8bSTaniya Das #define CLK_HW_DIV 2 250eae1e37SSibi Sankar #define LUT_TURBO_IND 1 262849dd8bSTaniya Das 27275157b3SThara Gopinath #define HZ_PER_KHZ 1000 28275157b3SThara Gopinath 29dcd1fd72SManivannan Sadhasivam struct qcom_cpufreq_soc_data { 30dcd1fd72SManivannan Sadhasivam u32 reg_enable; 31dcd1fd72SManivannan Sadhasivam u32 reg_freq_lut; 32dcd1fd72SManivannan Sadhasivam u32 reg_volt_lut; 33275157b3SThara Gopinath u32 reg_current_vote; 34dcd1fd72SManivannan Sadhasivam u32 reg_perf_state; 35dcd1fd72SManivannan Sadhasivam u8 lut_row_size; 36dcd1fd72SManivannan Sadhasivam }; 37dcd1fd72SManivannan Sadhasivam 38dcd1fd72SManivannan Sadhasivam struct qcom_cpufreq_data { 39dcd1fd72SManivannan Sadhasivam void __iomem *base; 4067fc209bSShawn Guo struct resource *res; 41dcd1fd72SManivannan Sadhasivam const struct qcom_cpufreq_soc_data *soc_data; 42275157b3SThara Gopinath 43275157b3SThara Gopinath /* 44275157b3SThara Gopinath * Mutex to synchronize between de-init sequence and re-starting LMh 45275157b3SThara Gopinath * polling/interrupts 46275157b3SThara Gopinath */ 47275157b3SThara Gopinath struct mutex throttle_lock; 48275157b3SThara Gopinath int throttle_irq; 49275157b3SThara Gopinath bool cancel_throttle; 50275157b3SThara Gopinath struct delayed_work throttle_work; 51275157b3SThara Gopinath struct cpufreq_policy *policy; 52dcd1fd72SManivannan Sadhasivam }; 532849dd8bSTaniya Das 542849dd8bSTaniya Das static unsigned long cpu_hw_rate, xo_rate; 5551c843cfSSibi Sankar static bool icc_scaling_enabled; 5651c843cfSSibi Sankar 5751c843cfSSibi Sankar static int qcom_cpufreq_set_bw(struct cpufreq_policy *policy, 5851c843cfSSibi Sankar unsigned long freq_khz) 5951c843cfSSibi Sankar { 6051c843cfSSibi Sankar unsigned long freq_hz = freq_khz * 1000; 6151c843cfSSibi Sankar struct dev_pm_opp *opp; 6251c843cfSSibi Sankar struct device *dev; 6351c843cfSSibi Sankar int ret; 6451c843cfSSibi Sankar 6551c843cfSSibi Sankar dev = get_cpu_device(policy->cpu); 6651c843cfSSibi Sankar if (!dev) 6751c843cfSSibi Sankar return -ENODEV; 6851c843cfSSibi Sankar 6951c843cfSSibi Sankar opp = dev_pm_opp_find_freq_exact(dev, freq_hz, true); 7051c843cfSSibi Sankar if (IS_ERR(opp)) 7151c843cfSSibi Sankar return PTR_ERR(opp); 7251c843cfSSibi Sankar 738d25157fSViresh Kumar ret = dev_pm_opp_set_opp(dev, opp); 7451c843cfSSibi Sankar dev_pm_opp_put(opp); 7551c843cfSSibi Sankar return ret; 7651c843cfSSibi Sankar } 7751c843cfSSibi Sankar 7851c843cfSSibi Sankar static int qcom_cpufreq_update_opp(struct device *cpu_dev, 7951c843cfSSibi Sankar unsigned long freq_khz, 8051c843cfSSibi Sankar unsigned long volt) 8151c843cfSSibi Sankar { 8251c843cfSSibi Sankar unsigned long freq_hz = freq_khz * 1000; 8351c843cfSSibi Sankar int ret; 8451c843cfSSibi Sankar 8551c843cfSSibi Sankar /* Skip voltage update if the opp table is not available */ 8651c843cfSSibi Sankar if (!icc_scaling_enabled) 8751c843cfSSibi Sankar return dev_pm_opp_add(cpu_dev, freq_hz, volt); 8851c843cfSSibi Sankar 8951c843cfSSibi Sankar ret = dev_pm_opp_adjust_voltage(cpu_dev, freq_hz, volt, volt, volt); 9051c843cfSSibi Sankar if (ret) { 9151c843cfSSibi Sankar dev_err(cpu_dev, "Voltage update failed freq=%ld\n", freq_khz); 9251c843cfSSibi Sankar return ret; 9351c843cfSSibi Sankar } 9451c843cfSSibi Sankar 9551c843cfSSibi Sankar return dev_pm_opp_enable(cpu_dev, freq_hz); 9651c843cfSSibi Sankar } 972849dd8bSTaniya Das 982849dd8bSTaniya Das static int qcom_cpufreq_hw_target_index(struct cpufreq_policy *policy, 992849dd8bSTaniya Das unsigned int index) 1002849dd8bSTaniya Das { 101dcd1fd72SManivannan Sadhasivam struct qcom_cpufreq_data *data = policy->driver_data; 102dcd1fd72SManivannan Sadhasivam const struct qcom_cpufreq_soc_data *soc_data = data->soc_data; 103ada54f35SDouglas RAILLARD unsigned long freq = policy->freq_table[index].frequency; 1042849dd8bSTaniya Das 105dcd1fd72SManivannan Sadhasivam writel_relaxed(index, data->base + soc_data->reg_perf_state); 1062849dd8bSTaniya Das 10751c843cfSSibi Sankar if (icc_scaling_enabled) 10851c843cfSSibi Sankar qcom_cpufreq_set_bw(policy, freq); 10951c843cfSSibi Sankar 1102849dd8bSTaniya Das return 0; 1112849dd8bSTaniya Das } 1122849dd8bSTaniya Das 1132849dd8bSTaniya Das static unsigned int qcom_cpufreq_hw_get(unsigned int cpu) 1142849dd8bSTaniya Das { 115dcd1fd72SManivannan Sadhasivam struct qcom_cpufreq_data *data; 116dcd1fd72SManivannan Sadhasivam const struct qcom_cpufreq_soc_data *soc_data; 1172849dd8bSTaniya Das struct cpufreq_policy *policy; 1182849dd8bSTaniya Das unsigned int index; 1192849dd8bSTaniya Das 1202849dd8bSTaniya Das policy = cpufreq_cpu_get_raw(cpu); 1212849dd8bSTaniya Das if (!policy) 1222849dd8bSTaniya Das return 0; 1232849dd8bSTaniya Das 124dcd1fd72SManivannan Sadhasivam data = policy->driver_data; 125dcd1fd72SManivannan Sadhasivam soc_data = data->soc_data; 1262849dd8bSTaniya Das 127dcd1fd72SManivannan Sadhasivam index = readl_relaxed(data->base + soc_data->reg_perf_state); 1282849dd8bSTaniya Das index = min(index, LUT_MAX_ENTRIES - 1); 1292849dd8bSTaniya Das 1302849dd8bSTaniya Das return policy->freq_table[index].frequency; 1312849dd8bSTaniya Das } 1322849dd8bSTaniya Das 1332849dd8bSTaniya Das static unsigned int qcom_cpufreq_hw_fast_switch(struct cpufreq_policy *policy, 1342849dd8bSTaniya Das unsigned int target_freq) 1352849dd8bSTaniya Das { 136dcd1fd72SManivannan Sadhasivam struct qcom_cpufreq_data *data = policy->driver_data; 137dcd1fd72SManivannan Sadhasivam const struct qcom_cpufreq_soc_data *soc_data = data->soc_data; 138292072c3SViresh Kumar unsigned int index; 1392849dd8bSTaniya Das 1402849dd8bSTaniya Das index = policy->cached_resolved_idx; 141dcd1fd72SManivannan Sadhasivam writel_relaxed(index, data->base + soc_data->reg_perf_state); 1422849dd8bSTaniya Das 1431a0419b0SIonela Voinescu return policy->freq_table[index].frequency; 1442849dd8bSTaniya Das } 1452849dd8bSTaniya Das 14655538fbcSTaniya Das static int qcom_cpufreq_hw_read_lut(struct device *cpu_dev, 147dcd1fd72SManivannan Sadhasivam struct cpufreq_policy *policy) 1482849dd8bSTaniya Das { 1490eae1e37SSibi Sankar u32 data, src, lval, i, core_count, prev_freq = 0, freq; 15055538fbcSTaniya Das u32 volt; 1512849dd8bSTaniya Das struct cpufreq_frequency_table *table; 15251c843cfSSibi Sankar struct dev_pm_opp *opp; 15351c843cfSSibi Sankar unsigned long rate; 15451c843cfSSibi Sankar int ret; 155dcd1fd72SManivannan Sadhasivam struct qcom_cpufreq_data *drv_data = policy->driver_data; 156dcd1fd72SManivannan Sadhasivam const struct qcom_cpufreq_soc_data *soc_data = drv_data->soc_data; 1572849dd8bSTaniya Das 1582849dd8bSTaniya Das table = kcalloc(LUT_MAX_ENTRIES + 1, sizeof(*table), GFP_KERNEL); 1592849dd8bSTaniya Das if (!table) 1602849dd8bSTaniya Das return -ENOMEM; 1612849dd8bSTaniya Das 16251c843cfSSibi Sankar ret = dev_pm_opp_of_add_table(cpu_dev); 16351c843cfSSibi Sankar if (!ret) { 16451c843cfSSibi Sankar /* Disable all opps and cross-validate against LUT later */ 16551c843cfSSibi Sankar icc_scaling_enabled = true; 16651c843cfSSibi Sankar for (rate = 0; ; rate++) { 16751c843cfSSibi Sankar opp = dev_pm_opp_find_freq_ceil(cpu_dev, &rate); 16851c843cfSSibi Sankar if (IS_ERR(opp)) 16951c843cfSSibi Sankar break; 17051c843cfSSibi Sankar 17151c843cfSSibi Sankar dev_pm_opp_put(opp); 17251c843cfSSibi Sankar dev_pm_opp_disable(cpu_dev, rate); 17351c843cfSSibi Sankar } 17451c843cfSSibi Sankar } else if (ret != -ENODEV) { 17551c843cfSSibi Sankar dev_err(cpu_dev, "Invalid opp table in device tree\n"); 17651c843cfSSibi Sankar return ret; 17751c843cfSSibi Sankar } else { 178afdb219bSSibi Sankar policy->fast_switch_possible = true; 17951c843cfSSibi Sankar icc_scaling_enabled = false; 18051c843cfSSibi Sankar } 18151c843cfSSibi Sankar 1822849dd8bSTaniya Das for (i = 0; i < LUT_MAX_ENTRIES; i++) { 183dcd1fd72SManivannan Sadhasivam data = readl_relaxed(drv_data->base + soc_data->reg_freq_lut + 184dcd1fd72SManivannan Sadhasivam i * soc_data->lut_row_size); 1852849dd8bSTaniya Das src = FIELD_GET(LUT_SRC, data); 1862849dd8bSTaniya Das lval = FIELD_GET(LUT_L_VAL, data); 1872849dd8bSTaniya Das core_count = FIELD_GET(LUT_CORE_COUNT, data); 1882849dd8bSTaniya Das 189dcd1fd72SManivannan Sadhasivam data = readl_relaxed(drv_data->base + soc_data->reg_volt_lut + 190dcd1fd72SManivannan Sadhasivam i * soc_data->lut_row_size); 19155538fbcSTaniya Das volt = FIELD_GET(LUT_VOLT, data) * 1000; 19255538fbcSTaniya Das 1932849dd8bSTaniya Das if (src) 1942849dd8bSTaniya Das freq = xo_rate * lval / 1000; 1952849dd8bSTaniya Das else 1962849dd8bSTaniya Das freq = cpu_hw_rate / 1000; 1972849dd8bSTaniya Das 1980eae1e37SSibi Sankar if (freq != prev_freq && core_count != LUT_TURBO_IND) { 199bc9b9c5aSMatthias Kaehlcke if (!qcom_cpufreq_update_opp(cpu_dev, freq, volt)) { 2002849dd8bSTaniya Das table[i].frequency = freq; 20155538fbcSTaniya Das dev_dbg(cpu_dev, "index=%d freq=%d, core_count %d\n", i, 2022849dd8bSTaniya Das freq, core_count); 203bc9b9c5aSMatthias Kaehlcke } else { 204bc9b9c5aSMatthias Kaehlcke dev_warn(cpu_dev, "failed to update OPP for freq=%d\n", freq); 205bc9b9c5aSMatthias Kaehlcke table[i].frequency = CPUFREQ_ENTRY_INVALID; 206bc9b9c5aSMatthias Kaehlcke } 207bc9b9c5aSMatthias Kaehlcke 2080eae1e37SSibi Sankar } else if (core_count == LUT_TURBO_IND) { 20955538fbcSTaniya Das table[i].frequency = CPUFREQ_ENTRY_INVALID; 2102849dd8bSTaniya Das } 2112849dd8bSTaniya Das 2122849dd8bSTaniya Das /* 2132849dd8bSTaniya Das * Two of the same frequencies with the same core counts means 2142849dd8bSTaniya Das * end of table 2152849dd8bSTaniya Das */ 2160eae1e37SSibi Sankar if (i > 0 && prev_freq == freq) { 2172849dd8bSTaniya Das struct cpufreq_frequency_table *prev = &table[i - 1]; 2182849dd8bSTaniya Das 2192849dd8bSTaniya Das /* 2202849dd8bSTaniya Das * Only treat the last frequency that might be a boost 2212849dd8bSTaniya Das * as the boost frequency 2222849dd8bSTaniya Das */ 2230eae1e37SSibi Sankar if (prev->frequency == CPUFREQ_ENTRY_INVALID) { 224bc9b9c5aSMatthias Kaehlcke if (!qcom_cpufreq_update_opp(cpu_dev, prev_freq, volt)) { 2252849dd8bSTaniya Das prev->frequency = prev_freq; 2262849dd8bSTaniya Das prev->flags = CPUFREQ_BOOST_FREQ; 227bc9b9c5aSMatthias Kaehlcke } else { 228bc9b9c5aSMatthias Kaehlcke dev_warn(cpu_dev, "failed to update OPP for freq=%d\n", 229bc9b9c5aSMatthias Kaehlcke freq); 230bc9b9c5aSMatthias Kaehlcke } 2312849dd8bSTaniya Das } 2322849dd8bSTaniya Das 2332849dd8bSTaniya Das break; 2342849dd8bSTaniya Das } 2352849dd8bSTaniya Das 2362849dd8bSTaniya Das prev_freq = freq; 2372849dd8bSTaniya Das } 2382849dd8bSTaniya Das 2392849dd8bSTaniya Das table[i].frequency = CPUFREQ_TABLE_END; 2402849dd8bSTaniya Das policy->freq_table = table; 24155538fbcSTaniya Das dev_pm_opp_set_sharing_cpus(cpu_dev, policy->cpus); 2422849dd8bSTaniya Das 2432849dd8bSTaniya Das return 0; 2442849dd8bSTaniya Das } 2452849dd8bSTaniya Das 2462849dd8bSTaniya Das static void qcom_get_related_cpus(int index, struct cpumask *m) 2472849dd8bSTaniya Das { 2482849dd8bSTaniya Das struct device_node *cpu_np; 2492849dd8bSTaniya Das struct of_phandle_args args; 2502849dd8bSTaniya Das int cpu, ret; 2512849dd8bSTaniya Das 2522849dd8bSTaniya Das for_each_possible_cpu(cpu) { 2532849dd8bSTaniya Das cpu_np = of_cpu_device_node_get(cpu); 2542849dd8bSTaniya Das if (!cpu_np) 2552849dd8bSTaniya Das continue; 2562849dd8bSTaniya Das 2572849dd8bSTaniya Das ret = of_parse_phandle_with_args(cpu_np, "qcom,freq-domain", 2582849dd8bSTaniya Das "#freq-domain-cells", 0, 2592849dd8bSTaniya Das &args); 2602849dd8bSTaniya Das of_node_put(cpu_np); 2612849dd8bSTaniya Das if (ret < 0) 2622849dd8bSTaniya Das continue; 2632849dd8bSTaniya Das 2642849dd8bSTaniya Das if (index == args.args[0]) 2652849dd8bSTaniya Das cpumask_set_cpu(cpu, m); 2662849dd8bSTaniya Das } 2672849dd8bSTaniya Das } 2682849dd8bSTaniya Das 269275157b3SThara Gopinath static unsigned int qcom_lmh_get_throttle_freq(struct qcom_cpufreq_data *data) 270275157b3SThara Gopinath { 271275157b3SThara Gopinath unsigned int val = readl_relaxed(data->base + data->soc_data->reg_current_vote); 272275157b3SThara Gopinath 273275157b3SThara Gopinath return (val & 0x3FF) * 19200; 274275157b3SThara Gopinath } 275275157b3SThara Gopinath 276275157b3SThara Gopinath static void qcom_lmh_dcvs_notify(struct qcom_cpufreq_data *data) 277275157b3SThara Gopinath { 278275157b3SThara Gopinath unsigned long max_capacity, capacity, freq_hz, throttled_freq; 279275157b3SThara Gopinath struct cpufreq_policy *policy = data->policy; 280275157b3SThara Gopinath int cpu = cpumask_first(policy->cpus); 281275157b3SThara Gopinath struct device *dev = get_cpu_device(cpu); 282275157b3SThara Gopinath struct dev_pm_opp *opp; 283275157b3SThara Gopinath unsigned int freq; 284275157b3SThara Gopinath 285275157b3SThara Gopinath /* 286275157b3SThara Gopinath * Get the h/w throttled frequency, normalize it using the 287275157b3SThara Gopinath * registered opp table and use it to calculate thermal pressure. 288275157b3SThara Gopinath */ 289275157b3SThara Gopinath freq = qcom_lmh_get_throttle_freq(data); 290275157b3SThara Gopinath freq_hz = freq * HZ_PER_KHZ; 291275157b3SThara Gopinath 292275157b3SThara Gopinath opp = dev_pm_opp_find_freq_floor(dev, &freq_hz); 293275157b3SThara Gopinath if (IS_ERR(opp) && PTR_ERR(opp) == -ERANGE) 294275157b3SThara Gopinath dev_pm_opp_find_freq_ceil(dev, &freq_hz); 295275157b3SThara Gopinath 296275157b3SThara Gopinath throttled_freq = freq_hz / HZ_PER_KHZ; 297275157b3SThara Gopinath 298275157b3SThara Gopinath /* Update thermal pressure */ 299275157b3SThara Gopinath 300275157b3SThara Gopinath max_capacity = arch_scale_cpu_capacity(cpu); 301275157b3SThara Gopinath capacity = mult_frac(max_capacity, throttled_freq, policy->cpuinfo.max_freq); 302275157b3SThara Gopinath 303275157b3SThara Gopinath /* Don't pass boost capacity to scheduler */ 304275157b3SThara Gopinath if (capacity > max_capacity) 305275157b3SThara Gopinath capacity = max_capacity; 306275157b3SThara Gopinath 307275157b3SThara Gopinath arch_set_thermal_pressure(policy->cpus, max_capacity - capacity); 308275157b3SThara Gopinath 309275157b3SThara Gopinath /* 310275157b3SThara Gopinath * In the unlikely case policy is unregistered do not enable 311275157b3SThara Gopinath * polling or h/w interrupt 312275157b3SThara Gopinath */ 313275157b3SThara Gopinath mutex_lock(&data->throttle_lock); 314275157b3SThara Gopinath if (data->cancel_throttle) 315275157b3SThara Gopinath goto out; 316275157b3SThara Gopinath 317275157b3SThara Gopinath /* 318275157b3SThara Gopinath * If h/w throttled frequency is higher than what cpufreq has requested 319275157b3SThara Gopinath * for, then stop polling and switch back to interrupt mechanism. 320275157b3SThara Gopinath */ 321275157b3SThara Gopinath if (throttled_freq >= qcom_cpufreq_hw_get(cpu)) 322275157b3SThara Gopinath enable_irq(data->throttle_irq); 323275157b3SThara Gopinath else 324275157b3SThara Gopinath mod_delayed_work(system_highpri_wq, &data->throttle_work, 325275157b3SThara Gopinath msecs_to_jiffies(10)); 326275157b3SThara Gopinath 327275157b3SThara Gopinath out: 328275157b3SThara Gopinath mutex_unlock(&data->throttle_lock); 329275157b3SThara Gopinath } 330275157b3SThara Gopinath 331275157b3SThara Gopinath static void qcom_lmh_dcvs_poll(struct work_struct *work) 332275157b3SThara Gopinath { 333275157b3SThara Gopinath struct qcom_cpufreq_data *data; 334275157b3SThara Gopinath 335275157b3SThara Gopinath data = container_of(work, struct qcom_cpufreq_data, throttle_work.work); 336275157b3SThara Gopinath qcom_lmh_dcvs_notify(data); 337275157b3SThara Gopinath } 338275157b3SThara Gopinath 339275157b3SThara Gopinath static irqreturn_t qcom_lmh_dcvs_handle_irq(int irq, void *data) 340275157b3SThara Gopinath { 341275157b3SThara Gopinath struct qcom_cpufreq_data *c_data = data; 342275157b3SThara Gopinath 343275157b3SThara Gopinath /* Disable interrupt and enable polling */ 344275157b3SThara Gopinath disable_irq_nosync(c_data->throttle_irq); 345275157b3SThara Gopinath qcom_lmh_dcvs_notify(c_data); 346275157b3SThara Gopinath 347275157b3SThara Gopinath return 0; 348275157b3SThara Gopinath } 349275157b3SThara Gopinath 350dcd1fd72SManivannan Sadhasivam static const struct qcom_cpufreq_soc_data qcom_soc_data = { 351dcd1fd72SManivannan Sadhasivam .reg_enable = 0x0, 352dcd1fd72SManivannan Sadhasivam .reg_freq_lut = 0x110, 353dcd1fd72SManivannan Sadhasivam .reg_volt_lut = 0x114, 354275157b3SThara Gopinath .reg_current_vote = 0x704, 355dcd1fd72SManivannan Sadhasivam .reg_perf_state = 0x920, 356dcd1fd72SManivannan Sadhasivam .lut_row_size = 32, 357dcd1fd72SManivannan Sadhasivam }; 358dcd1fd72SManivannan Sadhasivam 35949b59f4cSManivannan Sadhasivam static const struct qcom_cpufreq_soc_data epss_soc_data = { 36049b59f4cSManivannan Sadhasivam .reg_enable = 0x0, 36149b59f4cSManivannan Sadhasivam .reg_freq_lut = 0x100, 36249b59f4cSManivannan Sadhasivam .reg_volt_lut = 0x200, 36349b59f4cSManivannan Sadhasivam .reg_perf_state = 0x320, 36449b59f4cSManivannan Sadhasivam .lut_row_size = 4, 36549b59f4cSManivannan Sadhasivam }; 36649b59f4cSManivannan Sadhasivam 367dcd1fd72SManivannan Sadhasivam static const struct of_device_id qcom_cpufreq_hw_match[] = { 368dcd1fd72SManivannan Sadhasivam { .compatible = "qcom,cpufreq-hw", .data = &qcom_soc_data }, 36949b59f4cSManivannan Sadhasivam { .compatible = "qcom,cpufreq-epss", .data = &epss_soc_data }, 370dcd1fd72SManivannan Sadhasivam {} 371dcd1fd72SManivannan Sadhasivam }; 372dcd1fd72SManivannan Sadhasivam MODULE_DEVICE_TABLE(of, qcom_cpufreq_hw_match); 373dcd1fd72SManivannan Sadhasivam 374275157b3SThara Gopinath static int qcom_cpufreq_hw_lmh_init(struct cpufreq_policy *policy, int index) 375275157b3SThara Gopinath { 376275157b3SThara Gopinath struct qcom_cpufreq_data *data = policy->driver_data; 377275157b3SThara Gopinath struct platform_device *pdev = cpufreq_get_driver_data(); 378275157b3SThara Gopinath char irq_name[15]; 379275157b3SThara Gopinath int ret; 380275157b3SThara Gopinath 381275157b3SThara Gopinath /* 382275157b3SThara Gopinath * Look for LMh interrupt. If no interrupt line is specified / 383275157b3SThara Gopinath * if there is an error, allow cpufreq to be enabled as usual. 384275157b3SThara Gopinath */ 385275157b3SThara Gopinath data->throttle_irq = platform_get_irq(pdev, index); 386275157b3SThara Gopinath if (data->throttle_irq <= 0) 387275157b3SThara Gopinath return data->throttle_irq == -EPROBE_DEFER ? -EPROBE_DEFER : 0; 388275157b3SThara Gopinath 389275157b3SThara Gopinath data->cancel_throttle = false; 390275157b3SThara Gopinath data->policy = policy; 391275157b3SThara Gopinath 392275157b3SThara Gopinath mutex_init(&data->throttle_lock); 393275157b3SThara Gopinath INIT_DEFERRABLE_WORK(&data->throttle_work, qcom_lmh_dcvs_poll); 394275157b3SThara Gopinath 395275157b3SThara Gopinath snprintf(irq_name, sizeof(irq_name), "dcvsh-irq-%u", policy->cpu); 396275157b3SThara Gopinath ret = request_threaded_irq(data->throttle_irq, NULL, qcom_lmh_dcvs_handle_irq, 397275157b3SThara Gopinath IRQF_ONESHOT, irq_name, data); 398275157b3SThara Gopinath if (ret) { 399275157b3SThara Gopinath dev_err(&pdev->dev, "Error registering %s: %d\n", irq_name, ret); 400275157b3SThara Gopinath return 0; 401275157b3SThara Gopinath } 402275157b3SThara Gopinath 403275157b3SThara Gopinath return 0; 404275157b3SThara Gopinath } 405275157b3SThara Gopinath 406275157b3SThara Gopinath static void qcom_cpufreq_hw_lmh_exit(struct qcom_cpufreq_data *data) 407275157b3SThara Gopinath { 408275157b3SThara Gopinath if (data->throttle_irq <= 0) 409275157b3SThara Gopinath return; 410275157b3SThara Gopinath 411275157b3SThara Gopinath mutex_lock(&data->throttle_lock); 412275157b3SThara Gopinath data->cancel_throttle = true; 413275157b3SThara Gopinath mutex_unlock(&data->throttle_lock); 414275157b3SThara Gopinath 415275157b3SThara Gopinath cancel_delayed_work_sync(&data->throttle_work); 416275157b3SThara Gopinath free_irq(data->throttle_irq, data); 417275157b3SThara Gopinath } 418275157b3SThara Gopinath 4192849dd8bSTaniya Das static int qcom_cpufreq_hw_cpu_init(struct cpufreq_policy *policy) 4202849dd8bSTaniya Das { 421bd74e286SManivannan Sadhasivam struct platform_device *pdev = cpufreq_get_driver_data(); 422bd74e286SManivannan Sadhasivam struct device *dev = &pdev->dev; 4232849dd8bSTaniya Das struct of_phandle_args args; 4242849dd8bSTaniya Das struct device_node *cpu_np; 42555538fbcSTaniya Das struct device *cpu_dev; 42667fc209bSShawn Guo struct resource *res; 4272849dd8bSTaniya Das void __iomem *base; 428dcd1fd72SManivannan Sadhasivam struct qcom_cpufreq_data *data; 4292849dd8bSTaniya Das int ret, index; 4302849dd8bSTaniya Das 43155538fbcSTaniya Das cpu_dev = get_cpu_device(policy->cpu); 43255538fbcSTaniya Das if (!cpu_dev) { 43355538fbcSTaniya Das pr_err("%s: failed to get cpu%d device\n", __func__, 43455538fbcSTaniya Das policy->cpu); 43555538fbcSTaniya Das return -ENODEV; 43655538fbcSTaniya Das } 43755538fbcSTaniya Das 4382849dd8bSTaniya Das cpu_np = of_cpu_device_node_get(policy->cpu); 4392849dd8bSTaniya Das if (!cpu_np) 4402849dd8bSTaniya Das return -EINVAL; 4412849dd8bSTaniya Das 4422849dd8bSTaniya Das ret = of_parse_phandle_with_args(cpu_np, "qcom,freq-domain", 4432849dd8bSTaniya Das "#freq-domain-cells", 0, &args); 4442849dd8bSTaniya Das of_node_put(cpu_np); 4452849dd8bSTaniya Das if (ret) 4462849dd8bSTaniya Das return ret; 4472849dd8bSTaniya Das 4482849dd8bSTaniya Das index = args.args[0]; 4492849dd8bSTaniya Das 45067fc209bSShawn Guo res = platform_get_resource(pdev, IORESOURCE_MEM, index); 45167fc209bSShawn Guo if (!res) { 45267fc209bSShawn Guo dev_err(dev, "failed to get mem resource %d\n", index); 45367fc209bSShawn Guo return -ENODEV; 45467fc209bSShawn Guo } 4552849dd8bSTaniya Das 45667fc209bSShawn Guo if (!request_mem_region(res->start, resource_size(res), res->name)) { 45767fc209bSShawn Guo dev_err(dev, "failed to request resource %pR\n", res); 45867fc209bSShawn Guo return -EBUSY; 45967fc209bSShawn Guo } 46067fc209bSShawn Guo 46167fc209bSShawn Guo base = ioremap(res->start, resource_size(res)); 462536eb97aSWei Yongjun if (!base) { 46367fc209bSShawn Guo dev_err(dev, "failed to map resource %pR\n", res); 464536eb97aSWei Yongjun ret = -ENOMEM; 46567fc209bSShawn Guo goto release_region; 46667fc209bSShawn Guo } 46767fc209bSShawn Guo 46867fc209bSShawn Guo data = kzalloc(sizeof(*data), GFP_KERNEL); 469dcd1fd72SManivannan Sadhasivam if (!data) { 470dcd1fd72SManivannan Sadhasivam ret = -ENOMEM; 47167fc209bSShawn Guo goto unmap_base; 472dcd1fd72SManivannan Sadhasivam } 473dcd1fd72SManivannan Sadhasivam 474dcd1fd72SManivannan Sadhasivam data->soc_data = of_device_get_match_data(&pdev->dev); 475dcd1fd72SManivannan Sadhasivam data->base = base; 47667fc209bSShawn Guo data->res = res; 4772849dd8bSTaniya Das 4782849dd8bSTaniya Das /* HW should be in enabled state to proceed */ 479dcd1fd72SManivannan Sadhasivam if (!(readl_relaxed(base + data->soc_data->reg_enable) & 0x1)) { 4802849dd8bSTaniya Das dev_err(dev, "Domain-%d cpufreq hardware not enabled\n", index); 4812849dd8bSTaniya Das ret = -ENODEV; 4822849dd8bSTaniya Das goto error; 4832849dd8bSTaniya Das } 4842849dd8bSTaniya Das 4852849dd8bSTaniya Das qcom_get_related_cpus(index, policy->cpus); 4862849dd8bSTaniya Das if (!cpumask_weight(policy->cpus)) { 4872849dd8bSTaniya Das dev_err(dev, "Domain-%d failed to get related CPUs\n", index); 4882849dd8bSTaniya Das ret = -ENOENT; 4892849dd8bSTaniya Das goto error; 4902849dd8bSTaniya Das } 4912849dd8bSTaniya Das 492dcd1fd72SManivannan Sadhasivam policy->driver_data = data; 493*f0712aceSTaniya Das policy->dvfs_possible_from_any_cpu = true; 4942849dd8bSTaniya Das 495dcd1fd72SManivannan Sadhasivam ret = qcom_cpufreq_hw_read_lut(cpu_dev, policy); 4962849dd8bSTaniya Das if (ret) { 4972849dd8bSTaniya Das dev_err(dev, "Domain-%d failed to read LUT\n", index); 4982849dd8bSTaniya Das goto error; 4992849dd8bSTaniya Das } 5002849dd8bSTaniya Das 50155538fbcSTaniya Das ret = dev_pm_opp_get_opp_count(cpu_dev); 50255538fbcSTaniya Das if (ret <= 0) { 50355538fbcSTaniya Das dev_err(cpu_dev, "Failed to add OPPs\n"); 50455538fbcSTaniya Das ret = -ENODEV; 50555538fbcSTaniya Das goto error; 50655538fbcSTaniya Das } 50755538fbcSTaniya Das 50826699172SShawn Guo if (policy_has_boost_freq(policy)) { 50926699172SShawn Guo ret = cpufreq_enable_boost_support(); 51026699172SShawn Guo if (ret) 51126699172SShawn Guo dev_warn(cpu_dev, "failed to enable boost: %d\n", ret); 51226699172SShawn Guo } 51326699172SShawn Guo 514275157b3SThara Gopinath ret = qcom_cpufreq_hw_lmh_init(policy, index); 515275157b3SThara Gopinath if (ret) 516275157b3SThara Gopinath goto error; 517275157b3SThara Gopinath 5182849dd8bSTaniya Das return 0; 5192849dd8bSTaniya Das error: 52067fc209bSShawn Guo kfree(data); 52167fc209bSShawn Guo unmap_base: 52202fc4095SShawn Guo iounmap(base); 52367fc209bSShawn Guo release_region: 52467fc209bSShawn Guo release_mem_region(res->start, resource_size(res)); 5252849dd8bSTaniya Das return ret; 5262849dd8bSTaniya Das } 5272849dd8bSTaniya Das 5282849dd8bSTaniya Das static int qcom_cpufreq_hw_cpu_exit(struct cpufreq_policy *policy) 5292849dd8bSTaniya Das { 53055538fbcSTaniya Das struct device *cpu_dev = get_cpu_device(policy->cpu); 531dcd1fd72SManivannan Sadhasivam struct qcom_cpufreq_data *data = policy->driver_data; 53267fc209bSShawn Guo struct resource *res = data->res; 53367fc209bSShawn Guo void __iomem *base = data->base; 5342849dd8bSTaniya Das 53555538fbcSTaniya Das dev_pm_opp_remove_all_dynamic(cpu_dev); 53651c843cfSSibi Sankar dev_pm_opp_of_cpumask_remove_table(policy->related_cpus); 537275157b3SThara Gopinath qcom_cpufreq_hw_lmh_exit(data); 5382849dd8bSTaniya Das kfree(policy->freq_table); 53967fc209bSShawn Guo kfree(data); 54067fc209bSShawn Guo iounmap(base); 54167fc209bSShawn Guo release_mem_region(res->start, resource_size(res)); 5422849dd8bSTaniya Das 5432849dd8bSTaniya Das return 0; 5442849dd8bSTaniya Das } 5452849dd8bSTaniya Das 5462849dd8bSTaniya Das static struct freq_attr *qcom_cpufreq_hw_attr[] = { 5472849dd8bSTaniya Das &cpufreq_freq_attr_scaling_available_freqs, 5482849dd8bSTaniya Das &cpufreq_freq_attr_scaling_boost_freqs, 5492849dd8bSTaniya Das NULL 5502849dd8bSTaniya Das }; 5512849dd8bSTaniya Das 5522849dd8bSTaniya Das static struct cpufreq_driver cpufreq_qcom_hw_driver = { 5535ae4a4b4SViresh Kumar .flags = CPUFREQ_NEED_INITIAL_FREQ_CHECK | 5544c5ff1c8SAmit Kucheria CPUFREQ_HAVE_GOVERNOR_PER_POLICY | 5554c5ff1c8SAmit Kucheria CPUFREQ_IS_COOLING_DEV, 5562849dd8bSTaniya Das .verify = cpufreq_generic_frequency_table_verify, 5572849dd8bSTaniya Das .target_index = qcom_cpufreq_hw_target_index, 5582849dd8bSTaniya Das .get = qcom_cpufreq_hw_get, 5592849dd8bSTaniya Das .init = qcom_cpufreq_hw_cpu_init, 5602849dd8bSTaniya Das .exit = qcom_cpufreq_hw_cpu_exit, 561e96c2153SViresh Kumar .register_em = cpufreq_register_em_with_opp, 5622849dd8bSTaniya Das .fast_switch = qcom_cpufreq_hw_fast_switch, 5632849dd8bSTaniya Das .name = "qcom-cpufreq-hw", 5642849dd8bSTaniya Das .attr = qcom_cpufreq_hw_attr, 5652849dd8bSTaniya Das }; 5662849dd8bSTaniya Das 5672849dd8bSTaniya Das static int qcom_cpufreq_hw_driver_probe(struct platform_device *pdev) 5682849dd8bSTaniya Das { 56951c843cfSSibi Sankar struct device *cpu_dev; 5702849dd8bSTaniya Das struct clk *clk; 5712849dd8bSTaniya Das int ret; 5722849dd8bSTaniya Das 5732849dd8bSTaniya Das clk = clk_get(&pdev->dev, "xo"); 5742849dd8bSTaniya Das if (IS_ERR(clk)) 5752849dd8bSTaniya Das return PTR_ERR(clk); 5762849dd8bSTaniya Das 5772849dd8bSTaniya Das xo_rate = clk_get_rate(clk); 5782849dd8bSTaniya Das clk_put(clk); 5792849dd8bSTaniya Das 5802849dd8bSTaniya Das clk = clk_get(&pdev->dev, "alternate"); 5812849dd8bSTaniya Das if (IS_ERR(clk)) 5822849dd8bSTaniya Das return PTR_ERR(clk); 5832849dd8bSTaniya Das 5842849dd8bSTaniya Das cpu_hw_rate = clk_get_rate(clk) / CLK_HW_DIV; 5852849dd8bSTaniya Das clk_put(clk); 5862849dd8bSTaniya Das 587bd74e286SManivannan Sadhasivam cpufreq_qcom_hw_driver.driver_data = pdev; 5882849dd8bSTaniya Das 58951c843cfSSibi Sankar /* Check for optional interconnect paths on CPU0 */ 59051c843cfSSibi Sankar cpu_dev = get_cpu_device(0); 59151c843cfSSibi Sankar if (!cpu_dev) 59251c843cfSSibi Sankar return -EPROBE_DEFER; 59351c843cfSSibi Sankar 59451c843cfSSibi Sankar ret = dev_pm_opp_of_find_icc_paths(cpu_dev, NULL); 59551c843cfSSibi Sankar if (ret) 59651c843cfSSibi Sankar return ret; 59751c843cfSSibi Sankar 5982849dd8bSTaniya Das ret = cpufreq_register_driver(&cpufreq_qcom_hw_driver); 5992849dd8bSTaniya Das if (ret) 6002849dd8bSTaniya Das dev_err(&pdev->dev, "CPUFreq HW driver failed to register\n"); 6012849dd8bSTaniya Das else 6022849dd8bSTaniya Das dev_dbg(&pdev->dev, "QCOM CPUFreq HW driver initialized\n"); 6032849dd8bSTaniya Das 6042849dd8bSTaniya Das return ret; 6052849dd8bSTaniya Das } 6062849dd8bSTaniya Das 6072849dd8bSTaniya Das static int qcom_cpufreq_hw_driver_remove(struct platform_device *pdev) 6082849dd8bSTaniya Das { 6092849dd8bSTaniya Das return cpufreq_unregister_driver(&cpufreq_qcom_hw_driver); 6102849dd8bSTaniya Das } 6112849dd8bSTaniya Das 6122849dd8bSTaniya Das static struct platform_driver qcom_cpufreq_hw_driver = { 6132849dd8bSTaniya Das .probe = qcom_cpufreq_hw_driver_probe, 6142849dd8bSTaniya Das .remove = qcom_cpufreq_hw_driver_remove, 6152849dd8bSTaniya Das .driver = { 6162849dd8bSTaniya Das .name = "qcom-cpufreq-hw", 6172849dd8bSTaniya Das .of_match_table = qcom_cpufreq_hw_match, 6182849dd8bSTaniya Das }, 6192849dd8bSTaniya Das }; 6202849dd8bSTaniya Das 6212849dd8bSTaniya Das static int __init qcom_cpufreq_hw_init(void) 6222849dd8bSTaniya Das { 6232849dd8bSTaniya Das return platform_driver_register(&qcom_cpufreq_hw_driver); 6242849dd8bSTaniya Das } 62511ff4bddSAmit Kucheria postcore_initcall(qcom_cpufreq_hw_init); 6262849dd8bSTaniya Das 6272849dd8bSTaniya Das static void __exit qcom_cpufreq_hw_exit(void) 6282849dd8bSTaniya Das { 6292849dd8bSTaniya Das platform_driver_unregister(&qcom_cpufreq_hw_driver); 6302849dd8bSTaniya Das } 6312849dd8bSTaniya Das module_exit(qcom_cpufreq_hw_exit); 6322849dd8bSTaniya Das 6332849dd8bSTaniya Das MODULE_DESCRIPTION("QCOM CPUFREQ HW Driver"); 6342849dd8bSTaniya Das MODULE_LICENSE("GPL v2"); 635