summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/devicetree/bindings/nvmem/mediatek,efuse.yaml88
-rw-r--r--Documentation/devicetree/bindings/nvmem/microchip,sama7g5-otpc.yaml50
-rw-r--r--Documentation/devicetree/bindings/nvmem/mtk-efuse.txt43
-rw-r--r--MAINTAINERS8
-rw-r--r--drivers/nvmem/Kconfig7
-rw-r--r--drivers/nvmem/Makefile2
-rw-r--r--drivers/nvmem/microchip-otpc.c288
-rw-r--r--include/dt-bindings/nvmem/microchip,sama7g5-otpc.h12
8 files changed, 455 insertions, 43 deletions
diff --git a/Documentation/devicetree/bindings/nvmem/mediatek,efuse.yaml b/Documentation/devicetree/bindings/nvmem/mediatek,efuse.yaml
new file mode 100644
index 000000000000..7c7233e29ecf
--- /dev/null
+++ b/Documentation/devicetree/bindings/nvmem/mediatek,efuse.yaml
@@ -0,0 +1,88 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/nvmem/mediatek,efuse.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: MediaTek efuse
+
+description: |
+ MediaTek's efuse is used for storing calibration data, it can be accessed
+ on ARM devices usiong I/O mapped memory.
+
+maintainers:
+ - Andrew-CT Chen <andrew-ct.chen@mediatek.com>
+ - Lala Lin <lala.lin@mediatek.com>
+
+allOf:
+ - $ref: "nvmem.yaml#"
+
+properties:
+ $nodename:
+ pattern: "^efuse@[0-9a-f]+$"
+
+ compatible:
+ oneOf:
+ - items:
+ - enum:
+ - mediatek,mt7622-efuse
+ - mediatek,mt7623-efuse
+ - mediatek,mt8173-efuse
+ - mediatek,mt8183-efuse
+ - mediatek,mt8192-efuse
+ - mediatek,mt8195-efuse
+ - mediatek,mt8516-efuse
+ - const: mediatek,efuse
+ - const: mediatek,mt8173-efuse
+ deprecated: true
+
+ reg:
+ maxItems: 1
+
+required:
+ - compatible
+ - reg
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ efuse@11c10000 {
+ compatible = "mediatek,mt8195-efuse", "mediatek,efuse";
+ reg = <0x11c10000 0x1000>;
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ u3_tx_imp_p0: usb3-tx-imp@184,1 {
+ reg = <0x184 0x1>;
+ bits = <0 5>;
+ };
+ u3_rx_imp_p0: usb3-rx-imp@184,2 {
+ reg = <0x184 0x2>;
+ bits = <5 5>;
+ };
+ u3_intr_p0: usb3-intr@185 {
+ reg = <0x185 0x1>;
+ bits = <2 6>;
+ };
+ comb_tx_imp_p1: usb3-tx-imp@186,1 {
+ reg = <0x186 0x1>;
+ bits = <0 5>;
+ };
+ comb_rx_imp_p1: usb3-rx-imp@186,2 {
+ reg = <0x186 0x2>;
+ bits = <5 5>;
+ };
+ comb_intr_p1: usb3-intr@187 {
+ reg = <0x187 0x1>;
+ bits = <2 6>;
+ };
+ u2_intr_p0: usb2-intr-p0@188,1 {
+ reg = <0x188 0x1>;
+ bits = <0 5>;
+ };
+ u2_intr_p1: usb2-intr-p1@188,2 {
+ reg = <0x188 0x2>;
+ bits = <5 5>;
+ };
+ };
diff --git a/Documentation/devicetree/bindings/nvmem/microchip,sama7g5-otpc.yaml b/Documentation/devicetree/bindings/nvmem/microchip,sama7g5-otpc.yaml
new file mode 100644
index 000000000000..c3c96fd0baac
--- /dev/null
+++ b/Documentation/devicetree/bindings/nvmem/microchip,sama7g5-otpc.yaml
@@ -0,0 +1,50 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/nvmem/microchip,sama7g5-otpc.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Microchip SAMA7G5 OTP Controller (OTPC)
+
+maintainers:
+ - Claudiu Beznea <claudiu.beznea@microchip.com>
+
+description: |
+ OTP controller drives a NVMEM memory where system specific data
+ (e.g. calibration data for analog cells, hardware configuration
+ settings, chip identifiers) or user specific data could be stored.
+
+allOf:
+ - $ref: "nvmem.yaml#"
+
+properties:
+ compatible:
+ items:
+ - const: microchip,sama7g5-otpc
+ - const: syscon
+
+ reg:
+ maxItems: 1
+
+required:
+ - compatible
+ - reg
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/nvmem/microchip,sama7g5-otpc.h>
+
+ otpc: efuse@e8c00000 {
+ compatible = "microchip,sama7g5-otpc", "syscon";
+ reg = <0xe8c00000 0xec>;
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ temperature_calib: calib@1 {
+ reg = <OTP_PKT(1) 76>;
+ };
+ };
+
+...
diff --git a/Documentation/devicetree/bindings/nvmem/mtk-efuse.txt b/Documentation/devicetree/bindings/nvmem/mtk-efuse.txt
deleted file mode 100644
index 39d529599444..000000000000
--- a/Documentation/devicetree/bindings/nvmem/mtk-efuse.txt
+++ /dev/null
@@ -1,43 +0,0 @@
-= Mediatek MTK-EFUSE device tree bindings =
-
-This binding is intended to represent MTK-EFUSE which is found in most Mediatek SOCs.
-
-Required properties:
-- compatible: should be
- "mediatek,mt7622-efuse", "mediatek,efuse": for MT7622
- "mediatek,mt7623-efuse", "mediatek,efuse": for MT7623
- "mediatek,mt8173-efuse" or "mediatek,efuse": for MT8173
- "mediatek,mt8192-efuse", "mediatek,efuse": for MT8192
- "mediatek,mt8195-efuse", "mediatek,efuse": for MT8195
- "mediatek,mt8516-efuse", "mediatek,efuse": for MT8516
-- reg: Should contain registers location and length
-- bits: contain the bits range by offset and size
-
-= Data cells =
-Are child nodes of MTK-EFUSE, bindings of which as described in
-bindings/nvmem/nvmem.txt
-
-Example:
-
- efuse: efuse@10206000 {
- compatible = "mediatek,mt8173-efuse";
- reg = <0 0x10206000 0 0x1000>;
- #address-cells = <1>;
- #size-cells = <1>;
-
- /* Data cells */
- thermal_calibration: calib@528 {
- reg = <0x528 0xc>;
- };
- };
-
-= Data consumers =
-Are device nodes which consume nvmem data cells.
-
-For example:
-
- thermal {
- ...
- nvmem-cells = <&thermal_calibration>;
- nvmem-cell-names = "calibration";
- };
diff --git a/MAINTAINERS b/MAINTAINERS
index ab956d6ac778..996bcbea50b5 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -13247,6 +13247,14 @@ S: Supported
F: Documentation/devicetree/bindings/mtd/atmel-nand.txt
F: drivers/mtd/nand/raw/atmel/*
+MICROCHIP OTPC DRIVER
+M: Claudiu Beznea <claudiu.beznea@microchip.com>
+L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
+S: Supported
+F: Documentation/devicetree/bindings/nvmem/microchip,sama7g5-otpc.yaml
+F: drivers/nvmem/microchip-otpc.c
+F: include/dt-bindings/nvmem/microchip,sama7g5-otpc.h
+
MICROCHIP PWM DRIVER
M: Claudiu Beznea <claudiu.beznea@microchip.com>
L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
diff --git a/drivers/nvmem/Kconfig b/drivers/nvmem/Kconfig
index 967d0084800e..d72d879a6d34 100644
--- a/drivers/nvmem/Kconfig
+++ b/drivers/nvmem/Kconfig
@@ -107,6 +107,13 @@ config MTK_EFUSE
This driver can also be built as a module. If so, the module
will be called efuse-mtk.
+config MICROCHIP_OTPC
+ tristate "Microchip OTPC support"
+ depends on ARCH_AT91 || COMPILE_TEST
+ help
+ This driver enable the OTP controller available on Microchip SAMA7G5
+ SoCs. It controlls the access to the OTP memory connected to it.
+
config NVMEM_NINTENDO_OTP
tristate "Nintendo Wii and Wii U OTP Support"
depends on WII || COMPILE_TEST
diff --git a/drivers/nvmem/Makefile b/drivers/nvmem/Makefile
index 00e136a0a123..c710b64f9fe4 100644
--- a/drivers/nvmem/Makefile
+++ b/drivers/nvmem/Makefile
@@ -67,3 +67,5 @@ obj-$(CONFIG_NVMEM_SUNPLUS_OCOTP) += nvmem_sunplus_ocotp.o
nvmem_sunplus_ocotp-y := sunplus-ocotp.o
obj-$(CONFIG_NVMEM_APPLE_EFUSES) += nvmem-apple-efuses.o
nvmem-apple-efuses-y := apple-efuses.o
+obj-$(CONFIG_MICROCHIP_OTPC) += nvmem-microchip-otpc.o
+nvmem-microchip-otpc-y := microchip-otpc.o
diff --git a/drivers/nvmem/microchip-otpc.c b/drivers/nvmem/microchip-otpc.c
new file mode 100644
index 000000000000..436e0dc4f337
--- /dev/null
+++ b/drivers/nvmem/microchip-otpc.c
@@ -0,0 +1,288 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * OTP Memory controller
+ *
+ * Copyright (C) 2022 Microchip Technology Inc. and its subsidiaries
+ *
+ * Author: Claudiu Beznea <claudiu.beznea@microchip.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/iopoll.h>
+#include <linux/module.h>
+#include <linux/nvmem-provider.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+
+#define MCHP_OTPC_CR (0x0)
+#define MCHP_OTPC_CR_READ BIT(6)
+#define MCHP_OTPC_MR (0x4)
+#define MCHP_OTPC_MR_ADDR GENMASK(31, 16)
+#define MCHP_OTPC_AR (0x8)
+#define MCHP_OTPC_SR (0xc)
+#define MCHP_OTPC_SR_READ BIT(6)
+#define MCHP_OTPC_HR (0x20)
+#define MCHP_OTPC_HR_SIZE GENMASK(15, 8)
+#define MCHP_OTPC_DR (0x24)
+
+#define MCHP_OTPC_NAME "mchp-otpc"
+#define MCHP_OTPC_SIZE (11 * 1024)
+
+/**
+ * struct mchp_otpc - OTPC private data structure
+ * @base: base address
+ * @dev: struct device pointer
+ * @packets: list of packets in OTP memory
+ * @npackets: number of packets in OTP memory
+ */
+struct mchp_otpc {
+ void __iomem *base;
+ struct device *dev;
+ struct list_head packets;
+ u32 npackets;
+};
+
+/**
+ * struct mchp_otpc_packet - OTPC packet data structure
+ * @list: list head
+ * @id: packet ID
+ * @offset: packet offset (in words) in OTP memory
+ */
+struct mchp_otpc_packet {
+ struct list_head list;
+ u32 id;
+ u32 offset;
+};
+
+static struct mchp_otpc_packet *mchp_otpc_id_to_packet(struct mchp_otpc *otpc,
+ u32 id)
+{
+ struct mchp_otpc_packet *packet;
+
+ if (id >= otpc->npackets)
+ return NULL;
+
+ list_for_each_entry(packet, &otpc->packets, list) {
+ if (packet->id == id)
+ return packet;
+ }
+
+ return NULL;
+}
+
+static int mchp_otpc_prepare_read(struct mchp_otpc *otpc,
+ unsigned int offset)
+{
+ u32 tmp;
+
+ /* Set address. */
+ tmp = readl_relaxed(otpc->base + MCHP_OTPC_MR);
+ tmp &= ~MCHP_OTPC_MR_ADDR;
+ tmp |= FIELD_PREP(MCHP_OTPC_MR_ADDR, offset);
+ writel_relaxed(tmp, otpc->base + MCHP_OTPC_MR);
+
+ /* Set read. */
+ tmp = readl_relaxed(otpc->base + MCHP_OTPC_CR);
+ tmp |= MCHP_OTPC_CR_READ;
+ writel_relaxed(tmp, otpc->base + MCHP_OTPC_CR);
+
+ /* Wait for packet to be transferred into temporary buffers. */
+ return read_poll_timeout(readl_relaxed, tmp, !(tmp & MCHP_OTPC_SR_READ),
+ 10000, 2000, false, otpc->base + MCHP_OTPC_SR);
+}
+
+/*
+ * OTPC memory is organized into packets. Each packets contains a header and
+ * a payload. Header is 4 bytes long and contains the size of the payload.
+ * Payload size varies. The memory footprint is something as follows:
+ *
+ * Memory offset Memory footprint Packet ID
+ * ------------- ---------------- ---------
+ *
+ * 0x0 +------------+ <-- packet 0
+ * | header 0 |
+ * 0x4 +------------+
+ * | payload 0 |
+ * . .
+ * . ... .
+ * . .
+ * offset1 +------------+ <-- packet 1
+ * | header 1 |
+ * offset1 + 0x4 +------------+
+ * | payload 1 |
+ * . .
+ * . ... .
+ * . .
+ * offset2 +------------+ <-- packet 2
+ * . .
+ * . ... .
+ * . .
+ * offsetN +------------+ <-- packet N
+ * | header N |
+ * offsetN + 0x4 +------------+
+ * | payload N |
+ * . .
+ * . ... .
+ * . .
+ * +------------+
+ *
+ * where offset1, offset2, offsetN depends on the size of payload 0, payload 1,
+ * payload N-1.
+ *
+ * The access to memory is done on a per packet basis: the control registers
+ * need to be updated with an offset address (within a packet range) and the
+ * data registers will be update by controller with information contained by
+ * that packet. E.g. if control registers are updated with any address within
+ * the range [offset1, offset2) the data registers are updated by controller
+ * with packet 1. Header data is accessible though MCHP_OTPC_HR register.
+ * Payload data is accessible though MCHP_OTPC_DR and MCHP_OTPC_AR registers.
+ * There is no direct mapping b/w the offset requested by software and the
+ * offset returned by hardware.
+ *
+ * For this, the read function will return the first requested bytes in the
+ * packet. The user will have to be aware of the memory footprint before doing
+ * the read request.
+ */
+static int mchp_otpc_read(void *priv, unsigned int off, void *val,
+ size_t bytes)
+{
+ struct mchp_otpc *otpc = priv;
+ struct mchp_otpc_packet *packet;
+ u32 *buf = val;
+ u32 offset;
+ size_t len = 0;
+ int ret, payload_size;
+
+ /*
+ * We reach this point with off being multiple of stride = 4 to
+ * be able to cross the subsystem. Inside the driver we use continuous
+ * unsigned integer numbers for packet id, thus devide off by 4
+ * before passing it to mchp_otpc_id_to_packet().
+ */
+ packet = mchp_otpc_id_to_packet(otpc, off / 4);
+ if (!packet)
+ return -EINVAL;
+ offset = packet->offset;
+
+ while (len < bytes) {
+ ret = mchp_otpc_prepare_read(otpc, offset);
+ if (ret)
+ return ret;
+
+ /* Read and save header content. */
+ *buf++ = readl_relaxed(otpc->base + MCHP_OTPC_HR);
+ len += sizeof(*buf);
+ offset++;
+ if (len >= bytes)
+ break;
+
+ /* Read and save payload content. */
+ payload_size = FIELD_GET(MCHP_OTPC_HR_SIZE, *(buf - 1));
+ writel_relaxed(0UL, otpc->base + MCHP_OTPC_AR);
+ do {
+ *buf++ = readl_relaxed(otpc->base + MCHP_OTPC_DR);
+ len += sizeof(*buf);
+ offset++;
+ payload_size--;
+ } while (payload_size >= 0 && len < bytes);
+ }
+
+ return 0;
+}
+
+static int mchp_otpc_init_packets_list(struct mchp_otpc *otpc, u32 *size)
+{
+ struct mchp_otpc_packet *packet;
+ u32 word, word_pos = 0, id = 0, npackets = 0, payload_size;
+ int ret;
+
+ INIT_LIST_HEAD(&otpc->packets);
+ *size = 0;
+
+ while (*size < MCHP_OTPC_SIZE) {
+ ret = mchp_otpc_prepare_read(otpc, word_pos);
+ if (ret)
+ return ret;
+
+ word = readl_relaxed(otpc->base + MCHP_OTPC_HR);
+ payload_size = FIELD_GET(MCHP_OTPC_HR_SIZE, word);
+ if (!payload_size)
+ break;
+
+ packet = devm_kzalloc(otpc->dev, sizeof(*packet), GFP_KERNEL);
+ if (!packet)
+ return -ENOMEM;
+
+ packet->id = id++;
+ packet->offset = word_pos;
+ INIT_LIST_HEAD(&packet->list);
+ list_add_tail(&packet->list, &otpc->packets);
+
+ /* Count size by adding header and paload sizes. */
+ *size += 4 * (payload_size + 1);
+ /* Next word: this packet (header, payload) position + 1. */
+ word_pos += payload_size + 2;
+
+ npackets++;
+ }
+
+ otpc->npackets = npackets;
+
+ return 0;
+}
+
+static struct nvmem_config mchp_nvmem_config = {
+ .name = MCHP_OTPC_NAME,
+ .type = NVMEM_TYPE_OTP,
+ .read_only = true,
+ .word_size = 4,
+ .stride = 4,
+ .reg_read = mchp_otpc_read,
+};
+
+static int mchp_otpc_probe(struct platform_device *pdev)
+{
+ struct nvmem_device *nvmem;
+ struct mchp_otpc *otpc;
+ u32 size;
+ int ret;
+
+ otpc = devm_kzalloc(&pdev->dev, sizeof(*otpc), GFP_KERNEL);
+ if (!otpc)
+ return -ENOMEM;
+
+ otpc->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(otpc->base))
+ return PTR_ERR(otpc->base);
+
+ otpc->dev = &pdev->dev;
+ ret = mchp_otpc_init_packets_list(otpc, &size);
+ if (ret)
+ return ret;
+
+ mchp_nvmem_config.dev = otpc->dev;
+ mchp_nvmem_config.size = size;
+ mchp_nvmem_config.priv = otpc;
+ nvmem = devm_nvmem_register(&pdev->dev, &mchp_nvmem_config);
+
+ return PTR_ERR_OR_ZERO(nvmem);
+}
+
+static const struct of_device_id __maybe_unused mchp_otpc_ids[] = {
+ { .compatible = "microchip,sama7g5-otpc", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, mchp_otpc_ids);
+
+static struct platform_driver mchp_otpc_driver = {
+ .probe = mchp_otpc_probe,
+ .driver = {
+ .name = MCHP_OTPC_NAME,
+ .of_match_table = of_match_ptr(mchp_otpc_ids),
+ },
+};
+module_platform_driver(mchp_otpc_driver);
+
+MODULE_AUTHOR("Claudiu Beznea <claudiu.beznea@microchip.com>");
+MODULE_DESCRIPTION("Microchip SAMA7G5 OTPC driver");
+MODULE_LICENSE("GPL");
diff --git a/include/dt-bindings/nvmem/microchip,sama7g5-otpc.h b/include/dt-bindings/nvmem/microchip,sama7g5-otpc.h
new file mode 100644
index 000000000000..f570b23165a2
--- /dev/null
+++ b/include/dt-bindings/nvmem/microchip,sama7g5-otpc.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */
+
+#ifndef _DT_BINDINGS_NVMEM_MICROCHIP_OTPC_H
+#define _DT_BINDINGS_NVMEM_MICROCHIP_OTPC_H
+
+/*
+ * Need to have it as a multiple of 4 as NVMEM memory is registered with
+ * stride = 4.
+ */
+#define OTP_PKT(id) ((id) * 4)
+
+#endif