/* * Rockchip CPUFreq Driver * * Copyright (C) 2017 Fuzhou Rockchip Electronics Co., Ltd * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * This program is distributed "as is" WITHOUT ANY WARRANTY of any * kind, whether express or implied; without even the implied warranty * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "cpufreq-dt.h" #include "rockchip-cpufreq.h" struct cluster_info { struct list_head list_head; struct monitor_dev_info *mdev_info; struct rockchip_opp_info opp_info; struct freq_qos_request dsu_qos_req; cpumask_t cpus; unsigned int idle_threshold_freq; int scale; bool is_idle_disabled; bool is_opp_shared_dsu; unsigned int regulator_count; unsigned long rate; unsigned long volt, mem_volt; }; static LIST_HEAD(cluster_info_list); static int px30_get_soc_info(struct device *dev, struct device_node *np, int *bin, int *process) { int ret = 0; u8 value = 0; if (!bin) return 0; if (of_property_match_string(np, "nvmem-cell-names", "performance") >= 0) { ret = rockchip_nvmem_cell_read_u8(np, "performance", &value); if (ret) { dev_err(dev, "Failed to get soc performance value\n"); return ret; } *bin = value; } if (*bin >= 0) dev_info(dev, "bin=%d\n", *bin); return ret; } static int rk3288_get_soc_info(struct device *dev, struct device_node *np, int *bin, int *process) { int ret = 0; u8 value = 0; char *name; if (!bin) goto next; if (of_property_match_string(np, "nvmem-cell-names", "special") >= 0) { ret = rockchip_nvmem_cell_read_u8(np, "special", &value); if (ret) { dev_err(dev, "Failed to get soc special value\n"); goto out; } if (value == 0xc) *bin = 0; else *bin = 1; } if (soc_is_rk3288w()) name = "performance-w"; else name = "performance"; if (of_property_match_string(np, "nvmem-cell-names", name) >= 0) { ret = rockchip_nvmem_cell_read_u8(np, name, &value); if (ret) { dev_err(dev, "Failed to get soc performance value\n"); goto out; } if (value & 0x2) *bin = 3; else if (value & 0x01) *bin = 2; } if (*bin >= 0) dev_info(dev, "bin=%d\n", *bin); next: if (!process) goto out; if (of_property_match_string(np, "nvmem-cell-names", "process") >= 0) { ret = rockchip_nvmem_cell_read_u8(np, "process", &value); if (ret) { dev_err(dev, "Failed to get soc process version\n"); goto out; } if (soc_is_rk3288() && (value == 0 || value == 1)) *process = 0; } if (*process >= 0) dev_info(dev, "process=%d\n", *process); out: return ret; } static int rk3399_get_soc_info(struct device *dev, struct device_node *np, int *bin, int *process) { int ret = 0; u8 value = 0; if (!bin) return 0; if (of_property_match_string(np, "nvmem-cell-names", "specification_serial_number") >= 0) { ret = rockchip_nvmem_cell_read_u8(np, "specification_serial_number", &value); if (ret) { dev_err(dev, "Failed to get specification_serial_number\n"); goto out; } if (value == 0xb) { *bin = 0; } else if (value == 0x1) { if (of_property_match_string(np, "nvmem-cell-names", "customer_demand") >= 0) { ret = rockchip_nvmem_cell_read_u8(np, "customer_demand", &value); if (ret) { dev_err(dev, "Failed to get customer_demand\n"); goto out; } if (value == 0x0) *bin = 0; else *bin = 1; } } else if (value == 0x10) { *bin = 1; } } out: if (*bin >= 0) dev_info(dev, "bin=%d\n", *bin); return ret; } static int rk3588_get_soc_info(struct device *dev, struct device_node *np, int *bin, int *process) { int ret = 0; u8 value = 0; if (!bin) return 0; if (of_property_match_string(np, "nvmem-cell-names", "specification_serial_number") >= 0) { ret = rockchip_nvmem_cell_read_u8(np, "specification_serial_number", &value); if (ret) { dev_err(dev, "Failed to get specification_serial_number\n"); return ret; } /* RK3588M */ if (value == 0xd) *bin = 1; /* RK3588J */ else if (value == 0xa) *bin = 2; } if (*bin < 0) *bin = 0; dev_info(dev, "bin=%d\n", *bin); return ret; } static int rk3588_change_length(struct device *dev, struct device_node *np, int bin, int process, int volt_sel) { struct clk *clk; unsigned long old_rate; unsigned int low_len_sel; u32 opp_flag = 0; int ret = 0; clk = clk_get(dev, NULL); if (IS_ERR(clk)) { dev_warn(dev, "failed to get cpu clk\n"); return PTR_ERR(clk); } /* RK3588 low speed grade should change to low length */ if (of_property_read_u32(np, "rockchip,pvtm-low-len-sel", &low_len_sel)) goto out; if (volt_sel > low_len_sel) goto out; opp_flag = OPP_LENGTH_LOW; old_rate = clk_get_rate(clk); ret = clk_set_rate(clk, old_rate | opp_flag); if (ret) { dev_err(dev, "failed to change length\n"); goto out; } clk_set_rate(clk, old_rate); out: clk_put(clk); return ret; } static int rk3588_set_supported_hw(struct device *dev, struct device_node *np, int bin, int process, int volt_sel) { struct opp_table *opp_table; u32 supported_hw[2]; if (!of_property_read_bool(np, "rockchip,supported-hw")) return 0; /* SoC Version */ supported_hw[0] = BIT(bin); /* Speed Grade */ supported_hw[1] = BIT(volt_sel); opp_table = dev_pm_opp_set_supported_hw(dev, supported_hw, 2); if (IS_ERR(opp_table)) { dev_err(dev, "failed to set supported opp\n"); return PTR_ERR(opp_table); } return 0; } static int rk3588_set_soc_info(struct device *dev, struct device_node *np, int bin, int process, int volt_sel) { if (volt_sel < 0) return 0; if (bin < 0) bin = 0; rk3588_change_length(dev, np, bin, process, volt_sel); rk3588_set_supported_hw(dev, np, bin, process, volt_sel); return 0; } static int rk3588_cpu_set_read_margin(struct device *dev, struct rockchip_opp_info *opp_info, u32 rm) { if (!opp_info->volt_rm_tbl) return 0; if (rm == opp_info->current_rm || rm == UINT_MAX) return 0; dev_dbg(dev, "set rm to %d\n", rm); if (opp_info->grf) { regmap_write(opp_info->grf, 0x20, 0x001c0000 | (rm << 2)); regmap_write(opp_info->grf, 0x28, 0x003c0000 | (rm << 2)); regmap_write(opp_info->grf, 0x2c, 0x003c0000 | (rm << 2)); regmap_write(opp_info->grf, 0x30, 0x00200020); udelay(1); regmap_write(opp_info->grf, 0x30, 0x00200000); } if (opp_info->dsu_grf) { regmap_write(opp_info->dsu_grf, 0x20, 0x001c0000 | (rm << 2)); regmap_write(opp_info->dsu_grf, 0x28, 0x003c0000 | (rm << 2)); regmap_write(opp_info->dsu_grf, 0x2c, 0x003c0000 | (rm << 2)); regmap_write(opp_info->dsu_grf, 0x30, 0x001c0000 | (rm << 2)); regmap_write(opp_info->dsu_grf, 0x38, 0x001c0000 | (rm << 2)); regmap_write(opp_info->dsu_grf, 0x18, 0x40004000); udelay(1); regmap_write(opp_info->dsu_grf, 0x18, 0x40000000); } opp_info->current_rm = rm; return 0; } static int rv1126_get_soc_info(struct device *dev, struct device_node *np, int *bin, int *process) { int ret = 0; u8 value = 0; if (of_property_match_string(np, "nvmem-cell-names", "performance") >= 0) { ret = rockchip_nvmem_cell_read_u8(np, "performance", &value); if (ret) { dev_err(dev, "Failed to get soc performance value\n"); return ret; } if (value == 0x1) *bin = 1; else *bin = 0; } if (*bin >= 0) dev_info(dev, "bin=%d\n", *bin); return ret; } static const struct rockchip_opp_data px30_cpu_opp_data = { .get_soc_info = px30_get_soc_info, }; static const struct rockchip_opp_data rk3288_cpu_opp_data = { .get_soc_info = rk3288_get_soc_info, }; static const struct rockchip_opp_data rk3399_cpu_opp_data = { .get_soc_info = rk3399_get_soc_info, }; static const struct rockchip_opp_data rk3588_cpu_opp_data = { .get_soc_info = rk3588_get_soc_info, .set_soc_info = rk3588_set_soc_info, .set_read_margin = rk3588_cpu_set_read_margin, }; static const struct rockchip_opp_data rv1126_cpu_opp_data = { .get_soc_info = rv1126_get_soc_info, }; static const struct of_device_id rockchip_cpufreq_of_match[] = { { .compatible = "rockchip,px30", .data = (void *)&px30_cpu_opp_data, }, { .compatible = "rockchip,rk3288", .data = (void *)&rk3288_cpu_opp_data, }, { .compatible = "rockchip,rk3288w", .data = (void *)&rk3288_cpu_opp_data, }, { .compatible = "rockchip,rk3326", .data = (void *)&px30_cpu_opp_data, }, { .compatible = "rockchip,rk3399", .data = (void *)&rk3399_cpu_opp_data, }, { .compatible = "rockchip,rk3588", .data = (void *)&rk3588_cpu_opp_data, }, { .compatible = "rockchip,rv1109", .data = (void *)&rv1126_cpu_opp_data, }, { .compatible = "rockchip,rv1126", .data = (void *)&rv1126_cpu_opp_data, }, {}, }; static struct cluster_info *rockchip_cluster_info_lookup(int cpu) { struct cluster_info *cluster; list_for_each_entry(cluster, &cluster_info_list, list_head) { if (cpumask_test_cpu(cpu, &cluster->cpus)) return cluster; } return NULL; } static int rockchip_cpufreq_set_volt(struct device *dev, struct regulator *reg, struct dev_pm_opp_supply *supply, char *reg_name) { int ret; dev_dbg(dev, "%s: %s voltages (uV): %lu %lu %lu\n", __func__, reg_name, supply->u_volt_min, supply->u_volt, supply->u_volt_max); ret = regulator_set_voltage_triplet(reg, supply->u_volt_min, supply->u_volt, supply->u_volt_max); if (ret) dev_err(dev, "%s: failed to set voltage (%lu %lu %lu uV): %d\n", __func__, supply->u_volt_min, supply->u_volt, supply->u_volt_max, ret); return ret; } static int cpu_opp_helper(struct dev_pm_set_opp_data *data) { struct dev_pm_opp_supply *old_supply_vdd = &data->old_opp.supplies[0]; struct dev_pm_opp_supply *old_supply_mem = &data->old_opp.supplies[1]; struct dev_pm_opp_supply *new_supply_vdd = &data->new_opp.supplies[0]; struct dev_pm_opp_supply *new_supply_mem = &data->new_opp.supplies[1]; struct regulator *vdd_reg = data->regulators[0]; struct regulator *mem_reg = data->regulators[1]; struct device *dev = data->dev; struct clk *clk = data->clk; struct cluster_info *cluster; struct rockchip_opp_info *opp_info; unsigned long old_freq = data->old_opp.rate; unsigned long new_freq = data->new_opp.rate; u32 target_rm = UINT_MAX; int ret = 0; cluster = rockchip_cluster_info_lookup(dev->id); if (!cluster) return -EINVAL; opp_info = &cluster->opp_info; rockchip_get_read_margin(dev, opp_info, new_supply_vdd->u_volt, &target_rm); /* Change frequency */ dev_dbg(dev, "%s: switching OPP: %lu Hz --> %lu Hz\n", __func__, old_freq, new_freq); /* Scaling up? Scale voltage before frequency */ if (new_freq >= old_freq) { ret = rockchip_set_intermediate_rate(dev, opp_info, clk, old_freq, new_freq, true, true); if (ret) { dev_err(dev, "%s: failed to set clk rate: %lu\n", __func__, new_freq); return -EINVAL; } ret = rockchip_cpufreq_set_volt(dev, mem_reg, new_supply_mem, "mem"); if (ret) goto restore_voltage; ret = rockchip_cpufreq_set_volt(dev, vdd_reg, new_supply_vdd, "vdd"); if (ret) goto restore_voltage; rockchip_set_read_margin(dev, opp_info, target_rm, true); ret = clk_set_rate(clk, new_freq); if (ret) { dev_err(dev, "%s: failed to set clk rate: %lu %d\n", __func__, new_freq, ret); goto restore_rm; } /* Scaling down? Scale voltage after frequency */ } else { ret = rockchip_set_intermediate_rate(dev, opp_info, clk, old_freq, new_freq, false, true); if (ret) { dev_err(dev, "%s: failed to set clk rate: %lu\n", __func__, new_freq); return -EINVAL; } rockchip_set_read_margin(dev, opp_info, target_rm, true); ret = clk_set_rate(clk, new_freq); if (ret) { dev_err(dev, "%s: failed to set clk rate: %lu %d\n", __func__, new_freq, ret); goto restore_rm; } ret = rockchip_cpufreq_set_volt(dev, vdd_reg, new_supply_vdd, "vdd"); if (ret) goto restore_freq; ret = rockchip_cpufreq_set_volt(dev, mem_reg, new_supply_mem, "mem"); if (ret) goto restore_freq; } cluster->volt = new_supply_vdd->u_volt; cluster->mem_volt = new_supply_mem->u_volt; return 0; restore_freq: if (clk_set_rate(clk, old_freq)) dev_err(dev, "%s: failed to restore old-freq (%lu Hz)\n", __func__, old_freq); restore_rm: rockchip_get_read_margin(dev, opp_info, old_supply_vdd->u_volt, &target_rm); rockchip_set_read_margin(dev, opp_info, target_rm, true); restore_voltage: rockchip_cpufreq_set_volt(dev, mem_reg, old_supply_mem, "mem"); rockchip_cpufreq_set_volt(dev, vdd_reg, old_supply_vdd, "vdd"); return ret; } static int rockchip_cpufreq_cluster_init(int cpu, struct cluster_info *cluster) { struct rockchip_opp_info *opp_info = &cluster->opp_info; struct opp_table *pname_table = NULL; struct opp_table *reg_table = NULL; struct opp_table *opp_table; struct device_node *np; struct device *dev; const char * const reg_names[] = {"cpu", "mem"}; char *reg_name = NULL; int bin = -EINVAL; int process = -EINVAL; int volt_sel = -EINVAL; int ret = 0; u32 freq = 0; dev = get_cpu_device(cpu); if (!dev) return -ENODEV; opp_info->dev = dev; if (of_find_property(dev->of_node, "cpu-supply", NULL)) reg_name = "cpu"; else if (of_find_property(dev->of_node, "cpu0-supply", NULL)) reg_name = "cpu0"; else return -ENOENT; np = of_parse_phandle(dev->of_node, "operating-points-v2", 0); if (!np) { dev_warn(dev, "OPP-v2 not supported\n"); return -ENOENT; } opp_info->grf = syscon_regmap_lookup_by_phandle(np, "rockchip,grf"); if (IS_ERR(opp_info->grf)) opp_info->grf = NULL; ret = dev_pm_opp_of_get_sharing_cpus(dev, &cluster->cpus); if (ret) { dev_err(dev, "Failed to get sharing cpus\n"); goto np_err; } cluster->is_opp_shared_dsu = of_property_read_bool(np, "rockchip,opp-shared-dsu"); if (!of_property_read_u32(np, "rockchip,idle-threshold-freq", &freq)) cluster->idle_threshold_freq = freq; rockchip_get_opp_data(rockchip_cpufreq_of_match, opp_info); if (opp_info->data && opp_info->data->set_read_margin) { opp_info->current_rm = UINT_MAX; opp_info->target_rm = UINT_MAX; opp_info->dsu_grf = syscon_regmap_lookup_by_phandle(np, "rockchip,dsu-grf"); if (IS_ERR(opp_info->dsu_grf)) opp_info->dsu_grf = NULL; rockchip_get_volt_rm_table(dev, np, "volt-mem-read-margin", &opp_info->volt_rm_tbl); of_property_read_u32(np, "low-volt-mem-read-margin", &opp_info->low_rm); if (!of_property_read_u32(np, "intermediate-threshold-freq", &freq)) opp_info->intermediate_threshold_freq = freq * 1000; rockchip_init_read_margin(dev, opp_info, reg_name); } if (opp_info->data && opp_info->data->get_soc_info) opp_info->data->get_soc_info(dev, np, &bin, &process); rockchip_get_soc_info(dev, np, &bin, &process); rockchip_get_scale_volt_sel(dev, "cpu_leakage", reg_name, bin, process, &cluster->scale, &volt_sel); if (opp_info->data && opp_info->data->set_soc_info) opp_info->data->set_soc_info(dev, np, bin, process, volt_sel); pname_table = rockchip_set_opp_prop_name(dev, process, volt_sel); rockchip_set_opp_supported_hw(dev, np, bin, volt_sel); if (of_find_property(dev->of_node, "cpu-supply", NULL) && of_find_property(dev->of_node, "mem-supply", NULL)) { cluster->regulator_count = 2; reg_table = dev_pm_opp_set_regulators(dev, reg_names, ARRAY_SIZE(reg_names)); if (IS_ERR(reg_table)) { ret = PTR_ERR(reg_table); goto pname_opp_table; } opp_table = dev_pm_opp_register_set_opp_helper(dev, cpu_opp_helper); if (IS_ERR(opp_table)) { ret = PTR_ERR(opp_table); goto reg_opp_table; } } else { cluster->regulator_count = 1; } of_node_put(np); return 0; reg_opp_table: if (reg_table) dev_pm_opp_put_regulators(reg_table); pname_opp_table: if (!IS_ERR_OR_NULL(pname_table)) dev_pm_opp_put_prop_name(pname_table); np_err: of_node_put(np); return ret; } int rockchip_cpufreq_adjust_power_scale(struct device *dev) { struct cluster_info *cluster; cluster = rockchip_cluster_info_lookup(dev->id); if (!cluster) return -EINVAL; rockchip_adjust_power_scale(dev, cluster->scale); rockchip_pvtpll_calibrate_opp(&cluster->opp_info); rockchip_pvtpll_add_length(&cluster->opp_info); return 0; } EXPORT_SYMBOL_GPL(rockchip_cpufreq_adjust_power_scale); int rockchip_cpufreq_opp_set_rate(struct device *dev, unsigned long target_freq) { struct cluster_info *cluster; struct dev_pm_opp *opp; unsigned long freq; int ret = 0; cluster = rockchip_cluster_info_lookup(dev->id); if (!cluster) return -EINVAL; rockchip_monitor_volt_adjust_lock(cluster->mdev_info); ret = dev_pm_opp_set_rate(dev, target_freq); if (!ret) { cluster->rate = target_freq; if (cluster->regulator_count == 1) { freq = target_freq; opp = dev_pm_opp_find_freq_ceil(cluster->opp_info.dev, &freq); if (!IS_ERR(opp)) { cluster->volt = dev_pm_opp_get_voltage(opp); dev_pm_opp_put(opp); } } } rockchip_monitor_volt_adjust_unlock(cluster->mdev_info); return ret; } EXPORT_SYMBOL_GPL(rockchip_cpufreq_opp_set_rate); static int rockchip_cpufreq_suspend(struct cpufreq_policy *policy) { int ret = 0; ret = cpufreq_generic_suspend(policy); if (!ret) rockchip_monitor_suspend_low_temp_adjust(policy->cpu); return ret; } static int rockchip_cpufreq_add_monitor(struct cluster_info *cluster, struct cpufreq_policy *policy) { struct device *dev = cluster->opp_info.dev; struct monitor_dev_profile *mdevp = NULL; struct monitor_dev_info *mdev_info = NULL; mdevp = kzalloc(sizeof(*mdevp), GFP_KERNEL); if (!mdevp) return -ENOMEM; mdevp->type = MONITOR_TYPE_CPU; mdevp->low_temp_adjust = rockchip_monitor_cpu_low_temp_adjust; mdevp->high_temp_adjust = rockchip_monitor_cpu_high_temp_adjust; mdevp->update_volt = rockchip_monitor_check_rate_volt; mdevp->data = (void *)policy; mdevp->opp_info = &cluster->opp_info; cpumask_copy(&mdevp->allowed_cpus, policy->cpus); mdev_info = rockchip_system_monitor_register(dev, mdevp); if (IS_ERR(mdev_info)) { kfree(mdevp); dev_err(dev, "failed to register system monitor\n"); return -EINVAL; } mdev_info->devp = mdevp; cluster->mdev_info = mdev_info; return 0; } static int rockchip_cpufreq_remove_monitor(struct cluster_info *cluster) { if (cluster->mdev_info) { kfree(cluster->mdev_info->devp); rockchip_system_monitor_unregister(cluster->mdev_info); cluster->mdev_info = NULL; } return 0; } static int rockchip_cpufreq_remove_dsu_qos(struct cluster_info *cluster) { struct cluster_info *ci; if (!cluster->is_opp_shared_dsu) return 0; list_for_each_entry(ci, &cluster_info_list, list_head) { if (ci->is_opp_shared_dsu) continue; if (freq_qos_request_active(&ci->dsu_qos_req)) freq_qos_remove_request(&ci->dsu_qos_req); } return 0; } static int rockchip_cpufreq_add_dsu_qos_req(struct cluster_info *cluster, struct cpufreq_policy *policy) { struct device *dev = cluster->opp_info.dev; struct cluster_info *ci; int ret; if (!cluster->is_opp_shared_dsu) return 0; list_for_each_entry(ci, &cluster_info_list, list_head) { if (ci->is_opp_shared_dsu) continue; ret = freq_qos_add_request(&policy->constraints, &ci->dsu_qos_req, FREQ_QOS_MIN, FREQ_QOS_MIN_DEFAULT_VALUE); if (ret < 0) { dev_err(dev, "failed to add dsu freq constraint\n"); goto error; } } return 0; error: rockchip_cpufreq_remove_dsu_qos(cluster); return ret; } static int rockchip_cpufreq_notifier(struct notifier_block *nb, unsigned long event, void *data) { struct cpufreq_policy *policy = data; struct cluster_info *cluster; cluster = rockchip_cluster_info_lookup(policy->cpu); if (!cluster) return NOTIFY_BAD; if (event == CPUFREQ_CREATE_POLICY) { if (rockchip_cpufreq_add_monitor(cluster, policy)) return NOTIFY_BAD; if (rockchip_cpufreq_add_dsu_qos_req(cluster, policy)) return NOTIFY_BAD; } else if (event == CPUFREQ_REMOVE_POLICY) { rockchip_cpufreq_remove_monitor(cluster); rockchip_cpufreq_remove_dsu_qos(cluster); } return NOTIFY_OK; } static struct notifier_block rockchip_cpufreq_notifier_block = { .notifier_call = rockchip_cpufreq_notifier, }; #ifdef MODULE static struct pm_qos_request idle_pm_qos; static int idle_disable_refcnt; static DEFINE_MUTEX(idle_disable_lock); static int rockchip_cpufreq_idle_state_disable(struct cpumask *cpumask, int index, bool disable) { mutex_lock(&idle_disable_lock); if (disable) { if (idle_disable_refcnt == 0) cpu_latency_qos_update_request(&idle_pm_qos, 0); idle_disable_refcnt++; } else { if (--idle_disable_refcnt == 0) cpu_latency_qos_update_request(&idle_pm_qos, PM_QOS_DEFAULT_VALUE); } mutex_unlock(&idle_disable_lock); return 0; } #else static int rockchip_cpufreq_idle_state_disable(struct cpumask *cpumask, int index, bool disable) { unsigned int cpu; for_each_cpu(cpu, cpumask) { struct cpuidle_device *dev = per_cpu(cpuidle_devices, cpu); struct cpuidle_driver *drv = cpuidle_get_cpu_driver(dev); if (!dev || !drv) continue; if (index >= drv->state_count) continue; cpuidle_driver_state_disabled(drv, index, disable); } if (disable) { preempt_disable(); for_each_cpu(cpu, cpumask) { if (cpu != smp_processor_id() && cpu_online(cpu)) wake_up_if_idle(cpu); } preempt_enable(); } return 0; } #endif #define cpu_to_dsu_freq(freq) ((freq) * 4 / 5) static int rockchip_cpufreq_update_dsu_req(struct cluster_info *cluster, unsigned int freq) { struct device *dev = cluster->opp_info.dev; unsigned int dsu_freq = rounddown(cpu_to_dsu_freq(freq), 100000); if (cluster->is_opp_shared_dsu || !freq_qos_request_active(&cluster->dsu_qos_req)) return 0; dev_dbg(dev, "cpu to dsu: %u -> %u\n", freq, dsu_freq); return freq_qos_update_request(&cluster->dsu_qos_req, dsu_freq); } static int rockchip_cpufreq_transition_notifier(struct notifier_block *nb, unsigned long event, void *data) { struct cpufreq_freqs *freqs = data; struct cpufreq_policy *policy = freqs->policy; struct cluster_info *cluster; cluster = rockchip_cluster_info_lookup(policy->cpu); if (!cluster) return NOTIFY_BAD; if (event == CPUFREQ_PRECHANGE) { if (cluster->idle_threshold_freq && freqs->new >= cluster->idle_threshold_freq && !cluster->is_idle_disabled) { rockchip_cpufreq_idle_state_disable(policy->cpus, 1, true); cluster->is_idle_disabled = true; } } else if (event == CPUFREQ_POSTCHANGE) { if (cluster->idle_threshold_freq && freqs->new < cluster->idle_threshold_freq && cluster->is_idle_disabled) { rockchip_cpufreq_idle_state_disable(policy->cpus, 1, false); cluster->is_idle_disabled = false; } rockchip_cpufreq_update_dsu_req(cluster, freqs->new); } return NOTIFY_OK; } static struct notifier_block rockchip_cpufreq_transition_notifier_block = { .notifier_call = rockchip_cpufreq_transition_notifier, }; static int rockchip_cpufreq_panic_notifier(struct notifier_block *nb, unsigned long v, void *p) { struct cluster_info *ci; struct device *dev; list_for_each_entry(ci, &cluster_info_list, list_head) { dev = ci->opp_info.dev; if (ci->regulator_count == 1) dev_info(dev, "cur_freq: %lu Hz, volt: %lu uV\n", ci->rate, ci->volt); else dev_info(dev, "cur_freq: %lu Hz, volt_vdd: %lu uV, volt_mem: %lu uV\n", ci->rate, ci->volt, ci->mem_volt); } return 0; } static struct notifier_block rockchip_cpufreq_panic_notifier_block = { .notifier_call = rockchip_cpufreq_panic_notifier, }; static int __init rockchip_cpufreq_driver_init(void) { struct cluster_info *cluster, *pos; struct cpufreq_dt_platform_data pdata = {0}; int cpu, ret; for_each_possible_cpu(cpu) { cluster = rockchip_cluster_info_lookup(cpu); if (cluster) continue; cluster = kzalloc(sizeof(*cluster), GFP_KERNEL); if (!cluster) { ret = -ENOMEM; goto release_cluster_info; } ret = rockchip_cpufreq_cluster_init(cpu, cluster); if (ret) { pr_err("Failed to initialize dvfs info cpu%d\n", cpu); goto release_cluster_info; } list_add(&cluster->list_head, &cluster_info_list); } pdata.have_governor_per_policy = true; pdata.suspend = rockchip_cpufreq_suspend; ret = cpufreq_register_notifier(&rockchip_cpufreq_notifier_block, CPUFREQ_POLICY_NOTIFIER); if (ret) { pr_err("failed to register cpufreq notifier\n"); goto release_cluster_info; } if (of_machine_is_compatible("rockchip,rk3588")) { ret = cpufreq_register_notifier(&rockchip_cpufreq_transition_notifier_block, CPUFREQ_TRANSITION_NOTIFIER); if (ret) { cpufreq_unregister_notifier(&rockchip_cpufreq_notifier_block, CPUFREQ_POLICY_NOTIFIER); pr_err("failed to register cpufreq notifier\n"); goto release_cluster_info; } #ifdef MODULE cpu_latency_qos_add_request(&idle_pm_qos, PM_QOS_DEFAULT_VALUE); #endif } ret = atomic_notifier_chain_register(&panic_notifier_list, &rockchip_cpufreq_panic_notifier_block); if (ret) pr_err("failed to register cpufreq panic notifier\n"); return PTR_ERR_OR_ZERO(platform_device_register_data(NULL, "cpufreq-dt", -1, (void *)&pdata, sizeof(struct cpufreq_dt_platform_data))); release_cluster_info: list_for_each_entry_safe(cluster, pos, &cluster_info_list, list_head) { list_del(&cluster->list_head); kfree(cluster); } return ret; } module_init(rockchip_cpufreq_driver_init); MODULE_AUTHOR("Finley Xiao "); MODULE_DESCRIPTION("Rockchip cpufreq driver"); MODULE_LICENSE("GPL v2");