2 * Copyright (c) 2017, NVIDIA CORPORATION. All rights reserved
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms and conditions of the GNU General Public License,
6 * version 2, as published by the Free Software Foundation.
8 * This program is distributed in the hope it will be useful, but WITHOUT
9 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
10 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
14 #include <linux/cpufreq.h>
15 #include <linux/dma-mapping.h>
16 #include <linux/module.h>
18 #include <linux/platform_device.h>
20 #include <soc/tegra/bpmp.h>
21 #include <soc/tegra/bpmp-abi.h>
23 #define EDVD_CORE_VOLT_FREQ(core) (0x20 + (core) * 0x4)
24 #define EDVD_CORE_VOLT_FREQ_F_SHIFT 0
25 #define EDVD_CORE_VOLT_FREQ_V_SHIFT 16
27 struct tegra186_cpufreq_cluster_info
{
30 unsigned int bpmp_cluster_id
;
34 static const struct tegra186_cpufreq_cluster_info tegra186_clusters
[] = {
38 .cpus
= { 1, 2, NO_CPU
, NO_CPU
},
44 .cpus
= { 0, 3, 4, 5 },
49 struct tegra186_cpufreq_cluster
{
50 const struct tegra186_cpufreq_cluster_info
*info
;
51 struct cpufreq_frequency_table
*table
;
54 struct tegra186_cpufreq_data
{
58 struct tegra186_cpufreq_cluster
*clusters
;
61 static int tegra186_cpufreq_init(struct cpufreq_policy
*policy
)
63 struct tegra186_cpufreq_data
*data
= cpufreq_get_driver_data();
66 for (i
= 0; i
< data
->num_clusters
; i
++) {
67 struct tegra186_cpufreq_cluster
*cluster
= &data
->clusters
[i
];
68 const struct tegra186_cpufreq_cluster_info
*info
=
72 for (core
= 0; core
< ARRAY_SIZE(info
->cpus
); core
++) {
73 if (info
->cpus
[core
] == policy
->cpu
)
76 if (core
== ARRAY_SIZE(info
->cpus
))
80 data
->regs
+ info
->offset
+ EDVD_CORE_VOLT_FREQ(core
);
81 cpufreq_table_validate_and_show(policy
, cluster
->table
);
84 policy
->cpuinfo
.transition_latency
= 300 * 1000;
89 static int tegra186_cpufreq_set_target(struct cpufreq_policy
*policy
,
92 struct cpufreq_frequency_table
*tbl
= policy
->freq_table
+ index
;
93 void __iomem
*edvd_reg
= policy
->driver_data
;
94 u32 edvd_val
= tbl
->driver_data
;
96 writel(edvd_val
, edvd_reg
);
101 static struct cpufreq_driver tegra186_cpufreq_driver
= {
103 .flags
= CPUFREQ_STICKY
| CPUFREQ_HAVE_GOVERNOR_PER_POLICY
,
104 .verify
= cpufreq_generic_frequency_table_verify
,
105 .target_index
= tegra186_cpufreq_set_target
,
106 .init
= tegra186_cpufreq_init
,
107 .attr
= cpufreq_generic_attr
,
110 static struct cpufreq_frequency_table
*init_vhint_table(
111 struct platform_device
*pdev
, struct tegra_bpmp
*bpmp
,
112 unsigned int cluster_id
)
114 struct cpufreq_frequency_table
*table
;
115 struct mrq_cpu_vhint_request req
;
116 struct tegra_bpmp_message msg
;
117 struct cpu_vhint_data
*data
;
118 int err
, i
, j
, num_rates
= 0;
122 virt
= dma_alloc_coherent(bpmp
->dev
, sizeof(*data
), &phys
,
123 GFP_KERNEL
| GFP_DMA32
);
125 return ERR_PTR(-ENOMEM
);
127 data
= (struct cpu_vhint_data
*)virt
;
129 memset(&req
, 0, sizeof(req
));
131 req
.cluster_id
= cluster_id
;
133 memset(&msg
, 0, sizeof(msg
));
134 msg
.mrq
= MRQ_CPU_VHINT
;
136 msg
.tx
.size
= sizeof(req
);
138 err
= tegra_bpmp_transfer(bpmp
, &msg
);
140 table
= ERR_PTR(err
);
144 for (i
= data
->vfloor
; i
<= data
->vceil
; i
++) {
145 u16 ndiv
= data
->ndiv
[i
];
147 if (ndiv
< data
->ndiv_min
|| ndiv
> data
->ndiv_max
)
150 /* Only store lowest voltage index for each rate */
151 if (i
> 0 && ndiv
== data
->ndiv
[i
- 1])
157 table
= devm_kcalloc(&pdev
->dev
, num_rates
+ 1, sizeof(*table
),
160 table
= ERR_PTR(-ENOMEM
);
164 for (i
= data
->vfloor
, j
= 0; i
<= data
->vceil
; i
++) {
165 struct cpufreq_frequency_table
*point
;
166 u16 ndiv
= data
->ndiv
[i
];
169 if (ndiv
< data
->ndiv_min
|| ndiv
> data
->ndiv_max
)
172 /* Only store lowest voltage index for each rate */
173 if (i
> 0 && ndiv
== data
->ndiv
[i
- 1])
176 edvd_val
|= i
<< EDVD_CORE_VOLT_FREQ_V_SHIFT
;
177 edvd_val
|= ndiv
<< EDVD_CORE_VOLT_FREQ_F_SHIFT
;
180 point
->driver_data
= edvd_val
;
181 point
->frequency
= data
->ref_clk_hz
* ndiv
/ data
->pdiv
/
185 table
[j
].frequency
= CPUFREQ_TABLE_END
;
188 dma_free_coherent(bpmp
->dev
, sizeof(*data
), virt
, phys
);
193 static int tegra186_cpufreq_probe(struct platform_device
*pdev
)
195 struct tegra186_cpufreq_data
*data
;
196 struct tegra_bpmp
*bpmp
;
197 struct resource
*res
;
198 unsigned int i
= 0, err
;
200 data
= devm_kzalloc(&pdev
->dev
, sizeof(*data
), GFP_KERNEL
);
204 data
->clusters
= devm_kcalloc(&pdev
->dev
, ARRAY_SIZE(tegra186_clusters
),
205 sizeof(*data
->clusters
), GFP_KERNEL
);
209 data
->num_clusters
= ARRAY_SIZE(tegra186_clusters
);
211 bpmp
= tegra_bpmp_get(&pdev
->dev
);
213 return PTR_ERR(bpmp
);
215 res
= platform_get_resource(pdev
, IORESOURCE_MEM
, 0);
216 data
->regs
= devm_ioremap_resource(&pdev
->dev
, res
);
217 if (IS_ERR(data
->regs
)) {
218 err
= PTR_ERR(data
->regs
);
222 for (i
= 0; i
< data
->num_clusters
; i
++) {
223 struct tegra186_cpufreq_cluster
*cluster
= &data
->clusters
[i
];
225 cluster
->info
= &tegra186_clusters
[i
];
226 cluster
->table
= init_vhint_table(
227 pdev
, bpmp
, cluster
->info
->bpmp_cluster_id
);
228 if (IS_ERR(cluster
->table
)) {
229 err
= PTR_ERR(cluster
->table
);
234 tegra_bpmp_put(bpmp
);
236 tegra186_cpufreq_driver
.driver_data
= data
;
238 err
= cpufreq_register_driver(&tegra186_cpufreq_driver
);
245 tegra_bpmp_put(bpmp
);
250 static int tegra186_cpufreq_remove(struct platform_device
*pdev
)
252 cpufreq_unregister_driver(&tegra186_cpufreq_driver
);
257 static const struct of_device_id tegra186_cpufreq_of_match
[] = {
258 { .compatible
= "nvidia,tegra186-ccplex-cluster", },
261 MODULE_DEVICE_TABLE(of
, tegra186_cpufreq_of_match
);
263 static struct platform_driver tegra186_cpufreq_platform_driver
= {
265 .name
= "tegra186-cpufreq",
266 .of_match_table
= tegra186_cpufreq_of_match
,
268 .probe
= tegra186_cpufreq_probe
,
269 .remove
= tegra186_cpufreq_remove
,
271 module_platform_driver(tegra186_cpufreq_platform_driver
);
273 MODULE_AUTHOR("Mikko Perttunen <mperttunen@nvidia.com>");
274 MODULE_DESCRIPTION("NVIDIA Tegra186 cpufreq driver");
275 MODULE_LICENSE("GPL v2");