This driver reads/writes to the extended OTP area using TF-A calls.

The driver has been tested on AM625 and AM62L, but should work on other
SoCs as well. The driver needs the SIP calls K3_SIP_OTP_READ and
K3_SIP_OTP_WRITE. These are currently only implemented in the TI
downstream TF-A for AM62L. For AM625 these calls need to be enabled with
additional TF-A patches. The driver is activated in the AM62L/AM625
device trees, but due to the limited availability of the SIP calls
the driver remains silent when the TF-A doesn't have support for
manipulating fuses.

Link: https://lore.barebox.org/[email protected]
Signed-off-by: Sascha Hauer <[email protected]>
---

changes since v1:
- Use correct max_register (Ahmad)
- Print message when TF-A doesn't support reading from OTP (Ahmad)

 arch/arm/dts/k3-am625.dtsi         |   4 +
 arch/arm/dts/k3-am62l-barebox.dtsi |   4 +
 drivers/nvmem/Kconfig              |   7 +
 drivers/nvmem/Makefile             |   1 +
 drivers/nvmem/k3-fuse.c            | 231 +++++++++++++++++++++++++++++
 5 files changed, 247 insertions(+)
 create mode 100644 drivers/nvmem/k3-fuse.c

diff --git a/arch/arm/dts/k3-am625.dtsi b/arch/arm/dts/k3-am625.dtsi
index a67b9f5d9a..bb0f046fd6 100644
--- a/arch/arm/dts/k3-am625.dtsi
+++ b/arch/arm/dts/k3-am625.dtsi
@@ -7,6 +7,10 @@ chosen {
                barebox,bootsource-mmc1 = &sdhci1;
                barebox,bootsource-mmc2 = &sdhci2;
        };
+
+       otp: otp {
+               compatible = "ti,am62x-otp";
+       };
 };
 
 &wkup_conf {
diff --git a/arch/arm/dts/k3-am62l-barebox.dtsi 
b/arch/arm/dts/k3-am62l-barebox.dtsi
index 2c1cbb3871..949e2746d5 100644
--- a/arch/arm/dts/k3-am62l-barebox.dtsi
+++ b/arch/arm/dts/k3-am62l-barebox.dtsi
@@ -64,4 +64,8 @@ secure_ddr: optee@80200000 {
                        no-map;
                };
        };
+
+       otp: otp {
+               compatible = "ti,am62l-otp";
+       };
 };
diff --git a/drivers/nvmem/Kconfig b/drivers/nvmem/Kconfig
index 49f90452df..d66c4a88fe 100644
--- a/drivers/nvmem/Kconfig
+++ b/drivers/nvmem/Kconfig
@@ -136,4 +136,11 @@ config NVMEM_ATMEL_I2C
        bool
        select BITREVERSE
 
+config TI_K3_OTP
+       bool "TI K3 OTP"
+       depends on ARCH_K3
+       select ARM_SMCCC
+       help
+         This adds support for the TI K3 SMC call based OTP found on AM62L 
SoCs.
+
 endif
diff --git a/drivers/nvmem/Makefile b/drivers/nvmem/Makefile
index 9cdc669a96..cb5e6d6330 100644
--- a/drivers/nvmem/Makefile
+++ b/drivers/nvmem/Makefile
@@ -36,3 +36,4 @@ obj-$(CONFIG_STARFIVE_OTP)    += starfive-otp.o
 obj-$(CONFIG_IMX_OCOTP_ELE)    += imx-ocotp-ele.o
 obj-$(CONFIG_NVMEM_ATMEL_I2C)  += atmel-i2c.o
 obj-$(CONFIG_NVMEM_ATMEL_SHA204A) += atmel-sha204a.o
