[PATCH 2/2] reset: Add Infineon SLB9670 TPM reset driver

Lukas Wunner
2023-09-26T19:30:11Z

From: Lino Sanfilippo <l.sanfilippo@kunbus.com>

Normally the platform firmware is responsible for taking a Trusted
Platform Module out of reset on boot and storing measurements into it.

However if the platform firmware is incapable of doing that -- as is the
case on the Raspberry Pi -- then the onus is on the kernel to take the
TPM out of reset before trying to attach a driver to it.

Provide a reset driver for such platforms.

The Infineon SLB9670 TPM requires a specific reset sequence on its RST#
pin which is documented in sections 5.4 and 5.5 of the datasheet:

https://www.infineon.com/dgdl/Infineon-SLB%209670VQ2.0-DataSheet-v01_04-EN.pdf?fileId=5546d4626fc1ce0b016fc78270350cd6

The sequence with minimum wait intervals is as follows:

deassert RST#
wait at least 60 ms
assert RST#
wait at least 2 usecs
deassert RST#
wait at least 60 ms
assert RST#
wait at least 2 usecs
deassert RST#
wait at least 60 ms before issuing the first TPM command

Signed-off-by: Lino Sanfilippo <l.sanfilippo@kunbus.com>
Signed-off-by: Lukas Wunner <lukas@wunner.de>
---
drivers/reset/Kconfig | 9 +++
drivers/reset/Makefile | 1 +
drivers/reset/reset-slb9670.c | 141 ++++++++++++++++++++++++++++++++++++++++++
3 files changed, 151 insertions(+)
create mode 100644 drivers/reset/reset-slb9670.c

diff --git a/drivers/reset/Kconfig b/drivers/reset/Kconfig
index ccd59dd..3296e33 100644
--- a/drivers/reset/Kconfig
+++ b/drivers/reset/Kconfig
@@ -229,6 +229,15 @@ config RESET_SIMPLE
- Allwinner SoCs
- SiFive FU740 SoCs

