1*3aa663a7Sriastradh /*	$NetBSD: nouveau_nvkm_subdev_therm_temp.c,v 1.5 2021/12/19 10:51:59 riastradh Exp $	*/
2d350ecf5Sriastradh 
3d350ecf5Sriastradh /*
4d350ecf5Sriastradh  * Copyright 2012 The Nouveau community
5d350ecf5Sriastradh  *
6d350ecf5Sriastradh  * Permission is hereby granted, free of charge, to any person obtaining a
7d350ecf5Sriastradh  * copy of this software and associated documentation files (the "Software"),
8d350ecf5Sriastradh  * to deal in the Software without restriction, including without limitation
9d350ecf5Sriastradh  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10d350ecf5Sriastradh  * and/or sell copies of the Software, and to permit persons to whom the
11d350ecf5Sriastradh  * Software is furnished to do so, subject to the following conditions:
12d350ecf5Sriastradh  *
13d350ecf5Sriastradh  * The above copyright notice and this permission notice shall be included in
14d350ecf5Sriastradh  * all copies or substantial portions of the Software.
15d350ecf5Sriastradh  *
16d350ecf5Sriastradh  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17d350ecf5Sriastradh  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18d350ecf5Sriastradh  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
19d350ecf5Sriastradh  * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
20d350ecf5Sriastradh  * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21d350ecf5Sriastradh  * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22d350ecf5Sriastradh  * OTHER DEALINGS IN THE SOFTWARE.
23d350ecf5Sriastradh  *
24d350ecf5Sriastradh  * Authors: Martin Peres
25d350ecf5Sriastradh  */
26d350ecf5Sriastradh #include <sys/cdefs.h>
27*3aa663a7Sriastradh __KERNEL_RCSID(0, "$NetBSD: nouveau_nvkm_subdev_therm_temp.c,v 1.5 2021/12/19 10:51:59 riastradh Exp $");
28d350ecf5Sriastradh 
29d350ecf5Sriastradh #include "priv.h"
30d350ecf5Sriastradh 
31d350ecf5Sriastradh static void
nvkm_therm_temp_set_defaults(struct nvkm_therm * therm)32d350ecf5Sriastradh nvkm_therm_temp_set_defaults(struct nvkm_therm *therm)
33d350ecf5Sriastradh {
34d350ecf5Sriastradh 	therm->bios_sensor.offset_constant = 0;
35d350ecf5Sriastradh 
36d350ecf5Sriastradh 	therm->bios_sensor.thrs_fan_boost.temp = 90;
37d350ecf5Sriastradh 	therm->bios_sensor.thrs_fan_boost.hysteresis = 3;
38d350ecf5Sriastradh 
39d350ecf5Sriastradh 	therm->bios_sensor.thrs_down_clock.temp = 95;
40d350ecf5Sriastradh 	therm->bios_sensor.thrs_down_clock.hysteresis = 3;
41d350ecf5Sriastradh 
42d350ecf5Sriastradh 	therm->bios_sensor.thrs_critical.temp = 105;
43d350ecf5Sriastradh 	therm->bios_sensor.thrs_critical.hysteresis = 5;
44d350ecf5Sriastradh 
45d350ecf5Sriastradh 	therm->bios_sensor.thrs_shutdown.temp = 135;
46d350ecf5Sriastradh 	therm->bios_sensor.thrs_shutdown.hysteresis = 5; /*not that it matters */
47d350ecf5Sriastradh }
48d350ecf5Sriastradh 
49d350ecf5Sriastradh static void
nvkm_therm_temp_safety_checks(struct nvkm_therm * therm)50d350ecf5Sriastradh nvkm_therm_temp_safety_checks(struct nvkm_therm *therm)
51d350ecf5Sriastradh {
52d350ecf5Sriastradh 	struct nvbios_therm_sensor *s = &therm->bios_sensor;
53d350ecf5Sriastradh 
54d350ecf5Sriastradh 	/* enforce a minimum hysteresis on thresholds */
55d350ecf5Sriastradh 	s->thrs_fan_boost.hysteresis = max_t(u8, s->thrs_fan_boost.hysteresis, 2);
56d350ecf5Sriastradh 	s->thrs_down_clock.hysteresis = max_t(u8, s->thrs_down_clock.hysteresis, 2);
57d350ecf5Sriastradh 	s->thrs_critical.hysteresis = max_t(u8, s->thrs_critical.hysteresis, 2);
58d350ecf5Sriastradh 	s->thrs_shutdown.hysteresis = max_t(u8, s->thrs_shutdown.hysteresis, 2);
59d350ecf5Sriastradh }
60d350ecf5Sriastradh 
61d350ecf5Sriastradh /* must be called with alarm_program_lock taken ! */
62d350ecf5Sriastradh void
nvkm_therm_sensor_set_threshold_state(struct nvkm_therm * therm,enum nvkm_therm_thrs thrs,enum nvkm_therm_thrs_state st)63d350ecf5Sriastradh nvkm_therm_sensor_set_threshold_state(struct nvkm_therm *therm,
64d350ecf5Sriastradh 				      enum nvkm_therm_thrs thrs,
65d350ecf5Sriastradh 				      enum nvkm_therm_thrs_state st)
66d350ecf5Sriastradh {
67d350ecf5Sriastradh 	therm->sensor.alarm_state[thrs] = st;
68d350ecf5Sriastradh }
69d350ecf5Sriastradh 
70d350ecf5Sriastradh /* must be called with alarm_program_lock taken ! */
71d350ecf5Sriastradh enum nvkm_therm_thrs_state
nvkm_therm_sensor_get_threshold_state(struct nvkm_therm * therm,enum nvkm_therm_thrs thrs)72d350ecf5Sriastradh nvkm_therm_sensor_get_threshold_state(struct nvkm_therm *therm,
73d350ecf5Sriastradh 				      enum nvkm_therm_thrs thrs)
74d350ecf5Sriastradh {
75d350ecf5Sriastradh 	return therm->sensor.alarm_state[thrs];
76d350ecf5Sriastradh }
77d350ecf5Sriastradh 
78d350ecf5Sriastradh static void
nv_poweroff_work(struct work_struct * work)79d350ecf5Sriastradh nv_poweroff_work(struct work_struct *work)
80d350ecf5Sriastradh {
81d350ecf5Sriastradh 	orderly_poweroff(true);
82d350ecf5Sriastradh 	kfree(work);
83d350ecf5Sriastradh }
84d350ecf5Sriastradh 
85d350ecf5Sriastradh void
nvkm_therm_sensor_event(struct nvkm_therm * therm,enum nvkm_therm_thrs thrs,enum nvkm_therm_thrs_direction dir)86d350ecf5Sriastradh nvkm_therm_sensor_event(struct nvkm_therm *therm, enum nvkm_therm_thrs thrs,
87d350ecf5Sriastradh 			enum nvkm_therm_thrs_direction dir)
88d350ecf5Sriastradh {
89d350ecf5Sriastradh 	struct nvkm_subdev *subdev = &therm->subdev;
90d350ecf5Sriastradh 	bool active;
91677dec6eSriastradh 	static const char * const thresholds[] = {
92d350ecf5Sriastradh 		"fanboost", "downclock", "critical", "shutdown"
93d350ecf5Sriastradh 	};
94d350ecf5Sriastradh 	int temperature = therm->func->temp_get(therm);
95d350ecf5Sriastradh 
96*3aa663a7Sriastradh 	if ((unsigned)thrs >= __arraycount(thresholds))
97d350ecf5Sriastradh 		return;
98d350ecf5Sriastradh 
99d350ecf5Sriastradh 	if (dir == NVKM_THERM_THRS_FALLING)
100d350ecf5Sriastradh 		nvkm_info(subdev,
101d350ecf5Sriastradh 			  "temperature (%i C) went below the '%s' threshold\n",
102677dec6eSriastradh 			  temperature, thresholds[thrs]);
103d350ecf5Sriastradh 	else
104d350ecf5Sriastradh 		nvkm_info(subdev, "temperature (%i C) hit the '%s' threshold\n",
105677dec6eSriastradh 			  temperature, thresholds[thrs]);
106d350ecf5Sriastradh 
107d350ecf5Sriastradh 	active = (dir == NVKM_THERM_THRS_RISING);
108d350ecf5Sriastradh 	switch (thrs) {
109d350ecf5Sriastradh 	case NVKM_THERM_THRS_FANBOOST:
110d350ecf5Sriastradh 		if (active) {
111d350ecf5Sriastradh 			nvkm_therm_fan_set(therm, true, 100);
112d350ecf5Sriastradh 			nvkm_therm_fan_mode(therm, NVKM_THERM_CTRL_AUTO);
113d350ecf5Sriastradh 		}
114d350ecf5Sriastradh 		break;
115d350ecf5Sriastradh 	case NVKM_THERM_THRS_DOWNCLOCK:
116d350ecf5Sriastradh 		if (therm->emergency.downclock)
117d350ecf5Sriastradh 			therm->emergency.downclock(therm, active);
118d350ecf5Sriastradh 		break;
119d350ecf5Sriastradh 	case NVKM_THERM_THRS_CRITICAL:
120d350ecf5Sriastradh 		if (therm->emergency.pause)
121d350ecf5Sriastradh 			therm->emergency.pause(therm, active);
122d350ecf5Sriastradh 		break;
123d350ecf5Sriastradh 	case NVKM_THERM_THRS_SHUTDOWN:
124d350ecf5Sriastradh 		if (active) {
125d350ecf5Sriastradh 			struct work_struct *work;
126d350ecf5Sriastradh 
127d350ecf5Sriastradh 			work = kmalloc(sizeof(*work), GFP_ATOMIC);
128d350ecf5Sriastradh 			if (work) {
129d350ecf5Sriastradh 				INIT_WORK(work, nv_poweroff_work);
130d350ecf5Sriastradh 				schedule_work(work);
131d350ecf5Sriastradh 			}
132d350ecf5Sriastradh 		}
133d350ecf5Sriastradh 		break;
134d350ecf5Sriastradh 	case NVKM_THERM_THRS_NR:
135d350ecf5Sriastradh 		break;
136d350ecf5Sriastradh 	}
137d350ecf5Sriastradh 
138d350ecf5Sriastradh }
139d350ecf5Sriastradh 
140d350ecf5Sriastradh /* must be called with alarm_program_lock taken ! */
141d350ecf5Sriastradh static void
nvkm_therm_threshold_hyst_polling(struct nvkm_therm * therm,const struct nvbios_therm_threshold * thrs,enum nvkm_therm_thrs thrs_name)142d350ecf5Sriastradh nvkm_therm_threshold_hyst_polling(struct nvkm_therm *therm,
143d350ecf5Sriastradh 				  const struct nvbios_therm_threshold *thrs,
144d350ecf5Sriastradh 				  enum nvkm_therm_thrs thrs_name)
145d350ecf5Sriastradh {
146d350ecf5Sriastradh 	enum nvkm_therm_thrs_direction direction;
147d350ecf5Sriastradh 	enum nvkm_therm_thrs_state prev_state, new_state;
148d350ecf5Sriastradh 	int temp = therm->func->temp_get(therm);
149d350ecf5Sriastradh 
150d350ecf5Sriastradh 	prev_state = nvkm_therm_sensor_get_threshold_state(therm, thrs_name);
151d350ecf5Sriastradh 
152d350ecf5Sriastradh 	if (temp >= thrs->temp && prev_state == NVKM_THERM_THRS_LOWER) {
153d350ecf5Sriastradh 		direction = NVKM_THERM_THRS_RISING;
154d350ecf5Sriastradh 		new_state = NVKM_THERM_THRS_HIGHER;
155d350ecf5Sriastradh 	} else if (temp <= thrs->temp - thrs->hysteresis &&
156d350ecf5Sriastradh 			prev_state == NVKM_THERM_THRS_HIGHER) {
157d350ecf5Sriastradh 		direction = NVKM_THERM_THRS_FALLING;
158d350ecf5Sriastradh 		new_state = NVKM_THERM_THRS_LOWER;
159d350ecf5Sriastradh 	} else
160d350ecf5Sriastradh 		return; /* nothing to do */
161d350ecf5Sriastradh 
162d350ecf5Sriastradh 	nvkm_therm_sensor_set_threshold_state(therm, thrs_name, new_state);
163d350ecf5Sriastradh 	nvkm_therm_sensor_event(therm, thrs_name, direction);
164d350ecf5Sriastradh }
165d350ecf5Sriastradh 
166d350ecf5Sriastradh static void
alarm_timer_callback(struct nvkm_alarm * alarm)167d350ecf5Sriastradh alarm_timer_callback(struct nvkm_alarm *alarm)
168d350ecf5Sriastradh {
169d350ecf5Sriastradh 	struct nvkm_therm *therm =
170d350ecf5Sriastradh 		container_of(alarm, struct nvkm_therm, sensor.therm_poll_alarm);
171d350ecf5Sriastradh 	struct nvbios_therm_sensor *sensor = &therm->bios_sensor;
172d350ecf5Sriastradh 	struct nvkm_timer *tmr = therm->subdev.device->timer;
173d350ecf5Sriastradh 	unsigned long flags;
174d350ecf5Sriastradh 
175d350ecf5Sriastradh 	spin_lock_irqsave(&therm->sensor.alarm_program_lock, flags);
176d350ecf5Sriastradh 
177d350ecf5Sriastradh 	nvkm_therm_threshold_hyst_polling(therm, &sensor->thrs_fan_boost,
178d350ecf5Sriastradh 					  NVKM_THERM_THRS_FANBOOST);
179d350ecf5Sriastradh 
180d350ecf5Sriastradh 	nvkm_therm_threshold_hyst_polling(therm,
181d350ecf5Sriastradh 					  &sensor->thrs_down_clock,
182d350ecf5Sriastradh 					  NVKM_THERM_THRS_DOWNCLOCK);
183d350ecf5Sriastradh 
184d350ecf5Sriastradh 	nvkm_therm_threshold_hyst_polling(therm, &sensor->thrs_critical,
185d350ecf5Sriastradh 					  NVKM_THERM_THRS_CRITICAL);
186d350ecf5Sriastradh 
187d350ecf5Sriastradh 	nvkm_therm_threshold_hyst_polling(therm, &sensor->thrs_shutdown,
188d350ecf5Sriastradh 					  NVKM_THERM_THRS_SHUTDOWN);
189d350ecf5Sriastradh 
190d350ecf5Sriastradh 	spin_unlock_irqrestore(&therm->sensor.alarm_program_lock, flags);
191d350ecf5Sriastradh 
192d350ecf5Sriastradh 	/* schedule the next poll in one second */
193d350ecf5Sriastradh 	if (therm->func->temp_get(therm) >= 0)
194d350ecf5Sriastradh 		nvkm_timer_alarm(tmr, 1000000000ULL, alarm);
195d350ecf5Sriastradh }
196d350ecf5Sriastradh 
197d350ecf5Sriastradh void
nvkm_therm_program_alarms_polling(struct nvkm_therm * therm)198d350ecf5Sriastradh nvkm_therm_program_alarms_polling(struct nvkm_therm *therm)
199d350ecf5Sriastradh {
200d350ecf5Sriastradh 	struct nvbios_therm_sensor *sensor = &therm->bios_sensor;
201d350ecf5Sriastradh 
202d350ecf5Sriastradh 	nvkm_debug(&therm->subdev,
203d350ecf5Sriastradh 		   "programmed thresholds [ %d(%d), %d(%d), %d(%d), %d(%d) ]\n",
204d350ecf5Sriastradh 		   sensor->thrs_fan_boost.temp,
205d350ecf5Sriastradh 		   sensor->thrs_fan_boost.hysteresis,
206d350ecf5Sriastradh 		   sensor->thrs_down_clock.temp,
207d350ecf5Sriastradh 		   sensor->thrs_down_clock.hysteresis,
208d350ecf5Sriastradh 		   sensor->thrs_critical.temp,
209d350ecf5Sriastradh 		   sensor->thrs_critical.hysteresis,
210d350ecf5Sriastradh 		   sensor->thrs_shutdown.temp,
211d350ecf5Sriastradh 		   sensor->thrs_shutdown.hysteresis);
212d350ecf5Sriastradh 
213d350ecf5Sriastradh 	alarm_timer_callback(&therm->sensor.therm_poll_alarm);
214d350ecf5Sriastradh }
215d350ecf5Sriastradh 
216d350ecf5Sriastradh int
nvkm_therm_sensor_init(struct nvkm_therm * therm)217d350ecf5Sriastradh nvkm_therm_sensor_init(struct nvkm_therm *therm)
218d350ecf5Sriastradh {
219d350ecf5Sriastradh 	therm->func->program_alarms(therm);
220d350ecf5Sriastradh 	return 0;
221d350ecf5Sriastradh }
222d350ecf5Sriastradh 
223d350ecf5Sriastradh int
nvkm_therm_sensor_fini(struct nvkm_therm * therm,bool suspend)224d350ecf5Sriastradh nvkm_therm_sensor_fini(struct nvkm_therm *therm, bool suspend)
225d350ecf5Sriastradh {
226d350ecf5Sriastradh 	struct nvkm_timer *tmr = therm->subdev.device->timer;
227d350ecf5Sriastradh 	if (suspend)
228677dec6eSriastradh 		nvkm_timer_alarm(tmr, 0, &therm->sensor.therm_poll_alarm);
229d350ecf5Sriastradh 	return 0;
230d350ecf5Sriastradh }
231d350ecf5Sriastradh 
232d350ecf5Sriastradh void
nvkm_therm_sensor_preinit(struct nvkm_therm * therm)233d350ecf5Sriastradh nvkm_therm_sensor_preinit(struct nvkm_therm *therm)
234d350ecf5Sriastradh {
235d350ecf5Sriastradh 	const char *sensor_avail = "yes";
236d350ecf5Sriastradh 
237d350ecf5Sriastradh 	if (therm->func->temp_get(therm) < 0)
238d350ecf5Sriastradh 		sensor_avail = "no";
239d350ecf5Sriastradh 
240d350ecf5Sriastradh 	nvkm_debug(&therm->subdev, "internal sensor: %s\n", sensor_avail);
241d350ecf5Sriastradh }
242d350ecf5Sriastradh 
243d350ecf5Sriastradh int
nvkm_therm_sensor_ctor(struct nvkm_therm * therm)244d350ecf5Sriastradh nvkm_therm_sensor_ctor(struct nvkm_therm *therm)
245d350ecf5Sriastradh {
246d350ecf5Sriastradh 	struct nvkm_subdev *subdev = &therm->subdev;
247d350ecf5Sriastradh 	struct nvkm_bios *bios = subdev->device->bios;
248d350ecf5Sriastradh 
249d350ecf5Sriastradh 	nvkm_alarm_init(&therm->sensor.therm_poll_alarm, alarm_timer_callback);
250d350ecf5Sriastradh 
251d350ecf5Sriastradh 	nvkm_therm_temp_set_defaults(therm);
252d350ecf5Sriastradh 	if (nvbios_therm_sensor_parse(bios, NVBIOS_THERM_DOMAIN_CORE,
253d350ecf5Sriastradh 				      &therm->bios_sensor))
254d350ecf5Sriastradh 		nvkm_error(subdev, "nvbios_therm_sensor_parse failed\n");
255d350ecf5Sriastradh 	nvkm_therm_temp_safety_checks(therm);
256d350ecf5Sriastradh 
257d350ecf5Sriastradh 	return 0;
258d350ecf5Sriastradh }
259