Skip to content

Commit c06b1f7

Browse files
m-kurbanovmiquelraynal
authored andcommitted
mtd: spinand: add OTP support
The MTD subsystem already supports accessing two OTP areas: user and factory. User areas can be written by the user. This patch provides the SPINAND_FACT_OTP_INFO and SPINAND_USER_OTP_INFO macros to add parameters to spinand_info. To implement OTP operations, the client (flash driver) is provided with callbacks for user area: .read(), .write(), .info(), .lock(), .erase(); and for factory area: .read(), .info(); Signed-off-by: Martin Kurbanov <[email protected]> Signed-off-by: Miquel Raynal <[email protected]>
1 parent 07d0aa9 commit c06b1f7

File tree

4 files changed

+347
-1
lines changed

4 files changed

+347
-1
lines changed

drivers/mtd/nand/spi/Makefile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# SPDX-License-Identifier: GPL-2.0
2-
spinand-objs := core.o alliancememory.o ato.o esmt.o foresee.o gigadevice.o macronix.o
2+
spinand-objs := core.o otp.o
3+
spinand-objs += alliancememory.o ato.o esmt.o foresee.o gigadevice.o macronix.o
34
spinand-objs += micron.o paragon.o skyhigh.o toshiba.o winbond.o xtx.o
45
obj-$(CONFIG_MTD_SPI_NAND) += spinand.o

