From: Lino Sanfilippo <[email protected]>

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 <[email protected]>
Signed-off-by: Lukas Wunner <[email protected]>
---
 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 <[email protected]>");
+MODULE_LICENSE("GPL");
-- 
2.40.1

Reply via email to