|
39 | 39 | #include <linux/rwsem.h> |
40 | 40 | #include <linux/wait.h> |
41 | 41 | #include <linux/topology.h> |
| 42 | +#include <linux/dmi.h> |
| 43 | +#include <linux/units.h> |
| 44 | +#include <asm/unaligned.h> |
42 | 45 |
|
43 | 46 | #include <acpi/cppc_acpi.h> |
44 | 47 |
|
@@ -1760,3 +1763,104 @@ unsigned int cppc_get_transition_latency(int cpu_num) |
1760 | 1763 | return latency_ns; |
1761 | 1764 | } |
1762 | 1765 | EXPORT_SYMBOL_GPL(cppc_get_transition_latency); |
| 1766 | + |
| 1767 | +/* Minimum struct length needed for the DMI processor entry we want */ |
| 1768 | +#define DMI_ENTRY_PROCESSOR_MIN_LENGTH 48 |
| 1769 | + |
| 1770 | +/* Offset in the DMI processor structure for the max frequency */ |
| 1771 | +#define DMI_PROCESSOR_MAX_SPEED 0x14 |
| 1772 | + |
| 1773 | +/* Callback function used to retrieve the max frequency from DMI */ |
| 1774 | +static void cppc_find_dmi_mhz(const struct dmi_header *dm, void *private) |
| 1775 | +{ |
| 1776 | + const u8 *dmi_data = (const u8 *)dm; |
| 1777 | + u16 *mhz = (u16 *)private; |
| 1778 | + |
| 1779 | + if (dm->type == DMI_ENTRY_PROCESSOR && |
| 1780 | + dm->length >= DMI_ENTRY_PROCESSOR_MIN_LENGTH) { |
| 1781 | + u16 val = (u16)get_unaligned((const u16 *) |
| 1782 | + (dmi_data + DMI_PROCESSOR_MAX_SPEED)); |
| 1783 | + *mhz = val > *mhz ? val : *mhz; |
| 1784 | + } |
| 1785 | +} |
| 1786 | + |
| 1787 | +/* Look up the max frequency in DMI */ |
| 1788 | +static u64 cppc_get_dmi_max_khz(void) |
| 1789 | +{ |
| 1790 | + u16 mhz = 0; |
| 1791 | + |
| 1792 | + dmi_walk(cppc_find_dmi_mhz, &mhz); |
| 1793 | + |
| 1794 | + /* |
| 1795 | + * Real stupid fallback value, just in case there is no |
| 1796 | + * actual value set. |
| 1797 | + */ |
| 1798 | + mhz = mhz ? mhz : 1; |
| 1799 | + |
| 1800 | + return KHZ_PER_MHZ * mhz; |
| 1801 | +} |
| 1802 | + |
| 1803 | +/* |
| 1804 | + * If CPPC lowest_freq and nominal_freq registers are exposed then we can |
| 1805 | + * use them to convert perf to freq and vice versa. The conversion is |
| 1806 | + * extrapolated as an affine function passing by the 2 points: |
| 1807 | + * - (Low perf, Low freq) |
| 1808 | + * - (Nominal perf, Nominal freq) |
| 1809 | + */ |
| 1810 | +unsigned int cppc_perf_to_khz(struct cppc_perf_caps *caps, unsigned int perf) |
| 1811 | +{ |
| 1812 | + s64 retval, offset = 0; |
| 1813 | + static u64 max_khz; |
| 1814 | + u64 mul, div; |
| 1815 | + |
| 1816 | + if (caps->lowest_freq && caps->nominal_freq) { |
| 1817 | + mul = caps->nominal_freq - caps->lowest_freq; |
| 1818 | + mul *= KHZ_PER_MHZ; |
| 1819 | + div = caps->nominal_perf - caps->lowest_perf; |
| 1820 | + offset = caps->nominal_freq * KHZ_PER_MHZ - |
| 1821 | + div64_u64(caps->nominal_perf * mul, div); |
| 1822 | + } else { |
| 1823 | + if (!max_khz) |
| 1824 | + max_khz = cppc_get_dmi_max_khz(); |
| 1825 | + mul = max_khz; |
| 1826 | + div = caps->highest_perf; |
| 1827 | + } |
| 1828 | + |
| 1829 | + retval = offset + div64_u64(perf * mul, div); |
| 1830 | + if (retval >= 0) |
| 1831 | + return retval; |
| 1832 | + return 0; |
| 1833 | +} |
| 1834 | +EXPORT_SYMBOL_GPL(cppc_perf_to_khz); |
| 1835 | + |
| 1836 | +unsigned int cppc_khz_to_perf(struct cppc_perf_caps *caps, unsigned int freq) |
| 1837 | +{ |
| 1838 | + s64 retval, offset = 0; |
| 1839 | + static u64 max_khz; |
| 1840 | + u64 mul, div; |
| 1841 | + |
| 1842 | + if (caps->lowest_freq && caps->nominal_freq) { |
| 1843 | + mul = caps->nominal_perf - caps->lowest_perf; |
| 1844 | + div = caps->nominal_freq - caps->lowest_freq; |
| 1845 | + /* |
| 1846 | + * We don't need to convert to kHz for computing offset and can |
| 1847 | + * directly use nominal_freq and lowest_freq as the div64_u64 |
| 1848 | + * will remove the frequency unit. |
| 1849 | + */ |
| 1850 | + offset = caps->nominal_perf - |
| 1851 | + div64_u64(caps->nominal_freq * mul, div); |
| 1852 | + /* But we need it for computing the perf level. */ |
| 1853 | + div *= KHZ_PER_MHZ; |
| 1854 | + } else { |
| 1855 | + if (!max_khz) |
| 1856 | + max_khz = cppc_get_dmi_max_khz(); |
| 1857 | + mul = caps->highest_perf; |
| 1858 | + div = max_khz; |
| 1859 | + } |
| 1860 | + |
| 1861 | + retval = offset + div64_u64(freq * mul, div); |
| 1862 | + if (retval >= 0) |
| 1863 | + return retval; |
| 1864 | + return 0; |
| 1865 | +} |
| 1866 | +EXPORT_SYMBOL_GPL(cppc_khz_to_perf); |
0 commit comments