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