drivers/i2c/tpm/cr50: Support interrupts for status

Support reading the ACPI GPE status (on x86) to determine when
the cr50 is ready to return response data or is done processing
written data.  If the interrupt is not defined by Kconfig then
it will continue to use the safe delay.

This was tested with reef hardware and a modified cr50 image
that generates interrupts at the intended points.

BUG=chrome-os-partner:53336

Change-Id: Ic8f805159650c45382cacac8840450a1f8b4d7a1
Signed-off-by: Duncan Laurie <dlaurie@chromium.org>
Reviewed-on: https://review.coreboot.org/16672
Tested-by: build bot (Jenkins)
Reviewed-by: Aaron Durbin <adurbin@chromium.org>
diff --git a/src/drivers/i2c/tpm/Kconfig b/src/drivers/i2c/tpm/Kconfig
index 1725eaf..a5ab077 100644
--- a/src/drivers/i2c/tpm/Kconfig
+++ b/src/drivers/i2c/tpm/Kconfig
@@ -30,6 +30,11 @@
 	default 2 # FIXME, workaround for Kconfig BS
 	depends on I2C_TPM
 
+config DRIVER_TPM_I2C_IRQ
+	int "IRQ or GPE to use for TPM interrupt"
+	default -1
+	depends on I2C_TPM
+
 config DRIVER_I2C_TPM_ACPI
 	bool "Generate I2C TPM ACPI device"
 	default y if ARCH_X86 && I2C_TPM
diff --git a/src/drivers/i2c/tpm/cr50.c b/src/drivers/i2c/tpm/cr50.c
index 37f0881..af7f2ae 100644
--- a/src/drivers/i2c/tpm/cr50.c
+++ b/src/drivers/i2c/tpm/cr50.c
@@ -41,6 +41,9 @@
 #include <timer.h>
 #include "tpm.h"
 
+#if IS_ENABLED(CONFIG_ARCH_X86)
+#include <arch/acpi.h>
+#endif
 
 #define CR50_MAX_BUFSIZE	63
 #define CR50_TIMEOUT_LONG_MS	2000	/* Long timeout while waiting for TPM */
@@ -55,6 +58,33 @@
 
 static struct tpm_inf_dev g_tpm_dev CAR_GLOBAL;
 
+/* Wait for interrupt to indicate the TPM is ready */
+static int cr50_i2c_wait_tpm_ready(struct tpm_chip *chip)
+{
+	struct stopwatch sw;
+
+	if (!chip->vendor.irq_status) {
+		/* Fixed delay if interrupt not supported */
+		mdelay(CR50_TIMEOUT_SHORT_MS);
+		return 0;
+	}
+
+	stopwatch_init_msecs_expire(&sw, 5 * CR50_TIMEOUT_SHORT_MS);
+
+	while (!chip->vendor.irq_status(chip->vendor.irq))
+		if (stopwatch_expired(&sw))
+			return -1;
+
+	return 0;
+}
+
+/* Clear pending interrupts */
+static void cr50_i2c_clear_tpm_irq(struct tpm_chip *chip)
+{
+	if (chip->vendor.irq_status)
+		chip->vendor.irq_status(chip->vendor.irq);
+}
+
 /*
  * cr50_i2c_read() - read from TPM register
  *
@@ -77,6 +107,9 @@
 	if (tpm_dev->addr == 0)
 		return -1;
 
+	/* Clear interrupt before starting transaction */
+	cr50_i2c_clear_tpm_irq(chip);
+
 	/* Send the register address byte to the TPM */
 	if (i2c_write_raw(tpm_dev->bus, tpm_dev->addr, &addr, 1)) {
 		printk(BIOS_ERR, "%s: Address write failed\n", __func__);
@@ -84,7 +117,8 @@
 	}
 
 	/* Wait for TPM to be ready with response data */
-	mdelay(CR50_TIMEOUT_SHORT_MS);
+	if (cr50_i2c_wait_tpm_ready(chip) < 0)
+		return -1;
 
 	/* Read response data from the TPM */
 	if (i2c_read_raw(tpm_dev->bus, tpm_dev->addr, buffer, len)) {
@@ -123,6 +157,9 @@
 	tpm_dev->buf[0] = addr;
 	memcpy(tpm_dev->buf + 1, buffer, len);
 
+	/* Clear interrupt before starting transaction */
+	cr50_i2c_clear_tpm_irq(chip);
+
 	/* Send write request buffer with address */
 	if (i2c_write_raw(tpm_dev->bus, tpm_dev->addr, tpm_dev->buf, len + 1)) {
 		printk(BIOS_ERR, "%s: Error writing to TPM\n", __func__);
@@ -130,9 +167,7 @@
 	}
 
 	/* Wait for TPM to be ready */
-	mdelay(CR50_TIMEOUT_SHORT_MS);
-
-	return 0;
+	return cr50_i2c_wait_tpm_ready(chip);
 }
 
 static int check_locality(struct tpm_chip *chip, int loc)
@@ -380,6 +415,7 @@
 	chip->vendor.recv = &cr50_i2c_tis_recv;
 	chip->vendor.send = &cr50_i2c_tis_send;
 	chip->vendor.cancel = &cr50_i2c_tis_ready;
+	chip->vendor.irq = CONFIG_DRIVER_TPM_I2C_IRQ;
 }
 
 int tpm_vendor_probe(unsigned bus, uint32_t addr)
@@ -434,8 +470,18 @@
 
 	cr50_vendor_init(chip);
 
-	/* Disable interrupts (not supported) */
-	chip->vendor.irq = 0;
+	/*
+	 * Interrupts are not supported this early in firmware,
+	 * use use an arch-specific method to query for interrupt status.
+	 */
+	if (chip->vendor.irq > 0) {
+#if IS_ENABLED(CONFIG_ARCH_X86)
+		/* Query GPE status for interrupt */
+		chip->vendor.irq_status = &acpi_get_gpe;
+#else
+		chip->vendor.irq = -1;
+#endif
+	}
 
 	if (request_locality(chip, 0) != 0)
 		return -1;
@@ -449,8 +495,8 @@
 		goto out_err;
 	}
 
-	printk(BIOS_DEBUG, "cr50 TPM %u:%02x (device-id 0x%X)\n",
-	       tpm_dev->bus, tpm_dev->addr, vendor >> 16);
+	printk(BIOS_DEBUG, "cr50 TPM 2.0 (i2c %u:0x%02x irq %d id 0x%x)\n",
+	       bus, dev_addr, chip->vendor.irq, vendor >> 16);
 
 	chip->is_open = 1;
 	return 0;
diff --git a/src/drivers/i2c/tpm/tpm.h b/src/drivers/i2c/tpm/tpm.h
index 048c848..b6dda1a 100644
--- a/src/drivers/i2c/tpm/tpm.h
+++ b/src/drivers/i2c/tpm/tpm.h
@@ -74,6 +74,7 @@
 	uint8_t req_complete_val;
 	uint8_t req_canceled;
 	int irq;
+	int (*irq_status)(int irq);
 	int (*recv)(struct tpm_chip *, uint8_t *, size_t);
 	int (*send)(struct tpm_chip *, uint8_t *, size_t);
 	void (*cancel)(struct tpm_chip *);