1 /* 2 * Match running platform with pre-defined OPP values for CPUFreq 3 * 4 * Author: Ajit Pal Singh <ajitpal.singh@st.com> 5 * Lee Jones <lee.jones@linaro.org> 6 * 7 * Copyright (C) 2015 STMicroelectronics (R&D) Limited 8 * 9 * This program is free software; you can redistribute it and/or modify 10 * it under the terms of the version 2 of the GNU General Public License as 11 * published by the Free Software Foundation 12 */ 13 14 #include <linux/cpu.h> 15 #include <linux/io.h> 16 #include <linux/mfd/syscon.h> 17 #include <linux/module.h> 18 #include <linux/of.h> 19 #include <linux/of_platform.h> 20 #include <linux/pm_opp.h> 21 #include <linux/regmap.h> 22 23 #define VERSION_ELEMENTS 3 24 #define MAX_PCODE_NAME_LEN 7 25 26 #define VERSION_SHIFT 28 27 #define HW_INFO_INDEX 1 28 #define MAJOR_ID_INDEX 1 29 #define MINOR_ID_INDEX 2 30 31 /* 32 * Only match on "suitable for ALL versions" entries 33 * 34 * This will be used with the BIT() macro. It sets the 35 * top bit of a 32bit value and is equal to 0x80000000. 36 */ 37 #define DEFAULT_VERSION 31 38 39 enum { 40 PCODE = 0, 41 SUBSTRATE, 42 DVFS_MAX_REGFIELDS, 43 }; 44 45 /** 46 * ST CPUFreq Driver Data 47 * 48 * @cpu_node CPU's OF node 49 * @syscfg_eng Engineering Syscon register map 50 * @regmap Syscon register map 51 */ 52 static struct sti_cpufreq_ddata { 53 struct device *cpu; 54 struct regmap *syscfg_eng; 55 struct regmap *syscfg; 56 } ddata; 57 58 static int sti_cpufreq_fetch_major(void) { 59 struct device_node *np = ddata.cpu->of_node; 60 struct device *dev = ddata.cpu; 61 unsigned int major_offset; 62 unsigned int socid; 63 int ret; 64 65 ret = of_property_read_u32_index(np, "st,syscfg", 66 MAJOR_ID_INDEX, &major_offset); 67 if (ret) { 68 dev_err(dev, "No major number offset provided in %s [%d]\n", 69 np->full_name, ret); 70 return ret; 71 } 72 73 ret = regmap_read(ddata.syscfg, major_offset, &socid); 74 if (ret) { 75 dev_err(dev, "Failed to read major number from syscon [%d]\n", 76 ret); 77 return ret; 78 } 79 80 return ((socid >> VERSION_SHIFT) & 0xf) + 1; 81 } 82 83 static int sti_cpufreq_fetch_minor(void) 84 { 85 struct device *dev = ddata.cpu; 86 struct device_node *np = dev->of_node; 87 unsigned int minor_offset; 88 unsigned int minid; 89 int ret; 90 91 ret = of_property_read_u32_index(np, "st,syscfg-eng", 92 MINOR_ID_INDEX, &minor_offset); 93 if (ret) { 94 dev_err(dev, 95 "No minor number offset provided %s [%d]\n", 96 np->full_name, ret); 97 return ret; 98 } 99 100 ret = regmap_read(ddata.syscfg_eng, minor_offset, &minid); 101 if (ret) { 102 dev_err(dev, 103 "Failed to read the minor number from syscon [%d]\n", 104 ret); 105 return ret; 106 } 107 108 return minid & 0xf; 109 } 110 111 static int sti_cpufreq_fetch_regmap_field(const struct reg_field *reg_fields, 112 int hw_info_offset, int field) 113 { 114 struct regmap_field *regmap_field; 115 struct reg_field reg_field = reg_fields[field]; 116 struct device *dev = ddata.cpu; 117 unsigned int value; 118 int ret; 119 120 reg_field.reg = hw_info_offset; 121 regmap_field = devm_regmap_field_alloc(dev, 122 ddata.syscfg_eng, 123 reg_field); 124 if (IS_ERR(regmap_field)) { 125 dev_err(dev, "Failed to allocate reg field\n"); 126 return PTR_ERR(regmap_field); 127 } 128 129 ret = regmap_field_read(regmap_field, &value); 130 if (ret) { 131 dev_err(dev, "Failed to read %s code\n", 132 field ? "SUBSTRATE" : "PCODE"); 133 return ret; 134 } 135 136 return value; 137 } 138 139 static const struct reg_field sti_stih407_dvfs_regfields[DVFS_MAX_REGFIELDS] = { 140 [PCODE] = REG_FIELD(0, 16, 19), 141 [SUBSTRATE] = REG_FIELD(0, 0, 2), 142 }; 143 144 static const struct reg_field *sti_cpufreq_match(void) 145 { 146 if (of_machine_is_compatible("st,stih407") || 147 of_machine_is_compatible("st,stih410")) 148 return sti_stih407_dvfs_regfields; 149 150 return NULL; 151 } 152 153 static int sti_cpufreq_set_opp_info(void) 154 { 155 struct device *dev = ddata.cpu; 156 struct device_node *np = dev->of_node; 157 const struct reg_field *reg_fields; 158 unsigned int hw_info_offset; 159 unsigned int version[VERSION_ELEMENTS]; 160 int pcode, substrate, major, minor; 161 int ret; 162 char name[MAX_PCODE_NAME_LEN]; 163 struct opp_table *opp_table; 164 165 reg_fields = sti_cpufreq_match(); 166 if (!reg_fields) { 167 dev_err(dev, "This SoC doesn't support voltage scaling\n"); 168 return -ENODEV; 169 } 170 171 ret = of_property_read_u32_index(np, "st,syscfg-eng", 172 HW_INFO_INDEX, &hw_info_offset); 173 if (ret) { 174 dev_warn(dev, "Failed to read HW info offset from DT\n"); 175 substrate = DEFAULT_VERSION; 176 pcode = 0; 177 goto use_defaults; 178 } 179 180 pcode = sti_cpufreq_fetch_regmap_field(reg_fields, 181 hw_info_offset, 182 PCODE); 183 if (pcode < 0) { 184 dev_warn(dev, "Failed to obtain process code\n"); 185 /* Use default pcode */ 186 pcode = 0; 187 } 188 189 substrate = sti_cpufreq_fetch_regmap_field(reg_fields, 190 hw_info_offset, 191 SUBSTRATE); 192 if (substrate) { 193 dev_warn(dev, "Failed to obtain substrate code\n"); 194 /* Use default substrate */ 195 substrate = DEFAULT_VERSION; 196 } 197 198 use_defaults: 199 major = sti_cpufreq_fetch_major(); 200 if (major < 0) { 201 dev_err(dev, "Failed to obtain major version\n"); 202 /* Use default major number */ 203 major = DEFAULT_VERSION; 204 } 205 206 minor = sti_cpufreq_fetch_minor(); 207 if (minor < 0) { 208 dev_err(dev, "Failed to obtain minor version\n"); 209 /* Use default minor number */ 210 minor = DEFAULT_VERSION; 211 } 212 213 snprintf(name, MAX_PCODE_NAME_LEN, "pcode%d", pcode); 214 215 opp_table = dev_pm_opp_set_prop_name(dev, name); 216 if (IS_ERR(opp_table)) { 217 dev_err(dev, "Failed to set prop name\n"); 218 return PTR_ERR(opp_table); 219 } 220 221 version[0] = BIT(major); 222 version[1] = BIT(minor); 223 version[2] = BIT(substrate); 224 225 opp_table = dev_pm_opp_set_supported_hw(dev, version, VERSION_ELEMENTS); 226 if (IS_ERR(opp_table)) { 227 dev_err(dev, "Failed to set supported hardware\n"); 228 return PTR_ERR(opp_table); 229 } 230 231 dev_dbg(dev, "pcode: %d major: %d minor: %d substrate: %d\n", 232 pcode, major, minor, substrate); 233 dev_dbg(dev, "version[0]: %x version[1]: %x version[2]: %x\n", 234 version[0], version[1], version[2]); 235 236 return 0; 237 } 238 239 static int sti_cpufreq_fetch_syscon_regsiters(void) 240 { 241 struct device *dev = ddata.cpu; 242 struct device_node *np = dev->of_node; 243 244 ddata.syscfg = syscon_regmap_lookup_by_phandle(np, "st,syscfg"); 245 if (IS_ERR(ddata.syscfg)) { 246 dev_err(dev, "\"st,syscfg\" not supplied\n"); 247 return PTR_ERR(ddata.syscfg); 248 } 249 250 ddata.syscfg_eng = syscon_regmap_lookup_by_phandle(np, "st,syscfg-eng"); 251 if (IS_ERR(ddata.syscfg_eng)) { 252 dev_err(dev, "\"st,syscfg-eng\" not supplied\n"); 253 return PTR_ERR(ddata.syscfg_eng); 254 } 255 256 return 0; 257 } 258 259 static int sti_cpufreq_init(void) 260 { 261 int ret; 262 263 if ((!of_machine_is_compatible("st,stih407")) && 264 (!of_machine_is_compatible("st,stih410"))) 265 return -ENODEV; 266 267 ddata.cpu = get_cpu_device(0); 268 if (!ddata.cpu) { 269 dev_err(ddata.cpu, "Failed to get device for CPU0\n"); 270 goto skip_voltage_scaling; 271 } 272 273 if (!of_get_property(ddata.cpu->of_node, "operating-points-v2", NULL)) { 274 dev_err(ddata.cpu, "OPP-v2 not supported\n"); 275 goto skip_voltage_scaling; 276 } 277 278 ret = sti_cpufreq_fetch_syscon_regsiters(); 279 if (ret) 280 goto skip_voltage_scaling; 281 282 ret = sti_cpufreq_set_opp_info(); 283 if (!ret) 284 goto register_cpufreq_dt; 285 286 skip_voltage_scaling: 287 dev_err(ddata.cpu, "Not doing voltage scaling\n"); 288 289 register_cpufreq_dt: 290 platform_device_register_simple("cpufreq-dt", -1, NULL, 0); 291 292 return 0; 293 } 294 module_init(sti_cpufreq_init); 295 296 MODULE_DESCRIPTION("STMicroelectronics CPUFreq/OPP driver"); 297 MODULE_AUTHOR("Ajitpal Singh <ajitpal.singh@st.com>"); 298 MODULE_AUTHOR("Lee Jones <lee.jones@linaro.org>"); 299 MODULE_LICENSE("GPL v2"); 300