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");
 | 