drivers/mtd/nand/spi/core.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1308,6 +1308,8 @@ int spinand_match_and_init(struct spinand_device *spinand,
13081308
spinand->id.len = 1 + table[i].devid.len;
13091309
spinand->select_target = table[i].select_target;
13101310
spinand->set_cont_read = table[i].set_cont_read;
1311+
spinand->fact_otp = &table[i].fact_otp;
1312+
spinand->user_otp = &table[i].user_otp;
13111313

13121314
op = spinand_select_op_variant(spinand,
13131315
info->op_variants.read_cache);
@@ -1494,6 +1496,12 @@ static int spinand_init(struct spinand_device *spinand)
14941496
mtd->_max_bad_blocks = nanddev_mtd_max_bad_blocks;
14951497
mtd->_resume = spinand_mtd_resume;
14961498

1499+
if (spinand_user_otp_size(spinand) || spinand_fact_otp_size(spinand)) {
1500+
ret = spinand_set_mtd_otp_ops(spinand);
1501+
if (ret)
1502+
goto err_cleanup_ecc_engine;
1503+
}
1504+
14971505
if (nand->ecc.engine) {
14981506
ret = mtd_ooblayout_count_freebytes(mtd);
14991507
if (ret < 0)

drivers/mtd/nand/spi/otp.c

Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
/*
3+
* Copyright (c) 2025, SaluteDevices. All Rights Reserved.
4+
*
5+
* Author: Martin Kurbanov <[email protected]>
6+
*/
7+
8+
#include <linux/mtd/mtd.h>
9+
#include <linux/mtd/spinand.h>
10+
11+
static size_t spinand_otp_size(struct spinand_device *spinand,
12+
const struct spinand_otp_layout *layout)
13+
{
14+
struct nand_device *nand = spinand_to_nand(spinand);
15+
size_t otp_pagesize = nanddev_page_size(nand) +
16+
nanddev_per_page_oobsize(nand);
17+
18+
return layout->npages * otp_pagesize;
19+
}
20+
21+
/**
22+
* spinand_fact_otp_size() - Get SPI-NAND factory OTP area size
23+
* @spinand: the spinand device
24+
*
25+
* Return: the OTP size.
26+
*/
27+
size_t spinand_fact_otp_size(struct spinand_device *spinand)
28+
{
29+
return spinand_otp_size(spinand, &spinand->fact_otp->layout);
30+
}
31+
32+
/**
33+
* spinand_user_otp_size() - Get SPI-NAND user OTP area size
34+
* @spinand: the spinand device
35+
*
36+
* Return: the OTP size.
37+
*/
38+
size_t spinand_user_otp_size(struct spinand_device *spinand)
39+
{
40+
return spinand_otp_size(spinand, &spinand->user_otp->layout);
41+
}
42+
43+
static int spinand_otp_check_bounds(struct spinand_device *spinand, loff_t ofs,
44+
size_t len,
45+
const struct spinand_otp_layout *layout)
46+
{
47+
if (ofs < 0 || ofs + len > spinand_otp_size(spinand, layout))
48+
return -EINVAL;
49+
50+
return 0;
51+
}
52+
53+
static int spinand_user_otp_check_bounds(struct spinand_device *spinand,
54+
loff_t ofs, size_t len)
55+
{
56+
return spinand_otp_check_bounds(spinand, ofs, len,
57+
&spinand->user_otp->layout);
58+
}
59+
60+
static int spinand_mtd_otp_info(struct mtd_info *mtd, size_t len,
61+
size_t *retlen, struct otp_info *buf,
62+
bool is_fact)
63+
{
64+
struct spinand_device *spinand = mtd_to_spinand(mtd);
65+
int ret;
66+
67+
*retlen = 0;
68+
69+
mutex_lock(&spinand->lock);
70+
71+
if (is_fact)
72+
ret = spinand->fact_otp->ops->info(spinand, len, buf, retlen);
73+
else
74+
ret = spinand->user_otp->ops->info(spinand, len, buf, retlen);
75+
76+
mutex_unlock(&spinand->lock);
77+
78+
return ret;
79+
}
80+
81+
static int spinand_mtd_fact_otp_info(struct mtd_info *mtd, size_t len,
82+
size_t *retlen, struct otp_info *buf)
83+
{
84+
return spinand_mtd_otp_info(mtd, len, retlen, buf, true);
85+
}
86+
87+
static int spinand_mtd_user_otp_info(struct mtd_info *mtd, size_t len,
88+
size_t *retlen, struct otp_info *buf)
89+
{
90+
return spinand_mtd_otp_info(mtd, len, retlen, buf, false);
91+
}
92+
93+
static int spinand_mtd_otp_read(struct mtd_info *mtd, loff_t ofs, size_t len,
94+
size_t *retlen, u8 *buf, bool is_fact)
95+
{
96+
struct spinand_device *spinand = mtd_to_spinand(mtd);
97+
int ret;
98+
99+
*retlen = 0;
100+
101+
if (!len)
102+
return 0;
103+
104+
ret = spinand_otp_check_bounds(spinand, ofs, len,
105+
is_fact ? &spinand->fact_otp->layout :
106+
&spinand->user_otp->layout);
107+
if (ret)
108+
return ret;
109+
110+
mutex_lock(&spinand->lock);
111+
112+
if (is_fact)
113+
ret = spinand->fact_otp->ops->read(spinand, ofs, len, retlen,
114+
buf);
115+
else
116+
ret = spinand->user_otp->ops->read(spinand, ofs, len, retlen,
117+
buf);
118+
119+
mutex_unlock(&spinand->lock);
120+
121+
return ret;
122+
}
123+
124+
static int spinand_mtd_fact_otp_read(struct mtd_info *mtd, loff_t ofs,
125+
size_t len, size_t *retlen, u8 *buf)
126+
{
127+
return spinand_mtd_otp_read(mtd, ofs, len, retlen, buf, true);
128+
}
129+
130+
static int spinand_mtd_user_otp_read(struct mtd_info *mtd, loff_t ofs,
131+
size_t len, size_t *retlen, u8 *buf)
132+
{
133+
return spinand_mtd_otp_read(mtd, ofs, len, retlen, buf, false);
134+
}
135+
136+
static int spinand_mtd_user_otp_write(struct mtd_info *mtd, loff_t ofs,
137+
size_t len, size_t *retlen, const u8 *buf)
138+
{
139+
struct spinand_device *spinand = mtd_to_spinand(mtd);
140+
const struct spinand_user_otp_ops *ops = spinand->user_otp->ops;
141+
int ret;
142+
143+
*retlen = 0;
144+
145+
if (!len)
146+
return 0;
147+
148+
ret = spinand_user_otp_check_bounds(spinand, ofs, len);
149+
if (ret)
150+
return ret;
151+
152+
mutex_lock(&spinand->lock);
153+
ret = ops->write(spinand, ofs, len, retlen, buf);
154+
mutex_unlock(&spinand->lock);
155+
156+
return ret;
157+
}
158+
159+
static int spinand_mtd_user_otp_erase(struct mtd_info *mtd, loff_t ofs,
160+
size_t len)
161+
{
162+
struct spinand_device *spinand = mtd_to_spinand(mtd);
163+
const struct spinand_user_otp_ops *ops = spinand->user_otp->ops;
164+
int ret;
165+
166+
if (!len)
167+
return 0;
168+
169+
ret = spinand_user_otp_check_bounds(spinand, ofs, len);
170+
if (ret)
171+
return ret;
172+
173+
mutex_lock(&spinand->lock);
174+
ret = ops->erase(spinand, ofs, len);
175+
mutex_unlock(&spinand->lock);
176+
177+
return ret;
178+
}
179+
180+
static int spinand_mtd_user_otp_lock(struct mtd_info *mtd, loff_t ofs,
181+
size_t len)
182+
{
183+
struct spinand_device *spinand = mtd_to_spinand(mtd);
184+
const struct spinand_user_otp_ops *ops = spinand->user_otp->ops;
185+
int ret;
186+
187+
if (!len)
188+
return 0;
189+
190+
ret = spinand_user_otp_check_bounds(spinand, ofs, len);
191+
if (ret)
192+
return ret;
193+
194+
mutex_lock(&spinand->lock);
195+
ret = ops->lock(spinand, ofs, len);
196+
mutex_unlock(&spinand->lock);
197+
198+
return ret;
199+
}
200+
201+
/**
202+
* spinand_set_mtd_otp_ops() - Setup OTP methods
203+
* @spinand: the spinand device
204+
*
205+
* Setup OTP methods.
206+
*
207+
* Return: 0 on success, a negative error code otherwise.
208+
*/
209+
int spinand_set_mtd_otp_ops(struct spinand_device *spinand)
210+
{
211+
struct mtd_info *mtd = spinand_to_mtd(spinand);
212+
const struct spinand_fact_otp_ops *fact_ops = spinand->fact_otp->ops;
213+
const struct spinand_user_otp_ops *user_ops = spinand->user_otp->ops;
214+
215+
if (!user_ops && !fact_ops)
216+
return -EINVAL;
217+
218+
if (user_ops) {
219+
if (user_ops->info)
220+
mtd->_get_user_prot_info = spinand_mtd_user_otp_info;
221+
222+
if (user_ops->read)
223+
mtd->_read_user_prot_reg = spinand_mtd_user_otp_read;
224+
225+
if (user_ops->write)
226+
mtd->_write_user_prot_reg = spinand_mtd_user_otp_write;
227+
228+
if (user_ops->lock)
229+
mtd->_lock_user_prot_reg = spinand_mtd_user_otp_lock;
230+
231+
if (user_ops->erase)
232+
mtd->_erase_user_prot_reg = spinand_mtd_user_otp_erase;
233+
}
234+
235+
if (fact_ops) {
236+
if (fact_ops->info)
237+
mtd->_get_fact_prot_info = spinand_mtd_fact_otp_info;
238+
239+
if (fact_ops->read)
240+
mtd->_read_fact_prot_reg = spinand_mtd_fact_otp_read;
241+
}
242+
243+
return 0;
244+
}

0 commit comments

Comments
 (0)