1 /* 2 * TI CPUFreq/OPP hw-supported driver 3 * 4 * Copyright (C) 2016-2017 Texas Instruments, Inc. 5 * Dave Gerlach <d-gerlach@ti.com> 6 * 7 * This program is free software; you can redistribute it and/or 8 * modify it under the terms of the GNU General Public License 9 * version 2 as published by the Free Software Foundation. 10 * 11 * This program is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU General Public License for more details. 15 */ 16 17 #include <linux/cpu.h> 18 #include <linux/io.h> 19 #include <linux/mfd/syscon.h> 20 #include <linux/module.h> 21 #include <linux/init.h> 22 #include <linux/of.h> 23 #include <linux/of_platform.h> 24 #include <linux/pm_opp.h> 25 #include <linux/regmap.h> 26 #include <linux/slab.h> 27 28 #define REVISION_MASK 0xF 29 #define REVISION_SHIFT 28 30 31 #define AM33XX_800M_ARM_MPU_MAX_FREQ 0x1E2F 32 #define AM43XX_600M_ARM_MPU_MAX_FREQ 0xFFA 33 34 #define DRA7_EFUSE_HAS_OD_MPU_OPP 11 35 #define DRA7_EFUSE_HAS_HIGH_MPU_OPP 15 36 #define DRA7_EFUSE_HAS_ALL_MPU_OPP 23 37 38 #define DRA7_EFUSE_NOM_MPU_OPP BIT(0) 39 #define DRA7_EFUSE_OD_MPU_OPP BIT(1) 40 #define DRA7_EFUSE_HIGH_MPU_OPP BIT(2) 41 42 #define VERSION_COUNT 2 43 44 struct ti_cpufreq_data; 45 46 struct ti_cpufreq_soc_data { 47 unsigned long (*efuse_xlate)(struct ti_cpufreq_data *opp_data, 48 unsigned long efuse); 49 unsigned long efuse_fallback; 50 unsigned long efuse_offset; 51 unsigned long efuse_mask; 52 unsigned long efuse_shift; 53 unsigned long rev_offset; 54 bool multi_regulator; 55 }; 56 57 struct ti_cpufreq_data { 58 struct device *cpu_dev; 59 struct device_node *opp_node; 60 struct regmap *syscon; 61 const struct ti_cpufreq_soc_data *soc_data; 62 struct opp_table *opp_table; 63 }; 64 65 static unsigned long amx3_efuse_xlate(struct ti_cpufreq_data *opp_data, 66 unsigned long efuse) 67 { 68 if (!efuse) 69 efuse = opp_data->soc_data->efuse_fallback; 70 /* AM335x and AM437x use "OPP disable" bits, so invert */ 71 return ~efuse; 72 } 73 74 static unsigned long dra7_efuse_xlate(struct ti_cpufreq_data *opp_data, 75 unsigned long efuse) 76 { 77 unsigned long calculated_efuse = DRA7_EFUSE_NOM_MPU_OPP; 78 79 /* 80 * The efuse on dra7 and am57 parts contains a specific 81 * value indicating the highest available OPP. 82 */ 83 84 switch (efuse) { 85 case DRA7_EFUSE_HAS_ALL_MPU_OPP: 86 case DRA7_EFUSE_HAS_HIGH_MPU_OPP: 87 calculated_efuse |= DRA7_EFUSE_HIGH_MPU_OPP; 88 case DRA7_EFUSE_HAS_OD_MPU_OPP: 89 calculated_efuse |= DRA7_EFUSE_OD_MPU_OPP; 90 } 91 92 return calculated_efuse; 93 } 94 95 static struct ti_cpufreq_soc_data am3x_soc_data = { 96 .efuse_xlate = amx3_efuse_xlate, 97 .efuse_fallback = AM33XX_800M_ARM_MPU_MAX_FREQ, 98 .efuse_offset = 0x07fc, 99 .efuse_mask = 0x1fff, 100 .rev_offset = 0x600, 101 .multi_regulator = false, 102 }; 103 104 static struct ti_cpufreq_soc_data am4x_soc_data = { 105 .efuse_xlate = amx3_efuse_xlate, 106 .efuse_fallback = AM43XX_600M_ARM_MPU_MAX_FREQ, 107 .efuse_offset = 0x0610, 108 .efuse_mask = 0x3f, 109 .rev_offset = 0x600, 110 .multi_regulator = false, 111 }; 112 113 static struct ti_cpufreq_soc_data dra7_soc_data = { 114 .efuse_xlate = dra7_efuse_xlate, 115 .efuse_offset = 0x020c, 116 .efuse_mask = 0xf80000, 117 .efuse_shift = 19, 118 .rev_offset = 0x204, 119 .multi_regulator = true, 120 }; 121 122 /** 123 * ti_cpufreq_get_efuse() - Parse and return efuse value present on SoC 124 * @opp_data: pointer to ti_cpufreq_data context 125 * @efuse_value: Set to the value parsed from efuse 126 * 127 * Returns error code if efuse not read properly. 128 */ 129 static int ti_cpufreq_get_efuse(struct ti_cpufreq_data *opp_data, 130 u32 *efuse_value) 131 { 132 struct device *dev = opp_data->cpu_dev; 133 u32 efuse; 134 int ret; 135 136 ret = regmap_read(opp_data->syscon, opp_data->soc_data->efuse_offset, 137 &efuse); 138 if (ret) { 139 dev_err(dev, 140 "Failed to read the efuse value from syscon: %d\n", 141 ret); 142 return ret; 143 } 144 145 efuse = (efuse & opp_data->soc_data->efuse_mask); 146 efuse >>= opp_data->soc_data->efuse_shift; 147 148 *efuse_value = opp_data->soc_data->efuse_xlate(opp_data, efuse); 149 150 return 0; 151 } 152 153 /** 154 * ti_cpufreq_get_rev() - Parse and return rev value present on SoC 155 * @opp_data: pointer to ti_cpufreq_data context 156 * @revision_value: Set to the value parsed from revision register 157 * 158 * Returns error code if revision not read properly. 159 */ 160 static int ti_cpufreq_get_rev(struct ti_cpufreq_data *opp_data, 161 u32 *revision_value) 162 { 163 struct device *dev = opp_data->cpu_dev; 164 u32 revision; 165 int ret; 166 167 ret = regmap_read(opp_data->syscon, opp_data->soc_data->rev_offset, 168 &revision); 169 if (ret) { 170 dev_err(dev, 171 "Failed to read the revision number from syscon: %d\n", 172 ret); 173 return ret; 174 } 175 176 *revision_value = BIT((revision >> REVISION_SHIFT) & REVISION_MASK); 177 178 return 0; 179 } 180 181 static int ti_cpufreq_setup_syscon_register(struct ti_cpufreq_data *opp_data) 182 { 183 struct device *dev = opp_data->cpu_dev; 184 struct device_node *np = opp_data->opp_node; 185 186 opp_data->syscon = syscon_regmap_lookup_by_phandle(np, 187 "syscon"); 188 if (IS_ERR(opp_data->syscon)) { 189 dev_err(dev, 190 "\"syscon\" is missing, cannot use OPPv2 table.\n"); 191 return PTR_ERR(opp_data->syscon); 192 } 193 194 return 0; 195 } 196 197 static const struct of_device_id ti_cpufreq_of_match[] = { 198 { .compatible = "ti,am33xx", .data = &am3x_soc_data, }, 199 { .compatible = "ti,am43", .data = &am4x_soc_data, }, 200 { .compatible = "ti,dra7", .data = &dra7_soc_data }, 201 {}, 202 }; 203 204 static const struct of_device_id *ti_cpufreq_match_node(void) 205 { 206 struct device_node *np; 207 const struct of_device_id *match; 208 209 np = of_find_node_by_path("/"); 210 match = of_match_node(ti_cpufreq_of_match, np); 211 of_node_put(np); 212 213 return match; 214 } 215 216 static int ti_cpufreq_probe(struct platform_device *pdev) 217 { 218 u32 version[VERSION_COUNT]; 219 const struct of_device_id *match; 220 struct opp_table *ti_opp_table; 221 struct ti_cpufreq_data *opp_data; 222 const char * const reg_names[] = {"vdd", "vbb"}; 223 int ret; 224 225 match = dev_get_platdata(&pdev->dev); 226 if (!match) 227 return -ENODEV; 228 229 opp_data = devm_kzalloc(&pdev->dev, sizeof(*opp_data), GFP_KERNEL); 230 if (!opp_data) 231 return -ENOMEM; 232 233 opp_data->soc_data = match->data; 234 235 opp_data->cpu_dev = get_cpu_device(0); 236 if (!opp_data->cpu_dev) { 237 pr_err("%s: Failed to get device for CPU0\n", __func__); 238 return -ENODEV; 239 } 240 241 opp_data->opp_node = dev_pm_opp_of_get_opp_desc_node(opp_data->cpu_dev); 242 if (!opp_data->opp_node) { 243 dev_info(opp_data->cpu_dev, 244 "OPP-v2 not supported, cpufreq-dt will attempt to use legacy tables.\n"); 245 goto register_cpufreq_dt; 246 } 247 248 ret = ti_cpufreq_setup_syscon_register(opp_data); 249 if (ret) 250 goto fail_put_node; 251 252 /* 253 * OPPs determine whether or not they are supported based on 254 * two metrics: 255 * 0 - SoC Revision 256 * 1 - eFuse value 257 */ 258 ret = ti_cpufreq_get_rev(opp_data, &version[0]); 259 if (ret) 260 goto fail_put_node; 261 262 ret = ti_cpufreq_get_efuse(opp_data, &version[1]); 263 if (ret) 264 goto fail_put_node; 265 266 ti_opp_table = dev_pm_opp_set_supported_hw(opp_data->cpu_dev, 267 version, VERSION_COUNT); 268 if (IS_ERR(ti_opp_table)) { 269 dev_err(opp_data->cpu_dev, 270 "Failed to set supported hardware\n"); 271 ret = PTR_ERR(ti_opp_table); 272 goto fail_put_node; 273 } 274 275 opp_data->opp_table = ti_opp_table; 276 277 if (opp_data->soc_data->multi_regulator) { 278 ti_opp_table = dev_pm_opp_set_regulators(opp_data->cpu_dev, 279 reg_names, 280 ARRAY_SIZE(reg_names)); 281 if (IS_ERR(ti_opp_table)) { 282 dev_pm_opp_put_supported_hw(opp_data->opp_table); 283 ret = PTR_ERR(ti_opp_table); 284 goto fail_put_node; 285 } 286 } 287 288 of_node_put(opp_data->opp_node); 289 register_cpufreq_dt: 290 platform_device_register_simple("cpufreq-dt", -1, NULL, 0); 291 292 return 0; 293 294 fail_put_node: 295 of_node_put(opp_data->opp_node); 296 297 return ret; 298 } 299 300 static int ti_cpufreq_init(void) 301 { 302 const struct of_device_id *match; 303 304 /* Check to ensure we are on a compatible platform */ 305 match = ti_cpufreq_match_node(); 306 if (match) 307 platform_device_register_data(NULL, "ti-cpufreq", -1, match, 308 sizeof(*match)); 309 310 return 0; 311 } 312 module_init(ti_cpufreq_init); 313 314 static struct platform_driver ti_cpufreq_driver = { 315 .probe = ti_cpufreq_probe, 316 .driver = { 317 .name = "ti-cpufreq", 318 }, 319 }; 320 builtin_platform_driver(ti_cpufreq_driver); 321 322 MODULE_DESCRIPTION("TI CPUFreq/OPP hw-supported driver"); 323 MODULE_AUTHOR("Dave Gerlach <d-gerlach@ti.com>"); 324 MODULE_LICENSE("GPL v2"); 325