1 // SPDX-License-Identifier: GPL-2.0
3 * OTP Memory controller
5 * Copyright (C) 2022 Microchip Technology Inc. and its subsidiaries
7 * Author: Claudiu Beznea <claudiu.beznea@microchip.com>
10 #include <linux/bitfield.h>
11 #include <linux/iopoll.h>
12 #include <linux/module.h>
13 #include <linux/nvmem-provider.h>
15 #include <linux/platform_device.h>
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)
28 #define MCHP_OTPC_NAME "mchp-otpc"
29 #define MCHP_OTPC_SIZE (11 * 1024)
32 * struct mchp_otpc - OTPC private data structure
34 * @dev: struct device pointer
35 * @packets: list of packets in OTP memory
36 * @npackets: number of packets in OTP memory
41 struct list_head packets
;
46 * struct mchp_otpc_packet - OTPC packet data structure
49 * @offset: packet offset (in words) in OTP memory
51 struct mchp_otpc_packet
{
52 struct list_head list
;
57 static struct mchp_otpc_packet
*mchp_otpc_id_to_packet(struct mchp_otpc
*otpc
,
60 struct mchp_otpc_packet
*packet
;
62 if (id
>= otpc
->npackets
)
65 list_for_each_entry(packet
, &otpc
->packets
, list
) {
73 static int mchp_otpc_prepare_read(struct mchp_otpc
*otpc
,
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
);
85 tmp
= readl_relaxed(otpc
->base
+ MCHP_OTPC_CR
);
86 tmp
|= MCHP_OTPC_CR_READ
;
87 writel_relaxed(tmp
, otpc
->base
+ MCHP_OTPC_CR
);
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
);
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:
99 * Memory offset Memory footprint Packet ID
100 * ------------- ---------------- ---------
102 * 0x0 +------------+ <-- packet 0
109 * offset1 +------------+ <-- packet 1
111 * offset1 + 0x4 +------------+
116 * offset2 +------------+ <-- packet 2
120 * offsetN +------------+ <-- packet N
122 * offsetN + 0x4 +------------+
129 * where offset1, offset2, offsetN depends on the size of payload 0, payload 1,
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.
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
146 static int mchp_otpc_read(void *priv
, unsigned int off
, void *val
,
149 struct mchp_otpc
*otpc
= priv
;
150 struct mchp_otpc_packet
*packet
;
154 int ret
, payload_size
;
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 divide off by 4
160 * before passing it to mchp_otpc_id_to_packet().
162 packet
= mchp_otpc_id_to_packet(otpc
, off
/ 4);
165 offset
= packet
->offset
;
167 while (len
< bytes
) {
168 ret
= mchp_otpc_prepare_read(otpc
, offset
);
172 /* Read and save header content. */
173 *buf
++ = readl_relaxed(otpc
->base
+ MCHP_OTPC_HR
);
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
);
183 *buf
++ = readl_relaxed(otpc
->base
+ MCHP_OTPC_DR
);
187 } while (payload_size
>= 0 && len
< bytes
);
193 static int mchp_otpc_init_packets_list(struct mchp_otpc
*otpc
, u32
*size
)
195 struct mchp_otpc_packet
*packet
;
196 u32 word
, word_pos
= 0, id
= 0, npackets
= 0, payload_size
;
199 INIT_LIST_HEAD(&otpc
->packets
);
202 while (*size
< MCHP_OTPC_SIZE
) {
203 ret
= mchp_otpc_prepare_read(otpc
, word_pos
);
207 word
= readl_relaxed(otpc
->base
+ MCHP_OTPC_HR
);
208 payload_size
= FIELD_GET(MCHP_OTPC_HR_SIZE
, word
);
212 packet
= devm_kzalloc(otpc
->dev
, sizeof(*packet
), GFP_KERNEL
);
217 packet
->offset
= word_pos
;
218 INIT_LIST_HEAD(&packet
->list
);
219 list_add_tail(&packet
->list
, &otpc
->packets
);
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;
229 otpc
->npackets
= npackets
;
234 static struct nvmem_config mchp_nvmem_config
= {
235 .name
= MCHP_OTPC_NAME
,
236 .type
= NVMEM_TYPE_OTP
,
240 .reg_read
= mchp_otpc_read
,
243 static int mchp_otpc_probe(struct platform_device
*pdev
)
245 struct nvmem_device
*nvmem
;
246 struct mchp_otpc
*otpc
;
250 otpc
= devm_kzalloc(&pdev
->dev
, sizeof(*otpc
), GFP_KERNEL
);
254 otpc
->base
= devm_platform_ioremap_resource(pdev
, 0);
255 if (IS_ERR(otpc
->base
))
256 return PTR_ERR(otpc
->base
);
258 otpc
->dev
= &pdev
->dev
;
259 ret
= mchp_otpc_init_packets_list(otpc
, &size
);
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
);
269 return PTR_ERR_OR_ZERO(nvmem
);
272 static const struct of_device_id __maybe_unused mchp_otpc_ids
[] = {
273 { .compatible
= "microchip,sama7g5-otpc", },
276 MODULE_DEVICE_TABLE(of
, mchp_otpc_ids
);
278 static struct platform_driver mchp_otpc_driver
= {
279 .probe
= mchp_otpc_probe
,
281 .name
= MCHP_OTPC_NAME
,
282 .of_match_table
= of_match_ptr(mchp_otpc_ids
),
285 module_platform_driver(mchp_otpc_driver
);
287 MODULE_AUTHOR("Claudiu Beznea <claudiu.beznea@microchip.com>");
288 MODULE_DESCRIPTION("Microchip SAMA7G5 OTPC driver");
289 MODULE_LICENSE("GPL");