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