diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:49:45 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:49:45 +0000 |
commit | 2c3c1048746a4622d8c89a29670120dc8fab93c4 (patch) | |
tree | 848558de17fb3008cdf4d861b01ac7781903ce39 /drivers/mtd/devices | |
parent | Initial commit. (diff) | |
download | linux-2c3c1048746a4622d8c89a29670120dc8fab93c4.tar.xz linux-2c3c1048746a4622d8c89a29670120dc8fab93c4.zip |
Adding upstream version 6.1.76.upstream/6.1.76upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/mtd/devices')
-rw-r--r-- | drivers/mtd/devices/Kconfig | 224 | ||||
-rw-r--r-- | drivers/mtd/devices/Makefile | 24 | ||||
-rw-r--r-- | drivers/mtd/devices/bcm47xxsflash.c | 382 | ||||
-rw-r--r-- | drivers/mtd/devices/bcm47xxsflash.h | 81 | ||||
-rw-r--r-- | drivers/mtd/devices/block2mtd.c | 511 | ||||
-rw-r--r-- | drivers/mtd/devices/docg3.c | 2086 | ||||
-rw-r--r-- | drivers/mtd/devices/docg3.h | 343 | ||||
-rw-r--r-- | drivers/mtd/devices/lart.c | 682 | ||||
-rw-r--r-- | drivers/mtd/devices/mchp23k256.c | 260 | ||||
-rw-r--r-- | drivers/mtd/devices/mchp48l640.c | 384 | ||||
-rw-r--r-- | drivers/mtd/devices/ms02-nv.c | 306 | ||||
-rw-r--r-- | drivers/mtd/devices/ms02-nv.h | 101 | ||||
-rw-r--r-- | drivers/mtd/devices/mtd_dataflash.c | 956 | ||||
-rw-r--r-- | drivers/mtd/devices/mtdram.c | 187 | ||||
-rw-r--r-- | drivers/mtd/devices/phram.c | 442 | ||||
-rw-r--r-- | drivers/mtd/devices/pmc551.c | 847 | ||||
-rw-r--r-- | drivers/mtd/devices/powernv_flash.c | 297 | ||||
-rw-r--r-- | drivers/mtd/devices/serial_flash_cmds.h | 49 | ||||
-rw-r--r-- | drivers/mtd/devices/slram.c | 344 | ||||
-rw-r--r-- | drivers/mtd/devices/spear_smi.c | 1116 | ||||
-rw-r--r-- | drivers/mtd/devices/sst25l.c | 421 | ||||
-rw-r--r-- | drivers/mtd/devices/st_spi_fsm.c | 2174 |
22 files changed, 12217 insertions, 0 deletions
diff --git a/drivers/mtd/devices/Kconfig b/drivers/mtd/devices/Kconfig new file mode 100644 index 000000000..79cb981ec --- /dev/null +++ b/drivers/mtd/devices/Kconfig @@ -0,0 +1,224 @@ +# SPDX-License-Identifier: GPL-2.0-only +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.rst>. + 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_MCHP23K256 + tristate "Microchip 23K256 SRAM" + depends on SPI_MASTER + help + This enables access to Microchip 23K256 SRAM chips, using SPI. + + Set up your spi devices with the right board-specific + platform data, or a device tree description if you want to + specify device partitioning + +config MTD_MCHP48L640 + tristate "Microchip 48L640 EERAM" + depends on SPI_MASTER + help + This enables access to Microchip 48L640 EERAM chips, using SPI. + +config MTD_SPEAR_SMI + tristate "SPEAR MTD NOR Support through SMI controller" + depends on PLAT_SPEAR || COMPILE_TEST + 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 "Support for serial flash on BCMA bus" + depends on BCMA_SFLASH && (MIPS || ARM) + help + BCMA bus can have various flash memories attached, they are + registered by bcma as platform devices. This enables driver for + serial flash memories. + +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. + +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). + +config MTD_POWERNV_FLASH + tristate "powernv flash MTD driver" + depends on PPC_POWERNV + help + This provides an MTD device to access flash on powernv OPAL + platforms from Linux. This device abstracts away the + firmware interface for flash access. + +comment "Disk-On-Chip Device Drivers" + +config MTD_DOCG3 + tristate "M-Systems Disk-On-Chip G3" + select BCH + select BCH_CONST_PARAMS if !MTD_NAND_ECC_SW_BCH + 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 diff --git a/drivers/mtd/devices/Makefile b/drivers/mtd/devices/Makefile new file mode 100644 index 000000000..0362cf6bd --- /dev/null +++ b/drivers/mtd/devices/Makefile @@ -0,0 +1,24 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# 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_MCHP23K256) += mchp23k256.o +obj-$(CONFIG_MTD_MCHP48L640) += mchp48l640.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 +obj-$(CONFIG_MTD_POWERNV_FLASH) += powernv_flash.o + + +CFLAGS_docg3.o += -I$(src) diff --git a/drivers/mtd/devices/bcm47xxsflash.c b/drivers/mtd/devices/bcm47xxsflash.c new file mode 100644 index 000000000..3af50db8b --- /dev/null +++ b/drivers/mtd/devices/bcm47xxsflash.c @@ -0,0 +1,382 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/ioport.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; + + 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; + } + + return bcm47xxsflash_poll(b47s, HZ); +} + +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; + size_t orig_len = len; + + /* Check address range */ + if ((from + len) > mtd->size) + return -EINVAL; + + /* Read as much as possible using fast MMIO window */ + if (from < BCM47XXSFLASH_WINDOW_SZ) { + size_t memcpy_len; + + memcpy_len = min(len, (size_t)(BCM47XXSFLASH_WINDOW_SZ - from)); + memcpy_fromio(buf, b47s->window + from, memcpy_len); + from += memcpy_len; + len -= memcpy_len; + buf += memcpy_len; + } + + /* Use indirect access for content out of the window */ + for (; len; len--) { + b47s->cc_write(b47s, BCMA_CC_FLASHADDR, from++); + bcm47xxsflash_cmd(b47s, OPCODE_ST_READ4B); + *buf++ = b47s->cc_read(b47s, BCMA_CC_FLASHDATA); + } + + *retlen = orig_len; + + return orig_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 device *dev) +{ + struct mtd_info *mtd = &b47s->mtd; + + mtd->priv = b47s; + mtd->dev.parent = dev; + mtd->name = "bcm47xxsflash"; + + 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 device *dev = &pdev->dev; + struct bcma_sflash *sflash = dev_get_platdata(dev); + struct bcm47xxsflash *b47s; + struct resource *res; + int err; + + b47s = devm_kzalloc(dev, sizeof(*b47s), GFP_KERNEL); + if (!b47s) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(dev, "invalid resource\n"); + return -EINVAL; + } + if (!devm_request_mem_region(dev, res->start, resource_size(res), + res->name)) { + dev_err(dev, "can't request region for resource %pR\n", res); + return -EBUSY; + } + + 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; + + /* + * On old MIPS devices cache was magically invalidated when needed, + * allowing us to use cached access and gain some performance. Trying + * the same on ARM based BCM53573 results in flash corruptions, we need + * to use uncached access for it. + * + * It may be arch specific, but right now there is only 1 ARM SoC using + * this driver, so let's follow Broadcom's reference code and check + * ChipCommon revision. + */ + if (b47s->bcma_cc->core->id.rev == 54) + b47s->window = ioremap(res->start, resource_size(res)); + else + b47s->window = ioremap_cache(res->start, resource_size(res)); + if (!b47s->window) { + dev_err(dev, "ioremap failed for resource %pR\n", res); + return -ENOMEM; + } + + 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->blocksize = sflash->blocksize; + b47s->numblocks = sflash->numblocks; + b47s->size = sflash->size; + bcm47xxsflash_fill_mtd(b47s, &pdev->dev); + + platform_set_drvdata(pdev, b47s); + + err = mtd_device_parse_register(&b47s->mtd, probes, NULL, NULL, 0); + if (err) { + pr_err("Failed to register MTD device: %d\n", err); + iounmap(b47s->window); + 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 bcm47xxsflash *b47s = platform_get_drvdata(pdev); + + mtd_device_unregister(&b47s->mtd); + iounmap(b47s->window); + + return 0; +} + +static struct platform_driver bcma_sflash_driver = { + .probe = bcm47xxsflash_bcma_probe, + .remove = bcm47xxsflash_bcma_remove, + .driver = { + .name = "bcma_sflash", + }, +}; + +/************************************************** + * Init + **************************************************/ + +module_platform_driver(bcma_sflash_driver); diff --git a/drivers/mtd/devices/bcm47xxsflash.h b/drivers/mtd/devices/bcm47xxsflash.h new file mode 100644 index 000000000..fef0d5e42 --- /dev/null +++ b/drivers/mtd/devices/bcm47xxsflash.h @@ -0,0 +1,81 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __BCM47XXSFLASH_H +#define __BCM47XXSFLASH_H + +#include <linux/mtd/mtd.h> + +#define BCM47XXSFLASH_WINDOW_SZ SZ_16M + +/* 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 */ +#define OPCODE_ST_READ4B 0x6313 /* Read Data Bytes in 4Byte addressing mode */ + +/* 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; + + void __iomem *window; + + u32 blocksize; + u16 numblocks; + u32 size; + + struct mtd_info mtd; +}; + +#endif /* BCM47XXSFLASH */ diff --git a/drivers/mtd/devices/block2mtd.c b/drivers/mtd/devices/block2mtd.c new file mode 100644 index 000000000..4cd37ec45 --- /dev/null +++ b/drivers/mtd/devices/block2mtd.c @@ -0,0 +1,511 @@ +/* + * 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 + +/* + * When the first attempt at device initialization fails, we may need to + * wait a little bit and retry. This timeout, by default 3 seconds, gives + * device time to start up. Required on BCM2708 and a few other chipsets. + */ +#define MTD_DEFAULT_TIMEOUT 3 + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/fs.h> +#include <linux/blkdev.h> +#include <linux/backing-dev.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> + +/* Maximum number of comma-separated items in the 'block2mtd=' parameter */ +#define BLOCK2MTD_PARAM_MAX_COUNT 3 + +/* 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, pgoff_t 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; + pgoff_t 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; + } + + put_page(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; + + 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); + + 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; + pgoff_t 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); + put_page(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; + pgoff_t 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); + } + put_page(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, + char *label, int timeout) +{ +#ifndef MODULE + int i; +#endif + 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 + /* + * We might not have the root device mounted at this point. + * Try to resolve the device name by other means. + */ + for (i = 0; IS_ERR(bdev) && i <= timeout; i++) { + dev_t devt; + + if (i) + /* + * Calling wait_for_device_probe in the first loop + * was not enough, sleep for a bit in subsequent + * go-arounds. + */ + msleep(1000); + wait_for_device_probe(); + + devt = name_to_dev_t(devname); + if (!devt) + continue; + 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 */ + if (!label) + name = kasprintf(GFP_KERNEL, "block2mtd: %s", devname); + else + name = kstrdup(label, GFP_KERNEL); + 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, + label ? label : 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; + fallthrough; + case 'M': + result *= 1024; + fallthrough; + 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; +/* 80 for device, 12 for erase size */ +static char block2mtd_paramline[80 + 12]; +#endif + +static int block2mtd_setup2(const char *val) +{ + /* 80 for device, 12 for erase size, 80 for name, 8 for timeout */ + char buf[80 + 12 + 80 + 8]; + char *str = buf; + char *token[BLOCK2MTD_PARAM_MAX_COUNT]; + char *name; + char *label = NULL; + size_t erase_size = PAGE_SIZE; + unsigned long timeout = MTD_DEFAULT_TIMEOUT; + 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 < BLOCK2MTD_PARAM_MAX_COUNT; 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; + } + + /* Optional argument when custom label is used */ + if (token[1] && strlen(token[1])) { + ret = parse_num(&erase_size, token[1]); + if (ret) { + pr_err("illegal erase size\n"); + return 0; + } + } + + if (token[2]) { + label = token[2]; + pr_info("Using custom MTD label '%s' for dev %s\n", label, name); + } + + add_device(name, erase_size, label, timeout); + + return 0; +} + + +static int block2mtd_setup(const char *val, const 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(). */ + + strscpy(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>][,<label>]]\""); + +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); + } +} + +late_initcall(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"); diff --git a/drivers/mtd/devices/docg3.c b/drivers/mtd/devices/docg3.c new file mode 100644 index 000000000..a7714e3de --- /dev/null +++ b/drivers/mtd/devices/docg3.c @@ -0,0 +1,2086 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Handles the M-Systems DiskOnChip G3 chip + * + * Copyright (C) 2011 Robert Jarzmik + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/string.h> +#include <linux/slab.h> +#include <linux/io.h> +#include <linux/delay.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/partitions.h> +#include <linux/bitmap.h> +#include <linux/bitrev.h> +#include <linux/bch.h> + +#include <linux/debugfs.h> +#include <linux/seq_file.h> + +#define CREATE_TRACE_POINTS +#include "docg3.h" + +/* + * This driver handles the DiskOnChip G3 flash memory. + * + * As no specification is available from M-Systems/Sandisk, this drivers lacks + * several functions available on the chip, as : + * - IPL write + * + * The bus data width (8bits versus 16bits) is not handled (if_cfg flag), and + * the driver assumes a 16bits data bus. + * + * DocG3 relies on 2 ECC algorithms, which are handled in hardware : + * - a 1 byte Hamming code stored in the OOB for each page + * - a 7 bytes BCH code stored in the OOB for each page + * The BCH ECC is : + * - BCH is in GF(2^14) + * - BCH is over data of 520 bytes (512 page + 7 page_info bytes + * + 1 hamming byte) + * - BCH can correct up to 4 bits (t = 4) + * - BCH syndroms are calculated in hardware, and checked in hardware as well + * + */ + +static unsigned int reliable_mode; +module_param(reliable_mode, uint, 0); +MODULE_PARM_DESC(reliable_mode, "Set the docg3 mode (0=normal MLC, 1=fast, " + "2=reliable) : MLC normal operations are in normal mode"); + +static int docg3_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + if (section) + return -ERANGE; + + /* byte 7 is Hamming ECC, byte 8-14 are BCH ECC */ + oobregion->offset = 7; + oobregion->length = 8; + + return 0; +} + +static int docg3_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + if (section > 1) + return -ERANGE; + + /* free bytes: byte 0 until byte 6, byte 15 */ + if (!section) { + oobregion->offset = 0; + oobregion->length = 7; + } else { + oobregion->offset = 15; + oobregion->length = 1; + } + + return 0; +} + +static const struct mtd_ooblayout_ops nand_ooblayout_docg3_ops = { + .ecc = docg3_ooblayout_ecc, + .free = docg3_ooblayout_free, +}; + +static inline u8 doc_readb(struct docg3 *docg3, u16 reg) +{ + u8 val = readb(docg3->cascade->base + reg); + + trace_docg3_io(0, 8, reg, (int)val); + return val; +} + +static inline u16 doc_readw(struct docg3 *docg3, u16 reg) +{ + u16 val = readw(docg3->cascade->base + reg); + + trace_docg3_io(0, 16, reg, (int)val); + return val; +} + +static inline void doc_writeb(struct docg3 *docg3, u8 val, u16 reg) +{ + writeb(val, docg3->cascade->base + reg); + trace_docg3_io(1, 8, reg, val); +} + +static inline void doc_writew(struct docg3 *docg3, u16 val, u16 reg) +{ + writew(val, docg3->cascade->base + reg); + trace_docg3_io(1, 16, reg, val); +} + +static inline void doc_flash_command(struct docg3 *docg3, u8 cmd) +{ + doc_writeb(docg3, cmd, DOC_FLASHCOMMAND); +} + +static inline void doc_flash_sequence(struct docg3 *docg3, u8 seq) +{ + doc_writeb(docg3, seq, DOC_FLASHSEQUENCE); +} + +static inline void doc_flash_address(struct docg3 *docg3, u8 addr) +{ + doc_writeb(docg3, addr, DOC_FLASHADDRESS); +} + +static char const * const part_probes[] = { "cmdlinepart", "saftlpart", NULL }; + +static int doc_register_readb(struct docg3 *docg3, int reg) +{ + u8 val; + + doc_writew(docg3, reg, DOC_READADDRESS); + val = doc_readb(docg3, reg); + doc_vdbg("Read register %04x : %02x\n", reg, val); + return val; +} + +static int doc_register_readw(struct docg3 *docg3, int reg) +{ + u16 val; + + doc_writew(docg3, reg, DOC_READADDRESS); + val = doc_readw(docg3, reg); + doc_vdbg("Read register %04x : %04x\n", reg, val); + return val; +} + +/** + * doc_delay - delay docg3 operations + * @docg3: the device + * @nbNOPs: the number of NOPs to issue + * + * As no specification is available, the right timings between chip commands are + * unknown. The only available piece of information are the observed nops on a + * working docg3 chip. + * Therefore, doc_delay relies on a busy loop of NOPs, instead of scheduler + * friendlier msleep() functions or blocking mdelay(). + */ +static void doc_delay(struct docg3 *docg3, int nbNOPs) +{ + int i; + + doc_vdbg("NOP x %d\n", nbNOPs); + for (i = 0; i < nbNOPs; i++) + doc_writeb(docg3, 0, DOC_NOP); +} + +static int is_prot_seq_error(struct docg3 *docg3) +{ + int ctrl; + + ctrl = doc_register_readb(docg3, DOC_FLASHCONTROL); + return ctrl & (DOC_CTRL_PROTECTION_ERROR | DOC_CTRL_SEQUENCE_ERROR); +} + +static int doc_is_ready(struct docg3 *docg3) +{ + int ctrl; + + ctrl = doc_register_readb(docg3, DOC_FLASHCONTROL); + return ctrl & DOC_CTRL_FLASHREADY; +} + +static int doc_wait_ready(struct docg3 *docg3) +{ + int maxWaitCycles = 100; + + do { + doc_delay(docg3, 4); + cpu_relax(); + } while (!doc_is_ready(docg3) && maxWaitCycles--); + doc_delay(docg3, 2); + if (maxWaitCycles > 0) + return 0; + else + return -EIO; +} + +static int doc_reset_seq(struct docg3 *docg3) +{ + int ret; + + doc_writeb(docg3, 0x10, DOC_FLASHCONTROL); + doc_flash_sequence(docg3, DOC_SEQ_RESET); + doc_flash_command(docg3, DOC_CMD_RESET); + doc_delay(docg3, 2); + ret = doc_wait_ready(docg3); + + doc_dbg("doc_reset_seq() -> isReady=%s\n", ret ? "false" : "true"); + return ret; +} + +/** + * doc_read_data_area - Read data from data area + * @docg3: the device + * @buf: the buffer to fill in (might be NULL is dummy reads) + * @len: the length to read + * @first: first time read, DOC_READADDRESS should be set + * + * Reads bytes from flash data. Handles the single byte / even bytes reads. + */ +static void doc_read_data_area(struct docg3 *docg3, void *buf, int len, + int first) +{ + int i, cdr, len4; + u16 data16, *dst16; + u8 data8, *dst8; + + doc_dbg("doc_read_data_area(buf=%p, len=%d)\n", buf, len); + cdr = len & 0x1; + len4 = len - cdr; + + if (first) + doc_writew(docg3, DOC_IOSPACE_DATA, DOC_READADDRESS); + dst16 = buf; + for (i = 0; i < len4; i += 2) { + data16 = doc_readw(docg3, DOC_IOSPACE_DATA); + if (dst16) { + *dst16 = data16; + dst16++; + } + } + + if (cdr) { + doc_writew(docg3, DOC_IOSPACE_DATA | DOC_READADDR_ONE_BYTE, + DOC_READADDRESS); + doc_delay(docg3, 1); + dst8 = (u8 *)dst16; + for (i = 0; i < cdr; i++) { + data8 = doc_readb(docg3, DOC_IOSPACE_DATA); + if (dst8) { + *dst8 = data8; + dst8++; + } + } + } +} + +/** + * doc_write_data_area - Write data into data area + * @docg3: the device + * @buf: the buffer to get input bytes from + * @len: the length to write + * + * Writes bytes into flash data. Handles the single byte / even bytes writes. + */ +static void doc_write_data_area(struct docg3 *docg3, const void *buf, int len) +{ + int i, cdr, len4; + u16 *src16; + u8 *src8; + + doc_dbg("doc_write_data_area(buf=%p, len=%d)\n", buf, len); + cdr = len & 0x3; + len4 = len - cdr; + + doc_writew(docg3, DOC_IOSPACE_DATA, DOC_READADDRESS); + src16 = (u16 *)buf; + for (i = 0; i < len4; i += 2) { + doc_writew(docg3, *src16, DOC_IOSPACE_DATA); + src16++; + } + + src8 = (u8 *)src16; + for (i = 0; i < cdr; i++) { + doc_writew(docg3, DOC_IOSPACE_DATA | DOC_READADDR_ONE_BYTE, + DOC_READADDRESS); + doc_writeb(docg3, *src8, DOC_IOSPACE_DATA); + src8++; + } +} + +/** + * doc_set_reliable_mode - Sets the flash to normal or reliable data mode + * @docg3: the device + * + * The reliable data mode is a bit slower than the fast mode, but less errors + * occur. Entering the reliable mode cannot be done without entering the fast + * mode first. + * + * In reliable mode, pages 2*n and 2*n+1 are clones. Writing to page 0 of blocks + * (4,5) make the hardware write also to page 1 of blocks blocks(4,5). Reading + * from page 0 of blocks (4,5) or from page 1 of blocks (4,5) gives the same + * result, which is a logical and between bytes from page 0 and page 1 (which is + * consistent with the fact that writing to a page is _clearing_ bits of that + * page). + */ +static void doc_set_reliable_mode(struct docg3 *docg3) +{ + static char *strmode[] = { "normal", "fast", "reliable", "invalid" }; + + doc_dbg("doc_set_reliable_mode(%s)\n", strmode[docg3->reliable]); + switch (docg3->reliable) { + case 0: + break; + case 1: + doc_flash_sequence(docg3, DOC_SEQ_SET_FASTMODE); + doc_flash_command(docg3, DOC_CMD_FAST_MODE); + break; + case 2: + doc_flash_sequence(docg3, DOC_SEQ_SET_RELIABLEMODE); + doc_flash_command(docg3, DOC_CMD_FAST_MODE); + doc_flash_command(docg3, DOC_CMD_RELIABLE_MODE); + break; + default: + doc_err("doc_set_reliable_mode(): invalid mode\n"); + break; + } + doc_delay(docg3, 2); +} + +/** + * doc_set_asic_mode - Set the ASIC mode + * @docg3: the device + * @mode: the mode + * + * The ASIC can work in 3 modes : + * - RESET: all registers are zeroed + * - NORMAL: receives and handles commands + * - POWERDOWN: minimal poweruse, flash parts shut off + */ +static void doc_set_asic_mode(struct docg3 *docg3, u8 mode) +{ + int i; + + for (i = 0; i < 12; i++) + doc_readb(docg3, DOC_IOSPACE_IPL); + + mode |= DOC_ASICMODE_MDWREN; + doc_dbg("doc_set_asic_mode(%02x)\n", mode); + doc_writeb(docg3, mode, DOC_ASICMODE); + doc_writeb(docg3, ~mode, DOC_ASICMODECONFIRM); + doc_delay(docg3, 1); +} + +/** + * doc_set_device_id - Sets the devices id for cascaded G3 chips + * @docg3: the device + * @id: the chip to select (amongst 0, 1, 2, 3) + * + * There can be 4 cascaded G3 chips. This function selects the one which will + * should be the active one. + */ +static void doc_set_device_id(struct docg3 *docg3, int id) +{ + u8 ctrl; + + doc_dbg("doc_set_device_id(%d)\n", id); + doc_writeb(docg3, id, DOC_DEVICESELECT); + ctrl = doc_register_readb(docg3, DOC_FLASHCONTROL); + + ctrl &= ~DOC_CTRL_VIOLATION; + ctrl |= DOC_CTRL_CE; + doc_writeb(docg3, ctrl, DOC_FLASHCONTROL); +} + +/** + * doc_set_extra_page_mode - Change flash page layout + * @docg3: the device + * + * Normally, the flash page is split into the data (512 bytes) and the out of + * band data (16 bytes). For each, 4 more bytes can be accessed, where the wear + * leveling counters are stored. To access this last area of 4 bytes, a special + * mode must be input to the flash ASIC. + * + * Returns 0 if no error occurred, -EIO else. + */ +static int doc_set_extra_page_mode(struct docg3 *docg3) +{ + int fctrl; + + doc_dbg("doc_set_extra_page_mode()\n"); + doc_flash_sequence(docg3, DOC_SEQ_PAGE_SIZE_532); + doc_flash_command(docg3, DOC_CMD_PAGE_SIZE_532); + doc_delay(docg3, 2); + + fctrl = doc_register_readb(docg3, DOC_FLASHCONTROL); + if (fctrl & (DOC_CTRL_PROTECTION_ERROR | DOC_CTRL_SEQUENCE_ERROR)) + return -EIO; + else + return 0; +} + +/** + * doc_setup_addr_sector - Setup blocks/page/ofs address for one plane + * @docg3: the device + * @sector: the sector + */ +static void doc_setup_addr_sector(struct docg3 *docg3, int sector) +{ + doc_delay(docg3, 1); + doc_flash_address(docg3, sector & 0xff); + doc_flash_address(docg3, (sector >> 8) & 0xff); + doc_flash_address(docg3, (sector >> 16) & 0xff); + doc_delay(docg3, 1); +} + +/** + * doc_setup_writeaddr_sector - Setup blocks/page/ofs address for one plane + * @docg3: the device + * @sector: the sector + * @ofs: the offset in the page, between 0 and (512 + 16 + 512) + */ +static void doc_setup_writeaddr_sector(struct docg3 *docg3, int sector, int ofs) +{ + ofs = ofs >> 2; + doc_delay(docg3, 1); + doc_flash_address(docg3, ofs & 0xff); + doc_flash_address(docg3, sector & 0xff); + doc_flash_address(docg3, (sector >> 8) & 0xff); + doc_flash_address(docg3, (sector >> 16) & 0xff); + doc_delay(docg3, 1); +} + +/** + * doc_read_seek - Set both flash planes to the specified block, page for reading + * @docg3: the device + * @block0: the first plane block index + * @block1: the second plane block index + * @page: the page index within the block + * @wear: if true, read will occur on the 4 extra bytes of the wear area + * @ofs: offset in page to read + * + * Programs the flash even and odd planes to the specific block and page. + * Alternatively, programs the flash to the wear area of the specified page. + */ +static int doc_read_seek(struct docg3 *docg3, int block0, int block1, int page, + int wear, int ofs) +{ + int sector, ret = 0; + + doc_dbg("doc_seek(blocks=(%d,%d), page=%d, ofs=%d, wear=%d)\n", + block0, block1, page, ofs, wear); + + if (!wear && (ofs < 2 * DOC_LAYOUT_PAGE_SIZE)) { + doc_flash_sequence(docg3, DOC_SEQ_SET_PLANE1); + doc_flash_command(docg3, DOC_CMD_READ_PLANE1); + doc_delay(docg3, 2); + } else { + doc_flash_sequence(docg3, DOC_SEQ_SET_PLANE2); + doc_flash_command(docg3, DOC_CMD_READ_PLANE2); + doc_delay(docg3, 2); + } + + doc_set_reliable_mode(docg3); + if (wear) + ret = doc_set_extra_page_mode(docg3); + if (ret) + goto out; + + doc_flash_sequence(docg3, DOC_SEQ_READ); + sector = (block0 << DOC_ADDR_BLOCK_SHIFT) + (page & DOC_ADDR_PAGE_MASK); + doc_flash_command(docg3, DOC_CMD_PROG_BLOCK_ADDR); + doc_setup_addr_sector(docg3, sector); + + sector = (block1 << DOC_ADDR_BLOCK_SHIFT) + (page & DOC_ADDR_PAGE_MASK); + doc_flash_command(docg3, DOC_CMD_PROG_BLOCK_ADDR); + doc_setup_addr_sector(docg3, sector); + doc_delay(docg3, 1); + +out: + return ret; +} + +/** + * doc_write_seek - Set both flash planes to the specified block, page for writing + * @docg3: the device + * @block0: the first plane block index + * @block1: the second plane block index + * @page: the page index within the block + * @ofs: offset in page to write + * + * Programs the flash even and odd planes to the specific block and page. + * Alternatively, programs the flash to the wear area of the specified page. + */ +static int doc_write_seek(struct docg3 *docg3, int block0, int block1, int page, + int ofs) +{ + int ret = 0, sector; + + doc_dbg("doc_write_seek(blocks=(%d,%d), page=%d, ofs=%d)\n", + block0, block1, page, ofs); + + doc_set_reliable_mode(docg3); + + if (ofs < 2 * DOC_LAYOUT_PAGE_SIZE) { + doc_flash_sequence(docg3, DOC_SEQ_SET_PLANE1); + doc_flash_command(docg3, DOC_CMD_READ_PLANE1); + doc_delay(docg3, 2); + } else { + doc_flash_sequence(docg3, DOC_SEQ_SET_PLANE2); + doc_flash_command(docg3, DOC_CMD_READ_PLANE2); + doc_delay(docg3, 2); + } + + doc_flash_sequence(docg3, DOC_SEQ_PAGE_SETUP); + doc_flash_command(docg3, DOC_CMD_PROG_CYCLE1); + + sector = (block0 << DOC_ADDR_BLOCK_SHIFT) + (page & DOC_ADDR_PAGE_MASK); + doc_setup_writeaddr_sector(docg3, sector, ofs); + + doc_flash_command(docg3, DOC_CMD_PROG_CYCLE3); + doc_delay(docg3, 2); + ret = doc_wait_ready(docg3); + if (ret) + goto out; + + doc_flash_command(docg3, DOC_CMD_PROG_CYCLE1); + sector = (block1 << DOC_ADDR_BLOCK_SHIFT) + (page & DOC_ADDR_PAGE_MASK); + doc_setup_writeaddr_sector(docg3, sector, ofs); + doc_delay(docg3, 1); + +out: + return ret; +} + + +/** + * doc_read_page_ecc_init - Initialize hardware ECC engine + * @docg3: the device + * @len: the number of bytes covered by the ECC (BCH covered) + * + * The function does initialize the hardware ECC engine to compute the Hamming + * ECC (on 1 byte) and the BCH hardware ECC (on 7 bytes). + * + * Return 0 if succeeded, -EIO on error + */ +static int doc_read_page_ecc_init(struct docg3 *docg3, int len) +{ + doc_writew(docg3, DOC_ECCCONF0_READ_MODE + | DOC_ECCCONF0_BCH_ENABLE | DOC_ECCCONF0_HAMMING_ENABLE + | (len & DOC_ECCCONF0_DATA_BYTES_MASK), + DOC_ECCCONF0); + doc_delay(docg3, 4); + doc_register_readb(docg3, DOC_FLASHCONTROL); + return doc_wait_ready(docg3); +} + +/** + * doc_write_page_ecc_init - Initialize hardware BCH ECC engine + * @docg3: the device + * @len: the number of bytes covered by the ECC (BCH covered) + * + * The function does initialize the hardware ECC engine to compute the Hamming + * ECC (on 1 byte) and the BCH hardware ECC (on 7 bytes). + * + * Return 0 if succeeded, -EIO on error + */ +static int doc_write_page_ecc_init(struct docg3 *docg3, int len) +{ + doc_writew(docg3, DOC_ECCCONF0_WRITE_MODE + | DOC_ECCCONF0_BCH_ENABLE | DOC_ECCCONF0_HAMMING_ENABLE + | (len & DOC_ECCCONF0_DATA_BYTES_MASK), + DOC_ECCCONF0); + doc_delay(docg3, 4); + doc_register_readb(docg3, DOC_FLASHCONTROL); + return doc_wait_ready(docg3); +} + +/** + * doc_ecc_disable - Disable Hamming and BCH ECC hardware calculator + * @docg3: the device + * + * Disables the hardware ECC generator and checker, for unchecked reads (as when + * reading OOB only or write status byte). + */ +static void doc_ecc_disable(struct docg3 *docg3) +{ + doc_writew(docg3, DOC_ECCCONF0_READ_MODE, DOC_ECCCONF0); + doc_delay(docg3, 4); +} + +/** + * doc_hamming_ecc_init - Initialize hardware Hamming ECC engine + * @docg3: the device + * @nb_bytes: the number of bytes covered by the ECC (Hamming covered) + * + * This function programs the ECC hardware to compute the hamming code on the + * last provided N bytes to the hardware generator. + */ +static void doc_hamming_ecc_init(struct docg3 *docg3, int nb_bytes) +{ + u8 ecc_conf1; + + ecc_conf1 = doc_register_readb(docg3, DOC_ECCCONF1); + ecc_conf1 &= ~DOC_ECCCONF1_HAMMING_BITS_MASK; + ecc_conf1 |= (nb_bytes & DOC_ECCCONF1_HAMMING_BITS_MASK); + doc_writeb(docg3, ecc_conf1, DOC_ECCCONF1); +} + +/** + * doc_ecc_bch_fix_data - Fix if need be read data from flash + * @docg3: the device + * @buf: the buffer of read data (512 + 7 + 1 bytes) + * @hwecc: the hardware calculated ECC. + * It's in fact recv_ecc ^ calc_ecc, where recv_ecc was read from OOB + * area data, and calc_ecc the ECC calculated by the hardware generator. + * + * Checks if the received data matches the ECC, and if an error is detected, + * tries to fix the bit flips (at most 4) in the buffer buf. As the docg3 + * understands the (data, ecc, syndroms) in an inverted order in comparison to + * the BCH library, the function reverses the order of bits (ie. bit7 and bit0, + * bit6 and bit 1, ...) for all ECC data. + * + * The hardware ecc unit produces oob_ecc ^ calc_ecc. The kernel's bch + * algorithm is used to decode this. However the hw operates on page + * data in a bit order that is the reverse of that of the bch alg, + * requiring that the bits be reversed on the result. Thanks to Ivan + * Djelic for his analysis. + * + * Returns number of fixed bits (0, 1, 2, 3, 4) or -EBADMSG if too many bit + * errors were detected and cannot be fixed. + */ +static int doc_ecc_bch_fix_data(struct docg3 *docg3, void *buf, u8 *hwecc) +{ + u8 ecc[DOC_ECC_BCH_SIZE]; + int errorpos[DOC_ECC_BCH_T], i, numerrs; + + for (i = 0; i < DOC_ECC_BCH_SIZE; i++) + ecc[i] = bitrev8(hwecc[i]); + numerrs = bch_decode(docg3->cascade->bch, NULL, + DOC_ECC_BCH_COVERED_BYTES, + NULL, ecc, NULL, errorpos); + BUG_ON(numerrs == -EINVAL); + if (numerrs < 0) + goto out; + + for (i = 0; i < numerrs; i++) + errorpos[i] = (errorpos[i] & ~7) | (7 - (errorpos[i] & 7)); + for (i = 0; i < numerrs; i++) + if (errorpos[i] < DOC_ECC_BCH_COVERED_BYTES*8) + /* error is located in data, correct it */ + change_bit(errorpos[i], buf); +out: + doc_dbg("doc_ecc_bch_fix_data: flipped %d bits\n", numerrs); + return numerrs; +} + + +/** + * doc_read_page_prepare - Prepares reading data from a flash page + * @docg3: the device + * @block0: the first plane block index on flash memory + * @block1: the second plane block index on flash memory + * @page: the page index in the block + * @offset: the offset in the page (must be a multiple of 4) + * + * Prepares the page to be read in the flash memory : + * - tell ASIC to map the flash pages + * - tell ASIC to be in read mode + * + * After a call to this method, a call to doc_read_page_finish is mandatory, + * to end the read cycle of the flash. + * + * Read data from a flash page. The length to be read must be between 0 and + * (page_size + oob_size + wear_size), ie. 532, and a multiple of 4 (because + * the extra bytes reading is not implemented). + * + * As pages are grouped by 2 (in 2 planes), reading from a page must be done + * in two steps: + * - one read of 512 bytes at offset 0 + * - one read of 512 bytes at offset 512 + 16 + * + * Returns 0 if successful, -EIO if a read error occurred. + */ +static int doc_read_page_prepare(struct docg3 *docg3, int block0, int block1, + int page, int offset) +{ + int wear_area = 0, ret = 0; + + doc_dbg("doc_read_page_prepare(blocks=(%d,%d), page=%d, ofsInPage=%d)\n", + block0, block1, page, offset); + if (offset >= DOC_LAYOUT_WEAR_OFFSET) + wear_area = 1; + if (!wear_area && offset > (DOC_LAYOUT_PAGE_OOB_SIZE * 2)) + return -EINVAL; + + doc_set_device_id(docg3, docg3->device_id); + ret = doc_reset_seq(docg3); + if (ret) + goto err; + + /* Program the flash address block and page */ + ret = doc_read_seek(docg3, block0, block1, page, wear_area, offset); + if (ret) + goto err; + + doc_flash_command(docg3, DOC_CMD_READ_ALL_PLANES); + doc_delay(docg3, 2); + doc_wait_ready(docg3); + + doc_flash_command(docg3, DOC_CMD_SET_ADDR_READ); + doc_delay(docg3, 1); + if (offset >= DOC_LAYOUT_PAGE_SIZE * 2) + offset -= 2 * DOC_LAYOUT_PAGE_SIZE; + doc_flash_address(docg3, offset >> 2); + doc_delay(docg3, 1); + doc_wait_ready(docg3); + + doc_flash_command(docg3, DOC_CMD_READ_FLASH); + + return 0; +err: + doc_writeb(docg3, 0, DOC_DATAEND); + doc_delay(docg3, 2); + return -EIO; +} + +/** + * doc_read_page_getbytes - Reads bytes from a prepared page + * @docg3: the device + * @len: the number of bytes to be read (must be a multiple of 4) + * @buf: the buffer to be filled in (or NULL is forget bytes) + * @first: 1 if first time read, DOC_READADDRESS should be set + * @last_odd: 1 if last read ended up on an odd byte + * + * Reads bytes from a prepared page. There is a trickery here : if the last read + * ended up on an odd offset in the 1024 bytes double page, ie. between the 2 + * planes, the first byte must be read apart. If a word (16bit) read was used, + * the read would return the byte of plane 2 as low *and* high endian, which + * will mess the read. + * + */ +static int doc_read_page_getbytes(struct docg3 *docg3, int len, u_char *buf, + int first, int last_odd) +{ + if (last_odd && len > 0) { + doc_read_data_area(docg3, buf, 1, first); + doc_read_data_area(docg3, buf ? buf + 1 : buf, len - 1, 0); + } else { + doc_read_data_area(docg3, buf, len, first); + } + doc_delay(docg3, 2); + return len; +} + +/** + * doc_write_page_putbytes - Writes bytes into a prepared page + * @docg3: the device + * @len: the number of bytes to be written + * @buf: the buffer of input bytes + * + */ +static void doc_write_page_putbytes(struct docg3 *docg3, int len, + const u_char *buf) +{ + doc_write_data_area(docg3, buf, len); + doc_delay(docg3, 2); +} + +/** + * doc_get_bch_hw_ecc - Get hardware calculated BCH ECC + * @docg3: the device + * @hwecc: the array of 7 integers where the hardware ecc will be stored + */ +static void doc_get_bch_hw_ecc(struct docg3 *docg3, u8 *hwecc) +{ + int i; + + for (i = 0; i < DOC_ECC_BCH_SIZE; i++) + hwecc[i] = doc_register_readb(docg3, DOC_BCH_HW_ECC(i)); +} + +/** + * doc_page_finish - Ends reading/writing of a flash page + * @docg3: the device + */ +static void doc_page_finish(struct docg3 *docg3) +{ + doc_writeb(docg3, 0, DOC_DATAEND); + doc_delay(docg3, 2); +} + +/** + * doc_read_page_finish - Ends reading of a flash page + * @docg3: the device + * + * As a side effect, resets the chip selector to 0. This ensures that after each + * read operation, the floor 0 is selected. Therefore, if the systems halts, the + * reboot will boot on floor 0, where the IPL is. + */ +static void doc_read_page_finish(struct docg3 *docg3) +{ + doc_page_finish(docg3); + doc_set_device_id(docg3, 0); +} + +/** + * calc_block_sector - Calculate blocks, pages and ofs. + * + * @from: offset in flash + * @block0: first plane block index calculated + * @block1: second plane block index calculated + * @page: page calculated + * @ofs: offset in page + * @reliable: 0 if docg3 in normal mode, 1 if docg3 in fast mode, 2 if docg3 in + * reliable mode. + * + * The calculation is based on the reliable/normal mode. In normal mode, the 64 + * pages of a block are available. In reliable mode, as pages 2*n and 2*n+1 are + * clones, only 32 pages per block are available. + */ +static void calc_block_sector(loff_t from, int *block0, int *block1, int *page, + int *ofs, int reliable) +{ + uint sector, pages_biblock; + + pages_biblock = DOC_LAYOUT_PAGES_PER_BLOCK * DOC_LAYOUT_NBPLANES; + if (reliable == 1 || reliable == 2) + pages_biblock /= 2; + + sector = from / DOC_LAYOUT_PAGE_SIZE; + *block0 = sector / pages_biblock * DOC_LAYOUT_NBPLANES; + *block1 = *block0 + 1; + *page = sector % pages_biblock; + *page /= DOC_LAYOUT_NBPLANES; + if (reliable == 1 || reliable == 2) + *page *= 2; + if (sector % 2) + *ofs = DOC_LAYOUT_PAGE_OOB_SIZE; + else + *ofs = 0; +} + +/** + * doc_read_oob - Read out of band bytes from flash + * @mtd: the device + * @from: the offset from first block and first page, in bytes, aligned on page + * size + * @ops: the mtd oob structure + * + * Reads flash memory OOB area of pages. + * + * Returns 0 if read successful, of -EIO, -EINVAL if an error occurred + */ +static int doc_read_oob(struct mtd_info *mtd, loff_t from, + struct mtd_oob_ops *ops) +{ + struct docg3 *docg3 = mtd->priv; + int block0, block1, page, ret, skip, ofs = 0; + u8 *oobbuf = ops->oobbuf; + u8 *buf = ops->datbuf; + size_t len, ooblen, nbdata, nboob; + u8 hwecc[DOC_ECC_BCH_SIZE], eccconf1; + struct mtd_ecc_stats old_stats; + int max_bitflips = 0; + + if (buf) + len = ops->len; + else + len = 0; + if (oobbuf) + ooblen = ops->ooblen; + else + ooblen = 0; + + if (oobbuf && ops->mode == MTD_OPS_PLACE_OOB) + oobbuf += ops->ooboffs; + + doc_dbg("doc_read_oob(from=%lld, mode=%d, data=(%p:%zu), oob=(%p:%zu))\n", + from, ops->mode, buf, len, oobbuf, ooblen); + if (ooblen % DOC_LAYOUT_OOB_SIZE) + return -EINVAL; + + ops->oobretlen = 0; + ops->retlen = 0; + ret = 0; + skip = from % DOC_LAYOUT_PAGE_SIZE; + mutex_lock(&docg3->cascade->lock); + old_stats = mtd->ecc_stats; + while (ret >= 0 && (len > 0 || ooblen > 0)) { + calc_block_sector(from - skip, &block0, &block1, &page, &ofs, + docg3->reliable); + nbdata = min_t(size_t, len, DOC_LAYOUT_PAGE_SIZE - skip); + nboob = min_t(size_t, ooblen, (size_t)DOC_LAYOUT_OOB_SIZE); + ret = doc_read_page_prepare(docg3, block0, block1, page, ofs); + if (ret < 0) + goto out; + ret = doc_read_page_ecc_init(docg3, DOC_ECC_BCH_TOTAL_BYTES); + if (ret < 0) + goto err_in_read; + ret = doc_read_page_getbytes(docg3, skip, NULL, 1, 0); + if (ret < skip) + goto err_in_read; + ret = doc_read_page_getbytes(docg3, nbdata, buf, 0, skip % 2); + if (ret < nbdata) + goto err_in_read; + doc_read_page_getbytes(docg3, + DOC_LAYOUT_PAGE_SIZE - nbdata - skip, + NULL, 0, (skip + nbdata) % 2); + ret = doc_read_page_getbytes(docg3, nboob, oobbuf, 0, 0); + if (ret < nboob) + goto err_in_read; + doc_read_page_getbytes(docg3, DOC_LAYOUT_OOB_SIZE - nboob, + NULL, 0, nboob % 2); + + doc_get_bch_hw_ecc(docg3, hwecc); + eccconf1 = doc_register_readb(docg3, DOC_ECCCONF1); + + if (nboob >= DOC_LAYOUT_OOB_SIZE) { + doc_dbg("OOB - INFO: %*phC\n", 7, oobbuf); + doc_dbg("OOB - HAMMING: %02x\n", oobbuf[7]); + doc_dbg("OOB - BCH_ECC: %*phC\n", 7, oobbuf + 8); + doc_dbg("OOB - UNUSED: %02x\n", oobbuf[15]); + } + doc_dbg("ECC checks: ECCConf1=%x\n", eccconf1); + doc_dbg("ECC HW_ECC: %*phC\n", 7, hwecc); + + ret = -EIO; + if (is_prot_seq_error(docg3)) + goto err_in_read; + ret = 0; + if ((block0 >= DOC_LAYOUT_BLOCK_FIRST_DATA) && + (eccconf1 & DOC_ECCCONF1_BCH_SYNDROM_ERR) && + (eccconf1 & DOC_ECCCONF1_PAGE_IS_WRITTEN) && + (ops->mode != MTD_OPS_RAW) && + (nbdata == DOC_LAYOUT_PAGE_SIZE)) { + ret = doc_ecc_bch_fix_data(docg3, buf, hwecc); + if (ret < 0) { + mtd->ecc_stats.failed++; + ret = -EBADMSG; + } + if (ret > 0) { + mtd->ecc_stats.corrected += ret; + max_bitflips = max(max_bitflips, ret); + ret = max_bitflips; + } + } + + doc_read_page_finish(docg3); + ops->retlen += nbdata; + ops->oobretlen += nboob; + buf += nbdata; + oobbuf += nboob; + len -= nbdata; + ooblen -= nboob; + from += DOC_LAYOUT_PAGE_SIZE; + skip = 0; + } + +out: + if (ops->stats) { + ops->stats->uncorrectable_errors += + mtd->ecc_stats.failed - old_stats.failed; + ops->stats->corrected_bitflips += + mtd->ecc_stats.corrected - old_stats.corrected; + } + mutex_unlock(&docg3->cascade->lock); + return ret; +err_in_read: + doc_read_page_finish(docg3); + goto out; +} + +static int doc_reload_bbt(struct docg3 *docg3) +{ + int block = DOC_LAYOUT_BLOCK_BBT; + int ret = 0, nbpages, page; + u_char *buf = docg3->bbt; + + nbpages = DIV_ROUND_UP(docg3->max_block + 1, 8 * DOC_LAYOUT_PAGE_SIZE); + for (page = 0; !ret && (page < nbpages); page++) { + ret = doc_read_page_prepare(docg3, block, block + 1, + page + DOC_LAYOUT_PAGE_BBT, 0); + if (!ret) + ret = doc_read_page_ecc_init(docg3, + DOC_LAYOUT_PAGE_SIZE); + if (!ret) + doc_read_page_getbytes(docg3, DOC_LAYOUT_PAGE_SIZE, + buf, 1, 0); + buf += DOC_LAYOUT_PAGE_SIZE; + } + doc_read_page_finish(docg3); + return ret; +} + +/** + * doc_block_isbad - Checks whether a block is good or not + * @mtd: the device + * @from: the offset to find the correct block + * + * Returns 1 if block is bad, 0 if block is good + */ +static int doc_block_isbad(struct mtd_info *mtd, loff_t from) +{ + struct docg3 *docg3 = mtd->priv; + int block0, block1, page, ofs, is_good; + + calc_block_sector(from, &block0, &block1, &page, &ofs, + docg3->reliable); + doc_dbg("doc_block_isbad(from=%lld) => block=(%d,%d), page=%d, ofs=%d\n", + from, block0, block1, page, ofs); + + if (block0 < DOC_LAYOUT_BLOCK_FIRST_DATA) + return 0; + if (block1 > docg3->max_block) + return -EINVAL; + + is_good = docg3->bbt[block0 >> 3] & (1 << (block0 & 0x7)); + return !is_good; +} + +#if 0 +/** + * doc_get_erase_count - Get block erase count + * @docg3: the device + * @from: the offset in which the block is. + * + * Get the number of times a block was erased. The number is the maximum of + * erase times between first and second plane (which should be equal normally). + * + * Returns The number of erases, or -EINVAL or -EIO on error. + */ +static int doc_get_erase_count(struct docg3 *docg3, loff_t from) +{ + u8 buf[DOC_LAYOUT_WEAR_SIZE]; + int ret, plane1_erase_count, plane2_erase_count; + int block0, block1, page, ofs; + + doc_dbg("doc_get_erase_count(from=%lld, buf=%p)\n", from, buf); + if (from % DOC_LAYOUT_PAGE_SIZE) + return -EINVAL; + calc_block_sector(from, &block0, &block1, &page, &ofs, docg3->reliable); + if (block1 > docg3->max_block) + return -EINVAL; + + ret = doc_reset_seq(docg3); + if (!ret) + ret = doc_read_page_prepare(docg3, block0, block1, page, + ofs + DOC_LAYOUT_WEAR_OFFSET, 0); + if (!ret) + ret = doc_read_page_getbytes(docg3, DOC_LAYOUT_WEAR_SIZE, + buf, 1, 0); + doc_read_page_finish(docg3); + + if (ret || (buf[0] != DOC_ERASE_MARK) || (buf[2] != DOC_ERASE_MARK)) + return -EIO; + plane1_erase_count = (u8)(~buf[1]) | ((u8)(~buf[4]) << 8) + | ((u8)(~buf[5]) << 16); + plane2_erase_count = (u8)(~buf[3]) | ((u8)(~buf[6]) << 8) + | ((u8)(~buf[7]) << 16); + + return max(plane1_erase_count, plane2_erase_count); +} +#endif + +/** + * doc_get_op_status - get erase/write operation status + * @docg3: the device + * + * Queries the status from the chip, and returns it + * + * Returns the status (bits DOC_PLANES_STATUS_*) + */ +static int doc_get_op_status(struct docg3 *docg3) +{ + u8 status; + + doc_flash_sequence(docg3, DOC_SEQ_PLANES_STATUS); + doc_flash_command(docg3, DOC_CMD_PLANES_STATUS); + doc_delay(docg3, 5); + + doc_ecc_disable(docg3); + doc_read_data_area(docg3, &status, 1, 1); + return status; +} + +/** + * doc_write_erase_wait_status - wait for write or erase completion + * @docg3: the device + * + * Wait for the chip to be ready again after erase or write operation, and check + * erase/write status. + * + * Returns 0 if erase successful, -EIO if erase/write issue, -ETIMEOUT if + * timeout + */ +static int doc_write_erase_wait_status(struct docg3 *docg3) +{ + int i, status, ret = 0; + + for (i = 0; !doc_is_ready(docg3) && i < 5; i++) + msleep(20); + if (!doc_is_ready(docg3)) { + doc_dbg("Timeout reached and the chip is still not ready\n"); + ret = -EAGAIN; + goto out; + } + + status = doc_get_op_status(docg3); + if (status & DOC_PLANES_STATUS_FAIL) { + doc_dbg("Erase/Write failed on (a) plane(s), status = %x\n", + status); + ret = -EIO; + } + +out: + doc_page_finish(docg3); + return ret; +} + +/** + * doc_erase_block - Erase a couple of blocks + * @docg3: the device + * @block0: the first block to erase (leftmost plane) + * @block1: the second block to erase (rightmost plane) + * + * Erase both blocks, and return operation status + * + * Returns 0 if erase successful, -EIO if erase issue, -ETIMEOUT if chip not + * ready for too long + */ +static int doc_erase_block(struct docg3 *docg3, int block0, int block1) +{ + int ret, sector; + + doc_dbg("doc_erase_block(blocks=(%d,%d))\n", block0, block1); + ret = doc_reset_seq(docg3); + if (ret) + return -EIO; + + doc_set_reliable_mode(docg3); + doc_flash_sequence(docg3, DOC_SEQ_ERASE); + + sector = block0 << DOC_ADDR_BLOCK_SHIFT; + doc_flash_command(docg3, DOC_CMD_PROG_BLOCK_ADDR); + doc_setup_addr_sector(docg3, sector); + sector = block1 << DOC_ADDR_BLOCK_SHIFT; + doc_flash_command(docg3, DOC_CMD_PROG_BLOCK_ADDR); + doc_setup_addr_sector(docg3, sector); + doc_delay(docg3, 1); + + doc_flash_command(docg3, DOC_CMD_ERASECYCLE2); + doc_delay(docg3, 2); + + if (is_prot_seq_error(docg3)) { + doc_err("Erase blocks %d,%d error\n", block0, block1); + return -EIO; + } + + return doc_write_erase_wait_status(docg3); +} + +/** + * doc_erase - Erase a portion of the chip + * @mtd: the device + * @info: the erase info + * + * Erase a bunch of contiguous blocks, by pairs, as a "mtd" page of 1024 is + * split into 2 pages of 512 bytes on 2 contiguous blocks. + * + * Returns 0 if erase successful, -EINVAL if addressing error, -EIO if erase + * issue + */ +static int doc_erase(struct mtd_info *mtd, struct erase_info *info) +{ + struct docg3 *docg3 = mtd->priv; + uint64_t len; + int block0, block1, page, ret = 0, ofs = 0; + + doc_dbg("doc_erase(from=%lld, len=%lld\n", info->addr, info->len); + + calc_block_sector(info->addr + info->len, &block0, &block1, &page, + &ofs, docg3->reliable); + if (info->addr + info->len > mtd->size || page || ofs) + return -EINVAL; + + calc_block_sector(info->addr, &block0, &block1, &page, &ofs, + docg3->reliable); + mutex_lock(&docg3->cascade->lock); + doc_set_device_id(docg3, docg3->device_id); + doc_set_reliable_mode(docg3); + for (len = info->len; !ret && len > 0; len -= mtd->erasesize) { + ret = doc_erase_block(docg3, block0, block1); + block0 += 2; + block1 += 2; + } + mutex_unlock(&docg3->cascade->lock); + + return ret; +} + +/** + * doc_write_page - Write a single page to the chip + * @docg3: the device + * @to: the offset from first block and first page, in bytes, aligned on page + * size + * @buf: buffer to get bytes from + * @oob: buffer to get out of band bytes from (can be NULL if no OOB should be + * written) + * @autoecc: if 0, all 16 bytes from OOB are taken, regardless of HW Hamming or + * BCH computations. If 1, only bytes 0-7 and byte 15 are taken, + * remaining ones are filled with hardware Hamming and BCH + * computations. Its value is not meaningfull is oob == NULL. + * + * Write one full page (ie. 1 page split on two planes), of 512 bytes, with the + * OOB data. The OOB ECC is automatically computed by the hardware Hamming and + * BCH generator if autoecc is not null. + * + * Returns 0 if write successful, -EIO if write error, -EAGAIN if timeout + */ +static int doc_write_page(struct docg3 *docg3, loff_t to, const u_char *buf, + const u_char *oob, int autoecc) +{ + int block0, block1, page, ret, ofs = 0; + u8 hwecc[DOC_ECC_BCH_SIZE], hamming; + + doc_dbg("doc_write_page(to=%lld)\n", to); + calc_block_sector(to, &block0, &block1, &page, &ofs, docg3->reliable); + + doc_set_device_id(docg3, docg3->device_id); + ret = doc_reset_seq(docg3); + if (ret) + goto err; + + /* Program the flash address block and page */ + ret = doc_write_seek(docg3, block0, block1, page, ofs); + if (ret) + goto err; + + doc_write_page_ecc_init(docg3, DOC_ECC_BCH_TOTAL_BYTES); + doc_delay(docg3, 2); + doc_write_page_putbytes(docg3, DOC_LAYOUT_PAGE_SIZE, buf); + + if (oob && autoecc) { + doc_write_page_putbytes(docg3, DOC_LAYOUT_OOB_PAGEINFO_SZ, oob); + doc_delay(docg3, 2); + oob += DOC_LAYOUT_OOB_UNUSED_OFS; + + hamming = doc_register_readb(docg3, DOC_HAMMINGPARITY); + doc_delay(docg3, 2); + doc_write_page_putbytes(docg3, DOC_LAYOUT_OOB_HAMMING_SZ, + &hamming); + doc_delay(docg3, 2); + + doc_get_bch_hw_ecc(docg3, hwecc); + doc_write_page_putbytes(docg3, DOC_LAYOUT_OOB_BCH_SZ, hwecc); + doc_delay(docg3, 2); + + doc_write_page_putbytes(docg3, DOC_LAYOUT_OOB_UNUSED_SZ, oob); + } + if (oob && !autoecc) + doc_write_page_putbytes(docg3, DOC_LAYOUT_OOB_SIZE, oob); + + doc_delay(docg3, 2); + doc_page_finish(docg3); + doc_delay(docg3, 2); + doc_flash_command(docg3, DOC_CMD_PROG_CYCLE2); + doc_delay(docg3, 2); + + /* + * The wait status will perform another doc_page_finish() call, but that + * seems to please the docg3, so leave it. + */ + ret = doc_write_erase_wait_status(docg3); + return ret; +err: + doc_read_page_finish(docg3); + return ret; +} + +/** + * doc_guess_autoecc - Guess autoecc mode from mbd_oob_ops + * @ops: the oob operations + * + * Returns 0 or 1 if success, -EINVAL if invalid oob mode + */ +static int doc_guess_autoecc(struct mtd_oob_ops *ops) +{ + int autoecc; + + switch (ops->mode) { + case MTD_OPS_PLACE_OOB: + case MTD_OPS_AUTO_OOB: + autoecc = 1; + break; + case MTD_OPS_RAW: + autoecc = 0; + break; + default: + autoecc = -EINVAL; + } + return autoecc; +} + +/** + * doc_fill_autooob - Fill a 16 bytes OOB from 8 non-ECC bytes + * @dst: the target 16 bytes OOB buffer + * @oobsrc: the source 8 bytes non-ECC OOB buffer + * + */ +static void doc_fill_autooob(u8 *dst, u8 *oobsrc) +{ + memcpy(dst, oobsrc, DOC_LAYOUT_OOB_PAGEINFO_SZ); + dst[DOC_LAYOUT_OOB_UNUSED_OFS] = oobsrc[DOC_LAYOUT_OOB_PAGEINFO_SZ]; +} + +/** + * doc_backup_oob - Backup OOB into docg3 structure + * @docg3: the device + * @to: the page offset in the chip + * @ops: the OOB size and buffer + * + * As the docg3 should write a page with its OOB in one pass, and some userland + * applications do write_oob() to setup the OOB and then write(), store the OOB + * into a temporary storage. This is very dangerous, as 2 concurrent + * applications could store an OOB, and then write their pages (which will + * result into one having its OOB corrupted). + * + * The only reliable way would be for userland to call doc_write_oob() with both + * the page data _and_ the OOB area. + * + * Returns 0 if success, -EINVAL if ops content invalid + */ +static int doc_backup_oob(struct docg3 *docg3, loff_t to, + struct mtd_oob_ops *ops) +{ + int ooblen = ops->ooblen, autoecc; + + if (ooblen != DOC_LAYOUT_OOB_SIZE) + return -EINVAL; + autoecc = doc_guess_autoecc(ops); + if (autoecc < 0) + return autoecc; + + docg3->oob_write_ofs = to; + docg3->oob_autoecc = autoecc; + if (ops->mode == MTD_OPS_AUTO_OOB) { + doc_fill_autooob(docg3->oob_write_buf, ops->oobbuf); + ops->oobretlen = 8; + } else { + memcpy(docg3->oob_write_buf, ops->oobbuf, DOC_LAYOUT_OOB_SIZE); + ops->oobretlen = DOC_LAYOUT_OOB_SIZE; + } + return 0; +} + +/** + * doc_write_oob - Write out of band bytes to flash + * @mtd: the device + * @ofs: the offset from first block and first page, in bytes, aligned on page + * size + * @ops: the mtd oob structure + * + * Either write OOB data into a temporary buffer, for the subsequent write + * page. The provided OOB should be 16 bytes long. If a data buffer is provided + * as well, issue the page write. + * Or provide data without OOB, and then a all zeroed OOB will be used (ECC will + * still be filled in if asked for). + * + * Returns 0 is successful, EINVAL if length is not 14 bytes + */ +static int doc_write_oob(struct mtd_info *mtd, loff_t ofs, + struct mtd_oob_ops *ops) +{ + struct docg3 *docg3 = mtd->priv; + int ret, autoecc, oobdelta; + u8 *oobbuf = ops->oobbuf; + u8 *buf = ops->datbuf; + size_t len, ooblen; + u8 oob[DOC_LAYOUT_OOB_SIZE]; + + if (buf) + len = ops->len; + else + len = 0; + if (oobbuf) + ooblen = ops->ooblen; + else + ooblen = 0; + + if (oobbuf && ops->mode == MTD_OPS_PLACE_OOB) + oobbuf += ops->ooboffs; + + doc_dbg("doc_write_oob(from=%lld, mode=%d, data=(%p:%zu), oob=(%p:%zu))\n", + ofs, ops->mode, buf, len, oobbuf, ooblen); + switch (ops->mode) { + case MTD_OPS_PLACE_OOB: + case MTD_OPS_RAW: + oobdelta = mtd->oobsize; + break; + case MTD_OPS_AUTO_OOB: + oobdelta = mtd->oobavail; + break; + default: + return -EINVAL; + } + if ((len % DOC_LAYOUT_PAGE_SIZE) || (ooblen % oobdelta) || + (ofs % DOC_LAYOUT_PAGE_SIZE)) + return -EINVAL; + if (len && ooblen && + (len / DOC_LAYOUT_PAGE_SIZE) != (ooblen / oobdelta)) + return -EINVAL; + + ops->oobretlen = 0; + ops->retlen = 0; + ret = 0; + if (len == 0 && ooblen == 0) + return -EINVAL; + if (len == 0 && ooblen > 0) + return doc_backup_oob(docg3, ofs, ops); + + autoecc = doc_guess_autoecc(ops); + if (autoecc < 0) + return autoecc; + + mutex_lock(&docg3->cascade->lock); + while (!ret && len > 0) { + memset(oob, 0, sizeof(oob)); + if (ofs == docg3->oob_write_ofs) + memcpy(oob, docg3->oob_write_buf, DOC_LAYOUT_OOB_SIZE); + else if (ooblen > 0 && ops->mode == MTD_OPS_AUTO_OOB) + doc_fill_autooob(oob, oobbuf); + else if (ooblen > 0) + memcpy(oob, oobbuf, DOC_LAYOUT_OOB_SIZE); + ret = doc_write_page(docg3, ofs, buf, oob, autoecc); + + ofs += DOC_LAYOUT_PAGE_SIZE; + len -= DOC_LAYOUT_PAGE_SIZE; + buf += DOC_LAYOUT_PAGE_SIZE; + if (ooblen) { + oobbuf += oobdelta; + ooblen -= oobdelta; + ops->oobretlen += oobdelta; + } + ops->retlen += DOC_LAYOUT_PAGE_SIZE; + } + + doc_set_device_id(docg3, 0); + mutex_unlock(&docg3->cascade->lock); + return ret; +} + +static struct docg3 *sysfs_dev2docg3(struct device *dev, + struct device_attribute *attr) +{ + int floor; + struct mtd_info **docg3_floors = dev_get_drvdata(dev); + + floor = attr->attr.name[1] - '0'; + if (floor < 0 || floor >= DOC_MAX_NBFLOORS) + return NULL; + else + return docg3_floors[floor]->priv; +} + +static ssize_t dps0_is_key_locked(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct docg3 *docg3 = sysfs_dev2docg3(dev, attr); + int dps0; + + mutex_lock(&docg3->cascade->lock); + doc_set_device_id(docg3, docg3->device_id); + dps0 = doc_register_readb(docg3, DOC_DPS0_STATUS); + doc_set_device_id(docg3, 0); + mutex_unlock(&docg3->cascade->lock); + + return sprintf(buf, "%d\n", !(dps0 & DOC_DPS_KEY_OK)); +} + +static ssize_t dps1_is_key_locked(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct docg3 *docg3 = sysfs_dev2docg3(dev, attr); + int dps1; + + mutex_lock(&docg3->cascade->lock); + doc_set_device_id(docg3, docg3->device_id); + dps1 = doc_register_readb(docg3, DOC_DPS1_STATUS); + doc_set_device_id(docg3, 0); + mutex_unlock(&docg3->cascade->lock); + + return sprintf(buf, "%d\n", !(dps1 & DOC_DPS_KEY_OK)); +} + +static ssize_t dps0_insert_key(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct docg3 *docg3 = sysfs_dev2docg3(dev, attr); + int i; + + if (count != DOC_LAYOUT_DPS_KEY_LENGTH) + return -EINVAL; + + mutex_lock(&docg3->cascade->lock); + doc_set_device_id(docg3, docg3->device_id); + for (i = 0; i < DOC_LAYOUT_DPS_KEY_LENGTH; i++) + doc_writeb(docg3, buf[i], DOC_DPS0_KEY); + doc_set_device_id(docg3, 0); + mutex_unlock(&docg3->cascade->lock); + return count; +} + +static ssize_t dps1_insert_key(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct docg3 *docg3 = sysfs_dev2docg3(dev, attr); + int i; + + if (count != DOC_LAYOUT_DPS_KEY_LENGTH) + return -EINVAL; + + mutex_lock(&docg3->cascade->lock); + doc_set_device_id(docg3, docg3->device_id); + for (i = 0; i < DOC_LAYOUT_DPS_KEY_LENGTH; i++) + doc_writeb(docg3, buf[i], DOC_DPS1_KEY); + doc_set_device_id(docg3, 0); + mutex_unlock(&docg3->cascade->lock); + return count; +} + +#define FLOOR_SYSFS(id) { \ + __ATTR(f##id##_dps0_is_keylocked, S_IRUGO, dps0_is_key_locked, NULL), \ + __ATTR(f##id##_dps1_is_keylocked, S_IRUGO, dps1_is_key_locked, NULL), \ + __ATTR(f##id##_dps0_protection_key, S_IWUSR|S_IWGRP, NULL, dps0_insert_key), \ + __ATTR(f##id##_dps1_protection_key, S_IWUSR|S_IWGRP, NULL, dps1_insert_key), \ +} + +static struct device_attribute doc_sys_attrs[DOC_MAX_NBFLOORS][4] = { + FLOOR_SYSFS(0), FLOOR_SYSFS(1), FLOOR_SYSFS(2), FLOOR_SYSFS(3) +}; + +static int doc_register_sysfs(struct platform_device *pdev, + struct docg3_cascade *cascade) +{ + struct device *dev = &pdev->dev; + int floor; + int ret; + int i; + + for (floor = 0; + floor < DOC_MAX_NBFLOORS && cascade->floors[floor]; + floor++) { + for (i = 0; i < 4; i++) { + ret = device_create_file(dev, &doc_sys_attrs[floor][i]); + if (ret) + goto remove_files; + } + } + + return 0; + +remove_files: + do { + while (--i >= 0) + device_remove_file(dev, &doc_sys_attrs[floor][i]); + i = 4; + } while (--floor >= 0); + + return ret; +} + +static void doc_unregister_sysfs(struct platform_device *pdev, + struct docg3_cascade *cascade) +{ + struct device *dev = &pdev->dev; + int floor, i; + + for (floor = 0; floor < DOC_MAX_NBFLOORS && cascade->floors[floor]; + floor++) + for (i = 0; i < 4; i++) + device_remove_file(dev, &doc_sys_attrs[floor][i]); +} + +/* + * Debug sysfs entries + */ +static int flashcontrol_show(struct seq_file *s, void *p) +{ + struct docg3 *docg3 = (struct docg3 *)s->private; + + u8 fctrl; + + mutex_lock(&docg3->cascade->lock); + fctrl = doc_register_readb(docg3, DOC_FLASHCONTROL); + mutex_unlock(&docg3->cascade->lock); + + seq_printf(s, "FlashControl : 0x%02x (%s,CE# %s,%s,%s,flash %s)\n", + fctrl, + fctrl & DOC_CTRL_VIOLATION ? "protocol violation" : "-", + fctrl & DOC_CTRL_CE ? "active" : "inactive", + fctrl & DOC_CTRL_PROTECTION_ERROR ? "protection error" : "-", + fctrl & DOC_CTRL_SEQUENCE_ERROR ? "sequence error" : "-", + fctrl & DOC_CTRL_FLASHREADY ? "ready" : "not ready"); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(flashcontrol); + +static int asic_mode_show(struct seq_file *s, void *p) +{ + struct docg3 *docg3 = (struct docg3 *)s->private; + + int pctrl, mode; + + mutex_lock(&docg3->cascade->lock); + pctrl = doc_register_readb(docg3, DOC_ASICMODE); + mode = pctrl & 0x03; + mutex_unlock(&docg3->cascade->lock); + + seq_printf(s, + "%04x : RAM_WE=%d,RSTIN_RESET=%d,BDETCT_RESET=%d,WRITE_ENABLE=%d,POWERDOWN=%d,MODE=%d%d (", + pctrl, + pctrl & DOC_ASICMODE_RAM_WE ? 1 : 0, + pctrl & DOC_ASICMODE_RSTIN_RESET ? 1 : 0, + pctrl & DOC_ASICMODE_BDETCT_RESET ? 1 : 0, + pctrl & DOC_ASICMODE_MDWREN ? 1 : 0, + pctrl & DOC_ASICMODE_POWERDOWN ? 1 : 0, + mode >> 1, mode & 0x1); + + switch (mode) { + case DOC_ASICMODE_RESET: + seq_puts(s, "reset"); + break; + case DOC_ASICMODE_NORMAL: + seq_puts(s, "normal"); + break; + case DOC_ASICMODE_POWERDOWN: + seq_puts(s, "powerdown"); + break; + } + seq_puts(s, ")\n"); + return 0; +} +DEFINE_SHOW_ATTRIBUTE(asic_mode); + +static int device_id_show(struct seq_file *s, void *p) +{ + struct docg3 *docg3 = (struct docg3 *)s->private; + int id; + + mutex_lock(&docg3->cascade->lock); + id = doc_register_readb(docg3, DOC_DEVICESELECT); + mutex_unlock(&docg3->cascade->lock); + + seq_printf(s, "DeviceId = %d\n", id); + return 0; +} +DEFINE_SHOW_ATTRIBUTE(device_id); + +static int protection_show(struct seq_file *s, void *p) +{ + struct docg3 *docg3 = (struct docg3 *)s->private; + int protect, dps0, dps0_low, dps0_high, dps1, dps1_low, dps1_high; + + mutex_lock(&docg3->cascade->lock); + protect = doc_register_readb(docg3, DOC_PROTECTION); + dps0 = doc_register_readb(docg3, DOC_DPS0_STATUS); + dps0_low = doc_register_readw(docg3, DOC_DPS0_ADDRLOW); + dps0_high = doc_register_readw(docg3, DOC_DPS0_ADDRHIGH); + dps1 = doc_register_readb(docg3, DOC_DPS1_STATUS); + dps1_low = doc_register_readw(docg3, DOC_DPS1_ADDRLOW); + dps1_high = doc_register_readw(docg3, DOC_DPS1_ADDRHIGH); + mutex_unlock(&docg3->cascade->lock); + + seq_printf(s, "Protection = 0x%02x (", protect); + if (protect & DOC_PROTECT_FOUNDRY_OTP_LOCK) + seq_puts(s, "FOUNDRY_OTP_LOCK,"); + if (protect & DOC_PROTECT_CUSTOMER_OTP_LOCK) + seq_puts(s, "CUSTOMER_OTP_LOCK,"); + if (protect & DOC_PROTECT_LOCK_INPUT) + seq_puts(s, "LOCK_INPUT,"); + if (protect & DOC_PROTECT_STICKY_LOCK) + seq_puts(s, "STICKY_LOCK,"); + if (protect & DOC_PROTECT_PROTECTION_ENABLED) + seq_puts(s, "PROTECTION ON,"); + if (protect & DOC_PROTECT_IPL_DOWNLOAD_LOCK) + seq_puts(s, "IPL_DOWNLOAD_LOCK,"); + if (protect & DOC_PROTECT_PROTECTION_ERROR) + seq_puts(s, "PROTECT_ERR,"); + else + seq_puts(s, "NO_PROTECT_ERR"); + seq_puts(s, ")\n"); + + seq_printf(s, "DPS0 = 0x%02x : Protected area [0x%x - 0x%x] : OTP=%d, READ=%d, WRITE=%d, HW_LOCK=%d, KEY_OK=%d\n", + dps0, dps0_low, dps0_high, + !!(dps0 & DOC_DPS_OTP_PROTECTED), + !!(dps0 & DOC_DPS_READ_PROTECTED), + !!(dps0 & DOC_DPS_WRITE_PROTECTED), + !!(dps0 & DOC_DPS_HW_LOCK_ENABLED), + !!(dps0 & DOC_DPS_KEY_OK)); + seq_printf(s, "DPS1 = 0x%02x : Protected area [0x%x - 0x%x] : OTP=%d, READ=%d, WRITE=%d, HW_LOCK=%d, KEY_OK=%d\n", + dps1, dps1_low, dps1_high, + !!(dps1 & DOC_DPS_OTP_PROTECTED), + !!(dps1 & DOC_DPS_READ_PROTECTED), + !!(dps1 & DOC_DPS_WRITE_PROTECTED), + !!(dps1 & DOC_DPS_HW_LOCK_ENABLED), + !!(dps1 & DOC_DPS_KEY_OK)); + return 0; +} +DEFINE_SHOW_ATTRIBUTE(protection); + +static void __init doc_dbg_register(struct mtd_info *floor) +{ + struct dentry *root = floor->dbg.dfs_dir; + struct docg3 *docg3 = floor->priv; + + if (IS_ERR_OR_NULL(root)) { + if (IS_ENABLED(CONFIG_DEBUG_FS) && + !IS_ENABLED(CONFIG_MTD_PARTITIONED_MASTER)) + dev_warn(floor->dev.parent, + "CONFIG_MTD_PARTITIONED_MASTER must be enabled to expose debugfs stuff\n"); + return; + } + + debugfs_create_file("docg3_flashcontrol", S_IRUSR, root, docg3, + &flashcontrol_fops); + debugfs_create_file("docg3_asic_mode", S_IRUSR, root, docg3, + &asic_mode_fops); + debugfs_create_file("docg3_device_id", S_IRUSR, root, docg3, + &device_id_fops); + debugfs_create_file("docg3_protection", S_IRUSR, root, docg3, + &protection_fops); +} + +/** + * doc_set_driver_info - Fill the mtd_info structure and docg3 structure + * @chip_id: The chip ID of the supported chip + * @mtd: The structure to fill + */ +static int __init doc_set_driver_info(int chip_id, struct mtd_info *mtd) +{ + struct docg3 *docg3 = mtd->priv; + int cfg; + + cfg = doc_register_readb(docg3, DOC_CONFIGURATION); + docg3->if_cfg = (cfg & DOC_CONF_IF_CFG ? 1 : 0); + docg3->reliable = reliable_mode; + + switch (chip_id) { + case DOC_CHIPID_G3: + mtd->name = devm_kasprintf(docg3->dev, GFP_KERNEL, "docg3.%d", + docg3->device_id); + if (!mtd->name) + return -ENOMEM; + docg3->max_block = 2047; + break; + } + mtd->type = MTD_NANDFLASH; + mtd->flags = MTD_CAP_NANDFLASH; + mtd->size = (docg3->max_block + 1) * DOC_LAYOUT_BLOCK_SIZE; + if (docg3->reliable == 2) + mtd->size /= 2; + mtd->erasesize = DOC_LAYOUT_BLOCK_SIZE * DOC_LAYOUT_NBPLANES; + if (docg3->reliable == 2) + mtd->erasesize /= 2; + mtd->writebufsize = mtd->writesize = DOC_LAYOUT_PAGE_SIZE; + mtd->oobsize = DOC_LAYOUT_OOB_SIZE; + mtd->_erase = doc_erase; + mtd->_read_oob = doc_read_oob; + mtd->_write_oob = doc_write_oob; + mtd->_block_isbad = doc_block_isbad; + mtd_set_ooblayout(mtd, &nand_ooblayout_docg3_ops); + mtd->oobavail = 8; + mtd->ecc_strength = DOC_ECC_BCH_T; + + return 0; +} + +/** + * doc_probe_device - Check if a device is available + * @cascade: the cascade of chips this devices will belong to + * @floor: the floor of the probed device + * @dev: the device + * + * Checks whether a device at the specified IO range, and floor is available. + * + * Returns a mtd_info struct if there is a device, ENODEV if none found, ENOMEM + * if a memory allocation failed. If floor 0 is checked, a reset of the ASIC is + * launched. + */ +static struct mtd_info * __init +doc_probe_device(struct docg3_cascade *cascade, int floor, struct device *dev) +{ + int ret, bbt_nbpages; + u16 chip_id, chip_id_inv; + struct docg3 *docg3; + struct mtd_info *mtd; + + ret = -ENOMEM; + docg3 = kzalloc(sizeof(struct docg3), GFP_KERNEL); + if (!docg3) + goto nomem1; + mtd = kzalloc(sizeof(struct mtd_info), GFP_KERNEL); + if (!mtd) + goto nomem2; + mtd->priv = docg3; + mtd->dev.parent = dev; + bbt_nbpages = DIV_ROUND_UP(docg3->max_block + 1, + 8 * DOC_LAYOUT_PAGE_SIZE); + docg3->bbt = kcalloc(DOC_LAYOUT_PAGE_SIZE, bbt_nbpages, GFP_KERNEL); + if (!docg3->bbt) + goto nomem3; + + docg3->dev = dev; + docg3->device_id = floor; + docg3->cascade = cascade; + doc_set_device_id(docg3, docg3->device_id); + if (!floor) + doc_set_asic_mode(docg3, DOC_ASICMODE_RESET); + doc_set_asic_mode(docg3, DOC_ASICMODE_NORMAL); + + chip_id = doc_register_readw(docg3, DOC_CHIPID); + chip_id_inv = doc_register_readw(docg3, DOC_CHIPID_INV); + + ret = 0; + if (chip_id != (u16)(~chip_id_inv)) { + goto nomem4; + } + + switch (chip_id) { + case DOC_CHIPID_G3: + doc_info("Found a G3 DiskOnChip at addr %p, floor %d\n", + docg3->cascade->base, floor); + break; + default: + doc_err("Chip id %04x is not a DiskOnChip G3 chip\n", chip_id); + goto nomem4; + } + + ret = doc_set_driver_info(chip_id, mtd); + if (ret) + goto nomem4; + + doc_hamming_ecc_init(docg3, DOC_LAYOUT_OOB_PAGEINFO_SZ); + doc_reload_bbt(docg3); + return mtd; + +nomem4: + kfree(docg3->bbt); +nomem3: + kfree(mtd); +nomem2: + kfree(docg3); +nomem1: + return ret ? ERR_PTR(ret) : NULL; +} + +/** + * doc_release_device - Release a docg3 floor + * @mtd: the device + */ +static void doc_release_device(struct mtd_info *mtd) +{ + struct docg3 *docg3 = mtd->priv; + + mtd_device_unregister(mtd); + kfree(docg3->bbt); + kfree(docg3); + kfree(mtd); +} + +/** + * docg3_resume - Awakens docg3 floor + * @pdev: platfrom device + * + * Returns 0 (always successful) + */ +static int docg3_resume(struct platform_device *pdev) +{ + int i; + struct docg3_cascade *cascade; + struct mtd_info **docg3_floors, *mtd; + struct docg3 *docg3; + + cascade = platform_get_drvdata(pdev); + docg3_floors = cascade->floors; + mtd = docg3_floors[0]; + docg3 = mtd->priv; + + doc_dbg("docg3_resume()\n"); + for (i = 0; i < 12; i++) + doc_readb(docg3, DOC_IOSPACE_IPL); + return 0; +} + +/** + * docg3_suspend - Put in low power mode the docg3 floor + * @pdev: platform device + * @state: power state + * + * Shuts off most of docg3 circuitery to lower power consumption. + * + * Returns 0 if suspend succeeded, -EIO if chip refused suspend + */ +static int docg3_suspend(struct platform_device *pdev, pm_message_t state) +{ + int floor, i; + struct docg3_cascade *cascade; + struct mtd_info **docg3_floors, *mtd; + struct docg3 *docg3; + u8 ctrl, pwr_down; + + cascade = platform_get_drvdata(pdev); + docg3_floors = cascade->floors; + for (floor = 0; floor < DOC_MAX_NBFLOORS; floor++) { + mtd = docg3_floors[floor]; + if (!mtd) + continue; + docg3 = mtd->priv; + + doc_writeb(docg3, floor, DOC_DEVICESELECT); + ctrl = doc_register_readb(docg3, DOC_FLASHCONTROL); + ctrl &= ~DOC_CTRL_VIOLATION & ~DOC_CTRL_CE; + doc_writeb(docg3, ctrl, DOC_FLASHCONTROL); + + for (i = 0; i < 10; i++) { + usleep_range(3000, 4000); + pwr_down = doc_register_readb(docg3, DOC_POWERMODE); + if (pwr_down & DOC_POWERDOWN_READY) + break; + } + if (pwr_down & DOC_POWERDOWN_READY) { + doc_dbg("docg3_suspend(): floor %d powerdown ok\n", + floor); + } else { + doc_err("docg3_suspend(): floor %d powerdown failed\n", + floor); + return -EIO; + } + } + + mtd = docg3_floors[0]; + docg3 = mtd->priv; + doc_set_asic_mode(docg3, DOC_ASICMODE_POWERDOWN); + return 0; +} + +/** + * docg3_probe - Probe the IO space for a DiskOnChip G3 chip + * @pdev: platform device + * + * Probes for a G3 chip at the specified IO space in the platform data + * ressources. The floor 0 must be available. + * + * Returns 0 on success, -ENOMEM, -ENXIO on error + */ +static int __init docg3_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mtd_info *mtd; + struct resource *ress; + void __iomem *base; + int ret, floor; + struct docg3_cascade *cascade; + + ret = -ENXIO; + ress = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!ress) { + dev_err(dev, "No I/O memory resource defined\n"); + return ret; + } + + ret = -ENOMEM; + base = devm_ioremap(dev, ress->start, DOC_IOSPACE_SIZE); + if (!base) { + dev_err(dev, "devm_ioremap dev failed\n"); + return ret; + } + + cascade = devm_kcalloc(dev, DOC_MAX_NBFLOORS, sizeof(*cascade), + GFP_KERNEL); + if (!cascade) + return ret; + cascade->base = base; + mutex_init(&cascade->lock); + cascade->bch = bch_init(DOC_ECC_BCH_M, DOC_ECC_BCH_T, + DOC_ECC_BCH_PRIMPOLY, false); + if (!cascade->bch) + return ret; + + for (floor = 0; floor < DOC_MAX_NBFLOORS; floor++) { + mtd = doc_probe_device(cascade, floor, dev); + if (IS_ERR(mtd)) { + ret = PTR_ERR(mtd); + goto err_probe; + } + if (!mtd) { + if (floor == 0) + goto notfound; + else + continue; + } + cascade->floors[floor] = mtd; + ret = mtd_device_parse_register(mtd, part_probes, NULL, NULL, + 0); + if (ret) + goto err_probe; + + doc_dbg_register(cascade->floors[floor]); + } + + ret = doc_register_sysfs(pdev, cascade); + if (ret) + goto err_probe; + + platform_set_drvdata(pdev, cascade); + return 0; + +notfound: + ret = -ENODEV; + dev_info(dev, "No supported DiskOnChip found\n"); +err_probe: + bch_free(cascade->bch); + for (floor = 0; floor < DOC_MAX_NBFLOORS; floor++) + if (cascade->floors[floor]) + doc_release_device(cascade->floors[floor]); + return ret; +} + +/** + * docg3_release - Release the driver + * @pdev: the platform device + * + * Returns 0 + */ +static int docg3_release(struct platform_device *pdev) +{ + struct docg3_cascade *cascade = platform_get_drvdata(pdev); + struct docg3 *docg3 = cascade->floors[0]->priv; + int floor; + + doc_unregister_sysfs(pdev, cascade); + for (floor = 0; floor < DOC_MAX_NBFLOORS; floor++) + if (cascade->floors[floor]) + doc_release_device(cascade->floors[floor]); + + bch_free(docg3->cascade->bch); + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id docg3_dt_ids[] = { + { .compatible = "m-systems,diskonchip-g3" }, + {} +}; +MODULE_DEVICE_TABLE(of, docg3_dt_ids); +#endif + +static struct platform_driver g3_driver = { + .driver = { + .name = "docg3", + .of_match_table = of_match_ptr(docg3_dt_ids), + }, + .suspend = docg3_suspend, + .resume = docg3_resume, + .remove = docg3_release, +}; + +module_platform_driver_probe(g3_driver, docg3_probe); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Robert Jarzmik <robert.jarzmik@free.fr>"); +MODULE_DESCRIPTION("MTD driver for DiskOnChip G3"); diff --git a/drivers/mtd/devices/docg3.h b/drivers/mtd/devices/docg3.h new file mode 100644 index 000000000..2c0c5114e --- /dev/null +++ b/drivers/mtd/devices/docg3.h @@ -0,0 +1,343 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Handles the M-Systems DiskOnChip G3 chip + * + * Copyright (C) 2011 Robert Jarzmik + */ + +#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 + */ +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]; +}; + +#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) +#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> diff --git a/drivers/mtd/devices/lart.c b/drivers/mtd/devices/lart.c new file mode 100644 index 000000000..aecd441e4 --- /dev/null +++ b/drivers/mtd/devices/lart.c @@ -0,0 +1,682 @@ +// SPDX-License-Identifier: GPL-2.0-only + +/* + * MTD driver for the 28F160F3 Flash Memory (non-CFI) on LART. + * + * Author: Abraham vd Merwe <abraham@2d3d.co.za> + * + * Copyright (c) 2001, 2d3D, Inc. + * + * 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 PART_BLOB_START 0x00000000 +#define PART_BLOB_LEN (NUM_BLOB_BLOCKS * FLASH_BLOCKSIZE_PARAM) + +/* kernel */ +#define NUM_KERNEL_BLOCKS 7 +#define PART_KERNEL_START (PART_BLOB_START + PART_BLOB_LEN) +#define PART_KERNEL_LEN (NUM_KERNEL_BLOCKS * FLASH_BLOCKSIZE_MAIN) + +/* initial ramdisk */ +#define NUM_INITRD_BLOCKS 24 +#define PART_INITRD_START (PART_KERNEL_START + PART_KERNEL_LEN) +#define PART_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)) + 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++; + } + + 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 const struct mtd_partition lart_partitions[] = { + /* blob */ + { + .name = "blob", + .offset = PART_BLOB_START, + .size = PART_BLOB_LEN, + }, + /* kernel */ + { + .name = "kernel", + .offset = PART_KERNEL_START, /* MTDPART_OFS_APPEND */ + .size = PART_KERNEL_LEN, + }, + /* initial ramdisk / file system */ + { + .name = "file system", + .offset = PART_INITRD_START, /* MTDPART_OFS_APPEND */ + .size = PART_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"); diff --git a/drivers/mtd/devices/mchp23k256.c b/drivers/mtd/devices/mchp23k256.c new file mode 100644 index 000000000..3a6ea7a6a --- /dev/null +++ b/drivers/mtd/devices/mchp23k256.c @@ -0,0 +1,260 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * mchp23k256.c + * + * Driver for Microchip 23k256 SPI RAM chips + * + * Copyright © 2016 Andrew Lunn <andrew@lunn.ch> + */ +#include <linux/device.h> +#include <linux/module.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/partitions.h> +#include <linux/mutex.h> +#include <linux/sched.h> +#include <linux/sizes.h> +#include <linux/spi/flash.h> +#include <linux/spi/spi.h> +#include <linux/of_device.h> + +#define MAX_CMD_SIZE 4 + +struct mchp23_caps { + u8 addr_width; + unsigned int size; +}; + +struct mchp23k256_flash { + struct spi_device *spi; + struct mutex lock; + struct mtd_info mtd; + const struct mchp23_caps *caps; +}; + +#define MCHP23K256_CMD_WRITE_STATUS 0x01 +#define MCHP23K256_CMD_WRITE 0x02 +#define MCHP23K256_CMD_READ 0x03 +#define MCHP23K256_MODE_SEQ BIT(6) + +#define to_mchp23k256_flash(x) container_of(x, struct mchp23k256_flash, mtd) + +static void mchp23k256_addr2cmd(struct mchp23k256_flash *flash, + unsigned int addr, u8 *cmd) +{ + int i; + + /* + * Address is sent in big endian (MSB first) and we skip + * the first entry of the cmd array which contains the cmd + * opcode. + */ + for (i = flash->caps->addr_width; i > 0; i--, addr >>= 8) + cmd[i] = addr; +} + +static int mchp23k256_cmdsz(struct mchp23k256_flash *flash) +{ + return 1 + flash->caps->addr_width; +} + +static int mchp23k256_write(struct mtd_info *mtd, loff_t to, size_t len, + size_t *retlen, const unsigned char *buf) +{ + struct mchp23k256_flash *flash = to_mchp23k256_flash(mtd); + struct spi_transfer transfer[2] = {}; + struct spi_message message; + unsigned char command[MAX_CMD_SIZE]; + int ret, cmd_len; + + spi_message_init(&message); + + cmd_len = mchp23k256_cmdsz(flash); + + command[0] = MCHP23K256_CMD_WRITE; + mchp23k256_addr2cmd(flash, to, command); + + transfer[0].tx_buf = command; + transfer[0].len = cmd_len; + spi_message_add_tail(&transfer[0], &message); + + transfer[1].tx_buf = buf; + transfer[1].len = len; + spi_message_add_tail(&transfer[1], &message); + + mutex_lock(&flash->lock); + + ret = spi_sync(flash->spi, &message); + + mutex_unlock(&flash->lock); + + if (ret) + return ret; + + if (retlen && message.actual_length > cmd_len) + *retlen += message.actual_length - cmd_len; + + return 0; +} + +static int mchp23k256_read(struct mtd_info *mtd, loff_t from, size_t len, + size_t *retlen, unsigned char *buf) +{ + struct mchp23k256_flash *flash = to_mchp23k256_flash(mtd); + struct spi_transfer transfer[2] = {}; + struct spi_message message; + unsigned char command[MAX_CMD_SIZE]; + int ret, cmd_len; + + spi_message_init(&message); + + cmd_len = mchp23k256_cmdsz(flash); + + memset(&transfer, 0, sizeof(transfer)); + command[0] = MCHP23K256_CMD_READ; + mchp23k256_addr2cmd(flash, from, command); + + transfer[0].tx_buf = command; + transfer[0].len = cmd_len; + 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); + + ret = spi_sync(flash->spi, &message); + + mutex_unlock(&flash->lock); + + if (ret) + return ret; + + if (retlen && message.actual_length > cmd_len) + *retlen += message.actual_length - cmd_len; + + return 0; +} + +/* + * Set the device into sequential mode. This allows read/writes to the + * entire SRAM in a single operation + */ +static int mchp23k256_set_mode(struct spi_device *spi) +{ + struct spi_transfer transfer = {}; + struct spi_message message; + unsigned char command[2]; + + spi_message_init(&message); + + command[0] = MCHP23K256_CMD_WRITE_STATUS; + command[1] = MCHP23K256_MODE_SEQ; + + transfer.tx_buf = command; + transfer.len = sizeof(command); + spi_message_add_tail(&transfer, &message); + + return spi_sync(spi, &message); +} + +static const struct mchp23_caps mchp23k256_caps = { + .size = SZ_32K, + .addr_width = 2, +}; + +static const struct mchp23_caps mchp23lcv1024_caps = { + .size = SZ_128K, + .addr_width = 3, +}; + +static int mchp23k256_probe(struct spi_device *spi) +{ + struct mchp23k256_flash *flash; + struct flash_platform_data *data; + int err; + + 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); + + err = mchp23k256_set_mode(spi); + if (err) + return err; + + data = dev_get_platdata(&spi->dev); + + flash->caps = of_device_get_match_data(&spi->dev); + if (!flash->caps) + flash->caps = &mchp23k256_caps; + + mtd_set_of_node(&flash->mtd, spi->dev.of_node); + flash->mtd.dev.parent = &spi->dev; + flash->mtd.type = MTD_RAM; + flash->mtd.flags = MTD_CAP_RAM; + flash->mtd.writesize = 1; + flash->mtd.size = flash->caps->size; + flash->mtd._read = mchp23k256_read; + flash->mtd._write = mchp23k256_write; + + err = mtd_device_register(&flash->mtd, data ? data->parts : NULL, + data ? data->nr_parts : 0); + if (err) + return err; + + return 0; +} + +static void mchp23k256_remove(struct spi_device *spi) +{ + struct mchp23k256_flash *flash = spi_get_drvdata(spi); + + WARN_ON(mtd_device_unregister(&flash->mtd)); +} + +static const struct of_device_id mchp23k256_of_table[] = { + { + .compatible = "microchip,mchp23k256", + .data = &mchp23k256_caps, + }, + { + .compatible = "microchip,mchp23lcv1024", + .data = &mchp23lcv1024_caps, + }, + {} +}; +MODULE_DEVICE_TABLE(of, mchp23k256_of_table); + +static const struct spi_device_id mchp23k256_spi_ids[] = { + { + .name = "mchp23k256", + .driver_data = (kernel_ulong_t)&mchp23k256_caps, + }, + { + .name = "mchp23lcv1024", + .driver_data = (kernel_ulong_t)&mchp23lcv1024_caps, + }, + {} +}; +MODULE_DEVICE_TABLE(spi, mchp23k256_spi_ids); + +static struct spi_driver mchp23k256_driver = { + .driver = { + .name = "mchp23k256", + .of_match_table = mchp23k256_of_table, + }, + .probe = mchp23k256_probe, + .remove = mchp23k256_remove, + .id_table = mchp23k256_spi_ids, +}; + +module_spi_driver(mchp23k256_driver); + +MODULE_DESCRIPTION("MTD SPI driver for MCHP23K256 RAM chips"); +MODULE_AUTHOR("Andrew Lunn <andre@lunn.ch>"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("spi:mchp23k256"); diff --git a/drivers/mtd/devices/mchp48l640.c b/drivers/mtd/devices/mchp48l640.c new file mode 100644 index 000000000..40cd50411 --- /dev/null +++ b/drivers/mtd/devices/mchp48l640.c @@ -0,0 +1,384 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for Microchip 48L640 64 Kb SPI Serial EERAM + * + * Copyright Heiko Schocher <hs@denx.de> + * + * datasheet: http://ww1.microchip.com/downloads/en/DeviceDoc/20006055B.pdf + * + * we set continuous mode but reading/writing more bytes than + * pagesize seems to bring chip into state where readden values + * are wrong ... no idea why. + * + */ +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/jiffies.h> +#include <linux/module.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/partitions.h> +#include <linux/mutex.h> +#include <linux/sched.h> +#include <linux/sizes.h> +#include <linux/spi/flash.h> +#include <linux/spi/spi.h> +#include <linux/of_device.h> + +struct mchp48_caps { + unsigned int size; + unsigned int page_size; +}; + +struct mchp48l640_flash { + struct spi_device *spi; + struct mutex lock; + struct mtd_info mtd; + const struct mchp48_caps *caps; +}; + +#define MCHP48L640_CMD_WREN 0x06 +#define MCHP48L640_CMD_WRDI 0x04 +#define MCHP48L640_CMD_WRITE 0x02 +#define MCHP48L640_CMD_READ 0x03 +#define MCHP48L640_CMD_WRSR 0x01 +#define MCHP48L640_CMD_RDSR 0x05 + +#define MCHP48L640_STATUS_RDY 0x01 +#define MCHP48L640_STATUS_WEL 0x02 +#define MCHP48L640_STATUS_BP0 0x04 +#define MCHP48L640_STATUS_BP1 0x08 +#define MCHP48L640_STATUS_SWM 0x10 +#define MCHP48L640_STATUS_PRO 0x20 +#define MCHP48L640_STATUS_ASE 0x40 + +#define MCHP48L640_TIMEOUT 100 + +#define MAX_CMD_SIZE 0x10 + +#define to_mchp48l640_flash(x) container_of(x, struct mchp48l640_flash, mtd) + +static int mchp48l640_mkcmd(struct mchp48l640_flash *flash, u8 cmd, loff_t addr, char *buf) +{ + buf[0] = cmd; + buf[1] = addr >> 8; + buf[2] = addr; + + return 3; +} + +static int mchp48l640_read_status(struct mchp48l640_flash *flash, int *status) +{ + unsigned char cmd[2]; + int ret; + + cmd[0] = MCHP48L640_CMD_RDSR; + cmd[1] = 0x00; + mutex_lock(&flash->lock); + ret = spi_write_then_read(flash->spi, &cmd[0], 1, &cmd[1], 1); + mutex_unlock(&flash->lock); + if (!ret) + *status = cmd[1]; + dev_dbg(&flash->spi->dev, "read status ret: %d status: %x", ret, *status); + + return ret; +} + +static int mchp48l640_waitforbit(struct mchp48l640_flash *flash, int bit, bool set) +{ + int ret, status; + unsigned long deadline; + + deadline = jiffies + msecs_to_jiffies(MCHP48L640_TIMEOUT); + do { + ret = mchp48l640_read_status(flash, &status); + dev_dbg(&flash->spi->dev, "read status ret: %d bit: %x %sset status: %x", + ret, bit, (set ? "" : "not"), status); + if (ret) + return ret; + + if (set) { + if ((status & bit) == bit) + return 0; + } else { + if ((status & bit) == 0) + return 0; + } + + usleep_range(1000, 2000); + } while (!time_after_eq(jiffies, deadline)); + + dev_err(&flash->spi->dev, "Timeout waiting for bit %x %s set in status register.", + bit, (set ? "" : "not")); + return -ETIMEDOUT; +} + +static int mchp48l640_write_prepare(struct mchp48l640_flash *flash, bool enable) +{ + unsigned char cmd[2]; + int ret; + + if (enable) + cmd[0] = MCHP48L640_CMD_WREN; + else + cmd[0] = MCHP48L640_CMD_WRDI; + + mutex_lock(&flash->lock); + ret = spi_write(flash->spi, cmd, 1); + mutex_unlock(&flash->lock); + + if (ret) + dev_err(&flash->spi->dev, "write %sable failed ret: %d", + (enable ? "en" : "dis"), ret); + + dev_dbg(&flash->spi->dev, "write %sable success ret: %d", + (enable ? "en" : "dis"), ret); + if (enable) + return mchp48l640_waitforbit(flash, MCHP48L640_STATUS_WEL, true); + + return ret; +} + +static int mchp48l640_set_mode(struct mchp48l640_flash *flash) +{ + unsigned char cmd[2]; + int ret; + + ret = mchp48l640_write_prepare(flash, true); + if (ret) + return ret; + + cmd[0] = MCHP48L640_CMD_WRSR; + cmd[1] = MCHP48L640_STATUS_PRO; + + mutex_lock(&flash->lock); + ret = spi_write(flash->spi, cmd, 2); + mutex_unlock(&flash->lock); + if (ret) + dev_err(&flash->spi->dev, "Could not set continuous mode ret: %d", ret); + + return mchp48l640_waitforbit(flash, MCHP48L640_STATUS_PRO, true); +} + +static int mchp48l640_wait_rdy(struct mchp48l640_flash *flash) +{ + return mchp48l640_waitforbit(flash, MCHP48L640_STATUS_RDY, false); +}; + +static int mchp48l640_write_page(struct mtd_info *mtd, loff_t to, size_t len, + size_t *retlen, const unsigned char *buf) +{ + struct mchp48l640_flash *flash = to_mchp48l640_flash(mtd); + unsigned char *cmd; + int ret; + int cmdlen; + + cmd = kmalloc((3 + len), GFP_KERNEL | GFP_DMA); + if (!cmd) + return -ENOMEM; + + ret = mchp48l640_wait_rdy(flash); + if (ret) + goto fail; + + ret = mchp48l640_write_prepare(flash, true); + if (ret) + goto fail; + + mutex_lock(&flash->lock); + cmdlen = mchp48l640_mkcmd(flash, MCHP48L640_CMD_WRITE, to, cmd); + memcpy(&cmd[cmdlen], buf, len); + ret = spi_write(flash->spi, cmd, cmdlen + len); + mutex_unlock(&flash->lock); + if (!ret) + *retlen += len; + else + goto fail; + + ret = mchp48l640_waitforbit(flash, MCHP48L640_STATUS_WEL, false); + if (ret) + goto fail; + + kfree(cmd); + return 0; +fail: + kfree(cmd); + dev_err(&flash->spi->dev, "write fail with: %d", ret); + return ret; +}; + +static int mchp48l640_write(struct mtd_info *mtd, loff_t to, size_t len, + size_t *retlen, const unsigned char *buf) +{ + struct mchp48l640_flash *flash = to_mchp48l640_flash(mtd); + int ret; + size_t wlen = 0; + loff_t woff = to; + size_t ws; + size_t page_sz = flash->caps->page_size; + + /* + * we set PRO bit (page rollover), but writing length > page size + * does result in total chaos, so write in 32 byte chunks. + */ + while (wlen < len) { + ws = min((len - wlen), page_sz); + ret = mchp48l640_write_page(mtd, woff, ws, retlen, &buf[wlen]); + if (ret) + return ret; + wlen += ws; + woff += ws; + } + + return 0; +} + +static int mchp48l640_read_page(struct mtd_info *mtd, loff_t from, size_t len, + size_t *retlen, unsigned char *buf) +{ + struct mchp48l640_flash *flash = to_mchp48l640_flash(mtd); + unsigned char *cmd; + int ret; + int cmdlen; + + cmd = kmalloc((3 + len), GFP_KERNEL | GFP_DMA); + if (!cmd) + return -ENOMEM; + + ret = mchp48l640_wait_rdy(flash); + if (ret) + goto fail; + + mutex_lock(&flash->lock); + cmdlen = mchp48l640_mkcmd(flash, MCHP48L640_CMD_READ, from, cmd); + ret = spi_write_then_read(flash->spi, cmd, cmdlen, buf, len); + mutex_unlock(&flash->lock); + if (!ret) + *retlen += len; + + kfree(cmd); + return ret; + +fail: + kfree(cmd); + dev_err(&flash->spi->dev, "read fail with: %d", ret); + return ret; +} + +static int mchp48l640_read(struct mtd_info *mtd, loff_t from, size_t len, + size_t *retlen, unsigned char *buf) +{ + struct mchp48l640_flash *flash = to_mchp48l640_flash(mtd); + int ret; + size_t wlen = 0; + loff_t woff = from; + size_t ws; + size_t page_sz = flash->caps->page_size; + + /* + * we set PRO bit (page rollover), but if read length > page size + * does result in total chaos in result ... + */ + while (wlen < len) { + ws = min((len - wlen), page_sz); + ret = mchp48l640_read_page(mtd, woff, ws, retlen, &buf[wlen]); + if (ret) + return ret; + wlen += ws; + woff += ws; + } + + return 0; +}; + +static const struct mchp48_caps mchp48l640_caps = { + .size = SZ_8K, + .page_size = 32, +}; + +static int mchp48l640_probe(struct spi_device *spi) +{ + struct mchp48l640_flash *flash; + struct flash_platform_data *data; + int err; + int status; + + 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); + + err = mchp48l640_read_status(flash, &status); + if (err) + return err; + + err = mchp48l640_set_mode(flash); + if (err) + return err; + + data = dev_get_platdata(&spi->dev); + + flash->caps = of_device_get_match_data(&spi->dev); + if (!flash->caps) + flash->caps = &mchp48l640_caps; + + mtd_set_of_node(&flash->mtd, spi->dev.of_node); + flash->mtd.dev.parent = &spi->dev; + flash->mtd.type = MTD_RAM; + flash->mtd.flags = MTD_CAP_RAM; + flash->mtd.writesize = flash->caps->page_size; + flash->mtd.size = flash->caps->size; + flash->mtd._read = mchp48l640_read; + flash->mtd._write = mchp48l640_write; + + err = mtd_device_register(&flash->mtd, data ? data->parts : NULL, + data ? data->nr_parts : 0); + if (err) + return err; + + return 0; +} + +static void mchp48l640_remove(struct spi_device *spi) +{ + struct mchp48l640_flash *flash = spi_get_drvdata(spi); + + WARN_ON(mtd_device_unregister(&flash->mtd)); +} + +static const struct of_device_id mchp48l640_of_table[] = { + { + .compatible = "microchip,48l640", + .data = &mchp48l640_caps, + }, + {} +}; +MODULE_DEVICE_TABLE(of, mchp48l640_of_table); + +static const struct spi_device_id mchp48l640_spi_ids[] = { + { + .name = "48l640", + .driver_data = (kernel_ulong_t)&mchp48l640_caps, + }, + {} +}; +MODULE_DEVICE_TABLE(spi, mchp48l640_spi_ids); + +static struct spi_driver mchp48l640_driver = { + .driver = { + .name = "mchp48l640", + .of_match_table = mchp48l640_of_table, + }, + .probe = mchp48l640_probe, + .remove = mchp48l640_remove, + .id_table = mchp48l640_spi_ids, +}; + +module_spi_driver(mchp48l640_driver); + +MODULE_DESCRIPTION("MTD SPI driver for Microchip 48l640 EERAM chips"); +MODULE_AUTHOR("Heiko Schocher <hs@denx.de>"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("spi:mchp48l640"); diff --git a/drivers/mtd/devices/ms02-nv.c b/drivers/mtd/devices/ms02-nv.c new file mode 100644 index 000000000..08f76ff83 --- /dev/null +++ b/drivers/mtd/devices/ms02-nv.c @@ -0,0 +1,306 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2001 Maciej W. Rozycki + */ + +#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; + } + + 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); diff --git a/drivers/mtd/devices/ms02-nv.h b/drivers/mtd/devices/ms02-nv.h new file mode 100644 index 000000000..737e4735d --- /dev/null +++ b/drivers/mtd/devices/ms02-nv.h @@ -0,0 +1,101 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * 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. + */ + +#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; +}; diff --git a/drivers/mtd/devices/mtd_dataflash.c b/drivers/mtd/devices/mtd_dataflash.c new file mode 100644 index 000000000..25bad4318 --- /dev/null +++ b/drivers/mtd/devices/mtd_dataflash.c @@ -0,0 +1,956 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Atmel AT45xxx DataFlash MTD driver for lightweight SPI framework + * + * Largely derived from at91_dataflash.c: + * Copyright (C) 2003-2005 SAN People (Pty) Ltd +*/ +#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 */ + +#define CFI_MFR_ATMEL 0x1F + +#define DATAFLASH_SHIFT_EXTID 24 +#define DATAFLASH_SHIFT_ID 40 + +struct dataflash { + u8 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; +}; + +static const struct spi_device_id dataflash_dev_ids[] = { + { "at45" }, + { "dataflash" }, + { }, +}; +MODULE_DEVICE_TABLE(spi, dataflash_dev_ids); + +#ifdef CONFIG_OF +static const struct of_device_id dataflash_dt_ids[] = { + { .compatible = "atmel,at45", }, + { .compatible = "atmel,dataflash", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, dataflash_dt_ids); +#endif + +static const struct spi_device_id dataflash_spi_ids[] = { + { .name = "at45", }, + { .name = "dataflash", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(spi, dataflash_spi_ids); + +/* ......................................................................... */ + +/* + * 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) { + dev_dbg(&spi->dev, "status %d?\n", status); + status = 0; + } + + if (status & (1 << 7)) /* RDY/nBSY */ + return status; + + usleep_range(3000, 4000); + } +} + +/* ......................................................................... */ + +/* + * 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 = { }; + struct spi_message msg; + unsigned blocksize = priv->page_size << 3; + u8 *command; + u32 rem; + + dev_dbg(&spi->dev, "erase addr=0x%llx len 0x%llx\n", + (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] = (u8)(pageaddr >> 16); + command[2] = (u8)(pageaddr >> 8); + command[3] = 0; + + dev_dbg(&spi->dev, "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) { + dev_err(&spi->dev, "erase %x, err %d\n", + 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); + + 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] = { }; + struct spi_message msg; + unsigned int addr; + u8 *command; + int status; + + dev_dbg(&priv->spi->dev, "read 0x%x..0x%x\n", + (unsigned int)from, (unsigned int)(from + len)); + + /* Calculate flash page/byte address */ + addr = (((unsigned)from / priv->page_size) << priv->page_offset) + + ((unsigned)from % priv->page_size); + + command = priv->command; + + dev_dbg(&priv->spi->dev, "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] = (u8)(addr >> 16); + command[2] = (u8)(addr >> 8); + command[3] = (u8)(addr >> 0); + /* plus 4 "don't care" bytes */ + + status = spi_sync(priv->spi, &msg); + mutex_unlock(&priv->lock); + + if (status >= 0) { + *retlen = msg.actual_length - 8; + status = 0; + } else + dev_dbg(&priv->spi->dev, "read %x..%x --> %d\n", + (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] = { }; + struct spi_message msg; + unsigned int pageaddr, addr, offset, writelen; + size_t remaining = len; + u_char *writebuf = (u_char *) buf; + int status = -EINVAL; + u8 *command; + + dev_dbg(&spi->dev, "write 0x%x..0x%x\n", + (unsigned int)to, (unsigned int)(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) { + dev_dbg(&spi->dev, "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; + + dev_dbg(&spi->dev, "TRANSFER: (%x) %x %x %x\n", + command[0], command[1], command[2], command[3]); + + status = spi_sync(spi, &msg); + if (status < 0) + dev_dbg(&spi->dev, "xfer %u -> %d\n", + 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); + + dev_dbg(&spi->dev, "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) + dev_dbg(&spi->dev, "pgm %u/%u -> %d\n", + 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; + + dev_dbg(&spi->dev, "COMPARE: (%x) %x %x %x\n", + command[0], command[1], command[2], command[3]); + + status = spi_sync(spi, &msg); + if (status < 0) + dev_dbg(&spi->dev, "compare %u -> %d\n", + addr, status); + + status = dataflash_waitready(priv->spi); + + /* Check result of the compare operation */ + if (status & (1 << 6)) { + dev_err(&spi->dev, "compare page %u, err %d\n", + 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, + u8 *buf, loff_t off, size_t len) +{ + struct spi_message m; + size_t l; + u8 *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, const u_char *buf) +{ + struct spi_message m; + const size_t l = 4 + 64; + u8 *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 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->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; + mtd_set_of_node(device, spi->dev.of_node); + + 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); + + err = mtd_device_register(device, + 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. + */ + u64 jedec_id; + + /* The size listed here is what works with OP_ERASE_PAGE. */ + unsigned nr_pages; + u16 pagesize; + u16 pageoffset; + + u16 flags; +#define SUP_EXTID 0x0004 /* supports extended ID data */ +#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}, + + { "AT45DB641E", 0x1f28000100ULL, 32768, 264, 9, SUP_EXTID | SUP_POW2PS}, + { "at45db641e", 0x1f28000100ULL, 32768, 256, 8, SUP_EXTID | SUP_POW2PS | IS_POW2PS}, +}; + +static struct flash_info *jedec_lookup(struct spi_device *spi, + u64 jedec, bool use_extid) +{ + struct flash_info *info; + int status; + + for (info = dataflash_data; + info < dataflash_data + ARRAY_SIZE(dataflash_data); + info++) { + if (use_extid && !(info->flags & SUP_EXTID)) + continue; + + if (info->jedec_id == jedec) { + dev_dbg(&spi->dev, "OTP, sector protect%s\n", + (info->flags & SUP_POW2PS) ? + ", binary pagesize" : ""); + if (info->flags & SUP_POW2PS) { + status = dataflash_status(spi); + if (status < 0) { + dev_dbg(&spi->dev, "status error %d\n", + 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; + } + } + + return ERR_PTR(-ENODEV); +} + +static struct flash_info *jedec_probe(struct spi_device *spi) +{ + int ret; + u8 code = OP_READ_ID; + u64 jedec; + u8 id[sizeof(jedec)] = {0}; + const unsigned int id_size = 5; + struct flash_info *info; + + /* + * JEDEC also defines an optional "extended device information" + * string for after vendor-specific data, after the three bytes + * we use here. Supporting some chips might require using it. + * + * 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. + */ + ret = spi_write_then_read(spi, &code, 1, id, id_size); + if (ret < 0) { + dev_dbg(&spi->dev, "error %d reading JEDEC ID\n", ret); + return ERR_PTR(ret); + } + + if (id[0] != CFI_MFR_ATMEL) + return NULL; + + jedec = be64_to_cpup((__be64 *)id); + + /* + * First, try to match device using extended device + * information + */ + info = jedec_lookup(spi, jedec >> DATAFLASH_SHIFT_EXTID, true); + if (!IS_ERR(info)) + return info; + /* + * If that fails, make another pass using regular ID + * information + */ + info = jedec_lookup(spi, jedec >> DATAFLASH_SHIFT_ID, false); + if (!IS_ERR(info)) + 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 %016llx 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) { + dev_dbg(&spi->dev, "status error %d\n", 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) + dev_dbg(&spi->dev, "add_dataflash --> %d\n", status); + + return status; +} + +static void dataflash_remove(struct spi_device *spi) +{ + struct dataflash *flash = spi_get_drvdata(spi); + + dev_dbg(&spi->dev, "remove\n"); + + WARN_ON(mtd_device_unregister(&flash->mtd)); + + kfree(flash); +} + +static struct spi_driver dataflash_driver = { + .driver = { + .name = "mtd_dataflash", + .of_match_table = of_match_ptr(dataflash_dt_ids), + }, + .id_table = dataflash_dev_ids, + + .probe = dataflash_probe, + .remove = dataflash_remove, + .id_table = dataflash_spi_ids, + + /* 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"); diff --git a/drivers/mtd/devices/mtdram.c b/drivers/mtd/devices/mtdram.c new file mode 100644 index 000000000..1c97fabc4 --- /dev/null +++ b/drivers/mtd/devices/mtdram.c @@ -0,0 +1,187 @@ +/* + * 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/mm.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; +static unsigned long writebuf_size = 64; +#define MTDRAM_TOTAL_SIZE (total_size * 1024) +#define MTDRAM_ERASE_SIZE (erase_size * 1024) + +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"); +module_param(writebuf_size, ulong, 0); +MODULE_PARM_DESC(writebuf_size, "Device write buf size in Bytes (Default: 64)"); + +// We could store these in the mtd structure, but we only support 1 device.. +static struct mtd_info *mtd_info; + +static int check_offs_len(struct mtd_info *mtd, loff_t ofs, uint64_t len) +{ + int ret = 0; + + /* Start address must align on block boundary */ + if (mtd_mod_by_eb(ofs, mtd)) { + pr_debug("%s: unaligned address\n", __func__); + ret = -EINVAL; + } + + /* Length must align on block boundary */ + if (mtd_mod_by_eb(len, mtd)) { + pr_debug("%s: length not block aligned\n", __func__); + ret = -EINVAL; + } + + return ret; +} + +static int ram_erase(struct mtd_info *mtd, struct erase_info *instr) +{ + if (check_offs_len(mtd, instr->addr, instr->len)) + return -EINVAL; + memset((char *)mtd->priv + instr->addr, 0xff, instr->len); + + 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; + + if (phys) { + /* limit retlen to the number of contiguous physical pages */ + unsigned long page_ofs = offset_in_page(*virt); + void *addr = *virt - page_ofs; + unsigned long pfn1, pfn0 = vmalloc_to_pfn(addr); + + *phys = __pfn_to_phys(pfn0) + page_ofs; + len += page_ofs; + while (len > PAGE_SIZE) { + len -= PAGE_SIZE; + addr += PAGE_SIZE; + pfn0++; + pfn1 = vmalloc_to_pfn(addr); + if (pfn1 != pfn0) { + *retlen = addr - *virt; + break; + } + } + } + + return 0; +} + +static int ram_unpoint(struct mtd_info *mtd, loff_t from, size_t len) +{ + return 0; +} + +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 = writebuf_size; + mtd->erasesize = MTDRAM_ERASE_SIZE; + mtd->priv = mapped_address; + + mtd->owner = THIS_MODULE; + mtd->_erase = ram_erase; + mtd->_point = ram_point; + mtd->_unpoint = ram_unpoint; + mtd->_read = ram_read; + mtd->_write = ram_write; + + if (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"); diff --git a/drivers/mtd/devices/phram.c b/drivers/mtd/devices/phram.c new file mode 100644 index 000000000..208bd4d87 --- /dev/null +++ b/drivers/mtd/devices/phram.c @@ -0,0 +1,442 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * 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>[,<erasesize>] + * <name> may be up to 63 characters. + * <start>, <len>, and <erasesize> can be octal, decimal or hexadecimal. If followed + * by "ki", "Mi" or "Gi", the numbers will be interpreted as kilo, mega or + * gigabytes. <erasesize> is optional and defaults to PAGE_SIZE. + * + * Example: + * phram=swap,64Mi,128Mi phram=test,900Mi,1Mi,64Ki + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/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> +#include <asm/div64.h> +#include <linux/platform_device.h> +#include <linux/of_address.h> +#include <linux/of.h> + +struct phram_mtd_list { + struct mtd_info mtd; + struct list_head list; + bool cached; +}; + +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); + + 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 int phram_map(struct phram_mtd_list *phram, phys_addr_t start, size_t len) +{ + void *addr = NULL; + + if (phram->cached) + addr = memremap(start, len, MEMREMAP_WB); + else + addr = (void __force *)ioremap(start, len); + if (!addr) + return -EIO; + + phram->mtd.priv = addr; + + return 0; +} + +static void phram_unmap(struct phram_mtd_list *phram) +{ + void *addr = phram->mtd.priv; + + if (phram->cached) { + memunmap(addr); + return; + } + + iounmap((void __iomem *)addr); +} + +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); + phram_unmap(this); + kfree(this->mtd.name); + kfree(this); + } +} + +static int register_device(struct platform_device *pdev, const char *name, + phys_addr_t start, size_t len, uint32_t erasesize) +{ + struct device_node *np = pdev ? pdev->dev.of_node : NULL; + bool cached = np ? !of_property_read_bool(np, "no-map") : false; + struct phram_mtd_list *new; + int ret = -ENOMEM; + + new = kzalloc(sizeof(*new), GFP_KERNEL); + if (!new) + goto out0; + + new->cached = cached; + + ret = phram_map(new, start, len); + if (ret) { + 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 = erasesize; + new->mtd.writesize = 1; + + mtd_set_of_node(&new->mtd, np); + + ret = -EAGAIN; + if (mtd_device_register(&new->mtd, NULL, 0)) { + pr_err("Failed to register new device\n"); + goto out2; + } + + if (pdev) + platform_set_drvdata(pdev, new); + else + list_add_tail(&new->list, &phram_list); + + return 0; + +out2: + phram_unmap(new); +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; + fallthrough; + case 'M': + shift += 10; + fallthrough; + 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>[,<erasesize>] for module case + * - phram.phram=<device>,<address>,<size>[,<erasesize>] for built-in case + * We leave 64 bytes for the device name, 20 for the address , 20 for the + * size and 20 for the erasesize. + * Example: phram.phram=rootfs,0xa0000000,512Mi,65536 + */ +static char phram_paramline[64 + 20 + 20 + 20]; +#endif + +static int phram_setup(const char *val) +{ + char buf[64 + 20 + 20 + 20], *str = buf; + char *token[4]; + char *name; + uint64_t start; + uint64_t len; + uint64_t erasesize = PAGE_SIZE; + uint32_t rem; + 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 < 4; 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) { + parse_err("illegal start address\n"); + goto error; + } + + ret = parse_num64(&len, token[2]); + if (ret) { + parse_err("illegal device length\n"); + goto error; + } + + if (token[3]) { + ret = parse_num64(&erasesize, token[3]); + if (ret) { + parse_err("illegal erasesize\n"); + goto error; + } + } + + if (len == 0 || erasesize == 0 || erasesize > len + || erasesize > UINT_MAX) { + parse_err("illegal erasesize or len\n"); + ret = -EINVAL; + goto error; + } + + div_u64_rem(len, (uint32_t)erasesize, &rem); + if (rem) { + parse_err("len is not multiple of erasesize\n"); + ret = -EINVAL; + goto error; + } + + ret = register_device(NULL, name, start, len, (uint32_t)erasesize); + if (ret) + goto error; + + pr_info("%s device: %#llx at %#llx for erasesize %#llx\n", name, len, start, erasesize); + return 0; + +error: + kfree(name); + return ret; +} + +static int phram_param_call(const char *val, const 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, 0200); +MODULE_PARM_DESC(phram, "Memory region to map. \"phram=<name>,<start>,<length>[,<erasesize>]\""); + +#ifdef CONFIG_OF +static const struct of_device_id phram_of_match[] = { + { .compatible = "phram" }, + {} +}; +MODULE_DEVICE_TABLE(of, phram_of_match); +#endif + +static int phram_probe(struct platform_device *pdev) +{ + struct resource *res; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENOMEM; + + /* mtd_set_of_node() reads name from "label" */ + return register_device(pdev, NULL, res->start, resource_size(res), + PAGE_SIZE); +} + +static int phram_remove(struct platform_device *pdev) +{ + struct phram_mtd_list *phram = platform_get_drvdata(pdev); + + mtd_device_unregister(&phram->mtd); + phram_unmap(phram); + kfree(phram); + + return 0; +} + +static struct platform_driver phram_driver = { + .probe = phram_probe, + .remove = phram_remove, + .driver = { + .name = "phram", + .of_match_table = of_match_ptr(phram_of_match), + }, +}; + +static int __init init_phram(void) +{ + int ret; + + ret = platform_driver_register(&phram_driver); + if (ret) + return ret; + +#ifndef MODULE + if (phram_paramline[0]) + ret = phram_setup(phram_paramline); + phram_init_called = 1; +#endif + + if (ret) + platform_driver_unregister(&phram_driver); + + return ret; +} + +static void __exit cleanup_phram(void) +{ + unregister_devices(); + platform_driver_unregister(&phram_driver); +} + +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"); diff --git a/drivers/mtd/devices/pmc551.c b/drivers/mtd/devices/pmc551.c new file mode 100644 index 000000000..6597fc2aa --- /dev/null +++ b/drivers/mtd/devices/pmc551.c @@ -0,0 +1,847 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * PMC551 PCI Mezzanine Ram Device + * + * Author: + * Mark Ferrell <mferrell@mvista.com> + * Copyright 1999,2000 Nortel Networks + * + * 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 <linux/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; /* start address offset hi */ + 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); + + 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: +#ifdef CONFIG_MTD_PMC551_DEBUG + printk(KERN_DEBUG "pmc551_erase() done\n"); +#endif + + 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; /* start address offset hi */ + 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); + 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; /* start address offset hi */ + 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); + 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 __init 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 */ + 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); diff --git a/drivers/mtd/devices/powernv_flash.c b/drivers/mtd/devices/powernv_flash.c new file mode 100644 index 000000000..36e060386 --- /dev/null +++ b/drivers/mtd/devices/powernv_flash.c @@ -0,0 +1,297 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * OPAL PNOR flash MTD abstraction + * + * Copyright IBM 2015 + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/platform_device.h> +#include <linux/string.h> +#include <linux/slab.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/partitions.h> + +#include <linux/debugfs.h> +#include <linux/seq_file.h> + +#include <asm/opal.h> + + +/* + * This driver creates the a Linux MTD abstraction for platform PNOR flash + * backed by OPAL calls + */ + +struct powernv_flash { + struct mtd_info mtd; + u32 id; +}; + +enum flash_op { + FLASH_OP_READ, + FLASH_OP_WRITE, + FLASH_OP_ERASE, +}; + +/* + * Don't return -ERESTARTSYS if we can't get a token, the MTD core + * might have split up the call from userspace and called into the + * driver more than once, we'll already have done some amount of work. + */ +static int powernv_flash_async_op(struct mtd_info *mtd, enum flash_op op, + loff_t offset, size_t len, size_t *retlen, u_char *buf) +{ + struct powernv_flash *info = (struct powernv_flash *)mtd->priv; + struct device *dev = &mtd->dev; + int token; + struct opal_msg msg; + int rc; + + dev_dbg(dev, "%s(op=%d, offset=0x%llx, len=%zu)\n", + __func__, op, offset, len); + + token = opal_async_get_token_interruptible(); + if (token < 0) { + if (token != -ERESTARTSYS) + dev_err(dev, "Failed to get an async token\n"); + else + token = -EINTR; + return token; + } + + switch (op) { + case FLASH_OP_READ: + rc = opal_flash_read(info->id, offset, __pa(buf), len, token); + break; + case FLASH_OP_WRITE: + rc = opal_flash_write(info->id, offset, __pa(buf), len, token); + break; + case FLASH_OP_ERASE: + rc = opal_flash_erase(info->id, offset, len, token); + break; + default: + WARN_ON_ONCE(1); + opal_async_release_token(token); + return -EIO; + } + + if (rc == OPAL_ASYNC_COMPLETION) { + rc = opal_async_wait_response_interruptible(token, &msg); + if (rc) { + /* + * If we return the mtd core will free the + * buffer we've just passed to OPAL but OPAL + * will continue to read or write from that + * memory. + * It may be tempting to ultimately return 0 + * if we're doing a read or a write since we + * are going to end up waiting until OPAL is + * done. However, because the MTD core sends + * us the userspace request in chunks, we need + * it to know we've been interrupted. + */ + rc = -EINTR; + if (opal_async_wait_response(token, &msg)) + dev_err(dev, "opal_async_wait_response() failed\n"); + goto out; + } + rc = opal_get_async_rc(msg); + } + + /* + * OPAL does mutual exclusion on the flash, it will return + * OPAL_BUSY. + * During firmware updates by the service processor OPAL may + * be (temporarily) prevented from accessing the flash, in + * this case OPAL will also return OPAL_BUSY. + * Both cases aren't errors exactly but the flash could have + * changed, userspace should be informed. + */ + if (rc != OPAL_SUCCESS && rc != OPAL_BUSY) + dev_err(dev, "opal_flash_async_op(op=%d) failed (rc %d)\n", + op, rc); + + if (rc == OPAL_SUCCESS && retlen) + *retlen = len; + + rc = opal_error_code(rc); +out: + opal_async_release_token(token); + return rc; +} + +/** + * powernv_flash_read + * @mtd: the device + * @from: the offset to read from + * @len: the number of bytes to read + * @retlen: the number of bytes actually read + * @buf: the filled in buffer + * + * Returns 0 if read successful, or -ERRNO if an error occurred + */ +static int powernv_flash_read(struct mtd_info *mtd, loff_t from, size_t len, + size_t *retlen, u_char *buf) +{ + return powernv_flash_async_op(mtd, FLASH_OP_READ, from, + len, retlen, buf); +} + +/** + * powernv_flash_write + * @mtd: the device + * @to: the offset to write to + * @len: the number of bytes to write + * @retlen: the number of bytes actually written + * @buf: the buffer to get bytes from + * + * Returns 0 if write successful, -ERRNO if error occurred + */ +static int powernv_flash_write(struct mtd_info *mtd, loff_t to, size_t len, + size_t *retlen, const u_char *buf) +{ + return powernv_flash_async_op(mtd, FLASH_OP_WRITE, to, + len, retlen, (u_char *)buf); +} + +/** + * powernv_flash_erase + * @mtd: the device + * @erase: the erase info + * Returns 0 if erase successful or -ERRNO if an error occurred + */ +static int powernv_flash_erase(struct mtd_info *mtd, struct erase_info *erase) +{ + int rc; + + rc = powernv_flash_async_op(mtd, FLASH_OP_ERASE, erase->addr, + erase->len, NULL, NULL); + if (rc) + erase->fail_addr = erase->addr; + + return rc; +} + +/** + * powernv_flash_set_driver_info - Fill the mtd_info structure and docg3 + * @dev: The device structure + * @mtd: The structure to fill + */ +static int powernv_flash_set_driver_info(struct device *dev, + struct mtd_info *mtd) +{ + u64 size; + u32 erase_size; + int rc; + + rc = of_property_read_u32(dev->of_node, "ibm,flash-block-size", + &erase_size); + if (rc) { + dev_err(dev, "couldn't get resource block size information\n"); + return rc; + } + + rc = of_property_read_u64(dev->of_node, "reg", &size); + if (rc) { + dev_err(dev, "couldn't get resource size information\n"); + return rc; + } + + /* + * Going to have to check what details I need to set and how to + * get them + */ + mtd->name = devm_kasprintf(dev, GFP_KERNEL, "%pOFP", dev->of_node); + mtd->type = MTD_NORFLASH; + mtd->flags = MTD_WRITEABLE; + mtd->size = size; + mtd->erasesize = erase_size; + mtd->writebufsize = mtd->writesize = 1; + mtd->owner = THIS_MODULE; + mtd->_erase = powernv_flash_erase; + mtd->_read = powernv_flash_read; + mtd->_write = powernv_flash_write; + mtd->dev.parent = dev; + mtd_set_of_node(mtd, dev->of_node); + return 0; +} + +/** + * powernv_flash_probe + * @pdev: platform device + * + * Returns 0 on success, -ENOMEM, -ENXIO on error + */ +static int powernv_flash_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct powernv_flash *data; + int ret; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->mtd.priv = data; + + ret = of_property_read_u32(dev->of_node, "ibm,opal-id", &(data->id)); + if (ret) { + dev_err(dev, "no device property 'ibm,opal-id'\n"); + return ret; + } + + ret = powernv_flash_set_driver_info(dev, &data->mtd); + if (ret) + return ret; + + dev_set_drvdata(dev, data); + + /* + * The current flash that skiboot exposes is one contiguous flash chip + * with an ffs partition at the start, it should prove easier for users + * to deal with partitions or not as they see fit + */ + return mtd_device_register(&data->mtd, NULL, 0); +} + +/** + * op_release - Release the driver + * @pdev: the platform device + * + * Returns 0 + */ +static int powernv_flash_release(struct platform_device *pdev) +{ + struct powernv_flash *data = dev_get_drvdata(&(pdev->dev)); + + /* All resources should be freed automatically */ + WARN_ON(mtd_device_unregister(&data->mtd)); + + return 0; +} + +static const struct of_device_id powernv_flash_match[] = { + { .compatible = "ibm,opal-flash" }, + {} +}; + +static struct platform_driver powernv_flash_driver = { + .driver = { + .name = "powernv_flash", + .of_match_table = powernv_flash_match, + }, + .remove = powernv_flash_release, + .probe = powernv_flash_probe, +}; + +module_platform_driver(powernv_flash_driver); + +MODULE_DEVICE_TABLE(of, powernv_flash_match); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Cyril Bur <cyril.bur@au1.ibm.com>"); +MODULE_DESCRIPTION("MTD abstraction for OPAL flash"); diff --git a/drivers/mtd/devices/serial_flash_cmds.h b/drivers/mtd/devices/serial_flash_cmds.h new file mode 100644 index 000000000..8b51a872f --- /dev/null +++ b/drivers/mtd/devices/serial_flash_cmds.h @@ -0,0 +1,49 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Generic/SFDP Flash Commands and Device Capabilities + * + * Copyright (C) 2013 Lee Jones <lee.jones@lianro.org> + */ + +#ifndef _MTD_SERIAL_FLASH_CMDS_H +#define _MTD_SERIAL_FLASH_CMDS_H + +/* Generic Flash Commands/OPCODEs */ +#define SPINOR_OP_WRVCR 0x81 +#define SPINOR_OP_RDVCR 0x85 + +/* JEDEC Standard - Serial Flash Discoverable Parmeters (SFDP) Commands */ +#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 */ + +/* 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 */ diff --git a/drivers/mtd/devices/slram.c b/drivers/mtd/devices/slram.c new file mode 100644 index 000000000..28131a127 --- /dev/null +++ b/drivers/mtd/devices/slram.c @@ -0,0 +1,344 @@ +// SPDX-License-Identifier: GPL-2.0-only +/*====================================================================== + + 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 <linux/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 <linux/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); + + 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 = + memremap(start, length, + MEMREMAP_WB | MEMREMAP_WT | MEMREMAP_WC))) { + E("slram: memremap 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"); + memunmap(((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); + memunmap(((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"); diff --git a/drivers/mtd/devices/spear_smi.c b/drivers/mtd/devices/spear_smi.c new file mode 100644 index 000000000..f58742486 --- /dev/null +++ b/drivers/mtd/devices/spear_smi.c @@ -0,0 +1,1116 @@ +/* + * SMI (Serial Memory Controller) device driver for Serial NOR Flash on + * SPEAr platform + * The serial nor interface is largely based on m25p80.c, however the SPI + * interface has been replaced by SMI. + * + * Copyright © 2010 STMicroelectronics. + * Ashish Priyadarshi + * Shiraz Hashim <shiraz.linux.kernel@gmail.com> + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/errno.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/ioport.h> +#include <linux/jiffies.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/param.h> +#include <linux/platform_device.h> +#include <linux/pm.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/partitions.h> +#include <linux/mtd/spear_smi.h> +#include <linux/mutex.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/wait.h> +#include <linux/of.h> +#include <linux/of_address.h> + +/* SMI clock rate */ +#define SMI_MAX_CLOCK_FREQ 50000000 /* 50 MHz */ + +/* MAX time out to safely come out of a erase or write busy conditions */ +#define SMI_PROBE_TIMEOUT (HZ / 10) +#define SMI_MAX_TIME_OUT (3 * HZ) + +/* timeout for command completion */ +#define SMI_CMD_TIMEOUT (HZ / 10) + +/* registers of smi */ +#define SMI_CR1 0x0 /* SMI control register 1 */ +#define SMI_CR2 0x4 /* SMI control register 2 */ +#define SMI_SR 0x8 /* SMI status register */ +#define SMI_TR 0xC /* SMI transmit register */ +#define SMI_RR 0x10 /* SMI receive register */ + +/* defines for control_reg 1 */ +#define BANK_EN (0xF << 0) /* enables all banks */ +#define DSEL_TIME (0x6 << 4) /* Deselect time 6 + 1 SMI_CK periods */ +#define SW_MODE (0x1 << 28) /* enables SW Mode */ +#define WB_MODE (0x1 << 29) /* Write Burst Mode */ +#define FAST_MODE (0x1 << 15) /* Fast Mode */ +#define HOLD1 (0x1 << 16) /* Clock Hold period selection */ + +/* defines for control_reg 2 */ +#define SEND (0x1 << 7) /* Send data */ +#define TFIE (0x1 << 8) /* Transmission Flag Interrupt Enable */ +#define WCIE (0x1 << 9) /* Write Complete Interrupt Enable */ +#define RD_STATUS_REG (0x1 << 10) /* reads status reg */ +#define WE (0x1 << 11) /* Write Enable */ + +#define TX_LEN_SHIFT 0 +#define RX_LEN_SHIFT 4 +#define BANK_SHIFT 12 + +/* defines for status register */ +#define SR_WIP 0x1 /* Write in progress */ +#define SR_WEL 0x2 /* Write enable latch */ +#define SR_BP0 0x4 /* Block protect 0 */ +#define SR_BP1 0x8 /* Block protect 1 */ +#define SR_BP2 0x10 /* Block protect 2 */ +#define SR_SRWD 0x80 /* SR write protect */ +#define TFF 0x100 /* Transfer Finished Flag */ +#define WCF 0x200 /* Transfer Finished Flag */ +#define ERF1 0x400 /* Forbidden Write Request */ +#define ERF2 0x800 /* Forbidden Access */ + +#define WM_SHIFT 12 + +/* flash opcodes */ +#define OPCODE_RDID 0x9f /* Read JEDEC ID */ + +/* Flash Device Ids maintenance section */ + +/* data structure to maintain flash ids from different vendors */ +struct flash_device { + char *name; + u8 erase_cmd; + u32 device_id; + u32 pagesize; + unsigned long sectorsize; + unsigned long size_in_bytes; +}; + +#define FLASH_ID(n, es, id, psize, ssize, size) \ +{ \ + .name = n, \ + .erase_cmd = es, \ + .device_id = id, \ + .pagesize = psize, \ + .sectorsize = ssize, \ + .size_in_bytes = size \ +} + +static struct flash_device flash_devices[] = { + FLASH_ID("st m25p16" , 0xd8, 0x00152020, 0x100, 0x10000, 0x200000), + FLASH_ID("st m25p32" , 0xd8, 0x00162020, 0x100, 0x10000, 0x400000), + FLASH_ID("st m25p64" , 0xd8, 0x00172020, 0x100, 0x10000, 0x800000), + FLASH_ID("st m25p128" , 0xd8, 0x00182020, 0x100, 0x40000, 0x1000000), + FLASH_ID("st m25p05" , 0xd8, 0x00102020, 0x80 , 0x8000 , 0x10000), + FLASH_ID("st m25p10" , 0xd8, 0x00112020, 0x80 , 0x8000 , 0x20000), + FLASH_ID("st m25p20" , 0xd8, 0x00122020, 0x100, 0x10000, 0x40000), + FLASH_ID("st m25p40" , 0xd8, 0x00132020, 0x100, 0x10000, 0x80000), + FLASH_ID("st m25p80" , 0xd8, 0x00142020, 0x100, 0x10000, 0x100000), + FLASH_ID("st m45pe10" , 0xd8, 0x00114020, 0x100, 0x10000, 0x20000), + FLASH_ID("st m45pe20" , 0xd8, 0x00124020, 0x100, 0x10000, 0x40000), + FLASH_ID("st m45pe40" , 0xd8, 0x00134020, 0x100, 0x10000, 0x80000), + FLASH_ID("st m45pe80" , 0xd8, 0x00144020, 0x100, 0x10000, 0x100000), + FLASH_ID("sp s25fl004" , 0xd8, 0x00120201, 0x100, 0x10000, 0x80000), + FLASH_ID("sp s25fl008" , 0xd8, 0x00130201, 0x100, 0x10000, 0x100000), + FLASH_ID("sp s25fl016" , 0xd8, 0x00140201, 0x100, 0x10000, 0x200000), + FLASH_ID("sp s25fl032" , 0xd8, 0x00150201, 0x100, 0x10000, 0x400000), + FLASH_ID("sp s25fl064" , 0xd8, 0x00160201, 0x100, 0x10000, 0x800000), + FLASH_ID("atmel 25f512" , 0x52, 0x0065001F, 0x80 , 0x8000 , 0x10000), + FLASH_ID("atmel 25f1024" , 0x52, 0x0060001F, 0x100, 0x8000 , 0x20000), + FLASH_ID("atmel 25f2048" , 0x52, 0x0063001F, 0x100, 0x10000, 0x40000), + FLASH_ID("atmel 25f4096" , 0x52, 0x0064001F, 0x100, 0x10000, 0x80000), + FLASH_ID("atmel 25fs040" , 0xd7, 0x0004661F, 0x100, 0x10000, 0x80000), + FLASH_ID("mac 25l512" , 0xd8, 0x001020C2, 0x010, 0x10000, 0x10000), + FLASH_ID("mac 25l1005" , 0xd8, 0x001120C2, 0x010, 0x10000, 0x20000), + FLASH_ID("mac 25l2005" , 0xd8, 0x001220C2, 0x010, 0x10000, 0x40000), + FLASH_ID("mac 25l4005" , 0xd8, 0x001320C2, 0x010, 0x10000, 0x80000), + FLASH_ID("mac 25l4005a" , 0xd8, 0x001320C2, 0x010, 0x10000, 0x80000), + FLASH_ID("mac 25l8005" , 0xd8, 0x001420C2, 0x010, 0x10000, 0x100000), + FLASH_ID("mac 25l1605" , 0xd8, 0x001520C2, 0x100, 0x10000, 0x200000), + FLASH_ID("mac 25l1605a" , 0xd8, 0x001520C2, 0x010, 0x10000, 0x200000), + FLASH_ID("mac 25l3205" , 0xd8, 0x001620C2, 0x100, 0x10000, 0x400000), + FLASH_ID("mac 25l3205a" , 0xd8, 0x001620C2, 0x100, 0x10000, 0x400000), + FLASH_ID("mac 25l6405" , 0xd8, 0x001720C2, 0x100, 0x10000, 0x800000), +}; + +/* Define spear specific structures */ + +struct spear_snor_flash; + +/** + * struct spear_smi - Structure for SMI Device + * + * @clk: functional clock + * @status: current status register of SMI. + * @clk_rate: functional clock rate of SMI (default: SMI_MAX_CLOCK_FREQ) + * @lock: lock to prevent parallel access of SMI. + * @io_base: base address for registers of SMI. + * @pdev: platform device + * @cmd_complete: queue to wait for command completion of NOR-flash. + * @num_flashes: number of flashes actually present on board. + * @flash: separate structure for each Serial NOR-flash attached to SMI. + */ +struct spear_smi { + struct clk *clk; + u32 status; + unsigned long clk_rate; + struct mutex lock; + void __iomem *io_base; + struct platform_device *pdev; + wait_queue_head_t cmd_complete; + u32 num_flashes; + struct spear_snor_flash *flash[MAX_NUM_FLASH_CHIP]; +}; + +/** + * struct spear_snor_flash - Structure for Serial NOR Flash + * + * @bank: Bank number(0, 1, 2, 3) for each NOR-flash. + * @dev_id: Device ID of NOR-flash. + * @lock: lock to manage flash read, write and erase operations + * @mtd: MTD info for each NOR-flash. + * @num_parts: Total number of partition in each bank of NOR-flash. + * @parts: Partition info for each bank of NOR-flash. + * @page_size: Page size of NOR-flash. + * @base_addr: Base address of NOR-flash. + * @erase_cmd: erase command may vary on different flash types + * @fast_mode: flash supports read in fast mode + */ +struct spear_snor_flash { + u32 bank; + u32 dev_id; + struct mutex lock; + struct mtd_info mtd; + u32 num_parts; + struct mtd_partition *parts; + u32 page_size; + void __iomem *base_addr; + u8 erase_cmd; + u8 fast_mode; +}; + +static inline struct spear_snor_flash *get_flash_data(struct mtd_info *mtd) +{ + return container_of(mtd, struct spear_snor_flash, mtd); +} + +/** + * spear_smi_read_sr - Read status register of flash through SMI + * @dev: structure of SMI information. + * @bank: bank to which flash is connected + * + * This routine will return the status register of the flash chip present at the + * given bank. + */ +static int spear_smi_read_sr(struct spear_smi *dev, u32 bank) +{ + int ret; + u32 ctrlreg1; + + mutex_lock(&dev->lock); + dev->status = 0; /* Will be set in interrupt handler */ + + ctrlreg1 = readl(dev->io_base + SMI_CR1); + /* program smi in hw mode */ + writel(ctrlreg1 & ~(SW_MODE | WB_MODE), dev->io_base + SMI_CR1); + + /* performing a rsr instruction in hw mode */ + writel((bank << BANK_SHIFT) | RD_STATUS_REG | TFIE, + dev->io_base + SMI_CR2); + + /* wait for tff */ + ret = wait_event_interruptible_timeout(dev->cmd_complete, + dev->status & TFF, SMI_CMD_TIMEOUT); + + /* copy dev->status (lower 16 bits) in order to release lock */ + if (ret > 0) + ret = dev->status & 0xffff; + else if (ret == 0) + ret = -ETIMEDOUT; + + /* restore the ctrl regs state */ + writel(ctrlreg1, dev->io_base + SMI_CR1); + writel(0, dev->io_base + SMI_CR2); + mutex_unlock(&dev->lock); + + return ret; +} + +/** + * spear_smi_wait_till_ready - wait till flash is ready + * @dev: structure of SMI information. + * @bank: flash corresponding to this bank + * @timeout: timeout for busy wait condition + * + * This routine checks for WIP (write in progress) bit in Status register + * If successful the routine returns 0 else -EBUSY + */ +static int spear_smi_wait_till_ready(struct spear_smi *dev, u32 bank, + unsigned long timeout) +{ + unsigned long finish; + int status; + + finish = jiffies + timeout; + do { + status = spear_smi_read_sr(dev, bank); + if (status < 0) { + if (status == -ETIMEDOUT) + continue; /* try till finish */ + return status; + } else if (!(status & SR_WIP)) { + return 0; + } + + cond_resched(); + } while (!time_after_eq(jiffies, finish)); + + dev_err(&dev->pdev->dev, "smi controller is busy, timeout\n"); + return -EBUSY; +} + +/** + * spear_smi_int_handler - SMI Interrupt Handler. + * @irq: irq number + * @dev_id: structure of SMI device, embedded in dev_id. + * + * The handler clears all interrupt conditions and records the status in + * dev->status which is used by the driver later. + */ +static irqreturn_t spear_smi_int_handler(int irq, void *dev_id) +{ + u32 status = 0; + struct spear_smi *dev = dev_id; + + status = readl(dev->io_base + SMI_SR); + + if (unlikely(!status)) + return IRQ_NONE; + + /* clear all interrupt conditions */ + writel(0, dev->io_base + SMI_SR); + + /* copy the status register in dev->status */ + dev->status |= status; + + /* send the completion */ + wake_up_interruptible(&dev->cmd_complete); + + return IRQ_HANDLED; +} + +/** + * spear_smi_hw_init - initializes the smi controller. + * @dev: structure of smi device + * + * this routine initializes the smi controller wit the default values + */ +static void spear_smi_hw_init(struct spear_smi *dev) +{ + unsigned long rate = 0; + u32 prescale = 0; + u32 val; + + rate = clk_get_rate(dev->clk); + + /* functional clock of smi */ + prescale = DIV_ROUND_UP(rate, dev->clk_rate); + + /* + * setting the standard values, fast mode, prescaler for + * SMI_MAX_CLOCK_FREQ (50MHz) operation and bank enable + */ + val = HOLD1 | BANK_EN | DSEL_TIME | (prescale << 8); + + mutex_lock(&dev->lock); + /* clear all interrupt conditions */ + writel(0, dev->io_base + SMI_SR); + + writel(val, dev->io_base + SMI_CR1); + mutex_unlock(&dev->lock); +} + +/** + * get_flash_index - match chip id from a flash list. + * @flash_id: a valid nor flash chip id obtained from board. + * + * try to validate the chip id by matching from a list, if not found then simply + * returns negative. In case of success returns index in to the flash devices + * array. + */ +static int get_flash_index(u32 flash_id) +{ + int index; + + /* Matches chip-id to entire list of 'serial-nor flash' ids */ + for (index = 0; index < ARRAY_SIZE(flash_devices); index++) { + if (flash_devices[index].device_id == flash_id) + return index; + } + + /* Memory chip is not listed and not supported */ + return -ENODEV; +} + +/** + * spear_smi_write_enable - Enable the flash to do write operation + * @dev: structure of SMI device + * @bank: enable write for flash connected to this bank + * + * Set write enable latch with Write Enable command. + * Returns 0 on success. + */ +static int spear_smi_write_enable(struct spear_smi *dev, u32 bank) +{ + int ret; + u32 ctrlreg1; + + mutex_lock(&dev->lock); + dev->status = 0; /* Will be set in interrupt handler */ + + ctrlreg1 = readl(dev->io_base + SMI_CR1); + /* program smi in h/w mode */ + writel(ctrlreg1 & ~SW_MODE, dev->io_base + SMI_CR1); + + /* give the flash, write enable command */ + writel((bank << BANK_SHIFT) | WE | TFIE, dev->io_base + SMI_CR2); + + ret = wait_event_interruptible_timeout(dev->cmd_complete, + dev->status & TFF, SMI_CMD_TIMEOUT); + + /* restore the ctrl regs state */ + writel(ctrlreg1, dev->io_base + SMI_CR1); + writel(0, dev->io_base + SMI_CR2); + + if (ret == 0) { + ret = -EIO; + dev_err(&dev->pdev->dev, + "smi controller failed on write enable\n"); + } else if (ret > 0) { + /* check whether write mode status is set for required bank */ + if (dev->status & (1 << (bank + WM_SHIFT))) + ret = 0; + else { + dev_err(&dev->pdev->dev, "couldn't enable write\n"); + ret = -EIO; + } + } + + mutex_unlock(&dev->lock); + return ret; +} + +static inline u32 +get_sector_erase_cmd(struct spear_snor_flash *flash, u32 offset) +{ + u32 cmd; + u8 *x = (u8 *)&cmd; + + x[0] = flash->erase_cmd; + x[1] = offset >> 16; + x[2] = offset >> 8; + x[3] = offset; + + return cmd; +} + +/** + * spear_smi_erase_sector - erase one sector of flash + * @dev: structure of SMI information + * @command: erase command to be send + * @bank: bank to which this command needs to be send + * @bytes: size of command + * + * Erase one sector of flash memory at offset ``offset'' which is any + * address within the sector which should be erased. + * Returns 0 if successful, non-zero otherwise. + */ +static int spear_smi_erase_sector(struct spear_smi *dev, + u32 bank, u32 command, u32 bytes) +{ + u32 ctrlreg1 = 0; + int ret; + + ret = spear_smi_wait_till_ready(dev, bank, SMI_MAX_TIME_OUT); + if (ret) + return ret; + + ret = spear_smi_write_enable(dev, bank); + if (ret) + return ret; + + mutex_lock(&dev->lock); + + ctrlreg1 = readl(dev->io_base + SMI_CR1); + writel((ctrlreg1 | SW_MODE) & ~WB_MODE, dev->io_base + SMI_CR1); + + /* send command in sw mode */ + writel(command, dev->io_base + SMI_TR); + + writel((bank << BANK_SHIFT) | SEND | TFIE | (bytes << TX_LEN_SHIFT), + dev->io_base + SMI_CR2); + + ret = wait_event_interruptible_timeout(dev->cmd_complete, + dev->status & TFF, SMI_CMD_TIMEOUT); + + if (ret == 0) { + ret = -EIO; + dev_err(&dev->pdev->dev, "sector erase failed\n"); + } else if (ret > 0) + ret = 0; /* success */ + + /* restore ctrl regs */ + writel(ctrlreg1, dev->io_base + SMI_CR1); + writel(0, dev->io_base + SMI_CR2); + + mutex_unlock(&dev->lock); + return ret; +} + +/** + * spear_mtd_erase - perform flash erase operation as requested by user + * @mtd: Provides the memory characteristics + * @e_info: Provides the erase information + * + * Erase an address range on the flash chip. The address range may extend + * one or more erase sectors. Return an error is there is a problem erasing. + */ +static int spear_mtd_erase(struct mtd_info *mtd, struct erase_info *e_info) +{ + struct spear_snor_flash *flash = get_flash_data(mtd); + struct spear_smi *dev = mtd->priv; + u32 addr, command, bank; + int len, ret; + + if (!flash || !dev) + return -ENODEV; + + bank = flash->bank; + if (bank > dev->num_flashes - 1) { + dev_err(&dev->pdev->dev, "Invalid Bank Num"); + return -EINVAL; + } + + addr = e_info->addr; + len = e_info->len; + + mutex_lock(&flash->lock); + + /* now erase sectors in loop */ + while (len) { + command = get_sector_erase_cmd(flash, addr); + /* preparing the command for flash */ + ret = spear_smi_erase_sector(dev, bank, command, 4); + if (ret) { + mutex_unlock(&flash->lock); + return ret; + } + addr += mtd->erasesize; + len -= mtd->erasesize; + } + + mutex_unlock(&flash->lock); + + return 0; +} + +/** + * spear_mtd_read - performs flash read operation as requested by the user + * @mtd: MTD information of the memory bank + * @from: Address from which to start read + * @len: Number of bytes to be read + * @retlen: Fills the Number of bytes actually read + * @buf: Fills this after reading + * + * Read an address range from the flash chip. The address range + * may be any size provided it is within the physical boundaries. + * Returns 0 on success, non zero otherwise + */ +static int spear_mtd_read(struct mtd_info *mtd, loff_t from, size_t len, + size_t *retlen, u8 *buf) +{ + struct spear_snor_flash *flash = get_flash_data(mtd); + struct spear_smi *dev = mtd->priv; + void __iomem *src; + u32 ctrlreg1, val; + int ret; + + if (!flash || !dev) + return -ENODEV; + + if (flash->bank > dev->num_flashes - 1) { + dev_err(&dev->pdev->dev, "Invalid Bank Num"); + return -EINVAL; + } + + /* select address as per bank number */ + src = flash->base_addr + from; + + mutex_lock(&flash->lock); + + /* wait till previous write/erase is done. */ + ret = spear_smi_wait_till_ready(dev, flash->bank, SMI_MAX_TIME_OUT); + if (ret) { + mutex_unlock(&flash->lock); + return ret; + } + + mutex_lock(&dev->lock); + /* put smi in hw mode not wbt mode */ + ctrlreg1 = val = readl(dev->io_base + SMI_CR1); + val &= ~(SW_MODE | WB_MODE); + if (flash->fast_mode) + val |= FAST_MODE; + + writel(val, dev->io_base + SMI_CR1); + + memcpy_fromio(buf, src, len); + + /* restore ctrl reg1 */ + writel(ctrlreg1, dev->io_base + SMI_CR1); + mutex_unlock(&dev->lock); + + *retlen = len; + mutex_unlock(&flash->lock); + + return 0; +} + +/* + * The purpose of this function is to ensure a memcpy_toio() with byte writes + * only. Its structure is inspired from the ARM implementation of _memcpy_toio() + * which also does single byte writes but cannot be used here as this is just an + * implementation detail and not part of the API. Not mentioning the comment + * stating that _memcpy_toio() should be optimized. + */ +static void spear_smi_memcpy_toio_b(volatile void __iomem *dest, + const void *src, size_t len) +{ + const unsigned char *from = src; + + while (len) { + len--; + writeb(*from, dest); + from++; + dest++; + } +} + +static inline int spear_smi_cpy_toio(struct spear_smi *dev, u32 bank, + void __iomem *dest, const void *src, size_t len) +{ + int ret; + u32 ctrlreg1; + + /* wait until finished previous write command. */ + ret = spear_smi_wait_till_ready(dev, bank, SMI_MAX_TIME_OUT); + if (ret) + return ret; + + /* put smi in write enable */ + ret = spear_smi_write_enable(dev, bank); + if (ret) + return ret; + + /* put smi in hw, write burst mode */ + mutex_lock(&dev->lock); + + ctrlreg1 = readl(dev->io_base + SMI_CR1); + writel((ctrlreg1 | WB_MODE) & ~SW_MODE, dev->io_base + SMI_CR1); + + /* + * In Write Burst mode (WB_MODE), the specs states that writes must be: + * - incremental + * - of the same size + * The ARM implementation of memcpy_toio() will optimize the number of + * I/O by using as much 4-byte writes as possible, surrounded by + * 2-byte/1-byte access if: + * - the destination is not 4-byte aligned + * - the length is not a multiple of 4-byte. + * Avoid this alternance of write access size by using our own 'byte + * access' helper if at least one of the two conditions above is true. + */ + if (IS_ALIGNED(len, sizeof(u32)) && + IS_ALIGNED((uintptr_t)dest, sizeof(u32))) + memcpy_toio(dest, src, len); + else + spear_smi_memcpy_toio_b(dest, src, len); + + writel(ctrlreg1, dev->io_base + SMI_CR1); + + mutex_unlock(&dev->lock); + return 0; +} + +/** + * spear_mtd_write - performs write operation as requested by the user. + * @mtd: MTD information of the memory bank. + * @to: Address to write. + * @len: Number of bytes to be written. + * @retlen: Number of bytes actually wrote. + * @buf: Buffer from which the data to be taken. + * + * Write an address range to the flash chip. Data must be written in + * flash_page_size chunks. The address range may be any size provided + * it is within the physical boundaries. + * Returns 0 on success, non zero otherwise + */ +static int spear_mtd_write(struct mtd_info *mtd, loff_t to, size_t len, + size_t *retlen, const u8 *buf) +{ + struct spear_snor_flash *flash = get_flash_data(mtd); + struct spear_smi *dev = mtd->priv; + void __iomem *dest; + u32 page_offset, page_size; + int ret; + + if (!flash || !dev) + return -ENODEV; + + if (flash->bank > dev->num_flashes - 1) { + dev_err(&dev->pdev->dev, "Invalid Bank Num"); + return -EINVAL; + } + + /* select address as per bank number */ + dest = flash->base_addr + to; + mutex_lock(&flash->lock); + + page_offset = (u32)to % flash->page_size; + + /* do if all the bytes fit onto one page */ + if (page_offset + len <= flash->page_size) { + ret = spear_smi_cpy_toio(dev, flash->bank, dest, buf, len); + if (!ret) + *retlen += len; + } else { + u32 i; + + /* the size of data remaining on the first page */ + page_size = flash->page_size - page_offset; + + ret = spear_smi_cpy_toio(dev, flash->bank, dest, buf, + page_size); + if (ret) + goto err_write; + else + *retlen += page_size; + + /* write everything in pagesize chunks */ + for (i = page_size; i < len; i += page_size) { + page_size = len - i; + if (page_size > flash->page_size) + page_size = flash->page_size; + + ret = spear_smi_cpy_toio(dev, flash->bank, dest + i, + buf + i, page_size); + if (ret) + break; + else + *retlen += page_size; + } + } + +err_write: + mutex_unlock(&flash->lock); + + return ret; +} + +/** + * spear_smi_probe_flash - Detects the NOR Flash chip. + * @dev: structure of SMI information. + * @bank: bank on which flash must be probed + * + * This routine will check whether there exists a flash chip on a given memory + * bank ID. + * Return index of the probed flash in flash devices structure + */ +static int spear_smi_probe_flash(struct spear_smi *dev, u32 bank) +{ + int ret; + u32 val = 0; + + ret = spear_smi_wait_till_ready(dev, bank, SMI_PROBE_TIMEOUT); + if (ret) + return ret; + + mutex_lock(&dev->lock); + + dev->status = 0; /* Will be set in interrupt handler */ + /* put smi in sw mode */ + val = readl(dev->io_base + SMI_CR1); + writel(val | SW_MODE, dev->io_base + SMI_CR1); + + /* send readid command in sw mode */ + writel(OPCODE_RDID, dev->io_base + SMI_TR); + + val = (bank << BANK_SHIFT) | SEND | (1 << TX_LEN_SHIFT) | + (3 << RX_LEN_SHIFT) | TFIE; + writel(val, dev->io_base + SMI_CR2); + + /* wait for TFF */ + ret = wait_event_interruptible_timeout(dev->cmd_complete, + dev->status & TFF, SMI_CMD_TIMEOUT); + if (ret <= 0) { + ret = -ENODEV; + goto err_probe; + } + + /* get memory chip id */ + val = readl(dev->io_base + SMI_RR); + val &= 0x00ffffff; + ret = get_flash_index(val); + +err_probe: + /* clear sw mode */ + val = readl(dev->io_base + SMI_CR1); + writel(val & ~SW_MODE, dev->io_base + SMI_CR1); + + mutex_unlock(&dev->lock); + return ret; +} + + +#ifdef CONFIG_OF +static int spear_smi_probe_config_dt(struct platform_device *pdev, + struct device_node *np) +{ + struct spear_smi_plat_data *pdata = dev_get_platdata(&pdev->dev); + struct device_node *pp; + const __be32 *addr; + u32 val; + int len; + int i = 0; + + if (!np) + return -ENODEV; + + of_property_read_u32(np, "clock-rate", &val); + pdata->clk_rate = val; + + pdata->board_flash_info = devm_kzalloc(&pdev->dev, + sizeof(*pdata->board_flash_info), + GFP_KERNEL); + if (!pdata->board_flash_info) + return -ENOMEM; + + /* Fill structs for each subnode (flash device) */ + for_each_child_of_node(np, pp) { + pdata->np[i] = pp; + + /* Read base-addr and size from DT */ + addr = of_get_property(pp, "reg", &len); + pdata->board_flash_info->mem_base = be32_to_cpup(&addr[0]); + pdata->board_flash_info->size = be32_to_cpup(&addr[1]); + + if (of_get_property(pp, "st,smi-fast-mode", NULL)) + pdata->board_flash_info->fast_mode = 1; + + i++; + } + + pdata->num_flashes = i; + + return 0; +} +#else +static int spear_smi_probe_config_dt(struct platform_device *pdev, + struct device_node *np) +{ + return -ENOSYS; +} +#endif + +static int spear_smi_setup_banks(struct platform_device *pdev, + u32 bank, struct device_node *np) +{ + struct spear_smi *dev = platform_get_drvdata(pdev); + struct spear_smi_flash_info *flash_info; + struct spear_smi_plat_data *pdata; + struct spear_snor_flash *flash; + struct mtd_partition *parts = NULL; + int count = 0; + int flash_index; + int ret = 0; + + pdata = dev_get_platdata(&pdev->dev); + if (bank > pdata->num_flashes - 1) + return -EINVAL; + + flash_info = &pdata->board_flash_info[bank]; + if (!flash_info) + return -ENODEV; + + flash = devm_kzalloc(&pdev->dev, sizeof(*flash), GFP_ATOMIC); + if (!flash) + return -ENOMEM; + flash->bank = bank; + flash->fast_mode = flash_info->fast_mode ? 1 : 0; + mutex_init(&flash->lock); + + /* verify whether nor flash is really present on board */ + flash_index = spear_smi_probe_flash(dev, bank); + if (flash_index < 0) { + dev_info(&dev->pdev->dev, "smi-nor%d not found\n", bank); + return flash_index; + } + /* map the memory for nor flash chip */ + flash->base_addr = devm_ioremap(&pdev->dev, flash_info->mem_base, + flash_info->size); + if (!flash->base_addr) + return -EIO; + + dev->flash[bank] = flash; + flash->mtd.priv = dev; + + if (flash_info->name) + flash->mtd.name = flash_info->name; + else + flash->mtd.name = flash_devices[flash_index].name; + + flash->mtd.dev.parent = &pdev->dev; + mtd_set_of_node(&flash->mtd, np); + flash->mtd.type = MTD_NORFLASH; + flash->mtd.writesize = 1; + flash->mtd.flags = MTD_CAP_NORFLASH; + flash->mtd.size = flash_info->size; + flash->mtd.erasesize = flash_devices[flash_index].sectorsize; + flash->page_size = flash_devices[flash_index].pagesize; + flash->mtd.writebufsize = flash->page_size; + flash->erase_cmd = flash_devices[flash_index].erase_cmd; + flash->mtd._erase = spear_mtd_erase; + flash->mtd._read = spear_mtd_read; + flash->mtd._write = spear_mtd_write; + flash->dev_id = flash_devices[flash_index].device_id; + + dev_info(&dev->pdev->dev, "mtd .name=%s .size=%llx(%lluM)\n", + flash->mtd.name, flash->mtd.size, + flash->mtd.size / (1024 * 1024)); + + dev_info(&dev->pdev->dev, ".erasesize = 0x%x(%uK)\n", + flash->mtd.erasesize, flash->mtd.erasesize / 1024); + +#ifndef CONFIG_OF + if (flash_info->partitions) { + parts = flash_info->partitions; + count = flash_info->nr_partitions; + } +#endif + + ret = mtd_device_register(&flash->mtd, parts, count); + if (ret) { + dev_err(&dev->pdev->dev, "Err MTD partition=%d\n", ret); + return ret; + } + + return 0; +} + +/** + * spear_smi_probe - Entry routine + * @pdev: platform device structure + * + * This is the first routine which gets invoked during booting and does all + * initialization/allocation work. The routine looks for available memory banks, + * and do proper init for any found one. + * Returns 0 on success, non zero otherwise + */ +static int spear_smi_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct spear_smi_plat_data *pdata = NULL; + struct spear_smi *dev; + struct resource *smi_base; + int irq, ret = 0; + int i; + + if (np) { + pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) { + ret = -ENOMEM; + goto err; + } + pdev->dev.platform_data = pdata; + ret = spear_smi_probe_config_dt(pdev, np); + if (ret) { + ret = -ENODEV; + dev_err(&pdev->dev, "no platform data\n"); + goto err; + } + } else { + pdata = dev_get_platdata(&pdev->dev); + if (!pdata) { + ret = -ENODEV; + dev_err(&pdev->dev, "no platform data\n"); + goto err; + } + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + ret = -ENODEV; + goto err; + } + + dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL); + if (!dev) { + ret = -ENOMEM; + goto err; + } + + smi_base = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + dev->io_base = devm_ioremap_resource(&pdev->dev, smi_base); + if (IS_ERR(dev->io_base)) { + ret = PTR_ERR(dev->io_base); + goto err; + } + + dev->pdev = pdev; + dev->clk_rate = pdata->clk_rate; + + if (dev->clk_rate > SMI_MAX_CLOCK_FREQ) + dev->clk_rate = SMI_MAX_CLOCK_FREQ; + + dev->num_flashes = pdata->num_flashes; + + if (dev->num_flashes > MAX_NUM_FLASH_CHIP) { + dev_err(&pdev->dev, "exceeding max number of flashes\n"); + dev->num_flashes = MAX_NUM_FLASH_CHIP; + } + + dev->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(dev->clk)) { + ret = PTR_ERR(dev->clk); + goto err; + } + + ret = clk_prepare_enable(dev->clk); + if (ret) + goto err; + + ret = devm_request_irq(&pdev->dev, irq, spear_smi_int_handler, 0, + pdev->name, dev); + if (ret) { + dev_err(&dev->pdev->dev, "SMI IRQ allocation failed\n"); + goto err_irq; + } + + mutex_init(&dev->lock); + init_waitqueue_head(&dev->cmd_complete); + spear_smi_hw_init(dev); + platform_set_drvdata(pdev, dev); + + /* loop for each serial nor-flash which is connected to smi */ + for (i = 0; i < dev->num_flashes; i++) { + ret = spear_smi_setup_banks(pdev, i, pdata->np[i]); + if (ret) { + dev_err(&dev->pdev->dev, "bank setup failed\n"); + goto err_irq; + } + } + + return 0; + +err_irq: + clk_disable_unprepare(dev->clk); +err: + return ret; +} + +/** + * spear_smi_remove - Exit routine + * @pdev: platform device structure + * + * free all allocations and delete the partitions. + */ +static int spear_smi_remove(struct platform_device *pdev) +{ + struct spear_smi *dev; + struct spear_snor_flash *flash; + int i; + + dev = platform_get_drvdata(pdev); + + /* clean up for all nor flash */ + for (i = 0; i < dev->num_flashes; i++) { + flash = dev->flash[i]; + if (!flash) + continue; + + /* clean up mtd stuff */ + WARN_ON(mtd_device_unregister(&flash->mtd)); + } + + clk_disable_unprepare(dev->clk); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int spear_smi_suspend(struct device *dev) +{ + struct spear_smi *sdev = dev_get_drvdata(dev); + + if (sdev && sdev->clk) + clk_disable_unprepare(sdev->clk); + + return 0; +} + +static int spear_smi_resume(struct device *dev) +{ + struct spear_smi *sdev = dev_get_drvdata(dev); + int ret = -EPERM; + + if (sdev && sdev->clk) + ret = clk_prepare_enable(sdev->clk); + + if (!ret) + spear_smi_hw_init(sdev); + return ret; +} +#endif + +static SIMPLE_DEV_PM_OPS(spear_smi_pm_ops, spear_smi_suspend, spear_smi_resume); + +#ifdef CONFIG_OF +static const struct of_device_id spear_smi_id_table[] = { + { .compatible = "st,spear600-smi" }, + {} +}; +MODULE_DEVICE_TABLE(of, spear_smi_id_table); +#endif + +static struct platform_driver spear_smi_driver = { + .driver = { + .name = "smi", + .bus = &platform_bus_type, + .of_match_table = of_match_ptr(spear_smi_id_table), + .pm = &spear_smi_pm_ops, + }, + .probe = spear_smi_probe, + .remove = spear_smi_remove, +}; +module_platform_driver(spear_smi_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Ashish Priyadarshi, Shiraz Hashim <shiraz.linux.kernel@gmail.com>"); +MODULE_DESCRIPTION("MTD SMI driver for serial nor flash chips"); diff --git a/drivers/mtd/devices/sst25l.c b/drivers/mtd/devices/sst25l.c new file mode 100644 index 000000000..8813994ce --- /dev/null +++ b/drivers/mtd/devices/sst25l.c @@ -0,0 +1,421 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * 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 + */ + +#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); + dev_err(&flash->spi->dev, "Erase failed\n"); + return err; + } + + addr += mtd->erasesize; + } + + mutex_unlock(&flash->lock); + + 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; + + flash->mtd.dev.parent = &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_register(&flash->mtd, data ? data->parts : NULL, + data ? data->nr_parts : 0); + if (ret) + return -ENODEV; + + return 0; +} + +static void sst25l_remove(struct spi_device *spi) +{ + struct sst25l_flash *flash = spi_get_drvdata(spi); + + WARN_ON(mtd_device_unregister(&flash->mtd)); +} + +static struct spi_driver sst25l_driver = { + .driver = { + .name = "sst25l", + }, + .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"); diff --git a/drivers/mtd/devices/st_spi_fsm.c b/drivers/mtd/devices/st_spi_fsm.c new file mode 100644 index 000000000..54861d889 --- /dev/null +++ b/drivers/mtd/devices/st_spi_fsm.c @@ -0,0 +1,2174 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * st_spi_fsm.c - ST Fast Sequence Mode (FSM) Serial Flash Controller + * + * Author: Angus Clark <angus.clark@st.com> + * + * Copyright (C) 2010-2014 STMicroelectronics Limited + * + * JEDEC probe based on drivers/mtd/devices/m25p80.c + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/regmap.h> +#include <linux/platform_device.h> +#include <linux/mfd/syscon.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/partitions.h> +#include <linux/mtd/spi-nor.h> +#include <linux/sched.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/of.h> +#include <linux/clk.h> + +#include "serial_flash_cmds.h" + +/* + * FSM SPI Controller Registers + */ +#define SPI_CLOCKDIV 0x0010 +#define SPI_MODESELECT 0x0018 +#define SPI_CONFIGDATA 0x0020 +#define SPI_STA_MODE_CHANGE 0x0028 +#define SPI_FAST_SEQ_TRANSFER_SIZE 0x0100 +#define SPI_FAST_SEQ_ADD1 0x0104 +#define SPI_FAST_SEQ_ADD2 0x0108 +#define SPI_FAST_SEQ_ADD_CFG 0x010c +#define SPI_FAST_SEQ_OPC1 0x0110 +#define SPI_FAST_SEQ_OPC2 0x0114 +#define SPI_FAST_SEQ_OPC3 0x0118 +#define SPI_FAST_SEQ_OPC4 0x011c +#define SPI_FAST_SEQ_OPC5 0x0120 +#define SPI_MODE_BITS 0x0124 +#define SPI_DUMMY_BITS 0x0128 +#define SPI_FAST_SEQ_FLASH_STA_DATA 0x012c +#define SPI_FAST_SEQ_1 0x0130 +#define SPI_FAST_SEQ_2 0x0134 +#define SPI_FAST_SEQ_3 0x0138 +#define SPI_FAST_SEQ_4 0x013c +#define SPI_FAST_SEQ_CFG 0x0140 +#define SPI_FAST_SEQ_STA 0x0144 +#define SPI_QUAD_BOOT_SEQ_INIT_1 0x0148 +#define SPI_QUAD_BOOT_SEQ_INIT_2 0x014c +#define SPI_QUAD_BOOT_READ_SEQ_1 0x0150 +#define SPI_QUAD_BOOT_READ_SEQ_2 0x0154 +#define SPI_PROGRAM_ERASE_TIME 0x0158 +#define SPI_MULT_PAGE_REPEAT_SEQ_1 0x015c +#define SPI_MULT_PAGE_REPEAT_SEQ_2 0x0160 +#define SPI_STATUS_WR_TIME_REG 0x0164 +#define SPI_FAST_SEQ_DATA_REG 0x0300 + +/* + * Register: SPI_MODESELECT + */ +#define SPI_MODESELECT_CONTIG 0x01 +#define SPI_MODESELECT_FASTREAD 0x02 +#define SPI_MODESELECT_DUALIO 0x04 +#define SPI_MODESELECT_FSM 0x08 +#define SPI_MODESELECT_QUADBOOT 0x10 + +/* + * Register: SPI_CONFIGDATA + */ +#define SPI_CFG_DEVICE_ST 0x1 +#define SPI_CFG_DEVICE_ATMEL 0x4 +#define SPI_CFG_MIN_CS_HIGH(x) (((x) & 0xfff) << 4) +#define SPI_CFG_CS_SETUPHOLD(x) (((x) & 0xff) << 16) +#define SPI_CFG_DATA_HOLD(x) (((x) & 0xff) << 24) + +#define SPI_CFG_DEFAULT_MIN_CS_HIGH SPI_CFG_MIN_CS_HIGH(0x0AA) +#define SPI_CFG_DEFAULT_CS_SETUPHOLD SPI_CFG_CS_SETUPHOLD(0xA0) +#define SPI_CFG_DEFAULT_DATA_HOLD SPI_CFG_DATA_HOLD(0x00) + +/* + * Register: SPI_FAST_SEQ_TRANSFER_SIZE + */ +#define TRANSFER_SIZE(x) ((x) * 8) + +/* + * Register: SPI_FAST_SEQ_ADD_CFG + */ +#define ADR_CFG_CYCLES_ADD1(x) ((x) << 0) +#define ADR_CFG_PADS_1_ADD1 (0x0 << 6) +#define ADR_CFG_PADS_2_ADD1 (0x1 << 6) +#define ADR_CFG_PADS_4_ADD1 (0x3 << 6) +#define ADR_CFG_CSDEASSERT_ADD1 (1 << 8) +#define ADR_CFG_CYCLES_ADD2(x) ((x) << (0+16)) +#define ADR_CFG_PADS_1_ADD2 (0x0 << (6+16)) +#define ADR_CFG_PADS_2_ADD2 (0x1 << (6+16)) +#define ADR_CFG_PADS_4_ADD2 (0x3 << (6+16)) +#define ADR_CFG_CSDEASSERT_ADD2 (1 << (8+16)) + +/* + * Register: SPI_FAST_SEQ_n + */ +#define SEQ_OPC_OPCODE(x) ((x) << 0) +#define SEQ_OPC_CYCLES(x) ((x) << 8) +#define SEQ_OPC_PADS_1 (0x0 << 14) +#define SEQ_OPC_PADS_2 (0x1 << 14) +#define SEQ_OPC_PADS_4 (0x3 << 14) +#define SEQ_OPC_CSDEASSERT (1 << 16) + +/* + * Register: SPI_FAST_SEQ_CFG + */ +#define SEQ_CFG_STARTSEQ (1 << 0) +#define SEQ_CFG_SWRESET (1 << 5) +#define SEQ_CFG_CSDEASSERT (1 << 6) +#define SEQ_CFG_READNOTWRITE (1 << 7) +#define SEQ_CFG_ERASE (1 << 8) +#define SEQ_CFG_PADS_1 (0x0 << 16) +#define SEQ_CFG_PADS_2 (0x1 << 16) +#define SEQ_CFG_PADS_4 (0x3 << 16) + +/* + * Register: SPI_MODE_BITS + */ +#define MODE_DATA(x) (x & 0xff) +#define MODE_CYCLES(x) ((x & 0x3f) << 16) +#define MODE_PADS_1 (0x0 << 22) +#define MODE_PADS_2 (0x1 << 22) +#define MODE_PADS_4 (0x3 << 22) +#define DUMMY_CSDEASSERT (1 << 24) + +/* + * Register: SPI_DUMMY_BITS + */ +#define DUMMY_CYCLES(x) ((x & 0x3f) << 16) +#define DUMMY_PADS_1 (0x0 << 22) +#define DUMMY_PADS_2 (0x1 << 22) +#define DUMMY_PADS_4 (0x3 << 22) +#define DUMMY_CSDEASSERT (1 << 24) + +/* + * Register: SPI_FAST_SEQ_FLASH_STA_DATA + */ +#define STA_DATA_BYTE1(x) ((x & 0xff) << 0) +#define STA_DATA_BYTE2(x) ((x & 0xff) << 8) +#define STA_PADS_1 (0x0 << 16) +#define STA_PADS_2 (0x1 << 16) +#define STA_PADS_4 (0x3 << 16) +#define STA_CSDEASSERT (0x1 << 20) +#define STA_RDNOTWR (0x1 << 21) + +/* + * FSM SPI Instruction Opcodes + */ +#define STFSM_OPC_CMD 0x1 +#define STFSM_OPC_ADD 0x2 +#define STFSM_OPC_STA 0x3 +#define STFSM_OPC_MODE 0x4 +#define STFSM_OPC_DUMMY 0x5 +#define STFSM_OPC_DATA 0x6 +#define STFSM_OPC_WAIT 0x7 +#define STFSM_OPC_JUMP 0x8 +#define STFSM_OPC_GOTO 0x9 +#define STFSM_OPC_STOP 0xF + +/* + * FSM SPI Instructions (== opcode + operand). + */ +#define STFSM_INSTR(cmd, op) ((cmd) | ((op) << 4)) + +#define STFSM_INST_CMD1 STFSM_INSTR(STFSM_OPC_CMD, 1) +#define STFSM_INST_CMD2 STFSM_INSTR(STFSM_OPC_CMD, 2) +#define STFSM_INST_CMD3 STFSM_INSTR(STFSM_OPC_CMD, 3) +#define STFSM_INST_CMD4 STFSM_INSTR(STFSM_OPC_CMD, 4) +#define STFSM_INST_CMD5 STFSM_INSTR(STFSM_OPC_CMD, 5) +#define STFSM_INST_ADD1 STFSM_INSTR(STFSM_OPC_ADD, 1) +#define STFSM_INST_ADD2 STFSM_INSTR(STFSM_OPC_ADD, 2) + +#define STFSM_INST_DATA_WRITE STFSM_INSTR(STFSM_OPC_DATA, 1) +#define STFSM_INST_DATA_READ STFSM_INSTR(STFSM_OPC_DATA, 2) + +#define STFSM_INST_STA_RD1 STFSM_INSTR(STFSM_OPC_STA, 0x1) +#define STFSM_INST_STA_WR1 STFSM_INSTR(STFSM_OPC_STA, 0x1) +#define STFSM_INST_STA_RD2 STFSM_INSTR(STFSM_OPC_STA, 0x2) +#define STFSM_INST_STA_WR1_2 STFSM_INSTR(STFSM_OPC_STA, 0x3) + +#define STFSM_INST_MODE STFSM_INSTR(STFSM_OPC_MODE, 0) +#define STFSM_INST_DUMMY STFSM_INSTR(STFSM_OPC_DUMMY, 0) +#define STFSM_INST_WAIT STFSM_INSTR(STFSM_OPC_WAIT, 0) +#define STFSM_INST_STOP STFSM_INSTR(STFSM_OPC_STOP, 0) + +#define STFSM_DEFAULT_EMI_FREQ 100000000UL /* 100 MHz */ +#define STFSM_DEFAULT_WR_TIME (STFSM_DEFAULT_EMI_FREQ * (15/1000)) /* 15ms */ + +#define STFSM_FLASH_SAFE_FREQ 10000000UL /* 10 MHz */ + +#define STFSM_MAX_WAIT_SEQ_MS 1000 /* FSM execution time */ + +/* S25FLxxxS commands */ +#define S25FL_CMD_WRITE4_1_1_4 0x34 +#define S25FL_CMD_SE4 0xdc +#define S25FL_CMD_CLSR 0x30 +#define S25FL_CMD_DYBWR 0xe1 +#define S25FL_CMD_DYBRD 0xe0 +#define S25FL_CMD_WRITE4 0x12 /* Note, opcode clashes with + * 'SPINOR_OP_WRITE_1_4_4' + * as found on N25Qxxx devices! */ + +/* Status register */ +#define FLASH_STATUS_BUSY 0x01 +#define FLASH_STATUS_WEL 0x02 +#define FLASH_STATUS_BP0 0x04 +#define FLASH_STATUS_BP1 0x08 +#define FLASH_STATUS_BP2 0x10 +#define FLASH_STATUS_SRWP0 0x80 +#define FLASH_STATUS_TIMEOUT 0xff +/* S25FL Error Flags */ +#define S25FL_STATUS_E_ERR 0x20 +#define S25FL_STATUS_P_ERR 0x40 + +#define N25Q_CMD_WRVCR 0x81 +#define N25Q_CMD_RDVCR 0x85 +#define N25Q_CMD_RDVECR 0x65 +#define N25Q_CMD_RDNVCR 0xb5 +#define N25Q_CMD_WRNVCR 0xb1 + +#define FLASH_PAGESIZE 256 /* In Bytes */ +#define FLASH_PAGESIZE_32 (FLASH_PAGESIZE / 4) /* In uint32_t */ +#define FLASH_MAX_BUSY_WAIT (300 * HZ) /* Maximum 'CHIPERASE' time */ + +/* + * Flags to tweak operation of default read/write/erase routines + */ +#define CFG_READ_TOGGLE_32BIT_ADDR 0x00000001 +#define CFG_WRITE_TOGGLE_32BIT_ADDR 0x00000002 +#define CFG_ERASESEC_TOGGLE_32BIT_ADDR 0x00000008 +#define CFG_S25FL_CHECK_ERROR_FLAGS 0x00000010 + +struct stfsm_seq { + uint32_t data_size; + uint32_t addr1; + uint32_t addr2; + uint32_t addr_cfg; + uint32_t seq_opc[5]; + uint32_t mode; + uint32_t dummy; + uint32_t status; + uint8_t seq[16]; + uint32_t seq_cfg; +} __packed __aligned(4); + +struct stfsm { + struct device *dev; + void __iomem *base; + struct mtd_info mtd; + struct mutex lock; + struct flash_info *info; + struct clk *clk; + + uint32_t configuration; + uint32_t fifo_dir_delay; + bool booted_from_spi; + bool reset_signal; + bool reset_por; + + struct stfsm_seq stfsm_seq_read; + struct stfsm_seq stfsm_seq_write; + struct stfsm_seq stfsm_seq_en_32bit_addr; +}; + +/* Parameters to configure a READ or WRITE FSM sequence */ +struct seq_rw_config { + uint32_t flags; /* flags to support config */ + uint8_t cmd; /* FLASH command */ + int write; /* Write Sequence */ + uint8_t addr_pads; /* No. of addr pads (MODE & DUMMY) */ + uint8_t data_pads; /* No. of data pads */ + uint8_t mode_data; /* MODE data */ + uint8_t mode_cycles; /* No. of MODE cycles */ + uint8_t dummy_cycles; /* No. of DUMMY cycles */ +}; + +/* SPI Flash Device Table */ +struct flash_info { + char *name; + /* + * JEDEC id zero means "no ID" (most older chips); otherwise it has + * a high byte of zero plus three data bytes: the manufacturer id, + * then a two byte device id. + */ + u32 jedec_id; + u16 ext_id; + /* + * The size listed here is what works with SPINOR_OP_SE, which isn't + * necessarily called a "sector" by the vendor. + */ + unsigned sector_size; + u16 n_sectors; + u32 flags; + /* + * Note, where FAST_READ is supported, freq_max specifies the + * FAST_READ frequency, not the READ frequency. + */ + u32 max_freq; + int (*config)(struct stfsm *); +}; + +static int stfsm_n25q_config(struct stfsm *fsm); +static int stfsm_mx25_config(struct stfsm *fsm); +static int stfsm_s25fl_config(struct stfsm *fsm); +static int stfsm_w25q_config(struct stfsm *fsm); + +static struct flash_info flash_types[] = { + /* + * ST Microelectronics/Numonyx -- + * (newer production versions may have feature updates + * (eg faster operating frequency) + */ +#define M25P_FLAG (FLASH_FLAG_READ_WRITE | FLASH_FLAG_READ_FAST) + { "m25p40", 0x202013, 0, 64 * 1024, 8, M25P_FLAG, 25, NULL }, + { "m25p80", 0x202014, 0, 64 * 1024, 16, M25P_FLAG, 25, NULL }, + { "m25p16", 0x202015, 0, 64 * 1024, 32, M25P_FLAG, 25, NULL }, + { "m25p32", 0x202016, 0, 64 * 1024, 64, M25P_FLAG, 50, NULL }, + { "m25p64", 0x202017, 0, 64 * 1024, 128, M25P_FLAG, 50, NULL }, + { "m25p128", 0x202018, 0, 256 * 1024, 64, M25P_FLAG, 50, NULL }, + +#define M25PX_FLAG (FLASH_FLAG_READ_WRITE | \ + FLASH_FLAG_READ_FAST | \ + FLASH_FLAG_READ_1_1_2 | \ + FLASH_FLAG_WRITE_1_1_2) + { "m25px32", 0x207116, 0, 64 * 1024, 64, M25PX_FLAG, 75, NULL }, + { "m25px64", 0x207117, 0, 64 * 1024, 128, M25PX_FLAG, 75, NULL }, + + /* Macronix MX25xxx + * - Support for 'FLASH_FLAG_WRITE_1_4_4' is omitted for devices + * where operating frequency must be reduced. + */ +#define MX25_FLAG (FLASH_FLAG_READ_WRITE | \ + FLASH_FLAG_READ_FAST | \ + FLASH_FLAG_READ_1_1_2 | \ + FLASH_FLAG_READ_1_2_2 | \ + FLASH_FLAG_READ_1_1_4 | \ + FLASH_FLAG_SE_4K | \ + FLASH_FLAG_SE_32K) + { "mx25l3255e", 0xc29e16, 0, 64 * 1024, 64, + (MX25_FLAG | FLASH_FLAG_WRITE_1_4_4), 86, + stfsm_mx25_config}, + { "mx25l25635e", 0xc22019, 0, 64*1024, 512, + (MX25_FLAG | FLASH_FLAG_32BIT_ADDR | FLASH_FLAG_RESET), 70, + stfsm_mx25_config }, + { "mx25l25655e", 0xc22619, 0, 64*1024, 512, + (MX25_FLAG | FLASH_FLAG_32BIT_ADDR | FLASH_FLAG_RESET), 70, + stfsm_mx25_config}, + +#define N25Q_FLAG (FLASH_FLAG_READ_WRITE | \ + FLASH_FLAG_READ_FAST | \ + FLASH_FLAG_READ_1_1_2 | \ + FLASH_FLAG_READ_1_2_2 | \ + FLASH_FLAG_READ_1_1_4 | \ + FLASH_FLAG_READ_1_4_4 | \ + FLASH_FLAG_WRITE_1_1_2 | \ + FLASH_FLAG_WRITE_1_2_2 | \ + FLASH_FLAG_WRITE_1_1_4 | \ + FLASH_FLAG_WRITE_1_4_4) + { "n25q128", 0x20ba18, 0, 64 * 1024, 256, N25Q_FLAG, 108, + stfsm_n25q_config }, + { "n25q256", 0x20ba19, 0, 64 * 1024, 512, + N25Q_FLAG | FLASH_FLAG_32BIT_ADDR, 108, stfsm_n25q_config }, + + /* + * Spansion S25FLxxxP + * - 256KiB and 64KiB sector variants (identified by ext. JEDEC) + */ +#define S25FLXXXP_FLAG (FLASH_FLAG_READ_WRITE | \ + FLASH_FLAG_READ_1_1_2 | \ + FLASH_FLAG_READ_1_2_2 | \ + FLASH_FLAG_READ_1_1_4 | \ + FLASH_FLAG_READ_1_4_4 | \ + FLASH_FLAG_WRITE_1_1_4 | \ + FLASH_FLAG_READ_FAST) + { "s25fl032p", 0x010215, 0x4d00, 64 * 1024, 64, S25FLXXXP_FLAG, 80, + stfsm_s25fl_config}, + { "s25fl129p0", 0x012018, 0x4d00, 256 * 1024, 64, S25FLXXXP_FLAG, 80, + stfsm_s25fl_config }, + { "s25fl129p1", 0x012018, 0x4d01, 64 * 1024, 256, S25FLXXXP_FLAG, 80, + stfsm_s25fl_config }, + + /* + * Spansion S25FLxxxS + * - 256KiB and 64KiB sector variants (identified by ext. JEDEC) + * - RESET# signal supported by die but not bristled out on all + * package types. The package type is a function of board design, + * so this information is captured in the board's flags. + * - Supports 'DYB' sector protection. Depending on variant, sectors + * may default to locked state on power-on. + */ +#define S25FLXXXS_FLAG (S25FLXXXP_FLAG | \ + FLASH_FLAG_RESET | \ + FLASH_FLAG_DYB_LOCKING) + { "s25fl128s0", 0x012018, 0x0300, 256 * 1024, 64, S25FLXXXS_FLAG, 80, + stfsm_s25fl_config }, + { "s25fl128s1", 0x012018, 0x0301, 64 * 1024, 256, S25FLXXXS_FLAG, 80, + stfsm_s25fl_config }, + { "s25fl256s0", 0x010219, 0x4d00, 256 * 1024, 128, + S25FLXXXS_FLAG | FLASH_FLAG_32BIT_ADDR, 80, stfsm_s25fl_config }, + { "s25fl256s1", 0x010219, 0x4d01, 64 * 1024, 512, + S25FLXXXS_FLAG | FLASH_FLAG_32BIT_ADDR, 80, stfsm_s25fl_config }, + + /* Winbond -- w25x "blocks" are 64K, "sectors" are 4KiB */ +#define W25X_FLAG (FLASH_FLAG_READ_WRITE | \ + FLASH_FLAG_READ_FAST | \ + FLASH_FLAG_READ_1_1_2 | \ + FLASH_FLAG_WRITE_1_1_2) + { "w25x40", 0xef3013, 0, 64 * 1024, 8, W25X_FLAG, 75, NULL }, + { "w25x80", 0xef3014, 0, 64 * 1024, 16, W25X_FLAG, 75, NULL }, + { "w25x16", 0xef3015, 0, 64 * 1024, 32, W25X_FLAG, 75, NULL }, + { "w25x32", 0xef3016, 0, 64 * 1024, 64, W25X_FLAG, 75, NULL }, + { "w25x64", 0xef3017, 0, 64 * 1024, 128, W25X_FLAG, 75, NULL }, + + /* Winbond -- w25q "blocks" are 64K, "sectors" are 4KiB */ +#define W25Q_FLAG (FLASH_FLAG_READ_WRITE | \ + FLASH_FLAG_READ_FAST | \ + FLASH_FLAG_READ_1_1_2 | \ + FLASH_FLAG_READ_1_2_2 | \ + FLASH_FLAG_READ_1_1_4 | \ + FLASH_FLAG_READ_1_4_4 | \ + FLASH_FLAG_WRITE_1_1_4) + { "w25q80", 0xef4014, 0, 64 * 1024, 16, W25Q_FLAG, 80, + stfsm_w25q_config }, + { "w25q16", 0xef4015, 0, 64 * 1024, 32, W25Q_FLAG, 80, + stfsm_w25q_config }, + { "w25q32", 0xef4016, 0, 64 * 1024, 64, W25Q_FLAG, 80, + stfsm_w25q_config }, + { "w25q64", 0xef4017, 0, 64 * 1024, 128, W25Q_FLAG, 80, + stfsm_w25q_config }, + + /* Sentinel */ + { NULL, 0x000000, 0, 0, 0, 0, 0, NULL }, +}; + +/* + * FSM message sequence configurations: + * + * All configs are presented in order of preference + */ + +/* Default READ configurations, in order of preference */ +static struct seq_rw_config default_read_configs[] = { + {FLASH_FLAG_READ_1_4_4, SPINOR_OP_READ_1_4_4, 0, 4, 4, 0x00, 2, 4}, + {FLASH_FLAG_READ_1_1_4, SPINOR_OP_READ_1_1_4, 0, 1, 4, 0x00, 4, 0}, + {FLASH_FLAG_READ_1_2_2, SPINOR_OP_READ_1_2_2, 0, 2, 2, 0x00, 4, 0}, + {FLASH_FLAG_READ_1_1_2, SPINOR_OP_READ_1_1_2, 0, 1, 2, 0x00, 0, 8}, + {FLASH_FLAG_READ_FAST, SPINOR_OP_READ_FAST, 0, 1, 1, 0x00, 0, 8}, + {FLASH_FLAG_READ_WRITE, SPINOR_OP_READ, 0, 1, 1, 0x00, 0, 0}, + {0x00, 0, 0, 0, 0, 0x00, 0, 0}, +}; + +/* Default WRITE configurations */ +static struct seq_rw_config default_write_configs[] = { + {FLASH_FLAG_WRITE_1_4_4, SPINOR_OP_WRITE_1_4_4, 1, 4, 4, 0x00, 0, 0}, + {FLASH_FLAG_WRITE_1_1_4, SPINOR_OP_WRITE_1_1_4, 1, 1, 4, 0x00, 0, 0}, + {FLASH_FLAG_WRITE_1_2_2, SPINOR_OP_WRITE_1_2_2, 1, 2, 2, 0x00, 0, 0}, + {FLASH_FLAG_WRITE_1_1_2, SPINOR_OP_WRITE_1_1_2, 1, 1, 2, 0x00, 0, 0}, + {FLASH_FLAG_READ_WRITE, SPINOR_OP_WRITE, 1, 1, 1, 0x00, 0, 0}, + {0x00, 0, 0, 0, 0, 0x00, 0, 0}, +}; + +/* + * [N25Qxxx] Configuration + */ +#define N25Q_VCR_DUMMY_CYCLES(x) (((x) & 0xf) << 4) +#define N25Q_VCR_XIP_DISABLED ((uint8_t)0x1 << 3) +#define N25Q_VCR_WRAP_CONT 0x3 + +/* N25Q 3-byte Address READ configurations + * - 'FAST' variants configured for 8 dummy cycles. + * + * Note, the number of dummy cycles used for 'FAST' READ operations is + * configurable and would normally be tuned according to the READ command and + * operating frequency. However, this applies universally to all 'FAST' READ + * commands, including those used by the SPIBoot controller, and remains in + * force until the device is power-cycled. Since the SPIBoot controller is + * hard-wired to use 8 dummy cycles, we must configure the device to also use 8 + * cycles. + */ +static struct seq_rw_config n25q_read3_configs[] = { + {FLASH_FLAG_READ_1_4_4, SPINOR_OP_READ_1_4_4, 0, 4, 4, 0x00, 0, 8}, + {FLASH_FLAG_READ_1_1_4, SPINOR_OP_READ_1_1_4, 0, 1, 4, 0x00, 0, 8}, + {FLASH_FLAG_READ_1_2_2, SPINOR_OP_READ_1_2_2, 0, 2, 2, 0x00, 0, 8}, + {FLASH_FLAG_READ_1_1_2, SPINOR_OP_READ_1_1_2, 0, 1, 2, 0x00, 0, 8}, + {FLASH_FLAG_READ_FAST, SPINOR_OP_READ_FAST, 0, 1, 1, 0x00, 0, 8}, + {FLASH_FLAG_READ_WRITE, SPINOR_OP_READ, 0, 1, 1, 0x00, 0, 0}, + {0x00, 0, 0, 0, 0, 0x00, 0, 0}, +}; + +/* N25Q 4-byte Address READ configurations + * - use special 4-byte address READ commands (reduces overheads, and + * reduces risk of hitting watchdog reset issues). + * - 'FAST' variants configured for 8 dummy cycles (see note above.) + */ +static struct seq_rw_config n25q_read4_configs[] = { + {FLASH_FLAG_READ_1_4_4, SPINOR_OP_READ_1_4_4_4B, 0, 4, 4, 0x00, 0, 8}, + {FLASH_FLAG_READ_1_1_4, SPINOR_OP_READ_1_1_4_4B, 0, 1, 4, 0x00, 0, 8}, + {FLASH_FLAG_READ_1_2_2, SPINOR_OP_READ_1_2_2_4B, 0, 2, 2, 0x00, 0, 8}, + {FLASH_FLAG_READ_1_1_2, SPINOR_OP_READ_1_1_2_4B, 0, 1, 2, 0x00, 0, 8}, + {FLASH_FLAG_READ_FAST, SPINOR_OP_READ_FAST_4B, 0, 1, 1, 0x00, 0, 8}, + {FLASH_FLAG_READ_WRITE, SPINOR_OP_READ_4B, 0, 1, 1, 0x00, 0, 0}, + {0x00, 0, 0, 0, 0, 0x00, 0, 0}, +}; + +/* + * [MX25xxx] Configuration + */ +#define MX25_STATUS_QE (0x1 << 6) + +static int stfsm_mx25_en_32bit_addr_seq(struct stfsm_seq *seq) +{ + seq->seq_opc[0] = (SEQ_OPC_PADS_1 | + SEQ_OPC_CYCLES(8) | + SEQ_OPC_OPCODE(SPINOR_OP_EN4B) | + SEQ_OPC_CSDEASSERT); + + seq->seq[0] = STFSM_INST_CMD1; + seq->seq[1] = STFSM_INST_WAIT; + seq->seq[2] = STFSM_INST_STOP; + + seq->seq_cfg = (SEQ_CFG_PADS_1 | + SEQ_CFG_ERASE | + SEQ_CFG_READNOTWRITE | + SEQ_CFG_CSDEASSERT | + SEQ_CFG_STARTSEQ); + + return 0; +} + +/* + * [S25FLxxx] Configuration + */ +#define STFSM_S25FL_CONFIG_QE (0x1 << 1) + +/* + * S25FLxxxS devices provide three ways of supporting 32-bit addressing: Bank + * Register, Extended Address Modes, and a 32-bit address command set. The + * 32-bit address command set is used here, since it avoids any problems with + * entering a state that is incompatible with the SPIBoot Controller. + */ +static struct seq_rw_config stfsm_s25fl_read4_configs[] = { + {FLASH_FLAG_READ_1_4_4, SPINOR_OP_READ_1_4_4_4B, 0, 4, 4, 0x00, 2, 4}, + {FLASH_FLAG_READ_1_1_4, SPINOR_OP_READ_1_1_4_4B, 0, 1, 4, 0x00, 0, 8}, + {FLASH_FLAG_READ_1_2_2, SPINOR_OP_READ_1_2_2_4B, 0, 2, 2, 0x00, 4, 0}, + {FLASH_FLAG_READ_1_1_2, SPINOR_OP_READ_1_1_2_4B, 0, 1, 2, 0x00, 0, 8}, + {FLASH_FLAG_READ_FAST, SPINOR_OP_READ_FAST_4B, 0, 1, 1, 0x00, 0, 8}, + {FLASH_FLAG_READ_WRITE, SPINOR_OP_READ_4B, 0, 1, 1, 0x00, 0, 0}, + {0x00, 0, 0, 0, 0, 0x00, 0, 0}, +}; + +static struct seq_rw_config stfsm_s25fl_write4_configs[] = { + {FLASH_FLAG_WRITE_1_1_4, S25FL_CMD_WRITE4_1_1_4, 1, 1, 4, 0x00, 0, 0}, + {FLASH_FLAG_READ_WRITE, S25FL_CMD_WRITE4, 1, 1, 1, 0x00, 0, 0}, + {0x00, 0, 0, 0, 0, 0x00, 0, 0}, +}; + +/* + * [W25Qxxx] Configuration + */ +#define W25Q_STATUS_QE (0x1 << 1) + +static struct stfsm_seq stfsm_seq_read_jedec = { + .data_size = TRANSFER_SIZE(8), + .seq_opc[0] = (SEQ_OPC_PADS_1 | + SEQ_OPC_CYCLES(8) | + SEQ_OPC_OPCODE(SPINOR_OP_RDID)), + .seq = { + STFSM_INST_CMD1, + STFSM_INST_DATA_READ, + STFSM_INST_STOP, + }, + .seq_cfg = (SEQ_CFG_PADS_1 | + SEQ_CFG_READNOTWRITE | + SEQ_CFG_CSDEASSERT | + SEQ_CFG_STARTSEQ), +}; + +static struct stfsm_seq stfsm_seq_read_status_fifo = { + .data_size = TRANSFER_SIZE(4), + .seq_opc[0] = (SEQ_OPC_PADS_1 | + SEQ_OPC_CYCLES(8) | + SEQ_OPC_OPCODE(SPINOR_OP_RDSR)), + .seq = { + STFSM_INST_CMD1, + STFSM_INST_DATA_READ, + STFSM_INST_STOP, + }, + .seq_cfg = (SEQ_CFG_PADS_1 | + SEQ_CFG_READNOTWRITE | + SEQ_CFG_CSDEASSERT | + SEQ_CFG_STARTSEQ), +}; + +static struct stfsm_seq stfsm_seq_erase_sector = { + /* 'addr_cfg' configured during initialisation */ + .seq_opc = { + (SEQ_OPC_PADS_1 | SEQ_OPC_CYCLES(8) | + SEQ_OPC_OPCODE(SPINOR_OP_WREN) | SEQ_OPC_CSDEASSERT), + + (SEQ_OPC_PADS_1 | SEQ_OPC_CYCLES(8) | + SEQ_OPC_OPCODE(SPINOR_OP_SE)), + }, + .seq = { + STFSM_INST_CMD1, + STFSM_INST_CMD2, + STFSM_INST_ADD1, + STFSM_INST_ADD2, + STFSM_INST_STOP, + }, + .seq_cfg = (SEQ_CFG_PADS_1 | + SEQ_CFG_READNOTWRITE | + SEQ_CFG_CSDEASSERT | + SEQ_CFG_STARTSEQ), +}; + +static struct stfsm_seq stfsm_seq_erase_chip = { + .seq_opc = { + (SEQ_OPC_PADS_1 | SEQ_OPC_CYCLES(8) | + SEQ_OPC_OPCODE(SPINOR_OP_WREN) | SEQ_OPC_CSDEASSERT), + + (SEQ_OPC_PADS_1 | SEQ_OPC_CYCLES(8) | + SEQ_OPC_OPCODE(SPINOR_OP_CHIP_ERASE) | SEQ_OPC_CSDEASSERT), + }, + .seq = { + STFSM_INST_CMD1, + STFSM_INST_CMD2, + STFSM_INST_WAIT, + STFSM_INST_STOP, + }, + .seq_cfg = (SEQ_CFG_PADS_1 | + SEQ_CFG_ERASE | + SEQ_CFG_READNOTWRITE | + SEQ_CFG_CSDEASSERT | + SEQ_CFG_STARTSEQ), +}; + +static struct stfsm_seq stfsm_seq_write_status = { + .seq_opc[0] = (SEQ_OPC_PADS_1 | SEQ_OPC_CYCLES(8) | + SEQ_OPC_OPCODE(SPINOR_OP_WREN) | SEQ_OPC_CSDEASSERT), + .seq_opc[1] = (SEQ_OPC_PADS_1 | SEQ_OPC_CYCLES(8) | + SEQ_OPC_OPCODE(SPINOR_OP_WRSR)), + .seq = { + STFSM_INST_CMD1, + STFSM_INST_CMD2, + STFSM_INST_STA_WR1, + STFSM_INST_STOP, + }, + .seq_cfg = (SEQ_CFG_PADS_1 | + SEQ_CFG_READNOTWRITE | + SEQ_CFG_CSDEASSERT | + SEQ_CFG_STARTSEQ), +}; + +/* Dummy sequence to read one byte of data from flash into the FIFO */ +static const struct stfsm_seq stfsm_seq_load_fifo_byte = { + .data_size = TRANSFER_SIZE(1), + .seq_opc[0] = (SEQ_OPC_PADS_1 | + SEQ_OPC_CYCLES(8) | + SEQ_OPC_OPCODE(SPINOR_OP_RDID)), + .seq = { + STFSM_INST_CMD1, + STFSM_INST_DATA_READ, + STFSM_INST_STOP, + }, + .seq_cfg = (SEQ_CFG_PADS_1 | + SEQ_CFG_READNOTWRITE | + SEQ_CFG_CSDEASSERT | + SEQ_CFG_STARTSEQ), +}; + +static int stfsm_n25q_en_32bit_addr_seq(struct stfsm_seq *seq) +{ + seq->seq_opc[0] = (SEQ_OPC_PADS_1 | SEQ_OPC_CYCLES(8) | + SEQ_OPC_OPCODE(SPINOR_OP_EN4B)); + seq->seq_opc[1] = (SEQ_OPC_PADS_1 | SEQ_OPC_CYCLES(8) | + SEQ_OPC_OPCODE(SPINOR_OP_WREN) | + SEQ_OPC_CSDEASSERT); + + seq->seq[0] = STFSM_INST_CMD2; + seq->seq[1] = STFSM_INST_CMD1; + seq->seq[2] = STFSM_INST_WAIT; + seq->seq[3] = STFSM_INST_STOP; + + seq->seq_cfg = (SEQ_CFG_PADS_1 | + SEQ_CFG_ERASE | + SEQ_CFG_READNOTWRITE | + SEQ_CFG_CSDEASSERT | + SEQ_CFG_STARTSEQ); + + return 0; +} + +static inline int stfsm_is_idle(struct stfsm *fsm) +{ + return readl(fsm->base + SPI_FAST_SEQ_STA) & 0x10; +} + +static inline uint32_t stfsm_fifo_available(struct stfsm *fsm) +{ + return (readl(fsm->base + SPI_FAST_SEQ_STA) >> 5) & 0x7f; +} + +static inline void stfsm_load_seq(struct stfsm *fsm, + const struct stfsm_seq *seq) +{ + void __iomem *dst = fsm->base + SPI_FAST_SEQ_TRANSFER_SIZE; + const uint32_t *src = (const uint32_t *)seq; + int words = sizeof(*seq) / sizeof(*src); + + BUG_ON(!stfsm_is_idle(fsm)); + + while (words--) { + writel(*src, dst); + src++; + dst += 4; + } +} + +static void stfsm_wait_seq(struct stfsm *fsm) +{ + unsigned long deadline; + int timeout = 0; + + deadline = jiffies + msecs_to_jiffies(STFSM_MAX_WAIT_SEQ_MS); + + while (!timeout) { + if (time_after_eq(jiffies, deadline)) + timeout = 1; + + if (stfsm_is_idle(fsm)) + return; + + cond_resched(); + } + + dev_err(fsm->dev, "timeout on sequence completion\n"); +} + +static void stfsm_read_fifo(struct stfsm *fsm, uint32_t *buf, uint32_t size) +{ + uint32_t remaining = size >> 2; + uint32_t avail; + uint32_t words; + + dev_dbg(fsm->dev, "Reading %d bytes from FIFO\n", size); + + BUG_ON((((uintptr_t)buf) & 0x3) || (size & 0x3)); + + while (remaining) { + for (;;) { + avail = stfsm_fifo_available(fsm); + if (avail) + break; + udelay(1); + } + words = min(avail, remaining); + remaining -= words; + + readsl(fsm->base + SPI_FAST_SEQ_DATA_REG, buf, words); + buf += words; + } +} + +/* + * Clear the data FIFO + * + * Typically, this is only required during driver initialisation, where no + * assumptions can be made regarding the state of the FIFO. + * + * The process of clearing the FIFO is complicated by fact that while it is + * possible for the FIFO to contain an arbitrary number of bytes [1], the + * SPI_FAST_SEQ_STA register only reports the number of complete 32-bit words + * present. Furthermore, data can only be drained from the FIFO by reading + * complete 32-bit words. + * + * With this in mind, a two stage process is used to the clear the FIFO: + * + * 1. Read any complete 32-bit words from the FIFO, as reported by the + * SPI_FAST_SEQ_STA register. + * + * 2. Mop up any remaining bytes. At this point, it is not known if there + * are 0, 1, 2, or 3 bytes in the FIFO. To handle all cases, a dummy FSM + * sequence is used to load one byte at a time, until a complete 32-bit + * word is formed; at most, 4 bytes will need to be loaded. + * + * [1] It is theoretically possible for the FIFO to contain an arbitrary number + * of bits. However, since there are no known use-cases that leave + * incomplete bytes in the FIFO, only words and bytes are considered here. + */ +static void stfsm_clear_fifo(struct stfsm *fsm) +{ + const struct stfsm_seq *seq = &stfsm_seq_load_fifo_byte; + uint32_t words, i; + + /* 1. Clear any 32-bit words */ + words = stfsm_fifo_available(fsm); + if (words) { + for (i = 0; i < words; i++) + readl(fsm->base + SPI_FAST_SEQ_DATA_REG); + dev_dbg(fsm->dev, "cleared %d words from FIFO\n", words); + } + + /* + * 2. Clear any remaining bytes + * - Load the FIFO, one byte at a time, until a complete 32-bit word + * is available. + */ + for (i = 0, words = 0; i < 4 && !words; i++) { + stfsm_load_seq(fsm, seq); + stfsm_wait_seq(fsm); + words = stfsm_fifo_available(fsm); + } + + /* - A single word must be available now */ + if (words != 1) { + dev_err(fsm->dev, "failed to clear bytes from the data FIFO\n"); + return; + } + + /* - Read the 32-bit word */ + readl(fsm->base + SPI_FAST_SEQ_DATA_REG); + + dev_dbg(fsm->dev, "cleared %d byte(s) from the data FIFO\n", 4 - i); +} + +static int stfsm_write_fifo(struct stfsm *fsm, const uint32_t *buf, + uint32_t size) +{ + uint32_t words = size >> 2; + + dev_dbg(fsm->dev, "writing %d bytes to FIFO\n", size); + + BUG_ON((((uintptr_t)buf) & 0x3) || (size & 0x3)); + + writesl(fsm->base + SPI_FAST_SEQ_DATA_REG, buf, words); + + return size; +} + +static int stfsm_enter_32bit_addr(struct stfsm *fsm, int enter) +{ + struct stfsm_seq *seq = &fsm->stfsm_seq_en_32bit_addr; + uint32_t cmd = enter ? SPINOR_OP_EN4B : SPINOR_OP_EX4B; + + seq->seq_opc[0] = (SEQ_OPC_PADS_1 | + SEQ_OPC_CYCLES(8) | + SEQ_OPC_OPCODE(cmd) | + SEQ_OPC_CSDEASSERT); + + stfsm_load_seq(fsm, seq); + + stfsm_wait_seq(fsm); + + return 0; +} + +static uint8_t stfsm_wait_busy(struct stfsm *fsm) +{ + struct stfsm_seq *seq = &stfsm_seq_read_status_fifo; + unsigned long deadline; + uint32_t status; + int timeout = 0; + + /* Use RDRS1 */ + seq->seq_opc[0] = (SEQ_OPC_PADS_1 | + SEQ_OPC_CYCLES(8) | + SEQ_OPC_OPCODE(SPINOR_OP_RDSR)); + + /* Load read_status sequence */ + stfsm_load_seq(fsm, seq); + + /* + * Repeat until busy bit is deasserted, or timeout, or error (S25FLxxxS) + */ + deadline = jiffies + FLASH_MAX_BUSY_WAIT; + while (!timeout) { + if (time_after_eq(jiffies, deadline)) + timeout = 1; + + stfsm_wait_seq(fsm); + + stfsm_read_fifo(fsm, &status, 4); + + if ((status & FLASH_STATUS_BUSY) == 0) + return 0; + + if ((fsm->configuration & CFG_S25FL_CHECK_ERROR_FLAGS) && + ((status & S25FL_STATUS_P_ERR) || + (status & S25FL_STATUS_E_ERR))) + return (uint8_t)(status & 0xff); + + if (!timeout) + /* Restart */ + writel(seq->seq_cfg, fsm->base + SPI_FAST_SEQ_CFG); + + cond_resched(); + } + + dev_err(fsm->dev, "timeout on wait_busy\n"); + + return FLASH_STATUS_TIMEOUT; +} + +static int stfsm_read_status(struct stfsm *fsm, uint8_t cmd, + uint8_t *data, int bytes) +{ + struct stfsm_seq *seq = &stfsm_seq_read_status_fifo; + uint32_t tmp; + uint8_t *t = (uint8_t *)&tmp; + int i; + + dev_dbg(fsm->dev, "read 'status' register [0x%02x], %d byte(s)\n", + cmd, bytes); + + BUG_ON(bytes != 1 && bytes != 2); + + seq->seq_opc[0] = (SEQ_OPC_PADS_1 | SEQ_OPC_CYCLES(8) | + SEQ_OPC_OPCODE(cmd)); + + stfsm_load_seq(fsm, seq); + + stfsm_read_fifo(fsm, &tmp, 4); + + for (i = 0; i < bytes; i++) + data[i] = t[i]; + + stfsm_wait_seq(fsm); + + return 0; +} + +static int stfsm_write_status(struct stfsm *fsm, uint8_t cmd, + uint16_t data, int bytes, int wait_busy) +{ + struct stfsm_seq *seq = &stfsm_seq_write_status; + + dev_dbg(fsm->dev, + "write 'status' register [0x%02x], %d byte(s), 0x%04x\n" + " %s wait-busy\n", cmd, bytes, data, wait_busy ? "with" : "no"); + + BUG_ON(bytes != 1 && bytes != 2); + + seq->seq_opc[1] = (SEQ_OPC_PADS_1 | SEQ_OPC_CYCLES(8) | + SEQ_OPC_OPCODE(cmd)); + + seq->status = (uint32_t)data | STA_PADS_1 | STA_CSDEASSERT; + seq->seq[2] = (bytes == 1) ? STFSM_INST_STA_WR1 : STFSM_INST_STA_WR1_2; + + stfsm_load_seq(fsm, seq); + + stfsm_wait_seq(fsm); + + if (wait_busy) + stfsm_wait_busy(fsm); + + return 0; +} + +/* + * SoC reset on 'boot-from-spi' systems + * + * Certain modes of operation cause the Flash device to enter a particular state + * for a period of time (e.g. 'Erase Sector', 'Quad Enable', and 'Enter 32-bit + * Addr' commands). On boot-from-spi systems, it is important to consider what + * happens if a warm reset occurs during this period. The SPIBoot controller + * assumes that Flash device is in its default reset state, 24-bit address mode, + * and ready to accept commands. This can be achieved using some form of + * on-board logic/controller to force a device POR in response to a SoC-level + * reset or by making use of the device reset signal if available (limited + * number of devices only). + * + * Failure to take such precautions can cause problems following a warm reset. + * For some operations (e.g. ERASE), there is little that can be done. For + * other modes of operation (e.g. 32-bit addressing), options are often + * available that can help minimise the window in which a reset could cause a + * problem. + * + */ +static bool stfsm_can_handle_soc_reset(struct stfsm *fsm) +{ + /* Reset signal is available on the board and supported by the device */ + if (fsm->reset_signal && fsm->info->flags & FLASH_FLAG_RESET) + return true; + + /* Board-level logic forces a power-on-reset */ + if (fsm->reset_por) + return true; + + /* Reset is not properly handled and may result in failure to reboot */ + return false; +} + +/* Configure 'addr_cfg' according to addressing mode */ +static void stfsm_prepare_erasesec_seq(struct stfsm *fsm, + struct stfsm_seq *seq) +{ + int addr1_cycles = fsm->info->flags & FLASH_FLAG_32BIT_ADDR ? 16 : 8; + + seq->addr_cfg = (ADR_CFG_CYCLES_ADD1(addr1_cycles) | + ADR_CFG_PADS_1_ADD1 | + ADR_CFG_CYCLES_ADD2(16) | + ADR_CFG_PADS_1_ADD2 | + ADR_CFG_CSDEASSERT_ADD2); +} + +/* Search for preferred configuration based on available flags */ +static struct seq_rw_config * +stfsm_search_seq_rw_configs(struct stfsm *fsm, + struct seq_rw_config cfgs[]) +{ + struct seq_rw_config *config; + int flags = fsm->info->flags; + + for (config = cfgs; config->cmd != 0; config++) + if ((config->flags & flags) == config->flags) + return config; + + return NULL; +} + +/* Prepare a READ/WRITE sequence according to configuration parameters */ +static void stfsm_prepare_rw_seq(struct stfsm *fsm, + struct stfsm_seq *seq, + struct seq_rw_config *cfg) +{ + int addr1_cycles, addr2_cycles; + int i = 0; + + memset(seq, 0, sizeof(*seq)); + + /* Add READ/WRITE OPC */ + seq->seq_opc[i++] = (SEQ_OPC_PADS_1 | + SEQ_OPC_CYCLES(8) | + SEQ_OPC_OPCODE(cfg->cmd)); + + /* Add WREN OPC for a WRITE sequence */ + if (cfg->write) + seq->seq_opc[i++] = (SEQ_OPC_PADS_1 | + SEQ_OPC_CYCLES(8) | + SEQ_OPC_OPCODE(SPINOR_OP_WREN) | + SEQ_OPC_CSDEASSERT); + + /* Address configuration (24 or 32-bit addresses) */ + addr1_cycles = (fsm->info->flags & FLASH_FLAG_32BIT_ADDR) ? 16 : 8; + addr1_cycles /= cfg->addr_pads; + addr2_cycles = 16 / cfg->addr_pads; + seq->addr_cfg = ((addr1_cycles & 0x3f) << 0 | /* ADD1 cycles */ + (cfg->addr_pads - 1) << 6 | /* ADD1 pads */ + (addr2_cycles & 0x3f) << 16 | /* ADD2 cycles */ + ((cfg->addr_pads - 1) << 22)); /* ADD2 pads */ + + /* Data/Sequence configuration */ + seq->seq_cfg = ((cfg->data_pads - 1) << 16 | + SEQ_CFG_STARTSEQ | + SEQ_CFG_CSDEASSERT); + if (!cfg->write) + seq->seq_cfg |= SEQ_CFG_READNOTWRITE; + + /* Mode configuration (no. of pads taken from addr cfg) */ + seq->mode = ((cfg->mode_data & 0xff) << 0 | /* data */ + (cfg->mode_cycles & 0x3f) << 16 | /* cycles */ + (cfg->addr_pads - 1) << 22); /* pads */ + + /* Dummy configuration (no. of pads taken from addr cfg) */ + seq->dummy = ((cfg->dummy_cycles & 0x3f) << 16 | /* cycles */ + (cfg->addr_pads - 1) << 22); /* pads */ + + + /* Instruction sequence */ + i = 0; + if (cfg->write) + seq->seq[i++] = STFSM_INST_CMD2; + + seq->seq[i++] = STFSM_INST_CMD1; + + seq->seq[i++] = STFSM_INST_ADD1; + seq->seq[i++] = STFSM_INST_ADD2; + + if (cfg->mode_cycles) + seq->seq[i++] = STFSM_INST_MODE; + + if (cfg->dummy_cycles) + seq->seq[i++] = STFSM_INST_DUMMY; + + seq->seq[i++] = + cfg->write ? STFSM_INST_DATA_WRITE : STFSM_INST_DATA_READ; + seq->seq[i++] = STFSM_INST_STOP; +} + +static int stfsm_search_prepare_rw_seq(struct stfsm *fsm, + struct stfsm_seq *seq, + struct seq_rw_config *cfgs) +{ + struct seq_rw_config *config; + + config = stfsm_search_seq_rw_configs(fsm, cfgs); + if (!config) { + dev_err(fsm->dev, "failed to find suitable config\n"); + return -EINVAL; + } + + stfsm_prepare_rw_seq(fsm, seq, config); + + return 0; +} + +/* Prepare a READ/WRITE/ERASE 'default' sequences */ +static int stfsm_prepare_rwe_seqs_default(struct stfsm *fsm) +{ + uint32_t flags = fsm->info->flags; + int ret; + + /* Configure 'READ' sequence */ + ret = stfsm_search_prepare_rw_seq(fsm, &fsm->stfsm_seq_read, + default_read_configs); + if (ret) { + dev_err(fsm->dev, + "failed to prep READ sequence with flags [0x%08x]\n", + flags); + return ret; + } + + /* Configure 'WRITE' sequence */ + ret = stfsm_search_prepare_rw_seq(fsm, &fsm->stfsm_seq_write, + default_write_configs); + if (ret) { + dev_err(fsm->dev, + "failed to prep WRITE sequence with flags [0x%08x]\n", + flags); + return ret; + } + + /* Configure 'ERASE_SECTOR' sequence */ + stfsm_prepare_erasesec_seq(fsm, &stfsm_seq_erase_sector); + + return 0; +} + +static int stfsm_mx25_config(struct stfsm *fsm) +{ + uint32_t flags = fsm->info->flags; + uint32_t data_pads; + uint8_t sta; + int ret; + bool soc_reset; + + /* + * Use default READ/WRITE sequences + */ + ret = stfsm_prepare_rwe_seqs_default(fsm); + if (ret) + return ret; + + /* + * Configure 32-bit Address Support + */ + if (flags & FLASH_FLAG_32BIT_ADDR) { + /* Configure 'enter_32bitaddr' FSM sequence */ + stfsm_mx25_en_32bit_addr_seq(&fsm->stfsm_seq_en_32bit_addr); + + soc_reset = stfsm_can_handle_soc_reset(fsm); + if (soc_reset || !fsm->booted_from_spi) + /* If we can handle SoC resets, we enable 32-bit address + * mode pervasively */ + stfsm_enter_32bit_addr(fsm, 1); + + else + /* Else, enable/disable 32-bit addressing before/after + * each operation */ + fsm->configuration = (CFG_READ_TOGGLE_32BIT_ADDR | + CFG_WRITE_TOGGLE_32BIT_ADDR | + CFG_ERASESEC_TOGGLE_32BIT_ADDR); + } + + /* Check status of 'QE' bit, update if required. */ + stfsm_read_status(fsm, SPINOR_OP_RDSR, &sta, 1); + data_pads = ((fsm->stfsm_seq_read.seq_cfg >> 16) & 0x3) + 1; + if (data_pads == 4) { + if (!(sta & MX25_STATUS_QE)) { + /* Set 'QE' */ + sta |= MX25_STATUS_QE; + + stfsm_write_status(fsm, SPINOR_OP_WRSR, sta, 1, 1); + } + } else { + if (sta & MX25_STATUS_QE) { + /* Clear 'QE' */ + sta &= ~MX25_STATUS_QE; + + stfsm_write_status(fsm, SPINOR_OP_WRSR, sta, 1, 1); + } + } + + return 0; +} + +static int stfsm_n25q_config(struct stfsm *fsm) +{ + uint32_t flags = fsm->info->flags; + uint8_t vcr; + int ret = 0; + bool soc_reset; + + /* Configure 'READ' sequence */ + if (flags & FLASH_FLAG_32BIT_ADDR) + ret = stfsm_search_prepare_rw_seq(fsm, &fsm->stfsm_seq_read, + n25q_read4_configs); + else + ret = stfsm_search_prepare_rw_seq(fsm, &fsm->stfsm_seq_read, + n25q_read3_configs); + if (ret) { + dev_err(fsm->dev, + "failed to prepare READ sequence with flags [0x%08x]\n", + flags); + return ret; + } + + /* Configure 'WRITE' sequence (default configs) */ + ret = stfsm_search_prepare_rw_seq(fsm, &fsm->stfsm_seq_write, + default_write_configs); + if (ret) { + dev_err(fsm->dev, + "preparing WRITE sequence using flags [0x%08x] failed\n", + flags); + return ret; + } + + /* * Configure 'ERASE_SECTOR' sequence */ + stfsm_prepare_erasesec_seq(fsm, &stfsm_seq_erase_sector); + + /* Configure 32-bit address support */ + if (flags & FLASH_FLAG_32BIT_ADDR) { + stfsm_n25q_en_32bit_addr_seq(&fsm->stfsm_seq_en_32bit_addr); + + soc_reset = stfsm_can_handle_soc_reset(fsm); + if (soc_reset || !fsm->booted_from_spi) { + /* + * If we can handle SoC resets, we enable 32-bit + * address mode pervasively + */ + stfsm_enter_32bit_addr(fsm, 1); + } else { + /* + * If not, enable/disable for WRITE and ERASE + * operations (READ uses special commands) + */ + fsm->configuration = (CFG_WRITE_TOGGLE_32BIT_ADDR | + CFG_ERASESEC_TOGGLE_32BIT_ADDR); + } + } + + /* + * Configure device to use 8 dummy cycles + */ + vcr = (N25Q_VCR_DUMMY_CYCLES(8) | N25Q_VCR_XIP_DISABLED | + N25Q_VCR_WRAP_CONT); + stfsm_write_status(fsm, N25Q_CMD_WRVCR, vcr, 1, 0); + + return 0; +} + +static void stfsm_s25fl_prepare_erasesec_seq_32(struct stfsm_seq *seq) +{ + seq->seq_opc[1] = (SEQ_OPC_PADS_1 | + SEQ_OPC_CYCLES(8) | + SEQ_OPC_OPCODE(S25FL_CMD_SE4)); + + seq->addr_cfg = (ADR_CFG_CYCLES_ADD1(16) | + ADR_CFG_PADS_1_ADD1 | + ADR_CFG_CYCLES_ADD2(16) | + ADR_CFG_PADS_1_ADD2 | + ADR_CFG_CSDEASSERT_ADD2); +} + +static void stfsm_s25fl_read_dyb(struct stfsm *fsm, uint32_t offs, uint8_t *dby) +{ + uint32_t tmp; + struct stfsm_seq seq = { + .data_size = TRANSFER_SIZE(4), + .seq_opc[0] = (SEQ_OPC_PADS_1 | + SEQ_OPC_CYCLES(8) | + SEQ_OPC_OPCODE(S25FL_CMD_DYBRD)), + .addr_cfg = (ADR_CFG_CYCLES_ADD1(16) | + ADR_CFG_PADS_1_ADD1 | + ADR_CFG_CYCLES_ADD2(16) | + ADR_CFG_PADS_1_ADD2), + .addr1 = (offs >> 16) & 0xffff, + .addr2 = offs & 0xffff, + .seq = { + STFSM_INST_CMD1, + STFSM_INST_ADD1, + STFSM_INST_ADD2, + STFSM_INST_DATA_READ, + STFSM_INST_STOP, + }, + .seq_cfg = (SEQ_CFG_PADS_1 | + SEQ_CFG_READNOTWRITE | + SEQ_CFG_CSDEASSERT | + SEQ_CFG_STARTSEQ), + }; + + stfsm_load_seq(fsm, &seq); + + stfsm_read_fifo(fsm, &tmp, 4); + + *dby = (uint8_t)(tmp >> 24); + + stfsm_wait_seq(fsm); +} + +static void stfsm_s25fl_write_dyb(struct stfsm *fsm, uint32_t offs, uint8_t dby) +{ + struct stfsm_seq seq = { + .seq_opc[0] = (SEQ_OPC_PADS_1 | SEQ_OPC_CYCLES(8) | + SEQ_OPC_OPCODE(SPINOR_OP_WREN) | + SEQ_OPC_CSDEASSERT), + .seq_opc[1] = (SEQ_OPC_PADS_1 | SEQ_OPC_CYCLES(8) | + SEQ_OPC_OPCODE(S25FL_CMD_DYBWR)), + .addr_cfg = (ADR_CFG_CYCLES_ADD1(16) | + ADR_CFG_PADS_1_ADD1 | + ADR_CFG_CYCLES_ADD2(16) | + ADR_CFG_PADS_1_ADD2), + .status = (uint32_t)dby | STA_PADS_1 | STA_CSDEASSERT, + .addr1 = (offs >> 16) & 0xffff, + .addr2 = offs & 0xffff, + .seq = { + STFSM_INST_CMD1, + STFSM_INST_CMD2, + STFSM_INST_ADD1, + STFSM_INST_ADD2, + STFSM_INST_STA_WR1, + STFSM_INST_STOP, + }, + .seq_cfg = (SEQ_CFG_PADS_1 | + SEQ_CFG_READNOTWRITE | + SEQ_CFG_CSDEASSERT | + SEQ_CFG_STARTSEQ), + }; + + stfsm_load_seq(fsm, &seq); + stfsm_wait_seq(fsm); + + stfsm_wait_busy(fsm); +} + +static int stfsm_s25fl_clear_status_reg(struct stfsm *fsm) +{ + struct stfsm_seq seq = { + .seq_opc[0] = (SEQ_OPC_PADS_1 | + SEQ_OPC_CYCLES(8) | + SEQ_OPC_OPCODE(S25FL_CMD_CLSR) | + SEQ_OPC_CSDEASSERT), + .seq_opc[1] = (SEQ_OPC_PADS_1 | + SEQ_OPC_CYCLES(8) | + SEQ_OPC_OPCODE(SPINOR_OP_WRDI) | + SEQ_OPC_CSDEASSERT), + .seq = { + STFSM_INST_CMD1, + STFSM_INST_CMD2, + STFSM_INST_WAIT, + STFSM_INST_STOP, + }, + .seq_cfg = (SEQ_CFG_PADS_1 | + SEQ_CFG_ERASE | + SEQ_CFG_READNOTWRITE | + SEQ_CFG_CSDEASSERT | + SEQ_CFG_STARTSEQ), + }; + + stfsm_load_seq(fsm, &seq); + + stfsm_wait_seq(fsm); + + return 0; +} + +static int stfsm_s25fl_config(struct stfsm *fsm) +{ + struct flash_info *info = fsm->info; + uint32_t flags = info->flags; + uint32_t data_pads; + uint32_t offs; + uint16_t sta_wr; + uint8_t sr1, cr1, dyb; + int update_sr = 0; + int ret; + + if (flags & FLASH_FLAG_32BIT_ADDR) { + /* + * Prepare Read/Write/Erase sequences according to S25FLxxx + * 32-bit address command set + */ + ret = stfsm_search_prepare_rw_seq(fsm, &fsm->stfsm_seq_read, + stfsm_s25fl_read4_configs); + if (ret) + return ret; + + ret = stfsm_search_prepare_rw_seq(fsm, &fsm->stfsm_seq_write, + stfsm_s25fl_write4_configs); + if (ret) + return ret; + + stfsm_s25fl_prepare_erasesec_seq_32(&stfsm_seq_erase_sector); + + } else { + /* Use default configurations for 24-bit addressing */ + ret = stfsm_prepare_rwe_seqs_default(fsm); + if (ret) + return ret; + } + + /* + * For devices that support 'DYB' sector locking, check lock status and + * unlock sectors if necessary (some variants power-on with sectors + * locked by default) + */ + if (flags & FLASH_FLAG_DYB_LOCKING) { + offs = 0; + for (offs = 0; offs < info->sector_size * info->n_sectors;) { + stfsm_s25fl_read_dyb(fsm, offs, &dyb); + if (dyb == 0x00) + stfsm_s25fl_write_dyb(fsm, offs, 0xff); + + /* Handle bottom/top 4KiB parameter sectors */ + if ((offs < info->sector_size * 2) || + (offs >= (info->sector_size - info->n_sectors * 4))) + offs += 0x1000; + else + offs += 0x10000; + } + } + + /* Check status of 'QE' bit, update if required. */ + stfsm_read_status(fsm, SPINOR_OP_RDCR, &cr1, 1); + data_pads = ((fsm->stfsm_seq_read.seq_cfg >> 16) & 0x3) + 1; + if (data_pads == 4) { + if (!(cr1 & STFSM_S25FL_CONFIG_QE)) { + /* Set 'QE' */ + cr1 |= STFSM_S25FL_CONFIG_QE; + + update_sr = 1; + } + } else { + if (cr1 & STFSM_S25FL_CONFIG_QE) { + /* Clear 'QE' */ + cr1 &= ~STFSM_S25FL_CONFIG_QE; + + update_sr = 1; + } + } + if (update_sr) { + stfsm_read_status(fsm, SPINOR_OP_RDSR, &sr1, 1); + sta_wr = ((uint16_t)cr1 << 8) | sr1; + stfsm_write_status(fsm, SPINOR_OP_WRSR, sta_wr, 2, 1); + } + + /* + * S25FLxxx devices support Program and Error error flags. + * Configure driver to check flags and clear if necessary. + */ + fsm->configuration |= CFG_S25FL_CHECK_ERROR_FLAGS; + + return 0; +} + +static int stfsm_w25q_config(struct stfsm *fsm) +{ + uint32_t data_pads; + uint8_t sr1, sr2; + uint16_t sr_wr; + int update_sr = 0; + int ret; + + ret = stfsm_prepare_rwe_seqs_default(fsm); + if (ret) + return ret; + + /* Check status of 'QE' bit, update if required. */ + stfsm_read_status(fsm, SPINOR_OP_RDCR, &sr2, 1); + data_pads = ((fsm->stfsm_seq_read.seq_cfg >> 16) & 0x3) + 1; + if (data_pads == 4) { + if (!(sr2 & W25Q_STATUS_QE)) { + /* Set 'QE' */ + sr2 |= W25Q_STATUS_QE; + update_sr = 1; + } + } else { + if (sr2 & W25Q_STATUS_QE) { + /* Clear 'QE' */ + sr2 &= ~W25Q_STATUS_QE; + update_sr = 1; + } + } + if (update_sr) { + /* Write status register */ + stfsm_read_status(fsm, SPINOR_OP_RDSR, &sr1, 1); + sr_wr = ((uint16_t)sr2 << 8) | sr1; + stfsm_write_status(fsm, SPINOR_OP_WRSR, sr_wr, 2, 1); + } + + return 0; +} + +static int stfsm_read(struct stfsm *fsm, uint8_t *buf, uint32_t size, + uint32_t offset) +{ + struct stfsm_seq *seq = &fsm->stfsm_seq_read; + uint32_t data_pads; + uint32_t read_mask; + uint32_t size_ub; + uint32_t size_lb; + uint32_t size_mop; + uint32_t tmp[4]; + uint32_t page_buf[FLASH_PAGESIZE_32]; + uint8_t *p; + + dev_dbg(fsm->dev, "reading %d bytes from 0x%08x\n", size, offset); + + /* Enter 32-bit address mode, if required */ + if (fsm->configuration & CFG_READ_TOGGLE_32BIT_ADDR) + stfsm_enter_32bit_addr(fsm, 1); + + /* Must read in multiples of 32 cycles (or 32*pads/8 Bytes) */ + data_pads = ((seq->seq_cfg >> 16) & 0x3) + 1; + read_mask = (data_pads << 2) - 1; + + /* Handle non-aligned buf */ + p = ((uintptr_t)buf & 0x3) ? (uint8_t *)page_buf : buf; + + /* Handle non-aligned size */ + size_ub = (size + read_mask) & ~read_mask; + size_lb = size & ~read_mask; + size_mop = size & read_mask; + + seq->data_size = TRANSFER_SIZE(size_ub); + seq->addr1 = (offset >> 16) & 0xffff; + seq->addr2 = offset & 0xffff; + + stfsm_load_seq(fsm, seq); + + if (size_lb) + stfsm_read_fifo(fsm, (uint32_t *)p, size_lb); + + if (size_mop) { + stfsm_read_fifo(fsm, tmp, read_mask + 1); + memcpy(p + size_lb, &tmp, size_mop); + } + + /* Handle non-aligned buf */ + if ((uintptr_t)buf & 0x3) + memcpy(buf, page_buf, size); + + /* Wait for sequence to finish */ + stfsm_wait_seq(fsm); + + stfsm_clear_fifo(fsm); + + /* Exit 32-bit address mode, if required */ + if (fsm->configuration & CFG_READ_TOGGLE_32BIT_ADDR) + stfsm_enter_32bit_addr(fsm, 0); + + return 0; +} + +static int stfsm_write(struct stfsm *fsm, const uint8_t *buf, + uint32_t size, uint32_t offset) +{ + struct stfsm_seq *seq = &fsm->stfsm_seq_write; + uint32_t data_pads; + uint32_t write_mask; + uint32_t size_ub; + uint32_t size_lb; + uint32_t size_mop; + uint32_t tmp[4]; + uint32_t i; + uint32_t page_buf[FLASH_PAGESIZE_32]; + uint8_t *t = (uint8_t *)&tmp; + const uint8_t *p; + int ret; + + dev_dbg(fsm->dev, "writing %d bytes to 0x%08x\n", size, offset); + + /* Enter 32-bit address mode, if required */ + if (fsm->configuration & CFG_WRITE_TOGGLE_32BIT_ADDR) + stfsm_enter_32bit_addr(fsm, 1); + + /* Must write in multiples of 32 cycles (or 32*pads/8 bytes) */ + data_pads = ((seq->seq_cfg >> 16) & 0x3) + 1; + write_mask = (data_pads << 2) - 1; + + /* Handle non-aligned buf */ + if ((uintptr_t)buf & 0x3) { + memcpy(page_buf, buf, size); + p = (uint8_t *)page_buf; + } else { + p = buf; + } + + /* Handle non-aligned size */ + size_ub = (size + write_mask) & ~write_mask; + size_lb = size & ~write_mask; + size_mop = size & write_mask; + + seq->data_size = TRANSFER_SIZE(size_ub); + seq->addr1 = (offset >> 16) & 0xffff; + seq->addr2 = offset & 0xffff; + + /* Need to set FIFO to write mode, before writing data to FIFO (see + * GNBvb79594) + */ + writel(0x00040000, fsm->base + SPI_FAST_SEQ_CFG); + + /* + * Before writing data to the FIFO, apply a small delay to allow a + * potential change of FIFO direction to complete. + */ + if (fsm->fifo_dir_delay == 0) + readl(fsm->base + SPI_FAST_SEQ_CFG); + else + udelay(fsm->fifo_dir_delay); + + + /* Write data to FIFO, before starting sequence (see GNBvd79593) */ + if (size_lb) { + stfsm_write_fifo(fsm, (uint32_t *)p, size_lb); + p += size_lb; + } + + /* Handle non-aligned size */ + if (size_mop) { + memset(t, 0xff, write_mask + 1); /* fill with 0xff's */ + for (i = 0; i < size_mop; i++) + t[i] = *p++; + + stfsm_write_fifo(fsm, tmp, write_mask + 1); + } + + /* Start sequence */ + stfsm_load_seq(fsm, seq); + + /* Wait for sequence to finish */ + stfsm_wait_seq(fsm); + + /* Wait for completion */ + ret = stfsm_wait_busy(fsm); + if (ret && fsm->configuration & CFG_S25FL_CHECK_ERROR_FLAGS) + stfsm_s25fl_clear_status_reg(fsm); + + /* Exit 32-bit address mode, if required */ + if (fsm->configuration & CFG_WRITE_TOGGLE_32BIT_ADDR) + stfsm_enter_32bit_addr(fsm, 0); + + return 0; +} + +/* + * Read an address range from the flash chip. The address range + * may be any size provided it is within the physical boundaries. + */ +static int stfsm_mtd_read(struct mtd_info *mtd, loff_t from, size_t len, + size_t *retlen, u_char *buf) +{ + struct stfsm *fsm = dev_get_drvdata(mtd->dev.parent); + uint32_t bytes; + + dev_dbg(fsm->dev, "%s from 0x%08x, len %zd\n", + __func__, (u32)from, len); + + mutex_lock(&fsm->lock); + + while (len > 0) { + bytes = min_t(size_t, len, FLASH_PAGESIZE); + + stfsm_read(fsm, buf, bytes, from); + + buf += bytes; + from += bytes; + len -= bytes; + + *retlen += bytes; + } + + mutex_unlock(&fsm->lock); + + return 0; +} + +static int stfsm_erase_sector(struct stfsm *fsm, uint32_t offset) +{ + struct stfsm_seq *seq = &stfsm_seq_erase_sector; + int ret; + + dev_dbg(fsm->dev, "erasing sector at 0x%08x\n", offset); + + /* Enter 32-bit address mode, if required */ + if (fsm->configuration & CFG_ERASESEC_TOGGLE_32BIT_ADDR) + stfsm_enter_32bit_addr(fsm, 1); + + seq->addr1 = (offset >> 16) & 0xffff; + seq->addr2 = offset & 0xffff; + + stfsm_load_seq(fsm, seq); + + stfsm_wait_seq(fsm); + + /* Wait for completion */ + ret = stfsm_wait_busy(fsm); + if (ret && fsm->configuration & CFG_S25FL_CHECK_ERROR_FLAGS) + stfsm_s25fl_clear_status_reg(fsm); + + /* Exit 32-bit address mode, if required */ + if (fsm->configuration & CFG_ERASESEC_TOGGLE_32BIT_ADDR) + stfsm_enter_32bit_addr(fsm, 0); + + return ret; +} + +static int stfsm_erase_chip(struct stfsm *fsm) +{ + const struct stfsm_seq *seq = &stfsm_seq_erase_chip; + + dev_dbg(fsm->dev, "erasing chip\n"); + + stfsm_load_seq(fsm, seq); + + stfsm_wait_seq(fsm); + + return stfsm_wait_busy(fsm); +} + +/* + * Write an address range to the flash chip. Data must be written in + * FLASH_PAGESIZE chunks. The address range may be any size provided + * it is within the physical boundaries. + */ +static int stfsm_mtd_write(struct mtd_info *mtd, loff_t to, size_t len, + size_t *retlen, const u_char *buf) +{ + struct stfsm *fsm = dev_get_drvdata(mtd->dev.parent); + + u32 page_offs; + u32 bytes; + uint8_t *b = (uint8_t *)buf; + int ret = 0; + + dev_dbg(fsm->dev, "%s to 0x%08x, len %zd\n", __func__, (u32)to, len); + + /* Offset within page */ + page_offs = to % FLASH_PAGESIZE; + + mutex_lock(&fsm->lock); + + while (len) { + /* Write up to page boundary */ + bytes = min_t(size_t, FLASH_PAGESIZE - page_offs, len); + + ret = stfsm_write(fsm, b, bytes, to); + if (ret) + goto out1; + + b += bytes; + len -= bytes; + to += bytes; + + /* We are now page-aligned */ + page_offs = 0; + + *retlen += bytes; + + } + +out1: + mutex_unlock(&fsm->lock); + + return ret; +} + +/* + * Erase an address range on the flash chip. The address range may extend + * one or more erase sectors. Return an error is there is a problem erasing. + */ +static int stfsm_mtd_erase(struct mtd_info *mtd, struct erase_info *instr) +{ + struct stfsm *fsm = dev_get_drvdata(mtd->dev.parent); + u32 addr, len; + int ret; + + dev_dbg(fsm->dev, "%s at 0x%llx, len %lld\n", __func__, + (long long)instr->addr, (long long)instr->len); + + addr = instr->addr; + len = instr->len; + + mutex_lock(&fsm->lock); + + /* Whole-chip erase? */ + if (len == mtd->size) { + ret = stfsm_erase_chip(fsm); + if (ret) + goto out1; + } else { + while (len) { + ret = stfsm_erase_sector(fsm, addr); + if (ret) + goto out1; + + addr += mtd->erasesize; + len -= mtd->erasesize; + } + } + + mutex_unlock(&fsm->lock); + + return 0; + +out1: + mutex_unlock(&fsm->lock); + + return ret; +} + +static void stfsm_read_jedec(struct stfsm *fsm, uint8_t *jedec) +{ + const struct stfsm_seq *seq = &stfsm_seq_read_jedec; + uint32_t tmp[2]; + + stfsm_load_seq(fsm, seq); + + stfsm_read_fifo(fsm, tmp, 8); + + memcpy(jedec, tmp, 5); + + stfsm_wait_seq(fsm); +} + +static struct flash_info *stfsm_jedec_probe(struct stfsm *fsm) +{ + struct flash_info *info; + u16 ext_jedec; + u32 jedec; + u8 id[5]; + + stfsm_read_jedec(fsm, id); + + jedec = id[0] << 16 | id[1] << 8 | id[2]; + /* + * 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. + */ + ext_jedec = id[3] << 8 | id[4]; + + dev_dbg(fsm->dev, "JEDEC = 0x%08x [%5ph]\n", jedec, id); + + for (info = flash_types; info->name; info++) { + if (info->jedec_id == jedec) { + if (info->ext_id && info->ext_id != ext_jedec) + continue; + return info; + } + } + dev_err(fsm->dev, "Unrecognized JEDEC id %06x\n", jedec); + + return NULL; +} + +static int stfsm_set_mode(struct stfsm *fsm, uint32_t mode) +{ + int ret, timeout = 10; + + /* Wait for controller to accept mode change */ + while (--timeout) { + ret = readl(fsm->base + SPI_STA_MODE_CHANGE); + if (ret & 0x1) + break; + udelay(1); + } + + if (!timeout) + return -EBUSY; + + writel(mode, fsm->base + SPI_MODESELECT); + + return 0; +} + +static void stfsm_set_freq(struct stfsm *fsm, uint32_t spi_freq) +{ + uint32_t emi_freq; + uint32_t clk_div; + + emi_freq = clk_get_rate(fsm->clk); + + /* + * Calculate clk_div - values between 2 and 128 + * Multiple of 2, rounded up + */ + clk_div = 2 * DIV_ROUND_UP(emi_freq, 2 * spi_freq); + if (clk_div < 2) + clk_div = 2; + else if (clk_div > 128) + clk_div = 128; + + /* + * Determine a suitable delay for the IP to complete a change of + * direction of the FIFO. The required delay is related to the clock + * divider used. The following heuristics are based on empirical tests, + * using a 100MHz EMI clock. + */ + if (clk_div <= 4) + fsm->fifo_dir_delay = 0; + else if (clk_div <= 10) + fsm->fifo_dir_delay = 1; + else + fsm->fifo_dir_delay = DIV_ROUND_UP(clk_div, 10); + + dev_dbg(fsm->dev, "emi_clk = %uHZ, spi_freq = %uHZ, clk_div = %u\n", + emi_freq, spi_freq, clk_div); + + writel(clk_div, fsm->base + SPI_CLOCKDIV); +} + +static int stfsm_init(struct stfsm *fsm) +{ + int ret; + + /* Perform a soft reset of the FSM controller */ + writel(SEQ_CFG_SWRESET, fsm->base + SPI_FAST_SEQ_CFG); + udelay(1); + writel(0, fsm->base + SPI_FAST_SEQ_CFG); + + /* Set clock to 'safe' frequency initially */ + stfsm_set_freq(fsm, STFSM_FLASH_SAFE_FREQ); + + /* Switch to FSM */ + ret = stfsm_set_mode(fsm, SPI_MODESELECT_FSM); + if (ret) + return ret; + + /* Set timing parameters */ + writel(SPI_CFG_DEVICE_ST | + SPI_CFG_DEFAULT_MIN_CS_HIGH | + SPI_CFG_DEFAULT_CS_SETUPHOLD | + SPI_CFG_DEFAULT_DATA_HOLD, + fsm->base + SPI_CONFIGDATA); + writel(STFSM_DEFAULT_WR_TIME, fsm->base + SPI_STATUS_WR_TIME_REG); + + /* + * Set the FSM 'WAIT' delay to the minimum workable value. Note, for + * our purposes, the WAIT instruction is used purely to achieve + * "sequence validity" rather than actually implement a delay. + */ + writel(0x00000001, fsm->base + SPI_PROGRAM_ERASE_TIME); + + /* Clear FIFO, just in case */ + stfsm_clear_fifo(fsm); + + return 0; +} + +static void stfsm_fetch_platform_configs(struct platform_device *pdev) +{ + struct stfsm *fsm = platform_get_drvdata(pdev); + struct device_node *np = pdev->dev.of_node; + struct regmap *regmap; + uint32_t boot_device_reg; + uint32_t boot_device_spi; + uint32_t boot_device; /* Value we read from *boot_device_reg */ + int ret; + + /* Booting from SPI NOR Flash is the default */ + fsm->booted_from_spi = true; + + regmap = syscon_regmap_lookup_by_phandle(np, "st,syscfg"); + if (IS_ERR(regmap)) + goto boot_device_fail; + + fsm->reset_signal = of_property_read_bool(np, "st,reset-signal"); + + fsm->reset_por = of_property_read_bool(np, "st,reset-por"); + + /* Where in the syscon the boot device information lives */ + ret = of_property_read_u32(np, "st,boot-device-reg", &boot_device_reg); + if (ret) + goto boot_device_fail; + + /* Boot device value when booted from SPI NOR */ + ret = of_property_read_u32(np, "st,boot-device-spi", &boot_device_spi); + if (ret) + goto boot_device_fail; + + ret = regmap_read(regmap, boot_device_reg, &boot_device); + if (ret) + goto boot_device_fail; + + if (boot_device != boot_device_spi) + fsm->booted_from_spi = false; + + return; + +boot_device_fail: + dev_warn(&pdev->dev, + "failed to fetch boot device, assuming boot from SPI\n"); +} + +static int stfsm_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct flash_info *info; + struct resource *res; + struct stfsm *fsm; + int ret; + + if (!np) { + dev_err(&pdev->dev, "No DT found\n"); + return -EINVAL; + } + + fsm = devm_kzalloc(&pdev->dev, sizeof(*fsm), GFP_KERNEL); + if (!fsm) + return -ENOMEM; + + fsm->dev = &pdev->dev; + + platform_set_drvdata(pdev, fsm); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "Resource not found\n"); + return -ENODEV; + } + + fsm->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(fsm->base)) { + dev_err(&pdev->dev, + "Failed to reserve memory region %pR\n", res); + return PTR_ERR(fsm->base); + } + + fsm->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(fsm->clk)) { + dev_err(fsm->dev, "Couldn't find EMI clock.\n"); + return PTR_ERR(fsm->clk); + } + + ret = clk_prepare_enable(fsm->clk); + if (ret) { + dev_err(fsm->dev, "Failed to enable EMI clock.\n"); + return ret; + } + + mutex_init(&fsm->lock); + + ret = stfsm_init(fsm); + if (ret) { + dev_err(&pdev->dev, "Failed to initialise FSM Controller\n"); + goto err_clk_unprepare; + } + + stfsm_fetch_platform_configs(pdev); + + /* Detect SPI FLASH device */ + info = stfsm_jedec_probe(fsm); + if (!info) { + ret = -ENODEV; + goto err_clk_unprepare; + } + fsm->info = info; + + /* Use device size to determine address width */ + if (info->sector_size * info->n_sectors > 0x1000000) + info->flags |= FLASH_FLAG_32BIT_ADDR; + + /* + * Configure READ/WRITE/ERASE sequences according to platform and + * device flags. + */ + if (info->config) + ret = info->config(fsm); + else + ret = stfsm_prepare_rwe_seqs_default(fsm); + if (ret) + goto err_clk_unprepare; + + fsm->mtd.name = info->name; + fsm->mtd.dev.parent = &pdev->dev; + mtd_set_of_node(&fsm->mtd, np); + fsm->mtd.type = MTD_NORFLASH; + fsm->mtd.writesize = 4; + fsm->mtd.writebufsize = fsm->mtd.writesize; + fsm->mtd.flags = MTD_CAP_NORFLASH; + fsm->mtd.size = info->sector_size * info->n_sectors; + fsm->mtd.erasesize = info->sector_size; + + fsm->mtd._read = stfsm_mtd_read; + fsm->mtd._write = stfsm_mtd_write; + fsm->mtd._erase = stfsm_mtd_erase; + + dev_info(&pdev->dev, + "Found serial flash device: %s\n" + " size = %llx (%lldMiB) erasesize = 0x%08x (%uKiB)\n", + info->name, + (long long)fsm->mtd.size, (long long)(fsm->mtd.size >> 20), + fsm->mtd.erasesize, (fsm->mtd.erasesize >> 10)); + + ret = mtd_device_register(&fsm->mtd, NULL, 0); + if (ret) { +err_clk_unprepare: + clk_disable_unprepare(fsm->clk); + } + + return ret; +} + +static int stfsm_remove(struct platform_device *pdev) +{ + struct stfsm *fsm = platform_get_drvdata(pdev); + + WARN_ON(mtd_device_unregister(&fsm->mtd)); + + clk_disable_unprepare(fsm->clk); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int stfsmfsm_suspend(struct device *dev) +{ + struct stfsm *fsm = dev_get_drvdata(dev); + + clk_disable_unprepare(fsm->clk); + + return 0; +} + +static int stfsmfsm_resume(struct device *dev) +{ + struct stfsm *fsm = dev_get_drvdata(dev); + + return clk_prepare_enable(fsm->clk); +} +#endif + +static SIMPLE_DEV_PM_OPS(stfsm_pm_ops, stfsmfsm_suspend, stfsmfsm_resume); + +static const struct of_device_id stfsm_match[] = { + { .compatible = "st,spi-fsm", }, + {}, +}; +MODULE_DEVICE_TABLE(of, stfsm_match); + +static struct platform_driver stfsm_driver = { + .probe = stfsm_probe, + .remove = stfsm_remove, + .driver = { + .name = "st-spi-fsm", + .of_match_table = stfsm_match, + .pm = &stfsm_pm_ops, + }, +}; +module_platform_driver(stfsm_driver); + +MODULE_AUTHOR("Angus Clark <angus.clark@st.com>"); +MODULE_DESCRIPTION("ST SPI FSM driver"); +MODULE_LICENSE("GPL"); |