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