mirror of
https://github.com/openwrt/openwrt.git
synced 2024-12-20 22:23:27 +00:00
8dfe69cdfc
Signed-off-by: Rafał Miłecki <rafal@milecki.pl>
390 lines
12 KiB
Diff
390 lines
12 KiB
Diff
From 98830350d3fc824c1ff5c338140fe20f041a5916 Mon Sep 17 00:00:00 2001
|
|
From: Claudiu Beznea <claudiu.beznea@microchip.com>
|
|
Date: Wed, 6 Jul 2022 11:06:22 +0100
|
|
Subject: [PATCH] nvmem: microchip-otpc: add support
|
|
|
|
Add support for Microchip OTP controller available on SAMA7G5. The OTPC
|
|
controls the access to a non-volatile memory. The memory behind OTPC is
|
|
organized into packets, packets are composed by a fixed length header
|
|
(4 bytes long) and a variable length payload (payload length is available
|
|
in the header). When software request the data at an offset in memory
|
|
the OTPC will return (via header + data registers) the whole packet that
|
|
has a word at that offset. For the OTP memory layout like below:
|
|
|
|
offset OTP Memory layout
|
|
|
|
. .
|
|
. ... .
|
|
. .
|
|
0x0E +-----------+ <--- packet X
|
|
| header X |
|
|
0x12 +-----------+
|
|
| payload X |
|
|
0x16 | |
|
|
| |
|
|
0x1A | |
|
|
+-----------+
|
|
. .
|
|
. ... .
|
|
. .
|
|
|
|
if user requests data at address 0x16 the data started at 0x0E will be
|
|
returned by controller. User will be able to fetch the whole packet
|
|
starting at 0x0E (or parts of the packet) via proper registers. The same
|
|
packet will be returned if software request the data at offset 0x0E or
|
|
0x12 or 0x1A.
|
|
|
|
The OTP will be populated by Microchip with at least 2 packets first one
|
|
being boot configuration packet and the 2nd one being temperature
|
|
calibration packet. The packet order will be preserved b/w different chip
|
|
revisions but the packet sizes may change.
|
|
|
|
For the above reasons and to keep the same software able to work on all
|
|
chip variants the read function of the driver is working with a packet
|
|
id instead of an offset in OTP memory.
|
|
|
|
Signed-off-by: Claudiu Beznea <claudiu.beznea@microchip.com>
|
|
Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
|
|
Link: https://lore.kernel.org/r/20220706100627.6534-3-srinivas.kandagatla@linaro.org
|
|
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
|
|
---
|
|
MAINTAINERS | 8 +
|
|
drivers/nvmem/Kconfig | 7 +
|
|
drivers/nvmem/Makefile | 2 +
|
|
drivers/nvmem/microchip-otpc.c | 288 +++++++++++++++++++++++++++++++++
|
|
4 files changed, 305 insertions(+)
|
|
create mode 100644 drivers/nvmem/microchip-otpc.c
|
|
|
|
--- a/MAINTAINERS
|
|
+++ b/MAINTAINERS
|
|
@@ -11565,6 +11565,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: 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)
|
|
--- 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
|
|
--- a/drivers/nvmem/Makefile
|
|
+++ b/drivers/nvmem/Makefile
|
|
@@ -67,3 +67,5 @@ obj-$(CONFIG_NVMEM_SUNPLUS_OCOTP) += nvm
|
|
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
|
|
--- /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");
|