diff options
Diffstat (limited to 'drivers/gpu/drm/nouveau/nvkm/subdev/therm/fan.c')
-rw-r--r-- | drivers/gpu/drm/nouveau/nvkm/subdev/therm/fan.c | 287 |
1 files changed, 287 insertions, 0 deletions
diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/therm/fan.c b/drivers/gpu/drm/nouveau/nvkm/subdev/therm/fan.c new file mode 100644 index 000000000000..3656d605168f --- /dev/null +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/therm/fan.c | |||
@@ -0,0 +1,287 @@ | |||
1 | /* | ||
2 | * Copyright 2012 Red Hat Inc. | ||
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 COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR | ||
18 | * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, | ||
19 | * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR | ||
20 | * OTHER DEALINGS IN THE SOFTWARE. | ||
21 | * | ||
22 | * Authors: Ben Skeggs | ||
23 | * Martin Peres | ||
24 | */ | ||
25 | |||
26 | #include "priv.h" | ||
27 | |||
28 | #include <core/object.h> | ||
29 | #include <core/device.h> | ||
30 | |||
31 | #include <subdev/gpio.h> | ||
32 | #include <subdev/timer.h> | ||
33 | |||
34 | #include <subdev/bios/fan.h> | ||
35 | |||
36 | static int | ||
37 | nouveau_fan_update(struct nouveau_fan *fan, bool immediate, int target) | ||
38 | { | ||
39 | struct nouveau_therm *therm = fan->parent; | ||
40 | struct nouveau_therm_priv *priv = (void *)therm; | ||
41 | struct nouveau_timer *ptimer = nouveau_timer(priv); | ||
42 | unsigned long flags; | ||
43 | int ret = 0; | ||
44 | int duty; | ||
45 | |||
46 | /* update target fan speed, restricting to allowed range */ | ||
47 | spin_lock_irqsave(&fan->lock, flags); | ||
48 | if (target < 0) | ||
49 | target = fan->percent; | ||
50 | target = max_t(u8, target, fan->bios.min_duty); | ||
51 | target = min_t(u8, target, fan->bios.max_duty); | ||
52 | if (fan->percent != target) { | ||
53 | nv_debug(therm, "FAN target: %d\n", target); | ||
54 | fan->percent = target; | ||
55 | } | ||
56 | |||
57 | /* check that we're not already at the target duty cycle */ | ||
58 | duty = fan->get(therm); | ||
59 | if (duty == target) { | ||
60 | spin_unlock_irqrestore(&fan->lock, flags); | ||
61 | return 0; | ||
62 | } | ||
63 | |||
64 | /* smooth out the fanspeed increase/decrease */ | ||
65 | if (!immediate && duty >= 0) { | ||
66 | /* the constant "3" is a rough approximation taken from | ||
67 | * nvidia's behaviour. | ||
68 | * it is meant to bump the fan speed more incrementally | ||
69 | */ | ||
70 | if (duty < target) | ||
71 | duty = min(duty + 3, target); | ||
72 | else if (duty > target) | ||
73 | duty = max(duty - 3, target); | ||
74 | } else { | ||
75 | duty = target; | ||
76 | } | ||
77 | |||
78 | nv_debug(therm, "FAN update: %d\n", duty); | ||
79 | ret = fan->set(therm, duty); | ||
80 | if (ret) { | ||
81 | spin_unlock_irqrestore(&fan->lock, flags); | ||
82 | return ret; | ||
83 | } | ||
84 | |||
85 | /* fan speed updated, drop the fan lock before grabbing the | ||
86 | * alarm-scheduling lock and risking a deadlock | ||
87 | */ | ||
88 | spin_unlock_irqrestore(&fan->lock, flags); | ||
89 | |||
90 | /* schedule next fan update, if not at target speed already */ | ||
91 | if (list_empty(&fan->alarm.head) && target != duty) { | ||
92 | u16 bump_period = fan->bios.bump_period; | ||
93 | u16 slow_down_period = fan->bios.slow_down_period; | ||
94 | u64 delay; | ||
95 | |||
96 | if (duty > target) | ||
97 | delay = slow_down_period; | ||
98 | else if (duty == target) | ||
99 | delay = min(bump_period, slow_down_period) ; | ||
100 | else | ||
101 | delay = bump_period; | ||
102 | |||
103 | ptimer->alarm(ptimer, delay * 1000 * 1000, &fan->alarm); | ||
104 | } | ||
105 | |||
106 | return ret; | ||
107 | } | ||
108 | |||
109 | static void | ||
110 | nouveau_fan_alarm(struct nouveau_alarm *alarm) | ||
111 | { | ||
112 | struct nouveau_fan *fan = container_of(alarm, struct nouveau_fan, alarm); | ||
113 | nouveau_fan_update(fan, false, -1); | ||
114 | } | ||
115 | |||
116 | int | ||
117 | nouveau_therm_fan_get(struct nouveau_therm *therm) | ||
118 | { | ||
119 | struct nouveau_therm_priv *priv = (void *)therm; | ||
120 | return priv->fan->get(therm); | ||
121 | } | ||
122 | |||
123 | int | ||
124 | nouveau_therm_fan_set(struct nouveau_therm *therm, bool immediate, int percent) | ||
125 | { | ||
126 | struct nouveau_therm_priv *priv = (void *)therm; | ||
127 | return nouveau_fan_update(priv->fan, immediate, percent); | ||
128 | } | ||
129 | |||
130 | int | ||
131 | nouveau_therm_fan_sense(struct nouveau_therm *therm) | ||
132 | { | ||
133 | struct nouveau_therm_priv *priv = (void *)therm; | ||
134 | struct nouveau_timer *ptimer = nouveau_timer(therm); | ||
135 | struct nouveau_gpio *gpio = nouveau_gpio(therm); | ||
136 | u32 cycles, cur, prev; | ||
137 | u64 start, end, tach; | ||
138 | |||
139 | if (priv->fan->tach.func == DCB_GPIO_UNUSED) | ||
140 | return -ENODEV; | ||
141 | |||
142 | /* Time a complete rotation and extrapolate to RPM: | ||
143 | * When the fan spins, it changes the value of GPIO FAN_SENSE. | ||
144 | * We get 4 changes (0 -> 1 -> 0 -> 1) per complete rotation. | ||
145 | */ | ||
146 | start = ptimer->read(ptimer); | ||
147 | prev = gpio->get(gpio, 0, priv->fan->tach.func, priv->fan->tach.line); | ||
148 | cycles = 0; | ||
149 | do { | ||
150 | usleep_range(500, 1000); /* supports 0 < rpm < 7500 */ | ||
151 | |||
152 | cur = gpio->get(gpio, 0, priv->fan->tach.func, priv->fan->tach.line); | ||
153 | if (prev != cur) { | ||
154 | if (!start) | ||
155 | start = ptimer->read(ptimer); | ||
156 | cycles++; | ||
157 | prev = cur; | ||
158 | } | ||
159 | } while (cycles < 5 && ptimer->read(ptimer) - start < 250000000); | ||
160 | end = ptimer->read(ptimer); | ||
161 | |||
162 | if (cycles == 5) { | ||
163 | tach = (u64)60000000000ULL; | ||
164 | do_div(tach, (end - start)); | ||
165 | return tach; | ||
166 | } else | ||
167 | return 0; | ||
168 | } | ||
169 | |||
170 | int | ||
171 | nouveau_therm_fan_user_get(struct nouveau_therm *therm) | ||
172 | { | ||
173 | return nouveau_therm_fan_get(therm); | ||
174 | } | ||
175 | |||
176 | int | ||
177 | nouveau_therm_fan_user_set(struct nouveau_therm *therm, int percent) | ||
178 | { | ||
179 | struct nouveau_therm_priv *priv = (void *)therm; | ||
180 | |||
181 | if (priv->mode != NOUVEAU_THERM_CTRL_MANUAL) | ||
182 | return -EINVAL; | ||
183 | |||
184 | return nouveau_therm_fan_set(therm, true, percent); | ||
185 | } | ||
186 | |||
187 | static void | ||
188 | nouveau_therm_fan_set_defaults(struct nouveau_therm *therm) | ||
189 | { | ||
190 | struct nouveau_therm_priv *priv = (void *)therm; | ||
191 | |||
192 | priv->fan->bios.pwm_freq = 0; | ||
193 | priv->fan->bios.min_duty = 0; | ||
194 | priv->fan->bios.max_duty = 100; | ||
195 | priv->fan->bios.bump_period = 500; | ||
196 | priv->fan->bios.slow_down_period = 2000; | ||
197 | priv->fan->bios.linear_min_temp = 40; | ||
198 | priv->fan->bios.linear_max_temp = 85; | ||
199 | } | ||
200 | |||
201 | static void | ||
202 | nouveau_therm_fan_safety_checks(struct nouveau_therm *therm) | ||
203 | { | ||
204 | struct nouveau_therm_priv *priv = (void *)therm; | ||
205 | |||
206 | if (priv->fan->bios.min_duty > 100) | ||
207 | priv->fan->bios.min_duty = 100; | ||
208 | if (priv->fan->bios.max_duty > 100) | ||
209 | priv->fan->bios.max_duty = 100; | ||
210 | |||
211 | if (priv->fan->bios.min_duty > priv->fan->bios.max_duty) | ||
212 | priv->fan->bios.min_duty = priv->fan->bios.max_duty; | ||
213 | } | ||
214 | |||
215 | int | ||
216 | nouveau_therm_fan_init(struct nouveau_therm *therm) | ||
217 | { | ||
218 | return 0; | ||
219 | } | ||
220 | |||
221 | int | ||
222 | nouveau_therm_fan_fini(struct nouveau_therm *therm, bool suspend) | ||
223 | { | ||
224 | struct nouveau_therm_priv *priv = (void *)therm; | ||
225 | struct nouveau_timer *ptimer = nouveau_timer(therm); | ||
226 | |||
227 | if (suspend) | ||
228 | ptimer->alarm_cancel(ptimer, &priv->fan->alarm); | ||
229 | return 0; | ||
230 | } | ||
231 | |||
232 | int | ||
233 | nouveau_therm_fan_ctor(struct nouveau_therm *therm) | ||
234 | { | ||
235 | struct nouveau_therm_priv *priv = (void *)therm; | ||
236 | struct nouveau_gpio *gpio = nouveau_gpio(therm); | ||
237 | struct nouveau_bios *bios = nouveau_bios(therm); | ||
238 | struct dcb_gpio_func func; | ||
239 | int ret; | ||
240 | |||
241 | /* attempt to locate a drivable fan, and determine control method */ | ||
242 | ret = gpio->find(gpio, 0, DCB_GPIO_FAN, 0xff, &func); | ||
243 | if (ret == 0) { | ||
244 | /* FIXME: is this really the place to perform such checks ? */ | ||
245 | if (func.line != 16 && func.log[0] & DCB_GPIO_LOG_DIR_IN) { | ||
246 | nv_debug(therm, "GPIO_FAN is in input mode\n"); | ||
247 | ret = -EINVAL; | ||
248 | } else { | ||
249 | ret = nouveau_fanpwm_create(therm, &func); | ||
250 | if (ret != 0) | ||
251 | ret = nouveau_fantog_create(therm, &func); | ||
252 | } | ||
253 | } | ||
254 | |||
255 | /* no controllable fan found, create a dummy fan module */ | ||
256 | if (ret != 0) { | ||
257 | ret = nouveau_fannil_create(therm); | ||
258 | if (ret) | ||
259 | return ret; | ||
260 | } | ||
261 | |||
262 | nv_info(therm, "FAN control: %s\n", priv->fan->type); | ||
263 | |||
264 | /* read the current speed, it is useful when resuming */ | ||
265 | priv->fan->percent = nouveau_therm_fan_get(therm); | ||
266 | |||
267 | /* attempt to detect a tachometer connection */ | ||
268 | ret = gpio->find(gpio, 0, DCB_GPIO_FAN_SENSE, 0xff, &priv->fan->tach); | ||
269 | if (ret) | ||
270 | priv->fan->tach.func = DCB_GPIO_UNUSED; | ||
271 | |||
272 | /* initialise fan bump/slow update handling */ | ||
273 | priv->fan->parent = therm; | ||
274 | nouveau_alarm_init(&priv->fan->alarm, nouveau_fan_alarm); | ||
275 | spin_lock_init(&priv->fan->lock); | ||
276 | |||
277 | /* other random init... */ | ||
278 | nouveau_therm_fan_set_defaults(therm); | ||
279 | nvbios_perf_fan_parse(bios, &priv->fan->perf); | ||
280 | if (!nvbios_fan_parse(bios, &priv->fan->bios)) { | ||
281 | nv_debug(therm, "parsing the fan table failed\n"); | ||
282 | if (nvbios_therm_fan_parse(bios, &priv->fan->bios)) | ||
283 | nv_error(therm, "parsing both fan tables failed\n"); | ||
284 | } | ||
285 | nouveau_therm_fan_safety_checks(therm); | ||
286 | return 0; | ||
287 | } | ||