Creation of Cybook 2416 (actually Gen4) repository
This commit is contained in:
271
drivers/mtd/devices/Kconfig
Normal file
271
drivers/mtd/devices/Kconfig
Normal file
@@ -0,0 +1,271 @@
|
||||
# drivers/mtd/maps/Kconfig
|
||||
# $Id: Kconfig,v 1.1.1.1 2007/06/12 07:27:09 eyryu Exp $
|
||||
|
||||
menu "Self-contained MTD device drivers"
|
||||
depends on MTD!=n
|
||||
|
||||
config MTD_PMC551
|
||||
tristate "Ramix PMC551 PCI Mezzanine RAM card support"
|
||||
depends on MTD && 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 MTD && 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/modules.txt>. The module will
|
||||
be called ms02-nv.o.
|
||||
|
||||
config MTD_DATAFLASH
|
||||
tristate "Support for AT45xxx DataFlash"
|
||||
depends on MTD && 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_M25P80
|
||||
tristate "Support for M25 SPI Flash"
|
||||
depends on MTD && SPI_MASTER && EXPERIMENTAL
|
||||
help
|
||||
This enables access to ST M25P80 and similar SPI flash chips,
|
||||
used for program and data storage. Set up your spi devices
|
||||
with the right board-specific platform data.
|
||||
|
||||
config MTD_SLRAM
|
||||
tristate "Uncached system RAM"
|
||||
depends on MTD
|
||||
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"
|
||||
depends on MTD
|
||||
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 && MTD
|
||||
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"
|
||||
depends on MTD
|
||||
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 MTD && 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
|
||||
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
|
||||
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
|
||||
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_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 "0x0000" if MTD_DOCPROBE_ADVANCED
|
||||
default "0" if !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 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
|
||||
|
||||
19
drivers/mtd/devices/Makefile
Normal file
19
drivers/mtd/devices/Makefile
Normal file
@@ -0,0 +1,19 @@
|
||||
#
|
||||
# linux/drivers/devices/Makefile
|
||||
#
|
||||
# $Id: Makefile,v 1.1.1.1 2007/06/12 07:27:09 eyryu Exp $
|
||||
|
||||
obj-$(CONFIG_MTD_DOC2000) += doc2000.o
|
||||
obj-$(CONFIG_MTD_DOC2001) += doc2001.o
|
||||
obj-$(CONFIG_MTD_DOC2001PLUS) += doc2001plus.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_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
|
||||
540
drivers/mtd/devices/block2mtd.c
Normal file
540
drivers/mtd/devices/block2mtd.c
Normal file
@@ -0,0 +1,540 @@
|
||||
/*
|
||||
* $Id: block2mtd.c,v 1.1.1.1 2007/06/12 07:27:09 eyryu Exp $
|
||||
*
|
||||
* block2mtd.c - create an mtd from a block device
|
||||
*
|
||||
* Copyright (C) 2001,2002 Simon Evans <spse@secret.org.uk>
|
||||
* Copyright (C) 2004-2006 Jörn 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/buffer_head.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/mount.h>
|
||||
|
||||
#define VERSION "$Revision: 1.1.1.1 $"
|
||||
|
||||
|
||||
#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);
|
||||
|
||||
|
||||
#define PAGE_READAHEAD 64
|
||||
static void cache_readahead(struct address_space *mapping, int index)
|
||||
{
|
||||
filler_t *filler = (filler_t*)mapping->a_ops->readpage;
|
||||
int i, pagei;
|
||||
unsigned ret = 0;
|
||||
unsigned long end_index;
|
||||
struct page *page;
|
||||
LIST_HEAD(page_pool);
|
||||
struct inode *inode = mapping->host;
|
||||
loff_t isize = i_size_read(inode);
|
||||
|
||||
if (!isize) {
|
||||
INFO("iSize=0 in cache_readahead\n");
|
||||
return;
|
||||
}
|
||||
|
||||
end_index = ((isize - 1) >> PAGE_CACHE_SHIFT);
|
||||
|
||||
read_lock_irq(&mapping->tree_lock);
|
||||
for (i = 0; i < PAGE_READAHEAD; i++) {
|
||||
pagei = index + i;
|
||||
if (pagei > end_index) {
|
||||
INFO("Overrun end of disk in cache readahead\n");
|
||||
break;
|
||||
}
|
||||
page = radix_tree_lookup(&mapping->page_tree, pagei);
|
||||
if (page && (!i))
|
||||
break;
|
||||
if (page)
|
||||
continue;
|
||||
read_unlock_irq(&mapping->tree_lock);
|
||||
page = page_cache_alloc_cold(mapping);
|
||||
read_lock_irq(&mapping->tree_lock);
|
||||
if (!page)
|
||||
break;
|
||||
page->index = pagei;
|
||||
list_add(&page->lru, &page_pool);
|
||||
ret++;
|
||||
}
|
||||
read_unlock_irq(&mapping->tree_lock);
|
||||
if (ret)
|
||||
read_cache_pages(mapping, &page_pool, filler, NULL);
|
||||
}
|
||||
|
||||
|
||||
static struct page* page_readahead(struct address_space *mapping, int index)
|
||||
{
|
||||
filler_t *filler = (filler_t*)mapping->a_ops->readpage;
|
||||
cache_readahead(mapping, index);
|
||||
return read_cache_page(mapping, index, filler, 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_readahead(mapping, index);
|
||||
if (!page)
|
||||
return -ENOMEM;
|
||||
if (IS_ERR(page))
|
||||
return PTR_ERR(page);
|
||||
|
||||
max = (u_long*)page_address(page) + PAGE_SIZE;
|
||||
for (p=(u_long*)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;
|
||||
|
||||
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;
|
||||
|
||||
if (from > mtd->size)
|
||||
return -EINVAL;
|
||||
if (from + len > mtd->size)
|
||||
len = mtd->size - from;
|
||||
|
||||
if (retlen)
|
||||
*retlen = 0;
|
||||
|
||||
while (len) {
|
||||
if ((offset + len) > PAGE_SIZE)
|
||||
cpylen = PAGE_SIZE - offset; // multiple pages
|
||||
else
|
||||
cpylen = len; // this page
|
||||
len = len - cpylen;
|
||||
|
||||
// Get page
|
||||
page = page_readahead(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;
|
||||
|
||||
if (retlen)
|
||||
*retlen = 0;
|
||||
while (len) {
|
||||
if ((offset+len) > PAGE_SIZE)
|
||||
cpylen = PAGE_SIZE - offset; // multiple pages
|
||||
else
|
||||
cpylen = len; // this page
|
||||
len = len - cpylen;
|
||||
|
||||
// Get page
|
||||
page = page_readahead(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;
|
||||
|
||||
if (!len)
|
||||
return 0;
|
||||
if (to >= mtd->size)
|
||||
return -ENOSPC;
|
||||
if (to + len > mtd->size)
|
||||
len = mtd->size - to;
|
||||
|
||||
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);
|
||||
close_bdev_excl(dev->blkdev);
|
||||
}
|
||||
|
||||
kfree(dev);
|
||||
}
|
||||
|
||||
|
||||
/* FIXME: ensure that mtd->size % erase_size == 0 */
|
||||
static struct block2mtd_dev *add_device(char *devname, int erase_size)
|
||||
{
|
||||
struct block_device *bdev;
|
||||
struct block2mtd_dev *dev;
|
||||
|
||||
if (!devname)
|
||||
return NULL;
|
||||
|
||||
dev = kzalloc(sizeof(struct block2mtd_dev), GFP_KERNEL);
|
||||
if (!dev)
|
||||
return NULL;
|
||||
|
||||
/* Get a handle on the device */
|
||||
bdev = open_bdev_excl(devname, O_RDWR, NULL);
|
||||
#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 dev = name_to_dev_t(devname);
|
||||
if (dev != 0) {
|
||||
bdev = open_by_devnum(dev, FMODE_WRITE | FMODE_READ);
|
||||
}
|
||||
}
|
||||
#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 */
|
||||
dev->mtd.name = kmalloc(sizeof("block2mtd: ") + strlen(devname),
|
||||
GFP_KERNEL);
|
||||
if (!dev->mtd.name)
|
||||
goto devinit_err;
|
||||
|
||||
sprintf(dev->mtd.name, "block2mtd: %s", devname);
|
||||
|
||||
dev->mtd.size = dev->blkdev->bd_inode->i_size & PAGE_MASK;
|
||||
dev->mtd.erasesize = erase_size;
|
||||
dev->mtd.writesize = 1;
|
||||
dev->mtd.type = MTD_RAM;
|
||||
dev->mtd.flags = MTD_CAP_RAM;
|
||||
dev->mtd.erase = block2mtd_erase;
|
||||
dev->mtd.write = block2mtd_write;
|
||||
dev->mtd.writev = default_mtd_writev;
|
||||
dev->mtd.sync = block2mtd_sync;
|
||||
dev->mtd.read = block2mtd_read;
|
||||
dev->mtd.priv = dev;
|
||||
dev->mtd.owner = THIS_MODULE;
|
||||
|
||||
if (add_mtd_device(&dev->mtd)) {
|
||||
/* Device didnt 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("blkmtd: "),
|
||||
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("block2mtd: " fmt "\n", ## args); \
|
||||
return 0; \
|
||||
} while (0)
|
||||
|
||||
#ifndef MODULE
|
||||
static int block2mtd_init_called = 0;
|
||||
static __initdata 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) {
|
||||
kfree(name);
|
||||
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;
|
||||
INFO("version " VERSION);
|
||||
|
||||
#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);
|
||||
del_mtd_device(&dev->mtd);
|
||||
INFO("mtd%d: [%s] removed", dev->mtd.index,
|
||||
dev->mtd.name + strlen("blkmtd: "));
|
||||
list_del(&dev->list);
|
||||
block2mtd_free_device(dev);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
module_init(block2mtd_init);
|
||||
module_exit(block2mtd_exit);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Simon Evans <spse@secret.org.uk> and others");
|
||||
MODULE_DESCRIPTION("Emulate an MTD using a block device");
|
||||
1205
drivers/mtd/devices/doc2000.c
Normal file
1205
drivers/mtd/devices/doc2000.c
Normal file
File diff suppressed because it is too large
Load Diff
845
drivers/mtd/devices/doc2001.c
Normal file
845
drivers/mtd/devices/doc2001.c
Normal file
@@ -0,0 +1,845 @@
|
||||
|
||||
/*
|
||||
* Linux driver for Disk-On-Chip Millennium
|
||||
* (c) 1999 Machine Vision Holdings, Inc.
|
||||
* (c) 1999, 2000 David Woodhouse <dwmw2@infradead.org>
|
||||
*
|
||||
* $Id: doc2001.c,v 1.1.1.1 2007/06/12 07:27:09 eyryu Exp $
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <asm/errno.h>
|
||||
#include <asm/io.h>
|
||||
#include <asm/uaccess.h>
|
||||
#include <linux/miscdevice.h>
|
||||
#include <linux/pci.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;
|
||||
|
||||
DEBUG(MTD_DEBUG_LEVEL3,
|
||||
"_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)
|
||||
DEBUG(MTD_DEBUG_LEVEL2, "_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;
|
||||
mtd->size = 0;
|
||||
|
||||
/* FIXME: erase size is not always 8KiB */
|
||||
mtd->erasesize = 0x2000;
|
||||
|
||||
mtd->writesize = 512;
|
||||
mtd->oobsize = 16;
|
||||
mtd->owner = THIS_MODULE;
|
||||
mtd->erase = doc_erase;
|
||||
mtd->point = NULL;
|
||||
mtd->unpoint = NULL;
|
||||
mtd->read = doc_read;
|
||||
mtd->write = doc_write;
|
||||
mtd->read_oob = doc_read_oob;
|
||||
mtd->write_oob = doc_write_oob;
|
||||
mtd->sync = NULL;
|
||||
|
||||
this->totlen = 0;
|
||||
this->numchips = 0;
|
||||
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;
|
||||
add_mtd_device(mtd);
|
||||
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 read past end of device */
|
||||
if (from >= this->totlen)
|
||||
return -EINVAL;
|
||||
|
||||
/* 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 syndrom 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)];
|
||||
|
||||
/* Don't allow write past end of device */
|
||||
if (to >= this->totlen)
|
||||
return -EINVAL;
|
||||
|
||||
#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 ??) */
|
||||
*retlen = 0;
|
||||
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_OOB_PLACE);
|
||||
|
||||
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_OOB_PLACE);
|
||||
|
||||
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;
|
||||
|
||||
del_mtd_device(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");
|
||||
1110
drivers/mtd/devices/doc2001plus.c
Normal file
1110
drivers/mtd/devices/doc2001plus.c
Normal file
File diff suppressed because it is too large
Load Diff
526
drivers/mtd/devices/docecc.c
Normal file
526
drivers/mtd/devices/docecc.c
Normal file
@@ -0,0 +1,526 @@
|
||||
/*
|
||||
* 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
|
||||
* syndrom into a standard syndom.
|
||||
*
|
||||
* Author: Fabrice Bellard (fabrice.bellard@netgem.com)
|
||||
* Copyright (C) 2000 Netgem S.A.
|
||||
*
|
||||
* $Id: docecc.c,v 1.1.1.1 2007/06/12 07:27:09 eyryu Exp $
|
||||
*
|
||||
* 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/miscdevice.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#include <linux/mtd/compatmac.h> /* for min() in older kernels */
|
||||
#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]".
|
||||
Similarily, 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).
|
||||
Similarily, 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(®[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");
|
||||
348
drivers/mtd/devices/docprobe.c
Normal file
348
drivers/mtd/devices/docprobe.c
Normal file
@@ -0,0 +1,348 @@
|
||||
|
||||
/* 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> */
|
||||
|
||||
/* $Id: docprobe.c,v 1.1.1.1 2007/06/12 07:27:09 eyryu Exp $ */
|
||||
|
||||
|
||||
|
||||
/* 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>
|
||||
#include <linux/mtd/compatmac.h>
|
||||
|
||||
/* Where to look for the devices? */
|
||||
#ifndef CONFIG_MTD_DOCPROBE_ADDRESS
|
||||
#define CONFIG_MTD_DOCPROBE_ADDRESS 0
|
||||
#endif
|
||||
|
||||
|
||||
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 */
|
||||
#elif defined(__PPC__)
|
||||
0xe4000000,
|
||||
#elif defined(CONFIG_MOMENCO_OCELOT)
|
||||
0x2f000000,
|
||||
0xff000000,
|
||||
#elif defined(CONFIG_MOMENCO_OCELOT_G) || defined (CONFIG_MOMENCO_OCELOT_C)
|
||||
0xff000000,
|
||||
##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 = kmalloc(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]);
|
||||
|
||||
memset((char *)mtd,0, sizeof(struct mtd_info));
|
||||
memset((char *)this, 0, sizeof(struct DiskOnChip));
|
||||
|
||||
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");
|
||||
|
||||
711
drivers/mtd/devices/lart.c
Normal file
711
drivers/mtd/devices/lart.c
Normal file
@@ -0,0 +1,711 @@
|
||||
|
||||
/*
|
||||
* MTD driver for the 28F160F3 Flash Memory (non-CFI) on LART.
|
||||
*
|
||||
* $Id: lart.c,v 1.1.1.1 2007/06/12 07:27:09 eyryu Exp $
|
||||
*
|
||||
* 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/tech/
|
||||
*
|
||||
* 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
|
||||
|
||||
/* partition support */
|
||||
#define HAVE_PARTITIONS
|
||||
|
||||
#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>
|
||||
#ifdef HAVE_PARTITIONS
|
||||
#include <linux/mtd/partitions.h>
|
||||
#endif
|
||||
|
||||
#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",__FUNCTION__,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",__FUNCTION__,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",__FUNCTION__,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 || 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",__FUNCTION__,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 successfull? */
|
||||
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",__FUNCTION__,instr->addr,instr->len);
|
||||
#endif
|
||||
|
||||
/* sanity checks */
|
||||
if (instr->addr + instr->len > mtd->size) return (-EINVAL);
|
||||
|
||||
/*
|
||||
* 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 (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 ((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",__FUNCTION__,(__u32) from,len);
|
||||
#endif
|
||||
|
||||
/* sanity checks */
|
||||
if (!len) return (0);
|
||||
if (from + len > mtd->size) return (-EINVAL);
|
||||
|
||||
/* 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",__FUNCTION__,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 successfull? */
|
||||
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",__FUNCTION__,(__u32) to,len);
|
||||
#endif
|
||||
|
||||
*retlen = 0;
|
||||
|
||||
/* sanity checks */
|
||||
if (!len) return (0);
|
||||
if (to + len > mtd->size) return (-EINVAL);
|
||||
|
||||
/* 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,
|
||||
}
|
||||
};
|
||||
|
||||
#ifdef HAVE_PARTITIONS
|
||||
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 */
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
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.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);
|
||||
|
||||
#ifdef HAVE_PARTITIONS
|
||||
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
|
||||
#endif
|
||||
|
||||
#ifndef HAVE_PARTITIONS
|
||||
result = add_mtd_device (&mtd);
|
||||
#else
|
||||
result = add_mtd_partitions (&mtd,lart_partitions, ARRAY_SIZE(lart_partitions));
|
||||
#endif
|
||||
|
||||
return (result);
|
||||
}
|
||||
|
||||
void __exit lart_flash_exit (void)
|
||||
{
|
||||
#ifndef HAVE_PARTITIONS
|
||||
del_mtd_device (&mtd);
|
||||
#else
|
||||
del_mtd_partitions (&mtd);
|
||||
#endif
|
||||
}
|
||||
|
||||
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");
|
||||
|
||||
|
||||
583
drivers/mtd/devices/m25p80.c
Normal file
583
drivers/mtd/devices/m25p80.c
Normal file
@@ -0,0 +1,583 @@
|
||||
/*
|
||||
* MTD SPI driver for ST M25Pxx 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/module.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include <linux/mtd/partitions.h>
|
||||
#include <linux/spi/spi.h>
|
||||
#include <linux/spi/flash.h>
|
||||
|
||||
#include <asm/semaphore.h>
|
||||
|
||||
|
||||
/* NOTE: AT 25F and SST 25LF series are very similar,
|
||||
* but commands for sector erase and chip id differ...
|
||||
*/
|
||||
|
||||
#define FLASH_PAGESIZE 256
|
||||
|
||||
/* Flash opcodes. */
|
||||
#define OPCODE_WREN 6 /* Write enable */
|
||||
#define OPCODE_RDSR 5 /* Read status register */
|
||||
#define OPCODE_READ 3 /* Read data bytes */
|
||||
#define OPCODE_PP 2 /* Page program */
|
||||
#define OPCODE_SE 0xd8 /* Sector erase */
|
||||
#define OPCODE_RES 0xab /* Read Electronic Signature */
|
||||
#define OPCODE_RDID 0x9f /* Read JEDEC ID */
|
||||
|
||||
/* Status Register bits. */
|
||||
#define SR_WIP 1 /* Write in progress */
|
||||
#define SR_WEL 2 /* Write enable latch */
|
||||
#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_COUNT 100000
|
||||
|
||||
|
||||
#ifdef CONFIG_MTD_PARTITIONS
|
||||
#define mtd_has_partitions() (1)
|
||||
#else
|
||||
#define mtd_has_partitions() (0)
|
||||
#endif
|
||||
|
||||
/****************************************************************************/
|
||||
|
||||
struct m25p {
|
||||
struct spi_device *spi;
|
||||
struct semaphore lock;
|
||||
struct mtd_info mtd;
|
||||
unsigned partitioned;
|
||||
u8 command[4];
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Service routine to read status register until ready, or timeout occurs.
|
||||
* Returns non-zero if error.
|
||||
*/
|
||||
static int wait_till_ready(struct m25p *flash)
|
||||
{
|
||||
int count;
|
||||
int sr;
|
||||
|
||||
/* one chip guarantees max 5 msec wait here after page writes,
|
||||
* but potentially three seconds (!) after page erase.
|
||||
*/
|
||||
for (count = 0; count < MAX_READY_WAIT_COUNT; count++) {
|
||||
if ((sr = read_sr(flash)) < 0)
|
||||
break;
|
||||
else if (!(sr & SR_WIP))
|
||||
return 0;
|
||||
|
||||
/* REVISIT sometimes sleeping would be best */
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* 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)
|
||||
{
|
||||
DEBUG(MTD_DEBUG_LEVEL3, "%s: %s at 0x%08x\n", flash->spi->dev.bus_id,
|
||||
__FUNCTION__, 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] = OPCODE_SE;
|
||||
flash->command[1] = offset >> 16;
|
||||
flash->command[2] = offset >> 8;
|
||||
flash->command[3] = offset;
|
||||
|
||||
spi_write(flash->spi, flash->command, sizeof(flash->command));
|
||||
|
||||
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;
|
||||
|
||||
DEBUG(MTD_DEBUG_LEVEL2, "%s: %s %s 0x%08x, len %d\n",
|
||||
flash->spi->dev.bus_id, __FUNCTION__, "at",
|
||||
(u32)instr->addr, instr->len);
|
||||
|
||||
/* sanity checks */
|
||||
if (instr->addr + instr->len > flash->mtd.size)
|
||||
return -EINVAL;
|
||||
if ((instr->addr % mtd->erasesize) != 0
|
||||
|| (instr->len % mtd->erasesize) != 0) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
addr = instr->addr;
|
||||
len = instr->len;
|
||||
|
||||
down(&flash->lock);
|
||||
|
||||
/* now erase those sectors */
|
||||
while (len) {
|
||||
if (erase_sector(flash, addr)) {
|
||||
instr->state = MTD_ERASE_FAILED;
|
||||
up(&flash->lock);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
addr += mtd->erasesize;
|
||||
len -= mtd->erasesize;
|
||||
}
|
||||
|
||||
up(&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;
|
||||
|
||||
DEBUG(MTD_DEBUG_LEVEL2, "%s: %s %s 0x%08x, len %zd\n",
|
||||
flash->spi->dev.bus_id, __FUNCTION__, "from",
|
||||
(u32)from, len);
|
||||
|
||||
/* sanity checks */
|
||||
if (!len)
|
||||
return 0;
|
||||
|
||||
if (from + len > flash->mtd.size)
|
||||
return -EINVAL;
|
||||
|
||||
spi_message_init(&m);
|
||||
memset(t, 0, (sizeof t));
|
||||
|
||||
t[0].tx_buf = flash->command;
|
||||
t[0].len = sizeof(flash->command);
|
||||
spi_message_add_tail(&t[0], &m);
|
||||
|
||||
t[1].rx_buf = buf;
|
||||
t[1].len = len;
|
||||
spi_message_add_tail(&t[1], &m);
|
||||
|
||||
/* Byte count starts at zero. */
|
||||
if (retlen)
|
||||
*retlen = 0;
|
||||
|
||||
down(&flash->lock);
|
||||
|
||||
/* Wait till previous write/erase is done. */
|
||||
if (wait_till_ready(flash)) {
|
||||
/* REVISIT status return?? */
|
||||
up(&flash->lock);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* NOTE: OPCODE_FAST_READ (if available) is faster... */
|
||||
|
||||
/* Set up the write data buffer. */
|
||||
flash->command[0] = OPCODE_READ;
|
||||
flash->command[1] = from >> 16;
|
||||
flash->command[2] = from >> 8;
|
||||
flash->command[3] = from;
|
||||
|
||||
spi_sync(flash->spi, &m);
|
||||
|
||||
*retlen = m.actual_length - sizeof(flash->command);
|
||||
|
||||
up(&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;
|
||||
|
||||
DEBUG(MTD_DEBUG_LEVEL2, "%s: %s %s 0x%08x, len %zd\n",
|
||||
flash->spi->dev.bus_id, __FUNCTION__, "to",
|
||||
(u32)to, len);
|
||||
|
||||
if (retlen)
|
||||
*retlen = 0;
|
||||
|
||||
/* sanity checks */
|
||||
if (!len)
|
||||
return(0);
|
||||
|
||||
if (to + len > flash->mtd.size)
|
||||
return -EINVAL;
|
||||
|
||||
spi_message_init(&m);
|
||||
memset(t, 0, (sizeof t));
|
||||
|
||||
t[0].tx_buf = flash->command;
|
||||
t[0].len = sizeof(flash->command);
|
||||
spi_message_add_tail(&t[0], &m);
|
||||
|
||||
t[1].tx_buf = buf;
|
||||
spi_message_add_tail(&t[1], &m);
|
||||
|
||||
down(&flash->lock);
|
||||
|
||||
/* Wait until finished previous write command. */
|
||||
if (wait_till_ready(flash))
|
||||
return 1;
|
||||
|
||||
write_enable(flash);
|
||||
|
||||
/* Set up the opcode in the write buffer. */
|
||||
flash->command[0] = OPCODE_PP;
|
||||
flash->command[1] = to >> 16;
|
||||
flash->command[2] = to >> 8;
|
||||
flash->command[3] = to;
|
||||
|
||||
/* what page do we start with? */
|
||||
page_offset = to % FLASH_PAGESIZE;
|
||||
|
||||
/* do all the bytes fit onto one page? */
|
||||
if (page_offset + len <= FLASH_PAGESIZE) {
|
||||
t[1].len = len;
|
||||
|
||||
spi_sync(flash->spi, &m);
|
||||
|
||||
*retlen = m.actual_length - sizeof(flash->command);
|
||||
} else {
|
||||
u32 i;
|
||||
|
||||
/* the size of data remaining on the first page */
|
||||
page_size = FLASH_PAGESIZE - page_offset;
|
||||
|
||||
t[1].len = page_size;
|
||||
spi_sync(flash->spi, &m);
|
||||
|
||||
*retlen = m.actual_length - sizeof(flash->command);
|
||||
|
||||
/* write everything in PAGESIZE chunks */
|
||||
for (i = page_size; i < len; i += page_size) {
|
||||
page_size = len - i;
|
||||
if (page_size > FLASH_PAGESIZE)
|
||||
page_size = FLASH_PAGESIZE;
|
||||
|
||||
/* write the next page to flash */
|
||||
flash->command[1] = (to + i) >> 16;
|
||||
flash->command[2] = (to + i) >> 8;
|
||||
flash->command[3] = (to + i);
|
||||
|
||||
t[1].tx_buf = buf + i;
|
||||
t[1].len = page_size;
|
||||
|
||||
wait_till_ready(flash);
|
||||
|
||||
write_enable(flash);
|
||||
|
||||
spi_sync(flash->spi, &m);
|
||||
|
||||
if (retlen)
|
||||
*retlen += m.actual_length
|
||||
- sizeof(flash->command);
|
||||
}
|
||||
}
|
||||
|
||||
up(&flash->lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/****************************************************************************/
|
||||
|
||||
/*
|
||||
* SPI device driver setup and teardown
|
||||
*/
|
||||
|
||||
struct flash_info {
|
||||
char *name;
|
||||
u8 id;
|
||||
u16 jedec_id;
|
||||
unsigned sector_size;
|
||||
unsigned n_sectors;
|
||||
};
|
||||
|
||||
static struct flash_info __devinitdata m25p_data [] = {
|
||||
/* REVISIT: fill in JEDEC ids, for parts that have them */
|
||||
{ "m25p05", 0x05, 0x2010, 32 * 1024, 2 },
|
||||
{ "m25p10", 0x10, 0x2011, 32 * 1024, 4 },
|
||||
{ "m25p20", 0x11, 0x2012, 64 * 1024, 4 },
|
||||
{ "m25p40", 0x12, 0x2013, 64 * 1024, 8 },
|
||||
{ "m25p80", 0x13, 0x0000, 64 * 1024, 16 },
|
||||
{ "m25p16", 0x14, 0x2015, 64 * 1024, 32 },
|
||||
{ "m25p32", 0x15, 0x2016, 64 * 1024, 64 },
|
||||
{ "m25p64", 0x16, 0x2017, 64 * 1024, 128 },
|
||||
};
|
||||
|
||||
/*
|
||||
* 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)
|
||||
{
|
||||
struct flash_platform_data *data;
|
||||
struct m25p *flash;
|
||||
struct flash_info *info;
|
||||
unsigned i;
|
||||
|
||||
/* Platform data helps sort out which chip type we have, as
|
||||
* well as how this board partitions it.
|
||||
*/
|
||||
data = spi->dev.platform_data;
|
||||
if (!data || !data->type) {
|
||||
/* FIXME some chips can identify themselves with RES
|
||||
* or JEDEC get-id commands. Try them ...
|
||||
*/
|
||||
DEBUG(MTD_DEBUG_LEVEL1, "%s: no chip id\n",
|
||||
spi->dev.bus_id);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
for (i = 0, info = m25p_data; i < ARRAY_SIZE(m25p_data); i++, info++) {
|
||||
if (strcmp(data->type, info->name) == 0)
|
||||
break;
|
||||
}
|
||||
if (i == ARRAY_SIZE(m25p_data)) {
|
||||
DEBUG(MTD_DEBUG_LEVEL1, "%s: unrecognized id %s\n",
|
||||
spi->dev.bus_id, data->type);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
flash = kzalloc(sizeof *flash, GFP_KERNEL);
|
||||
if (!flash)
|
||||
return -ENOMEM;
|
||||
|
||||
flash->spi = spi;
|
||||
init_MUTEX(&flash->lock);
|
||||
dev_set_drvdata(&spi->dev, flash);
|
||||
|
||||
if (data->name)
|
||||
flash->mtd.name = data->name;
|
||||
else
|
||||
flash->mtd.name = spi->dev.bus_id;
|
||||
|
||||
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.erasesize = info->sector_size;
|
||||
flash->mtd.erase = m25p80_erase;
|
||||
flash->mtd.read = m25p80_read;
|
||||
flash->mtd.write = m25p80_write;
|
||||
|
||||
dev_info(&spi->dev, "%s (%d Kbytes)\n", info->name,
|
||||
flash->mtd.size / 1024);
|
||||
|
||||
DEBUG(MTD_DEBUG_LEVEL2,
|
||||
"mtd .name = %s, .size = 0x%.8x (%uM) "
|
||||
".erasesize = 0x%.8x (%uK) .numeraseregions = %d\n",
|
||||
flash->mtd.name,
|
||||
flash->mtd.size, flash->mtd.size / (1024*1024),
|
||||
flash->mtd.erasesize, flash->mtd.erasesize / 1024,
|
||||
flash->mtd.numeraseregions);
|
||||
|
||||
if (flash->mtd.numeraseregions)
|
||||
for (i = 0; i < flash->mtd.numeraseregions; i++)
|
||||
DEBUG(MTD_DEBUG_LEVEL2,
|
||||
"mtd.eraseregions[%d] = { .offset = 0x%.8x, "
|
||||
".erasesize = 0x%.8x (%uK), "
|
||||
".numblocks = %d }\n",
|
||||
i, 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).
|
||||
*/
|
||||
if (mtd_has_partitions()) {
|
||||
struct mtd_partition *parts = NULL;
|
||||
int nr_parts = 0;
|
||||
|
||||
#ifdef CONFIG_MTD_CMDLINE_PARTS
|
||||
static const char *part_probes[] = { "cmdlinepart", NULL, };
|
||||
|
||||
nr_parts = parse_mtd_partitions(&flash->mtd,
|
||||
part_probes, &parts, 0);
|
||||
#endif
|
||||
|
||||
if (nr_parts <= 0 && data && data->parts) {
|
||||
parts = data->parts;
|
||||
nr_parts = data->nr_parts;
|
||||
}
|
||||
|
||||
if (nr_parts > 0) {
|
||||
for (i = 0; i < data->nr_parts; i++) {
|
||||
DEBUG(MTD_DEBUG_LEVEL2, "partitions[%d] = "
|
||||
"{.name = %s, .offset = 0x%.8x, "
|
||||
".size = 0x%.8x (%uK) }\n",
|
||||
i, data->parts[i].name,
|
||||
data->parts[i].offset,
|
||||
data->parts[i].size,
|
||||
data->parts[i].size / 1024);
|
||||
}
|
||||
flash->partitioned = 1;
|
||||
return add_mtd_partitions(&flash->mtd, parts, nr_parts);
|
||||
}
|
||||
} else if (data->nr_parts)
|
||||
dev_warn(&spi->dev, "ignoring %d default partitions on %s\n",
|
||||
data->nr_parts, data->name);
|
||||
|
||||
return add_mtd_device(&flash->mtd) == 1 ? -ENODEV : 0;
|
||||
}
|
||||
|
||||
|
||||
static int __devexit m25p_remove(struct spi_device *spi)
|
||||
{
|
||||
struct m25p *flash = dev_get_drvdata(&spi->dev);
|
||||
int status;
|
||||
|
||||
/* Clean up MTD stuff. */
|
||||
if (mtd_has_partitions() && flash->partitioned)
|
||||
status = del_mtd_partitions(&flash->mtd);
|
||||
else
|
||||
status = del_mtd_device(&flash->mtd);
|
||||
if (status == 0)
|
||||
kfree(flash);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static struct spi_driver m25p80_driver = {
|
||||
.driver = {
|
||||
.name = "m25p80",
|
||||
.bus = &spi_bus_type,
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = m25p_probe,
|
||||
.remove = __devexit_p(m25p_remove),
|
||||
};
|
||||
|
||||
|
||||
static int m25p80_init(void)
|
||||
{
|
||||
return spi_register_driver(&m25p80_driver);
|
||||
}
|
||||
|
||||
|
||||
static void m25p80_exit(void)
|
||||
{
|
||||
spi_unregister_driver(&m25p80_driver);
|
||||
}
|
||||
|
||||
|
||||
module_init(m25p80_init);
|
||||
module_exit(m25p80_exit);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Mike Lavender");
|
||||
MODULE_DESCRIPTION("MTD SPI driver for ST M25Pxx flash chips");
|
||||
321
drivers/mtd/devices/ms02-nv.c
Normal file
321
drivers/mtd/devices/ms02-nv.c
Normal file
@@ -0,0 +1,321 @@
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
* $Id: ms02-nv.c,v 1.1.1.1 2007/06/12 07:27:09 eyryu Exp $
|
||||
*/
|
||||
|
||||
#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;
|
||||
|
||||
if (from + len > mtd->size)
|
||||
return -EINVAL;
|
||||
|
||||
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;
|
||||
|
||||
if (to + len > mtd->size)
|
||||
return -EINVAL;
|
||||
|
||||
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 (add_mtd_device(mtd)) {
|
||||
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;
|
||||
|
||||
del_mtd_device(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);
|
||||
107
drivers/mtd/devices/ms02-nv.h
Normal file
107
drivers/mtd/devices/ms02-nv.h
Normal file
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
* $Id: ms02-nv.h,v 1.1.1.1 2007/06/12 07:27:09 eyryu Exp $
|
||||
*/
|
||||
|
||||
#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;
|
||||
};
|
||||
629
drivers/mtd/devices/mtd_dataflash.c
Normal file
629
drivers/mtd/devices/mtd_dataflash.c
Normal file
@@ -0,0 +1,629 @@
|
||||
/*
|
||||
* 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/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 use SPI (yet), or distinguish between MMC and DataFlash
|
||||
* protocols during enumeration.
|
||||
*/
|
||||
|
||||
#define CONFIG_DATAFLASH_WRITE_VERIFY
|
||||
|
||||
/* 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 0x9A /* OTP bits */
|
||||
|
||||
|
||||
struct dataflash {
|
||||
u8 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 semaphore lock;
|
||||
struct spi_device *spi;
|
||||
|
||||
struct mtd_info mtd;
|
||||
};
|
||||
|
||||
#ifdef CONFIG_MTD_PARTITIONS
|
||||
#define mtd_has_partitions() (1)
|
||||
#else
|
||||
#define mtd_has_partitions() (0)
|
||||
#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) {
|
||||
DEBUG(MTD_DEBUG_LEVEL1, "%s: status %d?\n",
|
||||
spi->dev.bus_id, 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 = (struct dataflash *)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;
|
||||
u8 *command;
|
||||
|
||||
DEBUG(MTD_DEBUG_LEVEL2, "%s: erase addr=0x%x len 0x%x\n",
|
||||
spi->dev.bus_id,
|
||||
instr->addr, instr->len);
|
||||
|
||||
/* Sanity checks */
|
||||
if ((instr->addr + instr->len) > mtd->size
|
||||
|| (instr->len % priv->page_size) != 0
|
||||
|| (instr->addr % priv->page_size) != 0)
|
||||
return -EINVAL;
|
||||
|
||||
spi_message_init(&msg);
|
||||
|
||||
x.tx_buf = command = priv->command;
|
||||
x.len = 4;
|
||||
spi_message_add_tail(&x, &msg);
|
||||
|
||||
down(&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 = 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] = (u8)(pageaddr >> 16);
|
||||
command[2] = (u8)(pageaddr >> 8);
|
||||
command[3] = 0;
|
||||
|
||||
DEBUG(MTD_DEBUG_LEVEL3, "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",
|
||||
spi->dev.bus_id, 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;
|
||||
}
|
||||
}
|
||||
up(&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 = (struct dataflash *)mtd->priv;
|
||||
struct spi_transfer x[2] = { { .tx_dma = 0, }, };
|
||||
struct spi_message msg;
|
||||
unsigned int addr;
|
||||
u8 *command;
|
||||
int status;
|
||||
|
||||
DEBUG(MTD_DEBUG_LEVEL2, "%s: read 0x%x..0x%x\n",
|
||||
priv->spi->dev.bus_id, (unsigned)from, (unsigned)(from + len));
|
||||
|
||||
*retlen = 0;
|
||||
|
||||
/* Sanity checks */
|
||||
if (!len)
|
||||
return 0;
|
||||
if (from + len > mtd->size)
|
||||
return -EINVAL;
|
||||
|
||||
/* Calculate flash page/byte address */
|
||||
addr = (((unsigned)from / priv->page_size) << priv->page_offset)
|
||||
+ ((unsigned)from % priv->page_size);
|
||||
|
||||
command = priv->command;
|
||||
|
||||
DEBUG(MTD_DEBUG_LEVEL3, "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);
|
||||
|
||||
down(&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] = (u8)(addr >> 16);
|
||||
command[2] = (u8)(addr >> 8);
|
||||
command[3] = (u8)(addr >> 0);
|
||||
/* plus 4 "don't care" bytes */
|
||||
|
||||
status = spi_sync(priv->spi, &msg);
|
||||
up(&priv->lock);
|
||||
|
||||
if (status >= 0) {
|
||||
*retlen = msg.actual_length - 8;
|
||||
status = 0;
|
||||
} else
|
||||
DEBUG(MTD_DEBUG_LEVEL1, "%s: read %x..%x --> %d\n",
|
||||
priv->spi->dev.bus_id,
|
||||
(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 = (struct dataflash *)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;
|
||||
u8 *command;
|
||||
|
||||
DEBUG(MTD_DEBUG_LEVEL2, "%s: write 0x%x..0x%x\n",
|
||||
spi->dev.bus_id, (unsigned)to, (unsigned)(to + len));
|
||||
|
||||
*retlen = 0;
|
||||
|
||||
/* Sanity checks */
|
||||
if (!len)
|
||||
return 0;
|
||||
if ((to + len) > mtd->size)
|
||||
return -EINVAL;
|
||||
|
||||
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;
|
||||
|
||||
down(&priv->lock);
|
||||
while (remaining > 0) {
|
||||
DEBUG(MTD_DEBUG_LEVEL3, "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;
|
||||
|
||||
DEBUG(MTD_DEBUG_LEVEL3, "TRANSFER: (%x) %x %x %x\n",
|
||||
command[0], command[1], command[2], command[3]);
|
||||
|
||||
status = spi_sync(spi, &msg);
|
||||
if (status < 0)
|
||||
DEBUG(MTD_DEBUG_LEVEL1, "%s: xfer %u -> %d \n",
|
||||
spi->dev.bus_id, 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);
|
||||
|
||||
DEBUG(MTD_DEBUG_LEVEL3, "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)
|
||||
DEBUG(MTD_DEBUG_LEVEL1, "%s: pgm %u/%u -> %d \n",
|
||||
spi->dev.bus_id, addr, writelen, status);
|
||||
|
||||
(void) dataflash_waitready(priv->spi);
|
||||
|
||||
|
||||
#ifdef CONFIG_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;
|
||||
|
||||
DEBUG(MTD_DEBUG_LEVEL3, "COMPARE: (%x) %x %x %x\n",
|
||||
command[0], command[1], command[2], command[3]);
|
||||
|
||||
status = spi_sync(spi, &msg);
|
||||
if (status < 0)
|
||||
DEBUG(MTD_DEBUG_LEVEL1, "%s: compare %u -> %d \n",
|
||||
spi->dev.bus_id, addr, status);
|
||||
|
||||
status = dataflash_waitready(priv->spi);
|
||||
|
||||
/* Check result of the compare operation */
|
||||
if ((status & (1 << 6)) == 1) {
|
||||
printk(KERN_ERR "%s: compare page %u, err %d\n",
|
||||
spi->dev.bus_id, pageaddr, status);
|
||||
remaining = 0;
|
||||
status = -EIO;
|
||||
break;
|
||||
} else
|
||||
status = 0;
|
||||
|
||||
#endif /* CONFIG_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;
|
||||
}
|
||||
up(&priv->lock);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/* ......................................................................... */
|
||||
|
||||
/*
|
||||
* Register DataFlash device with MTD subsystem.
|
||||
*/
|
||||
static int __devinit
|
||||
add_dataflash(struct spi_device *spi, char *name,
|
||||
int nr_pages, int pagesize, int pageoffset)
|
||||
{
|
||||
struct dataflash *priv;
|
||||
struct mtd_info *device;
|
||||
struct flash_platform_data *pdata = spi->dev.platform_data;
|
||||
|
||||
priv = kzalloc(sizeof *priv, GFP_KERNEL);
|
||||
if (!priv)
|
||||
return -ENOMEM;
|
||||
|
||||
init_MUTEX(&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;
|
||||
|
||||
dev_info(&spi->dev, "%s (%d KBytes)\n", name, device->size/1024);
|
||||
dev_set_drvdata(&spi->dev, priv);
|
||||
|
||||
if (mtd_has_partitions()) {
|
||||
struct mtd_partition *parts;
|
||||
int nr_parts = 0;
|
||||
|
||||
#ifdef CONFIG_MTD_CMDLINE_PARTS
|
||||
static const char *part_probes[] = { "cmdlinepart", NULL, };
|
||||
|
||||
nr_parts = parse_mtd_partitions(device, part_probes, &parts, 0);
|
||||
#endif
|
||||
|
||||
if (nr_parts <= 0 && pdata && pdata->parts) {
|
||||
parts = pdata->parts;
|
||||
nr_parts = pdata->nr_parts;
|
||||
}
|
||||
|
||||
if (nr_parts > 0) {
|
||||
priv->partitioned = 1;
|
||||
return add_mtd_partitions(device, parts, nr_parts);
|
||||
}
|
||||
} else if (pdata && pdata->nr_parts)
|
||||
dev_warn(&spi->dev, "ignoring %d default partitions on %s\n",
|
||||
pdata->nr_parts, device->name);
|
||||
|
||||
return add_mtd_device(device) == 1 ? -ENODEV : 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Detect and initialize DataFlash device:
|
||||
*
|
||||
* Device Density ID code #Pages PageSize Offset
|
||||
* AT45DB011B 1Mbit (128K) xx0011xx (0x0c) 512 264 9
|
||||
* AT45DB021B 2Mbit (256K) xx0101xx (0x14) 1025 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;
|
||||
|
||||
status = dataflash_status(spi);
|
||||
if (status <= 0 || status == 0xff) {
|
||||
DEBUG(MTD_DEBUG_LEVEL1, "%s: status error %d\n",
|
||||
spi->dev.bus_id, 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", 1025, 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:
|
||||
DEBUG(MTD_DEBUG_LEVEL1, "%s: unsupported device (%x)\n",
|
||||
spi->dev.bus_id, status & 0x3c);
|
||||
status = -ENODEV;
|
||||
}
|
||||
|
||||
if (status < 0)
|
||||
DEBUG(MTD_DEBUG_LEVEL1, "%s: add_dataflash --> %d\n",
|
||||
spi->dev.bus_id, status);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static int __devexit dataflash_remove(struct spi_device *spi)
|
||||
{
|
||||
struct dataflash *flash = dev_get_drvdata(&spi->dev);
|
||||
int status;
|
||||
|
||||
DEBUG(MTD_DEBUG_LEVEL1, "%s: remove\n", spi->dev.bus_id);
|
||||
|
||||
if (mtd_has_partitions() && flash->partitioned)
|
||||
status = del_mtd_partitions(&flash->mtd);
|
||||
else
|
||||
status = del_mtd_device(&flash->mtd);
|
||||
if (status == 0)
|
||||
kfree(flash);
|
||||
return status;
|
||||
}
|
||||
|
||||
static struct spi_driver dataflash_driver = {
|
||||
.driver = {
|
||||
.name = "mtd_dataflash",
|
||||
.bus = &spi_bus_type,
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
|
||||
.probe = dataflash_probe,
|
||||
.remove = __devexit_p(dataflash_remove),
|
||||
|
||||
/* FIXME: investigate suspend and resume... */
|
||||
};
|
||||
|
||||
static int __init dataflash_init(void)
|
||||
{
|
||||
return spi_register_driver(&dataflash_driver);
|
||||
}
|
||||
module_init(dataflash_init);
|
||||
|
||||
static void __exit dataflash_exit(void)
|
||||
{
|
||||
spi_unregister_driver(&dataflash_driver);
|
||||
}
|
||||
module_exit(dataflash_exit);
|
||||
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Andrew Victor, David Brownell");
|
||||
MODULE_DESCRIPTION("MTD DataFlash driver");
|
||||
161
drivers/mtd/devices/mtdram.c
Normal file
161
drivers/mtd/devices/mtdram.c
Normal file
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* mtdram - a test mtd device
|
||||
* $Id: mtdram.c,v 1.1.1.1 2007/06/12 07:27:09 eyryu Exp $
|
||||
* 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/compatmac.h>
|
||||
#include <linux/mtd/mtd.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)
|
||||
{
|
||||
if (instr->addr + instr->len > mtd->size)
|
||||
return -EINVAL;
|
||||
|
||||
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, u_char **mtdbuf)
|
||||
{
|
||||
if (from + len > mtd->size)
|
||||
return -EINVAL;
|
||||
|
||||
*mtdbuf = mtd->priv + from;
|
||||
*retlen = len;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void ram_unpoint(struct mtd_info *mtd, u_char * addr, loff_t from,
|
||||
size_t len)
|
||||
{
|
||||
}
|
||||
|
||||
static int ram_read(struct mtd_info *mtd, loff_t from, size_t len,
|
||||
size_t *retlen, u_char *buf)
|
||||
{
|
||||
if (from + len > mtd->size)
|
||||
return -EINVAL;
|
||||
|
||||
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)
|
||||
{
|
||||
if (to + len > mtd->size)
|
||||
return -EINVAL;
|
||||
|
||||
memcpy((char *)mtd->priv + to, buf, len);
|
||||
|
||||
*retlen = len;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __exit cleanup_mtdram(void)
|
||||
{
|
||||
if (mtd_info) {
|
||||
del_mtd_device(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->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->read = ram_read;
|
||||
mtd->write = ram_write;
|
||||
|
||||
if (add_mtd_device(mtd)) {
|
||||
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");
|
||||
303
drivers/mtd/devices/phram.c
Normal file
303
drivers/mtd/devices/phram.c
Normal file
@@ -0,0 +1,303 @@
|
||||
/**
|
||||
* $Id: phram.c,v 1.1.1.1 2007/06/12 07:27:09 eyryu Exp $
|
||||
*
|
||||
* Copyright (c) ???? Jochen Schäuble <psionic@psionic.de>
|
||||
* Copyright (c) 2003-2004 Jörn 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
|
||||
*/
|
||||
#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>
|
||||
|
||||
#define ERROR(fmt, args...) printk(KERN_ERR "phram: " fmt , ## args)
|
||||
|
||||
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;
|
||||
|
||||
if (instr->addr + instr->len > mtd->size)
|
||||
return -EINVAL;
|
||||
|
||||
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, u_char **mtdbuf)
|
||||
{
|
||||
u_char *start = mtd->priv;
|
||||
|
||||
if (from + len > mtd->size)
|
||||
return -EINVAL;
|
||||
|
||||
*mtdbuf = start + from;
|
||||
*retlen = len;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void phram_unpoint(struct mtd_info *mtd, u_char *addr, loff_t from,
|
||||
size_t len)
|
||||
{
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
if (from >= mtd->size)
|
||||
return -EINVAL;
|
||||
|
||||
if (len > mtd->size - from)
|
||||
len = mtd->size - from;
|
||||
|
||||
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;
|
||||
|
||||
if (to >= mtd->size)
|
||||
return -EINVAL;
|
||||
|
||||
if (len > mtd->size - to)
|
||||
len = mtd->size - to;
|
||||
|
||||
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) {
|
||||
del_mtd_device(&this->mtd);
|
||||
iounmap(this->mtd.priv);
|
||||
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) {
|
||||
ERROR("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 (add_mtd_device(&new->mtd)) {
|
||||
ERROR("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 { \
|
||||
ERROR(fmt , ## args); \
|
||||
return 0; \
|
||||
} while (0)
|
||||
|
||||
static int phram_setup(const char *val, struct kernel_param *kp)
|
||||
{
|
||||
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 == -ENOMEM)
|
||||
parse_err("out of memory\n");
|
||||
if (ret == -ENOSPC)
|
||||
parse_err("name too long\n");
|
||||
if (ret)
|
||||
return 0;
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
register_device(name, start, len);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
module_param_call(phram, phram_setup, NULL, NULL, 000);
|
||||
MODULE_PARM_DESC(phram,"Memory region to map. \"map=<name>,<start>,<length>\"");
|
||||
|
||||
|
||||
static int __init init_phram(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __exit cleanup_phram(void)
|
||||
{
|
||||
unregister_devices();
|
||||
}
|
||||
|
||||
module_init(init_phram);
|
||||
module_exit(cleanup_phram);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Jörn Engel <joern@wh.fh-wedel.de>");
|
||||
MODULE_DESCRIPTION("MTD driver for physical RAM");
|
||||
869
drivers/mtd/devices/pmc551.c
Normal file
869
drivers/mtd/devices/pmc551.c
Normal file
@@ -0,0 +1,869 @@
|
||||
/*
|
||||
* $Id: pmc551.c,v 1.1.1.1 2007/06/12 07:27:09 eyryu Exp $
|
||||
*
|
||||
* 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 it's DRAM banks have 64M
|
||||
* of ram configured (making a grand total of 256M 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 then
|
||||
* 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/FIXME's:
|
||||
* * 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 <asm/system.h>
|
||||
#include <linux/pci.h>
|
||||
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include <linux/mtd/pmc551.h>
|
||||
#include <linux/mtd/compatmac.h>
|
||||
|
||||
static struct mtd_info *pmc551list;
|
||||
|
||||
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;
|
||||
|
||||
/* Is it past the end? */
|
||||
if (end > mtd->size) {
|
||||
#ifdef CONFIG_MTD_PMC551_DEBUG
|
||||
printk(KERN_DEBUG "pmc551_erase() out of bounds (%ld > %ld)\n",
|
||||
(long)end, (long)mtd->size);
|
||||
#endif
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
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, &ptr);
|
||||
|
||||
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, &ptr);
|
||||
}
|
||||
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, u_char ** mtdbuf)
|
||||
{
|
||||
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
|
||||
|
||||
if (from + len > mtd->size) {
|
||||
#ifdef CONFIG_MTD_PMC551_DEBUG
|
||||
printk(KERN_DEBUG "pmc551_point() out of bounds (%ld > %ld)\n",
|
||||
(long)from + len, (long)mtd->size);
|
||||
#endif
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
*mtdbuf = priv->start + soff_lo;
|
||||
*retlen = len;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void pmc551_unpoint(struct mtd_info *mtd, u_char * addr, loff_t from,
|
||||
size_t len)
|
||||
{
|
||||
#ifdef CONFIG_MTD_PMC551_DEBUG
|
||||
printk(KERN_DEBUG "pmc551_unpoint()\n");
|
||||
#endif
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
/* Is it past the end? */
|
||||
if (end > mtd->size) {
|
||||
#ifdef CONFIG_MTD_PMC551_DEBUG
|
||||
printk(KERN_DEBUG "pmc551_read() out of bounds (%ld > %ld)\n",
|
||||
(long)end, (long)mtd->size);
|
||||
#endif
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
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, &ptr);
|
||||
|
||||
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, &ptr);
|
||||
}
|
||||
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;
|
||||
/* Is it past the end? or did the u32 wrap? */
|
||||
if (end > mtd->size) {
|
||||
#ifdef CONFIG_MTD_PMC551_DEBUG
|
||||
printk(KERN_DEBUG "pmc551_write() out of bounds (end: %ld, "
|
||||
"size: %ld, to: %ld)\n", (long)end, (long)mtd->size,
|
||||
(long)to);
|
||||
#endif
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
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, &ptr);
|
||||
|
||||
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, &ptr);
|
||||
}
|
||||
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 basicly 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 u32 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 tendancy 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%c (0x%x) of %sprefetchable memory at "
|
||||
"0x%llx\n", (size < 1024) ? size : (size < 1048576) ?
|
||||
size >> 10 : size >> 20,
|
||||
(size < 1024) ? 'B' : (size < 1048576) ? 'K' : 'M', 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;
|
||||
#if defined(CONFIG_MTD_PMC551_APERTURE_SIZE)
|
||||
static int asize = CONFIG_MTD_PMC551_APERTURE_SIZE
|
||||
#else
|
||||
static int asize = 0;
|
||||
#endif
|
||||
|
||||
module_param(msize, int, 0);
|
||||
MODULE_PARM_DESC(msize, "memory size in Megabytes [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 count, found = 0;
|
||||
struct mtd_info *mtd;
|
||||
u32 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 (count = 0; count < MAX_MTD_DEVICES; count++) {
|
||||
|
||||
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 (add_mtd_device(mtd)) {
|
||||
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 add_mtd_device worked */
|
||||
pci_dev_get(PCI_Device);
|
||||
|
||||
printk(KERN_NOTICE "Registered pmc551 memory device.\n");
|
||||
printk(KERN_NOTICE "Mapped %dM 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%c\n",
|
||||
(length < 1024) ? length :
|
||||
(length < 1048576) ? length >> 10 : length >> 20,
|
||||
(length < 1024) ? 'B' : (length < 1048576) ? 'K' : 'M');
|
||||
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 %dM starting at "
|
||||
"0x%p\n", priv->asize >> 20, priv->start);
|
||||
pci_iounmap(priv->dev, priv->start);
|
||||
}
|
||||
pci_dev_put(priv->dev);
|
||||
|
||||
kfree(mtd->priv);
|
||||
del_mtd_device(mtd);
|
||||
kfree(mtd);
|
||||
found++;
|
||||
}
|
||||
|
||||
printk(KERN_NOTICE "pmc551: %d pmc551 devices unloaded\n", found);
|
||||
}
|
||||
|
||||
module_init(init_pmc551);
|
||||
module_exit(cleanup_pmc551);
|
||||
366
drivers/mtd/devices/slram.c
Normal file
366
drivers/mtd/devices/slram.c
Normal file
@@ -0,0 +1,366 @@
|
||||
/*======================================================================
|
||||
|
||||
$Id: slram.c,v 1.1.1.1 2007/06/12 07:27:09 eyryu Exp $
|
||||
|
||||
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 contigous memory region. Therfore
|
||||
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 <asm/system.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 *, u_char **);
|
||||
static void slram_unpoint(struct mtd_info *, u_char *, 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;
|
||||
|
||||
if (instr->addr + instr->len > mtd->size) {
|
||||
return(-EINVAL);
|
||||
}
|
||||
|
||||
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, u_char **mtdbuf)
|
||||
{
|
||||
slram_priv_t *priv = mtd->priv;
|
||||
|
||||
if (from + len > mtd->size)
|
||||
return -EINVAL;
|
||||
|
||||
*mtdbuf = priv->start + from;
|
||||
*retlen = len;
|
||||
return(0);
|
||||
}
|
||||
|
||||
static void slram_unpoint(struct mtd_info *mtd, u_char *addr, loff_t from, size_t len)
|
||||
{
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
if (from > mtd->size)
|
||||
return -EINVAL;
|
||||
|
||||
if (from + len > mtd->size)
|
||||
len = mtd->size - from;
|
||||
|
||||
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;
|
||||
|
||||
if (to + len > mtd->size)
|
||||
return -EINVAL;
|
||||
|
||||
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 (add_mtd_device((*curmtd)->mtdinfo)) {
|
||||
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;
|
||||
del_mtd_device(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;
|
||||
} 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 ((devstart < 0) || (devlength < 0) || (devlength % SLRAM_BLK_SZ != 0)) {
|
||||
E("slram: Illegal start / length parameter.\n");
|
||||
return(-EINVAL);
|
||||
}
|
||||
|
||||
if ((devstart = register_device(devname, devstart, devlength))){
|
||||
unregister_devices();
|
||||
return((int)devstart);
|
||||
}
|
||||
return(0);
|
||||
}
|
||||
|
||||
#ifndef MODULE
|
||||
|
||||
static int __init mtd_slram_setup(char *str)
|
||||
{
|
||||
map = str;
|
||||
return(1);
|
||||
}
|
||||
|
||||
__setup("slram=", mtd_slram_setup);
|
||||
|
||||
#endif
|
||||
|
||||
static int 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; (map[count]) && (count < SLRAM_MAX_DEVICES_PARAMS);
|
||||
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");
|
||||
Reference in New Issue
Block a user