// SPDX-License-Identifier: GPL-2.0+
/*
 * Host interface (LPC or eSPI) configuration on Nuvoton BMC
 * Copyright (c) 2022 Nuvoton Technology Corp.
 */

#include <dm.h>
#include <regmap.h>
#include <syscon.h>
#include <asm/io.h>
#include <dm/device_compat.h>
#include <linux/bitfield.h>

#define SMC_CTL_REG_ADDR	0xc0001001
#define SMC_CTL_HOSTWAIT	0x80

/* GCR Register Offsets */
#define HIFCR			0x50
#define MFSEL1			0x260
#define MFSEL4			0x26c

/* ESPI Register offsets */
#define ESPICFG			0x4
#define ESPIHINDP		0x80
#define ESPI_TEN		0xF0
#define ESPI_ENG		0xF1

/* MFSEL bit fileds */
#define MFSEL1_LPCSEL		BIT(26)
#define MFSEL4_ESPISEL		BIT(8)

/* ESPICFG bit fileds */
#define CHSUPP_MASK		GENMASK(27, 24)
#define IOMODE_MASK		GENMASK(9, 8)
#define IOMODE_SDQ		FIELD_PREP(IOMODE_MASK, 3)
#define MAXFREQ_MASK		GENMASK(12, 10)
#define MAXFREQ_33MHZ		FIELD_PREP(MAXFREQ_MASK, 2)

/* ESPIHINDP bit fileds */
#define AUTO_SBLD		BIT(4)
#define AUTO_HS1		BIT(8)
#define AUTO_HS2		BIT(12)
#define AUTO_HS3		BIT(16)

#define ESPI_TEN_ENABLE		0x55
#define ESPI_TEN_DISABLE	0

/* KCS/BPC interrupt control */
#define BPCFEN			0x46
#define FRIE			BIT(3)
#define HRIE			BIT(4)
#define KCS1CTL			0x18
#define KCS2CTL			0x2a
#define KCS3CTL			0x3c
#define IBFIE			BIT(0)
#define OBEIE			BIT(1)

static int npcm_host_intf_bind(struct udevice *dev)
{
	struct regmap *syscon;
	void __iomem *base, *kcs_base;
	u32 ch_supp, val;
	u32 ioaddr;
	const char *type;
	int ret;

	syscon = syscon_regmap_lookup_by_phandle(dev, "syscon");
	if (IS_ERR(syscon)) {
		dev_err(dev, "%s: unable to get syscon, dev %s\n", __func__, dev->name);
		return PTR_ERR(syscon);
	}

	ioaddr  = dev_read_u32_default(dev, "ioaddr", 0);
	if (ioaddr)
		regmap_write(syscon, HIFCR, ioaddr);

	type = dev_read_string(dev, "type");
	if (!type)
		return -EINVAL;

	if (!strcmp(type, "espi")) {
		base = dev_read_addr_ptr(dev);
		if (!base)
			return -EINVAL;

		ret = dev_read_u32(dev, "channel-support", &ch_supp);
		if (ret)
			return ret;

		/* Select eSPI pins function */
		regmap_update_bits(syscon, MFSEL1, MFSEL1_LPCSEL, 0);
		regmap_update_bits(syscon, MFSEL4, MFSEL4_ESPISEL, MFSEL4_ESPISEL);

		val = AUTO_SBLD | AUTO_HS1 | AUTO_HS2 | AUTO_HS3 | ch_supp;
		writel(val, base + ESPIHINDP);

		val = readl(base + ESPICFG);
		val &= ~(CHSUPP_MASK | IOMODE_MASK | MAXFREQ_MASK);
		val |= IOMODE_SDQ | MAXFREQ_33MHZ | FIELD_PREP(CHSUPP_MASK, ch_supp);
		writel(val, base + ESPICFG);

		if (device_is_compatible(dev, "nuvoton,npcm845-host-intf")) {
			/* Workaround: avoid eSPI module getting into wrong state */
			writeb(ESPI_TEN_ENABLE, base + ESPI_TEN);
			writeb(BIT(6), base + ESPI_ENG);
			writeb(ESPI_TEN_DISABLE, base + ESPI_TEN);
		}
	} else if (!strcmp(type, "lpc")) {
		/* Select LPC pin function */
		regmap_update_bits(syscon, MFSEL4, MFSEL4_ESPISEL, 0);
		regmap_update_bits(syscon, MFSEL1, MFSEL1_LPCSEL, MFSEL1_LPCSEL);
	}

	/* Release host wait */
	setbits_8(SMC_CTL_REG_ADDR, SMC_CTL_HOSTWAIT);

	kcs_base = dev_read_addr_index_ptr(dev, 1);
	if (kcs_base) {
		/* Disable KCS/BPC interrupts */
		clrbits_8(kcs_base + BPCFEN, FRIE | HRIE);
		clrbits_8(kcs_base + KCS1CTL, IBFIE | OBEIE);
		clrbits_8(kcs_base + KCS2CTL, IBFIE | OBEIE);
		clrbits_8(kcs_base + KCS3CTL, IBFIE | OBEIE);
	}

	return 0;
}

static const struct udevice_id npcm_hostintf_ids[] = {
	{ .compatible = "nuvoton,npcm750-host-intf" },
	{ .compatible = "nuvoton,npcm845-host-intf" },
	{ }
};

U_BOOT_DRIVER(npcm_host_intf) = {
	.name	= "npcm_host_intf",
	.id	= UCLASS_MISC,
	.of_match = npcm_hostintf_ids,
	.bind = npcm_host_intf_bind,
};
