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

#include <assert.h>
#include <console/uart.h>
#include <device/mmio.h>
#include <device/pci_def.h>
#include <device/pci_ops.h>
#include <southbridge/intel/lynxpoint/iobp.h>
#include <southbridge/intel/lynxpoint/lp_gpio.h>
#include <southbridge/intel/lynxpoint/pch.h>
#include <types.h>

static pci_devfn_t get_uart_pci_device(void)
{
	switch (CONFIG_UART_FOR_CONSOLE) {
		case 0:  return PCI_DEV(0, 0x15, 5);
		case 1:  return PCI_DEV(0, 0x15, 6);
		default: return dead_code_t(pci_devfn_t);
	}
}

/* TODO: Figure out if all steps are actually necessary */
void uart_bootblock_init(void)
{
	const pci_devfn_t dev = get_uart_pci_device();

	/* Program IOBP GPIODF */
	pch_iobp_update(SIO_IOBP_GPIODF, ~0x131f, 0x131f);

	/* Program IOBP CB000180h[5:0] = 111111b (undefined register) */
	pch_iobp_update(0xcb000180, ~0x0000003f, 0x0000003f);

	/* Set and enable MMIO BAR */
	pci_write_config32(dev, PCI_BASE_ADDRESS_0, CONFIG_CONSOLE_UART_BASE_ADDRESS);
	pci_or_config16(dev, PCI_COMMAND, PCI_COMMAND_MEMORY);

	void *const bar = (void *)(uintptr_t)CONFIG_CONSOLE_UART_BASE_ADDRESS;

	/* Initialize LTR */
	clrbits32(bar + SIO_REG_PPR_GEN, SIO_REG_PPR_GEN_LTR_MODE_MASK);
	clrbits32(bar + SIO_REG_PPR_RST, SIO_REG_PPR_RST_ASSERT);

	/* Take UART out of reset */
	setbits32(bar + SIO_REG_PPR_RST, SIO_REG_PPR_RST_ASSERT);

	/* Set M and N divisor inputs and enable clock */
	uint32_t ppr_clock = 0;
	ppr_clock |= SIO_REG_PPR_CLOCK_EN;
	ppr_clock |= SIO_REG_PPR_CLOCK_UPDATE;
	ppr_clock |= SIO_REG_PPR_CLOCK_N_DIV << 16;
	ppr_clock |= SIO_REG_PPR_CLOCK_M_DIV << 1;
	write32(bar + SIO_REG_PPR_CLOCK, ppr_clock);
}