Skip to content

Commit b741d3f

Browse files
m-kurbanovmiquelraynal
authored andcommitted
mtd: spinand: micron: OTP access for MT29F2G01ABAGD
Support for OTP area access on Micron MT29F2G01ABAGD chip. Signed-off-by: Martin Kurbanov <[email protected]> Signed-off-by: Miquel Raynal <[email protected]>
1 parent 9ad2857 commit b741d3f

File tree

1 file changed

+134
-1
lines changed

1 file changed

+134
-1
lines changed

drivers/mtd/nand/spi/micron.c

Lines changed: 134 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
#include <linux/device.h>
1010
#include <linux/kernel.h>
1111
#include <linux/mtd/spinand.h>
12+
#include <linux/spi/spi-mem.h>
13+
#include <linux/string.h>
1214

1315
#define SPINAND_MFR_MICRON 0x2c
1416

@@ -28,6 +30,10 @@
2830

2931
#define MICRON_SELECT_DIE(x) ((x) << 6)
3032

33+
#define MICRON_MT29F2G01ABAGD_CFG_OTP_STATE BIT(7)
34+
#define MICRON_MT29F2G01ABAGD_CFG_OTP_LOCK \
35+
(CFG_OTP_ENABLE | MICRON_MT29F2G01ABAGD_CFG_OTP_STATE)
36+
3137
static SPINAND_OP_VARIANTS(quadio_read_cache_variants,
3238
SPINAND_PAGE_READ_FROM_CACHE_QUADIO_OP(0, 2, NULL, 0),
3339
SPINAND_PAGE_READ_FROM_CACHE_X4_OP(0, 1, NULL, 0),
@@ -168,6 +174,131 @@ static int micron_8_ecc_get_status(struct spinand_device *spinand,
168174
return -EINVAL;
169175
}
170176

177+
static int mt29f2g01abagd_otp_is_locked(struct spinand_device *spinand)
178+
{
179+
size_t bufsize = spinand_otp_page_size(spinand);
180+
size_t retlen;
181+
u8 *buf;
182+
int ret;
183+
184+
buf = kmalloc(bufsize, GFP_KERNEL);
185+
if (!buf)
186+
return -ENOMEM;
187+
188+
ret = spinand_upd_cfg(spinand,
189+
MICRON_MT29F2G01ABAGD_CFG_OTP_LOCK,
190+
MICRON_MT29F2G01ABAGD_CFG_OTP_STATE);
191+
if (ret)
192+
goto free_buf;
193+
194+
ret = spinand_user_otp_read(spinand, 0, bufsize, &retlen, buf);
195+
196+
if (spinand_upd_cfg(spinand, MICRON_MT29F2G01ABAGD_CFG_OTP_LOCK,
197+
0)) {
198+
dev_warn(&spinand_to_mtd(spinand)->dev,
199+
"Can not disable OTP mode\n");
200+
ret = -EIO;
201+
}
202+
203+
if (ret)
204+
goto free_buf;
205+
206+
/* If all zeros, then the OTP area is locked. */
207+
if (mem_is_zero(buf, bufsize))
208+
ret = 1;
209+
210+
free_buf:
211+
kfree(buf);
212+
return ret;
213+
}
214+
215+
static int mt29f2g01abagd_otp_info(struct spinand_device *spinand, size_t len,
216+
struct otp_info *buf, size_t *retlen,
217+
bool user)
218+
{
219+
int locked;
220+
221+
if (len < sizeof(*buf))
222+
return -EINVAL;
223+
224+
locked = mt29f2g01abagd_otp_is_locked(spinand);
225+
if (locked < 0)
226+
return locked;
227+
228+
buf->locked = locked;
229+
buf->start = 0;
230+
buf->length = user ? spinand_user_otp_size(spinand) :
231+
spinand_fact_otp_size(spinand);
232+
233+
*retlen = sizeof(*buf);
234+
return 0;
235+
}
236+
237+
static int mt29f2g01abagd_fact_otp_info(struct spinand_device *spinand,
238+
size_t len, struct otp_info *buf,
239+
size_t *retlen)
240+
{
241+
return mt29f2g01abagd_otp_info(spinand, len, buf, retlen, false);
242+
}
243+
244+
static int mt29f2g01abagd_user_otp_info(struct spinand_device *spinand,
245+
size_t len, struct otp_info *buf,
246+
size_t *retlen)
247+
{
248+
return mt29f2g01abagd_otp_info(spinand, len, buf, retlen, true);
249+
}
250+
251+
static int mt29f2g01abagd_otp_lock(struct spinand_device *spinand, loff_t from,
252+
size_t len)
253+
{
254+
struct spi_mem_op write_op = SPINAND_WR_EN_DIS_OP(true);
255+
struct spi_mem_op exec_op = SPINAND_PROG_EXEC_OP(0);
256+
u8 status;
257+
int ret;
258+
259+
ret = spinand_upd_cfg(spinand,
260+
MICRON_MT29F2G01ABAGD_CFG_OTP_LOCK,
261+
MICRON_MT29F2G01ABAGD_CFG_OTP_LOCK);
262+
if (!ret)
263+
return ret;
264+
265+
ret = spi_mem_exec_op(spinand->spimem, &write_op);
266+
if (!ret)
267+
goto out;
268+
269+
ret = spi_mem_exec_op(spinand->spimem, &exec_op);
270+
if (!ret)
271+
goto out;
272+
273+
ret = spinand_wait(spinand,
274+
SPINAND_WRITE_INITIAL_DELAY_US,
275+
SPINAND_WRITE_POLL_DELAY_US,
276+
&status);
277+
if (!ret && (status & STATUS_PROG_FAILED))
278+
ret = -EIO;
279+
280+
out:
281+
if (spinand_upd_cfg(spinand, MICRON_MT29F2G01ABAGD_CFG_OTP_LOCK, 0)) {
282+
dev_warn(&spinand_to_mtd(spinand)->dev,
283+
"Can not disable OTP mode\n");
284+
ret = -EIO;
285+
}
286+
287+
return ret;
288+
}
289+
290+
static const struct spinand_user_otp_ops mt29f2g01abagd_user_otp_ops = {
291+
.info = mt29f2g01abagd_user_otp_info,
292+
.lock = mt29f2g01abagd_otp_lock,
293+
.read = spinand_user_otp_read,
294+
.write = spinand_user_otp_write,
295+
};
296+
297+
static const struct spinand_fact_otp_ops mt29f2g01abagd_fact_otp_ops = {
298+
.info = mt29f2g01abagd_fact_otp_info,
299+
.read = spinand_fact_otp_read,
300+
};
301+
171302
static const struct spinand_info micron_spinand_table[] = {
172303
/* M79A 2Gb 3.3V */
173304
SPINAND_INFO("MT29F2G01ABAGD",
@@ -179,7 +310,9 @@ static const struct spinand_info micron_spinand_table[] = {
179310
&x4_update_cache_variants),
180311
0,
181312
SPINAND_ECCINFO(&micron_8_ooblayout,
182-
micron_8_ecc_get_status)),
313+
micron_8_ecc_get_status),
314+
SPINAND_USER_OTP_INFO(12, 2, &mt29f2g01abagd_user_otp_ops),
315+
SPINAND_FACT_OTP_INFO(2, 0, &mt29f2g01abagd_fact_otp_ops)),
183316
/* M79A 2Gb 1.8V */
184317
SPINAND_INFO("MT29F2G01ABBGD",
185318
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x25),

0 commit comments

Comments
 (0)