1 // SPDX-License-Identifier: GPL-2.0+ 2 /* 3 * CPU frequency scaling support for Armada 37xx platform. 4 * 5 * Copyright (C) 2017 Marvell 6 * 7 * Gregory CLEMENT <gregory.clement@free-electrons.com> 8 */ 9 10 #include <linux/clk.h> 11 #include <linux/cpu.h> 12 #include <linux/cpufreq.h> 13 #include <linux/err.h> 14 #include <linux/interrupt.h> 15 #include <linux/io.h> 16 #include <linux/mfd/syscon.h> 17 #include <linux/module.h> 18 #include <linux/of_address.h> 19 #include <linux/of_device.h> 20 #include <linux/of_irq.h> 21 #include <linux/platform_device.h> 22 #include <linux/pm_opp.h> 23 #include <linux/regmap.h> 24 #include <linux/slab.h> 25 26 /* Power management in North Bridge register set */ 27 #define ARMADA_37XX_NB_L0L1 0x18 28 #define ARMADA_37XX_NB_L2L3 0x1C 29 #define ARMADA_37XX_NB_TBG_DIV_OFF 13 30 #define ARMADA_37XX_NB_TBG_DIV_MASK 0x7 31 #define ARMADA_37XX_NB_CLK_SEL_OFF 11 32 #define ARMADA_37XX_NB_CLK_SEL_MASK 0x1 33 #define ARMADA_37XX_NB_CLK_SEL_TBG 0x1 34 #define ARMADA_37XX_NB_TBG_SEL_OFF 9 35 #define ARMADA_37XX_NB_TBG_SEL_MASK 0x3 36 #define ARMADA_37XX_NB_VDD_SEL_OFF 6 37 #define ARMADA_37XX_NB_VDD_SEL_MASK 0x3 38 #define ARMADA_37XX_NB_CONFIG_SHIFT 16 39 #define ARMADA_37XX_NB_DYN_MOD 0x24 40 #define ARMADA_37XX_NB_CLK_SEL_EN BIT(26) 41 #define ARMADA_37XX_NB_TBG_EN BIT(28) 42 #define ARMADA_37XX_NB_DIV_EN BIT(29) 43 #define ARMADA_37XX_NB_VDD_EN BIT(30) 44 #define ARMADA_37XX_NB_DFS_EN BIT(31) 45 #define ARMADA_37XX_NB_CPU_LOAD 0x30 46 #define ARMADA_37XX_NB_CPU_LOAD_MASK 0x3 47 #define ARMADA_37XX_DVFS_LOAD_0 0 48 #define ARMADA_37XX_DVFS_LOAD_1 1 49 #define ARMADA_37XX_DVFS_LOAD_2 2 50 #define ARMADA_37XX_DVFS_LOAD_3 3 51 52 /* 53 * On Armada 37xx the Power management manages 4 level of CPU load, 54 * each level can be associated with a CPU clock source, a CPU 55 * divider, a VDD level, etc... 56 */ 57 #define LOAD_LEVEL_NR 4 58 59 struct armada_37xx_dvfs { 60 u32 cpu_freq_max; 61 u8 divider[LOAD_LEVEL_NR]; 62 }; 63 64 static struct armada_37xx_dvfs armada_37xx_dvfs[] = { 65 {.cpu_freq_max = 1200*1000*1000, .divider = {1, 2, 4, 6} }, 66 {.cpu_freq_max = 1000*1000*1000, .divider = {1, 2, 4, 5} }, 67 {.cpu_freq_max = 800*1000*1000, .divider = {1, 2, 3, 4} }, 68 {.cpu_freq_max = 600*1000*1000, .divider = {2, 4, 5, 6} }, 69 }; 70 71 static struct armada_37xx_dvfs *armada_37xx_cpu_freq_info_get(u32 freq) 72 { 73 int i; 74 75 for (i = 0; i < ARRAY_SIZE(armada_37xx_dvfs); i++) { 76 if (freq == armada_37xx_dvfs[i].cpu_freq_max) 77 return &armada_37xx_dvfs[i]; 78 } 79 80 pr_err("Unsupported CPU frequency %d MHz\n", freq/1000000); 81 return NULL; 82 } 83 84 /* 85 * Setup the four level managed by the hardware. Once the four level 86 * will be configured then the DVFS will be enabled. 87 */ 88 static void __init armada37xx_cpufreq_dvfs_setup(struct regmap *base, 89 struct clk *clk, u8 *divider) 90 { 91 int load_lvl; 92 struct clk *parent; 93 94 for (load_lvl = 0; load_lvl < LOAD_LEVEL_NR; load_lvl++) { 95 unsigned int reg, mask, val, offset = 0; 96 97 if (load_lvl <= ARMADA_37XX_DVFS_LOAD_1) 98 reg = ARMADA_37XX_NB_L0L1; 99 else 100 reg = ARMADA_37XX_NB_L2L3; 101 102 if (load_lvl == ARMADA_37XX_DVFS_LOAD_0 || 103 load_lvl == ARMADA_37XX_DVFS_LOAD_2) 104 offset += ARMADA_37XX_NB_CONFIG_SHIFT; 105 106 /* Set cpu clock source, for all the level we use TBG */ 107 val = ARMADA_37XX_NB_CLK_SEL_TBG << ARMADA_37XX_NB_CLK_SEL_OFF; 108 mask = (ARMADA_37XX_NB_CLK_SEL_MASK 109 << ARMADA_37XX_NB_CLK_SEL_OFF); 110 111 /* 112 * Set cpu divider based on the pre-computed array in 113 * order to have balanced step. 114 */ 115 val |= divider[load_lvl] << ARMADA_37XX_NB_TBG_DIV_OFF; 116 mask |= (ARMADA_37XX_NB_TBG_DIV_MASK 117 << ARMADA_37XX_NB_TBG_DIV_OFF); 118 119 /* Set VDD divider which is actually the load level. */ 120 val |= load_lvl << ARMADA_37XX_NB_VDD_SEL_OFF; 121 mask |= (ARMADA_37XX_NB_VDD_SEL_MASK 122 << ARMADA_37XX_NB_VDD_SEL_OFF); 123 124 val <<= offset; 125 mask <<= offset; 126 127 regmap_update_bits(base, reg, mask, val); 128 } 129 130 /* 131 * Set cpu clock source, for all the level we keep the same 132 * clock source that the one already configured. For this one 133 * we need to use the clock framework 134 */ 135 parent = clk_get_parent(clk); 136 clk_set_parent(clk, parent); 137 } 138 139 static void __init armada37xx_cpufreq_disable_dvfs(struct regmap *base) 140 { 141 unsigned int reg = ARMADA_37XX_NB_DYN_MOD, 142 mask = ARMADA_37XX_NB_DFS_EN; 143 144 regmap_update_bits(base, reg, mask, 0); 145 } 146 147 static void __init armada37xx_cpufreq_enable_dvfs(struct regmap *base) 148 { 149 unsigned int val, reg = ARMADA_37XX_NB_CPU_LOAD, 150 mask = ARMADA_37XX_NB_CPU_LOAD_MASK; 151 152 /* Start with the highest load (0) */ 153 val = ARMADA_37XX_DVFS_LOAD_0; 154 regmap_update_bits(base, reg, mask, val); 155 156 /* Now enable DVFS for the CPUs */ 157 reg = ARMADA_37XX_NB_DYN_MOD; 158 mask = ARMADA_37XX_NB_CLK_SEL_EN | ARMADA_37XX_NB_TBG_EN | 159 ARMADA_37XX_NB_DIV_EN | ARMADA_37XX_NB_VDD_EN | 160 ARMADA_37XX_NB_DFS_EN; 161 162 regmap_update_bits(base, reg, mask, mask); 163 } 164 165 static int __init armada37xx_cpufreq_driver_init(void) 166 { 167 struct armada_37xx_dvfs *dvfs; 168 struct platform_device *pdev; 169 unsigned int cur_frequency; 170 struct regmap *nb_pm_base; 171 struct device *cpu_dev; 172 int load_lvl, ret; 173 struct clk *clk; 174 175 nb_pm_base = 176 syscon_regmap_lookup_by_compatible("marvell,armada-3700-nb-pm"); 177 178 if (IS_ERR(nb_pm_base)) 179 return -ENODEV; 180 181 /* Before doing any configuration on the DVFS first, disable it */ 182 armada37xx_cpufreq_disable_dvfs(nb_pm_base); 183 184 /* 185 * On CPU 0 register the operating points supported (which are 186 * the nominal CPU frequency and full integer divisions of 187 * it). 188 */ 189 cpu_dev = get_cpu_device(0); 190 if (!cpu_dev) { 191 dev_err(cpu_dev, "Cannot get CPU\n"); 192 return -ENODEV; 193 } 194 195 clk = clk_get(cpu_dev, 0); 196 if (IS_ERR(clk)) { 197 dev_err(cpu_dev, "Cannot get clock for CPU0\n"); 198 return PTR_ERR(clk); 199 } 200 201 /* Get nominal (current) CPU frequency */ 202 cur_frequency = clk_get_rate(clk); 203 if (!cur_frequency) { 204 dev_err(cpu_dev, "Failed to get clock rate for CPU\n"); 205 return -EINVAL; 206 } 207 208 dvfs = armada_37xx_cpu_freq_info_get(cur_frequency); 209 if (!dvfs) 210 return -EINVAL; 211 212 armada37xx_cpufreq_dvfs_setup(nb_pm_base, clk, dvfs->divider); 213 214 for (load_lvl = ARMADA_37XX_DVFS_LOAD_0; load_lvl < LOAD_LEVEL_NR; 215 load_lvl++) { 216 unsigned long freq = cur_frequency / dvfs->divider[load_lvl]; 217 218 ret = dev_pm_opp_add(cpu_dev, freq, 0); 219 if (ret) { 220 /* clean-up the already added opp before leaving */ 221 while (load_lvl-- > ARMADA_37XX_DVFS_LOAD_0) { 222 freq = cur_frequency / dvfs->divider[load_lvl]; 223 dev_pm_opp_remove(cpu_dev, freq); 224 } 225 return ret; 226 } 227 } 228 229 /* Now that everything is setup, enable the DVFS at hardware level */ 230 armada37xx_cpufreq_enable_dvfs(nb_pm_base); 231 232 pdev = platform_device_register_simple("cpufreq-dt", -1, NULL, 0); 233 234 return PTR_ERR_OR_ZERO(pdev); 235 } 236 /* late_initcall, to guarantee the driver is loaded after A37xx clock driver */ 237 late_initcall(armada37xx_cpufreq_driver_init); 238 239 MODULE_AUTHOR("Gregory CLEMENT <gregory.clement@free-electrons.com>"); 240 MODULE_DESCRIPTION("Armada 37xx cpufreq driver"); 241 MODULE_LICENSE("GPL"); 242