/* * Copyright (C) 2011-2013, NVIDIA CORPORATION. All Rights Reserved. * * 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 in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA * 02111-1307, USA */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "tegra_edp_private.h" #define KHZ 1000 #define FREQ_STEP_KHZ 12750 #define OVERRIDE_DEFAULT 6000 /* * "Safe entry" to be used when no match for speedo_id / * regulator_cur is found; must be the last one */ static struct tegra_edp_limits edp_default_limits[] = { {85, {1000000, 1000000, 1000000, 1000000} }, }; /* Constants for EDP calculations */ static const int temperatures[] = { /* degree celcius (C) */ 23, 40, 50, 60, 70, 74, 78, 82, 86, 90, 94, 98, 102, }; static DEFINE_MUTEX(tegra_cpu_lock); static struct tegra_edp cpu_edp; static inline s64 edp_pow(s64 val, int pwr) { s64 retval = 1; while (pwr) { if (pwr & 1) retval *= val; pwr >>= 1; if (pwr) val *= val; } return retval; } static int cal_leakage_ma(const struct tegra_edp_cpu_leakage_params *params, int iddq_ma, unsigned int voltage_mv, int temp_c) { int i, j, k; s64 leakage_ma, leakage_calc_step; /* Calculate leakage current */ leakage_ma = 0; for (i = 0; i <= 3; i++) { for (j = 0; j <= 3; j++) { for (k = 0; k <= 3; k++) { unsigned int scaled; int ijk; ijk = params->leakage_consts_ijk[i][j][k]; leakage_calc_step = ijk * edp_pow(iddq_ma, i); /* Convert (mA)^i to (A)^i */ leakage_calc_step = div64_s64(leakage_calc_step, edp_pow(1000, i)); leakage_calc_step *= edp_pow(voltage_mv, j); /* Convert (mV)^j to (V)^j */ leakage_calc_step = div64_s64(leakage_calc_step, edp_pow(1000, j)); leakage_calc_step *= edp_pow(temp_c, k); /* Convert (C)^k to (scaled_C)^k */ scaled = params->temp_scaled; leakage_calc_step = div64_s64(leakage_calc_step, edp_pow(scaled, k)); /* leakage_consts_ijk was scaled */ leakage_calc_step = div64_s64(leakage_calc_step, params->ijk_scaled); leakage_ma += leakage_calc_step; } } } return leakage_ma; } /* * Find the maximum frequency that results in dynamic and leakage current that * is less than the regulator current limit. * temp_c - valid or -EINVAL * power_mw - valid or -1 (infinite) or -EINVAL */ static int edp_calculate_maxf(struct device *dev, int temp_idx, int power_mw, int iddq_ma, int n_cores_idx, struct tegra_edp_freq_vol_table *freq_voltage_lut, unsigned int freq_voltage_lut_size) { struct tegra_edp *edp = dev_get_drvdata(dev); const struct tegra_edp_cpu_leakage_params *params; int temp_c = edp->temps[temp_idx]; unsigned int voltage_mv, freq_khz; unsigned int cur_effective; int f; s64 leakage_ma, dyn_ma; s64 leakage_mw, dyn_mw; params = &edp->params[edp->cpu_speedo_idx]; cur_effective = edp->regulator_cur - edp->edp_reg_override_ma; for (f = freq_voltage_lut_size - 1; f >= 0; f--) { freq_khz = freq_voltage_lut[f].freq / KHZ; voltage_mv = freq_voltage_lut[f].voltage_mv; /* Constrain Volt-Temp */ if (params->volt_temp_cap.temperature && temp_c > params->volt_temp_cap.temperature && params->volt_temp_cap.voltage_limit_mv && voltage_mv > params->volt_temp_cap.voltage_limit_mv) continue; /* Calculate leakage current */ leakage_ma = cal_leakage_ma(params, iddq_ma, voltage_mv, temp_c); /* if specified, set floor for leakage current */ if (params->leakage_min && leakage_ma <= params->leakage_min) leakage_ma = params->leakage_min; /* leakage cannot be negative => leakage model has error */ if (leakage_ma <= 0) { dev_err(dev, "VDD_CPU EDP failed: IDDQ too high (%d mA)\n", iddq_ma); return -EINVAL; } leakage_ma *= params->leakage_consts_n[n_cores_idx]; /* leakage_const_n was scaled */ leakage_ma = div64_s64(leakage_ma, params->consts_scaled); /* Calculate dynamic current */ dyn_ma = voltage_mv * freq_khz / 1000; /* Convert mV to V */ dyn_ma = div64_s64(dyn_ma, 1000); dyn_ma *= params->dyn_consts_n[n_cores_idx]; /* dyn_const_n was scaled */ dyn_ma = div64_s64(dyn_ma, params->dyn_scaled); if (power_mw != -1) { leakage_mw = leakage_ma * voltage_mv; dyn_mw = dyn_ma * voltage_mv; if (div64_s64(leakage_mw + dyn_mw, 1000) <= power_mw) return freq_khz; } else if ((leakage_ma + dyn_ma) <= cur_effective) { return freq_khz; } } return -EINVAL; } static int edp_relate_freq_voltage(struct device *dev, struct clk *clk_cpu_g, unsigned int cpu_speedo_idx, struct tegra_edp_freq_vol_table *lut, unsigned int lut_size) { unsigned int i, freq; int voltage_mv; for (i = 0, freq = 0; i < lut_size; i++, freq += FREQ_STEP_KHZ) { /* Predict voltages */ voltage_mv = tegra_dvfs_predict_millivolts(clk_cpu_g, freq * KHZ); if (voltage_mv < 0) { dev_err(dev, "Couldn't predict voltage: freq %u; err %d\n", freq, voltage_mv); return -EINVAL; } /* Cache frequency / voltage / voltage constant relationship */ lut[i].freq = freq * KHZ; lut[i].voltage_mv = voltage_mv; } return 0; } static int edp_find_speedo_idx(struct device *dev, int cpu_speedo_id) { struct tegra_edp *edp = dev_get_drvdata(dev); int i; for (i = 0; i < edp->params_size; i++) if (cpu_speedo_id == edp->params[i].cpu_speedo_id) return i; dev_err(dev, "Couldn't find cpu speedo id %d in freq/voltage LUT\n", cpu_speedo_id); return -EINVAL; } static int tegra_cpu_edp_cal_limits(struct device *dev) { struct tegra_edp *edp = dev_get_drvdata(dev); unsigned int temp_idx, n_cores_idx; unsigned int cpu_g_minf, cpu_g_maxf; unsigned int cpu_speedo_idx; unsigned int cap, limit; struct tegra_edp_limits *edp_calculated_limits; const struct tegra_edp_cpu_leakage_params *params; int cpu_speedo_id, iddq_ma; size_t size; int ret; struct clk *clk_cpu_g = clk_get_sys(NULL, "cclk_g"); struct tegra_edp_freq_vol_table *freq_voltage_lut; unsigned int freq_voltage_lut_size; /* Determine all inputs to EDP formula */ cpu_speedo_id = tegra_get_cpu_speedo_id(); iddq_ma = tegra_get_cpu_iddq_value(); cpu_speedo_idx = edp_find_speedo_idx(dev, cpu_speedo_id); if (cpu_speedo_idx < 0) return -EINVAL; edp->cpu_speedo_idx = cpu_speedo_idx; params = &edp->params[cpu_speedo_idx]; cpu_g_minf = 0; cpu_g_maxf = edp->max_cpu_freq; freq_voltage_lut_size = (cpu_g_maxf - cpu_g_minf) / FREQ_STEP_KHZ + 1; size = sizeof(struct tegra_edp_freq_vol_table) * freq_voltage_lut_size; freq_voltage_lut = kzalloc(size, GFP_KERNEL); if (!freq_voltage_lut) { dev_err(dev, "Failed to alloc mem for freq/voltage LUT\n"); return -ENOMEM; } ret = edp_relate_freq_voltage(dev, clk_cpu_g, cpu_speedo_idx, freq_voltage_lut, freq_voltage_lut_size); if (ret) goto err_free_lut; size = sizeof(struct tegra_edp_limits) * edp->temps_size; edp_calculated_limits = devm_kzalloc(dev, size, GFP_KERNEL); if (!edp_calculated_limits) { dev_err(dev, "Failed to alloc mem for edp calculatd limits\n"); ret = -ENOMEM; goto err_free_lut; } /* Calculate EDP table */ for (n_cores_idx = 0; n_cores_idx < num_possible_cpus(); n_cores_idx++) { for (temp_idx = 0; temp_idx < edp->temps_size; temp_idx++) { edp_calculated_limits[temp_idx].temperature = edp->temps[temp_idx]; limit = edp_calculate_maxf(dev, temp_idx, -1, iddq_ma, n_cores_idx, freq_voltage_lut, freq_voltage_lut_size); if (limit == -EINVAL) { ret = -EINVAL; goto err_free_limits; } /* apply safety cap if it is specified */ if (n_cores_idx < 4) { cap = params->safety_cap[n_cores_idx]; if (cap && cap < limit) limit = cap; } edp_calculated_limits[temp_idx]. freq_limits[n_cores_idx] = limit; } } /* * If this is an EDP table update, need to overwrite old table. * The old table's address must remain valid. */ if (edp->edp_limits != edp->def_edp_limits) { memcpy(edp->edp_limits, edp_calculated_limits, size); devm_kfree(dev, edp_calculated_limits); } else { edp->edp_limits = edp_calculated_limits; edp->edp_limits_size = edp->temps_size; } kfree(freq_voltage_lut); return 0; err_free_limits: devm_kfree(dev, edp_calculated_limits); err_free_lut: kfree(freq_voltage_lut); return ret; } int tegra_cpu_edp_init_trips(struct platform_device *pdev, struct thermal_trip_info *trips, int *num_trips, char *cdev_type, int margin) { struct device *dev; struct tegra_edp *cpu_edp; struct thermal_trip_info *trip_state; const struct tegra_edp_limits *cpu_edp_limits; int i, cpu_edp_limits_size; if (!pdev) return -EINVAL; dev = &pdev->dev; cpu_edp = dev_get_drvdata(dev); if (!cpu_edp || !cpu_edp->edp_init_done) return -EPROBE_DEFER; if (!trips || !num_trips) return -EINVAL; /* edp capping */ cpu_edp_limits = cpu_edp->edp_limits; cpu_edp_limits_size = cpu_edp->edp_limits_size; for (i = 0; i < cpu_edp_limits_size-1; i++) { trip_state = &trips[*num_trips]; trip_state->cdev_type = cdev_type; trip_state->trip_temp = (cpu_edp_limits[i].temperature * 1000) - margin; trip_state->trip_type = THERMAL_TRIP_ACTIVE; trip_state->upper = i + 1; trip_state->lower = trip_state->upper; trip_state->hysteresis = 1000; (*num_trips)++; if (*num_trips >= THERMAL_MAX_TRIPS) BUG(); } return 0; } EXPORT_SYMBOL(tegra_cpu_edp_init_trips); void tegra_cpu_edp_recal_limits(void) { if (!cpu_edp.edp_limits) return; if (tegra_cpu_edp_cal_limits(&cpu_edp.pdev->dev) == 0) return; /* Revert to default EDP table on error */ cpu_edp.edp_limits = cpu_edp.def_edp_limits; cpu_edp.edp_limits_size = cpu_edp.def_edp_limits_size; } EXPORT_SYMBOL(tegra_cpu_edp_recal_limits); static unsigned int edp_predict_limit(struct tegra_edp *edp, unsigned int cpus) { unsigned int limit = 0; BUG_ON(cpus == 0); if (edp->edp_limits) { int i = edp->edp_thermal_index; int size = edp->edp_limits_size; BUG_ON(i >= size); limit = edp->edp_limits[i].freq_limits[cpus - 1]; } return limit; } /* Must be called while holding tegra_cpu_lock */ static void tegra_edp_update_limit(struct device *dev) { struct tegra_edp *edp = dev_get_drvdata(dev); unsigned int cpus, limit; if (!edp->edp_limits) return; BUG_ON(!mutex_is_locked(&tegra_cpu_lock)); cpus = cpumask_weight(&edp->edp_cpumask); limit = edp_predict_limit(edp, cpus); edp->edp_freq_limit = limit; } static unsigned int tegra_edp_governor_speed(struct device *dev, unsigned int requested_speed) { struct tegra_edp *edp = dev_get_drvdata(dev); if ((!edp->edp_freq_limit) || (requested_speed <= edp->edp_freq_limit)) return requested_speed; else return edp->edp_freq_limit; } static int tegra_cpu_edp_init_data(struct device *dev, unsigned int regulator_ma) { struct tegra_edp *edp = dev_get_drvdata(dev); edp->edp_reg_override_ma = OVERRIDE_DEFAULT; edp->regulator_cur = regulator_ma + OVERRIDE_DEFAULT; edp->def_edp_limits = edp_default_limits; edp->def_edp_limits_size = ARRAY_SIZE(edp_default_limits); edp->temps = temperatures; edp->temps_size = ARRAY_SIZE(temperatures); edp->edp_limits = edp_default_limits; edp->edp_limits_size = ARRAY_SIZE(edp_default_limits); tegra_cpu_edp_cal_limits(dev); mutex_lock(&tegra_cpu_lock); edp->edp_cpumask = *cpu_online_mask; tegra_edp_update_limit(dev); mutex_unlock(&tegra_cpu_lock); dev_info(dev, "Init EDP limit: %u MHz\n", edp->edp_freq_limit/1000); return 0; } int tegra_cpu_edp_get_thermal_index(struct platform_device *pdev) { struct tegra_edp *edp = dev_get_drvdata(&pdev->dev); return edp->edp_thermal_index; } EXPORT_SYMBOL(tegra_cpu_edp_get_thermal_index); int tegra_cpu_edp_count_therm_floors(struct platform_device *pdev) { struct tegra_edp *edp = dev_get_drvdata(&pdev->dev); return edp->edp_limits_size - 1; } EXPORT_SYMBOL(tegra_cpu_edp_count_therm_floors); int tegra_cpu_edp_update_thermal_index(struct platform_device *pdev, unsigned long new_idx) { struct tegra_edp *edp = dev_get_drvdata(&pdev->dev); if (new_idx > (edp->edp_limits_size - 1)) return -ERANGE; if (!cpufreq_get(0)) return 0; mutex_lock(&tegra_cpu_lock); edp->edp_thermal_index = new_idx; edp->edp_cpumask = *cpu_online_mask; tegra_edp_update_limit(&pdev->dev); mutex_unlock(&tegra_cpu_lock); cpufreq_update_policy(0); return 0; } EXPORT_SYMBOL(tegra_cpu_edp_update_thermal_index); bool tegra_cpu_edp_ready(void) { return cpu_edp.edp_init_done; } EXPORT_SYMBOL(tegra_cpu_edp_ready); #ifdef CONFIG_DEBUG_FS static struct dentry *edp_debugfs_dir; static int edp_limit_debugfs_show(struct seq_file *s, void *data) { struct device *dev = s->private; struct tegra_edp *edp = dev_get_drvdata(dev); seq_printf(s, "%u\n", edp->edp_freq_limit); return 0; } static int edp_debugfs_show(struct seq_file *s, void *data) { struct device *dev = s->private; struct tegra_edp *edp = dev_get_drvdata(dev); int i, th_idx; th_idx = edp->edp_thermal_index; seq_printf(s, "-- VDD_CPU %sEDP table (%umA = %umA - %umA) --\n", edp->edp_limits == edp->def_edp_limits ? "**default** " : "", edp->regulator_cur - edp->edp_reg_override_ma, edp->regulator_cur, edp->edp_reg_override_ma); seq_printf(s, "%6s %10s %10s %10s %10s\n", " Temp.", "1-core", "2-cores", "3-cores", "4-cores"); for (i = 0; i < edp->edp_limits_size; i++) { seq_printf(s, "%c%3dC: %10u %10u %10u %10u\n", i == th_idx ? '>' : ' ', edp->edp_limits[i].temperature, edp->edp_limits[i].freq_limits[0], edp->edp_limits[i].freq_limits[1], edp->edp_limits[i].freq_limits[2], edp->edp_limits[i].freq_limits[3]); } return 0; } static int edp_reg_override_show(struct seq_file *s, void *data) { struct device *dev = s->private; struct tegra_edp *edp = dev_get_drvdata(dev); seq_printf(s, "Limit override: %u mA. Effective limit: %u mA\n", edp->edp_reg_override_ma, edp->regulator_cur - edp->edp_reg_override_ma); return 0; } static int edp_reg_override_write(struct file *file, const char __user *userbuf, size_t count, loff_t *ppos) { struct device *dev = file->f_path.dentry->d_inode->i_private; struct tegra_edp *edp = dev_get_drvdata(dev); char buf[32]; unsigned long res; unsigned int edp_reg_override_ma_temp; unsigned int edp_reg_override_ma_prev = edp->edp_reg_override_ma; if (sizeof(buf) <= count) goto override_err; if (copy_from_user(buf, userbuf, count)) goto override_err; /* terminate buffer and trim - white spaces may be appended * at the end when invoked from shell command line */ buf[count] = '\0'; strim(buf); if (!kstrtoul(buf, 10, &res)) edp_reg_override_ma_temp = (unsigned int)res; else goto override_err; if (edp_reg_override_ma_temp >= edp->regulator_cur) goto override_err; if (edp->edp_reg_override_ma == edp_reg_override_ma_temp) return count; edp->edp_reg_override_ma = edp_reg_override_ma_temp; if (tegra_cpu_edp_cal_limits(dev)) { /* Revert to previous override value if new value fails */ edp->edp_reg_override_ma = edp_reg_override_ma_prev; goto override_err; } if (cpufreq_update_policy(0)) { dev_err(dev, "FAILED: Set CPU freq cap with new VDD_CPU EDP table\n"); goto override_out; } dev_info(dev, "Reinitialized VDD_CPU EDP table with regulator current limit %u mA\n", edp->regulator_cur - edp->edp_reg_override_ma); return count; override_err: dev_err(dev, "FAILED: Reinitialize VDD_CPU EDP table with override \"%s\"", buf); override_out: return -EINVAL; } static int edp_debugfs_open(struct inode *inode, struct file *file) { return single_open(file, edp_debugfs_show, inode->i_private); } static int edp_limit_debugfs_open(struct inode *inode, struct file *file) { return single_open(file, edp_limit_debugfs_show, inode->i_private); } static int edp_reg_override_open(struct inode *inode, struct file *file) { return single_open(file, edp_reg_override_show, inode->i_private); } static const struct file_operations edp_debugfs_fops = { .open = edp_debugfs_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; static const struct file_operations edp_limit_debugfs_fops = { .open = edp_limit_debugfs_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; static const struct file_operations edp_reg_override_debugfs_fops = { .open = edp_reg_override_open, .read = seq_read, .write = edp_reg_override_write, .llseek = seq_lseek, .release = single_release, }; static int tegra_cpu_edp_debugfs_init(struct device *dev) { struct tegra_edp *edp = dev_get_drvdata(dev); struct dentry *d_edp; struct dentry *d_edp_limit; struct dentry *d_edp_reg_override; struct dentry *edp_dir; struct dentry *vdd_cpu_dir; if (!edp_debugfs_dir) edp_debugfs_dir = debugfs_create_dir("edp", NULL); if (!edp_debugfs_dir) goto edp_dir_err; edp_dir = edp_debugfs_dir; vdd_cpu_dir = debugfs_create_dir("edp_vdd_cpu", edp_dir); if (!vdd_cpu_dir) goto vdd_cpu_dir_err; d_edp = debugfs_create_file("edp", S_IRUGO, vdd_cpu_dir, dev, &edp_debugfs_fops); if (!d_edp) goto edp_err; d_edp_limit = debugfs_create_file("edp_limit", S_IRUGO, vdd_cpu_dir, dev, &edp_limit_debugfs_fops); if (!d_edp_limit) goto edp_limit_err; d_edp_reg_override = debugfs_create_file("edp_reg_override", S_IRUGO | S_IWUSR, vdd_cpu_dir, dev, &edp_reg_override_debugfs_fops); if (!d_edp_reg_override) goto edp_reg_override_err; edp->dir = vdd_cpu_dir; return 0; edp_reg_override_err: debugfs_remove(d_edp_limit); edp_limit_err: debugfs_remove(d_edp); edp_err: debugfs_remove(vdd_cpu_dir); vdd_cpu_dir_err: debugfs_remove(edp_dir); edp_dir_err: return -ENOMEM; } #endif /* CONFIG_DEBUG_FS */ static int tegra_cpu_edp_notify( struct notifier_block *nb, unsigned long event, void *hcpu) { struct platform_device *pdev = cpu_edp.pdev; struct device *dev = &pdev->dev; int ret = 0; unsigned int cpu_speed, new_speed; int cpu = (long)hcpu; switch (event) { case CPU_UP_PREPARE: mutex_lock(&tegra_cpu_lock); cpu_set(cpu, cpu_edp.edp_cpumask); tegra_edp_update_limit(dev); cpu_speed = cpufreq_get(0); new_speed = tegra_edp_governor_speed(dev, cpu_speed); if (new_speed < cpu_speed) { ret = cpufreq_update_policy(0); dev_dbg(dev, "cpu-tegra:%sforce EDP limit %u kHz\n", ret ? " failed to " : " ", new_speed); } if (ret) { cpu_clear(cpu, cpu_edp.edp_cpumask); tegra_edp_update_limit(dev); } mutex_unlock(&tegra_cpu_lock); break; case CPU_DEAD: mutex_lock(&tegra_cpu_lock); cpu_clear(cpu, cpu_edp.edp_cpumask); tegra_edp_update_limit(dev); mutex_unlock(&tegra_cpu_lock); cpufreq_update_policy(0); break; } return notifier_from_errno(ret); } static struct notifier_block tegra_cpu_edp_notifier = { .notifier_call = tegra_cpu_edp_notify, }; /** * edp_cpufreq_policy_notifier - Notifier callback for cpufreq policy change. * @nb: struct notifier_block * with callback info. * @event: value showing cpufreq event for which this function invoked. * @data: callback-specific data * * Callback to highjack the notification on cpufreq policy transition. * Every time there is a change in policy, we will intercept and * update the cpufreq policy with edp constraints. * * Return: 0 (success) */ static int edp_cpufreq_policy_notifier(struct notifier_block *nb, unsigned long event, void *data) { struct cpufreq_policy *policy = data; if (event != CPUFREQ_ADJUST) return 0; /* Limit max freq to be within edp limit. */ if (policy->max != cpu_edp.edp_freq_limit) cpufreq_verify_within_limits(policy, 0, cpu_edp.edp_freq_limit); return 0; } /* Notifier for cpufreq policy change */ static struct notifier_block edp_cpufreq_notifier_block = { .notifier_call = edp_cpufreq_policy_notifier, }; typedef int (*of_cpu_edp_init_t)(struct device_node *); static int of_cpu_edp_init(struct device_node *np, const struct of_device_id *matches) { int ret; for_each_matching_node(np, matches) { const struct of_device_id *match = of_match_node(matches, np); of_cpu_edp_init_t cpu_edp_init = (of_cpu_edp_init_t)match->data; if (!cpu_edp_init) return -EINVAL; ret = cpu_edp_init(np); if (ret) return ret; } cpufreq_register_notifier(&edp_cpufreq_notifier_block, CPUFREQ_POLICY_NOTIFIER); return 0; } static const struct of_device_id tegra_cpu_edp_match[] = { { .compatible = "nvidia,tegra124-cpu-edp", .data = tegra124_cpu_edp_init }, {}, }; static int tegra_cpu_edp_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; unsigned int regulator_ma; u32 val; int err; struct device_node *np = pdev->dev.of_node; if (!cpufreq_get(0)) { dev_warn(dev, "CPU clocks are not ready.\n"); return -EPROBE_DEFER; } err = of_property_read_u32(np, "regulator-ma", &val); if (!err) regulator_ma = val; else regulator_ma = 15000; dev_info(dev, "CPU regulator %d mA\n", regulator_ma); platform_set_drvdata(pdev, &cpu_edp); of_cpu_edp_init(np, tegra_cpu_edp_match); cpu_edp.pdev = pdev; err = tegra_cpu_edp_init_data(dev, regulator_ma); if (err) { dev_err(dev, "Failed to calculate cpu edp limits\n"); return 0; } register_hotcpu_notifier(&tegra_cpu_edp_notifier); #ifdef CONFIG_DEBUG_FS tegra_cpu_edp_debugfs_init(dev); #endif cpu_edp.edp_init_done = true; return 0; } static int tegra_cpu_edp_remove(struct platform_device *pdev) { #ifdef CONFIG_DEBUG_FS debugfs_remove_recursive(cpu_edp.dir); #endif unregister_hotcpu_notifier(&tegra_cpu_edp_notifier); return 0; } #ifdef CONFIG_PM_SLEEP static int tegra_cpu_edp_suspend(struct device *dev) { struct tegra_edp *edp = dev_get_drvdata(dev); int size; mutex_lock(&tegra_cpu_lock); if (edp->edp_limits) { size = edp->edp_limits_size - 1; edp->edp_freq_limit = edp->edp_limits[size].freq_limits[3]; } else { size = edp->def_edp_limits_size - 1; edp->edp_freq_limit = edp->def_edp_limits[size].freq_limits[3]; } mutex_unlock(&tegra_cpu_lock); cpufreq_update_policy(0); return 0; } static int tegra_cpu_edp_resume(struct device *dev) { struct tegra_edp *edp = dev_get_drvdata(dev); mutex_lock(&tegra_cpu_lock); edp->edp_cpumask = *cpu_online_mask; tegra_edp_update_limit(dev); mutex_unlock(&tegra_cpu_lock); cpufreq_update_policy(0); return 0; } static const struct dev_pm_ops tegra_cpu_edp_pm_ops = { .suspend = tegra_cpu_edp_suspend, .resume = tegra_cpu_edp_resume, }; #endif static struct platform_driver tegra_cpu_edp_driver = { .probe = tegra_cpu_edp_probe, .remove = tegra_cpu_edp_remove, .driver = { .name = "tegra-cpu-edp", .owner = THIS_MODULE, .of_match_table = tegra_cpu_edp_match, #ifdef CONFIG_PM_SLEEP .pm = &tegra_cpu_edp_pm_ops, #endif }, }; static int __init tegra_cpu_edp_init(void) { return platform_driver_register(&tegra_cpu_edp_driver); } static void __exit tegra_cpu_edp_exit(void) { platform_driver_unregister(&tegra_cpu_edp_driver); } module_init(tegra_cpu_edp_init); module_exit(tegra_cpu_edp_exit); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("Tegra VDD_CPU EDP management");