162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * OTP Memory controller
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (C) 2022 Microchip Technology Inc. and its subsidiaries
662306a36Sopenharmony_ci *
762306a36Sopenharmony_ci * Author: Claudiu Beznea <claudiu.beznea@microchip.com>
862306a36Sopenharmony_ci */
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ci#include <linux/bitfield.h>
1162306a36Sopenharmony_ci#include <linux/iopoll.h>
1262306a36Sopenharmony_ci#include <linux/module.h>
1362306a36Sopenharmony_ci#include <linux/nvmem-provider.h>
1462306a36Sopenharmony_ci#include <linux/of.h>
1562306a36Sopenharmony_ci#include <linux/platform_device.h>
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_ci#define MCHP_OTPC_CR			(0x0)
1862306a36Sopenharmony_ci#define MCHP_OTPC_CR_READ		BIT(6)
1962306a36Sopenharmony_ci#define MCHP_OTPC_MR			(0x4)
2062306a36Sopenharmony_ci#define MCHP_OTPC_MR_ADDR		GENMASK(31, 16)
2162306a36Sopenharmony_ci#define MCHP_OTPC_AR			(0x8)
2262306a36Sopenharmony_ci#define MCHP_OTPC_SR			(0xc)
2362306a36Sopenharmony_ci#define MCHP_OTPC_SR_READ		BIT(6)
2462306a36Sopenharmony_ci#define MCHP_OTPC_HR			(0x20)
2562306a36Sopenharmony_ci#define MCHP_OTPC_HR_SIZE		GENMASK(15, 8)
2662306a36Sopenharmony_ci#define MCHP_OTPC_DR			(0x24)
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_ci#define MCHP_OTPC_NAME			"mchp-otpc"
2962306a36Sopenharmony_ci#define MCHP_OTPC_SIZE			(11 * 1024)
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_ci/**
3262306a36Sopenharmony_ci * struct mchp_otpc - OTPC private data structure
3362306a36Sopenharmony_ci * @base: base address
3462306a36Sopenharmony_ci * @dev: struct device pointer
3562306a36Sopenharmony_ci * @packets: list of packets in OTP memory
3662306a36Sopenharmony_ci * @npackets: number of packets in OTP memory
3762306a36Sopenharmony_ci */
3862306a36Sopenharmony_cistruct mchp_otpc {
3962306a36Sopenharmony_ci	void __iomem *base;
4062306a36Sopenharmony_ci	struct device *dev;
4162306a36Sopenharmony_ci	struct list_head packets;
4262306a36Sopenharmony_ci	u32 npackets;
4362306a36Sopenharmony_ci};
4462306a36Sopenharmony_ci
4562306a36Sopenharmony_ci/**
4662306a36Sopenharmony_ci * struct mchp_otpc_packet - OTPC packet data structure
4762306a36Sopenharmony_ci * @list: list head
4862306a36Sopenharmony_ci * @id: packet ID
4962306a36Sopenharmony_ci * @offset: packet offset (in words) in OTP memory
5062306a36Sopenharmony_ci */
5162306a36Sopenharmony_cistruct mchp_otpc_packet {
5262306a36Sopenharmony_ci	struct list_head list;
5362306a36Sopenharmony_ci	u32 id;
5462306a36Sopenharmony_ci	u32 offset;
5562306a36Sopenharmony_ci};
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_cistatic struct mchp_otpc_packet *mchp_otpc_id_to_packet(struct mchp_otpc *otpc,
5862306a36Sopenharmony_ci						       u32 id)
5962306a36Sopenharmony_ci{
6062306a36Sopenharmony_ci	struct mchp_otpc_packet *packet;
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_ci	if (id >= otpc->npackets)
6362306a36Sopenharmony_ci		return NULL;
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_ci	list_for_each_entry(packet, &otpc->packets, list) {
6662306a36Sopenharmony_ci		if (packet->id == id)
6762306a36Sopenharmony_ci			return packet;
6862306a36Sopenharmony_ci	}
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_ci	return NULL;
7162306a36Sopenharmony_ci}
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_cistatic int mchp_otpc_prepare_read(struct mchp_otpc *otpc,
7462306a36Sopenharmony_ci				  unsigned int offset)
7562306a36Sopenharmony_ci{
7662306a36Sopenharmony_ci	u32 tmp;
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_ci	/* Set address. */
7962306a36Sopenharmony_ci	tmp = readl_relaxed(otpc->base + MCHP_OTPC_MR);
8062306a36Sopenharmony_ci	tmp &= ~MCHP_OTPC_MR_ADDR;
8162306a36Sopenharmony_ci	tmp |= FIELD_PREP(MCHP_OTPC_MR_ADDR, offset);
8262306a36Sopenharmony_ci	writel_relaxed(tmp, otpc->base + MCHP_OTPC_MR);
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_ci	/* Set read. */
8562306a36Sopenharmony_ci	tmp = readl_relaxed(otpc->base + MCHP_OTPC_CR);
8662306a36Sopenharmony_ci	tmp |= MCHP_OTPC_CR_READ;
8762306a36Sopenharmony_ci	writel_relaxed(tmp, otpc->base + MCHP_OTPC_CR);
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_ci	/* Wait for packet to be transferred into temporary buffers. */
9062306a36Sopenharmony_ci	return read_poll_timeout(readl_relaxed, tmp, !(tmp & MCHP_OTPC_SR_READ),
9162306a36Sopenharmony_ci				 10000, 2000, false, otpc->base + MCHP_OTPC_SR);
9262306a36Sopenharmony_ci}
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_ci/*
9562306a36Sopenharmony_ci * OTPC memory is organized into packets. Each packets contains a header and
9662306a36Sopenharmony_ci * a payload. Header is 4 bytes long and contains the size of the payload.
9762306a36Sopenharmony_ci * Payload size varies. The memory footprint is something as follows:
9862306a36Sopenharmony_ci *
9962306a36Sopenharmony_ci * Memory offset  Memory footprint     Packet ID
10062306a36Sopenharmony_ci * -------------  ----------------     ---------
10162306a36Sopenharmony_ci *
10262306a36Sopenharmony_ci * 0x0            +------------+   <-- packet 0
10362306a36Sopenharmony_ci *                | header  0  |
10462306a36Sopenharmony_ci * 0x4            +------------+
10562306a36Sopenharmony_ci *                | payload 0  |
10662306a36Sopenharmony_ci *                .            .
10762306a36Sopenharmony_ci *                .    ...     .
10862306a36Sopenharmony_ci *                .            .
10962306a36Sopenharmony_ci * offset1        +------------+   <-- packet 1
11062306a36Sopenharmony_ci *                | header  1  |
11162306a36Sopenharmony_ci * offset1 + 0x4  +------------+
11262306a36Sopenharmony_ci *                | payload 1  |
11362306a36Sopenharmony_ci *                .            .
11462306a36Sopenharmony_ci *                .    ...     .
11562306a36Sopenharmony_ci *                .            .
11662306a36Sopenharmony_ci * offset2        +------------+   <-- packet 2
11762306a36Sopenharmony_ci *                .            .
11862306a36Sopenharmony_ci *                .    ...     .
11962306a36Sopenharmony_ci *                .            .
12062306a36Sopenharmony_ci * offsetN        +------------+   <-- packet N
12162306a36Sopenharmony_ci *                | header  N  |
12262306a36Sopenharmony_ci * offsetN + 0x4  +------------+
12362306a36Sopenharmony_ci *                | payload N  |
12462306a36Sopenharmony_ci *                .            .
12562306a36Sopenharmony_ci *                .    ...     .
12662306a36Sopenharmony_ci *                .            .
12762306a36Sopenharmony_ci *                +------------+
12862306a36Sopenharmony_ci *
12962306a36Sopenharmony_ci * where offset1, offset2, offsetN depends on the size of payload 0, payload 1,
13062306a36Sopenharmony_ci * payload N-1.
13162306a36Sopenharmony_ci *
13262306a36Sopenharmony_ci * The access to memory is done on a per packet basis: the control registers
13362306a36Sopenharmony_ci * need to be updated with an offset address (within a packet range) and the
13462306a36Sopenharmony_ci * data registers will be update by controller with information contained by
13562306a36Sopenharmony_ci * that packet. E.g. if control registers are updated with any address within
13662306a36Sopenharmony_ci * the range [offset1, offset2) the data registers are updated by controller
13762306a36Sopenharmony_ci * with packet 1. Header data is accessible though MCHP_OTPC_HR register.
13862306a36Sopenharmony_ci * Payload data is accessible though MCHP_OTPC_DR and MCHP_OTPC_AR registers.
13962306a36Sopenharmony_ci * There is no direct mapping b/w the offset requested by software and the
14062306a36Sopenharmony_ci * offset returned by hardware.
14162306a36Sopenharmony_ci *
14262306a36Sopenharmony_ci * For this, the read function will return the first requested bytes in the
14362306a36Sopenharmony_ci * packet. The user will have to be aware of the memory footprint before doing
14462306a36Sopenharmony_ci * the read request.
14562306a36Sopenharmony_ci */
14662306a36Sopenharmony_cistatic int mchp_otpc_read(void *priv, unsigned int off, void *val,
14762306a36Sopenharmony_ci			  size_t bytes)
14862306a36Sopenharmony_ci{
14962306a36Sopenharmony_ci	struct mchp_otpc *otpc = priv;
15062306a36Sopenharmony_ci	struct mchp_otpc_packet *packet;
15162306a36Sopenharmony_ci	u32 *buf = val;
15262306a36Sopenharmony_ci	u32 offset;
15362306a36Sopenharmony_ci	size_t len = 0;
15462306a36Sopenharmony_ci	int ret, payload_size;
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_ci	/*
15762306a36Sopenharmony_ci	 * We reach this point with off being multiple of stride = 4 to
15862306a36Sopenharmony_ci	 * be able to cross the subsystem. Inside the driver we use continuous
15962306a36Sopenharmony_ci	 * unsigned integer numbers for packet id, thus devide off by 4
16062306a36Sopenharmony_ci	 * before passing it to mchp_otpc_id_to_packet().
16162306a36Sopenharmony_ci	 */
16262306a36Sopenharmony_ci	packet = mchp_otpc_id_to_packet(otpc, off / 4);
16362306a36Sopenharmony_ci	if (!packet)
16462306a36Sopenharmony_ci		return -EINVAL;
16562306a36Sopenharmony_ci	offset = packet->offset;
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_ci	while (len < bytes) {
16862306a36Sopenharmony_ci		ret = mchp_otpc_prepare_read(otpc, offset);
16962306a36Sopenharmony_ci		if (ret)
17062306a36Sopenharmony_ci			return ret;
17162306a36Sopenharmony_ci
17262306a36Sopenharmony_ci		/* Read and save header content. */
17362306a36Sopenharmony_ci		*buf++ = readl_relaxed(otpc->base + MCHP_OTPC_HR);
17462306a36Sopenharmony_ci		len += sizeof(*buf);
17562306a36Sopenharmony_ci		offset++;
17662306a36Sopenharmony_ci		if (len >= bytes)
17762306a36Sopenharmony_ci			break;
17862306a36Sopenharmony_ci
17962306a36Sopenharmony_ci		/* Read and save payload content. */
18062306a36Sopenharmony_ci		payload_size = FIELD_GET(MCHP_OTPC_HR_SIZE, *(buf - 1));
18162306a36Sopenharmony_ci		writel_relaxed(0UL, otpc->base + MCHP_OTPC_AR);
18262306a36Sopenharmony_ci		do {
18362306a36Sopenharmony_ci			*buf++ = readl_relaxed(otpc->base + MCHP_OTPC_DR);
18462306a36Sopenharmony_ci			len += sizeof(*buf);
18562306a36Sopenharmony_ci			offset++;
18662306a36Sopenharmony_ci			payload_size--;
18762306a36Sopenharmony_ci		} while (payload_size >= 0 && len < bytes);
18862306a36Sopenharmony_ci	}
18962306a36Sopenharmony_ci
19062306a36Sopenharmony_ci	return 0;
19162306a36Sopenharmony_ci}
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_cistatic int mchp_otpc_init_packets_list(struct mchp_otpc *otpc, u32 *size)
19462306a36Sopenharmony_ci{
19562306a36Sopenharmony_ci	struct mchp_otpc_packet *packet;
19662306a36Sopenharmony_ci	u32 word, word_pos = 0, id = 0, npackets = 0, payload_size;
19762306a36Sopenharmony_ci	int ret;
19862306a36Sopenharmony_ci
19962306a36Sopenharmony_ci	INIT_LIST_HEAD(&otpc->packets);
20062306a36Sopenharmony_ci	*size = 0;
20162306a36Sopenharmony_ci
20262306a36Sopenharmony_ci	while (*size < MCHP_OTPC_SIZE) {
20362306a36Sopenharmony_ci		ret = mchp_otpc_prepare_read(otpc, word_pos);
20462306a36Sopenharmony_ci		if (ret)
20562306a36Sopenharmony_ci			return ret;
20662306a36Sopenharmony_ci
20762306a36Sopenharmony_ci		word = readl_relaxed(otpc->base + MCHP_OTPC_HR);
20862306a36Sopenharmony_ci		payload_size = FIELD_GET(MCHP_OTPC_HR_SIZE, word);
20962306a36Sopenharmony_ci		if (!payload_size)
21062306a36Sopenharmony_ci			break;
21162306a36Sopenharmony_ci
21262306a36Sopenharmony_ci		packet = devm_kzalloc(otpc->dev, sizeof(*packet), GFP_KERNEL);
21362306a36Sopenharmony_ci		if (!packet)
21462306a36Sopenharmony_ci			return -ENOMEM;
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_ci		packet->id = id++;
21762306a36Sopenharmony_ci		packet->offset = word_pos;
21862306a36Sopenharmony_ci		INIT_LIST_HEAD(&packet->list);
21962306a36Sopenharmony_ci		list_add_tail(&packet->list, &otpc->packets);
22062306a36Sopenharmony_ci
22162306a36Sopenharmony_ci		/* Count size by adding header and paload sizes. */
22262306a36Sopenharmony_ci		*size += 4 * (payload_size + 1);
22362306a36Sopenharmony_ci		/* Next word: this packet (header, payload) position + 1. */
22462306a36Sopenharmony_ci		word_pos += payload_size + 2;
22562306a36Sopenharmony_ci
22662306a36Sopenharmony_ci		npackets++;
22762306a36Sopenharmony_ci	}
22862306a36Sopenharmony_ci
22962306a36Sopenharmony_ci	otpc->npackets = npackets;
23062306a36Sopenharmony_ci
23162306a36Sopenharmony_ci	return 0;
23262306a36Sopenharmony_ci}
23362306a36Sopenharmony_ci
23462306a36Sopenharmony_cistatic struct nvmem_config mchp_nvmem_config = {
23562306a36Sopenharmony_ci	.name = MCHP_OTPC_NAME,
23662306a36Sopenharmony_ci	.type = NVMEM_TYPE_OTP,
23762306a36Sopenharmony_ci	.read_only = true,
23862306a36Sopenharmony_ci	.word_size = 4,
23962306a36Sopenharmony_ci	.stride = 4,
24062306a36Sopenharmony_ci	.reg_read = mchp_otpc_read,
24162306a36Sopenharmony_ci};
24262306a36Sopenharmony_ci
24362306a36Sopenharmony_cistatic int mchp_otpc_probe(struct platform_device *pdev)
24462306a36Sopenharmony_ci{
24562306a36Sopenharmony_ci	struct nvmem_device *nvmem;
24662306a36Sopenharmony_ci	struct mchp_otpc *otpc;
24762306a36Sopenharmony_ci	u32 size;
24862306a36Sopenharmony_ci	int ret;
24962306a36Sopenharmony_ci
25062306a36Sopenharmony_ci	otpc = devm_kzalloc(&pdev->dev, sizeof(*otpc), GFP_KERNEL);
25162306a36Sopenharmony_ci	if (!otpc)
25262306a36Sopenharmony_ci		return -ENOMEM;
25362306a36Sopenharmony_ci
25462306a36Sopenharmony_ci	otpc->base = devm_platform_ioremap_resource(pdev, 0);
25562306a36Sopenharmony_ci	if (IS_ERR(otpc->base))
25662306a36Sopenharmony_ci		return PTR_ERR(otpc->base);
25762306a36Sopenharmony_ci
25862306a36Sopenharmony_ci	otpc->dev = &pdev->dev;
25962306a36Sopenharmony_ci	ret = mchp_otpc_init_packets_list(otpc, &size);
26062306a36Sopenharmony_ci	if (ret)
26162306a36Sopenharmony_ci		return ret;
26262306a36Sopenharmony_ci
26362306a36Sopenharmony_ci	mchp_nvmem_config.dev = otpc->dev;
26462306a36Sopenharmony_ci	mchp_nvmem_config.size = size;
26562306a36Sopenharmony_ci	mchp_nvmem_config.priv = otpc;
26662306a36Sopenharmony_ci	nvmem = devm_nvmem_register(&pdev->dev, &mchp_nvmem_config);
26762306a36Sopenharmony_ci
26862306a36Sopenharmony_ci	return PTR_ERR_OR_ZERO(nvmem);
26962306a36Sopenharmony_ci}
27062306a36Sopenharmony_ci
27162306a36Sopenharmony_cistatic const struct of_device_id __maybe_unused mchp_otpc_ids[] = {
27262306a36Sopenharmony_ci	{ .compatible = "microchip,sama7g5-otpc", },
27362306a36Sopenharmony_ci	{ },
27462306a36Sopenharmony_ci};
27562306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, mchp_otpc_ids);
27662306a36Sopenharmony_ci
27762306a36Sopenharmony_cistatic struct platform_driver mchp_otpc_driver = {
27862306a36Sopenharmony_ci	.probe = mchp_otpc_probe,
27962306a36Sopenharmony_ci	.driver = {
28062306a36Sopenharmony_ci		.name = MCHP_OTPC_NAME,
28162306a36Sopenharmony_ci		.of_match_table = of_match_ptr(mchp_otpc_ids),
28262306a36Sopenharmony_ci	},
28362306a36Sopenharmony_ci};
28462306a36Sopenharmony_cimodule_platform_driver(mchp_otpc_driver);
28562306a36Sopenharmony_ci
28662306a36Sopenharmony_ciMODULE_AUTHOR("Claudiu Beznea <claudiu.beznea@microchip.com>");
28762306a36Sopenharmony_ciMODULE_DESCRIPTION("Microchip SAMA7G5 OTPC driver");
28862306a36Sopenharmony_ciMODULE_LICENSE("GPL");
289