tpm: add support for TPMv2.x I2C chips

Add the tpm2_tis_i2c driver that should support any TPMv2 compliant
I2C chips, such as the NPCT75X chip.

[Ilias rename priv_auto_alloc_size to priv_auto]
Signed-off-by: Eddie James <eajames@linux.ibm.com>
Reviewed-by: Joel Stanley <joel@jms.id.au>
Reviewed-by: Ilias Apalodimas <ilias.apalodimas@linaro.org>
Signed-off-by: Ilias Apalodimas <ilias.apalodimas@linaro.org>
diff --git a/drivers/tpm/Kconfig b/drivers/tpm/Kconfig
index eceff27..d59102d 100644
--- a/drivers/tpm/Kconfig
+++ b/drivers/tpm/Kconfig
@@ -185,6 +185,15 @@
 	  to the device using the standard TPM Interface Specification (TIS)
 	  protocol.
 
+config TPM2_TIS_I2C
+	bool "Enable support for TPMv2.x I2C chips"
+	depends on TPM_V2 && DM_I2C
+	help
+	  This driver supports TPMv2.x devices connected on the I2C bus.
+	  The usual TPM operations and the 'tpm' command can be used to talk
+	  to the device using the standard TPM Interface Specification (TIS)
+	  protocol.
+
 config TPM2_FTPM_TEE
 	bool "TEE based fTPM Interface"
 	depends on TEE && OPTEE && TPM_V2
diff --git a/drivers/tpm/Makefile b/drivers/tpm/Makefile
index 5172523..9540fd7 100644
--- a/drivers/tpm/Makefile
+++ b/drivers/tpm/Makefile
@@ -13,5 +13,6 @@
 obj-$(CONFIG_$(SPL_TPL_)TPM2_CR50_I2C) += cr50_i2c.o
 obj-$(CONFIG_TPM2_TIS_SANDBOX) += tpm2_tis_sandbox.o sandbox_common.o
 obj-$(CONFIG_TPM2_TIS_SPI) += tpm2_tis_core.o tpm2_tis_spi.o
+obj-$(CONFIG_TPM2_TIS_I2C) += tpm2_tis_core.o tpm2_tis_i2c.o
 obj-$(CONFIG_TPM2_FTPM_TEE) += tpm2_ftpm_tee.o
 obj-$(CONFIG_TPM2_MMIO) += tpm2_tis_core.o tpm2_tis_mmio.o
