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, id: 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(list: &otpc->packets);
200 *size = 0;
201
202 while (*size < MCHP_OTPC_SIZE) {
203 ret = mchp_otpc_prepare_read(otpc, offset: 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(dev: otpc->dev, size: sizeof(*packet), GFP_KERNEL);
213 if (!packet)
214 return -ENOMEM;
215
216 packet->id = id++;
217 packet->offset = word_pos;
218 INIT_LIST_HEAD(list: &packet->list);
219 list_add_tail(new: &packet->list, head: &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(dev: &pdev->dev, size: sizeof(*otpc), GFP_KERNEL);
251 if (!otpc)
252 return -ENOMEM;
253
254 otpc->base = devm_platform_ioremap_resource(pdev, index: 0);
255 if (IS_ERR(ptr: otpc->base))
256 return PTR_ERR(ptr: otpc->base);
257
258 otpc->dev = &pdev->dev;
259 ret = mchp_otpc_init_packets_list(otpc, size: &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(dev: &pdev->dev, cfg: &mchp_nvmem_config);
268
269 return PTR_ERR_OR_ZERO(ptr: 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");
290

source code of linux/drivers/nvmem/microchip-otpc.c