Linux kernel mirror (for testing)
git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel
os
linux
1// SPDX-License-Identifier: GPL-2.0
2/* Copyright 2019 Collabora ltd. */
3#include <linux/devfreq.h>
4#include <linux/platform_device.h>
5#include <linux/pm_opp.h>
6#include <linux/clk.h>
7#include <linux/regulator/consumer.h>
8
9#include "panfrost_device.h"
10#include "panfrost_devfreq.h"
11#include "panfrost_features.h"
12#include "panfrost_issues.h"
13#include "panfrost_gpu.h"
14#include "panfrost_regs.h"
15
16static void panfrost_devfreq_update_utilization(struct panfrost_device *pfdev);
17
18static int panfrost_devfreq_target(struct device *dev, unsigned long *freq,
19 u32 flags)
20{
21 struct panfrost_device *pfdev = dev_get_drvdata(dev);
22 int err;
23
24 err = dev_pm_opp_set_rate(dev, *freq);
25 if (err)
26 return err;
27
28 *freq = clk_get_rate(pfdev->clock);
29
30 return 0;
31}
32
33static void panfrost_devfreq_reset(struct panfrost_device *pfdev)
34{
35 pfdev->devfreq.busy_time = 0;
36 pfdev->devfreq.idle_time = 0;
37 pfdev->devfreq.time_last_update = ktime_get();
38}
39
40static int panfrost_devfreq_get_dev_status(struct device *dev,
41 struct devfreq_dev_status *status)
42{
43 struct panfrost_device *pfdev = dev_get_drvdata(dev);
44
45 panfrost_devfreq_update_utilization(pfdev);
46
47 status->current_frequency = clk_get_rate(pfdev->clock);
48 status->total_time = ktime_to_ns(ktime_add(pfdev->devfreq.busy_time,
49 pfdev->devfreq.idle_time));
50
51 status->busy_time = ktime_to_ns(pfdev->devfreq.busy_time);
52
53 panfrost_devfreq_reset(pfdev);
54
55 dev_dbg(pfdev->dev, "busy %lu total %lu %lu %% freq %lu MHz\n", status->busy_time,
56 status->total_time,
57 status->busy_time / (status->total_time / 100),
58 status->current_frequency / 1000 / 1000);
59
60 return 0;
61}
62
63static int panfrost_devfreq_get_cur_freq(struct device *dev, unsigned long *freq)
64{
65 struct panfrost_device *pfdev = platform_get_drvdata(to_platform_device(dev));
66
67 *freq = clk_get_rate(pfdev->clock);
68
69 return 0;
70}
71
72static struct devfreq_dev_profile panfrost_devfreq_profile = {
73 .polling_ms = 50, /* ~3 frames */
74 .target = panfrost_devfreq_target,
75 .get_dev_status = panfrost_devfreq_get_dev_status,
76 .get_cur_freq = panfrost_devfreq_get_cur_freq,
77};
78
79int panfrost_devfreq_init(struct panfrost_device *pfdev)
80{
81 int ret;
82 struct dev_pm_opp *opp;
83 unsigned long cur_freq;
84
85 ret = dev_pm_opp_of_add_table(&pfdev->pdev->dev);
86 if (ret == -ENODEV) /* Optional, continue without devfreq */
87 return 0;
88 else if (ret)
89 return ret;
90
91 panfrost_devfreq_reset(pfdev);
92
93 cur_freq = clk_get_rate(pfdev->clock);
94
95 opp = devfreq_recommended_opp(&pfdev->pdev->dev, &cur_freq, 0);
96 if (IS_ERR(opp))
97 return PTR_ERR(opp);
98
99 panfrost_devfreq_profile.initial_freq = cur_freq;
100 dev_pm_opp_put(opp);
101
102 pfdev->devfreq.devfreq = devm_devfreq_add_device(&pfdev->pdev->dev,
103 &panfrost_devfreq_profile, DEVFREQ_GOV_SIMPLE_ONDEMAND,
104 NULL);
105 if (IS_ERR(pfdev->devfreq.devfreq)) {
106 DRM_DEV_ERROR(&pfdev->pdev->dev, "Couldn't initialize GPU devfreq\n");
107 ret = PTR_ERR(pfdev->devfreq.devfreq);
108 pfdev->devfreq.devfreq = NULL;
109 dev_pm_opp_of_remove_table(&pfdev->pdev->dev);
110 return ret;
111 }
112
113 return 0;
114}
115
116void panfrost_devfreq_fini(struct panfrost_device *pfdev)
117{
118 dev_pm_opp_of_remove_table(&pfdev->pdev->dev);
119}
120
121void panfrost_devfreq_resume(struct panfrost_device *pfdev)
122{
123 if (!pfdev->devfreq.devfreq)
124 return;
125
126 panfrost_devfreq_reset(pfdev);
127
128 devfreq_resume_device(pfdev->devfreq.devfreq);
129}
130
131void panfrost_devfreq_suspend(struct panfrost_device *pfdev)
132{
133 if (!pfdev->devfreq.devfreq)
134 return;
135
136 devfreq_suspend_device(pfdev->devfreq.devfreq);
137}
138
139static void panfrost_devfreq_update_utilization(struct panfrost_device *pfdev)
140{
141 ktime_t now;
142 ktime_t last;
143
144 if (!pfdev->devfreq.devfreq)
145 return;
146
147 now = ktime_get();
148 last = pfdev->devfreq.time_last_update;
149
150 if (atomic_read(&pfdev->devfreq.busy_count) > 0)
151 pfdev->devfreq.busy_time += ktime_sub(now, last);
152 else
153 pfdev->devfreq.idle_time += ktime_sub(now, last);
154
155 pfdev->devfreq.time_last_update = now;
156}
157
158void panfrost_devfreq_record_busy(struct panfrost_device *pfdev)
159{
160 panfrost_devfreq_update_utilization(pfdev);
161 atomic_inc(&pfdev->devfreq.busy_count);
162}
163
164void panfrost_devfreq_record_idle(struct panfrost_device *pfdev)
165{
166 int count;
167
168 panfrost_devfreq_update_utilization(pfdev);
169 count = atomic_dec_if_positive(&pfdev->devfreq.busy_count);
170 WARN_ON(count < 0);
171}