+obj-$(CONFIG_TI_K3_OTP)                += k3-fuse.o
diff --git a/drivers/nvmem/k3-fuse.c b/drivers/nvmem/k3-fuse.c
new file mode 100644
index 0000000000..214239f2cd
--- /dev/null
+++ b/drivers/nvmem/k3-fuse.c
@@ -0,0 +1,231 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <driver.h>
+#include <malloc.h>
+#include <xfuncs.h>
+#include <errno.h>
+#include <init.h>
+#include <io.h>
+#include <of.h>
+#include <linux/regmap.h>
+#include <linux/nvmem-provider.h>
+#include <linux/arm-smccc.h>
+#include <linux/bitmap.h>
+
+/*
+ * These SIP calls are currently only supported in the TI downstream
+ * TF-A
+ */
+#define K3_SIP_OTP_READ                0xC2000002
+#define K3_SIP_OTP_WRITE       0xC2000001
+
+struct ti_k3_otp_driver_data {
+       unsigned int skip_init;
+       unsigned int bits_per_row;
+       unsigned int nrows;
+};
+
+struct ti_k3_otp {
+       struct device *dev;
+       uint32_t *map;
+       const struct ti_k3_otp_driver_data *data;
+};
+
+static int ti_k3_otp_read_raw(unsigned int word, unsigned int *val)
+{
+       struct arm_smccc_res res;
+       unsigned int bank = 0;
+
+       /* TF-A ignores bank argument */
+       arm_smccc_smc(K3_SIP_OTP_READ, bank, word,
+                     0, 0, 0, 0, 0, &res);
+
+       if ((long)res.a0 == -1) /* SMC_UNK */
+               return -EOPNOTSUPP;
+
+       if (res.a0 != 0)
+               return -EIO;
+
+       *val = res.a1;
+
+       return 0;
+}
+
+/*
+ * Fuses are organized in rows where each row has a SoC specific number
+ * of fuses (25 on most K3 devices). When writing a fuse we always write
+ * to a single row of fuses. This means the upper 7 bits of each 32 bit word
+ * are unused. When reading from fuses these gaps are skipped, meaning the 
first
+ * word we read has 25 bits from row0 in the lower bits and 7 bits from row1
+ * in the upper bits.
+ * Additionally on some SoCs the very first n fuses are reserved. These bits
+ * cannot be written and are skipped while reading.
+ * These effects are reversed here which means that we actually provide a
+ * consistent register map between writing and reading.
+ *
+ * Rather than adjusting the write map we adjust the read map, because this
+ * way we provide one fuse row in each 32bit word and a fuse row is the 
granularity
+ * for write protection.
+ *
+ * The TI-SCI firmware updates the registers we read from only after a reset,
+ * so it doesn't hurt us when we read all registers upfront, you can't read
+ * back the values you've just written anyway.
+ */
+static int ti_k3_otp_read_map(struct ti_k3_otp *priv)
+{
+       uint32_t *map_raw;
+       int i, ret;
+       unsigned int bits_per_row = priv->data->bits_per_row;
+       unsigned int mask = (1 << bits_per_row) - 1;
+       unsigned long *bitmap = NULL;
+       int nbits = 32 * 32;
+       int nrows = priv->data->nrows;
+
+       map_raw = xzalloc(sizeof(uint32_t) * nrows);
+
+       for (i = 0; i < 32; i++) {
+               unsigned int val;
+
+               ret = ti_k3_otp_read_raw(i, &val);
+               if (ret)
+                       goto out;
+
+               map_raw[i] = val;
+       }
+
+       bitmap = bitmap_xzalloc(nbits);
+       bitmap_from_arr32(bitmap, map_raw, nbits);
+
+       if (priv->data->skip_init)
+               bitmap_shift_left(bitmap, bitmap, priv->data->skip_init, nbits);
+
+       for (i = 0; i < priv->data->nrows; i++) {
+               priv->map[i] = bitmap[0] & mask;
+               bitmap_shift_right(bitmap, bitmap, bits_per_row, nbits);
+       }
+
+       ret = 0;
+
+out:
+       free(bitmap);
+       free(map_raw);
+       return ret;
+}
+
+/*
+ * offset and size are assumed aligned to the size of the fuses (32-bit).
+ */
+static int ti_k3_otp_read(void *ctx, unsigned int offset, unsigned int *val)
+{
+       struct ti_k3_otp *priv = ctx;
+       unsigned int word = offset >> 2;
+
+       *val = priv->map[word];
+
+       return 0;
+}
+
+static int ti_k3_otp_write(void *ctx, unsigned int offset, unsigned int val)
+{
+       struct ti_k3_otp *priv = ctx;
+       struct arm_smccc_res res;
+       unsigned int bank = 0;
+       unsigned int word = offset >> 2;
+       unsigned int mask = val;
+
+       if (word == 0 && priv->data->skip_init) {
+               unsigned int skip_mask = GENMASK(priv->data->skip_init, 0);
+               if (val & skip_mask) {
+                       dev_err(priv->dev, "Lower %d bits of word 0 cannot be 
written\n",
+                               priv->data->skip_init);
+                       return -EINVAL;
+               }
+       }
+
+       if (val & GENMASK(31, priv->data->bits_per_row)) {
+               dev_err(priv->dev, "Each row only has %d bits",
+                       priv->data->bits_per_row);
+               return -EINVAL;
+       }
+
+       arm_smccc_smc(K3_SIP_OTP_WRITE, bank, word,
+                     val, mask, 0, 0, 0, &res);
+
+       if (res.a0 != 0) {
+               dev_err(priv->dev, "Writing fuse 0x%08x failed with: %lu\n",
+                       offset, res.a0);
+               return -EIO;
+       }
+
+       return 0;
+}
+
+static struct regmap_bus ti_k3_otp_regmap_bus = {
+       .reg_read = ti_k3_otp_read,
+       .reg_write = ti_k3_otp_write,
+};
+
+static int ti_k3_otp_probe(struct device *dev)
+{
+       struct ti_k3_otp *priv;
+       struct regmap_config config = {};
+       struct regmap *map;
+       int ret;
+
+       priv = xzalloc(sizeof(*priv));
+       priv->data = device_get_match_data(dev);
+       priv->dev = dev;
+       priv->map = xzalloc(sizeof(uint32_t) * priv->data->nrows);
+
+       config.name = "k3-otp";
+       config.reg_bits = 32;
+       config.val_bits = 32;
+       config.reg_stride = 4;
+       config.max_register = sizeof(uint32_t) * (priv->data->nrows - 1);
+
+       ret = ti_k3_otp_read_map(priv);
+       if (ret) {
+               /*
+                * Reading fuses is only supported by TI downstream TF-A and
+                * only on AM62L. Do not bother the user with error messages
+                * when it's not supported.
+                */
+               if (ret == -EOPNOTSUPP) {
+                       dev_info(dev, "TF-A doesn't support reading OTP");
+                       return -ENODEV;
+               }
+               return ret;
+       }
+
+       map = regmap_init(dev, &ti_k3_otp_regmap_bus, priv, &config);
+       if (IS_ERR(map))
+               return PTR_ERR(map);
+
+       return PTR_ERR_OR_ZERO(nvmem_regmap_register(map, "ti-k3-otp"));
+}
+
+struct ti_k3_otp_driver_data am62x_data = {
+       .skip_init = 2,
+       .bits_per_row = 25,
+       .nrows = 42,
+};
+
+struct ti_k3_otp_driver_data am62l_data = {
+       .skip_init = 0,
+       .bits_per_row = 25,
+       .nrows = 42,
+};
+
+static struct of_device_id ti_k3_otp_dt_ids[] = {
+       { .compatible = "ti,am62x-otp", .data = &am62x_data },
+       { .compatible = "ti,am62l-otp", .data = &am62l_data },
+       { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, ti_k3_otp_dt_ids);
+
+static struct driver ti_k3_otp_driver = {
+       .name   = "ti-k3-otp",
+       .probe  = ti_k3_otp_probe,
+       .of_compatible = ti_k3_otp_dt_ids,
+};
+device_platform_driver(ti_k3_otp_driver);
-- 
2.47.3


Reply via email to