mirror of
https://github.com/AetherDroid/android_kernel_samsung_on5xelte.git
synced 2025-09-08 17:18:05 -04:00
Fixed MTP to work with TWRP
This commit is contained in:
commit
f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions
228
drivers/mtd/devices/Kconfig
Normal file
228
drivers/mtd/devices/Kconfig
Normal file
|
@ -0,0 +1,228 @@
|
|||
menu "Self-contained MTD device drivers"
|
||||
depends on MTD!=n
|
||||
depends on HAS_IOMEM
|
||||
|
||||
config MTD_PMC551
|
||||
tristate "Ramix PMC551 PCI Mezzanine RAM card support"
|
||||
depends on PCI
|
||||
---help---
|
||||
This provides a MTD device driver for the Ramix PMC551 RAM PCI card
|
||||
from Ramix Inc. <http://www.ramix.com/products/memory/pmc551.html>.
|
||||
These devices come in memory configurations from 32M - 1G. If you
|
||||
have one, you probably want to enable this.
|
||||
|
||||
If this driver is compiled as a module you get the ability to select
|
||||
the size of the aperture window pointing into the devices memory.
|
||||
What this means is that if you have a 1G card, normally the kernel
|
||||
will use a 1G memory map as its view of the device. As a module,
|
||||
you can select a 1M window into the memory and the driver will
|
||||
"slide" the window around the PMC551's memory. This was
|
||||
particularly useful on the 2.2 kernels on PPC architectures as there
|
||||
was limited kernel space to deal with.
|
||||
|
||||
config MTD_PMC551_BUGFIX
|
||||
bool "PMC551 256M DRAM Bugfix"
|
||||
depends on MTD_PMC551
|
||||
help
|
||||
Some of Ramix's PMC551 boards with 256M configurations have invalid
|
||||
column and row mux values. This option will fix them, but will
|
||||
break other memory configurations. If unsure say N.
|
||||
|
||||
config MTD_PMC551_DEBUG
|
||||
bool "PMC551 Debugging"
|
||||
depends on MTD_PMC551
|
||||
help
|
||||
This option makes the PMC551 more verbose during its operation and
|
||||
is only really useful if you are developing on this driver or
|
||||
suspect a possible hardware or driver bug. If unsure say N.
|
||||
|
||||
config MTD_MS02NV
|
||||
tristate "DEC MS02-NV NVRAM module support"
|
||||
depends on MACH_DECSTATION
|
||||
help
|
||||
This is an MTD driver for the DEC's MS02-NV (54-20948-01) battery
|
||||
backed-up NVRAM module. The module was originally meant as an NFS
|
||||
accelerator. Say Y here if you have a DECstation 5000/2x0 or a
|
||||
DECsystem 5900 equipped with such a module.
|
||||
|
||||
If you want to compile this driver as a module ( = code which can be
|
||||
inserted in and removed from the running kernel whenever you want),
|
||||
say M here and read <file:Documentation/kbuild/modules.txt>.
|
||||
The module will be called ms02-nv.
|
||||
|
||||
config MTD_DATAFLASH
|
||||
tristate "Support for AT45xxx DataFlash"
|
||||
depends on SPI_MASTER
|
||||
help
|
||||
This enables access to AT45xxx DataFlash chips, using SPI.
|
||||
Sometimes DataFlash chips are packaged inside MMC-format
|
||||
cards; at this writing, the MMC stack won't handle those.
|
||||
|
||||
config MTD_DATAFLASH_WRITE_VERIFY
|
||||
bool "Verify DataFlash page writes"
|
||||
depends on MTD_DATAFLASH
|
||||
help
|
||||
This adds an extra check when data is written to the flash.
|
||||
It may help if you are verifying chip setup (timings etc) on
|
||||
your board. There is a rare possibility that even though the
|
||||
device thinks the write was successful, a bit could have been
|
||||
flipped accidentally due to device wear or something else.
|
||||
|
||||
config MTD_DATAFLASH_OTP
|
||||
bool "DataFlash OTP support (Security Register)"
|
||||
depends on MTD_DATAFLASH
|
||||
help
|
||||
Newer DataFlash chips (revisions C and D) support 128 bytes of
|
||||
one-time-programmable (OTP) data. The first half may be written
|
||||
(once) with up to 64 bytes of data, such as a serial number or
|
||||
other key product data. The second half is programmed with a
|
||||
unique-to-each-chip bit pattern at the factory.
|
||||
|
||||
config MTD_M25P80
|
||||
tristate "Support most SPI Flash chips (AT26DF, M25P, W25X, ...)"
|
||||
depends on SPI_MASTER && MTD_SPI_NOR
|
||||
help
|
||||
This enables access to most modern SPI flash chips, used for
|
||||
program and data storage. Series supported include Atmel AT26DF,
|
||||
Spansion S25SL, SST 25VF, ST M25P, and Winbond W25X. Other chips
|
||||
are supported as well. See the driver source for the current list,
|
||||
or to add other chips.
|
||||
|
||||
Note that the original DataFlash chips (AT45 series, not AT26DF),
|
||||
need an entirely different driver.
|
||||
|
||||
Set up your spi devices with the right board-specific platform data,
|
||||
if you want to specify device partitioning or to use a device which
|
||||
doesn't support the JEDEC ID instruction.
|
||||
|
||||
config MTD_SPEAR_SMI
|
||||
tristate "SPEAR MTD NOR Support through SMI controller"
|
||||
depends on PLAT_SPEAR
|
||||
default y
|
||||
help
|
||||
This enable SNOR support on SPEAR platforms using SMI controller
|
||||
|
||||
config MTD_SST25L
|
||||
tristate "Support SST25L (non JEDEC) SPI Flash chips"
|
||||
depends on SPI_MASTER
|
||||
help
|
||||
This enables access to the non JEDEC SST25L SPI flash chips, used
|
||||
for program and data storage.
|
||||
|
||||
Set up your spi devices with the right board-specific platform data,
|
||||
if you want to specify device partitioning.
|
||||
|
||||
config MTD_BCM47XXSFLASH
|
||||
tristate "R/O support for serial flash on BCMA bus"
|
||||
depends on BCMA_SFLASH
|
||||
help
|
||||
BCMA bus can have various flash memories attached, they are
|
||||
registered by bcma as platform devices. This enables driver for
|
||||
serial flash memories (only read-only mode is implemented).
|
||||
|
||||
config MTD_SLRAM
|
||||
tristate "Uncached system RAM"
|
||||
help
|
||||
If your CPU cannot cache all of the physical memory in your machine,
|
||||
you can still use it for storage or swap by using this driver to
|
||||
present it to the system as a Memory Technology Device.
|
||||
|
||||
config MTD_PHRAM
|
||||
tristate "Physical system RAM"
|
||||
help
|
||||
This is a re-implementation of the slram driver above.
|
||||
|
||||
Use this driver to access physical memory that the kernel proper
|
||||
doesn't have access to, memory beyond the mem=xxx limit, nvram,
|
||||
memory on the video card, etc...
|
||||
|
||||
config MTD_LART
|
||||
tristate "28F160xx flash driver for LART"
|
||||
depends on SA1100_LART
|
||||
help
|
||||
This enables the flash driver for LART. Please note that you do
|
||||
not need any mapping/chip driver for LART. This one does it all
|
||||
for you, so go disable all of those if you enabled some of them (:
|
||||
|
||||
config MTD_MTDRAM
|
||||
tristate "Test driver using RAM"
|
||||
help
|
||||
This enables a test MTD device driver which uses vmalloc() to
|
||||
provide storage. You probably want to say 'N' unless you're
|
||||
testing stuff.
|
||||
|
||||
config MTDRAM_TOTAL_SIZE
|
||||
int "MTDRAM device size in KiB"
|
||||
depends on MTD_MTDRAM
|
||||
default "4096"
|
||||
help
|
||||
This allows you to configure the total size of the MTD device
|
||||
emulated by the MTDRAM driver. If the MTDRAM driver is built
|
||||
as a module, it is also possible to specify this as a parameter when
|
||||
loading the module.
|
||||
|
||||
config MTDRAM_ERASE_SIZE
|
||||
int "MTDRAM erase block size in KiB"
|
||||
depends on MTD_MTDRAM
|
||||
default "128"
|
||||
help
|
||||
This allows you to configure the size of the erase blocks in the
|
||||
device emulated by the MTDRAM driver. If the MTDRAM driver is built
|
||||
as a module, it is also possible to specify this as a parameter when
|
||||
loading the module.
|
||||
|
||||
#If not a module (I don't want to test it as a module)
|
||||
config MTDRAM_ABS_POS
|
||||
hex "SRAM Hexadecimal Absolute position or 0"
|
||||
depends on MTD_MTDRAM=y
|
||||
default "0"
|
||||
help
|
||||
If you have system RAM accessible by the CPU but not used by Linux
|
||||
in normal operation, you can give the physical address at which the
|
||||
available RAM starts, and the MTDRAM driver will use it instead of
|
||||
allocating space from Linux's available memory. Otherwise, leave
|
||||
this set to zero. Most people will want to leave this as zero.
|
||||
|
||||
config MTD_BLOCK2MTD
|
||||
tristate "MTD using block device"
|
||||
depends on BLOCK
|
||||
help
|
||||
This driver allows a block device to appear as an MTD. It would
|
||||
generally be used in the following cases:
|
||||
|
||||
Using Compact Flash as an MTD, these usually present themselves to
|
||||
the system as an ATA drive.
|
||||
Testing MTD users (eg JFFS2) on large media and media that might
|
||||
be removed during a write (using the floppy drive).
|
||||
|
||||
comment "Disk-On-Chip Device Drivers"
|
||||
|
||||
config MTD_DOCG3
|
||||
tristate "M-Systems Disk-On-Chip G3"
|
||||
select BCH
|
||||
select BCH_CONST_PARAMS
|
||||
select BITREVERSE
|
||||
---help---
|
||||
This provides an MTD device driver for the M-Systems DiskOnChip
|
||||
G3 devices.
|
||||
|
||||
The driver provides access to G3 DiskOnChip, distributed by
|
||||
M-Systems and now Sandisk. The support is very experimental,
|
||||
and doesn't give access to any write operations.
|
||||
|
||||
config MTD_ST_SPI_FSM
|
||||
tristate "ST Microelectronics SPI FSM Serial Flash Controller"
|
||||
depends on ARCH_STI
|
||||
help
|
||||
This provides an MTD device driver for the ST Microelectronics
|
||||
SPI Fast Sequence Mode (FSM) Serial Flash Controller and support
|
||||
for a subset of connected Serial Flash devices.
|
||||
|
||||
if MTD_DOCG3
|
||||
config BCH_CONST_M
|
||||
default 14
|
||||
config BCH_CONST_T
|
||||
default 4
|
||||
endif
|
||||
|
||||
endmenu
|
21
drivers/mtd/devices/Makefile
Normal file
21
drivers/mtd/devices/Makefile
Normal file
|
@ -0,0 +1,21 @@
|
|||
#
|
||||
# linux/drivers/mtd/devices/Makefile
|
||||
#
|
||||
|
||||
obj-$(CONFIG_MTD_DOCG3) += docg3.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
|
||||
obj-$(CONFIG_MTD_SPEAR_SMI) += spear_smi.o
|
||||
obj-$(CONFIG_MTD_SST25L) += sst25l.o
|
||||
obj-$(CONFIG_MTD_BCM47XXSFLASH) += bcm47xxsflash.o
|
||||
obj-$(CONFIG_MTD_ST_SPI_FSM) += st_spi_fsm.o
|
||||
|
||||
|
||||
CFLAGS_docg3.o += -I$(src)
|
340
drivers/mtd/devices/bcm47xxsflash.c
Normal file
340
drivers/mtd/devices/bcm47xxsflash.c
Normal file
|
@ -0,0 +1,340 @@
|
|||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/bcma/bcma.h>
|
||||
|
||||
#include "bcm47xxsflash.h"
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_DESCRIPTION("Serial flash driver for BCMA bus");
|
||||
|
||||
static const char * const probes[] = { "bcm47xxpart", NULL };
|
||||
|
||||
/**************************************************
|
||||
* Various helpers
|
||||
**************************************************/
|
||||
|
||||
static void bcm47xxsflash_cmd(struct bcm47xxsflash *b47s, u32 opcode)
|
||||
{
|
||||
int i;
|
||||
|
||||
b47s->cc_write(b47s, BCMA_CC_FLASHCTL, BCMA_CC_FLASHCTL_START | opcode);
|
||||
for (i = 0; i < 1000; i++) {
|
||||
if (!(b47s->cc_read(b47s, BCMA_CC_FLASHCTL) &
|
||||
BCMA_CC_FLASHCTL_BUSY))
|
||||
return;
|
||||
cpu_relax();
|
||||
}
|
||||
pr_err("Control command failed (timeout)!\n");
|
||||
}
|
||||
|
||||
static int bcm47xxsflash_poll(struct bcm47xxsflash *b47s, int timeout)
|
||||
{
|
||||
unsigned long deadline = jiffies + timeout;
|
||||
|
||||
do {
|
||||
switch (b47s->type) {
|
||||
case BCM47XXSFLASH_TYPE_ST:
|
||||
bcm47xxsflash_cmd(b47s, OPCODE_ST_RDSR);
|
||||
if (!(b47s->cc_read(b47s, BCMA_CC_FLASHDATA) &
|
||||
SR_ST_WIP))
|
||||
return 0;
|
||||
break;
|
||||
case BCM47XXSFLASH_TYPE_ATMEL:
|
||||
bcm47xxsflash_cmd(b47s, OPCODE_AT_STATUS);
|
||||
if (b47s->cc_read(b47s, BCMA_CC_FLASHDATA) &
|
||||
SR_AT_READY)
|
||||
return 0;
|
||||
break;
|
||||
}
|
||||
|
||||
cpu_relax();
|
||||
udelay(1);
|
||||
} while (!time_after_eq(jiffies, deadline));
|
||||
|
||||
pr_err("Timeout waiting for flash to be ready!\n");
|
||||
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
/**************************************************
|
||||
* MTD ops
|
||||
**************************************************/
|
||||
|
||||
static int bcm47xxsflash_erase(struct mtd_info *mtd, struct erase_info *erase)
|
||||
{
|
||||
struct bcm47xxsflash *b47s = mtd->priv;
|
||||
int err;
|
||||
|
||||
switch (b47s->type) {
|
||||
case BCM47XXSFLASH_TYPE_ST:
|
||||
bcm47xxsflash_cmd(b47s, OPCODE_ST_WREN);
|
||||
b47s->cc_write(b47s, BCMA_CC_FLASHADDR, erase->addr);
|
||||
/* Newer flashes have "sub-sectors" which can be erased
|
||||
* independently with a new command: ST_SSE. The ST_SE command
|
||||
* erases 64KB just as before.
|
||||
*/
|
||||
if (b47s->blocksize < (64 * 1024))
|
||||
bcm47xxsflash_cmd(b47s, OPCODE_ST_SSE);
|
||||
else
|
||||
bcm47xxsflash_cmd(b47s, OPCODE_ST_SE);
|
||||
break;
|
||||
case BCM47XXSFLASH_TYPE_ATMEL:
|
||||
b47s->cc_write(b47s, BCMA_CC_FLASHADDR, erase->addr << 1);
|
||||
bcm47xxsflash_cmd(b47s, OPCODE_AT_PAGE_ERASE);
|
||||
break;
|
||||
}
|
||||
|
||||
err = bcm47xxsflash_poll(b47s, HZ);
|
||||
if (err)
|
||||
erase->state = MTD_ERASE_FAILED;
|
||||
else
|
||||
erase->state = MTD_ERASE_DONE;
|
||||
|
||||
if (erase->callback)
|
||||
erase->callback(erase);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int bcm47xxsflash_read(struct mtd_info *mtd, loff_t from, size_t len,
|
||||
size_t *retlen, u_char *buf)
|
||||
{
|
||||
struct bcm47xxsflash *b47s = mtd->priv;
|
||||
|
||||
/* Check address range */
|
||||
if ((from + len) > mtd->size)
|
||||
return -EINVAL;
|
||||
|
||||
memcpy_fromio(buf, (void __iomem *)KSEG0ADDR(b47s->window + from),
|
||||
len);
|
||||
*retlen = len;
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static int bcm47xxsflash_write_st(struct mtd_info *mtd, u32 offset, size_t len,
|
||||
const u_char *buf)
|
||||
{
|
||||
struct bcm47xxsflash *b47s = mtd->priv;
|
||||
int written = 0;
|
||||
|
||||
/* Enable writes */
|
||||
bcm47xxsflash_cmd(b47s, OPCODE_ST_WREN);
|
||||
|
||||
/* Write first byte */
|
||||
b47s->cc_write(b47s, BCMA_CC_FLASHADDR, offset);
|
||||
b47s->cc_write(b47s, BCMA_CC_FLASHDATA, *buf++);
|
||||
|
||||
/* Program page */
|
||||
if (b47s->bcma_cc->core->id.rev < 20) {
|
||||
bcm47xxsflash_cmd(b47s, OPCODE_ST_PP);
|
||||
return 1; /* 1B written */
|
||||
}
|
||||
|
||||
/* Program page and set CSA (on newer chips we can continue writing) */
|
||||
bcm47xxsflash_cmd(b47s, OPCODE_ST_CSA | OPCODE_ST_PP);
|
||||
offset++;
|
||||
len--;
|
||||
written++;
|
||||
|
||||
while (len > 0) {
|
||||
/* Page boundary, another function call is needed */
|
||||
if ((offset & 0xFF) == 0)
|
||||
break;
|
||||
|
||||
bcm47xxsflash_cmd(b47s, OPCODE_ST_CSA | *buf++);
|
||||
offset++;
|
||||
len--;
|
||||
written++;
|
||||
}
|
||||
|
||||
/* All done, drop CSA & poll */
|
||||
b47s->cc_write(b47s, BCMA_CC_FLASHCTL, 0);
|
||||
udelay(1);
|
||||
if (bcm47xxsflash_poll(b47s, HZ / 10))
|
||||
pr_err("Flash rejected dropping CSA\n");
|
||||
|
||||
return written;
|
||||
}
|
||||
|
||||
static int bcm47xxsflash_write_at(struct mtd_info *mtd, u32 offset, size_t len,
|
||||
const u_char *buf)
|
||||
{
|
||||
struct bcm47xxsflash *b47s = mtd->priv;
|
||||
u32 mask = b47s->blocksize - 1;
|
||||
u32 page = (offset & ~mask) << 1;
|
||||
u32 byte = offset & mask;
|
||||
int written = 0;
|
||||
|
||||
/* If we don't overwrite whole page, read it to the buffer first */
|
||||
if (byte || (len < b47s->blocksize)) {
|
||||
int err;
|
||||
|
||||
b47s->cc_write(b47s, BCMA_CC_FLASHADDR, page);
|
||||
bcm47xxsflash_cmd(b47s, OPCODE_AT_BUF1_LOAD);
|
||||
/* 250 us for AT45DB321B */
|
||||
err = bcm47xxsflash_poll(b47s, HZ / 1000);
|
||||
if (err) {
|
||||
pr_err("Timeout reading page 0x%X info buffer\n", page);
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
/* Change buffer content with our data */
|
||||
while (len > 0) {
|
||||
/* Page boundary, another function call is needed */
|
||||
if (byte == b47s->blocksize)
|
||||
break;
|
||||
|
||||
b47s->cc_write(b47s, BCMA_CC_FLASHADDR, byte++);
|
||||
b47s->cc_write(b47s, BCMA_CC_FLASHDATA, *buf++);
|
||||
bcm47xxsflash_cmd(b47s, OPCODE_AT_BUF1_WRITE);
|
||||
len--;
|
||||
written++;
|
||||
}
|
||||
|
||||
/* Program page with the buffer content */
|
||||
b47s->cc_write(b47s, BCMA_CC_FLASHADDR, page);
|
||||
bcm47xxsflash_cmd(b47s, OPCODE_AT_BUF1_PROGRAM);
|
||||
|
||||
return written;
|
||||
}
|
||||
|
||||
static int bcm47xxsflash_write(struct mtd_info *mtd, loff_t to, size_t len,
|
||||
size_t *retlen, const u_char *buf)
|
||||
{
|
||||
struct bcm47xxsflash *b47s = mtd->priv;
|
||||
int written;
|
||||
|
||||
/* Writing functions can return without writing all passed data, for
|
||||
* example when the hardware is too old or when we git page boundary.
|
||||
*/
|
||||
while (len > 0) {
|
||||
switch (b47s->type) {
|
||||
case BCM47XXSFLASH_TYPE_ST:
|
||||
written = bcm47xxsflash_write_st(mtd, to, len, buf);
|
||||
break;
|
||||
case BCM47XXSFLASH_TYPE_ATMEL:
|
||||
written = bcm47xxsflash_write_at(mtd, to, len, buf);
|
||||
break;
|
||||
default:
|
||||
BUG_ON(1);
|
||||
}
|
||||
if (written < 0) {
|
||||
pr_err("Error writing at offset 0x%llX\n", to);
|
||||
return written;
|
||||
}
|
||||
to += (loff_t)written;
|
||||
len -= written;
|
||||
*retlen += written;
|
||||
buf += written;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void bcm47xxsflash_fill_mtd(struct bcm47xxsflash *b47s)
|
||||
{
|
||||
struct mtd_info *mtd = &b47s->mtd;
|
||||
|
||||
mtd->priv = b47s;
|
||||
mtd->name = "bcm47xxsflash";
|
||||
mtd->owner = THIS_MODULE;
|
||||
|
||||
mtd->type = MTD_NORFLASH;
|
||||
mtd->flags = MTD_CAP_NORFLASH;
|
||||
mtd->size = b47s->size;
|
||||
mtd->erasesize = b47s->blocksize;
|
||||
mtd->writesize = 1;
|
||||
mtd->writebufsize = 1;
|
||||
|
||||
mtd->_erase = bcm47xxsflash_erase;
|
||||
mtd->_read = bcm47xxsflash_read;
|
||||
mtd->_write = bcm47xxsflash_write;
|
||||
}
|
||||
|
||||
/**************************************************
|
||||
* BCMA
|
||||
**************************************************/
|
||||
|
||||
static int bcm47xxsflash_bcma_cc_read(struct bcm47xxsflash *b47s, u16 offset)
|
||||
{
|
||||
return bcma_cc_read32(b47s->bcma_cc, offset);
|
||||
}
|
||||
|
||||
static void bcm47xxsflash_bcma_cc_write(struct bcm47xxsflash *b47s, u16 offset,
|
||||
u32 value)
|
||||
{
|
||||
bcma_cc_write32(b47s->bcma_cc, offset, value);
|
||||
}
|
||||
|
||||
static int bcm47xxsflash_bcma_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct bcma_sflash *sflash = dev_get_platdata(&pdev->dev);
|
||||
struct bcm47xxsflash *b47s;
|
||||
int err;
|
||||
|
||||
b47s = devm_kzalloc(&pdev->dev, sizeof(*b47s), GFP_KERNEL);
|
||||
if (!b47s)
|
||||
return -ENOMEM;
|
||||
sflash->priv = b47s;
|
||||
|
||||
b47s->bcma_cc = container_of(sflash, struct bcma_drv_cc, sflash);
|
||||
b47s->cc_read = bcm47xxsflash_bcma_cc_read;
|
||||
b47s->cc_write = bcm47xxsflash_bcma_cc_write;
|
||||
|
||||
switch (b47s->bcma_cc->capabilities & BCMA_CC_CAP_FLASHT) {
|
||||
case BCMA_CC_FLASHT_STSER:
|
||||
b47s->type = BCM47XXSFLASH_TYPE_ST;
|
||||
break;
|
||||
case BCMA_CC_FLASHT_ATSER:
|
||||
b47s->type = BCM47XXSFLASH_TYPE_ATMEL;
|
||||
break;
|
||||
}
|
||||
|
||||
b47s->window = sflash->window;
|
||||
b47s->blocksize = sflash->blocksize;
|
||||
b47s->numblocks = sflash->numblocks;
|
||||
b47s->size = sflash->size;
|
||||
bcm47xxsflash_fill_mtd(b47s);
|
||||
|
||||
err = mtd_device_parse_register(&b47s->mtd, probes, NULL, NULL, 0);
|
||||
if (err) {
|
||||
pr_err("Failed to register MTD device: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
if (bcm47xxsflash_poll(b47s, HZ / 10))
|
||||
pr_warn("Serial flash busy\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bcm47xxsflash_bcma_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct bcma_sflash *sflash = dev_get_platdata(&pdev->dev);
|
||||
struct bcm47xxsflash *b47s = sflash->priv;
|
||||
|
||||
mtd_device_unregister(&b47s->mtd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver bcma_sflash_driver = {
|
||||
.probe = bcm47xxsflash_bcma_probe,
|
||||
.remove = bcm47xxsflash_bcma_remove,
|
||||
.driver = {
|
||||
.name = "bcma_sflash",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
};
|
||||
|
||||
/**************************************************
|
||||
* Init
|
||||
**************************************************/
|
||||
|
||||
module_platform_driver(bcma_sflash_driver);
|
76
drivers/mtd/devices/bcm47xxsflash.h
Normal file
76
drivers/mtd/devices/bcm47xxsflash.h
Normal file
|
@ -0,0 +1,76 @@
|
|||
#ifndef __BCM47XXSFLASH_H
|
||||
#define __BCM47XXSFLASH_H
|
||||
|
||||
#include <linux/mtd/mtd.h>
|
||||
|
||||
/* Used for ST flashes only. */
|
||||
#define OPCODE_ST_WREN 0x0006 /* Write Enable */
|
||||
#define OPCODE_ST_WRDIS 0x0004 /* Write Disable */
|
||||
#define OPCODE_ST_RDSR 0x0105 /* Read Status Register */
|
||||
#define OPCODE_ST_WRSR 0x0101 /* Write Status Register */
|
||||
#define OPCODE_ST_READ 0x0303 /* Read Data Bytes */
|
||||
#define OPCODE_ST_PP 0x0302 /* Page Program */
|
||||
#define OPCODE_ST_SE 0x02d8 /* Sector Erase */
|
||||
#define OPCODE_ST_BE 0x00c7 /* Bulk Erase */
|
||||
#define OPCODE_ST_DP 0x00b9 /* Deep Power-down */
|
||||
#define OPCODE_ST_RES 0x03ab /* Read Electronic Signature */
|
||||
#define OPCODE_ST_CSA 0x1000 /* Keep chip select asserted */
|
||||
#define OPCODE_ST_SSE 0x0220 /* Sub-sector Erase */
|
||||
|
||||
/* Used for Atmel flashes only. */
|
||||
#define OPCODE_AT_READ 0x07e8
|
||||
#define OPCODE_AT_PAGE_READ 0x07d2
|
||||
#define OPCODE_AT_STATUS 0x01d7
|
||||
#define OPCODE_AT_BUF1_WRITE 0x0384
|
||||
#define OPCODE_AT_BUF2_WRITE 0x0387
|
||||
#define OPCODE_AT_BUF1_ERASE_PROGRAM 0x0283
|
||||
#define OPCODE_AT_BUF2_ERASE_PROGRAM 0x0286
|
||||
#define OPCODE_AT_BUF1_PROGRAM 0x0288
|
||||
#define OPCODE_AT_BUF2_PROGRAM 0x0289
|
||||
#define OPCODE_AT_PAGE_ERASE 0x0281
|
||||
#define OPCODE_AT_BLOCK_ERASE 0x0250
|
||||
#define OPCODE_AT_BUF1_WRITE_ERASE_PROGRAM 0x0382
|
||||
#define OPCODE_AT_BUF2_WRITE_ERASE_PROGRAM 0x0385
|
||||
#define OPCODE_AT_BUF1_LOAD 0x0253
|
||||
#define OPCODE_AT_BUF2_LOAD 0x0255
|
||||
#define OPCODE_AT_BUF1_COMPARE 0x0260
|
||||
#define OPCODE_AT_BUF2_COMPARE 0x0261
|
||||
#define OPCODE_AT_BUF1_REPROGRAM 0x0258
|
||||
#define OPCODE_AT_BUF2_REPROGRAM 0x0259
|
||||
|
||||
/* Status register bits for ST flashes */
|
||||
#define SR_ST_WIP 0x01 /* Write In Progress */
|
||||
#define SR_ST_WEL 0x02 /* Write Enable Latch */
|
||||
#define SR_ST_BP_MASK 0x1c /* Block Protect */
|
||||
#define SR_ST_BP_SHIFT 2
|
||||
#define SR_ST_SRWD 0x80 /* Status Register Write Disable */
|
||||
|
||||
/* Status register bits for Atmel flashes */
|
||||
#define SR_AT_READY 0x80
|
||||
#define SR_AT_MISMATCH 0x40
|
||||
#define SR_AT_ID_MASK 0x38
|
||||
#define SR_AT_ID_SHIFT 3
|
||||
|
||||
struct bcma_drv_cc;
|
||||
|
||||
enum bcm47xxsflash_type {
|
||||
BCM47XXSFLASH_TYPE_ATMEL,
|
||||
BCM47XXSFLASH_TYPE_ST,
|
||||
};
|
||||
|
||||
struct bcm47xxsflash {
|
||||
struct bcma_drv_cc *bcma_cc;
|
||||
int (*cc_read)(struct bcm47xxsflash *b47s, u16 offset);
|
||||
void (*cc_write)(struct bcm47xxsflash *b47s, u16 offset, u32 value);
|
||||
|
||||
enum bcm47xxsflash_type type;
|
||||
|
||||
u32 window;
|
||||
u32 blocksize;
|
||||
u16 numblocks;
|
||||
u32 size;
|
||||
|
||||
struct mtd_info mtd;
|
||||
};
|
||||
|
||||
#endif /* BCM47XXSFLASH */
|
472
drivers/mtd/devices/block2mtd.c
Normal file
472
drivers/mtd/devices/block2mtd.c
Normal file
|
@ -0,0 +1,472 @@
|
|||
/*
|
||||
* block2mtd.c - create an mtd from a block device
|
||||
*
|
||||
* Copyright (C) 2001,2002 Simon Evans <spse@secret.org.uk>
|
||||
* Copyright (C) 2004-2006 Joern Engel <joern@wh.fh-wedel.de>
|
||||
*
|
||||
* Licence: GPL
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/blkdev.h>
|
||||
#include <linux/bio.h>
|
||||
#include <linux/pagemap.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/mount.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/major.h>
|
||||
|
||||
/* Info for the block device */
|
||||
struct block2mtd_dev {
|
||||
struct list_head list;
|
||||
struct block_device *blkdev;
|
||||
struct mtd_info mtd;
|
||||
struct mutex write_mutex;
|
||||
};
|
||||
|
||||
|
||||
/* Static info about the MTD, used in cleanup_module */
|
||||
static LIST_HEAD(blkmtd_device_list);
|
||||
|
||||
|
||||
static struct page *page_read(struct address_space *mapping, int index)
|
||||
{
|
||||
return read_mapping_page(mapping, index, NULL);
|
||||
}
|
||||
|
||||
/* erase a specified part of the device */
|
||||
static int _block2mtd_erase(struct block2mtd_dev *dev, loff_t to, size_t len)
|
||||
{
|
||||
struct address_space *mapping = dev->blkdev->bd_inode->i_mapping;
|
||||
struct page *page;
|
||||
int index = to >> PAGE_SHIFT; // page index
|
||||
int pages = len >> PAGE_SHIFT;
|
||||
u_long *p;
|
||||
u_long *max;
|
||||
|
||||
while (pages) {
|
||||
page = page_read(mapping, index);
|
||||
if (IS_ERR(page))
|
||||
return PTR_ERR(page);
|
||||
|
||||
max = page_address(page) + PAGE_SIZE;
|
||||
for (p=page_address(page); p<max; p++)
|
||||
if (*p != -1UL) {
|
||||
lock_page(page);
|
||||
memset(page_address(page), 0xff, PAGE_SIZE);
|
||||
set_page_dirty(page);
|
||||
unlock_page(page);
|
||||
balance_dirty_pages_ratelimited(mapping);
|
||||
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) {
|
||||
pr_err("erase failed err = %d\n", err);
|
||||
instr->state = MTD_ERASE_FAILED;
|
||||
} else
|
||||
instr->state = MTD_ERASE_DONE;
|
||||
|
||||
mtd_erase_callback(instr);
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
static int block2mtd_read(struct mtd_info *mtd, loff_t from, size_t len,
|
||||
size_t *retlen, u_char *buf)
|
||||
{
|
||||
struct block2mtd_dev *dev = mtd->priv;
|
||||
struct page *page;
|
||||
int index = from >> PAGE_SHIFT;
|
||||
int offset = from & (PAGE_SIZE-1);
|
||||
int cpylen;
|
||||
|
||||
while (len) {
|
||||
if ((offset + len) > PAGE_SIZE)
|
||||
cpylen = PAGE_SIZE - offset; // multiple pages
|
||||
else
|
||||
cpylen = len; // this page
|
||||
len = len - cpylen;
|
||||
|
||||
page = page_read(dev->blkdev->bd_inode->i_mapping, index);
|
||||
if (IS_ERR(page))
|
||||
return PTR_ERR(page);
|
||||
|
||||
memcpy(buf, page_address(page) + offset, cpylen);
|
||||
page_cache_release(page);
|
||||
|
||||
if (retlen)
|
||||
*retlen += cpylen;
|
||||
buf += cpylen;
|
||||
offset = 0;
|
||||
index++;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* write data to the underlying device */
|
||||
static int _block2mtd_write(struct block2mtd_dev *dev, const u_char *buf,
|
||||
loff_t to, size_t len, size_t *retlen)
|
||||
{
|
||||
struct page *page;
|
||||
struct address_space *mapping = dev->blkdev->bd_inode->i_mapping;
|
||||
int index = to >> PAGE_SHIFT; // page index
|
||||
int offset = to & ~PAGE_MASK; // page offset
|
||||
int cpylen;
|
||||
|
||||
while (len) {
|
||||
if ((offset+len) > PAGE_SIZE)
|
||||
cpylen = PAGE_SIZE - offset; // multiple pages
|
||||
else
|
||||
cpylen = len; // this page
|
||||
len = len - cpylen;
|
||||
|
||||
page = page_read(mapping, index);
|
||||
if (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);
|
||||
balance_dirty_pages_ratelimited(mapping);
|
||||
}
|
||||
page_cache_release(page);
|
||||
|
||||
if (retlen)
|
||||
*retlen += cpylen;
|
||||
|
||||
buf += cpylen;
|
||||
offset = 0;
|
||||
index++;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int block2mtd_write(struct mtd_info *mtd, loff_t to, size_t len,
|
||||
size_t *retlen, const u_char *buf)
|
||||
{
|
||||
struct block2mtd_dev *dev = mtd->priv;
|
||||
int err;
|
||||
|
||||
mutex_lock(&dev->write_mutex);
|
||||
err = _block2mtd_write(dev, buf, to, len, retlen);
|
||||
mutex_unlock(&dev->write_mutex);
|
||||
if (err > 0)
|
||||
err = 0;
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
/* sync the device - wait until the write queue is empty */
|
||||
static void block2mtd_sync(struct mtd_info *mtd)
|
||||
{
|
||||
struct block2mtd_dev *dev = mtd->priv;
|
||||
sync_blockdev(dev->blkdev);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
static void block2mtd_free_device(struct block2mtd_dev *dev)
|
||||
{
|
||||
if (!dev)
|
||||
return;
|
||||
|
||||
kfree(dev->mtd.name);
|
||||
|
||||
if (dev->blkdev) {
|
||||
invalidate_mapping_pages(dev->blkdev->bd_inode->i_mapping,
|
||||
0, -1);
|
||||
blkdev_put(dev->blkdev, FMODE_READ|FMODE_WRITE|FMODE_EXCL);
|
||||
}
|
||||
|
||||
kfree(dev);
|
||||
}
|
||||
|
||||
|
||||
static struct block2mtd_dev *add_device(char *devname, int erase_size)
|
||||
{
|
||||
const fmode_t mode = FMODE_READ | FMODE_WRITE | FMODE_EXCL;
|
||||
struct block_device *bdev;
|
||||
struct block2mtd_dev *dev;
|
||||
char *name;
|
||||
|
||||
if (!devname)
|
||||
return NULL;
|
||||
|
||||
dev = kzalloc(sizeof(struct block2mtd_dev), GFP_KERNEL);
|
||||
if (!dev)
|
||||
return NULL;
|
||||
|
||||
/* Get a handle on the device */
|
||||
bdev = blkdev_get_by_path(devname, mode, dev);
|
||||
#ifndef MODULE
|
||||
if (IS_ERR(bdev)) {
|
||||
|
||||
/* We might not have rootfs mounted at this point. Try
|
||||
to resolve the device name by other means. */
|
||||
|
||||
dev_t devt = name_to_dev_t(devname);
|
||||
if (devt)
|
||||
bdev = blkdev_get_by_dev(devt, mode, dev);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (IS_ERR(bdev)) {
|
||||
pr_err("error: cannot open device %s\n", devname);
|
||||
goto err_free_block2mtd;
|
||||
}
|
||||
dev->blkdev = bdev;
|
||||
|
||||
if (MAJOR(bdev->bd_dev) == MTD_BLOCK_MAJOR) {
|
||||
pr_err("attempting to use an MTD device as a block device\n");
|
||||
goto err_free_block2mtd;
|
||||
}
|
||||
|
||||
if ((long)dev->blkdev->bd_inode->i_size % erase_size) {
|
||||
pr_err("erasesize must be a divisor of device size\n");
|
||||
goto err_free_block2mtd;
|
||||
}
|
||||
|
||||
mutex_init(&dev->write_mutex);
|
||||
|
||||
/* Setup the MTD structure */
|
||||
/* make the name contain the block device in */
|
||||
name = kasprintf(GFP_KERNEL, "block2mtd: %s", devname);
|
||||
if (!name)
|
||||
goto err_destroy_mutex;
|
||||
|
||||
dev->mtd.name = name;
|
||||
|
||||
dev->mtd.size = dev->blkdev->bd_inode->i_size & PAGE_MASK;
|
||||
dev->mtd.erasesize = erase_size;
|
||||
dev->mtd.writesize = 1;
|
||||
dev->mtd.writebufsize = PAGE_SIZE;
|
||||
dev->mtd.type = MTD_RAM;
|
||||
dev->mtd.flags = MTD_CAP_RAM;
|
||||
dev->mtd._erase = block2mtd_erase;
|
||||
dev->mtd._write = block2mtd_write;
|
||||
dev->mtd._sync = block2mtd_sync;
|
||||
dev->mtd._read = block2mtd_read;
|
||||
dev->mtd.priv = dev;
|
||||
dev->mtd.owner = THIS_MODULE;
|
||||
|
||||
if (mtd_device_register(&dev->mtd, NULL, 0)) {
|
||||
/* Device didn't get added, so free the entry */
|
||||
goto err_destroy_mutex;
|
||||
}
|
||||
list_add(&dev->list, &blkmtd_device_list);
|
||||
pr_info("mtd%d: [%s] erase_size = %dKiB [%d]\n",
|
||||
dev->mtd.index,
|
||||
dev->mtd.name + strlen("block2mtd: "),
|
||||
dev->mtd.erasesize >> 10, dev->mtd.erasesize);
|
||||
return dev;
|
||||
|
||||
err_destroy_mutex:
|
||||
mutex_destroy(&dev->write_mutex);
|
||||
err_free_block2mtd:
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
#ifndef MODULE
|
||||
static int block2mtd_init_called = 0;
|
||||
static char block2mtd_paramline[80 + 12]; /* 80 for device, 12 for erase size */
|
||||
#endif
|
||||
|
||||
static int block2mtd_setup2(const char *val)
|
||||
{
|
||||
char buf[80 + 12]; /* 80 for device, 12 for erase size */
|
||||
char *str = buf;
|
||||
char *token[2];
|
||||
char *name;
|
||||
size_t erase_size = PAGE_SIZE;
|
||||
int i, ret;
|
||||
|
||||
if (strnlen(val, sizeof(buf)) >= sizeof(buf)) {
|
||||
pr_err("parameter too long\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
strcpy(str, val);
|
||||
kill_final_newline(str);
|
||||
|
||||
for (i = 0; i < 2; i++)
|
||||
token[i] = strsep(&str, ",");
|
||||
|
||||
if (str) {
|
||||
pr_err("too many arguments\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!token[0]) {
|
||||
pr_err("no argument\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
name = token[0];
|
||||
if (strlen(name) + 1 > 80) {
|
||||
pr_err("device name too long\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (token[1]) {
|
||||
ret = parse_num(&erase_size, token[1]);
|
||||
if (ret) {
|
||||
pr_err("illegal erase size\n");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
add_device(name, erase_size);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int block2mtd_setup(const char *val, struct kernel_param *kp)
|
||||
{
|
||||
#ifdef MODULE
|
||||
return block2mtd_setup2(val);
|
||||
#else
|
||||
/* If more parameters are later passed in via
|
||||
/sys/module/block2mtd/parameters/block2mtd
|
||||
and block2mtd_init() has already been called,
|
||||
we can parse the argument now. */
|
||||
|
||||
if (block2mtd_init_called)
|
||||
return block2mtd_setup2(val);
|
||||
|
||||
/* During early boot stage, we only save the parameters
|
||||
here. We must parse them later: if the param passed
|
||||
from kernel boot command line, block2mtd_setup() is
|
||||
called so early that it is not possible to resolve
|
||||
the device (even kmalloc() fails). Deter that work to
|
||||
block2mtd_setup2(). */
|
||||
|
||||
strlcpy(block2mtd_paramline, val, sizeof(block2mtd_paramline));
|
||||
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
module_param_call(block2mtd, block2mtd_setup, NULL, NULL, 0200);
|
||||
MODULE_PARM_DESC(block2mtd, "Device to use. \"block2mtd=<dev>[,<erasesize>]\"");
|
||||
|
||||
static int __init block2mtd_init(void)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
#ifndef MODULE
|
||||
if (strlen(block2mtd_paramline))
|
||||
ret = block2mtd_setup2(block2mtd_paramline);
|
||||
block2mtd_init_called = 1;
|
||||
#endif
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
static void block2mtd_exit(void)
|
||||
{
|
||||
struct list_head *pos, *next;
|
||||
|
||||
/* Remove the MTD devices */
|
||||
list_for_each_safe(pos, next, &blkmtd_device_list) {
|
||||
struct block2mtd_dev *dev = list_entry(pos, typeof(*dev), list);
|
||||
block2mtd_sync(&dev->mtd);
|
||||
mtd_device_unregister(&dev->mtd);
|
||||
mutex_destroy(&dev->write_mutex);
|
||||
pr_info("mtd%d: [%s] removed\n",
|
||||
dev->mtd.index,
|
||||
dev->mtd.name + strlen("block2mtd: "));
|
||||
list_del(&dev->list);
|
||||
block2mtd_free_device(dev);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
module_init(block2mtd_init);
|
||||
module_exit(block2mtd_exit);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Joern Engel <joern@lazybastard.org>");
|
||||
MODULE_DESCRIPTION("Emulate an MTD using a block device");
|
2143
drivers/mtd/devices/docg3.c
Normal file
2143
drivers/mtd/devices/docg3.c
Normal file
File diff suppressed because it is too large
Load diff
370
drivers/mtd/devices/docg3.h
Normal file
370
drivers/mtd/devices/docg3.h
Normal file
|
@ -0,0 +1,370 @@
|
|||
/*
|
||||
* Handles the M-Systems DiskOnChip G3 chip
|
||||
*
|
||||
* Copyright (C) 2011 Robert Jarzmik
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _MTD_DOCG3_H
|
||||
#define _MTD_DOCG3_H
|
||||
|
||||
#include <linux/mtd/mtd.h>
|
||||
|
||||
/*
|
||||
* Flash memory areas :
|
||||
* - 0x0000 .. 0x07ff : IPL
|
||||
* - 0x0800 .. 0x0fff : Data area
|
||||
* - 0x1000 .. 0x17ff : Registers
|
||||
* - 0x1800 .. 0x1fff : Unknown
|
||||
*/
|
||||
#define DOC_IOSPACE_IPL 0x0000
|
||||
#define DOC_IOSPACE_DATA 0x0800
|
||||
#define DOC_IOSPACE_SIZE 0x2000
|
||||
|
||||
/*
|
||||
* DOC G3 layout and adressing scheme
|
||||
* A page address for the block "b", plane "P" and page "p":
|
||||
* address = [bbbb bPpp pppp]
|
||||
*/
|
||||
|
||||
#define DOC_ADDR_PAGE_MASK 0x3f
|
||||
#define DOC_ADDR_BLOCK_SHIFT 6
|
||||
#define DOC_LAYOUT_NBPLANES 2
|
||||
#define DOC_LAYOUT_PAGES_PER_BLOCK 64
|
||||
#define DOC_LAYOUT_PAGE_SIZE 512
|
||||
#define DOC_LAYOUT_OOB_SIZE 16
|
||||
#define DOC_LAYOUT_WEAR_SIZE 8
|
||||
#define DOC_LAYOUT_PAGE_OOB_SIZE \
|
||||
(DOC_LAYOUT_PAGE_SIZE + DOC_LAYOUT_OOB_SIZE)
|
||||
#define DOC_LAYOUT_WEAR_OFFSET (DOC_LAYOUT_PAGE_OOB_SIZE * 2)
|
||||
#define DOC_LAYOUT_BLOCK_SIZE \
|
||||
(DOC_LAYOUT_PAGES_PER_BLOCK * DOC_LAYOUT_PAGE_SIZE)
|
||||
|
||||
/*
|
||||
* ECC related constants
|
||||
*/
|
||||
#define DOC_ECC_BCH_M 14
|
||||
#define DOC_ECC_BCH_T 4
|
||||
#define DOC_ECC_BCH_PRIMPOLY 0x4443
|
||||
#define DOC_ECC_BCH_SIZE 7
|
||||
#define DOC_ECC_BCH_COVERED_BYTES \
|
||||
(DOC_LAYOUT_PAGE_SIZE + DOC_LAYOUT_OOB_PAGEINFO_SZ + \
|
||||
DOC_LAYOUT_OOB_HAMMING_SZ)
|
||||
#define DOC_ECC_BCH_TOTAL_BYTES \
|
||||
(DOC_ECC_BCH_COVERED_BYTES + DOC_LAYOUT_OOB_BCH_SZ)
|
||||
|
||||
/*
|
||||
* Blocks distribution
|
||||
*/
|
||||
#define DOC_LAYOUT_BLOCK_BBT 0
|
||||
#define DOC_LAYOUT_BLOCK_OTP 0
|
||||
#define DOC_LAYOUT_BLOCK_FIRST_DATA 6
|
||||
|
||||
#define DOC_LAYOUT_PAGE_BBT 4
|
||||
|
||||
/*
|
||||
* Extra page OOB (16 bytes wide) layout
|
||||
*/
|
||||
#define DOC_LAYOUT_OOB_PAGEINFO_OFS 0
|
||||
#define DOC_LAYOUT_OOB_HAMMING_OFS 7
|
||||
#define DOC_LAYOUT_OOB_BCH_OFS 8
|
||||
#define DOC_LAYOUT_OOB_UNUSED_OFS 15
|
||||
#define DOC_LAYOUT_OOB_PAGEINFO_SZ 7
|
||||
#define DOC_LAYOUT_OOB_HAMMING_SZ 1
|
||||
#define DOC_LAYOUT_OOB_BCH_SZ 7
|
||||
#define DOC_LAYOUT_OOB_UNUSED_SZ 1
|
||||
|
||||
|
||||
#define DOC_CHIPID_G3 0x200
|
||||
#define DOC_ERASE_MARK 0xaa
|
||||
#define DOC_MAX_NBFLOORS 4
|
||||
/*
|
||||
* Flash registers
|
||||
*/
|
||||
#define DOC_CHIPID 0x1000
|
||||
#define DOC_TEST 0x1004
|
||||
#define DOC_BUSLOCK 0x1006
|
||||
#define DOC_ENDIANCONTROL 0x1008
|
||||
#define DOC_DEVICESELECT 0x100a
|
||||
#define DOC_ASICMODE 0x100c
|
||||
#define DOC_CONFIGURATION 0x100e
|
||||
#define DOC_INTERRUPTCONTROL 0x1010
|
||||
#define DOC_READADDRESS 0x101a
|
||||
#define DOC_DATAEND 0x101e
|
||||
#define DOC_INTERRUPTSTATUS 0x1020
|
||||
|
||||
#define DOC_FLASHSEQUENCE 0x1032
|
||||
#define DOC_FLASHCOMMAND 0x1034
|
||||
#define DOC_FLASHADDRESS 0x1036
|
||||
#define DOC_FLASHCONTROL 0x1038
|
||||
#define DOC_NOP 0x103e
|
||||
|
||||
#define DOC_ECCCONF0 0x1040
|
||||
#define DOC_ECCCONF1 0x1042
|
||||
#define DOC_ECCPRESET 0x1044
|
||||
#define DOC_HAMMINGPARITY 0x1046
|
||||
#define DOC_BCH_HW_ECC(idx) (0x1048 + idx)
|
||||
|
||||
#define DOC_PROTECTION 0x1056
|
||||
#define DOC_DPS0_KEY 0x105c
|
||||
#define DOC_DPS1_KEY 0x105e
|
||||
#define DOC_DPS0_ADDRLOW 0x1060
|
||||
#define DOC_DPS0_ADDRHIGH 0x1062
|
||||
#define DOC_DPS1_ADDRLOW 0x1064
|
||||
#define DOC_DPS1_ADDRHIGH 0x1066
|
||||
#define DOC_DPS0_STATUS 0x106c
|
||||
#define DOC_DPS1_STATUS 0x106e
|
||||
|
||||
#define DOC_ASICMODECONFIRM 0x1072
|
||||
#define DOC_CHIPID_INV 0x1074
|
||||
#define DOC_POWERMODE 0x107c
|
||||
|
||||
/*
|
||||
* Flash sequences
|
||||
* A sequence is preset before one or more commands are input to the chip.
|
||||
*/
|
||||
#define DOC_SEQ_RESET 0x00
|
||||
#define DOC_SEQ_PAGE_SIZE_532 0x03
|
||||
#define DOC_SEQ_SET_FASTMODE 0x05
|
||||
#define DOC_SEQ_SET_RELIABLEMODE 0x09
|
||||
#define DOC_SEQ_READ 0x12
|
||||
#define DOC_SEQ_SET_PLANE1 0x0e
|
||||
#define DOC_SEQ_SET_PLANE2 0x10
|
||||
#define DOC_SEQ_PAGE_SETUP 0x1d
|
||||
#define DOC_SEQ_ERASE 0x27
|
||||
#define DOC_SEQ_PLANES_STATUS 0x31
|
||||
|
||||
/*
|
||||
* Flash commands
|
||||
*/
|
||||
#define DOC_CMD_READ_PLANE1 0x00
|
||||
#define DOC_CMD_SET_ADDR_READ 0x05
|
||||
#define DOC_CMD_READ_ALL_PLANES 0x30
|
||||
#define DOC_CMD_READ_PLANE2 0x50
|
||||
#define DOC_CMD_READ_FLASH 0xe0
|
||||
#define DOC_CMD_PAGE_SIZE_532 0x3c
|
||||
|
||||
#define DOC_CMD_PROG_BLOCK_ADDR 0x60
|
||||
#define DOC_CMD_PROG_CYCLE1 0x80
|
||||
#define DOC_CMD_PROG_CYCLE2 0x10
|
||||
#define DOC_CMD_PROG_CYCLE3 0x11
|
||||
#define DOC_CMD_ERASECYCLE2 0xd0
|
||||
#define DOC_CMD_READ_STATUS 0x70
|
||||
#define DOC_CMD_PLANES_STATUS 0x71
|
||||
|
||||
#define DOC_CMD_RELIABLE_MODE 0x22
|
||||
#define DOC_CMD_FAST_MODE 0xa2
|
||||
|
||||
#define DOC_CMD_RESET 0xff
|
||||
|
||||
/*
|
||||
* Flash register : DOC_FLASHCONTROL
|
||||
*/
|
||||
#define DOC_CTRL_VIOLATION 0x20
|
||||
#define DOC_CTRL_CE 0x10
|
||||
#define DOC_CTRL_UNKNOWN_BITS 0x08
|
||||
#define DOC_CTRL_PROTECTION_ERROR 0x04
|
||||
#define DOC_CTRL_SEQUENCE_ERROR 0x02
|
||||
#define DOC_CTRL_FLASHREADY 0x01
|
||||
|
||||
/*
|
||||
* Flash register : DOC_ASICMODE
|
||||
*/
|
||||
#define DOC_ASICMODE_RESET 0x00
|
||||
#define DOC_ASICMODE_NORMAL 0x01
|
||||
#define DOC_ASICMODE_POWERDOWN 0x02
|
||||
#define DOC_ASICMODE_MDWREN 0x04
|
||||
#define DOC_ASICMODE_BDETCT_RESET 0x08
|
||||
#define DOC_ASICMODE_RSTIN_RESET 0x10
|
||||
#define DOC_ASICMODE_RAM_WE 0x20
|
||||
|
||||
/*
|
||||
* Flash register : DOC_ECCCONF0
|
||||
*/
|
||||
#define DOC_ECCCONF0_WRITE_MODE 0x0000
|
||||
#define DOC_ECCCONF0_READ_MODE 0x8000
|
||||
#define DOC_ECCCONF0_AUTO_ECC_ENABLE 0x4000
|
||||
#define DOC_ECCCONF0_HAMMING_ENABLE 0x1000
|
||||
#define DOC_ECCCONF0_BCH_ENABLE 0x0800
|
||||
#define DOC_ECCCONF0_DATA_BYTES_MASK 0x07ff
|
||||
|
||||
/*
|
||||
* Flash register : DOC_ECCCONF1
|
||||
*/
|
||||
#define DOC_ECCCONF1_BCH_SYNDROM_ERR 0x80
|
||||
#define DOC_ECCCONF1_UNKOWN1 0x40
|
||||
#define DOC_ECCCONF1_PAGE_IS_WRITTEN 0x20
|
||||
#define DOC_ECCCONF1_UNKOWN3 0x10
|
||||
#define DOC_ECCCONF1_HAMMING_BITS_MASK 0x0f
|
||||
|
||||
/*
|
||||
* Flash register : DOC_PROTECTION
|
||||
*/
|
||||
#define DOC_PROTECT_FOUNDRY_OTP_LOCK 0x01
|
||||
#define DOC_PROTECT_CUSTOMER_OTP_LOCK 0x02
|
||||
#define DOC_PROTECT_LOCK_INPUT 0x04
|
||||
#define DOC_PROTECT_STICKY_LOCK 0x08
|
||||
#define DOC_PROTECT_PROTECTION_ENABLED 0x10
|
||||
#define DOC_PROTECT_IPL_DOWNLOAD_LOCK 0x20
|
||||
#define DOC_PROTECT_PROTECTION_ERROR 0x80
|
||||
|
||||
/*
|
||||
* Flash register : DOC_DPS0_STATUS and DOC_DPS1_STATUS
|
||||
*/
|
||||
#define DOC_DPS_OTP_PROTECTED 0x01
|
||||
#define DOC_DPS_READ_PROTECTED 0x02
|
||||
#define DOC_DPS_WRITE_PROTECTED 0x04
|
||||
#define DOC_DPS_HW_LOCK_ENABLED 0x08
|
||||
#define DOC_DPS_KEY_OK 0x80
|
||||
|
||||
/*
|
||||
* Flash register : DOC_CONFIGURATION
|
||||
*/
|
||||
#define DOC_CONF_IF_CFG 0x80
|
||||
#define DOC_CONF_MAX_ID_MASK 0x30
|
||||
#define DOC_CONF_VCCQ_3V 0x01
|
||||
|
||||
/*
|
||||
* Flash register : DOC_READADDRESS
|
||||
*/
|
||||
#define DOC_READADDR_INC 0x8000
|
||||
#define DOC_READADDR_ONE_BYTE 0x4000
|
||||
#define DOC_READADDR_ADDR_MASK 0x1fff
|
||||
|
||||
/*
|
||||
* Flash register : DOC_POWERMODE
|
||||
*/
|
||||
#define DOC_POWERDOWN_READY 0x80
|
||||
|
||||
/*
|
||||
* Status of erase and write operation
|
||||
*/
|
||||
#define DOC_PLANES_STATUS_FAIL 0x01
|
||||
#define DOC_PLANES_STATUS_PLANE0_KO 0x02
|
||||
#define DOC_PLANES_STATUS_PLANE1_KO 0x04
|
||||
|
||||
/*
|
||||
* DPS key management
|
||||
*
|
||||
* Each floor of docg3 has 2 protection areas: DPS0 and DPS1. These areas span
|
||||
* across block boundaries, and define whether these blocks can be read or
|
||||
* written.
|
||||
* The definition is dynamically stored in page 0 of blocks (2,3) for DPS0, and
|
||||
* page 0 of blocks (4,5) for DPS1.
|
||||
*/
|
||||
#define DOC_LAYOUT_DPS_KEY_LENGTH 8
|
||||
|
||||
/**
|
||||
* struct docg3_cascade - Cascade of 1 to 4 docg3 chips
|
||||
* @floors: floors (ie. one physical docg3 chip is one floor)
|
||||
* @base: IO space to access all chips in the cascade
|
||||
* @bch: the BCH correcting control structure
|
||||
* @lock: lock to protect docg3 IO space from concurrent accesses
|
||||
*/
|
||||
struct docg3_cascade {
|
||||
struct mtd_info *floors[DOC_MAX_NBFLOORS];
|
||||
void __iomem *base;
|
||||
struct bch_control *bch;
|
||||
struct mutex lock;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct docg3 - DiskOnChip driver private data
|
||||
* @dev: the device currently under control
|
||||
* @cascade: the cascade this device belongs to
|
||||
* @device_id: number of the cascaded DoCG3 device (0, 1, 2 or 3)
|
||||
* @if_cfg: if true, reads are on 16bits, else reads are on 8bits
|
||||
|
||||
* @reliable: if 0, docg3 in normal mode, if 1 docg3 in fast mode, if 2 in
|
||||
* reliable mode
|
||||
* Fast mode implies more errors than normal mode.
|
||||
* Reliable mode implies that page 2*n and 2*n+1 are clones.
|
||||
* @bbt: bad block table cache
|
||||
* @oob_write_ofs: offset of the MTD where this OOB should belong (ie. in next
|
||||
* page_write)
|
||||
* @oob_autoecc: if 1, use only bytes 0-7, 15, and fill the others with HW ECC
|
||||
* if 0, use all the 16 bytes.
|
||||
* @oob_write_buf: prepared OOB for next page_write
|
||||
* @debugfs_root: debugfs root node
|
||||
*/
|
||||
struct docg3 {
|
||||
struct device *dev;
|
||||
struct docg3_cascade *cascade;
|
||||
unsigned int device_id:4;
|
||||
unsigned int if_cfg:1;
|
||||
unsigned int reliable:2;
|
||||
int max_block;
|
||||
u8 *bbt;
|
||||
loff_t oob_write_ofs;
|
||||
int oob_autoecc;
|
||||
u8 oob_write_buf[DOC_LAYOUT_OOB_SIZE];
|
||||
struct dentry *debugfs_root;
|
||||
};
|
||||
|
||||
#define doc_err(fmt, arg...) dev_err(docg3->dev, (fmt), ## arg)
|
||||
#define doc_info(fmt, arg...) dev_info(docg3->dev, (fmt), ## arg)
|
||||
#define doc_dbg(fmt, arg...) dev_dbg(docg3->dev, (fmt), ## arg)
|
||||
#define doc_vdbg(fmt, arg...) dev_vdbg(docg3->dev, (fmt), ## arg)
|
||||
|
||||
#define DEBUGFS_RO_ATTR(name, show_fct) \
|
||||
static int name##_open(struct inode *inode, struct file *file) \
|
||||
{ return single_open(file, show_fct, inode->i_private); } \
|
||||
static const struct file_operations name##_fops = { \
|
||||
.owner = THIS_MODULE, \
|
||||
.open = name##_open, \
|
||||
.llseek = seq_lseek, \
|
||||
.read = seq_read, \
|
||||
.release = single_release \
|
||||
};
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Trace events part
|
||||
*/
|
||||
#undef TRACE_SYSTEM
|
||||
#define TRACE_SYSTEM docg3
|
||||
|
||||
#if !defined(_MTD_DOCG3_TRACE) || defined(TRACE_HEADER_MULTI_READ)
|
||||
#define _MTD_DOCG3_TRACE
|
||||
|
||||
#include <linux/tracepoint.h>
|
||||
|
||||
TRACE_EVENT(docg3_io,
|
||||
TP_PROTO(int op, int width, u16 reg, int val),
|
||||
TP_ARGS(op, width, reg, val),
|
||||
TP_STRUCT__entry(
|
||||
__field(int, op)
|
||||
__field(unsigned char, width)
|
||||
__field(u16, reg)
|
||||
__field(int, val)),
|
||||
TP_fast_assign(
|
||||
__entry->op = op;
|
||||
__entry->width = width;
|
||||
__entry->reg = reg;
|
||||
__entry->val = val;),
|
||||
TP_printk("docg3: %s%02d reg=%04x, val=%04x",
|
||||
__entry->op ? "write" : "read", __entry->width,
|
||||
__entry->reg, __entry->val)
|
||||
);
|
||||
#endif
|
||||
|
||||
/* This part must be outside protection */
|
||||
#undef TRACE_INCLUDE_PATH
|
||||
#undef TRACE_INCLUDE_FILE
|
||||
#define TRACE_INCLUDE_PATH .
|
||||
#define TRACE_INCLUDE_FILE docg3
|
||||
#include <trace/define_trace.h>
|
685
drivers/mtd/devices/lart.c
Normal file
685
drivers/mtd/devices/lart.c
Normal file
|
@ -0,0 +1,685 @@
|
|||
|
||||
/*
|
||||
* MTD driver for the 28F160F3 Flash Memory (non-CFI) on LART.
|
||||
*
|
||||
* Author: Abraham vd Merwe <abraham@2d3d.co.za>
|
||||
*
|
||||
* Copyright (c) 2001, 2d3D, Inc.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* References:
|
||||
*
|
||||
* [1] 3 Volt Fast Boot Block Flash Memory" Intel Datasheet
|
||||
* - Order Number: 290644-005
|
||||
* - January 2000
|
||||
*
|
||||
* [2] MTD internal API documentation
|
||||
* - http://www.linux-mtd.infradead.org/
|
||||
*
|
||||
* Limitations:
|
||||
*
|
||||
* Even though this driver is written for 3 Volt Fast Boot
|
||||
* Block Flash Memory, it is rather specific to LART. With
|
||||
* Minor modifications, notably the without data/address line
|
||||
* mangling and different bus settings, etc. it should be
|
||||
* trivial to adapt to other platforms.
|
||||
*
|
||||
* If somebody would sponsor me a different board, I'll
|
||||
* adapt the driver (:
|
||||
*/
|
||||
|
||||
/* debugging */
|
||||
//#define LART_DEBUG
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include <linux/mtd/partitions.h>
|
||||
|
||||
#ifndef CONFIG_SA1100_LART
|
||||
#error This is for LART architecture only
|
||||
#endif
|
||||
|
||||
static char module_name[] = "lart";
|
||||
|
||||
/*
|
||||
* These values is specific to 28Fxxxx3 flash memory.
|
||||
* See section 2.3.1 in "3 Volt Fast Boot Block Flash Memory" Intel Datasheet
|
||||
*/
|
||||
#define FLASH_BLOCKSIZE_PARAM (4096 * BUSWIDTH)
|
||||
#define FLASH_NUMBLOCKS_16m_PARAM 8
|
||||
#define FLASH_NUMBLOCKS_8m_PARAM 8
|
||||
|
||||
/*
|
||||
* These values is specific to 28Fxxxx3 flash memory.
|
||||
* See section 2.3.2 in "3 Volt Fast Boot Block Flash Memory" Intel Datasheet
|
||||
*/
|
||||
#define FLASH_BLOCKSIZE_MAIN (32768 * BUSWIDTH)
|
||||
#define FLASH_NUMBLOCKS_16m_MAIN 31
|
||||
#define FLASH_NUMBLOCKS_8m_MAIN 15
|
||||
|
||||
/*
|
||||
* These values are specific to LART
|
||||
*/
|
||||
|
||||
/* general */
|
||||
#define BUSWIDTH 4 /* don't change this - a lot of the code _will_ break if you change this */
|
||||
#define FLASH_OFFSET 0xe8000000 /* see linux/arch/arm/mach-sa1100/lart.c */
|
||||
|
||||
/* blob */
|
||||
#define NUM_BLOB_BLOCKS FLASH_NUMBLOCKS_16m_PARAM
|
||||
#define BLOB_START 0x00000000
|
||||
#define BLOB_LEN (NUM_BLOB_BLOCKS * FLASH_BLOCKSIZE_PARAM)
|
||||
|
||||
/* kernel */
|
||||
#define NUM_KERNEL_BLOCKS 7
|
||||
#define KERNEL_START (BLOB_START + BLOB_LEN)
|
||||
#define KERNEL_LEN (NUM_KERNEL_BLOCKS * FLASH_BLOCKSIZE_MAIN)
|
||||
|
||||
/* initial ramdisk */
|
||||
#define NUM_INITRD_BLOCKS 24
|
||||
#define INITRD_START (KERNEL_START + KERNEL_LEN)
|
||||
#define INITRD_LEN (NUM_INITRD_BLOCKS * FLASH_BLOCKSIZE_MAIN)
|
||||
|
||||
/*
|
||||
* See section 4.0 in "3 Volt Fast Boot Block Flash Memory" Intel Datasheet
|
||||
*/
|
||||
#define READ_ARRAY 0x00FF00FF /* Read Array/Reset */
|
||||
#define READ_ID_CODES 0x00900090 /* Read Identifier Codes */
|
||||
#define ERASE_SETUP 0x00200020 /* Block Erase */
|
||||
#define ERASE_CONFIRM 0x00D000D0 /* Block Erase and Program Resume */
|
||||
#define PGM_SETUP 0x00400040 /* Program */
|
||||
#define STATUS_READ 0x00700070 /* Read Status Register */
|
||||
#define STATUS_CLEAR 0x00500050 /* Clear Status Register */
|
||||
#define STATUS_BUSY 0x00800080 /* Write State Machine Status (WSMS) */
|
||||
#define STATUS_ERASE_ERR 0x00200020 /* Erase Status (ES) */
|
||||
#define STATUS_PGM_ERR 0x00100010 /* Program Status (PS) */
|
||||
|
||||
/*
|
||||
* See section 4.2 in "3 Volt Fast Boot Block Flash Memory" Intel Datasheet
|
||||
*/
|
||||
#define FLASH_MANUFACTURER 0x00890089
|
||||
#define FLASH_DEVICE_8mbit_TOP 0x88f188f1
|
||||
#define FLASH_DEVICE_8mbit_BOTTOM 0x88f288f2
|
||||
#define FLASH_DEVICE_16mbit_TOP 0x88f388f3
|
||||
#define FLASH_DEVICE_16mbit_BOTTOM 0x88f488f4
|
||||
|
||||
/***************************************************************************************************/
|
||||
|
||||
/*
|
||||
* The data line mapping on LART is as follows:
|
||||
*
|
||||
* U2 CPU | U3 CPU
|
||||
* -------------------
|
||||
* 0 20 | 0 12
|
||||
* 1 22 | 1 14
|
||||
* 2 19 | 2 11
|
||||
* 3 17 | 3 9
|
||||
* 4 24 | 4 0
|
||||
* 5 26 | 5 2
|
||||
* 6 31 | 6 7
|
||||
* 7 29 | 7 5
|
||||
* 8 21 | 8 13
|
||||
* 9 23 | 9 15
|
||||
* 10 18 | 10 10
|
||||
* 11 16 | 11 8
|
||||
* 12 25 | 12 1
|
||||
* 13 27 | 13 3
|
||||
* 14 30 | 14 6
|
||||
* 15 28 | 15 4
|
||||
*/
|
||||
|
||||
/* Mangle data (x) */
|
||||
#define DATA_TO_FLASH(x) \
|
||||
( \
|
||||
(((x) & 0x08009000) >> 11) + \
|
||||
(((x) & 0x00002000) >> 10) + \
|
||||
(((x) & 0x04004000) >> 8) + \
|
||||
(((x) & 0x00000010) >> 4) + \
|
||||
(((x) & 0x91000820) >> 3) + \
|
||||
(((x) & 0x22080080) >> 2) + \
|
||||
((x) & 0x40000400) + \
|
||||
(((x) & 0x00040040) << 1) + \
|
||||
(((x) & 0x00110000) << 4) + \
|
||||
(((x) & 0x00220100) << 5) + \
|
||||
(((x) & 0x00800208) << 6) + \
|
||||
(((x) & 0x00400004) << 9) + \
|
||||
(((x) & 0x00000001) << 12) + \
|
||||
(((x) & 0x00000002) << 13) \
|
||||
)
|
||||
|
||||
/* Unmangle data (x) */
|
||||
#define FLASH_TO_DATA(x) \
|
||||
( \
|
||||
(((x) & 0x00010012) << 11) + \
|
||||
(((x) & 0x00000008) << 10) + \
|
||||
(((x) & 0x00040040) << 8) + \
|
||||
(((x) & 0x00000001) << 4) + \
|
||||
(((x) & 0x12200104) << 3) + \
|
||||
(((x) & 0x08820020) << 2) + \
|
||||
((x) & 0x40000400) + \
|
||||
(((x) & 0x00080080) >> 1) + \
|
||||
(((x) & 0x01100000) >> 4) + \
|
||||
(((x) & 0x04402000) >> 5) + \
|
||||
(((x) & 0x20008200) >> 6) + \
|
||||
(((x) & 0x80000800) >> 9) + \
|
||||
(((x) & 0x00001000) >> 12) + \
|
||||
(((x) & 0x00004000) >> 13) \
|
||||
)
|
||||
|
||||
/*
|
||||
* The address line mapping on LART is as follows:
|
||||
*
|
||||
* U3 CPU | U2 CPU
|
||||
* -------------------
|
||||
* 0 2 | 0 2
|
||||
* 1 3 | 1 3
|
||||
* 2 9 | 2 9
|
||||
* 3 13 | 3 8
|
||||
* 4 8 | 4 7
|
||||
* 5 12 | 5 6
|
||||
* 6 11 | 6 5
|
||||
* 7 10 | 7 4
|
||||
* 8 4 | 8 10
|
||||
* 9 5 | 9 11
|
||||
* 10 6 | 10 12
|
||||
* 11 7 | 11 13
|
||||
*
|
||||
* BOOT BLOCK BOUNDARY
|
||||
*
|
||||
* 12 15 | 12 15
|
||||
* 13 14 | 13 14
|
||||
* 14 16 | 14 16
|
||||
*
|
||||
* MAIN BLOCK BOUNDARY
|
||||
*
|
||||
* 15 17 | 15 18
|
||||
* 16 18 | 16 17
|
||||
* 17 20 | 17 20
|
||||
* 18 19 | 18 19
|
||||
* 19 21 | 19 21
|
||||
*
|
||||
* As we can see from above, the addresses aren't mangled across
|
||||
* block boundaries, so we don't need to worry about address
|
||||
* translations except for sending/reading commands during
|
||||
* initialization
|
||||
*/
|
||||
|
||||
/* Mangle address (x) on chip U2 */
|
||||
#define ADDR_TO_FLASH_U2(x) \
|
||||
( \
|
||||
(((x) & 0x00000f00) >> 4) + \
|
||||
(((x) & 0x00042000) << 1) + \
|
||||
(((x) & 0x0009c003) << 2) + \
|
||||
(((x) & 0x00021080) << 3) + \
|
||||
(((x) & 0x00000010) << 4) + \
|
||||
(((x) & 0x00000040) << 5) + \
|
||||
(((x) & 0x00000024) << 7) + \
|
||||
(((x) & 0x00000008) << 10) \
|
||||
)
|
||||
|
||||
/* Unmangle address (x) on chip U2 */
|
||||
#define FLASH_U2_TO_ADDR(x) \
|
||||
( \
|
||||
(((x) << 4) & 0x00000f00) + \
|
||||
(((x) >> 1) & 0x00042000) + \
|
||||
(((x) >> 2) & 0x0009c003) + \
|
||||
(((x) >> 3) & 0x00021080) + \
|
||||
(((x) >> 4) & 0x00000010) + \
|
||||
(((x) >> 5) & 0x00000040) + \
|
||||
(((x) >> 7) & 0x00000024) + \
|
||||
(((x) >> 10) & 0x00000008) \
|
||||
)
|
||||
|
||||
/* Mangle address (x) on chip U3 */
|
||||
#define ADDR_TO_FLASH_U3(x) \
|
||||
( \
|
||||
(((x) & 0x00000080) >> 3) + \
|
||||
(((x) & 0x00000040) >> 1) + \
|
||||
(((x) & 0x00052020) << 1) + \
|
||||
(((x) & 0x00084f03) << 2) + \
|
||||
(((x) & 0x00029010) << 3) + \
|
||||
(((x) & 0x00000008) << 5) + \
|
||||
(((x) & 0x00000004) << 7) \
|
||||
)
|
||||
|
||||
/* Unmangle address (x) on chip U3 */
|
||||
#define FLASH_U3_TO_ADDR(x) \
|
||||
( \
|
||||
(((x) << 3) & 0x00000080) + \
|
||||
(((x) << 1) & 0x00000040) + \
|
||||
(((x) >> 1) & 0x00052020) + \
|
||||
(((x) >> 2) & 0x00084f03) + \
|
||||
(((x) >> 3) & 0x00029010) + \
|
||||
(((x) >> 5) & 0x00000008) + \
|
||||
(((x) >> 7) & 0x00000004) \
|
||||
)
|
||||
|
||||
/***************************************************************************************************/
|
||||
|
||||
static __u8 read8 (__u32 offset)
|
||||
{
|
||||
volatile __u8 *data = (__u8 *) (FLASH_OFFSET + offset);
|
||||
#ifdef LART_DEBUG
|
||||
printk (KERN_DEBUG "%s(): 0x%.8x -> 0x%.2x\n", __func__, offset, *data);
|
||||
#endif
|
||||
return (*data);
|
||||
}
|
||||
|
||||
static __u32 read32 (__u32 offset)
|
||||
{
|
||||
volatile __u32 *data = (__u32 *) (FLASH_OFFSET + offset);
|
||||
#ifdef LART_DEBUG
|
||||
printk (KERN_DEBUG "%s(): 0x%.8x -> 0x%.8x\n", __func__, offset, *data);
|
||||
#endif
|
||||
return (*data);
|
||||
}
|
||||
|
||||
static void write32 (__u32 x,__u32 offset)
|
||||
{
|
||||
volatile __u32 *data = (__u32 *) (FLASH_OFFSET + offset);
|
||||
*data = x;
|
||||
#ifdef LART_DEBUG
|
||||
printk (KERN_DEBUG "%s(): 0x%.8x <- 0x%.8x\n", __func__, offset, *data);
|
||||
#endif
|
||||
}
|
||||
|
||||
/***************************************************************************************************/
|
||||
|
||||
/*
|
||||
* Probe for 16mbit flash memory on a LART board without doing
|
||||
* too much damage. Since we need to write 1 dword to memory,
|
||||
* we're f**cked if this happens to be DRAM since we can't
|
||||
* restore the memory (otherwise we might exit Read Array mode).
|
||||
*
|
||||
* Returns 1 if we found 16mbit flash memory on LART, 0 otherwise.
|
||||
*/
|
||||
static int flash_probe (void)
|
||||
{
|
||||
__u32 manufacturer,devtype;
|
||||
|
||||
/* setup "Read Identifier Codes" mode */
|
||||
write32 (DATA_TO_FLASH (READ_ID_CODES),0x00000000);
|
||||
|
||||
/* probe U2. U2/U3 returns the same data since the first 3
|
||||
* address lines is mangled in the same way */
|
||||
manufacturer = FLASH_TO_DATA (read32 (ADDR_TO_FLASH_U2 (0x00000000)));
|
||||
devtype = FLASH_TO_DATA (read32 (ADDR_TO_FLASH_U2 (0x00000001)));
|
||||
|
||||
/* put the flash back into command mode */
|
||||
write32 (DATA_TO_FLASH (READ_ARRAY),0x00000000);
|
||||
|
||||
return (manufacturer == FLASH_MANUFACTURER && (devtype == FLASH_DEVICE_16mbit_TOP || devtype == FLASH_DEVICE_16mbit_BOTTOM));
|
||||
}
|
||||
|
||||
/*
|
||||
* Erase one block of flash memory at offset ``offset'' which is any
|
||||
* address within the block which should be erased.
|
||||
*
|
||||
* Returns 1 if successful, 0 otherwise.
|
||||
*/
|
||||
static inline int erase_block (__u32 offset)
|
||||
{
|
||||
__u32 status;
|
||||
|
||||
#ifdef LART_DEBUG
|
||||
printk (KERN_DEBUG "%s(): 0x%.8x\n", __func__, offset);
|
||||
#endif
|
||||
|
||||
/* erase and confirm */
|
||||
write32 (DATA_TO_FLASH (ERASE_SETUP),offset);
|
||||
write32 (DATA_TO_FLASH (ERASE_CONFIRM),offset);
|
||||
|
||||
/* wait for block erase to finish */
|
||||
do
|
||||
{
|
||||
write32 (DATA_TO_FLASH (STATUS_READ),offset);
|
||||
status = FLASH_TO_DATA (read32 (offset));
|
||||
}
|
||||
while ((~status & STATUS_BUSY) != 0);
|
||||
|
||||
/* put the flash back into command mode */
|
||||
write32 (DATA_TO_FLASH (READ_ARRAY),offset);
|
||||
|
||||
/* was the erase successful? */
|
||||
if ((status & STATUS_ERASE_ERR))
|
||||
{
|
||||
printk (KERN_WARNING "%s: erase error at address 0x%.8x.\n",module_name,offset);
|
||||
return (0);
|
||||
}
|
||||
|
||||
return (1);
|
||||
}
|
||||
|
||||
static int flash_erase (struct mtd_info *mtd,struct erase_info *instr)
|
||||
{
|
||||
__u32 addr,len;
|
||||
int i,first;
|
||||
|
||||
#ifdef LART_DEBUG
|
||||
printk (KERN_DEBUG "%s(addr = 0x%.8x, len = %d)\n", __func__, instr->addr, instr->len);
|
||||
#endif
|
||||
|
||||
/*
|
||||
* check that both start and end of the requested erase are
|
||||
* aligned with the erasesize at the appropriate addresses.
|
||||
*
|
||||
* skip all erase regions which are ended before the start of
|
||||
* the requested erase. Actually, to save on the calculations,
|
||||
* we skip to the first erase region which starts after the
|
||||
* start of the requested erase, and then go back one.
|
||||
*/
|
||||
for (i = 0; i < mtd->numeraseregions && instr->addr >= mtd->eraseregions[i].offset; i++) ;
|
||||
i--;
|
||||
|
||||
/*
|
||||
* ok, now i is pointing at the erase region in which this
|
||||
* erase request starts. Check the start of the requested
|
||||
* erase range is aligned with the erase size which is in
|
||||
* effect here.
|
||||
*/
|
||||
if (i < 0 || (instr->addr & (mtd->eraseregions[i].erasesize - 1)))
|
||||
return -EINVAL;
|
||||
|
||||
/* Remember the erase region we start on */
|
||||
first = i;
|
||||
|
||||
/*
|
||||
* next, check that the end of the requested erase is aligned
|
||||
* with the erase region at that address.
|
||||
*
|
||||
* as before, drop back one to point at the region in which
|
||||
* the address actually falls
|
||||
*/
|
||||
for (; i < mtd->numeraseregions && instr->addr + instr->len >= mtd->eraseregions[i].offset; i++) ;
|
||||
i--;
|
||||
|
||||
/* is the end aligned on a block boundary? */
|
||||
if (i < 0 || ((instr->addr + instr->len) & (mtd->eraseregions[i].erasesize - 1)))
|
||||
return -EINVAL;
|
||||
|
||||
addr = instr->addr;
|
||||
len = instr->len;
|
||||
|
||||
i = first;
|
||||
|
||||
/* now erase those blocks */
|
||||
while (len)
|
||||
{
|
||||
if (!erase_block (addr))
|
||||
{
|
||||
instr->state = MTD_ERASE_FAILED;
|
||||
return (-EIO);
|
||||
}
|
||||
|
||||
addr += mtd->eraseregions[i].erasesize;
|
||||
len -= mtd->eraseregions[i].erasesize;
|
||||
|
||||
if (addr == mtd->eraseregions[i].offset + (mtd->eraseregions[i].erasesize * mtd->eraseregions[i].numblocks)) i++;
|
||||
}
|
||||
|
||||
instr->state = MTD_ERASE_DONE;
|
||||
mtd_erase_callback(instr);
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
static int flash_read (struct mtd_info *mtd,loff_t from,size_t len,size_t *retlen,u_char *buf)
|
||||
{
|
||||
#ifdef LART_DEBUG
|
||||
printk (KERN_DEBUG "%s(from = 0x%.8x, len = %d)\n", __func__, (__u32)from, len);
|
||||
#endif
|
||||
|
||||
/* we always read len bytes */
|
||||
*retlen = len;
|
||||
|
||||
/* first, we read bytes until we reach a dword boundary */
|
||||
if (from & (BUSWIDTH - 1))
|
||||
{
|
||||
int gap = BUSWIDTH - (from & (BUSWIDTH - 1));
|
||||
|
||||
while (len && gap--) *buf++ = read8 (from++), len--;
|
||||
}
|
||||
|
||||
/* now we read dwords until we reach a non-dword boundary */
|
||||
while (len >= BUSWIDTH)
|
||||
{
|
||||
*((__u32 *) buf) = read32 (from);
|
||||
|
||||
buf += BUSWIDTH;
|
||||
from += BUSWIDTH;
|
||||
len -= BUSWIDTH;
|
||||
}
|
||||
|
||||
/* top up the last unaligned bytes */
|
||||
if (len & (BUSWIDTH - 1))
|
||||
while (len--) *buf++ = read8 (from++);
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Write one dword ``x'' to flash memory at offset ``offset''. ``offset''
|
||||
* must be 32 bits, i.e. it must be on a dword boundary.
|
||||
*
|
||||
* Returns 1 if successful, 0 otherwise.
|
||||
*/
|
||||
static inline int write_dword (__u32 offset,__u32 x)
|
||||
{
|
||||
__u32 status;
|
||||
|
||||
#ifdef LART_DEBUG
|
||||
printk (KERN_DEBUG "%s(): 0x%.8x <- 0x%.8x\n", __func__, offset, x);
|
||||
#endif
|
||||
|
||||
/* setup writing */
|
||||
write32 (DATA_TO_FLASH (PGM_SETUP),offset);
|
||||
|
||||
/* write the data */
|
||||
write32 (x,offset);
|
||||
|
||||
/* wait for the write to finish */
|
||||
do
|
||||
{
|
||||
write32 (DATA_TO_FLASH (STATUS_READ),offset);
|
||||
status = FLASH_TO_DATA (read32 (offset));
|
||||
}
|
||||
while ((~status & STATUS_BUSY) != 0);
|
||||
|
||||
/* put the flash back into command mode */
|
||||
write32 (DATA_TO_FLASH (READ_ARRAY),offset);
|
||||
|
||||
/* was the write successful? */
|
||||
if ((status & STATUS_PGM_ERR) || read32 (offset) != x)
|
||||
{
|
||||
printk (KERN_WARNING "%s: write error at address 0x%.8x.\n",module_name,offset);
|
||||
return (0);
|
||||
}
|
||||
|
||||
return (1);
|
||||
}
|
||||
|
||||
static int flash_write (struct mtd_info *mtd,loff_t to,size_t len,size_t *retlen,const u_char *buf)
|
||||
{
|
||||
__u8 tmp[4];
|
||||
int i,n;
|
||||
|
||||
#ifdef LART_DEBUG
|
||||
printk (KERN_DEBUG "%s(to = 0x%.8x, len = %d)\n", __func__, (__u32)to, len);
|
||||
#endif
|
||||
|
||||
/* sanity checks */
|
||||
if (!len) return (0);
|
||||
|
||||
/* first, we write a 0xFF.... padded byte until we reach a dword boundary */
|
||||
if (to & (BUSWIDTH - 1))
|
||||
{
|
||||
__u32 aligned = to & ~(BUSWIDTH - 1);
|
||||
int gap = to - aligned;
|
||||
|
||||
i = n = 0;
|
||||
|
||||
while (gap--) tmp[i++] = 0xFF;
|
||||
while (len && i < BUSWIDTH) tmp[i++] = buf[n++], len--;
|
||||
while (i < BUSWIDTH) tmp[i++] = 0xFF;
|
||||
|
||||
if (!write_dword (aligned,*((__u32 *) tmp))) return (-EIO);
|
||||
|
||||
to += n;
|
||||
buf += n;
|
||||
*retlen += n;
|
||||
}
|
||||
|
||||
/* now we write dwords until we reach a non-dword boundary */
|
||||
while (len >= BUSWIDTH)
|
||||
{
|
||||
if (!write_dword (to,*((__u32 *) buf))) return (-EIO);
|
||||
|
||||
to += BUSWIDTH;
|
||||
buf += BUSWIDTH;
|
||||
*retlen += BUSWIDTH;
|
||||
len -= BUSWIDTH;
|
||||
}
|
||||
|
||||
/* top up the last unaligned bytes, padded with 0xFF.... */
|
||||
if (len & (BUSWIDTH - 1))
|
||||
{
|
||||
i = n = 0;
|
||||
|
||||
while (len--) tmp[i++] = buf[n++];
|
||||
while (i < BUSWIDTH) tmp[i++] = 0xFF;
|
||||
|
||||
if (!write_dword (to,*((__u32 *) tmp))) return (-EIO);
|
||||
|
||||
*retlen += n;
|
||||
}
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
/***************************************************************************************************/
|
||||
|
||||
static struct mtd_info mtd;
|
||||
|
||||
static struct mtd_erase_region_info erase_regions[] = {
|
||||
/* parameter blocks */
|
||||
{
|
||||
.offset = 0x00000000,
|
||||
.erasesize = FLASH_BLOCKSIZE_PARAM,
|
||||
.numblocks = FLASH_NUMBLOCKS_16m_PARAM,
|
||||
},
|
||||
/* main blocks */
|
||||
{
|
||||
.offset = FLASH_BLOCKSIZE_PARAM * FLASH_NUMBLOCKS_16m_PARAM,
|
||||
.erasesize = FLASH_BLOCKSIZE_MAIN,
|
||||
.numblocks = FLASH_NUMBLOCKS_16m_MAIN,
|
||||
}
|
||||
};
|
||||
|
||||
static struct mtd_partition lart_partitions[] = {
|
||||
/* blob */
|
||||
{
|
||||
.name = "blob",
|
||||
.offset = BLOB_START,
|
||||
.size = BLOB_LEN,
|
||||
},
|
||||
/* kernel */
|
||||
{
|
||||
.name = "kernel",
|
||||
.offset = KERNEL_START, /* MTDPART_OFS_APPEND */
|
||||
.size = KERNEL_LEN,
|
||||
},
|
||||
/* initial ramdisk / file system */
|
||||
{
|
||||
.name = "file system",
|
||||
.offset = INITRD_START, /* MTDPART_OFS_APPEND */
|
||||
.size = INITRD_LEN, /* MTDPART_SIZ_FULL */
|
||||
}
|
||||
};
|
||||
#define NUM_PARTITIONS ARRAY_SIZE(lart_partitions)
|
||||
|
||||
static int __init lart_flash_init (void)
|
||||
{
|
||||
int result;
|
||||
memset (&mtd,0,sizeof (mtd));
|
||||
printk ("MTD driver for LART. Written by Abraham vd Merwe <abraham@2d3d.co.za>\n");
|
||||
printk ("%s: Probing for 28F160x3 flash on LART...\n",module_name);
|
||||
if (!flash_probe ())
|
||||
{
|
||||
printk (KERN_WARNING "%s: Found no LART compatible flash device\n",module_name);
|
||||
return (-ENXIO);
|
||||
}
|
||||
printk ("%s: This looks like a LART board to me.\n",module_name);
|
||||
mtd.name = module_name;
|
||||
mtd.type = MTD_NORFLASH;
|
||||
mtd.writesize = 1;
|
||||
mtd.writebufsize = 4;
|
||||
mtd.flags = MTD_CAP_NORFLASH;
|
||||
mtd.size = FLASH_BLOCKSIZE_PARAM * FLASH_NUMBLOCKS_16m_PARAM + FLASH_BLOCKSIZE_MAIN * FLASH_NUMBLOCKS_16m_MAIN;
|
||||
mtd.erasesize = FLASH_BLOCKSIZE_MAIN;
|
||||
mtd.numeraseregions = ARRAY_SIZE(erase_regions);
|
||||
mtd.eraseregions = erase_regions;
|
||||
mtd._erase = flash_erase;
|
||||
mtd._read = flash_read;
|
||||
mtd._write = flash_write;
|
||||
mtd.owner = THIS_MODULE;
|
||||
|
||||
#ifdef LART_DEBUG
|
||||
printk (KERN_DEBUG
|
||||
"mtd.name = %s\n"
|
||||
"mtd.size = 0x%.8x (%uM)\n"
|
||||
"mtd.erasesize = 0x%.8x (%uK)\n"
|
||||
"mtd.numeraseregions = %d\n",
|
||||
mtd.name,
|
||||
mtd.size,mtd.size / (1024*1024),
|
||||
mtd.erasesize,mtd.erasesize / 1024,
|
||||
mtd.numeraseregions);
|
||||
|
||||
if (mtd.numeraseregions)
|
||||
for (result = 0; result < mtd.numeraseregions; result++)
|
||||
printk (KERN_DEBUG
|
||||
"\n\n"
|
||||
"mtd.eraseregions[%d].offset = 0x%.8x\n"
|
||||
"mtd.eraseregions[%d].erasesize = 0x%.8x (%uK)\n"
|
||||
"mtd.eraseregions[%d].numblocks = %d\n",
|
||||
result,mtd.eraseregions[result].offset,
|
||||
result,mtd.eraseregions[result].erasesize,mtd.eraseregions[result].erasesize / 1024,
|
||||
result,mtd.eraseregions[result].numblocks);
|
||||
|
||||
printk ("\npartitions = %d\n", ARRAY_SIZE(lart_partitions));
|
||||
|
||||
for (result = 0; result < ARRAY_SIZE(lart_partitions); result++)
|
||||
printk (KERN_DEBUG
|
||||
"\n\n"
|
||||
"lart_partitions[%d].name = %s\n"
|
||||
"lart_partitions[%d].offset = 0x%.8x\n"
|
||||
"lart_partitions[%d].size = 0x%.8x (%uK)\n",
|
||||
result,lart_partitions[result].name,
|
||||
result,lart_partitions[result].offset,
|
||||
result,lart_partitions[result].size,lart_partitions[result].size / 1024);
|
||||
#endif
|
||||
|
||||
result = mtd_device_register(&mtd, lart_partitions,
|
||||
ARRAY_SIZE(lart_partitions));
|
||||
|
||||
return (result);
|
||||
}
|
||||
|
||||
static void __exit lart_flash_exit (void)
|
||||
{
|
||||
mtd_device_unregister(&mtd);
|
||||
}
|
||||
|
||||
module_init (lart_flash_init);
|
||||
module_exit (lart_flash_exit);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Abraham vd Merwe <abraham@2d3d.co.za>");
|
||||
MODULE_DESCRIPTION("MTD driver for Intel 28F160F3 on LART board");
|
333
drivers/mtd/devices/m25p80.c
Normal file
333
drivers/mtd/devices/m25p80.c
Normal file
|
@ -0,0 +1,333 @@
|
|||
/*
|
||||
* MTD SPI driver for ST M25Pxx (and similar) serial flash chips
|
||||
*
|
||||
* Author: Mike Lavender, mike@steroidmicros.com
|
||||
*
|
||||
* Copyright (c) 2005, Intec Automation Inc.
|
||||
*
|
||||
* Some parts are based on lart.c by Abraham Van Der Merwe
|
||||
*
|
||||
* Cleaned up and generalized based on mtd_dataflash.c
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/err.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/device.h>
|
||||
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include <linux/mtd/partitions.h>
|
||||
|
||||
#include <linux/spi/spi.h>
|
||||
#include <linux/spi/flash.h>
|
||||
#include <linux/mtd/spi-nor.h>
|
||||
|
||||
#define MAX_CMD_SIZE 6
|
||||
struct m25p {
|
||||
struct spi_device *spi;
|
||||
struct spi_nor spi_nor;
|
||||
struct mtd_info mtd;
|
||||
u8 command[MAX_CMD_SIZE];
|
||||
};
|
||||
|
||||
static int m25p80_read_reg(struct spi_nor *nor, u8 code, u8 *val, int len)
|
||||
{
|
||||
struct m25p *flash = nor->priv;
|
||||
struct spi_device *spi = flash->spi;
|
||||
int ret;
|
||||
|
||||
ret = spi_write_then_read(spi, &code, 1, val, len);
|
||||
if (ret < 0)
|
||||
dev_err(&spi->dev, "error %d reading %x\n", ret, code);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void m25p_addr2cmd(struct spi_nor *nor, unsigned int addr, u8 *cmd)
|
||||
{
|
||||
/* opcode is in cmd[0] */
|
||||
cmd[1] = addr >> (nor->addr_width * 8 - 8);
|
||||
cmd[2] = addr >> (nor->addr_width * 8 - 16);
|
||||
cmd[3] = addr >> (nor->addr_width * 8 - 24);
|
||||
cmd[4] = addr >> (nor->addr_width * 8 - 32);
|
||||
}
|
||||
|
||||
static int m25p_cmdsz(struct spi_nor *nor)
|
||||
{
|
||||
return 1 + nor->addr_width;
|
||||
}
|
||||
|
||||
static int m25p80_write_reg(struct spi_nor *nor, u8 opcode, u8 *buf, int len,
|
||||
int wr_en)
|
||||
{
|
||||
struct m25p *flash = nor->priv;
|
||||
struct spi_device *spi = flash->spi;
|
||||
|
||||
flash->command[0] = opcode;
|
||||
if (buf)
|
||||
memcpy(&flash->command[1], buf, len);
|
||||
|
||||
return spi_write(spi, flash->command, len + 1);
|
||||
}
|
||||
|
||||
static void m25p80_write(struct spi_nor *nor, loff_t to, size_t len,
|
||||
size_t *retlen, const u_char *buf)
|
||||
{
|
||||
struct m25p *flash = nor->priv;
|
||||
struct spi_device *spi = flash->spi;
|
||||
struct spi_transfer t[2] = {};
|
||||
struct spi_message m;
|
||||
int cmd_sz = m25p_cmdsz(nor);
|
||||
|
||||
spi_message_init(&m);
|
||||
|
||||
if (nor->program_opcode == SPINOR_OP_AAI_WP && nor->sst_write_second)
|
||||
cmd_sz = 1;
|
||||
|
||||
flash->command[0] = nor->program_opcode;
|
||||
m25p_addr2cmd(nor, to, flash->command);
|
||||
|
||||
t[0].tx_buf = flash->command;
|
||||
t[0].len = cmd_sz;
|
||||
spi_message_add_tail(&t[0], &m);
|
||||
|
||||
t[1].tx_buf = buf;
|
||||
t[1].len = len;
|
||||
spi_message_add_tail(&t[1], &m);
|
||||
|
||||
spi_sync(spi, &m);
|
||||
|
||||
*retlen += m.actual_length - cmd_sz;
|
||||
}
|
||||
|
||||
static inline unsigned int m25p80_rx_nbits(struct spi_nor *nor)
|
||||
{
|
||||
switch (nor->flash_read) {
|
||||
case SPI_NOR_DUAL:
|
||||
return 2;
|
||||
case SPI_NOR_QUAD:
|
||||
return 4;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Read an address range from the nor chip. The address range
|
||||
* may be any size provided it is within the physical boundaries.
|
||||
*/
|
||||
static int m25p80_read(struct spi_nor *nor, loff_t from, size_t len,
|
||||
size_t *retlen, u_char *buf)
|
||||
{
|
||||
struct m25p *flash = nor->priv;
|
||||
struct spi_device *spi = flash->spi;
|
||||
struct spi_transfer t[2];
|
||||
struct spi_message m;
|
||||
int dummy = nor->read_dummy;
|
||||
int ret;
|
||||
|
||||
/* Wait till previous write/erase is done. */
|
||||
ret = nor->wait_till_ready(nor);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
spi_message_init(&m);
|
||||
memset(t, 0, (sizeof t));
|
||||
|
||||
flash->command[0] = nor->read_opcode;
|
||||
m25p_addr2cmd(nor, from, flash->command);
|
||||
|
||||
t[0].tx_buf = flash->command;
|
||||
t[0].len = m25p_cmdsz(nor) + dummy;
|
||||
spi_message_add_tail(&t[0], &m);
|
||||
|
||||
t[1].rx_buf = buf;
|
||||
t[1].rx_nbits = m25p80_rx_nbits(nor);
|
||||
t[1].len = len;
|
||||
spi_message_add_tail(&t[1], &m);
|
||||
|
||||
spi_sync(spi, &m);
|
||||
|
||||
*retlen = m.actual_length - m25p_cmdsz(nor) - dummy;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int m25p80_erase(struct spi_nor *nor, loff_t offset)
|
||||
{
|
||||
struct m25p *flash = nor->priv;
|
||||
int ret;
|
||||
|
||||
dev_dbg(nor->dev, "%dKiB at 0x%08x\n",
|
||||
flash->mtd.erasesize / 1024, (u32)offset);
|
||||
|
||||
/* Wait until finished previous write command. */
|
||||
ret = nor->wait_till_ready(nor);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Send write enable, then erase commands. */
|
||||
ret = nor->write_reg(nor, SPINOR_OP_WREN, NULL, 0, 0);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Set up command buffer. */
|
||||
flash->command[0] = nor->erase_opcode;
|
||||
m25p_addr2cmd(nor, offset, flash->command);
|
||||
|
||||
spi_write(flash->spi, flash->command, m25p_cmdsz(nor));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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 m25p_probe(struct spi_device *spi)
|
||||
{
|
||||
struct mtd_part_parser_data ppdata;
|
||||
struct flash_platform_data *data;
|
||||
struct m25p *flash;
|
||||
struct spi_nor *nor;
|
||||
enum read_mode mode = SPI_NOR_NORMAL;
|
||||
char *flash_name = NULL;
|
||||
int ret;
|
||||
|
||||
data = dev_get_platdata(&spi->dev);
|
||||
|
||||
flash = devm_kzalloc(&spi->dev, sizeof(*flash), GFP_KERNEL);
|
||||
if (!flash)
|
||||
return -ENOMEM;
|
||||
|
||||
nor = &flash->spi_nor;
|
||||
|
||||
/* install the hooks */
|
||||
nor->read = m25p80_read;
|
||||
nor->write = m25p80_write;
|
||||
nor->erase = m25p80_erase;
|
||||
nor->write_reg = m25p80_write_reg;
|
||||
nor->read_reg = m25p80_read_reg;
|
||||
|
||||
nor->dev = &spi->dev;
|
||||
nor->mtd = &flash->mtd;
|
||||
nor->priv = flash;
|
||||
|
||||
spi_set_drvdata(spi, flash);
|
||||
flash->mtd.priv = nor;
|
||||
flash->spi = spi;
|
||||
|
||||
if (spi->mode & SPI_RX_QUAD)
|
||||
mode = SPI_NOR_QUAD;
|
||||
else if (spi->mode & SPI_RX_DUAL)
|
||||
mode = SPI_NOR_DUAL;
|
||||
|
||||
if (data && data->name)
|
||||
flash->mtd.name = data->name;
|
||||
|
||||
/* For some (historical?) reason many platforms provide two different
|
||||
* names in flash_platform_data: "name" and "type". Quite often name is
|
||||
* set to "m25p80" and then "type" provides a real chip name.
|
||||
* If that's the case, respect "type" and ignore a "name".
|
||||
*/
|
||||
if (data && data->type)
|
||||
flash_name = data->type;
|
||||
else
|
||||
flash_name = spi->modalias;
|
||||
|
||||
ret = spi_nor_scan(nor, flash_name, mode);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ppdata.of_node = spi->dev.of_node;
|
||||
|
||||
return mtd_device_parse_register(&flash->mtd, NULL, &ppdata,
|
||||
data ? data->parts : NULL,
|
||||
data ? data->nr_parts : 0);
|
||||
}
|
||||
|
||||
|
||||
static int m25p_remove(struct spi_device *spi)
|
||||
{
|
||||
struct m25p *flash = spi_get_drvdata(spi);
|
||||
|
||||
/* Clean up MTD stuff. */
|
||||
return mtd_device_unregister(&flash->mtd);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* XXX This needs to be kept in sync with spi_nor_ids. We can't share
|
||||
* it with spi-nor, because if this is built as a module then modpost
|
||||
* won't be able to read it and add appropriate aliases.
|
||||
*/
|
||||
static const struct spi_device_id m25p_ids[] = {
|
||||
{"at25fs010"}, {"at25fs040"}, {"at25df041a"}, {"at25df321a"},
|
||||
{"at25df641"}, {"at26f004"}, {"at26df081a"}, {"at26df161a"},
|
||||
{"at26df321"}, {"at45db081d"},
|
||||
{"en25f32"}, {"en25p32"}, {"en25q32b"}, {"en25p64"},
|
||||
{"en25q64"}, {"en25qh128"}, {"en25qh256"},
|
||||
{"f25l32pa"},
|
||||
{"mr25h256"}, {"mr25h10"},
|
||||
{"gd25q32"}, {"gd25q64"},
|
||||
{"160s33b"}, {"320s33b"}, {"640s33b"},
|
||||
{"mx25l2005a"}, {"mx25l4005a"}, {"mx25l8005"}, {"mx25l1606e"},
|
||||
{"mx25l3205d"}, {"mx25l3255e"}, {"mx25l6405d"}, {"mx25l12805d"},
|
||||
{"mx25l12855e"},{"mx25l25635e"},{"mx25l25655e"},{"mx66l51235l"},
|
||||
{"mx66l1g55g"},
|
||||
{"n25q064"}, {"n25q128a11"}, {"n25q128a13"}, {"n25q256a"},
|
||||
{"n25q512a"}, {"n25q512ax3"}, {"n25q00"},
|
||||
{"pm25lv512"}, {"pm25lv010"}, {"pm25lq032"},
|
||||
{"s25sl032p"}, {"s25sl064p"}, {"s25fl256s0"}, {"s25fl256s1"},
|
||||
{"s25fl512s"}, {"s70fl01gs"}, {"s25sl12800"}, {"s25sl12801"},
|
||||
{"s25fl129p0"}, {"s25fl129p1"}, {"s25sl004a"}, {"s25sl008a"},
|
||||
{"s25sl016a"}, {"s25sl032a"}, {"s25sl064a"}, {"s25fl008k"},
|
||||
{"s25fl016k"}, {"s25fl064k"},
|
||||
{"sst25vf040b"},{"sst25vf080b"},{"sst25vf016b"},{"sst25vf032b"},
|
||||
{"sst25vf064c"},{"sst25wf512"}, {"sst25wf010"}, {"sst25wf020"},
|
||||
{"sst25wf040"},
|
||||
{"m25p05"}, {"m25p10"}, {"m25p20"}, {"m25p40"},
|
||||
{"m25p80"}, {"m25p16"}, {"m25p32"}, {"m25p64"},
|
||||
{"m25p128"}, {"n25q032"},
|
||||
{"m25p05-nonjedec"}, {"m25p10-nonjedec"}, {"m25p20-nonjedec"},
|
||||
{"m25p40-nonjedec"}, {"m25p80-nonjedec"}, {"m25p16-nonjedec"},
|
||||
{"m25p32-nonjedec"}, {"m25p64-nonjedec"}, {"m25p128-nonjedec"},
|
||||
{"m45pe10"}, {"m45pe80"}, {"m45pe16"},
|
||||
{"m25pe20"}, {"m25pe80"}, {"m25pe16"},
|
||||
{"m25px16"}, {"m25px32"}, {"m25px32-s0"}, {"m25px32-s1"},
|
||||
{"m25px64"}, {"m25px80"},
|
||||
{"w25x10"}, {"w25x20"}, {"w25x40"}, {"w25x80"},
|
||||
{"w25x16"}, {"w25x32"}, {"w25q32"}, {"w25q32dw"},
|
||||
{"w25x64"}, {"w25q64"}, {"w25q80"}, {"w25q80bl"},
|
||||
{"w25q128"}, {"w25q256"}, {"cat25c11"},
|
||||
{"cat25c03"}, {"cat25c09"}, {"cat25c17"}, {"cat25128"},
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(spi, m25p_ids);
|
||||
|
||||
|
||||
static struct spi_driver m25p80_driver = {
|
||||
.driver = {
|
||||
.name = "m25p80",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.id_table = m25p_ids,
|
||||
.probe = m25p_probe,
|
||||
.remove = m25p_remove,
|
||||
|
||||
/* REVISIT: many of these chips have deep power-down modes, which
|
||||
* should clearly be entered on suspend() to minimize power use.
|
||||
* And also when they're otherwise idle...
|
||||
*/
|
||||
};
|
||||
|
||||
module_spi_driver(m25p80_driver);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Mike Lavender");
|
||||
MODULE_DESCRIPTION("MTD SPI driver for ST M25Pxx flash chips");
|
311
drivers/mtd/devices/ms02-nv.c
Normal file
311
drivers/mtd/devices/ms02-nv.c
Normal file
|
@ -0,0 +1,311 @@
|
|||
/*
|
||||
* Copyright (c) 2001 Maciej W. Rozycki
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version
|
||||
* 2 of the License, or (at your option) any later version.
|
||||
*/
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/ioport.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#include <asm/addrspace.h>
|
||||
#include <asm/bootinfo.h>
|
||||
#include <asm/dec/ioasic_addrs.h>
|
||||
#include <asm/dec/kn02.h>
|
||||
#include <asm/dec/kn03.h>
|
||||
#include <asm/io.h>
|
||||
#include <asm/paccess.h>
|
||||
|
||||
#include "ms02-nv.h"
|
||||
|
||||
|
||||
static char version[] __initdata =
|
||||
"ms02-nv.c: v.1.0.0 13 Aug 2001 Maciej W. Rozycki.\n";
|
||||
|
||||
MODULE_AUTHOR("Maciej W. Rozycki <macro@linux-mips.org>");
|
||||
MODULE_DESCRIPTION("DEC MS02-NV NVRAM module driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
|
||||
/*
|
||||
* Addresses we probe for an MS02-NV at. Modules may be located
|
||||
* at any 8MiB boundary within a 0MiB up to 112MiB range or at any 32MiB
|
||||
* boundary within a 0MiB up to 448MiB range. We don't support a module
|
||||
* at 0MiB, though.
|
||||
*/
|
||||
static ulong ms02nv_addrs[] __initdata = {
|
||||
0x07000000, 0x06800000, 0x06000000, 0x05800000, 0x05000000,
|
||||
0x04800000, 0x04000000, 0x03800000, 0x03000000, 0x02800000,
|
||||
0x02000000, 0x01800000, 0x01000000, 0x00800000
|
||||
};
|
||||
|
||||
static const char ms02nv_name[] = "DEC MS02-NV NVRAM";
|
||||
static const char ms02nv_res_diag_ram[] = "Diagnostic RAM";
|
||||
static const char ms02nv_res_user_ram[] = "General-purpose RAM";
|
||||
static const char ms02nv_res_csr[] = "Control and status register";
|
||||
|
||||
static struct mtd_info *root_ms02nv_mtd;
|
||||
|
||||
|
||||
static int ms02nv_read(struct mtd_info *mtd, loff_t from,
|
||||
size_t len, size_t *retlen, u_char *buf)
|
||||
{
|
||||
struct ms02nv_private *mp = mtd->priv;
|
||||
|
||||
memcpy(buf, mp->uaddr + from, len);
|
||||
*retlen = len;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ms02nv_write(struct mtd_info *mtd, loff_t to,
|
||||
size_t len, size_t *retlen, const u_char *buf)
|
||||
{
|
||||
struct ms02nv_private *mp = mtd->priv;
|
||||
|
||||
memcpy(mp->uaddr + to, buf, len);
|
||||
*retlen = len;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static inline uint ms02nv_probe_one(ulong addr)
|
||||
{
|
||||
ms02nv_uint *ms02nv_diagp;
|
||||
ms02nv_uint *ms02nv_magicp;
|
||||
uint ms02nv_diag;
|
||||
uint ms02nv_magic;
|
||||
size_t size;
|
||||
|
||||
int err;
|
||||
|
||||
/*
|
||||
* The firmware writes MS02NV_ID at MS02NV_MAGIC and also
|
||||
* a diagnostic status at MS02NV_DIAG.
|
||||
*/
|
||||
ms02nv_diagp = (ms02nv_uint *)(CKSEG1ADDR(addr + MS02NV_DIAG));
|
||||
ms02nv_magicp = (ms02nv_uint *)(CKSEG1ADDR(addr + MS02NV_MAGIC));
|
||||
err = get_dbe(ms02nv_magic, ms02nv_magicp);
|
||||
if (err)
|
||||
return 0;
|
||||
if (ms02nv_magic != MS02NV_ID)
|
||||
return 0;
|
||||
|
||||
ms02nv_diag = *ms02nv_diagp;
|
||||
size = (ms02nv_diag & MS02NV_DIAG_SIZE_MASK) << MS02NV_DIAG_SIZE_SHIFT;
|
||||
if (size > MS02NV_CSR)
|
||||
size = MS02NV_CSR;
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
static int __init ms02nv_init_one(ulong addr)
|
||||
{
|
||||
struct mtd_info *mtd;
|
||||
struct ms02nv_private *mp;
|
||||
struct resource *mod_res;
|
||||
struct resource *diag_res;
|
||||
struct resource *user_res;
|
||||
struct resource *csr_res;
|
||||
ulong fixaddr;
|
||||
size_t size, fixsize;
|
||||
|
||||
static int version_printed;
|
||||
|
||||
int ret = -ENODEV;
|
||||
|
||||
/* The module decodes 8MiB of address space. */
|
||||
mod_res = kzalloc(sizeof(*mod_res), GFP_KERNEL);
|
||||
if (!mod_res)
|
||||
return -ENOMEM;
|
||||
|
||||
mod_res->name = ms02nv_name;
|
||||
mod_res->start = addr;
|
||||
mod_res->end = addr + MS02NV_SLOT_SIZE - 1;
|
||||
mod_res->flags = IORESOURCE_MEM | IORESOURCE_BUSY;
|
||||
if (request_resource(&iomem_resource, mod_res) < 0)
|
||||
goto err_out_mod_res;
|
||||
|
||||
size = ms02nv_probe_one(addr);
|
||||
if (!size)
|
||||
goto err_out_mod_res_rel;
|
||||
|
||||
if (!version_printed) {
|
||||
printk(KERN_INFO "%s", version);
|
||||
version_printed = 1;
|
||||
}
|
||||
|
||||
ret = -ENOMEM;
|
||||
mtd = kzalloc(sizeof(*mtd), GFP_KERNEL);
|
||||
if (!mtd)
|
||||
goto err_out_mod_res_rel;
|
||||
mp = kzalloc(sizeof(*mp), GFP_KERNEL);
|
||||
if (!mp)
|
||||
goto err_out_mtd;
|
||||
|
||||
mtd->priv = mp;
|
||||
mp->resource.module = mod_res;
|
||||
|
||||
/* Firmware's diagnostic NVRAM area. */
|
||||
diag_res = kzalloc(sizeof(*diag_res), GFP_KERNEL);
|
||||
if (!diag_res)
|
||||
goto err_out_mp;
|
||||
|
||||
diag_res->name = ms02nv_res_diag_ram;
|
||||
diag_res->start = addr;
|
||||
diag_res->end = addr + MS02NV_RAM - 1;
|
||||
diag_res->flags = IORESOURCE_BUSY;
|
||||
request_resource(mod_res, diag_res);
|
||||
|
||||
mp->resource.diag_ram = diag_res;
|
||||
|
||||
/* User-available general-purpose NVRAM area. */
|
||||
user_res = kzalloc(sizeof(*user_res), GFP_KERNEL);
|
||||
if (!user_res)
|
||||
goto err_out_diag_res;
|
||||
|
||||
user_res->name = ms02nv_res_user_ram;
|
||||
user_res->start = addr + MS02NV_RAM;
|
||||
user_res->end = addr + size - 1;
|
||||
user_res->flags = IORESOURCE_BUSY;
|
||||
request_resource(mod_res, user_res);
|
||||
|
||||
mp->resource.user_ram = user_res;
|
||||
|
||||
/* Control and status register. */
|
||||
csr_res = kzalloc(sizeof(*csr_res), GFP_KERNEL);
|
||||
if (!csr_res)
|
||||
goto err_out_user_res;
|
||||
|
||||
csr_res->name = ms02nv_res_csr;
|
||||
csr_res->start = addr + MS02NV_CSR;
|
||||
csr_res->end = addr + MS02NV_CSR + 3;
|
||||
csr_res->flags = IORESOURCE_BUSY;
|
||||
request_resource(mod_res, csr_res);
|
||||
|
||||
mp->resource.csr = csr_res;
|
||||
|
||||
mp->addr = phys_to_virt(addr);
|
||||
mp->size = size;
|
||||
|
||||
/*
|
||||
* Hide the firmware's diagnostic area. It may get destroyed
|
||||
* upon a reboot. Take paging into account for mapping support.
|
||||
*/
|
||||
fixaddr = (addr + MS02NV_RAM + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1);
|
||||
fixsize = (size - (fixaddr - addr)) & ~(PAGE_SIZE - 1);
|
||||
mp->uaddr = phys_to_virt(fixaddr);
|
||||
|
||||
mtd->type = MTD_RAM;
|
||||
mtd->flags = MTD_CAP_RAM;
|
||||
mtd->size = fixsize;
|
||||
mtd->name = ms02nv_name;
|
||||
mtd->owner = THIS_MODULE;
|
||||
mtd->_read = ms02nv_read;
|
||||
mtd->_write = ms02nv_write;
|
||||
mtd->writesize = 1;
|
||||
|
||||
ret = -EIO;
|
||||
if (mtd_device_register(mtd, NULL, 0)) {
|
||||
printk(KERN_ERR
|
||||
"ms02-nv: Unable to register MTD device, aborting!\n");
|
||||
goto err_out_csr_res;
|
||||
}
|
||||
|
||||
printk(KERN_INFO "mtd%d: %s at 0x%08lx, size %zuMiB.\n",
|
||||
mtd->index, ms02nv_name, addr, size >> 20);
|
||||
|
||||
mp->next = root_ms02nv_mtd;
|
||||
root_ms02nv_mtd = mtd;
|
||||
|
||||
return 0;
|
||||
|
||||
|
||||
err_out_csr_res:
|
||||
release_resource(csr_res);
|
||||
kfree(csr_res);
|
||||
err_out_user_res:
|
||||
release_resource(user_res);
|
||||
kfree(user_res);
|
||||
err_out_diag_res:
|
||||
release_resource(diag_res);
|
||||
kfree(diag_res);
|
||||
err_out_mp:
|
||||
kfree(mp);
|
||||
err_out_mtd:
|
||||
kfree(mtd);
|
||||
err_out_mod_res_rel:
|
||||
release_resource(mod_res);
|
||||
err_out_mod_res:
|
||||
kfree(mod_res);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void __exit ms02nv_remove_one(void)
|
||||
{
|
||||
struct mtd_info *mtd = root_ms02nv_mtd;
|
||||
struct ms02nv_private *mp = mtd->priv;
|
||||
|
||||
root_ms02nv_mtd = mp->next;
|
||||
|
||||
mtd_device_unregister(mtd);
|
||||
|
||||
release_resource(mp->resource.csr);
|
||||
kfree(mp->resource.csr);
|
||||
release_resource(mp->resource.user_ram);
|
||||
kfree(mp->resource.user_ram);
|
||||
release_resource(mp->resource.diag_ram);
|
||||
kfree(mp->resource.diag_ram);
|
||||
release_resource(mp->resource.module);
|
||||
kfree(mp->resource.module);
|
||||
kfree(mp);
|
||||
kfree(mtd);
|
||||
}
|
||||
|
||||
|
||||
static int __init ms02nv_init(void)
|
||||
{
|
||||
volatile u32 *csr;
|
||||
uint stride = 0;
|
||||
int count = 0;
|
||||
int i;
|
||||
|
||||
switch (mips_machtype) {
|
||||
case MACH_DS5000_200:
|
||||
csr = (volatile u32 *)CKSEG1ADDR(KN02_SLOT_BASE + KN02_CSR);
|
||||
if (*csr & KN02_CSR_BNK32M)
|
||||
stride = 2;
|
||||
break;
|
||||
case MACH_DS5000_2X0:
|
||||
case MACH_DS5900:
|
||||
csr = (volatile u32 *)CKSEG1ADDR(KN03_SLOT_BASE + IOASIC_MCR);
|
||||
if (*csr & KN03_MCR_BNK32M)
|
||||
stride = 2;
|
||||
break;
|
||||
default:
|
||||
return -ENODEV;
|
||||
break;
|
||||
}
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(ms02nv_addrs); i++)
|
||||
if (!ms02nv_init_one(ms02nv_addrs[i] << stride))
|
||||
count++;
|
||||
|
||||
return (count > 0) ? 0 : -ENODEV;
|
||||
}
|
||||
|
||||
static void __exit ms02nv_cleanup(void)
|
||||
{
|
||||
while (root_ms02nv_mtd)
|
||||
ms02nv_remove_one();
|
||||
}
|
||||
|
||||
|
||||
module_init(ms02nv_init);
|
||||
module_exit(ms02nv_cleanup);
|
105
drivers/mtd/devices/ms02-nv.h
Normal file
105
drivers/mtd/devices/ms02-nv.h
Normal file
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
* Copyright (c) 2001, 2003 Maciej W. Rozycki
|
||||
*
|
||||
* DEC MS02-NV (54-20948-01) battery backed-up NVRAM module for
|
||||
* DECstation/DECsystem 5000/2x0 and DECsystem 5900 and 5900/260
|
||||
* systems.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version
|
||||
* 2 of the License, or (at your option) any later version.
|
||||
*/
|
||||
|
||||
#include <linux/ioport.h>
|
||||
#include <linux/mtd/mtd.h>
|
||||
|
||||
/*
|
||||
* Addresses are decoded as follows:
|
||||
*
|
||||
* 0x000000 - 0x3fffff SRAM
|
||||
* 0x400000 - 0x7fffff CSR
|
||||
*
|
||||
* Within the SRAM area the following ranges are forced by the system
|
||||
* firmware:
|
||||
*
|
||||
* 0x000000 - 0x0003ff diagnostic area, destroyed upon a reboot
|
||||
* 0x000400 - ENDofRAM storage area, available to operating systems
|
||||
*
|
||||
* but we can't really use the available area right from 0x000400 as
|
||||
* the first word is used by the firmware as a status flag passed
|
||||
* from an operating system. If anything but the valid data magic
|
||||
* ID value is found, the firmware considers the SRAM clean, i.e.
|
||||
* containing no valid data, and disables the battery resulting in
|
||||
* data being erased as soon as power is switched off. So the choice
|
||||
* for the start address of the user-available is 0x001000 which is
|
||||
* nicely page aligned. The area between 0x000404 and 0x000fff may
|
||||
* be used by the driver for own needs.
|
||||
*
|
||||
* The diagnostic area defines two status words to be read by an
|
||||
* operating system, a magic ID to distinguish a MS02-NV board from
|
||||
* anything else and a status information providing results of tests
|
||||
* as well as the size of SRAM available, which can be 1MiB or 2MiB
|
||||
* (that's what the firmware handles; no idea if 2MiB modules ever
|
||||
* existed).
|
||||
*
|
||||
* The firmware only handles the MS02-NV board if installed in the
|
||||
* last (15th) slot, so for any other location the status information
|
||||
* stored in the SRAM cannot be relied upon. But from the hardware
|
||||
* point of view there is no problem using up to 14 such boards in a
|
||||
* system -- only the 1st slot needs to be filled with a DRAM module.
|
||||
* The MS02-NV board is ECC-protected, like other MS02 memory boards.
|
||||
*
|
||||
* The state of the battery as provided by the CSR is reflected on
|
||||
* the two onboard LEDs. When facing the battery side of the board,
|
||||
* with the LEDs at the top left and the battery at the bottom right
|
||||
* (i.e. looking from the back side of the system box), their meaning
|
||||
* is as follows (the system has to be powered on):
|
||||
*
|
||||
* left LED battery disable status: lit = enabled
|
||||
* right LED battery condition status: lit = OK
|
||||
*/
|
||||
|
||||
/* MS02-NV iomem register offsets. */
|
||||
#define MS02NV_CSR 0x400000 /* control & status register */
|
||||
|
||||
/* MS02-NV CSR status bits. */
|
||||
#define MS02NV_CSR_BATT_OK 0x01 /* battery OK */
|
||||
#define MS02NV_CSR_BATT_OFF 0x02 /* battery disabled */
|
||||
|
||||
|
||||
/* MS02-NV memory offsets. */
|
||||
#define MS02NV_DIAG 0x0003f8 /* diagnostic status */
|
||||
#define MS02NV_MAGIC 0x0003fc /* MS02-NV magic ID */
|
||||
#define MS02NV_VALID 0x000400 /* valid data magic ID */
|
||||
#define MS02NV_RAM 0x001000 /* user-exposed RAM start */
|
||||
|
||||
/* MS02-NV diagnostic status bits. */
|
||||
#define MS02NV_DIAG_TEST 0x01 /* SRAM test done (?) */
|
||||
#define MS02NV_DIAG_RO 0x02 /* SRAM r/o test done */
|
||||
#define MS02NV_DIAG_RW 0x04 /* SRAM r/w test done */
|
||||
#define MS02NV_DIAG_FAIL 0x08 /* SRAM test failed */
|
||||
#define MS02NV_DIAG_SIZE_MASK 0xf0 /* SRAM size mask */
|
||||
#define MS02NV_DIAG_SIZE_SHIFT 0x10 /* SRAM size shift (left) */
|
||||
|
||||
/* MS02-NV general constants. */
|
||||
#define MS02NV_ID 0x03021966 /* MS02-NV magic ID value */
|
||||
#define MS02NV_VALID_ID 0xbd100248 /* valid data magic ID value */
|
||||
#define MS02NV_SLOT_SIZE 0x800000 /* size of the address space
|
||||
decoded by the module */
|
||||
|
||||
|
||||
typedef volatile u32 ms02nv_uint;
|
||||
|
||||
struct ms02nv_private {
|
||||
struct mtd_info *next;
|
||||
struct {
|
||||
struct resource *module;
|
||||
struct resource *diag_ram;
|
||||
struct resource *user_ram;
|
||||
struct resource *csr;
|
||||
} resource;
|
||||
u_char *addr;
|
||||
size_t size;
|
||||
u_char *uaddr;
|
||||
};
|
928
drivers/mtd/devices/mtd_dataflash.c
Normal file
928
drivers/mtd/devices/mtd_dataflash.c
Normal file
|
@ -0,0 +1,928 @@
|
|||
/*
|
||||
* 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/slab.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/math64.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_device.h>
|
||||
|
||||
#include <linux/spi/spi.h>
|
||||
#include <linux/spi/flash.h>
|
||||
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include <linux/mtd/partitions.h>
|
||||
|
||||
/*
|
||||
* DataFlash is a kind of SPI flash. Most AT45 chips have two buffers in
|
||||
* each chip, which may be used for double buffered I/O; but this driver
|
||||
* doesn't (yet) use these for any kind of i/o overlap or prefetching.
|
||||
*
|
||||
* Sometimes DataFlash is packaged in MMC-format cards, although the
|
||||
* MMC stack can't (yet?) distinguish between MMC and DataFlash
|
||||
* protocols during enumeration.
|
||||
*/
|
||||
|
||||
/* reads can bypass the buffers */
|
||||
#define OP_READ_CONTINUOUS 0xE8
|
||||
#define OP_READ_PAGE 0xD2
|
||||
|
||||
/* group B requests can run even while status reports "busy" */
|
||||
#define OP_READ_STATUS 0xD7 /* group B */
|
||||
|
||||
/* move data between host and buffer */
|
||||
#define OP_READ_BUFFER1 0xD4 /* group B */
|
||||
#define OP_READ_BUFFER2 0xD6 /* group B */
|
||||
#define OP_WRITE_BUFFER1 0x84 /* group B */
|
||||
#define OP_WRITE_BUFFER2 0x87 /* group B */
|
||||
|
||||
/* erasing flash */
|
||||
#define OP_ERASE_PAGE 0x81
|
||||
#define OP_ERASE_BLOCK 0x50
|
||||
|
||||
/* move data between buffer and flash */
|
||||
#define OP_TRANSFER_BUF1 0x53
|
||||
#define OP_TRANSFER_BUF2 0x55
|
||||
#define OP_MREAD_BUFFER1 0xD4
|
||||
#define OP_MREAD_BUFFER2 0xD6
|
||||
#define OP_MWERASE_BUFFER1 0x83
|
||||
#define OP_MWERASE_BUFFER2 0x86
|
||||
#define OP_MWRITE_BUFFER1 0x88 /* sector must be pre-erased */
|
||||
#define OP_MWRITE_BUFFER2 0x89 /* sector must be pre-erased */
|
||||
|
||||
/* write to buffer, then write-erase to flash */
|
||||
#define OP_PROGRAM_VIA_BUF1 0x82
|
||||
#define OP_PROGRAM_VIA_BUF2 0x85
|
||||
|
||||
/* compare buffer to flash */
|
||||
#define OP_COMPARE_BUF1 0x60
|
||||
#define OP_COMPARE_BUF2 0x61
|
||||
|
||||
/* read flash to buffer, then write-erase to flash */
|
||||
#define OP_REWRITE_VIA_BUF1 0x58
|
||||
#define OP_REWRITE_VIA_BUF2 0x59
|
||||
|
||||
/* newer chips report JEDEC manufacturer and device IDs; chip
|
||||
* serial number and OTP bits; and per-sector writeprotect.
|
||||
*/
|
||||
#define OP_READ_ID 0x9F
|
||||
#define OP_READ_SECURITY 0x77
|
||||
#define OP_WRITE_SECURITY_REVC 0x9A
|
||||
#define OP_WRITE_SECURITY 0x9B /* revision D */
|
||||
|
||||
|
||||
struct dataflash {
|
||||
uint8_t command[4];
|
||||
char name[24];
|
||||
|
||||
unsigned short page_offset; /* offset in flash address */
|
||||
unsigned int page_size; /* of bytes per page */
|
||||
|
||||
struct mutex lock;
|
||||
struct spi_device *spi;
|
||||
|
||||
struct mtd_info mtd;
|
||||
};
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
static const struct of_device_id dataflash_dt_ids[] = {
|
||||
{ .compatible = "atmel,at45", },
|
||||
{ .compatible = "atmel,dataflash", },
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
#endif
|
||||
|
||||
/* ......................................................................... */
|
||||
|
||||
/*
|
||||
* Return the status of the DataFlash device.
|
||||
*/
|
||||
static inline int dataflash_status(struct spi_device *spi)
|
||||
{
|
||||
/* NOTE: at45db321c over 25 MHz wants to write
|
||||
* a dummy byte after the opcode...
|
||||
*/
|
||||
return spi_w8r8(spi, OP_READ_STATUS);
|
||||
}
|
||||
|
||||
/*
|
||||
* Poll the DataFlash device until it is READY.
|
||||
* This usually takes 5-20 msec or so; more for sector erase.
|
||||
*/
|
||||
static int dataflash_waitready(struct spi_device *spi)
|
||||
{
|
||||
int status;
|
||||
|
||||
for (;;) {
|
||||
status = dataflash_status(spi);
|
||||
if (status < 0) {
|
||||
pr_debug("%s: status %d?\n",
|
||||
dev_name(&spi->dev), status);
|
||||
status = 0;
|
||||
}
|
||||
|
||||
if (status & (1 << 7)) /* RDY/nBSY */
|
||||
return status;
|
||||
|
||||
msleep(3);
|
||||
}
|
||||
}
|
||||
|
||||
/* ......................................................................... */
|
||||
|
||||
/*
|
||||
* Erase pages of flash.
|
||||
*/
|
||||
static int dataflash_erase(struct mtd_info *mtd, struct erase_info *instr)
|
||||
{
|
||||
struct dataflash *priv = mtd->priv;
|
||||
struct spi_device *spi = priv->spi;
|
||||
struct spi_transfer x = { .tx_dma = 0, };
|
||||
struct spi_message msg;
|
||||
unsigned blocksize = priv->page_size << 3;
|
||||
uint8_t *command;
|
||||
uint32_t rem;
|
||||
|
||||
pr_debug("%s: erase addr=0x%llx len 0x%llx\n",
|
||||
dev_name(&spi->dev), (long long)instr->addr,
|
||||
(long long)instr->len);
|
||||
|
||||
div_u64_rem(instr->len, priv->page_size, &rem);
|
||||
if (rem)
|
||||
return -EINVAL;
|
||||
div_u64_rem(instr->addr, priv->page_size, &rem);
|
||||
if (rem)
|
||||
return -EINVAL;
|
||||
|
||||
spi_message_init(&msg);
|
||||
|
||||
x.tx_buf = command = priv->command;
|
||||
x.len = 4;
|
||||
spi_message_add_tail(&x, &msg);
|
||||
|
||||
mutex_lock(&priv->lock);
|
||||
while (instr->len > 0) {
|
||||
unsigned int pageaddr;
|
||||
int status;
|
||||
int do_block;
|
||||
|
||||
/* Calculate flash page address; use block erase (for speed) if
|
||||
* we're at a block boundary and need to erase the whole block.
|
||||
*/
|
||||
pageaddr = div_u64(instr->addr, priv->page_size);
|
||||
do_block = (pageaddr & 0x7) == 0 && instr->len >= blocksize;
|
||||
pageaddr = pageaddr << priv->page_offset;
|
||||
|
||||
command[0] = do_block ? OP_ERASE_BLOCK : OP_ERASE_PAGE;
|
||||
command[1] = (uint8_t)(pageaddr >> 16);
|
||||
command[2] = (uint8_t)(pageaddr >> 8);
|
||||
command[3] = 0;
|
||||
|
||||
pr_debug("ERASE %s: (%x) %x %x %x [%i]\n",
|
||||
do_block ? "block" : "page",
|
||||
command[0], command[1], command[2], command[3],
|
||||
pageaddr);
|
||||
|
||||
status = spi_sync(spi, &msg);
|
||||
(void) dataflash_waitready(spi);
|
||||
|
||||
if (status < 0) {
|
||||
printk(KERN_ERR "%s: erase %x, err %d\n",
|
||||
dev_name(&spi->dev), pageaddr, status);
|
||||
/* REVISIT: can retry instr->retries times; or
|
||||
* giveup and instr->fail_addr = instr->addr;
|
||||
*/
|
||||
continue;
|
||||
}
|
||||
|
||||
if (do_block) {
|
||||
instr->addr += blocksize;
|
||||
instr->len -= blocksize;
|
||||
} else {
|
||||
instr->addr += priv->page_size;
|
||||
instr->len -= priv->page_size;
|
||||
}
|
||||
}
|
||||
mutex_unlock(&priv->lock);
|
||||
|
||||
/* Inform MTD subsystem that erase is complete */
|
||||
instr->state = MTD_ERASE_DONE;
|
||||
mtd_erase_callback(instr);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Read from the DataFlash device.
|
||||
* from : Start offset in flash device
|
||||
* len : Amount to read
|
||||
* retlen : About of data actually read
|
||||
* buf : Buffer containing the data
|
||||
*/
|
||||
static int dataflash_read(struct mtd_info *mtd, loff_t from, size_t len,
|
||||
size_t *retlen, u_char *buf)
|
||||
{
|
||||
struct dataflash *priv = mtd->priv;
|
||||
struct spi_transfer x[2] = { { .tx_dma = 0, }, };
|
||||
struct spi_message msg;
|
||||
unsigned int addr;
|
||||
uint8_t *command;
|
||||
int status;
|
||||
|
||||
pr_debug("%s: read 0x%x..0x%x\n", dev_name(&priv->spi->dev),
|
||||
(unsigned)from, (unsigned)(from + len));
|
||||
|
||||
/* Calculate flash page/byte address */
|
||||
addr = (((unsigned)from / priv->page_size) << priv->page_offset)
|
||||
+ ((unsigned)from % priv->page_size);
|
||||
|
||||
command = priv->command;
|
||||
|
||||
pr_debug("READ: (%x) %x %x %x\n",
|
||||
command[0], command[1], command[2], command[3]);
|
||||
|
||||
spi_message_init(&msg);
|
||||
|
||||
x[0].tx_buf = command;
|
||||
x[0].len = 8;
|
||||
spi_message_add_tail(&x[0], &msg);
|
||||
|
||||
x[1].rx_buf = buf;
|
||||
x[1].len = len;
|
||||
spi_message_add_tail(&x[1], &msg);
|
||||
|
||||
mutex_lock(&priv->lock);
|
||||
|
||||
/* Continuous read, max clock = f(car) which may be less than
|
||||
* the peak rate available. Some chips support commands with
|
||||
* fewer "don't care" bytes. Both buffers stay unchanged.
|
||||
*/
|
||||
command[0] = OP_READ_CONTINUOUS;
|
||||
command[1] = (uint8_t)(addr >> 16);
|
||||
command[2] = (uint8_t)(addr >> 8);
|
||||
command[3] = (uint8_t)(addr >> 0);
|
||||
/* plus 4 "don't care" bytes */
|
||||
|
||||
status = spi_sync(priv->spi, &msg);
|
||||
mutex_unlock(&priv->lock);
|
||||
|
||||
if (status >= 0) {
|
||||
*retlen = msg.actual_length - 8;
|
||||
status = 0;
|
||||
} else
|
||||
pr_debug("%s: read %x..%x --> %d\n",
|
||||
dev_name(&priv->spi->dev),
|
||||
(unsigned)from, (unsigned)(from + len),
|
||||
status);
|
||||
return status;
|
||||
}
|
||||
|
||||
/*
|
||||
* Write to the DataFlash device.
|
||||
* to : Start offset in flash device
|
||||
* len : Amount to write
|
||||
* retlen : Amount of data actually written
|
||||
* buf : Buffer containing the data
|
||||
*/
|
||||
static int dataflash_write(struct mtd_info *mtd, loff_t to, size_t len,
|
||||
size_t * retlen, const u_char * buf)
|
||||
{
|
||||
struct dataflash *priv = mtd->priv;
|
||||
struct spi_device *spi = priv->spi;
|
||||
struct spi_transfer x[2] = { { .tx_dma = 0, }, };
|
||||
struct spi_message msg;
|
||||
unsigned int pageaddr, addr, offset, writelen;
|
||||
size_t remaining = len;
|
||||
u_char *writebuf = (u_char *) buf;
|
||||
int status = -EINVAL;
|
||||
uint8_t *command;
|
||||
|
||||
pr_debug("%s: write 0x%x..0x%x\n",
|
||||
dev_name(&spi->dev), (unsigned)to, (unsigned)(to + len));
|
||||
|
||||
spi_message_init(&msg);
|
||||
|
||||
x[0].tx_buf = command = priv->command;
|
||||
x[0].len = 4;
|
||||
spi_message_add_tail(&x[0], &msg);
|
||||
|
||||
pageaddr = ((unsigned)to / priv->page_size);
|
||||
offset = ((unsigned)to % priv->page_size);
|
||||
if (offset + len > priv->page_size)
|
||||
writelen = priv->page_size - offset;
|
||||
else
|
||||
writelen = len;
|
||||
|
||||
mutex_lock(&priv->lock);
|
||||
while (remaining > 0) {
|
||||
pr_debug("write @ %i:%i len=%i\n",
|
||||
pageaddr, offset, writelen);
|
||||
|
||||
/* REVISIT:
|
||||
* (a) each page in a sector must be rewritten at least
|
||||
* once every 10K sibling erase/program operations.
|
||||
* (b) for pages that are already erased, we could
|
||||
* use WRITE+MWRITE not PROGRAM for ~30% speedup.
|
||||
* (c) WRITE to buffer could be done while waiting for
|
||||
* a previous MWRITE/MWERASE to complete ...
|
||||
* (d) error handling here seems to be mostly missing.
|
||||
*
|
||||
* Two persistent bits per page, plus a per-sector counter,
|
||||
* could support (a) and (b) ... we might consider using
|
||||
* the second half of sector zero, which is just one block,
|
||||
* to track that state. (On AT91, that sector should also
|
||||
* support boot-from-DataFlash.)
|
||||
*/
|
||||
|
||||
addr = pageaddr << priv->page_offset;
|
||||
|
||||
/* (1) Maybe transfer partial page to Buffer1 */
|
||||
if (writelen != priv->page_size) {
|
||||
command[0] = OP_TRANSFER_BUF1;
|
||||
command[1] = (addr & 0x00FF0000) >> 16;
|
||||
command[2] = (addr & 0x0000FF00) >> 8;
|
||||
command[3] = 0;
|
||||
|
||||
pr_debug("TRANSFER: (%x) %x %x %x\n",
|
||||
command[0], command[1], command[2], command[3]);
|
||||
|
||||
status = spi_sync(spi, &msg);
|
||||
if (status < 0)
|
||||
pr_debug("%s: xfer %u -> %d\n",
|
||||
dev_name(&spi->dev), addr, status);
|
||||
|
||||
(void) dataflash_waitready(priv->spi);
|
||||
}
|
||||
|
||||
/* (2) Program full page via Buffer1 */
|
||||
addr += offset;
|
||||
command[0] = OP_PROGRAM_VIA_BUF1;
|
||||
command[1] = (addr & 0x00FF0000) >> 16;
|
||||
command[2] = (addr & 0x0000FF00) >> 8;
|
||||
command[3] = (addr & 0x000000FF);
|
||||
|
||||
pr_debug("PROGRAM: (%x) %x %x %x\n",
|
||||
command[0], command[1], command[2], command[3]);
|
||||
|
||||
x[1].tx_buf = writebuf;
|
||||
x[1].len = writelen;
|
||||
spi_message_add_tail(x + 1, &msg);
|
||||
status = spi_sync(spi, &msg);
|
||||
spi_transfer_del(x + 1);
|
||||
if (status < 0)
|
||||
pr_debug("%s: pgm %u/%u -> %d\n",
|
||||
dev_name(&spi->dev), addr, writelen, status);
|
||||
|
||||
(void) dataflash_waitready(priv->spi);
|
||||
|
||||
|
||||
#ifdef CONFIG_MTD_DATAFLASH_WRITE_VERIFY
|
||||
|
||||
/* (3) Compare to Buffer1 */
|
||||
addr = pageaddr << priv->page_offset;
|
||||
command[0] = OP_COMPARE_BUF1;
|
||||
command[1] = (addr & 0x00FF0000) >> 16;
|
||||
command[2] = (addr & 0x0000FF00) >> 8;
|
||||
command[3] = 0;
|
||||
|
||||
pr_debug("COMPARE: (%x) %x %x %x\n",
|
||||
command[0], command[1], command[2], command[3]);
|
||||
|
||||
status = spi_sync(spi, &msg);
|
||||
if (status < 0)
|
||||
pr_debug("%s: compare %u -> %d\n",
|
||||
dev_name(&spi->dev), addr, status);
|
||||
|
||||
status = dataflash_waitready(priv->spi);
|
||||
|
||||
/* Check result of the compare operation */
|
||||
if (status & (1 << 6)) {
|
||||
printk(KERN_ERR "%s: compare page %u, err %d\n",
|
||||
dev_name(&spi->dev), pageaddr, status);
|
||||
remaining = 0;
|
||||
status = -EIO;
|
||||
break;
|
||||
} else
|
||||
status = 0;
|
||||
|
||||
#endif /* CONFIG_MTD_DATAFLASH_WRITE_VERIFY */
|
||||
|
||||
remaining = remaining - writelen;
|
||||
pageaddr++;
|
||||
offset = 0;
|
||||
writebuf += writelen;
|
||||
*retlen += writelen;
|
||||
|
||||
if (remaining > priv->page_size)
|
||||
writelen = priv->page_size;
|
||||
else
|
||||
writelen = remaining;
|
||||
}
|
||||
mutex_unlock(&priv->lock);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/* ......................................................................... */
|
||||
|
||||
#ifdef CONFIG_MTD_DATAFLASH_OTP
|
||||
|
||||
static int dataflash_get_otp_info(struct mtd_info *mtd, size_t len,
|
||||
size_t *retlen, struct otp_info *info)
|
||||
{
|
||||
/* Report both blocks as identical: bytes 0..64, locked.
|
||||
* Unless the user block changed from all-ones, we can't
|
||||
* tell whether it's still writable; so we assume it isn't.
|
||||
*/
|
||||
info->start = 0;
|
||||
info->length = 64;
|
||||
info->locked = 1;
|
||||
*retlen = sizeof(*info);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t otp_read(struct spi_device *spi, unsigned base,
|
||||
uint8_t *buf, loff_t off, size_t len)
|
||||
{
|
||||
struct spi_message m;
|
||||
size_t l;
|
||||
uint8_t *scratch;
|
||||
struct spi_transfer t;
|
||||
int status;
|
||||
|
||||
if (off > 64)
|
||||
return -EINVAL;
|
||||
|
||||
if ((off + len) > 64)
|
||||
len = 64 - off;
|
||||
|
||||
spi_message_init(&m);
|
||||
|
||||
l = 4 + base + off + len;
|
||||
scratch = kzalloc(l, GFP_KERNEL);
|
||||
if (!scratch)
|
||||
return -ENOMEM;
|
||||
|
||||
/* OUT: OP_READ_SECURITY, 3 don't-care bytes, zeroes
|
||||
* IN: ignore 4 bytes, data bytes 0..N (max 127)
|
||||
*/
|
||||
scratch[0] = OP_READ_SECURITY;
|
||||
|
||||
memset(&t, 0, sizeof t);
|
||||
t.tx_buf = scratch;
|
||||
t.rx_buf = scratch;
|
||||
t.len = l;
|
||||
spi_message_add_tail(&t, &m);
|
||||
|
||||
dataflash_waitready(spi);
|
||||
|
||||
status = spi_sync(spi, &m);
|
||||
if (status >= 0) {
|
||||
memcpy(buf, scratch + 4 + base + off, len);
|
||||
status = len;
|
||||
}
|
||||
|
||||
kfree(scratch);
|
||||
return status;
|
||||
}
|
||||
|
||||
static int dataflash_read_fact_otp(struct mtd_info *mtd,
|
||||
loff_t from, size_t len, size_t *retlen, u_char *buf)
|
||||
{
|
||||
struct dataflash *priv = mtd->priv;
|
||||
int status;
|
||||
|
||||
/* 64 bytes, from 0..63 ... start at 64 on-chip */
|
||||
mutex_lock(&priv->lock);
|
||||
status = otp_read(priv->spi, 64, buf, from, len);
|
||||
mutex_unlock(&priv->lock);
|
||||
|
||||
if (status < 0)
|
||||
return status;
|
||||
*retlen = status;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dataflash_read_user_otp(struct mtd_info *mtd,
|
||||
loff_t from, size_t len, size_t *retlen, u_char *buf)
|
||||
{
|
||||
struct dataflash *priv = mtd->priv;
|
||||
int status;
|
||||
|
||||
/* 64 bytes, from 0..63 ... start at 0 on-chip */
|
||||
mutex_lock(&priv->lock);
|
||||
status = otp_read(priv->spi, 0, buf, from, len);
|
||||
mutex_unlock(&priv->lock);
|
||||
|
||||
if (status < 0)
|
||||
return status;
|
||||
*retlen = status;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dataflash_write_user_otp(struct mtd_info *mtd,
|
||||
loff_t from, size_t len, size_t *retlen, u_char *buf)
|
||||
{
|
||||
struct spi_message m;
|
||||
const size_t l = 4 + 64;
|
||||
uint8_t *scratch;
|
||||
struct spi_transfer t;
|
||||
struct dataflash *priv = mtd->priv;
|
||||
int status;
|
||||
|
||||
if (from >= 64) {
|
||||
/*
|
||||
* Attempting to write beyond the end of OTP memory,
|
||||
* no data can be written.
|
||||
*/
|
||||
*retlen = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Truncate the write to fit into OTP memory. */
|
||||
if ((from + len) > 64)
|
||||
len = 64 - from;
|
||||
|
||||
/* OUT: OP_WRITE_SECURITY, 3 zeroes, 64 data-or-zero bytes
|
||||
* IN: ignore all
|
||||
*/
|
||||
scratch = kzalloc(l, GFP_KERNEL);
|
||||
if (!scratch)
|
||||
return -ENOMEM;
|
||||
scratch[0] = OP_WRITE_SECURITY;
|
||||
memcpy(scratch + 4 + from, buf, len);
|
||||
|
||||
spi_message_init(&m);
|
||||
|
||||
memset(&t, 0, sizeof t);
|
||||
t.tx_buf = scratch;
|
||||
t.len = l;
|
||||
spi_message_add_tail(&t, &m);
|
||||
|
||||
/* Write the OTP bits, if they've not yet been written.
|
||||
* This modifies SRAM buffer1.
|
||||
*/
|
||||
mutex_lock(&priv->lock);
|
||||
dataflash_waitready(priv->spi);
|
||||
status = spi_sync(priv->spi, &m);
|
||||
mutex_unlock(&priv->lock);
|
||||
|
||||
kfree(scratch);
|
||||
|
||||
if (status >= 0) {
|
||||
status = 0;
|
||||
*retlen = len;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
static char *otp_setup(struct mtd_info *device, char revision)
|
||||
{
|
||||
device->_get_fact_prot_info = dataflash_get_otp_info;
|
||||
device->_read_fact_prot_reg = dataflash_read_fact_otp;
|
||||
device->_get_user_prot_info = dataflash_get_otp_info;
|
||||
device->_read_user_prot_reg = dataflash_read_user_otp;
|
||||
|
||||
/* rev c parts (at45db321c and at45db1281 only!) use a
|
||||
* different write procedure; not (yet?) implemented.
|
||||
*/
|
||||
if (revision > 'c')
|
||||
device->_write_user_prot_reg = dataflash_write_user_otp;
|
||||
|
||||
return ", OTP";
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
static char *otp_setup(struct mtd_info *device, char revision)
|
||||
{
|
||||
return " (OTP)";
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/* ......................................................................... */
|
||||
|
||||
/*
|
||||
* Register DataFlash device with MTD subsystem.
|
||||
*/
|
||||
static int add_dataflash_otp(struct spi_device *spi, char *name, int nr_pages,
|
||||
int pagesize, int pageoffset, char revision)
|
||||
{
|
||||
struct dataflash *priv;
|
||||
struct mtd_info *device;
|
||||
struct mtd_part_parser_data ppdata;
|
||||
struct flash_platform_data *pdata = dev_get_platdata(&spi->dev);
|
||||
char *otp_tag = "";
|
||||
int err = 0;
|
||||
|
||||
priv = kzalloc(sizeof *priv, GFP_KERNEL);
|
||||
if (!priv)
|
||||
return -ENOMEM;
|
||||
|
||||
mutex_init(&priv->lock);
|
||||
priv->spi = spi;
|
||||
priv->page_size = pagesize;
|
||||
priv->page_offset = pageoffset;
|
||||
|
||||
/* name must be usable with cmdlinepart */
|
||||
sprintf(priv->name, "spi%d.%d-%s",
|
||||
spi->master->bus_num, spi->chip_select,
|
||||
name);
|
||||
|
||||
device = &priv->mtd;
|
||||
device->name = (pdata && pdata->name) ? pdata->name : priv->name;
|
||||
device->size = nr_pages * pagesize;
|
||||
device->erasesize = pagesize;
|
||||
device->writesize = pagesize;
|
||||
device->owner = THIS_MODULE;
|
||||
device->type = MTD_DATAFLASH;
|
||||
device->flags = MTD_WRITEABLE;
|
||||
device->_erase = dataflash_erase;
|
||||
device->_read = dataflash_read;
|
||||
device->_write = dataflash_write;
|
||||
device->priv = priv;
|
||||
|
||||
device->dev.parent = &spi->dev;
|
||||
|
||||
if (revision >= 'c')
|
||||
otp_tag = otp_setup(device, revision);
|
||||
|
||||
dev_info(&spi->dev, "%s (%lld KBytes) pagesize %d bytes%s\n",
|
||||
name, (long long)((device->size + 1023) >> 10),
|
||||
pagesize, otp_tag);
|
||||
spi_set_drvdata(spi, priv);
|
||||
|
||||
ppdata.of_node = spi->dev.of_node;
|
||||
err = mtd_device_parse_register(device, NULL, &ppdata,
|
||||
pdata ? pdata->parts : NULL,
|
||||
pdata ? pdata->nr_parts : 0);
|
||||
|
||||
if (!err)
|
||||
return 0;
|
||||
|
||||
kfree(priv);
|
||||
return err;
|
||||
}
|
||||
|
||||
static inline int add_dataflash(struct spi_device *spi, char *name,
|
||||
int nr_pages, int pagesize, int pageoffset)
|
||||
{
|
||||
return add_dataflash_otp(spi, name, nr_pages, pagesize,
|
||||
pageoffset, 0);
|
||||
}
|
||||
|
||||
struct flash_info {
|
||||
char *name;
|
||||
|
||||
/* JEDEC id has a high byte of zero plus three data bytes:
|
||||
* the manufacturer id, then a two byte device id.
|
||||
*/
|
||||
uint32_t jedec_id;
|
||||
|
||||
/* The size listed here is what works with OP_ERASE_PAGE. */
|
||||
unsigned nr_pages;
|
||||
uint16_t pagesize;
|
||||
uint16_t pageoffset;
|
||||
|
||||
uint16_t flags;
|
||||
#define SUP_POW2PS 0x0002 /* supports 2^N byte pages */
|
||||
#define IS_POW2PS 0x0001 /* uses 2^N byte pages */
|
||||
};
|
||||
|
||||
static struct flash_info dataflash_data[] = {
|
||||
|
||||
/*
|
||||
* NOTE: chips with SUP_POW2PS (rev D and up) need two entries,
|
||||
* one with IS_POW2PS and the other without. The entry with the
|
||||
* non-2^N byte page size can't name exact chip revisions without
|
||||
* losing backwards compatibility for cmdlinepart.
|
||||
*
|
||||
* These newer chips also support 128-byte security registers (with
|
||||
* 64 bytes one-time-programmable) and software write-protection.
|
||||
*/
|
||||
{ "AT45DB011B", 0x1f2200, 512, 264, 9, SUP_POW2PS},
|
||||
{ "at45db011d", 0x1f2200, 512, 256, 8, SUP_POW2PS | IS_POW2PS},
|
||||
|
||||
{ "AT45DB021B", 0x1f2300, 1024, 264, 9, SUP_POW2PS},
|
||||
{ "at45db021d", 0x1f2300, 1024, 256, 8, SUP_POW2PS | IS_POW2PS},
|
||||
|
||||
{ "AT45DB041x", 0x1f2400, 2048, 264, 9, SUP_POW2PS},
|
||||
{ "at45db041d", 0x1f2400, 2048, 256, 8, SUP_POW2PS | IS_POW2PS},
|
||||
|
||||
{ "AT45DB081B", 0x1f2500, 4096, 264, 9, SUP_POW2PS},
|
||||
{ "at45db081d", 0x1f2500, 4096, 256, 8, SUP_POW2PS | IS_POW2PS},
|
||||
|
||||
{ "AT45DB161x", 0x1f2600, 4096, 528, 10, SUP_POW2PS},
|
||||
{ "at45db161d", 0x1f2600, 4096, 512, 9, SUP_POW2PS | IS_POW2PS},
|
||||
|
||||
{ "AT45DB321x", 0x1f2700, 8192, 528, 10, 0}, /* rev C */
|
||||
|
||||
{ "AT45DB321x", 0x1f2701, 8192, 528, 10, SUP_POW2PS},
|
||||
{ "at45db321d", 0x1f2701, 8192, 512, 9, SUP_POW2PS | IS_POW2PS},
|
||||
|
||||
{ "AT45DB642x", 0x1f2800, 8192, 1056, 11, SUP_POW2PS},
|
||||
{ "at45db642d", 0x1f2800, 8192, 1024, 10, SUP_POW2PS | IS_POW2PS},
|
||||
};
|
||||
|
||||
static struct flash_info *jedec_probe(struct spi_device *spi)
|
||||
{
|
||||
int tmp;
|
||||
uint8_t code = OP_READ_ID;
|
||||
uint8_t id[3];
|
||||
uint32_t jedec;
|
||||
struct flash_info *info;
|
||||
int status;
|
||||
|
||||
/* JEDEC also defines an optional "extended device information"
|
||||
* string for after vendor-specific data, after the three bytes
|
||||
* we use here. Supporting some chips might require using it.
|
||||
*
|
||||
* If the vendor ID isn't Atmel's (0x1f), assume this call failed.
|
||||
* That's not an error; only rev C and newer chips handle it, and
|
||||
* only Atmel sells these chips.
|
||||
*/
|
||||
tmp = spi_write_then_read(spi, &code, 1, id, 3);
|
||||
if (tmp < 0) {
|
||||
pr_debug("%s: error %d reading JEDEC ID\n",
|
||||
dev_name(&spi->dev), tmp);
|
||||
return ERR_PTR(tmp);
|
||||
}
|
||||
if (id[0] != 0x1f)
|
||||
return NULL;
|
||||
|
||||
jedec = id[0];
|
||||
jedec = jedec << 8;
|
||||
jedec |= id[1];
|
||||
jedec = jedec << 8;
|
||||
jedec |= id[2];
|
||||
|
||||
for (tmp = 0, info = dataflash_data;
|
||||
tmp < ARRAY_SIZE(dataflash_data);
|
||||
tmp++, info++) {
|
||||
if (info->jedec_id == jedec) {
|
||||
pr_debug("%s: OTP, sector protect%s\n",
|
||||
dev_name(&spi->dev),
|
||||
(info->flags & SUP_POW2PS)
|
||||
? ", binary pagesize" : ""
|
||||
);
|
||||
if (info->flags & SUP_POW2PS) {
|
||||
status = dataflash_status(spi);
|
||||
if (status < 0) {
|
||||
pr_debug("%s: status error %d\n",
|
||||
dev_name(&spi->dev), status);
|
||||
return ERR_PTR(status);
|
||||
}
|
||||
if (status & 0x1) {
|
||||
if (info->flags & IS_POW2PS)
|
||||
return info;
|
||||
} else {
|
||||
if (!(info->flags & IS_POW2PS))
|
||||
return info;
|
||||
}
|
||||
} else
|
||||
return info;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Treat other chips as errors ... we won't know the right page
|
||||
* size (it might be binary) even when we can tell which density
|
||||
* class is involved (legacy chip id scheme).
|
||||
*/
|
||||
dev_warn(&spi->dev, "JEDEC id %06x not handled\n", jedec);
|
||||
return ERR_PTR(-ENODEV);
|
||||
}
|
||||
|
||||
/*
|
||||
* Detect and initialize DataFlash device, using JEDEC IDs on newer chips
|
||||
* or else the ID code embedded in the status bits:
|
||||
*
|
||||
* Device Density ID code #Pages PageSize Offset
|
||||
* AT45DB011B 1Mbit (128K) xx0011xx (0x0c) 512 264 9
|
||||
* AT45DB021B 2Mbit (256K) xx0101xx (0x14) 1024 264 9
|
||||
* AT45DB041B 4Mbit (512K) xx0111xx (0x1c) 2048 264 9
|
||||
* AT45DB081B 8Mbit (1M) xx1001xx (0x24) 4096 264 9
|
||||
* AT45DB0161B 16Mbit (2M) xx1011xx (0x2c) 4096 528 10
|
||||
* AT45DB0321B 32Mbit (4M) xx1101xx (0x34) 8192 528 10
|
||||
* AT45DB0642 64Mbit (8M) xx111xxx (0x3c) 8192 1056 11
|
||||
* AT45DB1282 128Mbit (16M) xx0100xx (0x10) 16384 1056 11
|
||||
*/
|
||||
static int dataflash_probe(struct spi_device *spi)
|
||||
{
|
||||
int status;
|
||||
struct flash_info *info;
|
||||
|
||||
/*
|
||||
* Try to detect dataflash by JEDEC ID.
|
||||
* If it succeeds we know we have either a C or D part.
|
||||
* D will support power of 2 pagesize option.
|
||||
* Both support the security register, though with different
|
||||
* write procedures.
|
||||
*/
|
||||
info = jedec_probe(spi);
|
||||
if (IS_ERR(info))
|
||||
return PTR_ERR(info);
|
||||
if (info != NULL)
|
||||
return add_dataflash_otp(spi, info->name, info->nr_pages,
|
||||
info->pagesize, info->pageoffset,
|
||||
(info->flags & SUP_POW2PS) ? 'd' : 'c');
|
||||
|
||||
/*
|
||||
* Older chips support only legacy commands, identifing
|
||||
* capacity using bits in the status byte.
|
||||
*/
|
||||
status = dataflash_status(spi);
|
||||
if (status <= 0 || status == 0xff) {
|
||||
pr_debug("%s: status error %d\n",
|
||||
dev_name(&spi->dev), status);
|
||||
if (status == 0 || status == 0xff)
|
||||
status = -ENODEV;
|
||||
return status;
|
||||
}
|
||||
|
||||
/* if there's a device there, assume it's dataflash.
|
||||
* board setup should have set spi->max_speed_max to
|
||||
* match f(car) for continuous reads, mode 0 or 3.
|
||||
*/
|
||||
switch (status & 0x3c) {
|
||||
case 0x0c: /* 0 0 1 1 x x */
|
||||
status = add_dataflash(spi, "AT45DB011B", 512, 264, 9);
|
||||
break;
|
||||
case 0x14: /* 0 1 0 1 x x */
|
||||
status = add_dataflash(spi, "AT45DB021B", 1024, 264, 9);
|
||||
break;
|
||||
case 0x1c: /* 0 1 1 1 x x */
|
||||
status = add_dataflash(spi, "AT45DB041x", 2048, 264, 9);
|
||||
break;
|
||||
case 0x24: /* 1 0 0 1 x x */
|
||||
status = add_dataflash(spi, "AT45DB081B", 4096, 264, 9);
|
||||
break;
|
||||
case 0x2c: /* 1 0 1 1 x x */
|
||||
status = add_dataflash(spi, "AT45DB161x", 4096, 528, 10);
|
||||
break;
|
||||
case 0x34: /* 1 1 0 1 x x */
|
||||
status = add_dataflash(spi, "AT45DB321x", 8192, 528, 10);
|
||||
break;
|
||||
case 0x38: /* 1 1 1 x x x */
|
||||
case 0x3c:
|
||||
status = add_dataflash(spi, "AT45DB642x", 8192, 1056, 11);
|
||||
break;
|
||||
/* obsolete AT45DB1282 not (yet?) supported */
|
||||
default:
|
||||
dev_info(&spi->dev, "unsupported device (%x)\n",
|
||||
status & 0x3c);
|
||||
status = -ENODEV;
|
||||
}
|
||||
|
||||
if (status < 0)
|
||||
pr_debug("%s: add_dataflash --> %d\n", dev_name(&spi->dev),
|
||||
status);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static int dataflash_remove(struct spi_device *spi)
|
||||
{
|
||||
struct dataflash *flash = spi_get_drvdata(spi);
|
||||
int status;
|
||||
|
||||
pr_debug("%s: remove\n", dev_name(&spi->dev));
|
||||
|
||||
status = mtd_device_unregister(&flash->mtd);
|
||||
if (status == 0)
|
||||
kfree(flash);
|
||||
return status;
|
||||
}
|
||||
|
||||
static struct spi_driver dataflash_driver = {
|
||||
.driver = {
|
||||
.name = "mtd_dataflash",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = of_match_ptr(dataflash_dt_ids),
|
||||
},
|
||||
|
||||
.probe = dataflash_probe,
|
||||
.remove = dataflash_remove,
|
||||
|
||||
/* FIXME: investigate suspend and resume... */
|
||||
};
|
||||
|
||||
module_spi_driver(dataflash_driver);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Andrew Victor, David Brownell");
|
||||
MODULE_DESCRIPTION("MTD DataFlash driver");
|
||||
MODULE_ALIAS("spi:mtd_dataflash");
|
158
drivers/mtd/devices/mtdram.c
Normal file
158
drivers/mtd/devices/mtdram.c
Normal file
|
@ -0,0 +1,158 @@
|
|||
/*
|
||||
* mtdram - a test mtd device
|
||||
* Author: Alexander Larsson <alex@cendio.se>
|
||||
*
|
||||
* Copyright (c) 1999 Alexander Larsson <alex@cendio.se>
|
||||
* Copyright (c) 2005 Joern Engel <joern@wh.fh-wedel.de>
|
||||
*
|
||||
* This code is GPL
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/ioport.h>
|
||||
#include <linux/vmalloc.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include <linux/mtd/mtdram.h>
|
||||
|
||||
static unsigned long total_size = CONFIG_MTDRAM_TOTAL_SIZE;
|
||||
static unsigned long erase_size = CONFIG_MTDRAM_ERASE_SIZE;
|
||||
#define MTDRAM_TOTAL_SIZE (total_size * 1024)
|
||||
#define MTDRAM_ERASE_SIZE (erase_size * 1024)
|
||||
|
||||
#ifdef MODULE
|
||||
module_param(total_size, ulong, 0);
|
||||
MODULE_PARM_DESC(total_size, "Total device size in KiB");
|
||||
module_param(erase_size, ulong, 0);
|
||||
MODULE_PARM_DESC(erase_size, "Device erase block size in KiB");
|
||||
#endif
|
||||
|
||||
// We could store these in the mtd structure, but we only support 1 device..
|
||||
static struct mtd_info *mtd_info;
|
||||
|
||||
static int ram_erase(struct mtd_info *mtd, struct erase_info *instr)
|
||||
{
|
||||
memset((char *)mtd->priv + instr->addr, 0xff, instr->len);
|
||||
instr->state = MTD_ERASE_DONE;
|
||||
mtd_erase_callback(instr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ram_point(struct mtd_info *mtd, loff_t from, size_t len,
|
||||
size_t *retlen, void **virt, resource_size_t *phys)
|
||||
{
|
||||
*virt = mtd->priv + from;
|
||||
*retlen = len;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ram_unpoint(struct mtd_info *mtd, loff_t from, size_t len)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Allow NOMMU mmap() to directly map the device (if not NULL)
|
||||
* - return the address to which the offset maps
|
||||
* - return -ENOSYS to indicate refusal to do the mapping
|
||||
*/
|
||||
static unsigned long ram_get_unmapped_area(struct mtd_info *mtd,
|
||||
unsigned long len,
|
||||
unsigned long offset,
|
||||
unsigned long flags)
|
||||
{
|
||||
return (unsigned long) mtd->priv + offset;
|
||||
}
|
||||
|
||||
static int ram_read(struct mtd_info *mtd, loff_t from, size_t len,
|
||||
size_t *retlen, u_char *buf)
|
||||
{
|
||||
memcpy(buf, mtd->priv + from, len);
|
||||
*retlen = len;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ram_write(struct mtd_info *mtd, loff_t to, size_t len,
|
||||
size_t *retlen, const u_char *buf)
|
||||
{
|
||||
memcpy((char *)mtd->priv + to, buf, len);
|
||||
*retlen = len;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __exit cleanup_mtdram(void)
|
||||
{
|
||||
if (mtd_info) {
|
||||
mtd_device_unregister(mtd_info);
|
||||
vfree(mtd_info->priv);
|
||||
kfree(mtd_info);
|
||||
}
|
||||
}
|
||||
|
||||
int mtdram_init_device(struct mtd_info *mtd, void *mapped_address,
|
||||
unsigned long size, const char *name)
|
||||
{
|
||||
memset(mtd, 0, sizeof(*mtd));
|
||||
|
||||
/* Setup the MTD structure */
|
||||
mtd->name = name;
|
||||
mtd->type = MTD_RAM;
|
||||
mtd->flags = MTD_CAP_RAM;
|
||||
mtd->size = size;
|
||||
mtd->writesize = 1;
|
||||
mtd->writebufsize = 64; /* Mimic CFI NOR flashes */
|
||||
mtd->erasesize = MTDRAM_ERASE_SIZE;
|
||||
mtd->priv = mapped_address;
|
||||
|
||||
mtd->owner = THIS_MODULE;
|
||||
mtd->_erase = ram_erase;
|
||||
mtd->_point = ram_point;
|
||||
mtd->_unpoint = ram_unpoint;
|
||||
mtd->_get_unmapped_area = ram_get_unmapped_area;
|
||||
mtd->_read = ram_read;
|
||||
mtd->_write = ram_write;
|
||||
|
||||
if (mtd_device_register(mtd, NULL, 0))
|
||||
return -EIO;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __init init_mtdram(void)
|
||||
{
|
||||
void *addr;
|
||||
int err;
|
||||
|
||||
if (!total_size)
|
||||
return -EINVAL;
|
||||
|
||||
/* Allocate some memory */
|
||||
mtd_info = kmalloc(sizeof(struct mtd_info), GFP_KERNEL);
|
||||
if (!mtd_info)
|
||||
return -ENOMEM;
|
||||
|
||||
addr = vmalloc(MTDRAM_TOTAL_SIZE);
|
||||
if (!addr) {
|
||||
kfree(mtd_info);
|
||||
mtd_info = NULL;
|
||||
return -ENOMEM;
|
||||
}
|
||||
err = mtdram_init_device(mtd_info, addr, MTDRAM_TOTAL_SIZE, "mtdram test device");
|
||||
if (err) {
|
||||
vfree(addr);
|
||||
kfree(mtd_info);
|
||||
mtd_info = NULL;
|
||||
return err;
|
||||
}
|
||||
memset(mtd_info->priv, 0xff, MTDRAM_TOTAL_SIZE);
|
||||
return err;
|
||||
}
|
||||
|
||||
module_init(init_mtdram);
|
||||
module_exit(cleanup_mtdram);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Alexander Larsson <alexl@redhat.com>");
|
||||
MODULE_DESCRIPTION("Simulated MTD driver for testing");
|
328
drivers/mtd/devices/phram.c
Normal file
328
drivers/mtd/devices/phram.c
Normal file
|
@ -0,0 +1,328 @@
|
|||
/**
|
||||
* Copyright (c) ???? Jochen Schäuble <psionic@psionic.de>
|
||||
* Copyright (c) 2003-2004 Joern Engel <joern@wh.fh-wedel.de>
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* one commend line parameter per device, each in the form:
|
||||
* phram=<name>,<start>,<len>
|
||||
* <name> may be up to 63 characters.
|
||||
* <start> and <len> can be octal, decimal or hexadecimal. If followed
|
||||
* by "ki", "Mi" or "Gi", the numbers will be interpreted as kilo, mega or
|
||||
* gigabytes.
|
||||
*
|
||||
* Example:
|
||||
* phram=swap,64Mi,128Mi phram=test,900Mi,1Mi
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
|
||||
#include <asm/io.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/mtd/mtd.h>
|
||||
|
||||
struct phram_mtd_list {
|
||||
struct mtd_info mtd;
|
||||
struct list_head list;
|
||||
};
|
||||
|
||||
static LIST_HEAD(phram_list);
|
||||
|
||||
static int phram_erase(struct mtd_info *mtd, struct erase_info *instr)
|
||||
{
|
||||
u_char *start = mtd->priv;
|
||||
|
||||
memset(start + instr->addr, 0xff, instr->len);
|
||||
|
||||
/*
|
||||
* This'll catch a few races. Free the thing before returning :)
|
||||
* I don't feel at all ashamed. This kind of thing is possible anyway
|
||||
* with flash, but unlikely.
|
||||
*/
|
||||
instr->state = MTD_ERASE_DONE;
|
||||
mtd_erase_callback(instr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int phram_point(struct mtd_info *mtd, loff_t from, size_t len,
|
||||
size_t *retlen, void **virt, resource_size_t *phys)
|
||||
{
|
||||
*virt = mtd->priv + from;
|
||||
*retlen = len;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int phram_unpoint(struct mtd_info *mtd, loff_t from, size_t len)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int phram_read(struct mtd_info *mtd, loff_t from, size_t len,
|
||||
size_t *retlen, u_char *buf)
|
||||
{
|
||||
u_char *start = mtd->priv;
|
||||
|
||||
memcpy(buf, start + from, len);
|
||||
*retlen = len;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int phram_write(struct mtd_info *mtd, loff_t to, size_t len,
|
||||
size_t *retlen, const u_char *buf)
|
||||
{
|
||||
u_char *start = mtd->priv;
|
||||
|
||||
memcpy(start + to, buf, len);
|
||||
*retlen = len;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void unregister_devices(void)
|
||||
{
|
||||
struct phram_mtd_list *this, *safe;
|
||||
|
||||
list_for_each_entry_safe(this, safe, &phram_list, list) {
|
||||
mtd_device_unregister(&this->mtd);
|
||||
iounmap(this->mtd.priv);
|
||||
kfree(this->mtd.name);
|
||||
kfree(this);
|
||||
}
|
||||
}
|
||||
|
||||
static int register_device(char *name, phys_addr_t start, size_t len)
|
||||
{
|
||||
struct phram_mtd_list *new;
|
||||
int ret = -ENOMEM;
|
||||
|
||||
new = kzalloc(sizeof(*new), GFP_KERNEL);
|
||||
if (!new)
|
||||
goto out0;
|
||||
|
||||
ret = -EIO;
|
||||
new->mtd.priv = ioremap(start, len);
|
||||
if (!new->mtd.priv) {
|
||||
pr_err("ioremap failed\n");
|
||||
goto out1;
|
||||
}
|
||||
|
||||
|
||||
new->mtd.name = name;
|
||||
new->mtd.size = len;
|
||||
new->mtd.flags = MTD_CAP_RAM;
|
||||
new->mtd._erase = phram_erase;
|
||||
new->mtd._point = phram_point;
|
||||
new->mtd._unpoint = phram_unpoint;
|
||||
new->mtd._read = phram_read;
|
||||
new->mtd._write = phram_write;
|
||||
new->mtd.owner = THIS_MODULE;
|
||||
new->mtd.type = MTD_RAM;
|
||||
new->mtd.erasesize = PAGE_SIZE;
|
||||
new->mtd.writesize = 1;
|
||||
|
||||
ret = -EAGAIN;
|
||||
if (mtd_device_register(&new->mtd, NULL, 0)) {
|
||||
pr_err("Failed to register new device\n");
|
||||
goto out2;
|
||||
}
|
||||
|
||||
list_add_tail(&new->list, &phram_list);
|
||||
return 0;
|
||||
|
||||
out2:
|
||||
iounmap(new->mtd.priv);
|
||||
out1:
|
||||
kfree(new);
|
||||
out0:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int parse_num64(uint64_t *num64, char *token)
|
||||
{
|
||||
size_t len;
|
||||
int shift = 0;
|
||||
int ret;
|
||||
|
||||
len = strlen(token);
|
||||
/* By dwmw2 editorial decree, "ki", "Mi" or "Gi" are to be used. */
|
||||
if (len > 2) {
|
||||
if (token[len - 1] == 'i') {
|
||||
switch (token[len - 2]) {
|
||||
case 'G':
|
||||
shift += 10;
|
||||
case 'M':
|
||||
shift += 10;
|
||||
case 'k':
|
||||
shift += 10;
|
||||
token[len - 2] = 0;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ret = kstrtou64(token, 0, num64);
|
||||
*num64 <<= shift;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int parse_name(char **pname, const char *token)
|
||||
{
|
||||
size_t len;
|
||||
char *name;
|
||||
|
||||
len = strlen(token) + 1;
|
||||
if (len > 64)
|
||||
return -ENOSPC;
|
||||
|
||||
name = kstrdup(token, GFP_KERNEL);
|
||||
if (!name)
|
||||
return -ENOMEM;
|
||||
|
||||
*pname = name;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static inline void kill_final_newline(char *str)
|
||||
{
|
||||
char *newline = strrchr(str, '\n');
|
||||
|
||||
if (newline && !newline[1])
|
||||
*newline = 0;
|
||||
}
|
||||
|
||||
|
||||
#define parse_err(fmt, args...) do { \
|
||||
pr_err(fmt , ## args); \
|
||||
return 1; \
|
||||
} while (0)
|
||||
|
||||
#ifndef MODULE
|
||||
static int phram_init_called;
|
||||
/*
|
||||
* This shall contain the module parameter if any. It is of the form:
|
||||
* - phram=<device>,<address>,<size> for module case
|
||||
* - phram.phram=<device>,<address>,<size> for built-in case
|
||||
* We leave 64 bytes for the device name, 20 for the address and 20 for the
|
||||
* size.
|
||||
* Example: phram.phram=rootfs,0xa0000000,512Mi
|
||||
*/
|
||||
static char phram_paramline[64 + 20 + 20];
|
||||
#endif
|
||||
|
||||
static int phram_setup(const char *val)
|
||||
{
|
||||
char buf[64 + 20 + 20], *str = buf;
|
||||
char *token[3];
|
||||
char *name;
|
||||
uint64_t start;
|
||||
uint64_t len;
|
||||
int i, ret;
|
||||
|
||||
if (strnlen(val, sizeof(buf)) >= sizeof(buf))
|
||||
parse_err("parameter too long\n");
|
||||
|
||||
strcpy(str, val);
|
||||
kill_final_newline(str);
|
||||
|
||||
for (i = 0; i < 3; i++)
|
||||
token[i] = strsep(&str, ",");
|
||||
|
||||
if (str)
|
||||
parse_err("too many arguments\n");
|
||||
|
||||
if (!token[2])
|
||||
parse_err("not enough arguments\n");
|
||||
|
||||
ret = parse_name(&name, token[0]);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = parse_num64(&start, token[1]);
|
||||
if (ret) {
|
||||
kfree(name);
|
||||
parse_err("illegal start address\n");
|
||||
}
|
||||
|
||||
ret = parse_num64(&len, token[2]);
|
||||
if (ret) {
|
||||
kfree(name);
|
||||
parse_err("illegal device length\n");
|
||||
}
|
||||
|
||||
ret = register_device(name, start, len);
|
||||
if (!ret)
|
||||
pr_info("%s device: %#llx at %#llx\n", name, len, start);
|
||||
else
|
||||
kfree(name);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int phram_param_call(const char *val, struct kernel_param *kp)
|
||||
{
|
||||
#ifdef MODULE
|
||||
return phram_setup(val);
|
||||
#else
|
||||
/*
|
||||
* If more parameters are later passed in via
|
||||
* /sys/module/phram/parameters/phram
|
||||
* and init_phram() has already been called,
|
||||
* we can parse the argument now.
|
||||
*/
|
||||
|
||||
if (phram_init_called)
|
||||
return phram_setup(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, phram_param_call() is
|
||||
* called so early that it is not possible to resolve
|
||||
* the device (even kmalloc() fails). Defer that work to
|
||||
* phram_setup().
|
||||
*/
|
||||
|
||||
if (strlen(val) >= sizeof(phram_paramline))
|
||||
return -ENOSPC;
|
||||
strcpy(phram_paramline, val);
|
||||
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
module_param_call(phram, phram_param_call, NULL, NULL, 000);
|
||||
MODULE_PARM_DESC(phram, "Memory region to map. \"phram=<name>,<start>,<length>\"");
|
||||
|
||||
|
||||
static int __init init_phram(void)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
#ifndef MODULE
|
||||
if (phram_paramline[0])
|
||||
ret = phram_setup(phram_paramline);
|
||||
phram_init_called = 1;
|
||||
#endif
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void __exit cleanup_phram(void)
|
||||
{
|
||||
unregister_devices();
|
||||
}
|
||||
|
||||
module_init(init_phram);
|
||||
module_exit(cleanup_phram);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Joern Engel <joern@wh.fh-wedel.de>");
|
||||
MODULE_DESCRIPTION("MTD driver for physical RAM");
|
857
drivers/mtd/devices/pmc551.c
Normal file
857
drivers/mtd/devices/pmc551.c
Normal file
|
@ -0,0 +1,857 @@
|
|||
/*
|
||||
* PMC551 PCI Mezzanine Ram Device
|
||||
*
|
||||
* Author:
|
||||
* Mark Ferrell <mferrell@mvista.com>
|
||||
* Copyright 1999,2000 Nortel Networks
|
||||
*
|
||||
* License:
|
||||
* As part of this driver was derived from the slram.c driver it
|
||||
* falls under the same license, which is GNU General Public
|
||||
* License v2
|
||||
*
|
||||
* Description:
|
||||
* This driver is intended to support the PMC551 PCI Ram device
|
||||
* from Ramix Inc. The PMC551 is a PMC Mezzanine module for
|
||||
* cPCI embedded systems. The device contains a single SROM
|
||||
* that initially programs the V370PDC chipset onboard the
|
||||
* device, and various banks of DRAM/SDRAM onboard. This driver
|
||||
* implements this PCI Ram device as an MTD (Memory Technology
|
||||
* Device) so that it can be used to hold a file system, or for
|
||||
* added swap space in embedded systems. Since the memory on
|
||||
* this board isn't as fast as main memory we do not try to hook
|
||||
* it into main memory as that would simply reduce performance
|
||||
* on the system. Using it as a block device allows us to use
|
||||
* it as high speed swap or for a high speed disk device of some
|
||||
* sort. Which becomes very useful on diskless systems in the
|
||||
* embedded market I might add.
|
||||
*
|
||||
* Notes:
|
||||
* Due to what I assume is more buggy SROM, the 64M PMC551 I
|
||||
* have available claims that all 4 of its DRAM banks have 64MiB
|
||||
* of ram configured (making a grand total of 256MiB onboard).
|
||||
* This is slightly annoying since the BAR0 size reflects the
|
||||
* aperture size, not the dram size, and the V370PDC supplies no
|
||||
* other method for memory size discovery. This problem is
|
||||
* mostly only relevant when compiled as a module, as the
|
||||
* unloading of the module with an aperture size smaller than
|
||||
* the ram will cause the driver to detect the onboard memory
|
||||
* size to be equal to the aperture size when the module is
|
||||
* reloaded. Soooo, to help, the module supports an msize
|
||||
* option to allow the specification of the onboard memory, and
|
||||
* an asize option, to allow the specification of the aperture
|
||||
* size. The aperture must be equal to or less then the memory
|
||||
* size, the driver will correct this if you screw it up. This
|
||||
* problem is not relevant for compiled in drivers as compiled
|
||||
* in drivers only init once.
|
||||
*
|
||||
* Credits:
|
||||
* Saeed Karamooz <saeed@ramix.com> of Ramix INC. for the
|
||||
* initial example code of how to initialize this device and for
|
||||
* help with questions I had concerning operation of the device.
|
||||
*
|
||||
* Most of the MTD code for this driver was originally written
|
||||
* for the slram.o module in the MTD drivers package which
|
||||
* allows the mapping of system memory into an MTD device.
|
||||
* Since the PMC551 memory module is accessed in the same
|
||||
* fashion as system memory, the slram.c code became a very nice
|
||||
* fit to the needs of this driver. All we added was PCI
|
||||
* detection/initialization to the driver and automatically figure
|
||||
* out the size via the PCI detection.o, later changes by Corey
|
||||
* Minyard set up the card to utilize a 1M sliding apature.
|
||||
*
|
||||
* Corey Minyard <minyard@nortelnetworks.com>
|
||||
* * Modified driver to utilize a sliding aperture instead of
|
||||
* mapping all memory into kernel space which turned out to
|
||||
* be very wasteful.
|
||||
* * Located a bug in the SROM's initialization sequence that
|
||||
* made the memory unusable, added a fix to code to touch up
|
||||
* the DRAM some.
|
||||
*
|
||||
* Bugs/FIXMEs:
|
||||
* * MUST fix the init function to not spin on a register
|
||||
* waiting for it to set .. this does not safely handle busted
|
||||
* devices that never reset the register correctly which will
|
||||
* cause the system to hang w/ a reboot being the only chance at
|
||||
* recover. [sort of fixed, could be better]
|
||||
* * Add I2C handling of the SROM so we can read the SROM's information
|
||||
* about the aperture size. This should always accurately reflect the
|
||||
* onboard memory size.
|
||||
* * Comb the init routine. It's still a bit cludgy on a few things.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <asm/uaccess.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/ptrace.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/timer.h>
|
||||
#include <linux/major.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/ioctl.h>
|
||||
#include <asm/io.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/mtd/mtd.h>
|
||||
|
||||
#define PMC551_VERSION \
|
||||
"Ramix PMC551 PCI Mezzanine Ram Driver. (C) 1999,2000 Nortel Networks.\n"
|
||||
|
||||
#define PCI_VENDOR_ID_V3_SEMI 0x11b0
|
||||
#define PCI_DEVICE_ID_V3_SEMI_V370PDC 0x0200
|
||||
|
||||
#define PMC551_PCI_MEM_MAP0 0x50
|
||||
#define PMC551_PCI_MEM_MAP1 0x54
|
||||
#define PMC551_PCI_MEM_MAP_MAP_ADDR_MASK 0x3ff00000
|
||||
#define PMC551_PCI_MEM_MAP_APERTURE_MASK 0x000000f0
|
||||
#define PMC551_PCI_MEM_MAP_REG_EN 0x00000002
|
||||
#define PMC551_PCI_MEM_MAP_ENABLE 0x00000001
|
||||
|
||||
#define PMC551_SDRAM_MA 0x60
|
||||
#define PMC551_SDRAM_CMD 0x62
|
||||
#define PMC551_DRAM_CFG 0x64
|
||||
#define PMC551_SYS_CTRL_REG 0x78
|
||||
|
||||
#define PMC551_DRAM_BLK0 0x68
|
||||
#define PMC551_DRAM_BLK1 0x6c
|
||||
#define PMC551_DRAM_BLK2 0x70
|
||||
#define PMC551_DRAM_BLK3 0x74
|
||||
#define PMC551_DRAM_BLK_GET_SIZE(x) (524288 << ((x >> 4) & 0x0f))
|
||||
#define PMC551_DRAM_BLK_SET_COL_MUX(x, v) (((x) & ~0x00007000) | (((v) & 0x7) << 12))
|
||||
#define PMC551_DRAM_BLK_SET_ROW_MUX(x, v) (((x) & ~0x00000f00) | (((v) & 0xf) << 8))
|
||||
|
||||
struct mypriv {
|
||||
struct pci_dev *dev;
|
||||
u_char *start;
|
||||
u32 base_map0;
|
||||
u32 curr_map0;
|
||||
u32 asize;
|
||||
struct mtd_info *nextpmc551;
|
||||
};
|
||||
|
||||
static struct mtd_info *pmc551list;
|
||||
|
||||
static int pmc551_point(struct mtd_info *mtd, loff_t from, size_t len,
|
||||
size_t *retlen, void **virt, resource_size_t *phys);
|
||||
|
||||
static int pmc551_erase(struct mtd_info *mtd, struct erase_info *instr)
|
||||
{
|
||||
struct mypriv *priv = mtd->priv;
|
||||
u32 soff_hi, soff_lo; /* start address offset hi/lo */
|
||||
u32 eoff_hi, eoff_lo; /* end address offset hi/lo */
|
||||
unsigned long end;
|
||||
u_char *ptr;
|
||||
size_t retlen;
|
||||
|
||||
#ifdef CONFIG_MTD_PMC551_DEBUG
|
||||
printk(KERN_DEBUG "pmc551_erase(pos:%ld, len:%ld)\n", (long)instr->addr,
|
||||
(long)instr->len);
|
||||
#endif
|
||||
|
||||
end = instr->addr + instr->len - 1;
|
||||
eoff_hi = end & ~(priv->asize - 1);
|
||||
soff_hi = instr->addr & ~(priv->asize - 1);
|
||||
eoff_lo = end & (priv->asize - 1);
|
||||
soff_lo = instr->addr & (priv->asize - 1);
|
||||
|
||||
pmc551_point(mtd, instr->addr, instr->len, &retlen,
|
||||
(void **)&ptr, NULL);
|
||||
|
||||
if (soff_hi == eoff_hi || mtd->size == priv->asize) {
|
||||
/* The whole thing fits within one access, so just one shot
|
||||
will do it. */
|
||||
memset(ptr, 0xff, instr->len);
|
||||
} else {
|
||||
/* We have to do multiple writes to get all the data
|
||||
written. */
|
||||
while (soff_hi != eoff_hi) {
|
||||
#ifdef CONFIG_MTD_PMC551_DEBUG
|
||||
printk(KERN_DEBUG "pmc551_erase() soff_hi: %ld, "
|
||||
"eoff_hi: %ld\n", (long)soff_hi, (long)eoff_hi);
|
||||
#endif
|
||||
memset(ptr, 0xff, priv->asize);
|
||||
if (soff_hi + priv->asize >= mtd->size) {
|
||||
goto out;
|
||||
}
|
||||
soff_hi += priv->asize;
|
||||
pmc551_point(mtd, (priv->base_map0 | soff_hi),
|
||||
priv->asize, &retlen,
|
||||
(void **)&ptr, NULL);
|
||||
}
|
||||
memset(ptr, 0xff, eoff_lo);
|
||||
}
|
||||
|
||||
out:
|
||||
instr->state = MTD_ERASE_DONE;
|
||||
#ifdef CONFIG_MTD_PMC551_DEBUG
|
||||
printk(KERN_DEBUG "pmc551_erase() done\n");
|
||||
#endif
|
||||
|
||||
mtd_erase_callback(instr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pmc551_point(struct mtd_info *mtd, loff_t from, size_t len,
|
||||
size_t *retlen, void **virt, resource_size_t *phys)
|
||||
{
|
||||
struct mypriv *priv = mtd->priv;
|
||||
u32 soff_hi;
|
||||
u32 soff_lo;
|
||||
|
||||
#ifdef CONFIG_MTD_PMC551_DEBUG
|
||||
printk(KERN_DEBUG "pmc551_point(%ld, %ld)\n", (long)from, (long)len);
|
||||
#endif
|
||||
|
||||
soff_hi = from & ~(priv->asize - 1);
|
||||
soff_lo = from & (priv->asize - 1);
|
||||
|
||||
/* Cheap hack optimization */
|
||||
if (priv->curr_map0 != from) {
|
||||
pci_write_config_dword(priv->dev, PMC551_PCI_MEM_MAP0,
|
||||
(priv->base_map0 | soff_hi));
|
||||
priv->curr_map0 = soff_hi;
|
||||
}
|
||||
|
||||
*virt = priv->start + soff_lo;
|
||||
*retlen = len;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pmc551_unpoint(struct mtd_info *mtd, loff_t from, size_t len)
|
||||
{
|
||||
#ifdef CONFIG_MTD_PMC551_DEBUG
|
||||
printk(KERN_DEBUG "pmc551_unpoint()\n");
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pmc551_read(struct mtd_info *mtd, loff_t from, size_t len,
|
||||
size_t * retlen, u_char * buf)
|
||||
{
|
||||
struct mypriv *priv = mtd->priv;
|
||||
u32 soff_hi, soff_lo; /* start address offset hi/lo */
|
||||
u32 eoff_hi, eoff_lo; /* end address offset hi/lo */
|
||||
unsigned long end;
|
||||
u_char *ptr;
|
||||
u_char *copyto = buf;
|
||||
|
||||
#ifdef CONFIG_MTD_PMC551_DEBUG
|
||||
printk(KERN_DEBUG "pmc551_read(pos:%ld, len:%ld) asize: %ld\n",
|
||||
(long)from, (long)len, (long)priv->asize);
|
||||
#endif
|
||||
|
||||
end = from + len - 1;
|
||||
soff_hi = from & ~(priv->asize - 1);
|
||||
eoff_hi = end & ~(priv->asize - 1);
|
||||
soff_lo = from & (priv->asize - 1);
|
||||
eoff_lo = end & (priv->asize - 1);
|
||||
|
||||
pmc551_point(mtd, from, len, retlen, (void **)&ptr, NULL);
|
||||
|
||||
if (soff_hi == eoff_hi) {
|
||||
/* The whole thing fits within one access, so just one shot
|
||||
will do it. */
|
||||
memcpy(copyto, ptr, len);
|
||||
copyto += len;
|
||||
} else {
|
||||
/* We have to do multiple writes to get all the data
|
||||
written. */
|
||||
while (soff_hi != eoff_hi) {
|
||||
#ifdef CONFIG_MTD_PMC551_DEBUG
|
||||
printk(KERN_DEBUG "pmc551_read() soff_hi: %ld, "
|
||||
"eoff_hi: %ld\n", (long)soff_hi, (long)eoff_hi);
|
||||
#endif
|
||||
memcpy(copyto, ptr, priv->asize);
|
||||
copyto += priv->asize;
|
||||
if (soff_hi + priv->asize >= mtd->size) {
|
||||
goto out;
|
||||
}
|
||||
soff_hi += priv->asize;
|
||||
pmc551_point(mtd, soff_hi, priv->asize, retlen,
|
||||
(void **)&ptr, NULL);
|
||||
}
|
||||
memcpy(copyto, ptr, eoff_lo);
|
||||
copyto += eoff_lo;
|
||||
}
|
||||
|
||||
out:
|
||||
#ifdef CONFIG_MTD_PMC551_DEBUG
|
||||
printk(KERN_DEBUG "pmc551_read() done\n");
|
||||
#endif
|
||||
*retlen = copyto - buf;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pmc551_write(struct mtd_info *mtd, loff_t to, size_t len,
|
||||
size_t * retlen, const u_char * buf)
|
||||
{
|
||||
struct mypriv *priv = mtd->priv;
|
||||
u32 soff_hi, soff_lo; /* start address offset hi/lo */
|
||||
u32 eoff_hi, eoff_lo; /* end address offset hi/lo */
|
||||
unsigned long end;
|
||||
u_char *ptr;
|
||||
const u_char *copyfrom = buf;
|
||||
|
||||
#ifdef CONFIG_MTD_PMC551_DEBUG
|
||||
printk(KERN_DEBUG "pmc551_write(pos:%ld, len:%ld) asize:%ld\n",
|
||||
(long)to, (long)len, (long)priv->asize);
|
||||
#endif
|
||||
|
||||
end = to + len - 1;
|
||||
soff_hi = to & ~(priv->asize - 1);
|
||||
eoff_hi = end & ~(priv->asize - 1);
|
||||
soff_lo = to & (priv->asize - 1);
|
||||
eoff_lo = end & (priv->asize - 1);
|
||||
|
||||
pmc551_point(mtd, to, len, retlen, (void **)&ptr, NULL);
|
||||
|
||||
if (soff_hi == eoff_hi) {
|
||||
/* The whole thing fits within one access, so just one shot
|
||||
will do it. */
|
||||
memcpy(ptr, copyfrom, len);
|
||||
copyfrom += len;
|
||||
} else {
|
||||
/* We have to do multiple writes to get all the data
|
||||
written. */
|
||||
while (soff_hi != eoff_hi) {
|
||||
#ifdef CONFIG_MTD_PMC551_DEBUG
|
||||
printk(KERN_DEBUG "pmc551_write() soff_hi: %ld, "
|
||||
"eoff_hi: %ld\n", (long)soff_hi, (long)eoff_hi);
|
||||
#endif
|
||||
memcpy(ptr, copyfrom, priv->asize);
|
||||
copyfrom += priv->asize;
|
||||
if (soff_hi >= mtd->size) {
|
||||
goto out;
|
||||
}
|
||||
soff_hi += priv->asize;
|
||||
pmc551_point(mtd, soff_hi, priv->asize, retlen,
|
||||
(void **)&ptr, NULL);
|
||||
}
|
||||
memcpy(ptr, copyfrom, eoff_lo);
|
||||
copyfrom += eoff_lo;
|
||||
}
|
||||
|
||||
out:
|
||||
#ifdef CONFIG_MTD_PMC551_DEBUG
|
||||
printk(KERN_DEBUG "pmc551_write() done\n");
|
||||
#endif
|
||||
*retlen = copyfrom - buf;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Fixup routines for the V370PDC
|
||||
* PCI device ID 0x020011b0
|
||||
*
|
||||
* This function basically kick starts the DRAM oboard the card and gets it
|
||||
* ready to be used. Before this is done the device reads VERY erratic, so
|
||||
* much that it can crash the Linux 2.2.x series kernels when a user cat's
|
||||
* /proc/pci .. though that is mainly a kernel bug in handling the PCI DEVSEL
|
||||
* register. FIXME: stop spinning on registers .. must implement a timeout
|
||||
* mechanism
|
||||
* returns the size of the memory region found.
|
||||
*/
|
||||
static int fixup_pmc551(struct pci_dev *dev)
|
||||
{
|
||||
#ifdef CONFIG_MTD_PMC551_BUGFIX
|
||||
u32 dram_data;
|
||||
#endif
|
||||
u32 size, dcmd, cfg, dtmp;
|
||||
u16 cmd, tmp, i;
|
||||
u8 bcmd, counter;
|
||||
|
||||
/* Sanity Check */
|
||||
if (!dev) {
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
/*
|
||||
* Attempt to reset the card
|
||||
* FIXME: Stop Spinning registers
|
||||
*/
|
||||
counter = 0;
|
||||
/* unlock registers */
|
||||
pci_write_config_byte(dev, PMC551_SYS_CTRL_REG, 0xA5);
|
||||
/* read in old data */
|
||||
pci_read_config_byte(dev, PMC551_SYS_CTRL_REG, &bcmd);
|
||||
/* bang the reset line up and down for a few */
|
||||
for (i = 0; i < 10; i++) {
|
||||
counter = 0;
|
||||
bcmd &= ~0x80;
|
||||
while (counter++ < 100) {
|
||||
pci_write_config_byte(dev, PMC551_SYS_CTRL_REG, bcmd);
|
||||
}
|
||||
counter = 0;
|
||||
bcmd |= 0x80;
|
||||
while (counter++ < 100) {
|
||||
pci_write_config_byte(dev, PMC551_SYS_CTRL_REG, bcmd);
|
||||
}
|
||||
}
|
||||
bcmd |= (0x40 | 0x20);
|
||||
pci_write_config_byte(dev, PMC551_SYS_CTRL_REG, bcmd);
|
||||
|
||||
/*
|
||||
* Take care and turn off the memory on the device while we
|
||||
* tweak the configurations
|
||||
*/
|
||||
pci_read_config_word(dev, PCI_COMMAND, &cmd);
|
||||
tmp = cmd & ~(PCI_COMMAND_IO | PCI_COMMAND_MEMORY);
|
||||
pci_write_config_word(dev, PCI_COMMAND, tmp);
|
||||
|
||||
/*
|
||||
* Disable existing aperture before probing memory size
|
||||
*/
|
||||
pci_read_config_dword(dev, PMC551_PCI_MEM_MAP0, &dcmd);
|
||||
dtmp = (dcmd | PMC551_PCI_MEM_MAP_ENABLE | PMC551_PCI_MEM_MAP_REG_EN);
|
||||
pci_write_config_dword(dev, PMC551_PCI_MEM_MAP0, dtmp);
|
||||
/*
|
||||
* Grab old BAR0 config so that we can figure out memory size
|
||||
* This is another bit of kludge going on. The reason for the
|
||||
* redundancy is I am hoping to retain the original configuration
|
||||
* previously assigned to the card by the BIOS or some previous
|
||||
* fixup routine in the kernel. So we read the old config into cfg,
|
||||
* then write all 1's to the memory space, read back the result into
|
||||
* "size", and then write back all the old config.
|
||||
*/
|
||||
pci_read_config_dword(dev, PCI_BASE_ADDRESS_0, &cfg);
|
||||
#ifndef CONFIG_MTD_PMC551_BUGFIX
|
||||
pci_write_config_dword(dev, PCI_BASE_ADDRESS_0, ~0);
|
||||
pci_read_config_dword(dev, PCI_BASE_ADDRESS_0, &size);
|
||||
size = (size & PCI_BASE_ADDRESS_MEM_MASK);
|
||||
size &= ~(size - 1);
|
||||
pci_write_config_dword(dev, PCI_BASE_ADDRESS_0, cfg);
|
||||
#else
|
||||
/*
|
||||
* Get the size of the memory by reading all the DRAM size values
|
||||
* and adding them up.
|
||||
*
|
||||
* KLUDGE ALERT: the boards we are using have invalid column and
|
||||
* row mux values. We fix them here, but this will break other
|
||||
* memory configurations.
|
||||
*/
|
||||
pci_read_config_dword(dev, PMC551_DRAM_BLK0, &dram_data);
|
||||
size = PMC551_DRAM_BLK_GET_SIZE(dram_data);
|
||||
dram_data = PMC551_DRAM_BLK_SET_COL_MUX(dram_data, 0x5);
|
||||
dram_data = PMC551_DRAM_BLK_SET_ROW_MUX(dram_data, 0x9);
|
||||
pci_write_config_dword(dev, PMC551_DRAM_BLK0, dram_data);
|
||||
|
||||
pci_read_config_dword(dev, PMC551_DRAM_BLK1, &dram_data);
|
||||
size += PMC551_DRAM_BLK_GET_SIZE(dram_data);
|
||||
dram_data = PMC551_DRAM_BLK_SET_COL_MUX(dram_data, 0x5);
|
||||
dram_data = PMC551_DRAM_BLK_SET_ROW_MUX(dram_data, 0x9);
|
||||
pci_write_config_dword(dev, PMC551_DRAM_BLK1, dram_data);
|
||||
|
||||
pci_read_config_dword(dev, PMC551_DRAM_BLK2, &dram_data);
|
||||
size += PMC551_DRAM_BLK_GET_SIZE(dram_data);
|
||||
dram_data = PMC551_DRAM_BLK_SET_COL_MUX(dram_data, 0x5);
|
||||
dram_data = PMC551_DRAM_BLK_SET_ROW_MUX(dram_data, 0x9);
|
||||
pci_write_config_dword(dev, PMC551_DRAM_BLK2, dram_data);
|
||||
|
||||
pci_read_config_dword(dev, PMC551_DRAM_BLK3, &dram_data);
|
||||
size += PMC551_DRAM_BLK_GET_SIZE(dram_data);
|
||||
dram_data = PMC551_DRAM_BLK_SET_COL_MUX(dram_data, 0x5);
|
||||
dram_data = PMC551_DRAM_BLK_SET_ROW_MUX(dram_data, 0x9);
|
||||
pci_write_config_dword(dev, PMC551_DRAM_BLK3, dram_data);
|
||||
|
||||
/*
|
||||
* Oops .. something went wrong
|
||||
*/
|
||||
if ((size &= PCI_BASE_ADDRESS_MEM_MASK) == 0) {
|
||||
return -ENODEV;
|
||||
}
|
||||
#endif /* CONFIG_MTD_PMC551_BUGFIX */
|
||||
|
||||
if ((cfg & PCI_BASE_ADDRESS_SPACE) != PCI_BASE_ADDRESS_SPACE_MEMORY) {
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
/*
|
||||
* Precharge Dram
|
||||
*/
|
||||
pci_write_config_word(dev, PMC551_SDRAM_MA, 0x0400);
|
||||
pci_write_config_word(dev, PMC551_SDRAM_CMD, 0x00bf);
|
||||
|
||||
/*
|
||||
* Wait until command has gone through
|
||||
* FIXME: register spinning issue
|
||||
*/
|
||||
do {
|
||||
pci_read_config_word(dev, PMC551_SDRAM_CMD, &cmd);
|
||||
if (counter++ > 100)
|
||||
break;
|
||||
} while ((PCI_COMMAND_IO) & cmd);
|
||||
|
||||
/*
|
||||
* Turn on auto refresh
|
||||
* The loop is taken directly from Ramix's example code. I assume that
|
||||
* this must be held high for some duration of time, but I can find no
|
||||
* documentation refrencing the reasons why.
|
||||
*/
|
||||
for (i = 1; i <= 8; i++) {
|
||||
pci_write_config_word(dev, PMC551_SDRAM_CMD, 0x0df);
|
||||
|
||||
/*
|
||||
* Make certain command has gone through
|
||||
* FIXME: register spinning issue
|
||||
*/
|
||||
counter = 0;
|
||||
do {
|
||||
pci_read_config_word(dev, PMC551_SDRAM_CMD, &cmd);
|
||||
if (counter++ > 100)
|
||||
break;
|
||||
} while ((PCI_COMMAND_IO) & cmd);
|
||||
}
|
||||
|
||||
pci_write_config_word(dev, PMC551_SDRAM_MA, 0x0020);
|
||||
pci_write_config_word(dev, PMC551_SDRAM_CMD, 0x0ff);
|
||||
|
||||
/*
|
||||
* Wait until command completes
|
||||
* FIXME: register spinning issue
|
||||
*/
|
||||
counter = 0;
|
||||
do {
|
||||
pci_read_config_word(dev, PMC551_SDRAM_CMD, &cmd);
|
||||
if (counter++ > 100)
|
||||
break;
|
||||
} while ((PCI_COMMAND_IO) & cmd);
|
||||
|
||||
pci_read_config_dword(dev, PMC551_DRAM_CFG, &dcmd);
|
||||
dcmd |= 0x02000000;
|
||||
pci_write_config_dword(dev, PMC551_DRAM_CFG, dcmd);
|
||||
|
||||
/*
|
||||
* Check to make certain fast back-to-back, if not
|
||||
* then set it so
|
||||
*/
|
||||
pci_read_config_word(dev, PCI_STATUS, &cmd);
|
||||
if ((cmd & PCI_COMMAND_FAST_BACK) == 0) {
|
||||
cmd |= PCI_COMMAND_FAST_BACK;
|
||||
pci_write_config_word(dev, PCI_STATUS, cmd);
|
||||
}
|
||||
|
||||
/*
|
||||
* Check to make certain the DEVSEL is set correctly, this device
|
||||
* has a tendency to assert DEVSEL and TRDY when a write is performed
|
||||
* to the memory when memory is read-only
|
||||
*/
|
||||
if ((cmd & PCI_STATUS_DEVSEL_MASK) != 0x0) {
|
||||
cmd &= ~PCI_STATUS_DEVSEL_MASK;
|
||||
pci_write_config_word(dev, PCI_STATUS, cmd);
|
||||
}
|
||||
/*
|
||||
* Set to be prefetchable and put everything back based on old cfg.
|
||||
* it's possible that the reset of the V370PDC nuked the original
|
||||
* setup
|
||||
*/
|
||||
/*
|
||||
cfg |= PCI_BASE_ADDRESS_MEM_PREFETCH;
|
||||
pci_write_config_dword( dev, PCI_BASE_ADDRESS_0, cfg );
|
||||
*/
|
||||
|
||||
/*
|
||||
* Turn PCI memory and I/O bus access back on
|
||||
*/
|
||||
pci_write_config_word(dev, PCI_COMMAND,
|
||||
PCI_COMMAND_MEMORY | PCI_COMMAND_IO);
|
||||
#ifdef CONFIG_MTD_PMC551_DEBUG
|
||||
/*
|
||||
* Some screen fun
|
||||
*/
|
||||
printk(KERN_DEBUG "pmc551: %d%sB (0x%x) of %sprefetchable memory at "
|
||||
"0x%llx\n", (size < 1024) ? size : (size < 1048576) ?
|
||||
size >> 10 : size >> 20,
|
||||
(size < 1024) ? "" : (size < 1048576) ? "Ki" : "Mi", size,
|
||||
((dcmd & (0x1 << 3)) == 0) ? "non-" : "",
|
||||
(unsigned long long)pci_resource_start(dev, 0));
|
||||
|
||||
/*
|
||||
* Check to see the state of the memory
|
||||
*/
|
||||
pci_read_config_dword(dev, PMC551_DRAM_BLK0, &dcmd);
|
||||
printk(KERN_DEBUG "pmc551: DRAM_BLK0 Flags: %s,%s\n"
|
||||
"pmc551: DRAM_BLK0 Size: %d at %d\n"
|
||||
"pmc551: DRAM_BLK0 Row MUX: %d, Col MUX: %d\n",
|
||||
(((0x1 << 1) & dcmd) == 0) ? "RW" : "RO",
|
||||
(((0x1 << 0) & dcmd) == 0) ? "Off" : "On",
|
||||
PMC551_DRAM_BLK_GET_SIZE(dcmd),
|
||||
((dcmd >> 20) & 0x7FF), ((dcmd >> 13) & 0x7),
|
||||
((dcmd >> 9) & 0xF));
|
||||
|
||||
pci_read_config_dword(dev, PMC551_DRAM_BLK1, &dcmd);
|
||||
printk(KERN_DEBUG "pmc551: DRAM_BLK1 Flags: %s,%s\n"
|
||||
"pmc551: DRAM_BLK1 Size: %d at %d\n"
|
||||
"pmc551: DRAM_BLK1 Row MUX: %d, Col MUX: %d\n",
|
||||
(((0x1 << 1) & dcmd) == 0) ? "RW" : "RO",
|
||||
(((0x1 << 0) & dcmd) == 0) ? "Off" : "On",
|
||||
PMC551_DRAM_BLK_GET_SIZE(dcmd),
|
||||
((dcmd >> 20) & 0x7FF), ((dcmd >> 13) & 0x7),
|
||||
((dcmd >> 9) & 0xF));
|
||||
|
||||
pci_read_config_dword(dev, PMC551_DRAM_BLK2, &dcmd);
|
||||
printk(KERN_DEBUG "pmc551: DRAM_BLK2 Flags: %s,%s\n"
|
||||
"pmc551: DRAM_BLK2 Size: %d at %d\n"
|
||||
"pmc551: DRAM_BLK2 Row MUX: %d, Col MUX: %d\n",
|
||||
(((0x1 << 1) & dcmd) == 0) ? "RW" : "RO",
|
||||
(((0x1 << 0) & dcmd) == 0) ? "Off" : "On",
|
||||
PMC551_DRAM_BLK_GET_SIZE(dcmd),
|
||||
((dcmd >> 20) & 0x7FF), ((dcmd >> 13) & 0x7),
|
||||
((dcmd >> 9) & 0xF));
|
||||
|
||||
pci_read_config_dword(dev, PMC551_DRAM_BLK3, &dcmd);
|
||||
printk(KERN_DEBUG "pmc551: DRAM_BLK3 Flags: %s,%s\n"
|
||||
"pmc551: DRAM_BLK3 Size: %d at %d\n"
|
||||
"pmc551: DRAM_BLK3 Row MUX: %d, Col MUX: %d\n",
|
||||
(((0x1 << 1) & dcmd) == 0) ? "RW" : "RO",
|
||||
(((0x1 << 0) & dcmd) == 0) ? "Off" : "On",
|
||||
PMC551_DRAM_BLK_GET_SIZE(dcmd),
|
||||
((dcmd >> 20) & 0x7FF), ((dcmd >> 13) & 0x7),
|
||||
((dcmd >> 9) & 0xF));
|
||||
|
||||
pci_read_config_word(dev, PCI_COMMAND, &cmd);
|
||||
printk(KERN_DEBUG "pmc551: Memory Access %s\n",
|
||||
(((0x1 << 1) & cmd) == 0) ? "off" : "on");
|
||||
printk(KERN_DEBUG "pmc551: I/O Access %s\n",
|
||||
(((0x1 << 0) & cmd) == 0) ? "off" : "on");
|
||||
|
||||
pci_read_config_word(dev, PCI_STATUS, &cmd);
|
||||
printk(KERN_DEBUG "pmc551: Devsel %s\n",
|
||||
((PCI_STATUS_DEVSEL_MASK & cmd) == 0x000) ? "Fast" :
|
||||
((PCI_STATUS_DEVSEL_MASK & cmd) == 0x200) ? "Medium" :
|
||||
((PCI_STATUS_DEVSEL_MASK & cmd) == 0x400) ? "Slow" : "Invalid");
|
||||
|
||||
printk(KERN_DEBUG "pmc551: %sFast Back-to-Back\n",
|
||||
((PCI_COMMAND_FAST_BACK & cmd) == 0) ? "Not " : "");
|
||||
|
||||
pci_read_config_byte(dev, PMC551_SYS_CTRL_REG, &bcmd);
|
||||
printk(KERN_DEBUG "pmc551: EEPROM is under %s control\n"
|
||||
"pmc551: System Control Register is %slocked to PCI access\n"
|
||||
"pmc551: System Control Register is %slocked to EEPROM access\n",
|
||||
(bcmd & 0x1) ? "software" : "hardware",
|
||||
(bcmd & 0x20) ? "" : "un", (bcmd & 0x40) ? "" : "un");
|
||||
#endif
|
||||
return size;
|
||||
}
|
||||
|
||||
/*
|
||||
* Kernel version specific module stuffages
|
||||
*/
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Mark Ferrell <mferrell@mvista.com>");
|
||||
MODULE_DESCRIPTION(PMC551_VERSION);
|
||||
|
||||
/*
|
||||
* Stuff these outside the ifdef so as to not bust compiled in driver support
|
||||
*/
|
||||
static int msize = 0;
|
||||
static int asize = 0;
|
||||
|
||||
module_param(msize, int, 0);
|
||||
MODULE_PARM_DESC(msize, "memory size in MiB [1 - 1024]");
|
||||
module_param(asize, int, 0);
|
||||
MODULE_PARM_DESC(asize, "aperture size, must be <= memsize [1-1024]");
|
||||
|
||||
/*
|
||||
* PMC551 Card Initialization
|
||||
*/
|
||||
static int __init init_pmc551(void)
|
||||
{
|
||||
struct pci_dev *PCI_Device = NULL;
|
||||
struct mypriv *priv;
|
||||
int found = 0;
|
||||
struct mtd_info *mtd;
|
||||
int length = 0;
|
||||
|
||||
if (msize) {
|
||||
msize = (1 << (ffs(msize) - 1)) << 20;
|
||||
if (msize > (1 << 30)) {
|
||||
printk(KERN_NOTICE "pmc551: Invalid memory size [%d]\n",
|
||||
msize);
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
if (asize) {
|
||||
asize = (1 << (ffs(asize) - 1)) << 20;
|
||||
if (asize > (1 << 30)) {
|
||||
printk(KERN_NOTICE "pmc551: Invalid aperture size "
|
||||
"[%d]\n", asize);
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
printk(KERN_INFO PMC551_VERSION);
|
||||
|
||||
/*
|
||||
* PCU-bus chipset probe.
|
||||
*/
|
||||
for (;;) {
|
||||
|
||||
if ((PCI_Device = pci_get_device(PCI_VENDOR_ID_V3_SEMI,
|
||||
PCI_DEVICE_ID_V3_SEMI_V370PDC,
|
||||
PCI_Device)) == NULL) {
|
||||
break;
|
||||
}
|
||||
|
||||
printk(KERN_NOTICE "pmc551: Found PCI V370PDC at 0x%llx\n",
|
||||
(unsigned long long)pci_resource_start(PCI_Device, 0));
|
||||
|
||||
/*
|
||||
* The PMC551 device acts VERY weird if you don't init it
|
||||
* first. i.e. it will not correctly report devsel. If for
|
||||
* some reason the sdram is in a wrote-protected state the
|
||||
* device will DEVSEL when it is written to causing problems
|
||||
* with the oldproc.c driver in
|
||||
* some kernels (2.2.*)
|
||||
*/
|
||||
if ((length = fixup_pmc551(PCI_Device)) <= 0) {
|
||||
printk(KERN_NOTICE "pmc551: Cannot init SDRAM\n");
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
* This is needed until the driver is capable of reading the
|
||||
* onboard I2C SROM to discover the "real" memory size.
|
||||
*/
|
||||
if (msize) {
|
||||
length = msize;
|
||||
printk(KERN_NOTICE "pmc551: Using specified memory "
|
||||
"size 0x%x\n", length);
|
||||
} else {
|
||||
msize = length;
|
||||
}
|
||||
|
||||
mtd = kzalloc(sizeof(struct mtd_info), GFP_KERNEL);
|
||||
if (!mtd)
|
||||
break;
|
||||
|
||||
priv = kzalloc(sizeof(struct mypriv), GFP_KERNEL);
|
||||
if (!priv) {
|
||||
kfree(mtd);
|
||||
break;
|
||||
}
|
||||
mtd->priv = priv;
|
||||
priv->dev = PCI_Device;
|
||||
|
||||
if (asize > length) {
|
||||
printk(KERN_NOTICE "pmc551: reducing aperture size to "
|
||||
"fit %dM\n", length >> 20);
|
||||
priv->asize = asize = length;
|
||||
} else if (asize == 0 || asize == length) {
|
||||
printk(KERN_NOTICE "pmc551: Using existing aperture "
|
||||
"size %dM\n", length >> 20);
|
||||
priv->asize = asize = length;
|
||||
} else {
|
||||
printk(KERN_NOTICE "pmc551: Using specified aperture "
|
||||
"size %dM\n", asize >> 20);
|
||||
priv->asize = asize;
|
||||
}
|
||||
priv->start = pci_iomap(PCI_Device, 0, priv->asize);
|
||||
|
||||
if (!priv->start) {
|
||||
printk(KERN_NOTICE "pmc551: Unable to map IO space\n");
|
||||
kfree(mtd->priv);
|
||||
kfree(mtd);
|
||||
break;
|
||||
}
|
||||
#ifdef CONFIG_MTD_PMC551_DEBUG
|
||||
printk(KERN_DEBUG "pmc551: setting aperture to %d\n",
|
||||
ffs(priv->asize >> 20) - 1);
|
||||
#endif
|
||||
|
||||
priv->base_map0 = (PMC551_PCI_MEM_MAP_REG_EN
|
||||
| PMC551_PCI_MEM_MAP_ENABLE
|
||||
| (ffs(priv->asize >> 20) - 1) << 4);
|
||||
priv->curr_map0 = priv->base_map0;
|
||||
pci_write_config_dword(priv->dev, PMC551_PCI_MEM_MAP0,
|
||||
priv->curr_map0);
|
||||
|
||||
#ifdef CONFIG_MTD_PMC551_DEBUG
|
||||
printk(KERN_DEBUG "pmc551: aperture set to %d\n",
|
||||
(priv->base_map0 & 0xF0) >> 4);
|
||||
#endif
|
||||
|
||||
mtd->size = msize;
|
||||
mtd->flags = MTD_CAP_RAM;
|
||||
mtd->_erase = pmc551_erase;
|
||||
mtd->_read = pmc551_read;
|
||||
mtd->_write = pmc551_write;
|
||||
mtd->_point = pmc551_point;
|
||||
mtd->_unpoint = pmc551_unpoint;
|
||||
mtd->type = MTD_RAM;
|
||||
mtd->name = "PMC551 RAM board";
|
||||
mtd->erasesize = 0x10000;
|
||||
mtd->writesize = 1;
|
||||
mtd->owner = THIS_MODULE;
|
||||
|
||||
if (mtd_device_register(mtd, NULL, 0)) {
|
||||
printk(KERN_NOTICE "pmc551: Failed to register new device\n");
|
||||
pci_iounmap(PCI_Device, priv->start);
|
||||
kfree(mtd->priv);
|
||||
kfree(mtd);
|
||||
break;
|
||||
}
|
||||
|
||||
/* Keep a reference as the mtd_device_register worked */
|
||||
pci_dev_get(PCI_Device);
|
||||
|
||||
printk(KERN_NOTICE "Registered pmc551 memory device.\n");
|
||||
printk(KERN_NOTICE "Mapped %dMiB of memory from 0x%p to 0x%p\n",
|
||||
priv->asize >> 20,
|
||||
priv->start, priv->start + priv->asize);
|
||||
printk(KERN_NOTICE "Total memory is %d%sB\n",
|
||||
(length < 1024) ? length :
|
||||
(length < 1048576) ? length >> 10 : length >> 20,
|
||||
(length < 1024) ? "" : (length < 1048576) ? "Ki" : "Mi");
|
||||
priv->nextpmc551 = pmc551list;
|
||||
pmc551list = mtd;
|
||||
found++;
|
||||
}
|
||||
|
||||
/* Exited early, reference left over */
|
||||
if (PCI_Device)
|
||||
pci_dev_put(PCI_Device);
|
||||
|
||||
if (!pmc551list) {
|
||||
printk(KERN_NOTICE "pmc551: not detected\n");
|
||||
return -ENODEV;
|
||||
} else {
|
||||
printk(KERN_NOTICE "pmc551: %d pmc551 devices loaded\n", found);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* PMC551 Card Cleanup
|
||||
*/
|
||||
static void __exit cleanup_pmc551(void)
|
||||
{
|
||||
int found = 0;
|
||||
struct mtd_info *mtd;
|
||||
struct mypriv *priv;
|
||||
|
||||
while ((mtd = pmc551list)) {
|
||||
priv = mtd->priv;
|
||||
pmc551list = priv->nextpmc551;
|
||||
|
||||
if (priv->start) {
|
||||
printk(KERN_DEBUG "pmc551: unmapping %dMiB starting at "
|
||||
"0x%p\n", priv->asize >> 20, priv->start);
|
||||
pci_iounmap(priv->dev, priv->start);
|
||||
}
|
||||
pci_dev_put(priv->dev);
|
||||
|
||||
kfree(mtd->priv);
|
||||
mtd_device_unregister(mtd);
|
||||
kfree(mtd);
|
||||
found++;
|
||||
}
|
||||
|
||||
printk(KERN_NOTICE "pmc551: %d pmc551 devices unloaded\n", found);
|
||||
}
|
||||
|
||||
module_init(init_pmc551);
|
||||
module_exit(cleanup_pmc551);
|
61
drivers/mtd/devices/serial_flash_cmds.h
Normal file
61
drivers/mtd/devices/serial_flash_cmds.h
Normal file
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* Generic/SFDP Flash Commands and Device Capabilities
|
||||
*
|
||||
* Copyright (C) 2013 Lee Jones <lee.jones@lianro.org>
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _MTD_SERIAL_FLASH_CMDS_H
|
||||
#define _MTD_SERIAL_FLASH_CMDS_H
|
||||
|
||||
/* Generic Flash Commands/OPCODEs */
|
||||
#define SPINOR_OP_RDSR2 0x35
|
||||
#define SPINOR_OP_WRVCR 0x81
|
||||
#define SPINOR_OP_RDVCR 0x85
|
||||
|
||||
/* JEDEC Standard - Serial Flash Discoverable Parmeters (SFDP) Commands */
|
||||
#define SPINOR_OP_READ_1_2_2 0xbb /* DUAL I/O READ */
|
||||
#define SPINOR_OP_READ_1_4_4 0xeb /* QUAD I/O READ */
|
||||
|
||||
#define SPINOR_OP_WRITE 0x02 /* PAGE PROGRAM */
|
||||
#define SPINOR_OP_WRITE_1_1_2 0xa2 /* DUAL INPUT PROGRAM */
|
||||
#define SPINOR_OP_WRITE_1_2_2 0xd2 /* DUAL INPUT EXT PROGRAM */
|
||||
#define SPINOR_OP_WRITE_1_1_4 0x32 /* QUAD INPUT PROGRAM */
|
||||
#define SPINOR_OP_WRITE_1_4_4 0x12 /* QUAD INPUT EXT PROGRAM */
|
||||
|
||||
/* READ commands with 32-bit addressing */
|
||||
#define SPINOR_OP_READ4_1_2_2 0xbc
|
||||
#define SPINOR_OP_READ4_1_4_4 0xec
|
||||
|
||||
/* Configuration flags */
|
||||
#define FLASH_FLAG_SINGLE 0x000000ff
|
||||
#define FLASH_FLAG_READ_WRITE 0x00000001
|
||||
#define FLASH_FLAG_READ_FAST 0x00000002
|
||||
#define FLASH_FLAG_SE_4K 0x00000004
|
||||
#define FLASH_FLAG_SE_32K 0x00000008
|
||||
#define FLASH_FLAG_CE 0x00000010
|
||||
#define FLASH_FLAG_32BIT_ADDR 0x00000020
|
||||
#define FLASH_FLAG_RESET 0x00000040
|
||||
#define FLASH_FLAG_DYB_LOCKING 0x00000080
|
||||
|
||||
#define FLASH_FLAG_DUAL 0x0000ff00
|
||||
#define FLASH_FLAG_READ_1_1_2 0x00000100
|
||||
#define FLASH_FLAG_READ_1_2_2 0x00000200
|
||||
#define FLASH_FLAG_READ_2_2_2 0x00000400
|
||||
#define FLASH_FLAG_WRITE_1_1_2 0x00001000
|
||||
#define FLASH_FLAG_WRITE_1_2_2 0x00002000
|
||||
#define FLASH_FLAG_WRITE_2_2_2 0x00004000
|
||||
|
||||
#define FLASH_FLAG_QUAD 0x00ff0000
|
||||
#define FLASH_FLAG_READ_1_1_4 0x00010000
|
||||
#define FLASH_FLAG_READ_1_4_4 0x00020000
|
||||
#define FLASH_FLAG_READ_4_4_4 0x00040000
|
||||
#define FLASH_FLAG_WRITE_1_1_4 0x00100000
|
||||
#define FLASH_FLAG_WRITE_1_4_4 0x00200000
|
||||
#define FLASH_FLAG_WRITE_4_4_4 0x00400000
|
||||
|
||||
#endif /* _MTD_SERIAL_FLASH_CMDS_H */
|
347
drivers/mtd/devices/slram.c
Normal file
347
drivers/mtd/devices/slram.c
Normal file
|
@ -0,0 +1,347 @@
|
|||
/*======================================================================
|
||||
|
||||
This driver provides a method to access memory not used by the kernel
|
||||
itself (i.e. if the kernel commandline mem=xxx is used). To actually
|
||||
use slram at least mtdblock or mtdchar is required (for block or
|
||||
character device access).
|
||||
|
||||
Usage:
|
||||
|
||||
if compiled as loadable module:
|
||||
modprobe slram map=<name>,<start>,<end/offset>
|
||||
if statically linked into the kernel use the following kernel cmd.line
|
||||
slram=<name>,<start>,<end/offset>
|
||||
|
||||
<name>: name of the device that will be listed in /proc/mtd
|
||||
<start>: start of the memory region, decimal or hex (0xabcdef)
|
||||
<end/offset>: end of the memory region. It's possible to use +0x1234
|
||||
to specify the offset instead of the absolute address
|
||||
|
||||
NOTE:
|
||||
With slram it's only possible to map a contiguous memory region. Therefore
|
||||
if there's a device mapped somewhere in the region specified slram will
|
||||
fail to load (see kernel log if modprobe fails).
|
||||
|
||||
-
|
||||
|
||||
Jochen Schaeuble <psionic@psionic.de>
|
||||
|
||||
======================================================================*/
|
||||
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <asm/uaccess.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/ptrace.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/timer.h>
|
||||
#include <linux/major.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/ioctl.h>
|
||||
#include <linux/init.h>
|
||||
#include <asm/io.h>
|
||||
|
||||
#include <linux/mtd/mtd.h>
|
||||
|
||||
#define SLRAM_MAX_DEVICES_PARAMS 6 /* 3 parameters / device */
|
||||
#define SLRAM_BLK_SZ 0x4000
|
||||
|
||||
#define T(fmt, args...) printk(KERN_DEBUG fmt, ## args)
|
||||
#define E(fmt, args...) printk(KERN_NOTICE fmt, ## args)
|
||||
|
||||
typedef struct slram_priv {
|
||||
u_char *start;
|
||||
u_char *end;
|
||||
} slram_priv_t;
|
||||
|
||||
typedef struct slram_mtd_list {
|
||||
struct mtd_info *mtdinfo;
|
||||
struct slram_mtd_list *next;
|
||||
} slram_mtd_list_t;
|
||||
|
||||
#ifdef MODULE
|
||||
static char *map[SLRAM_MAX_DEVICES_PARAMS];
|
||||
|
||||
module_param_array(map, charp, NULL, 0);
|
||||
MODULE_PARM_DESC(map, "List of memory regions to map. \"map=<name>, <start>, <length / end>\"");
|
||||
#else
|
||||
static char *map;
|
||||
#endif
|
||||
|
||||
static slram_mtd_list_t *slram_mtdlist = NULL;
|
||||
|
||||
static int slram_erase(struct mtd_info *, struct erase_info *);
|
||||
static int slram_point(struct mtd_info *, loff_t, size_t, size_t *, void **,
|
||||
resource_size_t *);
|
||||
static int slram_unpoint(struct mtd_info *, loff_t, size_t);
|
||||
static int slram_read(struct mtd_info *, loff_t, size_t, size_t *, u_char *);
|
||||
static int slram_write(struct mtd_info *, loff_t, size_t, size_t *, const u_char *);
|
||||
|
||||
static int slram_erase(struct mtd_info *mtd, struct erase_info *instr)
|
||||
{
|
||||
slram_priv_t *priv = mtd->priv;
|
||||
|
||||
memset(priv->start + instr->addr, 0xff, instr->len);
|
||||
/* This'll catch a few races. Free the thing before returning :)
|
||||
* I don't feel at all ashamed. This kind of thing is possible anyway
|
||||
* with flash, but unlikely.
|
||||
*/
|
||||
instr->state = MTD_ERASE_DONE;
|
||||
mtd_erase_callback(instr);
|
||||
return(0);
|
||||
}
|
||||
|
||||
static int slram_point(struct mtd_info *mtd, loff_t from, size_t len,
|
||||
size_t *retlen, void **virt, resource_size_t *phys)
|
||||
{
|
||||
slram_priv_t *priv = mtd->priv;
|
||||
|
||||
*virt = priv->start + from;
|
||||
*retlen = len;
|
||||
return(0);
|
||||
}
|
||||
|
||||
static int slram_unpoint(struct mtd_info *mtd, loff_t from, size_t len)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int slram_read(struct mtd_info *mtd, loff_t from, size_t len,
|
||||
size_t *retlen, u_char *buf)
|
||||
{
|
||||
slram_priv_t *priv = mtd->priv;
|
||||
|
||||
memcpy(buf, priv->start + from, len);
|
||||
*retlen = len;
|
||||
return(0);
|
||||
}
|
||||
|
||||
static int slram_write(struct mtd_info *mtd, loff_t to, size_t len,
|
||||
size_t *retlen, const u_char *buf)
|
||||
{
|
||||
slram_priv_t *priv = mtd->priv;
|
||||
|
||||
memcpy(priv->start + to, buf, len);
|
||||
*retlen = len;
|
||||
return(0);
|
||||
}
|
||||
|
||||
/*====================================================================*/
|
||||
|
||||
static int register_device(char *name, unsigned long start, unsigned long length)
|
||||
{
|
||||
slram_mtd_list_t **curmtd;
|
||||
|
||||
curmtd = &slram_mtdlist;
|
||||
while (*curmtd) {
|
||||
curmtd = &(*curmtd)->next;
|
||||
}
|
||||
|
||||
*curmtd = kmalloc(sizeof(slram_mtd_list_t), GFP_KERNEL);
|
||||
if (!(*curmtd)) {
|
||||
E("slram: Cannot allocate new MTD device.\n");
|
||||
return(-ENOMEM);
|
||||
}
|
||||
(*curmtd)->mtdinfo = kzalloc(sizeof(struct mtd_info), GFP_KERNEL);
|
||||
(*curmtd)->next = NULL;
|
||||
|
||||
if ((*curmtd)->mtdinfo) {
|
||||
(*curmtd)->mtdinfo->priv =
|
||||
kzalloc(sizeof(slram_priv_t), GFP_KERNEL);
|
||||
|
||||
if (!(*curmtd)->mtdinfo->priv) {
|
||||
kfree((*curmtd)->mtdinfo);
|
||||
(*curmtd)->mtdinfo = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (!(*curmtd)->mtdinfo) {
|
||||
E("slram: Cannot allocate new MTD device.\n");
|
||||
return(-ENOMEM);
|
||||
}
|
||||
|
||||
if (!(((slram_priv_t *)(*curmtd)->mtdinfo->priv)->start =
|
||||
ioremap(start, length))) {
|
||||
E("slram: ioremap failed\n");
|
||||
return -EIO;
|
||||
}
|
||||
((slram_priv_t *)(*curmtd)->mtdinfo->priv)->end =
|
||||
((slram_priv_t *)(*curmtd)->mtdinfo->priv)->start + length;
|
||||
|
||||
|
||||
(*curmtd)->mtdinfo->name = name;
|
||||
(*curmtd)->mtdinfo->size = length;
|
||||
(*curmtd)->mtdinfo->flags = MTD_CAP_RAM;
|
||||
(*curmtd)->mtdinfo->_erase = slram_erase;
|
||||
(*curmtd)->mtdinfo->_point = slram_point;
|
||||
(*curmtd)->mtdinfo->_unpoint = slram_unpoint;
|
||||
(*curmtd)->mtdinfo->_read = slram_read;
|
||||
(*curmtd)->mtdinfo->_write = slram_write;
|
||||
(*curmtd)->mtdinfo->owner = THIS_MODULE;
|
||||
(*curmtd)->mtdinfo->type = MTD_RAM;
|
||||
(*curmtd)->mtdinfo->erasesize = SLRAM_BLK_SZ;
|
||||
(*curmtd)->mtdinfo->writesize = 1;
|
||||
|
||||
if (mtd_device_register((*curmtd)->mtdinfo, NULL, 0)) {
|
||||
E("slram: Failed to register new device\n");
|
||||
iounmap(((slram_priv_t *)(*curmtd)->mtdinfo->priv)->start);
|
||||
kfree((*curmtd)->mtdinfo->priv);
|
||||
kfree((*curmtd)->mtdinfo);
|
||||
return(-EAGAIN);
|
||||
}
|
||||
T("slram: Registered device %s from %luKiB to %luKiB\n", name,
|
||||
(start / 1024), ((start + length) / 1024));
|
||||
T("slram: Mapped from 0x%p to 0x%p\n",
|
||||
((slram_priv_t *)(*curmtd)->mtdinfo->priv)->start,
|
||||
((slram_priv_t *)(*curmtd)->mtdinfo->priv)->end);
|
||||
return(0);
|
||||
}
|
||||
|
||||
static void unregister_devices(void)
|
||||
{
|
||||
slram_mtd_list_t *nextitem;
|
||||
|
||||
while (slram_mtdlist) {
|
||||
nextitem = slram_mtdlist->next;
|
||||
mtd_device_unregister(slram_mtdlist->mtdinfo);
|
||||
iounmap(((slram_priv_t *)slram_mtdlist->mtdinfo->priv)->start);
|
||||
kfree(slram_mtdlist->mtdinfo->priv);
|
||||
kfree(slram_mtdlist->mtdinfo);
|
||||
kfree(slram_mtdlist);
|
||||
slram_mtdlist = nextitem;
|
||||
}
|
||||
}
|
||||
|
||||
static unsigned long handle_unit(unsigned long value, char *unit)
|
||||
{
|
||||
if ((*unit == 'M') || (*unit == 'm')) {
|
||||
return(value * 1024 * 1024);
|
||||
} else if ((*unit == 'K') || (*unit == 'k')) {
|
||||
return(value * 1024);
|
||||
}
|
||||
return(value);
|
||||
}
|
||||
|
||||
static int parse_cmdline(char *devname, char *szstart, char *szlength)
|
||||
{
|
||||
char *buffer;
|
||||
unsigned long devstart;
|
||||
unsigned long devlength;
|
||||
|
||||
if ((!devname) || (!szstart) || (!szlength)) {
|
||||
unregister_devices();
|
||||
return(-EINVAL);
|
||||
}
|
||||
|
||||
devstart = simple_strtoul(szstart, &buffer, 0);
|
||||
devstart = handle_unit(devstart, buffer);
|
||||
|
||||
if (*(szlength) != '+') {
|
||||
devlength = simple_strtoul(szlength, &buffer, 0);
|
||||
devlength = handle_unit(devlength, buffer);
|
||||
if (devlength < devstart)
|
||||
goto err_out;
|
||||
|
||||
devlength -= devstart;
|
||||
} else {
|
||||
devlength = simple_strtoul(szlength + 1, &buffer, 0);
|
||||
devlength = handle_unit(devlength, buffer);
|
||||
}
|
||||
T("slram: devname=%s, devstart=0x%lx, devlength=0x%lx\n",
|
||||
devname, devstart, devlength);
|
||||
if (devlength % SLRAM_BLK_SZ != 0)
|
||||
goto err_out;
|
||||
|
||||
if ((devstart = register_device(devname, devstart, devlength))){
|
||||
unregister_devices();
|
||||
return((int)devstart);
|
||||
}
|
||||
return(0);
|
||||
|
||||
err_out:
|
||||
E("slram: Illegal length parameter.\n");
|
||||
return(-EINVAL);
|
||||
}
|
||||
|
||||
#ifndef MODULE
|
||||
|
||||
static int __init mtd_slram_setup(char *str)
|
||||
{
|
||||
map = str;
|
||||
return(1);
|
||||
}
|
||||
|
||||
__setup("slram=", mtd_slram_setup);
|
||||
|
||||
#endif
|
||||
|
||||
static int __init init_slram(void)
|
||||
{
|
||||
char *devname;
|
||||
|
||||
#ifndef MODULE
|
||||
char *devstart;
|
||||
char *devlength;
|
||||
|
||||
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;
|
||||
int i;
|
||||
|
||||
for (count = 0; count < SLRAM_MAX_DEVICES_PARAMS && map[count];
|
||||
count++) {
|
||||
}
|
||||
|
||||
if ((count % 3 != 0) || (count == 0)) {
|
||||
E("slram: not enough parameters.\n");
|
||||
return(-EINVAL);
|
||||
}
|
||||
for (i = 0; i < (count / 3); i++) {
|
||||
devname = map[i * 3];
|
||||
|
||||
if (parse_cmdline(devname, map[i * 3 + 1], map[i * 3 + 2])!=0) {
|
||||
return(-EINVAL);
|
||||
}
|
||||
|
||||
}
|
||||
#endif /* !MODULE */
|
||||
|
||||
return(0);
|
||||
}
|
||||
|
||||
static void __exit cleanup_slram(void)
|
||||
{
|
||||
unregister_devices();
|
||||
}
|
||||
|
||||
module_init(init_slram);
|
||||
module_exit(cleanup_slram);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Jochen Schaeuble <psionic@psionic.de>");
|
||||
MODULE_DESCRIPTION("MTD driver for uncached system RAM");
|
1093
drivers/mtd/devices/spear_smi.c
Normal file
1093
drivers/mtd/devices/spear_smi.c
Normal file
File diff suppressed because it is too large
Load diff
431
drivers/mtd/devices/sst25l.c
Normal file
431
drivers/mtd/devices/sst25l.c
Normal file
|
@ -0,0 +1,431 @@
|
|||
/*
|
||||
* sst25l.c
|
||||
*
|
||||
* Driver for SST25L SPI Flash chips
|
||||
*
|
||||
* Copyright © 2009 Bluewater Systems Ltd
|
||||
* Author: Andre Renaud <andre@bluewatersys.com>
|
||||
* Author: Ryan Mallon
|
||||
*
|
||||
* Based on m25p80.c
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/sched.h>
|
||||
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include <linux/mtd/partitions.h>
|
||||
|
||||
#include <linux/spi/spi.h>
|
||||
#include <linux/spi/flash.h>
|
||||
|
||||
/* Erases can take up to 3 seconds! */
|
||||
#define MAX_READY_WAIT_JIFFIES msecs_to_jiffies(3000)
|
||||
|
||||
#define SST25L_CMD_WRSR 0x01 /* Write status register */
|
||||
#define SST25L_CMD_WRDI 0x04 /* Write disable */
|
||||
#define SST25L_CMD_RDSR 0x05 /* Read status register */
|
||||
#define SST25L_CMD_WREN 0x06 /* Write enable */
|
||||
#define SST25L_CMD_READ 0x03 /* High speed read */
|
||||
|
||||
#define SST25L_CMD_EWSR 0x50 /* Enable write status register */
|
||||
#define SST25L_CMD_SECTOR_ERASE 0x20 /* Erase sector */
|
||||
#define SST25L_CMD_READ_ID 0x90 /* Read device ID */
|
||||
#define SST25L_CMD_AAI_PROGRAM 0xaf /* Auto address increment */
|
||||
|
||||
#define SST25L_STATUS_BUSY (1 << 0) /* Chip is busy */
|
||||
#define SST25L_STATUS_WREN (1 << 1) /* Write enabled */
|
||||
#define SST25L_STATUS_BP0 (1 << 2) /* Block protection 0 */
|
||||
#define SST25L_STATUS_BP1 (1 << 3) /* Block protection 1 */
|
||||
|
||||
struct sst25l_flash {
|
||||
struct spi_device *spi;
|
||||
struct mutex lock;
|
||||
struct mtd_info mtd;
|
||||
};
|
||||
|
||||
struct flash_info {
|
||||
const char *name;
|
||||
uint16_t device_id;
|
||||
unsigned page_size;
|
||||
unsigned nr_pages;
|
||||
unsigned erase_size;
|
||||
};
|
||||
|
||||
#define to_sst25l_flash(x) container_of(x, struct sst25l_flash, mtd)
|
||||
|
||||
static struct flash_info sst25l_flash_info[] = {
|
||||
{"sst25lf020a", 0xbf43, 256, 1024, 4096},
|
||||
{"sst25lf040a", 0xbf44, 256, 2048, 4096},
|
||||
};
|
||||
|
||||
static int sst25l_status(struct sst25l_flash *flash, int *status)
|
||||
{
|
||||
struct spi_message m;
|
||||
struct spi_transfer t;
|
||||
unsigned char cmd_resp[2];
|
||||
int err;
|
||||
|
||||
spi_message_init(&m);
|
||||
memset(&t, 0, sizeof(struct spi_transfer));
|
||||
|
||||
cmd_resp[0] = SST25L_CMD_RDSR;
|
||||
cmd_resp[1] = 0xff;
|
||||
t.tx_buf = cmd_resp;
|
||||
t.rx_buf = cmd_resp;
|
||||
t.len = sizeof(cmd_resp);
|
||||
spi_message_add_tail(&t, &m);
|
||||
err = spi_sync(flash->spi, &m);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
*status = cmd_resp[1];
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sst25l_write_enable(struct sst25l_flash *flash, int enable)
|
||||
{
|
||||
unsigned char command[2];
|
||||
int status, err;
|
||||
|
||||
command[0] = enable ? SST25L_CMD_WREN : SST25L_CMD_WRDI;
|
||||
err = spi_write(flash->spi, command, 1);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
command[0] = SST25L_CMD_EWSR;
|
||||
err = spi_write(flash->spi, command, 1);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
command[0] = SST25L_CMD_WRSR;
|
||||
command[1] = enable ? 0 : SST25L_STATUS_BP0 | SST25L_STATUS_BP1;
|
||||
err = spi_write(flash->spi, command, 2);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
if (enable) {
|
||||
err = sst25l_status(flash, &status);
|
||||
if (err)
|
||||
return err;
|
||||
if (!(status & SST25L_STATUS_WREN))
|
||||
return -EROFS;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sst25l_wait_till_ready(struct sst25l_flash *flash)
|
||||
{
|
||||
unsigned long deadline;
|
||||
int status, err;
|
||||
|
||||
deadline = jiffies + MAX_READY_WAIT_JIFFIES;
|
||||
do {
|
||||
err = sst25l_status(flash, &status);
|
||||
if (err)
|
||||
return err;
|
||||
if (!(status & SST25L_STATUS_BUSY))
|
||||
return 0;
|
||||
|
||||
cond_resched();
|
||||
} while (!time_after_eq(jiffies, deadline));
|
||||
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
static int sst25l_erase_sector(struct sst25l_flash *flash, uint32_t offset)
|
||||
{
|
||||
unsigned char command[4];
|
||||
int err;
|
||||
|
||||
err = sst25l_write_enable(flash, 1);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
command[0] = SST25L_CMD_SECTOR_ERASE;
|
||||
command[1] = offset >> 16;
|
||||
command[2] = offset >> 8;
|
||||
command[3] = offset;
|
||||
err = spi_write(flash->spi, command, 4);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
err = sst25l_wait_till_ready(flash);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
return sst25l_write_enable(flash, 0);
|
||||
}
|
||||
|
||||
static int sst25l_erase(struct mtd_info *mtd, struct erase_info *instr)
|
||||
{
|
||||
struct sst25l_flash *flash = to_sst25l_flash(mtd);
|
||||
uint32_t addr, end;
|
||||
int err;
|
||||
|
||||
/* Sanity checks */
|
||||
if ((uint32_t)instr->len % mtd->erasesize)
|
||||
return -EINVAL;
|
||||
|
||||
if ((uint32_t)instr->addr % mtd->erasesize)
|
||||
return -EINVAL;
|
||||
|
||||
addr = instr->addr;
|
||||
end = addr + instr->len;
|
||||
|
||||
mutex_lock(&flash->lock);
|
||||
|
||||
err = sst25l_wait_till_ready(flash);
|
||||
if (err) {
|
||||
mutex_unlock(&flash->lock);
|
||||
return err;
|
||||
}
|
||||
|
||||
while (addr < end) {
|
||||
err = sst25l_erase_sector(flash, addr);
|
||||
if (err) {
|
||||
mutex_unlock(&flash->lock);
|
||||
instr->state = MTD_ERASE_FAILED;
|
||||
dev_err(&flash->spi->dev, "Erase failed\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
addr += mtd->erasesize;
|
||||
}
|
||||
|
||||
mutex_unlock(&flash->lock);
|
||||
|
||||
instr->state = MTD_ERASE_DONE;
|
||||
mtd_erase_callback(instr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sst25l_read(struct mtd_info *mtd, loff_t from, size_t len,
|
||||
size_t *retlen, unsigned char *buf)
|
||||
{
|
||||
struct sst25l_flash *flash = to_sst25l_flash(mtd);
|
||||
struct spi_transfer transfer[2];
|
||||
struct spi_message message;
|
||||
unsigned char command[4];
|
||||
int ret;
|
||||
|
||||
spi_message_init(&message);
|
||||
memset(&transfer, 0, sizeof(transfer));
|
||||
|
||||
command[0] = SST25L_CMD_READ;
|
||||
command[1] = from >> 16;
|
||||
command[2] = from >> 8;
|
||||
command[3] = from;
|
||||
|
||||
transfer[0].tx_buf = command;
|
||||
transfer[0].len = sizeof(command);
|
||||
spi_message_add_tail(&transfer[0], &message);
|
||||
|
||||
transfer[1].rx_buf = buf;
|
||||
transfer[1].len = len;
|
||||
spi_message_add_tail(&transfer[1], &message);
|
||||
|
||||
mutex_lock(&flash->lock);
|
||||
|
||||
/* Wait for previous write/erase to complete */
|
||||
ret = sst25l_wait_till_ready(flash);
|
||||
if (ret) {
|
||||
mutex_unlock(&flash->lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
spi_sync(flash->spi, &message);
|
||||
|
||||
if (retlen && message.actual_length > sizeof(command))
|
||||
*retlen += message.actual_length - sizeof(command);
|
||||
|
||||
mutex_unlock(&flash->lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sst25l_write(struct mtd_info *mtd, loff_t to, size_t len,
|
||||
size_t *retlen, const unsigned char *buf)
|
||||
{
|
||||
struct sst25l_flash *flash = to_sst25l_flash(mtd);
|
||||
int i, j, ret, bytes, copied = 0;
|
||||
unsigned char command[5];
|
||||
|
||||
if ((uint32_t)to % mtd->writesize)
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&flash->lock);
|
||||
|
||||
ret = sst25l_write_enable(flash, 1);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
for (i = 0; i < len; i += mtd->writesize) {
|
||||
ret = sst25l_wait_till_ready(flash);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
/* Write the first byte of the page */
|
||||
command[0] = SST25L_CMD_AAI_PROGRAM;
|
||||
command[1] = (to + i) >> 16;
|
||||
command[2] = (to + i) >> 8;
|
||||
command[3] = (to + i);
|
||||
command[4] = buf[i];
|
||||
ret = spi_write(flash->spi, command, 5);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
copied++;
|
||||
|
||||
/*
|
||||
* Write the remaining bytes using auto address
|
||||
* increment mode
|
||||
*/
|
||||
bytes = min_t(uint32_t, mtd->writesize, len - i);
|
||||
for (j = 1; j < bytes; j++, copied++) {
|
||||
ret = sst25l_wait_till_ready(flash);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
command[1] = buf[i + j];
|
||||
ret = spi_write(flash->spi, command, 2);
|
||||
if (ret)
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
out:
|
||||
ret = sst25l_write_enable(flash, 0);
|
||||
|
||||
if (retlen)
|
||||
*retlen = copied;
|
||||
|
||||
mutex_unlock(&flash->lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct flash_info *sst25l_match_device(struct spi_device *spi)
|
||||
{
|
||||
struct flash_info *flash_info = NULL;
|
||||
struct spi_message m;
|
||||
struct spi_transfer t;
|
||||
unsigned char cmd_resp[6];
|
||||
int i, err;
|
||||
uint16_t id;
|
||||
|
||||
spi_message_init(&m);
|
||||
memset(&t, 0, sizeof(struct spi_transfer));
|
||||
|
||||
cmd_resp[0] = SST25L_CMD_READ_ID;
|
||||
cmd_resp[1] = 0;
|
||||
cmd_resp[2] = 0;
|
||||
cmd_resp[3] = 0;
|
||||
cmd_resp[4] = 0xff;
|
||||
cmd_resp[5] = 0xff;
|
||||
t.tx_buf = cmd_resp;
|
||||
t.rx_buf = cmd_resp;
|
||||
t.len = sizeof(cmd_resp);
|
||||
spi_message_add_tail(&t, &m);
|
||||
err = spi_sync(spi, &m);
|
||||
if (err < 0) {
|
||||
dev_err(&spi->dev, "error reading device id\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
id = (cmd_resp[4] << 8) | cmd_resp[5];
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(sst25l_flash_info); i++)
|
||||
if (sst25l_flash_info[i].device_id == id)
|
||||
flash_info = &sst25l_flash_info[i];
|
||||
|
||||
if (!flash_info)
|
||||
dev_err(&spi->dev, "unknown id %.4x\n", id);
|
||||
|
||||
return flash_info;
|
||||
}
|
||||
|
||||
static int sst25l_probe(struct spi_device *spi)
|
||||
{
|
||||
struct flash_info *flash_info;
|
||||
struct sst25l_flash *flash;
|
||||
struct flash_platform_data *data;
|
||||
int ret;
|
||||
|
||||
flash_info = sst25l_match_device(spi);
|
||||
if (!flash_info)
|
||||
return -ENODEV;
|
||||
|
||||
flash = devm_kzalloc(&spi->dev, sizeof(*flash), GFP_KERNEL);
|
||||
if (!flash)
|
||||
return -ENOMEM;
|
||||
|
||||
flash->spi = spi;
|
||||
mutex_init(&flash->lock);
|
||||
spi_set_drvdata(spi, flash);
|
||||
|
||||
data = dev_get_platdata(&spi->dev);
|
||||
if (data && data->name)
|
||||
flash->mtd.name = data->name;
|
||||
else
|
||||
flash->mtd.name = dev_name(&spi->dev);
|
||||
|
||||
flash->mtd.type = MTD_NORFLASH;
|
||||
flash->mtd.flags = MTD_CAP_NORFLASH;
|
||||
flash->mtd.erasesize = flash_info->erase_size;
|
||||
flash->mtd.writesize = flash_info->page_size;
|
||||
flash->mtd.writebufsize = flash_info->page_size;
|
||||
flash->mtd.size = flash_info->page_size * flash_info->nr_pages;
|
||||
flash->mtd._erase = sst25l_erase;
|
||||
flash->mtd._read = sst25l_read;
|
||||
flash->mtd._write = sst25l_write;
|
||||
|
||||
dev_info(&spi->dev, "%s (%lld KiB)\n", flash_info->name,
|
||||
(long long)flash->mtd.size >> 10);
|
||||
|
||||
pr_debug("mtd .name = %s, .size = 0x%llx (%lldMiB) "
|
||||
".erasesize = 0x%.8x (%uKiB) .numeraseregions = %d\n",
|
||||
flash->mtd.name,
|
||||
(long long)flash->mtd.size, (long long)(flash->mtd.size >> 20),
|
||||
flash->mtd.erasesize, flash->mtd.erasesize / 1024,
|
||||
flash->mtd.numeraseregions);
|
||||
|
||||
|
||||
ret = mtd_device_parse_register(&flash->mtd, NULL, NULL,
|
||||
data ? data->parts : NULL,
|
||||
data ? data->nr_parts : 0);
|
||||
if (ret)
|
||||
return -ENODEV;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sst25l_remove(struct spi_device *spi)
|
||||
{
|
||||
struct sst25l_flash *flash = spi_get_drvdata(spi);
|
||||
|
||||
return mtd_device_unregister(&flash->mtd);
|
||||
}
|
||||
|
||||
static struct spi_driver sst25l_driver = {
|
||||
.driver = {
|
||||
.name = "sst25l",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = sst25l_probe,
|
||||
.remove = sst25l_remove,
|
||||
};
|
||||
|
||||
module_spi_driver(sst25l_driver);
|
||||
|
||||
MODULE_DESCRIPTION("MTD SPI driver for SST25L Flash chips");
|
||||
MODULE_AUTHOR("Andre Renaud <andre@bluewatersys.com>, "
|
||||
"Ryan Mallon");
|
||||
MODULE_LICENSE("GPL");
|
2080
drivers/mtd/devices/st_spi_fsm.c
Normal file
2080
drivers/mtd/devices/st_spi_fsm.c
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue