Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
L
linux
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
linux
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 @@ int onenand_do_read_oob(struct mtd_info *mtd, loff_t from, size_t len,
...
@@ -885,10 +931,132 @@ int onenand_do_read_oob(struct mtd_info *mtd, loff_t from, size_t len,
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 @@ static int onenand_do_write_oob(struct mtd_info *mtd, loff_t to, size_t len,
...
@@ -1153,10 +1384,17 @@ static int onenand_do_write_oob(struct mtd_info *mtd, loff_t to, size_t len,
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