xref: /aosp_15_r20/external/coreboot/src/superio/fintek/f81803a/fan_control.c (revision b9411a12aaaa7e1e6a6fb7c5e057f44ee179a49c)
1 /* SPDX-License-Identifier: GPL-2.0-only */
2 
3 #include <arch/io.h>
4 #include <console/console.h>
5 #include "../common/fan_control.h"
6 #include "f81803a_hwm.h"
7 
8 static const char msg_err_invalid[] = "Error: invalid";
9 static const char msg_err_wrong_order[] = "Error: wrong order,";
10 static const char msg_err_fan[] = "fan";
11 static const char msg_err_temp_source[] = "temperature source";
12 static const char msg_err_type[] = "type";
13 static const char msg_err_mode[] = "mode";
14 static const char msg_err_rate[] = "change rate";
15 static const char msg_err_frequency[] = "frequency";
16 static const char msg_err_temp_sensor[] = "temperature sensor";
17 static const char msg_err_bondary[] = "boundary";
18 static const char msg_err_section[] = "section";
19 static const char no_msg[] = "";
20 
21 struct cross_ref {
22 	int selection;
23 	const char *message;
24 };
25 static struct cross_ref msg_table[] = {
26 	{HWM_STATUS_INVALID_FAN,		msg_err_fan},
27 	{HWM_STATUS_INVALID_TEMP_SOURCE,	msg_err_temp_source},
28 	{HWM_STATUS_INVALID_TYPE,		msg_err_type},
29 	{HWM_STATUS_INVALID_MODE,		msg_err_mode},
30 	{HWM_STATUS_INVALID_RATE,		msg_err_rate},
31 	{HWM_STATUS_INVALID_FREQUENCY,		msg_err_frequency},
32 	{HWM_STATUS_INVALID_TEMP_SENSOR,	msg_err_temp_sensor},
33 	{0, NULL},
34 };
35 
get_msg(int err)36 static const char *get_msg(int err)
37 {
38 	int i = 0;
39 	while (msg_table[i].selection) {
40 		if (msg_table[i].selection == err)
41 			return msg_table[i].message;
42 		i++;
43 	}
44 	return no_msg;
45 }
46 
message_invalid_1(int err,u8 fan)47 static int message_invalid_1(int err, u8 fan)
48 {
49 	if (err == HWM_STATUS_INVALID_FAN)
50 		printk(BIOS_ERR, "%s %s %d!\n", msg_err_invalid, get_msg(err), fan);
51 	else
52 		printk(BIOS_ERR, "%s Fan %d %s!\n", msg_err_invalid, fan, get_msg(err));
53 	return err;
54 }
55 
message_invalid_2(int err,u8 fan)56 static int message_invalid_2(int err, u8 fan)
57 {
58 	switch (err) {
59 	case HWM_STATUS_INVALID_BOUNDARY_VALUE:
60 		printk(BIOS_ERR, "%s fan %d %s value!\n", msg_err_invalid, fan,
61 					msg_err_bondary);
62 		break;
63 	case HWM_STATUS_INVALID_SECTION_VALUE:
64 		printk(BIOS_ERR, "%s fan %d %s value!\n", msg_err_invalid, fan,
65 					msg_err_section);
66 		break;
67 	case HWM_STATUS_BOUNDARY_WRONG_ORDER:
68 		printk(BIOS_ERR, "%s fan %d %s!\n", msg_err_wrong_order, fan, msg_err_bondary);
69 		break;
70 	case HWM_STATUS_SECTIONS_WRONG_ORDER:
71 		printk(BIOS_ERR, "%s fan %d %s!\n", msg_err_wrong_order, fan, msg_err_section);
72 		break;
73 	default:
74 		break;
75 	}
76 	return err;
77 }
78 
write_hwm_reg(u16 address,u8 index,u8 value)79 static void write_hwm_reg(u16 address, u8 index, u8 value)
80 {
81 	u16 index_add, data_add;
82 	index_add = address | 0x0001;	/* force odd address */
83 	data_add = index_add + 1;
84 	outb(index, index_add);
85 	outb(value, data_add);
86 }
87 
read_hwm_reg(u16 address,u8 index)88 static u8 read_hwm_reg(u16 address, u8 index)
89 {
90 	u16 index_add, data_add;
91 	index_add = address | 0x0001;	/* force odd address */
92 	data_add = index_add + 1;
93 	outb(index, index_add);
94 	return inb(data_add);
95 }
96 
hwm_reg_modify(u16 address,u8 index,u8 shift,u8 mask,u8 value)97 static void hwm_reg_modify(u16 address, u8 index, u8 shift, u8 mask,
98 								u8 value)
99 {
100 	u8 use_mask = mask << shift;
101 	u8 use_value = (value & mask) << shift;
102 	u8 temp = read_hwm_reg(address, index);
103 
104 	temp &= ~use_mask;
105 	temp |= use_value;
106 	write_hwm_reg(address, index, temp);
107 }
108 
109 /*
110  * Registers 0x94,0x95, 0x96 and 0x9b have 2 versions (banks) selected through
111  * bit 7 of register 0x9f.
112  */
select_hwm_bank(u16 address,u8 value)113 static inline void select_hwm_bank(u16 address, u8 value)
114 {
115 	hwm_reg_modify(address, FAN_FAULT_TIME_REG, FAN_FUNC_PROG_SEL_SHIFT,
116 				FAN_BIT_MASK, value);
117 }
118 
119 /*
120  * Boundaries and sections must be presented in the same order as in the HWM
121  * registers, that is, from highest value to lowest. This procedure checks for
122  * the correct order.
123  */
check_value_seq(u8 * values,u8 count)124 static int check_value_seq(u8 *values, u8 count)
125 {
126 	u8 last_value = CPU_DAMAGE_TEMP;
127 	u8 current_value, i;
128 	for (i = 0; i < count; i++) {
129 		current_value = values[i];
130 		if (current_value > CPU_DAMAGE_TEMP)
131 			return STATUS_INVALID_VALUE;
132 		if (current_value >= last_value)
133 			return STATUS_INVALID_ORDER;
134 		last_value = current_value;
135 	}
136 	return HWM_STATUS_SUCCESS;
137 }
138 
set_sensor_type(u16 base_address,external_sensor sensor,temp_sensor_type type)139 int set_sensor_type(u16 base_address, external_sensor sensor,
140 						temp_sensor_type type)
141 {
142 	u8 sensor_status = read_hwm_reg(base_address, TP_DIODE_STATUS);
143 
144 	printk(BIOS_DEBUG, "%s\n", __func__);
145 	switch (sensor) {
146 	case EXTERNAL_SENSOR1:
147 		if (sensor_status & TP_EXTERNAL_SENSOR1_OPEN) {
148 			printk(BIOS_WARNING, "Sensor 1 disconnected!\n");
149 			return HWM_STATUS_WARNING_SENSOR_DISCONNECTED;
150 		}
151 		hwm_reg_modify(base_address, TP_SENSOR_TYPE,
152 			TP_SENSOR1_TYPE_SHIFT, TP_SENSOR_TYPE_MASK, type);
153 		break;
154 	case EXTERNAL_SENSOR2:
155 		if (sensor_status & TP_EXTERNAL_SENSOR2_OPEN) {
156 			printk(BIOS_WARNING, "Sensor 2 disconnected!\n");
157 			return HWM_STATUS_WARNING_SENSOR_DISCONNECTED;
158 		}
159 		hwm_reg_modify(base_address, TP_SENSOR_TYPE,
160 			TP_SENSOR2_TYPE_SHIFT, TP_SENSOR_TYPE_MASK, type);
161 		break;
162 	case IGNORE_SENSOR:
163 		break;
164 	default:
165 		return message_invalid_1(HWM_STATUS_INVALID_TEMP_SENSOR, 0);
166 	}
167 	return HWM_STATUS_SUCCESS;
168 }
169 
set_fan_temperature_source(u16 base_address,u8 fan,fan_temp_source source)170 int set_fan_temperature_source(u16 base_address, u8 fan,
171 						fan_temp_source source)
172 {
173 	u8 index, high_value, low_value;
174 
175 	printk(BIOS_DEBUG, "%s\n", __func__);
176 	if ((fan < FIRST_FAN) || (fan > LAST_FAN))
177 		return message_invalid_1(HWM_STATUS_INVALID_FAN, fan);
178 	index = FAN_ADJUST(fan, FAN_TMP_MAPPING);
179 	high_value = (source >> 2) & FAN_BIT_MASK;
180 	low_value = source & FAN_TEMP_SEL_LOW_MASK;
181 	hwm_reg_modify(base_address, index, FAN_TEMP_SEL_HIGH_SHIFT,
182 				FAN_BIT_MASK, high_value);
183 	hwm_reg_modify(base_address, index, FAN_TEMP_SEL_LOW_SHIFT,
184 				FAN_TEMP_SEL_LOW_MASK, low_value);
185 	/*
186 	 * Fan 1 has a weight mechanism for adjusting for next fan speed. Basically the idea is
187 	 * to react more aggressively (normally CPU fan) based on how high another temperature
188 	 * (system, thermistor near the CPU, anything) is. This would be highly platform
189 	 * dependent, and by setting the weight temperature same as the control temperature.
190 	 * This code cancels the weight mechanism and make it work with any board. If a board
191 	 * wants to use the weight mechanism, OEM should implement it after calling the main
192 	 * HWM programming.
193 	 */
194 	if (fan == FIRST_FAN) {
195 		select_hwm_bank(base_address, 1);
196 		hwm_reg_modify(base_address, FAN_MODE_REG,
197 				FAN1_ADJ_SEL_SHIFT, FAN1_ADJ_SEL_MASK, source);
198 		select_hwm_bank(base_address, 0);
199 	}
200 	return HWM_STATUS_SUCCESS;
201 }
202 
set_fan_type_mode(u16 base_address,u8 fan,fan_type type,fan_mode mode)203 int set_fan_type_mode(u16 base_address, u8 fan, fan_type type, fan_mode mode)
204 {
205 	u8 shift;
206 
207 	printk(BIOS_DEBUG, "%s\n", __func__);
208 	if ((fan < FIRST_FAN) || (fan > LAST_FAN))
209 		return message_invalid_1(HWM_STATUS_INVALID_FAN, fan);
210 	select_hwm_bank(base_address, 0);
211 	if (type < FAN_TYPE_RESERVED) {
212 		shift = FAN_TYPE_SHIFT(fan);
213 		hwm_reg_modify(base_address, FAN_TYPE_REG, shift,
214 						FAN_TYPE_MASK, type);
215 	}
216 	if (mode < FAN_MODE_DEFAULT) {
217 		shift = FAN_MODE_SHIFT(fan);
218 		hwm_reg_modify(base_address, FAN_MODE_REG, shift,
219 						FAN_MODE_MASK, mode);
220 	}
221 	return HWM_STATUS_SUCCESS;
222 }
223 
set_pwm_frequency(u16 base_address,u8 fan,fan_pwm_freq frequency)224 int set_pwm_frequency(u16 base_address, u8 fan, fan_pwm_freq frequency)
225 {
226 	u8 shift, index, byte;
227 
228 	printk(BIOS_DEBUG, "%s\n", __func__);
229 	if ((fan < FIRST_FAN) || (fan > LAST_FAN))
230 		return message_invalid_1(HWM_STATUS_INVALID_FAN, fan);
231 	byte = read_hwm_reg(base_address, FAN_TYPE_REG);
232 	shift = FAN_TYPE_SHIFT(fan);
233 	if (((byte >> shift) & FAN_TYPE_PWM_CHECK) == FAN_TYPE_PWM_CHECK) {
234 		printk(BIOS_WARNING, "Fan %d not programmed as PWM!\n", fan);
235 		return HWM_STATUS_WARNING_FAN_NOT_PWM;
236 	}
237 	select_hwm_bank(base_address, 1);
238 	shift = FAN_FREQ_SEL_ADD_SHIFT(fan);
239 	byte = (frequency >> 1) & FAN_BIT_MASK;
240 	hwm_reg_modify(base_address, FAN_MODE_REG, shift, FAN_BIT_MASK,
241 									byte);
242 	select_hwm_bank(base_address, 0);
243 	index = FAN_ADJUST(fan, FAN_TMP_MAPPING);
244 	byte = frequency & FAN_BIT_MASK;
245 	hwm_reg_modify(base_address, index, FAN_PWM_FREQ_SEL_SHIFT,
246 							FAN_BIT_MASK, byte);
247 	return HWM_STATUS_SUCCESS;
248 }
249 
set_sections(u16 base_address,u8 fan,u8 * boundaries,u8 * sections)250 int set_sections(u16 base_address, u8 fan, u8 *boundaries, u8 *sections)
251 {
252 	int status, temp;
253 	u8 i, index, value;
254 
255 	printk(BIOS_DEBUG, "%s\n", __func__);
256 	if ((fan < FIRST_FAN) || (fan > LAST_FAN))
257 		return message_invalid_1(HWM_STATUS_INVALID_FAN, fan);
258 	status = check_value_seq(boundaries,
259 				FINTEK_BOUNDARIES_SIZE);
260 	if (status != HWM_STATUS_SUCCESS) {
261 		if (status == STATUS_INVALID_VALUE)
262 			return message_invalid_2(HWM_STATUS_INVALID_BOUNDARY_VALUE, fan);
263 		return message_invalid_2(HWM_STATUS_BOUNDARY_WRONG_ORDER, fan);
264 	}
265 	status = check_value_seq(sections,
266 				FINTEK_SECTIONS_SIZE);
267 	if (status != HWM_STATUS_SUCCESS) {
268 		if (status == STATUS_INVALID_VALUE)
269 			return message_invalid_2(HWM_STATUS_INVALID_SECTION_VALUE, fan);
270 		return message_invalid_2(HWM_STATUS_SECTIONS_WRONG_ORDER, fan);
271 	}
272 	index = FAN_ADJUST(fan, FAN_BOUND_TEMP);
273 	for (i = 0; i < FINTEK_BOUNDARIES_SIZE; i++) {
274 		value = boundaries[i];
275 		write_hwm_reg(base_address, index, value);
276 		index++;
277 	}
278 	index = FAN_ADJUST(fan, FAN_SECTION_SPEED);
279 	for (i = 0; i < FINTEK_SECTIONS_SIZE; i++) {
280 		value = sections[i];
281 		if (value > 100)
282 			return message_invalid_2(HWM_STATUS_INVALID_SECTION_VALUE, fan);
283 		temp = (255 * value) / 100;
284 		value = (u8)(temp & 0x00ff);
285 		write_hwm_reg(base_address, index, value);
286 		index++;
287 	}
288 	return HWM_STATUS_SUCCESS;
289 }
290 
set_fan_speed_change_rate(u16 base_address,u8 fan,fan_rate_up rate_up,fan_rate_down rate_down)291 int set_fan_speed_change_rate(u16 base_address, u8 fan, fan_rate_up rate_up,
292 						fan_rate_down rate_down)
293 {
294 	u8 shift, index;
295 
296 	printk(BIOS_DEBUG, "%s\n", __func__);
297 	if ((fan < FIRST_FAN) || (fan > LAST_FAN))
298 		return message_invalid_1(HWM_STATUS_INVALID_FAN, fan);
299 
300 	index = FAN_ADJUST(fan, FAN_TMP_MAPPING);
301 	shift = FAN_RATE_SHIFT(fan);
302 
303 	if (rate_up == FAN_UP_RATE_JUMP) {
304 		hwm_reg_modify(base_address, index, FAN_JUMP_UP_SHIFT,
305 							FAN_BIT_MASK, 1);
306 	} else {
307 		hwm_reg_modify(base_address, index, FAN_JUMP_UP_SHIFT,
308 							FAN_BIT_MASK, 0);
309 		if (rate_up < FAN_UP_RATE_DEFAULT) {
310 			hwm_reg_modify(base_address,	FAN_UP_RATE_REG,
311 					shift, FAN_RATE_MASK, rate_up);
312 		}
313 	}
314 
315 	if (rate_down == FAN_DOWN_RATE_JUMP) {
316 		hwm_reg_modify(base_address, index, FAN_JUMP_DOWN_SHIFT,
317 							FAN_BIT_MASK, 1);
318 	} else {
319 		hwm_reg_modify(base_address, index, FAN_JUMP_UP_SHIFT,
320 							FAN_BIT_MASK, 0);
321 		select_hwm_bank(base_address, 0);
322 		if (rate_down < FAN_DOWN_RATE_DEFAULT) {
323 			hwm_reg_modify(base_address,	FAN_DOWN_RATE_REG,
324 					shift, FAN_RATE_MASK, rate_down);
325 			hwm_reg_modify(base_address, FAN_DOWN_RATE_REG,
326 					FAN_DOWN_RATE_DIFF_FROM_UP_SHIFT,
327 					FAN_BIT_MASK, 0);
328 		}
329 		if (rate_down == FAN_DOWN_RATE_SAME_AS_UP) {
330 			hwm_reg_modify(base_address, FAN_DOWN_RATE_REG,
331 					FAN_DOWN_RATE_DIFF_FROM_UP_SHIFT,
332 					FAN_BIT_MASK, 1);
333 		}
334 	}
335 	return HWM_STATUS_SUCCESS;
336 }
337 
set_fan_follow(u16 base_address,u8 fan,fan_follow follow)338 int set_fan_follow(u16 base_address, u8 fan, fan_follow follow)
339 {
340 	u8 index;
341 
342 	printk(BIOS_DEBUG, "%s\n", __func__);
343 	if ((fan < FIRST_FAN) || (fan > LAST_FAN))
344 		return message_invalid_1(HWM_STATUS_INVALID_FAN, fan);
345 	index = FAN_ADJUST(fan, FAN_TMP_MAPPING);
346 	hwm_reg_modify(base_address, index, FAN_INTERPOLATION_SHIFT,
347 				FAN_BIT_MASK, follow);
348 	return HWM_STATUS_SUCCESS;
349 }
350