xref: /openbmc/linux/drivers/opp/cpu.c (revision c900529f3d9161bfde5cca0754f83b4d3c3e0220)
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