M7350v1_en_gpl

This commit is contained in:
T
2024-09-09 08:52:07 +00:00
commit f9cc65cfda
65988 changed files with 26357421 additions and 0 deletions
+360
View File
@@ -0,0 +1,360 @@
menu "Self-contained MTD device drivers"
depends on MTD!=n
depends on HAS_IOMEM
config MTD_PMC551
tristate "Ramix PMC551 PCI Mezzanine RAM card support"
depends on PCI
---help---
This provides a MTD device driver for the Ramix PMC551 RAM PCI card
from Ramix Inc. <http://www.ramix.com/products/memory/pmc551.html>.
These devices come in memory configurations from 32M - 1G. If you
have one, you probably want to enable this.
If this driver is compiled as a module you get the ability to select
the size of the aperture window pointing into the devices memory.
What this means is that if you have a 1G card, normally the kernel
will use a 1G memory map as its view of the device. As a module,
you can select a 1M window into the memory and the driver will
"slide" the window around the PMC551's memory. This was
particularly useful on the 2.2 kernels on PPC architectures as there
was limited kernel space to deal with.
config MTD_PMC551_BUGFIX
bool "PMC551 256M DRAM Bugfix"
depends on MTD_PMC551
help
Some of Ramix's PMC551 boards with 256M configurations have invalid
column and row mux values. This option will fix them, but will
break other memory configurations. If unsure say N.
config MTD_PMC551_DEBUG
bool "PMC551 Debugging"
depends on MTD_PMC551
help
This option makes the PMC551 more verbose during its operation and
is only really useful if you are developing on this driver or
suspect a possible hardware or driver bug. If unsure say N.
config MTD_MS02NV
tristate "DEC MS02-NV NVRAM module support"
depends on MACH_DECSTATION
help
This is an MTD driver for the DEC's MS02-NV (54-20948-01) battery
backed-up NVRAM module. The module was originally meant as an NFS
accelerator. Say Y here if you have a DECstation 5000/2x0 or a
DECsystem 5900 equipped with such a module.
If you want to compile this driver as a module ( = code which can be
inserted in and removed from the running kernel whenever you want),
say M here and read <file:Documentation/kbuild/modules.txt>.
The module will be called ms02-nv.
config MTD_MSM_NAND
tristate "MSM NAND Device Support"
depends on MTD && ARCH_MSM
select CRC16
select BITREVERSE
select MTD_NAND_IDS
default y
help
Support for some NAND chips connected to the MSM NAND controller.
config MTD_MSM_QPIC_NAND
tristate "MSM QPIC NAND Device Support"
depends on MTD && ARCH_MSM && !MTD_MSM_NAND
select CRC16
select BITREVERSE
select MTD_NAND_IDS
default n
help
Support for NAND controller in Qualcomm Parallel Interface
controller (QPIC). This new controller supports BAM mode
and BCH error correction mechanism. Based on the device
capabilities either 4 bit or 8 bit BCH ECC will be used.
config MTD_DATAFLASH
tristate "Support for AT45xxx DataFlash"
depends on SPI_MASTER && EXPERIMENTAL
help
This enables access to AT45xxx DataFlash chips, using SPI.
Sometimes DataFlash chips are packaged inside MMC-format
cards; at this writing, the MMC stack won't handle those.
config MTD_DATAFLASH_WRITE_VERIFY
bool "Verify DataFlash page writes"
depends on MTD_DATAFLASH
help
This adds an extra check when data is written to the flash.
It may help if you are verifying chip setup (timings etc) on
your board. There is a rare possibility that even though the
device thinks the write was successful, a bit could have been
flipped accidentally due to device wear or something else.
config MTD_DATAFLASH_OTP
bool "DataFlash OTP support (Security Register)"
depends on MTD_DATAFLASH
select HAVE_MTD_OTP
help
Newer DataFlash chips (revisions C and D) support 128 bytes of
one-time-programmable (OTP) data. The first half may be written
(once) with up to 64 bytes of data, such as a serial number or
other key product data. The second half is programmed with a
unique-to-each-chip bit pattern at the factory.
config MTD_M25P80
tristate "Support most SPI Flash chips (AT26DF, M25P, W25X, ...)"
depends on SPI_MASTER && EXPERIMENTAL
help
This enables access to most modern SPI flash chips, used for
program and data storage. Series supported include Atmel AT26DF,
Spansion S25SL, SST 25VF, ST M25P, and Winbond W25X. Other chips
are supported as well. See the driver source for the current list,
or to add other chips.
Note that the original DataFlash chips (AT45 series, not AT26DF),
need an entirely different driver.
Set up your spi devices with the right board-specific platform data,
if you want to specify device partitioning or to use a device which
doesn't support the JEDEC ID instruction.
config M25PXX_USE_FAST_READ
bool "Use FAST_READ OPCode allowing SPI CLK <= 50MHz"
depends on MTD_M25P80
default y
help
This option enables FAST_READ access supported by ST M25Pxx.
config MTD_SPEAR_SMI
tristate "SPEAR MTD NOR Support through SMI controller"
depends on PLAT_SPEAR
default y
help
This enable SNOR support on SPEAR platforms using SMI controller
config MTD_SST25L
tristate "Support SST25L (non JEDEC) SPI Flash chips"
depends on SPI_MASTER
help
This enables access to the non JEDEC SST25L SPI flash chips, used
for program and data storage.
Set up your spi devices with the right board-specific platform data,
if you want to specify device partitioning.
config MTD_SLRAM
tristate "Uncached system RAM"
help
If your CPU cannot cache all of the physical memory in your machine,
you can still use it for storage or swap by using this driver to
present it to the system as a Memory Technology Device.
config MTD_PHRAM
tristate "Physical system RAM"
help
This is a re-implementation of the slram driver above.
Use this driver to access physical memory that the kernel proper
doesn't have access to, memory beyond the mem=xxx limit, nvram,
memory on the video card, etc...
config MTD_LART
tristate "28F160xx flash driver for LART"
depends on SA1100_LART
help
This enables the flash driver for LART. Please note that you do
not need any mapping/chip driver for LART. This one does it all
for you, so go disable all of those if you enabled some of them (:
config MTD_MTDRAM
tristate "Test driver using RAM"
help
This enables a test MTD device driver which uses vmalloc() to
provide storage. You probably want to say 'N' unless you're
testing stuff.
config MTDRAM_TOTAL_SIZE
int "MTDRAM device size in KiB"
depends on MTD_MTDRAM
default "4096"
help
This allows you to configure the total size of the MTD device
emulated by the MTDRAM driver. If the MTDRAM driver is built
as a module, it is also possible to specify this as a parameter when
loading the module.
config MTDRAM_ERASE_SIZE
int "MTDRAM erase block size in KiB"
depends on MTD_MTDRAM
default "128"
help
This allows you to configure the size of the erase blocks in the
device emulated by the MTDRAM driver. If the MTDRAM driver is built
as a module, it is also possible to specify this as a parameter when
loading the module.
#If not a module (I don't want to test it as a module)
config MTDRAM_ABS_POS
hex "SRAM Hexadecimal Absolute position or 0"
depends on MTD_MTDRAM=y
default "0"
help
If you have system RAM accessible by the CPU but not used by Linux
in normal operation, you can give the physical address at which the
available RAM starts, and the MTDRAM driver will use it instead of
allocating space from Linux's available memory. Otherwise, leave
this set to zero. Most people will want to leave this as zero.
config MTD_BLOCK2MTD
tristate "MTD using block device"
depends on BLOCK
help
This driver allows a block device to appear as an MTD. It would
generally be used in the following cases:
Using Compact Flash as an MTD, these usually present themselves to
the system as an ATA drive.
Testing MTD users (eg JFFS2) on large media and media that might
be removed during a write (using the floppy drive).
comment "Disk-On-Chip Device Drivers"
config MTD_DOC2000
tristate "M-Systems Disk-On-Chip 2000 and Millennium (DEPRECATED)"
depends on MTD_NAND
select MTD_DOCPROBE
select MTD_NAND_IDS
---help---
This provides an MTD device driver for the M-Systems DiskOnChip
2000 and Millennium devices. Originally designed for the DiskOnChip
2000, it also now includes support for the DiskOnChip Millennium.
If you have problems with this driver and the DiskOnChip Millennium,
you may wish to try the alternative Millennium driver below. To use
the alternative driver, you will need to undefine DOC_SINGLE_DRIVER
in the <file:drivers/mtd/devices/docprobe.c> source code.
If you use this device, you probably also want to enable the NFTL
'NAND Flash Translation Layer' option below, which is used to
emulate a block device by using a kind of file system on the flash
chips.
NOTE: This driver is deprecated and will probably be removed soon.
Please try the new DiskOnChip driver under "NAND Flash Device
Drivers".
config MTD_DOC2001
tristate "M-Systems Disk-On-Chip Millennium-only alternative driver (DEPRECATED)"
depends on MTD_NAND
select MTD_DOCPROBE
select MTD_NAND_IDS
---help---
This provides an alternative MTD device driver for the M-Systems
DiskOnChip Millennium devices. Use this if you have problems with
the combined DiskOnChip 2000 and Millennium driver above. To get
the DiskOnChip probe code to load and use this driver instead of
the other one, you will need to undefine DOC_SINGLE_DRIVER near
the beginning of <file:drivers/mtd/devices/docprobe.c>.
If you use this device, you probably also want to enable the NFTL
'NAND Flash Translation Layer' option below, which is used to
emulate a block device by using a kind of file system on the flash
chips.
NOTE: This driver is deprecated and will probably be removed soon.
Please try the new DiskOnChip driver under "NAND Flash Device
Drivers".
config MTD_DOC2001PLUS
tristate "M-Systems Disk-On-Chip Millennium Plus"
depends on MTD_NAND
select MTD_DOCPROBE
select MTD_NAND_IDS
---help---
This provides an MTD device driver for the M-Systems DiskOnChip
Millennium Plus devices.
If you use this device, you probably also want to enable the INFTL
'Inverse NAND Flash Translation Layer' option below, which is used
to emulate a block device by using a kind of file system on the
flash chips.
NOTE: This driver will soon be replaced by the new DiskOnChip driver
under "NAND Flash Device Drivers" (currently that driver does not
support all Millennium Plus devices).
config MTD_DOCG3
tristate "M-Systems Disk-On-Chip G3"
select BCH
select BCH_CONST_PARAMS
---help---
This provides an MTD device driver for the M-Systems DiskOnChip
G3 devices.
The driver provides access to G3 DiskOnChip, distributed by
M-Systems and now Sandisk. The support is very experimental,
and doesn't give access to any write operations.
if MTD_DOCG3
config BCH_CONST_M
default 14
config BCH_CONST_T
default 4
endif
config MTD_DOCPROBE
tristate
select MTD_DOCECC
config MTD_DOCECC
tristate
config MTD_DOCPROBE_ADVANCED
bool "Advanced detection options for DiskOnChip"
depends on MTD_DOCPROBE
help
This option allows you to specify nonstandard address at which to
probe for a DiskOnChip, or to change the detection options. You
are unlikely to need any of this unless you are using LinuxBIOS.
Say 'N'.
config MTD_DOCPROBE_ADDRESS
hex "Physical address of DiskOnChip" if MTD_DOCPROBE_ADVANCED
depends on MTD_DOCPROBE
default "0x0"
---help---
By default, the probe for DiskOnChip devices will look for a
DiskOnChip at every multiple of 0x2000 between 0xC8000 and 0xEE000.
This option allows you to specify a single address at which to probe
for the device, which is useful if you have other devices in that
range which get upset when they are probed.
(Note that on PowerPC, the normal probe will only check at
0xE4000000.)
Normally, you should leave this set to zero, to allow the probe at
the normal addresses.
config MTD_DOCPROBE_HIGH
bool "Probe high addresses"
depends on MTD_DOCPROBE_ADVANCED
help
By default, the probe for DiskOnChip devices will look for a
DiskOnChip at every multiple of 0x2000 between 0xC8000 and 0xEE000.
This option changes to make it probe between 0xFFFC8000 and
0xFFFEE000. Unless you are using LinuxBIOS, this is unlikely to be
useful to you. Say 'N'.
config MTD_DOCPROBE_55AA
bool "Probe for 0x55 0xAA BIOS Extension Signature"
depends on MTD_DOCPROBE_ADVANCED
help
Check for the 0x55 0xAA signature of a DiskOnChip, and do not
continue with probing if it is absent. The signature will always be
present for a DiskOnChip 2000 or a normal DiskOnChip Millennium.
Only if you have overwritten the first block of a DiskOnChip
Millennium will it be absent. Enable this option if you are using
LinuxBIOS or if you need to recover a DiskOnChip Millennium on which
you have managed to wipe the first block.
endmenu
+25
View File
@@ -0,0 +1,25 @@
#
# linux/drivers/mtd/devices/Makefile
#
obj-$(CONFIG_MTD_DOC2000) += doc2000.o
obj-$(CONFIG_MTD_DOC2001) += doc2001.o
obj-$(CONFIG_MTD_DOC2001PLUS) += doc2001plus.o
obj-$(CONFIG_MTD_DOCG3) += docg3.o
obj-$(CONFIG_MTD_DOCPROBE) += docprobe.o
obj-$(CONFIG_MTD_DOCECC) += docecc.o
obj-$(CONFIG_MTD_SLRAM) += slram.o
obj-$(CONFIG_MTD_PHRAM) += phram.o
obj-$(CONFIG_MTD_PMC551) += pmc551.o
obj-$(CONFIG_MTD_MS02NV) += ms02-nv.o
obj-$(CONFIG_MTD_MSM_NAND) += msm_nand.o
obj-$(CONFIG_MTD_MSM_QPIC_NAND) += msm_qpic_nand.o
obj-$(CONFIG_MTD_MTDRAM) += mtdram.o
obj-$(CONFIG_MTD_LART) += lart.o
obj-$(CONFIG_MTD_BLOCK2MTD) += block2mtd.o
obj-$(CONFIG_MTD_DATAFLASH) += mtd_dataflash.o
obj-$(CONFIG_MTD_M25P80) += m25p80.o
obj-$(CONFIG_MTD_SPEAR_SMI) += spear_smi.o
obj-$(CONFIG_MTD_SST25L) += sst25l.o
CFLAGS_docg3.o += -I$(src)
+465
View File
@@ -0,0 +1,465 @@
/*
* block2mtd.c - create an mtd from a block device
*
* Copyright (C) 2001,2002 Simon Evans <spse@secret.org.uk>
* Copyright (C) 2004-2006 Joern Engel <joern@wh.fh-wedel.de>
*
* Licence: GPL
*/
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/blkdev.h>
#include <linux/bio.h>
#include <linux/pagemap.h>
#include <linux/list.h>
#include <linux/init.h>
#include <linux/mtd/mtd.h>
#include <linux/mutex.h>
#include <linux/mount.h>
#include <linux/slab.h>
#define ERROR(fmt, args...) printk(KERN_ERR "block2mtd: " fmt "\n" , ## args)
#define INFO(fmt, args...) printk(KERN_INFO "block2mtd: " fmt "\n" , ## args)
/* Info for the block device */
struct block2mtd_dev {
struct list_head list;
struct block_device *blkdev;
struct mtd_info mtd;
struct mutex write_mutex;
};
/* Static info about the MTD, used in cleanup_module */
static LIST_HEAD(blkmtd_device_list);
static struct page *page_read(struct address_space *mapping, int index)
{
return read_mapping_page(mapping, index, NULL);
}
/* erase a specified part of the device */
static int _block2mtd_erase(struct block2mtd_dev *dev, loff_t to, size_t len)
{
struct address_space *mapping = dev->blkdev->bd_inode->i_mapping;
struct page *page;
int index = to >> PAGE_SHIFT; // page index
int pages = len >> PAGE_SHIFT;
u_long *p;
u_long *max;
while (pages) {
page = page_read(mapping, index);
if (!page)
return -ENOMEM;
if (IS_ERR(page))
return PTR_ERR(page);
max = page_address(page) + PAGE_SIZE;
for (p=page_address(page); p<max; p++)
if (*p != -1UL) {
lock_page(page);
memset(page_address(page), 0xff, PAGE_SIZE);
set_page_dirty(page);
unlock_page(page);
break;
}
page_cache_release(page);
pages--;
index++;
}
return 0;
}
static int block2mtd_erase(struct mtd_info *mtd, struct erase_info *instr)
{
struct block2mtd_dev *dev = mtd->priv;
size_t from = instr->addr;
size_t len = instr->len;
int err;
instr->state = MTD_ERASING;
mutex_lock(&dev->write_mutex);
err = _block2mtd_erase(dev, from, len);
mutex_unlock(&dev->write_mutex);
if (err) {
ERROR("erase failed err = %d", err);
instr->state = MTD_ERASE_FAILED;
} else
instr->state = MTD_ERASE_DONE;
mtd_erase_callback(instr);
return err;
}
static int block2mtd_read(struct mtd_info *mtd, loff_t from, size_t len,
size_t *retlen, u_char *buf)
{
struct block2mtd_dev *dev = mtd->priv;
struct page *page;
int index = from >> PAGE_SHIFT;
int offset = from & (PAGE_SIZE-1);
int cpylen;
while (len) {
if ((offset + len) > PAGE_SIZE)
cpylen = PAGE_SIZE - offset; // multiple pages
else
cpylen = len; // this page
len = len - cpylen;
page = page_read(dev->blkdev->bd_inode->i_mapping, index);
if (!page)
return -ENOMEM;
if (IS_ERR(page))
return PTR_ERR(page);
memcpy(buf, page_address(page) + offset, cpylen);
page_cache_release(page);
if (retlen)
*retlen += cpylen;
buf += cpylen;
offset = 0;
index++;
}
return 0;
}
/* write data to the underlying device */
static int _block2mtd_write(struct block2mtd_dev *dev, const u_char *buf,
loff_t to, size_t len, size_t *retlen)
{
struct page *page;
struct address_space *mapping = dev->blkdev->bd_inode->i_mapping;
int index = to >> PAGE_SHIFT; // page index
int offset = to & ~PAGE_MASK; // page offset
int cpylen;
while (len) {
if ((offset+len) > PAGE_SIZE)
cpylen = PAGE_SIZE - offset; // multiple pages
else
cpylen = len; // this page
len = len - cpylen;
page = page_read(mapping, index);
if (!page)
return -ENOMEM;
if (IS_ERR(page))
return PTR_ERR(page);
if (memcmp(page_address(page)+offset, buf, cpylen)) {
lock_page(page);
memcpy(page_address(page) + offset, buf, cpylen);
set_page_dirty(page);
unlock_page(page);
}
page_cache_release(page);
if (retlen)
*retlen += cpylen;
buf += cpylen;
offset = 0;
index++;
}
return 0;
}
static int block2mtd_write(struct mtd_info *mtd, loff_t to, size_t len,
size_t *retlen, const u_char *buf)
{
struct block2mtd_dev *dev = mtd->priv;
int err;
mutex_lock(&dev->write_mutex);
err = _block2mtd_write(dev, buf, to, len, retlen);
mutex_unlock(&dev->write_mutex);
if (err > 0)
err = 0;
return err;
}
/* sync the device - wait until the write queue is empty */
static void block2mtd_sync(struct mtd_info *mtd)
{
struct block2mtd_dev *dev = mtd->priv;
sync_blockdev(dev->blkdev);
return;
}
static void block2mtd_free_device(struct block2mtd_dev *dev)
{
if (!dev)
return;
kfree(dev->mtd.name);
if (dev->blkdev) {
invalidate_mapping_pages(dev->blkdev->bd_inode->i_mapping,
0, -1);
blkdev_put(dev->blkdev, FMODE_READ|FMODE_WRITE|FMODE_EXCL);
}
kfree(dev);
}
/* FIXME: ensure that mtd->size % erase_size == 0 */
static struct block2mtd_dev *add_device(char *devname, int erase_size)
{
const fmode_t mode = FMODE_READ | FMODE_WRITE | FMODE_EXCL;
struct block_device *bdev;
struct block2mtd_dev *dev;
char *name;
if (!devname)
return NULL;
dev = kzalloc(sizeof(struct block2mtd_dev), GFP_KERNEL);
if (!dev)
return NULL;
/* Get a handle on the device */
bdev = blkdev_get_by_path(devname, mode, dev);
#ifndef MODULE
if (IS_ERR(bdev)) {
/* We might not have rootfs mounted at this point. Try
to resolve the device name by other means. */
dev_t devt = name_to_dev_t(devname);
if (devt)
bdev = blkdev_get_by_dev(devt, mode, dev);
}
#endif
if (IS_ERR(bdev)) {
ERROR("error: cannot open device %s", devname);
goto devinit_err;
}
dev->blkdev = bdev;
if (MAJOR(bdev->bd_dev) == MTD_BLOCK_MAJOR) {
ERROR("attempting to use an MTD device as a block device");
goto devinit_err;
}
mutex_init(&dev->write_mutex);
/* Setup the MTD structure */
/* make the name contain the block device in */
name = kasprintf(GFP_KERNEL, "block2mtd: %s", devname);
if (!name)
goto devinit_err;
dev->mtd.name = name;
dev->mtd.size = dev->blkdev->bd_inode->i_size & PAGE_MASK;
dev->mtd.erasesize = erase_size;
dev->mtd.writesize = 1;
dev->mtd.writebufsize = PAGE_SIZE;
dev->mtd.type = MTD_RAM;
dev->mtd.flags = MTD_CAP_RAM;
dev->mtd._erase = block2mtd_erase;
dev->mtd._write = block2mtd_write;
dev->mtd._writev = mtd_writev;
dev->mtd._sync = block2mtd_sync;
dev->mtd._read = block2mtd_read;
dev->mtd.priv = dev;
dev->mtd.owner = THIS_MODULE;
if (mtd_device_register(&dev->mtd, NULL, 0)) {
/* Device didn't get added, so free the entry */
goto devinit_err;
}
list_add(&dev->list, &blkmtd_device_list);
INFO("mtd%d: [%s] erase_size = %dKiB [%d]", dev->mtd.index,
dev->mtd.name + strlen("block2mtd: "),
dev->mtd.erasesize >> 10, dev->mtd.erasesize);
return dev;
devinit_err:
block2mtd_free_device(dev);
return NULL;
}
/* This function works similar to reguler strtoul. In addition, it
* allows some suffixes for a more human-readable number format:
* ki, Ki, kiB, KiB - multiply result with 1024
* Mi, MiB - multiply result with 1024^2
* Gi, GiB - multiply result with 1024^3
*/
static int ustrtoul(const char *cp, char **endp, unsigned int base)
{
unsigned long result = simple_strtoul(cp, endp, base);
switch (**endp) {
case 'G' :
result *= 1024;
case 'M':
result *= 1024;
case 'K':
case 'k':
result *= 1024;
/* By dwmw2 editorial decree, "ki", "Mi" or "Gi" are to be used. */
if ((*endp)[1] == 'i') {
if ((*endp)[2] == 'B')
(*endp) += 3;
else
(*endp) += 2;
}
}
return result;
}
static int parse_num(size_t *num, const char *token)
{
char *endp;
size_t n;
n = (size_t) ustrtoul(token, &endp, 0);
if (*endp)
return -EINVAL;
*num = n;
return 0;
}
static inline void kill_final_newline(char *str)
{
char *newline = strrchr(str, '\n');
if (newline && !newline[1])
*newline = 0;
}
#define parse_err(fmt, args...) do { \
ERROR(fmt, ## args); \
return 0; \
} while (0)
#ifndef MODULE
static int block2mtd_init_called = 0;
static char block2mtd_paramline[80 + 12]; /* 80 for device, 12 for erase size */
#endif
static int block2mtd_setup2(const char *val)
{
char buf[80 + 12]; /* 80 for device, 12 for erase size */
char *str = buf;
char *token[2];
char *name;
size_t erase_size = PAGE_SIZE;
int i, ret;
if (strnlen(val, sizeof(buf)) >= sizeof(buf))
parse_err("parameter too long");
strcpy(str, val);
kill_final_newline(str);
for (i = 0; i < 2; i++)
token[i] = strsep(&str, ",");
if (str)
parse_err("too many arguments");
if (!token[0])
parse_err("no argument");
name = token[0];
if (strlen(name) + 1 > 80)
parse_err("device name too long");
if (token[1]) {
ret = parse_num(&erase_size, token[1]);
if (ret) {
parse_err("illegal erase size");
}
}
add_device(name, erase_size);
return 0;
}
static int block2mtd_setup(const char *val, struct kernel_param *kp)
{
#ifdef MODULE
return block2mtd_setup2(val);
#else
/* If more parameters are later passed in via
/sys/module/block2mtd/parameters/block2mtd
and block2mtd_init() has already been called,
we can parse the argument now. */
if (block2mtd_init_called)
return block2mtd_setup2(val);
/* During early boot stage, we only save the parameters
here. We must parse them later: if the param passed
from kernel boot command line, block2mtd_setup() is
called so early that it is not possible to resolve
the device (even kmalloc() fails). Deter that work to
block2mtd_setup2(). */
strlcpy(block2mtd_paramline, val, sizeof(block2mtd_paramline));
return 0;
#endif
}
module_param_call(block2mtd, block2mtd_setup, NULL, NULL, 0200);
MODULE_PARM_DESC(block2mtd, "Device to use. \"block2mtd=<dev>[,<erasesize>]\"");
static int __init block2mtd_init(void)
{
int ret = 0;
#ifndef MODULE
if (strlen(block2mtd_paramline))
ret = block2mtd_setup2(block2mtd_paramline);
block2mtd_init_called = 1;
#endif
return ret;
}
static void __devexit block2mtd_exit(void)
{
struct list_head *pos, *next;
/* Remove the MTD devices */
list_for_each_safe(pos, next, &blkmtd_device_list) {
struct block2mtd_dev *dev = list_entry(pos, typeof(*dev), list);
block2mtd_sync(&dev->mtd);
mtd_device_unregister(&dev->mtd);
INFO("mtd%d: [%s] removed", dev->mtd.index,
dev->mtd.name + strlen("block2mtd: "));
list_del(&dev->list);
block2mtd_free_device(dev);
}
}
module_init(block2mtd_init);
module_exit(block2mtd_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Joern Engel <joern@lazybastard.org>");
MODULE_DESCRIPTION("Emulate an MTD using a block device");
File diff suppressed because it is too large Load Diff
+824
View File
@@ -0,0 +1,824 @@
/*
* Linux driver for Disk-On-Chip Millennium
* (c) 1999 Machine Vision Holdings, Inc.
* (c) 1999, 2000 David Woodhouse <dwmw2@infradead.org>
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <asm/errno.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/bitops.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/nand.h>
#include <linux/mtd/doc2000.h>
/* #define ECC_DEBUG */
/* I have no idea why some DoC chips can not use memcop_form|to_io().
* This may be due to the different revisions of the ASIC controller built-in or
* simplily a QA/Bug issue. Who knows ?? If you have trouble, please uncomment
* this:*/
#undef USE_MEMCPY
static int doc_read(struct mtd_info *mtd, loff_t from, size_t len,
size_t *retlen, u_char *buf);
static int doc_write(struct mtd_info *mtd, loff_t to, size_t len,
size_t *retlen, const u_char *buf);
static int doc_read_oob(struct mtd_info *mtd, loff_t ofs,
struct mtd_oob_ops *ops);
static int doc_write_oob(struct mtd_info *mtd, loff_t ofs,
struct mtd_oob_ops *ops);
static int doc_erase (struct mtd_info *mtd, struct erase_info *instr);
static struct mtd_info *docmillist = NULL;
/* Perform the required delay cycles by reading from the NOP register */
static void DoC_Delay(void __iomem * docptr, unsigned short cycles)
{
volatile char dummy;
int i;
for (i = 0; i < cycles; i++)
dummy = ReadDOC(docptr, NOP);
}
/* DOC_WaitReady: Wait for RDY line to be asserted by the flash chip */
static int _DoC_WaitReady(void __iomem * docptr)
{
unsigned short c = 0xffff;
pr_debug("_DoC_WaitReady called for out-of-line wait\n");
/* Out-of-line routine to wait for chip response */
while (!(ReadDOC(docptr, CDSNControl) & CDSN_CTRL_FR_B) && --c)
;
if (c == 0)
pr_debug("_DoC_WaitReady timed out.\n");
return (c == 0);
}
static inline int DoC_WaitReady(void __iomem * docptr)
{
/* This is inline, to optimise the common case, where it's ready instantly */
int ret = 0;
/* 4 read form NOP register should be issued in prior to the read from CDSNControl
see Software Requirement 11.4 item 2. */
DoC_Delay(docptr, 4);
if (!(ReadDOC(docptr, CDSNControl) & CDSN_CTRL_FR_B))
/* Call the out-of-line routine to wait */
ret = _DoC_WaitReady(docptr);
/* issue 2 read from NOP register after reading from CDSNControl register
see Software Requirement 11.4 item 2. */
DoC_Delay(docptr, 2);
return ret;
}
/* DoC_Command: Send a flash command to the flash chip through the CDSN IO register
with the internal pipeline. Each of 4 delay cycles (read from the NOP register) is
required after writing to CDSN Control register, see Software Requirement 11.4 item 3. */
static void DoC_Command(void __iomem * docptr, unsigned char command,
unsigned char xtraflags)
{
/* Assert the CLE (Command Latch Enable) line to the flash chip */
WriteDOC(xtraflags | CDSN_CTRL_CLE | CDSN_CTRL_CE, docptr, CDSNControl);
DoC_Delay(docptr, 4);
/* Send the command */
WriteDOC(command, docptr, Mil_CDSN_IO);
WriteDOC(0x00, docptr, WritePipeTerm);
/* Lower the CLE line */
WriteDOC(xtraflags | CDSN_CTRL_CE, docptr, CDSNControl);
DoC_Delay(docptr, 4);
}
/* DoC_Address: Set the current address for the flash chip through the CDSN IO register
with the internal pipeline. Each of 4 delay cycles (read from the NOP register) is
required after writing to CDSN Control register, see Software Requirement 11.4 item 3. */
static inline void DoC_Address(void __iomem * docptr, int numbytes, unsigned long ofs,
unsigned char xtraflags1, unsigned char xtraflags2)
{
/* Assert the ALE (Address Latch Enable) line to the flash chip */
WriteDOC(xtraflags1 | CDSN_CTRL_ALE | CDSN_CTRL_CE, docptr, CDSNControl);
DoC_Delay(docptr, 4);
/* Send the address */
switch (numbytes)
{
case 1:
/* Send single byte, bits 0-7. */
WriteDOC(ofs & 0xff, docptr, Mil_CDSN_IO);
WriteDOC(0x00, docptr, WritePipeTerm);
break;
case 2:
/* Send bits 9-16 followed by 17-23 */
WriteDOC((ofs >> 9) & 0xff, docptr, Mil_CDSN_IO);
WriteDOC((ofs >> 17) & 0xff, docptr, Mil_CDSN_IO);
WriteDOC(0x00, docptr, WritePipeTerm);
break;
case 3:
/* Send 0-7, 9-16, then 17-23 */
WriteDOC(ofs & 0xff, docptr, Mil_CDSN_IO);
WriteDOC((ofs >> 9) & 0xff, docptr, Mil_CDSN_IO);
WriteDOC((ofs >> 17) & 0xff, docptr, Mil_CDSN_IO);
WriteDOC(0x00, docptr, WritePipeTerm);
break;
default:
return;
}
/* Lower the ALE line */
WriteDOC(xtraflags1 | xtraflags2 | CDSN_CTRL_CE, docptr, CDSNControl);
DoC_Delay(docptr, 4);
}
/* DoC_SelectChip: Select a given flash chip within the current floor */
static int DoC_SelectChip(void __iomem * docptr, int chip)
{
/* Select the individual flash chip requested */
WriteDOC(chip, docptr, CDSNDeviceSelect);
DoC_Delay(docptr, 4);
/* Wait for it to be ready */
return DoC_WaitReady(docptr);
}
/* DoC_SelectFloor: Select a given floor (bank of flash chips) */
static int DoC_SelectFloor(void __iomem * docptr, int floor)
{
/* Select the floor (bank) of chips required */
WriteDOC(floor, docptr, FloorSelect);
/* Wait for the chip to be ready */
return DoC_WaitReady(docptr);
}
/* DoC_IdentChip: Identify a given NAND chip given {floor,chip} */
static int DoC_IdentChip(struct DiskOnChip *doc, int floor, int chip)
{
int mfr, id, i, j;
volatile char dummy;
/* Page in the required floor/chip
FIXME: is this supported by Millennium ?? */
DoC_SelectFloor(doc->virtadr, floor);
DoC_SelectChip(doc->virtadr, chip);
/* Reset the chip, see Software Requirement 11.4 item 1. */
DoC_Command(doc->virtadr, NAND_CMD_RESET, CDSN_CTRL_WP);
DoC_WaitReady(doc->virtadr);
/* Read the NAND chip ID: 1. Send ReadID command */
DoC_Command(doc->virtadr, NAND_CMD_READID, CDSN_CTRL_WP);
/* Read the NAND chip ID: 2. Send address byte zero */
DoC_Address(doc->virtadr, 1, 0x00, CDSN_CTRL_WP, 0x00);
/* Read the manufacturer and device id codes of the flash device through
CDSN IO register see Software Requirement 11.4 item 5.*/
dummy = ReadDOC(doc->virtadr, ReadPipeInit);
DoC_Delay(doc->virtadr, 2);
mfr = ReadDOC(doc->virtadr, Mil_CDSN_IO);
DoC_Delay(doc->virtadr, 2);
id = ReadDOC(doc->virtadr, Mil_CDSN_IO);
dummy = ReadDOC(doc->virtadr, LastDataRead);
/* No response - return failure */
if (mfr == 0xff || mfr == 0)
return 0;
/* FIXME: to deal with multi-flash on multi-Millennium case more carefully */
for (i = 0; nand_flash_ids[i].name != NULL; i++) {
if ( id == nand_flash_ids[i].id) {
/* Try to identify manufacturer */
for (j = 0; nand_manuf_ids[j].id != 0x0; j++) {
if (nand_manuf_ids[j].id == mfr)
break;
}
printk(KERN_INFO "Flash chip found: Manufacturer ID: %2.2X, "
"Chip ID: %2.2X (%s:%s)\n",
mfr, id, nand_manuf_ids[j].name, nand_flash_ids[i].name);
doc->mfr = mfr;
doc->id = id;
doc->chipshift = ffs((nand_flash_ids[i].chipsize << 20)) - 1;
break;
}
}
if (nand_flash_ids[i].name == NULL)
return 0;
else
return 1;
}
/* DoC_ScanChips: Find all NAND chips present in a DiskOnChip, and identify them */
static void DoC_ScanChips(struct DiskOnChip *this)
{
int floor, chip;
int numchips[MAX_FLOORS_MIL];
int ret;
this->numchips = 0;
this->mfr = 0;
this->id = 0;
/* For each floor, find the number of valid chips it contains */
for (floor = 0,ret = 1; floor < MAX_FLOORS_MIL; floor++) {
numchips[floor] = 0;
for (chip = 0; chip < MAX_CHIPS_MIL && ret != 0; chip++) {
ret = DoC_IdentChip(this, floor, chip);
if (ret) {
numchips[floor]++;
this->numchips++;
}
}
}
/* If there are none at all that we recognise, bail */
if (!this->numchips) {
printk("No flash chips recognised.\n");
return;
}
/* Allocate an array to hold the information for each chip */
this->chips = kmalloc(sizeof(struct Nand) * this->numchips, GFP_KERNEL);
if (!this->chips){
printk("No memory for allocating chip info structures\n");
return;
}
/* Fill out the chip array with {floor, chipno} for each
* detected chip in the device. */
for (floor = 0, ret = 0; floor < MAX_FLOORS_MIL; floor++) {
for (chip = 0 ; chip < numchips[floor] ; chip++) {
this->chips[ret].floor = floor;
this->chips[ret].chip = chip;
this->chips[ret].curadr = 0;
this->chips[ret].curmode = 0x50;
ret++;
}
}
/* Calculate and print the total size of the device */
this->totlen = this->numchips * (1 << this->chipshift);
printk(KERN_INFO "%d flash chips found. Total DiskOnChip size: %ld MiB\n",
this->numchips ,this->totlen >> 20);
}
static int DoCMil_is_alias(struct DiskOnChip *doc1, struct DiskOnChip *doc2)
{
int tmp1, tmp2, retval;
if (doc1->physadr == doc2->physadr)
return 1;
/* Use the alias resolution register which was set aside for this
* purpose. If it's value is the same on both chips, they might
* be the same chip, and we write to one and check for a change in
* the other. It's unclear if this register is usuable in the
* DoC 2000 (it's in the Millenium docs), but it seems to work. */
tmp1 = ReadDOC(doc1->virtadr, AliasResolution);
tmp2 = ReadDOC(doc2->virtadr, AliasResolution);
if (tmp1 != tmp2)
return 0;
WriteDOC((tmp1+1) % 0xff, doc1->virtadr, AliasResolution);
tmp2 = ReadDOC(doc2->virtadr, AliasResolution);
if (tmp2 == (tmp1+1) % 0xff)
retval = 1;
else
retval = 0;
/* Restore register contents. May not be necessary, but do it just to
* be safe. */
WriteDOC(tmp1, doc1->virtadr, AliasResolution);
return retval;
}
/* This routine is found from the docprobe code by symbol_get(),
* which will bump the use count of this module. */
void DoCMil_init(struct mtd_info *mtd)
{
struct DiskOnChip *this = mtd->priv;
struct DiskOnChip *old = NULL;
/* We must avoid being called twice for the same device. */
if (docmillist)
old = docmillist->priv;
while (old) {
if (DoCMil_is_alias(this, old)) {
printk(KERN_NOTICE "Ignoring DiskOnChip Millennium at "
"0x%lX - already configured\n", this->physadr);
iounmap(this->virtadr);
kfree(mtd);
return;
}
if (old->nextdoc)
old = old->nextdoc->priv;
else
old = NULL;
}
mtd->name = "DiskOnChip Millennium";
printk(KERN_NOTICE "DiskOnChip Millennium found at address 0x%lX\n",
this->physadr);
mtd->type = MTD_NANDFLASH;
mtd->flags = MTD_CAP_NANDFLASH;
/* FIXME: erase size is not always 8KiB */
mtd->erasesize = 0x2000;
mtd->writebufsize = mtd->writesize = 512;
mtd->oobsize = 16;
mtd->ecc_strength = 2;
mtd->owner = THIS_MODULE;
mtd->_erase = doc_erase;
mtd->_read = doc_read;
mtd->_write = doc_write;
mtd->_read_oob = doc_read_oob;
mtd->_write_oob = doc_write_oob;
this->curfloor = -1;
this->curchip = -1;
/* Ident all the chips present. */
DoC_ScanChips(this);
if (!this->totlen) {
kfree(mtd);
iounmap(this->virtadr);
} else {
this->nextdoc = docmillist;
docmillist = mtd;
mtd->size = this->totlen;
mtd_device_register(mtd, NULL, 0);
return;
}
}
EXPORT_SYMBOL_GPL(DoCMil_init);
static int doc_read (struct mtd_info *mtd, loff_t from, size_t len,
size_t *retlen, u_char *buf)
{
int i, ret;
volatile char dummy;
unsigned char syndrome[6], eccbuf[6];
struct DiskOnChip *this = mtd->priv;
void __iomem *docptr = this->virtadr;
struct Nand *mychip = &this->chips[from >> (this->chipshift)];
/* Don't allow a single read to cross a 512-byte block boundary */
if (from + len > ((from | 0x1ff) + 1))
len = ((from | 0x1ff) + 1) - from;
/* Find the chip which is to be used and select it */
if (this->curfloor != mychip->floor) {
DoC_SelectFloor(docptr, mychip->floor);
DoC_SelectChip(docptr, mychip->chip);
} else if (this->curchip != mychip->chip) {
DoC_SelectChip(docptr, mychip->chip);
}
this->curfloor = mychip->floor;
this->curchip = mychip->chip;
/* issue the Read0 or Read1 command depend on which half of the page
we are accessing. Polling the Flash Ready bit after issue 3 bytes
address in Sequence Read Mode, see Software Requirement 11.4 item 1.*/
DoC_Command(docptr, (from >> 8) & 1, CDSN_CTRL_WP);
DoC_Address(docptr, 3, from, CDSN_CTRL_WP, 0x00);
DoC_WaitReady(docptr);
/* init the ECC engine, see Reed-Solomon EDC/ECC 11.1 .*/
WriteDOC (DOC_ECC_RESET, docptr, ECCConf);
WriteDOC (DOC_ECC_EN, docptr, ECCConf);
/* Read the data via the internal pipeline through CDSN IO register,
see Pipelined Read Operations 11.3 */
dummy = ReadDOC(docptr, ReadPipeInit);
#ifndef USE_MEMCPY
for (i = 0; i < len-1; i++) {
/* N.B. you have to increase the source address in this way or the
ECC logic will not work properly */
buf[i] = ReadDOC(docptr, Mil_CDSN_IO + (i & 0xff));
}
#else
memcpy_fromio(buf, docptr + DoC_Mil_CDSN_IO, len - 1);
#endif
buf[len - 1] = ReadDOC(docptr, LastDataRead);
/* Let the caller know we completed it */
*retlen = len;
ret = 0;
/* Read the ECC data from Spare Data Area,
see Reed-Solomon EDC/ECC 11.1 */
dummy = ReadDOC(docptr, ReadPipeInit);
#ifndef USE_MEMCPY
for (i = 0; i < 5; i++) {
/* N.B. you have to increase the source address in this way or the
ECC logic will not work properly */
eccbuf[i] = ReadDOC(docptr, Mil_CDSN_IO + i);
}
#else
memcpy_fromio(eccbuf, docptr + DoC_Mil_CDSN_IO, 5);
#endif
eccbuf[5] = ReadDOC(docptr, LastDataRead);
/* Flush the pipeline */
dummy = ReadDOC(docptr, ECCConf);
dummy = ReadDOC(docptr, ECCConf);
/* Check the ECC Status */
if (ReadDOC(docptr, ECCConf) & 0x80) {
int nb_errors;
/* There was an ECC error */
#ifdef ECC_DEBUG
printk("DiskOnChip ECC Error: Read at %lx\n", (long)from);
#endif
/* Read the ECC syndrome through the DiskOnChip ECC logic.
These syndrome will be all ZERO when there is no error */
for (i = 0; i < 6; i++) {
syndrome[i] = ReadDOC(docptr, ECCSyndrome0 + i);
}
nb_errors = doc_decode_ecc(buf, syndrome);
#ifdef ECC_DEBUG
printk("ECC Errors corrected: %x\n", nb_errors);
#endif
if (nb_errors < 0) {
/* We return error, but have actually done the read. Not that
this can be told to user-space, via sys_read(), but at least
MTD-aware stuff can know about it by checking *retlen */
ret = -EIO;
}
}
#ifdef PSYCHO_DEBUG
printk("ECC DATA at %lx: %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X\n",
(long)from, eccbuf[0], eccbuf[1], eccbuf[2], eccbuf[3],
eccbuf[4], eccbuf[5]);
#endif
/* disable the ECC engine */
WriteDOC(DOC_ECC_DIS, docptr , ECCConf);
return ret;
}
static int doc_write (struct mtd_info *mtd, loff_t to, size_t len,
size_t *retlen, const u_char *buf)
{
int i,ret = 0;
char eccbuf[6];
volatile char dummy;
struct DiskOnChip *this = mtd->priv;
void __iomem *docptr = this->virtadr;
struct Nand *mychip = &this->chips[to >> (this->chipshift)];
#if 0
/* Don't allow a single write to cross a 512-byte block boundary */
if (to + len > ( (to | 0x1ff) + 1))
len = ((to | 0x1ff) + 1) - to;
#else
/* Don't allow writes which aren't exactly one block */
if (to & 0x1ff || len != 0x200)
return -EINVAL;
#endif
/* Find the chip which is to be used and select it */
if (this->curfloor != mychip->floor) {
DoC_SelectFloor(docptr, mychip->floor);
DoC_SelectChip(docptr, mychip->chip);
} else if (this->curchip != mychip->chip) {
DoC_SelectChip(docptr, mychip->chip);
}
this->curfloor = mychip->floor;
this->curchip = mychip->chip;
/* Reset the chip, see Software Requirement 11.4 item 1. */
DoC_Command(docptr, NAND_CMD_RESET, 0x00);
DoC_WaitReady(docptr);
/* Set device to main plane of flash */
DoC_Command(docptr, NAND_CMD_READ0, 0x00);
/* issue the Serial Data In command to initial the Page Program process */
DoC_Command(docptr, NAND_CMD_SEQIN, 0x00);
DoC_Address(docptr, 3, to, 0x00, 0x00);
DoC_WaitReady(docptr);
/* init the ECC engine, see Reed-Solomon EDC/ECC 11.1 .*/
WriteDOC (DOC_ECC_RESET, docptr, ECCConf);
WriteDOC (DOC_ECC_EN | DOC_ECC_RW, docptr, ECCConf);
/* Write the data via the internal pipeline through CDSN IO register,
see Pipelined Write Operations 11.2 */
#ifndef USE_MEMCPY
for (i = 0; i < len; i++) {
/* N.B. you have to increase the source address in this way or the
ECC logic will not work properly */
WriteDOC(buf[i], docptr, Mil_CDSN_IO + i);
}
#else
memcpy_toio(docptr + DoC_Mil_CDSN_IO, buf, len);
#endif
WriteDOC(0x00, docptr, WritePipeTerm);
/* Write ECC data to flash, the ECC info is generated by the DiskOnChip ECC logic
see Reed-Solomon EDC/ECC 11.1 */
WriteDOC(0, docptr, NOP);
WriteDOC(0, docptr, NOP);
WriteDOC(0, docptr, NOP);
/* Read the ECC data through the DiskOnChip ECC logic */
for (i = 0; i < 6; i++) {
eccbuf[i] = ReadDOC(docptr, ECCSyndrome0 + i);
}
/* ignore the ECC engine */
WriteDOC(DOC_ECC_DIS, docptr , ECCConf);
#ifndef USE_MEMCPY
/* Write the ECC data to flash */
for (i = 0; i < 6; i++) {
/* N.B. you have to increase the source address in this way or the
ECC logic will not work properly */
WriteDOC(eccbuf[i], docptr, Mil_CDSN_IO + i);
}
#else
memcpy_toio(docptr + DoC_Mil_CDSN_IO, eccbuf, 6);
#endif
/* write the block status BLOCK_USED (0x5555) at the end of ECC data
FIXME: this is only a hack for programming the IPL area for LinuxBIOS
and should be replace with proper codes in user space utilities */
WriteDOC(0x55, docptr, Mil_CDSN_IO);
WriteDOC(0x55, docptr, Mil_CDSN_IO + 1);
WriteDOC(0x00, docptr, WritePipeTerm);
#ifdef PSYCHO_DEBUG
printk("OOB data at %lx is %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X\n",
(long) to, eccbuf[0], eccbuf[1], eccbuf[2], eccbuf[3],
eccbuf[4], eccbuf[5]);
#endif
/* Commit the Page Program command and wait for ready
see Software Requirement 11.4 item 1.*/
DoC_Command(docptr, NAND_CMD_PAGEPROG, 0x00);
DoC_WaitReady(docptr);
/* Read the status of the flash device through CDSN IO register
see Software Requirement 11.4 item 5.*/
DoC_Command(docptr, NAND_CMD_STATUS, CDSN_CTRL_WP);
dummy = ReadDOC(docptr, ReadPipeInit);
DoC_Delay(docptr, 2);
if (ReadDOC(docptr, Mil_CDSN_IO) & 1) {
printk("Error programming flash\n");
/* Error in programming
FIXME: implement Bad Block Replacement (in nftl.c ??) */
ret = -EIO;
}
dummy = ReadDOC(docptr, LastDataRead);
/* Let the caller know we completed it */
*retlen = len;
return ret;
}
static int doc_read_oob(struct mtd_info *mtd, loff_t ofs,
struct mtd_oob_ops *ops)
{
#ifndef USE_MEMCPY
int i;
#endif
volatile char dummy;
struct DiskOnChip *this = mtd->priv;
void __iomem *docptr = this->virtadr;
struct Nand *mychip = &this->chips[ofs >> this->chipshift];
uint8_t *buf = ops->oobbuf;
size_t len = ops->len;
BUG_ON(ops->mode != MTD_OPS_PLACE_OOB);
ofs += ops->ooboffs;
/* Find the chip which is to be used and select it */
if (this->curfloor != mychip->floor) {
DoC_SelectFloor(docptr, mychip->floor);
DoC_SelectChip(docptr, mychip->chip);
} else if (this->curchip != mychip->chip) {
DoC_SelectChip(docptr, mychip->chip);
}
this->curfloor = mychip->floor;
this->curchip = mychip->chip;
/* disable the ECC engine */
WriteDOC (DOC_ECC_RESET, docptr, ECCConf);
WriteDOC (DOC_ECC_DIS, docptr, ECCConf);
/* issue the Read2 command to set the pointer to the Spare Data Area.
Polling the Flash Ready bit after issue 3 bytes address in
Sequence Read Mode, see Software Requirement 11.4 item 1.*/
DoC_Command(docptr, NAND_CMD_READOOB, CDSN_CTRL_WP);
DoC_Address(docptr, 3, ofs, CDSN_CTRL_WP, 0x00);
DoC_WaitReady(docptr);
/* Read the data out via the internal pipeline through CDSN IO register,
see Pipelined Read Operations 11.3 */
dummy = ReadDOC(docptr, ReadPipeInit);
#ifndef USE_MEMCPY
for (i = 0; i < len-1; i++) {
/* N.B. you have to increase the source address in this way or the
ECC logic will not work properly */
buf[i] = ReadDOC(docptr, Mil_CDSN_IO + i);
}
#else
memcpy_fromio(buf, docptr + DoC_Mil_CDSN_IO, len - 1);
#endif
buf[len - 1] = ReadDOC(docptr, LastDataRead);
ops->retlen = len;
return 0;
}
static int doc_write_oob(struct mtd_info *mtd, loff_t ofs,
struct mtd_oob_ops *ops)
{
#ifndef USE_MEMCPY
int i;
#endif
volatile char dummy;
int ret = 0;
struct DiskOnChip *this = mtd->priv;
void __iomem *docptr = this->virtadr;
struct Nand *mychip = &this->chips[ofs >> this->chipshift];
uint8_t *buf = ops->oobbuf;
size_t len = ops->len;
BUG_ON(ops->mode != MTD_OPS_PLACE_OOB);
ofs += ops->ooboffs;
/* Find the chip which is to be used and select it */
if (this->curfloor != mychip->floor) {
DoC_SelectFloor(docptr, mychip->floor);
DoC_SelectChip(docptr, mychip->chip);
} else if (this->curchip != mychip->chip) {
DoC_SelectChip(docptr, mychip->chip);
}
this->curfloor = mychip->floor;
this->curchip = mychip->chip;
/* disable the ECC engine */
WriteDOC (DOC_ECC_RESET, docptr, ECCConf);
WriteDOC (DOC_ECC_DIS, docptr, ECCConf);
/* Reset the chip, see Software Requirement 11.4 item 1. */
DoC_Command(docptr, NAND_CMD_RESET, CDSN_CTRL_WP);
DoC_WaitReady(docptr);
/* issue the Read2 command to set the pointer to the Spare Data Area. */
DoC_Command(docptr, NAND_CMD_READOOB, CDSN_CTRL_WP);
/* issue the Serial Data In command to initial the Page Program process */
DoC_Command(docptr, NAND_CMD_SEQIN, 0x00);
DoC_Address(docptr, 3, ofs, 0x00, 0x00);
/* Write the data via the internal pipeline through CDSN IO register,
see Pipelined Write Operations 11.2 */
#ifndef USE_MEMCPY
for (i = 0; i < len; i++) {
/* N.B. you have to increase the source address in this way or the
ECC logic will not work properly */
WriteDOC(buf[i], docptr, Mil_CDSN_IO + i);
}
#else
memcpy_toio(docptr + DoC_Mil_CDSN_IO, buf, len);
#endif
WriteDOC(0x00, docptr, WritePipeTerm);
/* Commit the Page Program command and wait for ready
see Software Requirement 11.4 item 1.*/
DoC_Command(docptr, NAND_CMD_PAGEPROG, 0x00);
DoC_WaitReady(docptr);
/* Read the status of the flash device through CDSN IO register
see Software Requirement 11.4 item 5.*/
DoC_Command(docptr, NAND_CMD_STATUS, 0x00);
dummy = ReadDOC(docptr, ReadPipeInit);
DoC_Delay(docptr, 2);
if (ReadDOC(docptr, Mil_CDSN_IO) & 1) {
printk("Error programming oob data\n");
/* FIXME: implement Bad Block Replacement (in nftl.c ??) */
ops->retlen = 0;
ret = -EIO;
}
dummy = ReadDOC(docptr, LastDataRead);
ops->retlen = len;
return ret;
}
int doc_erase (struct mtd_info *mtd, struct erase_info *instr)
{
volatile char dummy;
struct DiskOnChip *this = mtd->priv;
__u32 ofs = instr->addr;
__u32 len = instr->len;
void __iomem *docptr = this->virtadr;
struct Nand *mychip = &this->chips[ofs >> this->chipshift];
if (len != mtd->erasesize)
printk(KERN_WARNING "Erase not right size (%x != %x)n",
len, mtd->erasesize);
/* Find the chip which is to be used and select it */
if (this->curfloor != mychip->floor) {
DoC_SelectFloor(docptr, mychip->floor);
DoC_SelectChip(docptr, mychip->chip);
} else if (this->curchip != mychip->chip) {
DoC_SelectChip(docptr, mychip->chip);
}
this->curfloor = mychip->floor;
this->curchip = mychip->chip;
instr->state = MTD_ERASE_PENDING;
/* issue the Erase Setup command */
DoC_Command(docptr, NAND_CMD_ERASE1, 0x00);
DoC_Address(docptr, 2, ofs, 0x00, 0x00);
/* Commit the Erase Start command and wait for ready
see Software Requirement 11.4 item 1.*/
DoC_Command(docptr, NAND_CMD_ERASE2, 0x00);
DoC_WaitReady(docptr);
instr->state = MTD_ERASING;
/* Read the status of the flash device through CDSN IO register
see Software Requirement 11.4 item 5.
FIXME: it seems that we are not wait long enough, some blocks are not
erased fully */
DoC_Command(docptr, NAND_CMD_STATUS, CDSN_CTRL_WP);
dummy = ReadDOC(docptr, ReadPipeInit);
DoC_Delay(docptr, 2);
if (ReadDOC(docptr, Mil_CDSN_IO) & 1) {
printk("Error Erasing at 0x%x\n", ofs);
/* There was an error
FIXME: implement Bad Block Replacement (in nftl.c ??) */
instr->state = MTD_ERASE_FAILED;
} else
instr->state = MTD_ERASE_DONE;
dummy = ReadDOC(docptr, LastDataRead);
mtd_erase_callback(instr);
return 0;
}
/****************************************************************************
*
* Module stuff
*
****************************************************************************/
static void __exit cleanup_doc2001(void)
{
struct mtd_info *mtd;
struct DiskOnChip *this;
while ((mtd=docmillist)) {
this = mtd->priv;
docmillist = this->nextdoc;
mtd_device_unregister(mtd);
iounmap(this->virtadr);
kfree(this->chips);
kfree(mtd);
}
}
module_exit(cleanup_doc2001);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org> et al.");
MODULE_DESCRIPTION("Alternative driver for DiskOnChip Millennium");
File diff suppressed because it is too large Load Diff
+521
View File
@@ -0,0 +1,521 @@
/*
* ECC algorithm for M-systems disk on chip. We use the excellent Reed
* Solmon code of Phil Karn (karn@ka9q.ampr.org) available under the
* GNU GPL License. The rest is simply to convert the disk on chip
* syndrome into a standard syndome.
*
* Author: Fabrice Bellard (fabrice.bellard@netgem.com)
* Copyright (C) 2000 Netgem S.A.
*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <asm/errno.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/doc2000.h>
#define DEBUG_ECC 0
/* need to undef it (from asm/termbits.h) */
#undef B0
#define MM 10 /* Symbol size in bits */
#define KK (1023-4) /* Number of data symbols per block */
#define B0 510 /* First root of generator polynomial, alpha form */
#define PRIM 1 /* power of alpha used to generate roots of generator poly */
#define NN ((1 << MM) - 1)
typedef unsigned short dtype;
/* 1+x^3+x^10 */
static const int Pp[MM+1] = { 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1 };
/* This defines the type used to store an element of the Galois Field
* used by the code. Make sure this is something larger than a char if
* if anything larger than GF(256) is used.
*
* Note: unsigned char will work up to GF(256) but int seems to run
* faster on the Pentium.
*/
typedef int gf;
/* No legal value in index form represents zero, so
* we need a special value for this purpose
*/
#define A0 (NN)
/* Compute x % NN, where NN is 2**MM - 1,
* without a slow divide
*/
static inline gf
modnn(int x)
{
while (x >= NN) {
x -= NN;
x = (x >> MM) + (x & NN);
}
return x;
}
#define CLEAR(a,n) {\
int ci;\
for(ci=(n)-1;ci >=0;ci--)\
(a)[ci] = 0;\
}
#define COPY(a,b,n) {\
int ci;\
for(ci=(n)-1;ci >=0;ci--)\
(a)[ci] = (b)[ci];\
}
#define COPYDOWN(a,b,n) {\
int ci;\
for(ci=(n)-1;ci >=0;ci--)\
(a)[ci] = (b)[ci];\
}
#define Ldec 1
/* generate GF(2**m) from the irreducible polynomial p(X) in Pp[0]..Pp[m]
lookup tables: index->polynomial form alpha_to[] contains j=alpha**i;
polynomial form -> index form index_of[j=alpha**i] = i
alpha=2 is the primitive element of GF(2**m)
HARI's COMMENT: (4/13/94) alpha_to[] can be used as follows:
Let @ represent the primitive element commonly called "alpha" that
is the root of the primitive polynomial p(x). Then in GF(2^m), for any
0 <= i <= 2^m-2,
@^i = a(0) + a(1) @ + a(2) @^2 + ... + a(m-1) @^(m-1)
where the binary vector (a(0),a(1),a(2),...,a(m-1)) is the representation
of the integer "alpha_to[i]" with a(0) being the LSB and a(m-1) the MSB. Thus for
example the polynomial representation of @^5 would be given by the binary
representation of the integer "alpha_to[5]".
Similarly, index_of[] can be used as follows:
As above, let @ represent the primitive element of GF(2^m) that is
the root of the primitive polynomial p(x). In order to find the power
of @ (alpha) that has the polynomial representation
a(0) + a(1) @ + a(2) @^2 + ... + a(m-1) @^(m-1)
we consider the integer "i" whose binary representation with a(0) being LSB
and a(m-1) MSB is (a(0),a(1),...,a(m-1)) and locate the entry
"index_of[i]". Now, @^index_of[i] is that element whose polynomial
representation is (a(0),a(1),a(2),...,a(m-1)).
NOTE:
The element alpha_to[2^m-1] = 0 always signifying that the
representation of "@^infinity" = 0 is (0,0,0,...,0).
Similarly, the element index_of[0] = A0 always signifying
that the power of alpha which has the polynomial representation
(0,0,...,0) is "infinity".
*/
static void
generate_gf(dtype Alpha_to[NN + 1], dtype Index_of[NN + 1])
{
register int i, mask;
mask = 1;
Alpha_to[MM] = 0;
for (i = 0; i < MM; i++) {
Alpha_to[i] = mask;
Index_of[Alpha_to[i]] = i;
/* If Pp[i] == 1 then, term @^i occurs in poly-repr of @^MM */
if (Pp[i] != 0)
Alpha_to[MM] ^= mask; /* Bit-wise EXOR operation */
mask <<= 1; /* single left-shift */
}
Index_of[Alpha_to[MM]] = MM;
/*
* Have obtained poly-repr of @^MM. Poly-repr of @^(i+1) is given by
* poly-repr of @^i shifted left one-bit and accounting for any @^MM
* term that may occur when poly-repr of @^i is shifted.
*/
mask >>= 1;
for (i = MM + 1; i < NN; i++) {
if (Alpha_to[i - 1] >= mask)
Alpha_to[i] = Alpha_to[MM] ^ ((Alpha_to[i - 1] ^ mask) << 1);
else
Alpha_to[i] = Alpha_to[i - 1] << 1;
Index_of[Alpha_to[i]] = i;
}
Index_of[0] = A0;
Alpha_to[NN] = 0;
}
/*
* Performs ERRORS+ERASURES decoding of RS codes. bb[] is the content
* of the feedback shift register after having processed the data and
* the ECC.
*
* Return number of symbols corrected, or -1 if codeword is illegal
* or uncorrectable. If eras_pos is non-null, the detected error locations
* are written back. NOTE! This array must be at least NN-KK elements long.
* The corrected data are written in eras_val[]. They must be xor with the data
* to retrieve the correct data : data[erase_pos[i]] ^= erase_val[i] .
*
* First "no_eras" erasures are declared by the calling program. Then, the
* maximum # of errors correctable is t_after_eras = floor((NN-KK-no_eras)/2).
* If the number of channel errors is not greater than "t_after_eras" the
* transmitted codeword will be recovered. Details of algorithm can be found
* in R. Blahut's "Theory ... of Error-Correcting Codes".
* Warning: the eras_pos[] array must not contain duplicate entries; decoder failure
* will result. The decoder *could* check for this condition, but it would involve
* extra time on every decoding operation.
* */
static int
eras_dec_rs(dtype Alpha_to[NN + 1], dtype Index_of[NN + 1],
gf bb[NN - KK + 1], gf eras_val[NN-KK], int eras_pos[NN-KK],
int no_eras)
{
int deg_lambda, el, deg_omega;
int i, j, r,k;
gf u,q,tmp,num1,num2,den,discr_r;
gf lambda[NN-KK + 1], s[NN-KK + 1]; /* Err+Eras Locator poly
* and syndrome poly */
gf b[NN-KK + 1], t[NN-KK + 1], omega[NN-KK + 1];
gf root[NN-KK], reg[NN-KK + 1], loc[NN-KK];
int syn_error, count;
syn_error = 0;
for(i=0;i<NN-KK;i++)
syn_error |= bb[i];
if (!syn_error) {
/* if remainder is zero, data[] is a codeword and there are no
* errors to correct. So return data[] unmodified
*/
count = 0;
goto finish;
}
for(i=1;i<=NN-KK;i++){
s[i] = bb[0];
}
for(j=1;j<NN-KK;j++){
if(bb[j] == 0)
continue;
tmp = Index_of[bb[j]];
for(i=1;i<=NN-KK;i++)
s[i] ^= Alpha_to[modnn(tmp + (B0+i-1)*PRIM*j)];
}
/* undo the feedback register implicit multiplication and convert
syndromes to index form */
for(i=1;i<=NN-KK;i++) {
tmp = Index_of[s[i]];
if (tmp != A0)
tmp = modnn(tmp + 2 * KK * (B0+i-1)*PRIM);
s[i] = tmp;
}
CLEAR(&lambda[1],NN-KK);
lambda[0] = 1;
if (no_eras > 0) {
/* Init lambda to be the erasure locator polynomial */
lambda[1] = Alpha_to[modnn(PRIM * eras_pos[0])];
for (i = 1; i < no_eras; i++) {
u = modnn(PRIM*eras_pos[i]);
for (j = i+1; j > 0; j--) {
tmp = Index_of[lambda[j - 1]];
if(tmp != A0)
lambda[j] ^= Alpha_to[modnn(u + tmp)];
}
}
#if DEBUG_ECC >= 1
/* Test code that verifies the erasure locator polynomial just constructed
Needed only for decoder debugging. */
/* find roots of the erasure location polynomial */
for(i=1;i<=no_eras;i++)
reg[i] = Index_of[lambda[i]];
count = 0;
for (i = 1,k=NN-Ldec; i <= NN; i++,k = modnn(NN+k-Ldec)) {
q = 1;
for (j = 1; j <= no_eras; j++)
if (reg[j] != A0) {
reg[j] = modnn(reg[j] + j);
q ^= Alpha_to[reg[j]];
}
if (q != 0)
continue;
/* store root and error location number indices */
root[count] = i;
loc[count] = k;
count++;
}
if (count != no_eras) {
printf("\n lambda(x) is WRONG\n");
count = -1;
goto finish;
}
#if DEBUG_ECC >= 2
printf("\n Erasure positions as determined by roots of Eras Loc Poly:\n");
for (i = 0; i < count; i++)
printf("%d ", loc[i]);
printf("\n");
#endif
#endif
}
for(i=0;i<NN-KK+1;i++)
b[i] = Index_of[lambda[i]];
/*
* Begin Berlekamp-Massey algorithm to determine error+erasure
* locator polynomial
*/
r = no_eras;
el = no_eras;
while (++r <= NN-KK) { /* r is the step number */
/* Compute discrepancy at the r-th step in poly-form */
discr_r = 0;
for (i = 0; i < r; i++){
if ((lambda[i] != 0) && (s[r - i] != A0)) {
discr_r ^= Alpha_to[modnn(Index_of[lambda[i]] + s[r - i])];
}
}
discr_r = Index_of[discr_r]; /* Index form */
if (discr_r == A0) {
/* 2 lines below: B(x) <-- x*B(x) */
COPYDOWN(&b[1],b,NN-KK);
b[0] = A0;
} else {
/* 7 lines below: T(x) <-- lambda(x) - discr_r*x*b(x) */
t[0] = lambda[0];
for (i = 0 ; i < NN-KK; i++) {
if(b[i] != A0)
t[i+1] = lambda[i+1] ^ Alpha_to[modnn(discr_r + b[i])];
else
t[i+1] = lambda[i+1];
}
if (2 * el <= r + no_eras - 1) {
el = r + no_eras - el;
/*
* 2 lines below: B(x) <-- inv(discr_r) *
* lambda(x)
*/
for (i = 0; i <= NN-KK; i++)
b[i] = (lambda[i] == 0) ? A0 : modnn(Index_of[lambda[i]] - discr_r + NN);
} else {
/* 2 lines below: B(x) <-- x*B(x) */
COPYDOWN(&b[1],b,NN-KK);
b[0] = A0;
}
COPY(lambda,t,NN-KK+1);
}
}
/* Convert lambda to index form and compute deg(lambda(x)) */
deg_lambda = 0;
for(i=0;i<NN-KK+1;i++){
lambda[i] = Index_of[lambda[i]];
if(lambda[i] != A0)
deg_lambda = i;
}
/*
* Find roots of the error+erasure locator polynomial by Chien
* Search
*/
COPY(&reg[1],&lambda[1],NN-KK);
count = 0; /* Number of roots of lambda(x) */
for (i = 1,k=NN-Ldec; i <= NN; i++,k = modnn(NN+k-Ldec)) {
q = 1;
for (j = deg_lambda; j > 0; j--){
if (reg[j] != A0) {
reg[j] = modnn(reg[j] + j);
q ^= Alpha_to[reg[j]];
}
}
if (q != 0)
continue;
/* store root (index-form) and error location number */
root[count] = i;
loc[count] = k;
/* If we've already found max possible roots,
* abort the search to save time
*/
if(++count == deg_lambda)
break;
}
if (deg_lambda != count) {
/*
* deg(lambda) unequal to number of roots => uncorrectable
* error detected
*/
count = -1;
goto finish;
}
/*
* Compute err+eras evaluator poly omega(x) = s(x)*lambda(x) (modulo
* x**(NN-KK)). in index form. Also find deg(omega).
*/
deg_omega = 0;
for (i = 0; i < NN-KK;i++){
tmp = 0;
j = (deg_lambda < i) ? deg_lambda : i;
for(;j >= 0; j--){
if ((s[i + 1 - j] != A0) && (lambda[j] != A0))
tmp ^= Alpha_to[modnn(s[i + 1 - j] + lambda[j])];
}
if(tmp != 0)
deg_omega = i;
omega[i] = Index_of[tmp];
}
omega[NN-KK] = A0;
/*
* Compute error values in poly-form. num1 = omega(inv(X(l))), num2 =
* inv(X(l))**(B0-1) and den = lambda_pr(inv(X(l))) all in poly-form
*/
for (j = count-1; j >=0; j--) {
num1 = 0;
for (i = deg_omega; i >= 0; i--) {
if (omega[i] != A0)
num1 ^= Alpha_to[modnn(omega[i] + i * root[j])];
}
num2 = Alpha_to[modnn(root[j] * (B0 - 1) + NN)];
den = 0;
/* lambda[i+1] for i even is the formal derivative lambda_pr of lambda[i] */
for (i = min(deg_lambda,NN-KK-1) & ~1; i >= 0; i -=2) {
if(lambda[i+1] != A0)
den ^= Alpha_to[modnn(lambda[i+1] + i * root[j])];
}
if (den == 0) {
#if DEBUG_ECC >= 1
printf("\n ERROR: denominator = 0\n");
#endif
/* Convert to dual- basis */
count = -1;
goto finish;
}
/* Apply error to data */
if (num1 != 0) {
eras_val[j] = Alpha_to[modnn(Index_of[num1] + Index_of[num2] + NN - Index_of[den])];
} else {
eras_val[j] = 0;
}
}
finish:
for(i=0;i<count;i++)
eras_pos[i] = loc[i];
return count;
}
/***************************************************************************/
/* The DOC specific code begins here */
#define SECTOR_SIZE 512
/* The sector bytes are packed into NB_DATA MM bits words */
#define NB_DATA (((SECTOR_SIZE + 1) * 8 + 6) / MM)
/*
* Correct the errors in 'sector[]' by using 'ecc1[]' which is the
* content of the feedback shift register applyied to the sector and
* the ECC. Return the number of errors corrected (and correct them in
* sector), or -1 if error
*/
int doc_decode_ecc(unsigned char sector[SECTOR_SIZE], unsigned char ecc1[6])
{
int parity, i, nb_errors;
gf bb[NN - KK + 1];
gf error_val[NN-KK];
int error_pos[NN-KK], pos, bitpos, index, val;
dtype *Alpha_to, *Index_of;
/* init log and exp tables here to save memory. However, it is slower */
Alpha_to = kmalloc((NN + 1) * sizeof(dtype), GFP_KERNEL);
if (!Alpha_to)
return -1;
Index_of = kmalloc((NN + 1) * sizeof(dtype), GFP_KERNEL);
if (!Index_of) {
kfree(Alpha_to);
return -1;
}
generate_gf(Alpha_to, Index_of);
parity = ecc1[1];
bb[0] = (ecc1[4] & 0xff) | ((ecc1[5] & 0x03) << 8);
bb[1] = ((ecc1[5] & 0xfc) >> 2) | ((ecc1[2] & 0x0f) << 6);
bb[2] = ((ecc1[2] & 0xf0) >> 4) | ((ecc1[3] & 0x3f) << 4);
bb[3] = ((ecc1[3] & 0xc0) >> 6) | ((ecc1[0] & 0xff) << 2);
nb_errors = eras_dec_rs(Alpha_to, Index_of, bb,
error_val, error_pos, 0);
if (nb_errors <= 0)
goto the_end;
/* correct the errors */
for(i=0;i<nb_errors;i++) {
pos = error_pos[i];
if (pos >= NB_DATA && pos < KK) {
nb_errors = -1;
goto the_end;
}
if (pos < NB_DATA) {
/* extract bit position (MSB first) */
pos = 10 * (NB_DATA - 1 - pos) - 6;
/* now correct the following 10 bits. At most two bytes
can be modified since pos is even */
index = (pos >> 3) ^ 1;
bitpos = pos & 7;
if ((index >= 0 && index < SECTOR_SIZE) ||
index == (SECTOR_SIZE + 1)) {
val = error_val[i] >> (2 + bitpos);
parity ^= val;
if (index < SECTOR_SIZE)
sector[index] ^= val;
}
index = ((pos >> 3) + 1) ^ 1;
bitpos = (bitpos + 10) & 7;
if (bitpos == 0)
bitpos = 8;
if ((index >= 0 && index < SECTOR_SIZE) ||
index == (SECTOR_SIZE + 1)) {
val = error_val[i] << (8 - bitpos);
parity ^= val;
if (index < SECTOR_SIZE)
sector[index] ^= val;
}
}
}
/* use parity to test extra errors */
if ((parity & 0xff) != 0)
nb_errors = -1;
the_end:
kfree(Alpha_to);
kfree(Index_of);
return nb_errors;
}
EXPORT_SYMBOL_GPL(doc_decode_ecc);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Fabrice Bellard <fabrice.bellard@netgem.com>");
MODULE_DESCRIPTION("ECC code for correcting errors detected by DiskOnChip 2000 and Millennium ECC hardware");
File diff suppressed because it is too large Load Diff
+370
View File
@@ -0,0 +1,370 @@
/*
* Handles the M-Systems DiskOnChip G3 chip
*
* Copyright (C) 2011 Robert Jarzmik
*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#ifndef _MTD_DOCG3_H
#define _MTD_DOCG3_H
#include <linux/mtd/mtd.h>
/*
* Flash memory areas :
* - 0x0000 .. 0x07ff : IPL
* - 0x0800 .. 0x0fff : Data area
* - 0x1000 .. 0x17ff : Registers
* - 0x1800 .. 0x1fff : Unknown
*/
#define DOC_IOSPACE_IPL 0x0000
#define DOC_IOSPACE_DATA 0x0800
#define DOC_IOSPACE_SIZE 0x2000
/*
* DOC G3 layout and adressing scheme
* A page address for the block "b", plane "P" and page "p":
* address = [bbbb bPpp pppp]
*/
#define DOC_ADDR_PAGE_MASK 0x3f
#define DOC_ADDR_BLOCK_SHIFT 6
#define DOC_LAYOUT_NBPLANES 2
#define DOC_LAYOUT_PAGES_PER_BLOCK 64
#define DOC_LAYOUT_PAGE_SIZE 512
#define DOC_LAYOUT_OOB_SIZE 16
#define DOC_LAYOUT_WEAR_SIZE 8
#define DOC_LAYOUT_PAGE_OOB_SIZE \
(DOC_LAYOUT_PAGE_SIZE + DOC_LAYOUT_OOB_SIZE)
#define DOC_LAYOUT_WEAR_OFFSET (DOC_LAYOUT_PAGE_OOB_SIZE * 2)
#define DOC_LAYOUT_BLOCK_SIZE \
(DOC_LAYOUT_PAGES_PER_BLOCK * DOC_LAYOUT_PAGE_SIZE)
/*
* ECC related constants
*/
#define DOC_ECC_BCH_M 14
#define DOC_ECC_BCH_T 4
#define DOC_ECC_BCH_PRIMPOLY 0x4443
#define DOC_ECC_BCH_SIZE 7
#define DOC_ECC_BCH_COVERED_BYTES \
(DOC_LAYOUT_PAGE_SIZE + DOC_LAYOUT_OOB_PAGEINFO_SZ + \
DOC_LAYOUT_OOB_HAMMING_SZ)
#define DOC_ECC_BCH_TOTAL_BYTES \
(DOC_ECC_BCH_COVERED_BYTES + DOC_LAYOUT_OOB_BCH_SZ)
/*
* Blocks distribution
*/
#define DOC_LAYOUT_BLOCK_BBT 0
#define DOC_LAYOUT_BLOCK_OTP 0
#define DOC_LAYOUT_BLOCK_FIRST_DATA 6
#define DOC_LAYOUT_PAGE_BBT 4
/*
* Extra page OOB (16 bytes wide) layout
*/
#define DOC_LAYOUT_OOB_PAGEINFO_OFS 0
#define DOC_LAYOUT_OOB_HAMMING_OFS 7
#define DOC_LAYOUT_OOB_BCH_OFS 8
#define DOC_LAYOUT_OOB_UNUSED_OFS 15
#define DOC_LAYOUT_OOB_PAGEINFO_SZ 7
#define DOC_LAYOUT_OOB_HAMMING_SZ 1
#define DOC_LAYOUT_OOB_BCH_SZ 7
#define DOC_LAYOUT_OOB_UNUSED_SZ 1
#define DOC_CHIPID_G3 0x200
#define DOC_ERASE_MARK 0xaa
#define DOC_MAX_NBFLOORS 4
/*
* Flash registers
*/
#define DOC_CHIPID 0x1000
#define DOC_TEST 0x1004
#define DOC_BUSLOCK 0x1006
#define DOC_ENDIANCONTROL 0x1008
#define DOC_DEVICESELECT 0x100a
#define DOC_ASICMODE 0x100c
#define DOC_CONFIGURATION 0x100e
#define DOC_INTERRUPTCONTROL 0x1010
#define DOC_READADDRESS 0x101a
#define DOC_DATAEND 0x101e
#define DOC_INTERRUPTSTATUS 0x1020
#define DOC_FLASHSEQUENCE 0x1032
#define DOC_FLASHCOMMAND 0x1034
#define DOC_FLASHADDRESS 0x1036
#define DOC_FLASHCONTROL 0x1038
#define DOC_NOP 0x103e
#define DOC_ECCCONF0 0x1040
#define DOC_ECCCONF1 0x1042
#define DOC_ECCPRESET 0x1044
#define DOC_HAMMINGPARITY 0x1046
#define DOC_BCH_HW_ECC(idx) (0x1048 + idx)
#define DOC_PROTECTION 0x1056
#define DOC_DPS0_KEY 0x105c
#define DOC_DPS1_KEY 0x105e
#define DOC_DPS0_ADDRLOW 0x1060
#define DOC_DPS0_ADDRHIGH 0x1062
#define DOC_DPS1_ADDRLOW 0x1064
#define DOC_DPS1_ADDRHIGH 0x1066
#define DOC_DPS0_STATUS 0x106c
#define DOC_DPS1_STATUS 0x106e
#define DOC_ASICMODECONFIRM 0x1072
#define DOC_CHIPID_INV 0x1074
#define DOC_POWERMODE 0x107c
/*
* Flash sequences
* A sequence is preset before one or more commands are input to the chip.
*/
#define DOC_SEQ_RESET 0x00
#define DOC_SEQ_PAGE_SIZE_532 0x03
#define DOC_SEQ_SET_FASTMODE 0x05
#define DOC_SEQ_SET_RELIABLEMODE 0x09
#define DOC_SEQ_READ 0x12
#define DOC_SEQ_SET_PLANE1 0x0e
#define DOC_SEQ_SET_PLANE2 0x10
#define DOC_SEQ_PAGE_SETUP 0x1d
#define DOC_SEQ_ERASE 0x27
#define DOC_SEQ_PLANES_STATUS 0x31
/*
* Flash commands
*/
#define DOC_CMD_READ_PLANE1 0x00
#define DOC_CMD_SET_ADDR_READ 0x05
#define DOC_CMD_READ_ALL_PLANES 0x30
#define DOC_CMD_READ_PLANE2 0x50
#define DOC_CMD_READ_FLASH 0xe0
#define DOC_CMD_PAGE_SIZE_532 0x3c
#define DOC_CMD_PROG_BLOCK_ADDR 0x60
#define DOC_CMD_PROG_CYCLE1 0x80
#define DOC_CMD_PROG_CYCLE2 0x10
#define DOC_CMD_PROG_CYCLE3 0x11
#define DOC_CMD_ERASECYCLE2 0xd0
#define DOC_CMD_READ_STATUS 0x70
#define DOC_CMD_PLANES_STATUS 0x71
#define DOC_CMD_RELIABLE_MODE 0x22
#define DOC_CMD_FAST_MODE 0xa2
#define DOC_CMD_RESET 0xff
/*
* Flash register : DOC_FLASHCONTROL
*/
#define DOC_CTRL_VIOLATION 0x20
#define DOC_CTRL_CE 0x10
#define DOC_CTRL_UNKNOWN_BITS 0x08
#define DOC_CTRL_PROTECTION_ERROR 0x04
#define DOC_CTRL_SEQUENCE_ERROR 0x02
#define DOC_CTRL_FLASHREADY 0x01
/*
* Flash register : DOC_ASICMODE
*/
#define DOC_ASICMODE_RESET 0x00
#define DOC_ASICMODE_NORMAL 0x01
#define DOC_ASICMODE_POWERDOWN 0x02
#define DOC_ASICMODE_MDWREN 0x04
#define DOC_ASICMODE_BDETCT_RESET 0x08
#define DOC_ASICMODE_RSTIN_RESET 0x10
#define DOC_ASICMODE_RAM_WE 0x20
/*
* Flash register : DOC_ECCCONF0
*/
#define DOC_ECCCONF0_WRITE_MODE 0x0000
#define DOC_ECCCONF0_READ_MODE 0x8000
#define DOC_ECCCONF0_AUTO_ECC_ENABLE 0x4000
#define DOC_ECCCONF0_HAMMING_ENABLE 0x1000
#define DOC_ECCCONF0_BCH_ENABLE 0x0800
#define DOC_ECCCONF0_DATA_BYTES_MASK 0x07ff
/*
* Flash register : DOC_ECCCONF1
*/
#define DOC_ECCCONF1_BCH_SYNDROM_ERR 0x80
#define DOC_ECCCONF1_UNKOWN1 0x40
#define DOC_ECCCONF1_PAGE_IS_WRITTEN 0x20
#define DOC_ECCCONF1_UNKOWN3 0x10
#define DOC_ECCCONF1_HAMMING_BITS_MASK 0x0f
/*
* Flash register : DOC_PROTECTION
*/
#define DOC_PROTECT_FOUNDRY_OTP_LOCK 0x01
#define DOC_PROTECT_CUSTOMER_OTP_LOCK 0x02
#define DOC_PROTECT_LOCK_INPUT 0x04
#define DOC_PROTECT_STICKY_LOCK 0x08
#define DOC_PROTECT_PROTECTION_ENABLED 0x10
#define DOC_PROTECT_IPL_DOWNLOAD_LOCK 0x20
#define DOC_PROTECT_PROTECTION_ERROR 0x80
/*
* Flash register : DOC_DPS0_STATUS and DOC_DPS1_STATUS
*/
#define DOC_DPS_OTP_PROTECTED 0x01
#define DOC_DPS_READ_PROTECTED 0x02
#define DOC_DPS_WRITE_PROTECTED 0x04
#define DOC_DPS_HW_LOCK_ENABLED 0x08
#define DOC_DPS_KEY_OK 0x80
/*
* Flash register : DOC_CONFIGURATION
*/
#define DOC_CONF_IF_CFG 0x80
#define DOC_CONF_MAX_ID_MASK 0x30
#define DOC_CONF_VCCQ_3V 0x01
/*
* Flash register : DOC_READADDRESS
*/
#define DOC_READADDR_INC 0x8000
#define DOC_READADDR_ONE_BYTE 0x4000
#define DOC_READADDR_ADDR_MASK 0x1fff
/*
* Flash register : DOC_POWERMODE
*/
#define DOC_POWERDOWN_READY 0x80
/*
* Status of erase and write operation
*/
#define DOC_PLANES_STATUS_FAIL 0x01
#define DOC_PLANES_STATUS_PLANE0_KO 0x02
#define DOC_PLANES_STATUS_PLANE1_KO 0x04
/*
* DPS key management
*
* Each floor of docg3 has 2 protection areas: DPS0 and DPS1. These areas span
* across block boundaries, and define whether these blocks can be read or
* written.
* The definition is dynamically stored in page 0 of blocks (2,3) for DPS0, and
* page 0 of blocks (4,5) for DPS1.
*/
#define DOC_LAYOUT_DPS_KEY_LENGTH 8
/**
* struct docg3_cascade - Cascade of 1 to 4 docg3 chips
* @floors: floors (ie. one physical docg3 chip is one floor)
* @base: IO space to access all chips in the cascade
* @bch: the BCH correcting control structure
* @lock: lock to protect docg3 IO space from concurrent accesses
*/
struct docg3_cascade {
struct mtd_info *floors[DOC_MAX_NBFLOORS];
void __iomem *base;
struct bch_control *bch;
struct mutex lock;
};
/**
* struct docg3 - DiskOnChip driver private data
* @dev: the device currently under control
* @cascade: the cascade this device belongs to
* @device_id: number of the cascaded DoCG3 device (0, 1, 2 or 3)
* @if_cfg: if true, reads are on 16bits, else reads are on 8bits
* @reliable: if 0, docg3 in normal mode, if 1 docg3 in fast mode, if 2 in
* reliable mode
* Fast mode implies more errors than normal mode.
* Reliable mode implies that page 2*n and 2*n+1 are clones.
* @bbt: bad block table cache
* @oob_write_ofs: offset of the MTD where this OOB should belong (ie. in next
* page_write)
* @oob_autoecc: if 1, use only bytes 0-7, 15, and fill the others with HW ECC
* if 0, use all the 16 bytes.
* @oob_write_buf: prepared OOB for next page_write
* @debugfs_root: debugfs root node
*/
struct docg3 {
struct device *dev;
struct docg3_cascade *cascade;
unsigned int device_id:4;
unsigned int if_cfg:1;
unsigned int reliable:2;
int max_block;
u8 *bbt;
loff_t oob_write_ofs;
int oob_autoecc;
u8 oob_write_buf[DOC_LAYOUT_OOB_SIZE];
struct dentry *debugfs_root;
};
#define doc_err(fmt, arg...) dev_err(docg3->dev, (fmt), ## arg)
#define doc_info(fmt, arg...) dev_info(docg3->dev, (fmt), ## arg)
#define doc_dbg(fmt, arg...) dev_dbg(docg3->dev, (fmt), ## arg)
#define doc_vdbg(fmt, arg...) dev_vdbg(docg3->dev, (fmt), ## arg)
#define DEBUGFS_RO_ATTR(name, show_fct) \
static int name##_open(struct inode *inode, struct file *file) \
{ return single_open(file, show_fct, inode->i_private); } \
static const struct file_operations name##_fops = { \
.owner = THIS_MODULE, \
.open = name##_open, \
.llseek = seq_lseek, \
.read = seq_read, \
.release = single_release \
};
#endif
/*
* Trace events part
*/
#undef TRACE_SYSTEM
#define TRACE_SYSTEM docg3
#if !defined(_MTD_DOCG3_TRACE) || defined(TRACE_HEADER_MULTI_READ)
#define _MTD_DOCG3_TRACE
#include <linux/tracepoint.h>
TRACE_EVENT(docg3_io,
TP_PROTO(int op, int width, u16 reg, int val),
TP_ARGS(op, width, reg, val),
TP_STRUCT__entry(
__field(int, op)
__field(unsigned char, width)
__field(u16, reg)
__field(int, val)),
TP_fast_assign(
__entry->op = op;
__entry->width = width;
__entry->reg = reg;
__entry->val = val;),
TP_printk("docg3: %s%02d reg=%04x, val=%04x",
__entry->op ? "write" : "read", __entry->width,
__entry->reg, __entry->val)
);
#endif
/* This part must be outside protection */
#undef TRACE_INCLUDE_PATH
#undef TRACE_INCLUDE_FILE
#define TRACE_INCLUDE_PATH .
#define TRACE_INCLUDE_FILE docg3
#include <trace/define_trace.h>
+327
View File
@@ -0,0 +1,327 @@
/* Linux driver for Disk-On-Chip devices */
/* Probe routines common to all DoC devices */
/* (C) 1999 Machine Vision Holdings, Inc. */
/* (C) 1999-2003 David Woodhouse <dwmw2@infradead.org> */
/* DOC_PASSIVE_PROBE:
In order to ensure that the BIOS checksum is correct at boot time, and
hence that the onboard BIOS extension gets executed, the DiskOnChip
goes into reset mode when it is read sequentially: all registers
return 0xff until the chip is woken up again by writing to the
DOCControl register.
Unfortunately, this means that the probe for the DiskOnChip is unsafe,
because one of the first things it does is write to where it thinks
the DOCControl register should be - which may well be shared memory
for another device. I've had machines which lock up when this is
attempted. Hence the possibility to do a passive probe, which will fail
to detect a chip in reset mode, but is at least guaranteed not to lock
the machine.
If you have this problem, uncomment the following line:
#define DOC_PASSIVE_PROBE
*/
/* DOC_SINGLE_DRIVER:
Millennium driver has been merged into DOC2000 driver.
The old Millennium-only driver has been retained just in case there
are problems with the new code. If the combined driver doesn't work
for you, you can try the old one by undefining DOC_SINGLE_DRIVER
below and also enabling it in your configuration. If this fixes the
problems, please send a report to the MTD mailing list at
<linux-mtd@lists.infradead.org>.
*/
#define DOC_SINGLE_DRIVER
#include <linux/kernel.h>
#include <linux/module.h>
#include <asm/errno.h>
#include <asm/io.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/nand.h>
#include <linux/mtd/doc2000.h>
static unsigned long doc_config_location = CONFIG_MTD_DOCPROBE_ADDRESS;
module_param(doc_config_location, ulong, 0);
MODULE_PARM_DESC(doc_config_location, "Physical memory address at which to probe for DiskOnChip");
static unsigned long __initdata doc_locations[] = {
#if defined (__alpha__) || defined(__i386__) || defined(__x86_64__)
#ifdef CONFIG_MTD_DOCPROBE_HIGH
0xfffc8000, 0xfffca000, 0xfffcc000, 0xfffce000,
0xfffd0000, 0xfffd2000, 0xfffd4000, 0xfffd6000,
0xfffd8000, 0xfffda000, 0xfffdc000, 0xfffde000,
0xfffe0000, 0xfffe2000, 0xfffe4000, 0xfffe6000,
0xfffe8000, 0xfffea000, 0xfffec000, 0xfffee000,
#else /* CONFIG_MTD_DOCPROBE_HIGH */
0xc8000, 0xca000, 0xcc000, 0xce000,
0xd0000, 0xd2000, 0xd4000, 0xd6000,
0xd8000, 0xda000, 0xdc000, 0xde000,
0xe0000, 0xe2000, 0xe4000, 0xe6000,
0xe8000, 0xea000, 0xec000, 0xee000,
#endif /* CONFIG_MTD_DOCPROBE_HIGH */
#else
#warning Unknown architecture for DiskOnChip. No default probe locations defined
#endif
0xffffffff };
/* doccheck: Probe a given memory window to see if there's a DiskOnChip present */
static inline int __init doccheck(void __iomem *potential, unsigned long physadr)
{
void __iomem *window=potential;
unsigned char tmp, tmpb, tmpc, ChipID;
#ifndef DOC_PASSIVE_PROBE
unsigned char tmp2;
#endif
/* Routine copied from the Linux DOC driver */
#ifdef CONFIG_MTD_DOCPROBE_55AA
/* Check for 0x55 0xAA signature at beginning of window,
this is no longer true once we remove the IPL (for Millennium */
if (ReadDOC(window, Sig1) != 0x55 || ReadDOC(window, Sig2) != 0xaa)
return 0;
#endif /* CONFIG_MTD_DOCPROBE_55AA */
#ifndef DOC_PASSIVE_PROBE
/* It's not possible to cleanly detect the DiskOnChip - the
* bootup procedure will put the device into reset mode, and
* it's not possible to talk to it without actually writing
* to the DOCControl register. So we store the current contents
* of the DOCControl register's location, in case we later decide
* that it's not a DiskOnChip, and want to put it back how we
* found it.
*/
tmp2 = ReadDOC(window, DOCControl);
/* Reset the DiskOnChip ASIC */
WriteDOC(DOC_MODE_CLR_ERR | DOC_MODE_MDWREN | DOC_MODE_RESET,
window, DOCControl);
WriteDOC(DOC_MODE_CLR_ERR | DOC_MODE_MDWREN | DOC_MODE_RESET,
window, DOCControl);
/* Enable the DiskOnChip ASIC */
WriteDOC(DOC_MODE_CLR_ERR | DOC_MODE_MDWREN | DOC_MODE_NORMAL,
window, DOCControl);
WriteDOC(DOC_MODE_CLR_ERR | DOC_MODE_MDWREN | DOC_MODE_NORMAL,
window, DOCControl);
#endif /* !DOC_PASSIVE_PROBE */
/* We need to read the ChipID register four times. For some
newer DiskOnChip 2000 units, the first three reads will
return the DiskOnChip Millennium ident. Don't ask. */
ChipID = ReadDOC(window, ChipID);
switch (ChipID) {
case DOC_ChipID_Doc2k:
/* Check the TOGGLE bit in the ECC register */
tmp = ReadDOC(window, 2k_ECCStatus) & DOC_TOGGLE_BIT;
tmpb = ReadDOC(window, 2k_ECCStatus) & DOC_TOGGLE_BIT;
tmpc = ReadDOC(window, 2k_ECCStatus) & DOC_TOGGLE_BIT;
if (tmp != tmpb && tmp == tmpc)
return ChipID;
break;
case DOC_ChipID_DocMil:
/* Check for the new 2000 with Millennium ASIC */
ReadDOC(window, ChipID);
ReadDOC(window, ChipID);
if (ReadDOC(window, ChipID) != DOC_ChipID_DocMil)
ChipID = DOC_ChipID_Doc2kTSOP;
/* Check the TOGGLE bit in the ECC register */
tmp = ReadDOC(window, ECCConf) & DOC_TOGGLE_BIT;
tmpb = ReadDOC(window, ECCConf) & DOC_TOGGLE_BIT;
tmpc = ReadDOC(window, ECCConf) & DOC_TOGGLE_BIT;
if (tmp != tmpb && tmp == tmpc)
return ChipID;
break;
case DOC_ChipID_DocMilPlus16:
case DOC_ChipID_DocMilPlus32:
case 0:
/* Possible Millennium+, need to do more checks */
#ifndef DOC_PASSIVE_PROBE
/* Possibly release from power down mode */
for (tmp = 0; (tmp < 4); tmp++)
ReadDOC(window, Mplus_Power);
/* Reset the DiskOnChip ASIC */
tmp = DOC_MODE_RESET | DOC_MODE_MDWREN | DOC_MODE_RST_LAT |
DOC_MODE_BDECT;
WriteDOC(tmp, window, Mplus_DOCControl);
WriteDOC(~tmp, window, Mplus_CtrlConfirm);
mdelay(1);
/* Enable the DiskOnChip ASIC */
tmp = DOC_MODE_NORMAL | DOC_MODE_MDWREN | DOC_MODE_RST_LAT |
DOC_MODE_BDECT;
WriteDOC(tmp, window, Mplus_DOCControl);
WriteDOC(~tmp, window, Mplus_CtrlConfirm);
mdelay(1);
#endif /* !DOC_PASSIVE_PROBE */
ChipID = ReadDOC(window, ChipID);
switch (ChipID) {
case DOC_ChipID_DocMilPlus16:
case DOC_ChipID_DocMilPlus32:
/* Check the TOGGLE bit in the toggle register */
tmp = ReadDOC(window, Mplus_Toggle) & DOC_TOGGLE_BIT;
tmpb = ReadDOC(window, Mplus_Toggle) & DOC_TOGGLE_BIT;
tmpc = ReadDOC(window, Mplus_Toggle) & DOC_TOGGLE_BIT;
if (tmp != tmpb && tmp == tmpc)
return ChipID;
default:
break;
}
/* FALL TRHU */
default:
#ifdef CONFIG_MTD_DOCPROBE_55AA
printk(KERN_DEBUG "Possible DiskOnChip with unknown ChipID %2.2X found at 0x%lx\n",
ChipID, physadr);
#endif
#ifndef DOC_PASSIVE_PROBE
/* Put back the contents of the DOCControl register, in case it's not
* actually a DiskOnChip.
*/
WriteDOC(tmp2, window, DOCControl);
#endif
return 0;
}
printk(KERN_WARNING "DiskOnChip failed TOGGLE test, dropping.\n");
#ifndef DOC_PASSIVE_PROBE
/* Put back the contents of the DOCControl register: it's not a DiskOnChip */
WriteDOC(tmp2, window, DOCControl);
#endif
return 0;
}
static int docfound;
extern void DoC2k_init(struct mtd_info *);
extern void DoCMil_init(struct mtd_info *);
extern void DoCMilPlus_init(struct mtd_info *);
static void __init DoC_Probe(unsigned long physadr)
{
void __iomem *docptr;
struct DiskOnChip *this;
struct mtd_info *mtd;
int ChipID;
char namebuf[15];
char *name = namebuf;
void (*initroutine)(struct mtd_info *) = NULL;
docptr = ioremap(physadr, DOC_IOREMAP_LEN);
if (!docptr)
return;
if ((ChipID = doccheck(docptr, physadr))) {
if (ChipID == DOC_ChipID_Doc2kTSOP) {
/* Remove this at your own peril. The hardware driver works but nothing prevents you from erasing bad blocks */
printk(KERN_NOTICE "Refusing to drive DiskOnChip 2000 TSOP until Bad Block Table is correctly supported by INFTL\n");
iounmap(docptr);
return;
}
docfound = 1;
mtd = kzalloc(sizeof(struct DiskOnChip) + sizeof(struct mtd_info), GFP_KERNEL);
if (!mtd) {
printk(KERN_WARNING "Cannot allocate memory for data structures. Dropping.\n");
iounmap(docptr);
return;
}
this = (struct DiskOnChip *)(&mtd[1]);
mtd->priv = this;
this->virtadr = docptr;
this->physadr = physadr;
this->ChipID = ChipID;
sprintf(namebuf, "with ChipID %2.2X", ChipID);
switch(ChipID) {
case DOC_ChipID_Doc2kTSOP:
name="2000 TSOP";
initroutine = symbol_request(DoC2k_init);
break;
case DOC_ChipID_Doc2k:
name="2000";
initroutine = symbol_request(DoC2k_init);
break;
case DOC_ChipID_DocMil:
name="Millennium";
#ifdef DOC_SINGLE_DRIVER
initroutine = symbol_request(DoC2k_init);
#else
initroutine = symbol_request(DoCMil_init);
#endif /* DOC_SINGLE_DRIVER */
break;
case DOC_ChipID_DocMilPlus16:
case DOC_ChipID_DocMilPlus32:
name="MillenniumPlus";
initroutine = symbol_request(DoCMilPlus_init);
break;
}
if (initroutine) {
(*initroutine)(mtd);
symbol_put_addr(initroutine);
return;
}
printk(KERN_NOTICE "Cannot find driver for DiskOnChip %s at 0x%lX\n", name, physadr);
kfree(mtd);
}
iounmap(docptr);
}
/****************************************************************************
*
* Module stuff
*
****************************************************************************/
static int __init init_doc(void)
{
int i;
if (doc_config_location) {
printk(KERN_INFO "Using configured DiskOnChip probe address 0x%lx\n", doc_config_location);
DoC_Probe(doc_config_location);
} else {
for (i=0; (doc_locations[i] != 0xffffffff); i++) {
DoC_Probe(doc_locations[i]);
}
}
/* No banner message any more. Print a message if no DiskOnChip
found, so the user knows we at least tried. */
if (!docfound)
printk(KERN_INFO "No recognised DiskOnChip devices found\n");
return -EAGAIN;
}
module_init(init_doc);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>");
MODULE_DESCRIPTION("Probe code for DiskOnChip 2000 and Millennium devices");
+685
View File
@@ -0,0 +1,685 @@
/*
* MTD driver for the 28F160F3 Flash Memory (non-CFI) on LART.
*
* Author: Abraham vd Merwe <abraham@2d3d.co.za>
*
* Copyright (c) 2001, 2d3D, Inc.
*
* This code is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* References:
*
* [1] 3 Volt Fast Boot Block Flash Memory" Intel Datasheet
* - Order Number: 290644-005
* - January 2000
*
* [2] MTD internal API documentation
* - http://www.linux-mtd.infradead.org/
*
* Limitations:
*
* Even though this driver is written for 3 Volt Fast Boot
* Block Flash Memory, it is rather specific to LART. With
* Minor modifications, notably the without data/address line
* mangling and different bus settings, etc. it should be
* trivial to adapt to other platforms.
*
* If somebody would sponsor me a different board, I'll
* adapt the driver (:
*/
/* debugging */
//#define LART_DEBUG
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/init.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/partitions.h>
#ifndef CONFIG_SA1100_LART
#error This is for LART architecture only
#endif
static char module_name[] = "lart";
/*
* These values is specific to 28Fxxxx3 flash memory.
* See section 2.3.1 in "3 Volt Fast Boot Block Flash Memory" Intel Datasheet
*/
#define FLASH_BLOCKSIZE_PARAM (4096 * BUSWIDTH)
#define FLASH_NUMBLOCKS_16m_PARAM 8
#define FLASH_NUMBLOCKS_8m_PARAM 8
/*
* These values is specific to 28Fxxxx3 flash memory.
* See section 2.3.2 in "3 Volt Fast Boot Block Flash Memory" Intel Datasheet
*/
#define FLASH_BLOCKSIZE_MAIN (32768 * BUSWIDTH)
#define FLASH_NUMBLOCKS_16m_MAIN 31
#define FLASH_NUMBLOCKS_8m_MAIN 15
/*
* These values are specific to LART
*/
/* general */
#define BUSWIDTH 4 /* don't change this - a lot of the code _will_ break if you change this */
#define FLASH_OFFSET 0xe8000000 /* see linux/arch/arm/mach-sa1100/lart.c */
/* blob */
#define NUM_BLOB_BLOCKS FLASH_NUMBLOCKS_16m_PARAM
#define BLOB_START 0x00000000
#define BLOB_LEN (NUM_BLOB_BLOCKS * FLASH_BLOCKSIZE_PARAM)
/* kernel */
#define NUM_KERNEL_BLOCKS 7
#define KERNEL_START (BLOB_START + BLOB_LEN)
#define KERNEL_LEN (NUM_KERNEL_BLOCKS * FLASH_BLOCKSIZE_MAIN)
/* initial ramdisk */
#define NUM_INITRD_BLOCKS 24
#define INITRD_START (KERNEL_START + KERNEL_LEN)
#define INITRD_LEN (NUM_INITRD_BLOCKS * FLASH_BLOCKSIZE_MAIN)
/*
* See section 4.0 in "3 Volt Fast Boot Block Flash Memory" Intel Datasheet
*/
#define READ_ARRAY 0x00FF00FF /* Read Array/Reset */
#define READ_ID_CODES 0x00900090 /* Read Identifier Codes */
#define ERASE_SETUP 0x00200020 /* Block Erase */
#define ERASE_CONFIRM 0x00D000D0 /* Block Erase and Program Resume */
#define PGM_SETUP 0x00400040 /* Program */
#define STATUS_READ 0x00700070 /* Read Status Register */
#define STATUS_CLEAR 0x00500050 /* Clear Status Register */
#define STATUS_BUSY 0x00800080 /* Write State Machine Status (WSMS) */
#define STATUS_ERASE_ERR 0x00200020 /* Erase Status (ES) */
#define STATUS_PGM_ERR 0x00100010 /* Program Status (PS) */
/*
* See section 4.2 in "3 Volt Fast Boot Block Flash Memory" Intel Datasheet
*/
#define FLASH_MANUFACTURER 0x00890089
#define FLASH_DEVICE_8mbit_TOP 0x88f188f1
#define FLASH_DEVICE_8mbit_BOTTOM 0x88f288f2
#define FLASH_DEVICE_16mbit_TOP 0x88f388f3
#define FLASH_DEVICE_16mbit_BOTTOM 0x88f488f4
/***************************************************************************************************/
/*
* The data line mapping on LART is as follows:
*
* U2 CPU | U3 CPU
* -------------------
* 0 20 | 0 12
* 1 22 | 1 14
* 2 19 | 2 11
* 3 17 | 3 9
* 4 24 | 4 0
* 5 26 | 5 2
* 6 31 | 6 7
* 7 29 | 7 5
* 8 21 | 8 13
* 9 23 | 9 15
* 10 18 | 10 10
* 11 16 | 11 8
* 12 25 | 12 1
* 13 27 | 13 3
* 14 30 | 14 6
* 15 28 | 15 4
*/
/* Mangle data (x) */
#define DATA_TO_FLASH(x) \
( \
(((x) & 0x08009000) >> 11) + \
(((x) & 0x00002000) >> 10) + \
(((x) & 0x04004000) >> 8) + \
(((x) & 0x00000010) >> 4) + \
(((x) & 0x91000820) >> 3) + \
(((x) & 0x22080080) >> 2) + \
((x) & 0x40000400) + \
(((x) & 0x00040040) << 1) + \
(((x) & 0x00110000) << 4) + \
(((x) & 0x00220100) << 5) + \
(((x) & 0x00800208) << 6) + \
(((x) & 0x00400004) << 9) + \
(((x) & 0x00000001) << 12) + \
(((x) & 0x00000002) << 13) \
)
/* Unmangle data (x) */
#define FLASH_TO_DATA(x) \
( \
(((x) & 0x00010012) << 11) + \
(((x) & 0x00000008) << 10) + \
(((x) & 0x00040040) << 8) + \
(((x) & 0x00000001) << 4) + \
(((x) & 0x12200104) << 3) + \
(((x) & 0x08820020) << 2) + \
((x) & 0x40000400) + \
(((x) & 0x00080080) >> 1) + \
(((x) & 0x01100000) >> 4) + \
(((x) & 0x04402000) >> 5) + \
(((x) & 0x20008200) >> 6) + \
(((x) & 0x80000800) >> 9) + \
(((x) & 0x00001000) >> 12) + \
(((x) & 0x00004000) >> 13) \
)
/*
* The address line mapping on LART is as follows:
*
* U3 CPU | U2 CPU
* -------------------
* 0 2 | 0 2
* 1 3 | 1 3
* 2 9 | 2 9
* 3 13 | 3 8
* 4 8 | 4 7
* 5 12 | 5 6
* 6 11 | 6 5
* 7 10 | 7 4
* 8 4 | 8 10
* 9 5 | 9 11
* 10 6 | 10 12
* 11 7 | 11 13
*
* BOOT BLOCK BOUNDARY
*
* 12 15 | 12 15
* 13 14 | 13 14
* 14 16 | 14 16
*
* MAIN BLOCK BOUNDARY
*
* 15 17 | 15 18
* 16 18 | 16 17
* 17 20 | 17 20
* 18 19 | 18 19
* 19 21 | 19 21
*
* As we can see from above, the addresses aren't mangled across
* block boundaries, so we don't need to worry about address
* translations except for sending/reading commands during
* initialization
*/
/* Mangle address (x) on chip U2 */
#define ADDR_TO_FLASH_U2(x) \
( \
(((x) & 0x00000f00) >> 4) + \
(((x) & 0x00042000) << 1) + \
(((x) & 0x0009c003) << 2) + \
(((x) & 0x00021080) << 3) + \
(((x) & 0x00000010) << 4) + \
(((x) & 0x00000040) << 5) + \
(((x) & 0x00000024) << 7) + \
(((x) & 0x00000008) << 10) \
)
/* Unmangle address (x) on chip U2 */
#define FLASH_U2_TO_ADDR(x) \
( \
(((x) << 4) & 0x00000f00) + \
(((x) >> 1) & 0x00042000) + \
(((x) >> 2) & 0x0009c003) + \
(((x) >> 3) & 0x00021080) + \
(((x) >> 4) & 0x00000010) + \
(((x) >> 5) & 0x00000040) + \
(((x) >> 7) & 0x00000024) + \
(((x) >> 10) & 0x00000008) \
)
/* Mangle address (x) on chip U3 */
#define ADDR_TO_FLASH_U3(x) \
( \
(((x) & 0x00000080) >> 3) + \
(((x) & 0x00000040) >> 1) + \
(((x) & 0x00052020) << 1) + \
(((x) & 0x00084f03) << 2) + \
(((x) & 0x00029010) << 3) + \
(((x) & 0x00000008) << 5) + \
(((x) & 0x00000004) << 7) \
)
/* Unmangle address (x) on chip U3 */
#define FLASH_U3_TO_ADDR(x) \
( \
(((x) << 3) & 0x00000080) + \
(((x) << 1) & 0x00000040) + \
(((x) >> 1) & 0x00052020) + \
(((x) >> 2) & 0x00084f03) + \
(((x) >> 3) & 0x00029010) + \
(((x) >> 5) & 0x00000008) + \
(((x) >> 7) & 0x00000004) \
)
/***************************************************************************************************/
static __u8 read8 (__u32 offset)
{
volatile __u8 *data = (__u8 *) (FLASH_OFFSET + offset);
#ifdef LART_DEBUG
printk (KERN_DEBUG "%s(): 0x%.8x -> 0x%.2x\n", __func__, offset, *data);
#endif
return (*data);
}
static __u32 read32 (__u32 offset)
{
volatile __u32 *data = (__u32 *) (FLASH_OFFSET + offset);
#ifdef LART_DEBUG
printk (KERN_DEBUG "%s(): 0x%.8x -> 0x%.8x\n", __func__, offset, *data);
#endif
return (*data);
}
static void write32 (__u32 x,__u32 offset)
{
volatile __u32 *data = (__u32 *) (FLASH_OFFSET + offset);
*data = x;
#ifdef LART_DEBUG
printk (KERN_DEBUG "%s(): 0x%.8x <- 0x%.8x\n", __func__, offset, *data);
#endif
}
/***************************************************************************************************/
/*
* Probe for 16mbit flash memory on a LART board without doing
* too much damage. Since we need to write 1 dword to memory,
* we're f**cked if this happens to be DRAM since we can't
* restore the memory (otherwise we might exit Read Array mode).
*
* Returns 1 if we found 16mbit flash memory on LART, 0 otherwise.
*/
static int flash_probe (void)
{
__u32 manufacturer,devtype;
/* setup "Read Identifier Codes" mode */
write32 (DATA_TO_FLASH (READ_ID_CODES),0x00000000);
/* probe U2. U2/U3 returns the same data since the first 3
* address lines is mangled in the same way */
manufacturer = FLASH_TO_DATA (read32 (ADDR_TO_FLASH_U2 (0x00000000)));
devtype = FLASH_TO_DATA (read32 (ADDR_TO_FLASH_U2 (0x00000001)));
/* put the flash back into command mode */
write32 (DATA_TO_FLASH (READ_ARRAY),0x00000000);
return (manufacturer == FLASH_MANUFACTURER && (devtype == FLASH_DEVICE_16mbit_TOP || devtype == FLASH_DEVICE_16mbit_BOTTOM));
}
/*
* Erase one block of flash memory at offset ``offset'' which is any
* address within the block which should be erased.
*
* Returns 1 if successful, 0 otherwise.
*/
static inline int erase_block (__u32 offset)
{
__u32 status;
#ifdef LART_DEBUG
printk (KERN_DEBUG "%s(): 0x%.8x\n", __func__, offset);
#endif
/* erase and confirm */
write32 (DATA_TO_FLASH (ERASE_SETUP),offset);
write32 (DATA_TO_FLASH (ERASE_CONFIRM),offset);
/* wait for block erase to finish */
do
{
write32 (DATA_TO_FLASH (STATUS_READ),offset);
status = FLASH_TO_DATA (read32 (offset));
}
while ((~status & STATUS_BUSY) != 0);
/* put the flash back into command mode */
write32 (DATA_TO_FLASH (READ_ARRAY),offset);
/* was the erase successful? */
if ((status & STATUS_ERASE_ERR))
{
printk (KERN_WARNING "%s: erase error at address 0x%.8x.\n",module_name,offset);
return (0);
}
return (1);
}
static int flash_erase (struct mtd_info *mtd,struct erase_info *instr)
{
__u32 addr,len;
int i,first;
#ifdef LART_DEBUG
printk (KERN_DEBUG "%s(addr = 0x%.8x, len = %d)\n", __func__, instr->addr, instr->len);
#endif
/*
* check that both start and end of the requested erase are
* aligned with the erasesize at the appropriate addresses.
*
* skip all erase regions which are ended before the start of
* the requested erase. Actually, to save on the calculations,
* we skip to the first erase region which starts after the
* start of the requested erase, and then go back one.
*/
for (i = 0; i < mtd->numeraseregions && instr->addr >= mtd->eraseregions[i].offset; i++) ;
i--;
/*
* ok, now i is pointing at the erase region in which this
* erase request starts. Check the start of the requested
* erase range is aligned with the erase size which is in
* effect here.
*/
if (i < 0 || (instr->addr & (mtd->eraseregions[i].erasesize - 1)))
return -EINVAL;
/* Remember the erase region we start on */
first = i;
/*
* next, check that the end of the requested erase is aligned
* with the erase region at that address.
*
* as before, drop back one to point at the region in which
* the address actually falls
*/
for (; i < mtd->numeraseregions && instr->addr + instr->len >= mtd->eraseregions[i].offset; i++) ;
i--;
/* is the end aligned on a block boundary? */
if (i < 0 || ((instr->addr + instr->len) & (mtd->eraseregions[i].erasesize - 1)))
return -EINVAL;
addr = instr->addr;
len = instr->len;
i = first;
/* now erase those blocks */
while (len)
{
if (!erase_block (addr))
{
instr->state = MTD_ERASE_FAILED;
return (-EIO);
}
addr += mtd->eraseregions[i].erasesize;
len -= mtd->eraseregions[i].erasesize;
if (addr == mtd->eraseregions[i].offset + (mtd->eraseregions[i].erasesize * mtd->eraseregions[i].numblocks)) i++;
}
instr->state = MTD_ERASE_DONE;
mtd_erase_callback(instr);
return (0);
}
static int flash_read (struct mtd_info *mtd,loff_t from,size_t len,size_t *retlen,u_char *buf)
{
#ifdef LART_DEBUG
printk (KERN_DEBUG "%s(from = 0x%.8x, len = %d)\n", __func__, (__u32)from, len);
#endif
/* we always read len bytes */
*retlen = len;
/* first, we read bytes until we reach a dword boundary */
if (from & (BUSWIDTH - 1))
{
int gap = BUSWIDTH - (from & (BUSWIDTH - 1));
while (len && gap--) *buf++ = read8 (from++), len--;
}
/* now we read dwords until we reach a non-dword boundary */
while (len >= BUSWIDTH)
{
*((__u32 *) buf) = read32 (from);
buf += BUSWIDTH;
from += BUSWIDTH;
len -= BUSWIDTH;
}
/* top up the last unaligned bytes */
if (len & (BUSWIDTH - 1))
while (len--) *buf++ = read8 (from++);
return (0);
}
/*
* Write one dword ``x'' to flash memory at offset ``offset''. ``offset''
* must be 32 bits, i.e. it must be on a dword boundary.
*
* Returns 1 if successful, 0 otherwise.
*/
static inline int write_dword (__u32 offset,__u32 x)
{
__u32 status;
#ifdef LART_DEBUG
printk (KERN_DEBUG "%s(): 0x%.8x <- 0x%.8x\n", __func__, offset, x);
#endif
/* setup writing */
write32 (DATA_TO_FLASH (PGM_SETUP),offset);
/* write the data */
write32 (x,offset);
/* wait for the write to finish */
do
{
write32 (DATA_TO_FLASH (STATUS_READ),offset);
status = FLASH_TO_DATA (read32 (offset));
}
while ((~status & STATUS_BUSY) != 0);
/* put the flash back into command mode */
write32 (DATA_TO_FLASH (READ_ARRAY),offset);
/* was the write successful? */
if ((status & STATUS_PGM_ERR) || read32 (offset) != x)
{
printk (KERN_WARNING "%s: write error at address 0x%.8x.\n",module_name,offset);
return (0);
}
return (1);
}
static int flash_write (struct mtd_info *mtd,loff_t to,size_t len,size_t *retlen,const u_char *buf)
{
__u8 tmp[4];
int i,n;
#ifdef LART_DEBUG
printk (KERN_DEBUG "%s(to = 0x%.8x, len = %d)\n", __func__, (__u32)to, len);
#endif
/* sanity checks */
if (!len) return (0);
/* first, we write a 0xFF.... padded byte until we reach a dword boundary */
if (to & (BUSWIDTH - 1))
{
__u32 aligned = to & ~(BUSWIDTH - 1);
int gap = to - aligned;
i = n = 0;
while (gap--) tmp[i++] = 0xFF;
while (len && i < BUSWIDTH) tmp[i++] = buf[n++], len--;
while (i < BUSWIDTH) tmp[i++] = 0xFF;
if (!write_dword (aligned,*((__u32 *) tmp))) return (-EIO);
to += n;
buf += n;
*retlen += n;
}
/* now we write dwords until we reach a non-dword boundary */
while (len >= BUSWIDTH)
{
if (!write_dword (to,*((__u32 *) buf))) return (-EIO);
to += BUSWIDTH;
buf += BUSWIDTH;
*retlen += BUSWIDTH;
len -= BUSWIDTH;
}
/* top up the last unaligned bytes, padded with 0xFF.... */
if (len & (BUSWIDTH - 1))
{
i = n = 0;
while (len--) tmp[i++] = buf[n++];
while (i < BUSWIDTH) tmp[i++] = 0xFF;
if (!write_dword (to,*((__u32 *) tmp))) return (-EIO);
*retlen += n;
}
return (0);
}
/***************************************************************************************************/
static struct mtd_info mtd;
static struct mtd_erase_region_info erase_regions[] = {
/* parameter blocks */
{
.offset = 0x00000000,
.erasesize = FLASH_BLOCKSIZE_PARAM,
.numblocks = FLASH_NUMBLOCKS_16m_PARAM,
},
/* main blocks */
{
.offset = FLASH_BLOCKSIZE_PARAM * FLASH_NUMBLOCKS_16m_PARAM,
.erasesize = FLASH_BLOCKSIZE_MAIN,
.numblocks = FLASH_NUMBLOCKS_16m_MAIN,
}
};
static struct mtd_partition lart_partitions[] = {
/* blob */
{
.name = "blob",
.offset = BLOB_START,
.size = BLOB_LEN,
},
/* kernel */
{
.name = "kernel",
.offset = KERNEL_START, /* MTDPART_OFS_APPEND */
.size = KERNEL_LEN,
},
/* initial ramdisk / file system */
{
.name = "file system",
.offset = INITRD_START, /* MTDPART_OFS_APPEND */
.size = INITRD_LEN, /* MTDPART_SIZ_FULL */
}
};
#define NUM_PARTITIONS ARRAY_SIZE(lart_partitions)
static int __init lart_flash_init (void)
{
int result;
memset (&mtd,0,sizeof (mtd));
printk ("MTD driver for LART. Written by Abraham vd Merwe <abraham@2d3d.co.za>\n");
printk ("%s: Probing for 28F160x3 flash on LART...\n",module_name);
if (!flash_probe ())
{
printk (KERN_WARNING "%s: Found no LART compatible flash device\n",module_name);
return (-ENXIO);
}
printk ("%s: This looks like a LART board to me.\n",module_name);
mtd.name = module_name;
mtd.type = MTD_NORFLASH;
mtd.writesize = 1;
mtd.writebufsize = 4;
mtd.flags = MTD_CAP_NORFLASH;
mtd.size = FLASH_BLOCKSIZE_PARAM * FLASH_NUMBLOCKS_16m_PARAM + FLASH_BLOCKSIZE_MAIN * FLASH_NUMBLOCKS_16m_MAIN;
mtd.erasesize = FLASH_BLOCKSIZE_MAIN;
mtd.numeraseregions = ARRAY_SIZE(erase_regions);
mtd.eraseregions = erase_regions;
mtd._erase = flash_erase;
mtd._read = flash_read;
mtd._write = flash_write;
mtd.owner = THIS_MODULE;
#ifdef LART_DEBUG
printk (KERN_DEBUG
"mtd.name = %s\n"
"mtd.size = 0x%.8x (%uM)\n"
"mtd.erasesize = 0x%.8x (%uK)\n"
"mtd.numeraseregions = %d\n",
mtd.name,
mtd.size,mtd.size / (1024*1024),
mtd.erasesize,mtd.erasesize / 1024,
mtd.numeraseregions);
if (mtd.numeraseregions)
for (result = 0; result < mtd.numeraseregions; result++)
printk (KERN_DEBUG
"\n\n"
"mtd.eraseregions[%d].offset = 0x%.8x\n"
"mtd.eraseregions[%d].erasesize = 0x%.8x (%uK)\n"
"mtd.eraseregions[%d].numblocks = %d\n",
result,mtd.eraseregions[result].offset,
result,mtd.eraseregions[result].erasesize,mtd.eraseregions[result].erasesize / 1024,
result,mtd.eraseregions[result].numblocks);
printk ("\npartitions = %d\n", ARRAY_SIZE(lart_partitions));
for (result = 0; result < ARRAY_SIZE(lart_partitions); result++)
printk (KERN_DEBUG
"\n\n"
"lart_partitions[%d].name = %s\n"
"lart_partitions[%d].offset = 0x%.8x\n"
"lart_partitions[%d].size = 0x%.8x (%uK)\n",
result,lart_partitions[result].name,
result,lart_partitions[result].offset,
result,lart_partitions[result].size,lart_partitions[result].size / 1024);
#endif
result = mtd_device_register(&mtd, lart_partitions,
ARRAY_SIZE(lart_partitions));
return (result);
}
static void __exit lart_flash_exit (void)
{
mtd_device_unregister(&mtd);
}
module_init (lart_flash_init);
module_exit (lart_flash_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Abraham vd Merwe <abraham@2d3d.co.za>");
MODULE_DESCRIPTION("MTD driver for Intel 28F160F3 on LART board");
+981
View File
@@ -0,0 +1,981 @@
/*
* MTD SPI driver for ST M25Pxx (and similar) serial flash chips
*
* Author: Mike Lavender, mike@steroidmicros.com
*
* Copyright (c) 2005, Intec Automation Inc.
*
* Some parts are based on lart.c by Abraham Van Der Merwe
*
* Cleaned up and generalized based on mtd_dataflash.c
*
* This code is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
*/
#include <linux/init.h>
#include <linux/err.h>
#include <linux/errno.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/interrupt.h>
#include <linux/mutex.h>
#include <linux/math64.h>
#include <linux/slab.h>
#include <linux/sched.h>
#include <linux/mod_devicetable.h>
#include <linux/mtd/cfi.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/partitions.h>
#include <linux/of_platform.h>
#include <linux/spi/spi.h>
#include <linux/spi/flash.h>
/* Flash opcodes. */
#define OPCODE_WREN 0x06 /* Write enable */
#define OPCODE_RDSR 0x05 /* Read status register */
#define OPCODE_WRSR 0x01 /* Write status register 1 byte */
#define OPCODE_NORM_READ 0x03 /* Read data bytes (low frequency) */
#define OPCODE_FAST_READ 0x0b /* Read data bytes (high frequency) */
#define OPCODE_PP 0x02 /* Page program (up to 256 bytes) */
#define OPCODE_BE_4K 0x20 /* Erase 4KiB block */
#define OPCODE_BE_32K 0x52 /* Erase 32KiB block */
#define OPCODE_CHIP_ERASE 0xc7 /* Erase whole flash chip */
#define OPCODE_SE 0xd8 /* Sector erase (usually 64KiB) */
#define OPCODE_RDID 0x9f /* Read JEDEC ID */
/* Used for SST flashes only. */
#define OPCODE_BP 0x02 /* Byte program */
#define OPCODE_WRDI 0x04 /* Write disable */
#define OPCODE_AAI_WP 0xad /* Auto address increment word program */
/* Used for Macronix flashes only. */
#define OPCODE_EN4B 0xb7 /* Enter 4-byte mode */
#define OPCODE_EX4B 0xe9 /* Exit 4-byte mode */
/* Used for Spansion flashes only. */
#define OPCODE_BRWR 0x17 /* Bank register write */
/* Status Register bits. */
#define SR_WIP 1 /* Write in progress */
#define SR_WEL 2 /* Write enable latch */
/* meaning of other SR_* bits may differ between vendors */
#define SR_BP0 4 /* Block protect 0 */
#define SR_BP1 8 /* Block protect 1 */
#define SR_BP2 0x10 /* Block protect 2 */
#define SR_SRWD 0x80 /* SR write protect */
/* Define max times to check status register before we give up. */
#define MAX_READY_WAIT_JIFFIES (40 * HZ) /* M25P16 specs 40s max chip erase */
#define MAX_CMD_SIZE 5
#ifdef CONFIG_M25PXX_USE_FAST_READ
#define OPCODE_READ OPCODE_FAST_READ
#define FAST_READ_DUMMY_BYTE 1
#else
#define OPCODE_READ OPCODE_NORM_READ
#define FAST_READ_DUMMY_BYTE 0
#endif
#define JEDEC_MFR(_jedec_id) ((_jedec_id) >> 16)
/****************************************************************************/
struct m25p {
struct spi_device *spi;
struct mutex lock;
struct mtd_info mtd;
u16 page_size;
u16 addr_width;
u8 erase_opcode;
u8 *command;
};
static inline struct m25p *mtd_to_m25p(struct mtd_info *mtd)
{
return container_of(mtd, struct m25p, mtd);
}
/****************************************************************************/
/*
* Internal helper functions
*/
/*
* Read the status register, returning its value in the location
* Return the status register value.
* Returns negative if error occurred.
*/
static int read_sr(struct m25p *flash)
{
ssize_t retval;
u8 code = OPCODE_RDSR;
u8 val;
retval = spi_write_then_read(flash->spi, &code, 1, &val, 1);
if (retval < 0) {
dev_err(&flash->spi->dev, "error %d reading SR\n",
(int) retval);
return retval;
}
return val;
}
/*
* Write status register 1 byte
* Returns negative if error occurred.
*/
static int write_sr(struct m25p *flash, u8 val)
{
flash->command[0] = OPCODE_WRSR;
flash->command[1] = val;
return spi_write(flash->spi, flash->command, 2);
}
/*
* Set write enable latch with Write Enable command.
* Returns negative if error occurred.
*/
static inline int write_enable(struct m25p *flash)
{
u8 code = OPCODE_WREN;
return spi_write_then_read(flash->spi, &code, 1, NULL, 0);
}
/*
* Send write disble instruction to the chip.
*/
static inline int write_disable(struct m25p *flash)
{
u8 code = OPCODE_WRDI;
return spi_write_then_read(flash->spi, &code, 1, NULL, 0);
}
/*
* Enable/disable 4-byte addressing mode.
*/
static inline int set_4byte(struct m25p *flash, u32 jedec_id, int enable)
{
switch (JEDEC_MFR(jedec_id)) {
case CFI_MFR_MACRONIX:
flash->command[0] = enable ? OPCODE_EN4B : OPCODE_EX4B;
return spi_write(flash->spi, flash->command, 1);
default:
/* Spansion style */
flash->command[0] = OPCODE_BRWR;
flash->command[1] = enable << 7;
return spi_write(flash->spi, flash->command, 2);
}
}
/*
* Service routine to read status register until ready, or timeout occurs.
* Returns non-zero if error.
*/
static int wait_till_ready(struct m25p *flash)
{
unsigned long deadline;
int sr;
deadline = jiffies + MAX_READY_WAIT_JIFFIES;
do {
if ((sr = read_sr(flash)) < 0)
break;
else if (!(sr & SR_WIP))
return 0;
cond_resched();
} while (!time_after_eq(jiffies, deadline));
return 1;
}
/*
* Erase the whole flash memory
*
* Returns 0 if successful, non-zero otherwise.
*/
static int erase_chip(struct m25p *flash)
{
pr_debug("%s: %s %lldKiB\n", dev_name(&flash->spi->dev), __func__,
(long long)(flash->mtd.size >> 10));
/* Wait until finished previous write command. */
if (wait_till_ready(flash))
return 1;
/* Send write enable, then erase commands. */
write_enable(flash);
/* Set up command buffer. */
flash->command[0] = OPCODE_CHIP_ERASE;
spi_write(flash->spi, flash->command, 1);
return 0;
}
static void m25p_addr2cmd(struct m25p *flash, unsigned int addr, u8 *cmd)
{
/* opcode is in cmd[0] */
cmd[1] = addr >> (flash->addr_width * 8 - 8);
cmd[2] = addr >> (flash->addr_width * 8 - 16);
cmd[3] = addr >> (flash->addr_width * 8 - 24);
cmd[4] = addr >> (flash->addr_width * 8 - 32);
}
static int m25p_cmdsz(struct m25p *flash)
{
return 1 + flash->addr_width;
}
/*
* Erase one sector of flash memory at offset ``offset'' which is any
* address within the sector which should be erased.
*
* Returns 0 if successful, non-zero otherwise.
*/
static int erase_sector(struct m25p *flash, u32 offset)
{
pr_debug("%s: %s %dKiB at 0x%08x\n", dev_name(&flash->spi->dev),
__func__, flash->mtd.erasesize / 1024, offset);
/* Wait until finished previous write command. */
if (wait_till_ready(flash))
return 1;
/* Send write enable, then erase commands. */
write_enable(flash);
/* Set up command buffer. */
flash->command[0] = flash->erase_opcode;
m25p_addr2cmd(flash, offset, flash->command);
spi_write(flash->spi, flash->command, m25p_cmdsz(flash));
return 0;
}
/****************************************************************************/
/*
* MTD implementation
*/
/*
* Erase an address range on the flash chip. The address range may extend
* one or more erase sectors. Return an error is there is a problem erasing.
*/
static int m25p80_erase(struct mtd_info *mtd, struct erase_info *instr)
{
struct m25p *flash = mtd_to_m25p(mtd);
u32 addr,len;
uint32_t rem;
pr_debug("%s: %s at 0x%llx, len %lld\n", dev_name(&flash->spi->dev),
__func__, (long long)instr->addr,
(long long)instr->len);
div_u64_rem(instr->len, mtd->erasesize, &rem);
if (rem)
return -EINVAL;
addr = instr->addr;
len = instr->len;
mutex_lock(&flash->lock);
/* whole-chip erase? */
if (len == flash->mtd.size) {
if (erase_chip(flash)) {
instr->state = MTD_ERASE_FAILED;
mutex_unlock(&flash->lock);
return -EIO;
}
/* REVISIT in some cases we could speed up erasing large regions
* by using OPCODE_SE instead of OPCODE_BE_4K. We may have set up
* to use "small sector erase", but that's not always optimal.
*/
/* "sector"-at-a-time erase */
} else {
while (len) {
if (erase_sector(flash, addr)) {
instr->state = MTD_ERASE_FAILED;
mutex_unlock(&flash->lock);
return -EIO;
}
addr += mtd->erasesize;
len -= mtd->erasesize;
}
}
mutex_unlock(&flash->lock);
instr->state = MTD_ERASE_DONE;
mtd_erase_callback(instr);
return 0;
}
/*
* Read an address range from the flash chip. The address range
* may be any size provided it is within the physical boundaries.
*/
static int m25p80_read(struct mtd_info *mtd, loff_t from, size_t len,
size_t *retlen, u_char *buf)
{
struct m25p *flash = mtd_to_m25p(mtd);
struct spi_transfer t[2];
struct spi_message m;
pr_debug("%s: %s from 0x%08x, len %zd\n", dev_name(&flash->spi->dev),
__func__, (u32)from, len);
spi_message_init(&m);
memset(t, 0, (sizeof t));
/* NOTE:
* OPCODE_FAST_READ (if available) is faster.
* Should add 1 byte DUMMY_BYTE.
*/
t[0].tx_buf = flash->command;
t[0].len = m25p_cmdsz(flash) + FAST_READ_DUMMY_BYTE;
spi_message_add_tail(&t[0], &m);
t[1].rx_buf = buf;
t[1].len = len;
spi_message_add_tail(&t[1], &m);
mutex_lock(&flash->lock);
/* Wait till previous write/erase is done. */
if (wait_till_ready(flash)) {
/* REVISIT status return?? */
mutex_unlock(&flash->lock);
return 1;
}
/* FIXME switch to OPCODE_FAST_READ. It's required for higher
* clocks; and at this writing, every chip this driver handles
* supports that opcode.
*/
/* Set up the write data buffer. */
flash->command[0] = OPCODE_READ;
m25p_addr2cmd(flash, from, flash->command);
spi_sync(flash->spi, &m);
*retlen = m.actual_length - m25p_cmdsz(flash) - FAST_READ_DUMMY_BYTE;
mutex_unlock(&flash->lock);
return 0;
}
/*
* Write an address range to the flash chip. Data must be written in
* FLASH_PAGESIZE chunks. The address range may be any size provided
* it is within the physical boundaries.
*/
static int m25p80_write(struct mtd_info *mtd, loff_t to, size_t len,
size_t *retlen, const u_char *buf)
{
struct m25p *flash = mtd_to_m25p(mtd);
u32 page_offset, page_size;
struct spi_transfer t[2];
struct spi_message m;
pr_debug("%s: %s to 0x%08x, len %zd\n", dev_name(&flash->spi->dev),
__func__, (u32)to, len);
spi_message_init(&m);
memset(t, 0, (sizeof t));
t[0].tx_buf = flash->command;
t[0].len = m25p_cmdsz(flash);
spi_message_add_tail(&t[0], &m);
t[1].tx_buf = buf;
spi_message_add_tail(&t[1], &m);
mutex_lock(&flash->lock);
/* Wait until finished previous write command. */
if (wait_till_ready(flash)) {
mutex_unlock(&flash->lock);
return 1;
}
write_enable(flash);
/* Set up the opcode in the write buffer. */
flash->command[0] = OPCODE_PP;
m25p_addr2cmd(flash, to, flash->command);
page_offset = to & (flash->page_size - 1);
/* do all the bytes fit onto one page? */
if (page_offset + len <= flash->page_size) {
t[1].len = len;
spi_sync(flash->spi, &m);
*retlen = m.actual_length - m25p_cmdsz(flash);
} else {
u32 i;
/* the size of data remaining on the first page */
page_size = flash->page_size - page_offset;
t[1].len = page_size;
spi_sync(flash->spi, &m);
*retlen = m.actual_length - m25p_cmdsz(flash);
/* write everything in flash->page_size chunks */
for (i = page_size; i < len; i += page_size) {
page_size = len - i;
if (page_size > flash->page_size)
page_size = flash->page_size;
/* write the next page to flash */
m25p_addr2cmd(flash, to + i, flash->command);
t[1].tx_buf = buf + i;
t[1].len = page_size;
wait_till_ready(flash);
write_enable(flash);
spi_sync(flash->spi, &m);
*retlen += m.actual_length - m25p_cmdsz(flash);
}
}
mutex_unlock(&flash->lock);
return 0;
}
static int sst_write(struct mtd_info *mtd, loff_t to, size_t len,
size_t *retlen, const u_char *buf)
{
struct m25p *flash = mtd_to_m25p(mtd);
struct spi_transfer t[2];
struct spi_message m;
size_t actual;
int cmd_sz, ret;
pr_debug("%s: %s to 0x%08x, len %zd\n", dev_name(&flash->spi->dev),
__func__, (u32)to, len);
spi_message_init(&m);
memset(t, 0, (sizeof t));
t[0].tx_buf = flash->command;
t[0].len = m25p_cmdsz(flash);
spi_message_add_tail(&t[0], &m);
t[1].tx_buf = buf;
spi_message_add_tail(&t[1], &m);
mutex_lock(&flash->lock);
/* Wait until finished previous write command. */
ret = wait_till_ready(flash);
if (ret)
goto time_out;
write_enable(flash);
actual = to % 2;
/* Start write from odd address. */
if (actual) {
flash->command[0] = OPCODE_BP;
m25p_addr2cmd(flash, to, flash->command);
/* write one byte. */
t[1].len = 1;
spi_sync(flash->spi, &m);
ret = wait_till_ready(flash);
if (ret)
goto time_out;
*retlen += m.actual_length - m25p_cmdsz(flash);
}
to += actual;
flash->command[0] = OPCODE_AAI_WP;
m25p_addr2cmd(flash, to, flash->command);
/* Write out most of the data here. */
cmd_sz = m25p_cmdsz(flash);
for (; actual < len - 1; actual += 2) {
t[0].len = cmd_sz;
/* write two bytes. */
t[1].len = 2;
t[1].tx_buf = buf + actual;
spi_sync(flash->spi, &m);
ret = wait_till_ready(flash);
if (ret)
goto time_out;
*retlen += m.actual_length - cmd_sz;
cmd_sz = 1;
to += 2;
}
write_disable(flash);
ret = wait_till_ready(flash);
if (ret)
goto time_out;
/* Write out trailing byte if it exists. */
if (actual != len) {
write_enable(flash);
flash->command[0] = OPCODE_BP;
m25p_addr2cmd(flash, to, flash->command);
t[0].len = m25p_cmdsz(flash);
t[1].len = 1;
t[1].tx_buf = buf + actual;
spi_sync(flash->spi, &m);
ret = wait_till_ready(flash);
if (ret)
goto time_out;
*retlen += m.actual_length - m25p_cmdsz(flash);
write_disable(flash);
}
time_out:
mutex_unlock(&flash->lock);
return ret;
}
/****************************************************************************/
/*
* SPI device driver setup and teardown
*/
struct flash_info {
/* JEDEC id zero means "no ID" (most older chips); otherwise it has
* a high byte of zero plus three data bytes: the manufacturer id,
* then a two byte device id.
*/
u32 jedec_id;
u16 ext_id;
/* The size listed here is what works with OPCODE_SE, which isn't
* necessarily called a "sector" by the vendor.
*/
unsigned sector_size;
u16 n_sectors;
u16 page_size;
u16 addr_width;
u16 flags;
#define SECT_4K 0x01 /* OPCODE_BE_4K works uniformly */
#define M25P_NO_ERASE 0x02 /* No erase command needed */
};
#define INFO(_jedec_id, _ext_id, _sector_size, _n_sectors, _flags) \
((kernel_ulong_t)&(struct flash_info) { \
.jedec_id = (_jedec_id), \
.ext_id = (_ext_id), \
.sector_size = (_sector_size), \
.n_sectors = (_n_sectors), \
.page_size = 256, \
.flags = (_flags), \
})
#define CAT25_INFO(_sector_size, _n_sectors, _page_size, _addr_width) \
((kernel_ulong_t)&(struct flash_info) { \
.sector_size = (_sector_size), \
.n_sectors = (_n_sectors), \
.page_size = (_page_size), \
.addr_width = (_addr_width), \
.flags = M25P_NO_ERASE, \
})
/* NOTE: double check command sets and memory organization when you add
* more flash chips. This current list focusses on newer chips, which
* have been converging on command sets which including JEDEC ID.
*/
static const struct spi_device_id m25p_ids[] = {
/* Atmel -- some are (confusingly) marketed as "DataFlash" */
{ "at25fs010", INFO(0x1f6601, 0, 32 * 1024, 4, SECT_4K) },
{ "at25fs040", INFO(0x1f6604, 0, 64 * 1024, 8, SECT_4K) },
{ "at25df041a", INFO(0x1f4401, 0, 64 * 1024, 8, SECT_4K) },
{ "at25df321a", INFO(0x1f4701, 0, 64 * 1024, 64, SECT_4K) },
{ "at25df641", INFO(0x1f4800, 0, 64 * 1024, 128, SECT_4K) },
{ "at26f004", INFO(0x1f0400, 0, 64 * 1024, 8, SECT_4K) },
{ "at26df081a", INFO(0x1f4501, 0, 64 * 1024, 16, SECT_4K) },
{ "at26df161a", INFO(0x1f4601, 0, 64 * 1024, 32, SECT_4K) },
{ "at26df321", INFO(0x1f4700, 0, 64 * 1024, 64, SECT_4K) },
/* EON -- en25xxx */
{ "en25f32", INFO(0x1c3116, 0, 64 * 1024, 64, SECT_4K) },
{ "en25p32", INFO(0x1c2016, 0, 64 * 1024, 64, 0) },
{ "en25q32b", INFO(0x1c3016, 0, 64 * 1024, 64, 0) },
{ "en25p64", INFO(0x1c2017, 0, 64 * 1024, 128, 0) },
/* Intel/Numonyx -- xxxs33b */
{ "160s33b", INFO(0x898911, 0, 64 * 1024, 32, 0) },
{ "320s33b", INFO(0x898912, 0, 64 * 1024, 64, 0) },
{ "640s33b", INFO(0x898913, 0, 64 * 1024, 128, 0) },
/* Macronix */
{ "mx25l4005a", INFO(0xc22013, 0, 64 * 1024, 8, SECT_4K) },
{ "mx25l8005", INFO(0xc22014, 0, 64 * 1024, 16, 0) },
{ "mx25l1606e", INFO(0xc22015, 0, 64 * 1024, 32, SECT_4K) },
{ "mx25l3205d", INFO(0xc22016, 0, 64 * 1024, 64, 0) },
{ "mx25l6405d", INFO(0xc22017, 0, 64 * 1024, 128, 0) },
{ "mx25l12805d", INFO(0xc22018, 0, 64 * 1024, 256, 0) },
{ "mx25l12855e", INFO(0xc22618, 0, 64 * 1024, 256, 0) },
{ "mx25l25635e", INFO(0xc22019, 0, 64 * 1024, 512, 0) },
{ "mx25l25655e", INFO(0xc22619, 0, 64 * 1024, 512, 0) },
/* Spansion -- single (large) sector size only, at least
* for the chips listed here (without boot sectors).
*/
{ "s25sl004a", INFO(0x010212, 0, 64 * 1024, 8, 0) },
{ "s25sl008a", INFO(0x010213, 0, 64 * 1024, 16, 0) },
{ "s25sl016a", INFO(0x010214, 0, 64 * 1024, 32, 0) },
{ "s25sl032a", INFO(0x010215, 0, 64 * 1024, 64, 0) },
{ "s25sl032p", INFO(0x010215, 0x4d00, 64 * 1024, 64, SECT_4K) },
{ "s25sl064a", INFO(0x010216, 0, 64 * 1024, 128, 0) },
{ "s25fl256s0", INFO(0x010219, 0x4d00, 256 * 1024, 128, 0) },
{ "s25fl256s1", INFO(0x010219, 0x4d01, 64 * 1024, 512, 0) },
{ "s25fl512s", INFO(0x010220, 0x4d00, 256 * 1024, 256, 0) },
{ "s70fl01gs", INFO(0x010221, 0x4d00, 256 * 1024, 256, 0) },
{ "s25sl12800", INFO(0x012018, 0x0300, 256 * 1024, 64, 0) },
{ "s25sl12801", INFO(0x012018, 0x0301, 64 * 1024, 256, 0) },
{ "s25fl129p0", INFO(0x012018, 0x4d00, 256 * 1024, 64, 0) },
{ "s25fl129p1", INFO(0x012018, 0x4d01, 64 * 1024, 256, 0) },
{ "s25fl016k", INFO(0xef4015, 0, 64 * 1024, 32, SECT_4K) },
{ "s25fl064k", INFO(0xef4017, 0, 64 * 1024, 128, SECT_4K) },
/* SST -- large erase sizes are "overlays", "sectors" are 4K */
{ "sst25vf040b", INFO(0xbf258d, 0, 64 * 1024, 8, SECT_4K) },
{ "sst25vf080b", INFO(0xbf258e, 0, 64 * 1024, 16, SECT_4K) },
{ "sst25vf016b", INFO(0xbf2541, 0, 64 * 1024, 32, SECT_4K) },
{ "sst25vf032b", INFO(0xbf254a, 0, 64 * 1024, 64, SECT_4K) },
{ "sst25wf512", INFO(0xbf2501, 0, 64 * 1024, 1, SECT_4K) },
{ "sst25wf010", INFO(0xbf2502, 0, 64 * 1024, 2, SECT_4K) },
{ "sst25wf020", INFO(0xbf2503, 0, 64 * 1024, 4, SECT_4K) },
{ "sst25wf040", INFO(0xbf2504, 0, 64 * 1024, 8, SECT_4K) },
/* ST Microelectronics -- newer production may have feature updates */
{ "m25p05", INFO(0x202010, 0, 32 * 1024, 2, 0) },
{ "m25p10", INFO(0x202011, 0, 32 * 1024, 4, 0) },
{ "m25p20", INFO(0x202012, 0, 64 * 1024, 4, 0) },
{ "m25p40", INFO(0x202013, 0, 64 * 1024, 8, 0) },
{ "m25p80", INFO(0x202014, 0, 64 * 1024, 16, 0) },
{ "m25p16", INFO(0x202015, 0, 64 * 1024, 32, 0) },
{ "m25p32", INFO(0x202016, 0, 64 * 1024, 64, 0) },
{ "m25p64", INFO(0x202017, 0, 64 * 1024, 128, 0) },
{ "m25p128", INFO(0x202018, 0, 256 * 1024, 64, 0) },
{ "m25p05-nonjedec", INFO(0, 0, 32 * 1024, 2, 0) },
{ "m25p10-nonjedec", INFO(0, 0, 32 * 1024, 4, 0) },
{ "m25p20-nonjedec", INFO(0, 0, 64 * 1024, 4, 0) },
{ "m25p40-nonjedec", INFO(0, 0, 64 * 1024, 8, 0) },
{ "m25p80-nonjedec", INFO(0, 0, 64 * 1024, 16, 0) },
{ "m25p16-nonjedec", INFO(0, 0, 64 * 1024, 32, 0) },
{ "m25p32-nonjedec", INFO(0, 0, 64 * 1024, 64, 0) },
{ "m25p64-nonjedec", INFO(0, 0, 64 * 1024, 128, 0) },
{ "m25p128-nonjedec", INFO(0, 0, 256 * 1024, 64, 0) },
{ "m45pe10", INFO(0x204011, 0, 64 * 1024, 2, 0) },
{ "m45pe80", INFO(0x204014, 0, 64 * 1024, 16, 0) },
{ "m45pe16", INFO(0x204015, 0, 64 * 1024, 32, 0) },
{ "m25pe80", INFO(0x208014, 0, 64 * 1024, 16, 0) },
{ "m25pe16", INFO(0x208015, 0, 64 * 1024, 32, SECT_4K) },
{ "m25px32", INFO(0x207116, 0, 64 * 1024, 64, SECT_4K) },
{ "m25px32-s0", INFO(0x207316, 0, 64 * 1024, 64, SECT_4K) },
{ "m25px32-s1", INFO(0x206316, 0, 64 * 1024, 64, SECT_4K) },
{ "m25px64", INFO(0x207117, 0, 64 * 1024, 128, 0) },
/* Winbond -- w25x "blocks" are 64K, "sectors" are 4KiB */
{ "w25x10", INFO(0xef3011, 0, 64 * 1024, 2, SECT_4K) },
{ "w25x20", INFO(0xef3012, 0, 64 * 1024, 4, SECT_4K) },
{ "w25x40", INFO(0xef3013, 0, 64 * 1024, 8, SECT_4K) },
{ "w25x80", INFO(0xef3014, 0, 64 * 1024, 16, SECT_4K) },
{ "w25x16", INFO(0xef3015, 0, 64 * 1024, 32, SECT_4K) },
{ "w25x32", INFO(0xef3016, 0, 64 * 1024, 64, SECT_4K) },
{ "w25q32", INFO(0xef4016, 0, 64 * 1024, 64, SECT_4K) },
{ "w25x64", INFO(0xef3017, 0, 64 * 1024, 128, SECT_4K) },
{ "w25q64", INFO(0xef4017, 0, 64 * 1024, 128, SECT_4K) },
/* Catalyst / On Semiconductor -- non-JEDEC */
{ "cat25c11", CAT25_INFO( 16, 8, 16, 1) },
{ "cat25c03", CAT25_INFO( 32, 8, 16, 2) },
{ "cat25c09", CAT25_INFO( 128, 8, 32, 2) },
{ "cat25c17", CAT25_INFO( 256, 8, 32, 2) },
{ "cat25128", CAT25_INFO(2048, 8, 64, 2) },
{ },
};
MODULE_DEVICE_TABLE(spi, m25p_ids);
static const struct spi_device_id *__devinit jedec_probe(struct spi_device *spi)
{
int tmp;
u8 code = OPCODE_RDID;
u8 id[5];
u32 jedec;
u16 ext_jedec;
struct flash_info *info;
/* JEDEC also defines an optional "extended device information"
* string for after vendor-specific data, after the three bytes
* we use here. Supporting some chips might require using it.
*/
tmp = spi_write_then_read(spi, &code, 1, id, 5);
if (tmp < 0) {
pr_debug("%s: error %d reading JEDEC ID\n",
dev_name(&spi->dev), tmp);
return ERR_PTR(tmp);
}
jedec = id[0];
jedec = jedec << 8;
jedec |= id[1];
jedec = jedec << 8;
jedec |= id[2];
ext_jedec = id[3] << 8 | id[4];
for (tmp = 0; tmp < ARRAY_SIZE(m25p_ids) - 1; tmp++) {
info = (void *)m25p_ids[tmp].driver_data;
if (info->jedec_id == jedec) {
if (info->ext_id != 0 && info->ext_id != ext_jedec)
continue;
return &m25p_ids[tmp];
}
}
dev_err(&spi->dev, "unrecognized JEDEC id %06x\n", jedec);
return ERR_PTR(-ENODEV);
}
/*
* board specific setup should have ensured the SPI clock used here
* matches what the READ command supports, at least until this driver
* understands FAST_READ (for clocks over 25 MHz).
*/
static int __devinit m25p_probe(struct spi_device *spi)
{
const struct spi_device_id *id = spi_get_device_id(spi);
struct flash_platform_data *data;
struct m25p *flash;
struct flash_info *info;
unsigned i;
struct mtd_part_parser_data ppdata;
#ifdef CONFIG_MTD_OF_PARTS
if (!of_device_is_available(spi->dev.of_node))
return -ENODEV;
#endif
/* Platform data helps sort out which chip type we have, as
* well as how this board partitions it. If we don't have
* a chip ID, try the JEDEC id commands; they'll work for most
* newer chips, even if we don't recognize the particular chip.
*/
data = spi->dev.platform_data;
if (data && data->type) {
const struct spi_device_id *plat_id;
for (i = 0; i < ARRAY_SIZE(m25p_ids) - 1; i++) {
plat_id = &m25p_ids[i];
if (strcmp(data->type, plat_id->name))
continue;
break;
}
if (i < ARRAY_SIZE(m25p_ids) - 1)
id = plat_id;
else
dev_warn(&spi->dev, "unrecognized id %s\n", data->type);
}
info = (void *)id->driver_data;
if (info->jedec_id) {
const struct spi_device_id *jid;
jid = jedec_probe(spi);
if (IS_ERR(jid)) {
return PTR_ERR(jid);
} else if (jid != id) {
/*
* JEDEC knows better, so overwrite platform ID. We
* can't trust partitions any longer, but we'll let
* mtd apply them anyway, since some partitions may be
* marked read-only, and we don't want to lose that
* information, even if it's not 100% accurate.
*/
dev_warn(&spi->dev, "found %s, expected %s\n",
jid->name, id->name);
id = jid;
info = (void *)jid->driver_data;
}
}
flash = kzalloc(sizeof *flash, GFP_KERNEL);
if (!flash)
return -ENOMEM;
flash->command = kmalloc(MAX_CMD_SIZE + FAST_READ_DUMMY_BYTE, GFP_KERNEL);
if (!flash->command) {
kfree(flash);
return -ENOMEM;
}
flash->spi = spi;
mutex_init(&flash->lock);
dev_set_drvdata(&spi->dev, flash);
/*
* Atmel, SST and Intel/Numonyx serial flash tend to power
* up with the software protection bits set
*/
if (JEDEC_MFR(info->jedec_id) == CFI_MFR_ATMEL ||
JEDEC_MFR(info->jedec_id) == CFI_MFR_INTEL ||
JEDEC_MFR(info->jedec_id) == CFI_MFR_SST) {
write_enable(flash);
write_sr(flash, 0);
}
if (data && data->name)
flash->mtd.name = data->name;
else
flash->mtd.name = dev_name(&spi->dev);
flash->mtd.type = MTD_NORFLASH;
flash->mtd.writesize = 1;
flash->mtd.flags = MTD_CAP_NORFLASH;
flash->mtd.size = info->sector_size * info->n_sectors;
flash->mtd._erase = m25p80_erase;
flash->mtd._read = m25p80_read;
/* sst flash chips use AAI word program */
if (JEDEC_MFR(info->jedec_id) == CFI_MFR_SST)
flash->mtd._write = sst_write;
else
flash->mtd._write = m25p80_write;
/* prefer "small sector" erase if possible */
if (info->flags & SECT_4K) {
flash->erase_opcode = OPCODE_BE_4K;
flash->mtd.erasesize = 4096;
} else {
flash->erase_opcode = OPCODE_SE;
flash->mtd.erasesize = info->sector_size;
}
if (info->flags & M25P_NO_ERASE)
flash->mtd.flags |= MTD_NO_ERASE;
ppdata.of_node = spi->dev.of_node;
flash->mtd.dev.parent = &spi->dev;
flash->page_size = info->page_size;
flash->mtd.writebufsize = flash->page_size;
if (info->addr_width)
flash->addr_width = info->addr_width;
else {
/* enable 4-byte addressing if the device exceeds 16MiB */
if (flash->mtd.size > 0x1000000) {
flash->addr_width = 4;
set_4byte(flash, info->jedec_id, 1);
} else
flash->addr_width = 3;
}
dev_info(&spi->dev, "%s (%lld Kbytes)\n", id->name,
(long long)flash->mtd.size >> 10);
pr_debug("mtd .name = %s, .size = 0x%llx (%lldMiB) "
".erasesize = 0x%.8x (%uKiB) .numeraseregions = %d\n",
flash->mtd.name,
(long long)flash->mtd.size, (long long)(flash->mtd.size >> 20),
flash->mtd.erasesize, flash->mtd.erasesize / 1024,
flash->mtd.numeraseregions);
if (flash->mtd.numeraseregions)
for (i = 0; i < flash->mtd.numeraseregions; i++)
pr_debug("mtd.eraseregions[%d] = { .offset = 0x%llx, "
".erasesize = 0x%.8x (%uKiB), "
".numblocks = %d }\n",
i, (long long)flash->mtd.eraseregions[i].offset,
flash->mtd.eraseregions[i].erasesize,
flash->mtd.eraseregions[i].erasesize / 1024,
flash->mtd.eraseregions[i].numblocks);
/* partitions should match sector boundaries; and it may be good to
* use readonly partitions for writeprotected sectors (BP2..BP0).
*/
return mtd_device_parse_register(&flash->mtd, NULL, &ppdata,
data ? data->parts : NULL,
data ? data->nr_parts : 0);
}
static int __devexit m25p_remove(struct spi_device *spi)
{
struct m25p *flash = dev_get_drvdata(&spi->dev);
int status;
/* Clean up MTD stuff. */
status = mtd_device_unregister(&flash->mtd);
if (status == 0) {
kfree(flash->command);
kfree(flash);
}
return 0;
}
static struct spi_driver m25p80_driver = {
.driver = {
.name = "m25p80",
.owner = THIS_MODULE,
},
.id_table = m25p_ids,
.probe = m25p_probe,
.remove = __devexit_p(m25p_remove),
/* REVISIT: many of these chips have deep power-down modes, which
* should clearly be entered on suspend() to minimize power use.
* And also when they're otherwise idle...
*/
};
module_spi_driver(m25p80_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Mike Lavender");
MODULE_DESCRIPTION("MTD SPI driver for ST M25Pxx flash chips");
+311
View File
@@ -0,0 +1,311 @@
/*
* Copyright (c) 2001 Maciej W. Rozycki
*
* 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.
*/
#include <linux/init.h>
#include <linux/ioport.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mtd/mtd.h>
#include <linux/slab.h>
#include <linux/types.h>
#include <asm/addrspace.h>
#include <asm/bootinfo.h>
#include <asm/dec/ioasic_addrs.h>
#include <asm/dec/kn02.h>
#include <asm/dec/kn03.h>
#include <asm/io.h>
#include <asm/paccess.h>
#include "ms02-nv.h"
static char version[] __initdata =
"ms02-nv.c: v.1.0.0 13 Aug 2001 Maciej W. Rozycki.\n";
MODULE_AUTHOR("Maciej W. Rozycki <macro@linux-mips.org>");
MODULE_DESCRIPTION("DEC MS02-NV NVRAM module driver");
MODULE_LICENSE("GPL");
/*
* Addresses we probe for an MS02-NV at. Modules may be located
* at any 8MiB boundary within a 0MiB up to 112MiB range or at any 32MiB
* boundary within a 0MiB up to 448MiB range. We don't support a module
* at 0MiB, though.
*/
static ulong ms02nv_addrs[] __initdata = {
0x07000000, 0x06800000, 0x06000000, 0x05800000, 0x05000000,
0x04800000, 0x04000000, 0x03800000, 0x03000000, 0x02800000,
0x02000000, 0x01800000, 0x01000000, 0x00800000
};
static const char ms02nv_name[] = "DEC MS02-NV NVRAM";
static const char ms02nv_res_diag_ram[] = "Diagnostic RAM";
static const char ms02nv_res_user_ram[] = "General-purpose RAM";
static const char ms02nv_res_csr[] = "Control and status register";
static struct mtd_info *root_ms02nv_mtd;
static int ms02nv_read(struct mtd_info *mtd, loff_t from,
size_t len, size_t *retlen, u_char *buf)
{
struct ms02nv_private *mp = mtd->priv;
memcpy(buf, mp->uaddr + from, len);
*retlen = len;
return 0;
}
static int ms02nv_write(struct mtd_info *mtd, loff_t to,
size_t len, size_t *retlen, const u_char *buf)
{
struct ms02nv_private *mp = mtd->priv;
memcpy(mp->uaddr + to, buf, len);
*retlen = len;
return 0;
}
static inline uint ms02nv_probe_one(ulong addr)
{
ms02nv_uint *ms02nv_diagp;
ms02nv_uint *ms02nv_magicp;
uint ms02nv_diag;
uint ms02nv_magic;
size_t size;
int err;
/*
* The firmware writes MS02NV_ID at MS02NV_MAGIC and also
* a diagnostic status at MS02NV_DIAG.
*/
ms02nv_diagp = (ms02nv_uint *)(CKSEG1ADDR(addr + MS02NV_DIAG));
ms02nv_magicp = (ms02nv_uint *)(CKSEG1ADDR(addr + MS02NV_MAGIC));
err = get_dbe(ms02nv_magic, ms02nv_magicp);
if (err)
return 0;
if (ms02nv_magic != MS02NV_ID)
return 0;
ms02nv_diag = *ms02nv_diagp;
size = (ms02nv_diag & MS02NV_DIAG_SIZE_MASK) << MS02NV_DIAG_SIZE_SHIFT;
if (size > MS02NV_CSR)
size = MS02NV_CSR;
return size;
}
static int __init ms02nv_init_one(ulong addr)
{
struct mtd_info *mtd;
struct ms02nv_private *mp;
struct resource *mod_res;
struct resource *diag_res;
struct resource *user_res;
struct resource *csr_res;
ulong fixaddr;
size_t size, fixsize;
static int version_printed;
int ret = -ENODEV;
/* The module decodes 8MiB of address space. */
mod_res = kzalloc(sizeof(*mod_res), GFP_KERNEL);
if (!mod_res)
return -ENOMEM;
mod_res->name = ms02nv_name;
mod_res->start = addr;
mod_res->end = addr + MS02NV_SLOT_SIZE - 1;
mod_res->flags = IORESOURCE_MEM | IORESOURCE_BUSY;
if (request_resource(&iomem_resource, mod_res) < 0)
goto err_out_mod_res;
size = ms02nv_probe_one(addr);
if (!size)
goto err_out_mod_res_rel;
if (!version_printed) {
printk(KERN_INFO "%s", version);
version_printed = 1;
}
ret = -ENOMEM;
mtd = kzalloc(sizeof(*mtd), GFP_KERNEL);
if (!mtd)
goto err_out_mod_res_rel;
mp = kzalloc(sizeof(*mp), GFP_KERNEL);
if (!mp)
goto err_out_mtd;
mtd->priv = mp;
mp->resource.module = mod_res;
/* Firmware's diagnostic NVRAM area. */
diag_res = kzalloc(sizeof(*diag_res), GFP_KERNEL);
if (!diag_res)
goto err_out_mp;
diag_res->name = ms02nv_res_diag_ram;
diag_res->start = addr;
diag_res->end = addr + MS02NV_RAM - 1;
diag_res->flags = IORESOURCE_BUSY;
request_resource(mod_res, diag_res);
mp->resource.diag_ram = diag_res;
/* User-available general-purpose NVRAM area. */
user_res = kzalloc(sizeof(*user_res), GFP_KERNEL);
if (!user_res)
goto err_out_diag_res;
user_res->name = ms02nv_res_user_ram;
user_res->start = addr + MS02NV_RAM;
user_res->end = addr + size - 1;
user_res->flags = IORESOURCE_BUSY;
request_resource(mod_res, user_res);
mp->resource.user_ram = user_res;
/* Control and status register. */
csr_res = kzalloc(sizeof(*csr_res), GFP_KERNEL);
if (!csr_res)
goto err_out_user_res;
csr_res->name = ms02nv_res_csr;
csr_res->start = addr + MS02NV_CSR;
csr_res->end = addr + MS02NV_CSR + 3;
csr_res->flags = IORESOURCE_BUSY;
request_resource(mod_res, csr_res);
mp->resource.csr = csr_res;
mp->addr = phys_to_virt(addr);
mp->size = size;
/*
* Hide the firmware's diagnostic area. It may get destroyed
* upon a reboot. Take paging into account for mapping support.
*/
fixaddr = (addr + MS02NV_RAM + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1);
fixsize = (size - (fixaddr - addr)) & ~(PAGE_SIZE - 1);
mp->uaddr = phys_to_virt(fixaddr);
mtd->type = MTD_RAM;
mtd->flags = MTD_CAP_RAM;
mtd->size = fixsize;
mtd->name = (char *)ms02nv_name;
mtd->owner = THIS_MODULE;
mtd->_read = ms02nv_read;
mtd->_write = ms02nv_write;
mtd->writesize = 1;
ret = -EIO;
if (mtd_device_register(mtd, NULL, 0)) {
printk(KERN_ERR
"ms02-nv: Unable to register MTD device, aborting!\n");
goto err_out_csr_res;
}
printk(KERN_INFO "mtd%d: %s at 0x%08lx, size %zuMiB.\n",
mtd->index, ms02nv_name, addr, size >> 20);
mp->next = root_ms02nv_mtd;
root_ms02nv_mtd = mtd;
return 0;
err_out_csr_res:
release_resource(csr_res);
kfree(csr_res);
err_out_user_res:
release_resource(user_res);
kfree(user_res);
err_out_diag_res:
release_resource(diag_res);
kfree(diag_res);
err_out_mp:
kfree(mp);
err_out_mtd:
kfree(mtd);
err_out_mod_res_rel:
release_resource(mod_res);
err_out_mod_res:
kfree(mod_res);
return ret;
}
static void __exit ms02nv_remove_one(void)
{
struct mtd_info *mtd = root_ms02nv_mtd;
struct ms02nv_private *mp = mtd->priv;
root_ms02nv_mtd = mp->next;
mtd_device_unregister(mtd);
release_resource(mp->resource.csr);
kfree(mp->resource.csr);
release_resource(mp->resource.user_ram);
kfree(mp->resource.user_ram);
release_resource(mp->resource.diag_ram);
kfree(mp->resource.diag_ram);
release_resource(mp->resource.module);
kfree(mp->resource.module);
kfree(mp);
kfree(mtd);
}
static int __init ms02nv_init(void)
{
volatile u32 *csr;
uint stride = 0;
int count = 0;
int i;
switch (mips_machtype) {
case MACH_DS5000_200:
csr = (volatile u32 *)CKSEG1ADDR(KN02_SLOT_BASE + KN02_CSR);
if (*csr & KN02_CSR_BNK32M)
stride = 2;
break;
case MACH_DS5000_2X0:
case MACH_DS5900:
csr = (volatile u32 *)CKSEG1ADDR(KN03_SLOT_BASE + IOASIC_MCR);
if (*csr & KN03_MCR_BNK32M)
stride = 2;
break;
default:
return -ENODEV;
break;
}
for (i = 0; i < ARRAY_SIZE(ms02nv_addrs); i++)
if (!ms02nv_init_one(ms02nv_addrs[i] << stride))
count++;
return (count > 0) ? 0 : -ENODEV;
}
static void __exit ms02nv_cleanup(void)
{
while (root_ms02nv_mtd)
ms02nv_remove_one();
}
module_init(ms02nv_init);
module_exit(ms02nv_cleanup);
+105
View File
@@ -0,0 +1,105 @@
/*
* Copyright (c) 2001, 2003 Maciej W. Rozycki
*
* DEC MS02-NV (54-20948-01) battery backed-up NVRAM module for
* DECstation/DECsystem 5000/2x0 and DECsystem 5900 and 5900/260
* systems.
*
* 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.
*/
#include <linux/ioport.h>
#include <linux/mtd/mtd.h>
/*
* Addresses are decoded as follows:
*
* 0x000000 - 0x3fffff SRAM
* 0x400000 - 0x7fffff CSR
*
* Within the SRAM area the following ranges are forced by the system
* firmware:
*
* 0x000000 - 0x0003ff diagnostic area, destroyed upon a reboot
* 0x000400 - ENDofRAM storage area, available to operating systems
*
* but we can't really use the available area right from 0x000400 as
* the first word is used by the firmware as a status flag passed
* from an operating system. If anything but the valid data magic
* ID value is found, the firmware considers the SRAM clean, i.e.
* containing no valid data, and disables the battery resulting in
* data being erased as soon as power is switched off. So the choice
* for the start address of the user-available is 0x001000 which is
* nicely page aligned. The area between 0x000404 and 0x000fff may
* be used by the driver for own needs.
*
* The diagnostic area defines two status words to be read by an
* operating system, a magic ID to distinguish a MS02-NV board from
* anything else and a status information providing results of tests
* as well as the size of SRAM available, which can be 1MiB or 2MiB
* (that's what the firmware handles; no idea if 2MiB modules ever
* existed).
*
* The firmware only handles the MS02-NV board if installed in the
* last (15th) slot, so for any other location the status information
* stored in the SRAM cannot be relied upon. But from the hardware
* point of view there is no problem using up to 14 such boards in a
* system -- only the 1st slot needs to be filled with a DRAM module.
* The MS02-NV board is ECC-protected, like other MS02 memory boards.
*
* The state of the battery as provided by the CSR is reflected on
* the two onboard LEDs. When facing the battery side of the board,
* with the LEDs at the top left and the battery at the bottom right
* (i.e. looking from the back side of the system box), their meaning
* is as follows (the system has to be powered on):
*
* left LED battery disable status: lit = enabled
* right LED battery condition status: lit = OK
*/
/* MS02-NV iomem register offsets. */
#define MS02NV_CSR 0x400000 /* control & status register */
/* MS02-NV CSR status bits. */
#define MS02NV_CSR_BATT_OK 0x01 /* battery OK */
#define MS02NV_CSR_BATT_OFF 0x02 /* battery disabled */
/* MS02-NV memory offsets. */
#define MS02NV_DIAG 0x0003f8 /* diagnostic status */
#define MS02NV_MAGIC 0x0003fc /* MS02-NV magic ID */
#define MS02NV_VALID 0x000400 /* valid data magic ID */
#define MS02NV_RAM 0x001000 /* user-exposed RAM start */
/* MS02-NV diagnostic status bits. */
#define MS02NV_DIAG_TEST 0x01 /* SRAM test done (?) */
#define MS02NV_DIAG_RO 0x02 /* SRAM r/o test done */
#define MS02NV_DIAG_RW 0x04 /* SRAM r/w test done */
#define MS02NV_DIAG_FAIL 0x08 /* SRAM test failed */
#define MS02NV_DIAG_SIZE_MASK 0xf0 /* SRAM size mask */
#define MS02NV_DIAG_SIZE_SHIFT 0x10 /* SRAM size shift (left) */
/* MS02-NV general constants. */
#define MS02NV_ID 0x03021966 /* MS02-NV magic ID value */
#define MS02NV_VALID_ID 0xbd100248 /* valid data magic ID value */
#define MS02NV_SLOT_SIZE 0x800000 /* size of the address space
decoded by the module */
typedef volatile u32 ms02nv_uint;
struct ms02nv_private {
struct mtd_info *next;
struct {
struct resource *module;
struct resource *diag_ram;
struct resource *user_ram;
struct resource *csr;
} resource;
u_char *addr;
size_t size;
u_char *uaddr;
};
File diff suppressed because it is too large Load Diff
+192
View File
@@ -0,0 +1,192 @@
/* drivers/mtd/devices/msm_nand.h
*
* Copyright (c) 2008-2011, The Linux Foundation. All rights reserved.
* Copyright (C) 2007 Google, Inc.
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* 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.
*
*/
#ifndef __DRIVERS_MTD_DEVICES_MSM_NAND_H
#define __DRIVERS_MTD_DEVICES_MSM_NAND_H
#include <mach/msm_iomap.h>
extern unsigned long msm_nand_phys;
extern unsigned long msm_nandc01_phys;
extern unsigned long msm_nandc10_phys;
extern unsigned long msm_nandc11_phys;
extern unsigned long ebi2_register_base;
#define NC01(X) ((X) + msm_nandc01_phys - msm_nand_phys)
#define NC10(X) ((X) + msm_nandc10_phys - msm_nand_phys)
#define NC11(X) ((X) + msm_nandc11_phys - msm_nand_phys)
#define MSM_NAND_REG(off) (msm_nand_phys + (off))
#define MSM_NAND_FLASH_CMD MSM_NAND_REG(0x0000)
#define MSM_NAND_ADDR0 MSM_NAND_REG(0x0004)
#define MSM_NAND_ADDR1 MSM_NAND_REG(0x0008)
#define MSM_NAND_FLASH_CHIP_SELECT MSM_NAND_REG(0x000C)
#define MSM_NAND_EXEC_CMD MSM_NAND_REG(0x0010)
#define MSM_NAND_FLASH_STATUS MSM_NAND_REG(0x0014)
#define MSM_NAND_BUFFER_STATUS MSM_NAND_REG(0x0018)
#define MSM_NAND_SFLASHC_STATUS MSM_NAND_REG(0x001C)
#define MSM_NAND_DEV0_CFG0 MSM_NAND_REG(0x0020)
#define MSM_NAND_DEV0_CFG1 MSM_NAND_REG(0x0024)
#define MSM_NAND_DEV0_ECC_CFG MSM_NAND_REG(0x0028)
#define MSM_NAND_DEV1_ECC_CFG MSM_NAND_REG(0x002C)
#define MSM_NAND_DEV1_CFG0 MSM_NAND_REG(0x0030)
#define MSM_NAND_DEV1_CFG1 MSM_NAND_REG(0x0034)
#define MSM_NAND_SFLASHC_CMD MSM_NAND_REG(0x0038)
#define MSM_NAND_SFLASHC_EXEC_CMD MSM_NAND_REG(0x003C)
#define MSM_NAND_READ_ID MSM_NAND_REG(0x0040)
#define MSM_NAND_READ_STATUS MSM_NAND_REG(0x0044)
#define MSM_NAND_CONFIG_DATA MSM_NAND_REG(0x0050)
#define MSM_NAND_CONFIG MSM_NAND_REG(0x0054)
#define MSM_NAND_CONFIG_MODE MSM_NAND_REG(0x0058)
#define MSM_NAND_CONFIG_STATUS MSM_NAND_REG(0x0060)
#define MSM_NAND_MACRO1_REG MSM_NAND_REG(0x0064)
#define MSM_NAND_XFR_STEP1 MSM_NAND_REG(0x0070)
#define MSM_NAND_XFR_STEP2 MSM_NAND_REG(0x0074)
#define MSM_NAND_XFR_STEP3 MSM_NAND_REG(0x0078)
#define MSM_NAND_XFR_STEP4 MSM_NAND_REG(0x007C)
#define MSM_NAND_XFR_STEP5 MSM_NAND_REG(0x0080)
#define MSM_NAND_XFR_STEP6 MSM_NAND_REG(0x0084)
#define MSM_NAND_XFR_STEP7 MSM_NAND_REG(0x0088)
#define MSM_NAND_GENP_REG0 MSM_NAND_REG(0x0090)
#define MSM_NAND_GENP_REG1 MSM_NAND_REG(0x0094)
#define MSM_NAND_GENP_REG2 MSM_NAND_REG(0x0098)
#define MSM_NAND_GENP_REG3 MSM_NAND_REG(0x009C)
#define MSM_NAND_DEV_CMD0 MSM_NAND_REG(0x00A0)
#define MSM_NAND_DEV_CMD1 MSM_NAND_REG(0x00A4)
#define MSM_NAND_DEV_CMD2 MSM_NAND_REG(0x00A8)
#define MSM_NAND_DEV_CMD_VLD MSM_NAND_REG(0x00AC)
#define MSM_NAND_EBI2_MISR_SIG_REG MSM_NAND_REG(0x00B0)
#define MSM_NAND_ADDR2 MSM_NAND_REG(0x00C0)
#define MSM_NAND_ADDR3 MSM_NAND_REG(0x00C4)
#define MSM_NAND_ADDR4 MSM_NAND_REG(0x00C8)
#define MSM_NAND_ADDR5 MSM_NAND_REG(0x00CC)
#define MSM_NAND_DEV_CMD3 MSM_NAND_REG(0x00D0)
#define MSM_NAND_DEV_CMD4 MSM_NAND_REG(0x00D4)
#define MSM_NAND_DEV_CMD5 MSM_NAND_REG(0x00D8)
#define MSM_NAND_DEV_CMD6 MSM_NAND_REG(0x00DC)
#define MSM_NAND_SFLASHC_BURST_CFG MSM_NAND_REG(0x00E0)
#define MSM_NAND_ADDR6 MSM_NAND_REG(0x00E4)
#define MSM_NAND_EBI2_ECC_BUF_CFG MSM_NAND_REG(0x00F0)
#define MSM_NAND_HW_INFO MSM_NAND_REG(0x00FC)
#define MSM_NAND_FLASH_BUFFER MSM_NAND_REG(0x0100)
/* device commands */
#define MSM_NAND_CMD_SOFT_RESET 0x01
#define MSM_NAND_CMD_PAGE_READ 0x32
#define MSM_NAND_CMD_PAGE_READ_ECC 0x33
#define MSM_NAND_CMD_PAGE_READ_ALL 0x34
#define MSM_NAND_CMD_SEQ_PAGE_READ 0x15
#define MSM_NAND_CMD_PRG_PAGE 0x36
#define MSM_NAND_CMD_PRG_PAGE_ECC 0x37
#define MSM_NAND_CMD_PRG_PAGE_ALL 0x39
#define MSM_NAND_CMD_BLOCK_ERASE 0x3A
#define MSM_NAND_CMD_FETCH_ID 0x0B
#define MSM_NAND_CMD_STATUS 0x0C
#define MSM_NAND_CMD_RESET 0x0D
/* Sflash Commands */
#define MSM_NAND_SFCMD_DATXS 0x0
#define MSM_NAND_SFCMD_CMDXS 0x1
#define MSM_NAND_SFCMD_BURST 0x0
#define MSM_NAND_SFCMD_ASYNC 0x1
#define MSM_NAND_SFCMD_ABORT 0x1
#define MSM_NAND_SFCMD_REGRD 0x2
#define MSM_NAND_SFCMD_REGWR 0x3
#define MSM_NAND_SFCMD_INTLO 0x4
#define MSM_NAND_SFCMD_INTHI 0x5
#define MSM_NAND_SFCMD_DATRD 0x6
#define MSM_NAND_SFCMD_DATWR 0x7
#define SFLASH_PREPCMD(numxfr, offval, delval, trnstp, mode, opcode) \
((numxfr<<20)|(offval<<12)|(delval<<6)|(trnstp<<5)|(mode<<4)|opcode)
#define SFLASH_BCFG 0x20100327
/* Onenand addresses */
#define ONENAND_MANUFACTURER_ID 0xF000
#define ONENAND_DEVICE_ID 0xF001
#define ONENAND_VERSION_ID 0xF002
#define ONENAND_DATA_BUFFER_SIZE 0xF003
#define ONENAND_BOOT_BUFFER_SIZE 0xF004
#define ONENAND_AMOUNT_OF_BUFFERS 0xF005
#define ONENAND_TECHNOLOGY 0xF006
#define ONENAND_START_ADDRESS_1 0xF100
#define ONENAND_START_ADDRESS_2 0xF101
#define ONENAND_START_ADDRESS_3 0xF102
#define ONENAND_START_ADDRESS_4 0xF103
#define ONENAND_START_ADDRESS_5 0xF104
#define ONENAND_START_ADDRESS_6 0xF105
#define ONENAND_START_ADDRESS_7 0xF106
#define ONENAND_START_ADDRESS_8 0xF107
#define ONENAND_START_BUFFER 0xF200
#define ONENAND_COMMAND 0xF220
#define ONENAND_SYSTEM_CONFIG_1 0xF221
#define ONENAND_SYSTEM_CONFIG_2 0xF222
#define ONENAND_CONTROLLER_STATUS 0xF240
#define ONENAND_INTERRUPT_STATUS 0xF241
#define ONENAND_START_BLOCK_ADDRESS 0xF24C
#define ONENAND_WRITE_PROT_STATUS 0xF24E
#define ONENAND_ECC_STATUS 0xFF00
#define ONENAND_ECC_ERRPOS_MAIN0 0xFF01
#define ONENAND_ECC_ERRPOS_SPARE0 0xFF02
#define ONENAND_ECC_ERRPOS_MAIN1 0xFF03
#define ONENAND_ECC_ERRPOS_SPARE1 0xFF04
#define ONENAND_ECC_ERRPOS_MAIN2 0xFF05
#define ONENAND_ECC_ERRPOS_SPARE2 0xFF06
#define ONENAND_ECC_ERRPOS_MAIN3 0xFF07
#define ONENAND_ECC_ERRPOS_SPARE3 0xFF08
/* Onenand commands */
#define ONENAND_WP_US (1 << 2)
#define ONENAND_WP_LS (1 << 1)
#define ONENAND_CMDLOAD 0x0000
#define ONENAND_CMDLOADSPARE 0x0013
#define ONENAND_CMDPROG 0x0080
#define ONENAND_CMDPROGSPARE 0x001A
#define ONENAND_CMDERAS 0x0094
#define ONENAND_CMD_UNLOCK 0x0023
#define ONENAND_CMD_LOCK 0x002A
#define ONENAND_SYSCFG1_ECCENA(mode) (0x40E0 | (mode ? 0 : 0x8002))
#define ONENAND_SYSCFG1_ECCDIS(mode) (0x41E0 | (mode ? 0 : 0x8002))
#define ONENAND_CLRINTR 0x0000
#define ONENAND_STARTADDR1_RES 0x07FF
#define ONENAND_STARTADDR3_RES 0x07FF
#define DATARAM0_0 0x8
#define DEVICE_FLASHCORE_0 (0 << 15)
#define DEVICE_FLASHCORE_1 (1 << 15)
#define DEVICE_BUFFERRAM_0 (0 << 15)
#define DEVICE_BUFFERRAM_1 (1 << 15)
#define ONENAND_DEVICE_IS_DDP (1 << 3)
#define CLEAN_DATA_16 0xFFFF
#define CLEAN_DATA_32 0xFFFFFFFF
#define EBI2_REG(off) (ebi2_register_base + (off))
#define EBI2_CHIP_SELECT_CFG0 EBI2_REG(0x0000)
#define EBI2_CFG_REG EBI2_REG(0x0004)
#define EBI2_NAND_ADM_MUX EBI2_REG(0x005C)
extern struct flash_platform_data msm_nand_data;
#endif
File diff suppressed because it is too large Load Diff
+933
View File
@@ -0,0 +1,933 @@
/*
* Atmel AT45xxx DataFlash MTD driver for lightweight SPI framework
*
* Largely derived from at91_dataflash.c:
* Copyright (C) 2003-2005 SAN People (Pty) Ltd
*
* 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.
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/mutex.h>
#include <linux/err.h>
#include <linux/math64.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/spi/spi.h>
#include <linux/spi/flash.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/partitions.h>
/*
* DataFlash is a kind of SPI flash. Most AT45 chips have two buffers in
* each chip, which may be used for double buffered I/O; but this driver
* doesn't (yet) use these for any kind of i/o overlap or prefetching.
*
* Sometimes DataFlash is packaged in MMC-format cards, although the
* MMC stack can't (yet?) distinguish between MMC and DataFlash
* protocols during enumeration.
*/
/* reads can bypass the buffers */
#define OP_READ_CONTINUOUS 0xE8
#define OP_READ_PAGE 0xD2
/* group B requests can run even while status reports "busy" */
#define OP_READ_STATUS 0xD7 /* group B */
/* move data between host and buffer */
#define OP_READ_BUFFER1 0xD4 /* group B */
#define OP_READ_BUFFER2 0xD6 /* group B */
#define OP_WRITE_BUFFER1 0x84 /* group B */
#define OP_WRITE_BUFFER2 0x87 /* group B */
/* erasing flash */
#define OP_ERASE_PAGE 0x81
#define OP_ERASE_BLOCK 0x50
/* move data between buffer and flash */
#define OP_TRANSFER_BUF1 0x53
#define OP_TRANSFER_BUF2 0x55
#define OP_MREAD_BUFFER1 0xD4
#define OP_MREAD_BUFFER2 0xD6
#define OP_MWERASE_BUFFER1 0x83
#define OP_MWERASE_BUFFER2 0x86
#define OP_MWRITE_BUFFER1 0x88 /* sector must be pre-erased */
#define OP_MWRITE_BUFFER2 0x89 /* sector must be pre-erased */
/* write to buffer, then write-erase to flash */
#define OP_PROGRAM_VIA_BUF1 0x82
#define OP_PROGRAM_VIA_BUF2 0x85
/* compare buffer to flash */
#define OP_COMPARE_BUF1 0x60
#define OP_COMPARE_BUF2 0x61
/* read flash to buffer, then write-erase to flash */
#define OP_REWRITE_VIA_BUF1 0x58
#define OP_REWRITE_VIA_BUF2 0x59
/* newer chips report JEDEC manufacturer and device IDs; chip
* serial number and OTP bits; and per-sector writeprotect.
*/
#define OP_READ_ID 0x9F
#define OP_READ_SECURITY 0x77
#define OP_WRITE_SECURITY_REVC 0x9A
#define OP_WRITE_SECURITY 0x9B /* revision D */
struct dataflash {
uint8_t command[4];
char name[24];
unsigned partitioned:1;
unsigned short page_offset; /* offset in flash address */
unsigned int page_size; /* of bytes per page */
struct mutex lock;
struct spi_device *spi;
struct mtd_info mtd;
};
#ifdef CONFIG_OF
static const struct of_device_id dataflash_dt_ids[] = {
{ .compatible = "atmel,at45", },
{ .compatible = "atmel,dataflash", },
{ /* sentinel */ }
};
#else
#define dataflash_dt_ids NULL
#endif
/* ......................................................................... */
/*
* Return the status of the DataFlash device.
*/
static inline int dataflash_status(struct spi_device *spi)
{
/* NOTE: at45db321c over 25 MHz wants to write
* a dummy byte after the opcode...
*/
return spi_w8r8(spi, OP_READ_STATUS);
}
/*
* Poll the DataFlash device until it is READY.
* This usually takes 5-20 msec or so; more for sector erase.
*/
static int dataflash_waitready(struct spi_device *spi)
{
int status;
for (;;) {
status = dataflash_status(spi);
if (status < 0) {
pr_debug("%s: status %d?\n",
dev_name(&spi->dev), status);
status = 0;
}
if (status & (1 << 7)) /* RDY/nBSY */
return status;
msleep(3);
}
}
/* ......................................................................... */
/*
* Erase pages of flash.
*/
static int dataflash_erase(struct mtd_info *mtd, struct erase_info *instr)
{
struct dataflash *priv = mtd->priv;
struct spi_device *spi = priv->spi;
struct spi_transfer x = { .tx_dma = 0, };
struct spi_message msg;
unsigned blocksize = priv->page_size << 3;
uint8_t *command;
uint32_t rem;
pr_debug("%s: erase addr=0x%llx len 0x%llx\n",
dev_name(&spi->dev), (long long)instr->addr,
(long long)instr->len);
div_u64_rem(instr->len, priv->page_size, &rem);
if (rem)
return -EINVAL;
div_u64_rem(instr->addr, priv->page_size, &rem);
if (rem)
return -EINVAL;
spi_message_init(&msg);
x.tx_buf = command = priv->command;
x.len = 4;
spi_message_add_tail(&x, &msg);
mutex_lock(&priv->lock);
while (instr->len > 0) {
unsigned int pageaddr;
int status;
int do_block;
/* Calculate flash page address; use block erase (for speed) if
* we're at a block boundary and need to erase the whole block.
*/
pageaddr = div_u64(instr->addr, priv->page_size);
do_block = (pageaddr & 0x7) == 0 && instr->len >= blocksize;
pageaddr = pageaddr << priv->page_offset;
command[0] = do_block ? OP_ERASE_BLOCK : OP_ERASE_PAGE;
command[1] = (uint8_t)(pageaddr >> 16);
command[2] = (uint8_t)(pageaddr >> 8);
command[3] = 0;
pr_debug("ERASE %s: (%x) %x %x %x [%i]\n",
do_block ? "block" : "page",
command[0], command[1], command[2], command[3],
pageaddr);
status = spi_sync(spi, &msg);
(void) dataflash_waitready(spi);
if (status < 0) {
printk(KERN_ERR "%s: erase %x, err %d\n",
dev_name(&spi->dev), pageaddr, status);
/* REVISIT: can retry instr->retries times; or
* giveup and instr->fail_addr = instr->addr;
*/
continue;
}
if (do_block) {
instr->addr += blocksize;
instr->len -= blocksize;
} else {
instr->addr += priv->page_size;
instr->len -= priv->page_size;
}
}
mutex_unlock(&priv->lock);
/* Inform MTD subsystem that erase is complete */
instr->state = MTD_ERASE_DONE;
mtd_erase_callback(instr);
return 0;
}
/*
* Read from the DataFlash device.
* from : Start offset in flash device
* len : Amount to read
* retlen : About of data actually read
* buf : Buffer containing the data
*/
static int dataflash_read(struct mtd_info *mtd, loff_t from, size_t len,
size_t *retlen, u_char *buf)
{
struct dataflash *priv = mtd->priv;
struct spi_transfer x[2] = { { .tx_dma = 0, }, };
struct spi_message msg;
unsigned int addr;
uint8_t *command;
int status;
pr_debug("%s: read 0x%x..0x%x\n", dev_name(&priv->spi->dev),
(unsigned)from, (unsigned)(from + len));
/* Calculate flash page/byte address */
addr = (((unsigned)from / priv->page_size) << priv->page_offset)
+ ((unsigned)from % priv->page_size);
command = priv->command;
pr_debug("READ: (%x) %x %x %x\n",
command[0], command[1], command[2], command[3]);
spi_message_init(&msg);
x[0].tx_buf = command;
x[0].len = 8;
spi_message_add_tail(&x[0], &msg);
x[1].rx_buf = buf;
x[1].len = len;
spi_message_add_tail(&x[1], &msg);
mutex_lock(&priv->lock);
/* Continuous read, max clock = f(car) which may be less than
* the peak rate available. Some chips support commands with
* fewer "don't care" bytes. Both buffers stay unchanged.
*/
command[0] = OP_READ_CONTINUOUS;
command[1] = (uint8_t)(addr >> 16);
command[2] = (uint8_t)(addr >> 8);
command[3] = (uint8_t)(addr >> 0);
/* plus 4 "don't care" bytes */
status = spi_sync(priv->spi, &msg);
mutex_unlock(&priv->lock);
if (status >= 0) {
*retlen = msg.actual_length - 8;
status = 0;
} else
pr_debug("%s: read %x..%x --> %d\n",
dev_name(&priv->spi->dev),
(unsigned)from, (unsigned)(from + len),
status);
return status;
}
/*
* Write to the DataFlash device.
* to : Start offset in flash device
* len : Amount to write
* retlen : Amount of data actually written
* buf : Buffer containing the data
*/
static int dataflash_write(struct mtd_info *mtd, loff_t to, size_t len,
size_t * retlen, const u_char * buf)
{
struct dataflash *priv = mtd->priv;
struct spi_device *spi = priv->spi;
struct spi_transfer x[2] = { { .tx_dma = 0, }, };
struct spi_message msg;
unsigned int pageaddr, addr, offset, writelen;
size_t remaining = len;
u_char *writebuf = (u_char *) buf;
int status = -EINVAL;
uint8_t *command;
pr_debug("%s: write 0x%x..0x%x\n",
dev_name(&spi->dev), (unsigned)to, (unsigned)(to + len));
spi_message_init(&msg);
x[0].tx_buf = command = priv->command;
x[0].len = 4;
spi_message_add_tail(&x[0], &msg);
pageaddr = ((unsigned)to / priv->page_size);
offset = ((unsigned)to % priv->page_size);
if (offset + len > priv->page_size)
writelen = priv->page_size - offset;
else
writelen = len;
mutex_lock(&priv->lock);
while (remaining > 0) {
pr_debug("write @ %i:%i len=%i\n",
pageaddr, offset, writelen);
/* REVISIT:
* (a) each page in a sector must be rewritten at least
* once every 10K sibling erase/program operations.
* (b) for pages that are already erased, we could
* use WRITE+MWRITE not PROGRAM for ~30% speedup.
* (c) WRITE to buffer could be done while waiting for
* a previous MWRITE/MWERASE to complete ...
* (d) error handling here seems to be mostly missing.
*
* Two persistent bits per page, plus a per-sector counter,
* could support (a) and (b) ... we might consider using
* the second half of sector zero, which is just one block,
* to track that state. (On AT91, that sector should also
* support boot-from-DataFlash.)
*/
addr = pageaddr << priv->page_offset;
/* (1) Maybe transfer partial page to Buffer1 */
if (writelen != priv->page_size) {
command[0] = OP_TRANSFER_BUF1;
command[1] = (addr & 0x00FF0000) >> 16;
command[2] = (addr & 0x0000FF00) >> 8;
command[3] = 0;
pr_debug("TRANSFER: (%x) %x %x %x\n",
command[0], command[1], command[2], command[3]);
status = spi_sync(spi, &msg);
if (status < 0)
pr_debug("%s: xfer %u -> %d\n",
dev_name(&spi->dev), addr, status);
(void) dataflash_waitready(priv->spi);
}
/* (2) Program full page via Buffer1 */
addr += offset;
command[0] = OP_PROGRAM_VIA_BUF1;
command[1] = (addr & 0x00FF0000) >> 16;
command[2] = (addr & 0x0000FF00) >> 8;
command[3] = (addr & 0x000000FF);
pr_debug("PROGRAM: (%x) %x %x %x\n",
command[0], command[1], command[2], command[3]);
x[1].tx_buf = writebuf;
x[1].len = writelen;
spi_message_add_tail(x + 1, &msg);
status = spi_sync(spi, &msg);
spi_transfer_del(x + 1);
if (status < 0)
pr_debug("%s: pgm %u/%u -> %d\n",
dev_name(&spi->dev), addr, writelen, status);
(void) dataflash_waitready(priv->spi);
#ifdef CONFIG_MTD_DATAFLASH_WRITE_VERIFY
/* (3) Compare to Buffer1 */
addr = pageaddr << priv->page_offset;
command[0] = OP_COMPARE_BUF1;
command[1] = (addr & 0x00FF0000) >> 16;
command[2] = (addr & 0x0000FF00) >> 8;
command[3] = 0;
pr_debug("COMPARE: (%x) %x %x %x\n",
command[0], command[1], command[2], command[3]);
status = spi_sync(spi, &msg);
if (status < 0)
pr_debug("%s: compare %u -> %d\n",
dev_name(&spi->dev), addr, status);
status = dataflash_waitready(priv->spi);
/* Check result of the compare operation */
if (status & (1 << 6)) {
printk(KERN_ERR "%s: compare page %u, err %d\n",
dev_name(&spi->dev), pageaddr, status);
remaining = 0;
status = -EIO;
break;
} else
status = 0;
#endif /* CONFIG_MTD_DATAFLASH_WRITE_VERIFY */
remaining = remaining - writelen;
pageaddr++;
offset = 0;
writebuf += writelen;
*retlen += writelen;
if (remaining > priv->page_size)
writelen = priv->page_size;
else
writelen = remaining;
}
mutex_unlock(&priv->lock);
return status;
}
/* ......................................................................... */
#ifdef CONFIG_MTD_DATAFLASH_OTP
static int dataflash_get_otp_info(struct mtd_info *mtd,
struct otp_info *info, size_t len)
{
/* Report both blocks as identical: bytes 0..64, locked.
* Unless the user block changed from all-ones, we can't
* tell whether it's still writable; so we assume it isn't.
*/
info->start = 0;
info->length = 64;
info->locked = 1;
return sizeof(*info);
}
static ssize_t otp_read(struct spi_device *spi, unsigned base,
uint8_t *buf, loff_t off, size_t len)
{
struct spi_message m;
size_t l;
uint8_t *scratch;
struct spi_transfer t;
int status;
if (off > 64)
return -EINVAL;
if ((off + len) > 64)
len = 64 - off;
spi_message_init(&m);
l = 4 + base + off + len;
scratch = kzalloc(l, GFP_KERNEL);
if (!scratch)
return -ENOMEM;
/* OUT: OP_READ_SECURITY, 3 don't-care bytes, zeroes
* IN: ignore 4 bytes, data bytes 0..N (max 127)
*/
scratch[0] = OP_READ_SECURITY;
memset(&t, 0, sizeof t);
t.tx_buf = scratch;
t.rx_buf = scratch;
t.len = l;
spi_message_add_tail(&t, &m);
dataflash_waitready(spi);
status = spi_sync(spi, &m);
if (status >= 0) {
memcpy(buf, scratch + 4 + base + off, len);
status = len;
}
kfree(scratch);
return status;
}
static int dataflash_read_fact_otp(struct mtd_info *mtd,
loff_t from, size_t len, size_t *retlen, u_char *buf)
{
struct dataflash *priv = mtd->priv;
int status;
/* 64 bytes, from 0..63 ... start at 64 on-chip */
mutex_lock(&priv->lock);
status = otp_read(priv->spi, 64, buf, from, len);
mutex_unlock(&priv->lock);
if (status < 0)
return status;
*retlen = status;
return 0;
}
static int dataflash_read_user_otp(struct mtd_info *mtd,
loff_t from, size_t len, size_t *retlen, u_char *buf)
{
struct dataflash *priv = mtd->priv;
int status;
/* 64 bytes, from 0..63 ... start at 0 on-chip */
mutex_lock(&priv->lock);
status = otp_read(priv->spi, 0, buf, from, len);
mutex_unlock(&priv->lock);
if (status < 0)
return status;
*retlen = status;
return 0;
}
static int dataflash_write_user_otp(struct mtd_info *mtd,
loff_t from, size_t len, size_t *retlen, u_char *buf)
{
struct spi_message m;
const size_t l = 4 + 64;
uint8_t *scratch;
struct spi_transfer t;
struct dataflash *priv = mtd->priv;
int status;
if (len > 64)
return -EINVAL;
/* Strictly speaking, we *could* truncate the write ... but
* let's not do that for the only write that's ever possible.
*/
if ((from + len) > 64)
return -EINVAL;
/* OUT: OP_WRITE_SECURITY, 3 zeroes, 64 data-or-zero bytes
* IN: ignore all
*/
scratch = kzalloc(l, GFP_KERNEL);
if (!scratch)
return -ENOMEM;
scratch[0] = OP_WRITE_SECURITY;
memcpy(scratch + 4 + from, buf, len);
spi_message_init(&m);
memset(&t, 0, sizeof t);
t.tx_buf = scratch;
t.len = l;
spi_message_add_tail(&t, &m);
/* Write the OTP bits, if they've not yet been written.
* This modifies SRAM buffer1.
*/
mutex_lock(&priv->lock);
dataflash_waitready(priv->spi);
status = spi_sync(priv->spi, &m);
mutex_unlock(&priv->lock);
kfree(scratch);
if (status >= 0) {
status = 0;
*retlen = len;
}
return status;
}
static char *otp_setup(struct mtd_info *device, char revision)
{
device->_get_fact_prot_info = dataflash_get_otp_info;
device->_read_fact_prot_reg = dataflash_read_fact_otp;
device->_get_user_prot_info = dataflash_get_otp_info;
device->_read_user_prot_reg = dataflash_read_user_otp;
/* rev c parts (at45db321c and at45db1281 only!) use a
* different write procedure; not (yet?) implemented.
*/
if (revision > 'c')
device->_write_user_prot_reg = dataflash_write_user_otp;
return ", OTP";
}
#else
static char *otp_setup(struct mtd_info *device, char revision)
{
return " (OTP)";
}
#endif
/* ......................................................................... */
/*
* Register DataFlash device with MTD subsystem.
*/
static int __devinit
add_dataflash_otp(struct spi_device *spi, char *name,
int nr_pages, int pagesize, int pageoffset, char revision)
{
struct dataflash *priv;
struct mtd_info *device;
struct mtd_part_parser_data ppdata;
struct flash_platform_data *pdata = spi->dev.platform_data;
char *otp_tag = "";
int err = 0;
priv = kzalloc(sizeof *priv, GFP_KERNEL);
if (!priv)
return -ENOMEM;
mutex_init(&priv->lock);
priv->spi = spi;
priv->page_size = pagesize;
priv->page_offset = pageoffset;
/* name must be usable with cmdlinepart */
sprintf(priv->name, "spi%d.%d-%s",
spi->master->bus_num, spi->chip_select,
name);
device = &priv->mtd;
device->name = (pdata && pdata->name) ? pdata->name : priv->name;
device->size = nr_pages * pagesize;
device->erasesize = pagesize;
device->writesize = pagesize;
device->owner = THIS_MODULE;
device->type = MTD_DATAFLASH;
device->flags = MTD_WRITEABLE;
device->_erase = dataflash_erase;
device->_read = dataflash_read;
device->_write = dataflash_write;
device->priv = priv;
device->dev.parent = &spi->dev;
if (revision >= 'c')
otp_tag = otp_setup(device, revision);
dev_info(&spi->dev, "%s (%lld KBytes) pagesize %d bytes%s\n",
name, (long long)((device->size + 1023) >> 10),
pagesize, otp_tag);
dev_set_drvdata(&spi->dev, priv);
ppdata.of_node = spi->dev.of_node;
err = mtd_device_parse_register(device, NULL, &ppdata,
pdata ? pdata->parts : NULL,
pdata ? pdata->nr_parts : 0);
if (!err)
return 0;
dev_set_drvdata(&spi->dev, NULL);
kfree(priv);
return err;
}
static inline int __devinit
add_dataflash(struct spi_device *spi, char *name,
int nr_pages, int pagesize, int pageoffset)
{
return add_dataflash_otp(spi, name, nr_pages, pagesize,
pageoffset, 0);
}
struct flash_info {
char *name;
/* JEDEC id has a high byte of zero plus three data bytes:
* the manufacturer id, then a two byte device id.
*/
uint32_t jedec_id;
/* The size listed here is what works with OP_ERASE_PAGE. */
unsigned nr_pages;
uint16_t pagesize;
uint16_t pageoffset;
uint16_t flags;
#define SUP_POW2PS 0x0002 /* supports 2^N byte pages */
#define IS_POW2PS 0x0001 /* uses 2^N byte pages */
};
static struct flash_info __devinitdata dataflash_data [] = {
/*
* NOTE: chips with SUP_POW2PS (rev D and up) need two entries,
* one with IS_POW2PS and the other without. The entry with the
* non-2^N byte page size can't name exact chip revisions without
* losing backwards compatibility for cmdlinepart.
*
* These newer chips also support 128-byte security registers (with
* 64 bytes one-time-programmable) and software write-protection.
*/
{ "AT45DB011B", 0x1f2200, 512, 264, 9, SUP_POW2PS},
{ "at45db011d", 0x1f2200, 512, 256, 8, SUP_POW2PS | IS_POW2PS},
{ "AT45DB021B", 0x1f2300, 1024, 264, 9, SUP_POW2PS},
{ "at45db021d", 0x1f2300, 1024, 256, 8, SUP_POW2PS | IS_POW2PS},
{ "AT45DB041x", 0x1f2400, 2048, 264, 9, SUP_POW2PS},
{ "at45db041d", 0x1f2400, 2048, 256, 8, SUP_POW2PS | IS_POW2PS},
{ "AT45DB081B", 0x1f2500, 4096, 264, 9, SUP_POW2PS},
{ "at45db081d", 0x1f2500, 4096, 256, 8, SUP_POW2PS | IS_POW2PS},
{ "AT45DB161x", 0x1f2600, 4096, 528, 10, SUP_POW2PS},
{ "at45db161d", 0x1f2600, 4096, 512, 9, SUP_POW2PS | IS_POW2PS},
{ "AT45DB321x", 0x1f2700, 8192, 528, 10, 0}, /* rev C */
{ "AT45DB321x", 0x1f2701, 8192, 528, 10, SUP_POW2PS},
{ "at45db321d", 0x1f2701, 8192, 512, 9, SUP_POW2PS | IS_POW2PS},
{ "AT45DB642x", 0x1f2800, 8192, 1056, 11, SUP_POW2PS},
{ "at45db642d", 0x1f2800, 8192, 1024, 10, SUP_POW2PS | IS_POW2PS},
};
static struct flash_info *__devinit jedec_probe(struct spi_device *spi)
{
int tmp;
uint8_t code = OP_READ_ID;
uint8_t id[3];
uint32_t jedec;
struct flash_info *info;
int status;
/* JEDEC also defines an optional "extended device information"
* string for after vendor-specific data, after the three bytes
* we use here. Supporting some chips might require using it.
*
* If the vendor ID isn't Atmel's (0x1f), assume this call failed.
* That's not an error; only rev C and newer chips handle it, and
* only Atmel sells these chips.
*/
tmp = spi_write_then_read(spi, &code, 1, id, 3);
if (tmp < 0) {
pr_debug("%s: error %d reading JEDEC ID\n",
dev_name(&spi->dev), tmp);
return ERR_PTR(tmp);
}
if (id[0] != 0x1f)
return NULL;
jedec = id[0];
jedec = jedec << 8;
jedec |= id[1];
jedec = jedec << 8;
jedec |= id[2];
for (tmp = 0, info = dataflash_data;
tmp < ARRAY_SIZE(dataflash_data);
tmp++, info++) {
if (info->jedec_id == jedec) {
pr_debug("%s: OTP, sector protect%s\n",
dev_name(&spi->dev),
(info->flags & SUP_POW2PS)
? ", binary pagesize" : ""
);
if (info->flags & SUP_POW2PS) {
status = dataflash_status(spi);
if (status < 0) {
pr_debug("%s: status error %d\n",
dev_name(&spi->dev), status);
return ERR_PTR(status);
}
if (status & 0x1) {
if (info->flags & IS_POW2PS)
return info;
} else {
if (!(info->flags & IS_POW2PS))
return info;
}
} else
return info;
}
}
/*
* Treat other chips as errors ... we won't know the right page
* size (it might be binary) even when we can tell which density
* class is involved (legacy chip id scheme).
*/
dev_warn(&spi->dev, "JEDEC id %06x not handled\n", jedec);
return ERR_PTR(-ENODEV);
}
/*
* Detect and initialize DataFlash device, using JEDEC IDs on newer chips
* or else the ID code embedded in the status bits:
*
* Device Density ID code #Pages PageSize Offset
* AT45DB011B 1Mbit (128K) xx0011xx (0x0c) 512 264 9
* AT45DB021B 2Mbit (256K) xx0101xx (0x14) 1024 264 9
* AT45DB041B 4Mbit (512K) xx0111xx (0x1c) 2048 264 9
* AT45DB081B 8Mbit (1M) xx1001xx (0x24) 4096 264 9
* AT45DB0161B 16Mbit (2M) xx1011xx (0x2c) 4096 528 10
* AT45DB0321B 32Mbit (4M) xx1101xx (0x34) 8192 528 10
* AT45DB0642 64Mbit (8M) xx111xxx (0x3c) 8192 1056 11
* AT45DB1282 128Mbit (16M) xx0100xx (0x10) 16384 1056 11
*/
static int __devinit dataflash_probe(struct spi_device *spi)
{
int status;
struct flash_info *info;
/*
* Try to detect dataflash by JEDEC ID.
* If it succeeds we know we have either a C or D part.
* D will support power of 2 pagesize option.
* Both support the security register, though with different
* write procedures.
*/
info = jedec_probe(spi);
if (IS_ERR(info))
return PTR_ERR(info);
if (info != NULL)
return add_dataflash_otp(spi, info->name, info->nr_pages,
info->pagesize, info->pageoffset,
(info->flags & SUP_POW2PS) ? 'd' : 'c');
/*
* Older chips support only legacy commands, identifing
* capacity using bits in the status byte.
*/
status = dataflash_status(spi);
if (status <= 0 || status == 0xff) {
pr_debug("%s: status error %d\n",
dev_name(&spi->dev), status);
if (status == 0 || status == 0xff)
status = -ENODEV;
return status;
}
/* if there's a device there, assume it's dataflash.
* board setup should have set spi->max_speed_max to
* match f(car) for continuous reads, mode 0 or 3.
*/
switch (status & 0x3c) {
case 0x0c: /* 0 0 1 1 x x */
status = add_dataflash(spi, "AT45DB011B", 512, 264, 9);
break;
case 0x14: /* 0 1 0 1 x x */
status = add_dataflash(spi, "AT45DB021B", 1024, 264, 9);
break;
case 0x1c: /* 0 1 1 1 x x */
status = add_dataflash(spi, "AT45DB041x", 2048, 264, 9);
break;
case 0x24: /* 1 0 0 1 x x */
status = add_dataflash(spi, "AT45DB081B", 4096, 264, 9);
break;
case 0x2c: /* 1 0 1 1 x x */
status = add_dataflash(spi, "AT45DB161x", 4096, 528, 10);
break;
case 0x34: /* 1 1 0 1 x x */
status = add_dataflash(spi, "AT45DB321x", 8192, 528, 10);
break;
case 0x38: /* 1 1 1 x x x */
case 0x3c:
status = add_dataflash(spi, "AT45DB642x", 8192, 1056, 11);
break;
/* obsolete AT45DB1282 not (yet?) supported */
default:
pr_debug("%s: unsupported device (%x)\n", dev_name(&spi->dev),
status & 0x3c);
status = -ENODEV;
}
if (status < 0)
pr_debug("%s: add_dataflash --> %d\n", dev_name(&spi->dev),
status);
return status;
}
static int __devexit dataflash_remove(struct spi_device *spi)
{
struct dataflash *flash = dev_get_drvdata(&spi->dev);
int status;
pr_debug("%s: remove\n", dev_name(&spi->dev));
status = mtd_device_unregister(&flash->mtd);
if (status == 0) {
dev_set_drvdata(&spi->dev, NULL);
kfree(flash);
}
return status;
}
static struct spi_driver dataflash_driver = {
.driver = {
.name = "mtd_dataflash",
.owner = THIS_MODULE,
.of_match_table = dataflash_dt_ids,
},
.probe = dataflash_probe,
.remove = __devexit_p(dataflash_remove),
/* FIXME: investigate suspend and resume... */
};
module_spi_driver(dataflash_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Andrew Victor, David Brownell");
MODULE_DESCRIPTION("MTD DataFlash driver");
MODULE_ALIAS("spi:mtd_dataflash");
+158
View File
@@ -0,0 +1,158 @@
/*
* mtdram - a test mtd device
* Author: Alexander Larsson <alex@cendio.se>
*
* Copyright (c) 1999 Alexander Larsson <alex@cendio.se>
* Copyright (c) 2005 Joern Engel <joern@wh.fh-wedel.de>
*
* This code is GPL
*
*/
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/ioport.h>
#include <linux/vmalloc.h>
#include <linux/init.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/mtdram.h>
static unsigned long total_size = CONFIG_MTDRAM_TOTAL_SIZE;
static unsigned long erase_size = CONFIG_MTDRAM_ERASE_SIZE;
#define MTDRAM_TOTAL_SIZE (total_size * 1024)
#define MTDRAM_ERASE_SIZE (erase_size * 1024)
#ifdef MODULE
module_param(total_size, ulong, 0);
MODULE_PARM_DESC(total_size, "Total device size in KiB");
module_param(erase_size, ulong, 0);
MODULE_PARM_DESC(erase_size, "Device erase block size in KiB");
#endif
// We could store these in the mtd structure, but we only support 1 device..
static struct mtd_info *mtd_info;
static int ram_erase(struct mtd_info *mtd, struct erase_info *instr)
{
memset((char *)mtd->priv + instr->addr, 0xff, instr->len);
instr->state = MTD_ERASE_DONE;
mtd_erase_callback(instr);
return 0;
}
static int ram_point(struct mtd_info *mtd, loff_t from, size_t len,
size_t *retlen, void **virt, resource_size_t *phys)
{
*virt = mtd->priv + from;
*retlen = len;
return 0;
}
static int ram_unpoint(struct mtd_info *mtd, loff_t from, size_t len)
{
return 0;
}
/*
* Allow NOMMU mmap() to directly map the device (if not NULL)
* - return the address to which the offset maps
* - return -ENOSYS to indicate refusal to do the mapping
*/
static unsigned long ram_get_unmapped_area(struct mtd_info *mtd,
unsigned long len,
unsigned long offset,
unsigned long flags)
{
return (unsigned long) mtd->priv + offset;
}
static int ram_read(struct mtd_info *mtd, loff_t from, size_t len,
size_t *retlen, u_char *buf)
{
memcpy(buf, mtd->priv + from, len);
*retlen = len;
return 0;
}
static int ram_write(struct mtd_info *mtd, loff_t to, size_t len,
size_t *retlen, const u_char *buf)
{
memcpy((char *)mtd->priv + to, buf, len);
*retlen = len;
return 0;
}
static void __exit cleanup_mtdram(void)
{
if (mtd_info) {
mtd_device_unregister(mtd_info);
vfree(mtd_info->priv);
kfree(mtd_info);
}
}
int mtdram_init_device(struct mtd_info *mtd, void *mapped_address,
unsigned long size, char *name)
{
memset(mtd, 0, sizeof(*mtd));
/* Setup the MTD structure */
mtd->name = name;
mtd->type = MTD_RAM;
mtd->flags = MTD_CAP_RAM;
mtd->size = size;
mtd->writesize = 1;
mtd->writebufsize = 64; /* Mimic CFI NOR flashes */
mtd->erasesize = MTDRAM_ERASE_SIZE;
mtd->priv = mapped_address;
mtd->owner = THIS_MODULE;
mtd->_erase = ram_erase;
mtd->_point = ram_point;
mtd->_unpoint = ram_unpoint;
mtd->_get_unmapped_area = ram_get_unmapped_area;
mtd->_read = ram_read;
mtd->_write = ram_write;
if (mtd_device_register(mtd, NULL, 0))
return -EIO;
return 0;
}
static int __init init_mtdram(void)
{
void *addr;
int err;
if (!total_size)
return -EINVAL;
/* Allocate some memory */
mtd_info = kmalloc(sizeof(struct mtd_info), GFP_KERNEL);
if (!mtd_info)
return -ENOMEM;
addr = vmalloc(MTDRAM_TOTAL_SIZE);
if (!addr) {
kfree(mtd_info);
mtd_info = NULL;
return -ENOMEM;
}
err = mtdram_init_device(mtd_info, addr, MTDRAM_TOTAL_SIZE, "mtdram test device");
if (err) {
vfree(addr);
kfree(mtd_info);
mtd_info = NULL;
return err;
}
memset(mtd_info->priv, 0xff, MTDRAM_TOTAL_SIZE);
return err;
}
module_init(init_mtdram);
module_exit(cleanup_mtdram);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Alexander Larsson <alexl@redhat.com>");
MODULE_DESCRIPTION("Simulated MTD driver for testing");
+302
View File
@@ -0,0 +1,302 @@
/**
* Copyright (c) ???? Jochen Schäuble <psionic@psionic.de>
* Copyright (c) 2003-2004 Joern Engel <joern@wh.fh-wedel.de>
*
* Usage:
*
* one commend line parameter per device, each in the form:
* phram=<name>,<start>,<len>
* <name> may be up to 63 characters.
* <start> and <len> can be octal, decimal or hexadecimal. If followed
* by "ki", "Mi" or "Gi", the numbers will be interpreted as kilo, mega or
* gigabytes.
*
* Example:
* phram=swap,64Mi,128Mi phram=test,900Mi,1Mi
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <asm/io.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/slab.h>
#include <linux/mtd/mtd.h>
struct phram_mtd_list {
struct mtd_info mtd;
struct list_head list;
};
static LIST_HEAD(phram_list);
static int phram_erase(struct mtd_info *mtd, struct erase_info *instr)
{
u_char *start = mtd->priv;
memset(start + instr->addr, 0xff, instr->len);
/*
* This'll catch a few races. Free the thing before returning :)
* I don't feel at all ashamed. This kind of thing is possible anyway
* with flash, but unlikely.
*/
instr->state = MTD_ERASE_DONE;
mtd_erase_callback(instr);
return 0;
}
static int phram_point(struct mtd_info *mtd, loff_t from, size_t len,
size_t *retlen, void **virt, resource_size_t *phys)
{
*virt = mtd->priv + from;
*retlen = len;
return 0;
}
static int phram_unpoint(struct mtd_info *mtd, loff_t from, size_t len)
{
return 0;
}
static int phram_read(struct mtd_info *mtd, loff_t from, size_t len,
size_t *retlen, u_char *buf)
{
u_char *start = mtd->priv;
memcpy(buf, start + from, len);
*retlen = len;
return 0;
}
static int phram_write(struct mtd_info *mtd, loff_t to, size_t len,
size_t *retlen, const u_char *buf)
{
u_char *start = mtd->priv;
memcpy(start + to, buf, len);
*retlen = len;
return 0;
}
static void unregister_devices(void)
{
struct phram_mtd_list *this, *safe;
list_for_each_entry_safe(this, safe, &phram_list, list) {
mtd_device_unregister(&this->mtd);
iounmap(this->mtd.priv);
kfree(this->mtd.name);
kfree(this);
}
}
static int register_device(char *name, unsigned long start, unsigned long len)
{
struct phram_mtd_list *new;
int ret = -ENOMEM;
new = kzalloc(sizeof(*new), GFP_KERNEL);
if (!new)
goto out0;
ret = -EIO;
new->mtd.priv = ioremap(start, len);
if (!new->mtd.priv) {
pr_err("ioremap failed\n");
goto out1;
}
new->mtd.name = name;
new->mtd.size = len;
new->mtd.flags = MTD_CAP_RAM;
new->mtd._erase = phram_erase;
new->mtd._point = phram_point;
new->mtd._unpoint = phram_unpoint;
new->mtd._read = phram_read;
new->mtd._write = phram_write;
new->mtd.owner = THIS_MODULE;
new->mtd.type = MTD_RAM;
new->mtd.erasesize = PAGE_SIZE;
new->mtd.writesize = 1;
ret = -EAGAIN;
if (mtd_device_register(&new->mtd, NULL, 0)) {
pr_err("Failed to register new device\n");
goto out2;
}
list_add_tail(&new->list, &phram_list);
return 0;
out2:
iounmap(new->mtd.priv);
out1:
kfree(new);
out0:
return ret;
}
static int ustrtoul(const char *cp, char **endp, unsigned int base)
{
unsigned long result = simple_strtoul(cp, endp, base);
switch (**endp) {
case 'G':
result *= 1024;
case 'M':
result *= 1024;
case 'k':
result *= 1024;
/* By dwmw2 editorial decree, "ki", "Mi" or "Gi" are to be used. */
if ((*endp)[1] == 'i')
(*endp) += 2;
}
return result;
}
static int parse_num32(uint32_t *num32, const char *token)
{
char *endp;
unsigned long n;
n = ustrtoul(token, &endp, 0);
if (*endp)
return -EINVAL;
*num32 = n;
return 0;
}
static int parse_name(char **pname, const char *token)
{
size_t len;
char *name;
len = strlen(token) + 1;
if (len > 64)
return -ENOSPC;
name = kmalloc(len, GFP_KERNEL);
if (!name)
return -ENOMEM;
strcpy(name, token);
*pname = name;
return 0;
}
static inline void kill_final_newline(char *str)
{
char *newline = strrchr(str, '\n');
if (newline && !newline[1])
*newline = 0;
}
#define parse_err(fmt, args...) do { \
pr_err(fmt , ## args); \
return 1; \
} while (0)
/*
* This shall contain the module parameter if any. It is of the form:
* - phram=<device>,<address>,<size> for module case
* - phram.phram=<device>,<address>,<size> for built-in case
* We leave 64 bytes for the device name, 12 for the address and 12 for the
* size.
* Example: phram.phram=rootfs,0xa0000000,512Mi
*/
static __initdata char phram_paramline[64+12+12];
static int __init phram_setup(const char *val)
{
char buf[64+12+12], *str = buf;
char *token[3];
char *name;
uint32_t start;
uint32_t len;
int i, ret;
if (strnlen(val, sizeof(buf)) >= sizeof(buf))
parse_err("parameter too long\n");
strcpy(str, val);
kill_final_newline(str);
for (i=0; i<3; i++)
token[i] = strsep(&str, ",");
if (str)
parse_err("too many arguments\n");
if (!token[2])
parse_err("not enough arguments\n");
ret = parse_name(&name, token[0]);
if (ret)
return ret;
ret = parse_num32(&start, token[1]);
if (ret) {
kfree(name);
parse_err("illegal start address\n");
}
ret = parse_num32(&len, token[2]);
if (ret) {
kfree(name);
parse_err("illegal device length\n");
}
ret = register_device(name, start, len);
if (!ret)
pr_info("%s device: %#x at %#x\n", name, len, start);
else
kfree(name);
return ret;
}
static int __init phram_param_call(const char *val, struct kernel_param *kp)
{
/*
* This function is always called before 'init_phram()', whether
* built-in or module.
*/
if (strlen(val) >= sizeof(phram_paramline))
return -ENOSPC;
strcpy(phram_paramline, val);
return 0;
}
module_param_call(phram, phram_param_call, NULL, NULL, 000);
MODULE_PARM_DESC(phram, "Memory region to map. \"phram=<name>,<start>,<length>\"");
static int __init init_phram(void)
{
if (phram_paramline[0])
return phram_setup(phram_paramline);
return 0;
}
static void __exit cleanup_phram(void)
{
unregister_devices();
}
module_init(init_phram);
module_exit(cleanup_phram);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Joern Engel <joern@wh.fh-wedel.de>");
MODULE_DESCRIPTION("MTD driver for physical RAM");
+862
View File
@@ -0,0 +1,862 @@
/*
* PMC551 PCI Mezzanine Ram Device
*
* Author:
* Mark Ferrell <mferrell@mvista.com>
* Copyright 1999,2000 Nortel Networks
*
* License:
* As part of this driver was derived from the slram.c driver it
* falls under the same license, which is GNU General Public
* License v2
*
* Description:
* This driver is intended to support the PMC551 PCI Ram device
* from Ramix Inc. The PMC551 is a PMC Mezzanine module for
* cPCI embedded systems. The device contains a single SROM
* that initially programs the V370PDC chipset onboard the
* device, and various banks of DRAM/SDRAM onboard. This driver
* implements this PCI Ram device as an MTD (Memory Technology
* Device) so that it can be used to hold a file system, or for
* added swap space in embedded systems. Since the memory on
* this board isn't as fast as main memory we do not try to hook
* it into main memory as that would simply reduce performance
* on the system. Using it as a block device allows us to use
* it as high speed swap or for a high speed disk device of some
* sort. Which becomes very useful on diskless systems in the
* embedded market I might add.
*
* Notes:
* Due to what I assume is more buggy SROM, the 64M PMC551 I
* have available claims that all 4 of its DRAM banks have 64MiB
* of ram configured (making a grand total of 256MiB onboard).
* This is slightly annoying since the BAR0 size reflects the
* aperture size, not the dram size, and the V370PDC supplies no
* other method for memory size discovery. This problem is
* mostly only relevant when compiled as a module, as the
* unloading of the module with an aperture size smaller than
* the ram will cause the driver to detect the onboard memory
* size to be equal to the aperture size when the module is
* reloaded. Soooo, to help, the module supports an msize
* option to allow the specification of the onboard memory, and
* an asize option, to allow the specification of the aperture
* size. The aperture must be equal to or less then the memory
* size, the driver will correct this if you screw it up. This
* problem is not relevant for compiled in drivers as compiled
* in drivers only init once.
*
* Credits:
* Saeed Karamooz <saeed@ramix.com> of Ramix INC. for the
* initial example code of how to initialize this device and for
* help with questions I had concerning operation of the device.
*
* Most of the MTD code for this driver was originally written
* for the slram.o module in the MTD drivers package which
* allows the mapping of system memory into an MTD device.
* Since the PMC551 memory module is accessed in the same
* fashion as system memory, the slram.c code became a very nice
* fit to the needs of this driver. All we added was PCI
* detection/initialization to the driver and automatically figure
* out the size via the PCI detection.o, later changes by Corey
* Minyard set up the card to utilize a 1M sliding apature.
*
* Corey Minyard <minyard@nortelnetworks.com>
* * Modified driver to utilize a sliding aperture instead of
* mapping all memory into kernel space which turned out to
* be very wasteful.
* * Located a bug in the SROM's initialization sequence that
* made the memory unusable, added a fix to code to touch up
* the DRAM some.
*
* Bugs/FIXMEs:
* * MUST fix the init function to not spin on a register
* waiting for it to set .. this does not safely handle busted
* devices that never reset the register correctly which will
* cause the system to hang w/ a reboot being the only chance at
* recover. [sort of fixed, could be better]
* * Add I2C handling of the SROM so we can read the SROM's information
* about the aperture size. This should always accurately reflect the
* onboard memory size.
* * Comb the init routine. It's still a bit cludgy on a few things.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <asm/uaccess.h>
#include <linux/types.h>
#include <linux/init.h>
#include <linux/ptrace.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/timer.h>
#include <linux/major.h>
#include <linux/fs.h>
#include <linux/ioctl.h>
#include <asm/io.h>
#include <linux/pci.h>
#include <linux/mtd/mtd.h>
#define PMC551_VERSION \
"Ramix PMC551 PCI Mezzanine Ram Driver. (C) 1999,2000 Nortel Networks.\n"
#define PCI_VENDOR_ID_V3_SEMI 0x11b0
#define PCI_DEVICE_ID_V3_SEMI_V370PDC 0x0200
#define PMC551_PCI_MEM_MAP0 0x50
#define PMC551_PCI_MEM_MAP1 0x54
#define PMC551_PCI_MEM_MAP_MAP_ADDR_MASK 0x3ff00000
#define PMC551_PCI_MEM_MAP_APERTURE_MASK 0x000000f0
#define PMC551_PCI_MEM_MAP_REG_EN 0x00000002
#define PMC551_PCI_MEM_MAP_ENABLE 0x00000001
#define PMC551_SDRAM_MA 0x60
#define PMC551_SDRAM_CMD 0x62
#define PMC551_DRAM_CFG 0x64
#define PMC551_SYS_CTRL_REG 0x78
#define PMC551_DRAM_BLK0 0x68
#define PMC551_DRAM_BLK1 0x6c
#define PMC551_DRAM_BLK2 0x70
#define PMC551_DRAM_BLK3 0x74
#define PMC551_DRAM_BLK_GET_SIZE(x) (524288 << ((x >> 4) & 0x0f))
#define PMC551_DRAM_BLK_SET_COL_MUX(x, v) (((x) & ~0x00007000) | (((v) & 0x7) << 12))
#define PMC551_DRAM_BLK_SET_ROW_MUX(x, v) (((x) & ~0x00000f00) | (((v) & 0xf) << 8))
struct mypriv {
struct pci_dev *dev;
u_char *start;
u32 base_map0;
u32 curr_map0;
u32 asize;
struct mtd_info *nextpmc551;
};
static struct mtd_info *pmc551list;
static int pmc551_point(struct mtd_info *mtd, loff_t from, size_t len,
size_t *retlen, void **virt, resource_size_t *phys);
static int pmc551_erase(struct mtd_info *mtd, struct erase_info *instr)
{
struct mypriv *priv = mtd->priv;
u32 soff_hi, soff_lo; /* start address offset hi/lo */
u32 eoff_hi, eoff_lo; /* end address offset hi/lo */
unsigned long end;
u_char *ptr;
size_t retlen;
#ifdef CONFIG_MTD_PMC551_DEBUG
printk(KERN_DEBUG "pmc551_erase(pos:%ld, len:%ld)\n", (long)instr->addr,
(long)instr->len);
#endif
end = instr->addr + instr->len - 1;
eoff_hi = end & ~(priv->asize - 1);
soff_hi = instr->addr & ~(priv->asize - 1);
eoff_lo = end & (priv->asize - 1);
soff_lo = instr->addr & (priv->asize - 1);
pmc551_point(mtd, instr->addr, instr->len, &retlen,
(void **)&ptr, NULL);
if (soff_hi == eoff_hi || mtd->size == priv->asize) {
/* The whole thing fits within one access, so just one shot
will do it. */
memset(ptr, 0xff, instr->len);
} else {
/* We have to do multiple writes to get all the data
written. */
while (soff_hi != eoff_hi) {
#ifdef CONFIG_MTD_PMC551_DEBUG
printk(KERN_DEBUG "pmc551_erase() soff_hi: %ld, "
"eoff_hi: %ld\n", (long)soff_hi, (long)eoff_hi);
#endif
memset(ptr, 0xff, priv->asize);
if (soff_hi + priv->asize >= mtd->size) {
goto out;
}
soff_hi += priv->asize;
pmc551_point(mtd, (priv->base_map0 | soff_hi),
priv->asize, &retlen,
(void **)&ptr, NULL);
}
memset(ptr, 0xff, eoff_lo);
}
out:
instr->state = MTD_ERASE_DONE;
#ifdef CONFIG_MTD_PMC551_DEBUG
printk(KERN_DEBUG "pmc551_erase() done\n");
#endif
mtd_erase_callback(instr);
return 0;
}
static int pmc551_point(struct mtd_info *mtd, loff_t from, size_t len,
size_t *retlen, void **virt, resource_size_t *phys)
{
struct mypriv *priv = mtd->priv;
u32 soff_hi;
u32 soff_lo;
#ifdef CONFIG_MTD_PMC551_DEBUG
printk(KERN_DEBUG "pmc551_point(%ld, %ld)\n", (long)from, (long)len);
#endif
soff_hi = from & ~(priv->asize - 1);
soff_lo = from & (priv->asize - 1);
/* Cheap hack optimization */
if (priv->curr_map0 != from) {
pci_write_config_dword(priv->dev, PMC551_PCI_MEM_MAP0,
(priv->base_map0 | soff_hi));
priv->curr_map0 = soff_hi;
}
*virt = priv->start + soff_lo;
*retlen = len;
return 0;
}
static int pmc551_unpoint(struct mtd_info *mtd, loff_t from, size_t len)
{
#ifdef CONFIG_MTD_PMC551_DEBUG
printk(KERN_DEBUG "pmc551_unpoint()\n");
#endif
return 0;
}
static int pmc551_read(struct mtd_info *mtd, loff_t from, size_t len,
size_t * retlen, u_char * buf)
{
struct mypriv *priv = mtd->priv;
u32 soff_hi, soff_lo; /* start address offset hi/lo */
u32 eoff_hi, eoff_lo; /* end address offset hi/lo */
unsigned long end;
u_char *ptr;
u_char *copyto = buf;
#ifdef CONFIG_MTD_PMC551_DEBUG
printk(KERN_DEBUG "pmc551_read(pos:%ld, len:%ld) asize: %ld\n",
(long)from, (long)len, (long)priv->asize);
#endif
end = from + len - 1;
soff_hi = from & ~(priv->asize - 1);
eoff_hi = end & ~(priv->asize - 1);
soff_lo = from & (priv->asize - 1);
eoff_lo = end & (priv->asize - 1);
pmc551_point(mtd, from, len, retlen, (void **)&ptr, NULL);
if (soff_hi == eoff_hi) {
/* The whole thing fits within one access, so just one shot
will do it. */
memcpy(copyto, ptr, len);
copyto += len;
} else {
/* We have to do multiple writes to get all the data
written. */
while (soff_hi != eoff_hi) {
#ifdef CONFIG_MTD_PMC551_DEBUG
printk(KERN_DEBUG "pmc551_read() soff_hi: %ld, "
"eoff_hi: %ld\n", (long)soff_hi, (long)eoff_hi);
#endif
memcpy(copyto, ptr, priv->asize);
copyto += priv->asize;
if (soff_hi + priv->asize >= mtd->size) {
goto out;
}
soff_hi += priv->asize;
pmc551_point(mtd, soff_hi, priv->asize, retlen,
(void **)&ptr, NULL);
}
memcpy(copyto, ptr, eoff_lo);
copyto += eoff_lo;
}
out:
#ifdef CONFIG_MTD_PMC551_DEBUG
printk(KERN_DEBUG "pmc551_read() done\n");
#endif
*retlen = copyto - buf;
return 0;
}
static int pmc551_write(struct mtd_info *mtd, loff_t to, size_t len,
size_t * retlen, const u_char * buf)
{
struct mypriv *priv = mtd->priv;
u32 soff_hi, soff_lo; /* start address offset hi/lo */
u32 eoff_hi, eoff_lo; /* end address offset hi/lo */
unsigned long end;
u_char *ptr;
const u_char *copyfrom = buf;
#ifdef CONFIG_MTD_PMC551_DEBUG
printk(KERN_DEBUG "pmc551_write(pos:%ld, len:%ld) asize:%ld\n",
(long)to, (long)len, (long)priv->asize);
#endif
end = to + len - 1;
soff_hi = to & ~(priv->asize - 1);
eoff_hi = end & ~(priv->asize - 1);
soff_lo = to & (priv->asize - 1);
eoff_lo = end & (priv->asize - 1);
pmc551_point(mtd, to, len, retlen, (void **)&ptr, NULL);
if (soff_hi == eoff_hi) {
/* The whole thing fits within one access, so just one shot
will do it. */
memcpy(ptr, copyfrom, len);
copyfrom += len;
} else {
/* We have to do multiple writes to get all the data
written. */
while (soff_hi != eoff_hi) {
#ifdef CONFIG_MTD_PMC551_DEBUG
printk(KERN_DEBUG "pmc551_write() soff_hi: %ld, "
"eoff_hi: %ld\n", (long)soff_hi, (long)eoff_hi);
#endif
memcpy(ptr, copyfrom, priv->asize);
copyfrom += priv->asize;
if (soff_hi >= mtd->size) {
goto out;
}
soff_hi += priv->asize;
pmc551_point(mtd, soff_hi, priv->asize, retlen,
(void **)&ptr, NULL);
}
memcpy(ptr, copyfrom, eoff_lo);
copyfrom += eoff_lo;
}
out:
#ifdef CONFIG_MTD_PMC551_DEBUG
printk(KERN_DEBUG "pmc551_write() done\n");
#endif
*retlen = copyfrom - buf;
return 0;
}
/*
* Fixup routines for the V370PDC
* PCI device ID 0x020011b0
*
* This function basically kick starts the DRAM oboard the card and gets it
* ready to be used. Before this is done the device reads VERY erratic, so
* much that it can crash the Linux 2.2.x series kernels when a user cat's
* /proc/pci .. though that is mainly a kernel bug in handling the PCI DEVSEL
* register. FIXME: stop spinning on registers .. must implement a timeout
* mechanism
* returns the size of the memory region found.
*/
static int fixup_pmc551(struct pci_dev *dev)
{
#ifdef CONFIG_MTD_PMC551_BUGFIX
u32 dram_data;
#endif
u32 size, dcmd, cfg, dtmp;
u16 cmd, tmp, i;
u8 bcmd, counter;
/* Sanity Check */
if (!dev) {
return -ENODEV;
}
/*
* Attempt to reset the card
* FIXME: Stop Spinning registers
*/
counter = 0;
/* unlock registers */
pci_write_config_byte(dev, PMC551_SYS_CTRL_REG, 0xA5);
/* read in old data */
pci_read_config_byte(dev, PMC551_SYS_CTRL_REG, &bcmd);
/* bang the reset line up and down for a few */
for (i = 0; i < 10; i++) {
counter = 0;
bcmd &= ~0x80;
while (counter++ < 100) {
pci_write_config_byte(dev, PMC551_SYS_CTRL_REG, bcmd);
}
counter = 0;
bcmd |= 0x80;
while (counter++ < 100) {
pci_write_config_byte(dev, PMC551_SYS_CTRL_REG, bcmd);
}
}
bcmd |= (0x40 | 0x20);
pci_write_config_byte(dev, PMC551_SYS_CTRL_REG, bcmd);
/*
* Take care and turn off the memory on the device while we
* tweak the configurations
*/
pci_read_config_word(dev, PCI_COMMAND, &cmd);
tmp = cmd & ~(PCI_COMMAND_IO | PCI_COMMAND_MEMORY);
pci_write_config_word(dev, PCI_COMMAND, tmp);
/*
* Disable existing aperture before probing memory size
*/
pci_read_config_dword(dev, PMC551_PCI_MEM_MAP0, &dcmd);
dtmp = (dcmd | PMC551_PCI_MEM_MAP_ENABLE | PMC551_PCI_MEM_MAP_REG_EN);
pci_write_config_dword(dev, PMC551_PCI_MEM_MAP0, dtmp);
/*
* Grab old BAR0 config so that we can figure out memory size
* This is another bit of kludge going on. The reason for the
* redundancy is I am hoping to retain the original configuration
* previously assigned to the card by the BIOS or some previous
* fixup routine in the kernel. So we read the old config into cfg,
* then write all 1's to the memory space, read back the result into
* "size", and then write back all the old config.
*/
pci_read_config_dword(dev, PCI_BASE_ADDRESS_0, &cfg);
#ifndef CONFIG_MTD_PMC551_BUGFIX
pci_write_config_dword(dev, PCI_BASE_ADDRESS_0, ~0);
pci_read_config_dword(dev, PCI_BASE_ADDRESS_0, &size);
size = (size & PCI_BASE_ADDRESS_MEM_MASK);
size &= ~(size - 1);
pci_write_config_dword(dev, PCI_BASE_ADDRESS_0, cfg);
#else
/*
* Get the size of the memory by reading all the DRAM size values
* and adding them up.
*
* KLUDGE ALERT: the boards we are using have invalid column and
* row mux values. We fix them here, but this will break other
* memory configurations.
*/
pci_read_config_dword(dev, PMC551_DRAM_BLK0, &dram_data);
size = PMC551_DRAM_BLK_GET_SIZE(dram_data);
dram_data = PMC551_DRAM_BLK_SET_COL_MUX(dram_data, 0x5);
dram_data = PMC551_DRAM_BLK_SET_ROW_MUX(dram_data, 0x9);
pci_write_config_dword(dev, PMC551_DRAM_BLK0, dram_data);
pci_read_config_dword(dev, PMC551_DRAM_BLK1, &dram_data);
size += PMC551_DRAM_BLK_GET_SIZE(dram_data);
dram_data = PMC551_DRAM_BLK_SET_COL_MUX(dram_data, 0x5);
dram_data = PMC551_DRAM_BLK_SET_ROW_MUX(dram_data, 0x9);
pci_write_config_dword(dev, PMC551_DRAM_BLK1, dram_data);
pci_read_config_dword(dev, PMC551_DRAM_BLK2, &dram_data);
size += PMC551_DRAM_BLK_GET_SIZE(dram_data);
dram_data = PMC551_DRAM_BLK_SET_COL_MUX(dram_data, 0x5);
dram_data = PMC551_DRAM_BLK_SET_ROW_MUX(dram_data, 0x9);
pci_write_config_dword(dev, PMC551_DRAM_BLK2, dram_data);
pci_read_config_dword(dev, PMC551_DRAM_BLK3, &dram_data);
size += PMC551_DRAM_BLK_GET_SIZE(dram_data);
dram_data = PMC551_DRAM_BLK_SET_COL_MUX(dram_data, 0x5);
dram_data = PMC551_DRAM_BLK_SET_ROW_MUX(dram_data, 0x9);
pci_write_config_dword(dev, PMC551_DRAM_BLK3, dram_data);
/*
* Oops .. something went wrong
*/
if ((size &= PCI_BASE_ADDRESS_MEM_MASK) == 0) {
return -ENODEV;
}
#endif /* CONFIG_MTD_PMC551_BUGFIX */
if ((cfg & PCI_BASE_ADDRESS_SPACE) != PCI_BASE_ADDRESS_SPACE_MEMORY) {
return -ENODEV;
}
/*
* Precharge Dram
*/
pci_write_config_word(dev, PMC551_SDRAM_MA, 0x0400);
pci_write_config_word(dev, PMC551_SDRAM_CMD, 0x00bf);
/*
* Wait until command has gone through
* FIXME: register spinning issue
*/
do {
pci_read_config_word(dev, PMC551_SDRAM_CMD, &cmd);
if (counter++ > 100)
break;
} while ((PCI_COMMAND_IO) & cmd);
/*
* Turn on auto refresh
* The loop is taken directly from Ramix's example code. I assume that
* this must be held high for some duration of time, but I can find no
* documentation refrencing the reasons why.
*/
for (i = 1; i <= 8; i++) {
pci_write_config_word(dev, PMC551_SDRAM_CMD, 0x0df);
/*
* Make certain command has gone through
* FIXME: register spinning issue
*/
counter = 0;
do {
pci_read_config_word(dev, PMC551_SDRAM_CMD, &cmd);
if (counter++ > 100)
break;
} while ((PCI_COMMAND_IO) & cmd);
}
pci_write_config_word(dev, PMC551_SDRAM_MA, 0x0020);
pci_write_config_word(dev, PMC551_SDRAM_CMD, 0x0ff);
/*
* Wait until command completes
* FIXME: register spinning issue
*/
counter = 0;
do {
pci_read_config_word(dev, PMC551_SDRAM_CMD, &cmd);
if (counter++ > 100)
break;
} while ((PCI_COMMAND_IO) & cmd);
pci_read_config_dword(dev, PMC551_DRAM_CFG, &dcmd);
dcmd |= 0x02000000;
pci_write_config_dword(dev, PMC551_DRAM_CFG, dcmd);
/*
* Check to make certain fast back-to-back, if not
* then set it so
*/
pci_read_config_word(dev, PCI_STATUS, &cmd);
if ((cmd & PCI_COMMAND_FAST_BACK) == 0) {
cmd |= PCI_COMMAND_FAST_BACK;
pci_write_config_word(dev, PCI_STATUS, cmd);
}
/*
* Check to make certain the DEVSEL is set correctly, this device
* has a tendency to assert DEVSEL and TRDY when a write is performed
* to the memory when memory is read-only
*/
if ((cmd & PCI_STATUS_DEVSEL_MASK) != 0x0) {
cmd &= ~PCI_STATUS_DEVSEL_MASK;
pci_write_config_word(dev, PCI_STATUS, cmd);
}
/*
* Set to be prefetchable and put everything back based on old cfg.
* it's possible that the reset of the V370PDC nuked the original
* setup
*/
/*
cfg |= PCI_BASE_ADDRESS_MEM_PREFETCH;
pci_write_config_dword( dev, PCI_BASE_ADDRESS_0, cfg );
*/
/*
* Turn PCI memory and I/O bus access back on
*/
pci_write_config_word(dev, PCI_COMMAND,
PCI_COMMAND_MEMORY | PCI_COMMAND_IO);
#ifdef CONFIG_MTD_PMC551_DEBUG
/*
* Some screen fun
*/
printk(KERN_DEBUG "pmc551: %d%sB (0x%x) of %sprefetchable memory at "
"0x%llx\n", (size < 1024) ? size : (size < 1048576) ?
size >> 10 : size >> 20,
(size < 1024) ? "" : (size < 1048576) ? "Ki" : "Mi", size,
((dcmd & (0x1 << 3)) == 0) ? "non-" : "",
(unsigned long long)pci_resource_start(dev, 0));
/*
* Check to see the state of the memory
*/
pci_read_config_dword(dev, PMC551_DRAM_BLK0, &dcmd);
printk(KERN_DEBUG "pmc551: DRAM_BLK0 Flags: %s,%s\n"
"pmc551: DRAM_BLK0 Size: %d at %d\n"
"pmc551: DRAM_BLK0 Row MUX: %d, Col MUX: %d\n",
(((0x1 << 1) & dcmd) == 0) ? "RW" : "RO",
(((0x1 << 0) & dcmd) == 0) ? "Off" : "On",
PMC551_DRAM_BLK_GET_SIZE(dcmd),
((dcmd >> 20) & 0x7FF), ((dcmd >> 13) & 0x7),
((dcmd >> 9) & 0xF));
pci_read_config_dword(dev, PMC551_DRAM_BLK1, &dcmd);
printk(KERN_DEBUG "pmc551: DRAM_BLK1 Flags: %s,%s\n"
"pmc551: DRAM_BLK1 Size: %d at %d\n"
"pmc551: DRAM_BLK1 Row MUX: %d, Col MUX: %d\n",
(((0x1 << 1) & dcmd) == 0) ? "RW" : "RO",
(((0x1 << 0) & dcmd) == 0) ? "Off" : "On",
PMC551_DRAM_BLK_GET_SIZE(dcmd),
((dcmd >> 20) & 0x7FF), ((dcmd >> 13) & 0x7),
((dcmd >> 9) & 0xF));
pci_read_config_dword(dev, PMC551_DRAM_BLK2, &dcmd);
printk(KERN_DEBUG "pmc551: DRAM_BLK2 Flags: %s,%s\n"
"pmc551: DRAM_BLK2 Size: %d at %d\n"
"pmc551: DRAM_BLK2 Row MUX: %d, Col MUX: %d\n",
(((0x1 << 1) & dcmd) == 0) ? "RW" : "RO",
(((0x1 << 0) & dcmd) == 0) ? "Off" : "On",
PMC551_DRAM_BLK_GET_SIZE(dcmd),
((dcmd >> 20) & 0x7FF), ((dcmd >> 13) & 0x7),
((dcmd >> 9) & 0xF));
pci_read_config_dword(dev, PMC551_DRAM_BLK3, &dcmd);
printk(KERN_DEBUG "pmc551: DRAM_BLK3 Flags: %s,%s\n"
"pmc551: DRAM_BLK3 Size: %d at %d\n"
"pmc551: DRAM_BLK3 Row MUX: %d, Col MUX: %d\n",
(((0x1 << 1) & dcmd) == 0) ? "RW" : "RO",
(((0x1 << 0) & dcmd) == 0) ? "Off" : "On",
PMC551_DRAM_BLK_GET_SIZE(dcmd),
((dcmd >> 20) & 0x7FF), ((dcmd >> 13) & 0x7),
((dcmd >> 9) & 0xF));
pci_read_config_word(dev, PCI_COMMAND, &cmd);
printk(KERN_DEBUG "pmc551: Memory Access %s\n",
(((0x1 << 1) & cmd) == 0) ? "off" : "on");
printk(KERN_DEBUG "pmc551: I/O Access %s\n",
(((0x1 << 0) & cmd) == 0) ? "off" : "on");
pci_read_config_word(dev, PCI_STATUS, &cmd);
printk(KERN_DEBUG "pmc551: Devsel %s\n",
((PCI_STATUS_DEVSEL_MASK & cmd) == 0x000) ? "Fast" :
((PCI_STATUS_DEVSEL_MASK & cmd) == 0x200) ? "Medium" :
((PCI_STATUS_DEVSEL_MASK & cmd) == 0x400) ? "Slow" : "Invalid");
printk(KERN_DEBUG "pmc551: %sFast Back-to-Back\n",
((PCI_COMMAND_FAST_BACK & cmd) == 0) ? "Not " : "");
pci_read_config_byte(dev, PMC551_SYS_CTRL_REG, &bcmd);
printk(KERN_DEBUG "pmc551: EEPROM is under %s control\n"
"pmc551: System Control Register is %slocked to PCI access\n"
"pmc551: System Control Register is %slocked to EEPROM access\n",
(bcmd & 0x1) ? "software" : "hardware",
(bcmd & 0x20) ? "" : "un", (bcmd & 0x40) ? "" : "un");
#endif
return size;
}
/*
* Kernel version specific module stuffages
*/
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Mark Ferrell <mferrell@mvista.com>");
MODULE_DESCRIPTION(PMC551_VERSION);
/*
* Stuff these outside the ifdef so as to not bust compiled in driver support
*/
static int msize = 0;
static int asize = 0;
module_param(msize, int, 0);
MODULE_PARM_DESC(msize, "memory size in MiB [1 - 1024]");
module_param(asize, int, 0);
MODULE_PARM_DESC(asize, "aperture size, must be <= memsize [1-1024]");
/*
* PMC551 Card Initialization
*/
static int __init init_pmc551(void)
{
struct pci_dev *PCI_Device = NULL;
struct mypriv *priv;
int found = 0;
struct mtd_info *mtd;
int length = 0;
if (msize) {
msize = (1 << (ffs(msize) - 1)) << 20;
if (msize > (1 << 30)) {
printk(KERN_NOTICE "pmc551: Invalid memory size [%d]\n",
msize);
return -EINVAL;
}
}
if (asize) {
asize = (1 << (ffs(asize) - 1)) << 20;
if (asize > (1 << 30)) {
printk(KERN_NOTICE "pmc551: Invalid aperture size "
"[%d]\n", asize);
return -EINVAL;
}
}
printk(KERN_INFO PMC551_VERSION);
/*
* PCU-bus chipset probe.
*/
for (;;) {
if ((PCI_Device = pci_get_device(PCI_VENDOR_ID_V3_SEMI,
PCI_DEVICE_ID_V3_SEMI_V370PDC,
PCI_Device)) == NULL) {
break;
}
printk(KERN_NOTICE "pmc551: Found PCI V370PDC at 0x%llx\n",
(unsigned long long)pci_resource_start(PCI_Device, 0));
/*
* The PMC551 device acts VERY weird if you don't init it
* first. i.e. it will not correctly report devsel. If for
* some reason the sdram is in a wrote-protected state the
* device will DEVSEL when it is written to causing problems
* with the oldproc.c driver in
* some kernels (2.2.*)
*/
if ((length = fixup_pmc551(PCI_Device)) <= 0) {
printk(KERN_NOTICE "pmc551: Cannot init SDRAM\n");
break;
}
/*
* This is needed until the driver is capable of reading the
* onboard I2C SROM to discover the "real" memory size.
*/
if (msize) {
length = msize;
printk(KERN_NOTICE "pmc551: Using specified memory "
"size 0x%x\n", length);
} else {
msize = length;
}
mtd = kzalloc(sizeof(struct mtd_info), GFP_KERNEL);
if (!mtd) {
printk(KERN_NOTICE "pmc551: Cannot allocate new MTD "
"device.\n");
break;
}
priv = kzalloc(sizeof(struct mypriv), GFP_KERNEL);
if (!priv) {
printk(KERN_NOTICE "pmc551: Cannot allocate new MTD "
"device.\n");
kfree(mtd);
break;
}
mtd->priv = priv;
priv->dev = PCI_Device;
if (asize > length) {
printk(KERN_NOTICE "pmc551: reducing aperture size to "
"fit %dM\n", length >> 20);
priv->asize = asize = length;
} else if (asize == 0 || asize == length) {
printk(KERN_NOTICE "pmc551: Using existing aperture "
"size %dM\n", length >> 20);
priv->asize = asize = length;
} else {
printk(KERN_NOTICE "pmc551: Using specified aperture "
"size %dM\n", asize >> 20);
priv->asize = asize;
}
priv->start = pci_iomap(PCI_Device, 0, priv->asize);
if (!priv->start) {
printk(KERN_NOTICE "pmc551: Unable to map IO space\n");
kfree(mtd->priv);
kfree(mtd);
break;
}
#ifdef CONFIG_MTD_PMC551_DEBUG
printk(KERN_DEBUG "pmc551: setting aperture to %d\n",
ffs(priv->asize >> 20) - 1);
#endif
priv->base_map0 = (PMC551_PCI_MEM_MAP_REG_EN
| PMC551_PCI_MEM_MAP_ENABLE
| (ffs(priv->asize >> 20) - 1) << 4);
priv->curr_map0 = priv->base_map0;
pci_write_config_dword(priv->dev, PMC551_PCI_MEM_MAP0,
priv->curr_map0);
#ifdef CONFIG_MTD_PMC551_DEBUG
printk(KERN_DEBUG "pmc551: aperture set to %d\n",
(priv->base_map0 & 0xF0) >> 4);
#endif
mtd->size = msize;
mtd->flags = MTD_CAP_RAM;
mtd->_erase = pmc551_erase;
mtd->_read = pmc551_read;
mtd->_write = pmc551_write;
mtd->_point = pmc551_point;
mtd->_unpoint = pmc551_unpoint;
mtd->type = MTD_RAM;
mtd->name = "PMC551 RAM board";
mtd->erasesize = 0x10000;
mtd->writesize = 1;
mtd->owner = THIS_MODULE;
if (mtd_device_register(mtd, NULL, 0)) {
printk(KERN_NOTICE "pmc551: Failed to register new device\n");
pci_iounmap(PCI_Device, priv->start);
kfree(mtd->priv);
kfree(mtd);
break;
}
/* Keep a reference as the mtd_device_register worked */
pci_dev_get(PCI_Device);
printk(KERN_NOTICE "Registered pmc551 memory device.\n");
printk(KERN_NOTICE "Mapped %dMiB of memory from 0x%p to 0x%p\n",
priv->asize >> 20,
priv->start, priv->start + priv->asize);
printk(KERN_NOTICE "Total memory is %d%sB\n",
(length < 1024) ? length :
(length < 1048576) ? length >> 10 : length >> 20,
(length < 1024) ? "" : (length < 1048576) ? "Ki" : "Mi");
priv->nextpmc551 = pmc551list;
pmc551list = mtd;
found++;
}
/* Exited early, reference left over */
if (PCI_Device)
pci_dev_put(PCI_Device);
if (!pmc551list) {
printk(KERN_NOTICE "pmc551: not detected\n");
return -ENODEV;
} else {
printk(KERN_NOTICE "pmc551: %d pmc551 devices loaded\n", found);
return 0;
}
}
/*
* PMC551 Card Cleanup
*/
static void __exit cleanup_pmc551(void)
{
int found = 0;
struct mtd_info *mtd;
struct mypriv *priv;
while ((mtd = pmc551list)) {
priv = mtd->priv;
pmc551list = priv->nextpmc551;
if (priv->start) {
printk(KERN_DEBUG "pmc551: unmapping %dMiB starting at "
"0x%p\n", priv->asize >> 20, priv->start);
pci_iounmap(priv->dev, priv->start);
}
pci_dev_put(priv->dev);
kfree(mtd->priv);
mtd_device_unregister(mtd);
kfree(mtd);
found++;
}
printk(KERN_NOTICE "pmc551: %d pmc551 devices unloaded\n", found);
}
module_init(init_pmc551);
module_exit(cleanup_pmc551);
+349
View File
@@ -0,0 +1,349 @@
/*======================================================================
This driver provides a method to access memory not used by the kernel
itself (i.e. if the kernel commandline mem=xxx is used). To actually
use slram at least mtdblock or mtdchar is required (for block or
character device access).
Usage:
if compiled as loadable module:
modprobe slram map=<name>,<start>,<end/offset>
if statically linked into the kernel use the following kernel cmd.line
slram=<name>,<start>,<end/offset>
<name>: name of the device that will be listed in /proc/mtd
<start>: start of the memory region, decimal or hex (0xabcdef)
<end/offset>: end of the memory region. It's possible to use +0x1234
to specify the offset instead of the absolute address
NOTE:
With slram it's only possible to map a contiguous memory region. Therefore
if there's a device mapped somewhere in the region specified slram will
fail to load (see kernel log if modprobe fails).
-
Jochen Schaeuble <psionic@psionic.de>
======================================================================*/
#include <linux/module.h>
#include <asm/uaccess.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/ptrace.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/timer.h>
#include <linux/major.h>
#include <linux/fs.h>
#include <linux/ioctl.h>
#include <linux/init.h>
#include <asm/io.h>
#include <linux/mtd/mtd.h>
#define SLRAM_MAX_DEVICES_PARAMS 6 /* 3 parameters / device */
#define SLRAM_BLK_SZ 0x4000
#define T(fmt, args...) printk(KERN_DEBUG fmt, ## args)
#define E(fmt, args...) printk(KERN_NOTICE fmt, ## args)
typedef struct slram_priv {
u_char *start;
u_char *end;
} slram_priv_t;
typedef struct slram_mtd_list {
struct mtd_info *mtdinfo;
struct slram_mtd_list *next;
} slram_mtd_list_t;
#ifdef MODULE
static char *map[SLRAM_MAX_DEVICES_PARAMS];
module_param_array(map, charp, NULL, 0);
MODULE_PARM_DESC(map, "List of memory regions to map. \"map=<name>, <start>, <length / end>\"");
#else
static char *map;
#endif
static slram_mtd_list_t *slram_mtdlist = NULL;
static int slram_erase(struct mtd_info *, struct erase_info *);
static int slram_point(struct mtd_info *, loff_t, size_t, size_t *, void **,
resource_size_t *);
static int slram_unpoint(struct mtd_info *, loff_t, size_t);
static int slram_read(struct mtd_info *, loff_t, size_t, size_t *, u_char *);
static int slram_write(struct mtd_info *, loff_t, size_t, size_t *, const u_char *);
static int slram_erase(struct mtd_info *mtd, struct erase_info *instr)
{
slram_priv_t *priv = mtd->priv;
memset(priv->start + instr->addr, 0xff, instr->len);
/* This'll catch a few races. Free the thing before returning :)
* I don't feel at all ashamed. This kind of thing is possible anyway
* with flash, but unlikely.
*/
instr->state = MTD_ERASE_DONE;
mtd_erase_callback(instr);
return(0);
}
static int slram_point(struct mtd_info *mtd, loff_t from, size_t len,
size_t *retlen, void **virt, resource_size_t *phys)
{
slram_priv_t *priv = mtd->priv;
*virt = priv->start + from;
*retlen = len;
return(0);
}
static int slram_unpoint(struct mtd_info *mtd, loff_t from, size_t len)
{
return 0;
}
static int slram_read(struct mtd_info *mtd, loff_t from, size_t len,
size_t *retlen, u_char *buf)
{
slram_priv_t *priv = mtd->priv;
memcpy(buf, priv->start + from, len);
*retlen = len;
return(0);
}
static int slram_write(struct mtd_info *mtd, loff_t to, size_t len,
size_t *retlen, const u_char *buf)
{
slram_priv_t *priv = mtd->priv;
memcpy(priv->start + to, buf, len);
*retlen = len;
return(0);
}
/*====================================================================*/
static int register_device(char *name, unsigned long start, unsigned long length)
{
slram_mtd_list_t **curmtd;
curmtd = &slram_mtdlist;
while (*curmtd) {
curmtd = &(*curmtd)->next;
}
*curmtd = kmalloc(sizeof(slram_mtd_list_t), GFP_KERNEL);
if (!(*curmtd)) {
E("slram: Cannot allocate new MTD device.\n");
return(-ENOMEM);
}
(*curmtd)->mtdinfo = kzalloc(sizeof(struct mtd_info), GFP_KERNEL);
(*curmtd)->next = NULL;
if ((*curmtd)->mtdinfo) {
(*curmtd)->mtdinfo->priv =
kzalloc(sizeof(slram_priv_t), GFP_KERNEL);
if (!(*curmtd)->mtdinfo->priv) {
kfree((*curmtd)->mtdinfo);
(*curmtd)->mtdinfo = NULL;
}
}
if (!(*curmtd)->mtdinfo) {
E("slram: Cannot allocate new MTD device.\n");
return(-ENOMEM);
}
if (!(((slram_priv_t *)(*curmtd)->mtdinfo->priv)->start =
ioremap(start, length))) {
E("slram: ioremap failed\n");
return -EIO;
}
((slram_priv_t *)(*curmtd)->mtdinfo->priv)->end =
((slram_priv_t *)(*curmtd)->mtdinfo->priv)->start + length;
(*curmtd)->mtdinfo->name = name;
(*curmtd)->mtdinfo->size = length;
(*curmtd)->mtdinfo->flags = MTD_CAP_RAM;
(*curmtd)->mtdinfo->_erase = slram_erase;
(*curmtd)->mtdinfo->_point = slram_point;
(*curmtd)->mtdinfo->_unpoint = slram_unpoint;
(*curmtd)->mtdinfo->_read = slram_read;
(*curmtd)->mtdinfo->_write = slram_write;
(*curmtd)->mtdinfo->owner = THIS_MODULE;
(*curmtd)->mtdinfo->type = MTD_RAM;
(*curmtd)->mtdinfo->erasesize = SLRAM_BLK_SZ;
(*curmtd)->mtdinfo->writesize = 1;
if (mtd_device_register((*curmtd)->mtdinfo, NULL, 0)) {
E("slram: Failed to register new device\n");
iounmap(((slram_priv_t *)(*curmtd)->mtdinfo->priv)->start);
kfree((*curmtd)->mtdinfo->priv);
kfree((*curmtd)->mtdinfo);
return(-EAGAIN);
}
T("slram: Registered device %s from %luKiB to %luKiB\n", name,
(start / 1024), ((start + length) / 1024));
T("slram: Mapped from 0x%p to 0x%p\n",
((slram_priv_t *)(*curmtd)->mtdinfo->priv)->start,
((slram_priv_t *)(*curmtd)->mtdinfo->priv)->end);
return(0);
}
static void unregister_devices(void)
{
slram_mtd_list_t *nextitem;
while (slram_mtdlist) {
nextitem = slram_mtdlist->next;
mtd_device_unregister(slram_mtdlist->mtdinfo);
iounmap(((slram_priv_t *)slram_mtdlist->mtdinfo->priv)->start);
kfree(slram_mtdlist->mtdinfo->priv);
kfree(slram_mtdlist->mtdinfo);
kfree(slram_mtdlist);
slram_mtdlist = nextitem;
}
}
static unsigned long handle_unit(unsigned long value, char *unit)
{
if ((*unit == 'M') || (*unit == 'm')) {
return(value * 1024 * 1024);
} else if ((*unit == 'K') || (*unit == 'k')) {
return(value * 1024);
}
return(value);
}
static int parse_cmdline(char *devname, char *szstart, char *szlength)
{
char *buffer;
unsigned long devstart;
unsigned long devlength;
if ((!devname) || (!szstart) || (!szlength)) {
unregister_devices();
return(-EINVAL);
}
devstart = simple_strtoul(szstart, &buffer, 0);
devstart = handle_unit(devstart, buffer);
if (*(szlength) != '+') {
devlength = simple_strtoul(szlength, &buffer, 0);
devlength = handle_unit(devlength, buffer) - devstart;
if (devlength < devstart)
goto err_out;
devlength -= devstart;
} else {
devlength = simple_strtoul(szlength + 1, &buffer, 0);
devlength = handle_unit(devlength, buffer);
}
T("slram: devname=%s, devstart=0x%lx, devlength=0x%lx\n",
devname, devstart, devlength);
if (devlength % SLRAM_BLK_SZ != 0)
goto err_out;
if ((devstart = register_device(devname, devstart, devlength))){
unregister_devices();
return((int)devstart);
}
return(0);
err_out:
E("slram: Illegal length parameter.\n");
return(-EINVAL);
}
#ifndef MODULE
static int __init mtd_slram_setup(char *str)
{
map = str;
return(1);
}
__setup("slram=", mtd_slram_setup);
#endif
static int __init init_slram(void)
{
char *devname;
int i;
#ifndef MODULE
char *devstart;
char *devlength;
i = 0;
if (!map) {
E("slram: not enough parameters.\n");
return(-EINVAL);
}
while (map) {
devname = devstart = devlength = NULL;
if (!(devname = strsep(&map, ","))) {
E("slram: No devicename specified.\n");
break;
}
T("slram: devname = %s\n", devname);
if ((!map) || (!(devstart = strsep(&map, ",")))) {
E("slram: No devicestart specified.\n");
}
T("slram: devstart = %s\n", devstart);
if ((!map) || (!(devlength = strsep(&map, ",")))) {
E("slram: No devicelength / -end specified.\n");
}
T("slram: devlength = %s\n", devlength);
if (parse_cmdline(devname, devstart, devlength) != 0) {
return(-EINVAL);
}
}
#else
int count;
for (count = 0; count < SLRAM_MAX_DEVICES_PARAMS && map[count];
count++) {
}
if ((count % 3 != 0) || (count == 0)) {
E("slram: not enough parameters.\n");
return(-EINVAL);
}
for (i = 0; i < (count / 3); i++) {
devname = map[i * 3];
if (parse_cmdline(devname, map[i * 3 + 1], map[i * 3 + 2])!=0) {
return(-EINVAL);
}
}
#endif /* !MODULE */
return(0);
}
static void __exit cleanup_slram(void)
{
unregister_devices();
}
module_init(init_slram);
module_exit(cleanup_slram);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Jochen Schaeuble <psionic@psionic.de>");
MODULE_DESCRIPTION("MTD driver for uncached system RAM");
File diff suppressed because it is too large Load Diff
+439
View File
@@ -0,0 +1,439 @@
/*
* sst25l.c
*
* Driver for SST25L SPI Flash chips
*
* Copyright © 2009 Bluewater Systems Ltd
* Author: Andre Renaud <andre@bluewatersys.com>
* Author: Ryan Mallon
*
* Based on m25p80.c
*
* This code is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/mutex.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/sched.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/partitions.h>
#include <linux/spi/spi.h>
#include <linux/spi/flash.h>
/* Erases can take up to 3 seconds! */
#define MAX_READY_WAIT_JIFFIES msecs_to_jiffies(3000)
#define SST25L_CMD_WRSR 0x01 /* Write status register */
#define SST25L_CMD_WRDI 0x04 /* Write disable */
#define SST25L_CMD_RDSR 0x05 /* Read status register */
#define SST25L_CMD_WREN 0x06 /* Write enable */
#define SST25L_CMD_READ 0x03 /* High speed read */
#define SST25L_CMD_EWSR 0x50 /* Enable write status register */
#define SST25L_CMD_SECTOR_ERASE 0x20 /* Erase sector */
#define SST25L_CMD_READ_ID 0x90 /* Read device ID */
#define SST25L_CMD_AAI_PROGRAM 0xaf /* Auto address increment */
#define SST25L_STATUS_BUSY (1 << 0) /* Chip is busy */
#define SST25L_STATUS_WREN (1 << 1) /* Write enabled */
#define SST25L_STATUS_BP0 (1 << 2) /* Block protection 0 */
#define SST25L_STATUS_BP1 (1 << 3) /* Block protection 1 */
struct sst25l_flash {
struct spi_device *spi;
struct mutex lock;
struct mtd_info mtd;
};
struct flash_info {
const char *name;
uint16_t device_id;
unsigned page_size;
unsigned nr_pages;
unsigned erase_size;
};
#define to_sst25l_flash(x) container_of(x, struct sst25l_flash, mtd)
static struct flash_info __devinitdata sst25l_flash_info[] = {
{"sst25lf020a", 0xbf43, 256, 1024, 4096},
{"sst25lf040a", 0xbf44, 256, 2048, 4096},
};
static int sst25l_status(struct sst25l_flash *flash, int *status)
{
struct spi_message m;
struct spi_transfer t;
unsigned char cmd_resp[2];
int err;
spi_message_init(&m);
memset(&t, 0, sizeof(struct spi_transfer));
cmd_resp[0] = SST25L_CMD_RDSR;
cmd_resp[1] = 0xff;
t.tx_buf = cmd_resp;
t.rx_buf = cmd_resp;
t.len = sizeof(cmd_resp);
spi_message_add_tail(&t, &m);
err = spi_sync(flash->spi, &m);
if (err < 0)
return err;
*status = cmd_resp[1];
return 0;
}
static int sst25l_write_enable(struct sst25l_flash *flash, int enable)
{
unsigned char command[2];
int status, err;
command[0] = enable ? SST25L_CMD_WREN : SST25L_CMD_WRDI;
err = spi_write(flash->spi, command, 1);
if (err)
return err;
command[0] = SST25L_CMD_EWSR;
err = spi_write(flash->spi, command, 1);
if (err)
return err;
command[0] = SST25L_CMD_WRSR;
command[1] = enable ? 0 : SST25L_STATUS_BP0 | SST25L_STATUS_BP1;
err = spi_write(flash->spi, command, 2);
if (err)
return err;
if (enable) {
err = sst25l_status(flash, &status);
if (err)
return err;
if (!(status & SST25L_STATUS_WREN))
return -EROFS;
}
return 0;
}
static int sst25l_wait_till_ready(struct sst25l_flash *flash)
{
unsigned long deadline;
int status, err;
deadline = jiffies + MAX_READY_WAIT_JIFFIES;
do {
err = sst25l_status(flash, &status);
if (err)
return err;
if (!(status & SST25L_STATUS_BUSY))
return 0;
cond_resched();
} while (!time_after_eq(jiffies, deadline));
return -ETIMEDOUT;
}
static int sst25l_erase_sector(struct sst25l_flash *flash, uint32_t offset)
{
unsigned char command[4];
int err;
err = sst25l_write_enable(flash, 1);
if (err)
return err;
command[0] = SST25L_CMD_SECTOR_ERASE;
command[1] = offset >> 16;
command[2] = offset >> 8;
command[3] = offset;
err = spi_write(flash->spi, command, 4);
if (err)
return err;
err = sst25l_wait_till_ready(flash);
if (err)
return err;
return sst25l_write_enable(flash, 0);
}
static int sst25l_erase(struct mtd_info *mtd, struct erase_info *instr)
{
struct sst25l_flash *flash = to_sst25l_flash(mtd);
uint32_t addr, end;
int err;
/* Sanity checks */
if ((uint32_t)instr->len % mtd->erasesize)
return -EINVAL;
if ((uint32_t)instr->addr % mtd->erasesize)
return -EINVAL;
addr = instr->addr;
end = addr + instr->len;
mutex_lock(&flash->lock);
err = sst25l_wait_till_ready(flash);
if (err) {
mutex_unlock(&flash->lock);
return err;
}
while (addr < end) {
err = sst25l_erase_sector(flash, addr);
if (err) {
mutex_unlock(&flash->lock);
instr->state = MTD_ERASE_FAILED;
dev_err(&flash->spi->dev, "Erase failed\n");
return err;
}
addr += mtd->erasesize;
}
mutex_unlock(&flash->lock);
instr->state = MTD_ERASE_DONE;
mtd_erase_callback(instr);
return 0;
}
static int sst25l_read(struct mtd_info *mtd, loff_t from, size_t len,
size_t *retlen, unsigned char *buf)
{
struct sst25l_flash *flash = to_sst25l_flash(mtd);
struct spi_transfer transfer[2];
struct spi_message message;
unsigned char command[4];
int ret;
spi_message_init(&message);
memset(&transfer, 0, sizeof(transfer));
command[0] = SST25L_CMD_READ;
command[1] = from >> 16;
command[2] = from >> 8;
command[3] = from;
transfer[0].tx_buf = command;
transfer[0].len = sizeof(command);
spi_message_add_tail(&transfer[0], &message);
transfer[1].rx_buf = buf;
transfer[1].len = len;
spi_message_add_tail(&transfer[1], &message);
mutex_lock(&flash->lock);
/* Wait for previous write/erase to complete */
ret = sst25l_wait_till_ready(flash);
if (ret) {
mutex_unlock(&flash->lock);
return ret;
}
spi_sync(flash->spi, &message);
if (retlen && message.actual_length > sizeof(command))
*retlen += message.actual_length - sizeof(command);
mutex_unlock(&flash->lock);
return 0;
}
static int sst25l_write(struct mtd_info *mtd, loff_t to, size_t len,
size_t *retlen, const unsigned char *buf)
{
struct sst25l_flash *flash = to_sst25l_flash(mtd);
int i, j, ret, bytes, copied = 0;
unsigned char command[5];
if ((uint32_t)to % mtd->writesize)
return -EINVAL;
mutex_lock(&flash->lock);
ret = sst25l_write_enable(flash, 1);
if (ret)
goto out;
for (i = 0; i < len; i += mtd->writesize) {
ret = sst25l_wait_till_ready(flash);
if (ret)
goto out;
/* Write the first byte of the page */
command[0] = SST25L_CMD_AAI_PROGRAM;
command[1] = (to + i) >> 16;
command[2] = (to + i) >> 8;
command[3] = (to + i);
command[4] = buf[i];
ret = spi_write(flash->spi, command, 5);
if (ret < 0)
goto out;
copied++;
/*
* Write the remaining bytes using auto address
* increment mode
*/
bytes = min_t(uint32_t, mtd->writesize, len - i);
for (j = 1; j < bytes; j++, copied++) {
ret = sst25l_wait_till_ready(flash);
if (ret)
goto out;
command[1] = buf[i + j];
ret = spi_write(flash->spi, command, 2);
if (ret)
goto out;
}
}
out:
ret = sst25l_write_enable(flash, 0);
if (retlen)
*retlen = copied;
mutex_unlock(&flash->lock);
return ret;
}
static struct flash_info *__devinit sst25l_match_device(struct spi_device *spi)
{
struct flash_info *flash_info = NULL;
struct spi_message m;
struct spi_transfer t;
unsigned char cmd_resp[6];
int i, err;
uint16_t id;
spi_message_init(&m);
memset(&t, 0, sizeof(struct spi_transfer));
cmd_resp[0] = SST25L_CMD_READ_ID;
cmd_resp[1] = 0;
cmd_resp[2] = 0;
cmd_resp[3] = 0;
cmd_resp[4] = 0xff;
cmd_resp[5] = 0xff;
t.tx_buf = cmd_resp;
t.rx_buf = cmd_resp;
t.len = sizeof(cmd_resp);
spi_message_add_tail(&t, &m);
err = spi_sync(spi, &m);
if (err < 0) {
dev_err(&spi->dev, "error reading device id\n");
return NULL;
}
id = (cmd_resp[4] << 8) | cmd_resp[5];
for (i = 0; i < ARRAY_SIZE(sst25l_flash_info); i++)
if (sst25l_flash_info[i].device_id == id)
flash_info = &sst25l_flash_info[i];
if (!flash_info)
dev_err(&spi->dev, "unknown id %.4x\n", id);
return flash_info;
}
static int __devinit sst25l_probe(struct spi_device *spi)
{
struct flash_info *flash_info;
struct sst25l_flash *flash;
struct flash_platform_data *data;
int ret;
flash_info = sst25l_match_device(spi);
if (!flash_info)
return -ENODEV;
flash = kzalloc(sizeof(struct sst25l_flash), GFP_KERNEL);
if (!flash)
return -ENOMEM;
flash->spi = spi;
mutex_init(&flash->lock);
dev_set_drvdata(&spi->dev, flash);
data = spi->dev.platform_data;
if (data && data->name)
flash->mtd.name = data->name;
else
flash->mtd.name = dev_name(&spi->dev);
flash->mtd.type = MTD_NORFLASH;
flash->mtd.flags = MTD_CAP_NORFLASH;
flash->mtd.erasesize = flash_info->erase_size;
flash->mtd.writesize = flash_info->page_size;
flash->mtd.writebufsize = flash_info->page_size;
flash->mtd.size = flash_info->page_size * flash_info->nr_pages;
flash->mtd._erase = sst25l_erase;
flash->mtd._read = sst25l_read;
flash->mtd._write = sst25l_write;
dev_info(&spi->dev, "%s (%lld KiB)\n", flash_info->name,
(long long)flash->mtd.size >> 10);
pr_debug("mtd .name = %s, .size = 0x%llx (%lldMiB) "
".erasesize = 0x%.8x (%uKiB) .numeraseregions = %d\n",
flash->mtd.name,
(long long)flash->mtd.size, (long long)(flash->mtd.size >> 20),
flash->mtd.erasesize, flash->mtd.erasesize / 1024,
flash->mtd.numeraseregions);
ret = mtd_device_parse_register(&flash->mtd, NULL, NULL,
data ? data->parts : NULL,
data ? data->nr_parts : 0);
if (ret) {
kfree(flash);
dev_set_drvdata(&spi->dev, NULL);
return -ENODEV;
}
return 0;
}
static int __devexit sst25l_remove(struct spi_device *spi)
{
struct sst25l_flash *flash = dev_get_drvdata(&spi->dev);
int ret;
ret = mtd_device_unregister(&flash->mtd);
if (ret == 0)
kfree(flash);
return ret;
}
static struct spi_driver sst25l_driver = {
.driver = {
.name = "sst25l",
.owner = THIS_MODULE,
},
.probe = sst25l_probe,
.remove = __devexit_p(sst25l_remove),
};
module_spi_driver(sst25l_driver);
MODULE_DESCRIPTION("MTD SPI driver for SST25L Flash chips");
MODULE_AUTHOR("Andre Renaud <andre@bluewatersys.com>, "
"Ryan Mallon");
MODULE_LICENSE("GPL");