+config RESET_SLB9670
+ tristate "Infineon SLB9670 TPM Reset Driver"
+ depends on TCG_TIS_SPI
+ help
+ This enables the reset driver for the Infineon SLB9670 Trusted
+ Platform Module. Only say Y here if your platform firmware is
+ incapable of taking the TPM out of reset on boot, requiring the
+ kernel to do so.
+
config RESET_SOCFPGA
bool "SoCFPGA Reset Driver" if COMPILE_TEST && (!ARM || !ARCH_INTEL_SOCFPGA)
default ARM && ARCH_INTEL_SOCFPGA
diff --git a/drivers/reset/Makefile b/drivers/reset/Makefile
index 8270da8..d9c182e 100644
--- a/drivers/reset/Makefile
+++ b/drivers/reset/Makefile
@@ -30,6 +30,7 @@ obj-$(CONFIG_RESET_RASPBERRYPI) += reset-raspberrypi.o
obj-$(CONFIG_RESET_RZG2L_USBPHY_CTRL) += reset-rzg2l-usbphy-ctrl.o
obj-$(CONFIG_RESET_SCMI) += reset-scmi.o
obj-$(CONFIG_RESET_SIMPLE) += reset-simple.o
+obj-$(CONFIG_RESET_SLB9670) += reset-slb9670.o
obj-$(CONFIG_RESET_SOCFPGA) += reset-socfpga.o
obj-$(CONFIG_RESET_SUNPLUS) += reset-sunplus.o
obj-$(CONFIG_RESET_SUNXI) += reset-sunxi.o
diff --git a/drivers/reset/reset-slb9670.c b/drivers/reset/reset-slb9670.c
new file mode 100644
index 00000000..cc09ab5
--- /dev/null
+++ b/drivers/reset/reset-slb9670.c
@@ -0,0 +1,141 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Reset driver for Infineon SLB9670 Trusted Platform Module
+ *
+ * Copyright (C) 2022 KUNBUS GmbH
+ */
+
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/mod_devicetable.h>
+#include <linux/platform_device.h>
+#include <linux/reset-controller.h>
+
+/*
+ * Time intervals used in the reset sequence:
+ *
+ * RSTIN: minimum time to hold the reset line deasserted
+ * WRST: minimum time to hold the reset line asserted
+ */
+#define SLB9670_TIME_RSTIN 60 /* msecs */
+#define SLB9670_TIME_WRST 2 /* usecs */
+
+struct reset_slb9670 {
+ struct reset_controller_dev rcdev;
+ struct gpio_desc *gpio;
+};
+
+static inline struct reset_slb9670 *
+to_reset_slb9670(struct reset_controller_dev *rcdev)
+{
+ return container_of(rcdev, struct reset_slb9670, rcdev);
+}
+
+static int reset_slb9670_assert(struct reset_controller_dev *rcdev,
+ unsigned long id)
+{
+ struct reset_slb9670 *reset_slb9670 = to_reset_slb9670(rcdev);
+
+ gpiod_set_value(reset_slb9670->gpio, 1);
+ return 0;
+}
+
+static int reset_slb9670_deassert(struct reset_controller_dev *rcdev,
+ unsigned long id)
+{
+ struct reset_slb9670 *reset_slb9670 = to_reset_slb9670(rcdev);
+
+ /*
+ * Perform the reset sequence: Deassert and assert the reset line twice
+ * and wait the respective time intervals. After a last wait interval
+ * of RSTIN the chip is ready to receive the first command.
+ */
+ gpiod_set_value(reset_slb9670->gpio, 0);
+ msleep(SLB9670_TIME_RSTIN);
+ gpiod_set_value(reset_slb9670->gpio, 1);
+ udelay(SLB9670_TIME_WRST);
+ gpiod_set_value(reset_slb9670->gpio, 0);
+ msleep(SLB9670_TIME_RSTIN);
+ gpiod_set_value(reset_slb9670->gpio, 1);
+ udelay(SLB9670_TIME_WRST);
+ gpiod_set_value(reset_slb9670->gpio, 0);
+ msleep(SLB9670_TIME_RSTIN);
+
+ return 0;
+}
+
+static int reset_slb9670_reset(struct reset_controller_dev *rcdev,
+ unsigned long id)
+{
+ int ret;
+
+ ret = reset_slb9670_assert(rcdev, id);
+ if (ret)
+ return ret;
+
+ return reset_slb9670_deassert(rcdev, id);
+}
+
+static int reset_slb9670_status(struct reset_controller_dev *rcdev,
+ unsigned long id)
+{
+ struct reset_slb9670 *reset_slb9670 = to_reset_slb9670(rcdev);
+
+ return gpiod_get_value(reset_slb9670->gpio);
+}
+
+static const struct reset_control_ops reset_slb9670_ops = {
+ .assert = reset_slb9670_assert,
+ .deassert = reset_slb9670_deassert,
+ .reset = reset_slb9670_reset,
+ .status = reset_slb9670_status,
+};
+
+static int reset_slb9670_of_xlate(struct reset_controller_dev *rcdev,
+ const struct of_phandle_args *reset_spec)
+{
+ return 0;
+}
+
+static int reset_slb9670_probe(struct platform_device *pdev)
+{
+ struct reset_slb9670 *reset_slb9670;
+ struct device *dev = &pdev->dev;
+
+ reset_slb9670 = devm_kzalloc(dev, sizeof(*reset_slb9670), GFP_KERNEL);
+ if (!reset_slb9670)
+ return -ENOMEM;
+
+ reset_slb9670->gpio = devm_gpiod_get(dev, "reset", GPIOD_ASIS);
+ if (IS_ERR(reset_slb9670->gpio))
+ return dev_err_probe(dev, PTR_ERR(reset_slb9670->gpio),
+ "cannot get reset gpio\n");
+
+ reset_slb9670->rcdev.nr_resets = 1;
+ reset_slb9670->rcdev.owner = THIS_MODULE;
+ reset_slb9670->rcdev.of_node = dev->of_node;
+ reset_slb9670->rcdev.ops = &reset_slb9670_ops;
+ reset_slb9670->rcdev.of_xlate = reset_slb9670_of_xlate;
+ reset_slb9670->rcdev.of_reset_n_cells = 0;
+
+ return devm_reset_controller_register(dev, &reset_slb9670->rcdev);
+}
+
+static const struct of_device_id reset_slb9670_dt_ids[] = {
+ { .compatible = "infineon,slb9670-reset" },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, reset_slb9670_dt_ids);
+
+static struct platform_driver reset_slb9670_driver = {
+ .probe = reset_slb9670_probe,
+ .driver = {
+ .name = "reset-slb9670",
+ .of_match_table = reset_slb9670_dt_ids,
+ },
+};
+module_platform_driver(reset_slb9670_driver);
+
+MODULE_DESCRIPTION("Infineon SLB9670 TPM Reset Driver");
+MODULE_AUTHOR("Lino Sanfilippo <l.sanfilippo@kunbus.com>");
+MODULE_LICENSE("GPL");
--
2.40.1