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, 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 | |
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(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 | |
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(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 | |
272 | static const struct of_device_id __maybe_unused mchp_otpc_ids[] = { |
273 | { .compatible = "microchip,sama7g5-otpc" , }, |
274 | { }, |
275 | }; |
276 | MODULE_DEVICE_TABLE(of, mchp_otpc_ids); |
277 | |
278 | static 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 | }; |
285 | module_platform_driver(mchp_otpc_driver); |
286 | |
287 | MODULE_AUTHOR("Claudiu Beznea <claudiu.beznea@microchip.com>" ); |
288 | MODULE_DESCRIPTION("Microchip SAMA7G5 OTPC driver" ); |
289 | MODULE_LICENSE("GPL" ); |
290 | |