Thomas Gleixner | 2025cf9 | 2019-05-29 07:18:02 -0700 | [diff] [blame] | 1 | // SPDX-License-Identifier: GPL-2.0-only |
Mikko Perttunen | 939dc6f | 2017-04-11 11:09:15 +0300 | [diff] [blame] | 2 | /* |
| 3 | * Copyright (c) 2017, NVIDIA CORPORATION. All rights reserved |
Mikko Perttunen | 939dc6f | 2017-04-11 11:09:15 +0300 | [diff] [blame] | 4 | */ |
| 5 | |
| 6 | #include <linux/cpufreq.h> |
| 7 | #include <linux/dma-mapping.h> |
| 8 | #include <linux/module.h> |
| 9 | #include <linux/of.h> |
| 10 | #include <linux/platform_device.h> |
| 11 | |
| 12 | #include <soc/tegra/bpmp.h> |
| 13 | #include <soc/tegra/bpmp-abi.h> |
| 14 | |
Jon Hunter | cfef4bc | 2020-12-02 09:14:17 +0000 | [diff] [blame] | 15 | #define TEGRA186_NUM_CLUSTERS 2 |
Jon Hunter | b7b4e78 | 2020-12-02 09:14:16 +0000 | [diff] [blame] | 16 | #define EDVD_OFFSET_A57(core) ((SZ_64K * 6) + (0x20 + (core) * 0x4)) |
| 17 | #define EDVD_OFFSET_DENVER(core) ((SZ_64K * 7) + (0x20 + (core) * 0x4)) |
| 18 | #define EDVD_CORE_VOLT_FREQ_F_SHIFT 0 |
| 19 | #define EDVD_CORE_VOLT_FREQ_F_MASK 0xffff |
| 20 | #define EDVD_CORE_VOLT_FREQ_V_SHIFT 16 |
| 21 | |
| 22 | struct tegra186_cpufreq_cpu { |
Jon Hunter | cfef4bc | 2020-12-02 09:14:17 +0000 | [diff] [blame] | 23 | unsigned int bpmp_cluster_id; |
Jon Hunter | b7b4e78 | 2020-12-02 09:14:16 +0000 | [diff] [blame] | 24 | unsigned int edvd_offset; |
| 25 | }; |
| 26 | |
| 27 | static const struct tegra186_cpufreq_cpu tegra186_cpus[] = { |
| 28 | /* CPU0 - A57 Cluster */ |
| 29 | { |
Jon Hunter | cfef4bc | 2020-12-02 09:14:17 +0000 | [diff] [blame] | 30 | .bpmp_cluster_id = 1, |
Jon Hunter | b7b4e78 | 2020-12-02 09:14:16 +0000 | [diff] [blame] | 31 | .edvd_offset = EDVD_OFFSET_A57(0) |
| 32 | }, |
| 33 | /* CPU1 - Denver Cluster */ |
| 34 | { |
Jon Hunter | cfef4bc | 2020-12-02 09:14:17 +0000 | [diff] [blame] | 35 | .bpmp_cluster_id = 0, |
Jon Hunter | b7b4e78 | 2020-12-02 09:14:16 +0000 | [diff] [blame] | 36 | .edvd_offset = EDVD_OFFSET_DENVER(0) |
| 37 | }, |
| 38 | /* CPU2 - Denver Cluster */ |
| 39 | { |
Jon Hunter | cfef4bc | 2020-12-02 09:14:17 +0000 | [diff] [blame] | 40 | .bpmp_cluster_id = 0, |
Jon Hunter | b7b4e78 | 2020-12-02 09:14:16 +0000 | [diff] [blame] | 41 | .edvd_offset = EDVD_OFFSET_DENVER(1) |
| 42 | }, |
| 43 | /* CPU3 - A57 Cluster */ |
| 44 | { |
Jon Hunter | cfef4bc | 2020-12-02 09:14:17 +0000 | [diff] [blame] | 45 | .bpmp_cluster_id = 1, |
Jon Hunter | b7b4e78 | 2020-12-02 09:14:16 +0000 | [diff] [blame] | 46 | .edvd_offset = EDVD_OFFSET_A57(1) |
| 47 | }, |
| 48 | /* CPU4 - A57 Cluster */ |
| 49 | { |
Jon Hunter | cfef4bc | 2020-12-02 09:14:17 +0000 | [diff] [blame] | 50 | .bpmp_cluster_id = 1, |
Jon Hunter | b7b4e78 | 2020-12-02 09:14:16 +0000 | [diff] [blame] | 51 | .edvd_offset = EDVD_OFFSET_A57(2) |
| 52 | }, |
| 53 | /* CPU5 - A57 Cluster */ |
| 54 | { |
Mikko Perttunen | 939dc6f | 2017-04-11 11:09:15 +0300 | [diff] [blame] | 55 | .bpmp_cluster_id = 1, |
Jon Hunter | cfef4bc | 2020-12-02 09:14:17 +0000 | [diff] [blame] | 56 | .edvd_offset = EDVD_OFFSET_A57(3) |
Mikko Perttunen | 939dc6f | 2017-04-11 11:09:15 +0300 | [diff] [blame] | 57 | }, |
| 58 | }; |
| 59 | |
| 60 | struct tegra186_cpufreq_cluster { |
Mikko Perttunen | 939dc6f | 2017-04-11 11:09:15 +0300 | [diff] [blame] | 61 | struct cpufreq_frequency_table *table; |
Jon Hunter | e010d1d | 2020-11-03 11:55:14 +0000 | [diff] [blame] | 62 | u32 ref_clk_khz; |
| 63 | u32 div; |
Mikko Perttunen | 939dc6f | 2017-04-11 11:09:15 +0300 | [diff] [blame] | 64 | }; |
| 65 | |
| 66 | struct tegra186_cpufreq_data { |
| 67 | void __iomem *regs; |
Mikko Perttunen | 939dc6f | 2017-04-11 11:09:15 +0300 | [diff] [blame] | 68 | struct tegra186_cpufreq_cluster *clusters; |
Jon Hunter | b7b4e78 | 2020-12-02 09:14:16 +0000 | [diff] [blame] | 69 | const struct tegra186_cpufreq_cpu *cpus; |
Mikko Perttunen | 939dc6f | 2017-04-11 11:09:15 +0300 | [diff] [blame] | 70 | }; |
| 71 | |
| 72 | static int tegra186_cpufreq_init(struct cpufreq_policy *policy) |
| 73 | { |
| 74 | struct tegra186_cpufreq_data *data = cpufreq_get_driver_data(); |
Jon Hunter | cfef4bc | 2020-12-02 09:14:17 +0000 | [diff] [blame] | 75 | unsigned int cluster = data->cpus[policy->cpu].bpmp_cluster_id; |
Mikko Perttunen | 939dc6f | 2017-04-11 11:09:15 +0300 | [diff] [blame] | 76 | |
Jon Hunter | cfef4bc | 2020-12-02 09:14:17 +0000 | [diff] [blame] | 77 | policy->freq_table = data->clusters[cluster].table; |
Mikko Perttunen | 939dc6f | 2017-04-11 11:09:15 +0300 | [diff] [blame] | 78 | policy->cpuinfo.transition_latency = 300 * 1000; |
Jon Hunter | b7b4e78 | 2020-12-02 09:14:16 +0000 | [diff] [blame] | 79 | policy->driver_data = NULL; |
Mikko Perttunen | 939dc6f | 2017-04-11 11:09:15 +0300 | [diff] [blame] | 80 | |
| 81 | return 0; |
| 82 | } |
| 83 | |
| 84 | static int tegra186_cpufreq_set_target(struct cpufreq_policy *policy, |
| 85 | unsigned int index) |
| 86 | { |
Jon Hunter | b7b4e78 | 2020-12-02 09:14:16 +0000 | [diff] [blame] | 87 | struct tegra186_cpufreq_data *data = cpufreq_get_driver_data(); |
Mikko Perttunen | 939dc6f | 2017-04-11 11:09:15 +0300 | [diff] [blame] | 88 | struct cpufreq_frequency_table *tbl = policy->freq_table + index; |
Jon Hunter | b7b4e78 | 2020-12-02 09:14:16 +0000 | [diff] [blame] | 89 | unsigned int edvd_offset = data->cpus[policy->cpu].edvd_offset; |
Mikko Perttunen | 939dc6f | 2017-04-11 11:09:15 +0300 | [diff] [blame] | 90 | u32 edvd_val = tbl->driver_data; |
| 91 | |
Jon Hunter | b7b4e78 | 2020-12-02 09:14:16 +0000 | [diff] [blame] | 92 | writel(edvd_val, data->regs + edvd_offset); |
Mikko Perttunen | 939dc6f | 2017-04-11 11:09:15 +0300 | [diff] [blame] | 93 | |
| 94 | return 0; |
| 95 | } |
| 96 | |
Jon Hunter | b89c01c | 2020-08-24 15:59:07 +0100 | [diff] [blame] | 97 | static unsigned int tegra186_cpufreq_get(unsigned int cpu) |
| 98 | { |
Jon Hunter | e010d1d | 2020-11-03 11:55:14 +0000 | [diff] [blame] | 99 | struct tegra186_cpufreq_data *data = cpufreq_get_driver_data(); |
Jon Hunter | cfef4bc | 2020-12-02 09:14:17 +0000 | [diff] [blame] | 100 | struct tegra186_cpufreq_cluster *cluster; |
Jon Hunter | b89c01c | 2020-08-24 15:59:07 +0100 | [diff] [blame] | 101 | struct cpufreq_policy *policy; |
Jon Hunter | cfef4bc | 2020-12-02 09:14:17 +0000 | [diff] [blame] | 102 | unsigned int edvd_offset, cluster_id; |
Jon Hunter | b89c01c | 2020-08-24 15:59:07 +0100 | [diff] [blame] | 103 | u32 ndiv; |
| 104 | |
| 105 | policy = cpufreq_cpu_get(cpu); |
| 106 | if (!policy) |
| 107 | return 0; |
| 108 | |
Jon Hunter | b7b4e78 | 2020-12-02 09:14:16 +0000 | [diff] [blame] | 109 | edvd_offset = data->cpus[policy->cpu].edvd_offset; |
| 110 | ndiv = readl(data->regs + edvd_offset) & EDVD_CORE_VOLT_FREQ_F_MASK; |
Jon Hunter | cfef4bc | 2020-12-02 09:14:17 +0000 | [diff] [blame] | 111 | cluster_id = data->cpus[policy->cpu].bpmp_cluster_id; |
| 112 | cluster = &data->clusters[cluster_id]; |
Jon Hunter | b89c01c | 2020-08-24 15:59:07 +0100 | [diff] [blame] | 113 | cpufreq_cpu_put(policy); |
| 114 | |
Jon Hunter | cfef4bc | 2020-12-02 09:14:17 +0000 | [diff] [blame] | 115 | return (cluster->ref_clk_khz * ndiv) / cluster->div; |
Jon Hunter | b89c01c | 2020-08-24 15:59:07 +0100 | [diff] [blame] | 116 | } |
| 117 | |
Mikko Perttunen | 939dc6f | 2017-04-11 11:09:15 +0300 | [diff] [blame] | 118 | static struct cpufreq_driver tegra186_cpufreq_driver = { |
| 119 | .name = "tegra186", |
Viresh Kumar | 5ae4a4b | 2021-02-02 10:25:11 +0530 | [diff] [blame] | 120 | .flags = CPUFREQ_HAVE_GOVERNOR_PER_POLICY | |
Mian Yousaf Kaukab | 6cc3d0e | 2019-10-23 11:27:03 +0200 | [diff] [blame] | 121 | CPUFREQ_NEED_INITIAL_FREQ_CHECK, |
Jon Hunter | b89c01c | 2020-08-24 15:59:07 +0100 | [diff] [blame] | 122 | .get = tegra186_cpufreq_get, |
Mikko Perttunen | 939dc6f | 2017-04-11 11:09:15 +0300 | [diff] [blame] | 123 | .verify = cpufreq_generic_frequency_table_verify, |
| 124 | .target_index = tegra186_cpufreq_set_target, |
| 125 | .init = tegra186_cpufreq_init, |
| 126 | .attr = cpufreq_generic_attr, |
| 127 | }; |
| 128 | |
| 129 | static struct cpufreq_frequency_table *init_vhint_table( |
| 130 | struct platform_device *pdev, struct tegra_bpmp *bpmp, |
Jon Hunter | cfef4bc | 2020-12-02 09:14:17 +0000 | [diff] [blame] | 131 | struct tegra186_cpufreq_cluster *cluster, unsigned int cluster_id) |
Mikko Perttunen | 939dc6f | 2017-04-11 11:09:15 +0300 | [diff] [blame] | 132 | { |
| 133 | struct cpufreq_frequency_table *table; |
| 134 | struct mrq_cpu_vhint_request req; |
| 135 | struct tegra_bpmp_message msg; |
| 136 | struct cpu_vhint_data *data; |
| 137 | int err, i, j, num_rates = 0; |
| 138 | dma_addr_t phys; |
| 139 | void *virt; |
| 140 | |
| 141 | virt = dma_alloc_coherent(bpmp->dev, sizeof(*data), &phys, |
Christoph Hellwig | a3ade38 | 2018-10-13 17:17:00 +0200 | [diff] [blame] | 142 | GFP_KERNEL); |
Mikko Perttunen | 939dc6f | 2017-04-11 11:09:15 +0300 | [diff] [blame] | 143 | if (!virt) |
| 144 | return ERR_PTR(-ENOMEM); |
| 145 | |
| 146 | data = (struct cpu_vhint_data *)virt; |
| 147 | |
| 148 | memset(&req, 0, sizeof(req)); |
| 149 | req.addr = phys; |
Jon Hunter | cfef4bc | 2020-12-02 09:14:17 +0000 | [diff] [blame] | 150 | req.cluster_id = cluster_id; |
Mikko Perttunen | 939dc6f | 2017-04-11 11:09:15 +0300 | [diff] [blame] | 151 | |
| 152 | memset(&msg, 0, sizeof(msg)); |
| 153 | msg.mrq = MRQ_CPU_VHINT; |
| 154 | msg.tx.data = &req; |
| 155 | msg.tx.size = sizeof(req); |
| 156 | |
| 157 | err = tegra_bpmp_transfer(bpmp, &msg); |
| 158 | if (err) { |
| 159 | table = ERR_PTR(err); |
| 160 | goto free; |
| 161 | } |
Mikko Perttunen | c2ace21 | 2021-09-15 11:55:16 +0300 | [diff] [blame] | 162 | if (msg.rx.ret) { |
| 163 | table = ERR_PTR(-EINVAL); |
| 164 | goto free; |
| 165 | } |
Mikko Perttunen | 939dc6f | 2017-04-11 11:09:15 +0300 | [diff] [blame] | 166 | |
| 167 | for (i = data->vfloor; i <= data->vceil; i++) { |
| 168 | u16 ndiv = data->ndiv[i]; |
| 169 | |
| 170 | if (ndiv < data->ndiv_min || ndiv > data->ndiv_max) |
| 171 | continue; |
| 172 | |
| 173 | /* Only store lowest voltage index for each rate */ |
| 174 | if (i > 0 && ndiv == data->ndiv[i - 1]) |
| 175 | continue; |
| 176 | |
| 177 | num_rates++; |
| 178 | } |
| 179 | |
| 180 | table = devm_kcalloc(&pdev->dev, num_rates + 1, sizeof(*table), |
| 181 | GFP_KERNEL); |
| 182 | if (!table) { |
| 183 | table = ERR_PTR(-ENOMEM); |
| 184 | goto free; |
| 185 | } |
| 186 | |
Jon Hunter | e010d1d | 2020-11-03 11:55:14 +0000 | [diff] [blame] | 187 | cluster->ref_clk_khz = data->ref_clk_hz / 1000; |
| 188 | cluster->div = data->pdiv * data->mdiv; |
| 189 | |
Mikko Perttunen | 939dc6f | 2017-04-11 11:09:15 +0300 | [diff] [blame] | 190 | for (i = data->vfloor, j = 0; i <= data->vceil; i++) { |
| 191 | struct cpufreq_frequency_table *point; |
| 192 | u16 ndiv = data->ndiv[i]; |
| 193 | u32 edvd_val = 0; |
| 194 | |
| 195 | if (ndiv < data->ndiv_min || ndiv > data->ndiv_max) |
| 196 | continue; |
| 197 | |
| 198 | /* Only store lowest voltage index for each rate */ |
| 199 | if (i > 0 && ndiv == data->ndiv[i - 1]) |
| 200 | continue; |
| 201 | |
| 202 | edvd_val |= i << EDVD_CORE_VOLT_FREQ_V_SHIFT; |
| 203 | edvd_val |= ndiv << EDVD_CORE_VOLT_FREQ_F_SHIFT; |
| 204 | |
| 205 | point = &table[j++]; |
| 206 | point->driver_data = edvd_val; |
Jon Hunter | e010d1d | 2020-11-03 11:55:14 +0000 | [diff] [blame] | 207 | point->frequency = (cluster->ref_clk_khz * ndiv) / cluster->div; |
Mikko Perttunen | 939dc6f | 2017-04-11 11:09:15 +0300 | [diff] [blame] | 208 | } |
| 209 | |
| 210 | table[j].frequency = CPUFREQ_TABLE_END; |
| 211 | |
| 212 | free: |
| 213 | dma_free_coherent(bpmp->dev, sizeof(*data), virt, phys); |
| 214 | |
| 215 | return table; |
| 216 | } |
| 217 | |
| 218 | static int tegra186_cpufreq_probe(struct platform_device *pdev) |
| 219 | { |
| 220 | struct tegra186_cpufreq_data *data; |
| 221 | struct tegra_bpmp *bpmp; |
Mikko Perttunen | 939dc6f | 2017-04-11 11:09:15 +0300 | [diff] [blame] | 222 | unsigned int i = 0, err; |
| 223 | |
| 224 | data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); |
| 225 | if (!data) |
| 226 | return -ENOMEM; |
| 227 | |
Jon Hunter | cfef4bc | 2020-12-02 09:14:17 +0000 | [diff] [blame] | 228 | data->clusters = devm_kcalloc(&pdev->dev, TEGRA186_NUM_CLUSTERS, |
Mikko Perttunen | 939dc6f | 2017-04-11 11:09:15 +0300 | [diff] [blame] | 229 | sizeof(*data->clusters), GFP_KERNEL); |
| 230 | if (!data->clusters) |
| 231 | return -ENOMEM; |
| 232 | |
Jon Hunter | b7b4e78 | 2020-12-02 09:14:16 +0000 | [diff] [blame] | 233 | data->cpus = tegra186_cpus; |
Mikko Perttunen | 939dc6f | 2017-04-11 11:09:15 +0300 | [diff] [blame] | 234 | |
| 235 | bpmp = tegra_bpmp_get(&pdev->dev); |
| 236 | if (IS_ERR(bpmp)) |
| 237 | return PTR_ERR(bpmp); |
| 238 | |
Yangtao Li | 308beaf | 2019-12-15 14:05:22 +0000 | [diff] [blame] | 239 | data->regs = devm_platform_ioremap_resource(pdev, 0); |
Mikko Perttunen | 939dc6f | 2017-04-11 11:09:15 +0300 | [diff] [blame] | 240 | if (IS_ERR(data->regs)) { |
| 241 | err = PTR_ERR(data->regs); |
| 242 | goto put_bpmp; |
| 243 | } |
| 244 | |
Jon Hunter | cfef4bc | 2020-12-02 09:14:17 +0000 | [diff] [blame] | 245 | for (i = 0; i < TEGRA186_NUM_CLUSTERS; i++) { |
Mikko Perttunen | 939dc6f | 2017-04-11 11:09:15 +0300 | [diff] [blame] | 246 | struct tegra186_cpufreq_cluster *cluster = &data->clusters[i]; |
| 247 | |
Jon Hunter | cfef4bc | 2020-12-02 09:14:17 +0000 | [diff] [blame] | 248 | cluster->table = init_vhint_table(pdev, bpmp, cluster, i); |
Mikko Perttunen | 939dc6f | 2017-04-11 11:09:15 +0300 | [diff] [blame] | 249 | if (IS_ERR(cluster->table)) { |
| 250 | err = PTR_ERR(cluster->table); |
| 251 | goto put_bpmp; |
| 252 | } |
| 253 | } |
| 254 | |
Mikko Perttunen | 939dc6f | 2017-04-11 11:09:15 +0300 | [diff] [blame] | 255 | tegra186_cpufreq_driver.driver_data = data; |
| 256 | |
| 257 | err = cpufreq_register_driver(&tegra186_cpufreq_driver); |
Mikko Perttunen | 939dc6f | 2017-04-11 11:09:15 +0300 | [diff] [blame] | 258 | |
| 259 | put_bpmp: |
| 260 | tegra_bpmp_put(bpmp); |
| 261 | |
| 262 | return err; |
| 263 | } |
| 264 | |
| 265 | static int tegra186_cpufreq_remove(struct platform_device *pdev) |
| 266 | { |
| 267 | cpufreq_unregister_driver(&tegra186_cpufreq_driver); |
| 268 | |
| 269 | return 0; |
| 270 | } |
| 271 | |
| 272 | static const struct of_device_id tegra186_cpufreq_of_match[] = { |
| 273 | { .compatible = "nvidia,tegra186-ccplex-cluster", }, |
| 274 | { } |
| 275 | }; |
| 276 | MODULE_DEVICE_TABLE(of, tegra186_cpufreq_of_match); |
| 277 | |
| 278 | static struct platform_driver tegra186_cpufreq_platform_driver = { |
| 279 | .driver = { |
| 280 | .name = "tegra186-cpufreq", |
| 281 | .of_match_table = tegra186_cpufreq_of_match, |
| 282 | }, |
| 283 | .probe = tegra186_cpufreq_probe, |
| 284 | .remove = tegra186_cpufreq_remove, |
| 285 | }; |
| 286 | module_platform_driver(tegra186_cpufreq_platform_driver); |
| 287 | |
| 288 | MODULE_AUTHOR("Mikko Perttunen <mperttunen@nvidia.com>"); |
| 289 | MODULE_DESCRIPTION("NVIDIA Tegra186 cpufreq driver"); |
| 290 | MODULE_LICENSE("GPL v2"); |