aboutsummaryrefslogtreecommitdiffstats
path: root/roms/skiboot/libstb/drivers
diff options
context:
space:
mode:
Diffstat (limited to 'roms/skiboot/libstb/drivers')
-rw-r--r--roms/skiboot/libstb/drivers/Makefile.inc11
-rw-r--r--roms/skiboot/libstb/drivers/tpm_i2c_interface.c50
-rw-r--r--roms/skiboot/libstb/drivers/tpm_i2c_interface.h15
-rw-r--r--roms/skiboot/libstb/drivers/tpm_i2c_nuvoton.c627
-rw-r--r--roms/skiboot/libstb/drivers/tpm_i2c_nuvoton.h9
5 files changed, 712 insertions, 0 deletions
diff --git a/roms/skiboot/libstb/drivers/Makefile.inc b/roms/skiboot/libstb/drivers/Makefile.inc
new file mode 100644
index 000000000..80356eb36
--- /dev/null
+++ b/roms/skiboot/libstb/drivers/Makefile.inc
@@ -0,0 +1,11 @@
+# -*-Makefile-*-
+
+DRIVERS_DIR = libstb/drivers
+
+SUBDIRS += $(DRIVERS_DIR)
+
+DRIVERS_SRCS = tpm_i2c_interface.c tpm_i2c_nuvoton.c
+DRIVERS_OBJS = $(DRIVERS_SRCS:%.c=%.o)
+DRIVERS = $(DRIVERS_DIR)/built-in.a
+
+$(DRIVERS): $(DRIVERS_OBJS:%=$(DRIVERS_DIR)/%)
diff --git a/roms/skiboot/libstb/drivers/tpm_i2c_interface.c b/roms/skiboot/libstb/drivers/tpm_i2c_interface.c
new file mode 100644
index 000000000..84cbab5bb
--- /dev/null
+++ b/roms/skiboot/libstb/drivers/tpm_i2c_interface.c
@@ -0,0 +1,50 @@
+// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+/* Copyright 2013-2018 IBM Corp. */
+
+#include <skiboot.h>
+#include <opal-api.h>
+#include <i2c.h>
+
+#include "tpm_i2c_interface.h"
+#include "../status_codes.h"
+
+/* TPMs can clock strech I2C operations for a LOOOOOOONG */
+#define I2C_BYTE_TIMEOUT_MS 2000 /* 2000ms/byte timeout */
+
+/**
+ * tpm_i2c_request_send - send request to i2c bus
+ * @tpm_bus_id: i2c bus id
+ * @tpm_dev_addr: address of the tpm device
+ * @read_write: SMBUS_READ or SMBUS_WRITE
+ * @offset: any of the I2C interface offset defined
+ * @offset_bytes: offset size in bytes
+ * @buf: data to be read or written
+ * @buflen: buf length
+ *
+ * This interacts with skiboot i2c API to send an I2C request to the tpm
+ * device
+ *
+ * Returns: Zero on success otherwise a negative error code
+ */
+int tpm_i2c_request_send(struct tpm_dev *tpm_device, int read_write,
+ uint32_t offset, uint32_t offset_bytes, void* buf,
+ size_t buflen)
+{
+ int rc, timeout;
+
+ /*
+ * Set the request timeout to 30ms per byte. Otherwise, we get
+ * an I2C master timeout for all requests sent to the device
+ * since the I2C master's timeout is too short (1ms per byte).
+ */
+ timeout = (buflen + offset_bytes + 2) * I2C_BYTE_TIMEOUT_MS;
+
+ rc = i2c_request_send(tpm_device->bus_id, tpm_device->i2c_addr,
+ read_write, offset, offset_bytes, buf, buflen,
+ timeout);
+ if (rc == OPAL_PARAMETER)
+ return STB_ARG_ERROR;
+ else if (rc < 0)
+ return STB_DRIVER_ERROR;
+ return 0;
+}
diff --git a/roms/skiboot/libstb/drivers/tpm_i2c_interface.h b/roms/skiboot/libstb/drivers/tpm_i2c_interface.h
new file mode 100644
index 000000000..514d8f835
--- /dev/null
+++ b/roms/skiboot/libstb/drivers/tpm_i2c_interface.h
@@ -0,0 +1,15 @@
+// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+/* Copyright 2013-2018 IBM Corp. */
+
+#ifndef __TPM_I2C_H
+#define __TPM_I2C_H
+
+#include <i2c.h>
+#include <stdlib.h>
+
+#include "../tpm_chip.h"
+
+extern int tpm_i2c_request_send(struct tpm_dev *tpm_device, int read_write,
+ uint32_t offset, uint32_t offset_bytes, void* buf,
+ size_t buflen);
+#endif /* __TPM_I2C_H */
diff --git a/roms/skiboot/libstb/drivers/tpm_i2c_nuvoton.c b/roms/skiboot/libstb/drivers/tpm_i2c_nuvoton.c
new file mode 100644
index 000000000..0aa9711db
--- /dev/null
+++ b/roms/skiboot/libstb/drivers/tpm_i2c_nuvoton.c
@@ -0,0 +1,627 @@
+// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+/* Copyright 2013-2018 IBM Corp. */
+
+#include <timebase.h>
+#include <skiboot.h>
+#include <device.h>
+#include <i2c.h>
+#include "../status_codes.h"
+#include "../tpm_chip.h"
+#include "tpm_i2c_interface.h"
+#include "tpm_i2c_nuvoton.h"
+#include <opal-api.h>
+
+//#define DBG(fmt, ...) prlog(PR_DEBUG, fmt, ##__VA_ARGS__)
+#define DBG(fmt, ...)
+
+#define DRIVER_NAME "i2c_tpm_nuvoton"
+
+/* I2C interface offsets */
+#define TPM_STS 0x00
+#define TPM_BURST_COUNT 0x01
+#define TPM_DATA_FIFO_W 0x20
+#define TPM_DATA_FIFO_R 0x40
+#define TPM_VID_DID 0x60
+
+/* Bit masks for the TPM STATUS register */
+#define TPM_STS_VALID 0x80
+#define TPM_STS_COMMAND_READY 0x40
+#define TPM_STS_GO 0x20
+#define TPM_STS_DATA_AVAIL 0x10
+#define TPM_STS_EXPECT 0x08
+
+
+/* TPM Driver values */
+#define MAX_STSVALID_POLLS 5
+#define TPM_TIMEOUT_INTERVAL 10
+#define TPM_NUVOTON_VID 0x5010FE00
+#define TPM_VENDOR_ID_MASK 0xFFFFFF00
+
+static struct tpm_dev *tpm_device = NULL;
+
+static int tpm_status_write_byte(uint8_t byte)
+{
+ uint8_t value = byte;
+ return tpm_i2c_request_send(tpm_device, SMBUS_WRITE, TPM_STS, 1, &value,
+ sizeof(value));
+}
+
+static int tpm_status_read_byte(uint8_t offset, uint8_t *byte)
+{
+ return tpm_i2c_request_send(tpm_device, SMBUS_READ, offset, 1, byte,
+ sizeof(uint8_t));
+}
+
+static bool tpm_check_status(uint8_t status, uint8_t mask, uint8_t expected)
+{
+ return ((status & mask) == expected);
+}
+
+static int tpm_wait_for_command_ready(void)
+{
+ uint64_t start, stop, now;
+ int rc;
+ uint8_t status;
+
+ start = mftb();
+ stop = start + msecs_to_tb(TPM_TIMEOUT_B);
+
+ do {
+ now = mftb();
+ rc = tpm_status_read_byte(TPM_STS, &status);
+ if (rc < 0) {
+ /**
+ * @fwts-label TPMReadCmdReady
+ * @fwts-advice Either the tpm device or the tpm-i2c
+ * interface doesn't seem to be working properly. Check
+ * the return code (rc) for further details.
+ */
+ prlog(PR_ERR, "NUVOTON: fail to read sts.commandReady, "
+ "rc=%d\n", rc);
+ return STB_DRIVER_ERROR;
+ }
+ if (tpm_check_status(status,
+ TPM_STS_COMMAND_READY,
+ TPM_STS_COMMAND_READY)) {
+ DBG("--- Command ready, delay=%lu/%d\n",
+ tb_to_msecs(now-start), TPM_TIMEOUT_B);
+ return 0;
+ }
+ if (tb_compare(now, stop) == TB_ABEFOREB)
+ time_wait_ms(TPM_TIMEOUT_INTERVAL);
+ else
+ break;
+ } while (1);
+
+ return STB_TPM_TIMEOUT;
+}
+
+static int tpm_set_command_ready(void)
+{
+ int rc, retries;
+ /*
+ * The first write to command ready may just abort an
+ * outstanding command, so we poll twice
+ */
+ for (retries = 0; retries < 2; retries++) {
+ rc = tpm_status_write_byte(TPM_STS_COMMAND_READY);
+ if (rc < 0) {
+ /**
+ * @fwts-label TPMWriteCmdReady
+ * @fwts-advice Either the tpm device or the tpm-i2c
+ * interface doesn't seem to be working properly. Check
+ * the return code (rc) for further details.
+ */
+ prlog(PR_ERR, "NUVOTON: fail to write sts.commandReady, "
+ "rc=%d\n", rc);
+ return STB_DRIVER_ERROR;
+ }
+ rc = tpm_wait_for_command_ready();
+ if (rc == STB_TPM_TIMEOUT)
+ continue;
+ return rc;
+ }
+ /**
+ * @fwts-label TPMCmdReadyTimeout
+ * @fwts-advice The command ready bit of the tpm status register is
+ * taking longer to be settled. Either the wait time need to be
+ * increased or the TPM device is not functional.
+ */
+ prlog(PR_ERR, "NUVOTON: timeout on sts.commandReady, delay > %d\n",
+ 2*TPM_TIMEOUT_B);
+ return STB_TPM_TIMEOUT;
+}
+
+static int tpm_wait_for_fifo_status(uint8_t mask, uint8_t expected)
+{
+ int retries, rc;
+ uint8_t status;
+
+ for(retries = 0; retries <= MAX_STSVALID_POLLS; retries++) {
+ rc = tpm_status_read_byte(TPM_STS, &status);
+ if (rc < 0) {
+ /**
+ * @fwts-label TPMReadFifoStatus
+ * @fwts-advice Either the tpm device or the tpm-i2c
+ * interface doesn't seem to be working properly. Check
+ * the return code (rc) for further details.
+ */
+ prlog(PR_ERR, "NUVOTON: fail to read fifo status: "
+ "mask %x, expected %x, rc=%d\n", mask, expected,
+ rc);
+ return STB_DRIVER_ERROR;
+ }
+ if (tpm_check_status(status, mask, expected))
+ return 0;
+ /* Wait TPM STS register be settled */
+ time_wait_ms(5);
+ }
+ return STB_TPM_TIMEOUT;
+}
+
+static int tpm_wait_for_data_avail(void)
+{
+ uint64_t start, stop, now;
+ uint8_t status;
+ int rc;
+
+ start = mftb();
+ stop = start + msecs_to_tb(TPM_TIMEOUT_A);
+
+ do {
+ now = mftb();
+ rc = tpm_status_read_byte(TPM_STS, &status);
+ if (rc < 0) {
+ /**
+ * @fwts-label TPMReadDataAvail
+ * @fwts-advice Either the tpm device or the tpm-i2c
+ * interface doesn't seem to be working properly. Check
+ * the return code (rc) for further details.
+ */
+ prlog(PR_ERR, "NUVOTON: fail to read sts.dataAvail, "
+ "rc=%d\n", rc);
+ return STB_DRIVER_ERROR;
+ }
+ if (tpm_check_status(status,
+ TPM_STS_VALID | TPM_STS_DATA_AVAIL,
+ TPM_STS_VALID | TPM_STS_DATA_AVAIL)) {
+ DBG("---- Data available. delay=%lu/%d\n",
+ tb_to_msecs(now-start), TPM_TIMEOUT_A);
+ return 0;
+ }
+ if (tb_compare(now, stop) == TB_ABEFOREB)
+ time_wait_ms(TPM_TIMEOUT_INTERVAL);
+ else
+ break;
+ } while (1);
+ /**
+ * @fwts-label TPMDataAvailBitTimeout
+ * @fwts-advice The data avail bit of the tpm status register is taking
+ * longer to be settled. Either the wait time need to be increased or
+ * the TPM device is not functional.
+ */
+ prlog(PR_ERR, "NUVOTON: timeout on sts.dataAvail, delay=%lu/%d\n",
+ tb_to_msecs(now-start), TPM_TIMEOUT_A);
+ return STB_TPM_TIMEOUT;
+}
+
+static int tpm_read_burst_count(void)
+{
+ uint64_t start, stop, now;
+ uint8_t burst_count;
+ int rc;
+
+ start = mftb();
+ stop = start + msecs_to_tb(TPM_TIMEOUT_D);
+ burst_count = 0;
+
+ do {
+ now = mftb();
+ /* In i2C, burstCount is 1 byte */
+ rc = tpm_status_read_byte(TPM_BURST_COUNT, &burst_count);
+ if (rc == 0 && burst_count > 0) {
+ DBG("---- burst_count=%d, delay=%lu/%d\n", burst_count,
+ tb_to_msecs(now-start), TPM_TIMEOUT_D);
+ return (int) burst_count;
+ }
+ if (rc < 0) {
+ /**
+ * @fwts-label TPMReadBurstCount
+ * @fwts-advice Either the tpm device or the tpm-i2c
+ * interface doesn't seem to be working properly. Check
+ * the return code (rc) for further details.
+ */
+ prlog(PR_ERR, "NUVOTON: fail to read sts.burstCount, "
+ "rc=%d\n", rc);
+ return STB_DRIVER_ERROR;
+ }
+ if (tb_compare(now, stop) == TB_ABEFOREB)
+ time_wait_ms(TPM_TIMEOUT_INTERVAL);
+ else
+ break;
+ } while (1);
+
+ /**
+ * @fwts-label TPMBurstCountTimeout
+ * @fwts-advice The burstcount bit of the tpm status register is
+ * taking longer to be settled. Either the wait time need to be
+ * increased or the TPM device is not functional.
+ */
+ prlog(PR_ERR, "NUVOTON: timeout on sts.burstCount, delay=%lu/%d\n",
+ tb_to_msecs(now-start), TPM_TIMEOUT_D);
+ return STB_TPM_TIMEOUT;
+}
+
+static int tpm_write_fifo(uint8_t* buf, size_t buflen)
+{
+ int rc, burst_count;
+ size_t count, bytes;
+
+ /*
+ * We will transfer the command except for the last byte
+ * that will be transfered separately to allow for
+ * overflow checking
+ */
+ count = 0;
+ do {
+ burst_count = tpm_read_burst_count();
+ if (burst_count < 0)
+ return burst_count;
+
+ bytes = (count + burst_count > buflen - 1 ?
+ (buflen - 1 - count) : burst_count);
+
+ rc = tpm_i2c_request_send(tpm_device,
+ SMBUS_WRITE, TPM_DATA_FIFO_W,
+ 1, &buf[count], bytes);
+ count += bytes;
+ DBG("%s FIFO: %zd bytes written, count=%zd, rc=%d\n",
+ (rc) ? "!!!!" : "----", bytes, count, rc);
+ if (rc < 0) {
+ /**
+ * @fwts-label TPMWriteFifo
+ * @fwts-advice Either the tpm device or the tpm-i2c
+ * interface doesn't seem to be working properly. Check
+ * the return code (rc) for further details.
+ */
+ prlog(PR_ERR, "NUVOTON: fail to write fifo, "
+ "count=%zd, rc=%d\n", count, rc);
+ return STB_DRIVER_ERROR;
+ }
+
+ rc = tpm_wait_for_fifo_status(TPM_STS_VALID | TPM_STS_EXPECT,
+ TPM_STS_VALID | TPM_STS_EXPECT);
+ if (rc == STB_DRIVER_ERROR)
+ return rc;
+ if (rc == STB_TPM_TIMEOUT) {
+ /**
+ * @fwts-label TPMWriteFifoNotExpecting
+ * @fwts-advice The write to the TPM FIFO overflowed,
+ * the TPM is not expecting more data. This indicates a
+ * bug in the TPM device driver.
+ */
+ prlog(PR_ERR, "NUVOTON: write FIFO overflow, not expecting "
+ "more data\n");
+ return STB_TPM_OVERFLOW;
+ }
+ } while (count < buflen - 1);
+
+ /*
+ * Write the last byte
+ */
+ burst_count = tpm_read_burst_count();
+ if (burst_count < 0)
+ return burst_count;
+
+ rc = tpm_i2c_request_send(tpm_device,
+ SMBUS_WRITE,
+ TPM_DATA_FIFO_W, 1,
+ &buf[count], 1);
+ count++;
+ DBG("%s FIFO: last byte written, count=%zd, rc=%d\n",
+ (rc) ? "!!!!" : "----", count, rc);
+
+ if (rc < 0) {
+ /**
+ * @fwts-label TPMWriteFifoLastByte
+ * @fwts-advice Either the tpm device or the tpm-i2c interface
+ * doesn't seem to be working properly. Check the return code
+ * (rc) for further details.
+ */
+ prlog(PR_ERR, "NUVOTON: fail to write fifo (last byte), "
+ "count=%zd, rc=%d\n", count, rc);
+ return STB_DRIVER_ERROR;
+ }
+ rc = tpm_wait_for_fifo_status(TPM_STS_VALID | TPM_STS_EXPECT,
+ TPM_STS_VALID | TPM_STS_EXPECT);
+ if (rc == STB_DRIVER_ERROR)
+ return rc;
+ if (rc == 0) {
+ /**
+ * @fwts-label TPMWriteFifoExpecting
+ * @fwts-advice The write to the TPM FIFO overflowed.
+ * It is expecting more data even though we think we
+ * are done. This indicates a bug in the TPM device
+ * driver.
+ */
+ prlog(PR_ERR, "TPM: write FIFO overflow, expecting "
+ "more data\n");
+ return STB_TPM_OVERFLOW;
+ }
+ return 0;
+}
+
+static int tpm_read_fifo(uint8_t* buf, size_t* buflen)
+{
+ int rc, burst_count;
+ size_t count;
+
+ rc = tpm_wait_for_data_avail();
+ if (rc < 0)
+ goto error;
+
+ count = 0;
+ do {
+ burst_count = tpm_read_burst_count();
+ if (burst_count < 0) {
+ rc = burst_count;
+ goto error;
+ }
+ if (count + burst_count > *buflen) {
+ /**
+ * @fwts-label TPMReadFifoOverflow
+ * @fwts-advice The read from TPM FIFO overflowed. It is
+ * expecting more data even though we think we are done.
+ * This indicates a bug in the TPM device driver.
+ */
+ prlog(PR_ERR, "NUVOTON: overflow on fifo read, c=%zd, "
+ "bc=%d, bl=%zd\n", count, burst_count, *buflen);
+ rc = STB_TPM_OVERFLOW;
+ goto error;
+ }
+ rc = tpm_i2c_request_send(tpm_device,
+ SMBUS_READ,
+ TPM_DATA_FIFO_R, 1,
+ &buf[count], burst_count);
+ count += burst_count;
+ DBG("%s FIFO: %d bytes read, count=%zd, rc=%d\n",
+ (rc) ? "!!!!" : "----", burst_count, count, rc);
+ if (rc < 0) {
+ /**
+ * @fwts-label TPMReadFifo
+ * @fwts-advice Either the tpm device or the tpm-i2c interface
+ * doesn't seem to be working properly. Check the return code
+ * (rc) for further details.
+ */
+ prlog(PR_ERR, "NUVOTON: fail to read fifo, count=%zd, "
+ "rc=%d\n", count, rc);
+ rc = STB_DRIVER_ERROR;
+ goto error;
+ }
+ rc = tpm_wait_for_fifo_status(
+ TPM_STS_VALID | TPM_STS_DATA_AVAIL,
+ TPM_STS_VALID | TPM_STS_DATA_AVAIL);
+ if (rc == STB_DRIVER_ERROR)
+ goto error;
+ } while (rc == 0);
+
+ *buflen = count;
+ return 0;
+
+error:
+ *buflen = 0;
+ return rc;
+}
+
+static int tpm_transmit(struct tpm_dev *dev, uint8_t* buf, size_t cmdlen,
+ size_t* buflen)
+{
+ int rc = 0;
+ if (!dev) {
+ /**
+ * @fwts-label TPMDeviceNotInitialized
+ * @fwts-advice TPM device is not initialized. This indicates a
+ * bug in the tpm_transmit() caller
+ */
+ prlog(PR_ERR, "TPM: tpm device not initialized\n");
+ return STB_ARG_ERROR;
+ }
+ tpm_device = dev;
+ DBG("**** %s: dev %#x/%#x buf %016llx cmdlen %zu"
+ " buflen %zu ****\n",
+ __func__, dev->bus_id, dev->i2c_addr, *(uint64_t *) buf,
+ cmdlen, *buflen);
+
+ DBG("step 1/5: set command ready\n");
+ rc = tpm_set_command_ready();
+ if (rc < 0)
+ goto out;
+
+ DBG("step 2/5: write FIFO\n");
+ rc = tpm_write_fifo(buf, cmdlen);
+ if (rc < 0)
+ goto out;
+
+ DBG("step 3/5: write sts.go\n");
+ rc = tpm_status_write_byte(TPM_STS_GO);
+ if (rc < 0) {
+ /**
+ * @fwts-label TPMWriteGo
+ * @fwts-advice Either the tpm device or the tpm-i2c interface
+ * doesn't seem to be working properly. Check the return code
+ * (rc) for further details.
+ */
+ prlog(PR_ERR, "NUVOTON: fail to write sts.go, rc=%d\n", rc);
+ rc = STB_DRIVER_ERROR;
+ goto out;
+ }
+
+ DBG("step 4/5: read FIFO\n");
+ rc = tpm_read_fifo(buf, buflen);
+ if (rc < 0)
+ goto out;
+
+ DBG("step 5/5: release tpm\n");
+ rc = tpm_status_write_byte(TPM_STS_COMMAND_READY);
+ if (rc < 0) {
+ /**
+ * @fwts-label TPMReleaseTpm
+ * @fwts-advice Either the tpm device or the tpm-i2c interface
+ * doesn't seem to be working properly. Check the return code
+ * (rc) for further details.
+ */
+ prlog(PR_ERR, "NUVOTON: fail to release tpm, rc=%d\n", rc);
+ rc = STB_DRIVER_ERROR;
+ }
+
+out:
+ DBG("**** tpm_transmit %s, rc=%d ****\n",
+ (rc) ? "ERROR" : "SUCCESS", rc);
+ return rc;
+}
+
+static struct tpm_driver tpm_i2c_nuvoton_driver = {
+ .name = DRIVER_NAME,
+ .transmit = tpm_transmit,
+};
+
+static int nuvoton_tpm_quirk(void *data, struct i2c_request *req, int *rc)
+{
+ struct tpm_dev *tpm_device = data;
+ struct dt_node *dev;
+ uint16_t addr;
+ bool found;
+
+ /*
+ * The nuvoton TPM firmware has a problem where a single byte read or
+ * zero byte write to one of its I2C addresses causes the TPM to lock
+ * up the bus. Once locked up the bus can only be recovered by power
+ * cycling the TPM. Unfortunately, we don't have the ability to
+ * power cycle the TPM because allowing it to be reset at runtime
+ * would undermine the TPM's security model (we can reset it and
+ * send it whatever measurements we like to unlock it's secrets).
+ * So the best we can do here is try avoid triggering the problem
+ * in the first place.
+ *
+ * For a bit of added fun the TPM also appears to check for traffic
+ * on a few different I2C bus addresses. It does this even when not
+ * configured to respond on those addresses so you can trigger the
+ * bug by sending traffic... somwhere. To work around this we block
+ * sending I2C requests on the TPM's bus unless the DT explicitly
+ * tells us there is a device there.
+ */
+
+ /* first, check if this a known address */
+ addr = req->dev_addr;
+ found = false;
+
+ dt_for_each_child(req->bus->dt_node, dev) {
+ if (dt_prop_get_u32(dev, "reg") == addr) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ *rc = OPAL_I2C_TIMEOUT;
+ return 1;
+ }
+
+ /* second, check if it's a bad transaction to the TPM */
+ if (tpm_device->bus_id == req->bus->opal_id &&
+ tpm_device->i2c_addr == req->dev_addr &&
+ ((req->op == I2C_READ && req->rw_len == 1) ||
+ (req->op == I2C_WRITE && req->rw_len == 0))) {
+ *rc = OPAL_I2C_TIMEOUT;
+ prlog(PR_DEBUG,"NUVOTON: Squashed i2c probe to avoid locking "
+ "I2C bus\n");
+ return 1;
+ }
+
+ return 0;
+}
+
+void tpm_i2c_nuvoton_probe(void)
+{
+ struct tpm_dev *tpm_device = NULL;
+ struct dt_node *node = NULL;
+ struct i2c_bus *bus;
+ const char *name;
+ uint32_t vendor = 0;
+
+ dt_for_each_compatible(dt_root, node, "nuvoton,npct650") {
+ if (!dt_node_is_enabled(node))
+ continue;
+ tpm_device = (struct tpm_dev*) malloc(sizeof(struct tpm_dev));
+ assert(tpm_device);
+ /*
+ * Read TPM device address and bus id. Make sure the properties
+ * really exist if the default value is returned.
+ */
+ tpm_device->i2c_addr = dt_prop_get_u32_def(node, "reg", 0);
+ if (!tpm_device->i2c_addr && !dt_find_property(node, "reg")) {
+ /*
+ * @fwts-label NuvotonRegNotFound
+ * @fwts-advice reg property not found. This indicates
+ * a Hostboot bug if the property really doesn't exist
+ * in the tpm node.
+ */
+ prlog(PR_ERR, "NUVOTON: reg property not found, "
+ "tpm node %p\n", node);
+ goto disable;
+ }
+ tpm_device->bus_id = dt_prop_get_u32_def(node->parent,
+ "ibm,opal-id", 0);
+ if (!tpm_device->bus_id &&
+ !dt_find_property(node->parent, "ibm,opal-id")) {
+ /*
+ * @fwts-label NuvotonIbmOpalIdNotFound
+ * @fwts-advice ibm,opal-id property not found. This
+ * indicates a Hostboot bug if the property really
+ * doesn't exist in the tpm node.
+ */
+ prlog(PR_ERR, "NUVOTON: ibm,opal-id property not "
+ "found, tpm node parent %p\n", node->parent);
+ goto disable;
+ }
+ /* ensure there's really the TPM we expect at that address */
+ if (tpm_i2c_request_send(tpm_device, SMBUS_READ, TPM_VID_DID,
+ 1, &vendor, sizeof(vendor))) {
+ prlog(PR_ERR, "NUVOTON: i2c device inaccessible\n");
+ goto disable;
+ }
+ if ((vendor & TPM_VENDOR_ID_MASK) != TPM_NUVOTON_VID) {
+ prlog(PR_ERR, "NUVOTON: expected vendor id mismatch\n");
+ goto disable;
+ }
+ if (tpm_register_chip(node, tpm_device,
+ &tpm_i2c_nuvoton_driver)) {
+ free(tpm_device);
+ continue;
+ }
+ tss_tpm_register(tpm_device, &tpm_i2c_nuvoton_driver);
+ bus = i2c_find_bus_by_id(tpm_device->bus_id);
+ assert(bus->check_quirk == NULL);
+ bus->check_quirk = nuvoton_tpm_quirk;
+ bus->check_quirk_data = tpm_device;
+ name = dt_prop_get(node->parent, "ibm,port-name");
+
+ prlog(PR_NOTICE, "NUVOTON: TPM I2C workaround applied to %s\n",
+ name);
+
+ /*
+ * Tweak for linux. It doesn't have a driver compatible
+ * with "nuvoton,npct650"
+ */
+ if (!dt_node_is_compatible(node, "nuvoton,npct601")) {
+ dt_check_del_prop(node, "compatible");
+ dt_add_property_strings(node, "compatible",
+ "nuvoton,npct650", "nuvoton,npct601");
+ }
+ }
+ return;
+disable:
+ dt_add_property_string(node, "status", "disabled");
+ prlog(PR_NOTICE, "TPM: tpm node %p disabled\n", node);
+ free(tpm_device);
+}
diff --git a/roms/skiboot/libstb/drivers/tpm_i2c_nuvoton.h b/roms/skiboot/libstb/drivers/tpm_i2c_nuvoton.h
new file mode 100644
index 000000000..53cf1e1da
--- /dev/null
+++ b/roms/skiboot/libstb/drivers/tpm_i2c_nuvoton.h
@@ -0,0 +1,9 @@
+// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+/* Copyright 2013-2016 IBM Corp. */
+
+#ifndef __TPM_I2C_NUVOTON_H
+#define __TPM_I2C_NUVOTON_H
+
+extern void tpm_i2c_nuvoton_probe(void);
+
+#endif /* __TPM_I2C_NUVOTON_H */