dm: tpm: Add a uclass for Trusted Platform Modules

Add a new uclass for TPMs which uses almost the same TIS (TPM Interface
Specification) as is currently implemented. Since init() is handled by the
normal driver model probe() method, we don't need to implement that. Also
rename the transfer method to xfer() which is a less clumbsy name.

Once all drivers and users are converted to driver model we can remove the
old code.

Signed-off-by: Simon Glass <sjg@chromium.org>
Acked-by: Christophe Ricard<christophe-h.ricard@st.com>
Reviewed-by: Heiko Schocher <hs@denx.de>
diff --git a/drivers/tpm/tpm-uclass.c b/drivers/tpm/tpm-uclass.c
new file mode 100644
index 0000000..b6e1fc5
--- /dev/null
+++ b/drivers/tpm/tpm-uclass.c
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2015 Google, Inc
+ * Written by Simon Glass <sjg@chromium.org>
+ *
+ * SPDX-License-Identifier:	GPL-2.0+
+ */
+
+#include <common.h>
+#include <dm.h>
+#include <tpm.h>
+#include <linux/unaligned/be_byteshift.h>
+#include "tpm_internal.h"
+
+int tpm_open(struct udevice *dev)
+{
+	struct tpm_ops *ops = tpm_get_ops(dev);
+
+	if (!ops->open)
+		return -ENOSYS;
+
+	return ops->open(dev);
+}
+
+int tpm_close(struct udevice *dev)
+{
+	struct tpm_ops *ops = tpm_get_ops(dev);
+
+	if (!ops->close)
+		return -ENOSYS;
+
+	return ops->close(dev);
+}
+
+int tpm_get_desc(struct udevice *dev, char *buf, int size)
+{
+	struct tpm_ops *ops = tpm_get_ops(dev);
+
+	if (!ops->get_desc)
+		return -ENOSYS;
+
+	return ops->get_desc(dev, buf, size);
+}
+
+/* Returns max number of milliseconds to wait */
+static ulong tpm_tis_i2c_calc_ordinal_duration(struct tpm_chip_priv *priv,
+					       u32 ordinal)
+{
+	int duration_idx = TPM_UNDEFINED;
+	int duration = 0;
+
+	if (ordinal < TPM_MAX_ORDINAL) {
+		duration_idx = tpm_ordinal_duration[ordinal];
+	} else if ((ordinal & TPM_PROTECTED_ORDINAL_MASK) <
+			TPM_MAX_PROTECTED_ORDINAL) {
+		duration_idx = tpm_protected_ordinal_duration[
+				ordinal & TPM_PROTECTED_ORDINAL_MASK];
+	}
+
+	if (duration_idx != TPM_UNDEFINED)
+		duration = priv->duration_ms[duration_idx];
+
+	if (duration <= 0)
+		return 2 * 60 * 1000; /* Two minutes timeout */
+	else
+		return duration;
+}
+
+int tpm_xfer(struct udevice *dev, const uint8_t *sendbuf, size_t send_size,
+	uint8_t *recvbuf, size_t *recv_size)
+{
+	struct tpm_chip_priv *priv = dev_get_uclass_priv(dev);
+	struct tpm_ops *ops = tpm_get_ops(dev);
+	ulong start, stop;
+	uint count, ordinal;
+	int ret, ret2;
+
+	if (ops->xfer)
+		return ops->xfer(dev, sendbuf, send_size, recvbuf, recv_size);
+
+	if (!ops->send || !ops->recv)
+		return -ENOSYS;
+
+	/* switch endianess: big->little */
+	count = get_unaligned_be32(sendbuf + TPM_CMD_COUNT_BYTE);
+	ordinal = get_unaligned_be32(sendbuf + TPM_CMD_ORDINAL_BYTE);
+
+	if (count == 0) {
+		debug("no data\n");
+		return -ENODATA;
+	}
+	if (count > send_size) {
+		debug("invalid count value %x %zx\n", count, send_size);
+		return -E2BIG;
+	}
+
+	debug("%s: Calling send\n", __func__);
+	ret = ops->send(dev, sendbuf, send_size);
+	if (ret < 0)
+		return ret;
+
+	start = get_timer(0);
+	stop = tpm_tis_i2c_calc_ordinal_duration(priv, ordinal);
+	do {
+		ret = ops->recv(dev, priv->buf, sizeof(priv->buf));
+		if (ret >= 0) {
+			if (ret > *recv_size)
+				return -ENOSPC;
+			memcpy(recvbuf, priv->buf, ret);
+			*recv_size = ret;
+			ret = 0;
+			break;
+		} else if (ret != -EAGAIN) {
+			return ret;
+		}
+
+		mdelay(priv->retry_time_ms);
+		if (get_timer(start) > stop) {
+			ret = -ETIMEDOUT;
+			break;
+		}
+	} while (ret);
+
+	ret2 = ops->cleanup ? ops->cleanup(dev) : 0;
+
+	return ret2 ? ret2 : ret;
+}
+
+UCLASS_DRIVER(tpm) = {
+	.id             = UCLASS_TPM,
+	.name           = "tpm",
+	.flags          = DM_UC_FLAG_SEQ_ALIAS,
+	.per_device_auto_alloc_size	= sizeof(struct tpm_chip_priv),
+};