diff options
Diffstat (limited to 'drivers/nvmem')
-rw-r--r-- | drivers/nvmem/Kconfig | 8 | ||||
-rw-r--r-- | drivers/nvmem/Makefile | 2 | ||||
-rw-r--r-- | drivers/nvmem/core.c | 8 | ||||
-rw-r--r-- | drivers/nvmem/imx-ocotp-scu.c | 16 | ||||
-rw-r--r-- | drivers/nvmem/imx-ocotp.c | 79 | ||||
-rw-r--r-- | drivers/nvmem/qcom-spmi-sdam.c | 192 |
6 files changed, 271 insertions, 34 deletions
diff --git a/drivers/nvmem/Kconfig b/drivers/nvmem/Kconfig index 73567e922491..35efab1ba8d9 100644 --- a/drivers/nvmem/Kconfig +++ b/drivers/nvmem/Kconfig @@ -109,6 +109,14 @@ config QCOM_QFPROM This driver can also be built as a module. If so, the module will be called nvmem_qfprom. +config NVMEM_SPMI_SDAM + tristate "SPMI SDAM Support" + depends on SPMI + help + This driver supports the Shared Direct Access Memory Module on + Qualcomm Technologies, Inc. PMICs. It provides the clients + an interface to read/write to the SDAM module's shared memory. + config ROCKCHIP_EFUSE tristate "Rockchip eFuse Support" depends on ARCH_ROCKCHIP || COMPILE_TEST diff --git a/drivers/nvmem/Makefile b/drivers/nvmem/Makefile index 9e667823edb3..6b466cd1427b 100644 --- a/drivers/nvmem/Makefile +++ b/drivers/nvmem/Makefile @@ -28,6 +28,8 @@ obj-$(CONFIG_MTK_EFUSE) += nvmem_mtk-efuse.o nvmem_mtk-efuse-y := mtk-efuse.o obj-$(CONFIG_QCOM_QFPROM) += nvmem_qfprom.o nvmem_qfprom-y := qfprom.o +obj-$(CONFIG_NVMEM_SPMI_SDAM) += nvmem_qcom-spmi-sdam.o +nvmem_qcom-spmi-sdam-y += qcom-spmi-sdam.o obj-$(CONFIG_ROCKCHIP_EFUSE) += nvmem_rockchip_efuse.o nvmem_rockchip_efuse-y := rockchip-efuse.o obj-$(CONFIG_ROCKCHIP_OTP) += nvmem-rockchip-otp.o diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c index 9f1ee9c766ec..1e4a798dce6e 100644 --- a/drivers/nvmem/core.c +++ b/drivers/nvmem/core.c @@ -83,7 +83,7 @@ static void nvmem_cell_drop(struct nvmem_cell *cell) list_del(&cell->node); mutex_unlock(&nvmem_mutex); of_node_put(cell->np); - kfree(cell->name); + kfree_const(cell->name); kfree(cell); } @@ -110,7 +110,9 @@ static int nvmem_cell_info_to_nvmem_cell(struct nvmem_device *nvmem, cell->nvmem = nvmem; cell->offset = info->offset; cell->bytes = info->bytes; - cell->name = info->name; + cell->name = kstrdup_const(info->name, GFP_KERNEL); + if (!cell->name) + return -ENOMEM; cell->bit_offset = info->bit_offset; cell->nbits = info->nbits; @@ -300,7 +302,7 @@ static int nvmem_add_cells_from_of(struct nvmem_device *nvmem) dev_err(dev, "cell %s unaligned to nvmem stride %d\n", cell->name, nvmem->stride); /* Cells already added will be freed later. */ - kfree(cell->name); + kfree_const(cell->name); kfree(cell); return -EINVAL; } diff --git a/drivers/nvmem/imx-ocotp-scu.c b/drivers/nvmem/imx-ocotp-scu.c index 03f1ab23ad51..399e1eb8b4c1 100644 --- a/drivers/nvmem/imx-ocotp-scu.c +++ b/drivers/nvmem/imx-ocotp-scu.c @@ -15,8 +15,7 @@ #include <linux/platform_device.h> #include <linux/slab.h> -#define IMX_SIP_OTP 0xC200000A -#define IMX_SIP_OTP_WRITE 0x2 +#define IMX_SIP_OTP_WRITE 0xc200000B enum ocotp_devtype { IMX8QXP, @@ -139,8 +138,8 @@ static int imx_scu_ocotp_read(void *context, unsigned int offset, void *p; int i, ret; - index = offset >> 2; - num_bytes = round_up((offset % 4) + bytes, 4); + index = offset; + num_bytes = round_up(bytes, 4); count = num_bytes >> 2; if (count > (priv->data->nregs - index)) @@ -169,7 +168,7 @@ static int imx_scu_ocotp_read(void *context, unsigned int offset, buf++; } - memcpy(val, (u8 *)p + offset % 4, bytes); + memcpy(val, (u8 *)p, bytes); mutex_unlock(&scu_ocotp_mutex); @@ -189,10 +188,10 @@ static int imx_scu_ocotp_write(void *context, unsigned int offset, int ret; /* allow only writing one complete OTP word at a time */ - if ((bytes != 4) || (offset % 4)) + if (bytes != 4) return -EINVAL; - index = offset >> 2; + index = offset; if (in_hole(context, index)) return -EINVAL; @@ -212,8 +211,7 @@ static int imx_scu_ocotp_write(void *context, unsigned int offset, mutex_lock(&scu_ocotp_mutex); - arm_smccc_smc(IMX_SIP_OTP, IMX_SIP_OTP_WRITE, index, *buf, - 0, 0, 0, 0, &res); + arm_smccc_smc(IMX_SIP_OTP_WRITE, index, *buf, 0, 0, 0, 0, 0, &res); mutex_unlock(&scu_ocotp_mutex); diff --git a/drivers/nvmem/imx-ocotp.c b/drivers/nvmem/imx-ocotp.c index fc40555ca4cd..4ba9cc8f76df 100644 --- a/drivers/nvmem/imx-ocotp.c +++ b/drivers/nvmem/imx-ocotp.c @@ -44,6 +44,14 @@ #define IMX_OCOTP_BM_CTRL_ERROR 0x00000200 #define IMX_OCOTP_BM_CTRL_REL_SHADOWS 0x00000400 +#define IMX_OCOTP_BM_CTRL_DEFAULT \ + { \ + .bm_addr = IMX_OCOTP_BM_CTRL_ADDR, \ + .bm_busy = IMX_OCOTP_BM_CTRL_BUSY, \ + .bm_error = IMX_OCOTP_BM_CTRL_ERROR, \ + .bm_rel_shadows = IMX_OCOTP_BM_CTRL_REL_SHADOWS,\ + } + #define TIMING_STROBE_PROG_US 10 /* Min time to blow a fuse */ #define TIMING_STROBE_READ_NS 37 /* Min time before read */ #define TIMING_RELAX_NS 17 @@ -62,18 +70,31 @@ struct ocotp_priv { struct nvmem_config *config; }; +struct ocotp_ctrl_reg { + u32 bm_addr; + u32 bm_busy; + u32 bm_error; + u32 bm_rel_shadows; +}; + struct ocotp_params { unsigned int nregs; unsigned int bank_address_words; void (*set_timing)(struct ocotp_priv *priv); + struct ocotp_ctrl_reg ctrl; }; -static int imx_ocotp_wait_for_busy(void __iomem *base, u32 flags) +static int imx_ocotp_wait_for_busy(struct ocotp_priv *priv, u32 flags) { int count; u32 c, mask; + u32 bm_ctrl_busy, bm_ctrl_error; + void __iomem *base = priv->base; - mask = IMX_OCOTP_BM_CTRL_BUSY | IMX_OCOTP_BM_CTRL_ERROR | flags; + bm_ctrl_busy = priv->params->ctrl.bm_busy; + bm_ctrl_error = priv->params->ctrl.bm_error; + + mask = bm_ctrl_busy | bm_ctrl_error | flags; for (count = 10000; count >= 0; count--) { c = readl(base + IMX_OCOTP_ADDR_CTRL); @@ -97,7 +118,7 @@ static int imx_ocotp_wait_for_busy(void __iomem *base, u32 flags) * - A read is performed to from a fuse word which has been read * locked. */ - if (c & IMX_OCOTP_BM_CTRL_ERROR) + if (c & bm_ctrl_error) return -EPERM; return -ETIMEDOUT; } @@ -105,15 +126,18 @@ static int imx_ocotp_wait_for_busy(void __iomem *base, u32 flags) return 0; } -static void imx_ocotp_clr_err_if_set(void __iomem *base) +static void imx_ocotp_clr_err_if_set(struct ocotp_priv *priv) { - u32 c; + u32 c, bm_ctrl_error; + void __iomem *base = priv->base; + + bm_ctrl_error = priv->params->ctrl.bm_error; c = readl(base + IMX_OCOTP_ADDR_CTRL); - if (!(c & IMX_OCOTP_BM_CTRL_ERROR)) + if (!(c & bm_ctrl_error)) return; - writel(IMX_OCOTP_BM_CTRL_ERROR, base + IMX_OCOTP_ADDR_CTRL_CLR); + writel(bm_ctrl_error, base + IMX_OCOTP_ADDR_CTRL_CLR); } static int imx_ocotp_read(void *context, unsigned int offset, @@ -140,7 +164,7 @@ static int imx_ocotp_read(void *context, unsigned int offset, return ret; } - ret = imx_ocotp_wait_for_busy(priv->base, 0); + ret = imx_ocotp_wait_for_busy(priv, 0); if (ret < 0) { dev_err(priv->dev, "timeout during read setup\n"); goto read_end; @@ -157,7 +181,7 @@ static int imx_ocotp_read(void *context, unsigned int offset, * issued */ if (*(buf - 1) == IMX_OCOTP_READ_LOCKED_VAL) - imx_ocotp_clr_err_if_set(priv->base); + imx_ocotp_clr_err_if_set(priv); } ret = 0; @@ -274,7 +298,7 @@ static int imx_ocotp_write(void *context, unsigned int offset, void *val, * write or reload must be completed before a write access can be * requested. */ - ret = imx_ocotp_wait_for_busy(priv->base, 0); + ret = imx_ocotp_wait_for_busy(priv, 0); if (ret < 0) { dev_err(priv->dev, "timeout during timing setup\n"); goto write_end; @@ -306,8 +330,8 @@ static int imx_ocotp_write(void *context, unsigned int offset, void *val, } ctrl = readl(priv->base + IMX_OCOTP_ADDR_CTRL); - ctrl &= ~IMX_OCOTP_BM_CTRL_ADDR; - ctrl |= waddr & IMX_OCOTP_BM_CTRL_ADDR; + ctrl &= ~priv->params->ctrl.bm_addr; + ctrl |= waddr & priv->params->ctrl.bm_addr; ctrl |= IMX_OCOTP_WR_UNLOCK; writel(ctrl, priv->base + IMX_OCOTP_ADDR_CTRL); @@ -374,11 +398,11 @@ static int imx_ocotp_write(void *context, unsigned int offset, void *val, * be set. It must be cleared by software before any new write access * can be issued. */ - ret = imx_ocotp_wait_for_busy(priv->base, 0); + ret = imx_ocotp_wait_for_busy(priv, 0); if (ret < 0) { if (ret == -EPERM) { dev_err(priv->dev, "failed write to locked region"); - imx_ocotp_clr_err_if_set(priv->base); + imx_ocotp_clr_err_if_set(priv); } else { dev_err(priv->dev, "timeout during data write\n"); } @@ -394,10 +418,10 @@ static int imx_ocotp_write(void *context, unsigned int offset, void *val, udelay(2); /* reload all shadow registers */ - writel(IMX_OCOTP_BM_CTRL_REL_SHADOWS, + writel(priv->params->ctrl.bm_rel_shadows, priv->base + IMX_OCOTP_ADDR_CTRL_SET); - ret = imx_ocotp_wait_for_busy(priv->base, - IMX_OCOTP_BM_CTRL_REL_SHADOWS); + ret = imx_ocotp_wait_for_busy(priv, + priv->params->ctrl.bm_rel_shadows); if (ret < 0) { dev_err(priv->dev, "timeout during shadow register reload\n"); goto write_end; @@ -424,65 +448,76 @@ static const struct ocotp_params imx6q_params = { .nregs = 128, .bank_address_words = 0, .set_timing = imx_ocotp_set_imx6_timing, + .ctrl = IMX_OCOTP_BM_CTRL_DEFAULT, }; static const struct ocotp_params imx6sl_params = { .nregs = 64, .bank_address_words = 0, .set_timing = imx_ocotp_set_imx6_timing, + .ctrl = IMX_OCOTP_BM_CTRL_DEFAULT, }; static const struct ocotp_params imx6sll_params = { .nregs = 128, .bank_address_words = 0, .set_timing = imx_ocotp_set_imx6_timing, + .ctrl = IMX_OCOTP_BM_CTRL_DEFAULT, }; static const struct ocotp_params imx6sx_params = { .nregs = 128, .bank_address_words = 0, .set_timing = imx_ocotp_set_imx6_timing, + .ctrl = IMX_OCOTP_BM_CTRL_DEFAULT, }; static const struct ocotp_params imx6ul_params = { .nregs = 128, .bank_address_words = 0, .set_timing = imx_ocotp_set_imx6_timing, + .ctrl = IMX_OCOTP_BM_CTRL_DEFAULT, }; static const struct ocotp_params imx6ull_params = { .nregs = 64, .bank_address_words = 0, .set_timing = imx_ocotp_set_imx6_timing, + .ctrl = IMX_OCOTP_BM_CTRL_DEFAULT, }; static const struct ocotp_params imx7d_params = { .nregs = 64, .bank_address_words = 4, .set_timing = imx_ocotp_set_imx7_timing, + .ctrl = IMX_OCOTP_BM_CTRL_DEFAULT, }; static const struct ocotp_params imx7ulp_params = { .nregs = 256, .bank_address_words = 0, + .ctrl = IMX_OCOTP_BM_CTRL_DEFAULT, }; static const struct ocotp_params imx8mq_params = { .nregs = 256, .bank_address_words = 0, .set_timing = imx_ocotp_set_imx6_timing, + .ctrl = IMX_OCOTP_BM_CTRL_DEFAULT, }; static const struct ocotp_params imx8mm_params = { .nregs = 256, .bank_address_words = 0, .set_timing = imx_ocotp_set_imx6_timing, + .ctrl = IMX_OCOTP_BM_CTRL_DEFAULT, }; static const struct ocotp_params imx8mn_params = { .nregs = 256, .bank_address_words = 0, .set_timing = imx_ocotp_set_imx6_timing, + .ctrl = IMX_OCOTP_BM_CTRL_DEFAULT, }; static const struct of_device_id imx_ocotp_dt_ids[] = { @@ -521,17 +556,17 @@ static int imx_ocotp_probe(struct platform_device *pdev) if (IS_ERR(priv->clk)) return PTR_ERR(priv->clk); - clk_prepare_enable(priv->clk); - imx_ocotp_clr_err_if_set(priv->base); - clk_disable_unprepare(priv->clk); - priv->params = of_device_get_match_data(&pdev->dev); imx_ocotp_nvmem_config.size = 4 * priv->params->nregs; imx_ocotp_nvmem_config.dev = dev; imx_ocotp_nvmem_config.priv = priv; priv->config = &imx_ocotp_nvmem_config; - nvmem = devm_nvmem_register(dev, &imx_ocotp_nvmem_config); + clk_prepare_enable(priv->clk); + imx_ocotp_clr_err_if_set(priv); + clk_disable_unprepare(priv->clk); + + nvmem = devm_nvmem_register(dev, &imx_ocotp_nvmem_config); return PTR_ERR_OR_ZERO(nvmem); } diff --git a/drivers/nvmem/qcom-spmi-sdam.c b/drivers/nvmem/qcom-spmi-sdam.c new file mode 100644 index 000000000000..8682cda448d6 --- /dev/null +++ b/drivers/nvmem/qcom-spmi-sdam.c @@ -0,0 +1,192 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2017 The Linux Foundation. All rights reserved. + */ + +#include <linux/device.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/nvmem-provider.h> +#include <linux/regmap.h> + +#define SDAM_MEM_START 0x40 +#define REGISTER_MAP_ID 0x40 +#define REGISTER_MAP_VERSION 0x41 +#define SDAM_SIZE 0x44 +#define SDAM_PBS_TRIG_SET 0xE5 +#define SDAM_PBS_TRIG_CLR 0xE6 + +struct sdam_chip { + struct platform_device *pdev; + struct regmap *regmap; + struct nvmem_config sdam_config; + unsigned int base; + unsigned int size; +}; + +/* read only register offsets */ +static const u8 sdam_ro_map[] = { + REGISTER_MAP_ID, + REGISTER_MAP_VERSION, + SDAM_SIZE +}; + +static bool sdam_is_valid(struct sdam_chip *sdam, unsigned int offset, + size_t len) +{ + unsigned int sdam_mem_end = SDAM_MEM_START + sdam->size - 1; + + if (!len) + return false; + + if (offset >= SDAM_MEM_START && offset <= sdam_mem_end + && (offset + len - 1) <= sdam_mem_end) + return true; + else if ((offset == SDAM_PBS_TRIG_SET || offset == SDAM_PBS_TRIG_CLR) + && (len == 1)) + return true; + + return false; +} + +static bool sdam_is_ro(unsigned int offset, size_t len) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(sdam_ro_map); i++) + if (offset <= sdam_ro_map[i] && (offset + len) > sdam_ro_map[i]) + return true; + + return false; +} + +static int sdam_read(void *priv, unsigned int offset, void *val, + size_t bytes) +{ + struct sdam_chip *sdam = priv; + struct device *dev = &sdam->pdev->dev; + int rc; + + if (!sdam_is_valid(sdam, offset, bytes)) { + dev_err(dev, "Invalid SDAM offset %#x len=%zd\n", + offset, bytes); + return -EINVAL; + } + + rc = regmap_bulk_read(sdam->regmap, sdam->base + offset, val, bytes); + if (rc < 0) + dev_err(dev, "Failed to read SDAM offset %#x len=%zd, rc=%d\n", + offset, bytes, rc); + + return rc; +} + +static int sdam_write(void *priv, unsigned int offset, void *val, + size_t bytes) +{ + struct sdam_chip *sdam = priv; + struct device *dev = &sdam->pdev->dev; + int rc; + + if (!sdam_is_valid(sdam, offset, bytes)) { + dev_err(dev, "Invalid SDAM offset %#x len=%zd\n", + offset, bytes); + return -EINVAL; + } + + if (sdam_is_ro(offset, bytes)) { + dev_err(dev, "Invalid write offset %#x len=%zd\n", + offset, bytes); + return -EINVAL; + } + + rc = regmap_bulk_write(sdam->regmap, sdam->base + offset, val, bytes); + if (rc < 0) + dev_err(dev, "Failed to write SDAM offset %#x len=%zd, rc=%d\n", + offset, bytes, rc); + + return rc; +} + +static int sdam_probe(struct platform_device *pdev) +{ + struct sdam_chip *sdam; + struct nvmem_device *nvmem; + unsigned int val; + int rc; + + sdam = devm_kzalloc(&pdev->dev, sizeof(*sdam), GFP_KERNEL); + if (!sdam) + return -ENOMEM; + + sdam->regmap = dev_get_regmap(pdev->dev.parent, NULL); + if (!sdam->regmap) { + dev_err(&pdev->dev, "Failed to get regmap handle\n"); + return -ENXIO; + } + + rc = of_property_read_u32(pdev->dev.of_node, "reg", &sdam->base); + if (rc < 0) { + dev_err(&pdev->dev, "Failed to get SDAM base, rc=%d\n", rc); + return -EINVAL; + } + + rc = regmap_read(sdam->regmap, sdam->base + SDAM_SIZE, &val); + if (rc < 0) { + dev_err(&pdev->dev, "Failed to read SDAM_SIZE rc=%d\n", rc); + return -EINVAL; + } + sdam->size = val * 32; + + sdam->sdam_config.dev = &pdev->dev; + sdam->sdam_config.name = "spmi_sdam"; + sdam->sdam_config.id = pdev->id; + sdam->sdam_config.owner = THIS_MODULE, + sdam->sdam_config.stride = 1; + sdam->sdam_config.word_size = 1; + sdam->sdam_config.reg_read = sdam_read; + sdam->sdam_config.reg_write = sdam_write; + sdam->sdam_config.priv = sdam; + + nvmem = devm_nvmem_register(&pdev->dev, &sdam->sdam_config); + if (IS_ERR(nvmem)) { + dev_err(&pdev->dev, + "Failed to register SDAM nvmem device rc=%ld\n", + PTR_ERR(nvmem)); + return -ENXIO; + } + dev_dbg(&pdev->dev, + "SDAM base=%#x size=%u registered successfully\n", + sdam->base, sdam->size); + + return 0; +} + +static const struct of_device_id sdam_match_table[] = { + { .compatible = "qcom,spmi-sdam" }, + {}, +}; + +static struct platform_driver sdam_driver = { + .driver = { + .name = "qcom,spmi-sdam", + .of_match_table = sdam_match_table, + }, + .probe = sdam_probe, +}; + +static int __init sdam_init(void) +{ + return platform_driver_register(&sdam_driver); +} +subsys_initcall(sdam_init); + +static void __exit sdam_exit(void) +{ + return platform_driver_unregister(&sdam_driver); +} +module_exit(sdam_exit); + +MODULE_DESCRIPTION("QCOM SPMI SDAM driver"); +MODULE_LICENSE("GPL v2"); |