1d2912cb1SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
27813dd6fSViresh Kumar /*
37813dd6fSViresh Kumar * Generic OPP helper interface for CPU device
47813dd6fSViresh Kumar *
57813dd6fSViresh Kumar * Copyright (C) 2009-2014 Texas Instruments Incorporated.
67813dd6fSViresh Kumar * Nishanth Menon
77813dd6fSViresh Kumar * Romit Dasgupta
87813dd6fSViresh Kumar * Kevin Hilman
97813dd6fSViresh Kumar */
107813dd6fSViresh Kumar
117813dd6fSViresh Kumar #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
127813dd6fSViresh Kumar
137813dd6fSViresh Kumar #include <linux/cpu.h>
147813dd6fSViresh Kumar #include <linux/cpufreq.h>
157813dd6fSViresh Kumar #include <linux/err.h>
167813dd6fSViresh Kumar #include <linux/errno.h>
177813dd6fSViresh Kumar #include <linux/export.h>
187813dd6fSViresh Kumar #include <linux/slab.h>
197813dd6fSViresh Kumar
207813dd6fSViresh Kumar #include "opp.h"
217813dd6fSViresh Kumar
227813dd6fSViresh Kumar #ifdef CONFIG_CPU_FREQ
237813dd6fSViresh Kumar
247813dd6fSViresh Kumar /**
257813dd6fSViresh Kumar * dev_pm_opp_init_cpufreq_table() - create a cpufreq table for a device
267813dd6fSViresh Kumar * @dev: device for which we do this operation
27*a5a29791SViresh Kumar * @opp_table: Cpufreq table returned back to caller
287813dd6fSViresh Kumar *
297813dd6fSViresh Kumar * Generate a cpufreq table for a provided device- this assumes that the
307813dd6fSViresh Kumar * opp table is already initialized and ready for usage.
317813dd6fSViresh Kumar *
327813dd6fSViresh Kumar * This function allocates required memory for the cpufreq table. It is
337813dd6fSViresh Kumar * expected that the caller does the required maintenance such as freeing
347813dd6fSViresh Kumar * the table as required.
357813dd6fSViresh Kumar *
367813dd6fSViresh Kumar * Returns -EINVAL for bad pointers, -ENODEV if the device is not found, -ENOMEM
377813dd6fSViresh Kumar * if no memory available for the operation (table is not populated), returns 0
387813dd6fSViresh Kumar * if successful and table is populated.
397813dd6fSViresh Kumar *
407813dd6fSViresh Kumar * WARNING: It is important for the callers to ensure refreshing their copy of
417813dd6fSViresh Kumar * the table if any of the mentioned functions have been invoked in the interim.
427813dd6fSViresh Kumar */
dev_pm_opp_init_cpufreq_table(struct device * dev,struct cpufreq_frequency_table ** opp_table)437813dd6fSViresh Kumar int dev_pm_opp_init_cpufreq_table(struct device *dev,
44d6134583SViresh Kumar struct cpufreq_frequency_table **opp_table)
457813dd6fSViresh Kumar {
467813dd6fSViresh Kumar struct dev_pm_opp *opp;
477813dd6fSViresh Kumar struct cpufreq_frequency_table *freq_table = NULL;
487813dd6fSViresh Kumar int i, max_opps, ret = 0;
497813dd6fSViresh Kumar unsigned long rate;
507813dd6fSViresh Kumar
517813dd6fSViresh Kumar max_opps = dev_pm_opp_get_opp_count(dev);
527813dd6fSViresh Kumar if (max_opps <= 0)
537813dd6fSViresh Kumar return max_opps ? max_opps : -ENODATA;
547813dd6fSViresh Kumar
554a823c0bSJia-Ju Bai freq_table = kcalloc((max_opps + 1), sizeof(*freq_table), GFP_KERNEL);
567813dd6fSViresh Kumar if (!freq_table)
577813dd6fSViresh Kumar return -ENOMEM;
587813dd6fSViresh Kumar
597813dd6fSViresh Kumar for (i = 0, rate = 0; i < max_opps; i++, rate++) {
607813dd6fSViresh Kumar /* find next rate */
617813dd6fSViresh Kumar opp = dev_pm_opp_find_freq_ceil(dev, &rate);
627813dd6fSViresh Kumar if (IS_ERR(opp)) {
637813dd6fSViresh Kumar ret = PTR_ERR(opp);
647813dd6fSViresh Kumar goto out;
657813dd6fSViresh Kumar }
667813dd6fSViresh Kumar freq_table[i].driver_data = i;
677813dd6fSViresh Kumar freq_table[i].frequency = rate / 1000;
687813dd6fSViresh Kumar
697813dd6fSViresh Kumar /* Is Boost/turbo opp ? */
707813dd6fSViresh Kumar if (dev_pm_opp_is_turbo(opp))
717813dd6fSViresh Kumar freq_table[i].flags = CPUFREQ_BOOST_FREQ;
727813dd6fSViresh Kumar
737813dd6fSViresh Kumar dev_pm_opp_put(opp);
747813dd6fSViresh Kumar }
757813dd6fSViresh Kumar
767813dd6fSViresh Kumar freq_table[i].driver_data = i;
777813dd6fSViresh Kumar freq_table[i].frequency = CPUFREQ_TABLE_END;
787813dd6fSViresh Kumar
79d6134583SViresh Kumar *opp_table = &freq_table[0];
807813dd6fSViresh Kumar
817813dd6fSViresh Kumar out:
827813dd6fSViresh Kumar if (ret)
837813dd6fSViresh Kumar kfree(freq_table);
847813dd6fSViresh Kumar
857813dd6fSViresh Kumar return ret;
867813dd6fSViresh Kumar }
877813dd6fSViresh Kumar EXPORT_SYMBOL_GPL(dev_pm_opp_init_cpufreq_table);
887813dd6fSViresh Kumar
897813dd6fSViresh Kumar /**
907813dd6fSViresh Kumar * dev_pm_opp_free_cpufreq_table() - free the cpufreq table
917813dd6fSViresh Kumar * @dev: device for which we do this operation
92*a5a29791SViresh Kumar * @opp_table: table to free
937813dd6fSViresh Kumar *
947813dd6fSViresh Kumar * Free up the table allocated by dev_pm_opp_init_cpufreq_table
957813dd6fSViresh Kumar */
dev_pm_opp_free_cpufreq_table(struct device * dev,struct cpufreq_frequency_table ** opp_table)967813dd6fSViresh Kumar void dev_pm_opp_free_cpufreq_table(struct device *dev,
97d6134583SViresh Kumar struct cpufreq_frequency_table **opp_table)
987813dd6fSViresh Kumar {
99d6134583SViresh Kumar if (!opp_table)
1007813dd6fSViresh Kumar return;
1017813dd6fSViresh Kumar
102d6134583SViresh Kumar kfree(*opp_table);
103d6134583SViresh Kumar *opp_table = NULL;
1047813dd6fSViresh Kumar }
1057813dd6fSViresh Kumar EXPORT_SYMBOL_GPL(dev_pm_opp_free_cpufreq_table);
1067813dd6fSViresh Kumar #endif /* CONFIG_CPU_FREQ */
1077813dd6fSViresh Kumar
_dev_pm_opp_cpumask_remove_table(const struct cpumask * cpumask,int last_cpu)1082a4eb735SViresh Kumar void _dev_pm_opp_cpumask_remove_table(const struct cpumask *cpumask,
109404b1369SViresh Kumar int last_cpu)
1107813dd6fSViresh Kumar {
1117813dd6fSViresh Kumar struct device *cpu_dev;
1127813dd6fSViresh Kumar int cpu;
1137813dd6fSViresh Kumar
1147813dd6fSViresh Kumar WARN_ON(cpumask_empty(cpumask));
1157813dd6fSViresh Kumar
1167813dd6fSViresh Kumar for_each_cpu(cpu, cpumask) {
117404b1369SViresh Kumar if (cpu == last_cpu)
118404b1369SViresh Kumar break;
119404b1369SViresh Kumar
1207813dd6fSViresh Kumar cpu_dev = get_cpu_device(cpu);
1217813dd6fSViresh Kumar if (!cpu_dev) {
1227813dd6fSViresh Kumar pr_err("%s: failed to get cpu%d device\n", __func__,
1237813dd6fSViresh Kumar cpu);
1247813dd6fSViresh Kumar continue;
1257813dd6fSViresh Kumar }
1267813dd6fSViresh Kumar
1278aaf6264SViresh Kumar dev_pm_opp_remove_table(cpu_dev);
1287813dd6fSViresh Kumar }
1297813dd6fSViresh Kumar }
1307813dd6fSViresh Kumar
1317813dd6fSViresh Kumar /**
1327813dd6fSViresh Kumar * dev_pm_opp_cpumask_remove_table() - Removes OPP table for @cpumask
1337813dd6fSViresh Kumar * @cpumask: cpumask for which OPP table needs to be removed
1347813dd6fSViresh Kumar *
1357813dd6fSViresh Kumar * This removes the OPP tables for CPUs present in the @cpumask.
1367813dd6fSViresh Kumar * This should be used to remove all the OPPs entries associated with
1377813dd6fSViresh Kumar * the cpus in @cpumask.
1387813dd6fSViresh Kumar */
dev_pm_opp_cpumask_remove_table(const struct cpumask * cpumask)1397813dd6fSViresh Kumar void dev_pm_opp_cpumask_remove_table(const struct cpumask *cpumask)
1407813dd6fSViresh Kumar {
1412a4eb735SViresh Kumar _dev_pm_opp_cpumask_remove_table(cpumask, -1);
1427813dd6fSViresh Kumar }
1437813dd6fSViresh Kumar EXPORT_SYMBOL_GPL(dev_pm_opp_cpumask_remove_table);
1447813dd6fSViresh Kumar
1457813dd6fSViresh Kumar /**
1467813dd6fSViresh Kumar * dev_pm_opp_set_sharing_cpus() - Mark OPP table as shared by few CPUs
1477813dd6fSViresh Kumar * @cpu_dev: CPU device for which we do this operation
1487813dd6fSViresh Kumar * @cpumask: cpumask of the CPUs which share the OPP table with @cpu_dev
1497813dd6fSViresh Kumar *
1507813dd6fSViresh Kumar * This marks OPP table of the @cpu_dev as shared by the CPUs present in
1517813dd6fSViresh Kumar * @cpumask.
1527813dd6fSViresh Kumar *
1537813dd6fSViresh Kumar * Returns -ENODEV if OPP table isn't already present.
1547813dd6fSViresh Kumar */
dev_pm_opp_set_sharing_cpus(struct device * cpu_dev,const struct cpumask * cpumask)1557813dd6fSViresh Kumar int dev_pm_opp_set_sharing_cpus(struct device *cpu_dev,
1567813dd6fSViresh Kumar const struct cpumask *cpumask)
1577813dd6fSViresh Kumar {
1587813dd6fSViresh Kumar struct opp_device *opp_dev;
1597813dd6fSViresh Kumar struct opp_table *opp_table;
1607813dd6fSViresh Kumar struct device *dev;
1617813dd6fSViresh Kumar int cpu, ret = 0;
1627813dd6fSViresh Kumar
1637813dd6fSViresh Kumar opp_table = _find_opp_table(cpu_dev);
1647813dd6fSViresh Kumar if (IS_ERR(opp_table))
1657813dd6fSViresh Kumar return PTR_ERR(opp_table);
1667813dd6fSViresh Kumar
1677813dd6fSViresh Kumar for_each_cpu(cpu, cpumask) {
1687813dd6fSViresh Kumar if (cpu == cpu_dev->id)
1697813dd6fSViresh Kumar continue;
1707813dd6fSViresh Kumar
1717813dd6fSViresh Kumar dev = get_cpu_device(cpu);
1727813dd6fSViresh Kumar if (!dev) {
1737813dd6fSViresh Kumar dev_err(cpu_dev, "%s: failed to get cpu%d device\n",
1747813dd6fSViresh Kumar __func__, cpu);
1757813dd6fSViresh Kumar continue;
1767813dd6fSViresh Kumar }
1777813dd6fSViresh Kumar
1787813dd6fSViresh Kumar opp_dev = _add_opp_dev(dev, opp_table);
1797813dd6fSViresh Kumar if (!opp_dev) {
1807813dd6fSViresh Kumar dev_err(dev, "%s: failed to add opp-dev for cpu%d device\n",
1817813dd6fSViresh Kumar __func__, cpu);
1827813dd6fSViresh Kumar continue;
1837813dd6fSViresh Kumar }
1847813dd6fSViresh Kumar
1857813dd6fSViresh Kumar /* Mark opp-table as multiple CPUs are sharing it now */
1867813dd6fSViresh Kumar opp_table->shared_opp = OPP_TABLE_ACCESS_SHARED;
1877813dd6fSViresh Kumar }
1887813dd6fSViresh Kumar
1897813dd6fSViresh Kumar dev_pm_opp_put_opp_table(opp_table);
1907813dd6fSViresh Kumar
1917813dd6fSViresh Kumar return ret;
1927813dd6fSViresh Kumar }
1937813dd6fSViresh Kumar EXPORT_SYMBOL_GPL(dev_pm_opp_set_sharing_cpus);
1947813dd6fSViresh Kumar
1957813dd6fSViresh Kumar /**
1967813dd6fSViresh Kumar * dev_pm_opp_get_sharing_cpus() - Get cpumask of CPUs sharing OPPs with @cpu_dev
1977813dd6fSViresh Kumar * @cpu_dev: CPU device for which we do this operation
1987813dd6fSViresh Kumar * @cpumask: cpumask to update with information of sharing CPUs
1997813dd6fSViresh Kumar *
2007813dd6fSViresh Kumar * This updates the @cpumask with CPUs that are sharing OPPs with @cpu_dev.
2017813dd6fSViresh Kumar *
2027813dd6fSViresh Kumar * Returns -ENODEV if OPP table isn't already present and -EINVAL if the OPP
2037813dd6fSViresh Kumar * table's status is access-unknown.
2047813dd6fSViresh Kumar */
dev_pm_opp_get_sharing_cpus(struct device * cpu_dev,struct cpumask * cpumask)2057813dd6fSViresh Kumar int dev_pm_opp_get_sharing_cpus(struct device *cpu_dev, struct cpumask *cpumask)
2067813dd6fSViresh Kumar {
2077813dd6fSViresh Kumar struct opp_device *opp_dev;
2087813dd6fSViresh Kumar struct opp_table *opp_table;
2097813dd6fSViresh Kumar int ret = 0;
2107813dd6fSViresh Kumar
2117813dd6fSViresh Kumar opp_table = _find_opp_table(cpu_dev);
2127813dd6fSViresh Kumar if (IS_ERR(opp_table))
2137813dd6fSViresh Kumar return PTR_ERR(opp_table);
2147813dd6fSViresh Kumar
2157813dd6fSViresh Kumar if (opp_table->shared_opp == OPP_TABLE_ACCESS_UNKNOWN) {
2167813dd6fSViresh Kumar ret = -EINVAL;
2177813dd6fSViresh Kumar goto put_opp_table;
2187813dd6fSViresh Kumar }
2197813dd6fSViresh Kumar
2207813dd6fSViresh Kumar cpumask_clear(cpumask);
2217813dd6fSViresh Kumar
2227813dd6fSViresh Kumar if (opp_table->shared_opp == OPP_TABLE_ACCESS_SHARED) {
2233d255699SViresh Kumar mutex_lock(&opp_table->lock);
2247813dd6fSViresh Kumar list_for_each_entry(opp_dev, &opp_table->dev_list, node)
2257813dd6fSViresh Kumar cpumask_set_cpu(opp_dev->dev->id, cpumask);
2263d255699SViresh Kumar mutex_unlock(&opp_table->lock);
2277813dd6fSViresh Kumar } else {
2287813dd6fSViresh Kumar cpumask_set_cpu(cpu_dev->id, cpumask);
2297813dd6fSViresh Kumar }
2307813dd6fSViresh Kumar
2317813dd6fSViresh Kumar put_opp_table:
2327813dd6fSViresh Kumar dev_pm_opp_put_opp_table(opp_table);
2337813dd6fSViresh Kumar
2347813dd6fSViresh Kumar return ret;
2357813dd6fSViresh Kumar }
2367813dd6fSViresh Kumar EXPORT_SYMBOL_GPL(dev_pm_opp_get_sharing_cpus);
237