diff options
Diffstat (limited to 'drivers/nvmem')
-rw-r--r-- | drivers/nvmem/Kconfig | 24 | ||||
-rw-r--r-- | drivers/nvmem/Makefile | 4 | ||||
-rw-r--r-- | drivers/nvmem/brcm_nvram.c | 90 | ||||
-rw-r--r-- | drivers/nvmem/core.c | 47 | ||||
-rw-r--r-- | drivers/nvmem/layerscape-sfp.c | 89 | ||||
-rw-r--r-- | drivers/nvmem/meson-mx-efuse.c | 3 | ||||
-rw-r--r-- | drivers/nvmem/qfprom.c | 4 | ||||
-rw-r--r-- | drivers/nvmem/sunplus-ocotp.c | 228 | ||||
-rw-r--r-- | drivers/nvmem/sunxi_sid.c | 6 |
9 files changed, 455 insertions, 40 deletions
diff --git a/drivers/nvmem/Kconfig b/drivers/nvmem/Kconfig index da414617a54d..555aa77a574d 100644 --- a/drivers/nvmem/Kconfig +++ b/drivers/nvmem/Kconfig @@ -300,4 +300,28 @@ config NVMEM_BRCM_NVRAM This driver provides support for Broadcom's NVRAM that can be accessed using I/O mapping. +config NVMEM_LAYERSCAPE_SFP + tristate "Layerscape SFP (Security Fuse Processor) support" + depends on ARCH_LAYERSCAPE || COMPILE_TEST + depends on HAS_IOMEM + help + This driver provides support to read the eFuses on Freescale + Layerscape SoC's. For example, the vendor provides a per part + unique ID there. + + This driver can also be built as a module. If so, the module + will be called layerscape-sfp. + +config NVMEM_SUNPLUS_OCOTP + tristate "Sunplus SoC OTP support" + depends on SOC_SP7021 || COMPILE_TEST + depends on HAS_IOMEM + help + This is a driver for the On-chip OTP controller (OCOTP) available + on Sunplus SoCs. It provides access to 128 bytes of one-time + programmable eFuse. + + This driver can also be built as a module. If so, the module + will be called nvmem-sunplus-ocotp. + endif diff --git a/drivers/nvmem/Makefile b/drivers/nvmem/Makefile index dcbbde35b6a8..891958e29d25 100644 --- a/drivers/nvmem/Makefile +++ b/drivers/nvmem/Makefile @@ -61,3 +61,7 @@ obj-$(CONFIG_NVMEM_RMEM) += nvmem-rmem.o nvmem-rmem-y := rmem.o obj-$(CONFIG_NVMEM_BRCM_NVRAM) += nvmem_brcm_nvram.o nvmem_brcm_nvram-y := brcm_nvram.o +obj-$(CONFIG_NVMEM_LAYERSCAPE_SFP) += nvmem-layerscape-sfp.o +nvmem-layerscape-sfp-y := layerscape-sfp.o +obj-$(CONFIG_NVMEM_SUNPLUS_OCOTP) += nvmem_sunplus_ocotp.o +nvmem_sunplus_ocotp-y := sunplus-ocotp.o diff --git a/drivers/nvmem/brcm_nvram.c b/drivers/nvmem/brcm_nvram.c index bd2ecaaf4585..439f00b9eef6 100644 --- a/drivers/nvmem/brcm_nvram.c +++ b/drivers/nvmem/brcm_nvram.c @@ -6,12 +6,26 @@ #include <linux/io.h> #include <linux/mod_devicetable.h> #include <linux/module.h> +#include <linux/nvmem-consumer.h> #include <linux/nvmem-provider.h> #include <linux/platform_device.h> +#include <linux/slab.h> + +#define NVRAM_MAGIC "FLSH" struct brcm_nvram { struct device *dev; void __iomem *base; + struct nvmem_cell_info *cells; + int ncells; +}; + +struct brcm_nvram_header { + char magic[4]; + __le32 len; + __le32 crc_ver_init; /* 0:7 crc, 8:15 ver, 16:31 sdram_init */ + __le32 config_refresh; /* 0:15 sdram_config, 16:31 sdram_refresh */ + __le32 config_ncdl; /* ncdl values for memc */ }; static int brcm_nvram_read(void *context, unsigned int offset, void *val, @@ -26,6 +40,75 @@ static int brcm_nvram_read(void *context, unsigned int offset, void *val, return 0; } +static int brcm_nvram_add_cells(struct brcm_nvram *priv, uint8_t *data, + size_t len) +{ + struct device *dev = priv->dev; + char *var, *value, *eq; + int idx; + + priv->ncells = 0; + for (var = data + sizeof(struct brcm_nvram_header); + var < (char *)data + len && *var; + var += strlen(var) + 1) { + priv->ncells++; + } + + priv->cells = devm_kcalloc(dev, priv->ncells, sizeof(*priv->cells), GFP_KERNEL); + if (!priv->cells) + return -ENOMEM; + + for (var = data + sizeof(struct brcm_nvram_header), idx = 0; + var < (char *)data + len && *var; + var = value + strlen(value) + 1, idx++) { + eq = strchr(var, '='); + if (!eq) + break; + *eq = '\0'; + value = eq + 1; + + priv->cells[idx].name = devm_kstrdup(dev, var, GFP_KERNEL); + if (!priv->cells[idx].name) + return -ENOMEM; + priv->cells[idx].offset = value - (char *)data; + priv->cells[idx].bytes = strlen(value); + } + + return 0; +} + +static int brcm_nvram_parse(struct brcm_nvram *priv) +{ + struct device *dev = priv->dev; + struct brcm_nvram_header header; + uint8_t *data; + size_t len; + int err; + + memcpy_fromio(&header, priv->base, sizeof(header)); + + if (memcmp(header.magic, NVRAM_MAGIC, 4)) { + dev_err(dev, "Invalid NVRAM magic\n"); + return -EINVAL; + } + + len = le32_to_cpu(header.len); + + data = kcalloc(1, len, GFP_KERNEL); + memcpy_fromio(data, priv->base, len); + data[len - 1] = '\0'; + + err = brcm_nvram_add_cells(priv, data, len); + if (err) { + dev_err(dev, "Failed to add cells: %d\n", err); + return err; + } + + kfree(data); + + return 0; +} + static int brcm_nvram_probe(struct platform_device *pdev) { struct nvmem_config config = { @@ -35,6 +118,7 @@ static int brcm_nvram_probe(struct platform_device *pdev) struct device *dev = &pdev->dev; struct resource *res; struct brcm_nvram *priv; + int err; priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); if (!priv) @@ -46,7 +130,13 @@ static int brcm_nvram_probe(struct platform_device *pdev) if (IS_ERR(priv->base)) return PTR_ERR(priv->base); + err = brcm_nvram_parse(priv); + if (err) + return err; + config.dev = dev; + config.cells = priv->cells; + config.ncells = priv->ncells; config.priv = priv; config.size = resource_size(res); diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c index 9fd1602b539d..f58d9bc7aa08 100644 --- a/drivers/nvmem/core.c +++ b/drivers/nvmem/core.c @@ -903,13 +903,14 @@ static void nvmem_device_release(struct kref *kref) */ void nvmem_unregister(struct nvmem_device *nvmem) { - kref_put(&nvmem->refcnt, nvmem_device_release); + if (nvmem) + kref_put(&nvmem->refcnt, nvmem_device_release); } EXPORT_SYMBOL_GPL(nvmem_unregister); -static void devm_nvmem_release(struct device *dev, void *res) +static void devm_nvmem_unregister(void *nvmem) { - nvmem_unregister(*(struct nvmem_device **)res); + nvmem_unregister(nvmem); } /** @@ -926,47 +927,21 @@ static void devm_nvmem_release(struct device *dev, void *res) struct nvmem_device *devm_nvmem_register(struct device *dev, const struct nvmem_config *config) { - struct nvmem_device **ptr, *nvmem; - - ptr = devres_alloc(devm_nvmem_release, sizeof(*ptr), GFP_KERNEL); - if (!ptr) - return ERR_PTR(-ENOMEM); + struct nvmem_device *nvmem; + int ret; nvmem = nvmem_register(config); + if (IS_ERR(nvmem)) + return nvmem; - if (!IS_ERR(nvmem)) { - *ptr = nvmem; - devres_add(dev, ptr); - } else { - devres_free(ptr); - } + ret = devm_add_action_or_reset(dev, devm_nvmem_unregister, nvmem); + if (ret) + return ERR_PTR(ret); return nvmem; } EXPORT_SYMBOL_GPL(devm_nvmem_register); -static int devm_nvmem_match(struct device *dev, void *res, void *data) -{ - struct nvmem_device **r = res; - - return *r == data; -} - -/** - * devm_nvmem_unregister() - Unregister previously registered managed nvmem - * device. - * - * @dev: Device that uses the nvmem device. - * @nvmem: Pointer to previously registered nvmem device. - * - * Return: Will be negative on error or zero on success. - */ -int devm_nvmem_unregister(struct device *dev, struct nvmem_device *nvmem) -{ - return devres_release(dev, devm_nvmem_release, devm_nvmem_match, nvmem); -} -EXPORT_SYMBOL(devm_nvmem_unregister); - static struct nvmem_device *__nvmem_device_get(void *data, int (*match)(struct device *dev, const void *data)) { diff --git a/drivers/nvmem/layerscape-sfp.c b/drivers/nvmem/layerscape-sfp.c new file mode 100644 index 000000000000..e591c1511e33 --- /dev/null +++ b/drivers/nvmem/layerscape-sfp.c @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Layerscape SFP driver + * + * Copyright (c) 2022 Michael Walle <michael@walle.cc> + * + */ + +#include <linux/device.h> +#include <linux/io.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/nvmem-provider.h> +#include <linux/platform_device.h> +#include <linux/property.h> + +#define LAYERSCAPE_SFP_OTP_OFFSET 0x0200 + +struct layerscape_sfp_priv { + void __iomem *base; +}; + +struct layerscape_sfp_data { + int size; +}; + +static int layerscape_sfp_read(void *context, unsigned int offset, void *val, + size_t bytes) +{ + struct layerscape_sfp_priv *priv = context; + + memcpy_fromio(val, priv->base + LAYERSCAPE_SFP_OTP_OFFSET + offset, + bytes); + + return 0; +} + +static struct nvmem_config layerscape_sfp_nvmem_config = { + .name = "fsl-sfp", + .reg_read = layerscape_sfp_read, +}; + +static int layerscape_sfp_probe(struct platform_device *pdev) +{ + const struct layerscape_sfp_data *data; + struct layerscape_sfp_priv *priv; + struct nvmem_device *nvmem; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(priv->base)) + return PTR_ERR(priv->base); + + data = device_get_match_data(&pdev->dev); + + layerscape_sfp_nvmem_config.size = data->size; + layerscape_sfp_nvmem_config.dev = &pdev->dev; + layerscape_sfp_nvmem_config.priv = priv; + + nvmem = devm_nvmem_register(&pdev->dev, &layerscape_sfp_nvmem_config); + + return PTR_ERR_OR_ZERO(nvmem); +} + +static const struct layerscape_sfp_data ls1028a_data = { + .size = 0x88, +}; + +static const struct of_device_id layerscape_sfp_dt_ids[] = { + { .compatible = "fsl,ls1028a-sfp", .data = &ls1028a_data }, + {}, +}; +MODULE_DEVICE_TABLE(of, layerscape_sfp_dt_ids); + +static struct platform_driver layerscape_sfp_driver = { + .probe = layerscape_sfp_probe, + .driver = { + .name = "layerscape_sfp", + .of_match_table = layerscape_sfp_dt_ids, + }, +}; +module_platform_driver(layerscape_sfp_driver); + +MODULE_AUTHOR("Michael Walle <michael@walle.cc>"); +MODULE_DESCRIPTION("Layerscape Security Fuse Processor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/nvmem/meson-mx-efuse.c b/drivers/nvmem/meson-mx-efuse.c index 07c9f38c1c60..13eb14316f46 100644 --- a/drivers/nvmem/meson-mx-efuse.c +++ b/drivers/nvmem/meson-mx-efuse.c @@ -209,8 +209,7 @@ static int meson_mx_efuse_probe(struct platform_device *pdev) if (IS_ERR(efuse->base)) return PTR_ERR(efuse->base); - efuse->config.name = devm_kstrdup(&pdev->dev, drvdata->name, - GFP_KERNEL); + efuse->config.name = drvdata->name; efuse->config.owner = THIS_MODULE; efuse->config.dev = &pdev->dev; efuse->config.priv = efuse; diff --git a/drivers/nvmem/qfprom.c b/drivers/nvmem/qfprom.c index c500d6235bf6..162132c7dab9 100644 --- a/drivers/nvmem/qfprom.c +++ b/drivers/nvmem/qfprom.c @@ -22,7 +22,7 @@ /* Amount of time required to hold charge to blow fuse in micro-seconds */ #define QFPROM_FUSE_BLOW_POLL_US 100 -#define QFPROM_FUSE_BLOW_TIMEOUT_US 1000 +#define QFPROM_FUSE_BLOW_TIMEOUT_US 10000 #define QFPROM_BLOW_STATUS_OFFSET 0x048 #define QFPROM_BLOW_STATUS_BUSY 0x1 @@ -244,7 +244,7 @@ err_clk_prepared: } /** - * qfprom_efuse_reg_write() - Write to fuses. + * qfprom_reg_write() - Write to fuses. * @context: Our driver data. * @reg: The offset to write at. * @_val: Pointer to data to write. diff --git a/drivers/nvmem/sunplus-ocotp.c b/drivers/nvmem/sunplus-ocotp.c new file mode 100644 index 000000000000..2dc59c22eb55 --- /dev/null +++ b/drivers/nvmem/sunplus-ocotp.c @@ -0,0 +1,228 @@ +// SPDX-License-Identifier: GPL-2.0 + +/* + * The OCOTP driver for Sunplus SP7021 + * + * Copyright (C) 2019 Sunplus Technology Inc., All rights reserved. + */ + +#include <linux/bitfield.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/io.h> +#include <linux/iopoll.h> +#include <linux/module.h> +#include <linux/nvmem-provider.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> + +/* + * OTP memory + * Each bank contains 4 words (32 bits). + * Bank 0 starts at offset 0 from the base. + */ + +#define OTP_WORDS_PER_BANK 4 +#define OTP_WORD_SIZE sizeof(u32) +#define OTP_BIT_ADDR_OF_BANK (8 * OTP_WORD_SIZE * OTP_WORDS_PER_BANK) +#define QAC628_OTP_NUM_BANKS 8 +#define QAC628_OTP_SIZE (QAC628_OTP_NUM_BANKS * OTP_WORDS_PER_BANK * OTP_WORD_SIZE) +#define OTP_READ_TIMEOUT_US 200000 + +/* HB_GPIO */ +#define ADDRESS_8_DATA 0x20 + +/* OTP_RX */ +#define OTP_CONTROL_2 0x48 +#define OTP_RD_PERIOD GENMASK(15, 8) +#define OTP_RD_PERIOD_MASK ~GENMASK(15, 8) +#define CPU_CLOCK FIELD_PREP(OTP_RD_PERIOD, 30) +#define SEL_BAK_KEY2 BIT(5) +#define SEL_BAK_KEY2_MASK ~BIT(5) +#define SW_TRIM_EN BIT(4) +#define SW_TRIM_EN_MASK ~BIT(4) +#define SEL_BAK_KEY BIT(3) +#define SEL_BAK_KEY_MASK ~BIT(3) +#define OTP_READ BIT(2) +#define OTP_LOAD_SECURE_DATA BIT(1) +#define OTP_LOAD_SECURE_DATA_MASK ~BIT(1) +#define OTP_DO_CRC BIT(0) +#define OTP_DO_CRC_MASK ~BIT(0) +#define OTP_STATUS 0x4c +#define OTP_READ_DONE BIT(4) +#define OTP_READ_DONE_MASK ~BIT(4) +#define OTP_LOAD_SECURE_DONE_MASK ~BIT(2) +#define OTP_READ_ADDRESS 0x50 + +enum base_type { + HB_GPIO, + OTPRX, + BASEMAX, +}; + +struct sp_ocotp_priv { + struct device *dev; + void __iomem *base[BASEMAX]; + struct clk *clk; +}; + +struct sp_ocotp_data { + int size; +}; + +const struct sp_ocotp_data sp_otp_v0 = { + .size = QAC628_OTP_SIZE, +}; + +static int sp_otp_read_real(struct sp_ocotp_priv *otp, int addr, char *value) +{ + unsigned int addr_data; + unsigned int byte_shift; + unsigned int status; + int ret; + + addr_data = addr % (OTP_WORD_SIZE * OTP_WORDS_PER_BANK); + addr_data = addr_data / OTP_WORD_SIZE; + + byte_shift = addr % (OTP_WORD_SIZE * OTP_WORDS_PER_BANK); + byte_shift = byte_shift % OTP_WORD_SIZE; + + addr = addr / (OTP_WORD_SIZE * OTP_WORDS_PER_BANK); + addr = addr * OTP_BIT_ADDR_OF_BANK; + + writel(readl(otp->base[OTPRX] + OTP_STATUS) & OTP_READ_DONE_MASK & + OTP_LOAD_SECURE_DONE_MASK, otp->base[OTPRX] + OTP_STATUS); + writel(addr, otp->base[OTPRX] + OTP_READ_ADDRESS); + writel(readl(otp->base[OTPRX] + OTP_CONTROL_2) | OTP_READ, + otp->base[OTPRX] + OTP_CONTROL_2); + writel(readl(otp->base[OTPRX] + OTP_CONTROL_2) & SEL_BAK_KEY2_MASK & SW_TRIM_EN_MASK + & SEL_BAK_KEY_MASK & OTP_LOAD_SECURE_DATA_MASK & OTP_DO_CRC_MASK, + otp->base[OTPRX] + OTP_CONTROL_2); + writel((readl(otp->base[OTPRX] + OTP_CONTROL_2) & OTP_RD_PERIOD_MASK) | CPU_CLOCK, + otp->base[OTPRX] + OTP_CONTROL_2); + + ret = readl_poll_timeout(otp->base[OTPRX] + OTP_STATUS, status, + status & OTP_READ_DONE, 10, OTP_READ_TIMEOUT_US); + + if (ret < 0) + return ret; + + *value = (readl(otp->base[HB_GPIO] + ADDRESS_8_DATA + addr_data * OTP_WORD_SIZE) + >> (8 * byte_shift)) & 0xff; + + return ret; +} + +static int sp_ocotp_read(void *priv, unsigned int offset, void *value, size_t bytes) +{ + struct sp_ocotp_priv *otp = priv; + unsigned int addr; + char *buf = value; + char val[4]; + int ret; + + ret = clk_enable(otp->clk); + if (ret) + return ret; + + *buf = 0; + for (addr = offset; addr < (offset + bytes); addr++) { + ret = sp_otp_read_real(otp, addr, val); + if (ret < 0) { + dev_err(otp->dev, "OTP read fail:%d at %d", ret, addr); + goto disable_clk; + } + + *buf++ = *val; + } + +disable_clk: + clk_disable(otp->clk); + + return ret; +} + +static struct nvmem_config sp_ocotp_nvmem_config = { + .name = "sp-ocotp", + .read_only = true, + .word_size = 1, + .size = QAC628_OTP_SIZE, + .stride = 1, + .reg_read = sp_ocotp_read, + .owner = THIS_MODULE, +}; + +static int sp_ocotp_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct nvmem_device *nvmem; + struct sp_ocotp_priv *otp; + struct resource *res; + int ret; + + otp = devm_kzalloc(dev, sizeof(*otp), GFP_KERNEL); + if (!otp) + return -ENOMEM; + + otp->dev = dev; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "hb_gpio"); + otp->base[HB_GPIO] = devm_ioremap_resource(dev, res); + if (IS_ERR(otp->base[HB_GPIO])) + return PTR_ERR(otp->base[HB_GPIO]); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "otprx"); + otp->base[OTPRX] = devm_ioremap_resource(dev, res); + if (IS_ERR(otp->base[OTPRX])) + return PTR_ERR(otp->base[OTPRX]); + + otp->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(otp->clk)) + return dev_err_probe(&pdev->dev, PTR_ERR(otp->clk), + "devm_clk_get fail\n"); + + ret = clk_prepare(otp->clk); + if (ret < 0) { + dev_err(dev, "failed to prepare clk: %d\n", ret); + return ret; + } + + sp_ocotp_nvmem_config.priv = otp; + sp_ocotp_nvmem_config.dev = dev; + + nvmem = devm_nvmem_register(dev, &sp_ocotp_nvmem_config); + if (IS_ERR(nvmem)) + return dev_err_probe(&pdev->dev, PTR_ERR(nvmem), + "register nvmem device fail\n"); + + platform_set_drvdata(pdev, nvmem); + + dev_dbg(dev, "banks:%d x wpb:%d x wsize:%d = %d", + (int)QAC628_OTP_NUM_BANKS, (int)OTP_WORDS_PER_BANK, + (int)OTP_WORD_SIZE, (int)QAC628_OTP_SIZE); + + dev_info(dev, "by Sunplus (C) 2020"); + + return 0; +} + +static const struct of_device_id sp_ocotp_dt_ids[] = { + { .compatible = "sunplus,sp7021-ocotp", .data = &sp_otp_v0 }, + { } +}; +MODULE_DEVICE_TABLE(of, sp_ocotp_dt_ids); + +static struct platform_driver sp_otp_driver = { + .probe = sp_ocotp_probe, + .driver = { + .name = "sunplus,sp7021-ocotp", + .of_match_table = sp_ocotp_dt_ids, + } +}; +module_platform_driver(sp_otp_driver); + +MODULE_AUTHOR("Vincent Shih <vincent.sunplus@gmail.com>"); +MODULE_DESCRIPTION("Sunplus On-Chip OTP driver"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/nvmem/sunxi_sid.c b/drivers/nvmem/sunxi_sid.c index 275b9155e473..5750e1f4bcdb 100644 --- a/drivers/nvmem/sunxi_sid.c +++ b/drivers/nvmem/sunxi_sid.c @@ -184,6 +184,11 @@ static const struct sunxi_sid_cfg sun8i_h3_cfg = { .need_register_readout = true, }; +static const struct sunxi_sid_cfg sun20i_d1_cfg = { + .value_offset = 0x200, + .size = 0x100, +}; + static const struct sunxi_sid_cfg sun50i_a64_cfg = { .value_offset = 0x200, .size = 0x100, @@ -200,6 +205,7 @@ static const struct of_device_id sunxi_sid_of_match[] = { { .compatible = "allwinner,sun7i-a20-sid", .data = &sun7i_a20_cfg }, { .compatible = "allwinner,sun8i-a83t-sid", .data = &sun50i_a64_cfg }, { .compatible = "allwinner,sun8i-h3-sid", .data = &sun8i_h3_cfg }, + { .compatible = "allwinner,sun20i-d1-sid", .data = &sun20i_d1_cfg }, { .compatible = "allwinner,sun50i-a64-sid", .data = &sun50i_a64_cfg }, { .compatible = "allwinner,sun50i-h5-sid", .data = &sun50i_a64_cfg }, { .compatible = "allwinner,sun50i-h6-sid", .data = &sun50i_h6_cfg }, |