Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
L
linux-davinci
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Redmine
Redmine
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Operations
Operations
Metrics
Environments
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
linux
linux-davinci
Commits
c1f16258
Commit
c1f16258
authored
Feb 09, 2007
by
David Woodhouse
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'master' of
git://git.infradead.org/~kmpark/onenand-mtd-2.6
parents
552a8278
cde36b37
Changes
5
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
446 additions
and
182 deletions
+446
-182
drivers/mtd/onenand/onenand_base.c
drivers/mtd/onenand/onenand_base.c
+413
-159
drivers/mtd/onenand/onenand_bbt.c
drivers/mtd/onenand/onenand_bbt.c
+15
-12
include/linux/mtd/bbm.h
include/linux/mtd/bbm.h
+7
-0
include/linux/mtd/onenand.h
include/linux/mtd/onenand.h
+6
-9
include/linux/mtd/onenand_regs.h
include/linux/mtd/onenand_regs.h
+5
-2
No files found.
drivers/mtd/onenand/onenand_base.c
View file @
c1f16258
/*
/*
* linux/drivers/mtd/onenand/onenand_base.c
* linux/drivers/mtd/onenand/onenand_base.c
*
*
* Copyright (C) 2005-200
6
Samsung Electronics
* Copyright (C) 2005-200
7
Samsung Electronics
* Kyungmin Park <kyungmin.park@samsung.com>
* Kyungmin Park <kyungmin.park@samsung.com>
*
*
* This program is free software; you can redistribute it and/or modify
* This program is free software; you can redistribute it and/or modify
...
@@ -94,16 +94,9 @@ static void onenand_writew(unsigned short value, void __iomem *addr)
...
@@ -94,16 +94,9 @@ static void onenand_writew(unsigned short value, void __iomem *addr)
*/
*/
static
int
onenand_block_address
(
struct
onenand_chip
*
this
,
int
block
)
static
int
onenand_block_address
(
struct
onenand_chip
*
this
,
int
block
)
{
{
if
(
this
->
device_id
&
ONENAND_DEVICE_IS_DDP
)
{
/* Device Flash Core select, NAND Flash Block Address */
/* Device Flash Core select, NAND Flash Block Address */
if
(
block
&
this
->
density_mask
)
int
dfs
=
0
;
return
ONENAND_DDP_CHIP1
|
(
block
^
this
->
density_mask
);
if
(
block
&
this
->
density_mask
)
dfs
=
1
;
return
(
dfs
<<
ONENAND_DDP_SHIFT
)
|
(
block
&
(
this
->
density_mask
-
1
));
}
return
block
;
return
block
;
}
}
...
@@ -118,17 +111,11 @@ static int onenand_block_address(struct onenand_chip *this, int block)
...
@@ -118,17 +111,11 @@ static int onenand_block_address(struct onenand_chip *this, int block)
*/
*/
static
int
onenand_bufferram_address
(
struct
onenand_chip
*
this
,
int
block
)
static
int
onenand_bufferram_address
(
struct
onenand_chip
*
this
,
int
block
)
{
{
if
(
this
->
device_id
&
ONENAND_DEVICE_IS_DDP
)
{
/* Device BufferRAM Select */
/* Device BufferRAM Select */
if
(
block
&
this
->
density_mask
)
int
dbs
=
0
;
return
ONENAND_DDP_CHIP1
;
if
(
block
&
this
->
density_mask
)
dbs
=
1
;
return
(
dbs
<<
ONENAND_DDP_SHIFT
);
return
ONENAND_DDP_CHIP0
;
}
return
0
;
}
}
/**
/**
...
@@ -317,22 +304,25 @@ static int onenand_wait(struct mtd_info *mtd, int state)
...
@@ -317,22 +304,25 @@ static int onenand_wait(struct mtd_info *mtd, int state)
ctrl
=
this
->
read_word
(
this
->
base
+
ONENAND_REG_CTRL_STATUS
);
ctrl
=
this
->
read_word
(
this
->
base
+
ONENAND_REG_CTRL_STATUS
);
if
(
ctrl
&
ONENAND_CTRL_ERROR
)
{
if
(
ctrl
&
ONENAND_CTRL_ERROR
)
{
DEBUG
(
MTD_DEBUG_LEVEL0
,
"onenand_wait: controller error = 0x%04x
\n
"
,
ctrl
);
printk
(
KERN_ERR
"onenand_wait: controller error = 0x%04x
\n
"
,
ctrl
);
if
(
ctrl
&
ONENAND_CTRL_LOCK
)
if
(
ctrl
&
ONENAND_CTRL_LOCK
)
DEBUG
(
MTD_DEBUG_LEVEL0
,
"onenand_wait: it's locked error.
\n
"
);
printk
(
KERN_ERR
"onenand_wait: it's locked error.
\n
"
);
return
ctrl
;
return
ctrl
;
}
}
if
(
interrupt
&
ONENAND_INT_READ
)
{
if
(
interrupt
&
ONENAND_INT_READ
)
{
int
ecc
=
this
->
read_word
(
this
->
base
+
ONENAND_REG_ECC_STATUS
);
int
ecc
=
this
->
read_word
(
this
->
base
+
ONENAND_REG_ECC_STATUS
);
if
(
ecc
)
{
if
(
ecc
)
{
DEBUG
(
MTD_DEBUG_LEVEL0
,
"onenand_wait: ECC error = 0x%04x
\n
"
,
ecc
);
printk
(
KERN_ERR
"onenand_wait: ECC error = 0x%04x
\n
"
,
ecc
);
if
(
ecc
&
ONENAND_ECC_2BIT_ALL
)
{
if
(
ecc
&
ONENAND_ECC_2BIT_ALL
)
{
mtd
->
ecc_stats
.
failed
++
;
mtd
->
ecc_stats
.
failed
++
;
return
ecc
;
return
ecc
;
}
else
if
(
ecc
&
ONENAND_ECC_1BIT_ALL
)
}
else
if
(
ecc
&
ONENAND_ECC_1BIT_ALL
)
mtd
->
ecc_stats
.
corrected
++
;
mtd
->
ecc_stats
.
corrected
++
;
}
}
}
else
if
(
state
==
FL_READING
)
{
printk
(
KERN_ERR
"onenand_wait: read timeout! ctrl=0x%04x intr=0x%04x
\n
"
,
ctrl
,
interrupt
);
return
-
EIO
;
}
}
return
0
;
return
0
;
...
@@ -587,22 +577,32 @@ static int onenand_write_bufferram(struct mtd_info *mtd, int area,
...
@@ -587,22 +577,32 @@ static int onenand_write_bufferram(struct mtd_info *mtd, int area,
static
int
onenand_check_bufferram
(
struct
mtd_info
*
mtd
,
loff_t
addr
)
static
int
onenand_check_bufferram
(
struct
mtd_info
*
mtd
,
loff_t
addr
)
{
{
struct
onenand_chip
*
this
=
mtd
->
priv
;
struct
onenand_chip
*
this
=
mtd
->
priv
;
int
block
,
page
;
int
block
page
,
found
=
0
;
int
i
;
unsigned
int
i
;
block
=
(
int
)
(
addr
>>
this
->
erase_shift
);
blockpage
=
(
int
)
(
addr
>>
this
->
page_shift
);
page
=
(
int
)
(
addr
>>
this
->
page_shift
);
page
&=
this
->
page_mask
;
/* Is there valid data? */
i
=
ONENAND_CURRENT_BUFFERRAM
(
this
);
i
=
ONENAND_CURRENT_BUFFERRAM
(
this
);
if
(
this
->
bufferram
[
i
].
blockpage
==
blockpage
)
found
=
1
;
else
{
/* Check another BufferRAM */
i
=
ONENAND_NEXT_BUFFERRAM
(
this
);
if
(
this
->
bufferram
[
i
].
blockpage
==
blockpage
)
{
ONENAND_SET_NEXT_BUFFERRAM
(
this
);
found
=
1
;
}
}
/* Is there valid data? */
if
(
found
&&
ONENAND_IS_DDP
(
this
))
{
if
(
this
->
bufferram
[
i
].
block
==
block
&&
/* Select DataRAM for DDP */
this
->
bufferram
[
i
].
page
==
page
&&
int
block
=
(
int
)
(
addr
>>
this
->
erase_shift
);
this
->
bufferram
[
i
].
valid
)
int
value
=
onenand_bufferram_address
(
this
,
block
);
return
1
;
this
->
write_word
(
value
,
this
->
base
+
ONENAND_REG_START_ADDRESS2
);
}
return
0
;
return
found
;
}
}
/**
/**
...
@@ -613,31 +613,26 @@ static int onenand_check_bufferram(struct mtd_info *mtd, loff_t addr)
...
@@ -613,31 +613,26 @@ static int onenand_check_bufferram(struct mtd_info *mtd, loff_t addr)
*
*
* Update BufferRAM information
* Update BufferRAM information
*/
*/
static
int
onenand_update_bufferram
(
struct
mtd_info
*
mtd
,
loff_t
addr
,
static
void
onenand_update_bufferram
(
struct
mtd_info
*
mtd
,
loff_t
addr
,
int
valid
)
int
valid
)
{
{
struct
onenand_chip
*
this
=
mtd
->
priv
;
struct
onenand_chip
*
this
=
mtd
->
priv
;
int
block
,
page
;
int
blockpage
;
int
i
;
unsigned
int
i
;
block
=
(
int
)
(
addr
>>
this
->
erase_shift
);
blockpage
=
(
int
)
(
addr
>>
this
->
page_shift
);
page
=
(
int
)
(
addr
>>
this
->
page_shift
);
page
&=
this
->
page_mask
;
/* Invalidate BufferRAM */
/* Invalidate another BufferRAM */
for
(
i
=
0
;
i
<
MAX_BUFFERRAM
;
i
++
)
{
i
=
ONENAND_NEXT_BUFFERRAM
(
this
);
if
(
this
->
bufferram
[
i
].
block
==
block
&&
if
(
this
->
bufferram
[
i
].
blockpage
==
blockpage
)
this
->
bufferram
[
i
].
page
==
page
)
this
->
bufferram
[
i
].
blockpage
=
-
1
;
this
->
bufferram
[
i
].
valid
=
0
;
}
/* Update BufferRAM */
/* Update BufferRAM */
i
=
ONENAND_CURRENT_BUFFERRAM
(
this
);
i
=
ONENAND_CURRENT_BUFFERRAM
(
this
);
this
->
bufferram
[
i
].
block
=
block
;
if
(
valid
)
this
->
bufferram
[
i
].
page
=
page
;
this
->
bufferram
[
i
].
blockpage
=
blockpage
;
this
->
bufferram
[
i
].
valid
=
valid
;
else
this
->
bufferram
[
i
].
blockpage
=
-
1
;
return
0
;
}
}
/**
/**
...
@@ -716,7 +711,7 @@ static int onenand_read(struct mtd_info *mtd, loff_t from, size_t len,
...
@@ -716,7 +711,7 @@ static int onenand_read(struct mtd_info *mtd, loff_t from, size_t len,
/* Do not allow reads past end of device */
/* Do not allow reads past end of device */
if
((
from
+
len
)
>
mtd
->
size
)
{
if
((
from
+
len
)
>
mtd
->
size
)
{
DEBUG
(
MTD_DEBUG_LEVEL0
,
"onenand_read: Attempt read beyond end of device
\n
"
);
printk
(
KERN_ERR
"onenand_read: Attempt read beyond end of device
\n
"
);
*
retlen
=
0
;
*
retlen
=
0
;
return
-
EINVAL
;
return
-
EINVAL
;
}
}
...
@@ -724,8 +719,6 @@ static int onenand_read(struct mtd_info *mtd, loff_t from, size_t len,
...
@@ -724,8 +719,6 @@ static int onenand_read(struct mtd_info *mtd, loff_t from, size_t len,
/* Grab the lock and see if the device is available */
/* Grab the lock and see if the device is available */
onenand_get_device
(
mtd
,
FL_READING
);
onenand_get_device
(
mtd
,
FL_READING
);
/* TODO handling oob */
stats
=
mtd
->
ecc_stats
;
stats
=
mtd
->
ecc_stats
;
/* Read-while-load method */
/* Read-while-load method */
...
@@ -754,9 +747,9 @@ static int onenand_read(struct mtd_info *mtd, loff_t from, size_t len,
...
@@ -754,9 +747,9 @@ static int onenand_read(struct mtd_info *mtd, loff_t from, size_t len,
* Now we issued chip 1 read and pointed chip 1
* Now we issued chip 1 read and pointed chip 1
* bufferam so we have to point chip 0 bufferam.
* bufferam so we have to point chip 0 bufferam.
*/
*/
if
(
this
->
device_id
&
ONENAND_DEVICE_IS_DDP
&&
if
(
ONENAND_IS_DDP
(
this
)
&&
unlikely
(
from
==
(
this
->
chipsize
>>
1
)))
{
unlikely
(
from
==
(
this
->
chipsize
>>
1
)))
{
this
->
write_word
(
0
,
this
->
base
+
ONENAND_REG_START_ADDRESS2
);
this
->
write_word
(
ONENAND_DDP_CHIP
0
,
this
->
base
+
ONENAND_REG_START_ADDRESS2
);
boundary
=
1
;
boundary
=
1
;
}
else
}
else
boundary
=
0
;
boundary
=
0
;
...
@@ -770,7 +763,7 @@ static int onenand_read(struct mtd_info *mtd, loff_t from, size_t len,
...
@@ -770,7 +763,7 @@ static int onenand_read(struct mtd_info *mtd, loff_t from, size_t len,
break
;
break
;
/* Set up for next read from bufferRAM */
/* Set up for next read from bufferRAM */
if
(
unlikely
(
boundary
))
if
(
unlikely
(
boundary
))
this
->
write_word
(
0x8000
,
this
->
base
+
ONENAND_REG_START_ADDRESS2
);
this
->
write_word
(
ONENAND_DDP_CHIP1
,
this
->
base
+
ONENAND_REG_START_ADDRESS2
);
ONENAND_SET_NEXT_BUFFERRAM
(
this
);
ONENAND_SET_NEXT_BUFFERRAM
(
this
);
buf
+=
thislen
;
buf
+=
thislen
;
thislen
=
min_t
(
int
,
mtd
->
writesize
,
len
-
read
);
thislen
=
min_t
(
int
,
mtd
->
writesize
,
len
-
read
);
...
@@ -800,6 +793,44 @@ static int onenand_read(struct mtd_info *mtd, loff_t from, size_t len,
...
@@ -800,6 +793,44 @@ static int onenand_read(struct mtd_info *mtd, loff_t from, size_t len,
return
mtd
->
ecc_stats
.
corrected
-
stats
.
corrected
?
-
EUCLEAN
:
0
;
return
mtd
->
ecc_stats
.
corrected
-
stats
.
corrected
?
-
EUCLEAN
:
0
;
}
}
/**
* onenand_transfer_auto_oob - [Internal] oob auto-placement transfer
* @param mtd MTD device structure
* @param buf destination address
* @param column oob offset to read from
* @param thislen oob length to read
*/
static
int
onenand_transfer_auto_oob
(
struct
mtd_info
*
mtd
,
uint8_t
*
buf
,
int
column
,
int
thislen
)
{
struct
onenand_chip
*
this
=
mtd
->
priv
;
struct
nand_oobfree
*
free
;
int
readcol
=
column
;
int
readend
=
column
+
thislen
;
int
lastgap
=
0
;
uint8_t
*
oob_buf
=
this
->
page_buf
+
mtd
->
writesize
;
for
(
free
=
this
->
ecclayout
->
oobfree
;
free
->
length
;
++
free
)
{
if
(
readcol
>=
lastgap
)
readcol
+=
free
->
offset
-
lastgap
;
if
(
readend
>=
lastgap
)
readend
+=
free
->
offset
-
lastgap
;
lastgap
=
free
->
offset
+
free
->
length
;
}
this
->
read_bufferram
(
mtd
,
ONENAND_SPARERAM
,
oob_buf
,
0
,
mtd
->
oobsize
);
for
(
free
=
this
->
ecclayout
->
oobfree
;
free
->
length
;
++
free
)
{
int
free_end
=
free
->
offset
+
free
->
length
;
if
(
free
->
offset
<
readend
&&
free_end
>
readcol
)
{
int
st
=
max_t
(
int
,
free
->
offset
,
readcol
);
int
ed
=
min_t
(
int
,
free_end
,
readend
);
int
n
=
ed
-
st
;
memcpy
(
buf
,
oob_buf
+
st
,
n
);
buf
+=
n
;
}
}
return
0
;
}
/**
/**
* onenand_do_read_oob - [MTD Interface] OneNAND read out-of-band
* onenand_do_read_oob - [MTD Interface] OneNAND read out-of-band
* @param mtd MTD device structure
* @param mtd MTD device structure
...
@@ -807,14 +838,15 @@ static int onenand_read(struct mtd_info *mtd, loff_t from, size_t len,
...
@@ -807,14 +838,15 @@ static int onenand_read(struct mtd_info *mtd, loff_t from, size_t len,
* @param len number of bytes to read
* @param len number of bytes to read
* @param retlen pointer to variable to store the number of read bytes
* @param retlen pointer to variable to store the number of read bytes
* @param buf the databuffer to put data
* @param buf the databuffer to put data
* @param mode operation mode
*
*
* OneNAND read out-of-band data from the spare area
* OneNAND read out-of-band data from the spare area
*/
*/
int
onenand_do_read_oob
(
struct
mtd_info
*
mtd
,
loff_t
from
,
size_t
len
,
static
int
onenand_do_read_oob
(
struct
mtd_info
*
mtd
,
loff_t
from
,
size_t
len
,
size_t
*
retlen
,
u_char
*
buf
)
size_t
*
retlen
,
u_char
*
buf
,
mtd_oob_mode_t
mode
)
{
{
struct
onenand_chip
*
this
=
mtd
->
priv
;
struct
onenand_chip
*
this
=
mtd
->
priv
;
int
read
=
0
,
thislen
,
column
;
int
read
=
0
,
thislen
,
column
,
oobsize
;
int
ret
=
0
;
int
ret
=
0
;
DEBUG
(
MTD_DEBUG_LEVEL3
,
"onenand_read_oob: from = 0x%08x, len = %i
\n
"
,
(
unsigned
int
)
from
,
(
int
)
len
);
DEBUG
(
MTD_DEBUG_LEVEL3
,
"onenand_read_oob: from = 0x%08x, len = %i
\n
"
,
(
unsigned
int
)
from
,
(
int
)
len
);
...
@@ -822,21 +854,33 @@ int onenand_do_read_oob(struct mtd_info *mtd, loff_t from, size_t len,
...
@@ -822,21 +854,33 @@ int onenand_do_read_oob(struct mtd_info *mtd, loff_t from, size_t len,
/* Initialize return length value */
/* Initialize return length value */
*
retlen
=
0
;
*
retlen
=
0
;
if
(
mode
==
MTD_OOB_AUTO
)
oobsize
=
this
->
ecclayout
->
oobavail
;
else
oobsize
=
mtd
->
oobsize
;
column
=
from
&
(
mtd
->
oobsize
-
1
);
if
(
unlikely
(
column
>=
oobsize
))
{
printk
(
KERN_ERR
"onenand_read_oob: Attempted to start read outside oob
\n
"
);
return
-
EINVAL
;
}
/* Do not allow reads past end of device */
/* Do not allow reads past end of device */
if
(
unlikely
((
from
+
len
)
>
mtd
->
size
))
{
if
(
unlikely
(
from
>=
mtd
->
size
||
DEBUG
(
MTD_DEBUG_LEVEL0
,
"onenand_read_oob: Attempt read beyond end of device
\n
"
);
column
+
len
>
((
mtd
->
size
>>
this
->
page_shift
)
-
(
from
>>
this
->
page_shift
))
*
oobsize
))
{
printk
(
KERN_ERR
"onenand_read_oob: Attempted to read beyond end of device
\n
"
);
return
-
EINVAL
;
return
-
EINVAL
;
}
}
/* Grab the lock and see if the device is available */
/* Grab the lock and see if the device is available */
onenand_get_device
(
mtd
,
FL_READING
);
onenand_get_device
(
mtd
,
FL_READING
);
column
=
from
&
(
mtd
->
oobsize
-
1
);
while
(
read
<
len
)
{
while
(
read
<
len
)
{
cond_resched
();
cond_resched
();
thislen
=
mtd
->
oobsize
-
column
;
thislen
=
oobsize
-
column
;
thislen
=
min_t
(
int
,
thislen
,
len
);
thislen
=
min_t
(
int
,
thislen
,
len
);
this
->
command
(
mtd
,
ONENAND_CMD_READOOB
,
from
,
mtd
->
oobsize
);
this
->
command
(
mtd
,
ONENAND_CMD_READOOB
,
from
,
mtd
->
oobsize
);
...
@@ -846,11 +890,14 @@ int onenand_do_read_oob(struct mtd_info *mtd, loff_t from, size_t len,
...
@@ -846,11 +890,14 @@ int onenand_do_read_oob(struct mtd_info *mtd, loff_t from, size_t len,
ret
=
this
->
wait
(
mtd
,
FL_READING
);
ret
=
this
->
wait
(
mtd
,
FL_READING
);
/* First copy data and check return value for ECC handling */
/* First copy data and check return value for ECC handling */
this
->
read_bufferram
(
mtd
,
ONENAND_SPARERAM
,
buf
,
column
,
thislen
);
if
(
mode
==
MTD_OOB_AUTO
)
onenand_transfer_auto_oob
(
mtd
,
buf
,
column
,
thislen
);
else
this
->
read_bufferram
(
mtd
,
ONENAND_SPARERAM
,
buf
,
column
,
thislen
);
if
(
ret
)
{
if
(
ret
)
{
DEBUG
(
MTD_DEBUG_LEVEL0
,
"onenand_read_oob: read failed = 0x%x
\n
"
,
ret
);
printk
(
KERN_ERR
"onenand_read_oob: read failed = 0x%x
\n
"
,
ret
);
goto
out
;
break
;
}
}
read
+=
thislen
;
read
+=
thislen
;
...
@@ -868,7 +915,6 @@ int onenand_do_read_oob(struct mtd_info *mtd, loff_t from, size_t len,
...
@@ -868,7 +915,6 @@ int onenand_do_read_oob(struct mtd_info *mtd, loff_t from, size_t len,
}
}
}
}
out:
/* Deselect and wake up anyone waiting on the device */
/* Deselect and wake up anyone waiting on the device */
onenand_release_device
(
mtd
);
onenand_release_device
(
mtd
);
...
@@ -885,10 +931,132 @@ out:
...
@@ -885,10 +931,132 @@ out:
static
int
onenand_read_oob
(
struct
mtd_info
*
mtd
,
loff_t
from
,
static
int
onenand_read_oob
(
struct
mtd_info
*
mtd
,
loff_t
from
,
struct
mtd_oob_ops
*
ops
)
struct
mtd_oob_ops
*
ops
)
{
{
BUG_ON
(
ops
->
mode
!=
MTD_OOB_PLACE
);
switch
(
ops
->
mode
)
{
case
MTD_OOB_PLACE
:
case
MTD_OOB_AUTO
:
break
;
case
MTD_OOB_RAW
:
/* Not implemented yet */
default:
return
-
EINVAL
;
}
return
onenand_do_read_oob
(
mtd
,
from
+
ops
->
ooboffs
,
ops
->
ooblen
,
return
onenand_do_read_oob
(
mtd
,
from
+
ops
->
ooboffs
,
ops
->
ooblen
,
&
ops
->
oobretlen
,
ops
->
oobbuf
);
&
ops
->
oobretlen
,
ops
->
oobbuf
,
ops
->
mode
);
}
/**
* onenand_bbt_wait - [DEFAULT] wait until the command is done
* @param mtd MTD device structure
* @param state state to select the max. timeout value
*
* Wait for command done.
*/
static
int
onenand_bbt_wait
(
struct
mtd_info
*
mtd
,
int
state
)
{
struct
onenand_chip
*
this
=
mtd
->
priv
;
unsigned
long
timeout
;
unsigned
int
interrupt
;
unsigned
int
ctrl
;
/* The 20 msec is enough */
timeout
=
jiffies
+
msecs_to_jiffies
(
20
);
while
(
time_before
(
jiffies
,
timeout
))
{
interrupt
=
this
->
read_word
(
this
->
base
+
ONENAND_REG_INTERRUPT
);
if
(
interrupt
&
ONENAND_INT_MASTER
)
break
;
}
/* To get correct interrupt status in timeout case */
interrupt
=
this
->
read_word
(
this
->
base
+
ONENAND_REG_INTERRUPT
);
ctrl
=
this
->
read_word
(
this
->
base
+
ONENAND_REG_CTRL_STATUS
);
if
(
ctrl
&
ONENAND_CTRL_ERROR
)
{
printk
(
KERN_DEBUG
"onenand_bbt_wait: controller error = 0x%04x
\n
"
,
ctrl
);
/* Initial bad block case */
if
(
ctrl
&
ONENAND_CTRL_LOAD
)
return
ONENAND_BBT_READ_ERROR
;
return
ONENAND_BBT_READ_FATAL_ERROR
;
}
if
(
interrupt
&
ONENAND_INT_READ
)
{
int
ecc
=
this
->
read_word
(
this
->
base
+
ONENAND_REG_ECC_STATUS
);
if
(
ecc
&
ONENAND_ECC_2BIT_ALL
)
return
ONENAND_BBT_READ_ERROR
;
}
else
{
printk
(
KERN_ERR
"onenand_bbt_wait: read timeout!"
"ctrl=0x%04x intr=0x%04x
\n
"
,
ctrl
,
interrupt
);
return
ONENAND_BBT_READ_FATAL_ERROR
;
}
return
0
;
}
/**
* onenand_bbt_read_oob - [MTD Interface] OneNAND read out-of-band for bbt scan
* @param mtd MTD device structure
* @param from offset to read from
* @param @ops oob operation description structure
*
* OneNAND read out-of-band data from the spare area for bbt scan
*/
int
onenand_bbt_read_oob
(
struct
mtd_info
*
mtd
,
loff_t
from
,
struct
mtd_oob_ops
*
ops
)
{
struct
onenand_chip
*
this
=
mtd
->
priv
;
int
read
=
0
,
thislen
,
column
;
int
ret
=
0
;
size_t
len
=
ops
->
ooblen
;
u_char
*
buf
=
ops
->
oobbuf
;
DEBUG
(
MTD_DEBUG_LEVEL3
,
"onenand_bbt_read_oob: from = 0x%08x, len = %i
\n
"
,
(
unsigned
int
)
from
,
len
);
/* Initialize return value */
ops
->
oobretlen
=
0
;
/* Do not allow reads past end of device */
if
(
unlikely
((
from
+
len
)
>
mtd
->
size
))
{
printk
(
KERN_ERR
"onenand_bbt_read_oob: Attempt read beyond end of device
\n
"
);
return
ONENAND_BBT_READ_FATAL_ERROR
;
}
/* Grab the lock and see if the device is available */
onenand_get_device
(
mtd
,
FL_READING
);
column
=
from
&
(
mtd
->
oobsize
-
1
);
while
(
read
<
len
)
{
cond_resched
();
thislen
=
mtd
->
oobsize
-
column
;
thislen
=
min_t
(
int
,
thislen
,
len
);
this
->
command
(
mtd
,
ONENAND_CMD_READOOB
,
from
,
mtd
->
oobsize
);
onenand_update_bufferram
(
mtd
,
from
,
0
);
ret
=
onenand_bbt_wait
(
mtd
,
FL_READING
);
if
(
ret
)
break
;
this
->
read_bufferram
(
mtd
,
ONENAND_SPARERAM
,
buf
,
column
,
thislen
);
read
+=
thislen
;
if
(
read
==
len
)
break
;
buf
+=
thislen
;
/* Read more? */
if
(
read
<
len
)
{
/* Update Page size */
from
+=
mtd
->
writesize
;
column
=
0
;
}
}
/* Deselect and wake up anyone waiting on the device */
onenand_release_device
(
mtd
);
ops
->
oobretlen
=
read
;
return
ret
;
}
}
#ifdef CONFIG_MTD_ONENAND_VERIFY_WRITE
#ifdef CONFIG_MTD_ONENAND_VERIFY_WRITE
...
@@ -897,14 +1065,12 @@ static int onenand_read_oob(struct mtd_info *mtd, loff_t from,
...
@@ -897,14 +1065,12 @@ static int onenand_read_oob(struct mtd_info *mtd, loff_t from,
* @param mtd MTD device structure
* @param mtd MTD device structure
* @param buf the databuffer to verify
* @param buf the databuffer to verify
* @param to offset to read from
* @param to offset to read from
* @param len number of bytes to read and compare
*
*
*/
*/
static
int
onenand_verify_oob
(
struct
mtd_info
*
mtd
,
const
u_char
*
buf
,
loff_t
to
,
int
len
)
static
int
onenand_verify_oob
(
struct
mtd_info
*
mtd
,
const
u_char
*
buf
,
loff_t
to
)
{
{
struct
onenand_chip
*
this
=
mtd
->
priv
;
struct
onenand_chip
*
this
=
mtd
->
priv
;
char
*
readp
=
this
->
page_buf
;
char
*
readp
=
this
->
page_buf
+
mtd
->
writesize
;
int
column
=
to
&
(
mtd
->
oobsize
-
1
);
int
status
,
i
;
int
status
,
i
;
this
->
command
(
mtd
,
ONENAND_CMD_READOOB
,
to
,
mtd
->
oobsize
);
this
->
command
(
mtd
,
ONENAND_CMD_READOOB
,
to
,
mtd
->
oobsize
);
...
@@ -913,9 +1079,8 @@ static int onenand_verify_oob(struct mtd_info *mtd, const u_char *buf, loff_t to
...
@@ -913,9 +1079,8 @@ static int onenand_verify_oob(struct mtd_info *mtd, const u_char *buf, loff_t to
if
(
status
)
if
(
status
)
return
status
;
return
status
;
this
->
read_bufferram
(
mtd
,
ONENAND_SPARERAM
,
readp
,
column
,
len
);
this
->
read_bufferram
(
mtd
,
ONENAND_SPARERAM
,
readp
,
0
,
mtd
->
oobsize
);
for
(
i
=
0
;
i
<
mtd
->
oobsize
;
i
++
)
for
(
i
=
0
;
i
<
len
;
i
++
)
if
(
buf
[
i
]
!=
0xFF
&&
buf
[
i
]
!=
readp
[
i
])
if
(
buf
[
i
]
!=
0xFF
&&
buf
[
i
]
!=
readp
[
i
])
return
-
EBADMSG
;
return
-
EBADMSG
;
...
@@ -923,41 +1088,51 @@ static int onenand_verify_oob(struct mtd_info *mtd, const u_char *buf, loff_t to
...
@@ -923,41 +1088,51 @@ static int onenand_verify_oob(struct mtd_info *mtd, const u_char *buf, loff_t to
}
}
/**
/**
* onenand_verify_page - [GENERIC] verify the chip contents after a write
* onenand_verify - [GENERIC] verify the chip contents after a write
* @param mtd MTD device structure
* @param mtd MTD device structure
* @param buf the databuffer to verify
* @param buf the databuffer to verify
* @param addr offset to read from
* @param len number of bytes to read and compare
*
*
* Check DataRAM area directly
*/
*/
static
int
onenand_verify
_page
(
struct
mtd_info
*
mtd
,
u_char
*
buf
,
loff_t
addr
)
static
int
onenand_verify
(
struct
mtd_info
*
mtd
,
const
u_char
*
buf
,
loff_t
addr
,
size_t
len
)
{
{
struct
onenand_chip
*
this
=
mtd
->
priv
;
struct
onenand_chip
*
this
=
mtd
->
priv
;
void
__iomem
*
dataram
0
,
*
dataram1
;
void
__iomem
*
dataram
;
int
ret
=
0
;
int
ret
=
0
;
int
thislen
,
column
;
/* In partial page write, just skip it */
while
(
len
!=
0
)
{
if
((
addr
&
(
mtd
->
writesize
-
1
))
!=
0
)
thislen
=
min_t
(
int
,
mtd
->
writesize
,
len
);
return
0
;
column
=
addr
&
(
mtd
->
writesize
-
1
);
if
(
column
+
thislen
>
mtd
->
writesize
)
thislen
=
mtd
->
writesize
-
column
;
this
->
command
(
mtd
,
ONENAND_CMD_READ
,
addr
,
mtd
->
writesize
);
this
->
command
(
mtd
,
ONENAND_CMD_READ
,
addr
,
mtd
->
writesize
);
ret
=
this
->
wait
(
mtd
,
FL_READING
);
onenand_update_bufferram
(
mtd
,
addr
,
0
);
if
(
ret
)
return
ret
;
ret
=
this
->
wait
(
mtd
,
FL_READING
);
if
(
ret
)
return
ret
;
onenand_update_bufferram
(
mtd
,
addr
,
1
);
onenand_update_bufferram
(
mtd
,
addr
,
1
);
/* Check, if the two dataram areas are same */
dataram
=
this
->
base
+
ONENAND_DATARAM
;
dataram0
=
this
->
base
+
ONENAND_DATARAM
;
dataram
+=
onenand_bufferram_offset
(
mtd
,
ONENAND_DATARAM
);
dataram1
=
dataram0
+
mtd
->
writesize
;
if
(
memcmp
(
dataram0
,
dataram1
,
mtd
->
writesize
))
if
(
memcmp
(
buf
,
dataram
+
column
,
thislen
))
return
-
EBADMSG
;
return
-
EBADMSG
;
len
-=
thislen
;
buf
+=
thislen
;
addr
+=
thislen
;
}
return
0
;
return
0
;
}
}
#else
#else
#define onenand_verify
_page(...)
(0)
#define onenand_verify
(...)
(0)
#define onenand_verify_oob(...) (0)
#define onenand_verify_oob(...) (0)
#endif
#endif
...
@@ -988,60 +1163,57 @@ static int onenand_write(struct mtd_info *mtd, loff_t to, size_t len,
...
@@ -988,60 +1163,57 @@ static int onenand_write(struct mtd_info *mtd, loff_t to, size_t len,
/* Do not allow writes past end of device */
/* Do not allow writes past end of device */
if
(
unlikely
((
to
+
len
)
>
mtd
->
size
))
{
if
(
unlikely
((
to
+
len
)
>
mtd
->
size
))
{
DEBUG
(
MTD_DEBUG_LEVEL0
,
"onenand_write: Attempt write to past end of device
\n
"
);
printk
(
KERN_ERR
"onenand_write: Attempt write to past end of device
\n
"
);
return
-
EINVAL
;
return
-
EINVAL
;
}
}
/* Reject writes, which are not page aligned */
/* Reject writes, which are not page aligned */
if
(
unlikely
(
NOTALIGNED
(
to
))
||
unlikely
(
NOTALIGNED
(
len
)))
{
if
(
unlikely
(
NOTALIGNED
(
to
))
||
unlikely
(
NOTALIGNED
(
len
)))
{
DEBUG
(
MTD_DEBUG_LEVEL0
,
"onenand_write: Attempt to write not page aligned data
\n
"
);
printk
(
KERN_ERR
"onenand_write: Attempt to write not page aligned data
\n
"
);
return
-
EINVAL
;
return
-
EINVAL
;
}
}
column
=
to
&
(
mtd
->
writesize
-
1
);
column
=
to
&
(
mtd
->
writesize
-
1
);
subpage
=
column
||
(
len
&
(
mtd
->
writesize
-
1
));
/* Grab the lock and see if the device is available */
/* Grab the lock and see if the device is available */
onenand_get_device
(
mtd
,
FL_WRITING
);
onenand_get_device
(
mtd
,
FL_WRITING
);
/* Loop until all data write */
/* Loop until all data write */
while
(
written
<
len
)
{
while
(
written
<
len
)
{
int
bytes
=
mtd
->
writesize
;
int
thislen
=
min_t
(
int
,
mtd
->
writesize
-
column
,
len
-
written
);
int
thislen
=
min_t
(
int
,
bytes
,
len
-
written
);
u_char
*
wbuf
=
(
u_char
*
)
buf
;
u_char
*
wbuf
=
(
u_char
*
)
buf
;
cond_resched
();
cond_resched
();
this
->
command
(
mtd
,
ONENAND_CMD_BUFFERRAM
,
to
,
bytes
);
this
->
command
(
mtd
,
ONENAND_CMD_BUFFERRAM
,
to
,
thislen
);
/* Partial page write */
/* Partial page write */
subpage
=
thislen
<
mtd
->
writesize
;
if
(
subpage
)
{
if
(
subpage
)
{
bytes
=
min_t
(
int
,
bytes
-
column
,
(
int
)
len
);
memset
(
this
->
page_buf
,
0xff
,
mtd
->
writesize
);
memset
(
this
->
page_buf
,
0xff
,
mtd
->
writesize
);
memcpy
(
this
->
page_buf
+
column
,
buf
,
bytes
);
memcpy
(
this
->
page_buf
+
column
,
buf
,
thislen
);
wbuf
=
this
->
page_buf
;
wbuf
=
this
->
page_buf
;
/* Even though partial write, we need page size */
thislen
=
mtd
->
writesize
;
}
}
this
->
write_bufferram
(
mtd
,
ONENAND_DATARAM
,
wbuf
,
0
,
thislen
);
this
->
write_bufferram
(
mtd
,
ONENAND_DATARAM
,
wbuf
,
0
,
mtd
->
writesize
);
this
->
write_bufferram
(
mtd
,
ONENAND_SPARERAM
,
ffchars
,
0
,
mtd
->
oobsize
);
this
->
write_bufferram
(
mtd
,
ONENAND_SPARERAM
,
ffchars
,
0
,
mtd
->
oobsize
);
this
->
command
(
mtd
,
ONENAND_CMD_PROG
,
to
,
mtd
->
writesize
);
this
->
command
(
mtd
,
ONENAND_CMD_PROG
,
to
,
mtd
->
writesize
);
ret
=
this
->
wait
(
mtd
,
FL_WRITING
);
/* In partial page write we don't update bufferram */
/* In partial page write we don't update bufferram */
onenand_update_bufferram
(
mtd
,
to
,
!
subpage
);
onenand_update_bufferram
(
mtd
,
to
,
!
ret
&&
!
subpage
);
ret
=
this
->
wait
(
mtd
,
FL_WRITING
);
if
(
ret
)
{
if
(
ret
)
{
DEBUG
(
MTD_DEBUG_LEVEL0
,
"onenand_write: write filaed %d
\n
"
,
ret
);
printk
(
KERN_ERR
"onenand_write: write filaed %d
\n
"
,
ret
);
break
;
break
;
}
}
/* Only check verify write turn on */
/* Only check verify write turn on */
ret
=
onenand_verify
_page
(
mtd
,
(
u_char
*
)
wbuf
,
to
);
ret
=
onenand_verify
(
mtd
,
(
u_char
*
)
wbuf
,
to
,
thislen
);
if
(
ret
)
{
if
(
ret
)
{
DEBUG
(
MTD_DEBUG_LEVEL0
,
"onenand_write: verify failed %d
\n
"
,
ret
);
printk
(
KERN_ERR
"onenand_write: verify failed %d
\n
"
,
ret
);
break
;
break
;
}
}
...
@@ -1063,6 +1235,43 @@ static int onenand_write(struct mtd_info *mtd, loff_t to, size_t len,
...
@@ -1063,6 +1235,43 @@ static int onenand_write(struct mtd_info *mtd, loff_t to, size_t len,
return
ret
;
return
ret
;
}
}
/**
* onenand_fill_auto_oob - [Internal] oob auto-placement transfer
* @param mtd MTD device structure
* @param oob_buf oob buffer
* @param buf source address
* @param column oob offset to write to
* @param thislen oob length to write
*/
static
int
onenand_fill_auto_oob
(
struct
mtd_info
*
mtd
,
u_char
*
oob_buf
,
const
u_char
*
buf
,
int
column
,
int
thislen
)
{
struct
onenand_chip
*
this
=
mtd
->
priv
;
struct
nand_oobfree
*
free
;
int
writecol
=
column
;
int
writeend
=
column
+
thislen
;
int
lastgap
=
0
;
for
(
free
=
this
->
ecclayout
->
oobfree
;
free
->
length
;
++
free
)
{
if
(
writecol
>=
lastgap
)
writecol
+=
free
->
offset
-
lastgap
;
if
(
writeend
>=
lastgap
)
writeend
+=
free
->
offset
-
lastgap
;
lastgap
=
free
->
offset
+
free
->
length
;
}
for
(
free
=
this
->
ecclayout
->
oobfree
;
free
->
length
;
++
free
)
{
int
free_end
=
free
->
offset
+
free
->
length
;
if
(
free
->
offset
<
writeend
&&
free_end
>
writecol
)
{
int
st
=
max_t
(
int
,
free
->
offset
,
writecol
);
int
ed
=
min_t
(
int
,
free_end
,
writeend
);
int
n
=
ed
-
st
;
memcpy
(
oob_buf
+
st
,
buf
,
n
);
buf
+=
n
;
}
}
return
0
;
}
/**
/**
* onenand_do_write_oob - [Internal] OneNAND write out-of-band
* onenand_do_write_oob - [Internal] OneNAND write out-of-band
* @param mtd MTD device structure
* @param mtd MTD device structure
...
@@ -1070,14 +1279,15 @@ static int onenand_write(struct mtd_info *mtd, loff_t to, size_t len,
...
@@ -1070,14 +1279,15 @@ static int onenand_write(struct mtd_info *mtd, loff_t to, size_t len,
* @param len number of bytes to write
* @param len number of bytes to write
* @param retlen pointer to variable to store the number of written bytes
* @param retlen pointer to variable to store the number of written bytes
* @param buf the data to write
* @param buf the data to write
* @param mode operation mode
*
*
* OneNAND write out-of-band
* OneNAND write out-of-band
*/
*/
static
int
onenand_do_write_oob
(
struct
mtd_info
*
mtd
,
loff_t
to
,
size_t
len
,
static
int
onenand_do_write_oob
(
struct
mtd_info
*
mtd
,
loff_t
to
,
size_t
len
,
size_t
*
retlen
,
const
u_char
*
buf
)
size_t
*
retlen
,
const
u_char
*
buf
,
mtd_oob_mode_t
mode
)
{
{
struct
onenand_chip
*
this
=
mtd
->
priv
;
struct
onenand_chip
*
this
=
mtd
->
priv
;
int
column
,
ret
=
0
;
int
column
,
ret
=
0
,
oobsize
;
int
written
=
0
;
int
written
=
0
;
DEBUG
(
MTD_DEBUG_LEVEL3
,
"onenand_write_oob: to = 0x%08x, len = %i
\n
"
,
(
unsigned
int
)
to
,
(
int
)
len
);
DEBUG
(
MTD_DEBUG_LEVEL3
,
"onenand_write_oob: to = 0x%08x, len = %i
\n
"
,
(
unsigned
int
)
to
,
(
int
)
len
);
...
@@ -1085,9 +1295,30 @@ static int onenand_do_write_oob(struct mtd_info *mtd, loff_t to, size_t len,
...
@@ -1085,9 +1295,30 @@ static int onenand_do_write_oob(struct mtd_info *mtd, loff_t to, size_t len,
/* Initialize retlen, in case of early exit */
/* Initialize retlen, in case of early exit */
*
retlen
=
0
;
*
retlen
=
0
;
/* Do not allow writes past end of device */
if
(
mode
==
MTD_OOB_AUTO
)
if
(
unlikely
((
to
+
len
)
>
mtd
->
size
))
{
oobsize
=
this
->
ecclayout
->
oobavail
;
DEBUG
(
MTD_DEBUG_LEVEL0
,
"onenand_write_oob: Attempt write to past end of device
\n
"
);
else
oobsize
=
mtd
->
oobsize
;
column
=
to
&
(
mtd
->
oobsize
-
1
);
if
(
unlikely
(
column
>=
oobsize
))
{
printk
(
KERN_ERR
"onenand_write_oob: Attempted to start write outside oob
\n
"
);
return
-
EINVAL
;
}
/* For compatibility with NAND: Do not allow write past end of page */
if
(
column
+
len
>
oobsize
)
{
printk
(
KERN_ERR
"onenand_write_oob: "
"Attempt to write past end of page
\n
"
);
return
-
EINVAL
;
}
/* Do not allow reads past end of device */
if
(
unlikely
(
to
>=
mtd
->
size
||
column
+
len
>
((
mtd
->
size
>>
this
->
page_shift
)
-
(
to
>>
this
->
page_shift
))
*
oobsize
))
{
printk
(
KERN_ERR
"onenand_write_oob: Attempted to write past end of device
\n
"
);
return
-
EINVAL
;
return
-
EINVAL
;
}
}
...
@@ -1096,18 +1327,19 @@ static int onenand_do_write_oob(struct mtd_info *mtd, loff_t to, size_t len,
...
@@ -1096,18 +1327,19 @@ static int onenand_do_write_oob(struct mtd_info *mtd, loff_t to, size_t len,
/* Loop until all data write */
/* Loop until all data write */
while
(
written
<
len
)
{
while
(
written
<
len
)
{
int
thislen
=
min_t
(
int
,
mtd
->
oobsize
,
len
-
written
);
int
thislen
=
min_t
(
int
,
oobsize
,
len
-
written
);
cond_resched
();
cond_resched
();
column
=
to
&
(
mtd
->
oobsize
-
1
);
this
->
command
(
mtd
,
ONENAND_CMD_BUFFERRAM
,
to
,
mtd
->
oobsize
);
this
->
command
(
mtd
,
ONENAND_CMD_BUFFERRAM
,
to
,
mtd
->
oobsize
);
/* We send data to spare ram with oobsize
/* We send data to spare ram with oobsize
* to prevent byte access */
* to prevent byte access */
memset
(
this
->
page_buf
,
0xff
,
mtd
->
oobsize
);
memset
(
this
->
page_buf
,
0xff
,
mtd
->
oobsize
);
memcpy
(
this
->
page_buf
+
column
,
buf
,
thislen
);
if
(
mode
==
MTD_OOB_AUTO
)
onenand_fill_auto_oob
(
mtd
,
this
->
page_buf
,
buf
,
column
,
thislen
);
else
memcpy
(
this
->
page_buf
+
column
,
buf
,
thislen
);
this
->
write_bufferram
(
mtd
,
ONENAND_SPARERAM
,
this
->
page_buf
,
0
,
mtd
->
oobsize
);
this
->
write_bufferram
(
mtd
,
ONENAND_SPARERAM
,
this
->
page_buf
,
0
,
mtd
->
oobsize
);
this
->
command
(
mtd
,
ONENAND_CMD_PROGOOB
,
to
,
mtd
->
oobsize
);
this
->
command
(
mtd
,
ONENAND_CMD_PROGOOB
,
to
,
mtd
->
oobsize
);
...
@@ -1116,26 +1348,25 @@ static int onenand_do_write_oob(struct mtd_info *mtd, loff_t to, size_t len,
...
@@ -1116,26 +1348,25 @@ static int onenand_do_write_oob(struct mtd_info *mtd, loff_t to, size_t len,
ret
=
this
->
wait
(
mtd
,
FL_WRITING
);
ret
=
this
->
wait
(
mtd
,
FL_WRITING
);
if
(
ret
)
{
if
(
ret
)
{
DEBUG
(
MTD_DEBUG_LEVEL0
,
"onenand_write_oob: write fila
ed %d
\n
"
,
ret
);
printk
(
KERN_ERR
"onenand_write_oob: write fail
ed %d
\n
"
,
ret
);
goto
out
;
break
;
}
}
ret
=
onenand_verify_oob
(
mtd
,
buf
,
to
,
thislen
);
ret
=
onenand_verify_oob
(
mtd
,
this
->
page_buf
,
to
);
if
(
ret
)
{
if
(
ret
)
{
DEBUG
(
MTD_DEBUG_LEVEL0
,
"onenand_write_oob: verify failed %d
\n
"
,
ret
);
printk
(
KERN_ERR
"onenand_write_oob: verify failed %d
\n
"
,
ret
);
goto
out
;
break
;
}
}
written
+=
thislen
;
written
+=
thislen
;
if
(
written
==
len
)
if
(
written
==
len
)
break
;
break
;
to
+=
thislen
;
to
+=
mtd
->
writesize
;
buf
+=
thislen
;
buf
+=
thislen
;
column
=
0
;
}
}
out:
/* Deselect and wake up anyone waiting on the device */
/* Deselect and wake up anyone waiting on the device */
onenand_release_device
(
mtd
);
onenand_release_device
(
mtd
);
...
@@ -1153,10 +1384,17 @@ out:
...
@@ -1153,10 +1384,17 @@ out:
static
int
onenand_write_oob
(
struct
mtd_info
*
mtd
,
loff_t
to
,
static
int
onenand_write_oob
(
struct
mtd_info
*
mtd
,
loff_t
to
,
struct
mtd_oob_ops
*
ops
)
struct
mtd_oob_ops
*
ops
)
{
{
BUG_ON
(
ops
->
mode
!=
MTD_OOB_PLACE
);
switch
(
ops
->
mode
)
{
case
MTD_OOB_PLACE
:
case
MTD_OOB_AUTO
:
break
;
case
MTD_OOB_RAW
:
/* Not implemented yet */
default:
return
-
EINVAL
;
}
return
onenand_do_write_oob
(
mtd
,
to
+
ops
->
ooboffs
,
ops
->
ooblen
,
return
onenand_do_write_oob
(
mtd
,
to
+
ops
->
ooboffs
,
ops
->
ooblen
,
&
ops
->
oobretlen
,
ops
->
oobbuf
);
&
ops
->
oobretlen
,
ops
->
oobbuf
,
ops
->
mode
);
}
}
/**
/**
...
@@ -1199,19 +1437,19 @@ static int onenand_erase(struct mtd_info *mtd, struct erase_info *instr)
...
@@ -1199,19 +1437,19 @@ static int onenand_erase(struct mtd_info *mtd, struct erase_info *instr)
/* Start address must align on block boundary */
/* Start address must align on block boundary */
if
(
unlikely
(
instr
->
addr
&
(
block_size
-
1
)))
{
if
(
unlikely
(
instr
->
addr
&
(
block_size
-
1
)))
{
DEBUG
(
MTD_DEBUG_LEVEL0
,
"onenand_erase: Unaligned address
\n
"
);
printk
(
KERN_ERR
"onenand_erase: Unaligned address
\n
"
);
return
-
EINVAL
;
return
-
EINVAL
;
}
}
/* Length must align on block boundary */
/* Length must align on block boundary */
if
(
unlikely
(
instr
->
len
&
(
block_size
-
1
)))
{
if
(
unlikely
(
instr
->
len
&
(
block_size
-
1
)))
{
DEBUG
(
MTD_DEBUG_LEVEL0
,
"onenand_erase: Length not block aligned
\n
"
);
printk
(
KERN_ERR
"onenand_erase: Length not block aligned
\n
"
);
return
-
EINVAL
;
return
-
EINVAL
;
}
}
/* Do not allow erase past end of device */
/* Do not allow erase past end of device */
if
(
unlikely
((
instr
->
len
+
instr
->
addr
)
>
mtd
->
size
))
{
if
(
unlikely
((
instr
->
len
+
instr
->
addr
)
>
mtd
->
size
))
{
DEBUG
(
MTD_DEBUG_LEVEL0
,
"onenand_erase: Erase past end of device
\n
"
);
printk
(
KERN_ERR
"onenand_erase: Erase past end of device
\n
"
);
return
-
EINVAL
;
return
-
EINVAL
;
}
}
...
@@ -1241,7 +1479,7 @@ static int onenand_erase(struct mtd_info *mtd, struct erase_info *instr)
...
@@ -1241,7 +1479,7 @@ static int onenand_erase(struct mtd_info *mtd, struct erase_info *instr)
ret
=
this
->
wait
(
mtd
,
FL_ERASING
);
ret
=
this
->
wait
(
mtd
,
FL_ERASING
);
/* Check, if it is write protected */
/* Check, if it is write protected */
if
(
ret
)
{
if
(
ret
)
{
DEBUG
(
MTD_DEBUG_LEVEL0
,
"onenand_erase: Failed erase, block %d
\n
"
,
(
unsigned
)
(
addr
>>
this
->
erase_shift
));
printk
(
KERN_ERR
"onenand_erase: Failed erase, block %d
\n
"
,
(
unsigned
)
(
addr
>>
this
->
erase_shift
));
instr
->
state
=
MTD_ERASE_FAILED
;
instr
->
state
=
MTD_ERASE_FAILED
;
instr
->
fail_addr
=
addr
;
instr
->
fail_addr
=
addr
;
goto
erase_exit
;
goto
erase_exit
;
...
@@ -1322,7 +1560,7 @@ static int onenand_default_block_markbad(struct mtd_info *mtd, loff_t ofs)
...
@@ -1322,7 +1560,7 @@ static int onenand_default_block_markbad(struct mtd_info *mtd, loff_t ofs)
/* We write two bytes, so we dont have to mess with 16 bit access */
/* We write two bytes, so we dont have to mess with 16 bit access */
ofs
+=
mtd
->
oobsize
+
(
bbm
->
badblockpos
&
~
0x01
);
ofs
+=
mtd
->
oobsize
+
(
bbm
->
badblockpos
&
~
0x01
);
return
onenand_do_write_oob
(
mtd
,
ofs
,
2
,
&
retlen
,
buf
);
return
onenand_do_write_oob
(
mtd
,
ofs
,
2
,
&
retlen
,
buf
,
MTD_OOB_PLACE
);
}
}
/**
/**
...
@@ -1491,6 +1729,8 @@ static int onenand_unlock_all(struct mtd_info *mtd)
...
@@ -1491,6 +1729,8 @@ static int onenand_unlock_all(struct mtd_info *mtd)
struct
onenand_chip
*
this
=
mtd
->
priv
;
struct
onenand_chip
*
this
=
mtd
->
priv
;
if
(
this
->
options
&
ONENAND_HAS_UNLOCK_ALL
)
{
if
(
this
->
options
&
ONENAND_HAS_UNLOCK_ALL
)
{
/* Set start block address */
this
->
write_word
(
0
,
this
->
base
+
ONENAND_REG_START_BLOCK_ADDRESS
);
/* Write unlock command */
/* Write unlock command */
this
->
command
(
mtd
,
ONENAND_CMD_UNLOCK_ALL
,
0
,
0
);
this
->
command
(
mtd
,
ONENAND_CMD_UNLOCK_ALL
,
0
,
0
);
...
@@ -1503,13 +1743,10 @@ static int onenand_unlock_all(struct mtd_info *mtd)
...
@@ -1503,13 +1743,10 @@ static int onenand_unlock_all(struct mtd_info *mtd)
continue
;
continue
;
/* Workaround for all block unlock in DDP */
/* Workaround for all block unlock in DDP */
if
(
this
->
device_id
&
ONENAND_DEVICE_IS_DDP
)
{
if
(
ONENAND_IS_DDP
(
this
))
{
loff_t
ofs
;
size_t
len
;
/* 1st block on another chip */
/* 1st block on another chip */
ofs
=
this
->
chipsize
>>
1
;
loff_t
ofs
=
this
->
chipsize
>>
1
;
len
=
1
<<
this
->
erase_shift
;
size_t
len
=
mtd
->
erasesize
;
onenand_unlock
(
mtd
,
ofs
,
len
);
onenand_unlock
(
mtd
,
ofs
,
len
);
}
}
...
@@ -1617,7 +1854,7 @@ static int do_otp_lock(struct mtd_info *mtd, loff_t from, size_t len,
...
@@ -1617,7 +1854,7 @@ static int do_otp_lock(struct mtd_info *mtd, loff_t from, size_t len,
this
->
command
(
mtd
,
ONENAND_CMD_OTP_ACCESS
,
0
,
0
);
this
->
command
(
mtd
,
ONENAND_CMD_OTP_ACCESS
,
0
,
0
);
this
->
wait
(
mtd
,
FL_OTPING
);
this
->
wait
(
mtd
,
FL_OTPING
);
ret
=
onenand_do_write_oob
(
mtd
,
from
,
len
,
retlen
,
buf
);
ret
=
onenand_do_write_oob
(
mtd
,
from
,
len
,
retlen
,
buf
,
MTD_OOB_PLACE
);
/* Exit OTP access mode */
/* Exit OTP access mode */
this
->
command
(
mtd
,
ONENAND_CMD_RESET
,
0
,
0
);
this
->
command
(
mtd
,
ONENAND_CMD_RESET
,
0
,
0
);
...
@@ -1823,12 +2060,13 @@ static int onenand_lock_user_prot_reg(struct mtd_info *mtd, loff_t from,
...
@@ -1823,12 +2060,13 @@ static int onenand_lock_user_prot_reg(struct mtd_info *mtd, loff_t from,
#endif
/* CONFIG_MTD_ONENAND_OTP */
#endif
/* CONFIG_MTD_ONENAND_OTP */
/**
/**
* onenand_
lock_scheme - Check and set OneNAND lock scheme
* onenand_
check_features - Check and set OneNAND features
* @param mtd MTD data structure
* @param mtd MTD data structure
*
*
* Check and set OneNAND lock scheme
* Check and set OneNAND features
* - lock scheme
*/
*/
static
void
onenand_
lock_scheme
(
struct
mtd_info
*
mtd
)
static
void
onenand_
check_features
(
struct
mtd_info
*
mtd
)
{
{
struct
onenand_chip
*
this
=
mtd
->
priv
;
struct
onenand_chip
*
this
=
mtd
->
priv
;
unsigned
int
density
,
process
;
unsigned
int
density
,
process
;
...
@@ -1961,26 +2199,28 @@ static int onenand_probe(struct mtd_info *mtd)
...
@@ -1961,26 +2199,28 @@ static int onenand_probe(struct mtd_info *mtd)
density
=
dev_id
>>
ONENAND_DEVICE_DENSITY_SHIFT
;
density
=
dev_id
>>
ONENAND_DEVICE_DENSITY_SHIFT
;
this
->
chipsize
=
(
16
<<
density
)
<<
20
;
this
->
chipsize
=
(
16
<<
density
)
<<
20
;
/* Set density mask. it is used for DDP */
/* Set density mask. it is used for DDP */
this
->
density_mask
=
(
1
<<
(
density
+
6
));
if
(
ONENAND_IS_DDP
(
this
))
this
->
density_mask
=
(
1
<<
(
density
+
6
));
else
this
->
density_mask
=
0
;
/* OneNAND page size & block size */
/* OneNAND page size & block size */
/* The data buffer size is equal to page size */
/* The data buffer size is equal to page size */
mtd
->
writesize
=
this
->
read_word
(
this
->
base
+
ONENAND_REG_DATA_BUFFER_SIZE
);
mtd
->
writesize
=
this
->
read_word
(
this
->
base
+
ONENAND_REG_DATA_BUFFER_SIZE
);
mtd
->
oobsize
=
mtd
->
writesize
>>
5
;
mtd
->
oobsize
=
mtd
->
writesize
>>
5
;
/* Page
rs per block is
always 64 in OneNAND */
/* Page
s per a block are
always 64 in OneNAND */
mtd
->
erasesize
=
mtd
->
writesize
<<
6
;
mtd
->
erasesize
=
mtd
->
writesize
<<
6
;
this
->
erase_shift
=
ffs
(
mtd
->
erasesize
)
-
1
;
this
->
erase_shift
=
ffs
(
mtd
->
erasesize
)
-
1
;
this
->
page_shift
=
ffs
(
mtd
->
writesize
)
-
1
;
this
->
page_shift
=
ffs
(
mtd
->
writesize
)
-
1
;
this
->
ppb_shift
=
(
this
->
erase_shift
-
this
->
page_shift
);
this
->
page_mask
=
(
1
<<
(
this
->
erase_shift
-
this
->
page_shift
))
-
1
;
this
->
page_mask
=
(
mtd
->
erasesize
/
mtd
->
writesize
)
-
1
;
/* REVIST: Multichip handling */
/* REVIST: Multichip handling */
mtd
->
size
=
this
->
chipsize
;
mtd
->
size
=
this
->
chipsize
;
/* Check OneNAND
lock scheme
*/
/* Check OneNAND
features
*/
onenand_
lock_scheme
(
mtd
);
onenand_
check_features
(
mtd
);
return
0
;
return
0
;
}
}
...
@@ -2021,6 +2261,7 @@ static void onenand_resume(struct mtd_info *mtd)
...
@@ -2021,6 +2261,7 @@ static void onenand_resume(struct mtd_info *mtd)
*/
*/
int
onenand_scan
(
struct
mtd_info
*
mtd
,
int
maxchips
)
int
onenand_scan
(
struct
mtd_info
*
mtd
,
int
maxchips
)
{
{
int
i
;
struct
onenand_chip
*
this
=
mtd
->
priv
;
struct
onenand_chip
*
this
=
mtd
->
priv
;
if
(
!
this
->
read_word
)
if
(
!
this
->
read_word
)
...
@@ -2092,6 +2333,16 @@ int onenand_scan(struct mtd_info *mtd, int maxchips)
...
@@ -2092,6 +2333,16 @@ int onenand_scan(struct mtd_info *mtd, int maxchips)
}
}
this
->
subpagesize
=
mtd
->
writesize
>>
mtd
->
subpage_sft
;
this
->
subpagesize
=
mtd
->
writesize
>>
mtd
->
subpage_sft
;
/*
* The number of bytes available for a client to place data into
* the out of band area
*/
this
->
ecclayout
->
oobavail
=
0
;
for
(
i
=
0
;
this
->
ecclayout
->
oobfree
[
i
].
length
;
i
++
)
this
->
ecclayout
->
oobavail
+=
this
->
ecclayout
->
oobfree
[
i
].
length
;
mtd
->
ecclayout
=
this
->
ecclayout
;
mtd
->
ecclayout
=
this
->
ecclayout
;
/* Fill in remaining MTD driver data */
/* Fill in remaining MTD driver data */
...
@@ -2144,8 +2395,11 @@ void onenand_release(struct mtd_info *mtd)
...
@@ -2144,8 +2395,11 @@ void onenand_release(struct mtd_info *mtd)
del_mtd_device
(
mtd
);
del_mtd_device
(
mtd
);
/* Free bad block table memory, if allocated */
/* Free bad block table memory, if allocated */
if
(
this
->
bbm
)
if
(
this
->
bbm
)
{
struct
bbm_info
*
bbm
=
this
->
bbm
;
kfree
(
bbm
->
bbt
);
kfree
(
this
->
bbm
);
kfree
(
this
->
bbm
);
}
/* Buffer allocated by onenand_scan */
/* Buffer allocated by onenand_scan */
if
(
this
->
options
&
ONENAND_PAGEBUF_ALLOC
)
if
(
this
->
options
&
ONENAND_PAGEBUF_ALLOC
)
kfree
(
this
->
page_buf
);
kfree
(
this
->
page_buf
);
...
...
drivers/mtd/onenand/onenand_bbt.c
View file @
c1f16258
...
@@ -17,8 +17,8 @@
...
@@ -17,8 +17,8 @@
#include <linux/mtd/onenand.h>
#include <linux/mtd/onenand.h>
#include <linux/mtd/compatmac.h>
#include <linux/mtd/compatmac.h>
extern
int
onenand_
do_read_oob
(
struct
mtd_info
*
mtd
,
loff_t
from
,
size_t
len
,
extern
int
onenand_
bbt_read_oob
(
struct
mtd_info
*
mtd
,
loff_t
from
,
size_t
*
retlen
,
u_char
*
buf
);
struct
mtd_oob_ops
*
ops
);
/**
/**
* check_short_pattern - [GENERIC] check if a pattern is in the buffer
* check_short_pattern - [GENERIC] check if a pattern is in the buffer
...
@@ -65,10 +65,11 @@ static int create_bbt(struct mtd_info *mtd, uint8_t *buf, struct nand_bbt_descr
...
@@ -65,10 +65,11 @@ static int create_bbt(struct mtd_info *mtd, uint8_t *buf, struct nand_bbt_descr
int
startblock
;
int
startblock
;
loff_t
from
;
loff_t
from
;
size_t
readlen
,
ooblen
;
size_t
readlen
,
ooblen
;
struct
mtd_oob_ops
ops
;
printk
(
KERN_INFO
"Scanning device for bad blocks
\n
"
);
printk
(
KERN_INFO
"Scanning device for bad blocks
\n
"
);
len
=
1
;
len
=
2
;
/* We need only read few bytes from the OOB area */
/* We need only read few bytes from the OOB area */
scanlen
=
ooblen
=
0
;
scanlen
=
ooblen
=
0
;
...
@@ -82,22 +83,24 @@ static int create_bbt(struct mtd_info *mtd, uint8_t *buf, struct nand_bbt_descr
...
@@ -82,22 +83,24 @@ static int create_bbt(struct mtd_info *mtd, uint8_t *buf, struct nand_bbt_descr
startblock
=
0
;
startblock
=
0
;
from
=
0
;
from
=
0
;
ops
.
mode
=
MTD_OOB_PLACE
;
ops
.
ooblen
=
readlen
;
ops
.
oobbuf
=
buf
;
ops
.
len
=
ops
.
ooboffs
=
ops
.
retlen
=
ops
.
oobretlen
=
0
;
for
(
i
=
startblock
;
i
<
numblocks
;
)
{
for
(
i
=
startblock
;
i
<
numblocks
;
)
{
int
ret
;
int
ret
;
for
(
j
=
0
;
j
<
len
;
j
++
)
{
for
(
j
=
0
;
j
<
len
;
j
++
)
{
size_t
retlen
;
/* No need to read pages fully,
/* No need to read pages fully,
* just read required OOB bytes */
* just read required OOB bytes */
ret
=
onenand_do_read_oob
(
mtd
,
from
+
j
*
mtd
->
writesize
+
bd
->
offs
,
ret
=
onenand_bbt_read_oob
(
mtd
,
from
+
j
*
mtd
->
writesize
+
bd
->
offs
,
&
ops
);
readlen
,
&
retlen
,
&
buf
[
0
]);
/* If it is a initial bad block, just ignore it */
/* If it is a initial bad block, just ignore it */
if
(
ret
&&
!
(
ret
&
ONENAND_CTRL_LOAD
)
)
if
(
ret
==
ONENAND_BBT_READ_FATAL_ERROR
)
return
ret
;
return
-
EIO
;
if
(
check_short_pattern
(
&
buf
[
j
*
scanlen
],
scanlen
,
mtd
->
writesize
,
bd
))
{
if
(
ret
||
check_short_pattern
(
&
buf
[
j
*
scanlen
],
scanlen
,
mtd
->
writesize
,
bd
))
{
bbm
->
bbt
[
i
>>
3
]
|=
0x03
<<
(
i
&
0x6
);
bbm
->
bbt
[
i
>>
3
]
|=
0x03
<<
(
i
&
0x6
);
printk
(
KERN_WARNING
"Bad eraseblock %d at 0x%08x
\n
"
,
printk
(
KERN_WARNING
"Bad eraseblock %d at 0x%08x
\n
"
,
i
>>
1
,
(
unsigned
int
)
from
);
i
>>
1
,
(
unsigned
int
)
from
);
...
@@ -168,8 +171,8 @@ static int onenand_isbad_bbt(struct mtd_info *mtd, loff_t offs, int allowbbt)
...
@@ -168,8 +171,8 @@ static int onenand_isbad_bbt(struct mtd_info *mtd, loff_t offs, int allowbbt)
* marked good / bad blocks and writes the bad block table(s) to
* marked good / bad blocks and writes the bad block table(s) to
* the selected place.
* the selected place.
*
*
* The bad block table memory is allocated here. It
must be
freed
* The bad block table memory is allocated here. It
is
freed
* by
calling the onenand_free_bbt
function.
* by
the onenand_release
function.
*
*
*/
*/
int
onenand_scan_bbt
(
struct
mtd_info
*
mtd
,
struct
nand_bbt_descr
*
bd
)
int
onenand_scan_bbt
(
struct
mtd_info
*
mtd
,
struct
nand_bbt_descr
*
bd
)
...
...
include/linux/mtd/bbm.h
View file @
c1f16258
...
@@ -92,6 +92,13 @@ struct nand_bbt_descr {
...
@@ -92,6 +92,13 @@ struct nand_bbt_descr {
*/
*/
#define ONENAND_BADBLOCK_POS 0
#define ONENAND_BADBLOCK_POS 0
/*
* Bad block scanning errors
*/
#define ONENAND_BBT_READ_ERROR 1
#define ONENAND_BBT_READ_ECC_ERROR 2
#define ONENAND_BBT_READ_FATAL_ERROR 4
/**
/**
* struct bbm_info - [GENERIC] Bad Block Table data structure
* struct bbm_info - [GENERIC] Bad Block Table data structure
* @bbt_erase_shift: [INTERN] number of address bits in a bbt entry
* @bbt_erase_shift: [INTERN] number of address bits in a bbt entry
...
...
include/linux/mtd/onenand.h
View file @
c1f16258
/*
/*
* linux/include/linux/mtd/onenand.h
* linux/include/linux/mtd/onenand.h
*
*
* Copyright (C) 2005-200
6
Samsung Electronics
* Copyright (C) 2005-200
7
Samsung Electronics
* Kyungmin Park <kyungmin.park@samsung.com>
* Kyungmin Park <kyungmin.park@samsung.com>
*
*
* This program is free software; you can redistribute it and/or modify
* This program is free software; you can redistribute it and/or modify
...
@@ -42,14 +42,10 @@ typedef enum {
...
@@ -42,14 +42,10 @@ typedef enum {
/**
/**
* struct onenand_bufferram - OneNAND BufferRAM Data
* struct onenand_bufferram - OneNAND BufferRAM Data
* @block: block address in BufferRAM
* @blockpage: block & page address in BufferRAM
* @page: page address in BufferRAM
* @valid: valid flag
*/
*/
struct
onenand_bufferram
{
struct
onenand_bufferram
{
int
block
;
int
blockpage
;
int
page
;
int
valid
;
};
};
/**
/**
...
@@ -63,7 +59,6 @@ struct onenand_bufferram {
...
@@ -63,7 +59,6 @@ struct onenand_bufferram {
* partly be set to inform onenand_scan about
* partly be set to inform onenand_scan about
* @erase_shift: [INTERN] number of address bits in a block
* @erase_shift: [INTERN] number of address bits in a block
* @page_shift: [INTERN] number of address bits in a page
* @page_shift: [INTERN] number of address bits in a page
* @ppb_shift: [INTERN] number of address bits in a pages per block
* @page_mask: [INTERN] a page per block mask
* @page_mask: [INTERN] a page per block mask
* @bufferram_index: [INTERN] BufferRAM index
* @bufferram_index: [INTERN] BufferRAM index
* @bufferram: [INTERN] BufferRAM info
* @bufferram: [INTERN] BufferRAM info
...
@@ -103,7 +98,6 @@ struct onenand_chip {
...
@@ -103,7 +98,6 @@ struct onenand_chip {
unsigned
int
erase_shift
;
unsigned
int
erase_shift
;
unsigned
int
page_shift
;
unsigned
int
page_shift
;
unsigned
int
ppb_shift
;
/* Pages per block shift */
unsigned
int
page_mask
;
unsigned
int
page_mask
;
unsigned
int
bufferram_index
;
unsigned
int
bufferram_index
;
...
@@ -150,6 +144,9 @@ struct onenand_chip {
...
@@ -150,6 +144,9 @@ struct onenand_chip {
#define ONENAND_SET_SYS_CFG1(v, this) \
#define ONENAND_SET_SYS_CFG1(v, this) \
(this->write_word(v, this->base + ONENAND_REG_SYS_CFG1))
(this->write_word(v, this->base + ONENAND_REG_SYS_CFG1))
#define ONENAND_IS_DDP(this) \
(this->device_id & ONENAND_DEVICE_IS_DDP)
/* Check byte access in OneNAND */
/* Check byte access in OneNAND */
#define ONENAND_CHECK_BYTE_ACCESS(addr) (addr & 0x1)
#define ONENAND_CHECK_BYTE_ACCESS(addr) (addr & 0x1)
...
...
include/linux/mtd/onenand_regs.h
View file @
c1f16258
...
@@ -3,7 +3,8 @@
...
@@ -3,7 +3,8 @@
*
*
* OneNAND Register header file
* OneNAND Register header file
*
*
* Copyright (C) 2005-2006 Samsung Electronics
* Copyright (C) 2005-2007 Samsung Electronics
* Kyungmin Park <kyungmin.park@samsung.com>
*
*
* This program is free software; you can redistribute it and/or modify
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* it under the terms of the GNU General Public License version 2 as
...
@@ -80,9 +81,11 @@
...
@@ -80,9 +81,11 @@
#define ONENAND_VERSION_PROCESS_SHIFT (8)
#define ONENAND_VERSION_PROCESS_SHIFT (8)
/*
/*
* Start Address 1 F100h (R/W)
* Start Address 1 F100h (R/W)
& Start Address 2 F101h (R/W)
*/
*/
#define ONENAND_DDP_SHIFT (15)
#define ONENAND_DDP_SHIFT (15)
#define ONENAND_DDP_CHIP0 (0)
#define ONENAND_DDP_CHIP1 (1 << ONENAND_DDP_SHIFT)
/*
/*
* Start Address 8 F107h (R/W)
* Start Address 8 F107h (R/W)
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment