summaryrefslogtreecommitdiff
path: root/src/superio/fintek/f81803a/fan_control.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/superio/fintek/f81803a/fan_control.c')
-rw-r--r--src/superio/fintek/f81803a/fan_control.c362
1 files changed, 362 insertions, 0 deletions
diff --git a/src/superio/fintek/f81803a/fan_control.c b/src/superio/fintek/f81803a/fan_control.c
new file mode 100644
index 0000000000..17ae9c6a20
--- /dev/null
+++ b/src/superio/fintek/f81803a/fan_control.c
@@ -0,0 +1,362 @@
+/*
+ * This file is part of the coreboot project.
+ *
+ * Copyright (C) 2019 Richard Spiegel <richard.spiegel@silverbackltd.com>
+ * Copyright (C) 2019 Silverback ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <console/console.h>
+#include "../common/fan_control.h"
+#include "f81803a_hwm.h"
+
+static const char msg_err_invalid[] = "Error: invalid";
+static const char msg_err_wrong_order[] = "Error: wrong order,";
+static const char msg_err_fan[] = "fan";
+static const char msg_err_temp_source[] = "temperature source";
+static const char msg_err_type[] = "type";
+static const char msg_err_mode[] = "mode";
+static const char msg_err_rate[] = "change rate";
+static const char msg_err_frequency[] = "frequency";
+static const char msg_err_temp_sensor[] = "temperature sensor";
+static const char msg_err_bondary[] = "boundary";
+static const char msg_err_section[] = "section";
+static const char no_msg[] = "";
+
+struct cross_ref {
+ int selection;
+ const char *message;
+};
+static struct cross_ref msg_table[] = {
+ {HWM_STATUS_INVALID_FAN, msg_err_fan},
+ {HWM_STATUS_INVALID_TEMP_SOURCE, msg_err_temp_source},
+ {HWM_STATUS_INVALID_TYPE, msg_err_type},
+ {HWM_STATUS_INVALID_MODE, msg_err_mode},
+ {HWM_STATUS_INVALID_RATE, msg_err_rate},
+ {HWM_STATUS_INVALID_FREQUENCY, msg_err_frequency},
+ {HWM_STATUS_INVALID_TEMP_SENSOR, msg_err_temp_sensor},
+ {0, NULL},
+};
+
+static const char *get_msg(int err)
+{
+ int i = 0;
+ while (msg_table[i].selection) {
+ if (msg_table[i].selection == err)
+ return msg_table[i].message;
+ i++;
+ }
+ return no_msg;
+}
+
+static int message_invalid_1(int err, u8 fan)
+{
+ if (err == HWM_STATUS_INVALID_FAN)
+ printk(BIOS_ERR, "%s %s %d!\n", msg_err_invalid, get_msg(err), fan);
+ else
+ printk(BIOS_ERR, "%s Fan %d %s!\n", msg_err_invalid, fan, get_msg(err));
+ return err;
+}
+
+static int message_invalid_2(int err, u8 fan)
+{
+ switch (err) {
+ case HWM_STATUS_INVALID_BOUNDARY_VALUE:
+ printk(BIOS_ERR, "%s fan %d %s value!\n", msg_err_invalid, fan,
+ msg_err_bondary);
+ break;
+ case HWM_STATUS_INVALID_SECTION_VALUE:
+ printk(BIOS_ERR, "%s fan %d %s value!\n", msg_err_invalid, fan,
+ msg_err_section);
+ break;
+ case HWM_STATUS_BOUNDARY_WRONG_ORDER:
+ printk(BIOS_ERR, "%s fan %d %s!\n", msg_err_wrong_order, fan, msg_err_bondary);
+ break;
+ case HWM_STATUS_SECTIONS_WRONG_ORDER:
+ printk(BIOS_ERR, "%s fan %d %s!\n", msg_err_wrong_order, fan, msg_err_section);
+ break;
+ default:
+ break;
+ }
+ return err;
+}
+
+static void write_hwm_reg(u16 address, u8 index, u8 value)
+{
+ u16 index_add, data_add;
+ index_add = address | 0x0001; /* force odd address */
+ data_add = index_add + 1;
+ outb(index, index_add);
+ outb(value, data_add);
+}
+
+static u8 read_hwm_reg(u16 address, u8 index)
+{
+ u16 index_add, data_add;
+ index_add = address | 0x0001; /* force odd address */
+ data_add = index_add + 1;
+ outb(index, index_add);
+ return inb(data_add);
+}
+
+static void hwm_reg_modify(u16 address, u8 index, u8 shift, u8 mask,
+ u8 value)
+{
+ u8 use_mask = mask << shift;
+ u8 use_value = (value & mask) << shift;
+ u8 temp = read_hwm_reg(address, index);
+
+ temp &= ~use_mask;
+ temp |= use_value;
+ write_hwm_reg(address, index, temp);
+}
+
+/*
+ * Registers 0x94,0x95, 0x96 and 0x9b have 2 versions (banks) selected through
+ * bit 7 of register 0x9f.
+ */
+static inline void select_hwm_bank(u16 address, u8 value)
+{
+ hwm_reg_modify(address, FAN_FAULT_TIME_REG, FAN_FUNC_PROG_SEL_SHIFT,
+ FAN_BIT_MASK, value);
+}
+
+/*
+ * Boundaries and sections must be presented in the same order as in the HWM
+ * registers, that is, from highest value to lowest. This procedure checks for
+ * the correct order.
+ */
+static int check_value_seq(u8 *values, u8 count)
+{
+ u8 last_value = CPU_DAMAGE_TEMP;
+ u8 current_value, i;
+ for (i = 0; i < count; i++) {
+ current_value = values[i];
+ if (current_value > CPU_DAMAGE_TEMP)
+ return STATUS_INVALID_VALUE;
+ if (current_value >= last_value)
+ return STATUS_INVALID_ORDER;
+ last_value = current_value;
+ }
+ return HWM_STATUS_SUCCESS;
+}
+
+int set_sensor_type(u16 base_address, external_sensor sensor,
+ temp_sensor_type type)
+{
+ u8 sensor_status = read_hwm_reg(base_address, TP_DIODE_STATUS);
+
+ printk(BIOS_DEBUG, "%s\n", __func__);
+ switch (sensor) {
+ case EXTERNAL_SENSOR1:
+ if (sensor_status & TP_EXTERNAL_SENSOR1_OPEN) {
+ printk(BIOS_WARNING, "Sensor 1 disconected!\n");
+ return HWM_STATUS_WARNING_SENSOR_DISCONECTED;
+ }
+ hwm_reg_modify(base_address, TP_SENSOR_TYPE,
+ TP_SENSOR1_TYPE_SHIFT, TP_SENSOR_TYPE_MASK, type);
+ break;
+ case EXTERNAL_SENSOR2:
+ if (sensor_status & TP_EXTERNAL_SENSOR2_OPEN) {
+ printk(BIOS_WARNING, "Sensor 2 disconected!\n");
+ return HWM_STATUS_WARNING_SENSOR_DISCONECTED;
+ }
+ hwm_reg_modify(base_address, TP_SENSOR_TYPE,
+ TP_SENSOR2_TYPE_SHIFT, TP_SENSOR_TYPE_MASK, type);
+ break;
+ case IGNORE_SENSOR:
+ break;
+ default:
+ return message_invalid_1(HWM_STATUS_INVALID_TEMP_SENSOR, 0);
+ }
+ return HWM_STATUS_SUCCESS;
+}
+
+int set_fan_temperature_source(u16 base_address, u8 fan,
+ fan_temp_source source)
+{
+ u8 index, high_value, low_value;
+
+ printk(BIOS_DEBUG, "%s\n", __func__);
+ if ((fan < FIRST_FAN) || (fan > LAST_FAN))
+ return message_invalid_1(HWM_STATUS_INVALID_FAN, fan);
+ index = FAN_ADJUST(fan, FAN_TMP_MAPPING);
+ high_value = (source >> 2) & FAN_BIT_MASK;
+ low_value = source & FAN_TEMP_SEL_LOW_MASK;
+ hwm_reg_modify(base_address, index, FAN_TEMP_SEL_HIGH_SHIFT,
+ FAN_BIT_MASK, high_value);
+ hwm_reg_modify(base_address, index, FAN_TEMP_SEL_LOW_SHIFT,
+ FAN_TEMP_SEL_LOW_MASK, low_value);
+ /*
+ * Fan 1 has a weight mechanism for adjusting for next fan speed. Basically the idea is
+ * to react more aggressively (normally CPU fan) based on how high another temperature
+ * (system, thermistor near the CPU, anything) is. This would be highly platform
+ * dependent, and by setting the weight temperature same as the control temperature.
+ * This code cancels the weight mechanism and make it work with any board. If a board
+ * wants to use the weight mechanism, OEM should implement it after calling the main
+ * HWM programming.
+ */
+ if (fan == FIRST_FAN) {
+ select_hwm_bank(base_address, 1);
+ hwm_reg_modify(base_address, FAN_MODE_REG,
+ FAN1_ADJ_SEL_SHIFT, FAN1_ADJ_SEL_MASK, source);
+ select_hwm_bank(base_address, 0);
+ }
+ return HWM_STATUS_SUCCESS;
+}
+
+int set_fan_type_mode(u16 base_address, u8 fan, fan_type type, fan_mode mode)
+{
+ u8 shift;
+
+ printk(BIOS_DEBUG, "%s\n", __func__);
+ if ((fan < FIRST_FAN) || (fan > LAST_FAN))
+ return message_invalid_1(HWM_STATUS_INVALID_FAN, fan);
+ select_hwm_bank(base_address, 0);
+ if (type < FAN_TYPE_RESERVED) {
+ shift = FAN_TYPE_SHIFT(fan);
+ hwm_reg_modify(base_address, FAN_TYPE_REG, shift,
+ FAN_TYPE_MASK, type);
+ }
+ if (mode < FAN_MODE_DEFAULT) {
+ shift = FAN_MODE_SHIFT(fan);
+ hwm_reg_modify(base_address, FAN_MODE_REG, shift,
+ FAN_MODE_MASK, mode);
+ }
+ return HWM_STATUS_SUCCESS;
+}
+
+int set_pwm_frequency(u16 base_address, u8 fan, fan_pwm_freq frequency)
+{
+ u8 shift, index, byte;
+
+ printk(BIOS_DEBUG, "%s\n", __func__);
+ if ((fan < FIRST_FAN) || (fan > LAST_FAN))
+ return message_invalid_1(HWM_STATUS_INVALID_FAN, fan);
+ byte = read_hwm_reg(base_address, FAN_TYPE_REG);
+ shift = FAN_TYPE_SHIFT(fan);
+ if (((byte >> shift) & FAN_TYPE_PWM_CHECK) == FAN_TYPE_PWM_CHECK) {
+ printk(BIOS_WARNING, "Fan %d not programmed as PWM!\n", fan);
+ return HWM_STATUS_WARNING_FAN_NOT_PWM;
+ }
+ select_hwm_bank(base_address, 1);
+ shift = FAN_FREQ_SEL_ADD_SHIFT(fan);
+ byte = (frequency >> 1) & FAN_BIT_MASK;
+ hwm_reg_modify(base_address, FAN_MODE_REG, shift, FAN_BIT_MASK,
+ byte);
+ select_hwm_bank(base_address, 0);
+ index = FAN_ADJUST(fan, FAN_TMP_MAPPING);
+ byte = frequency & FAN_BIT_MASK;
+ hwm_reg_modify(base_address, index, FAN_PWM_FREQ_SEL_SHIFT,
+ FAN_BIT_MASK, byte);
+ return HWM_STATUS_SUCCESS;
+}
+
+int set_sections(u16 base_address, u8 fan, u8 *boundaries, u8 *sections)
+{
+ int status, temp;
+ u8 i, index, value;
+
+ printk(BIOS_DEBUG, "%s\n", __func__);
+ if ((fan < FIRST_FAN) || (fan > LAST_FAN))
+ return message_invalid_1(HWM_STATUS_INVALID_FAN, fan);
+ status = check_value_seq(boundaries,
+ FINTEK_BOUNDARIES_SIZE);
+ if (status != HWM_STATUS_SUCCESS) {
+ if (status == STATUS_INVALID_VALUE)
+ return message_invalid_2(HWM_STATUS_INVALID_BOUNDARY_VALUE, fan);
+ return message_invalid_2(HWM_STATUS_BOUNDARY_WRONG_ORDER, fan);
+ }
+ status = check_value_seq(sections,
+ FINTEK_SECTIONS_SIZE);
+ if (status != HWM_STATUS_SUCCESS) {
+ if (status == STATUS_INVALID_VALUE)
+ return message_invalid_2(HWM_STATUS_INVALID_SECTION_VALUE, fan);
+ return message_invalid_2(HWM_STATUS_SECTIONS_WRONG_ORDER, fan);
+ }
+ index = FAN_ADJUST(fan, FAN_BOUND_TEMP);
+ for (i = 0; i < FINTEK_BOUNDARIES_SIZE; i++) {
+ value = boundaries[i];
+ write_hwm_reg(base_address, index, value);
+ index++;
+ }
+ index = FAN_ADJUST(fan, FAN_SECTION_SPEED);
+ for (i = 0; i < FINTEK_SECTIONS_SIZE; i++) {
+ value = sections[i];
+ if (value > 100)
+ return message_invalid_2(HWM_STATUS_INVALID_SECTION_VALUE, fan);
+ temp = (255 * value) / 100;
+ value = (u8) (temp & 0x00ff);
+ write_hwm_reg(base_address, index, value);
+ index++;
+ }
+ return HWM_STATUS_SUCCESS;
+}
+
+int set_fan_speed_change_rate(u16 base_address, u8 fan, fan_rate_up rate_up,
+ fan_rate_down rate_down)
+{
+ u8 shift, index;
+
+ printk(BIOS_DEBUG, "%s\n", __func__);
+ if ((fan < FIRST_FAN) || (fan > LAST_FAN))
+ return message_invalid_1(HWM_STATUS_INVALID_FAN, fan);
+
+ index = FAN_ADJUST(fan, FAN_TMP_MAPPING);
+ shift = FAN_RATE_SHIFT(fan);
+
+ if (rate_up == FAN_UP_RATE_JUMP) {
+ hwm_reg_modify(base_address, index, FAN_JUMP_UP_SHIFT,
+ FAN_BIT_MASK, 1);
+ } else {
+ hwm_reg_modify(base_address, index, FAN_JUMP_UP_SHIFT,
+ FAN_BIT_MASK, 0);
+ if (rate_up < FAN_UP_RATE_DEFAULT) {
+ hwm_reg_modify(base_address, FAN_UP_RATE_REG,
+ shift, FAN_RATE_MASK, rate_up);
+ }
+ }
+
+ if (rate_down == FAN_DOWN_RATE_JUMP) {
+ hwm_reg_modify(base_address, index, FAN_JUMP_DOWN_SHIFT,
+ FAN_BIT_MASK, 1);
+ } else {
+ hwm_reg_modify(base_address, index, FAN_JUMP_UP_SHIFT,
+ FAN_BIT_MASK, 0);
+ select_hwm_bank(base_address, 0);
+ if (rate_down < FAN_DOWN_RATE_DEFAULT) {
+ hwm_reg_modify(base_address, FAN_DOWN_RATE_REG,
+ shift, FAN_RATE_MASK, rate_down);
+ hwm_reg_modify(base_address, FAN_DOWN_RATE_REG,
+ FAN_DOWN_RATE_DIFF_FROM_UP_SHIFT,
+ FAN_BIT_MASK, 0);
+ }
+ if (rate_down == FAN_DOWN_RATE_SAME_AS_UP) {
+ hwm_reg_modify(base_address, FAN_DOWN_RATE_REG,
+ FAN_DOWN_RATE_DIFF_FROM_UP_SHIFT,
+ FAN_BIT_MASK, 1);
+ }
+ }
+ return HWM_STATUS_SUCCESS;
+}
+
+int set_fan_follow(u16 base_address, u8 fan, fan_follow follow)
+{
+ u8 index;
+
+ printk(BIOS_DEBUG, "%s\n", __func__);
+ if ((fan < FIRST_FAN) || (fan > LAST_FAN))
+ return message_invalid_1(HWM_STATUS_INVALID_FAN, fan);
+ index = FAN_ADJUST(fan, FAN_TMP_MAPPING);
+ hwm_reg_modify(base_address, index, FAN_INTERPOLATION_SHIFT,
+ FAN_BIT_MASK, follow);
+ return HWM_STATUS_SUCCESS;
+}