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