Linux Audio

Check our new training course

Loading...
Note: File does not exist in v3.1.
  1// SPDX-License-Identifier: GPL-2.0
  2/*
  3 * OTP Memory controller
  4 *
  5 * Copyright (C) 2022 Microchip Technology Inc. and its subsidiaries
  6 *
  7 * Author: Claudiu Beznea <claudiu.beznea@microchip.com>
  8 */
  9
 10#include <linux/bitfield.h>
 11#include <linux/iopoll.h>
 12#include <linux/module.h>
 13#include <linux/nvmem-provider.h>
 14#include <linux/of.h>
 15#include <linux/platform_device.h>
 16
 17#define MCHP_OTPC_CR			(0x0)
 18#define MCHP_OTPC_CR_READ		BIT(6)
 19#define MCHP_OTPC_MR			(0x4)
 20#define MCHP_OTPC_MR_ADDR		GENMASK(31, 16)
 21#define MCHP_OTPC_AR			(0x8)
 22#define MCHP_OTPC_SR			(0xc)
 23#define MCHP_OTPC_SR_READ		BIT(6)
 24#define MCHP_OTPC_HR			(0x20)
 25#define MCHP_OTPC_HR_SIZE		GENMASK(15, 8)
 26#define MCHP_OTPC_DR			(0x24)
 27
 28#define MCHP_OTPC_NAME			"mchp-otpc"
 29#define MCHP_OTPC_SIZE			(11 * 1024)
 30
 31/**
 32 * struct mchp_otpc - OTPC private data structure
 33 * @base: base address
 34 * @dev: struct device pointer
 35 * @packets: list of packets in OTP memory
 36 * @npackets: number of packets in OTP memory
 37 */
 38struct mchp_otpc {
 39	void __iomem *base;
 40	struct device *dev;
 41	struct list_head packets;
 42	u32 npackets;
 43};
 44
 45/**
 46 * struct mchp_otpc_packet - OTPC packet data structure
 47 * @list: list head
 48 * @id: packet ID
 49 * @offset: packet offset (in words) in OTP memory
 50 */
 51struct mchp_otpc_packet {
 52	struct list_head list;
 53	u32 id;
 54	u32 offset;
 55};
 56
 57static struct mchp_otpc_packet *mchp_otpc_id_to_packet(struct mchp_otpc *otpc,
 58						       u32 id)
 59{
 60	struct mchp_otpc_packet *packet;
 61
 62	if (id >= otpc->npackets)
 63		return NULL;
 64
 65	list_for_each_entry(packet, &otpc->packets, list) {
 66		if (packet->id == id)
 67			return packet;
 68	}
 69
 70	return NULL;
 71}
 72
 73static int mchp_otpc_prepare_read(struct mchp_otpc *otpc,
 74				  unsigned int offset)
 75{
 76	u32 tmp;
 77
 78	/* Set address. */
 79	tmp = readl_relaxed(otpc->base + MCHP_OTPC_MR);
 80	tmp &= ~MCHP_OTPC_MR_ADDR;
 81	tmp |= FIELD_PREP(MCHP_OTPC_MR_ADDR, offset);
 82	writel_relaxed(tmp, otpc->base + MCHP_OTPC_MR);
 83
 84	/* Set read. */
 85	tmp = readl_relaxed(otpc->base + MCHP_OTPC_CR);
 86	tmp |= MCHP_OTPC_CR_READ;
 87	writel_relaxed(tmp, otpc->base + MCHP_OTPC_CR);
 88
 89	/* Wait for packet to be transferred into temporary buffers. */
 90	return read_poll_timeout(readl_relaxed, tmp, !(tmp & MCHP_OTPC_SR_READ),
 91				 10000, 2000, false, otpc->base + MCHP_OTPC_SR);
 92}
 93
 94/*
 95 * OTPC memory is organized into packets. Each packets contains a header and
 96 * a payload. Header is 4 bytes long and contains the size of the payload.
 97 * Payload size varies. The memory footprint is something as follows:
 98 *
 99 * Memory offset  Memory footprint     Packet ID
100 * -------------  ----------------     ---------
101 *
102 * 0x0            +------------+   <-- packet 0
103 *                | header  0  |
104 * 0x4            +------------+
105 *                | payload 0  |
106 *                .            .
107 *                .    ...     .
108 *                .            .
109 * offset1        +------------+   <-- packet 1
110 *                | header  1  |
111 * offset1 + 0x4  +------------+
112 *                | payload 1  |
113 *                .            .
114 *                .    ...     .
115 *                .            .
116 * offset2        +------------+   <-- packet 2
117 *                .            .
118 *                .    ...     .
119 *                .            .
120 * offsetN        +------------+   <-- packet N
121 *                | header  N  |
122 * offsetN + 0x4  +------------+
123 *                | payload N  |
124 *                .            .
125 *                .    ...     .
126 *                .            .
127 *                +------------+
128 *
129 * where offset1, offset2, offsetN depends on the size of payload 0, payload 1,
130 * payload N-1.
131 *
132 * The access to memory is done on a per packet basis: the control registers
133 * need to be updated with an offset address (within a packet range) and the
134 * data registers will be update by controller with information contained by
135 * that packet. E.g. if control registers are updated with any address within
136 * the range [offset1, offset2) the data registers are updated by controller
137 * with packet 1. Header data is accessible though MCHP_OTPC_HR register.
138 * Payload data is accessible though MCHP_OTPC_DR and MCHP_OTPC_AR registers.
139 * There is no direct mapping b/w the offset requested by software and the
140 * offset returned by hardware.
141 *
142 * For this, the read function will return the first requested bytes in the
143 * packet. The user will have to be aware of the memory footprint before doing
144 * the read request.
145 */
146static int mchp_otpc_read(void *priv, unsigned int off, void *val,
147			  size_t bytes)
148{
149	struct mchp_otpc *otpc = priv;
150	struct mchp_otpc_packet *packet;
151	u32 *buf = val;
152	u32 offset;
153	size_t len = 0;
154	int ret, payload_size;
155
156	/*
157	 * We reach this point with off being multiple of stride = 4 to
158	 * be able to cross the subsystem. Inside the driver we use continuous
159	 * unsigned integer numbers for packet id, thus devide off by 4
160	 * before passing it to mchp_otpc_id_to_packet().
161	 */
162	packet = mchp_otpc_id_to_packet(otpc, off / 4);
163	if (!packet)
164		return -EINVAL;
165	offset = packet->offset;
166
167	while (len < bytes) {
168		ret = mchp_otpc_prepare_read(otpc, offset);
169		if (ret)
170			return ret;
171
172		/* Read and save header content. */
173		*buf++ = readl_relaxed(otpc->base + MCHP_OTPC_HR);
174		len += sizeof(*buf);
175		offset++;
176		if (len >= bytes)
177			break;
178
179		/* Read and save payload content. */
180		payload_size = FIELD_GET(MCHP_OTPC_HR_SIZE, *(buf - 1));
181		writel_relaxed(0UL, otpc->base + MCHP_OTPC_AR);
182		do {
183			*buf++ = readl_relaxed(otpc->base + MCHP_OTPC_DR);
184			len += sizeof(*buf);
185			offset++;
186			payload_size--;
187		} while (payload_size >= 0 && len < bytes);
188	}
189
190	return 0;
191}
192
193static int mchp_otpc_init_packets_list(struct mchp_otpc *otpc, u32 *size)
194{
195	struct mchp_otpc_packet *packet;
196	u32 word, word_pos = 0, id = 0, npackets = 0, payload_size;
197	int ret;
198
199	INIT_LIST_HEAD(&otpc->packets);
200	*size = 0;
201
202	while (*size < MCHP_OTPC_SIZE) {
203		ret = mchp_otpc_prepare_read(otpc, word_pos);
204		if (ret)
205			return ret;
206
207		word = readl_relaxed(otpc->base + MCHP_OTPC_HR);
208		payload_size = FIELD_GET(MCHP_OTPC_HR_SIZE, word);
209		if (!payload_size)
210			break;
211
212		packet = devm_kzalloc(otpc->dev, sizeof(*packet), GFP_KERNEL);
213		if (!packet)
214			return -ENOMEM;
215
216		packet->id = id++;
217		packet->offset = word_pos;
218		INIT_LIST_HEAD(&packet->list);
219		list_add_tail(&packet->list, &otpc->packets);
220
221		/* Count size by adding header and paload sizes. */
222		*size += 4 * (payload_size + 1);
223		/* Next word: this packet (header, payload) position + 1. */
224		word_pos += payload_size + 2;
225
226		npackets++;
227	}
228
229	otpc->npackets = npackets;
230
231	return 0;
232}
233
234static struct nvmem_config mchp_nvmem_config = {
235	.name = MCHP_OTPC_NAME,
236	.type = NVMEM_TYPE_OTP,
237	.read_only = true,
238	.word_size = 4,
239	.stride = 4,
240	.reg_read = mchp_otpc_read,
241};
242
243static int mchp_otpc_probe(struct platform_device *pdev)
244{
245	struct nvmem_device *nvmem;
246	struct mchp_otpc *otpc;
247	u32 size;
248	int ret;
249
250	otpc = devm_kzalloc(&pdev->dev, sizeof(*otpc), GFP_KERNEL);
251	if (!otpc)
252		return -ENOMEM;
253
254	otpc->base = devm_platform_ioremap_resource(pdev, 0);
255	if (IS_ERR(otpc->base))
256		return PTR_ERR(otpc->base);
257
258	otpc->dev = &pdev->dev;
259	ret = mchp_otpc_init_packets_list(otpc, &size);
260	if (ret)
261		return ret;
262
263	mchp_nvmem_config.dev = otpc->dev;
264	mchp_nvmem_config.add_legacy_fixed_of_cells = true;
265	mchp_nvmem_config.size = size;
266	mchp_nvmem_config.priv = otpc;
267	nvmem = devm_nvmem_register(&pdev->dev, &mchp_nvmem_config);
268
269	return PTR_ERR_OR_ZERO(nvmem);
270}
271
272static const struct of_device_id __maybe_unused mchp_otpc_ids[] = {
273	{ .compatible = "microchip,sama7g5-otpc", },
274	{ },
275};
276MODULE_DEVICE_TABLE(of, mchp_otpc_ids);
277
278static struct platform_driver mchp_otpc_driver = {
279	.probe = mchp_otpc_probe,
280	.driver = {
281		.name = MCHP_OTPC_NAME,
282		.of_match_table = of_match_ptr(mchp_otpc_ids),
283	},
284};
285module_platform_driver(mchp_otpc_driver);
286
287MODULE_AUTHOR("Claudiu Beznea <claudiu.beznea@microchip.com>");
288MODULE_DESCRIPTION("Microchip SAMA7G5 OTPC driver");
289MODULE_LICENSE("GPL");