aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/devfreq/tegra20-devfreq.c
blob: ff82bac9ee4ec498384938d7536e614de649bcc5 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
// SPDX-License-Identifier: GPL-2.0
/*
 * NVIDIA Tegra20 devfreq driver
 *
 * Copyright (C) 2019 GRATE-DRIVER project
 */

#include <linux/clk.h>
#include <linux/devfreq.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/pm_opp.h>
#include <linux/slab.h>

#include <soc/tegra/mc.h>

#include "governor.h"

#define MC_STAT_CONTROL				0x90
#define MC_STAT_EMC_CLOCK_LIMIT			0xa0
#define MC_STAT_EMC_CLOCKS			0xa4
#define MC_STAT_EMC_CONTROL			0xa8
#define MC_STAT_EMC_COUNT			0xb8

#define EMC_GATHER_CLEAR			(1 << 8)
#define EMC_GATHER_ENABLE			(3 << 8)

struct tegra_devfreq {
	struct devfreq *devfreq;
	struct clk *emc_clock;
	void __iomem *regs;
};

static int tegra_devfreq_target(struct device *dev, unsigned long *freq,
				u32 flags)
{
	struct tegra_devfreq *tegra = dev_get_drvdata(dev);
	struct devfreq *devfreq = tegra->devfreq;
	struct dev_pm_opp *opp;
	unsigned long rate;
	int err;

	opp = devfreq_recommended_opp(dev, freq, flags);
	if (IS_ERR(opp))
		return PTR_ERR(opp);

	rate = dev_pm_opp_get_freq(opp);
	dev_pm_opp_put(opp);

	err = clk_set_min_rate(tegra->emc_clock, rate);
	if (err)
		return err;

	err = clk_set_rate(tegra->emc_clock, 0);
	if (err)
		goto restore_min_rate;

	return 0;

restore_min_rate:
	clk_set_min_rate(tegra->emc_clock, devfreq->previous_freq);

	return err;
}

static int tegra_devfreq_get_dev_status(struct device *dev,
					struct devfreq_dev_status *stat)
{
	struct tegra_devfreq *tegra = dev_get_drvdata(dev);

	/*
	 * EMC_COUNT returns number of memory events, that number is lower
	 * than the number of clocks. Conversion ratio of 1/8 results in a
	 * bit higher bandwidth than actually needed, it is good enough for
	 * the time being because drivers don't support requesting minimum
	 * needed memory bandwidth yet.
	 *
	 * TODO: adjust the ratio value once relevant drivers will support
	 * memory bandwidth management.
	 */
	stat->busy_time = readl_relaxed(tegra->regs + MC_STAT_EMC_COUNT);
	stat->total_time = readl_relaxed(tegra->regs + MC_STAT_EMC_CLOCKS) / 8;
	stat->current_frequency = clk_get_rate(tegra->emc_clock);

	writel_relaxed(EMC_GATHER_CLEAR, tegra->regs + MC_STAT_CONTROL);
	writel_relaxed(EMC_GATHER_ENABLE, tegra->regs + MC_STAT_CONTROL);

	return 0;
}

static struct devfreq_dev_profile tegra_devfreq_profile = {
	.polling_ms	= 500,
	.target		= tegra_devfreq_target,
	.get_dev_status	= tegra_devfreq_get_dev_status,
};

static struct tegra_mc *tegra_get_memory_controller(void)
{
	struct platform_device *pdev;
	struct device_node *np;
	struct tegra_mc *mc;

	np = of_find_compatible_node(NULL, NULL, "nvidia,tegra20-mc-gart");
	if (!np)
		return ERR_PTR(-ENOENT);

	pdev = of_find_device_by_node(np);
	of_node_put(np);
	if (!pdev)
		return ERR_PTR(-ENODEV);

	mc = platform_get_drvdata(pdev);
	if (!mc)
		return ERR_PTR(-EPROBE_DEFER);

	return mc;
}

static int tegra_devfreq_probe(struct platform_device *pdev)
{
	struct tegra_devfreq *tegra;
	struct tegra_mc *mc;
	unsigned long max_rate;
	unsigned long rate;
	int err;

	mc = tegra_get_memory_controller();
	if (IS_ERR(mc)) {
		err = PTR_ERR(mc);
		dev_err(&pdev->dev, "failed to get memory controller: %d\n",
			err);
		return err;
	}

	tegra = devm_kzalloc(&pdev->dev, sizeof(*tegra), GFP_KERNEL);
	if (!tegra)
		return -ENOMEM;

	/* EMC is a system-critical clock that is always enabled */
	tegra->emc_clock = devm_clk_get(&pdev->dev, "emc");
	if (IS_ERR(tegra->emc_clock)) {
		err = PTR_ERR(tegra->emc_clock);
		dev_err(&pdev->dev, "failed to get emc clock: %d\n", err);
		return err;
	}

	tegra->regs = mc->regs;

	max_rate = clk_round_rate(tegra->emc_clock, ULONG_MAX);

	for (rate = 0; rate <= max_rate; rate++) {
		rate = clk_round_rate(tegra->emc_clock, rate);

		err = dev_pm_opp_add(&pdev->dev, rate, 0);
		if (err) {
			dev_err(&pdev->dev, "failed to add opp: %d\n", err);
			goto remove_opps;
		}
	}

	/*
	 * Reset statistic gathers state, select global bandwidth for the
	 * statistics collection mode and set clocks counter saturation
	 * limit to maximum.
	 */
	writel_relaxed(0x00000000, tegra->regs + MC_STAT_CONTROL);
	writel_relaxed(0x00000000, tegra->regs + MC_STAT_EMC_CONTROL);
	writel_relaxed(0xffffffff, tegra->regs + MC_STAT_EMC_CLOCK_LIMIT);

	platform_set_drvdata(pdev, tegra);

	tegra->devfreq = devfreq_add_device(&pdev->dev, &tegra_devfreq_profile,
					    DEVFREQ_GOV_SIMPLE_ONDEMAND, NULL);
	if (IS_ERR(tegra->devfreq)) {
		err = PTR_ERR(tegra->devfreq);
		goto remove_opps;
	}

	return 0;

remove_opps:
	dev_pm_opp_remove_all_dynamic(&pdev->dev);

	return err;
}

static int tegra_devfreq_remove(struct platform_device *pdev)
{
	struct tegra_devfreq *tegra = platform_get_drvdata(pdev);

	devfreq_remove_device(tegra->devfreq);
	dev_pm_opp_remove_all_dynamic(&pdev->dev);

	return 0;
}

static struct platform_driver tegra_devfreq_driver = {
	.probe		= tegra_devfreq_probe,
	.remove		= tegra_devfreq_remove,
	.driver		= {
		.name	= "tegra20-devfreq",
	},
};
module_platform_driver(tegra_devfreq_driver);

MODULE_ALIAS("platform:tegra20-devfreq");
MODULE_AUTHOR("Dmitry Osipenko <digetx@gmail.com>");
MODULE_DESCRIPTION("NVIDIA Tegra20 devfreq driver");
MODULE_LICENSE("GPL v2");