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/init.h> 21 #include <linux/of.h> 22 #include <linux/of_platform.h> 23 #include <linux/pm_opp.h> 24 #include <linux/regmap.h> 25 #include <linux/slab.h> 26 27 #define REVISION_MASK 0xF 28 #define REVISION_SHIFT 28 29 30 #define AM33XX_800M_ARM_MPU_MAX_FREQ 0x1E2F 31 #define AM43XX_600M_ARM_MPU_MAX_FREQ 0xFFA 32 33 #define DRA7_EFUSE_HAS_OD_MPU_OPP 11 34 #define DRA7_EFUSE_HAS_HIGH_MPU_OPP 15 35 #define DRA7_EFUSE_HAS_ALL_MPU_OPP 23 36 37 #define DRA7_EFUSE_NOM_MPU_OPP BIT(0) 38 #define DRA7_EFUSE_OD_MPU_OPP BIT(1) 39 #define DRA7_EFUSE_HIGH_MPU_OPP BIT(2) 40 41 #define VERSION_COUNT 2 42 43 struct ti_cpufreq_data; 44 45 struct ti_cpufreq_soc_data { 46 unsigned long (*efuse_xlate)(struct ti_cpufreq_data *opp_data, 47 unsigned long efuse); 48 unsigned long efuse_fallback; 49 unsigned long efuse_offset; 50 unsigned long efuse_mask; 51 unsigned long efuse_shift; 52 unsigned long rev_offset; 53 }; 54 55 struct ti_cpufreq_data { 56 struct device *cpu_dev; 57 struct device_node *opp_node; 58 struct regmap *syscon; 59 const struct ti_cpufreq_soc_data *soc_data; 60 }; 61 62 static unsigned long amx3_efuse_xlate(struct ti_cpufreq_data *opp_data, 63 unsigned long efuse) 64 { 65 if (!efuse) 66 efuse = opp_data->soc_data->efuse_fallback; 67 /* AM335x and AM437x use "OPP disable" bits, so invert */ 68 return ~efuse; 69 } 70 71 static unsigned long dra7_efuse_xlate(struct ti_cpufreq_data *opp_data, 72 unsigned long efuse) 73 { 74 unsigned long calculated_efuse = DRA7_EFUSE_NOM_MPU_OPP; 75 76 /* 77 * The efuse on dra7 and am57 parts contains a specific 78 * value indicating the highest available OPP. 79 */ 80 81 switch (efuse) { 82 case DRA7_EFUSE_HAS_ALL_MPU_OPP: 83 case DRA7_EFUSE_HAS_HIGH_MPU_OPP: 84 calculated_efuse |= DRA7_EFUSE_HIGH_MPU_OPP; 85 case DRA7_EFUSE_HAS_OD_MPU_OPP: 86 calculated_efuse |= DRA7_EFUSE_OD_MPU_OPP; 87 } 88 89 return calculated_efuse; 90 } 91 92 static struct ti_cpufreq_soc_data am3x_soc_data = { 93 .efuse_xlate = amx3_efuse_xlate, 94 .efuse_fallback = AM33XX_800M_ARM_MPU_MAX_FREQ, 95 .efuse_offset = 0x07fc, 96 .efuse_mask = 0x1fff, 97 .rev_offset = 0x600, 98 }; 99 100 static struct ti_cpufreq_soc_data am4x_soc_data = { 101 .efuse_xlate = amx3_efuse_xlate, 102 .efuse_fallback = AM43XX_600M_ARM_MPU_MAX_FREQ, 103 .efuse_offset = 0x0610, 104 .efuse_mask = 0x3f, 105 .rev_offset = 0x600, 106 }; 107 108 static struct ti_cpufreq_soc_data dra7_soc_data = { 109 .efuse_xlate = dra7_efuse_xlate, 110 .efuse_offset = 0x020c, 111 .efuse_mask = 0xf80000, 112 .efuse_shift = 19, 113 .rev_offset = 0x204, 114 }; 115 116 /** 117 * ti_cpufreq_get_efuse() - Parse and return efuse value present on SoC 118 * @opp_data: pointer to ti_cpufreq_data context 119 * @efuse_value: Set to the value parsed from efuse 120 * 121 * Returns error code if efuse not read properly. 122 */ 123 static int ti_cpufreq_get_efuse(struct ti_cpufreq_data *opp_data, 124 u32 *efuse_value) 125 { 126 struct device *dev = opp_data->cpu_dev; 127 u32 efuse; 128 int ret; 129 130 ret = regmap_read(opp_data->syscon, opp_data->soc_data->efuse_offset, 131 &efuse); 132 if (ret) { 133 dev_err(dev, 134 "Failed to read the efuse value from syscon: %d\n", 135 ret); 136 return ret; 137 } 138 139 efuse = (efuse & opp_data->soc_data->efuse_mask); 140 efuse >>= opp_data->soc_data->efuse_shift; 141 142 *efuse_value = opp_data->soc_data->efuse_xlate(opp_data, efuse); 143 144 return 0; 145 } 146 147 /** 148 * ti_cpufreq_get_rev() - Parse and return rev value present on SoC 149 * @opp_data: pointer to ti_cpufreq_data context 150 * @revision_value: Set to the value parsed from revision register 151 * 152 * Returns error code if revision not read properly. 153 */ 154 static int ti_cpufreq_get_rev(struct ti_cpufreq_data *opp_data, 155 u32 *revision_value) 156 { 157 struct device *dev = opp_data->cpu_dev; 158 u32 revision; 159 int ret; 160 161 ret = regmap_read(opp_data->syscon, opp_data->soc_data->rev_offset, 162 &revision); 163 if (ret) { 164 dev_err(dev, 165 "Failed to read the revision number from syscon: %d\n", 166 ret); 167 return ret; 168 } 169 170 *revision_value = BIT((revision >> REVISION_SHIFT) & REVISION_MASK); 171 172 return 0; 173 } 174 175 static int ti_cpufreq_setup_syscon_register(struct ti_cpufreq_data *opp_data) 176 { 177 struct device *dev = opp_data->cpu_dev; 178 struct device_node *np = opp_data->opp_node; 179 180 opp_data->syscon = syscon_regmap_lookup_by_phandle(np, 181 "syscon"); 182 if (IS_ERR(opp_data->syscon)) { 183 dev_err(dev, 184 "\"syscon\" is missing, cannot use OPPv2 table.\n"); 185 return PTR_ERR(opp_data->syscon); 186 } 187 188 return 0; 189 } 190 191 static const struct of_device_id ti_cpufreq_of_match[] = { 192 { .compatible = "ti,am33xx", .data = &am3x_soc_data, }, 193 { .compatible = "ti,am43", .data = &am4x_soc_data, }, 194 { .compatible = "ti,dra7", .data = &dra7_soc_data }, 195 {}, 196 }; 197 198 static int ti_cpufreq_init(void) 199 { 200 u32 version[VERSION_COUNT]; 201 struct device_node *np; 202 const struct of_device_id *match; 203 struct ti_cpufreq_data *opp_data; 204 int ret; 205 206 np = of_find_node_by_path("/"); 207 match = of_match_node(ti_cpufreq_of_match, np); 208 if (!match) 209 return -ENODEV; 210 211 opp_data = kzalloc(sizeof(*opp_data), GFP_KERNEL); 212 if (!opp_data) 213 return -ENOMEM; 214 215 opp_data->soc_data = match->data; 216 217 opp_data->cpu_dev = get_cpu_device(0); 218 if (!opp_data->cpu_dev) { 219 pr_err("%s: Failed to get device for CPU0\n", __func__); 220 return -ENODEV; 221 } 222 223 opp_data->opp_node = dev_pm_opp_of_get_opp_desc_node(opp_data->cpu_dev); 224 if (!opp_data->opp_node) { 225 dev_info(opp_data->cpu_dev, 226 "OPP-v2 not supported, cpufreq-dt will attempt to use legacy tables.\n"); 227 goto register_cpufreq_dt; 228 } 229 230 ret = ti_cpufreq_setup_syscon_register(opp_data); 231 if (ret) 232 goto fail_put_node; 233 234 /* 235 * OPPs determine whether or not they are supported based on 236 * two metrics: 237 * 0 - SoC Revision 238 * 1 - eFuse value 239 */ 240 ret = ti_cpufreq_get_rev(opp_data, &version[0]); 241 if (ret) 242 goto fail_put_node; 243 244 ret = ti_cpufreq_get_efuse(opp_data, &version[1]); 245 if (ret) 246 goto fail_put_node; 247 248 ret = PTR_ERR_OR_ZERO(dev_pm_opp_set_supported_hw(opp_data->cpu_dev, 249 version, VERSION_COUNT)); 250 if (ret) { 251 dev_err(opp_data->cpu_dev, 252 "Failed to set supported hardware\n"); 253 goto fail_put_node; 254 } 255 256 of_node_put(opp_data->opp_node); 257 258 register_cpufreq_dt: 259 platform_device_register_simple("cpufreq-dt", -1, NULL, 0); 260 261 return 0; 262 263 fail_put_node: 264 of_node_put(opp_data->opp_node); 265 266 return ret; 267 } 268 device_initcall(ti_cpufreq_init); 269