diff options
author | Martin Peres <martin.peres@labri.fr> | 2012-11-04 18:18:49 -0500 |
---|---|---|
committer | Ben Skeggs <bskeggs@redhat.com> | 2013-02-20 01:00:23 -0500 |
commit | 0083b91dac482c70eeb96745d9ef604f904da3e5 (patch) | |
tree | 657ddc161b7e1190fb1bbd6a48a5c9b2b1dfd482 | |
parent | 9d7175c808793b3e30db455da7529d3c05b00712 (diff) |
drm/nouveau/therm: implement support for temperature alarms
For now, we only boost the fan speed to the maximum and auto-mode
when hitting the FAN_BOOST threshold and halt the computer when it
reaches the shutdown temperature. The downclock and critical thresholds
do nothing.
On nv43:50 and nva3+, temperature is polled because of the limited hardware.
I'll improve the nva3+ situation by implementing alarm management in PDAEMON
whenever I can but polling once every second shouldn't be such a problem.
v2 (Ben Skeggs):
- rebased
v3: fixed false-detections and threshold reprogrammation handling on nv50:nvc0
Signed-off-by: Ben Skeggs <bskeggs@redhat.com>
Signed-off-by: Martin Peres <martin.peres@labri.fr>
-rw-r--r-- | drivers/gpu/drm/nouveau/core/os.h | 1 | ||||
-rw-r--r-- | drivers/gpu/drm/nouveau/core/subdev/mc/nv98.c | 1 | ||||
-rw-r--r-- | drivers/gpu/drm/nouveau/core/subdev/therm/base.c | 11 | ||||
-rw-r--r-- | drivers/gpu/drm/nouveau/core/subdev/therm/nv40.c | 17 | ||||
-rw-r--r-- | drivers/gpu/drm/nouveau/core/subdev/therm/nv50.c | 153 | ||||
-rw-r--r-- | drivers/gpu/drm/nouveau/core/subdev/therm/nva3.c | 1 | ||||
-rw-r--r-- | drivers/gpu/drm/nouveau/core/subdev/therm/nvd0.c | 1 | ||||
-rw-r--r-- | drivers/gpu/drm/nouveau/core/subdev/therm/priv.h | 44 | ||||
-rw-r--r-- | drivers/gpu/drm/nouveau/core/subdev/therm/temp.c | 138 |
9 files changed, 366 insertions, 1 deletions
diff --git a/drivers/gpu/drm/nouveau/core/os.h b/drivers/gpu/drm/nouveau/core/os.h index cfe3b9cad156..eb496033b55c 100644 --- a/drivers/gpu/drm/nouveau/core/os.h +++ b/drivers/gpu/drm/nouveau/core/os.h | |||
@@ -16,6 +16,7 @@ | |||
16 | #include <linux/vmalloc.h> | 16 | #include <linux/vmalloc.h> |
17 | #include <linux/acpi.h> | 17 | #include <linux/acpi.h> |
18 | #include <linux/dmi.h> | 18 | #include <linux/dmi.h> |
19 | #include <linux/reboot.h> | ||
19 | 20 | ||
20 | #include <asm/unaligned.h> | 21 | #include <asm/unaligned.h> |
21 | 22 | ||
diff --git a/drivers/gpu/drm/nouveau/core/subdev/mc/nv98.c b/drivers/gpu/drm/nouveau/core/subdev/mc/nv98.c index b93fd094c806..3a80b29dce0f 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/mc/nv98.c +++ b/drivers/gpu/drm/nouveau/core/subdev/mc/nv98.c | |||
@@ -35,6 +35,7 @@ nv98_mc_intr[] = { | |||
35 | { 0x00001000, NVDEV_ENGINE_GR }, | 35 | { 0x00001000, NVDEV_ENGINE_GR }, |
36 | { 0x00004000, NVDEV_ENGINE_CRYPT }, /* NV84:NVA3 */ | 36 | { 0x00004000, NVDEV_ENGINE_CRYPT }, /* NV84:NVA3 */ |
37 | { 0x00008000, NVDEV_ENGINE_BSP }, | 37 | { 0x00008000, NVDEV_ENGINE_BSP }, |
38 | { 0x00080000, NVDEV_SUBDEV_THERM }, /* NVA3:NVC0 */ | ||
38 | { 0x00100000, NVDEV_SUBDEV_TIMER }, | 39 | { 0x00100000, NVDEV_SUBDEV_TIMER }, |
39 | { 0x00200000, NVDEV_SUBDEV_GPIO }, | 40 | { 0x00200000, NVDEV_SUBDEV_GPIO }, |
40 | { 0x00400000, NVDEV_ENGINE_COPY0 }, /* NVA3- */ | 41 | { 0x00400000, NVDEV_ENGINE_COPY0 }, /* NVA3- */ |
diff --git a/drivers/gpu/drm/nouveau/core/subdev/therm/base.c b/drivers/gpu/drm/nouveau/core/subdev/therm/base.c index dee764c7b693..f54495a0907a 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/therm/base.c +++ b/drivers/gpu/drm/nouveau/core/subdev/therm/base.c | |||
@@ -123,7 +123,7 @@ nouveau_therm_alarm(struct nouveau_alarm *alarm) | |||
123 | nouveau_therm_update(&priv->base, -1); | 123 | nouveau_therm_update(&priv->base, -1); |
124 | } | 124 | } |
125 | 125 | ||
126 | static int | 126 | int |
127 | nouveau_therm_mode(struct nouveau_therm *therm, int mode) | 127 | nouveau_therm_mode(struct nouveau_therm *therm, int mode) |
128 | { | 128 | { |
129 | struct nouveau_therm_priv *priv = (void *)therm; | 129 | struct nouveau_therm_priv *priv = (void *)therm; |
@@ -212,27 +212,35 @@ nouveau_therm_attr_set(struct nouveau_therm *therm, | |||
212 | return nouveau_therm_mode(therm, value); | 212 | return nouveau_therm_mode(therm, value); |
213 | case NOUVEAU_THERM_ATTR_THRS_FAN_BOOST: | 213 | case NOUVEAU_THERM_ATTR_THRS_FAN_BOOST: |
214 | priv->bios_sensor.thrs_fan_boost.temp = value; | 214 | priv->bios_sensor.thrs_fan_boost.temp = value; |
215 | priv->sensor.program_alarms(therm); | ||
215 | return 0; | 216 | return 0; |
216 | case NOUVEAU_THERM_ATTR_THRS_FAN_BOOST_HYST: | 217 | case NOUVEAU_THERM_ATTR_THRS_FAN_BOOST_HYST: |
217 | priv->bios_sensor.thrs_fan_boost.hysteresis = value; | 218 | priv->bios_sensor.thrs_fan_boost.hysteresis = value; |
219 | priv->sensor.program_alarms(therm); | ||
218 | return 0; | 220 | return 0; |
219 | case NOUVEAU_THERM_ATTR_THRS_DOWN_CLK: | 221 | case NOUVEAU_THERM_ATTR_THRS_DOWN_CLK: |
220 | priv->bios_sensor.thrs_down_clock.temp = value; | 222 | priv->bios_sensor.thrs_down_clock.temp = value; |
223 | priv->sensor.program_alarms(therm); | ||
221 | return 0; | 224 | return 0; |
222 | case NOUVEAU_THERM_ATTR_THRS_DOWN_CLK_HYST: | 225 | case NOUVEAU_THERM_ATTR_THRS_DOWN_CLK_HYST: |
223 | priv->bios_sensor.thrs_down_clock.hysteresis = value; | 226 | priv->bios_sensor.thrs_down_clock.hysteresis = value; |
227 | priv->sensor.program_alarms(therm); | ||
224 | return 0; | 228 | return 0; |
225 | case NOUVEAU_THERM_ATTR_THRS_CRITICAL: | 229 | case NOUVEAU_THERM_ATTR_THRS_CRITICAL: |
226 | priv->bios_sensor.thrs_critical.temp = value; | 230 | priv->bios_sensor.thrs_critical.temp = value; |
231 | priv->sensor.program_alarms(therm); | ||
227 | return 0; | 232 | return 0; |
228 | case NOUVEAU_THERM_ATTR_THRS_CRITICAL_HYST: | 233 | case NOUVEAU_THERM_ATTR_THRS_CRITICAL_HYST: |
229 | priv->bios_sensor.thrs_critical.hysteresis = value; | 234 | priv->bios_sensor.thrs_critical.hysteresis = value; |
235 | priv->sensor.program_alarms(therm); | ||
230 | return 0; | 236 | return 0; |
231 | case NOUVEAU_THERM_ATTR_THRS_SHUTDOWN: | 237 | case NOUVEAU_THERM_ATTR_THRS_SHUTDOWN: |
232 | priv->bios_sensor.thrs_shutdown.temp = value; | 238 | priv->bios_sensor.thrs_shutdown.temp = value; |
239 | priv->sensor.program_alarms(therm); | ||
233 | return 0; | 240 | return 0; |
234 | case NOUVEAU_THERM_ATTR_THRS_SHUTDOWN_HYST: | 241 | case NOUVEAU_THERM_ATTR_THRS_SHUTDOWN_HYST: |
235 | priv->bios_sensor.thrs_shutdown.hysteresis = value; | 242 | priv->bios_sensor.thrs_shutdown.hysteresis = value; |
243 | priv->sensor.program_alarms(therm); | ||
236 | return 0; | 244 | return 0; |
237 | } | 245 | } |
238 | 246 | ||
@@ -253,6 +261,7 @@ _nouveau_therm_init(struct nouveau_object *object) | |||
253 | if (priv->fan->percent >= 0) | 261 | if (priv->fan->percent >= 0) |
254 | therm->fan_set(therm, priv->fan->percent); | 262 | therm->fan_set(therm, priv->fan->percent); |
255 | 263 | ||
264 | priv->sensor.program_alarms(therm); | ||
256 | return 0; | 265 | return 0; |
257 | } | 266 | } |
258 | 267 | ||
diff --git a/drivers/gpu/drm/nouveau/core/subdev/therm/nv40.c b/drivers/gpu/drm/nouveau/core/subdev/therm/nv40.c index 3f7c11500598..5c17577553f6 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/therm/nv40.c +++ b/drivers/gpu/drm/nouveau/core/subdev/therm/nv40.c | |||
@@ -38,6 +38,7 @@ nv40_sensor_setup(struct nouveau_therm *therm) | |||
38 | if (device->chipset >= 0x46) { | 38 | if (device->chipset >= 0x46) { |
39 | nv_mask(therm, 0x15b8, 0x80000000, 0); | 39 | nv_mask(therm, 0x15b8, 0x80000000, 0); |
40 | nv_wr32(therm, 0x15b0, 0x80003fff); | 40 | nv_wr32(therm, 0x15b0, 0x80003fff); |
41 | mdelay(10); /* wait for the temperature to stabilize */ | ||
41 | return nv_rd32(therm, 0x15b4) & 0x3fff; | 42 | return nv_rd32(therm, 0x15b4) & 0x3fff; |
42 | } else { | 43 | } else { |
43 | nv_wr32(therm, 0x15b0, 0xff); | 44 | nv_wr32(therm, 0x15b0, 0xff); |
@@ -135,6 +136,20 @@ nv40_fan_pwm_set(struct nouveau_therm *therm, int line, u32 divs, u32 duty) | |||
135 | return 0; | 136 | return 0; |
136 | } | 137 | } |
137 | 138 | ||
139 | static void | ||
140 | nv40_therm_intr(struct nouveau_subdev *subdev) | ||
141 | { | ||
142 | struct nouveau_therm *therm = nouveau_therm(subdev); | ||
143 | uint32_t stat = nv_rd32(therm, 0x1100); | ||
144 | |||
145 | /* traitement */ | ||
146 | |||
147 | /* ack all IRQs */ | ||
148 | nv_wr32(therm, 0x1100, 0x70000); | ||
149 | |||
150 | nv_error(therm, "THERM received an IRQ: stat = %x\n", stat); | ||
151 | } | ||
152 | |||
138 | static int | 153 | static int |
139 | nv40_therm_ctor(struct nouveau_object *parent, | 154 | nv40_therm_ctor(struct nouveau_object *parent, |
140 | struct nouveau_object *engine, | 155 | struct nouveau_object *engine, |
@@ -153,6 +168,8 @@ nv40_therm_ctor(struct nouveau_object *parent, | |||
153 | priv->base.base.pwm_get = nv40_fan_pwm_get; | 168 | priv->base.base.pwm_get = nv40_fan_pwm_get; |
154 | priv->base.base.pwm_set = nv40_fan_pwm_set; | 169 | priv->base.base.pwm_set = nv40_fan_pwm_set; |
155 | priv->base.base.temp_get = nv40_temp_get; | 170 | priv->base.base.temp_get = nv40_temp_get; |
171 | priv->base.sensor.program_alarms = nouveau_therm_program_alarms_polling; | ||
172 | nv_subdev(priv)->intr = nv40_therm_intr; | ||
156 | return nouveau_therm_preinit(&priv->base.base); | 173 | return nouveau_therm_preinit(&priv->base.base); |
157 | } | 174 | } |
158 | 175 | ||
diff --git a/drivers/gpu/drm/nouveau/core/subdev/therm/nv50.c b/drivers/gpu/drm/nouveau/core/subdev/therm/nv50.c index 8bb4c2a15efa..4b7fe24f5506 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/therm/nv50.c +++ b/drivers/gpu/drm/nouveau/core/subdev/therm/nv50.c | |||
@@ -124,6 +124,141 @@ nv50_temp_get(struct nouveau_therm *therm) | |||
124 | return nv_rd32(therm, 0x20400); | 124 | return nv_rd32(therm, 0x20400); |
125 | } | 125 | } |
126 | 126 | ||
127 | static void | ||
128 | nv50_therm_program_alarms(struct nouveau_therm *therm) | ||
129 | { | ||
130 | struct nouveau_therm_priv *priv = (void *)therm; | ||
131 | struct nvbios_therm_sensor *sensor = &priv->bios_sensor; | ||
132 | unsigned long flags; | ||
133 | |||
134 | spin_lock_irqsave(&priv->sensor.alarm_program_lock, flags); | ||
135 | |||
136 | /* enable RISING and FALLING IRQs for shutdown, THRS 0, 1, 2 and 4 */ | ||
137 | nv_wr32(therm, 0x20000, 0x000003ff); | ||
138 | |||
139 | /* shutdown: The computer should be shutdown when reached */ | ||
140 | nv_wr32(therm, 0x20484, sensor->thrs_shutdown.hysteresis); | ||
141 | nv_wr32(therm, 0x20480, sensor->thrs_shutdown.temp); | ||
142 | |||
143 | /* THRS_1 : fan boost*/ | ||
144 | nv_wr32(therm, 0x204c4, sensor->thrs_fan_boost.temp); | ||
145 | |||
146 | /* THRS_2 : critical */ | ||
147 | nv_wr32(therm, 0x204c0, sensor->thrs_critical.temp); | ||
148 | |||
149 | /* THRS_4 : down clock */ | ||
150 | nv_wr32(therm, 0x20414, sensor->thrs_down_clock.temp); | ||
151 | spin_unlock_irqrestore(&priv->sensor.alarm_program_lock, flags); | ||
152 | |||
153 | nv_info(therm, | ||
154 | "Programmed thresholds [ %d(%d), %d(%d), %d(%d), %d(%d) ]\n", | ||
155 | sensor->thrs_fan_boost.temp, sensor->thrs_fan_boost.hysteresis, | ||
156 | sensor->thrs_down_clock.temp, | ||
157 | sensor->thrs_down_clock.hysteresis, | ||
158 | sensor->thrs_critical.temp, sensor->thrs_critical.hysteresis, | ||
159 | sensor->thrs_shutdown.temp, sensor->thrs_shutdown.hysteresis); | ||
160 | |||
161 | } | ||
162 | |||
163 | /* must be called with alarm_program_lock taken ! */ | ||
164 | static void | ||
165 | nv50_therm_threshold_hyst_emulation(struct nouveau_therm *therm, | ||
166 | uint32_t thrs_reg, u8 status_bit, | ||
167 | const struct nvbios_therm_threshold *thrs, | ||
168 | enum nouveau_therm_thrs thrs_name) | ||
169 | { | ||
170 | enum nouveau_therm_thrs_direction direction; | ||
171 | enum nouveau_therm_thrs_state prev_state, new_state; | ||
172 | int temp, cur; | ||
173 | |||
174 | prev_state = nouveau_therm_sensor_get_threshold_state(therm, thrs_name); | ||
175 | temp = nv_rd32(therm, thrs_reg); | ||
176 | |||
177 | /* program the next threshold */ | ||
178 | if (temp == thrs->temp) { | ||
179 | nv_wr32(therm, thrs_reg, thrs->temp - thrs->hysteresis); | ||
180 | new_state = NOUVEAU_THERM_THRS_HIGHER; | ||
181 | } else { | ||
182 | nv_wr32(therm, thrs_reg, thrs->temp); | ||
183 | new_state = NOUVEAU_THERM_THRS_LOWER; | ||
184 | } | ||
185 | |||
186 | /* fix the state (in case someone reprogrammed the alarms) */ | ||
187 | cur = therm->temp_get(therm); | ||
188 | if (new_state == NOUVEAU_THERM_THRS_LOWER && cur > thrs->temp) | ||
189 | new_state = NOUVEAU_THERM_THRS_HIGHER; | ||
190 | else if (new_state == NOUVEAU_THERM_THRS_HIGHER && | ||
191 | cur < thrs->temp - thrs->hysteresis) | ||
192 | new_state = NOUVEAU_THERM_THRS_LOWER; | ||
193 | nouveau_therm_sensor_set_threshold_state(therm, thrs_name, new_state); | ||
194 | |||
195 | /* find the direction */ | ||
196 | if (prev_state < new_state) | ||
197 | direction = NOUVEAU_THERM_THRS_RISING; | ||
198 | else if (prev_state > new_state) | ||
199 | direction = NOUVEAU_THERM_THRS_FALLING; | ||
200 | else | ||
201 | return; | ||
202 | |||
203 | /* advertise a change in direction */ | ||
204 | nouveau_therm_sensor_event(therm, thrs_name, direction); | ||
205 | } | ||
206 | |||
207 | static void | ||
208 | nv50_therm_intr(struct nouveau_subdev *subdev) | ||
209 | { | ||
210 | struct nouveau_therm *therm = nouveau_therm(subdev); | ||
211 | struct nouveau_therm_priv *priv = (void *)therm; | ||
212 | struct nvbios_therm_sensor *sensor = &priv->bios_sensor; | ||
213 | unsigned long flags; | ||
214 | uint32_t intr; | ||
215 | |||
216 | spin_lock_irqsave(&priv->sensor.alarm_program_lock, flags); | ||
217 | |||
218 | intr = nv_rd32(therm, 0x20100); | ||
219 | |||
220 | /* THRS_4: downclock */ | ||
221 | if (intr & 0x002) { | ||
222 | nv50_therm_threshold_hyst_emulation(therm, 0x20414, 24, | ||
223 | &sensor->thrs_down_clock, | ||
224 | NOUVEAU_THERM_THRS_DOWNCLOCK); | ||
225 | intr &= ~0x002; | ||
226 | } | ||
227 | |||
228 | /* shutdown */ | ||
229 | if (intr & 0x004) { | ||
230 | nv50_therm_threshold_hyst_emulation(therm, 0x20480, 20, | ||
231 | &sensor->thrs_shutdown, | ||
232 | NOUVEAU_THERM_THRS_SHUTDOWN); | ||
233 | intr &= ~0x004; | ||
234 | } | ||
235 | |||
236 | /* THRS_1 : fan boost */ | ||
237 | if (intr & 0x008) { | ||
238 | nv50_therm_threshold_hyst_emulation(therm, 0x204c4, 21, | ||
239 | &sensor->thrs_fan_boost, | ||
240 | NOUVEAU_THERM_THRS_FANBOOST); | ||
241 | intr &= ~0x008; | ||
242 | } | ||
243 | |||
244 | /* THRS_2 : critical */ | ||
245 | if (intr & 0x010) { | ||
246 | nv50_therm_threshold_hyst_emulation(therm, 0x204c0, 22, | ||
247 | &sensor->thrs_critical, | ||
248 | NOUVEAU_THERM_THRS_CRITICAL); | ||
249 | intr &= ~0x010; | ||
250 | } | ||
251 | |||
252 | if (intr) | ||
253 | nv_error(therm, "unhandled intr 0x%08x\n", intr); | ||
254 | |||
255 | /* ACK everything */ | ||
256 | nv_wr32(therm, 0x20100, 0xffffffff); | ||
257 | nv_wr32(therm, 0x1100, 0x10000); /* PBUS */ | ||
258 | |||
259 | spin_unlock_irqrestore(&priv->sensor.alarm_program_lock, flags); | ||
260 | } | ||
261 | |||
127 | static int | 262 | static int |
128 | nv50_therm_ctor(struct nouveau_object *parent, | 263 | nv50_therm_ctor(struct nouveau_object *parent, |
129 | struct nouveau_object *engine, | 264 | struct nouveau_object *engine, |
@@ -143,6 +278,24 @@ nv50_therm_ctor(struct nouveau_object *parent, | |||
143 | priv->base.base.pwm_set = nv50_fan_pwm_set; | 278 | priv->base.base.pwm_set = nv50_fan_pwm_set; |
144 | priv->base.base.pwm_clock = nv50_fan_pwm_clock; | 279 | priv->base.base.pwm_clock = nv50_fan_pwm_clock; |
145 | priv->base.base.temp_get = nv50_temp_get; | 280 | priv->base.base.temp_get = nv50_temp_get; |
281 | priv->base.sensor.program_alarms = nv50_therm_program_alarms; | ||
282 | spin_lock_init(&priv->base.sensor.alarm_program_lock); | ||
283 | nv_subdev(priv)->intr = nv50_therm_intr; | ||
284 | |||
285 | /* init the thresholds */ | ||
286 | nouveau_therm_sensor_set_threshold_state(&priv->base.base, | ||
287 | NOUVEAU_THERM_THRS_SHUTDOWN, | ||
288 | NOUVEAU_THERM_THRS_LOWER); | ||
289 | nouveau_therm_sensor_set_threshold_state(&priv->base.base, | ||
290 | NOUVEAU_THERM_THRS_FANBOOST, | ||
291 | NOUVEAU_THERM_THRS_LOWER); | ||
292 | nouveau_therm_sensor_set_threshold_state(&priv->base.base, | ||
293 | NOUVEAU_THERM_THRS_CRITICAL, | ||
294 | NOUVEAU_THERM_THRS_LOWER); | ||
295 | nouveau_therm_sensor_set_threshold_state(&priv->base.base, | ||
296 | NOUVEAU_THERM_THRS_DOWNCLOCK, | ||
297 | NOUVEAU_THERM_THRS_LOWER); | ||
298 | |||
146 | return nouveau_therm_preinit(&priv->base.base); | 299 | return nouveau_therm_preinit(&priv->base.base); |
147 | } | 300 | } |
148 | 301 | ||
diff --git a/drivers/gpu/drm/nouveau/core/subdev/therm/nva3.c b/drivers/gpu/drm/nouveau/core/subdev/therm/nva3.c index 8625d219f5ca..2dcc5437116a 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/therm/nva3.c +++ b/drivers/gpu/drm/nouveau/core/subdev/therm/nva3.c | |||
@@ -83,6 +83,7 @@ nva3_therm_ctor(struct nouveau_object *parent, | |||
83 | priv->base.base.pwm_clock = nv50_fan_pwm_clock; | 83 | priv->base.base.pwm_clock = nv50_fan_pwm_clock; |
84 | priv->base.base.temp_get = nv50_temp_get; | 84 | priv->base.base.temp_get = nv50_temp_get; |
85 | priv->base.base.fan_sense = nva3_therm_fan_sense; | 85 | priv->base.base.fan_sense = nva3_therm_fan_sense; |
86 | priv->base.sensor.program_alarms = nouveau_therm_program_alarms_polling; | ||
86 | return nouveau_therm_preinit(&priv->base.base); | 87 | return nouveau_therm_preinit(&priv->base.base); |
87 | } | 88 | } |
88 | 89 | ||
diff --git a/drivers/gpu/drm/nouveau/core/subdev/therm/nvd0.c b/drivers/gpu/drm/nouveau/core/subdev/therm/nvd0.c index e722dcd4e37b..d7d30ee8332e 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/therm/nvd0.c +++ b/drivers/gpu/drm/nouveau/core/subdev/therm/nvd0.c | |||
@@ -137,6 +137,7 @@ nvd0_therm_ctor(struct nouveau_object *parent, | |||
137 | priv->base.base.pwm_clock = nvd0_fan_pwm_clock; | 137 | priv->base.base.pwm_clock = nvd0_fan_pwm_clock; |
138 | priv->base.base.temp_get = nv50_temp_get; | 138 | priv->base.base.temp_get = nv50_temp_get; |
139 | priv->base.base.fan_sense = nva3_therm_fan_sense; | 139 | priv->base.base.fan_sense = nva3_therm_fan_sense; |
140 | priv->base.sensor.program_alarms = nouveau_therm_program_alarms_polling; | ||
140 | return nouveau_therm_preinit(&priv->base.base); | 141 | return nouveau_therm_preinit(&priv->base.base); |
141 | } | 142 | } |
142 | 143 | ||
diff --git a/drivers/gpu/drm/nouveau/core/subdev/therm/priv.h b/drivers/gpu/drm/nouveau/core/subdev/therm/priv.h index 5c628b5832e6..c2413e6aae25 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/therm/priv.h +++ b/drivers/gpu/drm/nouveau/core/subdev/therm/priv.h | |||
@@ -50,6 +50,24 @@ struct nouveau_fan { | |||
50 | struct dcb_gpio_func tach; | 50 | struct dcb_gpio_func tach; |
51 | }; | 51 | }; |
52 | 52 | ||
53 | enum nouveau_therm_thrs_direction { | ||
54 | NOUVEAU_THERM_THRS_FALLING = 0, | ||
55 | NOUVEAU_THERM_THRS_RISING = 1 | ||
56 | }; | ||
57 | |||
58 | enum nouveau_therm_thrs_state { | ||
59 | NOUVEAU_THERM_THRS_LOWER = 0, | ||
60 | NOUVEAU_THERM_THRS_HIGHER = 1 | ||
61 | }; | ||
62 | |||
63 | enum nouveau_therm_thrs { | ||
64 | NOUVEAU_THERM_THRS_FANBOOST = 0, | ||
65 | NOUVEAU_THERM_THRS_DOWNCLOCK = 1, | ||
66 | NOUVEAU_THERM_THRS_CRITICAL = 2, | ||
67 | NOUVEAU_THERM_THRS_SHUTDOWN = 3, | ||
68 | NOUVEAU_THERM_THRS_NR | ||
69 | }; | ||
70 | |||
53 | struct nouveau_therm_priv { | 71 | struct nouveau_therm_priv { |
54 | struct nouveau_therm base; | 72 | struct nouveau_therm base; |
55 | 73 | ||
@@ -65,10 +83,25 @@ struct nouveau_therm_priv { | |||
65 | /* fan priv */ | 83 | /* fan priv */ |
66 | struct nouveau_fan *fan; | 84 | struct nouveau_fan *fan; |
67 | 85 | ||
86 | /* alarms priv */ | ||
87 | struct { | ||
88 | spinlock_t alarm_program_lock; | ||
89 | struct nouveau_alarm therm_poll_alarm; | ||
90 | enum nouveau_therm_thrs_state alarm_state[NOUVEAU_THERM_THRS_NR]; | ||
91 | void (*program_alarms)(struct nouveau_therm *); | ||
92 | } sensor; | ||
93 | |||
94 | /* what should be done if the card overheats */ | ||
95 | struct { | ||
96 | void (*downclock)(struct nouveau_therm *, bool active); | ||
97 | void (*pause)(struct nouveau_therm *, bool active); | ||
98 | } emergency; | ||
99 | |||
68 | /* ic */ | 100 | /* ic */ |
69 | struct i2c_client *ic; | 101 | struct i2c_client *ic; |
70 | }; | 102 | }; |
71 | 103 | ||
104 | int nouveau_therm_mode(struct nouveau_therm *therm, int mode); | ||
72 | int nouveau_therm_attr_get(struct nouveau_therm *therm, | 105 | int nouveau_therm_attr_get(struct nouveau_therm *therm, |
73 | enum nouveau_therm_attr_type type); | 106 | enum nouveau_therm_attr_type type); |
74 | int nouveau_therm_attr_set(struct nouveau_therm *therm, | 107 | int nouveau_therm_attr_set(struct nouveau_therm *therm, |
@@ -88,6 +121,17 @@ int nouveau_therm_fan_sense(struct nouveau_therm *therm); | |||
88 | 121 | ||
89 | int nouveau_therm_preinit(struct nouveau_therm *); | 122 | int nouveau_therm_preinit(struct nouveau_therm *); |
90 | 123 | ||
124 | void nouveau_therm_sensor_set_threshold_state(struct nouveau_therm *therm, | ||
125 | enum nouveau_therm_thrs thrs, | ||
126 | enum nouveau_therm_thrs_state st); | ||
127 | enum nouveau_therm_thrs_state | ||
128 | nouveau_therm_sensor_get_threshold_state(struct nouveau_therm *therm, | ||
129 | enum nouveau_therm_thrs thrs); | ||
130 | void nouveau_therm_sensor_event(struct nouveau_therm *therm, | ||
131 | enum nouveau_therm_thrs thrs, | ||
132 | enum nouveau_therm_thrs_direction dir); | ||
133 | void nouveau_therm_program_alarms_polling(struct nouveau_therm *therm); | ||
134 | |||
91 | int nv50_fan_pwm_ctrl(struct nouveau_therm *, int, bool); | 135 | int nv50_fan_pwm_ctrl(struct nouveau_therm *, int, bool); |
92 | int nv50_fan_pwm_get(struct nouveau_therm *, int, u32 *, u32 *); | 136 | int nv50_fan_pwm_get(struct nouveau_therm *, int, u32 *, u32 *); |
93 | int nv50_fan_pwm_set(struct nouveau_therm *, int, u32, u32); | 137 | int nv50_fan_pwm_set(struct nouveau_therm *, int, u32, u32); |
diff --git a/drivers/gpu/drm/nouveau/core/subdev/therm/temp.c b/drivers/gpu/drm/nouveau/core/subdev/therm/temp.c index 204282301fb1..d80c572de04f 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/therm/temp.c +++ b/drivers/gpu/drm/nouveau/core/subdev/therm/temp.c | |||
@@ -65,12 +65,150 @@ nouveau_therm_temp_safety_checks(struct nouveau_therm *therm) | |||
65 | priv->bios_sensor.offset_den = 1; | 65 | priv->bios_sensor.offset_den = 1; |
66 | } | 66 | } |
67 | 67 | ||
68 | /* must be called with alarm_program_lock taken ! */ | ||
69 | void nouveau_therm_sensor_set_threshold_state(struct nouveau_therm *therm, | ||
70 | enum nouveau_therm_thrs thrs, | ||
71 | enum nouveau_therm_thrs_state st) | ||
72 | { | ||
73 | struct nouveau_therm_priv *priv = (void *)therm; | ||
74 | priv->sensor.alarm_state[thrs] = st; | ||
75 | } | ||
76 | |||
77 | /* must be called with alarm_program_lock taken ! */ | ||
78 | enum nouveau_therm_thrs_state | ||
79 | nouveau_therm_sensor_get_threshold_state(struct nouveau_therm *therm, | ||
80 | enum nouveau_therm_thrs thrs) | ||
81 | { | ||
82 | struct nouveau_therm_priv *priv = (void *)therm; | ||
83 | return priv->sensor.alarm_state[thrs]; | ||
84 | } | ||
85 | |||
86 | void nouveau_therm_sensor_event(struct nouveau_therm *therm, | ||
87 | enum nouveau_therm_thrs thrs, | ||
88 | enum nouveau_therm_thrs_direction dir) | ||
89 | { | ||
90 | struct nouveau_therm_priv *priv = (void *)therm; | ||
91 | bool active; | ||
92 | const char *thresolds[] = { | ||
93 | "fanboost", "downclock", "critical", "shutdown" | ||
94 | }; | ||
95 | uint8_t temperature = therm->temp_get(therm); | ||
96 | |||
97 | if (thrs < 0 || thrs > 3) | ||
98 | return; | ||
99 | |||
100 | if (dir == NOUVEAU_THERM_THRS_FALLING) | ||
101 | nv_info(therm, "temperature (%u C) went bellow the '%s' threshold\n", | ||
102 | temperature, thresolds[thrs]); | ||
103 | else | ||
104 | nv_info(therm, "temperature (%u C) hit the '%s' threshold\n", | ||
105 | temperature, thresolds[thrs]); | ||
106 | |||
107 | active = (dir == NOUVEAU_THERM_THRS_RISING); | ||
108 | switch (thrs) { | ||
109 | case NOUVEAU_THERM_THRS_FANBOOST: | ||
110 | nouveau_therm_fan_set(therm, true, 100); | ||
111 | nouveau_therm_mode(therm, FAN_CONTROL_AUTO); | ||
112 | break; | ||
113 | case NOUVEAU_THERM_THRS_DOWNCLOCK: | ||
114 | if (priv->emergency.downclock) | ||
115 | priv->emergency.downclock(therm, active); | ||
116 | break; | ||
117 | case NOUVEAU_THERM_THRS_CRITICAL: | ||
118 | if (priv->emergency.pause) | ||
119 | priv->emergency.pause(therm, active); | ||
120 | break; | ||
121 | case NOUVEAU_THERM_THRS_SHUTDOWN: | ||
122 | orderly_poweroff(true); | ||
123 | break; | ||
124 | case NOUVEAU_THERM_THRS_NR: | ||
125 | break; | ||
126 | } | ||
127 | |||
128 | } | ||
129 | |||
130 | /* must be called with alarm_program_lock taken ! */ | ||
131 | static void | ||
132 | nouveau_therm_threshold_hyst_polling(struct nouveau_therm *therm, | ||
133 | const struct nvbios_therm_threshold *thrs, | ||
134 | enum nouveau_therm_thrs thrs_name) | ||
135 | { | ||
136 | enum nouveau_therm_thrs_direction direction; | ||
137 | enum nouveau_therm_thrs_state prev_state, new_state; | ||
138 | int temp = therm->temp_get(therm); | ||
139 | |||
140 | prev_state = nouveau_therm_sensor_get_threshold_state(therm, thrs_name); | ||
141 | |||
142 | if (temp >= thrs->temp && prev_state == NOUVEAU_THERM_THRS_LOWER) { | ||
143 | direction = NOUVEAU_THERM_THRS_RISING; | ||
144 | new_state = NOUVEAU_THERM_THRS_HIGHER; | ||
145 | } else if (temp <= thrs->temp - thrs->hysteresis && | ||
146 | prev_state == NOUVEAU_THERM_THRS_HIGHER) { | ||
147 | direction = NOUVEAU_THERM_THRS_FALLING; | ||
148 | new_state = NOUVEAU_THERM_THRS_LOWER; | ||
149 | } else | ||
150 | return; /* nothing to do */ | ||
151 | |||
152 | nouveau_therm_sensor_set_threshold_state(therm, thrs_name, new_state); | ||
153 | nouveau_therm_sensor_event(therm, thrs_name, direction); | ||
154 | } | ||
155 | |||
156 | static void | ||
157 | alarm_timer_callback(struct nouveau_alarm *alarm) | ||
158 | { | ||
159 | struct nouveau_therm_priv *priv = | ||
160 | container_of(alarm, struct nouveau_therm_priv, sensor.therm_poll_alarm); | ||
161 | struct nvbios_therm_sensor *sensor = &priv->bios_sensor; | ||
162 | struct nouveau_timer *ptimer = nouveau_timer(priv); | ||
163 | struct nouveau_therm *therm = &priv->base; | ||
164 | unsigned long flags; | ||
165 | |||
166 | spin_lock_irqsave(&priv->sensor.alarm_program_lock, flags); | ||
167 | |||
168 | nouveau_therm_threshold_hyst_polling(therm, &sensor->thrs_fan_boost, | ||
169 | NOUVEAU_THERM_THRS_FANBOOST); | ||
170 | |||
171 | nouveau_therm_threshold_hyst_polling(therm, &sensor->thrs_down_clock, | ||
172 | NOUVEAU_THERM_THRS_DOWNCLOCK); | ||
173 | |||
174 | nouveau_therm_threshold_hyst_polling(therm, &sensor->thrs_critical, | ||
175 | NOUVEAU_THERM_THRS_CRITICAL); | ||
176 | |||
177 | nouveau_therm_threshold_hyst_polling(therm, &sensor->thrs_shutdown, | ||
178 | NOUVEAU_THERM_THRS_SHUTDOWN); | ||
179 | |||
180 | /* schedule the next poll in one second */ | ||
181 | if (list_empty(&alarm->head)) | ||
182 | ptimer->alarm(ptimer, 1000 * 1000 * 1000, alarm); | ||
183 | |||
184 | spin_unlock_irqrestore(&priv->sensor.alarm_program_lock, flags); | ||
185 | } | ||
186 | |||
187 | void | ||
188 | nouveau_therm_program_alarms_polling(struct nouveau_therm *therm) | ||
189 | { | ||
190 | struct nouveau_therm_priv *priv = (void *)therm; | ||
191 | struct nvbios_therm_sensor *sensor = &priv->bios_sensor; | ||
192 | |||
193 | nv_info(therm, | ||
194 | "programmed thresholds [ %d(%d), %d(%d), %d(%d), %d(%d) ]\n", | ||
195 | sensor->thrs_fan_boost.temp, sensor->thrs_fan_boost.hysteresis, | ||
196 | sensor->thrs_down_clock.temp, | ||
197 | sensor->thrs_down_clock.hysteresis, | ||
198 | sensor->thrs_critical.temp, sensor->thrs_critical.hysteresis, | ||
199 | sensor->thrs_shutdown.temp, sensor->thrs_shutdown.hysteresis); | ||
200 | |||
201 | alarm_timer_callback(&priv->sensor.therm_poll_alarm); | ||
202 | } | ||
203 | |||
68 | int | 204 | int |
69 | nouveau_therm_sensor_ctor(struct nouveau_therm *therm) | 205 | nouveau_therm_sensor_ctor(struct nouveau_therm *therm) |
70 | { | 206 | { |
71 | struct nouveau_therm_priv *priv = (void *)therm; | 207 | struct nouveau_therm_priv *priv = (void *)therm; |
72 | struct nouveau_bios *bios = nouveau_bios(therm); | 208 | struct nouveau_bios *bios = nouveau_bios(therm); |
73 | 209 | ||
210 | nouveau_alarm_init(&priv->sensor.therm_poll_alarm, alarm_timer_callback); | ||
211 | |||
74 | nouveau_therm_temp_set_defaults(therm); | 212 | nouveau_therm_temp_set_defaults(therm); |
75 | if (nvbios_therm_sensor_parse(bios, NVBIOS_THERM_DOMAIN_CORE, | 213 | if (nvbios_therm_sensor_parse(bios, NVBIOS_THERM_DOMAIN_CORE, |
76 | &priv->bios_sensor)) | 214 | &priv->bios_sensor)) |