tpm: Add ramstage driver and interrupt configuration

This adds a ramstage driver for the TPM and allows the interrupt
to be configured in devicetree.cb.

The interrupt vector is set like other PNP devices, and the
interrupt polarity is set with a register configuration variable.

These values are written into locality 0 TPM_INT_VECTOR and
TPM_INT_ENABLE and then all interrupts are disabled so they are
not used in firmware but can be enabled by the OS.

It also adds an ACPI device for the TPM which will configure the
reported interrupt based on what has been written into the TPM
during ramstage.  The _STA method returns enabled if CONFIG_LPC_TPM
is enabled, and the _CRS method will only report an interrupt if one
has been set in the TPM itself.

The TPM memory address is added by the driver and declared in the
ACPI code.  In order to access it in ACPI a Kconfig entry is added for
the default TPM TIS 1.2 base address.  Note that IO address 0x2e is
required to be declared in ACPI for the kernel driver to probe correctly.

BUG=chrome-os-partner:33385
BRANCH=samus,auron
TEST=manual testing on samus:
1) Add TPM device in devicetree.cb with configured interrupt and
ensure that it is functional in the OS.
2) Test with active high and active low, edge triggered and level
triggered setups.
3) Ensure that with no device added to devicetree.cb that the TPM
is still functional in polling mode.

Change-Id: Iee2a1832394dfe32f3ea3700753b8ecc443c7fbf
Signed-off-by: Stefan Reinauer <reinauer@chromium.org>
Original-Commit-Id: fc2c106caae939467fb07f3a0207adee71dda48e
Original-Change-Id: Id8a5a251f193c71ab2209f85fb470120a3b6a80d
Original-Signed-off-by: Duncan Laurie <dlaurie@chromium.org>
Original-Reviewed-on: https://chromium-review.googlesource.com/226661
Original-Reviewed-by: Aaron Durbin <adurbin@chromium.org>
Reviewed-on: http://review.coreboot.org/9469
Tested-by: build bot (Jenkins)
Reviewed-by: Patrick Georgi <pgeorgi@google.com>
diff --git a/src/drivers/pc80/tpm/tpm.c b/src/drivers/pc80/tpm/tpm.c
index c0429dd..f1c5d97 100644
--- a/src/drivers/pc80/tpm/tpm.c
+++ b/src/drivers/pc80/tpm/tpm.c
@@ -35,6 +35,8 @@
 #include <console/console.h>
 #include <tpm.h>
 #include <arch/early_variables.h>
+#include <device/pnp.h>
+#include "chip.h"
 
 #define PREFIX "lpc_tpm: "
 
@@ -211,6 +213,20 @@
 	return value;
 }
 
+static inline void tpm_write_int_vector(int vector, int locality)
+{
+	TPM_DEBUG_IO_WRITE(TIS_REG_INT_VECTOR, vector);
+	writeb(vector & 0xf, TIS_REG(locality, TIS_REG_INT_VECTOR));
+}
+
+static inline void tpm_write_int_polarity(int polarity, int locality)
+{
+	/* Set polarity and leave all other bits at 0 */
+	u32 value = (polarity & 0x3) << 3;
+	TPM_DEBUG_IO_WRITE(TIS_REG_INT_ENABLE, value);
+	writel(value, TIS_REG(locality, TIS_REG_INT_ENABLE));
+}
+
 /*
  * tis_wait_sts()
  *
@@ -673,3 +689,95 @@
 
 	return tis_readresponse(recvbuf, recv_len);
 }
+
+#ifdef __RAMSTAGE__
+
+/*
+ * tis_setup_interrupt()
+ *
+ * Set up the interrupt vector and polarity for locality 0 and
+ * disable all interrupts so they are unused in firmware but can
+ * be enabled by the OS.
+ *
+ * The values used here must match what is passed in the TPM ACPI
+ * device if ACPI is used on the platform.
+ *
+ * @vector - TPM interrupt vector
+ * @polarity - TPM interrupt polarity
+ *
+ * Returns 0 on success, TPM_DRIVER_ERR on failure.
+ */
+static int tis_setup_interrupt(int vector, int polarity)
+{
+	u8 locality = 0;
+	int has_access = tis_has_access(locality);
+
+	/* Open connection and request access if not already granted */
+	if (!has_access && tis_open() < 0)
+		return TPM_DRIVER_ERR;
+
+	/* Set TPM interrupt vector */
+	tpm_write_int_vector(vector, locality);
+
+	/* Set TPM interupt polarity and disable interrupts */
+	tpm_write_int_polarity(polarity, locality);
+
+	/* Close connection if it was opened */
+	if (!has_access && tis_close() < 0)
+		return TPM_DRIVER_ERR;
+
+	return 0;
+}
+
+static void lpc_tpm_read_resources(struct device *dev)
+{
+	/* Static 5K memory region specified in Kconfig */
+	mmio_resource(dev, 0, CONFIG_TPM_TIS_BASE_ADDRESS >> 10, 0x5000 >> 10);
+}
+
+static void lpc_tpm_set_resources(struct device *dev)
+{
+	tpm_config_t *config = (tpm_config_t *)dev->chip_info;
+	struct resource *res;
+
+	for (res = dev->resource_list; res; res = res->next) {
+		if (!(res->flags & IORESOURCE_ASSIGNED))
+			continue;
+
+		if (res->flags & IORESOURCE_IRQ) {
+			/* Set interrupt vector */
+			tis_setup_interrupt((int)res->base,
+					    config->irq_polarity);
+		} else {
+			printk(BIOS_ERR,
+			       "ERROR: %s %02lx unknown resource type\n",
+			       dev_path(dev), res->index);
+			continue;
+		}
+
+		res->flags |= IORESOURCE_STORED;
+		report_resource_stored(dev, res, " <tpm>");
+	}
+}
+
+static struct device_operations lpc_tpm_ops = {
+	.read_resources   = &lpc_tpm_read_resources,
+	.set_resources    = &lpc_tpm_set_resources,
+};
+
+static struct pnp_info pnp_dev_info[] = {
+	{ .flags = PNP_IRQ0 }
+};
+
+static void enable_dev(struct device *dev)
+{
+	pnp_enable_devices(dev, &lpc_tpm_ops,
+			   ARRAY_SIZE(pnp_dev_info), pnp_dev_info);
+}
+
+struct chip_operations drivers_pc80_tpm_ops = {
+	CHIP_NAME("LPC TPM")
+	.enable_dev = enable_dev
+};
+
+#endif /* __RAMSTAGE__ */