diff options
Diffstat (limited to 'drivers/nvmem')
-rw-r--r-- | drivers/nvmem/Kconfig | 58 | ||||
-rw-r--r-- | drivers/nvmem/Makefile | 13 | ||||
-rw-r--r-- | drivers/nvmem/bsec.c | 248 | ||||
-rw-r--r-- | drivers/nvmem/core.c | 184 | ||||
-rw-r--r-- | drivers/nvmem/eeprom_93xx46.c | 38 | ||||
-rw-r--r-- | drivers/nvmem/imx-ocotp-ele.c | 241 | ||||
-rw-r--r-- | drivers/nvmem/kvx-otp-nv.c | 95 | ||||
-rw-r--r-- | drivers/nvmem/ocotp.c | 532 | ||||
-rw-r--r-- | drivers/nvmem/partition.c | 36 | ||||
-rw-r--r-- | drivers/nvmem/rave-sp-eeprom.c | 46 | ||||
-rw-r--r-- | drivers/nvmem/regmap.c | 92 | ||||
-rw-r--r-- | drivers/nvmem/rmem.c | 64 | ||||
-rw-r--r-- | drivers/nvmem/rockchip-otp.c | 448 | ||||
-rw-r--r-- | drivers/nvmem/snvs_lpgpr.c | 41 | ||||
-rw-r--r-- | drivers/nvmem/starfive-otp.c | 202 | ||||
-rw-r--r-- | drivers/nvmem/stm32-bsec-optee-ta.c | 298 | ||||
-rw-r--r-- | drivers/nvmem/stm32-bsec-optee-ta.h | 85 |
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(¶m, 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(¶m, 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 */ |