feat(tpm): add tpm drivers and framework

Add tpm2 drivers to tf-a with adequate framework
-implement a fifo spi interface that works
 with discrete tpm chip.
-implement tpm command layer interfaces that are used
 to initialize, start and make measurements and
 close the interface.
-tpm drivers are built using their own make file
 to allow for ease in porting across platforms,
 and across different interfaces.

Signed-off-by: Tushar Khandelwal <tushar.khandelwal@arm.com>
Signed-off-by: Abhi Singh <abhi.singh@arm.com>
Change-Id: Ie1a189f45c80f26f4dea16c3bd71b1503709e0ea
diff --git a/drivers/tpm/tpm2.mk b/drivers/tpm/tpm2.mk
new file mode 100644
index 0000000..4418b97
--- /dev/null
+++ b/drivers/tpm/tpm2.mk
@@ -0,0 +1,26 @@
+#
+# Copyright (c) 2025, Arm Limited. All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+
+TPM2_SRC_DIR	:= drivers/tpm/
+
+TPM2_SOURCES	:= ${TPM2_SRC_DIR}tpm2_cmds.c \
+                   ${TPM2_SRC_DIR}tpm2_chip.c
+
+# TPM Hash algorithm, used during Measured Boot
+# currently only accepts SHA-256
+ifeq (${MBOOT_TPM_HASH_ALG}, sha256)
+    TPM_ALG_ID			:=	TPM_ALG_SHA256
+    TCG_DIGEST_SIZE		:=	32U
+else
+    $(error "The selected MBOOT_TPM_HASH_ALG is invalid.")
+endif #MBOOT_TPM_HASH_ALG
+
+ifeq (${TPM_INTERFACE}, FIFO_SPI)
+    TPM2_SOURCES += ${TPM2_SRC_DIR}tpm2_fifo.c \
+                    ${TPM2_SRC_DIR}tpm2_fifo_spi.c
+else
+    $(error "The selected TPM_INTERFACE is invalid.")
+endif #TPM_INTERFACE
diff --git a/drivers/tpm/tpm2_chip.c b/drivers/tpm/tpm2_chip.c
new file mode 100644
index 0000000..537ce92
--- /dev/null
+++ b/drivers/tpm/tpm2_chip.c
@@ -0,0 +1,21 @@
+
+/*
+ * Copyright (c) 2025, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <drivers/tpm/tpm2_chip.h>
+
+/*
+ * TPM timeout values
+ * Reference: TCG PC Client Platform TPM Profile (PTP) Specification v1.05
+ */
+struct tpm_chip_data tpm_chip_data = {
+	.locality = -1,
+	.timeout_msec_a = 750,
+	.timeout_msec_b = 2000,
+	.timeout_msec_c = 200,
+	.timeout_msec_d = 30,
+	.address = 0,
+};
diff --git a/drivers/tpm/tpm2_cmds.c b/drivers/tpm/tpm2_cmds.c
new file mode 100644
index 0000000..b6422a8
--- /dev/null
+++ b/drivers/tpm/tpm2_cmds.c
@@ -0,0 +1,222 @@
+/*
+ * Copyright (c) 2025, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+#include <lib/libc/endian.h>
+
+#include <drivers/delay_timer.h>
+#include <drivers/tpm/tpm2.h>
+#include <drivers/tpm/tpm2_chip.h>
+#include <drivers/tpm/tpm2_interface.h>
+
+#define CMD_SIZE_OFFSET	6
+
+#define SINGLE_BYTE	1
+#define TWO_BYTES	2
+#define FOUR_BYTES	4
+
+static struct interface_ops *interface;
+
+static int tpm_xfer(struct tpm_chip_data *chip_data, const tpm_cmd *send, tpm_cmd *receive)
+{
+	int ret;
+
+	ret = interface->send(chip_data, send);
+	if (ret < 0) {
+		return ret;
+	}
+
+	ret = interface->receive(chip_data, receive);
+	if (ret < 0) {
+		return ret;
+	}
+
+	return TPM_SUCCESS;
+}
+
+int tpm_interface_init(struct tpm_chip_data *chip_data, uint8_t locality)
+{
+	int err;
+
+	interface = tpm_interface_getops(chip_data, locality);
+
+	err = interface->request_access(chip_data, locality);
+	if (err != 0) {
+		return err;
+	}
+
+	return interface->get_info(chip_data, locality);
+}
+
+int tpm_interface_close(struct tpm_chip_data *chip_data, uint8_t locality)
+{
+	return interface->release_locality(chip_data, locality);
+}
+
+static int tpm_update_buffer(tpm_cmd *buf, uint32_t new_data, size_t new_len)
+{
+	int i, j, start;
+	uint32_t command_size;
+
+	union {
+		uint8_t var8;
+		uint16_t var16;
+		uint32_t var32;
+		uint8_t array[4];
+	} tpm_new_data;
+
+	command_size = be32toh(buf->header.cmd_size);
+
+	if (command_size + new_len > MAX_SIZE_CMDBUF) {
+		ERROR("%s: buf size exceeded, increase MAX_SIZE_CMDBUF\n",
+			__func__);
+		return TPM_INVALID_PARAM;
+	}
+	/*
+	 * Subtract the cmd header size from the current command size
+	 * so the data buffer is written to starting at index 0.
+	 */
+	start = command_size - TPM_HEADER_SIZE;
+
+	/*
+	 * The TPM, according to the TCG spec, processes data in BE byte order,
+	 * in the case where the Host is LE, htobe correctly handles the byte order.
+	 * When updating the buffer, keep in mind to only pass sizeof(new_data) or
+	 * the variable type size for the new_len function parameter. This ensures
+	 * there is only the possiblility of writing 1, 2, or 4 bytes to the buffer,
+	 * and that the correct number of bytes are written to data[i].
+	 */
+	if (new_len == SINGLE_BYTE) {
+		tpm_new_data.var8 = new_data & 0xFF;
+	} else if (new_len == TWO_BYTES) {
+		tpm_new_data.var16 = htobe16(new_data & 0xFFFF);
+	} else if (new_len == FOUR_BYTES) {
+		tpm_new_data.var32 = htobe32(new_data);
+	} else {
+		ERROR("%s: Invalid data length\n", __func__);
+		return TPM_INVALID_PARAM;
+	}
+
+	for (i = start, j = 0; i < start + new_len; i++, j++) {
+		buf->data[i] = tpm_new_data.array[j];
+	}
+	buf->header.cmd_size = htobe32(command_size + new_len);
+
+	return TPM_SUCCESS;
+}
+
+
+int tpm_startup(struct tpm_chip_data *chip_data, uint16_t mode)
+{
+	tpm_cmd startup_cmd, startup_response;
+	uint32_t tpm_rc;
+	int ret;
+
+	memset(&startup_cmd, 0, sizeof(startup_cmd));
+	memset(&startup_response, 0, sizeof(startup_response));
+
+	startup_cmd.header.tag = htobe16(TPM_ST_NO_SESSIONS);
+	startup_cmd.header.cmd_size = htobe32(sizeof(tpm_cmd_hdr));
+	startup_cmd.header.cmd_code = htobe32(TPM_CMD_STARTUP);
+
+	ret = tpm_update_buffer(&startup_cmd, mode, sizeof(mode));
+	if (ret < 0) {
+		return ret;
+	}
+
+	ret = tpm_xfer(chip_data, &startup_cmd, &startup_response);
+	if (ret < 0) {
+		return ret;
+	}
+
+	tpm_rc = be32toh(startup_response.header.cmd_code);
+	if (tpm_rc != TPM_RESPONSE_SUCCESS) {
+		ERROR("%s: response code contains error = %X\n", __func__, tpm_rc);
+		return TPM_ERR_RESPONSE;
+	}
+
+	return TPM_SUCCESS;
+}
+
+int tpm_pcr_extend(struct tpm_chip_data *chip_data, uint32_t index,
+		uint16_t algorithm, const uint8_t *digest,
+		uint32_t digest_len)
+{
+	tpm_cmd pcr_extend_cmd, pcr_extend_response;
+	uint32_t tpm_rc;
+	int ret;
+
+	memset(&pcr_extend_cmd, 0, sizeof(pcr_extend_cmd));
+	memset(&pcr_extend_response, 0, sizeof(pcr_extend_response));
+
+	if (digest == NULL) {
+		return TPM_INVALID_PARAM;
+	}
+	pcr_extend_cmd.header.tag = htobe16(TPM_ST_SESSIONS);
+	pcr_extend_cmd.header.cmd_size = htobe32(sizeof(tpm_cmd_hdr));
+	pcr_extend_cmd.header.cmd_code = htobe32(TPM_CMD_PCR_EXTEND);
+
+	/* handle (PCR Index)*/
+	ret = tpm_update_buffer(&pcr_extend_cmd, index, sizeof(index));
+	if (ret < 0) {
+		return ret;
+	}
+
+	/* authorization size , session handle, nonce size, attributes*/
+	ret = tpm_update_buffer(&pcr_extend_cmd, TPM_MIN_AUTH_SIZE, sizeof(uint32_t));
+	if (ret < 0) {
+		return ret;
+	}
+	ret = tpm_update_buffer(&pcr_extend_cmd, TPM_RS_PW, sizeof(uint32_t));
+	if (ret < 0) {
+		return ret;
+	}
+	ret = tpm_update_buffer(&pcr_extend_cmd, TPM_ZERO_NONCE_SIZE, sizeof(uint16_t));
+	if (ret < 0) {
+		return ret;
+	}
+	ret = tpm_update_buffer(&pcr_extend_cmd, TPM_ATTRIBUTES_DISABLE, sizeof(uint8_t));
+	if (ret < 0) {
+		return ret;
+	}
+
+	/* hmac/password size */
+	ret = tpm_update_buffer(&pcr_extend_cmd, TPM_ZERO_HMAC_SIZE, sizeof(uint16_t));
+	if (ret < 0) {
+		return ret;
+	}
+
+	/* hashes count */
+	ret = tpm_update_buffer(&pcr_extend_cmd, TPM_SINGLE_HASH_COUNT, sizeof(uint32_t));
+	if (ret < 0) {
+		return ret;
+	}
+
+	/* hash algorithm */
+	ret = tpm_update_buffer(&pcr_extend_cmd, algorithm, sizeof(algorithm));
+	if (ret < 0) {
+		return ret;
+	}
+
+	/* digest */
+	for (int i = 0; i < digest_len; i++) {
+		ret = tpm_update_buffer(&pcr_extend_cmd, digest[i], sizeof(uint8_t));
+		if (ret < 0) {
+			return ret;
+		}
+	}
+
+	ret = tpm_xfer(chip_data, &pcr_extend_cmd, &pcr_extend_response);
+	if (ret < 0) {
+		return ret;
+	}
+
+	tpm_rc = be32toh(pcr_extend_response.header.cmd_code);
+	if (tpm_rc != TPM_RESPONSE_SUCCESS) {
+		ERROR("%s: response code contains error = %X\n", __func__, tpm_rc);
+		return TPM_ERR_RESPONSE;
+	}
+
+	return TPM_SUCCESS;
+}
diff --git a/drivers/tpm/tpm2_fifo.c b/drivers/tpm/tpm2_fifo.c
new file mode 100644
index 0000000..7c4b9d8
--- /dev/null
+++ b/drivers/tpm/tpm2_fifo.c
@@ -0,0 +1,322 @@
+/*
+ * Copyright (c) 2025, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+#include <lib/libc/endian.h>
+
+#include <drivers/delay_timer.h>
+#include <drivers/tpm/tpm2.h>
+#include <drivers/tpm/tpm2_chip.h>
+#include <drivers/tpm/tpm2_interface.h>
+
+#define LOCALITY_START_ADDRESS(x, y) \
+	((uint16_t)(x->address + (0x1000 * y)))
+
+static int tpm2_get_info(struct tpm_chip_data *chip_data, uint8_t locality)
+{
+	uint16_t tpm_base_addr = LOCALITY_START_ADDRESS(chip_data, locality);
+	uint32_t vid_did;
+	uint8_t revision;
+	int err;
+
+	err = tpm2_fifo_read_chunk(tpm_base_addr + TPM_FIFO_REG_VENDID, DWORD, &vid_did);
+	if (err < 0) {
+		return err;
+	}
+
+	err = tpm2_fifo_read_byte(tpm_base_addr + TPM_FIFO_REG_REVID, &revision);
+	if (err < 0) {
+		return err;
+	}
+
+	INFO("TPM Chip: vendor-id 0x%x, device-id 0x%x, revision-id: 0x%x\n",
+		0xFFFF & vid_did, vid_did >> 16, revision);
+
+	return TPM_SUCCESS;
+}
+
+static int tpm2_wait_reg_bits(uint16_t reg, uint8_t set, unsigned long timeout, uint8_t *status)
+{
+	int err;
+	uint64_t timeout_delay = timeout_init_us(timeout * 1000);
+
+	do {
+		err = tpm2_fifo_read_byte(reg, status);
+		if (err < 0) {
+			return err;
+		}
+		if ((*status & set) == set) {
+			return TPM_SUCCESS;
+		}
+	} while (!timeout_elapsed(timeout_delay));
+
+	return TPM_ERR_TIMEOUT;
+}
+
+static int tpm2_fifo_request_access(struct tpm_chip_data *chip_data, uint8_t locality)
+{
+	uint16_t tpm_base_addr = LOCALITY_START_ADDRESS(chip_data, locality);
+	uint8_t status;
+	int err;
+
+	err = tpm2_fifo_write_byte(tpm_base_addr + TPM_FIFO_REG_ACCESS, TPM_ACCESS_REQUEST_USE);
+	if (err < 0) {
+		return err;
+	}
+
+	err = tpm2_wait_reg_bits(tpm_base_addr + TPM_FIFO_REG_ACCESS,
+				TPM_ACCESS_ACTIVE_LOCALITY,
+				chip_data->timeout_msec_a, &status);
+	if (err == 0) {
+		chip_data->locality = locality;
+		return TPM_SUCCESS;
+	}
+
+	return err;
+}
+
+static int tpm2_fifo_release_locality(struct tpm_chip_data *chip_data, uint8_t locality)
+{
+	uint16_t tpm_base_addr = LOCALITY_START_ADDRESS(chip_data, locality);
+	uint8_t buf;
+	int err;
+
+	err = tpm2_fifo_read_byte(tpm_base_addr + TPM_FIFO_REG_ACCESS, &buf);
+	if (err < 0) {
+		return err;
+	}
+
+	if (buf & (TPM_ACCESS_REQUEST_PENDING | TPM_ACCESS_VALID)) {
+		return tpm2_fifo_write_byte(tpm_base_addr + TPM_FIFO_REG_ACCESS,
+				TPM_ACCESS_RELINQUISH_LOCALITY);
+	}
+
+	ERROR("%s: Unable to release locality\n", __func__);
+	return TPM_ERR_RESPONSE;
+}
+
+static int tpm2_fifo_prepare(struct tpm_chip_data *chip_data)
+{
+	uint16_t tpm_base_addr = LOCALITY_START_ADDRESS(chip_data, chip_data->locality);
+	uint8_t status;
+	int err;
+
+	err = tpm2_fifo_write_byte(tpm_base_addr + TPM_FIFO_REG_STATUS, TPM_STAT_COMMAND_READY);
+	if (err < 0) {
+		return err;
+	}
+
+	err = tpm2_wait_reg_bits(tpm_base_addr + TPM_FIFO_REG_STATUS,
+				TPM_STAT_COMMAND_READY,
+				chip_data->timeout_msec_b, &status);
+	if (err < 0) {
+		ERROR("%s: TPM Status Busy\n", __func__);
+		return err;
+	}
+
+	return TPM_SUCCESS;
+}
+
+static int tpm2_fifo_get_burstcount(struct tpm_chip_data *chip_data, uint16_t *burstcount)
+{
+	uint16_t tpm_base_addr = LOCALITY_START_ADDRESS(chip_data, chip_data->locality);
+	uint64_t timeout_delay = timeout_init_us(chip_data->timeout_msec_a * 1000);
+	int err;
+
+	if (burstcount == NULL) {
+		return TPM_INVALID_PARAM;
+	}
+
+	do {
+		uint8_t byte0, byte1;
+
+		err = tpm2_fifo_read_byte(tpm_base_addr + TPM_FIFO_REG_BURST_COUNT_LO, &byte0);
+		if (err < 0) {
+			return err;
+		}
+
+		err = tpm2_fifo_read_byte(tpm_base_addr + TPM_FIFO_REG_BURST_COUNT_HI, &byte1);
+		if (err < 0) {
+			return err;
+		}
+
+		*burstcount = (uint16_t)((byte1 << 8) + byte0);
+		if (*burstcount != 0U) {
+			return TPM_SUCCESS;
+		}
+	} while (!timeout_elapsed(timeout_delay));
+
+	return TPM_ERR_TIMEOUT;
+}
+
+static int tpm2_fifo_send(struct tpm_chip_data *chip_data, const tpm_cmd *buf)
+{
+	uint16_t tpm_base_addr = LOCALITY_START_ADDRESS(chip_data, chip_data->locality);
+	uint8_t status;
+	uint16_t burstcnt;
+	int err;
+	uint32_t len, index;
+
+	if (sizeof(buf->header) != TPM_HEADER_SIZE) {
+		ERROR("%s: invalid command header size.\n", __func__);
+		return TPM_INVALID_PARAM;
+	}
+
+	err = tpm2_fifo_read_byte(tpm_base_addr + TPM_FIFO_REG_STATUS, &status);
+	if (err < 0) {
+		return err;
+	}
+
+	if (!(status & TPM_STAT_COMMAND_READY)) {
+		err = tpm2_fifo_prepare(chip_data);
+		if (err < 0) {
+			return err;
+		}
+	}
+
+	/* write the command header to the TPM first */
+	const uint8_t *header_data = (const uint8_t *)&buf->header;
+
+	for (index = 0; index < TPM_HEADER_SIZE; index++) {
+		err = tpm2_fifo_write_byte(tpm_base_addr + TPM_FIFO_REG_DATA_FIFO,
+				header_data[index]);
+		if (err < 0) {
+			return err;
+		}
+	}
+
+	len =  be32toh(buf->header.cmd_size);
+
+	while (index < len) {
+		err = tpm2_fifo_get_burstcount(chip_data, &burstcnt);
+		if (err < 0) {
+			return err;
+		}
+
+		for (; burstcnt > 0U && index < len; burstcnt--) {
+			err = tpm2_fifo_write_byte(tpm_base_addr + TPM_FIFO_REG_DATA_FIFO,
+					buf->data[index - TPM_HEADER_SIZE]);
+			if (err < 0) {
+				return err;
+			}
+			index++;
+		}
+	}
+
+	err = tpm2_wait_reg_bits(tpm_base_addr + TPM_FIFO_REG_STATUS,
+				TPM_STAT_VALID,
+				chip_data->timeout_msec_c,
+				&status);
+	if (err < 0) {
+		return err;
+	}
+
+	if (status & TPM_STAT_EXPECT) {
+		ERROR("%s: TPM is still expecting data after command buffer is sent\n", __func__);
+		return TPM_ERR_TRANSFER;
+	}
+
+	err = tpm2_fifo_write_byte(tpm_base_addr + TPM_FIFO_REG_STATUS, TPM_STAT_GO);
+	if (err < 0) {
+		return err;
+	}
+
+	return TPM_SUCCESS;
+}
+
+static int tpm2_fifo_read_data(struct tpm_chip_data *chip_data, tpm_cmd *buf,
+			uint16_t tpm_base_addr, uint8_t *status, int *size, int bytes_expected)
+{
+	int err, read_size, loop_index;
+	uint16_t burstcnt;
+	uint8_t *read_data;
+
+	if (bytes_expected == TPM_READ_HEADER) {
+		/* read the response header from the TPM first */
+		read_data = (uint8_t *)&buf->header;
+		read_size = TPM_HEADER_SIZE;
+		loop_index = *size;
+	} else {
+		/* process the rest of the mssg with bytes_expected */
+		read_data = buf->data;
+		read_size = bytes_expected;
+		loop_index = *size - TPM_HEADER_SIZE;
+	}
+
+	err = tpm2_wait_reg_bits(tpm_base_addr + TPM_FIFO_REG_STATUS,
+				TPM_STAT_AVAIL,
+				chip_data->timeout_msec_c,
+				status);
+	if (err < 0) {
+		return err;
+	}
+
+	while (*size < read_size) {
+		err = tpm2_fifo_get_burstcount(chip_data, &burstcnt);
+		if (err < 0) {
+			ERROR("%s: TPM burst count error\n", __func__);
+			return err;
+		}
+
+		for (; burstcnt > 0U && loop_index < read_size;
+		    burstcnt--, loop_index++, (*size)++) {
+			err = tpm2_fifo_read_byte(
+				tpm_base_addr + TPM_FIFO_REG_DATA_FIFO,
+				(void *)&read_data[loop_index]);
+			if (err < 0) {
+				return err;
+			}
+		}
+	}
+
+	return TPM_SUCCESS;
+}
+
+static int tpm2_fifo_receive(struct tpm_chip_data *chip_data, tpm_cmd *buf)
+{
+	uint16_t tpm_base_addr = LOCALITY_START_ADDRESS(chip_data, chip_data->locality);
+	int size = 0, bytes_expected, err;
+	uint8_t status;
+
+	err = tpm2_fifo_read_data(chip_data, buf, tpm_base_addr, &status, &size, TPM_READ_HEADER);
+	if (err < 0) {
+		return err;
+	}
+
+	bytes_expected = be32toh(buf->header.cmd_size);
+	if (bytes_expected > sizeof(*buf)) {
+		ERROR("%s: tpm response buffer cannot store expected response\n", __func__);
+		return TPM_INVALID_PARAM;
+	}
+
+	if (size == bytes_expected) {
+		return size;
+	}
+
+	err = tpm2_fifo_read_data(chip_data, buf, tpm_base_addr, &status, &size, bytes_expected);
+	if (err < 0) {
+		return err;
+	}
+
+	if (size < bytes_expected) {
+		ERROR("%s: response buffer size is less than expected\n", __func__);
+		return TPM_ERR_RESPONSE;
+	}
+
+	return TPM_SUCCESS;
+}
+
+static interface_ops_t fifo_ops = {
+	.get_info = tpm2_get_info,
+	.send = tpm2_fifo_send,
+	.receive = tpm2_fifo_receive,
+	.request_access = tpm2_fifo_request_access,
+	.release_locality = tpm2_fifo_release_locality,
+};
+
+struct interface_ops *
+tpm_interface_getops(struct tpm_chip_data *chip_data,  uint8_t locality)
+{
+	return &fifo_ops;
+}
diff --git a/drivers/tpm/tpm2_fifo_spi.c b/drivers/tpm/tpm2_fifo_spi.c
new file mode 100644
index 0000000..16d87d9
--- /dev/null
+++ b/drivers/tpm/tpm2_fifo_spi.c
@@ -0,0 +1,161 @@
+/*
+ * Copyright (c) 2025, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <assert.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <string.h>
+
+#include <drivers/gpio_spi.h>
+#include <drivers/tpm/tpm2.h>
+#include <drivers/tpm/tpm2_chip.h>
+#include <drivers/tpm/tpm2_interface.h>
+
+#define ENCODE_LIMIT		128
+#define CS_ASSERT_OFFSET	0xD4
+#define RETRY_COUNT		50
+
+#define TPM_READ	false
+#define TPM_WRITE	true
+
+extern struct spi_plat *spidev;
+
+static int tpm2_spi_transfer(const void *data_out, void *data_in, uint8_t len)
+{
+	return spidev->ops->xfer(len, data_out, data_in);
+}
+
+/*
+ * Reference: TCG PC Client Platform TPM Profile (PTP) Specification v1.05
+ */
+static int tpm2_spi_start_transaction(uint16_t tpm_reg, bool write, uint8_t len)
+{
+	int rc;
+	uint8_t header[4];
+	uint8_t header_response[4];
+	uint8_t zero = 0, byte;
+	int retries;
+
+	/* check to make sure len does not exceed the encoding limit */
+	if (len > ENCODE_LIMIT) {
+		return TPM_INVALID_PARAM;
+	}
+
+	/*
+	 * 7.4.6 TPM SPI Bit protocol calls for the following header
+	 * to be sent to the TPM at the start of every attempted read/write.
+	 */
+
+	/* header[0] contains the r/w and the xfer size, if the msb is not
+	 * set, the operation is write, if it is set then it is read.
+	 * The size of the transfer is encoded, and must not overwrite
+	 * the msb, therefore an ENCODE LIMIT of 128 is present.
+	 */
+	header[0] = ((write) ? 0x00 : 0x80) | (len - 1);
+
+	/*
+	 * header[1] contains the address offset 0xD4_xxxx as defined
+	 * in the TPM spec, since the CS# is asserted.
+	 */
+	header[1] = CS_ASSERT_OFFSET;
+
+	/*
+	 * header[2] and header[3] contain the address of the register
+	 * to be read/written.
+	 */
+	header[2] = tpm_reg >> 8;
+	header[3] = tpm_reg;
+
+	rc = tpm2_spi_transfer(header, header_response, 4);
+	if (rc != 0) {
+		return TPM_ERR_TRANSFER;
+	}
+
+	/*
+	 * 7.4.5 Flow Control defines a wait state in order to accommodate
+	 * the TPM in case it needs to free its buffer.
+	 */
+	if ((header_response[3] & 0x01) != 0U) {
+		return TPM_SUCCESS;
+	}
+
+	/*
+	 * if the wait state over bit is not set in the initial header_response,
+	 * poll for the wait state over by sending a zeroed byte, if the
+	 * RETRY_COUNT is exceeded the transfer fails.
+	 */
+	for (retries = RETRY_COUNT; retries > 0; retries--) {
+		rc = tpm2_spi_transfer(&zero, &byte, 1);
+		if (rc != 0) {
+			return TPM_ERR_TRANSFER;
+		}
+		if ((byte & 0x01) != 0U) {
+			return TPM_SUCCESS;
+		}
+	}
+
+	if (retries == 0) {
+		ERROR("%s: TPM Timeout\n", __func__);
+		return TPM_ERR_TIMEOUT;
+	}
+
+	return TPM_SUCCESS;
+}
+
+static void tpm2_spi_end_transaction(void)
+{
+	spidev->ops->stop();
+}
+
+static void tpm2_spi_init(void)
+{
+	spidev->ops->get_access();
+	spidev->ops->start();
+}
+
+static int tpm2_fifo_io(uint16_t tpm_reg, bool is_write, uint8_t len, void *val)
+{
+	int rc;
+
+	tpm2_spi_init();
+	rc = tpm2_spi_start_transaction(tpm_reg, is_write, len);
+	if (rc != 0) {
+		tpm2_spi_end_transaction();
+		return rc;
+	}
+
+	rc = tpm2_spi_transfer(
+		is_write ? val : NULL,
+		is_write ? NULL : val,
+		len);
+	if (rc != 0) {
+		tpm2_spi_end_transaction();
+		return rc;
+	}
+
+	tpm2_spi_end_transaction();
+
+	return TPM_SUCCESS;
+}
+
+int tpm2_fifo_write_byte(uint16_t tpm_reg, uint8_t val)
+{
+	return tpm2_fifo_io(tpm_reg, TPM_WRITE, BYTE, &val);
+}
+
+int tpm2_fifo_read_byte(uint16_t tpm_reg, uint8_t *val)
+{
+	return tpm2_fifo_io(tpm_reg, TPM_READ, BYTE, val);
+}
+
+int tpm2_fifo_read_chunk(uint16_t tpm_reg, uint8_t len, void *val)
+{
+	if ((len != BYTE) && (len != WORD) && (len != DWORD)) {
+		return TPM_INVALID_PARAM;
+	}
+
+	return tpm2_fifo_io(tpm_reg, TPM_READ, len, val);
+}