/* SPDX-License-Identifier: GPL-2.0-only */

#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 disconnected!\n");
			return HWM_STATUS_WARNING_SENSOR_DISCONNECTED;
		}
		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 disconnected!\n");
			return HWM_STATUS_WARNING_SENSOR_DISCONNECTED;
		}
		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;
}