diff --git a/drivers/tpm/tpm2_tis_i2c.c b/drivers/tpm/tpm2_tis_i2c.c
new file mode 100644
index 0000000..99d1cf21
--- /dev/null
+++ b/drivers/tpm/tpm2_tis_i2c.c
@@ -0,0 +1,171 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2022 IBM Corp.
+ */
+
+#include <common.h>
+#include <dm.h>
+#include <fdtdec.h>
+#include <i2c.h>
+#include <log.h>
+#include <tpm-v2.h>
+#include <linux/bitops.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/compiler.h>
+#include <linux/types.h>
+#include <linux/unaligned/be_byteshift.h>
+#include <asm-generic/gpio.h>
+
+#include "tpm_tis.h"
+#include "tpm_internal.h"
+
+struct tpm_tis_chip_data {
+	unsigned int pcr_count;
+	unsigned int pcr_select_min;
+};
+
+static uint tpm_tis_i2c_address_to_register(u32 addr)
+{
+	addr &= 0xFFF;
+
+	/*
+	 * Adapt register addresses that have changed compared to older TIS
+	 * version.
+	 */
+	switch (addr) {
+	case TPM_ACCESS(0):
+		return 0x04;
+	case TPM_DID_VID(0):
+		return 0x48;
+	case TPM_RID(0):
+		return 0x4C;
+	default:
+		return addr;
+	}
+}
+
+static int tpm_tis_i2c_read(struct udevice *dev, u32 addr, u16 len, u8 *in)
+{
+	int rc;
+	int count = 0;
+	uint reg = tpm_tis_i2c_address_to_register(addr);
+
+	do {
+		rc = dm_i2c_read(dev, reg, in, len);
+		udelay(SLEEP_DURATION_US);
+	} while (rc && count++ < MAX_COUNT);
+
+	return rc;
+}
+
+static int tpm_tis_i2c_write(struct udevice *dev, u32 addr, u16 len,
+			     const u8 *out)
+{
+	int rc;
+	int count = 0;
+	uint reg = tpm_tis_i2c_address_to_register(addr);
+
+	do {
+		rc = dm_i2c_write(dev, reg, out, len);
+		udelay(SLEEP_DURATION_US);
+	} while (rc && count++ < MAX_COUNT);
+
+	return rc;
+}
+
+static int tpm_tis_i2c_read32(struct udevice *dev, u32 addr, u32 *result)
+{
+	__le32 result_le;
+	int rc;
+
+	rc = tpm_tis_i2c_read(dev, addr, sizeof(u32), (u8 *)&result_le);
+	if (!rc)
+		*result = le32_to_cpu(result_le);
+
+	return rc;
+}
+
+static int tpm_tis_i2c_write32(struct udevice *dev, u32 addr, u32 value)
+{
+	__le32 value_le = cpu_to_le32(value);
+
+	return tpm_tis_i2c_write(dev, addr, sizeof(value), (u8 *)&value_le);
+}
+
+static struct tpm_tis_phy_ops phy_ops = {
+	.read_bytes = tpm_tis_i2c_read,
+	.write_bytes = tpm_tis_i2c_write,
+	.read32 = tpm_tis_i2c_read32,
+	.write32 = tpm_tis_i2c_write32,
+};
+
+static int tpm_tis_i2c_probe(struct udevice *udev)
+{
+	struct tpm_tis_chip_data *drv_data = (void *)dev_get_driver_data(udev);
+	struct tpm_chip_priv *priv = dev_get_uclass_priv(udev);
+	int rc;
+	u8 loc = 0;
+
+	tpm_tis_ops_register(udev, &phy_ops);
+
+	/*
+	 * Force locality 0. The core driver doesn't actually write the
+	 * locality register and instead just reads/writes various access
+	 * bits of the selected locality.
+	 */
+	rc = dm_i2c_write(udev, 0, &loc, 1);
+	if (rc)
+		return rc;
+
+	rc = tpm_tis_init(udev);
+	if (rc)
+		return rc;
+
+	priv->pcr_count = drv_data->pcr_count;
+	priv->pcr_select_min = drv_data->pcr_select_min;
+	priv->version = TPM_V2;
+
+	return 0;
+}
+
+static int tpm_tis_i2c_remove(struct udevice *udev)
+{
+	return tpm_tis_cleanup(udev);
+}
+
+static const struct tpm_ops tpm_tis_i2c_ops = {
+	.open = tpm_tis_open,
+	.close = tpm_tis_close,
+	.get_desc = tpm_tis_get_desc,
+	.send = tpm_tis_send,
+	.recv = tpm_tis_recv,
+	.cleanup = tpm_tis_cleanup,
+};
+
+static const struct tpm_tis_chip_data tpm_tis_std_chip_data = {
+	.pcr_count = 24,
+	.pcr_select_min = 3,
+};
+
+static const struct udevice_id tpm_tis_i2c_ids[] = {
+	{
+		.compatible = "nuvoton,npct75x",
+		.data = (ulong)&tpm_tis_std_chip_data,
+	},
+	{
+		.compatible = "tcg,tpm-tis-i2c",
+		.data = (ulong)&tpm_tis_std_chip_data,
+	},
+	{ }
+};
+
+U_BOOT_DRIVER(tpm_tis_i2c) = {
+	.name = "tpm_tis_i2c",
+	.id = UCLASS_TPM,
+	.of_match = tpm_tis_i2c_ids,
+	.ops = &tpm_tis_i2c_ops,
+	.probe = tpm_tis_i2c_probe,
+	.remove = tpm_tis_i2c_remove,
+	.priv_auto = sizeof(struct tpm_chip),
+};