mirror of
				git://git.openwrt.org/openwrt/openwrt.git
				synced 2025-11-04 06:54:27 -05:00 
			
		
		
		
	Changelog: https://lore.kernel.org/stable/2023080341-curliness-salary-4158@gregkh/ 1. Needed to make a change to to package/kernel/linux/modules/netsupport.mk due to upstream moving vxlan to its own directory[1]. @john-tho suggested using the the 6.1 xvlan FILES to circumvent. 2. All patches automatically rebased. 1. https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/commit/?h=v5.15.124&id=77396fa9096abdbfbb87d63e73ad44d5621cf103 Signed-off-by: John Audia <therealgraysky@proton.me>
		
			
				
	
	
		
			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
 | 
						|
@@ -12358,6 +12358,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");
 |