blob: 41fd0d3d5069a322cda8caafba6be33a528d3934 [file] [log] [blame]
/*
* 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 <arch/io.h>
#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;
}