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 int ti_cpufreq_probe(struct platform_device *pdev) 205 { 206 u32 version[VERSION_COUNT]; 207 struct device_node *np; 208 const struct of_device_id *match; 209 struct opp_table *ti_opp_table; 210 struct ti_cpufreq_data *opp_data; 211 const char * const reg_names[] = {"vdd", "vbb"}; 212 int ret; 213 214 np = of_find_node_by_path("/"); 215 match = of_match_node(ti_cpufreq_of_match, np); 216 of_node_put(np); 217 if (!match) 218 return -ENODEV; 219 220 opp_data = devm_kzalloc(&pdev->dev, sizeof(*opp_data), GFP_KERNEL); 221 if (!opp_data) 222 return -ENOMEM; 223 224 opp_data->soc_data = match->data; 225 226 opp_data->cpu_dev = get_cpu_device(0); 227 if (!opp_data->cpu_dev) { 228 pr_err("%s: Failed to get device for CPU0\n", __func__); 229 return -ENODEV; 230 } 231 232 opp_data->opp_node = dev_pm_opp_of_get_opp_desc_node(opp_data->cpu_dev); 233 if (!opp_data->opp_node) { 234 dev_info(opp_data->cpu_dev, 235 "OPP-v2 not supported, cpufreq-dt will attempt to use legacy tables.\n"); 236 goto register_cpufreq_dt; 237 } 238 239 ret = ti_cpufreq_setup_syscon_register(opp_data); 240 if (ret) 241 goto fail_put_node; 242 243 /* 244 * OPPs determine whether or not they are supported based on 245 * two metrics: 246 * 0 - SoC Revision 247 * 1 - eFuse value 248 */ 249 ret = ti_cpufreq_get_rev(opp_data, &version[0]); 250 if (ret) 251 goto fail_put_node; 252 253 ret = ti_cpufreq_get_efuse(opp_data, &version[1]); 254 if (ret) 255 goto fail_put_node; 256 257 ti_opp_table = dev_pm_opp_set_supported_hw(opp_data->cpu_dev, 258 version, VERSION_COUNT); 259 if (IS_ERR(ti_opp_table)) { 260 dev_err(opp_data->cpu_dev, 261 "Failed to set supported hardware\n"); 262 ret = PTR_ERR(ti_opp_table); 263 goto fail_put_node; 264 } 265 266 opp_data->opp_table = ti_opp_table; 267 268 if (opp_data->soc_data->multi_regulator) { 269 ti_opp_table = dev_pm_opp_set_regulators(opp_data->cpu_dev, 270 reg_names, 271 ARRAY_SIZE(reg_names)); 272 if (IS_ERR(ti_opp_table)) { 273 dev_pm_opp_put_supported_hw(opp_data->opp_table); 274 ret = PTR_ERR(ti_opp_table); 275 goto fail_put_node; 276 } 277 } 278 279 of_node_put(opp_data->opp_node); 280 register_cpufreq_dt: 281 platform_device_register_simple("cpufreq-dt", -1, NULL, 0); 282 283 return 0; 284 285 fail_put_node: 286 of_node_put(opp_data->opp_node); 287 288 return ret; 289 } 290 291 static int ti_cpufreq_init(void) 292 { 293 platform_device_register_simple("ti-cpufreq", -1, NULL, 0); 294 return 0; 295 } 296 module_init(ti_cpufreq_init); 297 298 static struct platform_driver ti_cpufreq_driver = { 299 .probe = ti_cpufreq_probe, 300 .driver = { 301 .name = "ti-cpufreq", 302 }, 303 }; 304 builtin_platform_driver(ti_cpufreq_driver); 305 306 MODULE_DESCRIPTION("TI CPUFreq/OPP hw-supported driver"); 307 MODULE_AUTHOR("Dave Gerlach <d-gerlach@ti.com>"); 308 MODULE_LICENSE("GPL v2"); 309