diff options
author | Richard Spiegel <richard.spiegel@amd.corp-partner.google.com> | 2019-06-19 13:57:55 -0700 |
---|---|---|
committer | Felix Held <felix-coreboot@felixheld.de> | 2019-07-26 20:33:16 +0000 |
commit | ae5b3671b39f26b79685496d79845f9a4f3975db (patch) | |
tree | ecf8c1767c981718967af471c3df5d8b415d8758 /src/superio/fintek/f81803a/fan_control.c | |
parent | 150a61e103c9c8001a0fc7905c02d0c065b7f126 (diff) |
superio/fintek: Add f81803A
Add f81803A plus the capability to control the fan with any fintek SIO.
This will be done through a common API, though currently only F81803A will
have it implemented.
BUG=none.
TEST=Tested later with padmelon board.
Change-Id: I3d336e76bccc38452b1b1aefef5d4a4f7ee129a8
Signed-off-by: Richard Spiegel <richard.spiegel@silverbackltd.com>
Reviewed-on: https://review.coreboot.org/c/coreboot/+/33623
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Reviewed-by: Felix Held <felix-coreboot@felixheld.de>
Diffstat (limited to 'src/superio/fintek/f81803a/fan_control.c')
-rw-r--r-- | src/superio/fintek/f81803a/fan_control.c | 362 |
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; +} |