xref: /linux/drivers/nvmem/microchip-otpc.c (revision c6fbb759)
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  */
38 struct 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  */
51 struct mchp_otpc_packet {
52 	struct list_head list;
53 	u32 id;
54 	u32 offset;
55 };
56 
57 static 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 
73 static 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  */
146 static 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 
193 static 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 
234 static 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 
243 static 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.size = size;
265 	mchp_nvmem_config.priv = otpc;
266 	nvmem = devm_nvmem_register(&pdev->dev, &mchp_nvmem_config);
267 
268 	return PTR_ERR_OR_ZERO(nvmem);
269 }
270 
271 static const struct of_device_id __maybe_unused mchp_otpc_ids[] = {
272 	{ .compatible = "microchip,sama7g5-otpc", },
273 	{ },
274 };
275 MODULE_DEVICE_TABLE(of, mchp_otpc_ids);
276 
277 static struct platform_driver mchp_otpc_driver = {
278 	.probe = mchp_otpc_probe,
279 	.driver = {
280 		.name = MCHP_OTPC_NAME,
281 		.of_match_table = of_match_ptr(mchp_otpc_ids),
282 	},
283 };
284 module_platform_driver(mchp_otpc_driver);
285 
286 MODULE_AUTHOR("Claudiu Beznea <claudiu.beznea@microchip.com>");
287 MODULE_DESCRIPTION("Microchip SAMA7G5 OTPC driver");
288 MODULE_LICENSE("GPL");
289