summaryrefslogtreecommitdiffstats
path: root/drivers/nvmem
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/nvmem')
-rw-r--r--drivers/nvmem/Kconfig58
-rw-r--r--drivers/nvmem/Makefile13
-rw-r--r--drivers/nvmem/bsec.c248
-rw-r--r--drivers/nvmem/core.c184
-rw-r--r--drivers/nvmem/eeprom_93xx46.c38
-rw-r--r--drivers/nvmem/imx-ocotp-ele.c241
-rw-r--r--drivers/nvmem/kvx-otp-nv.c95
-rw-r--r--drivers/nvmem/ocotp.c532
-rw-r--r--drivers/nvmem/partition.c36
-rw-r--r--drivers/nvmem/rave-sp-eeprom.c46
-rw-r--r--drivers/nvmem/regmap.c92
-rw-r--r--drivers/nvmem/rmem.c64
-rw-r--r--drivers/nvmem/rockchip-otp.c448
-rw-r--r--drivers/nvmem/snvs_lpgpr.c41
-rw-r--r--drivers/nvmem/starfive-otp.c202
-rw-r--r--drivers/nvmem/stm32-bsec-optee-ta.c298
-rw-r--r--drivers/nvmem/stm32-bsec-optee-ta.h85
17 files changed, 2380 insertions, 341 deletions
diff --git a/drivers/nvmem/Kconfig b/drivers/nvmem/Kconfig
index 968342b281..255198b2ad 100644
--- a/drivers/nvmem/Kconfig
+++ b/drivers/nvmem/Kconfig
@@ -1,3 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0-only
menuconfig NVMEM
bool "NVMEM Support"
help
@@ -9,6 +10,12 @@ menuconfig NVMEM
if NVMEM
+config NVMEM_RMEM
+ bool "Reserved Memory Based Driver Support"
+ help
+ This driver maps reserved memory into an nvmem device. It might be
+ useful to expose information left by firmware in memory.
+
config NVMEM_SNVS_LPGPR
tristate "Freescale SNVS LPGPR support"
select MFD_SYSCON
@@ -18,7 +25,7 @@ config NVMEM_SNVS_LPGPR
config IMX_OCOTP
tristate "i.MX6 On Chip OTP controller"
- depends on ARCH_IMX6 || ARCH_VF610 || ARCH_IMX8MQ
+ depends on ARCH_IMX6 || ARCH_VF610 || ARCH_IMX8M || ARCH_IMX7
depends on OFDEVICE
help
This adds support for the i.MX6 On-Chip OTP controller. Currently the
@@ -37,6 +44,13 @@ config IMX_OCOTP_WRITE
mw -l -d /dev/imx-ocotp 0x8C 0x00001234
mw -l -d /dev/imx-ocotp 0x88 0x56789ABC
+config IMX_OCOTP_ELE
+ tristate "i.MX9 On Chip OTP controller"
+ depends on ARCH_IMX93
+ depends on OFDEVICE
+ help
+ This adds support for the i.MX9 On-Chip OTP controller.
+
config RAVE_SP_EEPROM
tristate "Rave SP EEPROM Support"
depends on RAVE_SP_CORE
@@ -51,12 +65,50 @@ config EEPROM_93XX46
supports both read and write commands and also the command to
erase the whole EEPROM.
+config NVMEM_ROCKCHIP_OTP
+ tristate "Rockchip OTP controller support"
+ depends on ARCH_ROCKCHIP || COMPILE_TEST
+ help
+ This is a simple driver to dump specified values of Rockchip SoC
+ from otp, such as cpu-leakage, id etc.
+
config STM32_BSEC
tristate "STM32 Boot and security and OTP control"
depends on ARCH_STM32MP
depends on OFDEVICE
help
- This adds support for the STM32 OTP controller. Reads and writes
- to will go to the shadow RAM, not the OTP fuses themselvers.
+ This adds support for the STM32 OTP controller.
+
+config STM32_BSEC_WRITE
+ bool
+ prompt "Enable write support of STM32 CPUs OTP fuses"
+ depends on STM32_BSEC
+ help
+ This adds write support to STM32 On-Chip OTP registers. Example of set
+ MAC to 12:34:56:78:9A:BC:
+ bsec0.permanent_write_enable=1
+ mw -l -d /dev/stm32-bsec 0x000000e4+4 0x78563412
+ mw -l -d /dev/stm32-bsec 0x000000e8+4 0x0000bc9a
+
+config STM32_BSEC_OPTEE_TA
+ def_bool STM32_BSEC && OPTEE
+ help
+ Say y here to enable the accesses to STM32MP SoC OTPs by the OP-TEE
+ trusted application STM32MP BSEC.
+
+config KVX_OTP_NV
+ tristate "kalray KVX OTP Non volatile regs Support"
+ depends on KVX
+ help
+ This is a simple driver to dump specified values of KVX OTP non
+ volatile regs.
+
+config STARFIVE_OTP
+ tristate "Starfive OTP Supprot"
+ depends on SOC_STARFIVE
+ depends on OFDEVICE
+ help
+ This adds support for the StarFive OTP controller. Only reading
+ is currently supported.
endif
diff --git a/drivers/nvmem/Makefile b/drivers/nvmem/Makefile
index 7101c5aca4..31db05e5a7 100644
--- a/drivers/nvmem/Makefile
+++ b/drivers/nvmem/Makefile
@@ -1,9 +1,12 @@
+# SPDX-License-Identifier: GPL-2.0-only
#
# Makefile for nvmem drivers.
#
obj-$(CONFIG_NVMEM) += nvmem_core.o
-nvmem_core-y := core.o
+nvmem_core-y := core.o regmap.o partition.o
+
+obj-$(CONFIG_NVMEM_RMEM) += rmem.o
# Devices
obj-$(CONFIG_NVMEM_SNVS_LPGPR) += nvmem_snvs_lpgpr.o
@@ -20,3 +23,11 @@ nvmem_eeprom_93xx46-y := eeprom_93xx46.o
obj-$(CONFIG_STM32_BSEC) += nvmem_bsec.o
nvmem_bsec-y := bsec.o
+nvmem_bsec-$(CONFIG_STM32_BSEC_OPTEE_TA) += stm32-bsec-optee-ta.o
+
+obj-$(CONFIG_KVX_OTP_NV) += nvmem-kvx-otp-nv.o
+nvmem-kvx-otp-nv-y := kvx-otp-nv.o
+
+obj-$(CONFIG_NVMEM_ROCKCHIP_OTP)+= rockchip-otp.o
+obj-$(CONFIG_STARFIVE_OTP) += starfive-otp.o
+obj-$(CONFIG_IMX_OCOTP_ELE) += imx-ocotp-ele.o
diff --git a/drivers/nvmem/bsec.c b/drivers/nvmem/bsec.c
index 836e62ecbc..22e30c6c2e 100644
--- a/drivers/nvmem/bsec.c
+++ b/drivers/nvmem/bsec.c
@@ -1,4 +1,4 @@
-// SPDX-License-Identifier: GPL-2.0
+// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2018, STMicroelectronics - All Rights Reserved
* Copyright (c) 2019 Ahmad Fatoum, Pengutronix
@@ -13,30 +13,33 @@
#include <net.h>
#include <io.h>
#include <of.h>
-#include <regmap.h>
-#include <mach/bsec.h>
+#include <linux/regmap.h>
+#include <mach/stm32mp/bsec.h>
#include <machine_id.h>
#include <linux/nvmem-provider.h>
+#include "stm32-bsec-optee-ta.h"
+
#define BSEC_OTP_SERIAL 13
struct bsec_priv {
- struct regmap *map;
- u32 svc_id;
- struct device_d dev;
+ struct device dev;
struct regmap_config map_config;
- struct nvmem_config config;
+ int permanent_write_enable;
+ u8 lower;
+ struct tee_context *ctx;
};
struct stm32_bsec_data {
- unsigned long svc_id;
- int num_regs;
+ size_t size;
+ u8 lower;
+ bool ta;
};
-static int bsec_smc(struct bsec_priv *priv, enum bsec_op op, u32 field,
+static int bsec_smc(enum bsec_op op, u32 field,
unsigned data2, unsigned *val)
{
- enum bsec_smc ret = stm32mp_smc(priv->svc_id, op, field / 4, data2, val);
+ enum bsec_smc ret = stm32mp_smc(STM32_SMC_BSEC, op, field / 4, data2, val);
switch(ret)
{
case BSEC_SMC_OK:
@@ -59,103 +62,64 @@ static int bsec_smc(struct bsec_priv *priv, enum bsec_op op, u32 field,
static int stm32_bsec_read_shadow(void *ctx, unsigned reg, unsigned *val)
{
- return bsec_smc(ctx, BSEC_SMC_READ_SHADOW, reg, 0, val);
+ return bsec_smc(BSEC_SMC_READ_SHADOW, reg, 0, val);
}
-static int stm32_bsec_reg_write_shadow(void *ctx, unsigned reg, unsigned val)
+static int stm32_bsec_reg_write(void *ctx, unsigned reg, unsigned val)
{
- return bsec_smc(ctx, BSEC_SMC_WRITE_SHADOW, reg, val, NULL);
+ struct bsec_priv *priv = ctx;
+
+ if (priv->permanent_write_enable)
+ return bsec_smc(BSEC_SMC_PROG_OTP, reg, val, NULL);
+ else
+ return bsec_smc(BSEC_SMC_WRITE_SHADOW, reg, val, NULL);
}
static struct regmap_bus stm32_bsec_regmap_bus = {
- .reg_write = stm32_bsec_reg_write_shadow,
+ .reg_write = stm32_bsec_reg_write,
.reg_read = stm32_bsec_read_shadow,
};
-static int stm32_bsec_write(struct device_d *dev, int offset,
- const void *val, int bytes)
-{
- struct bsec_priv *priv = dev->parent->priv;
-
- /* Allow only writing complete 32-bits aligned words */
- if ((bytes % 4) || (offset % 4))
- return -EINVAL;
-
- return regmap_bulk_write(priv->map, offset, val, bytes);
-}
-
-static int stm32_bsec_read(struct device_d *dev, int offset,
- void *buf, int bytes)
-{
- struct bsec_priv *priv = dev->parent->priv;
- u32 roffset, rbytes, val;
- u8 *buf8 = buf, *val8 = (u8 *)&val;
- int i, j = 0, ret, skip_bytes, size;
-
- /* Round unaligned access to 32-bits */
- roffset = rounddown(offset, 4);
- skip_bytes = offset & 0x3;
- rbytes = roundup(bytes + skip_bytes, 4);
-
- if (roffset + rbytes > priv->config.size)
- return -EINVAL;
-
- for (i = roffset; i < roffset + rbytes; i += 4) {
- ret = regmap_bulk_read(priv->map, i, &val, 4);
- if (ret) {
- dev_err(dev, "Can't read data%d (%d)\n", i, ret);
- return ret;
- }
-
- /* skip first bytes in case of unaligned read */
- if (skip_bytes)
- size = min(bytes, 4 - skip_bytes);
- else
- size = min(bytes, 4);
-
- memcpy(&buf8[j], &val8[skip_bytes], size);
- bytes -= size;
- j += size;
- skip_bytes = 0;
- }
-
- return 0;
-}
-
-static const struct nvmem_bus stm32_bsec_nvmem_bus = {
- .write = stm32_bsec_write,
- .read = stm32_bsec_read,
-};
-
static void stm32_bsec_set_unique_machine_id(struct regmap *map)
{
u32 unique_id[3];
int ret;
ret = regmap_bulk_read(map, BSEC_OTP_SERIAL * 4,
- unique_id, sizeof(unique_id));
+ unique_id, sizeof(unique_id) / 4);
if (ret)
return;
machine_id_set_hashable(unique_id, sizeof(unique_id));
}
-static int stm32_bsec_read_mac(struct regmap *map, int offset, u8 *mac)
+static int stm32_bsec_read_mac(struct bsec_priv *priv, int offset, u8 *mac)
{
- u8 res[8];
+ u32 val[2];
int ret;
- ret = regmap_bulk_read(map, offset * 4, res, 8);
+ if (priv->ctx) {
+ ret = stm32_bsec_optee_ta_read(priv->ctx, offset * 4, val, sizeof(val));
+ } else {
+ /* Some TF-A does not copy all of OTP into shadow registers, so make
+ * sure we read the _real_ OTP bits here.
+ */
+ ret = bsec_smc(BSEC_SMC_READ_OTP, offset * 4, 0, &val[0]);
+ if (!ret)
+ ret = bsec_smc(BSEC_SMC_READ_OTP, offset * 4 + 4, 0, &val[1]);
+ }
+
if (ret)
return ret;
- memcpy(mac, res, ETH_ALEN);
+ memcpy(mac, val, ETH_ALEN);
return 0;
}
-static void stm32_bsec_init_dt(struct bsec_priv *priv)
+static void stm32_bsec_init_dt(struct bsec_priv *priv, struct device *dev,
+ struct regmap *map)
{
- struct device_node *node = priv->dev.parent->device_node;
+ struct device_node *node = dev->of_node;
struct device_node *rnode;
u32 phandle, offset;
char mac[ETH_ALEN];
@@ -164,9 +128,6 @@ static void stm32_bsec_init_dt(struct bsec_priv *priv)
int len;
int ret;
- if (!node)
- return;
-
prop = of_get_property(node, "barebox,provide-mac-address", &len);
if (!prop)
return;
@@ -179,18 +140,66 @@ static void stm32_bsec_init_dt(struct bsec_priv *priv)
rnode = of_find_node_by_phandle(phandle);
offset = be32_to_cpup(prop++);
- ret = stm32_bsec_read_mac(priv->map, offset, mac);
+ ret = stm32_bsec_read_mac(priv, offset, mac);
if (ret) {
- dev_warn(&priv->dev, "error setting MAC address: %s\n",
- strerror(-ret));
+ dev_warn(dev, "error setting MAC address: %s\n", strerror(-ret));
return;
}
of_eth_register_ethaddr(rnode, mac);
}
-static int stm32_bsec_probe(struct device_d *dev)
+static int stm32_bsec_pta_read(void *context, unsigned int offset, unsigned int *val)
+{
+ struct bsec_priv *priv = context;
+
+ return stm32_bsec_optee_ta_read(priv->ctx, offset, val, sizeof(val));
+}
+
+static int stm32_bsec_pta_write(void *context, unsigned int offset, unsigned int val)
{
+ struct bsec_priv *priv = context;
+
+ if (!priv->permanent_write_enable)
+ return -EACCES;
+
+ return stm32_bsec_optee_ta_write(priv->ctx, priv->lower, offset, &val, sizeof(val));
+}
+
+static struct regmap_bus stm32_bsec_optee_regmap_bus = {
+ .reg_write = stm32_bsec_pta_write,
+ .reg_read = stm32_bsec_pta_read,
+};
+
+static bool stm32_bsec_smc_check(void)
+{
+ u32 val;
+ int ret;
+
+ /* check that the OP-TEE support the BSEC SMC (legacy mode) */
+ ret = bsec_smc(BSEC_SMC_READ_SHADOW, 0, 0, &val);
+
+ return !ret;
+}
+
+static bool optee_presence_check(void)
+{
+ struct device_node *np;
+ bool tee_detected = false;
+
+ /* check that the OP-TEE node is present and available. */
+ np = of_find_compatible_node(NULL, NULL, "linaro,optee-tz");
+ if (np && of_device_is_available(np))
+ tee_detected = true;
+ of_node_put(np);
+
+ return tee_detected;
+}
+
+static int stm32_bsec_probe(struct device *dev)
+{
+ const struct regmap_bus *regmap_bus;
+ struct regmap *map;
struct bsec_priv *priv;
int ret = 0;
const struct stm32_bsec_data *data;
@@ -202,8 +211,6 @@ static int stm32_bsec_probe(struct device_d *dev)
priv = xzalloc(sizeof(*priv));
- priv->svc_id = data->svc_id;
-
dev_set_name(&priv->dev, "bsec");
priv->dev.parent = dev;
register_device(&priv->dev);
@@ -211,43 +218,78 @@ static int stm32_bsec_probe(struct device_d *dev)
priv->map_config.reg_bits = 32;
priv->map_config.val_bits = 32;
priv->map_config.reg_stride = 4;
- priv->map_config.max_register = data->num_regs;
+ priv->map_config.max_register = data->size - priv->map_config.reg_stride;
+
+ priv->lower = data->lower;
+
+ if (data->ta || optee_presence_check()) {
+ ret = stm32_bsec_optee_ta_open(&priv->ctx);
+ if (ret) {
+ /* wait for OP-TEE client driver to be up and ready */
+ if (ret == -EPROBE_DEFER)
+ return -EPROBE_DEFER;
+ /* BSEC PTA is required or SMC not supported */
+ if (data->ta || !stm32_bsec_smc_check())
+ return ret;
+ }
+ }
+
+ if (priv->ctx)
+ regmap_bus = &stm32_bsec_optee_regmap_bus;
+ else
+ regmap_bus = &stm32_bsec_regmap_bus;
- priv->map = regmap_init(dev, &stm32_bsec_regmap_bus, priv, &priv->map_config);
- if (IS_ERR(priv->map))
- return PTR_ERR(priv->map);
+ map = regmap_init(dev, regmap_bus, priv, &priv->map_config);
+ if (IS_ERR(map))
+ return PTR_ERR(map);
- priv->config.name = "stm32-bsec";
- priv->config.dev = dev;
- priv->config.stride = 1;
- priv->config.word_size = 1;
- priv->config.size = data->num_regs;
- priv->config.bus = &stm32_bsec_nvmem_bus;
- dev->priv = priv;
+ if (IS_ENABLED(CONFIG_STM32_BSEC_WRITE)) {
+ dev_add_param_bool(&priv->dev, "permanent_write_enable",
+ NULL, NULL, &priv->permanent_write_enable, NULL);
+ }
- nvmem = nvmem_register(&priv->config);
+ nvmem = nvmem_regmap_register(map, "stm32-bsec");
if (IS_ERR(nvmem))
return PTR_ERR(nvmem);
if (IS_ENABLED(CONFIG_MACHINE_ID))
- stm32_bsec_set_unique_machine_id(priv->map);
+ stm32_bsec_set_unique_machine_id(map);
+
+ stm32_bsec_init_dt(priv, dev, map);
- stm32_bsec_init_dt(priv);
+ dev_dbg(dev, "using %s API\n", priv->ctx ? "OP-TEE" : "SiP");
return 0;
}
+/*
+ * STM32MP15/13 BSEC OTP regions: 4096 OTP bits (with 3072 effective bits)
+ * => 96 x 32-bits data words
+ * - Lower: 1K bits, 2:1 redundancy, incremental bit programming
+ * => 32 (x 32-bits) lower shadow registers = words 0 to 31
+ * - Upper: 2K bits, ECC protection, word programming only
+ * => 64 (x 32-bits) = words 32 to 95
+ */
static struct stm32_bsec_data stm32mp15_bsec_data = {
- .num_regs = 95 * 4,
- .svc_id = STM32_SMC_BSEC,
+ .size = 384,
+ .lower = 32,
+ .ta = false,
+};
+
+static const struct stm32_bsec_data stm32mp13_bsec_data = {
+ .size = 384,
+ .lower = 32,
+ .ta = true,
};
static __maybe_unused struct of_device_id stm32_bsec_dt_ids[] = {
{ .compatible = "st,stm32mp15-bsec", .data = &stm32mp15_bsec_data },
+ { .compatible = "st,stm32mp13-bsec", .data = &stm32mp13_bsec_data },
{ /* sentinel */ }
};
+MODULE_DEVICE_TABLE(of, stm32_bsec_dt_ids);
-static struct driver_d stm32_bsec_driver = {
+static struct driver stm32_bsec_driver = {
.name = "stm32_bsec",
.probe = stm32_bsec_probe,
.of_compatible = DRV_OF_COMPAT(stm32_bsec_dt_ids),
diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c
index 06e1414769..bf393fc180 100644
--- a/drivers/nvmem/core.c
+++ b/drivers/nvmem/core.c
@@ -1,17 +1,9 @@
+// SPDX-License-Identifier: GPL-2.0-only
/*
* nvmem framework core.
*
* Copyright (C) 2015 Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
* Copyright (C) 2013 Maxime Ripard <maxime.ripard@free-electrons.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 and
- * only version 2 as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
*/
#include <common.h>
@@ -23,8 +15,7 @@
struct nvmem_device {
const char *name;
- struct device_d dev;
- const struct nvmem_bus *bus;
+ struct device dev;
struct list_head node;
int stride;
int word_size;
@@ -33,10 +24,17 @@ struct nvmem_device {
size_t size;
bool read_only;
struct cdev cdev;
+ void *priv;
+ nvmem_cell_post_process_t cell_post_process;
+ int (*reg_write)(void *ctx, unsigned int reg,
+ const void *val, size_t val_size);
+ int (*reg_read)(void *ctx, unsigned int reg,
+ void *val, size_t val_size);
};
struct nvmem_cell {
const char *name;
+ const char *id;
int offset;
int bytes;
int bit_offset;
@@ -48,11 +46,14 @@ struct nvmem_cell {
static LIST_HEAD(nvmem_cells);
static LIST_HEAD(nvmem_devs);
-int nvmem_device_read(struct nvmem_device *nvmem, unsigned int offset,
- size_t bytes, void *buf);
-int nvmem_device_write(struct nvmem_device *nvmem, unsigned int offset,
- size_t bytes, const void *buf);
+void nvmem_devices_print(void)
+{
+ struct nvmem_device *dev;
+ list_for_each_entry(dev, &nvmem_devs, node) {
+ printf("%s\n", dev_name(&dev->dev));
+ }
+}
static ssize_t nvmem_cdev_read(struct cdev *cdev, void *buf, size_t count,
loff_t offset, unsigned long flags)
@@ -99,12 +100,12 @@ static struct cdev_operations nvmem_chrdev_ops = {
static int nvmem_register_cdev(struct nvmem_device *nvmem, const char *name)
{
- struct device_d *dev = &nvmem->dev;
+ struct device *dev = &nvmem->dev;
struct cdev *cdev = &nvmem->cdev;
const char *alias;
int ret;
- alias = of_alias_get(dev->device_node);
+ alias = of_alias_get(dev->of_node);
cdev->name = xstrdup(alias ?: name);
cdev->ops = &nvmem_chrdev_ops;
@@ -115,7 +116,7 @@ static int nvmem_register_cdev(struct nvmem_device *nvmem, const char *name)
if (ret)
return ret;
- of_parse_partitions(cdev, dev->device_node);
+ of_parse_partitions(cdev, dev->of_node);
of_partitions_register_fixup(cdev);
return 0;
@@ -129,7 +130,7 @@ static struct nvmem_device *of_nvmem_find(struct device_node *nvmem_np)
return NULL;
list_for_each_entry(dev, &nvmem_devs, node)
- if (dev->dev.device_node->name && !strcmp(dev->dev.device_node->name, nvmem_np->name))
+ if (dev->dev.of_node == nvmem_np)
return dev;
return NULL;
@@ -140,7 +141,7 @@ static struct nvmem_cell *nvmem_find_cell(const char *cell_id)
struct nvmem_cell *p;
list_for_each_entry(p, &nvmem_cells, node)
- if (p && !strcmp(p->name, cell_id))
+ if (!strcmp(p->name, cell_id))
return p;
return NULL;
@@ -149,6 +150,7 @@ static struct nvmem_cell *nvmem_find_cell(const char *cell_id)
static void nvmem_cell_drop(struct nvmem_cell *cell)
{
list_del(&cell->node);
+ kfree(cell->id);
kfree(cell);
}
@@ -209,17 +211,20 @@ struct nvmem_device *nvmem_register(const struct nvmem_config *config)
nvmem->word_size = config->word_size;
nvmem->size = config->size;
nvmem->dev.parent = config->dev;
- nvmem->bus = config->bus;
- np = config->dev->device_node;
- nvmem->dev.device_node = np;
+ nvmem->reg_read = config->reg_read;
+ nvmem->reg_write = config->reg_write;
+ np = config->cdev ? cdev_of_node(config->cdev) : config->dev->of_node;
+ nvmem->dev.of_node = np;
+ nvmem->priv = config->priv;
+ nvmem->cell_post_process = config->cell_post_process;
- nvmem->read_only = of_property_read_bool(np, "read-only") |
- config->read_only;
+ if (config->read_only || !config->reg_write || of_property_read_bool(np, "read-only"))
+ nvmem->read_only = true;
dev_set_name(&nvmem->dev, config->name);
nvmem->dev.id = DEVICE_ID_DYNAMIC;
- dev_dbg(&nvmem->dev, "Registering nvmem device %s\n", config->name);
+ dev_dbg(nvmem->dev.parent, "Registering nvmem device %s\n", config->name);
rval = register_device(&nvmem->dev);
if (rval) {
@@ -227,10 +232,12 @@ struct nvmem_device *nvmem_register(const struct nvmem_config *config)
return ERR_PTR(rval);
}
- rval = nvmem_register_cdev(nvmem, config->name);
- if (rval) {
- kfree(nvmem);
- return ERR_PTR(rval);
+ if (!config->cdev) {
+ rval = nvmem_register_cdev(nvmem, config->name);
+ if (rval) {
+ kfree(nvmem);
+ return ERR_PTR(rval);
+ }
}
list_add_tail(&nvmem->node, &nvmem_devs);
@@ -239,13 +246,26 @@ struct nvmem_device *nvmem_register(const struct nvmem_config *config)
}
EXPORT_SYMBOL_GPL(nvmem_register);
+static int of_nvmem_device_ensure_probed(struct device_node *np)
+{
+ if (of_device_is_compatible(np, "nvmem-cells"))
+ return of_partition_ensure_probed(np);
+
+ return of_device_ensure_probed(np);
+}
+
static struct nvmem_device *__nvmem_device_get(struct device_node *np,
struct nvmem_cell **cellp,
const char *cell_id)
{
struct nvmem_device *nvmem = NULL;
+ int ret;
if (np) {
+ ret = of_nvmem_device_ensure_probed(np);
+ if (ret)
+ return ERR_PTR(ret);
+
nvmem = of_nvmem_find(np);
if (!nvmem)
return ERR_PTR(-EPROBE_DEFER);
@@ -290,13 +310,14 @@ struct nvmem_device *of_nvmem_device_get(struct device_node *np, const char *id)
{
struct device_node *nvmem_np;
- int index;
+ int index = 0;
- index = of_property_match_string(np, "nvmem-names", id);
+ if (id)
+ index = of_property_match_string(np, "nvmem-names", id);
nvmem_np = of_parse_phandle(np, "nvmem", index);
if (!nvmem_np)
- return ERR_PTR(-EINVAL);
+ return ERR_PTR(-ENOENT);
return __nvmem_device_get(nvmem_np, NULL, NULL);
}
@@ -312,12 +333,13 @@ EXPORT_SYMBOL_GPL(of_nvmem_device_get);
* Return: ERR_PTR() on error or a valid pointer to a struct nvmem_device
* on success.
*/
-struct nvmem_device *nvmem_device_get(struct device_d *dev, const char *dev_name)
+struct nvmem_device *nvmem_device_get(struct device *dev,
+ const char *dev_name)
{
- if (dev->device_node) { /* try dt first */
+ if (dev->of_node) { /* try dt first */
struct nvmem_device *nvmem;
- nvmem = of_nvmem_device_get(dev->device_node, dev_name);
+ nvmem = of_nvmem_device_get(dev->of_node, dev_name);
if (!IS_ERR(nvmem) || PTR_ERR(nvmem) == -EPROBE_DEFER)
return nvmem;
@@ -387,8 +409,7 @@ struct nvmem_cell *of_nvmem_cell_get(struct device_node *np,
addr = of_get_property(cell_np, "reg", &len);
if (!addr || (len < 2 * sizeof(u32))) {
- dev_err(&nvmem->dev, "nvmem: invalid reg on %s\n",
- cell_np->full_name);
+ dev_err(&nvmem->dev, "nvmem: invalid reg on %pOF\n", cell_np);
rval = -EINVAL;
goto err_mem;
}
@@ -403,6 +424,7 @@ struct nvmem_cell *of_nvmem_cell_get(struct device_node *np,
cell->offset = be32_to_cpup(addr++);
cell->bytes = be32_to_cpup(addr);
cell->name = cell_np->name;
+ cell->id = kstrdup_const(name, GFP_KERNEL);
addr = of_get_property(cell_np, "bits", &len);
if (addr && len == (2 * sizeof(u32))) {
@@ -450,12 +472,12 @@ EXPORT_SYMBOL_GPL(of_nvmem_cell_get);
* to a struct nvmem_cell. The nvmem_cell will be freed by the
* nvmem_cell_put().
*/
-struct nvmem_cell *nvmem_cell_get(struct device_d *dev, const char *cell_id)
+struct nvmem_cell *nvmem_cell_get(struct device *dev, const char *cell_id)
{
struct nvmem_cell *cell;
- if (dev->device_node) { /* try dt first */
- cell = of_nvmem_cell_get(dev->device_node, cell_id);
+ if (dev->of_node) { /* try dt first */
+ cell = of_nvmem_cell_get(dev->of_node, cell_id);
if (!IS_ERR(cell) || PTR_ERR(cell) == -EPROBE_DEFER)
return cell;
}
@@ -512,14 +534,21 @@ static int __nvmem_cell_read(struct nvmem_device *nvmem,
{
int rc;
- rc = nvmem->bus->read(&nvmem->dev, cell->offset, buf, cell->bytes);
- if (IS_ERR_VALUE(rc))
+ rc = nvmem->reg_read(nvmem->priv, cell->offset, buf, cell->bytes);
+ if (rc < 0)
return rc;
/* shift bits in-place */
if (cell->bit_offset || cell->nbits)
nvmem_shift_read_buffer_in_place(cell, buf);
+ if (nvmem->cell_post_process) {
+ rc = nvmem->cell_post_process(nvmem->priv, cell->id,
+ cell->offset, buf, cell->bytes);
+ if (rc)
+ return rc;
+ }
+
*len = cell->bytes;
return 0;
@@ -548,7 +577,7 @@ void *nvmem_cell_read(struct nvmem_cell *cell, size_t *len)
return ERR_PTR(-ENOMEM);
rc = __nvmem_cell_read(nvmem, cell, buf, len);
- if (IS_ERR_VALUE(rc)) {
+ if (rc < 0) {
kfree(buf);
return ERR_PTR(rc);
}
@@ -577,7 +606,10 @@ static inline void *nvmem_cell_prepare_write_buffer(struct nvmem_cell *cell,
*b <<= bit_offset;
/* setup the first byte with lsb bits from nvmem */
- rc = nvmem->bus->read(&nvmem->dev, cell->offset, &v, 1);
+ rc = nvmem->reg_read(nvmem->priv, cell->offset, &v, 1);
+ if (rc < 0)
+ return ERR_PTR(rc);
+
*b++ |= GENMASK(bit_offset - 1, 0) & v;
/* setup rest of the byte if any */
@@ -594,8 +626,11 @@ static inline void *nvmem_cell_prepare_write_buffer(struct nvmem_cell *cell,
/* if it's not end on byte boundary */
if ((nbits + bit_offset) % BITS_PER_BYTE) {
/* setup the last byte with msb bits from nvmem */
- rc = nvmem->bus->read(&nvmem->dev, cell->offset + cell->bytes - 1,
+ rc = nvmem->reg_read(nvmem->priv, cell->offset + cell->bytes - 1,
&v, 1);
+ if (rc < 0)
+ return ERR_PTR(rc);
+
*p |= GENMASK(7, (nbits + bit_offset) % BITS_PER_BYTE) & v;
}
@@ -627,13 +662,13 @@ int nvmem_cell_write(struct nvmem_cell *cell, void *buf, size_t len)
return PTR_ERR(buf);
}
- rc = nvmem->bus->write(&nvmem->dev, cell->offset, buf, cell->bytes);
+ rc = nvmem->reg_write(nvmem->priv, cell->offset, buf, cell->bytes);
/* free the tmp buffer */
if (cell->bit_offset || cell->nbits)
kfree(buf);
- if (IS_ERR_VALUE(rc))
+ if (rc < 0)
return rc;
return len;
@@ -661,11 +696,11 @@ ssize_t nvmem_device_cell_read(struct nvmem_device *nvmem,
return -EINVAL;
rc = nvmem_cell_info_to_nvmem_cell(nvmem, info, &cell);
- if (IS_ERR_VALUE(rc))
+ if (rc < 0)
return rc;
rc = __nvmem_cell_read(nvmem, &cell, buf, &len);
- if (IS_ERR_VALUE(rc))
+ if (rc < 0)
return rc;
return len;
@@ -691,7 +726,7 @@ int nvmem_device_cell_write(struct nvmem_device *nvmem,
return -EINVAL;
rc = nvmem_cell_info_to_nvmem_cell(nvmem, info, &cell);
- if (IS_ERR_VALUE(rc))
+ if (rc < 0)
return rc;
return nvmem_cell_write(&cell, buf, cell.bytes);
@@ -724,9 +759,9 @@ int nvmem_device_read(struct nvmem_device *nvmem,
if (!bytes)
return 0;
- rc = nvmem->bus->read(&nvmem->dev, offset, buf, bytes);
+ rc = nvmem->reg_read(nvmem->priv, offset, buf, bytes);
- if (IS_ERR_VALUE(rc))
+ if (rc < 0)
return rc;
return bytes;
@@ -758,9 +793,9 @@ int nvmem_device_write(struct nvmem_device *nvmem,
if (!bytes)
return 0;
- rc = nvmem->bus->write(&nvmem->dev, offset, buf, bytes);
+ rc = nvmem->reg_write(nvmem->priv, offset, buf, bytes);
- if (IS_ERR_VALUE(rc))
+ if (rc < 0)
return rc;
@@ -790,3 +825,42 @@ void *nvmem_cell_get_and_read(struct device_node *np, const char *cell_name,
return value;
}
EXPORT_SYMBOL_GPL(nvmem_cell_get_and_read);
+
+/**
+ * nvmem_cell_read_variable_le_u32() - Read up to 32-bits of data as a little endian number.
+ *
+ * @dev: Device that requests the nvmem cell.
+ * @cell_id: Name of nvmem cell to read.
+ * @val: pointer to output value.
+ *
+ * Return: 0 on success or negative errno.
+ */
+int nvmem_cell_read_variable_le_u32(struct device *dev, const char *cell_id,
+ u32 *val)
+{
+ size_t len;
+ const u8 *buf;
+ int i;
+
+ len = sizeof(*val);
+
+ buf = nvmem_cell_get_and_read(dev->of_node, cell_id, len);
+ if (IS_ERR(buf))
+ return PTR_ERR(buf);
+
+ /* Copy w/ implicit endian conversion */
+ *val = 0;
+ for (i = 0; i < len; i++)
+ *val |= buf[i] << (8 * i);
+
+ kfree(buf);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(nvmem_cell_read_variable_le_u32);
+
+struct device *nvmem_device_get_device(struct nvmem_device *nvmem)
+{
+ return &nvmem->dev;
+}
+EXPORT_SYMBOL_GPL(nvmem_device_get_device);
diff --git a/drivers/nvmem/eeprom_93xx46.c b/drivers/nvmem/eeprom_93xx46.c
index 49ed396dc2..3180b0cb69 100644
--- a/drivers/nvmem/eeprom_93xx46.c
+++ b/drivers/nvmem/eeprom_93xx46.c
@@ -1,11 +1,8 @@
+// SPDX-License-Identifier: GPL-2.0-only
/*
* Driver for 93xx46 EEPROMs
*
* (C) 2011 DENX Software Engineering, Anatolij Gustschin <agust@denx.de>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
*/
#include <common.h>
@@ -13,8 +10,6 @@
#include <driver.h>
#include <of.h>
#include <spi/spi.h>
-#include <of.h>
-#include <spi/spi.h>
#include <malloc.h>
#include <gpio.h>
#include <of_gpio.h>
@@ -79,10 +74,9 @@ static inline bool has_quirk_instruction_length(struct eeprom_93xx46_dev *edev)
return edev->pdata->quirks & EEPROM_93XX46_QUIRK_INSTRUCTION_LENGTH;
}
-static int eeprom_93xx46_read(struct device_d *dev, int off,
- void *val, int count)
+static int eeprom_93xx46_read(void *ctx, unsigned off, void *val, size_t count)
{
- struct eeprom_93xx46_dev *edev = dev->parent->priv;
+ struct eeprom_93xx46_dev *edev = ctx;
char *buf = val;
int err = 0;
@@ -241,10 +235,9 @@ eeprom_93xx46_write_word(struct eeprom_93xx46_dev *edev,
return ret;
}
-static int eeprom_93xx46_write(struct device_d *dev, const int off,
- const void *val, int count)
+static int eeprom_93xx46_write(void *ctx, unsigned off, const void *val, size_t count)
{
- struct eeprom_93xx46_dev *edev = dev->parent->priv;
+ struct eeprom_93xx46_dev *edev = ctx;
const char *buf = val;
int i, ret, step = 1;
@@ -307,12 +300,13 @@ static const struct of_device_id eeprom_93xx46_of_table[] = {
{ .compatible = "atmel,at93c46d", .data = &atmel_at93c46d_data, },
{}
};
+MODULE_DEVICE_TABLE(of, eeprom_93xx46_of_table);
static int eeprom_93xx46_probe_dt(struct spi_device *spi)
{
const struct of_device_id *of_id =
of_match_device(eeprom_93xx46_of_table, &spi->dev);
- struct device_node *np = spi->dev.device_node;
+ struct device_node *np = spi->dev.of_node;
struct eeprom_93xx46_platform_data *pd;
enum of_gpio_flags of_flags;
unsigned long flags = GPIOF_OUT_INIT_INACTIVE;
@@ -369,19 +363,14 @@ static int eeprom_93xx46_probe_dt(struct spi_device *spi)
return 0;
}
-static const struct nvmem_bus eeprom_93xx46_nvmem_bus = {
- .write = eeprom_93xx46_write,
- .read = eeprom_93xx46_read,
-};
-
-static int eeprom_93xx46_probe(struct device_d *dev)
+static int eeprom_93xx46_probe(struct device *dev)
{
struct spi_device *spi = (struct spi_device *)dev->type_data;
struct eeprom_93xx46_platform_data *pd;
struct eeprom_93xx46_dev *edev;
int err;
- if (dev->device_node) {
+ if (dev->of_node) {
err = eeprom_93xx46_probe_dt(spi);
if (err < 0)
return err;
@@ -411,14 +400,15 @@ static int eeprom_93xx46_probe(struct device_d *dev)
edev->size = 128;
edev->nvmem_config.name = dev_name(&spi->dev);
edev->nvmem_config.dev = &spi->dev;
+ edev->nvmem_config.priv = edev;
edev->nvmem_config.read_only = pd->flags & EE_READONLY;
- edev->nvmem_config.bus = &eeprom_93xx46_nvmem_bus;
+ edev->nvmem_config.reg_write = eeprom_93xx46_write;
+ edev->nvmem_config.reg_read = eeprom_93xx46_read;
+
edev->nvmem_config.stride = 4;
edev->nvmem_config.word_size = 1;
edev->nvmem_config.size = edev->size;
- dev->priv = edev;
-
edev->nvmem = nvmem_register(&edev->nvmem_config);
if (IS_ERR(edev->nvmem)) {
err = PTR_ERR(edev->nvmem);
@@ -435,7 +425,7 @@ fail:
return err;
}
-static struct driver_d eeprom_93xx46_driver = {
+static struct driver eeprom_93xx46_driver = {
.name = "93xx46",
.probe = eeprom_93xx46_probe,
.of_compatible = DRV_OF_COMPAT(eeprom_93xx46_of_table),
diff --git a/drivers/nvmem/imx-ocotp-ele.c b/drivers/nvmem/imx-ocotp-ele.c
new file mode 100644
index 0000000000..e4e60ed6af
--- /dev/null
+++ b/drivers/nvmem/imx-ocotp-ele.c
@@ -0,0 +1,241 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * i.MX9 OCOTP fusebox driver
+ *
+ * Copyright 2023 NXP
+ */
+#include <common.h>
+#include <io.h>
+#include <linux/nvmem-provider.h>
+#include <linux/regmap.h>
+#include <mach/imx/ele.h>
+#include <machine_id.h>
+
+#define UNIQUE_ID_NUM 4
+#define OCOTP_UNIQUE_ID(n) (0xc0 + (n) * 4)
+
+enum fuse_type {
+ FUSE_FSB = 1,
+ FUSE_ELE = 2,
+ FUSE_INVALID = -1
+};
+
+struct ocotp_map_entry {
+ u32 start; /* start word */
+ u32 num; /* num words */
+ enum fuse_type type;
+};
+
+struct ocotp_devtype_data {
+ u32 reg_off;
+ char *name;
+ u32 size;
+ u32 num_entry;
+ u32 flag;
+ struct ocotp_map_entry *entry;
+};
+
+struct imx_ocotp_priv {
+ struct device *dev;
+ struct regmap *map;
+ void __iomem *base;
+ const struct ocotp_devtype_data *data;
+ struct regmap_config map_config;
+ int permanent_write_enable;
+};
+
+static enum fuse_type imx_ocotp_fuse_type(struct imx_ocotp_priv *priv, u32 index)
+{
+ const struct ocotp_devtype_data *data = priv->data;
+ u32 start, end;
+ int i;
+
+ for (i = 0; i < data->num_entry; i++) {
+ start = data->entry[i].start;
+ end = data->entry[i].start + data->entry[i].num;
+
+ if (index >= start && index < end)
+ return data->entry[i].type;
+ }
+
+ return FUSE_INVALID;
+}
+
+static int imx_ocotp_reg_read(void *context, unsigned int offset, unsigned int *val)
+{
+ struct imx_ocotp_priv *priv = context;
+ void __iomem *reg = priv->base + priv->data->reg_off;
+ u32 index;
+ enum fuse_type type;
+ int ret;
+ u32 fuse_word;
+
+ index = offset >> 2;
+
+ type = imx_ocotp_fuse_type(priv, index);
+
+ switch (type) {
+ case FUSE_ELE:
+ ret = ele_read_common_fuse(index, &fuse_word, NULL);
+ if (ret)
+ *val = 0xbeefdead;
+ else
+ *val = fuse_word;
+ break;
+ case FUSE_FSB:
+ *val = readl_relaxed(reg + (index << 2));
+ break;
+ default:
+ *val = 0xdeadbeef;
+ break;
+ }
+
+ return 0;
+};
+
+static int imx_ocotp_reg_write(void *context, unsigned int offset, unsigned int val)
+{
+ struct imx_ocotp_priv *priv = context;
+ u32 index;
+ int ret;
+
+ index = offset >> 2;
+
+ if (priv->permanent_write_enable)
+ ret = ele_write_fuse(index, val, false, NULL);
+ else
+ ret = ele_write_shadow_fuse(index, val, NULL);
+
+ return ret;
+}
+
+static int imx_ocotp_cell_pp(void *context, const char *id, unsigned int offset,
+ void *data, size_t bytes)
+{
+ /* Deal with some post processing of nvmem cell data */
+ if (id && !strcmp(id, "mac-address")) {
+ u8 *buf = data;
+
+ if (offset == 0x4ec) {
+ swap(buf[0], buf[5]);
+ swap(buf[1], buf[4]);
+ swap(buf[2], buf[3]);
+ } else if (offset == 0x4f2) {
+ swap(buf[0], buf[1]);
+ swap(buf[2], buf[5]);
+ swap(buf[3], buf[4]);
+ } else {
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+static struct regmap_bus imx_ocotp_regmap_bus = {
+ .reg_write = imx_ocotp_reg_write,
+ .reg_read = imx_ocotp_reg_read,
+};
+
+static void imx_ocotp_set_unique_machine_id(struct imx_ocotp_priv *priv)
+{
+ uint32_t unique_id_parts[UNIQUE_ID_NUM];
+ int i;
+
+ for (i = 0; i < UNIQUE_ID_NUM; i++)
+ if (imx_ocotp_reg_read(priv, OCOTP_UNIQUE_ID(i),
+ &unique_id_parts[i]))
+ return;
+
+ machine_id_set_hashable(unique_id_parts, sizeof(unique_id_parts));
+}
+
+static int permanent_write_enable_set(struct param_d *param, void *ctx)
+{
+ struct imx_ocotp_priv *priv = ctx;
+
+ if (priv->permanent_write_enable) {
+ dev_warn(priv->dev, "Enabling permanent write on fuses.\n");
+ dev_warn(priv->dev, "Writing fuses may damage your device. Be careful!\n");
+ }
+
+ return 0;
+}
+
+static int imx_ele_ocotp_probe(struct device *dev)
+{
+ struct imx_ocotp_priv *priv;
+ struct nvmem_device *nvmem;
+ struct resource *iores;
+ struct ocotp_devtype_data *data;
+ int ret;
+
+ priv = xzalloc(sizeof(*priv));
+ priv->dev = dev;
+
+ ret = dev_get_drvdata(dev, (const void **)&data);
+ if (ret)
+ return ret;
+
+ iores = dev_request_mem_resource(dev, 0);
+ if (IS_ERR(iores))
+ return PTR_ERR(iores);
+
+ priv->base = IOMEM(iores->start);
+ priv->data = data;
+
+ priv->map_config.reg_bits = 32;
+ priv->map_config.val_bits = 32;
+ priv->map_config.reg_stride = 4;
+ priv->map_config.max_register = priv->data->size - priv->map_config.reg_stride;
+
+ priv->map = regmap_init(dev, &imx_ocotp_regmap_bus, priv, &priv->map_config);
+ if (IS_ERR(priv->map))
+ return PTR_ERR(priv->map);
+
+ if (IS_ENABLED(CONFIG_MACHINE_ID))
+ imx_ocotp_set_unique_machine_id(priv);
+
+ nvmem = nvmem_regmap_register_with_pp(priv->map, "imx_ocotp",
+ imx_ocotp_cell_pp);
+ if (IS_ERR(nvmem))
+ return PTR_ERR(nvmem);
+
+ dev_add_param_bool(nvmem_device_get_device(nvmem), "permanent_write_enable",
+ permanent_write_enable_set, NULL, &priv->permanent_write_enable, priv);
+
+ return 0;
+}
+
+static struct ocotp_map_entry imx93_entries[] = {
+ { 0, 52, FUSE_FSB },
+ { 63, 1, FUSE_ELE},
+ { 128, 16, FUSE_ELE },
+ { 182, 1, FUSE_ELE },
+ { 188, 1, FUSE_ELE },
+ { 312, 200, FUSE_FSB }
+};
+
+static const struct ocotp_devtype_data imx93_ocotp_data = {
+ .reg_off = 0x8000,
+ .size = 2048,
+ .num_entry = ARRAY_SIZE(imx93_entries),
+ .entry = imx93_entries,
+};
+
+static const struct of_device_id imx_ele_ocotp_dt_ids[] = {
+ { .compatible = "fsl,imx93-ocotp", .data = &imx93_ocotp_data, },
+ {},
+};
+MODULE_DEVICE_TABLE(of, imx_ele_ocotp_dt_ids);
+
+static struct driver imx_ele_ocotp_driver = {
+ .name = "imx_ele_ocotp",
+ .of_match_table = imx_ele_ocotp_dt_ids,
+ .probe = imx_ele_ocotp_probe,
+};
+core_platform_driver(imx_ele_ocotp_driver);
+
+MODULE_DESCRIPTION("i.MX OCOTP/ELE driver");
+MODULE_AUTHOR("Peng Fan <peng.fan@nxp.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/nvmem/kvx-otp-nv.c b/drivers/nvmem/kvx-otp-nv.c
new file mode 100644
index 0000000000..c4591f5822
--- /dev/null
+++ b/drivers/nvmem/kvx-otp-nv.c
@@ -0,0 +1,95 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2020 Kalray Inc., Clément Léger
+ */
+
+#include <common.h>
+#include <driver.h>
+#include <malloc.h>
+#include <xfuncs.h>
+#include <errno.h>
+#include <init.h>
+#include <net.h>
+#include <io.h>
+
+#include <linux/nvmem-provider.h>
+
+#define OTP_NV_ALIGN 4
+#define OTP_NV_ALIGN_MASK (OTP_NV_ALIGN - 1)
+
+struct kvx_otp_nv_priv {
+ void __iomem *base;
+};
+
+static int kvx_otp_nv_read(void *context, unsigned int offset,
+ void *_val, size_t bytes)
+{
+ struct kvx_otp_nv_priv *priv = context;
+ u8 *val = _val;
+ u32 tmp, copy_size;
+ u8 skip = offset & OTP_NV_ALIGN_MASK;
+
+ offset &= ~OTP_NV_ALIGN_MASK;
+
+ while (bytes) {
+ tmp = readl(priv->base + offset);
+ if (skip != 0)
+ copy_size = min(OTP_NV_ALIGN - skip, (int) bytes);
+ else
+ copy_size = min(bytes, sizeof(tmp));
+
+ memcpy(val, ((u8 *) &tmp) + skip, copy_size);
+ skip = 0;
+
+ bytes -= copy_size;
+ val += copy_size;
+ offset += OTP_NV_ALIGN;
+ }
+
+ return 0;
+}
+
+static const struct of_device_id kvx_otp_nv_match[] = {
+ { .compatible = "kalray,kvx-otp-nv" },
+ { /* sentinel */},
+};
+MODULE_DEVICE_TABLE(of, kvx_otp_nv_match);
+
+static int kvx_otp_nv_probe(struct device *dev)
+{
+ struct resource *res;
+ struct nvmem_device *nvmem;
+ struct nvmem_config econfig = { 0 };
+ struct kvx_otp_nv_priv *priv;
+
+ priv = kzalloc(sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ res = dev_request_mem_resource(dev, 0);
+ if (IS_ERR(res))
+ return PTR_ERR(res);
+
+ priv->base = IOMEM(res->start);
+
+ econfig.name = "kvx-nv-regbank";
+ econfig.stride = 1;
+ econfig.word_size = 1;
+ econfig.size = resource_size(res);
+ econfig.dev = dev;
+ econfig.priv = priv;
+ econfig.reg_read = kvx_otp_nv_read;
+
+ dev->priv = priv;
+
+ nvmem = nvmem_register(&econfig);
+
+ return PTR_ERR_OR_ZERO(nvmem);
+}
+
+static struct driver kvx_otp_nv_driver = {
+ .name = "kvx-otp-nv",
+ .probe = kvx_otp_nv_probe,
+ .of_compatible = DRV_OF_COMPAT(kvx_otp_nv_match),
+};
+postcore_platform_driver(kvx_otp_nv_driver);
diff --git a/drivers/nvmem/ocotp.c b/drivers/nvmem/ocotp.c
index 34e33dee82..c282efefa8 100644
--- a/drivers/nvmem/ocotp.c
+++ b/drivers/nvmem/ocotp.c
@@ -1,3 +1,4 @@
+// SPDX-License-Identifier: GPL-2.0-only
/*
* ocotp.c - i.MX6 ocotp fusebox driver
*
@@ -8,15 +9,12 @@
* Copyright (c) 2010 Baruch Siach <baruch@tkos.co.il>,
* Orex Computed Radiography
*
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2
- * as published by the Free Software Foundation.
- *
* http://www.opensource.org/licenses/gpl-license.html
* http://www.gnu.org/copyleft/gpl.html
*/
#include <common.h>
+#include <deep-probe.h>
#include <driver.h>
#include <malloc.h>
#include <xfuncs.h>
@@ -26,11 +24,18 @@
#include <io.h>
#include <of.h>
#include <clock.h>
-#include <regmap.h>
+#include <linux/regmap.h>
+#include <linux/bits.h>
#include <linux/clk.h>
-#include <mach/ocotp.h>
#include <machine_id.h>
-#include <mach/ocotp-fusemap.h>
+#ifdef CONFIG_ARCH_IMX
+#include <mach/imx/ocotp.h>
+#include <mach/imx/ocotp-fusemap.h>
+#else
+#include <mach/mxs/ocotp.h>
+#include <mach/mxs/ocotp-fusemap.h>
+#endif
+#include <soc/imx8m/featctrl.h>
#include <linux/nvmem-provider.h>
/*
@@ -48,26 +53,45 @@
#define OCOTP_READ_CTRL 0x30
#define OCOTP_READ_FUSE_DATA 0x40
+#define MX7_OCOTP_DATA0 0x20
+#define MX7_OCOTP_DATA1 0x30
+#define MX7_OCOTP_DATA2 0x40
+#define MX7_OCOTP_DATA3 0x50
+#define MX7_OCOTP_READ_CTRL 0x60
+#define MX7_OCOTP_READ_FUSE_DATA0 0x70
+#define MX7_OCOTP_READ_FUSE_DATA1 0x80
+#define MX7_OCOTP_READ_FUSE_DATA2 0x90
+#define MX7_OCOTP_READ_FUSE_DATA3 0xA0
+
+#define DEF_FSOURCE 1001 /* > 1000 ns */
+#define DEF_STROBE_PROG 10000 /* IPG clocks */
+
/* OCOTP Registers bits and masks */
-#define OCOTP_CTRL_WR_UNLOCK 16
+#define OCOTP_CTRL_ADDR GENMASK(7, 0)
+#define OCOTP_CTRL_BUSY BIT(8)
+#define OCOTP_CTRL_ERROR BIT(9)
+#define OCOTP_CTRL_RELOAD_SHADOWS BIT(10)
+#define OCOTP_CTRL_WR_UNLOCK GENMASK(31, 16)
#define OCOTP_CTRL_WR_UNLOCK_KEY 0x3E77
-#define OCOTP_CTRL_WR_UNLOCK_MASK 0xFFFF0000
-#define OCOTP_CTRL_ADDR 0
-#define OCOTP_CTRL_ADDR_MASK 0x000000FF
-#define OCOTP_CTRL_BUSY (1 << 8)
-#define OCOTP_CTRL_ERROR (1 << 9)
-#define OCOTP_CTRL_RELOAD_SHADOWS (1 << 10)
-#define OCOTP_TIMING_STROBE_READ_MASK 0x003F0000
-#define OCOTP_TIMING_RELAX_MASK 0x0000F000
-#define OCOTP_TIMING_STROBE_PROG_MASK 0x00000FFF
-#define OCOTP_TIMING_WAIT_MASK 0x0FC00000
+/*
+ * i.MX8MP OCOTP CTRL has a different layout. See RM Rev.1 06/2021
+ * Section 6.3.5.1.2.4
+ */
+#define OCOTP_CTRL_ADDR_8MP GENMASK(8, 0)
+#define OCOTP_CTRL_BUSY_8MP BIT(9)
+#define OCOTP_CTRL_ERROR_8MP BIT(10)
+#define OCOTP_CTRL_RELOAD_SHADOWS_8MP BIT(11)
+#define OCOTP_CTRL_WR_UNLOCK_8MP GENMASK(31, 16)
-#define OCOTP_READ_CTRL_READ_FUSE 0x00000001
+#define OCOTP_TIMING_STROBE_READ GENMASK(21, 16)
+#define OCOTP_TIMING_RELAX GENMASK(15, 12)
+#define OCOTP_TIMING_STROBE_PROG GENMASK(11, 0)
+#define OCOTP_TIMING_WAIT GENMASK(27, 22)
-#define BF(value, field) FIELD_PREP(field##_MASK, value)
+#define OCOTP_READ_CTRL_READ_FUSE BIT(1)
-#define OCOTP_OFFSET_TO_ADDR(o) (OCOTP_OFFSET_TO_INDEX(o) * 4)
+#define OCOTP_OFFSET_TO_ADDR(o) (OCOTP_OFFSET_TO_INDEX(o) * 4)
/* Other definitions */
#define IMX6_OTP_DATA_ERROR_VAL 0xBADABADA
@@ -77,22 +101,56 @@
#define MAC_OFFSET_0 (0x22 * 4)
#define IMX6UL_MAC_OFFSET_1 (0x23 * 4)
#define MAC_OFFSET_1 (0x24 * 4)
+#define IMX8MP_MAC_OFFSET_1 (0x25 * 4)
#define MAX_MAC_OFFSETS 2
#define MAC_BYTES 8
#define UNIQUE_ID_NUM 2
+#define field_prep(_mask, _val) (((_val) << (ffs(_mask) - 1)) & (_mask))
+
enum imx_ocotp_format_mac_direction {
OCOTP_HW_TO_MAC,
OCOTP_MAC_TO_HW,
};
+struct ocotp_ctrl_reg {
+ u32 bm_addr;
+ u32 bm_busy;
+ u32 bm_error;
+ u32 bm_reload_shadows;
+ u32 bm_wr_unlock;
+};
+
+const struct ocotp_ctrl_reg ocotp_ctrl_reg_default = {
+ .bm_addr = OCOTP_CTRL_ADDR,
+ .bm_busy = OCOTP_CTRL_BUSY,
+ .bm_error = OCOTP_CTRL_ERROR,
+ .bm_reload_shadows = OCOTP_CTRL_RELOAD_SHADOWS,
+ .bm_wr_unlock = OCOTP_CTRL_WR_UNLOCK,
+};
+
+const struct ocotp_ctrl_reg ocotp_ctrl_reg_8mp = {
+ .bm_addr = OCOTP_CTRL_ADDR_8MP,
+ .bm_busy = OCOTP_CTRL_BUSY_8MP,
+ .bm_error = OCOTP_CTRL_ERROR_8MP,
+ .bm_reload_shadows = OCOTP_CTRL_RELOAD_SHADOWS_8MP,
+ .bm_wr_unlock = OCOTP_CTRL_WR_UNLOCK_8MP,
+};
+
+struct ocotp_priv;
+
struct imx_ocotp_data {
- int num_regs;
+ int nregs;
u32 (*addr_to_offset)(u32 addr);
void (*format_mac)(u8 *dst, const u8 *src,
enum imx_ocotp_format_mac_direction dir);
+ int (*set_timing)(struct ocotp_priv *priv);
+ int (*fuse_read)(struct ocotp_priv *priv, u32 addr, u32 *pdata);
+ int (*fuse_blow)(struct ocotp_priv *priv, u32 addr, u32 value);
u8 mac_offsets[MAX_MAC_OFFSETS];
u8 mac_offsets_num;
+ struct imx8m_featctrl_data *feat;
+ const struct ocotp_ctrl_reg *ctrl;
};
struct ocotp_priv_ethaddr {
@@ -106,14 +164,13 @@ struct ocotp_priv {
struct regmap *map;
void __iomem *base;
struct clk *clk;
- struct device_d dev;
+ struct device dev;
int permanent_write_enable;
int sense_enable;
struct ocotp_priv_ethaddr ethaddr[MAX_MAC_OFFSETS];
struct regmap_config map_config;
const struct imx_ocotp_data *data;
int mac_offset_idx;
- struct nvmem_config config;
};
static struct ocotp_priv *imx_ocotp;
@@ -158,10 +215,31 @@ static int imx6_ocotp_set_timing(struct ocotp_priv *priv)
1000000);
strobe_prog += 2 * (relax + 1) - 1;
- timing = readl(priv->base + OCOTP_TIMING) & OCOTP_TIMING_WAIT_MASK;
- timing |= BF(relax, OCOTP_TIMING_RELAX);
- timing |= BF(strobe_read, OCOTP_TIMING_STROBE_READ);
- timing |= BF(strobe_prog, OCOTP_TIMING_STROBE_PROG);
+ timing = readl(priv->base + OCOTP_TIMING) & OCOTP_TIMING_WAIT;
+ timing |= FIELD_PREP(OCOTP_TIMING_RELAX, relax);
+ timing |= FIELD_PREP(OCOTP_TIMING_STROBE_READ, strobe_read);
+ timing |= FIELD_PREP(OCOTP_TIMING_STROBE_PROG, strobe_prog);
+
+ writel(timing, priv->base + OCOTP_TIMING);
+
+ return 0;
+}
+
+static int imx7_ocotp_set_timing(struct ocotp_priv *priv)
+{
+ unsigned long clk_rate;
+ u64 fsource, strobe_prog;
+ u32 timing;
+
+ clk_rate = clk_get_rate(priv->clk);
+
+ fsource = DIV_ROUND_UP_ULL((u64)clk_rate * DEF_FSOURCE,
+ NSEC_PER_SEC) + 1;
+ strobe_prog = DIV_ROUND_CLOSEST_ULL((u64)clk_rate * DEF_STROBE_PROG,
+ NSEC_PER_SEC) + 1;
+
+ timing = strobe_prog & 0x00000FFF;
+ timing |= (fsource << 12) & 0x000FF000;
writel(timing, priv->base + OCOTP_TIMING);
@@ -171,8 +249,9 @@ static int imx6_ocotp_set_timing(struct ocotp_priv *priv)
static int imx6_ocotp_wait_busy(struct ocotp_priv *priv, u32 flags)
{
uint64_t start = get_time_ns();
+ u32 bm_ctrl_busy = priv->data->ctrl->bm_busy;
- while (readl(priv->base + OCOTP_CTRL) & (OCOTP_CTRL_BUSY | flags))
+ while (readl(priv->base + OCOTP_CTRL) & (bm_ctrl_busy | flags))
if (is_timeout(start, MSECOND))
return -ETIMEDOUT;
@@ -183,7 +262,7 @@ static int imx6_ocotp_prepare(struct ocotp_priv *priv)
{
int ret;
- ret = imx6_ocotp_set_timing(priv);
+ ret = priv->data->set_timing(priv);
if (ret)
return ret;
@@ -194,17 +273,20 @@ static int imx6_ocotp_prepare(struct ocotp_priv *priv)
return 0;
}
-static int fuse_read_addr(struct ocotp_priv *priv, u32 addr, u32 *pdata)
+static int imx6_fuse_read_addr(struct ocotp_priv *priv, u32 addr, u32 *pdata)
{
+ const u32 bm_ctrl_error = priv->data->ctrl->bm_error;
+ const u32 bm_ctrl_addr = priv->data->ctrl->bm_addr;
+ const u32 bm_ctrl_wr_unlock = priv->data->ctrl->bm_wr_unlock;
u32 ctrl_reg;
int ret;
- writel(OCOTP_CTRL_ERROR, priv->base + OCOTP_CTRL_CLR);
+ writel(bm_ctrl_error, priv->base + OCOTP_CTRL_CLR);
ctrl_reg = readl(priv->base + OCOTP_CTRL);
- ctrl_reg &= ~OCOTP_CTRL_ADDR_MASK;
- ctrl_reg &= ~OCOTP_CTRL_WR_UNLOCK_MASK;
- ctrl_reg |= BF(addr, OCOTP_CTRL_ADDR);
+ ctrl_reg &= ~bm_ctrl_addr;
+ ctrl_reg &= ~bm_ctrl_wr_unlock;
+ ctrl_reg |= field_prep(bm_ctrl_addr, addr);
writel(ctrl_reg, priv->base + OCOTP_CTRL);
writel(OCOTP_READ_CTRL_READ_FUSE, priv->base + OCOTP_READ_CTRL);
@@ -212,7 +294,7 @@ static int fuse_read_addr(struct ocotp_priv *priv, u32 addr, u32 *pdata)
if (ret)
return ret;
- if (readl(priv->base + OCOTP_CTRL) & OCOTP_CTRL_ERROR)
+ if (readl(priv->base + OCOTP_CTRL) & bm_ctrl_error)
*pdata = 0xbadabada;
else
*pdata = readl(priv->base + OCOTP_READ_FUSE_DATA);
@@ -220,6 +302,53 @@ static int fuse_read_addr(struct ocotp_priv *priv, u32 addr, u32 *pdata)
return 0;
}
+static int imx7_fuse_read_addr(struct ocotp_priv *priv, u32 index, u32 *pdata)
+{
+ const u32 bm_ctrl_error = priv->data->ctrl->bm_error;
+ const u32 bm_ctrl_addr = priv->data->ctrl->bm_addr;
+ const u32 bm_ctrl_wr_unlock = priv->data->ctrl->bm_wr_unlock;
+ u32 ctrl_reg;
+ u32 bank_addr;
+ u16 word;
+ int ret;
+
+ word = index & 0x3;
+ bank_addr = index >> 2;
+
+ writel(bm_ctrl_error, priv->base + OCOTP_CTRL_CLR);
+
+ ctrl_reg = readl(priv->base + OCOTP_CTRL);
+ ctrl_reg &= ~bm_ctrl_addr;
+ ctrl_reg &= ~bm_ctrl_wr_unlock;
+ ctrl_reg |= field_prep(bm_ctrl_addr, bank_addr);
+ writel(ctrl_reg, priv->base + OCOTP_CTRL);
+
+ writel(OCOTP_READ_CTRL_READ_FUSE, priv->base + MX7_OCOTP_READ_CTRL);
+ ret = imx6_ocotp_wait_busy(priv, 0);
+ if (ret)
+ return ret;
+
+ if (readl(priv->base + OCOTP_CTRL) & bm_ctrl_error)
+ *pdata = 0xbadabada;
+ else
+ switch (word) {
+ case 0:
+ *pdata = readl(priv->base + MX7_OCOTP_READ_FUSE_DATA0);
+ break;
+ case 1:
+ *pdata = readl(priv->base + MX7_OCOTP_READ_FUSE_DATA1);
+ break;
+ case 2:
+ *pdata = readl(priv->base + MX7_OCOTP_READ_FUSE_DATA2);
+ break;
+ case 3:
+ *pdata = readl(priv->base + MX7_OCOTP_READ_FUSE_DATA2);
+ break;
+ }
+
+ return 0;
+}
+
static int imx6_ocotp_read_one_u32(struct ocotp_priv *priv, u32 index, u32 *pdata)
{
int ret;
@@ -231,7 +360,7 @@ static int imx6_ocotp_read_one_u32(struct ocotp_priv *priv, u32 index, u32 *pdat
return ret;
}
- ret = fuse_read_addr(priv, index, pdata);
+ ret = priv->data->fuse_read(priv, index, pdata);
if (ret) {
dev_err(&priv->dev, "failed to read fuse 0x%08x\n", index);
return ret;
@@ -260,19 +389,31 @@ static int imx_ocotp_reg_read(void *ctx, unsigned int reg, unsigned int *val)
return 0;
}
-static int fuse_blow_addr(struct ocotp_priv *priv, u32 addr, u32 value)
+static void imx_ocotp_clear_unlock(struct ocotp_priv *priv, u32 index)
{
+ const u32 bm_ctrl_error = priv->data->ctrl->bm_error;
+ const u32 bm_ctrl_addr = priv->data->ctrl->bm_addr;
+ const u32 bm_ctrl_wr_unlock = priv->data->ctrl->bm_wr_unlock;
u32 ctrl_reg;
- int ret;
- writel(OCOTP_CTRL_ERROR, priv->base + OCOTP_CTRL_CLR);
+ writel(bm_ctrl_error, priv->base + OCOTP_CTRL_CLR);
/* Control register */
ctrl_reg = readl(priv->base + OCOTP_CTRL);
- ctrl_reg &= ~OCOTP_CTRL_ADDR_MASK;
- ctrl_reg |= BF(addr, OCOTP_CTRL_ADDR);
- ctrl_reg |= BF(OCOTP_CTRL_WR_UNLOCK_KEY, OCOTP_CTRL_WR_UNLOCK);
+ ctrl_reg &= ~bm_ctrl_addr;
+ ctrl_reg |= field_prep(bm_ctrl_addr, index);
+ ctrl_reg |= field_prep(bm_ctrl_wr_unlock, OCOTP_CTRL_WR_UNLOCK_KEY);
writel(ctrl_reg, priv->base + OCOTP_CTRL);
+}
+
+static int imx6_fuse_blow_addr(struct ocotp_priv *priv, u32 index, u32 value)
+{
+ const u32 bm_ctrl_error = priv->data->ctrl->bm_error;
+ int ret;
+
+ imx_ocotp_clear_unlock(priv, index);
+
+ writel(bm_ctrl_error, priv->base + OCOTP_CTRL_CLR);
writel(value, priv->base + OCOTP_DATA);
ret = imx6_ocotp_wait_busy(priv, 0);
@@ -284,18 +425,68 @@ static int fuse_blow_addr(struct ocotp_priv *priv, u32 addr, u32 value)
return 0;
}
+static int imx7_fuse_blow_addr(struct ocotp_priv *priv, u32 index, u32 value)
+{
+ int ret;
+ int word;
+ int bank_addr;
+
+ bank_addr = index >> 2;
+ word = index & 0x3;
+
+ imx_ocotp_clear_unlock(priv, bank_addr);
+
+ switch(word) {
+ case 0:
+ writel(0, priv->base + MX7_OCOTP_DATA1);
+ writel(0, priv->base + MX7_OCOTP_DATA2);
+ writel(0, priv->base + MX7_OCOTP_DATA3);
+ writel(value, priv->base + MX7_OCOTP_DATA0);
+ break;
+ case 1:
+ writel(value, priv->base + MX7_OCOTP_DATA1);
+ writel(0, priv->base + MX7_OCOTP_DATA2);
+ writel(0, priv->base + MX7_OCOTP_DATA3);
+ writel(0, priv->base + MX7_OCOTP_DATA0);
+ break;
+ case 2:
+ writel(value, priv->base + MX7_OCOTP_DATA2);
+ writel(0, priv->base + MX7_OCOTP_DATA3);
+ writel(0, priv->base + MX7_OCOTP_DATA1);
+ writel(0, priv->base + MX7_OCOTP_DATA0);
+ break;
+ case 3:
+ writel(value, priv->base + MX7_OCOTP_DATA3);
+ writel(0, priv->base + MX7_OCOTP_DATA1);
+ writel(0, priv->base + MX7_OCOTP_DATA2);
+ writel(0, priv->base + MX7_OCOTP_DATA0);
+ break;
+ }
+
+ ret = imx6_ocotp_wait_busy(priv, 0);
+ if (ret)
+ return ret;
+
+ /* Write postamble */
+ udelay(2000);
+ return 0;
+}
+
static int imx6_ocotp_reload_shadow(struct ocotp_priv *priv)
{
+ const u32 bm_ctrl_reload_shadows = priv->data->ctrl->bm_reload_shadows;
+
dev_info(&priv->dev, "reloading shadow registers...\n");
- writel(OCOTP_CTRL_RELOAD_SHADOWS, priv->base + OCOTP_CTRL_SET);
+ writel(bm_ctrl_reload_shadows, priv->base + OCOTP_CTRL_SET);
udelay(1);
- return imx6_ocotp_wait_busy(priv, OCOTP_CTRL_RELOAD_SHADOWS);
+ return imx6_ocotp_wait_busy(priv, bm_ctrl_reload_shadows);
}
static int imx6_ocotp_blow_one_u32(struct ocotp_priv *priv, u32 index, u32 data,
u32 *pfused_value)
{
+ const u32 bm_ctrl_error = priv->data->ctrl->bm_error;
int ret;
ret = imx6_ocotp_prepare(priv);
@@ -304,13 +495,13 @@ static int imx6_ocotp_blow_one_u32(struct ocotp_priv *priv, u32 index, u32 data,
return ret;
}
- ret = fuse_blow_addr(priv, index, data);
+ ret = priv->data->fuse_blow(priv, index, data);
if (ret) {
dev_err(&priv->dev, "fuse blow failed\n");
return ret;
}
- if (readl(priv->base + OCOTP_CTRL) & OCOTP_CTRL_ERROR) {
+ if (readl(priv->base + OCOTP_CTRL) & bm_ctrl_error) {
dev_err(&priv->dev, "bad write status\n");
return -EFAULT;
}
@@ -355,11 +546,17 @@ static void imx_ocotp_field_decode(uint32_t field, unsigned *word,
*mask = GENMASK(width, 0);
}
+static int imx_ocotp_ensure_probed(void);
+
int imx_ocotp_read_field(uint32_t field, unsigned *value)
{
unsigned word, bit, mask, val;
int ret;
+ ret = imx_ocotp_ensure_probed();
+ if (ret)
+ return ret;
+
imx_ocotp_field_decode(field, &word, &bit, &mask);
ret = imx_ocotp_reg_read(imx_ocotp, word, &val);
@@ -382,6 +579,10 @@ int imx_ocotp_write_field(uint32_t field, unsigned value)
unsigned word, bit, mask;
int ret;
+ ret = imx_ocotp_ensure_probed();
+ if (ret)
+ return ret;
+
imx_ocotp_field_decode(field, &word, &bit, &mask);
value &= mask;
@@ -399,14 +600,27 @@ int imx_ocotp_write_field(uint32_t field, unsigned value)
int imx_ocotp_permanent_write(int enable)
{
+ int ret;
+
+ ret = imx_ocotp_ensure_probed();
+ if (ret)
+ return ret;
+
imx_ocotp->permanent_write_enable = enable;
return 0;
}
-bool imx_ocotp_sense_enable(bool enable)
+int imx_ocotp_sense_enable(bool enable)
{
- const bool old_value = imx_ocotp->sense_enable;
+ bool old_value;
+ int ret;
+
+ ret = imx_ocotp_ensure_probed();
+ if (ret)
+ return ret;
+
+ old_value = imx_ocotp->sense_enable;
imx_ocotp->sense_enable = enable;
return old_value;
}
@@ -456,12 +670,12 @@ static int imx_ocotp_read_mac(const struct imx_ocotp_data *data,
u8 buf[MAC_BYTES];
int ret;
- ret = regmap_bulk_read(map, offset, buf, MAC_BYTES);
+ ret = regmap_bulk_read(map, offset, buf, MAC_BYTES / 4);
if (ret < 0)
return ret;
- if (offset != IMX6UL_MAC_OFFSET_1)
+ if (offset != IMX6UL_MAC_OFFSET_1 && offset != IMX8MP_MAC_OFFSET_1)
data->format_mac(mac, buf, OCOTP_HW_TO_MAC);
else
data->format_mac(mac, buf + 2, OCOTP_HW_TO_MAC);
@@ -483,11 +697,12 @@ static int imx_ocotp_set_mac(struct param_d *param, void *priv)
struct ocotp_priv_ethaddr *ethaddr = priv;
int ret;
- ret = regmap_bulk_read(ethaddr->map, ethaddr->offset, buf, MAC_BYTES);
+ ret = regmap_bulk_read(ethaddr->map, ethaddr->offset, buf, MAC_BYTES / 4);
if (ret < 0)
return ret;
- if (ethaddr->offset != IMX6UL_MAC_OFFSET_1)
+ if (ethaddr->offset != IMX6UL_MAC_OFFSET_1 &&
+ ethaddr->offset != IMX8MP_MAC_OFFSET_1)
ethaddr->data->format_mac(buf, ethaddr->value,
OCOTP_MAC_TO_HW);
else
@@ -495,7 +710,7 @@ static int imx_ocotp_set_mac(struct param_d *param, void *priv)
OCOTP_MAC_TO_HW);
return regmap_bulk_write(ethaddr->map, ethaddr->offset,
- buf, MAC_BYTES);
+ buf, MAC_BYTES / 4);
}
static struct regmap_bus imx_ocotp_regmap_bus = {
@@ -503,19 +718,33 @@ static struct regmap_bus imx_ocotp_regmap_bus = {
.reg_read = imx_ocotp_reg_read,
};
-static void imx_ocotp_init_dt(struct ocotp_priv *priv)
+static int imx_ocotp_cell_pp(void *context, const char *id, unsigned int offset,
+ void *data, size_t bytes)
+{
+ /* Deal with some post processing of nvmem cell data */
+ if (id && !strcmp(id, "mac-address")) {
+ u8 *buf = data;
+ int i;
+
+ for (i = 0; i < bytes/2; i++)
+ swap(buf[i], buf[bytes - i - 1]);
+ }
+
+ return 0;
+}
+
+static int imx_ocotp_init_dt(struct ocotp_priv *priv)
{
char mac[MAC_BYTES];
const __be32 *prop;
- struct device_node *node = priv->dev.parent->device_node;
- int len;
+ struct device_node *node = priv->dev.parent->of_node;
+ u32 tester3, tester4;
+ int ret, len = 0;
if (!node)
- return;
+ return 0;
prop = of_get_property(node, "barebox,provide-mac-address", &len);
- if (!prop)
- return;
for (; len >= MAC_ADDRESS_PROPLEN; len -= MAC_ADDRESS_PROPLEN) {
struct device_node *rnode;
@@ -533,22 +762,19 @@ static void imx_ocotp_init_dt(struct ocotp_priv *priv)
of_eth_register_ethaddr(rnode, mac);
}
-}
-static int imx_ocotp_write(struct device_d *dev, const int offset,
- const void *val, int bytes)
-{
- struct ocotp_priv *priv = dev->parent->priv;
+ if (!of_property_read_bool(node, "barebox,feature-controller"))
+ return 0;
- return regmap_bulk_write(priv->map, offset, val, bytes);
-}
+ ret = regmap_read(priv->map, OCOTP_OFFSET_TO_ADDR(0x440), &tester3);
+ if (ret != 0)
+ return ret;
-static int imx_ocotp_read(struct device_d *dev, const int offset, void *val,
- int bytes)
-{
- struct ocotp_priv *priv = dev->parent->priv;
+ ret = regmap_read(priv->map, OCOTP_OFFSET_TO_ADDR(0x450), &tester4);
+ if (ret != 0)
+ return ret;
- return regmap_bulk_read(priv->map, offset, val, bytes);
+ return imx8m_feat_ctrl_init(priv->dev.parent, tester3, tester4, priv->data->feat);
}
static void imx_ocotp_set_unique_machine_id(void)
@@ -564,12 +790,7 @@ static void imx_ocotp_set_unique_machine_id(void)
machine_id_set_hashable(unique_id_parts, sizeof(unique_id_parts));
}
-static const struct nvmem_bus imx_ocotp_nvmem_bus = {
- .write = imx_ocotp_write,
- .read = imx_ocotp_read,
-};
-
-static int imx_ocotp_probe(struct device_d *dev)
+static int imx_ocotp_probe(struct device *dev)
{
struct resource *iores;
struct ocotp_priv *priv;
@@ -600,21 +821,14 @@ static int imx_ocotp_probe(struct device_d *dev)
priv->map_config.reg_bits = 32;
priv->map_config.val_bits = 32;
priv->map_config.reg_stride = 4;
- priv->map_config.max_register = data->num_regs - 1;
+ priv->map_config.max_register = priv->map_config.reg_stride * (data->nregs - 1);
priv->map = regmap_init(dev, &imx_ocotp_regmap_bus, priv, &priv->map_config);
if (IS_ERR(priv->map))
return PTR_ERR(priv->map);
- priv->config.name = "imx-ocotp";
- priv->config.dev = dev;
- priv->config.stride = 4;
- priv->config.word_size = 4;
- priv->config.size = data->num_regs;
- priv->config.bus = &imx_ocotp_nvmem_bus;
- dev->priv = priv;
-
- nvmem = nvmem_register(&priv->config);
+ nvmem = nvmem_regmap_register_with_pp(priv->map, "imx-ocotp",
+ imx_ocotp_cell_pp);
if (IS_ERR(nvmem))
return PTR_ERR(nvmem);
@@ -652,10 +866,12 @@ static int imx_ocotp_probe(struct device_d *dev)
if (IS_ENABLED(CONFIG_MACHINE_ID))
imx_ocotp_set_unique_machine_id();
- imx_ocotp_init_dt(priv);
+ ret = imx_ocotp_init_dt(priv);
+ if (ret)
+ dev_warn(dev, "feature controller registration failed: %pe\n",
+ ERR_PTR(ret));
dev_add_param_bool(&(priv->dev), "sense_enable", NULL, NULL, &priv->sense_enable, priv);
-
return 0;
}
@@ -689,51 +905,147 @@ static u32 vf610_addr_to_offset(u32 addr)
}
static struct imx_ocotp_data imx6q_ocotp_data = {
- .num_regs = 512,
+ .nregs = 128,
.addr_to_offset = imx6q_addr_to_offset,
.mac_offsets_num = 1,
.mac_offsets = { MAC_OFFSET_0 },
.format_mac = imx_ocotp_format_mac,
+ .set_timing = imx6_ocotp_set_timing,
+ .fuse_blow = imx6_fuse_blow_addr,
+ .fuse_read = imx6_fuse_read_addr,
+ .ctrl = &ocotp_ctrl_reg_default,
};
static struct imx_ocotp_data imx6sl_ocotp_data = {
- .num_regs = 256,
+ .nregs = 64,
.addr_to_offset = imx6sl_addr_to_offset,
.mac_offsets_num = 1,
.mac_offsets = { MAC_OFFSET_0 },
.format_mac = imx_ocotp_format_mac,
+ .set_timing = imx6_ocotp_set_timing,
+ .fuse_blow = imx6_fuse_blow_addr,
+ .fuse_read = imx6_fuse_read_addr,
+ .ctrl = &ocotp_ctrl_reg_default,
};
static struct imx_ocotp_data imx6ul_ocotp_data = {
- .num_regs = 512,
+ .nregs = 144,
.addr_to_offset = imx6q_addr_to_offset,
.mac_offsets_num = 2,
.mac_offsets = { MAC_OFFSET_0, IMX6UL_MAC_OFFSET_1 },
.format_mac = imx_ocotp_format_mac,
+ .set_timing = imx6_ocotp_set_timing,
+ .fuse_blow = imx6_fuse_blow_addr,
+ .fuse_read = imx6_fuse_read_addr,
+ .ctrl = &ocotp_ctrl_reg_default,
};
static struct imx_ocotp_data imx6ull_ocotp_data = {
- .num_regs = 256,
+ .nregs = 80,
.addr_to_offset = imx6q_addr_to_offset,
.mac_offsets_num = 2,
.mac_offsets = { MAC_OFFSET_0, IMX6UL_MAC_OFFSET_1 },
.format_mac = imx_ocotp_format_mac,
+ .set_timing = imx6_ocotp_set_timing,
+ .fuse_blow = imx6_fuse_blow_addr,
+ .fuse_read = imx6_fuse_read_addr,
+ .ctrl = &ocotp_ctrl_reg_default,
};
static struct imx_ocotp_data vf610_ocotp_data = {
- .num_regs = 512,
+ .nregs = 128,
.addr_to_offset = vf610_addr_to_offset,
.mac_offsets_num = 2,
.mac_offsets = { MAC_OFFSET_0, MAC_OFFSET_1 },
.format_mac = vf610_ocotp_format_mac,
+ .set_timing = imx6_ocotp_set_timing,
+ .fuse_blow = imx6_fuse_blow_addr,
+ .fuse_read = imx6_fuse_read_addr,
+ .ctrl = &ocotp_ctrl_reg_default,
+};
+
+static struct imx8m_featctrl_data imx8mp_featctrl_data = {
+ .tester3.cpu_bitmask = 0xc0000,
+ .tester3.vpu_bitmask = 0x43000000,
+ .tester4.npu_bitmask = 0x8,
+ .tester4.gpu_bitmask = 0xc0,
+ .tester4.mipi_dsi_bitmask = 0x60000,
+ .tester4.lvds_bitmask = 0x180000,
+ .tester4.isp_bitmask = 0x3,
+ .tester4.dsp_bitmask = 0x10,
+};
+
+static struct imx_ocotp_data imx8mp_ocotp_data = {
+ .nregs = 384,
+ .addr_to_offset = imx6sl_addr_to_offset,
+ .mac_offsets_num = 2,
+ .mac_offsets = { 0x90, 0x94 },
+ .format_mac = imx_ocotp_format_mac,
+ .feat = &imx8mp_featctrl_data,
+ .set_timing = imx6_ocotp_set_timing,
+ .fuse_blow = imx6_fuse_blow_addr,
+ .fuse_read = imx6_fuse_read_addr,
+ .ctrl = &ocotp_ctrl_reg_8mp,
};
static struct imx_ocotp_data imx8mq_ocotp_data = {
- .num_regs = 2048,
+ .nregs = 256,
+ .addr_to_offset = imx6sl_addr_to_offset,
+ .mac_offsets_num = 1,
+ .mac_offsets = { 0x90 },
+ .format_mac = imx_ocotp_format_mac,
+ .set_timing = imx6_ocotp_set_timing,
+ .fuse_blow = imx6_fuse_blow_addr,
+ .fuse_read = imx6_fuse_read_addr,
+ .ctrl = &ocotp_ctrl_reg_default,
+};
+
+static struct imx8m_featctrl_data imx8mm_featctrl_data = {
+ .tester4.vpu_bitmask = 0x1c0000,
+ .tester4.cpu_bitmask = 0x3,
+};
+
+static struct imx_ocotp_data imx8mm_ocotp_data = {
+ .nregs = 256,
.addr_to_offset = imx6sl_addr_to_offset,
.mac_offsets_num = 1,
.mac_offsets = { 0x90 },
.format_mac = imx_ocotp_format_mac,
+ .set_timing = imx6_ocotp_set_timing,
+ .fuse_blow = imx6_fuse_blow_addr,
+ .fuse_read = imx6_fuse_read_addr,
+ .feat = &imx8mm_featctrl_data,
+ .ctrl = &ocotp_ctrl_reg_default,
+};
+
+static struct imx8m_featctrl_data imx8mn_featctrl_data = {
+ .tester4.gpu_bitmask = 0x1000000,
+ .tester4.cpu_bitmask = 0x3,
+};
+
+static struct imx_ocotp_data imx8mn_ocotp_data = {
+ .nregs = 256,
+ .addr_to_offset = imx6sl_addr_to_offset,
+ .mac_offsets_num = 1,
+ .mac_offsets = { 0x90 },
+ .format_mac = imx_ocotp_format_mac,
+ .set_timing = imx6_ocotp_set_timing,
+ .fuse_blow = imx6_fuse_blow_addr,
+ .fuse_read = imx6_fuse_read_addr,
+ .feat = &imx8mn_featctrl_data,
+ .ctrl = &ocotp_ctrl_reg_default,
+};
+
+static struct imx_ocotp_data imx7d_ocotp_data = {
+ .nregs = 64,
+ .addr_to_offset = imx6sl_addr_to_offset,
+ .mac_offsets_num = 1,
+ .mac_offsets = { MAC_OFFSET_0, IMX6UL_MAC_OFFSET_1 },
+ .format_mac = imx_ocotp_format_mac,
+ .set_timing = imx7_ocotp_set_timing,
+ .fuse_blow = imx7_fuse_blow_addr,
+ .fuse_read = imx7_fuse_read_addr,
+ .ctrl = &ocotp_ctrl_reg_default,
};
static __maybe_unused struct of_device_id imx_ocotp_dt_ids[] = {
@@ -753,17 +1065,43 @@ static __maybe_unused struct of_device_id imx_ocotp_dt_ids[] = {
.compatible = "fsl,imx6ull-ocotp",
.data = &imx6ull_ocotp_data,
}, {
+ .compatible = "fsl,imx7d-ocotp",
+ .data = &imx7d_ocotp_data,
+ }, {
+ .compatible = "fsl,imx8mp-ocotp",
+ .data = &imx8mp_ocotp_data,
+ }, {
.compatible = "fsl,imx8mq-ocotp",
.data = &imx8mq_ocotp_data,
}, {
+ .compatible = "fsl,imx8mm-ocotp",
+ .data = &imx8mm_ocotp_data,
+ }, {
+ .compatible = "fsl,imx8mn-ocotp",
+ .data = &imx8mn_ocotp_data,
+ }, {
.compatible = "fsl,vf610-ocotp",
.data = &vf610_ocotp_data,
}, {
/* sentinel */
}
};
+MODULE_DEVICE_TABLE(of, imx_ocotp_dt_ids);
+
+static int imx_ocotp_ensure_probed(void)
+{
+ if (!imx_ocotp && deep_probe_is_supported()) {
+ int ret;
+
+ ret = of_devices_ensure_probed_by_dev_id(imx_ocotp_dt_ids);
+ if (ret)
+ return ret;
+ }
+
+ return imx_ocotp ? 0 : -EPROBE_DEFER;
+}
-static struct driver_d imx_ocotp_driver = {
+static struct driver imx_ocotp_driver = {
.name = "imx_ocotp",
.probe = imx_ocotp_probe,
.of_compatible = DRV_OF_COMPAT(imx_ocotp_dt_ids),
diff --git a/drivers/nvmem/partition.c b/drivers/nvmem/partition.c
new file mode 100644
index 0000000000..14907e05ba
--- /dev/null
+++ b/drivers/nvmem/partition.c
@@ -0,0 +1,36 @@
+// SPDX-License-Identifier: GPL-2.0-only
+#include <common.h>
+#include <driver.h>
+#include <malloc.h>
+#include <xfuncs.h>
+#include <errno.h>
+#include <init.h>
+#include <io.h>
+#include <linux/nvmem-provider.h>
+
+static int nvmem_cdev_write(void *ctx, unsigned offset, const void *val, size_t bytes)
+{
+ return cdev_write(ctx, val, bytes, offset, 0);
+}
+
+static int nvmem_cdev_read(void *ctx, unsigned offset, void *buf, size_t bytes)
+{
+ return cdev_read(ctx, buf, bytes, offset, 0);
+}
+
+struct nvmem_device *nvmem_partition_register(struct cdev *cdev)
+{
+ struct nvmem_config config = {};
+
+ config.name = cdev->name;
+ config.dev = cdev->dev;
+ config.cdev = cdev;
+ config.priv = cdev;
+ config.stride = 1;
+ config.word_size = 1;
+ config.size = cdev->size;
+ config.reg_read = nvmem_cdev_read;
+ config.reg_write = nvmem_cdev_write;
+
+ return nvmem_register(&config);
+}
diff --git a/drivers/nvmem/rave-sp-eeprom.c b/drivers/nvmem/rave-sp-eeprom.c
index 6c6ed17f18..aae337853c 100644
--- a/drivers/nvmem/rave-sp-eeprom.c
+++ b/drivers/nvmem/rave-sp-eeprom.c
@@ -1,20 +1,8 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
/*
* EEPROM driver for RAVE SP
*
* Copyright (C) 2017 Zodiac Inflight Innovations
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation; either version 2 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#include <common.h>
@@ -280,28 +268,19 @@ static int rave_sp_eeprom_access(struct rave_sp_eeprom *eeprom,
return 0;
}
-static int rave_sp_eeprom_read(struct device_d *dev, const int offset,
- void *val, int bytes)
+static int rave_sp_eeprom_read(void *ctx, unsigned offset, void *val, size_t bytes)
{
- return rave_sp_eeprom_access(dev->parent->priv,
- RAVE_SP_EEPROM_READ,
+ return rave_sp_eeprom_access(ctx, RAVE_SP_EEPROM_READ,
offset, val, bytes);
}
-static int rave_sp_eeprom_write(struct device_d *dev, const int offset,
- const void *val, int bytes)
+static int rave_sp_eeprom_write(void *ctx, unsigned offset, const void *val, size_t bytes)
{
- return rave_sp_eeprom_access(dev->parent->priv,
- RAVE_SP_EEPROM_WRITE,
+ return rave_sp_eeprom_access(ctx, RAVE_SP_EEPROM_WRITE,
offset, (void *)val, bytes);
}
-static const struct nvmem_bus rave_sp_eeprom_nvmem_bus = {
- .write = rave_sp_eeprom_write,
- .read = rave_sp_eeprom_read,
-};
-
-static int rave_sp_eeprom_probe(struct device_d *dev)
+static int rave_sp_eeprom_probe(struct device *dev)
{
struct rave_sp *sp = dev->parent->priv;
struct nvmem_config config = { 0 };
@@ -309,7 +288,7 @@ static int rave_sp_eeprom_probe(struct device_d *dev)
struct nvmem_device *nvmem;
u32 reg[2], size;
- if (of_property_read_u32_array(dev->device_node,
+ if (of_property_read_u32_array(dev->of_node,
"reg", reg, ARRAY_SIZE(reg))) {
dev_err(dev, "Failed to parse \"reg\" property\n");
return -EINVAL;
@@ -329,8 +308,6 @@ static int rave_sp_eeprom_probe(struct device_d *dev)
eeprom->address = reg[0];
eeprom->sp = sp;
- dev->priv = eeprom;
-
if (size > SZ_8K)
eeprom->header_size = RAVE_SP_EEPROM_HEADER_BIG;
else
@@ -340,13 +317,15 @@ static int rave_sp_eeprom_probe(struct device_d *dev)
/*
* If a name is specified via DT, override the above with it.
*/
- of_property_read_string(dev->device_node, "zii,eeprom-name",
+ of_property_read_string(dev->of_node, "zii,eeprom-name",
&config.name);
config.dev = dev;
+ config.priv = eeprom;
config.word_size = 1;
config.stride = 1;
config.size = reg[1];
- config.bus = &rave_sp_eeprom_nvmem_bus;
+ config.reg_write = rave_sp_eeprom_write;
+ config.reg_read = rave_sp_eeprom_read;
nvmem = nvmem_register(&config);
if (IS_ERR(nvmem)) {
@@ -361,8 +340,9 @@ static __maybe_unused const struct of_device_id rave_sp_eeprom_of_match[] = {
{ .compatible = "zii,rave-sp-eeprom" },
{}
};
+MODULE_DEVICE_TABLE(of, rave_sp_eeprom_of_match);
-static struct driver_d rave_sp_eeprom_driver = {
+static struct driver rave_sp_eeprom_driver = {
.name = "rave-sp-eeprom",
.probe = rave_sp_eeprom_probe,
.of_compatible = DRV_OF_COMPAT(rave_sp_eeprom_of_match),
diff --git a/drivers/nvmem/regmap.c b/drivers/nvmem/regmap.c
new file mode 100644
index 0000000000..24712fbb0f
--- /dev/null
+++ b/drivers/nvmem/regmap.c
@@ -0,0 +1,92 @@
+// SPDX-License-Identifier: GPL-2.0-only
+#include <common.h>
+#include <driver.h>
+#include <malloc.h>
+#include <xfuncs.h>
+#include <errno.h>
+#include <init.h>
+#include <io.h>
+#include <linux/regmap.h>
+#include <linux/nvmem-provider.h>
+
+static int nvmem_regmap_write(void *ctx, unsigned offset, const void *val, size_t bytes)
+{
+ struct regmap *map = ctx;
+
+ /*
+ * eFuse writes going through this function may be irreversible,
+ * so expect users to observe alignment.
+ */
+ if (bytes % regmap_get_val_bytes(map))
+ return -EINVAL;
+
+ return regmap_bulk_write(map, offset, val,
+ bytes / regmap_get_val_bytes(map));
+}
+
+static int nvmem_regmap_read(void *ctx, unsigned offset, void *buf, size_t bytes)
+{
+ struct regmap *map = ctx;
+ size_t rbytes, stride, skip_bytes;
+ u32 roffset, val;
+ u8 *buf8 = buf, *val8 = (u8 *)&val;
+ int i, j = 0, ret, size;
+
+ stride = regmap_get_reg_stride(map);
+
+ roffset = rounddown(offset, stride);
+ skip_bytes = offset & (stride - 1);
+ rbytes = roundup(bytes + skip_bytes, stride);
+
+ if (roffset + rbytes > regmap_size_bytes(map))
+ return -EINVAL;
+
+ for (i = roffset; i < roffset + rbytes; i += stride) {
+ ret = regmap_read(map, i, &val);
+ if (ret) {
+ dev_err(regmap_get_device(map), "Can't read data%d (%d)\n", i, ret);
+ return ret;
+ }
+
+ /* skip first bytes in case of unaligned read */
+ if (skip_bytes)
+ size = min(bytes, stride - skip_bytes);
+ else
+ size = min(bytes, stride);
+
+ memcpy(&buf8[j], &val8[skip_bytes], size);
+ bytes -= size;
+ j += size;
+ skip_bytes = 0;
+ }
+
+ return 0;
+}
+
+struct nvmem_device *
+nvmem_regmap_register_with_pp(struct regmap *map, const char *name,
+ nvmem_cell_post_process_t cell_post_process)
+{
+ struct nvmem_config config = {};
+
+ /* Can be retrofitted if needed */
+ if (regmap_get_reg_stride(map) != regmap_get_val_bytes(map))
+ return ERR_PTR(-EINVAL);
+
+ config.name = name;
+ config.dev = regmap_get_device(map);
+ config.priv = map;
+ config.stride = 1;
+ config.word_size = 1;
+ config.size = regmap_size_bytes(map);
+ config.cell_post_process = cell_post_process;
+ config.reg_write = nvmem_regmap_write;
+ config.reg_read = nvmem_regmap_read;
+
+ return nvmem_register(&config);
+}
+
+struct nvmem_device *nvmem_regmap_register(struct regmap *map, const char *name)
+{
+ return nvmem_regmap_register_with_pp(map, name, NULL);
+}
diff --git a/drivers/nvmem/rmem.c b/drivers/nvmem/rmem.c
new file mode 100644
index 0000000000..afa0dd78c8
--- /dev/null
+++ b/drivers/nvmem/rmem.c
@@ -0,0 +1,64 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2020 Nicolas Saenz Julienne <nsaenzjulienne@suse.de>
+ */
+
+#include <io.h>
+#include <driver.h>
+#include <linux/nvmem-provider.h>
+#include <init.h>
+
+struct rmem {
+ struct device *dev;
+ const struct resource *mem;
+};
+
+static int rmem_read(void *context, unsigned int offset,
+ void *val, size_t bytes)
+{
+ struct rmem *rmem = context;
+ return mem_copy(rmem->dev, val, (void *)rmem->mem->start + offset,
+ bytes, offset, 0);
+}
+
+static int rmem_probe(struct device *dev)
+{
+ struct nvmem_config config = { };
+ struct resource *mem;
+ struct rmem *priv;
+
+ mem = dev_get_resource(dev, IORESOURCE_MEM, 0);
+ if (IS_ERR(mem))
+ return PTR_ERR(mem);
+
+ priv = kzalloc(sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->mem = mem;
+
+ config.dev = priv->dev = dev;
+ config.priv = priv;
+ config.name = "rmem";
+ config.size = resource_size(mem);
+ config.reg_read = rmem_read;
+
+ return PTR_ERR_OR_ZERO(nvmem_register(&config));
+}
+
+static const struct of_device_id rmem_match[] = {
+ { .compatible = "nvmem-rmem", },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, rmem_match);
+
+static struct driver rmem_driver = {
+ .name = "rmem",
+ .of_compatible = rmem_match,
+ .probe = rmem_probe,
+};
+device_platform_driver(rmem_driver);
+
+MODULE_AUTHOR("Nicolas Saenz Julienne <nsaenzjulienne@suse.de>");
+MODULE_DESCRIPTION("Reserved Memory Based nvmem Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/nvmem/rockchip-otp.c b/drivers/nvmem/rockchip-otp.c
new file mode 100644
index 0000000000..b8da4c5380
--- /dev/null
+++ b/drivers/nvmem/rockchip-otp.c
@@ -0,0 +1,448 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Rockchip OTP Driver
+ *
+ * Copyright (c) 2018 Rockchip Electronics Co. Ltd.
+ * Author: Finley Xiao <finley.xiao@rock-chips.com>
+ */
+
+#include <common.h>
+#include <driver.h>
+#include <init.h>
+#include <of.h>
+#include <of_device.h>
+#include <linux/clk.h>
+#include <linux/iopoll.h>
+#include <linux/nvmem-provider.h>
+#include <linux/reset.h>
+
+/* OTP Register Offsets */
+#define OTPC_SBPI_CTRL 0x0020
+#define OTPC_SBPI_CMD_VALID_PRE 0x0024
+#define OTPC_SBPI_CS_VALID_PRE 0x0028
+#define OTPC_SBPI_STATUS 0x002C
+#define OTPC_USER_CTRL 0x0100
+#define OTPC_USER_ADDR 0x0104
+#define OTPC_USER_ENABLE 0x0108
+#define OTPC_USER_Q 0x0124
+#define OTPC_INT_STATUS 0x0304
+#define OTPC_SBPI_CMD0_OFFSET 0x1000
+#define OTPC_SBPI_CMD1_OFFSET 0x1004
+
+/* OTP Register bits and masks */
+#define OTPC_USER_ADDR_MASK GENMASK(31, 16)
+#define OTPC_USE_USER BIT(0)
+#define OTPC_USE_USER_MASK GENMASK(16, 16)
+#define OTPC_USER_FSM_ENABLE BIT(0)
+#define OTPC_USER_FSM_ENABLE_MASK GENMASK(16, 16)
+#define OTPC_SBPI_DONE BIT(1)
+#define OTPC_USER_DONE BIT(2)
+
+#define SBPI_DAP_ADDR 0x02
+#define SBPI_DAP_ADDR_SHIFT 8
+#define SBPI_DAP_ADDR_MASK GENMASK(31, 24)
+#define SBPI_CMD_VALID_MASK GENMASK(31, 16)
+#define SBPI_DAP_CMD_WRF 0xC0
+#define SBPI_DAP_REG_ECC 0x3A
+#define SBPI_ECC_ENABLE 0x00
+#define SBPI_ECC_DISABLE 0x09
+#define SBPI_ENABLE BIT(0)
+#define SBPI_ENABLE_MASK GENMASK(16, 16)
+
+#define OTPC_TIMEOUT 10000
+
+#define RK3568_NBYTES 2
+
+/* RK3588 Register */
+#define RK3588_OTPC_AUTO_CTRL 0x04
+#define RK3588_OTPC_AUTO_EN 0x08
+#define RK3588_OTPC_INT_ST 0x84
+#define RK3588_OTPC_DOUT0 0x20
+#define RK3588_NO_SECURE_OFFSET 0x300
+#define RK3588_NBYTES 4
+#define RK3588_BURST_NUM 1
+#define RK3588_BURST_SHIFT 8
+#define RK3588_ADDR_SHIFT 16
+#define RK3588_AUTO_EN BIT(0)
+#define RK3588_RD_DONE BIT(1)
+
+struct rockchip_data {
+ int size;
+ const char * const *clks;
+ int num_clks;
+ int (*reg_read)(void *ctx, unsigned int reg, void *val, size_t val_size);
+};
+
+struct rockchip_otp {
+ struct device *dev;
+ void __iomem *base;
+ struct clk_bulk_data *clks;
+ struct reset_control *rst;
+ const struct rockchip_data *data;
+};
+
+static int rockchip_otp_reset(struct rockchip_otp *otp)
+{
+ int ret;
+
+ ret = reset_control_assert(otp->rst);
+ if (ret) {
+ dev_err(otp->dev, "failed to assert otp phy %d\n", ret);
+ return ret;
+ }
+
+ udelay(2);
+
+ ret = reset_control_deassert(otp->rst);
+ if (ret) {
+ dev_err(otp->dev, "failed to deassert otp phy %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int rockchip_otp_wait_status(struct rockchip_otp *otp,
+ unsigned int reg, u32 flag)
+{
+ u32 status = 0;
+ int ret;
+
+ ret = readl_poll_timeout(otp->base + reg, status,
+ (status & flag), OTPC_TIMEOUT);
+ if (ret)
+ return ret;
+
+ /* clean int status */
+ writel(flag, otp->base + reg);
+
+ return 0;
+}
+
+static int rockchip_otp_ecc_enable(struct rockchip_otp *otp, bool enable)
+{
+ int ret = 0;
+
+ writel(SBPI_DAP_ADDR_MASK | (SBPI_DAP_ADDR << SBPI_DAP_ADDR_SHIFT),
+ otp->base + OTPC_SBPI_CTRL);
+
+ writel(SBPI_CMD_VALID_MASK | 0x1, otp->base + OTPC_SBPI_CMD_VALID_PRE);
+ writel(SBPI_DAP_CMD_WRF | SBPI_DAP_REG_ECC,
+ otp->base + OTPC_SBPI_CMD0_OFFSET);
+ if (enable)
+ writel(SBPI_ECC_ENABLE, otp->base + OTPC_SBPI_CMD1_OFFSET);
+ else
+ writel(SBPI_ECC_DISABLE, otp->base + OTPC_SBPI_CMD1_OFFSET);
+
+ writel(SBPI_ENABLE_MASK | SBPI_ENABLE, otp->base + OTPC_SBPI_CTRL);
+
+ ret = rockchip_otp_wait_status(otp, OTPC_INT_STATUS, OTPC_SBPI_DONE);
+ if (ret < 0)
+ dev_err(otp->dev, "timeout during ecc_enable\n");
+
+ return ret;
+}
+
+static int px30_otp_read(void *context, unsigned int offset,
+ void *val, size_t bytes)
+{
+ struct rockchip_otp *otp = context;
+ u8 *buf = val;
+ int ret;
+
+ ret = rockchip_otp_reset(otp);
+ if (ret) {
+ dev_err(otp->dev, "failed to reset otp phy\n");
+ return ret;
+ }
+
+ ret = rockchip_otp_ecc_enable(otp, false);
+ if (ret < 0) {
+ dev_err(otp->dev, "rockchip_otp_ecc_enable err\n");
+ return ret;
+ }
+
+ writel(OTPC_USE_USER | OTPC_USE_USER_MASK, otp->base + OTPC_USER_CTRL);
+ udelay(5);
+ while (bytes--) {
+ writel(offset++ | OTPC_USER_ADDR_MASK,
+ otp->base + OTPC_USER_ADDR);
+ writel(OTPC_USER_FSM_ENABLE | OTPC_USER_FSM_ENABLE_MASK,
+ otp->base + OTPC_USER_ENABLE);
+ ret = rockchip_otp_wait_status(otp, OTPC_INT_STATUS, OTPC_USER_DONE);
+ if (ret < 0) {
+ dev_err(otp->dev, "timeout during read setup\n");
+ goto read_end;
+ }
+ *buf++ = readb(otp->base + OTPC_USER_Q);
+ }
+
+read_end:
+ writel(0x0 | OTPC_USE_USER_MASK, otp->base + OTPC_USER_CTRL);
+
+ return ret;
+}
+
+static int rk3568_otp_read(void *context, unsigned int offset, void *val,
+ size_t bytes)
+{
+ struct rockchip_otp *otp = context;
+ unsigned int addr_start, addr_end, addr_offset, addr_len;
+ u32 out_value;
+ u8 *buf;
+ int ret = 0, i = 0;
+
+ addr_start = rounddown(offset, RK3568_NBYTES) / RK3568_NBYTES;
+ addr_end = roundup(offset + bytes, RK3568_NBYTES) / RK3568_NBYTES;
+ addr_offset = offset % RK3568_NBYTES;
+ addr_len = addr_end - addr_start;
+
+ buf = kzalloc(array3_size(addr_len, RK3568_NBYTES, sizeof(*buf)),
+ GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ ret = rockchip_otp_reset(otp);
+ if (ret) {
+ dev_err(otp->dev, "failed to reset otp phy\n");
+ goto out;
+ }
+
+ ret = rockchip_otp_ecc_enable(otp, false);
+ if (ret < 0) {
+ dev_err(otp->dev, "rockchip_otp_ecc_enable err\n");
+ goto out;
+ }
+
+ writel(OTPC_USE_USER | OTPC_USE_USER_MASK, otp->base + OTPC_USER_CTRL);
+ udelay(5);
+ while (addr_len--) {
+ writel(addr_start++ | OTPC_USER_ADDR_MASK,
+ otp->base + OTPC_USER_ADDR);
+ writel(OTPC_USER_FSM_ENABLE | OTPC_USER_FSM_ENABLE_MASK,
+ otp->base + OTPC_USER_ENABLE);
+ ret = rockchip_otp_wait_status(otp, OTPC_INT_STATUS, OTPC_USER_DONE);
+ if (ret < 0) {
+ dev_err(otp->dev, "timeout during read setup\n");
+ goto read_end;
+ }
+ out_value = readl(otp->base + OTPC_USER_Q);
+ memcpy(&buf[i], &out_value, RK3568_NBYTES);
+ i += RK3568_NBYTES;
+ }
+
+ memcpy(val, buf + addr_offset, bytes);
+
+read_end:
+ writel(0x0 | OTPC_USE_USER_MASK, otp->base + OTPC_USER_CTRL);
+out:
+ kfree(buf);
+
+ return ret;
+}
+
+static int rk3588_otp_read(void *context, unsigned int offset,
+ void *val, size_t bytes)
+{
+ struct rockchip_otp *otp = context;
+ unsigned int addr_start, addr_end, addr_len;
+ int ret = 0, i = 0;
+ u32 data;
+ u8 *buf;
+
+ addr_start = round_down(offset, RK3588_NBYTES) / RK3588_NBYTES;
+ addr_end = round_up(offset + bytes, RK3588_NBYTES) / RK3588_NBYTES;
+ addr_len = addr_end - addr_start;
+ addr_start += RK3588_NO_SECURE_OFFSET;
+
+ buf = kzalloc(array_size(addr_len, RK3588_NBYTES), GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ while (addr_len--) {
+ writel((addr_start << RK3588_ADDR_SHIFT) |
+ (RK3588_BURST_NUM << RK3588_BURST_SHIFT),
+ otp->base + RK3588_OTPC_AUTO_CTRL);
+ writel(RK3588_AUTO_EN, otp->base + RK3588_OTPC_AUTO_EN);
+
+ ret = rockchip_otp_wait_status(otp, RK3588_OTPC_INT_ST,
+ RK3588_RD_DONE);
+ if (ret < 0) {
+ dev_err(otp->dev, "timeout during read setup\n");
+ goto read_end;
+ }
+
+ data = readl(otp->base + RK3588_OTPC_DOUT0);
+ memcpy(&buf[i], &data, RK3588_NBYTES);
+
+ i += RK3588_NBYTES;
+ addr_start++;
+ }
+
+ memcpy(val, buf + offset % RK3588_NBYTES, bytes);
+
+read_end:
+ kfree(buf);
+
+ return ret;
+}
+
+static int rockchip_otp_read(void *context, unsigned int offset,
+ void *val, size_t bytes)
+{
+ struct rockchip_otp *otp = context;
+ int ret;
+
+ if (!otp->data || !otp->data->reg_read)
+ return -EINVAL;
+
+ ret = clk_bulk_enable(otp->data->num_clks, otp->clks);
+ if (ret < 0) {
+ dev_err(otp->dev, "failed to prepare/enable clks\n");
+ return ret;
+ }
+
+ ret = otp->data->reg_read(context, offset, val, bytes);
+
+ clk_bulk_disable(otp->data->num_clks, otp->clks);
+
+ return ret;
+}
+
+static struct nvmem_config otp_config = {
+ .name = "rockchip-otp",
+ .read_only = true,
+ .stride = 1,
+ .word_size = 1,
+ .reg_read = rockchip_otp_read,
+};
+
+static const char * const px30_otp_clocks[] = {
+ "otp", "apb_pclk", "phy",
+};
+
+static const struct rockchip_data px30_data = {
+ .size = 0x40,
+ .clks = px30_otp_clocks,
+ .num_clks = ARRAY_SIZE(px30_otp_clocks),
+ .reg_read = px30_otp_read,
+};
+
+static const char * const rk3568_otp_clocks[] = {
+ "usr", "sbpi", "apb", "phy",
+};
+
+static const struct rockchip_data rk3568_data = {
+ .size = 0x80,
+ .clks = rk3568_otp_clocks,
+ .num_clks = ARRAY_SIZE(rk3568_otp_clocks),
+ .reg_read = rk3568_otp_read,
+};
+
+static const char * const rk3588_otp_clocks[] = {
+ "otp", "apb_pclk", "phy", "arb",
+};
+
+static const struct rockchip_data rk3588_data = {
+ .size = 0x400,
+ .clks = rk3588_otp_clocks,
+ .num_clks = ARRAY_SIZE(rk3588_otp_clocks),
+ .reg_read = rk3588_otp_read,
+};
+
+static __maybe_unused const struct of_device_id rockchip_otp_match[] = {
+ {
+ .compatible = "rockchip,px30-otp",
+ .data = &px30_data,
+ },
+ {
+ .compatible = "rockchip,rk3308-otp",
+ .data = &px30_data,
+ },
+ {
+ .compatible = "rockchip,rk3568-otp",
+ .data = &rk3568_data,
+ },
+ {
+ .compatible = "rockchip,rk3588-otp",
+ .data = &rk3588_data,
+ },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, rockchip_otp_match);
+
+static int rockchip_otp_probe(struct device *dev)
+{
+ struct rockchip_otp *otp;
+ const struct rockchip_data *data;
+ struct nvmem_device *nvmem;
+ struct resource *res;
+ int ret, i;
+
+ data = of_device_get_match_data(dev);
+ if (!data)
+ return dev_err_probe(dev, -EINVAL, "failed to get match data\n");
+
+ otp = kzalloc(sizeof(*otp), GFP_KERNEL);
+ if (!otp)
+ return -ENOMEM;
+
+ res = dev_request_mem_resource(dev, 0);
+ if (IS_ERR(res)) {
+ ret = PTR_ERR(res);
+ goto err_free;
+ }
+
+ otp->data = data;
+ otp->dev = dev;
+ otp->base = IOMEM(res->start);
+
+ otp->clks = kcalloc(data->num_clks, sizeof(*otp->clks), GFP_KERNEL);
+ if (!otp->clks) {
+ ret = -ENOMEM;
+ goto err_free;
+ }
+
+ for (i = 0; i < data->num_clks; ++i)
+ otp->clks[i].id = data->clks[i];
+
+ ret = clk_bulk_get_optional(dev, data->num_clks, otp->clks);
+ if (ret)
+ goto err_clk;
+
+ otp->rst = reset_control_get(dev, NULL);
+ if (IS_ERR(otp->rst)) {
+ ret = PTR_ERR(otp->rst);
+ goto err_rst;
+ }
+
+ otp_config.size = data->size;
+ otp_config.priv = otp;
+ otp_config.dev = dev;
+
+ nvmem = nvmem_register(&otp_config);
+ if (!IS_ERR(nvmem))
+ return 0;
+
+ ret = PTR_ERR(nvmem);
+
+ reset_control_put(otp->rst);
+
+err_rst:
+ clk_bulk_put_all(data->num_clks, otp->clks);
+
+err_clk:
+ kfree(otp->clks);
+
+err_free:
+ kfree(otp);
+
+ return ret;
+}
+
+static struct driver rockchip_otp_driver = {
+ .name = "rockchip-otp",
+ .probe = rockchip_otp_probe,
+ .of_compatible = DRV_OF_COMPAT(rockchip_otp_match),
+};
+device_platform_driver(rockchip_otp_driver);
diff --git a/drivers/nvmem/snvs_lpgpr.c b/drivers/nvmem/snvs_lpgpr.c
index fe7fe599f6..9bbee6d587 100644
--- a/drivers/nvmem/snvs_lpgpr.c
+++ b/drivers/nvmem/snvs_lpgpr.c
@@ -1,10 +1,7 @@
+// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2015 Pengutronix, Steffen Trumtrar <kernel@pengutronix.de>
* Copyright (c) 2017 Pengutronix, Oleksij Rempel <kernel@pengutronix.de>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2
- * as published by the Free Software Foundation.
*/
#include <common.h>
#include <driver.h>
@@ -13,7 +10,7 @@
#include <of.h>
#include <of_device.h>
#include <malloc.h>
-#include <regmap.h>
+#include <linux/regmap.h>
#include <mfd/syscon.h>
#include <linux/nvmem-provider.h>
@@ -30,7 +27,7 @@ struct snvs_lpgpr_cfg {
};
struct snvs_lpgpr_priv {
- struct device_d *dev;
+ struct device *dev;
struct regmap *regmap;
struct nvmem_config cfg;
const struct snvs_lpgpr_cfg *dcfg;
@@ -42,10 +39,9 @@ static const struct snvs_lpgpr_cfg snvs_lpgpr_cfg_imx6q = {
.offset_lplr = IMX6Q_SNVS_LPLR,
};
-static int snvs_lpgpr_write(struct device_d *dev, const int offset,
- const void *val, int bytes)
+static int snvs_lpgpr_write(void *ctx, unsigned offset, const void *val, size_t bytes)
{
- struct snvs_lpgpr_priv *priv = dev->parent->priv;
+ struct snvs_lpgpr_priv *priv = ctx;
const struct snvs_lpgpr_cfg *dcfg = priv->dcfg;
unsigned int lock_reg;
int ret;
@@ -65,27 +61,21 @@ static int snvs_lpgpr_write(struct device_d *dev, const int offset,
return -EPERM;
return regmap_bulk_write(priv->regmap, dcfg->offset + offset, val,
- bytes);
+ bytes / 4);
}
-static int snvs_lpgpr_read(struct device_d *dev, const int offset, void *val,
- int bytes)
+static int snvs_lpgpr_read(void *ctx, unsigned offset, void *val, size_t bytes)
{
- struct snvs_lpgpr_priv *priv = dev->parent->priv;
+ struct snvs_lpgpr_priv *priv = ctx;
const struct snvs_lpgpr_cfg *dcfg = priv->dcfg;
return regmap_bulk_read(priv->regmap, dcfg->offset + offset,
- val, bytes);
+ val, bytes / 4);
}
-static const struct nvmem_bus snvs_lpgpr_nvmem_bus = {
- .write = snvs_lpgpr_write,
- .read = snvs_lpgpr_read,
-};
-
-static int snvs_lpgpr_probe(struct device_d *dev)
+static int snvs_lpgpr_probe(struct device *dev)
{
- struct device_node *node = dev->device_node;
+ struct device_node *node = dev->of_node;
struct device_node *syscon_node;
struct snvs_lpgpr_priv *priv;
struct nvmem_config *cfg;
@@ -113,10 +103,12 @@ static int snvs_lpgpr_probe(struct device_d *dev)
cfg = &priv->cfg;
cfg->name = dev_name(dev);
cfg->dev = dev;
+ cfg->priv = priv;
cfg->stride = 4;
cfg->word_size = 4;
cfg->size = 4;
- cfg->bus = &snvs_lpgpr_nvmem_bus;
+ cfg->reg_write = snvs_lpgpr_write;
+ cfg->reg_read = snvs_lpgpr_read;
nvmem = nvmem_register(cfg);
if (IS_ERR(nvmem)) {
@@ -124,8 +116,6 @@ static int snvs_lpgpr_probe(struct device_d *dev)
return PTR_ERR(nvmem);
}
- dev->priv = priv;
-
return 0;
}
@@ -134,8 +124,9 @@ static __maybe_unused struct of_device_id snvs_lpgpr_dt_ids[] = {
{ .compatible = "fsl,imx6ul-snvs-lpgpr", .data = &snvs_lpgpr_cfg_imx6q },
{ },
};
+MODULE_DEVICE_TABLE(of, snvs_lpgpr_dt_ids);
-static struct driver_d snvs_lpgpr_driver = {
+static struct driver snvs_lpgpr_driver = {
.name = "snvs_lpgpr",
.probe = snvs_lpgpr_probe,
.of_compatible = DRV_OF_COMPAT(snvs_lpgpr_dt_ids),
diff --git a/drivers/nvmem/starfive-otp.c b/drivers/nvmem/starfive-otp.c
new file mode 100644
index 0000000000..47b94b1399
--- /dev/null
+++ b/drivers/nvmem/starfive-otp.c
@@ -0,0 +1,202 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright 2021 StarFive, Inc
+ */
+
+#include <common.h>
+#include <driver.h>
+#include <malloc.h>
+#include <xfuncs.h>
+#include <errno.h>
+#include <linux/gpio/consumer.h>
+#include <init.h>
+#include <net.h>
+#include <io.h>
+#include <of.h>
+#include <linux/regmap.h>
+#include <machine_id.h>
+#include <linux/reset.h>
+#include <linux/clk.h>
+#include <linux/nvmem-provider.h>
+
+// otp reg offset
+#define OTP_CFGR 0x00
+#define OTPC_IER 0x04
+#define OTPC_SRR 0x08
+#define OTP_OPRR 0x0c
+#define OTPC_CTLR 0x10
+#define OTPC_ADDRR 0x14
+#define OTPC_DINR 0x18
+#define OTPC_DOUTR 0x1c
+
+#define OTP_EMPTY_CELL_VALUE 0xffffffffUL
+
+// cfgr (offset 0x00)
+#define OTP_CFGR_PRG_CNT_MASK 0xff
+#define OTP_CFGR_PRG_CNT_SHIFT 0
+#define OTP_CFGR_DIV_1US_MASK 0xff
+#define OTP_CFGR_DIV_1US_SHIFT 8
+#define OTP_CFGR_RD_CYC_MASK 0x0f
+#define OTP_CFGR_RD_CYC_SHIFT 16
+
+// ier (offset 0x04)
+#define OTPC_IER_DONE_IE BIT(0)
+#define OTPC_IER_BUSY_OPR_IE BIT(1)
+
+// srr (offset 0x08)
+#define OTPC_SRR_DONE BIT(0)
+#define OTPC_SRR_BUSY_OPR BIT(1)
+#define OTPC_SRR_INFO_RD_LOCK BIT(29)
+#define OTPC_SRR_INFO_WR_LOCK BIT(30)
+#define OTPC_SRR_BUSY BIT(31)
+
+// oprr (offset 0x0c)
+#define OTP_OPRR_OPR_MASK 0x00000007
+#define OTP_OPRR_OPR_SHIFT 0
+
+#define OTP_OPR_STANDBY 0x0 // user mode
+#define OTP_OPR_READ 0x1 // user mode
+#define OTP_OPR_MARGIN_READ_PROG 0x2 // testing mode
+#define OTP_OPR_MARGIN_READ_INIT 0x3 // testing mode
+#define OTP_OPR_PROGRAM 0x4 // user mode
+#define OTP_OPR_DEEP_STANDBY 0x5 // user mode
+#define OTP_OPR_DEBUG 0x6 // user mode
+
+// ctlr (offset 0x10, see EG512X32TH028CW01_v1.0.pdf "Pin Description")
+#define OTPC_CTLR_PCE BIT(0)
+#define OTPC_CTLR_PTM_MASK 0x0000000e
+#define OTPC_CTLR_PTM_SHIFT 1
+#define OTPC_CTLR_PDSTB BIT(4)
+#define OTPC_CTLR_PTR BIT(5)
+#define OTPC_CTLR_PPROG BIT(6)
+#define OTPC_CTLR_PWE BIT(7)
+#define OTPC_CTLR_PCLK BIT(8)
+
+// addrr (offset 0x14)
+#define OTPC_ADDRR_PA_MASK 0x000001ff
+#define OTPC_ADDRR_PA_SHIFT 0
+
+/*
+ * data format:
+ * struct starfive_otp_data{
+ * char vendor[32];
+ * uint64_t sn;
+ * uint8_t mac_addr[6];
+ * uint8_t padding_0[2];
+ * }
+ */
+
+struct starfive_otp {
+ struct gpio_desc *power_gpio;
+ struct starfive_otp_regs __iomem *regs;
+};
+
+struct starfive_otp_regs {
+ /* TODO: add otp ememory_eg512x32 registers define */
+ u32 otp_cfg; /* timing Register */
+ u32 otpc_ie; /* interrupt Enable */
+ u32 otpc_sr; /* status Register */
+ u32 otp_opr; /* operation mode select Register */
+ u32 otpc_ctl; /* otp control port */
+ u32 otpc_addr; /* otp pa port */
+ u32 otpc_din; /* otp pdin port */
+ u32 otpc_dout; /* otp pdout */
+ u32 reserved[504];
+ u32 mem[512];
+};
+
+/*
+ * offset and size are assumed aligned to the size of the fuses (32-bit).
+ */
+static int starfive_otp_read(void *ctx, unsigned offset, unsigned *val)
+{
+ struct starfive_otp *priv = ctx;
+
+ gpiod_set_value(priv->power_gpio, true);
+ mdelay(10);
+
+ //otp set to read mode
+ writel(OTP_OPR_READ, &priv->regs->otp_opr);
+ mdelay(5);
+
+ /* read all requested fuses */
+ *val = readl(&priv->regs->mem[offset / 4]);
+
+ gpiod_set_value(priv->power_gpio, false);
+ mdelay(5);
+
+ return 0;
+}
+
+static int starfive_otp_write(void *ctx, unsigned offset, unsigned val)
+{
+ return -EOPNOTSUPP;
+}
+
+static struct regmap_bus starfive_otp_regmap_bus = {
+ .reg_read = starfive_otp_read,
+ .reg_write = starfive_otp_write,
+};
+
+static int starfive_otp_probe(struct device *dev)
+{
+ struct starfive_otp *priv;
+ struct regmap_config config = {};
+ struct resource *iores;
+ struct regmap *map;
+ struct clk *clk;
+ u32 total_fuses;
+ int ret;
+
+ clk = clk_get(dev, NULL);
+ if (IS_ERR(clk))
+ return PTR_ERR(clk);
+
+ clk_enable(clk);
+
+ ret = device_reset(dev);
+ if (ret)
+ return ret;
+
+ iores = dev_request_mem_resource(dev, 0);
+ if (IS_ERR(iores))
+ return PTR_ERR(iores);
+
+ ret = of_property_read_u32(dev->of_node, "fuse-count", &total_fuses);
+ if (ret < 0) {
+ dev_err(dev, "missing required fuse-count property\n");
+ return ret;
+ }
+
+ config.name = "starfive-otp";
+ config.reg_bits = 32;
+ config.val_bits = 32;
+ config.reg_stride = 4;
+ config.max_register = (total_fuses - 1) * config.reg_stride;
+
+ priv = xzalloc(sizeof(*priv));
+
+ priv->regs = IOMEM(iores->start);
+ priv->power_gpio = gpiod_get(dev, "power", GPIOD_OUT_LOW);
+ if (IS_ERR(priv->power_gpio))
+ return PTR_ERR(priv->power_gpio);
+
+ map = regmap_init(dev, &starfive_otp_regmap_bus, priv, &config);
+ if (IS_ERR(map))
+ return PTR_ERR(map);
+
+ return PTR_ERR_OR_ZERO(nvmem_regmap_register(map, "starfive-otp"));
+}
+
+static struct of_device_id starfive_otp_dt_ids[] = {
+ { .compatible = "starfive,fu740-otp" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, starfive_otp_dt_ids);
+
+static struct driver starfive_otp_driver = {
+ .name = "starfive_otp",
+ .probe = starfive_otp_probe,
+ .of_compatible = starfive_otp_dt_ids,
+};
+device_platform_driver(starfive_otp_driver);
diff --git a/drivers/nvmem/stm32-bsec-optee-ta.c b/drivers/nvmem/stm32-bsec-optee-ta.c
new file mode 100644
index 0000000000..f89ce791dd
--- /dev/null
+++ b/drivers/nvmem/stm32-bsec-optee-ta.c
@@ -0,0 +1,298 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * OP-TEE STM32MP BSEC PTA interface, used by STM32 ROMEM driver
+ *
+ * Copyright (C) 2022, STMicroelectronics - All Rights Reserved
+ */
+
+#include <linux/tee_drv.h>
+
+#include "stm32-bsec-optee-ta.h"
+
+/*
+ * Read OTP memory
+ *
+ * [in] value[0].a OTP start offset in byte
+ * [in] value[0].b Access type (0:shadow, 1:fuse, 2:lock)
+ * [out] memref[1].buffer Output buffer to store read values
+ * [out] memref[1].size Size of OTP to be read
+ *
+ * Return codes:
+ * TEE_SUCCESS - Invoke command success
+ * TEE_ERROR_BAD_PARAMETERS - Incorrect input param
+ * TEE_ERROR_ACCESS_DENIED - OTP not accessible by caller
+ */
+#define PTA_BSEC_READ_MEM 0x0
+
+/*
+ * Write OTP memory
+ *
+ * [in] value[0].a OTP start offset in byte
+ * [in] value[0].b Access type (0:shadow, 1:fuse, 2:lock)
+ * [in] memref[1].buffer Input buffer to read values
+ * [in] memref[1].size Size of OTP to be written
+ *
+ * Return codes:
+ * TEE_SUCCESS - Invoke command success
+ * TEE_ERROR_BAD_PARAMETERS - Incorrect input param
+ * TEE_ERROR_ACCESS_DENIED - OTP not accessible by caller
+ */
+#define PTA_BSEC_WRITE_MEM 0x1
+
+/* value of PTA_BSEC access type = value[in] b */
+#define SHADOW_ACCESS 0
+#define FUSE_ACCESS 1
+#define LOCK_ACCESS 2
+
+/* Bitfield definition for LOCK status */
+#define LOCK_PERM BIT(30)
+
+/* OP-TEE STM32MP BSEC TA UUID */
+static const uuid_t stm32mp_bsec_ta_uuid =
+ UUID_INIT(0x94cf71ad, 0x80e6, 0x40b5,
+ 0xa7, 0xc6, 0x3d, 0xc5, 0x01, 0xeb, 0x28, 0x03);
+
+/*
+ * Check whether this driver supports the BSEC TA in the TEE instance
+ * represented by the params (ver/data) to this function.
+ */
+static int stm32_bsec_optee_ta_match(struct tee_ioctl_version_data *ver,
+ const void *data)
+{
+ /* Currently this driver only supports GP compliant, OP-TEE based TA */
+ if ((ver->impl_id == TEE_IMPL_ID_OPTEE) &&
+ (ver->gen_caps & TEE_GEN_CAP_GP))
+ return 1;
+ else
+ return 0;
+}
+
+/* Open a session to OP-TEE for STM32MP BSEC TA */
+static int stm32_bsec_ta_open_session(struct tee_context *ctx, u32 *id)
+{
+ struct tee_ioctl_open_session_arg sess_arg;
+ int rc;
+
+ memset(&sess_arg, 0, sizeof(sess_arg));
+ export_uuid(sess_arg.uuid, &stm32mp_bsec_ta_uuid);
+ sess_arg.clnt_login = TEE_IOCTL_LOGIN_REE_KERNEL;
+ sess_arg.num_params = 0;
+
+ rc = tee_client_open_session(ctx, &sess_arg, NULL);
+ if ((rc < 0) || (sess_arg.ret != 0)) {
+ pr_err("%s: tee_client_open_session failed err:%#x, ret:%#x\n",
+ __func__, sess_arg.ret, rc);
+ if (!rc)
+ rc = -EINVAL;
+ } else {
+ *id = sess_arg.session;
+ }
+
+ return rc;
+}
+
+/* close a session to OP-TEE for STM32MP BSEC TA */
+static void stm32_bsec_ta_close_session(void *ctx, u32 id)
+{
+ tee_client_close_session(ctx, id);
+}
+
+/* stm32_bsec_optee_ta_open() - initialize the STM32MP BSEC TA */
+int stm32_bsec_optee_ta_open(struct tee_context **ctx)
+{
+ struct tee_context *tee_ctx;
+ u32 session_id;
+ int rc;
+
+ /* Open context with TEE driver */
+ tee_ctx = tee_client_open_context(NULL, stm32_bsec_optee_ta_match, NULL, NULL);
+ if (IS_ERR(tee_ctx)) {
+ rc = PTR_ERR(tee_ctx);
+ if (rc == -ENOENT)
+ return -EPROBE_DEFER;
+ pr_err("%s: tee_client_open_context failed (%d)\n", __func__, rc);
+
+ return rc;
+ }
+
+ /* Check STM32MP BSEC TA presence */
+ rc = stm32_bsec_ta_open_session(tee_ctx, &session_id);
+ if (rc) {
+ tee_client_close_context(tee_ctx);
+ return rc;
+ }
+
+ stm32_bsec_ta_close_session(tee_ctx, session_id);
+
+ *ctx = tee_ctx;
+
+ return 0;
+}
+
+/* stm32_bsec_optee_ta_open() - release the PTA STM32MP BSEC TA */
+void stm32_bsec_optee_ta_close(void *ctx)
+{
+ tee_client_close_context(ctx);
+}
+
+/* stm32_bsec_optee_ta_read() - nvmem read access using PTA client driver */
+int stm32_bsec_optee_ta_read(struct tee_context *ctx, unsigned int offset,
+ void *buf, size_t bytes)
+{
+ struct tee_shm *shm;
+ struct tee_ioctl_invoke_arg arg;
+ struct tee_param param[2];
+ u8 *shm_buf;
+ u32 start, num_bytes;
+ int ret;
+ u32 session_id;
+
+ ret = stm32_bsec_ta_open_session(ctx, &session_id);
+ if (ret)
+ return ret;
+
+ memset(&arg, 0, sizeof(arg));
+ memset(&param, 0, sizeof(param));
+
+ arg.func = PTA_BSEC_READ_MEM;
+ arg.session = session_id;
+ arg.num_params = 2;
+
+ /* align access on 32bits */
+ start = ALIGN_DOWN(offset, 4);
+ num_bytes = round_up(offset + bytes - start, 4);
+ param[0].attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT;
+ param[0].u.value.a = start;
+ param[0].u.value.b = SHADOW_ACCESS;
+
+ shm = tee_shm_alloc_kernel_buf(ctx, num_bytes);
+ if (IS_ERR(shm)) {
+ ret = PTR_ERR(shm);
+ goto out_tee_session;
+ }
+
+ param[1].attr = TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_OUTPUT;
+ param[1].u.memref.shm = shm;
+ param[1].u.memref.size = num_bytes;
+
+ ret = tee_client_invoke_func(ctx, &arg, param);
+ if (ret < 0 || arg.ret != 0) {
+ pr_err("TA_BSEC invoke failed TEE err:%#x, ret:%#x\n",
+ arg.ret, ret);
+ if (!ret)
+ ret = -EIO;
+ }
+ if (!ret) {
+ shm_buf = tee_shm_get_va(shm, 0);
+ if (IS_ERR(shm_buf)) {
+ ret = PTR_ERR(shm_buf);
+ pr_err("tee_shm_get_va failed for transmit (%d)\n", ret);
+ } else {
+ /* read data from 32 bits aligned buffer */
+ memcpy(buf, &shm_buf[offset % 4], bytes);
+ }
+ }
+
+ tee_shm_free(shm);
+
+out_tee_session:
+ stm32_bsec_ta_close_session(ctx, session_id);
+
+ return ret;
+}
+
+/* stm32_bsec_optee_ta_write() - nvmem write access using PTA client driver */
+int stm32_bsec_optee_ta_write(struct tee_context *ctx, unsigned int lower,
+ unsigned int offset, void *buf, size_t bytes)
+{ struct tee_shm *shm;
+ struct tee_ioctl_invoke_arg arg;
+ struct tee_param param[2];
+ u8 *shm_buf;
+ int ret;
+ u32 session_id;
+
+ ret = stm32_bsec_ta_open_session(ctx, &session_id);
+ if (ret)
+ return ret;
+
+ /* Allow only writing complete 32-bits aligned words */
+ if ((bytes % 4) || (offset % 4))
+ return -EINVAL;
+
+ memset(&arg, 0, sizeof(arg));
+ memset(&param, 0, sizeof(param));
+
+ arg.func = PTA_BSEC_WRITE_MEM;
+ arg.session = session_id;
+ arg.num_params = 2;
+
+ param[0].attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT;
+ param[0].u.value.a = offset;
+ param[0].u.value.b = FUSE_ACCESS;
+
+ shm = tee_shm_alloc_kernel_buf(ctx, bytes);
+ if (IS_ERR(shm)) {
+ ret = PTR_ERR(shm);
+ goto out_tee_session;
+ }
+
+ param[1].attr = TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INPUT;
+ param[1].u.memref.shm = shm;
+ param[1].u.memref.size = bytes;
+
+ shm_buf = tee_shm_get_va(shm, 0);
+ if (IS_ERR(shm_buf)) {
+ ret = PTR_ERR(shm_buf);
+ pr_err("tee_shm_get_va failed for transmit (%d)\n", ret);
+ tee_shm_free(shm);
+
+ goto out_tee_session;
+ }
+
+ memcpy(shm_buf, buf, bytes);
+
+ ret = tee_client_invoke_func(ctx, &arg, param);
+ if (ret < 0 || arg.ret != 0) {
+ pr_err("TA_BSEC invoke failed TEE err:%#x, ret:%#x\n", arg.ret, ret);
+ if (!ret)
+ ret = -EIO;
+ }
+ pr_debug("Write OTPs %d to %zu, ret=%d\n", offset / 4, (offset + bytes) / 4, ret);
+
+ /* Lock the upper OTPs with ECC protection, word programming only */
+ if (!ret && ((offset + bytes) >= (lower * 4))) {
+ u32 start, nb_lock;
+ u32 *lock = (u32 *)shm_buf;
+ int i;
+
+ /*
+ * don't lock the lower OTPs, no ECC protection and incremental
+ * bit programming, a second write is allowed
+ */
+ start = max_t(u32, offset, lower * 4);
+ nb_lock = (offset + bytes - start) / 4;
+
+ param[0].u.value.a = start;
+ param[0].u.value.b = LOCK_ACCESS;
+ param[1].u.memref.size = nb_lock * 4;
+
+ for (i = 0; i < nb_lock; i++)
+ lock[i] = LOCK_PERM;
+
+ ret = tee_client_invoke_func(ctx, &arg, param);
+ if (ret < 0 || arg.ret != 0) {
+ pr_err("TA_BSEC invoke failed TEE err:%#x, ret:%#x\n", arg.ret, ret);
+ if (!ret)
+ ret = -EIO;
+ }
+ pr_debug("Lock upper OTPs %d to %d, ret=%d\n",
+ start / 4, start / 4 + nb_lock, ret);
+ }
+
+ tee_shm_free(shm);
+
+out_tee_session:
+ stm32_bsec_ta_close_session(ctx, session_id);
+
+ return ret;
+}
diff --git a/drivers/nvmem/stm32-bsec-optee-ta.h b/drivers/nvmem/stm32-bsec-optee-ta.h
new file mode 100644
index 0000000000..7180476c40
--- /dev/null
+++ b/drivers/nvmem/stm32-bsec-optee-ta.h
@@ -0,0 +1,85 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * OP-TEE STM32MP BSEC PTA interface, used by STM32 ROMEM driver
+ *
+ * Copyright (C) 2022, STMicroelectronics - All Rights Reserved
+ */
+
+#include <asm-generic/errno.h>
+#include <linux/types.h>
+
+struct tee_context;
+
+#if IS_ENABLED(CONFIG_STM32_BSEC_OPTEE_TA)
+/**
+ * stm32_bsec_optee_ta_open() - initialize the STM32 BSEC TA
+ * @ctx: the OP-TEE context on success
+ *
+ * Return:
+ * On success, 0. On failure, -errno.
+ */
+int stm32_bsec_optee_ta_open(struct tee_context **ctx);
+
+/**
+ * stm32_bsec_optee_ta_close() - release the STM32 BSEC TA
+ * @ctx: the OP-TEE context
+ *
+ * This function used to clean the OP-TEE resources initialized in
+ * stm32_bsec_optee_ta_open(); it can be used as callback to
+ * devm_add_action_or_reset()
+ */
+void stm32_bsec_optee_ta_close(void *ctx);
+
+/**
+ * stm32_bsec_optee_ta_read() - nvmem read access using TA client driver
+ * @ctx: the OP-TEE context provided by stm32_bsec_optee_ta_open
+ * @offset: nvmem offset
+ * @buf: buffer to fill with nvem values
+ * @bytes: number of bytes to read
+ *
+ * Return:
+ * On success, 0. On failure, -errno.
+ */
+int stm32_bsec_optee_ta_read(struct tee_context *ctx, unsigned int offset,
+ void *buf, size_t bytes);
+
+/**
+ * stm32_bsec_optee_ta_write() - nvmem write access using TA client driver
+ * @ctx: the OP-TEE context provided by stm32_bsec_optee_ta_open
+ * @lower: number of lower OTP, not protected by ECC
+ * @offset: nvmem offset
+ * @buf: buffer with nvem values
+ * @bytes: number of bytes to write
+ *
+ * Return:
+ * On success, 0. On failure, -errno.
+ */
+int stm32_bsec_optee_ta_write(struct tee_context *ctx, unsigned int lower,
+ unsigned int offset, void *buf, size_t bytes);
+
+#else
+
+static inline int stm32_bsec_optee_ta_open(struct tee_context **ctx)
+{
+ return -EOPNOTSUPP;
+}
+
+static inline void stm32_bsec_optee_ta_close(void *ctx)
+{
+}
+
+static inline int stm32_bsec_optee_ta_read(struct tee_context *ctx,
+ unsigned int offset, void *buf,
+ size_t bytes)
+{
+ return -EOPNOTSUPP;
+}
+
+static inline int stm32_bsec_optee_ta_write(struct tee_context *ctx,
+ unsigned int lower,
+ unsigned int offset, void *buf,
+ size_t bytes)
+{
+ return -EOPNOTSUPP;
+}
+#endif /* CONFIG_NVMEM_STM32_BSEC_OPTEE_TA */