You've already forked linux-apfs
mirror of
https://github.com/linux-apfs/linux-apfs.git
synced 2026-05-01 15:00:59 -07:00
Merge tag 'for-linus-20130301' of git://git.infradead.org/linux-mtd
Pull MTD update from David Woodhouse:
"Fairly unexciting MTD merge for 3.9:
- misc clean-ups in the MTD command-line partitioning parser
(cmdlinepart)
- add flash locking support for STmicro chips serial flash chips, as
well as for CFI command set 2 chips.
- new driver for the ELM error correction HW module found in various
TI chips, enable the OMAP NAND driver to use the ELM HW error
correction
- added number of new serial flash IDs
- various fixes and improvements in the gpmi NAND driver
- bcm47xx NAND driver improvements
- make the mtdpart module actually removable"
* tag 'for-linus-20130301' of git://git.infradead.org/linux-mtd: (45 commits)
mtd: map: BUG() in non handled cases
mtd: bcm47xxnflash: use pr_fmt for module prefix in messages
mtd: davinci_nand: Use managed resources
mtd: mtd_torturetest can cause stack overflows
mtd: physmap_of: Convert device allocation to managed devm_kzalloc()
mtd: at91: atmel_nand: for PMECC, add code to check the ONFI parameter ECC requirement.
mtd: atmel_nand: make pmecc-cap, pmecc-sector-size in dts is optional.
mtd: atmel_nand: avoid to report an error when lookup table offset is 0.
mtd: bcm47xxsflash: adjust names of bus-specific functions
mtd: bcm47xxpart: improve probing of nvram partition
mtd: bcm47xxpart: add support for other erase sizes
mtd: bcm47xxnflash: register this as normal driver
mtd: bcm47xxnflash: fix message
mtd: bcm47xxsflash: register this as normal driver
mtd: bcm47xxsflash: write number of written bytes
mtd: gpmi: add sanity check for the ECC
mtd: gpmi: set the Golois Field bit for mx6q's BCH
mtd: devices: elm: Removes <xx> literals in elm DT node
mtd: gpmi: fix a dereferencing freed memory error
mtd: fix the wrong timeo for panic_nand_wait()
...
This commit is contained in:
@@ -0,0 +1,16 @@
|
|||||||
|
Error location module
|
||||||
|
|
||||||
|
Required properties:
|
||||||
|
- compatible: Must be "ti,am33xx-elm"
|
||||||
|
- reg: physical base address and size of the registers map.
|
||||||
|
- interrupts: Interrupt number for the elm.
|
||||||
|
|
||||||
|
Optional properties:
|
||||||
|
- ti,hwmods: Name of the hwmod associated to the elm
|
||||||
|
|
||||||
|
Example:
|
||||||
|
elm: elm@0 {
|
||||||
|
compatible = "ti,am3352-elm";
|
||||||
|
reg = <0x48080000 0x2000>;
|
||||||
|
interrupts = <4>;
|
||||||
|
};
|
||||||
@@ -26,6 +26,9 @@ file systems on embedded devices.
|
|||||||
- linux,mtd-name: allow to specify the mtd name for retro capability with
|
- linux,mtd-name: allow to specify the mtd name for retro capability with
|
||||||
physmap-flash drivers as boot loader pass the mtd partition via the old
|
physmap-flash drivers as boot loader pass the mtd partition via the old
|
||||||
device name physmap-flash.
|
device name physmap-flash.
|
||||||
|
- use-advanced-sector-protection: boolean to enable support for the
|
||||||
|
advanced sector protection (Spansion: PPB - Persistent Protection
|
||||||
|
Bits) locking.
|
||||||
|
|
||||||
For JEDEC compatible devices, the following additional properties
|
For JEDEC compatible devices, the following additional properties
|
||||||
are defined:
|
are defined:
|
||||||
|
|||||||
+2
-2
@@ -74,8 +74,8 @@ config MTD_REDBOOT_PARTS_READONLY
|
|||||||
endif # MTD_REDBOOT_PARTS
|
endif # MTD_REDBOOT_PARTS
|
||||||
|
|
||||||
config MTD_CMDLINE_PARTS
|
config MTD_CMDLINE_PARTS
|
||||||
bool "Command line partition table parsing"
|
tristate "Command line partition table parsing"
|
||||||
depends on MTD = "y"
|
depends on MTD
|
||||||
---help---
|
---help---
|
||||||
Allow generic configuration of the MTD partition tables via the kernel
|
Allow generic configuration of the MTD partition tables via the kernel
|
||||||
command line. Multiple flash resources are supported for hardware where
|
command line. Multiple flash resources are supported for hardware where
|
||||||
|
|||||||
@@ -142,7 +142,13 @@ static int __init ar7_parser_init(void)
|
|||||||
return register_mtd_parser(&ar7_parser);
|
return register_mtd_parser(&ar7_parser);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void __exit ar7_parser_exit(void)
|
||||||
|
{
|
||||||
|
deregister_mtd_parser(&ar7_parser);
|
||||||
|
}
|
||||||
|
|
||||||
module_init(ar7_parser_init);
|
module_init(ar7_parser_init);
|
||||||
|
module_exit(ar7_parser_exit);
|
||||||
|
|
||||||
MODULE_LICENSE("GPL");
|
MODULE_LICENSE("GPL");
|
||||||
MODULE_AUTHOR( "Felix Fietkau <nbd@openwrt.org>, "
|
MODULE_AUTHOR( "Felix Fietkau <nbd@openwrt.org>, "
|
||||||
|
|||||||
+34
-15
@@ -19,12 +19,6 @@
|
|||||||
/* 10 parts were found on sflash on Netgear WNDR4500 */
|
/* 10 parts were found on sflash on Netgear WNDR4500 */
|
||||||
#define BCM47XXPART_MAX_PARTS 12
|
#define BCM47XXPART_MAX_PARTS 12
|
||||||
|
|
||||||
/*
|
|
||||||
* Amount of bytes we read when analyzing each block of flash memory.
|
|
||||||
* Set it big enough to allow detecting partition and reading important data.
|
|
||||||
*/
|
|
||||||
#define BCM47XXPART_BYTES_TO_READ 0x404
|
|
||||||
|
|
||||||
/* Magics */
|
/* Magics */
|
||||||
#define BOARD_DATA_MAGIC 0x5246504D /* MPFR */
|
#define BOARD_DATA_MAGIC 0x5246504D /* MPFR */
|
||||||
#define POT_MAGIC1 0x54544f50 /* POTT */
|
#define POT_MAGIC1 0x54544f50 /* POTT */
|
||||||
@@ -59,13 +53,21 @@ static int bcm47xxpart_parse(struct mtd_info *master,
|
|||||||
uint32_t *buf;
|
uint32_t *buf;
|
||||||
size_t bytes_read;
|
size_t bytes_read;
|
||||||
uint32_t offset;
|
uint32_t offset;
|
||||||
uint32_t blocksize = 0x10000;
|
uint32_t blocksize = master->erasesize;
|
||||||
struct trx_header *trx;
|
struct trx_header *trx;
|
||||||
|
int trx_part = -1;
|
||||||
|
int last_trx_part = -1;
|
||||||
|
int max_bytes_to_read = 0x8004;
|
||||||
|
|
||||||
|
if (blocksize <= 0x10000)
|
||||||
|
blocksize = 0x10000;
|
||||||
|
if (blocksize == 0x20000)
|
||||||
|
max_bytes_to_read = 0x18004;
|
||||||
|
|
||||||
/* Alloc */
|
/* Alloc */
|
||||||
parts = kzalloc(sizeof(struct mtd_partition) * BCM47XXPART_MAX_PARTS,
|
parts = kzalloc(sizeof(struct mtd_partition) * BCM47XXPART_MAX_PARTS,
|
||||||
GFP_KERNEL);
|
GFP_KERNEL);
|
||||||
buf = kzalloc(BCM47XXPART_BYTES_TO_READ, GFP_KERNEL);
|
buf = kzalloc(max_bytes_to_read, GFP_KERNEL);
|
||||||
|
|
||||||
/* Parse block by block looking for magics */
|
/* Parse block by block looking for magics */
|
||||||
for (offset = 0; offset <= master->size - blocksize;
|
for (offset = 0; offset <= master->size - blocksize;
|
||||||
@@ -80,7 +82,7 @@ static int bcm47xxpart_parse(struct mtd_info *master,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Read beginning of the block */
|
/* Read beginning of the block */
|
||||||
if (mtd_read(master, offset, BCM47XXPART_BYTES_TO_READ,
|
if (mtd_read(master, offset, max_bytes_to_read,
|
||||||
&bytes_read, (uint8_t *)buf) < 0) {
|
&bytes_read, (uint8_t *)buf) < 0) {
|
||||||
pr_err("mtd_read error while parsing (offset: 0x%X)!\n",
|
pr_err("mtd_read error while parsing (offset: 0x%X)!\n",
|
||||||
offset);
|
offset);
|
||||||
@@ -95,9 +97,16 @@ static int bcm47xxpart_parse(struct mtd_info *master,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Standard NVRAM */
|
/* Standard NVRAM */
|
||||||
if (buf[0x000 / 4] == NVRAM_HEADER) {
|
if (buf[0x000 / 4] == NVRAM_HEADER ||
|
||||||
|
buf[0x1000 / 4] == NVRAM_HEADER ||
|
||||||
|
buf[0x8000 / 4] == NVRAM_HEADER ||
|
||||||
|
(blocksize == 0x20000 && (
|
||||||
|
buf[0x10000 / 4] == NVRAM_HEADER ||
|
||||||
|
buf[0x11000 / 4] == NVRAM_HEADER ||
|
||||||
|
buf[0x18000 / 4] == NVRAM_HEADER))) {
|
||||||
bcm47xxpart_add_part(&parts[curr_part++], "nvram",
|
bcm47xxpart_add_part(&parts[curr_part++], "nvram",
|
||||||
offset, 0);
|
offset, 0);
|
||||||
|
offset = rounddown(offset, blocksize);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,6 +140,10 @@ static int bcm47xxpart_parse(struct mtd_info *master,
|
|||||||
if (buf[0x000 / 4] == TRX_MAGIC) {
|
if (buf[0x000 / 4] == TRX_MAGIC) {
|
||||||
trx = (struct trx_header *)buf;
|
trx = (struct trx_header *)buf;
|
||||||
|
|
||||||
|
trx_part = curr_part;
|
||||||
|
bcm47xxpart_add_part(&parts[curr_part++], "firmware",
|
||||||
|
offset, 0);
|
||||||
|
|
||||||
i = 0;
|
i = 0;
|
||||||
/* We have LZMA loader if offset[2] points to sth */
|
/* We have LZMA loader if offset[2] points to sth */
|
||||||
if (trx->offset[2]) {
|
if (trx->offset[2]) {
|
||||||
@@ -154,6 +167,8 @@ static int bcm47xxpart_parse(struct mtd_info *master,
|
|||||||
offset + trx->offset[i], 0);
|
offset + trx->offset[i], 0);
|
||||||
i++;
|
i++;
|
||||||
|
|
||||||
|
last_trx_part = curr_part - 1;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* We have whole TRX scanned, skip to the next part. Use
|
* We have whole TRX scanned, skip to the next part. Use
|
||||||
* roundown (not roundup), as the loop will increase
|
* roundown (not roundup), as the loop will increase
|
||||||
@@ -169,11 +184,15 @@ static int bcm47xxpart_parse(struct mtd_info *master,
|
|||||||
* Assume that partitions end at the beginning of the one they are
|
* Assume that partitions end at the beginning of the one they are
|
||||||
* followed by.
|
* followed by.
|
||||||
*/
|
*/
|
||||||
for (i = 0; i < curr_part - 1; i++)
|
for (i = 0; i < curr_part; i++) {
|
||||||
parts[i].size = parts[i + 1].offset - parts[i].offset;
|
u64 next_part_offset = (i < curr_part - 1) ?
|
||||||
if (curr_part > 0)
|
parts[i + 1].offset : master->size;
|
||||||
parts[curr_part - 1].size =
|
|
||||||
master->size - parts[curr_part - 1].offset;
|
parts[i].size = next_part_offset - parts[i].offset;
|
||||||
|
if (i == last_trx_part && trx_part >= 0)
|
||||||
|
parts[trx_part].size = next_part_offset -
|
||||||
|
parts[trx_part].offset;
|
||||||
|
}
|
||||||
|
|
||||||
*pparts = parts;
|
*pparts = parts;
|
||||||
return curr_part;
|
return curr_part;
|
||||||
|
|||||||
@@ -33,6 +33,8 @@
|
|||||||
#include <linux/delay.h>
|
#include <linux/delay.h>
|
||||||
#include <linux/interrupt.h>
|
#include <linux/interrupt.h>
|
||||||
#include <linux/reboot.h>
|
#include <linux/reboot.h>
|
||||||
|
#include <linux/of.h>
|
||||||
|
#include <linux/of_platform.h>
|
||||||
#include <linux/mtd/map.h>
|
#include <linux/mtd/map.h>
|
||||||
#include <linux/mtd/mtd.h>
|
#include <linux/mtd/mtd.h>
|
||||||
#include <linux/mtd/cfi.h>
|
#include <linux/mtd/cfi.h>
|
||||||
@@ -74,6 +76,10 @@ static void put_chip(struct map_info *map, struct flchip *chip, unsigned long ad
|
|||||||
static int cfi_atmel_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len);
|
static int cfi_atmel_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len);
|
||||||
static int cfi_atmel_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len);
|
static int cfi_atmel_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len);
|
||||||
|
|
||||||
|
static int cfi_ppb_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len);
|
||||||
|
static int cfi_ppb_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len);
|
||||||
|
static int cfi_ppb_is_locked(struct mtd_info *mtd, loff_t ofs, uint64_t len);
|
||||||
|
|
||||||
static struct mtd_chip_driver cfi_amdstd_chipdrv = {
|
static struct mtd_chip_driver cfi_amdstd_chipdrv = {
|
||||||
.probe = NULL, /* Not usable directly */
|
.probe = NULL, /* Not usable directly */
|
||||||
.destroy = cfi_amdstd_destroy,
|
.destroy = cfi_amdstd_destroy,
|
||||||
@@ -496,6 +502,7 @@ static void cfi_fixup_m29ew_delay_after_resume(struct cfi_private *cfi)
|
|||||||
struct mtd_info *cfi_cmdset_0002(struct map_info *map, int primary)
|
struct mtd_info *cfi_cmdset_0002(struct map_info *map, int primary)
|
||||||
{
|
{
|
||||||
struct cfi_private *cfi = map->fldrv_priv;
|
struct cfi_private *cfi = map->fldrv_priv;
|
||||||
|
struct device_node __maybe_unused *np = map->device_node;
|
||||||
struct mtd_info *mtd;
|
struct mtd_info *mtd;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
@@ -570,6 +577,17 @@ struct mtd_info *cfi_cmdset_0002(struct map_info *map, int primary)
|
|||||||
cfi_tell_features(extp);
|
cfi_tell_features(extp);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef CONFIG_OF
|
||||||
|
if (np && of_property_read_bool(
|
||||||
|
np, "use-advanced-sector-protection")
|
||||||
|
&& extp->BlkProtUnprot == 8) {
|
||||||
|
printk(KERN_INFO " Advanced Sector Protection (PPB Locking) supported\n");
|
||||||
|
mtd->_lock = cfi_ppb_lock;
|
||||||
|
mtd->_unlock = cfi_ppb_unlock;
|
||||||
|
mtd->_is_locked = cfi_ppb_is_locked;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
bootloc = extp->TopBottom;
|
bootloc = extp->TopBottom;
|
||||||
if ((bootloc < 2) || (bootloc > 5)) {
|
if ((bootloc < 2) || (bootloc > 5)) {
|
||||||
printk(KERN_WARNING "%s: CFI contains unrecognised boot "
|
printk(KERN_WARNING "%s: CFI contains unrecognised boot "
|
||||||
@@ -2172,6 +2190,205 @@ static int cfi_atmel_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
|
|||||||
return cfi_varsize_frob(mtd, do_atmel_unlock, ofs, len, NULL);
|
return cfi_varsize_frob(mtd, do_atmel_unlock, ofs, len, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Advanced Sector Protection - PPB (Persistent Protection Bit) locking
|
||||||
|
*/
|
||||||
|
|
||||||
|
struct ppb_lock {
|
||||||
|
struct flchip *chip;
|
||||||
|
loff_t offset;
|
||||||
|
int locked;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define MAX_SECTORS 512
|
||||||
|
|
||||||
|
#define DO_XXLOCK_ONEBLOCK_LOCK ((void *)1)
|
||||||
|
#define DO_XXLOCK_ONEBLOCK_UNLOCK ((void *)2)
|
||||||
|
#define DO_XXLOCK_ONEBLOCK_GETLOCK ((void *)3)
|
||||||
|
|
||||||
|
static int __maybe_unused do_ppb_xxlock(struct map_info *map,
|
||||||
|
struct flchip *chip,
|
||||||
|
unsigned long adr, int len, void *thunk)
|
||||||
|
{
|
||||||
|
struct cfi_private *cfi = map->fldrv_priv;
|
||||||
|
unsigned long timeo;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
mutex_lock(&chip->mutex);
|
||||||
|
ret = get_chip(map, chip, adr + chip->start, FL_LOCKING);
|
||||||
|
if (ret) {
|
||||||
|
mutex_unlock(&chip->mutex);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
pr_debug("MTD %s(): XXLOCK 0x%08lx len %d\n", __func__, adr, len);
|
||||||
|
|
||||||
|
cfi_send_gen_cmd(0xAA, cfi->addr_unlock1, chip->start, map, cfi,
|
||||||
|
cfi->device_type, NULL);
|
||||||
|
cfi_send_gen_cmd(0x55, cfi->addr_unlock2, chip->start, map, cfi,
|
||||||
|
cfi->device_type, NULL);
|
||||||
|
/* PPB entry command */
|
||||||
|
cfi_send_gen_cmd(0xC0, cfi->addr_unlock1, chip->start, map, cfi,
|
||||||
|
cfi->device_type, NULL);
|
||||||
|
|
||||||
|
if (thunk == DO_XXLOCK_ONEBLOCK_LOCK) {
|
||||||
|
chip->state = FL_LOCKING;
|
||||||
|
map_write(map, CMD(0xA0), chip->start + adr);
|
||||||
|
map_write(map, CMD(0x00), chip->start + adr);
|
||||||
|
} else if (thunk == DO_XXLOCK_ONEBLOCK_UNLOCK) {
|
||||||
|
/*
|
||||||
|
* Unlocking of one specific sector is not supported, so we
|
||||||
|
* have to unlock all sectors of this device instead
|
||||||
|
*/
|
||||||
|
chip->state = FL_UNLOCKING;
|
||||||
|
map_write(map, CMD(0x80), chip->start);
|
||||||
|
map_write(map, CMD(0x30), chip->start);
|
||||||
|
} else if (thunk == DO_XXLOCK_ONEBLOCK_GETLOCK) {
|
||||||
|
chip->state = FL_JEDEC_QUERY;
|
||||||
|
/* Return locked status: 0->locked, 1->unlocked */
|
||||||
|
ret = !cfi_read_query(map, adr);
|
||||||
|
} else
|
||||||
|
BUG();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Wait for some time as unlocking of all sectors takes quite long
|
||||||
|
*/
|
||||||
|
timeo = jiffies + msecs_to_jiffies(2000); /* 2s max (un)locking */
|
||||||
|
for (;;) {
|
||||||
|
if (chip_ready(map, adr))
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (time_after(jiffies, timeo)) {
|
||||||
|
printk(KERN_ERR "Waiting for chip to be ready timed out.\n");
|
||||||
|
ret = -EIO;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
UDELAY(map, chip, adr, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Exit BC commands */
|
||||||
|
map_write(map, CMD(0x90), chip->start);
|
||||||
|
map_write(map, CMD(0x00), chip->start);
|
||||||
|
|
||||||
|
chip->state = FL_READY;
|
||||||
|
put_chip(map, chip, adr + chip->start);
|
||||||
|
mutex_unlock(&chip->mutex);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int __maybe_unused cfi_ppb_lock(struct mtd_info *mtd, loff_t ofs,
|
||||||
|
uint64_t len)
|
||||||
|
{
|
||||||
|
return cfi_varsize_frob(mtd, do_ppb_xxlock, ofs, len,
|
||||||
|
DO_XXLOCK_ONEBLOCK_LOCK);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int __maybe_unused cfi_ppb_unlock(struct mtd_info *mtd, loff_t ofs,
|
||||||
|
uint64_t len)
|
||||||
|
{
|
||||||
|
struct mtd_erase_region_info *regions = mtd->eraseregions;
|
||||||
|
struct map_info *map = mtd->priv;
|
||||||
|
struct cfi_private *cfi = map->fldrv_priv;
|
||||||
|
struct ppb_lock *sect;
|
||||||
|
unsigned long adr;
|
||||||
|
loff_t offset;
|
||||||
|
uint64_t length;
|
||||||
|
int chipnum;
|
||||||
|
int i;
|
||||||
|
int sectors;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* PPB unlocking always unlocks all sectors of the flash chip.
|
||||||
|
* We need to re-lock all previously locked sectors. So lets
|
||||||
|
* first check the locking status of all sectors and save
|
||||||
|
* it for future use.
|
||||||
|
*/
|
||||||
|
sect = kzalloc(MAX_SECTORS * sizeof(struct ppb_lock), GFP_KERNEL);
|
||||||
|
if (!sect)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This code to walk all sectors is a slightly modified version
|
||||||
|
* of the cfi_varsize_frob() code.
|
||||||
|
*/
|
||||||
|
i = 0;
|
||||||
|
chipnum = 0;
|
||||||
|
adr = 0;
|
||||||
|
sectors = 0;
|
||||||
|
offset = 0;
|
||||||
|
length = mtd->size;
|
||||||
|
|
||||||
|
while (length) {
|
||||||
|
int size = regions[i].erasesize;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Only test sectors that shall not be unlocked. The other
|
||||||
|
* sectors shall be unlocked, so lets keep their locking
|
||||||
|
* status at "unlocked" (locked=0) for the final re-locking.
|
||||||
|
*/
|
||||||
|
if ((adr < ofs) || (adr >= (ofs + len))) {
|
||||||
|
sect[sectors].chip = &cfi->chips[chipnum];
|
||||||
|
sect[sectors].offset = offset;
|
||||||
|
sect[sectors].locked = do_ppb_xxlock(
|
||||||
|
map, &cfi->chips[chipnum], adr, 0,
|
||||||
|
DO_XXLOCK_ONEBLOCK_GETLOCK);
|
||||||
|
}
|
||||||
|
|
||||||
|
adr += size;
|
||||||
|
offset += size;
|
||||||
|
length -= size;
|
||||||
|
|
||||||
|
if (offset == regions[i].offset + size * regions[i].numblocks)
|
||||||
|
i++;
|
||||||
|
|
||||||
|
if (adr >> cfi->chipshift) {
|
||||||
|
adr = 0;
|
||||||
|
chipnum++;
|
||||||
|
|
||||||
|
if (chipnum >= cfi->numchips)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
sectors++;
|
||||||
|
if (sectors >= MAX_SECTORS) {
|
||||||
|
printk(KERN_ERR "Only %d sectors for PPB locking supported!\n",
|
||||||
|
MAX_SECTORS);
|
||||||
|
kfree(sect);
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Now unlock the whole chip */
|
||||||
|
ret = cfi_varsize_frob(mtd, do_ppb_xxlock, ofs, len,
|
||||||
|
DO_XXLOCK_ONEBLOCK_UNLOCK);
|
||||||
|
if (ret) {
|
||||||
|
kfree(sect);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* PPB unlocking always unlocks all sectors of the flash chip.
|
||||||
|
* We need to re-lock all previously locked sectors.
|
||||||
|
*/
|
||||||
|
for (i = 0; i < sectors; i++) {
|
||||||
|
if (sect[i].locked)
|
||||||
|
do_ppb_xxlock(map, sect[i].chip, sect[i].offset, 0,
|
||||||
|
DO_XXLOCK_ONEBLOCK_LOCK);
|
||||||
|
}
|
||||||
|
|
||||||
|
kfree(sect);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int __maybe_unused cfi_ppb_is_locked(struct mtd_info *mtd, loff_t ofs,
|
||||||
|
uint64_t len)
|
||||||
|
{
|
||||||
|
return cfi_varsize_frob(mtd, do_ppb_xxlock, ofs, len,
|
||||||
|
DO_XXLOCK_ONEBLOCK_GETLOCK) ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
static void cfi_amdstd_sync (struct mtd_info *mtd)
|
static void cfi_amdstd_sync (struct mtd_info *mtd)
|
||||||
{
|
{
|
||||||
|
|||||||
+36
-13
@@ -22,11 +22,22 @@
|
|||||||
*
|
*
|
||||||
* mtdparts=<mtddef>[;<mtddef]
|
* mtdparts=<mtddef>[;<mtddef]
|
||||||
* <mtddef> := <mtd-id>:<partdef>[,<partdef>]
|
* <mtddef> := <mtd-id>:<partdef>[,<partdef>]
|
||||||
* where <mtd-id> is the name from the "cat /proc/mtd" command
|
* <partdef> := <size>[@<offset>][<name>][ro][lk]
|
||||||
* <partdef> := <size>[@offset][<name>][ro][lk]
|
|
||||||
* <mtd-id> := unique name used in mapping driver/device (mtd->name)
|
* <mtd-id> := unique name used in mapping driver/device (mtd->name)
|
||||||
* <size> := standard linux memsize OR "-" to denote all remaining space
|
* <size> := standard linux memsize OR "-" to denote all remaining space
|
||||||
|
* size is automatically truncated at end of device
|
||||||
|
* if specified or trucated size is 0 the part is skipped
|
||||||
|
* <offset> := standard linux memsize
|
||||||
|
* if omitted the part will immediately follow the previous part
|
||||||
|
* or 0 if the first part
|
||||||
* <name> := '(' NAME ')'
|
* <name> := '(' NAME ')'
|
||||||
|
* NAME will appear in /proc/mtd
|
||||||
|
*
|
||||||
|
* <size> and <offset> can be specified such that the parts are out of order
|
||||||
|
* in physical memory and may even overlap.
|
||||||
|
*
|
||||||
|
* The parts are assigned MTD numbers in the order they are specified in the
|
||||||
|
* command line regardless of their order in physical memory.
|
||||||
*
|
*
|
||||||
* Examples:
|
* Examples:
|
||||||
*
|
*
|
||||||
@@ -70,6 +81,7 @@ struct cmdline_mtd_partition {
|
|||||||
static struct cmdline_mtd_partition *partitions;
|
static struct cmdline_mtd_partition *partitions;
|
||||||
|
|
||||||
/* the command line passed to mtdpart_setup() */
|
/* the command line passed to mtdpart_setup() */
|
||||||
|
static char *mtdparts;
|
||||||
static char *cmdline;
|
static char *cmdline;
|
||||||
static int cmdline_parsed;
|
static int cmdline_parsed;
|
||||||
|
|
||||||
@@ -330,16 +342,6 @@ static int parse_cmdline_partitions(struct mtd_info *master,
|
|||||||
if (part->parts[i].size == SIZE_REMAINING)
|
if (part->parts[i].size == SIZE_REMAINING)
|
||||||
part->parts[i].size = master->size - offset;
|
part->parts[i].size = master->size - offset;
|
||||||
|
|
||||||
if (part->parts[i].size == 0) {
|
|
||||||
printk(KERN_WARNING ERRP
|
|
||||||
"%s: skipping zero sized partition\n",
|
|
||||||
part->mtd_id);
|
|
||||||
part->num_parts--;
|
|
||||||
memmove(&part->parts[i], &part->parts[i + 1],
|
|
||||||
sizeof(*part->parts) * (part->num_parts - i));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (offset + part->parts[i].size > master->size) {
|
if (offset + part->parts[i].size > master->size) {
|
||||||
printk(KERN_WARNING ERRP
|
printk(KERN_WARNING ERRP
|
||||||
"%s: partitioning exceeds flash size, truncating\n",
|
"%s: partitioning exceeds flash size, truncating\n",
|
||||||
@@ -347,6 +349,16 @@ static int parse_cmdline_partitions(struct mtd_info *master,
|
|||||||
part->parts[i].size = master->size - offset;
|
part->parts[i].size = master->size - offset;
|
||||||
}
|
}
|
||||||
offset += part->parts[i].size;
|
offset += part->parts[i].size;
|
||||||
|
|
||||||
|
if (part->parts[i].size == 0) {
|
||||||
|
printk(KERN_WARNING ERRP
|
||||||
|
"%s: skipping zero sized partition\n",
|
||||||
|
part->mtd_id);
|
||||||
|
part->num_parts--;
|
||||||
|
memmove(&part->parts[i], &part->parts[i + 1],
|
||||||
|
sizeof(*part->parts) * (part->num_parts - i));
|
||||||
|
i--;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
*pparts = kmemdup(part->parts, sizeof(*part->parts) * part->num_parts,
|
*pparts = kmemdup(part->parts, sizeof(*part->parts) * part->num_parts,
|
||||||
@@ -365,7 +377,7 @@ static int parse_cmdline_partitions(struct mtd_info *master,
|
|||||||
*
|
*
|
||||||
* This function needs to be visible for bootloaders.
|
* This function needs to be visible for bootloaders.
|
||||||
*/
|
*/
|
||||||
static int mtdpart_setup(char *s)
|
static int __init mtdpart_setup(char *s)
|
||||||
{
|
{
|
||||||
cmdline = s;
|
cmdline = s;
|
||||||
return 1;
|
return 1;
|
||||||
@@ -381,10 +393,21 @@ static struct mtd_part_parser cmdline_parser = {
|
|||||||
|
|
||||||
static int __init cmdline_parser_init(void)
|
static int __init cmdline_parser_init(void)
|
||||||
{
|
{
|
||||||
|
if (mtdparts)
|
||||||
|
mtdpart_setup(mtdparts);
|
||||||
return register_mtd_parser(&cmdline_parser);
|
return register_mtd_parser(&cmdline_parser);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void __exit cmdline_parser_exit(void)
|
||||||
|
{
|
||||||
|
deregister_mtd_parser(&cmdline_parser);
|
||||||
|
}
|
||||||
|
|
||||||
module_init(cmdline_parser_init);
|
module_init(cmdline_parser_init);
|
||||||
|
module_exit(cmdline_parser_exit);
|
||||||
|
|
||||||
|
MODULE_PARM_DESC(mtdparts, "Partitioning specification");
|
||||||
|
module_param(mtdparts, charp, 0);
|
||||||
|
|
||||||
MODULE_LICENSE("GPL");
|
MODULE_LICENSE("GPL");
|
||||||
MODULE_AUTHOR("Marius Groeger <mag@sysgo.de>");
|
MODULE_AUTHOR("Marius Groeger <mag@sysgo.de>");
|
||||||
|
|||||||
@@ -17,8 +17,10 @@ obj-$(CONFIG_MTD_LART) += lart.o
|
|||||||
obj-$(CONFIG_MTD_BLOCK2MTD) += block2mtd.o
|
obj-$(CONFIG_MTD_BLOCK2MTD) += block2mtd.o
|
||||||
obj-$(CONFIG_MTD_DATAFLASH) += mtd_dataflash.o
|
obj-$(CONFIG_MTD_DATAFLASH) += mtd_dataflash.o
|
||||||
obj-$(CONFIG_MTD_M25P80) += m25p80.o
|
obj-$(CONFIG_MTD_M25P80) += m25p80.o
|
||||||
|
obj-$(CONFIG_MTD_NAND_OMAP_BCH) += elm.o
|
||||||
obj-$(CONFIG_MTD_SPEAR_SMI) += spear_smi.o
|
obj-$(CONFIG_MTD_SPEAR_SMI) += spear_smi.o
|
||||||
obj-$(CONFIG_MTD_SST25L) += sst25l.o
|
obj-$(CONFIG_MTD_SST25L) += sst25l.o
|
||||||
obj-$(CONFIG_MTD_BCM47XXSFLASH) += bcm47xxsflash.o
|
obj-$(CONFIG_MTD_BCM47XXSFLASH) += bcm47xxsflash.o
|
||||||
|
|
||||||
CFLAGS_docg3.o += -I$(src)
|
|
||||||
|
CFLAGS_docg3.o += -I$(src)
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
#include <linux/platform_device.h>
|
#include <linux/platform_device.h>
|
||||||
#include <linux/bcma/bcma.h>
|
#include <linux/bcma/bcma.h>
|
||||||
|
|
||||||
|
#include "bcm47xxsflash.h"
|
||||||
|
|
||||||
MODULE_LICENSE("GPL");
|
MODULE_LICENSE("GPL");
|
||||||
MODULE_DESCRIPTION("Serial flash driver for BCMA bus");
|
MODULE_DESCRIPTION("Serial flash driver for BCMA bus");
|
||||||
|
|
||||||
@@ -13,26 +15,28 @@ static const char *probes[] = { "bcm47xxpart", NULL };
|
|||||||
static int bcm47xxsflash_read(struct mtd_info *mtd, loff_t from, size_t len,
|
static int bcm47xxsflash_read(struct mtd_info *mtd, loff_t from, size_t len,
|
||||||
size_t *retlen, u_char *buf)
|
size_t *retlen, u_char *buf)
|
||||||
{
|
{
|
||||||
struct bcma_sflash *sflash = mtd->priv;
|
struct bcm47xxsflash *b47s = mtd->priv;
|
||||||
|
|
||||||
/* Check address range */
|
/* Check address range */
|
||||||
if ((from + len) > mtd->size)
|
if ((from + len) > mtd->size)
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
|
|
||||||
memcpy_fromio(buf, (void __iomem *)KSEG0ADDR(sflash->window + from),
|
memcpy_fromio(buf, (void __iomem *)KSEG0ADDR(b47s->window + from),
|
||||||
len);
|
len);
|
||||||
|
*retlen = len;
|
||||||
|
|
||||||
return len;
|
return len;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void bcm47xxsflash_fill_mtd(struct bcma_sflash *sflash,
|
static void bcm47xxsflash_fill_mtd(struct bcm47xxsflash *b47s)
|
||||||
struct mtd_info *mtd)
|
|
||||||
{
|
{
|
||||||
mtd->priv = sflash;
|
struct mtd_info *mtd = &b47s->mtd;
|
||||||
|
|
||||||
|
mtd->priv = b47s;
|
||||||
mtd->name = "bcm47xxsflash";
|
mtd->name = "bcm47xxsflash";
|
||||||
mtd->owner = THIS_MODULE;
|
mtd->owner = THIS_MODULE;
|
||||||
mtd->type = MTD_ROM;
|
mtd->type = MTD_ROM;
|
||||||
mtd->size = sflash->size;
|
mtd->size = b47s->size;
|
||||||
mtd->_read = bcm47xxsflash_read;
|
mtd->_read = bcm47xxsflash_read;
|
||||||
|
|
||||||
/* TODO: implement writing support and verify/change following code */
|
/* TODO: implement writing support and verify/change following code */
|
||||||
@@ -40,19 +44,30 @@ static void bcm47xxsflash_fill_mtd(struct bcma_sflash *sflash,
|
|||||||
mtd->writebufsize = mtd->writesize = 1;
|
mtd->writebufsize = mtd->writesize = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int bcm47xxsflash_probe(struct platform_device *pdev)
|
/**************************************************
|
||||||
|
* BCMA
|
||||||
|
**************************************************/
|
||||||
|
|
||||||
|
static int bcm47xxsflash_bcma_probe(struct platform_device *pdev)
|
||||||
{
|
{
|
||||||
struct bcma_sflash *sflash = dev_get_platdata(&pdev->dev);
|
struct bcma_sflash *sflash = dev_get_platdata(&pdev->dev);
|
||||||
|
struct bcm47xxsflash *b47s;
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
sflash->mtd = kzalloc(sizeof(struct mtd_info), GFP_KERNEL);
|
b47s = kzalloc(sizeof(*b47s), GFP_KERNEL);
|
||||||
if (!sflash->mtd) {
|
if (!b47s) {
|
||||||
err = -ENOMEM;
|
err = -ENOMEM;
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
bcm47xxsflash_fill_mtd(sflash, sflash->mtd);
|
sflash->priv = b47s;
|
||||||
|
|
||||||
err = mtd_device_parse_register(sflash->mtd, probes, NULL, NULL, 0);
|
b47s->window = sflash->window;
|
||||||
|
b47s->blocksize = sflash->blocksize;
|
||||||
|
b47s->numblocks = sflash->numblocks;
|
||||||
|
b47s->size = sflash->size;
|
||||||
|
bcm47xxsflash_fill_mtd(b47s);
|
||||||
|
|
||||||
|
err = mtd_device_parse_register(&b47s->mtd, probes, NULL, NULL, 0);
|
||||||
if (err) {
|
if (err) {
|
||||||
pr_err("Failed to register MTD device: %d\n", err);
|
pr_err("Failed to register MTD device: %d\n", err);
|
||||||
goto err_dev_reg;
|
goto err_dev_reg;
|
||||||
@@ -61,34 +76,40 @@ static int bcm47xxsflash_probe(struct platform_device *pdev)
|
|||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
err_dev_reg:
|
err_dev_reg:
|
||||||
kfree(sflash->mtd);
|
kfree(&b47s->mtd);
|
||||||
out:
|
out:
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int bcm47xxsflash_remove(struct platform_device *pdev)
|
static int bcm47xxsflash_bcma_remove(struct platform_device *pdev)
|
||||||
{
|
{
|
||||||
struct bcma_sflash *sflash = dev_get_platdata(&pdev->dev);
|
struct bcma_sflash *sflash = dev_get_platdata(&pdev->dev);
|
||||||
|
struct bcm47xxsflash *b47s = sflash->priv;
|
||||||
|
|
||||||
mtd_device_unregister(sflash->mtd);
|
mtd_device_unregister(&b47s->mtd);
|
||||||
kfree(sflash->mtd);
|
kfree(b47s);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct platform_driver bcma_sflash_driver = {
|
static struct platform_driver bcma_sflash_driver = {
|
||||||
.remove = bcm47xxsflash_remove,
|
.probe = bcm47xxsflash_bcma_probe,
|
||||||
|
.remove = bcm47xxsflash_bcma_remove,
|
||||||
.driver = {
|
.driver = {
|
||||||
.name = "bcma_sflash",
|
.name = "bcma_sflash",
|
||||||
.owner = THIS_MODULE,
|
.owner = THIS_MODULE,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**************************************************
|
||||||
|
* Init
|
||||||
|
**************************************************/
|
||||||
|
|
||||||
static int __init bcm47xxsflash_init(void)
|
static int __init bcm47xxsflash_init(void)
|
||||||
{
|
{
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
err = platform_driver_probe(&bcma_sflash_driver, bcm47xxsflash_probe);
|
err = platform_driver_register(&bcma_sflash_driver);
|
||||||
if (err)
|
if (err)
|
||||||
pr_err("Failed to register BCMA serial flash driver: %d\n",
|
pr_err("Failed to register BCMA serial flash driver: %d\n",
|
||||||
err);
|
err);
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
#ifndef __BCM47XXSFLASH_H
|
||||||
|
#define __BCM47XXSFLASH_H
|
||||||
|
|
||||||
|
#include <linux/mtd/mtd.h>
|
||||||
|
|
||||||
|
struct bcm47xxsflash {
|
||||||
|
u32 window;
|
||||||
|
u32 blocksize;
|
||||||
|
u16 numblocks;
|
||||||
|
u32 size;
|
||||||
|
|
||||||
|
struct mtd_info mtd;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* BCM47XXSFLASH */
|
||||||
@@ -0,0 +1,404 @@
|
|||||||
|
/*
|
||||||
|
* Error Location Module
|
||||||
|
*
|
||||||
|
* Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com/
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/platform_device.h>
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/interrupt.h>
|
||||||
|
#include <linux/io.h>
|
||||||
|
#include <linux/of.h>
|
||||||
|
#include <linux/pm_runtime.h>
|
||||||
|
#include <linux/platform_data/elm.h>
|
||||||
|
|
||||||
|
#define ELM_IRQSTATUS 0x018
|
||||||
|
#define ELM_IRQENABLE 0x01c
|
||||||
|
#define ELM_LOCATION_CONFIG 0x020
|
||||||
|
#define ELM_PAGE_CTRL 0x080
|
||||||
|
#define ELM_SYNDROME_FRAGMENT_0 0x400
|
||||||
|
#define ELM_SYNDROME_FRAGMENT_6 0x418
|
||||||
|
#define ELM_LOCATION_STATUS 0x800
|
||||||
|
#define ELM_ERROR_LOCATION_0 0x880
|
||||||
|
|
||||||
|
/* ELM Interrupt Status Register */
|
||||||
|
#define INTR_STATUS_PAGE_VALID BIT(8)
|
||||||
|
|
||||||
|
/* ELM Interrupt Enable Register */
|
||||||
|
#define INTR_EN_PAGE_MASK BIT(8)
|
||||||
|
|
||||||
|
/* ELM Location Configuration Register */
|
||||||
|
#define ECC_BCH_LEVEL_MASK 0x3
|
||||||
|
|
||||||
|
/* ELM syndrome */
|
||||||
|
#define ELM_SYNDROME_VALID BIT(16)
|
||||||
|
|
||||||
|
/* ELM_LOCATION_STATUS Register */
|
||||||
|
#define ECC_CORRECTABLE_MASK BIT(8)
|
||||||
|
#define ECC_NB_ERRORS_MASK 0x1f
|
||||||
|
|
||||||
|
/* ELM_ERROR_LOCATION_0-15 Registers */
|
||||||
|
#define ECC_ERROR_LOCATION_MASK 0x1fff
|
||||||
|
|
||||||
|
#define ELM_ECC_SIZE 0x7ff
|
||||||
|
|
||||||
|
#define SYNDROME_FRAGMENT_REG_SIZE 0x40
|
||||||
|
#define ERROR_LOCATION_SIZE 0x100
|
||||||
|
|
||||||
|
struct elm_info {
|
||||||
|
struct device *dev;
|
||||||
|
void __iomem *elm_base;
|
||||||
|
struct completion elm_completion;
|
||||||
|
struct list_head list;
|
||||||
|
enum bch_ecc bch_type;
|
||||||
|
};
|
||||||
|
|
||||||
|
static LIST_HEAD(elm_devices);
|
||||||
|
|
||||||
|
static void elm_write_reg(struct elm_info *info, int offset, u32 val)
|
||||||
|
{
|
||||||
|
writel(val, info->elm_base + offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
static u32 elm_read_reg(struct elm_info *info, int offset)
|
||||||
|
{
|
||||||
|
return readl(info->elm_base + offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* elm_config - Configure ELM module
|
||||||
|
* @dev: ELM device
|
||||||
|
* @bch_type: Type of BCH ecc
|
||||||
|
*/
|
||||||
|
void elm_config(struct device *dev, enum bch_ecc bch_type)
|
||||||
|
{
|
||||||
|
u32 reg_val;
|
||||||
|
struct elm_info *info = dev_get_drvdata(dev);
|
||||||
|
|
||||||
|
reg_val = (bch_type & ECC_BCH_LEVEL_MASK) | (ELM_ECC_SIZE << 16);
|
||||||
|
elm_write_reg(info, ELM_LOCATION_CONFIG, reg_val);
|
||||||
|
info->bch_type = bch_type;
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL(elm_config);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* elm_configure_page_mode - Enable/Disable page mode
|
||||||
|
* @info: elm info
|
||||||
|
* @index: index number of syndrome fragment vector
|
||||||
|
* @enable: enable/disable flag for page mode
|
||||||
|
*
|
||||||
|
* Enable page mode for syndrome fragment index
|
||||||
|
*/
|
||||||
|
static void elm_configure_page_mode(struct elm_info *info, int index,
|
||||||
|
bool enable)
|
||||||
|
{
|
||||||
|
u32 reg_val;
|
||||||
|
|
||||||
|
reg_val = elm_read_reg(info, ELM_PAGE_CTRL);
|
||||||
|
if (enable)
|
||||||
|
reg_val |= BIT(index); /* enable page mode */
|
||||||
|
else
|
||||||
|
reg_val &= ~BIT(index); /* disable page mode */
|
||||||
|
|
||||||
|
elm_write_reg(info, ELM_PAGE_CTRL, reg_val);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* elm_load_syndrome - Load ELM syndrome reg
|
||||||
|
* @info: elm info
|
||||||
|
* @err_vec: elm error vectors
|
||||||
|
* @ecc: buffer with calculated ecc
|
||||||
|
*
|
||||||
|
* Load syndrome fragment registers with calculated ecc in reverse order.
|
||||||
|
*/
|
||||||
|
static void elm_load_syndrome(struct elm_info *info,
|
||||||
|
struct elm_errorvec *err_vec, u8 *ecc)
|
||||||
|
{
|
||||||
|
int i, offset;
|
||||||
|
u32 val;
|
||||||
|
|
||||||
|
for (i = 0; i < ERROR_VECTOR_MAX; i++) {
|
||||||
|
|
||||||
|
/* Check error reported */
|
||||||
|
if (err_vec[i].error_reported) {
|
||||||
|
elm_configure_page_mode(info, i, true);
|
||||||
|
offset = ELM_SYNDROME_FRAGMENT_0 +
|
||||||
|
SYNDROME_FRAGMENT_REG_SIZE * i;
|
||||||
|
|
||||||
|
/* BCH8 */
|
||||||
|
if (info->bch_type) {
|
||||||
|
|
||||||
|
/* syndrome fragment 0 = ecc[9-12B] */
|
||||||
|
val = cpu_to_be32(*(u32 *) &ecc[9]);
|
||||||
|
elm_write_reg(info, offset, val);
|
||||||
|
|
||||||
|
/* syndrome fragment 1 = ecc[5-8B] */
|
||||||
|
offset += 4;
|
||||||
|
val = cpu_to_be32(*(u32 *) &ecc[5]);
|
||||||
|
elm_write_reg(info, offset, val);
|
||||||
|
|
||||||
|
/* syndrome fragment 2 = ecc[1-4B] */
|
||||||
|
offset += 4;
|
||||||
|
val = cpu_to_be32(*(u32 *) &ecc[1]);
|
||||||
|
elm_write_reg(info, offset, val);
|
||||||
|
|
||||||
|
/* syndrome fragment 3 = ecc[0B] */
|
||||||
|
offset += 4;
|
||||||
|
val = ecc[0];
|
||||||
|
elm_write_reg(info, offset, val);
|
||||||
|
} else {
|
||||||
|
/* syndrome fragment 0 = ecc[20-52b] bits */
|
||||||
|
val = (cpu_to_be32(*(u32 *) &ecc[3]) >> 4) |
|
||||||
|
((ecc[2] & 0xf) << 28);
|
||||||
|
elm_write_reg(info, offset, val);
|
||||||
|
|
||||||
|
/* syndrome fragment 1 = ecc[0-20b] bits */
|
||||||
|
offset += 4;
|
||||||
|
val = cpu_to_be32(*(u32 *) &ecc[0]) >> 12;
|
||||||
|
elm_write_reg(info, offset, val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Update ecc pointer with ecc byte size */
|
||||||
|
ecc += info->bch_type ? BCH8_SIZE : BCH4_SIZE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* elm_start_processing - start elm syndrome processing
|
||||||
|
* @info: elm info
|
||||||
|
* @err_vec: elm error vectors
|
||||||
|
*
|
||||||
|
* Set syndrome valid bit for syndrome fragment registers for which
|
||||||
|
* elm syndrome fragment registers are loaded. This enables elm module
|
||||||
|
* to start processing syndrome vectors.
|
||||||
|
*/
|
||||||
|
static void elm_start_processing(struct elm_info *info,
|
||||||
|
struct elm_errorvec *err_vec)
|
||||||
|
{
|
||||||
|
int i, offset;
|
||||||
|
u32 reg_val;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set syndrome vector valid, so that ELM module
|
||||||
|
* will process it for vectors error is reported
|
||||||
|
*/
|
||||||
|
for (i = 0; i < ERROR_VECTOR_MAX; i++) {
|
||||||
|
if (err_vec[i].error_reported) {
|
||||||
|
offset = ELM_SYNDROME_FRAGMENT_6 +
|
||||||
|
SYNDROME_FRAGMENT_REG_SIZE * i;
|
||||||
|
reg_val = elm_read_reg(info, offset);
|
||||||
|
reg_val |= ELM_SYNDROME_VALID;
|
||||||
|
elm_write_reg(info, offset, reg_val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* elm_error_correction - locate correctable error position
|
||||||
|
* @info: elm info
|
||||||
|
* @err_vec: elm error vectors
|
||||||
|
*
|
||||||
|
* On completion of processing by elm module, error location status
|
||||||
|
* register updated with correctable/uncorrectable error information.
|
||||||
|
* In case of correctable errors, number of errors located from
|
||||||
|
* elm location status register & read the positions from
|
||||||
|
* elm error location register.
|
||||||
|
*/
|
||||||
|
static void elm_error_correction(struct elm_info *info,
|
||||||
|
struct elm_errorvec *err_vec)
|
||||||
|
{
|
||||||
|
int i, j, errors = 0;
|
||||||
|
int offset;
|
||||||
|
u32 reg_val;
|
||||||
|
|
||||||
|
for (i = 0; i < ERROR_VECTOR_MAX; i++) {
|
||||||
|
|
||||||
|
/* Check error reported */
|
||||||
|
if (err_vec[i].error_reported) {
|
||||||
|
offset = ELM_LOCATION_STATUS + ERROR_LOCATION_SIZE * i;
|
||||||
|
reg_val = elm_read_reg(info, offset);
|
||||||
|
|
||||||
|
/* Check correctable error or not */
|
||||||
|
if (reg_val & ECC_CORRECTABLE_MASK) {
|
||||||
|
offset = ELM_ERROR_LOCATION_0 +
|
||||||
|
ERROR_LOCATION_SIZE * i;
|
||||||
|
|
||||||
|
/* Read count of correctable errors */
|
||||||
|
err_vec[i].error_count = reg_val &
|
||||||
|
ECC_NB_ERRORS_MASK;
|
||||||
|
|
||||||
|
/* Update the error locations in error vector */
|
||||||
|
for (j = 0; j < err_vec[i].error_count; j++) {
|
||||||
|
|
||||||
|
reg_val = elm_read_reg(info, offset);
|
||||||
|
err_vec[i].error_loc[j] = reg_val &
|
||||||
|
ECC_ERROR_LOCATION_MASK;
|
||||||
|
|
||||||
|
/* Update error location register */
|
||||||
|
offset += 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
errors += err_vec[i].error_count;
|
||||||
|
} else {
|
||||||
|
err_vec[i].error_uncorrectable = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Clearing interrupts for processed error vectors */
|
||||||
|
elm_write_reg(info, ELM_IRQSTATUS, BIT(i));
|
||||||
|
|
||||||
|
/* Disable page mode */
|
||||||
|
elm_configure_page_mode(info, i, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* elm_decode_bch_error_page - Locate error position
|
||||||
|
* @dev: device pointer
|
||||||
|
* @ecc_calc: calculated ECC bytes from GPMC
|
||||||
|
* @err_vec: elm error vectors
|
||||||
|
*
|
||||||
|
* Called with one or more error reported vectors & vectors with
|
||||||
|
* error reported is updated in err_vec[].error_reported
|
||||||
|
*/
|
||||||
|
void elm_decode_bch_error_page(struct device *dev, u8 *ecc_calc,
|
||||||
|
struct elm_errorvec *err_vec)
|
||||||
|
{
|
||||||
|
struct elm_info *info = dev_get_drvdata(dev);
|
||||||
|
u32 reg_val;
|
||||||
|
|
||||||
|
/* Enable page mode interrupt */
|
||||||
|
reg_val = elm_read_reg(info, ELM_IRQSTATUS);
|
||||||
|
elm_write_reg(info, ELM_IRQSTATUS, reg_val & INTR_STATUS_PAGE_VALID);
|
||||||
|
elm_write_reg(info, ELM_IRQENABLE, INTR_EN_PAGE_MASK);
|
||||||
|
|
||||||
|
/* Load valid ecc byte to syndrome fragment register */
|
||||||
|
elm_load_syndrome(info, err_vec, ecc_calc);
|
||||||
|
|
||||||
|
/* Enable syndrome processing for which syndrome fragment is updated */
|
||||||
|
elm_start_processing(info, err_vec);
|
||||||
|
|
||||||
|
/* Wait for ELM module to finish locating error correction */
|
||||||
|
wait_for_completion(&info->elm_completion);
|
||||||
|
|
||||||
|
/* Disable page mode interrupt */
|
||||||
|
reg_val = elm_read_reg(info, ELM_IRQENABLE);
|
||||||
|
elm_write_reg(info, ELM_IRQENABLE, reg_val & ~INTR_EN_PAGE_MASK);
|
||||||
|
elm_error_correction(info, err_vec);
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL(elm_decode_bch_error_page);
|
||||||
|
|
||||||
|
static irqreturn_t elm_isr(int this_irq, void *dev_id)
|
||||||
|
{
|
||||||
|
u32 reg_val;
|
||||||
|
struct elm_info *info = dev_id;
|
||||||
|
|
||||||
|
reg_val = elm_read_reg(info, ELM_IRQSTATUS);
|
||||||
|
|
||||||
|
/* All error vectors processed */
|
||||||
|
if (reg_val & INTR_STATUS_PAGE_VALID) {
|
||||||
|
elm_write_reg(info, ELM_IRQSTATUS,
|
||||||
|
reg_val & INTR_STATUS_PAGE_VALID);
|
||||||
|
complete(&info->elm_completion);
|
||||||
|
return IRQ_HANDLED;
|
||||||
|
}
|
||||||
|
|
||||||
|
return IRQ_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int elm_probe(struct platform_device *pdev)
|
||||||
|
{
|
||||||
|
int ret = 0;
|
||||||
|
struct resource *res, *irq;
|
||||||
|
struct elm_info *info;
|
||||||
|
|
||||||
|
info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
|
||||||
|
if (!info) {
|
||||||
|
dev_err(&pdev->dev, "failed to allocate memory\n");
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
info->dev = &pdev->dev;
|
||||||
|
|
||||||
|
irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
|
||||||
|
if (!irq) {
|
||||||
|
dev_err(&pdev->dev, "no irq resource defined\n");
|
||||||
|
return -ENODEV;
|
||||||
|
}
|
||||||
|
|
||||||
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||||
|
if (!res) {
|
||||||
|
dev_err(&pdev->dev, "no memory resource defined\n");
|
||||||
|
return -ENODEV;
|
||||||
|
}
|
||||||
|
|
||||||
|
info->elm_base = devm_request_and_ioremap(&pdev->dev, res);
|
||||||
|
if (!info->elm_base)
|
||||||
|
return -EADDRNOTAVAIL;
|
||||||
|
|
||||||
|
ret = devm_request_irq(&pdev->dev, irq->start, elm_isr, 0,
|
||||||
|
pdev->name, info);
|
||||||
|
if (ret) {
|
||||||
|
dev_err(&pdev->dev, "failure requesting irq %i\n", irq->start);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
pm_runtime_enable(&pdev->dev);
|
||||||
|
if (pm_runtime_get_sync(&pdev->dev)) {
|
||||||
|
ret = -EINVAL;
|
||||||
|
pm_runtime_disable(&pdev->dev);
|
||||||
|
dev_err(&pdev->dev, "can't enable clock\n");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
init_completion(&info->elm_completion);
|
||||||
|
INIT_LIST_HEAD(&info->list);
|
||||||
|
list_add(&info->list, &elm_devices);
|
||||||
|
platform_set_drvdata(pdev, info);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int elm_remove(struct platform_device *pdev)
|
||||||
|
{
|
||||||
|
pm_runtime_put_sync(&pdev->dev);
|
||||||
|
pm_runtime_disable(&pdev->dev);
|
||||||
|
platform_set_drvdata(pdev, NULL);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef CONFIG_OF
|
||||||
|
static const struct of_device_id elm_of_match[] = {
|
||||||
|
{ .compatible = "ti,am3352-elm" },
|
||||||
|
{},
|
||||||
|
};
|
||||||
|
MODULE_DEVICE_TABLE(of, elm_of_match);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static struct platform_driver elm_driver = {
|
||||||
|
.driver = {
|
||||||
|
.name = "elm",
|
||||||
|
.owner = THIS_MODULE,
|
||||||
|
.of_match_table = of_match_ptr(elm_of_match),
|
||||||
|
},
|
||||||
|
.probe = elm_probe,
|
||||||
|
.remove = elm_remove,
|
||||||
|
};
|
||||||
|
|
||||||
|
module_platform_driver(elm_driver);
|
||||||
|
|
||||||
|
MODULE_DESCRIPTION("ELM driver for BCH error correction");
|
||||||
|
MODULE_AUTHOR("Texas Instruments");
|
||||||
|
MODULE_ALIAS("platform: elm");
|
||||||
|
MODULE_LICENSE("GPL v2");
|
||||||
@@ -565,6 +565,96 @@ time_out:
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int m25p80_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
|
||||||
|
{
|
||||||
|
struct m25p *flash = mtd_to_m25p(mtd);
|
||||||
|
uint32_t offset = ofs;
|
||||||
|
uint8_t status_old, status_new;
|
||||||
|
int res = 0;
|
||||||
|
|
||||||
|
mutex_lock(&flash->lock);
|
||||||
|
/* Wait until finished previous command */
|
||||||
|
if (wait_till_ready(flash)) {
|
||||||
|
res = 1;
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
|
status_old = read_sr(flash);
|
||||||
|
|
||||||
|
if (offset < flash->mtd.size-(flash->mtd.size/2))
|
||||||
|
status_new = status_old | SR_BP2 | SR_BP1 | SR_BP0;
|
||||||
|
else if (offset < flash->mtd.size-(flash->mtd.size/4))
|
||||||
|
status_new = (status_old & ~SR_BP0) | SR_BP2 | SR_BP1;
|
||||||
|
else if (offset < flash->mtd.size-(flash->mtd.size/8))
|
||||||
|
status_new = (status_old & ~SR_BP1) | SR_BP2 | SR_BP0;
|
||||||
|
else if (offset < flash->mtd.size-(flash->mtd.size/16))
|
||||||
|
status_new = (status_old & ~(SR_BP0|SR_BP1)) | SR_BP2;
|
||||||
|
else if (offset < flash->mtd.size-(flash->mtd.size/32))
|
||||||
|
status_new = (status_old & ~SR_BP2) | SR_BP1 | SR_BP0;
|
||||||
|
else if (offset < flash->mtd.size-(flash->mtd.size/64))
|
||||||
|
status_new = (status_old & ~(SR_BP2|SR_BP0)) | SR_BP1;
|
||||||
|
else
|
||||||
|
status_new = (status_old & ~(SR_BP2|SR_BP1)) | SR_BP0;
|
||||||
|
|
||||||
|
/* Only modify protection if it will not unlock other areas */
|
||||||
|
if ((status_new&(SR_BP2|SR_BP1|SR_BP0)) >
|
||||||
|
(status_old&(SR_BP2|SR_BP1|SR_BP0))) {
|
||||||
|
write_enable(flash);
|
||||||
|
if (write_sr(flash, status_new) < 0) {
|
||||||
|
res = 1;
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err: mutex_unlock(&flash->lock);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int m25p80_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
|
||||||
|
{
|
||||||
|
struct m25p *flash = mtd_to_m25p(mtd);
|
||||||
|
uint32_t offset = ofs;
|
||||||
|
uint8_t status_old, status_new;
|
||||||
|
int res = 0;
|
||||||
|
|
||||||
|
mutex_lock(&flash->lock);
|
||||||
|
/* Wait until finished previous command */
|
||||||
|
if (wait_till_ready(flash)) {
|
||||||
|
res = 1;
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
|
status_old = read_sr(flash);
|
||||||
|
|
||||||
|
if (offset+len > flash->mtd.size-(flash->mtd.size/64))
|
||||||
|
status_new = status_old & ~(SR_BP2|SR_BP1|SR_BP0);
|
||||||
|
else if (offset+len > flash->mtd.size-(flash->mtd.size/32))
|
||||||
|
status_new = (status_old & ~(SR_BP2|SR_BP1)) | SR_BP0;
|
||||||
|
else if (offset+len > flash->mtd.size-(flash->mtd.size/16))
|
||||||
|
status_new = (status_old & ~(SR_BP2|SR_BP0)) | SR_BP1;
|
||||||
|
else if (offset+len > flash->mtd.size-(flash->mtd.size/8))
|
||||||
|
status_new = (status_old & ~SR_BP2) | SR_BP1 | SR_BP0;
|
||||||
|
else if (offset+len > flash->mtd.size-(flash->mtd.size/4))
|
||||||
|
status_new = (status_old & ~(SR_BP0|SR_BP1)) | SR_BP2;
|
||||||
|
else if (offset+len > flash->mtd.size-(flash->mtd.size/2))
|
||||||
|
status_new = (status_old & ~SR_BP1) | SR_BP2 | SR_BP0;
|
||||||
|
else
|
||||||
|
status_new = (status_old & ~SR_BP0) | SR_BP2 | SR_BP1;
|
||||||
|
|
||||||
|
/* Only modify protection if it will not lock other areas */
|
||||||
|
if ((status_new&(SR_BP2|SR_BP1|SR_BP0)) <
|
||||||
|
(status_old&(SR_BP2|SR_BP1|SR_BP0))) {
|
||||||
|
write_enable(flash);
|
||||||
|
if (write_sr(flash, status_new) < 0) {
|
||||||
|
res = 1;
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err: mutex_unlock(&flash->lock);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
/****************************************************************************/
|
/****************************************************************************/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -642,6 +732,10 @@ static const struct spi_device_id m25p_ids[] = {
|
|||||||
/* Everspin */
|
/* Everspin */
|
||||||
{ "mr25h256", CAT25_INFO( 32 * 1024, 1, 256, 2) },
|
{ "mr25h256", CAT25_INFO( 32 * 1024, 1, 256, 2) },
|
||||||
|
|
||||||
|
/* GigaDevice */
|
||||||
|
{ "gd25q32", INFO(0xc84016, 0, 64 * 1024, 64, SECT_4K) },
|
||||||
|
{ "gd25q64", INFO(0xc84017, 0, 64 * 1024, 128, SECT_4K) },
|
||||||
|
|
||||||
/* Intel/Numonyx -- xxxs33b */
|
/* Intel/Numonyx -- xxxs33b */
|
||||||
{ "160s33b", INFO(0x898911, 0, 64 * 1024, 32, 0) },
|
{ "160s33b", INFO(0x898911, 0, 64 * 1024, 32, 0) },
|
||||||
{ "320s33b", INFO(0x898912, 0, 64 * 1024, 64, 0) },
|
{ "320s33b", INFO(0x898912, 0, 64 * 1024, 64, 0) },
|
||||||
@@ -899,6 +993,12 @@ static int m25p_probe(struct spi_device *spi)
|
|||||||
flash->mtd._erase = m25p80_erase;
|
flash->mtd._erase = m25p80_erase;
|
||||||
flash->mtd._read = m25p80_read;
|
flash->mtd._read = m25p80_read;
|
||||||
|
|
||||||
|
/* flash protection support for STmicro chips */
|
||||||
|
if (JEDEC_MFR(info->jedec_id) == CFI_MFR_ST) {
|
||||||
|
flash->mtd._lock = m25p80_lock;
|
||||||
|
flash->mtd._unlock = m25p80_unlock;
|
||||||
|
}
|
||||||
|
|
||||||
/* sst flash chips use AAI word program */
|
/* sst flash chips use AAI word program */
|
||||||
if (JEDEC_MFR(info->jedec_id) == CFI_MFR_SST)
|
if (JEDEC_MFR(info->jedec_id) == CFI_MFR_SST)
|
||||||
flash->mtd._write = sst_write;
|
flash->mtd._write = sst_write;
|
||||||
|
|||||||
@@ -429,7 +429,7 @@ config MTD_GPIO_ADDR
|
|||||||
|
|
||||||
config MTD_UCLINUX
|
config MTD_UCLINUX
|
||||||
bool "Generic uClinux RAM/ROM filesystem support"
|
bool "Generic uClinux RAM/ROM filesystem support"
|
||||||
depends on MTD_RAM=y && (!MMU || COLDFIRE)
|
depends on (MTD_RAM=y || MTD_ROM=y) && (!MMU || COLDFIRE)
|
||||||
help
|
help
|
||||||
Map driver to support image based filesystems for uClinux.
|
Map driver to support image based filesystems for uClinux.
|
||||||
|
|
||||||
|
|||||||
@@ -68,9 +68,6 @@ static int of_flash_remove(struct platform_device *dev)
|
|||||||
kfree(info->list[i].res);
|
kfree(info->list[i].res);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
kfree(info);
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,8 +196,9 @@ static int of_flash_probe(struct platform_device *dev)
|
|||||||
map_indirect = of_property_read_bool(dp, "no-unaligned-direct-access");
|
map_indirect = of_property_read_bool(dp, "no-unaligned-direct-access");
|
||||||
|
|
||||||
err = -ENOMEM;
|
err = -ENOMEM;
|
||||||
info = kzalloc(sizeof(struct of_flash) +
|
info = devm_kzalloc(&dev->dev,
|
||||||
sizeof(struct of_flash_list) * count, GFP_KERNEL);
|
sizeof(struct of_flash) +
|
||||||
|
sizeof(struct of_flash_list) * count, GFP_KERNEL);
|
||||||
if (!info)
|
if (!info)
|
||||||
goto err_flash_remove;
|
goto err_flash_remove;
|
||||||
|
|
||||||
@@ -241,6 +239,7 @@ static int of_flash_probe(struct platform_device *dev)
|
|||||||
info->list[i].map.phys = res.start;
|
info->list[i].map.phys = res.start;
|
||||||
info->list[i].map.size = res_size;
|
info->list[i].map.size = res_size;
|
||||||
info->list[i].map.bankwidth = be32_to_cpup(width);
|
info->list[i].map.bankwidth = be32_to_cpup(width);
|
||||||
|
info->list[i].map.device_node = dp;
|
||||||
|
|
||||||
err = -ENOMEM;
|
err = -ENOMEM;
|
||||||
info->list[i].map.virt = ioremap(info->list[i].map.phys,
|
info->list[i].map.virt = ioremap(info->list[i].map.phys,
|
||||||
|
|||||||
@@ -23,12 +23,26 @@
|
|||||||
|
|
||||||
/****************************************************************************/
|
/****************************************************************************/
|
||||||
|
|
||||||
|
#ifdef CONFIG_MTD_ROM
|
||||||
|
#define MAP_NAME "rom"
|
||||||
|
#else
|
||||||
|
#define MAP_NAME "ram"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Blackfin uses uclinux_ram_map during startup, so it must not be static.
|
||||||
|
* Provide a dummy declaration to make sparse happy.
|
||||||
|
*/
|
||||||
|
extern struct map_info uclinux_ram_map;
|
||||||
|
|
||||||
struct map_info uclinux_ram_map = {
|
struct map_info uclinux_ram_map = {
|
||||||
.name = "RAM",
|
.name = MAP_NAME,
|
||||||
.phys = (unsigned long)__bss_stop,
|
|
||||||
.size = 0,
|
.size = 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static unsigned long physaddr = -1;
|
||||||
|
module_param(physaddr, ulong, S_IRUGO);
|
||||||
|
|
||||||
static struct mtd_info *uclinux_ram_mtdinfo;
|
static struct mtd_info *uclinux_ram_mtdinfo;
|
||||||
|
|
||||||
/****************************************************************************/
|
/****************************************************************************/
|
||||||
@@ -60,11 +74,17 @@ static int __init uclinux_mtd_init(void)
|
|||||||
struct map_info *mapp;
|
struct map_info *mapp;
|
||||||
|
|
||||||
mapp = &uclinux_ram_map;
|
mapp = &uclinux_ram_map;
|
||||||
|
|
||||||
|
if (physaddr == -1)
|
||||||
|
mapp->phys = (resource_size_t)__bss_stop;
|
||||||
|
else
|
||||||
|
mapp->phys = physaddr;
|
||||||
|
|
||||||
if (!mapp->size)
|
if (!mapp->size)
|
||||||
mapp->size = PAGE_ALIGN(ntohl(*((unsigned long *)(mapp->phys + 8))));
|
mapp->size = PAGE_ALIGN(ntohl(*((unsigned long *)(mapp->phys + 8))));
|
||||||
mapp->bankwidth = 4;
|
mapp->bankwidth = 4;
|
||||||
|
|
||||||
printk("uclinux[mtd]: RAM probe address=0x%x size=0x%x\n",
|
printk("uclinux[mtd]: probe address=0x%x size=0x%x\n",
|
||||||
(int) mapp->phys, (int) mapp->size);
|
(int) mapp->phys, (int) mapp->size);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -82,7 +102,7 @@ static int __init uclinux_mtd_init(void)
|
|||||||
|
|
||||||
simple_map_init(mapp);
|
simple_map_init(mapp);
|
||||||
|
|
||||||
mtd = do_map_probe("map_ram", mapp);
|
mtd = do_map_probe("map_" MAP_NAME, mapp);
|
||||||
if (!mtd) {
|
if (!mtd) {
|
||||||
printk("uclinux[mtd]: failed to find a mapping?\n");
|
printk("uclinux[mtd]: failed to find a mapping?\n");
|
||||||
return(-ENXIO);
|
return(-ENXIO);
|
||||||
@@ -118,6 +138,6 @@ module_exit(uclinux_mtd_cleanup);
|
|||||||
|
|
||||||
MODULE_LICENSE("GPL");
|
MODULE_LICENSE("GPL");
|
||||||
MODULE_AUTHOR("Greg Ungerer <gerg@snapgear.com>");
|
MODULE_AUTHOR("Greg Ungerer <gerg@snapgear.com>");
|
||||||
MODULE_DESCRIPTION("Generic RAM based MTD for uClinux");
|
MODULE_DESCRIPTION("Generic MTD for uClinux");
|
||||||
|
|
||||||
/****************************************************************************/
|
/****************************************************************************/
|
||||||
|
|||||||
+117
-24
@@ -101,6 +101,8 @@ struct atmel_nand_host {
|
|||||||
u8 pmecc_corr_cap;
|
u8 pmecc_corr_cap;
|
||||||
u16 pmecc_sector_size;
|
u16 pmecc_sector_size;
|
||||||
u32 pmecc_lookup_table_offset;
|
u32 pmecc_lookup_table_offset;
|
||||||
|
u32 pmecc_lookup_table_offset_512;
|
||||||
|
u32 pmecc_lookup_table_offset_1024;
|
||||||
|
|
||||||
int pmecc_bytes_per_sector;
|
int pmecc_bytes_per_sector;
|
||||||
int pmecc_sector_number;
|
int pmecc_sector_number;
|
||||||
@@ -908,6 +910,84 @@ static void atmel_pmecc_core_init(struct mtd_info *mtd)
|
|||||||
pmecc_writel(host->ecc, CTRL, PMECC_CTRL_ENABLE);
|
pmecc_writel(host->ecc, CTRL, PMECC_CTRL_ENABLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get ECC requirement in ONFI parameters, returns -1 if ONFI
|
||||||
|
* parameters is not supported.
|
||||||
|
* return 0 if success to get the ECC requirement.
|
||||||
|
*/
|
||||||
|
static int get_onfi_ecc_param(struct nand_chip *chip,
|
||||||
|
int *ecc_bits, int *sector_size)
|
||||||
|
{
|
||||||
|
*ecc_bits = *sector_size = 0;
|
||||||
|
|
||||||
|
if (chip->onfi_params.ecc_bits == 0xff)
|
||||||
|
/* TODO: the sector_size and ecc_bits need to be find in
|
||||||
|
* extended ecc parameter, currently we don't support it.
|
||||||
|
*/
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
*ecc_bits = chip->onfi_params.ecc_bits;
|
||||||
|
|
||||||
|
/* The default sector size (ecc codeword size) is 512 */
|
||||||
|
*sector_size = 512;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get ecc requirement from ONFI parameters ecc requirement.
|
||||||
|
* If pmecc-cap, pmecc-sector-size in DTS are not specified, this function
|
||||||
|
* will set them according to ONFI ecc requirement. Otherwise, use the
|
||||||
|
* value in DTS file.
|
||||||
|
* return 0 if success. otherwise return error code.
|
||||||
|
*/
|
||||||
|
static int pmecc_choose_ecc(struct atmel_nand_host *host,
|
||||||
|
int *cap, int *sector_size)
|
||||||
|
{
|
||||||
|
/* Get ECC requirement from ONFI parameters */
|
||||||
|
*cap = *sector_size = 0;
|
||||||
|
if (host->nand_chip.onfi_version) {
|
||||||
|
if (!get_onfi_ecc_param(&host->nand_chip, cap, sector_size))
|
||||||
|
dev_info(host->dev, "ONFI params, minimum required ECC: %d bits in %d bytes\n",
|
||||||
|
*cap, *sector_size);
|
||||||
|
else
|
||||||
|
dev_info(host->dev, "NAND chip ECC reqirement is in Extended ONFI parameter, we don't support yet.\n");
|
||||||
|
} else {
|
||||||
|
dev_info(host->dev, "NAND chip is not ONFI compliant, assume ecc_bits is 2 in 512 bytes");
|
||||||
|
}
|
||||||
|
if (*cap == 0 && *sector_size == 0) {
|
||||||
|
*cap = 2;
|
||||||
|
*sector_size = 512;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If dts file doesn't specify then use the one in ONFI parameters */
|
||||||
|
if (host->pmecc_corr_cap == 0) {
|
||||||
|
/* use the most fitable ecc bits (the near bigger one ) */
|
||||||
|
if (*cap <= 2)
|
||||||
|
host->pmecc_corr_cap = 2;
|
||||||
|
else if (*cap <= 4)
|
||||||
|
host->pmecc_corr_cap = 4;
|
||||||
|
else if (*cap < 8)
|
||||||
|
host->pmecc_corr_cap = 8;
|
||||||
|
else if (*cap < 12)
|
||||||
|
host->pmecc_corr_cap = 12;
|
||||||
|
else if (*cap < 24)
|
||||||
|
host->pmecc_corr_cap = 24;
|
||||||
|
else
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
if (host->pmecc_sector_size == 0) {
|
||||||
|
/* use the most fitable sector size (the near smaller one ) */
|
||||||
|
if (*sector_size >= 1024)
|
||||||
|
host->pmecc_sector_size = 1024;
|
||||||
|
else if (*sector_size >= 512)
|
||||||
|
host->pmecc_sector_size = 512;
|
||||||
|
else
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static int __init atmel_pmecc_nand_init_params(struct platform_device *pdev,
|
static int __init atmel_pmecc_nand_init_params(struct platform_device *pdev,
|
||||||
struct atmel_nand_host *host)
|
struct atmel_nand_host *host)
|
||||||
{
|
{
|
||||||
@@ -916,8 +996,22 @@ static int __init atmel_pmecc_nand_init_params(struct platform_device *pdev,
|
|||||||
struct resource *regs, *regs_pmerr, *regs_rom;
|
struct resource *regs, *regs_pmerr, *regs_rom;
|
||||||
int cap, sector_size, err_no;
|
int cap, sector_size, err_no;
|
||||||
|
|
||||||
|
err_no = pmecc_choose_ecc(host, &cap, §or_size);
|
||||||
|
if (err_no) {
|
||||||
|
dev_err(host->dev, "The NAND flash's ECC requirement are not support!");
|
||||||
|
return err_no;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cap != host->pmecc_corr_cap ||
|
||||||
|
sector_size != host->pmecc_sector_size)
|
||||||
|
dev_info(host->dev, "WARNING: Be Caution! Using different PMECC parameters from Nand ONFI ECC reqirement.\n");
|
||||||
|
|
||||||
cap = host->pmecc_corr_cap;
|
cap = host->pmecc_corr_cap;
|
||||||
sector_size = host->pmecc_sector_size;
|
sector_size = host->pmecc_sector_size;
|
||||||
|
host->pmecc_lookup_table_offset = (sector_size == 512) ?
|
||||||
|
host->pmecc_lookup_table_offset_512 :
|
||||||
|
host->pmecc_lookup_table_offset_1024;
|
||||||
|
|
||||||
dev_info(host->dev, "Initialize PMECC params, cap: %d, sector: %d\n",
|
dev_info(host->dev, "Initialize PMECC params, cap: %d, sector: %d\n",
|
||||||
cap, sector_size);
|
cap, sector_size);
|
||||||
|
|
||||||
@@ -1215,7 +1309,7 @@ static void atmel_nand_hwctl(struct mtd_info *mtd, int mode)
|
|||||||
static int atmel_of_init_port(struct atmel_nand_host *host,
|
static int atmel_of_init_port(struct atmel_nand_host *host,
|
||||||
struct device_node *np)
|
struct device_node *np)
|
||||||
{
|
{
|
||||||
u32 val, table_offset;
|
u32 val;
|
||||||
u32 offset[2];
|
u32 offset[2];
|
||||||
int ecc_mode;
|
int ecc_mode;
|
||||||
struct atmel_nand_data *board = &host->board;
|
struct atmel_nand_data *board = &host->board;
|
||||||
@@ -1259,42 +1353,41 @@ static int atmel_of_init_port(struct atmel_nand_host *host,
|
|||||||
|
|
||||||
/* use PMECC, get correction capability, sector size and lookup
|
/* use PMECC, get correction capability, sector size and lookup
|
||||||
* table offset.
|
* table offset.
|
||||||
|
* If correction bits and sector size are not specified, then find
|
||||||
|
* them from NAND ONFI parameters.
|
||||||
*/
|
*/
|
||||||
if (of_property_read_u32(np, "atmel,pmecc-cap", &val) != 0) {
|
if (of_property_read_u32(np, "atmel,pmecc-cap", &val) == 0) {
|
||||||
dev_err(host->dev, "Cannot decide PMECC Capability\n");
|
if ((val != 2) && (val != 4) && (val != 8) && (val != 12) &&
|
||||||
return -EINVAL;
|
(val != 24)) {
|
||||||
} else if ((val != 2) && (val != 4) && (val != 8) && (val != 12) &&
|
dev_err(host->dev,
|
||||||
(val != 24)) {
|
"Unsupported PMECC correction capability: %d; should be 2, 4, 8, 12 or 24\n",
|
||||||
dev_err(host->dev,
|
val);
|
||||||
"Unsupported PMECC correction capability: %d; should be 2, 4, 8, 12 or 24\n",
|
return -EINVAL;
|
||||||
val);
|
}
|
||||||
return -EINVAL;
|
host->pmecc_corr_cap = (u8)val;
|
||||||
}
|
}
|
||||||
host->pmecc_corr_cap = (u8)val;
|
|
||||||
|
|
||||||
if (of_property_read_u32(np, "atmel,pmecc-sector-size", &val) != 0) {
|
if (of_property_read_u32(np, "atmel,pmecc-sector-size", &val) == 0) {
|
||||||
dev_err(host->dev, "Cannot decide PMECC Sector Size\n");
|
if ((val != 512) && (val != 1024)) {
|
||||||
return -EINVAL;
|
dev_err(host->dev,
|
||||||
} else if ((val != 512) && (val != 1024)) {
|
"Unsupported PMECC sector size: %d; should be 512 or 1024 bytes\n",
|
||||||
dev_err(host->dev,
|
val);
|
||||||
"Unsupported PMECC sector size: %d; should be 512 or 1024 bytes\n",
|
return -EINVAL;
|
||||||
val);
|
}
|
||||||
return -EINVAL;
|
host->pmecc_sector_size = (u16)val;
|
||||||
}
|
}
|
||||||
host->pmecc_sector_size = (u16)val;
|
|
||||||
|
|
||||||
if (of_property_read_u32_array(np, "atmel,pmecc-lookup-table-offset",
|
if (of_property_read_u32_array(np, "atmel,pmecc-lookup-table-offset",
|
||||||
offset, 2) != 0) {
|
offset, 2) != 0) {
|
||||||
dev_err(host->dev, "Cannot get PMECC lookup table offset\n");
|
dev_err(host->dev, "Cannot get PMECC lookup table offset\n");
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
table_offset = host->pmecc_sector_size == 512 ? offset[0] : offset[1];
|
if (!offset[0] && !offset[1]) {
|
||||||
|
|
||||||
if (!table_offset) {
|
|
||||||
dev_err(host->dev, "Invalid PMECC lookup table offset\n");
|
dev_err(host->dev, "Invalid PMECC lookup table offset\n");
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
host->pmecc_lookup_table_offset = table_offset;
|
host->pmecc_lookup_table_offset_512 = offset[0];
|
||||||
|
host->pmecc_lookup_table_offset_1024 = offset[1];
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
#ifndef __BCM47XXNFLASH_H
|
#ifndef __BCM47XXNFLASH_H
|
||||||
#define __BCM47XXNFLASH_H
|
#define __BCM47XXNFLASH_H
|
||||||
|
|
||||||
|
#ifndef pr_fmt
|
||||||
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <linux/mtd/mtd.h>
|
#include <linux/mtd/mtd.h>
|
||||||
#include <linux/mtd/nand.h>
|
#include <linux/mtd/nand.h>
|
||||||
|
|
||||||
|
|||||||
@@ -9,14 +9,14 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "bcm47xxnflash.h"
|
||||||
|
|
||||||
#include <linux/module.h>
|
#include <linux/module.h>
|
||||||
#include <linux/kernel.h>
|
#include <linux/kernel.h>
|
||||||
#include <linux/slab.h>
|
#include <linux/slab.h>
|
||||||
#include <linux/platform_device.h>
|
#include <linux/platform_device.h>
|
||||||
#include <linux/bcma/bcma.h>
|
#include <linux/bcma/bcma.h>
|
||||||
|
|
||||||
#include "bcm47xxnflash.h"
|
|
||||||
|
|
||||||
MODULE_DESCRIPTION("NAND flash driver for BCMA bus");
|
MODULE_DESCRIPTION("NAND flash driver for BCMA bus");
|
||||||
MODULE_LICENSE("GPL");
|
MODULE_LICENSE("GPL");
|
||||||
MODULE_AUTHOR("Rafał Miłecki");
|
MODULE_AUTHOR("Rafał Miłecki");
|
||||||
@@ -77,6 +77,7 @@ static int bcm47xxnflash_remove(struct platform_device *pdev)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static struct platform_driver bcm47xxnflash_driver = {
|
static struct platform_driver bcm47xxnflash_driver = {
|
||||||
|
.probe = bcm47xxnflash_probe,
|
||||||
.remove = bcm47xxnflash_remove,
|
.remove = bcm47xxnflash_remove,
|
||||||
.driver = {
|
.driver = {
|
||||||
.name = "bcma_nflash",
|
.name = "bcma_nflash",
|
||||||
@@ -88,13 +89,10 @@ static int __init bcm47xxnflash_init(void)
|
|||||||
{
|
{
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
/*
|
err = platform_driver_register(&bcm47xxnflash_driver);
|
||||||
* Platform device "bcma_nflash" exists on SoCs and is registered very
|
|
||||||
* early, it won't be added during runtime (use platform_driver_probe).
|
|
||||||
*/
|
|
||||||
err = platform_driver_probe(&bcm47xxnflash_driver, bcm47xxnflash_probe);
|
|
||||||
if (err)
|
if (err)
|
||||||
pr_err("Failed to register serial flash driver: %d\n", err);
|
pr_err("Failed to register bcm47xx nand flash driver: %d\n",
|
||||||
|
err);
|
||||||
|
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,13 +9,13 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "bcm47xxnflash.h"
|
||||||
|
|
||||||
#include <linux/module.h>
|
#include <linux/module.h>
|
||||||
#include <linux/kernel.h>
|
#include <linux/kernel.h>
|
||||||
#include <linux/slab.h>
|
#include <linux/slab.h>
|
||||||
#include <linux/bcma/bcma.h>
|
#include <linux/bcma/bcma.h>
|
||||||
|
|
||||||
#include "bcm47xxnflash.h"
|
|
||||||
|
|
||||||
/* Broadcom uses 1'000'000 but it seems to be too many. Tests on WNDR4500 has
|
/* Broadcom uses 1'000'000 but it seems to be too many. Tests on WNDR4500 has
|
||||||
* shown ~1000 retries as maxiumum. */
|
* shown ~1000 retries as maxiumum. */
|
||||||
#define NFLASH_READY_RETRIES 10000
|
#define NFLASH_READY_RETRIES 10000
|
||||||
|
|||||||
@@ -606,7 +606,7 @@ static int __init nand_davinci_probe(struct platform_device *pdev)
|
|||||||
if (pdev->id < 0 || pdev->id > 3)
|
if (pdev->id < 0 || pdev->id > 3)
|
||||||
return -ENODEV;
|
return -ENODEV;
|
||||||
|
|
||||||
info = kzalloc(sizeof(*info), GFP_KERNEL);
|
info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
|
||||||
if (!info) {
|
if (!info) {
|
||||||
dev_err(&pdev->dev, "unable to allocate memory\n");
|
dev_err(&pdev->dev, "unable to allocate memory\n");
|
||||||
ret = -ENOMEM;
|
ret = -ENOMEM;
|
||||||
@@ -623,11 +623,11 @@ static int __init nand_davinci_probe(struct platform_device *pdev)
|
|||||||
goto err_nomem;
|
goto err_nomem;
|
||||||
}
|
}
|
||||||
|
|
||||||
vaddr = ioremap(res1->start, resource_size(res1));
|
vaddr = devm_request_and_ioremap(&pdev->dev, res1);
|
||||||
base = ioremap(res2->start, resource_size(res2));
|
base = devm_request_and_ioremap(&pdev->dev, res2);
|
||||||
if (!vaddr || !base) {
|
if (!vaddr || !base) {
|
||||||
dev_err(&pdev->dev, "ioremap failed\n");
|
dev_err(&pdev->dev, "ioremap failed\n");
|
||||||
ret = -EINVAL;
|
ret = -EADDRNOTAVAIL;
|
||||||
goto err_ioremap;
|
goto err_ioremap;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -717,7 +717,7 @@ static int __init nand_davinci_probe(struct platform_device *pdev)
|
|||||||
}
|
}
|
||||||
info->chip.ecc.mode = ecc_mode;
|
info->chip.ecc.mode = ecc_mode;
|
||||||
|
|
||||||
info->clk = clk_get(&pdev->dev, "aemif");
|
info->clk = devm_clk_get(&pdev->dev, "aemif");
|
||||||
if (IS_ERR(info->clk)) {
|
if (IS_ERR(info->clk)) {
|
||||||
ret = PTR_ERR(info->clk);
|
ret = PTR_ERR(info->clk);
|
||||||
dev_dbg(&pdev->dev, "unable to get AEMIF clock, err %d\n", ret);
|
dev_dbg(&pdev->dev, "unable to get AEMIF clock, err %d\n", ret);
|
||||||
@@ -845,8 +845,6 @@ err_timing:
|
|||||||
clk_disable_unprepare(info->clk);
|
clk_disable_unprepare(info->clk);
|
||||||
|
|
||||||
err_clk_enable:
|
err_clk_enable:
|
||||||
clk_put(info->clk);
|
|
||||||
|
|
||||||
spin_lock_irq(&davinci_nand_lock);
|
spin_lock_irq(&davinci_nand_lock);
|
||||||
if (ecc_mode == NAND_ECC_HW_SYNDROME)
|
if (ecc_mode == NAND_ECC_HW_SYNDROME)
|
||||||
ecc4_busy = false;
|
ecc4_busy = false;
|
||||||
@@ -855,13 +853,7 @@ err_clk_enable:
|
|||||||
err_ecc:
|
err_ecc:
|
||||||
err_clk:
|
err_clk:
|
||||||
err_ioremap:
|
err_ioremap:
|
||||||
if (base)
|
|
||||||
iounmap(base);
|
|
||||||
if (vaddr)
|
|
||||||
iounmap(vaddr);
|
|
||||||
|
|
||||||
err_nomem:
|
err_nomem:
|
||||||
kfree(info);
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -874,15 +866,9 @@ static int __exit nand_davinci_remove(struct platform_device *pdev)
|
|||||||
ecc4_busy = false;
|
ecc4_busy = false;
|
||||||
spin_unlock_irq(&davinci_nand_lock);
|
spin_unlock_irq(&davinci_nand_lock);
|
||||||
|
|
||||||
iounmap(info->base);
|
|
||||||
iounmap(info->vaddr);
|
|
||||||
|
|
||||||
nand_release(&info->mtd);
|
nand_release(&info->mtd);
|
||||||
|
|
||||||
clk_disable_unprepare(info->clk);
|
clk_disable_unprepare(info->clk);
|
||||||
clk_put(info->clk);
|
|
||||||
|
|
||||||
kfree(info);
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user