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

#include <arch/io.h>
#include <device/pnp.h>
#include <stdint.h>

#include "lpc47n207.h"

/*
 * This code tries to discover the SMSC LPC47N207 superio chip which can be
 * connected over an LPC dongle. The chip could be bootstrap mapped to one of
 * four LPC addresses: 0x2e, 0x4e, 0x162e, and 0x164e.
 *
 * Initializing the UART requires accesses to a few control registers. This
 * structure includes the register offset and the value to write (along with
 * the mask).
 */
typedef struct {
	u8 conf_reg;
	u8 value;
	u8 mask;
} uart_conf;

/* All regs/values to write to initialize the LPC47N207 UART */
static const uart_conf uart_conf_data [] = {
	{2, (1 << 3), (1 << 3)},    /* cr02, enable Primary UART power */
	{0xc, (1 << 6), (1 << 6)},  /* cr0c, enable Primary UART high speed */
	{0x24, (CONFIG_TTYS0_BASE >> 3) << 1, 0xff},  /* cr24, base addr */
};

void try_enabling_LPC47N207_uart(void)
{
	u8 reg_value;
	const uart_conf* conf_item;
	u16 lpc_ports[] = {0x2e, 0x4e, 0x162e, 0x164e};
	u16 lpc_port;
	int i, j;

#define CONF_ENABLE    0x55
#define CONF_DISABLE   0xaa

	for (j = 0; j < ARRAY_SIZE(lpc_ports); j++) {
		lpc_port = lpc_ports[j];

		/* enable CONFIG mode */
		outb(CONF_ENABLE, lpc_port);
		reg_value = inb(lpc_port);
		if (reg_value != CONF_ENABLE) {
			continue; /* There is no LPC device at this address */
		}

		do {
			/*
			 * Registers 12 and 13 hold config address, look for a
			 * match.
			 */
			outb(0x12, lpc_port);
			reg_value = inb(lpc_port + 1);
			if (reg_value != (lpc_port & 0xff))
				break;

			outb(0x13, lpc_port);
			reg_value = inb(lpc_port + 1);
			if (reg_value != (lpc_port >> 8))
				break;

			/* This must be the SMSC LPC 47N207, enable the UART. */
			for (i = 0; i < ARRAY_SIZE(uart_conf_data); i++) {
				u8 reg, value, mask;

				conf_item = uart_conf_data + i;

				reg = conf_item->conf_reg;
				value = conf_item->value;
				mask = conf_item->mask;

				outb(reg, lpc_port);
				reg_value = inb(lpc_port + 1);
				reg_value &= ~mask;
				reg_value |= (value & mask);
				outb(reg_value, lpc_port + 1);
			}
		} while (0);
		outb(CONF_DISABLE, lpc_port);
	}
}