]>
Commit | Line | Data |
---|---|---|
9f7fd620 VH |
1 | /* |
2 | * Copyright (c) 2014, NVIDIA CORPORATION. All rights reserved. | |
3 | * | |
4 | * Permission is hereby granted, free of charge, to any person obtaining a | |
5 | * copy of this software and associated documentation files (the "Software"), | |
6 | * to deal in the Software without restriction, including without limitation | |
7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, | |
8 | * and/or sell copies of the Software, and to permit persons to whom the | |
9 | * Software is furnished to do so, subject to the following conditions: | |
10 | * | |
11 | * The above copyright notice and this permission notice shall be included in | |
12 | * all copies or substantial portions of the Software. | |
13 | * | |
14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL | |
17 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | |
19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER | |
20 | * DEALINGS IN THE SOFTWARE. | |
21 | */ | |
e2ca4e7d | 22 | #define gk20a_pmu(p) container_of((p), struct gk20a_pmu, base.subdev) |
9f7fd620 VH |
23 | #include "priv.h" |
24 | ||
f3867f43 | 25 | #include <subdev/clk.h> |
9f7fd620 VH |
26 | #include <subdev/timer.h> |
27 | #include <subdev/volt.h> | |
28 | ||
29 | #define BUSY_SLOT 0 | |
30 | #define CLK_SLOT 7 | |
31 | ||
ebb58dc2 | 32 | struct gk20a_pmu_dvfs_data { |
9f7fd620 VH |
33 | int p_load_target; |
34 | int p_load_max; | |
35 | int p_smooth; | |
36 | unsigned int avg_load; | |
37 | }; | |
38 | ||
5a7d1e22 | 39 | struct gk20a_pmu { |
21b13791 BS |
40 | struct nvkm_pmu base; |
41 | struct nvkm_alarm alarm; | |
ebb58dc2 | 42 | struct gk20a_pmu_dvfs_data *data; |
9f7fd620 VH |
43 | }; |
44 | ||
ebb58dc2 | 45 | struct gk20a_pmu_dvfs_dev_status { |
9f7fd620 VH |
46 | unsigned long total; |
47 | unsigned long busy; | |
48 | int cur_state; | |
49 | }; | |
50 | ||
51 | static int | |
5a7d1e22 | 52 | gk20a_pmu_dvfs_target(struct gk20a_pmu *pmu, int *state) |
9f7fd620 | 53 | { |
6625f55c | 54 | struct nvkm_clk *clk = pmu->base.subdev.device->clk; |
9f7fd620 | 55 | |
21b13791 | 56 | return nvkm_clk_astate(clk, *state, 0, false); |
9f7fd620 VH |
57 | } |
58 | ||
59 | static int | |
5a7d1e22 | 60 | gk20a_pmu_dvfs_get_cur_state(struct gk20a_pmu *pmu, int *state) |
9f7fd620 | 61 | { |
6625f55c | 62 | struct nvkm_clk *clk = pmu->base.subdev.device->clk; |
9f7fd620 VH |
63 | |
64 | *state = clk->pstate; | |
65 | return 0; | |
66 | } | |
67 | ||
68 | static int | |
5a7d1e22 | 69 | gk20a_pmu_dvfs_get_target_state(struct gk20a_pmu *pmu, |
21b13791 | 70 | int *state, int load) |
9f7fd620 | 71 | { |
5a7d1e22 | 72 | struct gk20a_pmu_dvfs_data *data = pmu->data; |
6625f55c | 73 | struct nvkm_clk *clk = pmu->base.subdev.device->clk; |
9f7fd620 VH |
74 | int cur_level, level; |
75 | ||
76 | /* For GK20A, the performance level is directly mapped to pstate */ | |
77 | level = cur_level = clk->pstate; | |
78 | ||
79 | if (load > data->p_load_max) { | |
80 | level = min(clk->state_nr - 1, level + (clk->state_nr / 3)); | |
81 | } else { | |
82 | level += ((load - data->p_load_target) * 10 / | |
83 | data->p_load_target) / 2; | |
84 | level = max(0, level); | |
85 | level = min(clk->state_nr - 1, level); | |
86 | } | |
87 | ||
c19e329d BS |
88 | nvkm_trace(&pmu->base.subdev, "cur level = %d, new level = %d\n", |
89 | cur_level, level); | |
9f7fd620 VH |
90 | |
91 | *state = level; | |
92 | ||
93 | if (level == cur_level) | |
94 | return 0; | |
95 | else | |
96 | return 1; | |
97 | } | |
98 | ||
99 | static int | |
5a7d1e22 | 100 | gk20a_pmu_dvfs_get_dev_status(struct gk20a_pmu *pmu, |
21b13791 | 101 | struct gk20a_pmu_dvfs_dev_status *status) |
9f7fd620 | 102 | { |
bef002e8 BS |
103 | struct nvkm_device *device = pmu->base.subdev.device; |
104 | status->busy = nvkm_rd32(device, 0x10a508 + (BUSY_SLOT * 0x10)); | |
105 | status->total= nvkm_rd32(device, 0x10a508 + (CLK_SLOT * 0x10)); | |
9f7fd620 VH |
106 | return 0; |
107 | } | |
108 | ||
109 | static void | |
5a7d1e22 | 110 | gk20a_pmu_dvfs_reset_dev_status(struct gk20a_pmu *pmu) |
9f7fd620 | 111 | { |
bef002e8 BS |
112 | struct nvkm_device *device = pmu->base.subdev.device; |
113 | nvkm_wr32(device, 0x10a508 + (BUSY_SLOT * 0x10), 0x80000000); | |
114 | nvkm_wr32(device, 0x10a508 + (CLK_SLOT * 0x10), 0x80000000); | |
9f7fd620 VH |
115 | } |
116 | ||
117 | static void | |
21b13791 | 118 | gk20a_pmu_dvfs_work(struct nvkm_alarm *alarm) |
9f7fd620 | 119 | { |
5a7d1e22 BS |
120 | struct gk20a_pmu *pmu = |
121 | container_of(alarm, struct gk20a_pmu, alarm); | |
122 | struct gk20a_pmu_dvfs_data *data = pmu->data; | |
ebb58dc2 | 123 | struct gk20a_pmu_dvfs_dev_status status; |
c19e329d BS |
124 | struct nvkm_subdev *subdev = &pmu->base.subdev; |
125 | struct nvkm_device *device = subdev->device; | |
126 | struct nvkm_clk *clk = device->clk; | |
31649ecf | 127 | struct nvkm_timer *tmr = device->timer; |
c19e329d | 128 | struct nvkm_volt *volt = device->volt; |
9f7fd620 VH |
129 | u32 utilization = 0; |
130 | int state, ret; | |
131 | ||
132 | /* | |
ebb58dc2 | 133 | * The PMU is initialized before CLK and VOLT, so we have to make sure the |
9f7fd620 VH |
134 | * CLK and VOLT are ready here. |
135 | */ | |
136 | if (!clk || !volt) | |
137 | goto resched; | |
138 | ||
5a7d1e22 | 139 | ret = gk20a_pmu_dvfs_get_dev_status(pmu, &status); |
9f7fd620 | 140 | if (ret) { |
c19e329d | 141 | nvkm_warn(subdev, "failed to get device status\n"); |
9f7fd620 VH |
142 | goto resched; |
143 | } | |
144 | ||
145 | if (status.total) | |
146 | utilization = div_u64((u64)status.busy * 100, status.total); | |
147 | ||
148 | data->avg_load = (data->p_smooth * data->avg_load) + utilization; | |
149 | data->avg_load /= data->p_smooth + 1; | |
c19e329d BS |
150 | nvkm_trace(subdev, "utilization = %d %%, avg_load = %d %%\n", |
151 | utilization, data->avg_load); | |
9f7fd620 | 152 | |
5a7d1e22 | 153 | ret = gk20a_pmu_dvfs_get_cur_state(pmu, &state); |
9f7fd620 | 154 | if (ret) { |
c19e329d | 155 | nvkm_warn(subdev, "failed to get current state\n"); |
9f7fd620 VH |
156 | goto resched; |
157 | } | |
158 | ||
5a7d1e22 | 159 | if (gk20a_pmu_dvfs_get_target_state(pmu, &state, data->avg_load)) { |
c19e329d | 160 | nvkm_trace(subdev, "set new state to %d\n", state); |
5a7d1e22 | 161 | gk20a_pmu_dvfs_target(pmu, &state); |
9f7fd620 VH |
162 | } |
163 | ||
164 | resched: | |
5a7d1e22 | 165 | gk20a_pmu_dvfs_reset_dev_status(pmu); |
31649ecf | 166 | nvkm_timer_alarm(tmr, 100000000, alarm); |
9f7fd620 VH |
167 | } |
168 | ||
7120908d | 169 | static int |
e2ca4e7d | 170 | gk20a_pmu_fini(struct nvkm_subdev *subdev, bool suspend) |
9f7fd620 | 171 | { |
e2ca4e7d | 172 | struct gk20a_pmu *pmu = gk20a_pmu(subdev); |
31649ecf | 173 | nvkm_timer_alarm_cancel(subdev->device->timer, &pmu->alarm); |
e2ca4e7d BS |
174 | return 0; |
175 | } | |
9f7fd620 | 176 | |
e2ca4e7d BS |
177 | static void * |
178 | gk20a_pmu_dtor(struct nvkm_subdev *subdev) | |
179 | { | |
180 | return gk20a_pmu(subdev); | |
9f7fd620 VH |
181 | } |
182 | ||
7120908d | 183 | static int |
e2ca4e7d | 184 | gk20a_pmu_init(struct nvkm_subdev *subdev) |
9f7fd620 | 185 | { |
e2ca4e7d | 186 | struct gk20a_pmu *pmu = gk20a_pmu(subdev); |
bef002e8 | 187 | struct nvkm_device *device = pmu->base.subdev.device; |
9f7fd620 VH |
188 | |
189 | /* init pwr perf counter */ | |
bef002e8 BS |
190 | nvkm_wr32(device, 0x10a504 + (BUSY_SLOT * 0x10), 0x00200001); |
191 | nvkm_wr32(device, 0x10a50c + (BUSY_SLOT * 0x10), 0x00000002); | |
192 | nvkm_wr32(device, 0x10a50c + (CLK_SLOT * 0x10), 0x00000003); | |
9f7fd620 | 193 | |
31649ecf | 194 | nvkm_timer_alarm(device->timer, 2000000000, &pmu->alarm); |
e2ca4e7d | 195 | return 0; |
9f7fd620 VH |
196 | } |
197 | ||
7120908d AC |
198 | static struct gk20a_pmu_dvfs_data |
199 | gk20a_dvfs_data= { | |
9f7fd620 VH |
200 | .p_load_target = 70, |
201 | .p_load_max = 90, | |
202 | .p_smooth = 1, | |
203 | }; | |
204 | ||
e2ca4e7d BS |
205 | static const struct nvkm_subdev_func |
206 | gk20a_pmu = { | |
207 | .init = gk20a_pmu_init, | |
208 | .fini = gk20a_pmu_fini, | |
209 | .dtor = gk20a_pmu_dtor, | |
210 | }; | |
211 | ||
212 | int | |
213 | gk20a_pmu_new(struct nvkm_device *device, int index, struct nvkm_pmu **ppmu) | |
9f7fd620 | 214 | { |
e2ca4e7d | 215 | static const struct nvkm_pmu_func func = {}; |
5a7d1e22 | 216 | struct gk20a_pmu *pmu; |
9f7fd620 | 217 | |
e2ca4e7d BS |
218 | if (!(pmu = kzalloc(sizeof(*pmu), GFP_KERNEL))) |
219 | return -ENOMEM; | |
220 | pmu->base.func = &func; | |
221 | *ppmu = &pmu->base; | |
9f7fd620 | 222 | |
e2ca4e7d | 223 | nvkm_subdev_ctor(&gk20a_pmu, device, index, 0, &pmu->base.subdev); |
5a7d1e22 | 224 | pmu->data = &gk20a_dvfs_data; |
5a7d1e22 | 225 | nvkm_alarm_init(&pmu->alarm, gk20a_pmu_dvfs_work); |
9f7fd620 VH |
226 | return 0; |
227 | } |