mirror of https://gitee.com/openkylin/linux.git
This pull-request contains the following changes for MTD:
MTD core changes: - New Hyperbus framework - New _is_locked (concat) implementation - Various cleanups NAND core changes: - use longest matching pattern in ->exec_op() default parser - export NAND operation tracer - add flag to indicate panic_write in MTD - use kzalloc() instead of kmalloc() and memset() Raw NAND controller drivers changes: - brcmnand: * fix BCH ECC layout for large page NAND parts * fallback to detected ecc-strength, ecc-step-size * when oops in progress use pio and interrupt polling * code refactor code to introduce helper functions * add support for v7.3 controller - FSMC: * use nand_op_trace for operation tracing - GPMI: * move all driver code into single file * various cleanups (including dmaengine changes) * use runtime PM to manage clocks * implement exec_op - MTK: * correct low level time calculation of r/w cycle * improve data sampling timing for read cycle * add validity check for CE# pin setting * fix wrongly assigned OOB buffer pointer issue * re-license MTK NAND driver as Dual MIT/GPL - STM32: * manage the get_irq error case * increase DMA completion timeouts Raw NAND chips drivers changes: - Macronix: add read-retry support Onenand driver changes: - add support for 8Gb datasize chips - avoid fall-through warnings SPI-NAND changes: - define macros for page-read ops with three-byte addresses - add support for two-byte device IDs and then for GigaDevice GD5F1GQ4UFxxG - add initial support for Paragon PN26G0xA - handle the case where the last page read has bitflips SPI-NOR core changes: - add support for the mt25ql02g and w25q16jv flashes - print error in case of jedec read id fails - is25lp256: add post BFPT fix to correct the addr_width SPI NOR controller drivers changes: - intel-spi: Add support for Intel Elkhart Lake SPI serial flash - smt32: remove the driver as the driver was replaced by spi-stm32-qspi.c - cadence-quadspi: add reset control -----BEGIN PGP SIGNATURE----- iQEzBAABCgAdFiEE9HuaYnbmDhq/XIDIJWrqGEe9VoQFAl0qLr4ACgkQJWrqGEe9 VoQVCwf/ZCG5CldS3cs6B68kMJoZ/rJyJxBnxtIhffda2vw1KG/12o6XaDO9xA/R EwYrOTzlYZxzCZsNvWyHepG3Kj3d38CJ52ZqhavjpihwMlKKOgW/K39xuKWCrfxS sVMLz/UdrcsQfcPGAy7DSyqhzRAtupNxngCdEkIIMGFZWsv4uZfOFEGMrzUJ5RYN /okIyUE7Iz0dRq1/KXSl365V1MS8QP2eHFuHrUd38+kJ8TJnQjXX3Bmdul4aNTx+ HIIpykovoAn5BZ0YA4lJL90zVoDOWysARwHIAMDvJa8zS0wDTU16Tj2M6AQK+a4x hbIOOkeX0hTKpJvy7/khli5y1bn2mw== =L+tV -----END PGP SIGNATURE----- Merge tag 'mtd/for-5.3' of git://git.kernel.org/pub/scm/linux/kernel/git/mtd/linux Pull MTD updates from Miquel Raynal: "This contains the following changes for MTD: MTD core changes: - New Hyperbus framework - New _is_locked (concat) implementation - Various cleanups NAND core changes: - use longest matching pattern in ->exec_op() default parser - export NAND operation tracer - add flag to indicate panic_write in MTD - use kzalloc() instead of kmalloc() and memset() Raw NAND controller drivers changes: - brcmnand: - fix BCH ECC layout for large page NAND parts - fallback to detected ecc-strength, ecc-step-size - when oops in progress use pio and interrupt polling - code refactor code to introduce helper functions - add support for v7.3 controller - FSMC: - use nand_op_trace for operation tracing - GPMI: - move all driver code into single file - various cleanups (including dmaengine changes) - use runtime PM to manage clocks - implement exec_op - MTK: - correct low level time calculation of r/w cycle - improve data sampling timing for read cycle - add validity check for CE# pin setting - fix wrongly assigned OOB buffer pointer issue - re-license MTK NAND driver as Dual MIT/GPL - STM32: - manage the get_irq error case - increase DMA completion timeouts Raw NAND chips drivers changes: - Macronix: add read-retry support Onenand driver changes: - add support for 8Gb datasize chips - avoid fall-through warnings SPI-NAND changes: - define macros for page-read ops with three-byte addresses - add support for two-byte device IDs and then for GigaDevice GD5F1GQ4UFxxG - add initial support for Paragon PN26G0xA - handle the case where the last page read has bitflips SPI-NOR core changes: - add support for the mt25ql02g and w25q16jv flashes - print error in case of jedec read id fails - is25lp256: add post BFPT fix to correct the addr_width SPI NOR controller drivers changes: - intel-spi: Add support for Intel Elkhart Lake SPI serial flash - smt32: remove the driver as the driver was replaced by spi-stm32-qspi.c - cadence-quadspi: add reset control" * tag 'mtd/for-5.3' of git://git.kernel.org/pub/scm/linux/kernel/git/mtd/linux: (60 commits) mtd: concat: implement _is_locked mtd operation mtd: concat: refactor concat_lock/concat_unlock mtd: abi: do not use C++ style comments in uapi header mtd: afs: remove unneeded NULL check mtd: rawnand: stm32_fmc2: increase DMA completion timeouts mtd: rawnand: Use kzalloc() instead of kmalloc() and memset() mtd: hyperbus: Add driver for TI's HyperBus memory controller mtd: spinand: read returns badly if the last page has bitflips mtd: spinand: Add initial support for Paragon PN26G0xA mtd: rawnand: mtk: Re-license MTK NAND driver as Dual MIT/GPL mtd: rawnand: gpmi: remove double assignment to block_size dt-bindings: mtd: brcmnand: Add brcmnand, brcmnand-v7.3 support mtd: rawnand: brcmnand: Add support for v7.3 controller mtd: rawnand: brcmnand: Refactored code to introduce helper functions mtd: rawnand: brcmnand: When oops in progress use pio and interrupt polling mtd: Add flag to indicate panic_write mtd: rawnand: Add Macronix NAND read retry support mtd: onenand: Avoid fall-through warnings mtd: spinand: Add support for GigaDevice GD5F1GQ4UFxxG mtd: spinand: Add support for two-byte device IDs ...
This commit is contained in:
commit
3f06962273
|
@ -28,6 +28,7 @@ Required properties:
|
|||
brcm,brcmnand-v7.0
|
||||
brcm,brcmnand-v7.1
|
||||
brcm,brcmnand-v7.2
|
||||
brcm,brcmnand-v7.3
|
||||
brcm,brcmnand
|
||||
- reg : the register start and length for NAND register region.
|
||||
(optional) Flash DMA register range (if present)
|
||||
|
@ -101,10 +102,10 @@ Required properties:
|
|||
number (e.g., 0, 1, 2, etc.)
|
||||
- #address-cells : see partition.txt
|
||||
- #size-cells : see partition.txt
|
||||
- nand-ecc-strength : see nand-controller.yaml
|
||||
- nand-ecc-step-size : must be 512 or 1024. See nand-controller.yaml
|
||||
|
||||
Optional properties:
|
||||
- nand-ecc-strength : see nand-controller.yaml
|
||||
- nand-ecc-step-size : must be 512 or 1024. See nand-controller.yaml
|
||||
- nand-on-flash-bbt : boolean, to enable the on-flash BBT for this
|
||||
chip-select. See nand-controller.yaml
|
||||
- brcm,nand-oob-sector-size : integer, to denote the spare area sector size
|
||||
|
|
|
@ -35,6 +35,9 @@ custom properties:
|
|||
(qspi_n_ss_out).
|
||||
- cdns,tslch-ns : Delay in nanoseconds between setting qspi_n_ss_out low
|
||||
and first bit transfer.
|
||||
- resets : Must contain an entry for each entry in reset-names.
|
||||
See ../reset/reset.txt for details.
|
||||
- reset-names : Must include either "qspi" and/or "qspi-ocp".
|
||||
|
||||
Example:
|
||||
|
||||
|
@ -50,6 +53,8 @@ Example:
|
|||
cdns,fifo-depth = <128>;
|
||||
cdns,fifo-width = <4>;
|
||||
cdns,trigger-address = <0x00000000>;
|
||||
resets = <&rst QSPI_RESET>, <&rst QSPI_OCP_RESET>;
|
||||
reset-names = "qspi", "qspi-ocp";
|
||||
|
||||
flash0: n25q00@0 {
|
||||
...
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
Bindings for HyperFlash NOR flash chips compliant with Cypress HyperBus
|
||||
specification and supports Cypress CFI specification 1.5 command set.
|
||||
|
||||
Required properties:
|
||||
- compatible : "cypress,hyperflash", "cfi-flash" for HyperFlash NOR chips
|
||||
- reg : Address of flash's memory map
|
||||
|
||||
Example:
|
||||
|
||||
flash@0 {
|
||||
compatible = "cypress,hyperflash", "cfi-flash";
|
||||
reg = <0x0 0x4000000>;
|
||||
};
|
|
@ -1,43 +0,0 @@
|
|||
* STMicroelectronics Quad Serial Peripheral Interface(QuadSPI)
|
||||
|
||||
Required properties:
|
||||
- compatible: should be "st,stm32f469-qspi"
|
||||
- reg: the first contains the register location and length.
|
||||
the second contains the memory mapping address and length
|
||||
- reg-names: should contain the reg names "qspi" "qspi_mm"
|
||||
- interrupts: should contain the interrupt for the device
|
||||
- clocks: the phandle of the clock needed by the QSPI controller
|
||||
- A pinctrl must be defined to set pins in mode of operation for QSPI transfer
|
||||
|
||||
Optional properties:
|
||||
- resets: must contain the phandle to the reset controller.
|
||||
|
||||
A spi flash must be a child of the nor_flash node and could have some
|
||||
properties. Also see jedec,spi-nor.txt.
|
||||
|
||||
Required properties:
|
||||
- reg: chip-Select number (QSPI controller may connect 2 nor flashes)
|
||||
- spi-max-frequency: max frequency of spi bus
|
||||
|
||||
Optional property:
|
||||
- spi-rx-bus-width: see ../spi/spi-bus.txt for the description
|
||||
|
||||
Example:
|
||||
|
||||
qspi: spi@a0001000 {
|
||||
compatible = "st,stm32f469-qspi";
|
||||
reg = <0xa0001000 0x1000>, <0x90000000 0x10000000>;
|
||||
reg-names = "qspi", "qspi_mm";
|
||||
interrupts = <91>;
|
||||
resets = <&rcc STM32F4_AHB3_RESET(QSPI)>;
|
||||
clocks = <&rcc 0 STM32F4_AHB3_CLOCK(QSPI)>;
|
||||
pinctrl-names = "default";
|
||||
pinctrl-0 = <&pinctrl_qspi0>;
|
||||
|
||||
flash@0 {
|
||||
reg = <0>;
|
||||
spi-rx-bus-width = <4>;
|
||||
spi-max-frequency = <108000000>;
|
||||
...
|
||||
};
|
||||
};
|
|
@ -0,0 +1,51 @@
|
|||
Bindings for HyperBus Memory Controller (HBMC) on TI's K3 family of SoCs
|
||||
|
||||
Required properties:
|
||||
- compatible : "ti,am654-hbmc" for AM654 SoC
|
||||
- reg : Two entries:
|
||||
First entry pointed to the register space of HBMC controller
|
||||
Second entry pointing to the memory map region dedicated for
|
||||
MMIO access to attached flash devices
|
||||
- ranges : Address translation from offset within CS to allocated MMIO
|
||||
space in SoC
|
||||
|
||||
Optional properties:
|
||||
- mux-controls : phandle to the multiplexer that controls selection of
|
||||
HBMC vs OSPI inside Flash SubSystem (FSS). Default is OSPI,
|
||||
if property is absent.
|
||||
See Documentation/devicetree/bindings/mux/reg-mux.txt
|
||||
for mmio-mux binding details
|
||||
|
||||
Example:
|
||||
|
||||
system-controller@47000000 {
|
||||
compatible = "syscon", "simple-mfd";
|
||||
reg = <0x0 0x47000000 0x0 0x100>;
|
||||
#address-cells = <2>;
|
||||
#size-cells = <2>;
|
||||
ranges;
|
||||
|
||||
hbmc_mux: multiplexer {
|
||||
compatible = "mmio-mux";
|
||||
#mux-control-cells = <1>;
|
||||
mux-reg-masks = <0x4 0x2>; /* 0: reg 0x4, bit 1 */
|
||||
};
|
||||
};
|
||||
|
||||
hbmc: hyperbus@47034000 {
|
||||
compatible = "ti,am654-hbmc";
|
||||
reg = <0x0 0x47034000 0x0 0x100>,
|
||||
<0x5 0x00000000 0x1 0x0000000>;
|
||||
power-domains = <&k3_pds 55>;
|
||||
#address-cells = <2>;
|
||||
#size-cells = <1>;
|
||||
ranges = <0x0 0x0 0x5 0x00000000 0x4000000>, /* CS0 - 64MB */
|
||||
<0x1 0x0 0x5 0x04000000 0x4000000>; /* CS1 - 64MB */
|
||||
mux-controls = <&hbmc_mux 0>;
|
||||
|
||||
/* Slave flash node */
|
||||
flash@0,0 {
|
||||
compatible = "cypress,hyperflash", "cfi-flash";
|
||||
reg = <0x0 0x0 0x4000000>;
|
||||
};
|
||||
};
|
|
@ -7435,6 +7435,14 @@ F: include/asm-generic/mshyperv.h
|
|||
F: tools/hv/
|
||||
F: Documentation/ABI/stable/sysfs-bus-vmbus
|
||||
|
||||
HYPERBUS SUPPORT
|
||||
M: Vignesh Raghavendra <vigneshr@ti.com>
|
||||
S: Supported
|
||||
F: drivers/mtd/hyperbus/
|
||||
F: include/linux/mtd/hyperbus.h
|
||||
F: Documentation/devicetree/bindings/mtd/cypress,hyperflash.txt
|
||||
F: Documentation/devicetree/bindings/mtd/ti,am654-hbmc.txt
|
||||
|
||||
HYPERVISOR VIRTUAL CONSOLE DRIVER
|
||||
L: linuxppc-dev@lists.ozlabs.org
|
||||
S: Odd Fixes
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
#include <linux/of_device.h>
|
||||
#include <linux/of_dma.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/dma/mxs-dma.h>
|
||||
|
||||
#include <asm/irq.h>
|
||||
|
||||
|
@ -77,6 +78,7 @@
|
|||
#define BM_CCW_COMMAND (3 << 0)
|
||||
#define CCW_CHAIN (1 << 2)
|
||||
#define CCW_IRQ (1 << 3)
|
||||
#define CCW_WAIT4RDY (1 << 5)
|
||||
#define CCW_DEC_SEM (1 << 6)
|
||||
#define CCW_WAIT4END (1 << 7)
|
||||
#define CCW_HALT_ON_TERM (1 << 8)
|
||||
|
@ -477,16 +479,16 @@ static void mxs_dma_free_chan_resources(struct dma_chan *chan)
|
|||
* ......
|
||||
* ->device_prep_slave_sg(0);
|
||||
* ......
|
||||
* ->device_prep_slave_sg(DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
|
||||
* ->device_prep_slave_sg(DMA_CTRL_ACK);
|
||||
* ......
|
||||
* [3] If there are more than two DMA commands in the DMA chain, the code
|
||||
* should be:
|
||||
* ......
|
||||
* ->device_prep_slave_sg(0); // First
|
||||
* ......
|
||||
* ->device_prep_slave_sg(DMA_PREP_INTERRUPT [| DMA_CTRL_ACK]);
|
||||
* ->device_prep_slave_sg(DMA_CTRL_ACK]);
|
||||
* ......
|
||||
* ->device_prep_slave_sg(DMA_PREP_INTERRUPT | DMA_CTRL_ACK); // Last
|
||||
* ->device_prep_slave_sg(DMA_CTRL_ACK); // Last
|
||||
* ......
|
||||
*/
|
||||
static struct dma_async_tx_descriptor *mxs_dma_prep_slave_sg(
|
||||
|
@ -500,13 +502,12 @@ static struct dma_async_tx_descriptor *mxs_dma_prep_slave_sg(
|
|||
struct scatterlist *sg;
|
||||
u32 i, j;
|
||||
u32 *pio;
|
||||
bool append = flags & DMA_PREP_INTERRUPT;
|
||||
int idx = append ? mxs_chan->desc_count : 0;
|
||||
int idx = 0;
|
||||
|
||||
if (mxs_chan->status == DMA_IN_PROGRESS && !append)
|
||||
return NULL;
|
||||
if (mxs_chan->status == DMA_IN_PROGRESS)
|
||||
idx = mxs_chan->desc_count;
|
||||
|
||||
if (sg_len + (append ? idx : 0) > NUM_CCW) {
|
||||
if (sg_len + idx > NUM_CCW) {
|
||||
dev_err(mxs_dma->dma_device.dev,
|
||||
"maximum number of sg exceeded: %d > %d\n",
|
||||
sg_len, NUM_CCW);
|
||||
|
@ -520,7 +521,7 @@ static struct dma_async_tx_descriptor *mxs_dma_prep_slave_sg(
|
|||
* If the sg is prepared with append flag set, the sg
|
||||
* will be appended to the last prepared sg.
|
||||
*/
|
||||
if (append) {
|
||||
if (idx) {
|
||||
BUG_ON(idx < 1);
|
||||
ccw = &mxs_chan->ccw[idx - 1];
|
||||
ccw->next = mxs_chan->ccw_phys + sizeof(*ccw) * idx;
|
||||
|
@ -541,12 +542,14 @@ static struct dma_async_tx_descriptor *mxs_dma_prep_slave_sg(
|
|||
ccw->bits = 0;
|
||||
ccw->bits |= CCW_IRQ;
|
||||
ccw->bits |= CCW_DEC_SEM;
|
||||
if (flags & DMA_CTRL_ACK)
|
||||
if (flags & MXS_DMA_CTRL_WAIT4END)
|
||||
ccw->bits |= CCW_WAIT4END;
|
||||
ccw->bits |= CCW_HALT_ON_TERM;
|
||||
ccw->bits |= CCW_TERM_FLUSH;
|
||||
ccw->bits |= BF_CCW(sg_len, PIO_NUM);
|
||||
ccw->bits |= BF_CCW(MXS_DMA_CMD_NO_XFER, COMMAND);
|
||||
if (flags & MXS_DMA_CTRL_WAIT4RDY)
|
||||
ccw->bits |= CCW_WAIT4RDY;
|
||||
} else {
|
||||
for_each_sg(sgl, sg, sg_len, i) {
|
||||
if (sg_dma_len(sg) > MAX_XFER_BYTES) {
|
||||
|
@ -573,7 +576,7 @@ static struct dma_async_tx_descriptor *mxs_dma_prep_slave_sg(
|
|||
ccw->bits &= ~CCW_CHAIN;
|
||||
ccw->bits |= CCW_IRQ;
|
||||
ccw->bits |= CCW_DEC_SEM;
|
||||
if (flags & DMA_CTRL_ACK)
|
||||
if (flags & MXS_DMA_CTRL_WAIT4END)
|
||||
ccw->bits |= CCW_WAIT4END;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -274,4 +274,6 @@ source "drivers/mtd/spi-nor/Kconfig"
|
|||
|
||||
source "drivers/mtd/ubi/Kconfig"
|
||||
|
||||
source "drivers/mtd/hyperbus/Kconfig"
|
||||
|
||||
endif # MTD
|
||||
|
|
|
@ -34,3 +34,4 @@ obj-y += chips/ lpddr/ maps/ devices/ nand/ tests/
|
|||
|
||||
obj-$(CONFIG_MTD_SPI_NOR) += spi-nor/
|
||||
obj-$(CONFIG_MTD_UBI) += ubi/
|
||||
obj-$(CONFIG_MTD_HYPERBUS) += hyperbus/
|
||||
|
|
|
@ -49,6 +49,16 @@
|
|||
#define SST49LF008A 0x005a
|
||||
#define AT49BV6416 0x00d6
|
||||
|
||||
/*
|
||||
* Status Register bit description. Used by flash devices that don't
|
||||
* support DQ polling (e.g. HyperFlash)
|
||||
*/
|
||||
#define CFI_SR_DRB BIT(7)
|
||||
#define CFI_SR_ESB BIT(5)
|
||||
#define CFI_SR_PSB BIT(4)
|
||||
#define CFI_SR_WBASB BIT(3)
|
||||
#define CFI_SR_SLSB BIT(1)
|
||||
|
||||
static int cfi_amdstd_read (struct mtd_info *, loff_t, size_t, size_t *, u_char *);
|
||||
static int cfi_amdstd_write_words(struct mtd_info *, loff_t, size_t, size_t *, const u_char *);
|
||||
static int cfi_amdstd_write_buffers(struct mtd_info *, loff_t, size_t, size_t *, const u_char *);
|
||||
|
@ -97,6 +107,50 @@ static struct mtd_chip_driver cfi_amdstd_chipdrv = {
|
|||
.module = THIS_MODULE
|
||||
};
|
||||
|
||||
/*
|
||||
* Use status register to poll for Erase/write completion when DQ is not
|
||||
* supported. This is indicated by Bit[1:0] of SoftwareFeatures field in
|
||||
* CFI Primary Vendor-Specific Extended Query table 1.5
|
||||
*/
|
||||
static int cfi_use_status_reg(struct cfi_private *cfi)
|
||||
{
|
||||
struct cfi_pri_amdstd *extp = cfi->cmdset_priv;
|
||||
u8 poll_mask = CFI_POLL_STATUS_REG | CFI_POLL_DQ;
|
||||
|
||||
return extp->MinorVersion >= '5' &&
|
||||
(extp->SoftwareFeatures & poll_mask) == CFI_POLL_STATUS_REG;
|
||||
}
|
||||
|
||||
static void cfi_check_err_status(struct map_info *map, struct flchip *chip,
|
||||
unsigned long adr)
|
||||
{
|
||||
struct cfi_private *cfi = map->fldrv_priv;
|
||||
map_word status;
|
||||
|
||||
if (!cfi_use_status_reg(cfi))
|
||||
return;
|
||||
|
||||
cfi_send_gen_cmd(0x70, cfi->addr_unlock1, chip->start, map, cfi,
|
||||
cfi->device_type, NULL);
|
||||
status = map_read(map, adr);
|
||||
|
||||
if (map_word_bitsset(map, status, CMD(0x3a))) {
|
||||
unsigned long chipstatus = MERGESTATUS(status);
|
||||
|
||||
if (chipstatus & CFI_SR_ESB)
|
||||
pr_err("%s erase operation failed, status %lx\n",
|
||||
map->name, chipstatus);
|
||||
if (chipstatus & CFI_SR_PSB)
|
||||
pr_err("%s program operation failed, status %lx\n",
|
||||
map->name, chipstatus);
|
||||
if (chipstatus & CFI_SR_WBASB)
|
||||
pr_err("%s buffer program command aborted, status %lx\n",
|
||||
map->name, chipstatus);
|
||||
if (chipstatus & CFI_SR_SLSB)
|
||||
pr_err("%s sector write protected, status %lx\n",
|
||||
map->name, chipstatus);
|
||||
}
|
||||
}
|
||||
|
||||
/* #define DEBUG_CFI_FEATURES */
|
||||
|
||||
|
@ -742,10 +796,25 @@ static struct mtd_info *cfi_amdstd_setup(struct mtd_info *mtd)
|
|||
* correctly and is therefore not done (particularly with interleaved chips
|
||||
* as each chip must be checked independently of the others).
|
||||
*/
|
||||
static int __xipram chip_ready(struct map_info *map, unsigned long addr)
|
||||
static int __xipram chip_ready(struct map_info *map, struct flchip *chip,
|
||||
unsigned long addr)
|
||||
{
|
||||
struct cfi_private *cfi = map->fldrv_priv;
|
||||
map_word d, t;
|
||||
|
||||
if (cfi_use_status_reg(cfi)) {
|
||||
map_word ready = CMD(CFI_SR_DRB);
|
||||
/*
|
||||
* For chips that support status register, check device
|
||||
* ready bit
|
||||
*/
|
||||
cfi_send_gen_cmd(0x70, cfi->addr_unlock1, chip->start, map, cfi,
|
||||
cfi->device_type, NULL);
|
||||
d = map_read(map, addr);
|
||||
|
||||
return map_word_andequal(map, d, ready, ready);
|
||||
}
|
||||
|
||||
d = map_read(map, addr);
|
||||
t = map_read(map, addr);
|
||||
|
||||
|
@ -767,10 +836,30 @@ static int __xipram chip_ready(struct map_info *map, unsigned long addr)
|
|||
* as each chip must be checked independently of the others).
|
||||
*
|
||||
*/
|
||||
static int __xipram chip_good(struct map_info *map, unsigned long addr, map_word expected)
|
||||
static int __xipram chip_good(struct map_info *map, struct flchip *chip,
|
||||
unsigned long addr, map_word expected)
|
||||
{
|
||||
struct cfi_private *cfi = map->fldrv_priv;
|
||||
map_word oldd, curd;
|
||||
|
||||
if (cfi_use_status_reg(cfi)) {
|
||||
map_word ready = CMD(CFI_SR_DRB);
|
||||
map_word err = CMD(CFI_SR_PSB | CFI_SR_ESB);
|
||||
/*
|
||||
* For chips that support status register, check device
|
||||
* ready bit and Erase/Program status bit to know if
|
||||
* operation succeeded.
|
||||
*/
|
||||
cfi_send_gen_cmd(0x70, cfi->addr_unlock1, chip->start, map, cfi,
|
||||
cfi->device_type, NULL);
|
||||
curd = map_read(map, addr);
|
||||
|
||||
if (map_word_andequal(map, curd, ready, ready))
|
||||
return !map_word_bitsset(map, curd, err);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
oldd = map_read(map, addr);
|
||||
curd = map_read(map, addr);
|
||||
|
||||
|
@ -792,7 +881,7 @@ static int get_chip(struct map_info *map, struct flchip *chip, unsigned long adr
|
|||
|
||||
case FL_STATUS:
|
||||
for (;;) {
|
||||
if (chip_ready(map, adr))
|
||||
if (chip_ready(map, chip, adr))
|
||||
break;
|
||||
|
||||
if (time_after(jiffies, timeo)) {
|
||||
|
@ -830,7 +919,7 @@ static int get_chip(struct map_info *map, struct flchip *chip, unsigned long adr
|
|||
chip->state = FL_ERASE_SUSPENDING;
|
||||
chip->erase_suspended = 1;
|
||||
for (;;) {
|
||||
if (chip_ready(map, adr))
|
||||
if (chip_ready(map, chip, adr))
|
||||
break;
|
||||
|
||||
if (time_after(jiffies, timeo)) {
|
||||
|
@ -1362,7 +1451,7 @@ static int do_otp_lock(struct map_info *map, struct flchip *chip, loff_t adr,
|
|||
/* wait for chip to become ready */
|
||||
timeo = jiffies + msecs_to_jiffies(2);
|
||||
for (;;) {
|
||||
if (chip_ready(map, adr))
|
||||
if (chip_ready(map, chip, adr))
|
||||
break;
|
||||
|
||||
if (time_after(jiffies, timeo)) {
|
||||
|
@ -1628,22 +1717,24 @@ static int __xipram do_write_oneword(struct map_info *map, struct flchip *chip,
|
|||
continue;
|
||||
}
|
||||
|
||||
if (time_after(jiffies, timeo) && !chip_ready(map, adr)){
|
||||
if (time_after(jiffies, timeo) &&
|
||||
!chip_ready(map, chip, adr)) {
|
||||
xip_enable(map, chip, adr);
|
||||
printk(KERN_WARNING "MTD %s(): software timeout\n", __func__);
|
||||
xip_disable(map, chip, adr);
|
||||
break;
|
||||
}
|
||||
|
||||
if (chip_ready(map, adr))
|
||||
if (chip_ready(map, chip, adr))
|
||||
break;
|
||||
|
||||
/* Latency issues. Drop the lock, wait a while and retry */
|
||||
UDELAY(map, chip, adr, 1);
|
||||
}
|
||||
/* Did we succeed? */
|
||||
if (!chip_good(map, adr, datum)) {
|
||||
if (!chip_good(map, chip, adr, datum)) {
|
||||
/* reset on all failures. */
|
||||
cfi_check_err_status(map, chip, adr);
|
||||
map_write(map, CMD(0xF0), chip->start);
|
||||
/* FIXME - should have reset delay before continuing */
|
||||
|
||||
|
@ -1881,10 +1972,11 @@ static int __xipram do_write_buffer(struct map_info *map, struct flchip *chip,
|
|||
* We check "time_after" and "!chip_good" before checking "chip_good" to avoid
|
||||
* the failure due to scheduling.
|
||||
*/
|
||||
if (time_after(jiffies, timeo) && !chip_good(map, adr, datum))
|
||||
if (time_after(jiffies, timeo) &&
|
||||
!chip_good(map, chip, adr, datum))
|
||||
break;
|
||||
|
||||
if (chip_good(map, adr, datum)) {
|
||||
if (chip_good(map, chip, adr, datum)) {
|
||||
xip_enable(map, chip, adr);
|
||||
goto op_done;
|
||||
}
|
||||
|
@ -1901,6 +1993,7 @@ static int __xipram do_write_buffer(struct map_info *map, struct flchip *chip,
|
|||
* See e.g.
|
||||
* http://www.spansion.com/Support/Application%20Notes/MirrorBit_Write_Buffer_Prog_Page_Buffer_Read_AN.pdf
|
||||
*/
|
||||
cfi_check_err_status(map, chip, adr);
|
||||
cfi_send_gen_cmd(0xAA, cfi->addr_unlock1, chip->start, map, cfi,
|
||||
cfi->device_type, NULL);
|
||||
cfi_send_gen_cmd(0x55, cfi->addr_unlock2, chip->start, map, cfi,
|
||||
|
@ -2018,7 +2111,7 @@ static int cfi_amdstd_panic_wait(struct map_info *map, struct flchip *chip,
|
|||
* If the driver thinks the chip is idle, and no toggle bits
|
||||
* are changing, then the chip is actually idle for sure.
|
||||
*/
|
||||
if (chip->state == FL_READY && chip_ready(map, adr))
|
||||
if (chip->state == FL_READY && chip_ready(map, chip, adr))
|
||||
return 0;
|
||||
|
||||
/*
|
||||
|
@ -2035,7 +2128,7 @@ static int cfi_amdstd_panic_wait(struct map_info *map, struct flchip *chip,
|
|||
|
||||
/* wait for the chip to become ready */
|
||||
for (i = 0; i < jiffies_to_usecs(timeo); i++) {
|
||||
if (chip_ready(map, adr))
|
||||
if (chip_ready(map, chip, adr))
|
||||
return 0;
|
||||
|
||||
udelay(1);
|
||||
|
@ -2099,14 +2192,15 @@ static int do_panic_write_oneword(struct map_info *map, struct flchip *chip,
|
|||
map_write(map, datum, adr);
|
||||
|
||||
for (i = 0; i < jiffies_to_usecs(uWriteTimeout); i++) {
|
||||
if (chip_ready(map, adr))
|
||||
if (chip_ready(map, chip, adr))
|
||||
break;
|
||||
|
||||
udelay(1);
|
||||
}
|
||||
|
||||
if (!chip_good(map, adr, datum)) {
|
||||
if (!chip_good(map, chip, adr, datum)) {
|
||||
/* reset on all failures. */
|
||||
cfi_check_err_status(map, chip, adr);
|
||||
map_write(map, CMD(0xF0), chip->start);
|
||||
/* FIXME - should have reset delay before continuing */
|
||||
|
||||
|
@ -2300,7 +2394,7 @@ static int __xipram do_erase_chip(struct map_info *map, struct flchip *chip)
|
|||
chip->erase_suspended = 0;
|
||||
}
|
||||
|
||||
if (chip_good(map, adr, map_word_ff(map)))
|
||||
if (chip_good(map, chip, adr, map_word_ff(map)))
|
||||
break;
|
||||
|
||||
if (time_after(jiffies, timeo)) {
|
||||
|
@ -2316,6 +2410,7 @@ static int __xipram do_erase_chip(struct map_info *map, struct flchip *chip)
|
|||
/* Did we succeed? */
|
||||
if (ret) {
|
||||
/* reset on all failures. */
|
||||
cfi_check_err_status(map, chip, adr);
|
||||
map_write(map, CMD(0xF0), chip->start);
|
||||
/* FIXME - should have reset delay before continuing */
|
||||
|
||||
|
@ -2396,7 +2491,7 @@ static int __xipram do_erase_oneblock(struct map_info *map, struct flchip *chip,
|
|||
chip->erase_suspended = 0;
|
||||
}
|
||||
|
||||
if (chip_good(map, adr, map_word_ff(map)))
|
||||
if (chip_good(map, chip, adr, map_word_ff(map)))
|
||||
break;
|
||||
|
||||
if (time_after(jiffies, timeo)) {
|
||||
|
@ -2412,6 +2507,7 @@ static int __xipram do_erase_oneblock(struct map_info *map, struct flchip *chip,
|
|||
/* Did we succeed? */
|
||||
if (ret) {
|
||||
/* reset on all failures. */
|
||||
cfi_check_err_status(map, chip, adr);
|
||||
map_write(map, CMD(0xF0), chip->start);
|
||||
/* FIXME - should have reset delay before continuing */
|
||||
|
||||
|
@ -2533,8 +2629,6 @@ struct ppb_lock {
|
|||
int locked;
|
||||
};
|
||||
|
||||
#define MAX_SECTORS 512
|
||||
|
||||
#define DO_XXLOCK_ONEBLOCK_LOCK ((void *)1)
|
||||
#define DO_XXLOCK_ONEBLOCK_UNLOCK ((void *)2)
|
||||
#define DO_XXLOCK_ONEBLOCK_GETLOCK ((void *)3)
|
||||
|
@ -2589,7 +2683,7 @@ static int __maybe_unused do_ppb_xxlock(struct map_info *map,
|
|||
*/
|
||||
timeo = jiffies + msecs_to_jiffies(2000); /* 2s max (un)locking */
|
||||
for (;;) {
|
||||
if (chip_ready(map, adr))
|
||||
if (chip_ready(map, chip, adr))
|
||||
break;
|
||||
|
||||
if (time_after(jiffies, timeo)) {
|
||||
|
@ -2633,6 +2727,7 @@ static int __maybe_unused cfi_ppb_unlock(struct mtd_info *mtd, loff_t ofs,
|
|||
int i;
|
||||
int sectors;
|
||||
int ret;
|
||||
int max_sectors;
|
||||
|
||||
/*
|
||||
* PPB unlocking always unlocks all sectors of the flash chip.
|
||||
|
@ -2640,7 +2735,11 @@ static int __maybe_unused cfi_ppb_unlock(struct mtd_info *mtd, loff_t ofs,
|
|||
* first check the locking status of all sectors and save
|
||||
* it for future use.
|
||||
*/
|
||||
sect = kcalloc(MAX_SECTORS, sizeof(struct ppb_lock), GFP_KERNEL);
|
||||
max_sectors = 0;
|
||||
for (i = 0; i < mtd->numeraseregions; i++)
|
||||
max_sectors += regions[i].numblocks;
|
||||
|
||||
sect = kcalloc(max_sectors, sizeof(struct ppb_lock), GFP_KERNEL);
|
||||
if (!sect)
|
||||
return -ENOMEM;
|
||||
|
||||
|
@ -2689,9 +2788,9 @@ static int __maybe_unused cfi_ppb_unlock(struct mtd_info *mtd, loff_t ofs,
|
|||
}
|
||||
|
||||
sectors++;
|
||||
if (sectors >= MAX_SECTORS) {
|
||||
if (sectors >= max_sectors) {
|
||||
printk(KERN_ERR "Only %d sectors for PPB locking supported!\n",
|
||||
MAX_SECTORS);
|
||||
max_sectors);
|
||||
kfree(sect);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
menuconfig MTD_HYPERBUS
|
||||
tristate "HyperBus support"
|
||||
select MTD_CFI
|
||||
select MTD_MAP_BANK_WIDTH_2
|
||||
select MTD_CFI_AMDSTD
|
||||
select MTD_COMPLEX_MAPPINGS
|
||||
help
|
||||
This is the framework for the HyperBus which can be used by
|
||||
the HyperBus Controller driver to communicate with
|
||||
HyperFlash. See Cypress HyperBus specification for more
|
||||
details
|
||||
|
||||
if MTD_HYPERBUS
|
||||
|
||||
config HBMC_AM654
|
||||
tristate "HyperBus controller driver for AM65x SoC"
|
||||
select MULTIPLEXER
|
||||
select MUX_MMIO
|
||||
help
|
||||
This is the driver for HyperBus controller on TI's AM65x and
|
||||
other SoCs
|
||||
|
||||
endif # MTD_HYPERBUS
|
|
@ -0,0 +1,4 @@
|
|||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
obj-$(CONFIG_MTD_HYPERBUS) += hyperbus-core.o
|
||||
obj-$(CONFIG_HBMC_AM654) += hbmc-am654.o
|
|
@ -0,0 +1,147 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
//
|
||||
// Copyright (C) 2019 Texas Instruments Incorporated - http://www.ti.com/
|
||||
// Author: Vignesh Raghavendra <vigneshr@ti.com>
|
||||
|
||||
#include <linux/err.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mtd/cfi.h>
|
||||
#include <linux/mtd/hyperbus.h>
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include <linux/mux/consumer.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#define AM654_HBMC_CALIB_COUNT 25
|
||||
|
||||
struct am654_hbmc_priv {
|
||||
struct hyperbus_ctlr ctlr;
|
||||
struct hyperbus_device hbdev;
|
||||
struct mux_control *mux_ctrl;
|
||||
};
|
||||
|
||||
static int am654_hbmc_calibrate(struct hyperbus_device *hbdev)
|
||||
{
|
||||
struct map_info *map = &hbdev->map;
|
||||
struct cfi_private cfi;
|
||||
int count = AM654_HBMC_CALIB_COUNT;
|
||||
int pass_count = 0;
|
||||
int ret;
|
||||
|
||||
cfi.interleave = 1;
|
||||
cfi.device_type = CFI_DEVICETYPE_X16;
|
||||
cfi_send_gen_cmd(0xF0, 0, 0, map, &cfi, cfi.device_type, NULL);
|
||||
cfi_send_gen_cmd(0x98, 0x55, 0, map, &cfi, cfi.device_type, NULL);
|
||||
|
||||
while (count--) {
|
||||
ret = cfi_qry_present(map, 0, &cfi);
|
||||
if (ret)
|
||||
pass_count++;
|
||||
else
|
||||
pass_count = 0;
|
||||
if (pass_count == 5)
|
||||
break;
|
||||
}
|
||||
|
||||
cfi_qry_mode_off(0, map, &cfi);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct hyperbus_ops am654_hbmc_ops = {
|
||||
.calibrate = am654_hbmc_calibrate,
|
||||
};
|
||||
|
||||
static int am654_hbmc_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct am654_hbmc_priv *priv;
|
||||
int ret;
|
||||
|
||||
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
|
||||
if (!priv)
|
||||
return -ENOMEM;
|
||||
|
||||
platform_set_drvdata(pdev, priv);
|
||||
|
||||
if (of_property_read_bool(dev->of_node, "mux-controls")) {
|
||||
struct mux_control *control = devm_mux_control_get(dev, NULL);
|
||||
|
||||
if (IS_ERR(control))
|
||||
return PTR_ERR(control);
|
||||
|
||||
ret = mux_control_select(control, 1);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to select HBMC mux\n");
|
||||
return ret;
|
||||
}
|
||||
priv->mux_ctrl = control;
|
||||
}
|
||||
|
||||
pm_runtime_enable(dev);
|
||||
ret = pm_runtime_get_sync(dev);
|
||||
if (ret < 0) {
|
||||
pm_runtime_put_noidle(dev);
|
||||
goto disable_pm;
|
||||
}
|
||||
|
||||
priv->ctlr.dev = dev;
|
||||
priv->ctlr.ops = &am654_hbmc_ops;
|
||||
priv->hbdev.ctlr = &priv->ctlr;
|
||||
priv->hbdev.np = of_get_next_child(dev->of_node, NULL);
|
||||
ret = hyperbus_register_device(&priv->hbdev);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to register controller\n");
|
||||
pm_runtime_put_sync(&pdev->dev);
|
||||
goto disable_pm;
|
||||
}
|
||||
|
||||
return 0;
|
||||
disable_pm:
|
||||
pm_runtime_disable(dev);
|
||||
if (priv->mux_ctrl)
|
||||
mux_control_deselect(priv->mux_ctrl);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int am654_hbmc_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct am654_hbmc_priv *priv = platform_get_drvdata(pdev);
|
||||
int ret;
|
||||
|
||||
ret = hyperbus_unregister_device(&priv->hbdev);
|
||||
if (priv->mux_ctrl)
|
||||
mux_control_deselect(priv->mux_ctrl);
|
||||
pm_runtime_put_sync(&pdev->dev);
|
||||
pm_runtime_disable(&pdev->dev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct of_device_id am654_hbmc_dt_ids[] = {
|
||||
{
|
||||
.compatible = "ti,am654-hbmc",
|
||||
},
|
||||
{ /* end of table */ }
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(of, am654_hbmc_dt_ids);
|
||||
|
||||
static struct platform_driver am654_hbmc_platform_driver = {
|
||||
.probe = am654_hbmc_probe,
|
||||
.remove = am654_hbmc_remove,
|
||||
.driver = {
|
||||
.name = "hbmc-am654",
|
||||
.of_match_table = am654_hbmc_dt_ids,
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(am654_hbmc_platform_driver);
|
||||
|
||||
MODULE_DESCRIPTION("HBMC driver for AM654 SoC");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_ALIAS("platform:hbmc-am654");
|
||||
MODULE_AUTHOR("Vignesh Raghavendra <vigneshr@ti.com>");
|
|
@ -0,0 +1,153 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
//
|
||||
// Copyright (C) 2019 Texas Instruments Incorporated - http://www.ti.com/
|
||||
// Author: Vignesh Raghavendra <vigneshr@ti.com>
|
||||
|
||||
#include <linux/err.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mtd/hyperbus.h>
|
||||
#include <linux/mtd/map.h>
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
static struct hyperbus_device *map_to_hbdev(struct map_info *map)
|
||||
{
|
||||
return container_of(map, struct hyperbus_device, map);
|
||||
}
|
||||
|
||||
static map_word hyperbus_read16(struct map_info *map, unsigned long addr)
|
||||
{
|
||||
struct hyperbus_device *hbdev = map_to_hbdev(map);
|
||||
struct hyperbus_ctlr *ctlr = hbdev->ctlr;
|
||||
map_word read_data;
|
||||
|
||||
read_data.x[0] = ctlr->ops->read16(hbdev, addr);
|
||||
|
||||
return read_data;
|
||||
}
|
||||
|
||||
static void hyperbus_write16(struct map_info *map, map_word d,
|
||||
unsigned long addr)
|
||||
{
|
||||
struct hyperbus_device *hbdev = map_to_hbdev(map);
|
||||
struct hyperbus_ctlr *ctlr = hbdev->ctlr;
|
||||
|
||||
ctlr->ops->write16(hbdev, addr, d.x[0]);
|
||||
}
|
||||
|
||||
static void hyperbus_copy_from(struct map_info *map, void *to,
|
||||
unsigned long from, ssize_t len)
|
||||
{
|
||||
struct hyperbus_device *hbdev = map_to_hbdev(map);
|
||||
struct hyperbus_ctlr *ctlr = hbdev->ctlr;
|
||||
|
||||
ctlr->ops->copy_from(hbdev, to, from, len);
|
||||
}
|
||||
|
||||
static void hyperbus_copy_to(struct map_info *map, unsigned long to,
|
||||
const void *from, ssize_t len)
|
||||
{
|
||||
struct hyperbus_device *hbdev = map_to_hbdev(map);
|
||||
struct hyperbus_ctlr *ctlr = hbdev->ctlr;
|
||||
|
||||
ctlr->ops->copy_to(hbdev, to, from, len);
|
||||
}
|
||||
|
||||
int hyperbus_register_device(struct hyperbus_device *hbdev)
|
||||
{
|
||||
const struct hyperbus_ops *ops;
|
||||
struct hyperbus_ctlr *ctlr;
|
||||
struct device_node *np;
|
||||
struct map_info *map;
|
||||
struct resource res;
|
||||
struct device *dev;
|
||||
int ret;
|
||||
|
||||
if (!hbdev || !hbdev->np || !hbdev->ctlr || !hbdev->ctlr->dev) {
|
||||
pr_err("hyperbus: please fill all the necessary fields!\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
np = hbdev->np;
|
||||
ctlr = hbdev->ctlr;
|
||||
if (!of_device_is_compatible(np, "cypress,hyperflash"))
|
||||
return -ENODEV;
|
||||
|
||||
hbdev->memtype = HYPERFLASH;
|
||||
|
||||
ret = of_address_to_resource(np, 0, &res);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
dev = ctlr->dev;
|
||||
map = &hbdev->map;
|
||||
map->size = resource_size(&res);
|
||||
map->virt = devm_ioremap_resource(dev, &res);
|
||||
if (IS_ERR(map->virt))
|
||||
return PTR_ERR(map->virt);
|
||||
|
||||
map->name = dev_name(dev);
|
||||
map->bankwidth = 2;
|
||||
map->device_node = np;
|
||||
|
||||
simple_map_init(map);
|
||||
ops = ctlr->ops;
|
||||
if (ops) {
|
||||
if (ops->read16)
|
||||
map->read = hyperbus_read16;
|
||||
if (ops->write16)
|
||||
map->write = hyperbus_write16;
|
||||
if (ops->copy_to)
|
||||
map->copy_to = hyperbus_copy_to;
|
||||
if (ops->copy_from)
|
||||
map->copy_from = hyperbus_copy_from;
|
||||
|
||||
if (ops->calibrate && !ctlr->calibrated) {
|
||||
ret = ops->calibrate(hbdev);
|
||||
if (!ret) {
|
||||
dev_err(dev, "Calibration failed\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
ctlr->calibrated = true;
|
||||
}
|
||||
}
|
||||
|
||||
hbdev->mtd = do_map_probe("cfi_probe", map);
|
||||
if (!hbdev->mtd) {
|
||||
dev_err(dev, "probing of hyperbus device failed\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
hbdev->mtd->dev.parent = dev;
|
||||
mtd_set_of_node(hbdev->mtd, np);
|
||||
|
||||
ret = mtd_device_register(hbdev->mtd, NULL, 0);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to register mtd device\n");
|
||||
map_destroy(hbdev->mtd);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(hyperbus_register_device);
|
||||
|
||||
int hyperbus_unregister_device(struct hyperbus_device *hbdev)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
if (hbdev && hbdev->mtd) {
|
||||
ret = mtd_device_unregister(hbdev->mtd);
|
||||
map_destroy(hbdev->mtd);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(hyperbus_unregister_device);
|
||||
|
||||
MODULE_DESCRIPTION("HyperBus Framework");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_AUTHOR("Vignesh Raghavendra <vigneshr@ti.com>");
|
|
@ -437,7 +437,8 @@ static int concat_erase(struct mtd_info *mtd, struct erase_info *instr)
|
|||
return err;
|
||||
}
|
||||
|
||||
static int concat_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
|
||||
static int concat_xxlock(struct mtd_info *mtd, loff_t ofs, uint64_t len,
|
||||
bool is_lock)
|
||||
{
|
||||
struct mtd_concat *concat = CONCAT(mtd);
|
||||
int i, err = -EINVAL;
|
||||
|
@ -456,7 +457,10 @@ static int concat_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
|
|||
else
|
||||
size = len;
|
||||
|
||||
err = mtd_lock(subdev, ofs, size);
|
||||
if (is_lock)
|
||||
err = mtd_lock(subdev, ofs, size);
|
||||
else
|
||||
err = mtd_unlock(subdev, ofs, size);
|
||||
if (err)
|
||||
break;
|
||||
|
||||
|
@ -471,35 +475,33 @@ static int concat_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
|
|||
return err;
|
||||
}
|
||||
|
||||
static int concat_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
|
||||
{
|
||||
return concat_xxlock(mtd, ofs, len, true);
|
||||
}
|
||||
|
||||
static int concat_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
|
||||
{
|
||||
return concat_xxlock(mtd, ofs, len, false);
|
||||
}
|
||||
|
||||
static int concat_is_locked(struct mtd_info *mtd, loff_t ofs, uint64_t len)
|
||||
{
|
||||
struct mtd_concat *concat = CONCAT(mtd);
|
||||
int i, err = 0;
|
||||
int i, err = -EINVAL;
|
||||
|
||||
for (i = 0; i < concat->num_subdev; i++) {
|
||||
struct mtd_info *subdev = concat->subdev[i];
|
||||
uint64_t size;
|
||||
|
||||
if (ofs >= subdev->size) {
|
||||
size = 0;
|
||||
ofs -= subdev->size;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ofs + len > subdev->size)
|
||||
size = subdev->size - ofs;
|
||||
else
|
||||
size = len;
|
||||
|
||||
err = mtd_unlock(subdev, ofs, size);
|
||||
if (err)
|
||||
break;
|
||||
|
||||
len -= size;
|
||||
if (len == 0)
|
||||
break;
|
||||
|
||||
err = -EINVAL;
|
||||
ofs = 0;
|
||||
return mtd_is_locked(subdev, ofs, len);
|
||||
}
|
||||
|
||||
return err;
|
||||
|
@ -704,6 +706,7 @@ struct mtd_info *mtd_concat_create(struct mtd_info *subdev[], /* subdevices to c
|
|||
concat->mtd._sync = concat_sync;
|
||||
concat->mtd._lock = concat_lock;
|
||||
concat->mtd._unlock = concat_unlock;
|
||||
concat->mtd._is_locked = concat_is_locked;
|
||||
concat->mtd._suspend = concat_suspend;
|
||||
concat->mtd._resume = concat_resume;
|
||||
|
||||
|
|
|
@ -1124,6 +1124,9 @@ int mtd_panic_write(struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen,
|
|||
return -EROFS;
|
||||
if (!len)
|
||||
return 0;
|
||||
if (!mtd->oops_panic_write)
|
||||
mtd->oops_panic_write = true;
|
||||
|
||||
return mtd->_panic_write(mtd, to, len, retlen, buf);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(mtd_panic_write);
|
||||
|
|
|
@ -3257,6 +3257,8 @@ static void onenand_check_features(struct mtd_info *mtd)
|
|||
|
||||
/* Lock scheme */
|
||||
switch (density) {
|
||||
case ONENAND_DEVICE_DENSITY_8Gb:
|
||||
this->options |= ONENAND_HAS_NOP_1;
|
||||
case ONENAND_DEVICE_DENSITY_4Gb:
|
||||
if (ONENAND_IS_DDP(this))
|
||||
this->options |= ONENAND_HAS_2PLANE;
|
||||
|
@ -3277,12 +3279,15 @@ static void onenand_check_features(struct mtd_info *mtd)
|
|||
if ((this->version_id & 0xf) == 0xe)
|
||||
this->options |= ONENAND_HAS_NOP_1;
|
||||
}
|
||||
this->options |= ONENAND_HAS_UNLOCK_ALL;
|
||||
break;
|
||||
|
||||
case ONENAND_DEVICE_DENSITY_2Gb:
|
||||
/* 2Gb DDP does not have 2 plane */
|
||||
if (!ONENAND_IS_DDP(this))
|
||||
this->options |= ONENAND_HAS_2PLANE;
|
||||
this->options |= ONENAND_HAS_UNLOCK_ALL;
|
||||
break;
|
||||
|
||||
case ONENAND_DEVICE_DENSITY_1Gb:
|
||||
/* A-Die has all block unlock */
|
||||
|
|
|
@ -84,6 +84,12 @@ struct brcm_nand_dma_desc {
|
|||
#define FLASH_DMA_ECC_ERROR (1 << 8)
|
||||
#define FLASH_DMA_CORR_ERROR (1 << 9)
|
||||
|
||||
/* Bitfields for DMA_MODE */
|
||||
#define FLASH_DMA_MODE_STOP_ON_ERROR BIT(1) /* stop in Uncorr ECC error */
|
||||
#define FLASH_DMA_MODE_MODE BIT(0) /* link list */
|
||||
#define FLASH_DMA_MODE_MASK (FLASH_DMA_MODE_STOP_ON_ERROR | \
|
||||
FLASH_DMA_MODE_MODE)
|
||||
|
||||
/* 512B flash cache in the NAND controller HW */
|
||||
#define FC_SHIFT 9U
|
||||
#define FC_BYTES 512U
|
||||
|
@ -96,6 +102,51 @@ struct brcm_nand_dma_desc {
|
|||
#define NAND_CTRL_RDY (INTFC_CTLR_READY | INTFC_FLASH_READY)
|
||||
#define NAND_POLL_STATUS_TIMEOUT_MS 100
|
||||
|
||||
/* flash_dma registers */
|
||||
enum flash_dma_reg {
|
||||
FLASH_DMA_REVISION = 0,
|
||||
FLASH_DMA_FIRST_DESC,
|
||||
FLASH_DMA_FIRST_DESC_EXT,
|
||||
FLASH_DMA_CTRL,
|
||||
FLASH_DMA_MODE,
|
||||
FLASH_DMA_STATUS,
|
||||
FLASH_DMA_INTERRUPT_DESC,
|
||||
FLASH_DMA_INTERRUPT_DESC_EXT,
|
||||
FLASH_DMA_ERROR_STATUS,
|
||||
FLASH_DMA_CURRENT_DESC,
|
||||
FLASH_DMA_CURRENT_DESC_EXT,
|
||||
};
|
||||
|
||||
/* flash_dma registers v1*/
|
||||
static const u16 flash_dma_regs_v1[] = {
|
||||
[FLASH_DMA_REVISION] = 0x00,
|
||||
[FLASH_DMA_FIRST_DESC] = 0x04,
|
||||
[FLASH_DMA_FIRST_DESC_EXT] = 0x08,
|
||||
[FLASH_DMA_CTRL] = 0x0c,
|
||||
[FLASH_DMA_MODE] = 0x10,
|
||||
[FLASH_DMA_STATUS] = 0x14,
|
||||
[FLASH_DMA_INTERRUPT_DESC] = 0x18,
|
||||
[FLASH_DMA_INTERRUPT_DESC_EXT] = 0x1c,
|
||||
[FLASH_DMA_ERROR_STATUS] = 0x20,
|
||||
[FLASH_DMA_CURRENT_DESC] = 0x24,
|
||||
[FLASH_DMA_CURRENT_DESC_EXT] = 0x28,
|
||||
};
|
||||
|
||||
/* flash_dma registers v4 */
|
||||
static const u16 flash_dma_regs_v4[] = {
|
||||
[FLASH_DMA_REVISION] = 0x00,
|
||||
[FLASH_DMA_FIRST_DESC] = 0x08,
|
||||
[FLASH_DMA_FIRST_DESC_EXT] = 0x0c,
|
||||
[FLASH_DMA_CTRL] = 0x10,
|
||||
[FLASH_DMA_MODE] = 0x14,
|
||||
[FLASH_DMA_STATUS] = 0x18,
|
||||
[FLASH_DMA_INTERRUPT_DESC] = 0x20,
|
||||
[FLASH_DMA_INTERRUPT_DESC_EXT] = 0x24,
|
||||
[FLASH_DMA_ERROR_STATUS] = 0x28,
|
||||
[FLASH_DMA_CURRENT_DESC] = 0x30,
|
||||
[FLASH_DMA_CURRENT_DESC_EXT] = 0x34,
|
||||
};
|
||||
|
||||
/* Controller feature flags */
|
||||
enum {
|
||||
BRCMNAND_HAS_1K_SECTORS = BIT(0),
|
||||
|
@ -128,6 +179,8 @@ struct brcmnand_controller {
|
|||
/* List of NAND hosts (one for each chip-select) */
|
||||
struct list_head host_list;
|
||||
|
||||
/* flash_dma reg */
|
||||
const u16 *flash_dma_offsets;
|
||||
struct brcm_nand_dma_desc *dma_desc;
|
||||
dma_addr_t dma_pa;
|
||||
|
||||
|
@ -151,6 +204,7 @@ struct brcmnand_controller {
|
|||
u32 nand_cs_nand_xor;
|
||||
u32 corr_stat_threshold;
|
||||
u32 flash_dma_mode;
|
||||
bool pio_poll_mode;
|
||||
};
|
||||
|
||||
struct brcmnand_cfg {
|
||||
|
@ -462,7 +516,7 @@ static int brcmnand_revision_init(struct brcmnand_controller *ctrl)
|
|||
/* Register offsets */
|
||||
if (ctrl->nand_version >= 0x0702)
|
||||
ctrl->reg_offsets = brcmnand_regs_v72;
|
||||
else if (ctrl->nand_version >= 0x0701)
|
||||
else if (ctrl->nand_version == 0x0701)
|
||||
ctrl->reg_offsets = brcmnand_regs_v71;
|
||||
else if (ctrl->nand_version >= 0x0600)
|
||||
ctrl->reg_offsets = brcmnand_regs_v60;
|
||||
|
@ -507,7 +561,7 @@ static int brcmnand_revision_init(struct brcmnand_controller *ctrl)
|
|||
}
|
||||
|
||||
/* Maximum spare area sector size (per 512B) */
|
||||
if (ctrl->nand_version >= 0x0702)
|
||||
if (ctrl->nand_version == 0x0702)
|
||||
ctrl->max_oob = 128;
|
||||
else if (ctrl->nand_version >= 0x0600)
|
||||
ctrl->max_oob = 64;
|
||||
|
@ -538,6 +592,15 @@ static int brcmnand_revision_init(struct brcmnand_controller *ctrl)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static void brcmnand_flash_dma_revision_init(struct brcmnand_controller *ctrl)
|
||||
{
|
||||
/* flash_dma register offsets */
|
||||
if (ctrl->nand_version >= 0x0703)
|
||||
ctrl->flash_dma_offsets = flash_dma_regs_v4;
|
||||
else
|
||||
ctrl->flash_dma_offsets = flash_dma_regs_v1;
|
||||
}
|
||||
|
||||
static inline u32 brcmnand_read_reg(struct brcmnand_controller *ctrl,
|
||||
enum brcmnand_reg reg)
|
||||
{
|
||||
|
@ -580,6 +643,54 @@ static inline void brcmnand_write_fc(struct brcmnand_controller *ctrl,
|
|||
__raw_writel(val, ctrl->nand_fc + word * 4);
|
||||
}
|
||||
|
||||
static void brcmnand_clear_ecc_addr(struct brcmnand_controller *ctrl)
|
||||
{
|
||||
|
||||
/* Clear error addresses */
|
||||
brcmnand_write_reg(ctrl, BRCMNAND_UNCORR_ADDR, 0);
|
||||
brcmnand_write_reg(ctrl, BRCMNAND_CORR_ADDR, 0);
|
||||
brcmnand_write_reg(ctrl, BRCMNAND_UNCORR_EXT_ADDR, 0);
|
||||
brcmnand_write_reg(ctrl, BRCMNAND_CORR_EXT_ADDR, 0);
|
||||
}
|
||||
|
||||
static u64 brcmnand_get_uncorrecc_addr(struct brcmnand_controller *ctrl)
|
||||
{
|
||||
u64 err_addr;
|
||||
|
||||
err_addr = brcmnand_read_reg(ctrl, BRCMNAND_UNCORR_ADDR);
|
||||
err_addr |= ((u64)(brcmnand_read_reg(ctrl,
|
||||
BRCMNAND_UNCORR_EXT_ADDR)
|
||||
& 0xffff) << 32);
|
||||
|
||||
return err_addr;
|
||||
}
|
||||
|
||||
static u64 brcmnand_get_correcc_addr(struct brcmnand_controller *ctrl)
|
||||
{
|
||||
u64 err_addr;
|
||||
|
||||
err_addr = brcmnand_read_reg(ctrl, BRCMNAND_CORR_ADDR);
|
||||
err_addr |= ((u64)(brcmnand_read_reg(ctrl,
|
||||
BRCMNAND_CORR_EXT_ADDR)
|
||||
& 0xffff) << 32);
|
||||
|
||||
return err_addr;
|
||||
}
|
||||
|
||||
static void brcmnand_set_cmd_addr(struct mtd_info *mtd, u64 addr)
|
||||
{
|
||||
struct nand_chip *chip = mtd_to_nand(mtd);
|
||||
struct brcmnand_host *host = nand_get_controller_data(chip);
|
||||
struct brcmnand_controller *ctrl = host->ctrl;
|
||||
|
||||
brcmnand_write_reg(ctrl, BRCMNAND_CMD_EXT_ADDRESS,
|
||||
(host->cs << 16) | ((addr >> 32) & 0xffff));
|
||||
(void)brcmnand_read_reg(ctrl, BRCMNAND_CMD_EXT_ADDRESS);
|
||||
brcmnand_write_reg(ctrl, BRCMNAND_CMD_ADDRESS,
|
||||
lower_32_bits(addr));
|
||||
(void)brcmnand_read_reg(ctrl, BRCMNAND_CMD_ADDRESS);
|
||||
}
|
||||
|
||||
static inline u16 brcmnand_cs_offset(struct brcmnand_controller *ctrl, int cs,
|
||||
enum brcmnand_cs_reg reg)
|
||||
{
|
||||
|
@ -612,7 +723,7 @@ static void brcmnand_wr_corr_thresh(struct brcmnand_host *host, u8 val)
|
|||
enum brcmnand_reg reg = BRCMNAND_CORR_THRESHOLD;
|
||||
int cs = host->cs;
|
||||
|
||||
if (ctrl->nand_version >= 0x0702)
|
||||
if (ctrl->nand_version == 0x0702)
|
||||
bits = 7;
|
||||
else if (ctrl->nand_version >= 0x0600)
|
||||
bits = 6;
|
||||
|
@ -666,7 +777,7 @@ enum {
|
|||
|
||||
static inline u32 brcmnand_spare_area_mask(struct brcmnand_controller *ctrl)
|
||||
{
|
||||
if (ctrl->nand_version >= 0x0702)
|
||||
if (ctrl->nand_version == 0x0702)
|
||||
return GENMASK(7, 0);
|
||||
else if (ctrl->nand_version >= 0x0600)
|
||||
return GENMASK(6, 0);
|
||||
|
@ -796,39 +907,44 @@ static inline void brcmnand_set_wp(struct brcmnand_controller *ctrl, bool en)
|
|||
* Flash DMA
|
||||
***********************************************************************/
|
||||
|
||||
enum flash_dma_reg {
|
||||
FLASH_DMA_REVISION = 0x00,
|
||||
FLASH_DMA_FIRST_DESC = 0x04,
|
||||
FLASH_DMA_FIRST_DESC_EXT = 0x08,
|
||||
FLASH_DMA_CTRL = 0x0c,
|
||||
FLASH_DMA_MODE = 0x10,
|
||||
FLASH_DMA_STATUS = 0x14,
|
||||
FLASH_DMA_INTERRUPT_DESC = 0x18,
|
||||
FLASH_DMA_INTERRUPT_DESC_EXT = 0x1c,
|
||||
FLASH_DMA_ERROR_STATUS = 0x20,
|
||||
FLASH_DMA_CURRENT_DESC = 0x24,
|
||||
FLASH_DMA_CURRENT_DESC_EXT = 0x28,
|
||||
};
|
||||
|
||||
static inline bool has_flash_dma(struct brcmnand_controller *ctrl)
|
||||
{
|
||||
return ctrl->flash_dma_base;
|
||||
}
|
||||
|
||||
static inline void disable_ctrl_irqs(struct brcmnand_controller *ctrl)
|
||||
{
|
||||
if (ctrl->pio_poll_mode)
|
||||
return;
|
||||
|
||||
if (has_flash_dma(ctrl)) {
|
||||
ctrl->flash_dma_base = 0;
|
||||
disable_irq(ctrl->dma_irq);
|
||||
}
|
||||
|
||||
disable_irq(ctrl->irq);
|
||||
ctrl->pio_poll_mode = true;
|
||||
}
|
||||
|
||||
static inline bool flash_dma_buf_ok(const void *buf)
|
||||
{
|
||||
return buf && !is_vmalloc_addr(buf) &&
|
||||
likely(IS_ALIGNED((uintptr_t)buf, 4));
|
||||
}
|
||||
|
||||
static inline void flash_dma_writel(struct brcmnand_controller *ctrl, u8 offs,
|
||||
u32 val)
|
||||
static inline void flash_dma_writel(struct brcmnand_controller *ctrl,
|
||||
enum flash_dma_reg dma_reg, u32 val)
|
||||
{
|
||||
u16 offs = ctrl->flash_dma_offsets[dma_reg];
|
||||
|
||||
brcmnand_writel(val, ctrl->flash_dma_base + offs);
|
||||
}
|
||||
|
||||
static inline u32 flash_dma_readl(struct brcmnand_controller *ctrl, u8 offs)
|
||||
static inline u32 flash_dma_readl(struct brcmnand_controller *ctrl,
|
||||
enum flash_dma_reg dma_reg)
|
||||
{
|
||||
u16 offs = ctrl->flash_dma_offsets[dma_reg];
|
||||
|
||||
return brcmnand_readl(ctrl->flash_dma_base + offs);
|
||||
}
|
||||
|
||||
|
@ -931,7 +1047,7 @@ static int brcmnand_bch_ooblayout_ecc(struct mtd_info *mtd, int section,
|
|||
if (section >= sectors)
|
||||
return -ERANGE;
|
||||
|
||||
oobregion->offset = (section * (sas + 1)) - chip->ecc.bytes;
|
||||
oobregion->offset = ((section + 1) * sas) - chip->ecc.bytes;
|
||||
oobregion->length = chip->ecc.bytes;
|
||||
|
||||
return 0;
|
||||
|
@ -1205,9 +1321,12 @@ static void brcmnand_send_cmd(struct brcmnand_host *host, int cmd)
|
|||
{
|
||||
struct brcmnand_controller *ctrl = host->ctrl;
|
||||
int ret;
|
||||
u64 cmd_addr;
|
||||
|
||||
cmd_addr = brcmnand_read_reg(ctrl, BRCMNAND_CMD_ADDRESS);
|
||||
|
||||
dev_dbg(ctrl->dev, "send native cmd %d addr 0x%llx\n", cmd, cmd_addr);
|
||||
|
||||
dev_dbg(ctrl->dev, "send native cmd %d addr_lo 0x%x\n", cmd,
|
||||
brcmnand_read_reg(ctrl, BRCMNAND_CMD_ADDRESS));
|
||||
BUG_ON(ctrl->cmd_pending != 0);
|
||||
ctrl->cmd_pending = cmd;
|
||||
|
||||
|
@ -1229,15 +1348,42 @@ static void brcmnand_cmd_ctrl(struct nand_chip *chip, int dat,
|
|||
/* intentionally left blank */
|
||||
}
|
||||
|
||||
static bool brcmstb_nand_wait_for_completion(struct nand_chip *chip)
|
||||
{
|
||||
struct brcmnand_host *host = nand_get_controller_data(chip);
|
||||
struct brcmnand_controller *ctrl = host->ctrl;
|
||||
struct mtd_info *mtd = nand_to_mtd(chip);
|
||||
bool err = false;
|
||||
int sts;
|
||||
|
||||
if (mtd->oops_panic_write) {
|
||||
/* switch to interrupt polling and PIO mode */
|
||||
disable_ctrl_irqs(ctrl);
|
||||
sts = bcmnand_ctrl_poll_status(ctrl, NAND_CTRL_RDY,
|
||||
NAND_CTRL_RDY, 0);
|
||||
err = (sts < 0) ? true : false;
|
||||
} else {
|
||||
unsigned long timeo = msecs_to_jiffies(
|
||||
NAND_POLL_STATUS_TIMEOUT_MS);
|
||||
/* wait for completion interrupt */
|
||||
sts = wait_for_completion_timeout(&ctrl->done, timeo);
|
||||
err = (sts <= 0) ? true : false;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int brcmnand_waitfunc(struct nand_chip *chip)
|
||||
{
|
||||
struct brcmnand_host *host = nand_get_controller_data(chip);
|
||||
struct brcmnand_controller *ctrl = host->ctrl;
|
||||
unsigned long timeo = msecs_to_jiffies(100);
|
||||
bool err = false;
|
||||
|
||||
dev_dbg(ctrl->dev, "wait on native cmd %d\n", ctrl->cmd_pending);
|
||||
if (ctrl->cmd_pending &&
|
||||
wait_for_completion_timeout(&ctrl->done, timeo) <= 0) {
|
||||
if (ctrl->cmd_pending)
|
||||
err = brcmstb_nand_wait_for_completion(chip);
|
||||
|
||||
if (err) {
|
||||
u32 cmd = brcmnand_read_reg(ctrl, BRCMNAND_CMD_START)
|
||||
>> brcmnand_cmd_shift(ctrl);
|
||||
|
||||
|
@ -1366,12 +1512,7 @@ static void brcmnand_cmdfunc(struct nand_chip *chip, unsigned command,
|
|||
if (!native_cmd)
|
||||
return;
|
||||
|
||||
brcmnand_write_reg(ctrl, BRCMNAND_CMD_EXT_ADDRESS,
|
||||
(host->cs << 16) | ((addr >> 32) & 0xffff));
|
||||
(void)brcmnand_read_reg(ctrl, BRCMNAND_CMD_EXT_ADDRESS);
|
||||
brcmnand_write_reg(ctrl, BRCMNAND_CMD_ADDRESS, lower_32_bits(addr));
|
||||
(void)brcmnand_read_reg(ctrl, BRCMNAND_CMD_ADDRESS);
|
||||
|
||||
brcmnand_set_cmd_addr(mtd, addr);
|
||||
brcmnand_send_cmd(host, native_cmd);
|
||||
brcmnand_waitfunc(chip);
|
||||
|
||||
|
@ -1589,20 +1730,10 @@ static int brcmnand_read_by_pio(struct mtd_info *mtd, struct nand_chip *chip,
|
|||
struct brcmnand_controller *ctrl = host->ctrl;
|
||||
int i, j, ret = 0;
|
||||
|
||||
/* Clear error addresses */
|
||||
brcmnand_write_reg(ctrl, BRCMNAND_UNCORR_ADDR, 0);
|
||||
brcmnand_write_reg(ctrl, BRCMNAND_CORR_ADDR, 0);
|
||||
brcmnand_write_reg(ctrl, BRCMNAND_UNCORR_EXT_ADDR, 0);
|
||||
brcmnand_write_reg(ctrl, BRCMNAND_CORR_EXT_ADDR, 0);
|
||||
|
||||
brcmnand_write_reg(ctrl, BRCMNAND_CMD_EXT_ADDRESS,
|
||||
(host->cs << 16) | ((addr >> 32) & 0xffff));
|
||||
(void)brcmnand_read_reg(ctrl, BRCMNAND_CMD_EXT_ADDRESS);
|
||||
brcmnand_clear_ecc_addr(ctrl);
|
||||
|
||||
for (i = 0; i < trans; i++, addr += FC_BYTES) {
|
||||
brcmnand_write_reg(ctrl, BRCMNAND_CMD_ADDRESS,
|
||||
lower_32_bits(addr));
|
||||
(void)brcmnand_read_reg(ctrl, BRCMNAND_CMD_ADDRESS);
|
||||
brcmnand_set_cmd_addr(mtd, addr);
|
||||
/* SPARE_AREA_READ does not use ECC, so just use PAGE_READ */
|
||||
brcmnand_send_cmd(host, CMD_PAGE_READ);
|
||||
brcmnand_waitfunc(chip);
|
||||
|
@ -1622,21 +1753,15 @@ static int brcmnand_read_by_pio(struct mtd_info *mtd, struct nand_chip *chip,
|
|||
host->hwcfg.sector_size_1k);
|
||||
|
||||
if (!ret) {
|
||||
*err_addr = brcmnand_read_reg(ctrl,
|
||||
BRCMNAND_UNCORR_ADDR) |
|
||||
((u64)(brcmnand_read_reg(ctrl,
|
||||
BRCMNAND_UNCORR_EXT_ADDR)
|
||||
& 0xffff) << 32);
|
||||
*err_addr = brcmnand_get_uncorrecc_addr(ctrl);
|
||||
|
||||
if (*err_addr)
|
||||
ret = -EBADMSG;
|
||||
}
|
||||
|
||||
if (!ret) {
|
||||
*err_addr = brcmnand_read_reg(ctrl,
|
||||
BRCMNAND_CORR_ADDR) |
|
||||
((u64)(brcmnand_read_reg(ctrl,
|
||||
BRCMNAND_CORR_EXT_ADDR)
|
||||
& 0xffff) << 32);
|
||||
*err_addr = brcmnand_get_correcc_addr(ctrl);
|
||||
|
||||
if (*err_addr)
|
||||
ret = -EUCLEAN;
|
||||
}
|
||||
|
@ -1703,7 +1828,7 @@ static int brcmnand_read(struct mtd_info *mtd, struct nand_chip *chip,
|
|||
dev_dbg(ctrl->dev, "read %llx -> %p\n", (unsigned long long)addr, buf);
|
||||
|
||||
try_dmaread:
|
||||
brcmnand_write_reg(ctrl, BRCMNAND_UNCORR_COUNT, 0);
|
||||
brcmnand_clear_ecc_addr(ctrl);
|
||||
|
||||
if (has_flash_dma(ctrl) && !oob && flash_dma_buf_ok(buf)) {
|
||||
err = brcmnand_dma_trans(host, addr, buf, trans * FC_BYTES,
|
||||
|
@ -1850,15 +1975,9 @@ static int brcmnand_write(struct mtd_info *mtd, struct nand_chip *chip,
|
|||
goto out;
|
||||
}
|
||||
|
||||
brcmnand_write_reg(ctrl, BRCMNAND_CMD_EXT_ADDRESS,
|
||||
(host->cs << 16) | ((addr >> 32) & 0xffff));
|
||||
(void)brcmnand_read_reg(ctrl, BRCMNAND_CMD_EXT_ADDRESS);
|
||||
|
||||
for (i = 0; i < trans; i++, addr += FC_BYTES) {
|
||||
/* full address MUST be set before populating FC */
|
||||
brcmnand_write_reg(ctrl, BRCMNAND_CMD_ADDRESS,
|
||||
lower_32_bits(addr));
|
||||
(void)brcmnand_read_reg(ctrl, BRCMNAND_CMD_ADDRESS);
|
||||
brcmnand_set_cmd_addr(mtd, addr);
|
||||
|
||||
if (buf) {
|
||||
brcmnand_soc_data_bus_prepare(ctrl->soc, false);
|
||||
|
@ -2136,6 +2255,17 @@ static int brcmnand_setup_dev(struct brcmnand_host *host)
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (chip->ecc.mode != NAND_ECC_NONE &&
|
||||
(!chip->ecc.size || !chip->ecc.strength)) {
|
||||
if (chip->base.eccreq.step_size && chip->base.eccreq.strength) {
|
||||
/* use detected ECC parameters */
|
||||
chip->ecc.size = chip->base.eccreq.step_size;
|
||||
chip->ecc.strength = chip->base.eccreq.strength;
|
||||
dev_info(ctrl->dev, "Using ECC step-size %d, strength %d\n",
|
||||
chip->ecc.size, chip->ecc.strength);
|
||||
}
|
||||
}
|
||||
|
||||
switch (chip->ecc.size) {
|
||||
case 512:
|
||||
if (chip->ecc.algo == NAND_ECC_HAMMING)
|
||||
|
@ -2395,6 +2525,7 @@ static const struct of_device_id brcmnand_of_match[] = {
|
|||
{ .compatible = "brcm,brcmnand-v7.0" },
|
||||
{ .compatible = "brcm,brcmnand-v7.1" },
|
||||
{ .compatible = "brcm,brcmnand-v7.2" },
|
||||
{ .compatible = "brcm,brcmnand-v7.3" },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, brcmnand_of_match);
|
||||
|
@ -2481,7 +2612,11 @@ int brcmnand_probe(struct platform_device *pdev, struct brcmnand_soc *soc)
|
|||
goto err;
|
||||
}
|
||||
|
||||
flash_dma_writel(ctrl, FLASH_DMA_MODE, 1); /* linked-list */
|
||||
/* initialize the dma version */
|
||||
brcmnand_flash_dma_revision_init(ctrl);
|
||||
|
||||
/* linked-list and stop on error */
|
||||
flash_dma_writel(ctrl, FLASH_DMA_MODE, FLASH_DMA_MODE_MASK);
|
||||
flash_dma_writel(ctrl, FLASH_DMA_ERROR_STATUS, 0);
|
||||
|
||||
/* Allocate descriptor(s) */
|
||||
|
|
|
@ -613,28 +613,20 @@ static int fsmc_exec_op(struct nand_chip *chip, const struct nand_operation *op,
|
|||
for (op_id = 0; op_id < op->ninstrs; op_id++) {
|
||||
instr = &op->instrs[op_id];
|
||||
|
||||
nand_op_trace(" ", instr);
|
||||
|
||||
switch (instr->type) {
|
||||
case NAND_OP_CMD_INSTR:
|
||||
pr_debug(" ->CMD [0x%02x]\n",
|
||||
instr->ctx.cmd.opcode);
|
||||
|
||||
writeb_relaxed(instr->ctx.cmd.opcode, host->cmd_va);
|
||||
break;
|
||||
|
||||
case NAND_OP_ADDR_INSTR:
|
||||
pr_debug(" ->ADDR [%d cyc]",
|
||||
instr->ctx.addr.naddrs);
|
||||
|
||||
for (i = 0; i < instr->ctx.addr.naddrs; i++)
|
||||
writeb_relaxed(instr->ctx.addr.addrs[i],
|
||||
host->addr_va);
|
||||
break;
|
||||
|
||||
case NAND_OP_DATA_IN_INSTR:
|
||||
pr_debug(" ->DATA_IN [%d B%s]\n", instr->ctx.data.len,
|
||||
instr->ctx.data.force_8bit ?
|
||||
", force 8-bit" : "");
|
||||
|
||||
if (host->mode == USE_DMA_ACCESS)
|
||||
fsmc_read_buf_dma(host, instr->ctx.data.buf.in,
|
||||
instr->ctx.data.len);
|
||||
|
@ -644,10 +636,6 @@ static int fsmc_exec_op(struct nand_chip *chip, const struct nand_operation *op,
|
|||
break;
|
||||
|
||||
case NAND_OP_DATA_OUT_INSTR:
|
||||
pr_debug(" ->DATA_OUT [%d B%s]\n", instr->ctx.data.len,
|
||||
instr->ctx.data.force_8bit ?
|
||||
", force 8-bit" : "");
|
||||
|
||||
if (host->mode == USE_DMA_ACCESS)
|
||||
fsmc_write_buf_dma(host,
|
||||
instr->ctx.data.buf.out,
|
||||
|
@ -658,9 +646,6 @@ static int fsmc_exec_op(struct nand_chip *chip, const struct nand_operation *op,
|
|||
break;
|
||||
|
||||
case NAND_OP_WAITRDY_INSTR:
|
||||
pr_debug(" ->WAITRDY [max %d ms]\n",
|
||||
instr->ctx.waitrdy.timeout_ms);
|
||||
|
||||
ret = nand_soft_waitrdy(chip,
|
||||
instr->ctx.waitrdy.timeout_ms);
|
||||
break;
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
obj-$(CONFIG_MTD_NAND_GPMI_NAND) += gpmi_nand.o
|
||||
gpmi_nand-objs += gpmi-nand.o
|
||||
gpmi_nand-objs += gpmi-lib.o
|
||||
|
|
|
@ -1,934 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* Freescale GPMI NAND Flash Driver
|
||||
*
|
||||
* Copyright (C) 2008-2011 Freescale Semiconductor, Inc.
|
||||
* Copyright (C) 2008 Embedded Alley Solutions, Inc.
|
||||
*/
|
||||
#include <linux/delay.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include "gpmi-nand.h"
|
||||
#include "gpmi-regs.h"
|
||||
#include "bch-regs.h"
|
||||
|
||||
/* Converts time to clock cycles */
|
||||
#define TO_CYCLES(duration, period) DIV_ROUND_UP_ULL(duration, period)
|
||||
|
||||
#define MXS_SET_ADDR 0x4
|
||||
#define MXS_CLR_ADDR 0x8
|
||||
/*
|
||||
* Clear the bit and poll it cleared. This is usually called with
|
||||
* a reset address and mask being either SFTRST(bit 31) or CLKGATE
|
||||
* (bit 30).
|
||||
*/
|
||||
static int clear_poll_bit(void __iomem *addr, u32 mask)
|
||||
{
|
||||
int timeout = 0x400;
|
||||
|
||||
/* clear the bit */
|
||||
writel(mask, addr + MXS_CLR_ADDR);
|
||||
|
||||
/*
|
||||
* SFTRST needs 3 GPMI clocks to settle, the reference manual
|
||||
* recommends to wait 1us.
|
||||
*/
|
||||
udelay(1);
|
||||
|
||||
/* poll the bit becoming clear */
|
||||
while ((readl(addr) & mask) && --timeout)
|
||||
/* nothing */;
|
||||
|
||||
return !timeout;
|
||||
}
|
||||
|
||||
#define MODULE_CLKGATE (1 << 30)
|
||||
#define MODULE_SFTRST (1 << 31)
|
||||
/*
|
||||
* The current mxs_reset_block() will do two things:
|
||||
* [1] enable the module.
|
||||
* [2] reset the module.
|
||||
*
|
||||
* In most of the cases, it's ok.
|
||||
* But in MX23, there is a hardware bug in the BCH block (see erratum #2847).
|
||||
* If you try to soft reset the BCH block, it becomes unusable until
|
||||
* the next hard reset. This case occurs in the NAND boot mode. When the board
|
||||
* boots by NAND, the ROM of the chip will initialize the BCH blocks itself.
|
||||
* So If the driver tries to reset the BCH again, the BCH will not work anymore.
|
||||
* You will see a DMA timeout in this case. The bug has been fixed
|
||||
* in the following chips, such as MX28.
|
||||
*
|
||||
* To avoid this bug, just add a new parameter `just_enable` for
|
||||
* the mxs_reset_block(), and rewrite it here.
|
||||
*/
|
||||
static int gpmi_reset_block(void __iomem *reset_addr, bool just_enable)
|
||||
{
|
||||
int ret;
|
||||
int timeout = 0x400;
|
||||
|
||||
/* clear and poll SFTRST */
|
||||
ret = clear_poll_bit(reset_addr, MODULE_SFTRST);
|
||||
if (unlikely(ret))
|
||||
goto error;
|
||||
|
||||
/* clear CLKGATE */
|
||||
writel(MODULE_CLKGATE, reset_addr + MXS_CLR_ADDR);
|
||||
|
||||
if (!just_enable) {
|
||||
/* set SFTRST to reset the block */
|
||||
writel(MODULE_SFTRST, reset_addr + MXS_SET_ADDR);
|
||||
udelay(1);
|
||||
|
||||
/* poll CLKGATE becoming set */
|
||||
while ((!(readl(reset_addr) & MODULE_CLKGATE)) && --timeout)
|
||||
/* nothing */;
|
||||
if (unlikely(!timeout))
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* clear and poll SFTRST */
|
||||
ret = clear_poll_bit(reset_addr, MODULE_SFTRST);
|
||||
if (unlikely(ret))
|
||||
goto error;
|
||||
|
||||
/* clear and poll CLKGATE */
|
||||
ret = clear_poll_bit(reset_addr, MODULE_CLKGATE);
|
||||
if (unlikely(ret))
|
||||
goto error;
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
pr_err("%s(%p): module reset timeout\n", __func__, reset_addr);
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
static int __gpmi_enable_clk(struct gpmi_nand_data *this, bool v)
|
||||
{
|
||||
struct clk *clk;
|
||||
int ret;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < GPMI_CLK_MAX; i++) {
|
||||
clk = this->resources.clock[i];
|
||||
if (!clk)
|
||||
break;
|
||||
|
||||
if (v) {
|
||||
ret = clk_prepare_enable(clk);
|
||||
if (ret)
|
||||
goto err_clk;
|
||||
} else {
|
||||
clk_disable_unprepare(clk);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
|
||||
err_clk:
|
||||
for (; i > 0; i--)
|
||||
clk_disable_unprepare(this->resources.clock[i - 1]);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int gpmi_enable_clk(struct gpmi_nand_data *this)
|
||||
{
|
||||
return __gpmi_enable_clk(this, true);
|
||||
}
|
||||
|
||||
int gpmi_disable_clk(struct gpmi_nand_data *this)
|
||||
{
|
||||
return __gpmi_enable_clk(this, false);
|
||||
}
|
||||
|
||||
int gpmi_init(struct gpmi_nand_data *this)
|
||||
{
|
||||
struct resources *r = &this->resources;
|
||||
int ret;
|
||||
|
||||
ret = gpmi_enable_clk(this);
|
||||
if (ret)
|
||||
return ret;
|
||||
ret = gpmi_reset_block(r->gpmi_regs, false);
|
||||
if (ret)
|
||||
goto err_out;
|
||||
|
||||
/*
|
||||
* Reset BCH here, too. We got failures otherwise :(
|
||||
* See later BCH reset for explanation of MX23 and MX28 handling
|
||||
*/
|
||||
ret = gpmi_reset_block(r->bch_regs, GPMI_IS_MXS(this));
|
||||
if (ret)
|
||||
goto err_out;
|
||||
|
||||
/* Choose NAND mode. */
|
||||
writel(BM_GPMI_CTRL1_GPMI_MODE, r->gpmi_regs + HW_GPMI_CTRL1_CLR);
|
||||
|
||||
/* Set the IRQ polarity. */
|
||||
writel(BM_GPMI_CTRL1_ATA_IRQRDY_POLARITY,
|
||||
r->gpmi_regs + HW_GPMI_CTRL1_SET);
|
||||
|
||||
/* Disable Write-Protection. */
|
||||
writel(BM_GPMI_CTRL1_DEV_RESET, r->gpmi_regs + HW_GPMI_CTRL1_SET);
|
||||
|
||||
/* Select BCH ECC. */
|
||||
writel(BM_GPMI_CTRL1_BCH_MODE, r->gpmi_regs + HW_GPMI_CTRL1_SET);
|
||||
|
||||
/*
|
||||
* Decouple the chip select from dma channel. We use dma0 for all
|
||||
* the chips.
|
||||
*/
|
||||
writel(BM_GPMI_CTRL1_DECOUPLE_CS, r->gpmi_regs + HW_GPMI_CTRL1_SET);
|
||||
|
||||
gpmi_disable_clk(this);
|
||||
return 0;
|
||||
err_out:
|
||||
gpmi_disable_clk(this);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* This function is very useful. It is called only when the bug occur. */
|
||||
void gpmi_dump_info(struct gpmi_nand_data *this)
|
||||
{
|
||||
struct resources *r = &this->resources;
|
||||
struct bch_geometry *geo = &this->bch_geometry;
|
||||
u32 reg;
|
||||
int i;
|
||||
|
||||
dev_err(this->dev, "Show GPMI registers :\n");
|
||||
for (i = 0; i <= HW_GPMI_DEBUG / 0x10 + 1; i++) {
|
||||
reg = readl(r->gpmi_regs + i * 0x10);
|
||||
dev_err(this->dev, "offset 0x%.3x : 0x%.8x\n", i * 0x10, reg);
|
||||
}
|
||||
|
||||
/* start to print out the BCH info */
|
||||
dev_err(this->dev, "Show BCH registers :\n");
|
||||
for (i = 0; i <= HW_BCH_VERSION / 0x10 + 1; i++) {
|
||||
reg = readl(r->bch_regs + i * 0x10);
|
||||
dev_err(this->dev, "offset 0x%.3x : 0x%.8x\n", i * 0x10, reg);
|
||||
}
|
||||
dev_err(this->dev, "BCH Geometry :\n"
|
||||
"GF length : %u\n"
|
||||
"ECC Strength : %u\n"
|
||||
"Page Size in Bytes : %u\n"
|
||||
"Metadata Size in Bytes : %u\n"
|
||||
"ECC Chunk Size in Bytes: %u\n"
|
||||
"ECC Chunk Count : %u\n"
|
||||
"Payload Size in Bytes : %u\n"
|
||||
"Auxiliary Size in Bytes: %u\n"
|
||||
"Auxiliary Status Offset: %u\n"
|
||||
"Block Mark Byte Offset : %u\n"
|
||||
"Block Mark Bit Offset : %u\n",
|
||||
geo->gf_len,
|
||||
geo->ecc_strength,
|
||||
geo->page_size,
|
||||
geo->metadata_size,
|
||||
geo->ecc_chunk_size,
|
||||
geo->ecc_chunk_count,
|
||||
geo->payload_size,
|
||||
geo->auxiliary_size,
|
||||
geo->auxiliary_status_offset,
|
||||
geo->block_mark_byte_offset,
|
||||
geo->block_mark_bit_offset);
|
||||
}
|
||||
|
||||
/* Configures the geometry for BCH. */
|
||||
int bch_set_geometry(struct gpmi_nand_data *this)
|
||||
{
|
||||
struct resources *r = &this->resources;
|
||||
struct bch_geometry *bch_geo = &this->bch_geometry;
|
||||
unsigned int block_count;
|
||||
unsigned int block_size;
|
||||
unsigned int metadata_size;
|
||||
unsigned int ecc_strength;
|
||||
unsigned int page_size;
|
||||
unsigned int gf_len;
|
||||
int ret;
|
||||
|
||||
ret = common_nfc_set_geometry(this);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
block_count = bch_geo->ecc_chunk_count - 1;
|
||||
block_size = bch_geo->ecc_chunk_size;
|
||||
metadata_size = bch_geo->metadata_size;
|
||||
ecc_strength = bch_geo->ecc_strength >> 1;
|
||||
page_size = bch_geo->page_size;
|
||||
gf_len = bch_geo->gf_len;
|
||||
|
||||
ret = gpmi_enable_clk(this);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* Due to erratum #2847 of the MX23, the BCH cannot be soft reset on this
|
||||
* chip, otherwise it will lock up. So we skip resetting BCH on the MX23.
|
||||
* and MX28.
|
||||
*/
|
||||
ret = gpmi_reset_block(r->bch_regs, GPMI_IS_MXS(this));
|
||||
if (ret)
|
||||
goto err_out;
|
||||
|
||||
/* Configure layout 0. */
|
||||
writel(BF_BCH_FLASH0LAYOUT0_NBLOCKS(block_count)
|
||||
| BF_BCH_FLASH0LAYOUT0_META_SIZE(metadata_size)
|
||||
| BF_BCH_FLASH0LAYOUT0_ECC0(ecc_strength, this)
|
||||
| BF_BCH_FLASH0LAYOUT0_GF(gf_len, this)
|
||||
| BF_BCH_FLASH0LAYOUT0_DATA0_SIZE(block_size, this),
|
||||
r->bch_regs + HW_BCH_FLASH0LAYOUT0);
|
||||
|
||||
writel(BF_BCH_FLASH0LAYOUT1_PAGE_SIZE(page_size)
|
||||
| BF_BCH_FLASH0LAYOUT1_ECCN(ecc_strength, this)
|
||||
| BF_BCH_FLASH0LAYOUT1_GF(gf_len, this)
|
||||
| BF_BCH_FLASH0LAYOUT1_DATAN_SIZE(block_size, this),
|
||||
r->bch_regs + HW_BCH_FLASH0LAYOUT1);
|
||||
|
||||
/* Set *all* chip selects to use layout 0. */
|
||||
writel(0, r->bch_regs + HW_BCH_LAYOUTSELECT);
|
||||
|
||||
/* Enable interrupts. */
|
||||
writel(BM_BCH_CTRL_COMPLETE_IRQ_EN,
|
||||
r->bch_regs + HW_BCH_CTRL_SET);
|
||||
|
||||
gpmi_disable_clk(this);
|
||||
return 0;
|
||||
err_out:
|
||||
gpmi_disable_clk(this);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* <1> Firstly, we should know what's the GPMI-clock means.
|
||||
* The GPMI-clock is the internal clock in the gpmi nand controller.
|
||||
* If you set 100MHz to gpmi nand controller, the GPMI-clock's period
|
||||
* is 10ns. Mark the GPMI-clock's period as GPMI-clock-period.
|
||||
*
|
||||
* <2> Secondly, we should know what's the frequency on the nand chip pins.
|
||||
* The frequency on the nand chip pins is derived from the GPMI-clock.
|
||||
* We can get it from the following equation:
|
||||
*
|
||||
* F = G / (DS + DH)
|
||||
*
|
||||
* F : the frequency on the nand chip pins.
|
||||
* G : the GPMI clock, such as 100MHz.
|
||||
* DS : GPMI_HW_GPMI_TIMING0:DATA_SETUP
|
||||
* DH : GPMI_HW_GPMI_TIMING0:DATA_HOLD
|
||||
*
|
||||
* <3> Thirdly, when the frequency on the nand chip pins is above 33MHz,
|
||||
* the nand EDO(extended Data Out) timing could be applied.
|
||||
* The GPMI implements a feedback read strobe to sample the read data.
|
||||
* The feedback read strobe can be delayed to support the nand EDO timing
|
||||
* where the read strobe may deasserts before the read data is valid, and
|
||||
* read data is valid for some time after read strobe.
|
||||
*
|
||||
* The following figure illustrates some aspects of a NAND Flash read:
|
||||
*
|
||||
* |<---tREA---->|
|
||||
* | |
|
||||
* | | |
|
||||
* |<--tRP-->| |
|
||||
* | | |
|
||||
* __ ___|__________________________________
|
||||
* RDN \________/ |
|
||||
* |
|
||||
* /---------\
|
||||
* Read Data --------------< >---------
|
||||
* \---------/
|
||||
* | |
|
||||
* |<-D->|
|
||||
* FeedbackRDN ________ ____________
|
||||
* \___________/
|
||||
*
|
||||
* D stands for delay, set in the HW_GPMI_CTRL1:RDN_DELAY.
|
||||
*
|
||||
*
|
||||
* <4> Now, we begin to describe how to compute the right RDN_DELAY.
|
||||
*
|
||||
* 4.1) From the aspect of the nand chip pins:
|
||||
* Delay = (tREA + C - tRP) {1}
|
||||
*
|
||||
* tREA : the maximum read access time.
|
||||
* C : a constant to adjust the delay. default is 4000ps.
|
||||
* tRP : the read pulse width, which is exactly:
|
||||
* tRP = (GPMI-clock-period) * DATA_SETUP
|
||||
*
|
||||
* 4.2) From the aspect of the GPMI nand controller:
|
||||
* Delay = RDN_DELAY * 0.125 * RP {2}
|
||||
*
|
||||
* RP : the DLL reference period.
|
||||
* if (GPMI-clock-period > DLL_THRETHOLD)
|
||||
* RP = GPMI-clock-period / 2;
|
||||
* else
|
||||
* RP = GPMI-clock-period;
|
||||
*
|
||||
* Set the HW_GPMI_CTRL1:HALF_PERIOD if GPMI-clock-period
|
||||
* is greater DLL_THRETHOLD. In other SOCs, the DLL_THRETHOLD
|
||||
* is 16000ps, but in mx6q, we use 12000ps.
|
||||
*
|
||||
* 4.3) since {1} equals {2}, we get:
|
||||
*
|
||||
* (tREA + 4000 - tRP) * 8
|
||||
* RDN_DELAY = ----------------------- {3}
|
||||
* RP
|
||||
*/
|
||||
static void gpmi_nfc_compute_timings(struct gpmi_nand_data *this,
|
||||
const struct nand_sdr_timings *sdr)
|
||||
{
|
||||
struct gpmi_nfc_hardware_timing *hw = &this->hw;
|
||||
unsigned int dll_threshold_ps = this->devdata->max_chain_delay;
|
||||
unsigned int period_ps, reference_period_ps;
|
||||
unsigned int data_setup_cycles, data_hold_cycles, addr_setup_cycles;
|
||||
unsigned int tRP_ps;
|
||||
bool use_half_period;
|
||||
int sample_delay_ps, sample_delay_factor;
|
||||
u16 busy_timeout_cycles;
|
||||
u8 wrn_dly_sel;
|
||||
|
||||
if (sdr->tRC_min >= 30000) {
|
||||
/* ONFI non-EDO modes [0-3] */
|
||||
hw->clk_rate = 22000000;
|
||||
wrn_dly_sel = BV_GPMI_CTRL1_WRN_DLY_SEL_4_TO_8NS;
|
||||
} else if (sdr->tRC_min >= 25000) {
|
||||
/* ONFI EDO mode 4 */
|
||||
hw->clk_rate = 80000000;
|
||||
wrn_dly_sel = BV_GPMI_CTRL1_WRN_DLY_SEL_NO_DELAY;
|
||||
} else {
|
||||
/* ONFI EDO mode 5 */
|
||||
hw->clk_rate = 100000000;
|
||||
wrn_dly_sel = BV_GPMI_CTRL1_WRN_DLY_SEL_NO_DELAY;
|
||||
}
|
||||
|
||||
/* SDR core timings are given in picoseconds */
|
||||
period_ps = div_u64((u64)NSEC_PER_SEC * 1000, hw->clk_rate);
|
||||
|
||||
addr_setup_cycles = TO_CYCLES(sdr->tALS_min, period_ps);
|
||||
data_setup_cycles = TO_CYCLES(sdr->tDS_min, period_ps);
|
||||
data_hold_cycles = TO_CYCLES(sdr->tDH_min, period_ps);
|
||||
busy_timeout_cycles = TO_CYCLES(sdr->tWB_max + sdr->tR_max, period_ps);
|
||||
|
||||
hw->timing0 = BF_GPMI_TIMING0_ADDRESS_SETUP(addr_setup_cycles) |
|
||||
BF_GPMI_TIMING0_DATA_HOLD(data_hold_cycles) |
|
||||
BF_GPMI_TIMING0_DATA_SETUP(data_setup_cycles);
|
||||
hw->timing1 = BF_GPMI_TIMING1_BUSY_TIMEOUT(busy_timeout_cycles * 4096);
|
||||
|
||||
/*
|
||||
* Derive NFC ideal delay from {3}:
|
||||
*
|
||||
* (tREA + 4000 - tRP) * 8
|
||||
* RDN_DELAY = -----------------------
|
||||
* RP
|
||||
*/
|
||||
if (period_ps > dll_threshold_ps) {
|
||||
use_half_period = true;
|
||||
reference_period_ps = period_ps / 2;
|
||||
} else {
|
||||
use_half_period = false;
|
||||
reference_period_ps = period_ps;
|
||||
}
|
||||
|
||||
tRP_ps = data_setup_cycles * period_ps;
|
||||
sample_delay_ps = (sdr->tREA_max + 4000 - tRP_ps) * 8;
|
||||
if (sample_delay_ps > 0)
|
||||
sample_delay_factor = sample_delay_ps / reference_period_ps;
|
||||
else
|
||||
sample_delay_factor = 0;
|
||||
|
||||
hw->ctrl1n = BF_GPMI_CTRL1_WRN_DLY_SEL(wrn_dly_sel);
|
||||
if (sample_delay_factor)
|
||||
hw->ctrl1n |= BF_GPMI_CTRL1_RDN_DELAY(sample_delay_factor) |
|
||||
BM_GPMI_CTRL1_DLL_ENABLE |
|
||||
(use_half_period ? BM_GPMI_CTRL1_HALF_PERIOD : 0);
|
||||
}
|
||||
|
||||
void gpmi_nfc_apply_timings(struct gpmi_nand_data *this)
|
||||
{
|
||||
struct gpmi_nfc_hardware_timing *hw = &this->hw;
|
||||
struct resources *r = &this->resources;
|
||||
void __iomem *gpmi_regs = r->gpmi_regs;
|
||||
unsigned int dll_wait_time_us;
|
||||
|
||||
clk_set_rate(r->clock[0], hw->clk_rate);
|
||||
|
||||
writel(hw->timing0, gpmi_regs + HW_GPMI_TIMING0);
|
||||
writel(hw->timing1, gpmi_regs + HW_GPMI_TIMING1);
|
||||
|
||||
/*
|
||||
* Clear several CTRL1 fields, DLL must be disabled when setting
|
||||
* RDN_DELAY or HALF_PERIOD.
|
||||
*/
|
||||
writel(BM_GPMI_CTRL1_CLEAR_MASK, gpmi_regs + HW_GPMI_CTRL1_CLR);
|
||||
writel(hw->ctrl1n, gpmi_regs + HW_GPMI_CTRL1_SET);
|
||||
|
||||
/* Wait 64 clock cycles before using the GPMI after enabling the DLL */
|
||||
dll_wait_time_us = USEC_PER_SEC / hw->clk_rate * 64;
|
||||
if (!dll_wait_time_us)
|
||||
dll_wait_time_us = 1;
|
||||
|
||||
/* Wait for the DLL to settle. */
|
||||
udelay(dll_wait_time_us);
|
||||
}
|
||||
|
||||
int gpmi_setup_data_interface(struct nand_chip *chip, int chipnr,
|
||||
const struct nand_data_interface *conf)
|
||||
{
|
||||
struct gpmi_nand_data *this = nand_get_controller_data(chip);
|
||||
const struct nand_sdr_timings *sdr;
|
||||
|
||||
/* Retrieve required NAND timings */
|
||||
sdr = nand_get_sdr_timings(conf);
|
||||
if (IS_ERR(sdr))
|
||||
return PTR_ERR(sdr);
|
||||
|
||||
/* Only MX6 GPMI controller can reach EDO timings */
|
||||
if (sdr->tRC_min <= 25000 && !GPMI_IS_MX6(this))
|
||||
return -ENOTSUPP;
|
||||
|
||||
/* Stop here if this call was just a check */
|
||||
if (chipnr < 0)
|
||||
return 0;
|
||||
|
||||
/* Do the actual derivation of the controller timings */
|
||||
gpmi_nfc_compute_timings(this, sdr);
|
||||
|
||||
this->hw.must_apply_timings = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Clears a BCH interrupt. */
|
||||
void gpmi_clear_bch(struct gpmi_nand_data *this)
|
||||
{
|
||||
struct resources *r = &this->resources;
|
||||
writel(BM_BCH_CTRL_COMPLETE_IRQ, r->bch_regs + HW_BCH_CTRL_CLR);
|
||||
}
|
||||
|
||||
/* Returns the Ready/Busy status of the given chip. */
|
||||
int gpmi_is_ready(struct gpmi_nand_data *this, unsigned chip)
|
||||
{
|
||||
struct resources *r = &this->resources;
|
||||
uint32_t mask = 0;
|
||||
uint32_t reg = 0;
|
||||
|
||||
if (GPMI_IS_MX23(this)) {
|
||||
mask = MX23_BM_GPMI_DEBUG_READY0 << chip;
|
||||
reg = readl(r->gpmi_regs + HW_GPMI_DEBUG);
|
||||
} else if (GPMI_IS_MX28(this) || GPMI_IS_MX6(this)) {
|
||||
/*
|
||||
* In the imx6, all the ready/busy pins are bound
|
||||
* together. So we only need to check chip 0.
|
||||
*/
|
||||
if (GPMI_IS_MX6(this))
|
||||
chip = 0;
|
||||
|
||||
/* MX28 shares the same R/B register as MX6Q. */
|
||||
mask = MX28_BF_GPMI_STAT_READY_BUSY(1 << chip);
|
||||
reg = readl(r->gpmi_regs + HW_GPMI_STAT);
|
||||
} else
|
||||
dev_err(this->dev, "unknown arch.\n");
|
||||
return reg & mask;
|
||||
}
|
||||
|
||||
int gpmi_send_command(struct gpmi_nand_data *this)
|
||||
{
|
||||
struct dma_chan *channel = get_dma_chan(this);
|
||||
struct dma_async_tx_descriptor *desc;
|
||||
struct scatterlist *sgl;
|
||||
int chip = this->current_chip;
|
||||
int ret;
|
||||
u32 pio[3];
|
||||
|
||||
/* [1] send out the PIO words */
|
||||
pio[0] = BF_GPMI_CTRL0_COMMAND_MODE(BV_GPMI_CTRL0_COMMAND_MODE__WRITE)
|
||||
| BM_GPMI_CTRL0_WORD_LENGTH
|
||||
| BF_GPMI_CTRL0_CS(chip, this)
|
||||
| BF_GPMI_CTRL0_LOCK_CS(LOCK_CS_ENABLE, this)
|
||||
| BF_GPMI_CTRL0_ADDRESS(BV_GPMI_CTRL0_ADDRESS__NAND_CLE)
|
||||
| BM_GPMI_CTRL0_ADDRESS_INCREMENT
|
||||
| BF_GPMI_CTRL0_XFER_COUNT(this->command_length);
|
||||
pio[1] = pio[2] = 0;
|
||||
desc = dmaengine_prep_slave_sg(channel,
|
||||
(struct scatterlist *)pio,
|
||||
ARRAY_SIZE(pio), DMA_TRANS_NONE, 0);
|
||||
if (!desc)
|
||||
return -EINVAL;
|
||||
|
||||
/* [2] send out the COMMAND + ADDRESS string stored in @buffer */
|
||||
sgl = &this->cmd_sgl;
|
||||
|
||||
sg_init_one(sgl, this->cmd_buffer, this->command_length);
|
||||
dma_map_sg(this->dev, sgl, 1, DMA_TO_DEVICE);
|
||||
desc = dmaengine_prep_slave_sg(channel,
|
||||
sgl, 1, DMA_MEM_TO_DEV,
|
||||
DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
|
||||
if (!desc)
|
||||
return -EINVAL;
|
||||
|
||||
/* [3] submit the DMA */
|
||||
ret = start_dma_without_bch_irq(this, desc);
|
||||
|
||||
dma_unmap_sg(this->dev, sgl, 1, DMA_TO_DEVICE);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int gpmi_send_data(struct gpmi_nand_data *this, const void *buf, int len)
|
||||
{
|
||||
struct dma_async_tx_descriptor *desc;
|
||||
struct dma_chan *channel = get_dma_chan(this);
|
||||
int chip = this->current_chip;
|
||||
int ret;
|
||||
uint32_t command_mode;
|
||||
uint32_t address;
|
||||
u32 pio[2];
|
||||
|
||||
/* [1] PIO */
|
||||
command_mode = BV_GPMI_CTRL0_COMMAND_MODE__WRITE;
|
||||
address = BV_GPMI_CTRL0_ADDRESS__NAND_DATA;
|
||||
|
||||
pio[0] = BF_GPMI_CTRL0_COMMAND_MODE(command_mode)
|
||||
| BM_GPMI_CTRL0_WORD_LENGTH
|
||||
| BF_GPMI_CTRL0_CS(chip, this)
|
||||
| BF_GPMI_CTRL0_LOCK_CS(LOCK_CS_ENABLE, this)
|
||||
| BF_GPMI_CTRL0_ADDRESS(address)
|
||||
| BF_GPMI_CTRL0_XFER_COUNT(len);
|
||||
pio[1] = 0;
|
||||
desc = dmaengine_prep_slave_sg(channel, (struct scatterlist *)pio,
|
||||
ARRAY_SIZE(pio), DMA_TRANS_NONE, 0);
|
||||
if (!desc)
|
||||
return -EINVAL;
|
||||
|
||||
/* [2] send DMA request */
|
||||
prepare_data_dma(this, buf, len, DMA_TO_DEVICE);
|
||||
desc = dmaengine_prep_slave_sg(channel, &this->data_sgl,
|
||||
1, DMA_MEM_TO_DEV,
|
||||
DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
|
||||
if (!desc)
|
||||
return -EINVAL;
|
||||
|
||||
/* [3] submit the DMA */
|
||||
ret = start_dma_without_bch_irq(this, desc);
|
||||
|
||||
dma_unmap_sg(this->dev, &this->data_sgl, 1, DMA_TO_DEVICE);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int gpmi_read_data(struct gpmi_nand_data *this, void *buf, int len)
|
||||
{
|
||||
struct dma_async_tx_descriptor *desc;
|
||||
struct dma_chan *channel = get_dma_chan(this);
|
||||
int chip = this->current_chip;
|
||||
int ret;
|
||||
u32 pio[2];
|
||||
bool direct;
|
||||
|
||||
/* [1] : send PIO */
|
||||
pio[0] = BF_GPMI_CTRL0_COMMAND_MODE(BV_GPMI_CTRL0_COMMAND_MODE__READ)
|
||||
| BM_GPMI_CTRL0_WORD_LENGTH
|
||||
| BF_GPMI_CTRL0_CS(chip, this)
|
||||
| BF_GPMI_CTRL0_LOCK_CS(LOCK_CS_ENABLE, this)
|
||||
| BF_GPMI_CTRL0_ADDRESS(BV_GPMI_CTRL0_ADDRESS__NAND_DATA)
|
||||
| BF_GPMI_CTRL0_XFER_COUNT(len);
|
||||
pio[1] = 0;
|
||||
desc = dmaengine_prep_slave_sg(channel,
|
||||
(struct scatterlist *)pio,
|
||||
ARRAY_SIZE(pio), DMA_TRANS_NONE, 0);
|
||||
if (!desc)
|
||||
return -EINVAL;
|
||||
|
||||
/* [2] : send DMA request */
|
||||
direct = prepare_data_dma(this, buf, len, DMA_FROM_DEVICE);
|
||||
desc = dmaengine_prep_slave_sg(channel, &this->data_sgl,
|
||||
1, DMA_DEV_TO_MEM,
|
||||
DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
|
||||
if (!desc)
|
||||
return -EINVAL;
|
||||
|
||||
/* [3] : submit the DMA */
|
||||
|
||||
ret = start_dma_without_bch_irq(this, desc);
|
||||
|
||||
dma_unmap_sg(this->dev, &this->data_sgl, 1, DMA_FROM_DEVICE);
|
||||
if (!direct)
|
||||
memcpy(buf, this->data_buffer_dma, len);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int gpmi_send_page(struct gpmi_nand_data *this,
|
||||
dma_addr_t payload, dma_addr_t auxiliary)
|
||||
{
|
||||
struct bch_geometry *geo = &this->bch_geometry;
|
||||
uint32_t command_mode;
|
||||
uint32_t address;
|
||||
uint32_t ecc_command;
|
||||
uint32_t buffer_mask;
|
||||
struct dma_async_tx_descriptor *desc;
|
||||
struct dma_chan *channel = get_dma_chan(this);
|
||||
int chip = this->current_chip;
|
||||
u32 pio[6];
|
||||
|
||||
/* A DMA descriptor that does an ECC page read. */
|
||||
command_mode = BV_GPMI_CTRL0_COMMAND_MODE__WRITE;
|
||||
address = BV_GPMI_CTRL0_ADDRESS__NAND_DATA;
|
||||
ecc_command = BV_GPMI_ECCCTRL_ECC_CMD__BCH_ENCODE;
|
||||
buffer_mask = BV_GPMI_ECCCTRL_BUFFER_MASK__BCH_PAGE |
|
||||
BV_GPMI_ECCCTRL_BUFFER_MASK__BCH_AUXONLY;
|
||||
|
||||
pio[0] = BF_GPMI_CTRL0_COMMAND_MODE(command_mode)
|
||||
| BM_GPMI_CTRL0_WORD_LENGTH
|
||||
| BF_GPMI_CTRL0_CS(chip, this)
|
||||
| BF_GPMI_CTRL0_LOCK_CS(LOCK_CS_ENABLE, this)
|
||||
| BF_GPMI_CTRL0_ADDRESS(address)
|
||||
| BF_GPMI_CTRL0_XFER_COUNT(0);
|
||||
pio[1] = 0;
|
||||
pio[2] = BM_GPMI_ECCCTRL_ENABLE_ECC
|
||||
| BF_GPMI_ECCCTRL_ECC_CMD(ecc_command)
|
||||
| BF_GPMI_ECCCTRL_BUFFER_MASK(buffer_mask);
|
||||
pio[3] = geo->page_size;
|
||||
pio[4] = payload;
|
||||
pio[5] = auxiliary;
|
||||
|
||||
desc = dmaengine_prep_slave_sg(channel,
|
||||
(struct scatterlist *)pio,
|
||||
ARRAY_SIZE(pio), DMA_TRANS_NONE,
|
||||
DMA_CTRL_ACK);
|
||||
if (!desc)
|
||||
return -EINVAL;
|
||||
|
||||
return start_dma_with_bch_irq(this, desc);
|
||||
}
|
||||
|
||||
int gpmi_read_page(struct gpmi_nand_data *this,
|
||||
dma_addr_t payload, dma_addr_t auxiliary)
|
||||
{
|
||||
struct bch_geometry *geo = &this->bch_geometry;
|
||||
uint32_t command_mode;
|
||||
uint32_t address;
|
||||
uint32_t ecc_command;
|
||||
uint32_t buffer_mask;
|
||||
struct dma_async_tx_descriptor *desc;
|
||||
struct dma_chan *channel = get_dma_chan(this);
|
||||
int chip = this->current_chip;
|
||||
u32 pio[6];
|
||||
|
||||
/* [1] Wait for the chip to report ready. */
|
||||
command_mode = BV_GPMI_CTRL0_COMMAND_MODE__WAIT_FOR_READY;
|
||||
address = BV_GPMI_CTRL0_ADDRESS__NAND_DATA;
|
||||
|
||||
pio[0] = BF_GPMI_CTRL0_COMMAND_MODE(command_mode)
|
||||
| BM_GPMI_CTRL0_WORD_LENGTH
|
||||
| BF_GPMI_CTRL0_CS(chip, this)
|
||||
| BF_GPMI_CTRL0_LOCK_CS(LOCK_CS_ENABLE, this)
|
||||
| BF_GPMI_CTRL0_ADDRESS(address)
|
||||
| BF_GPMI_CTRL0_XFER_COUNT(0);
|
||||
pio[1] = 0;
|
||||
desc = dmaengine_prep_slave_sg(channel,
|
||||
(struct scatterlist *)pio, 2,
|
||||
DMA_TRANS_NONE, 0);
|
||||
if (!desc)
|
||||
return -EINVAL;
|
||||
|
||||
/* [2] Enable the BCH block and read. */
|
||||
command_mode = BV_GPMI_CTRL0_COMMAND_MODE__READ;
|
||||
address = BV_GPMI_CTRL0_ADDRESS__NAND_DATA;
|
||||
ecc_command = BV_GPMI_ECCCTRL_ECC_CMD__BCH_DECODE;
|
||||
buffer_mask = BV_GPMI_ECCCTRL_BUFFER_MASK__BCH_PAGE
|
||||
| BV_GPMI_ECCCTRL_BUFFER_MASK__BCH_AUXONLY;
|
||||
|
||||
pio[0] = BF_GPMI_CTRL0_COMMAND_MODE(command_mode)
|
||||
| BM_GPMI_CTRL0_WORD_LENGTH
|
||||
| BF_GPMI_CTRL0_CS(chip, this)
|
||||
| BF_GPMI_CTRL0_LOCK_CS(LOCK_CS_ENABLE, this)
|
||||
| BF_GPMI_CTRL0_ADDRESS(address)
|
||||
| BF_GPMI_CTRL0_XFER_COUNT(geo->page_size);
|
||||
|
||||
pio[1] = 0;
|
||||
pio[2] = BM_GPMI_ECCCTRL_ENABLE_ECC
|
||||
| BF_GPMI_ECCCTRL_ECC_CMD(ecc_command)
|
||||
| BF_GPMI_ECCCTRL_BUFFER_MASK(buffer_mask);
|
||||
pio[3] = geo->page_size;
|
||||
pio[4] = payload;
|
||||
pio[5] = auxiliary;
|
||||
desc = dmaengine_prep_slave_sg(channel,
|
||||
(struct scatterlist *)pio,
|
||||
ARRAY_SIZE(pio), DMA_TRANS_NONE,
|
||||
DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
|
||||
if (!desc)
|
||||
return -EINVAL;
|
||||
|
||||
/* [3] Disable the BCH block */
|
||||
command_mode = BV_GPMI_CTRL0_COMMAND_MODE__WAIT_FOR_READY;
|
||||
address = BV_GPMI_CTRL0_ADDRESS__NAND_DATA;
|
||||
|
||||
pio[0] = BF_GPMI_CTRL0_COMMAND_MODE(command_mode)
|
||||
| BM_GPMI_CTRL0_WORD_LENGTH
|
||||
| BF_GPMI_CTRL0_CS(chip, this)
|
||||
| BF_GPMI_CTRL0_LOCK_CS(LOCK_CS_ENABLE, this)
|
||||
| BF_GPMI_CTRL0_ADDRESS(address)
|
||||
| BF_GPMI_CTRL0_XFER_COUNT(geo->page_size);
|
||||
pio[1] = 0;
|
||||
pio[2] = 0; /* clear GPMI_HW_GPMI_ECCCTRL, disable the BCH. */
|
||||
desc = dmaengine_prep_slave_sg(channel,
|
||||
(struct scatterlist *)pio, 3,
|
||||
DMA_TRANS_NONE,
|
||||
DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
|
||||
if (!desc)
|
||||
return -EINVAL;
|
||||
|
||||
/* [4] submit the DMA */
|
||||
return start_dma_with_bch_irq(this, desc);
|
||||
}
|
||||
|
||||
/**
|
||||
* gpmi_copy_bits - copy bits from one memory region to another
|
||||
* @dst: destination buffer
|
||||
* @dst_bit_off: bit offset we're starting to write at
|
||||
* @src: source buffer
|
||||
* @src_bit_off: bit offset we're starting to read from
|
||||
* @nbits: number of bits to copy
|
||||
*
|
||||
* This functions copies bits from one memory region to another, and is used by
|
||||
* the GPMI driver to copy ECC sections which are not guaranteed to be byte
|
||||
* aligned.
|
||||
*
|
||||
* src and dst should not overlap.
|
||||
*
|
||||
*/
|
||||
void gpmi_copy_bits(u8 *dst, size_t dst_bit_off,
|
||||
const u8 *src, size_t src_bit_off,
|
||||
size_t nbits)
|
||||
{
|
||||
size_t i;
|
||||
size_t nbytes;
|
||||
u32 src_buffer = 0;
|
||||
size_t bits_in_src_buffer = 0;
|
||||
|
||||
if (!nbits)
|
||||
return;
|
||||
|
||||
/*
|
||||
* Move src and dst pointers to the closest byte pointer and store bit
|
||||
* offsets within a byte.
|
||||
*/
|
||||
src += src_bit_off / 8;
|
||||
src_bit_off %= 8;
|
||||
|
||||
dst += dst_bit_off / 8;
|
||||
dst_bit_off %= 8;
|
||||
|
||||
/*
|
||||
* Initialize the src_buffer value with bits available in the first
|
||||
* byte of data so that we end up with a byte aligned src pointer.
|
||||
*/
|
||||
if (src_bit_off) {
|
||||
src_buffer = src[0] >> src_bit_off;
|
||||
if (nbits >= (8 - src_bit_off)) {
|
||||
bits_in_src_buffer += 8 - src_bit_off;
|
||||
} else {
|
||||
src_buffer &= GENMASK(nbits - 1, 0);
|
||||
bits_in_src_buffer += nbits;
|
||||
}
|
||||
nbits -= bits_in_src_buffer;
|
||||
src++;
|
||||
}
|
||||
|
||||
/* Calculate the number of bytes that can be copied from src to dst. */
|
||||
nbytes = nbits / 8;
|
||||
|
||||
/* Try to align dst to a byte boundary. */
|
||||
if (dst_bit_off) {
|
||||
if (bits_in_src_buffer < (8 - dst_bit_off) && nbytes) {
|
||||
src_buffer |= src[0] << bits_in_src_buffer;
|
||||
bits_in_src_buffer += 8;
|
||||
src++;
|
||||
nbytes--;
|
||||
}
|
||||
|
||||
if (bits_in_src_buffer >= (8 - dst_bit_off)) {
|
||||
dst[0] &= GENMASK(dst_bit_off - 1, 0);
|
||||
dst[0] |= src_buffer << dst_bit_off;
|
||||
src_buffer >>= (8 - dst_bit_off);
|
||||
bits_in_src_buffer -= (8 - dst_bit_off);
|
||||
dst_bit_off = 0;
|
||||
dst++;
|
||||
if (bits_in_src_buffer > 7) {
|
||||
bits_in_src_buffer -= 8;
|
||||
dst[0] = src_buffer;
|
||||
dst++;
|
||||
src_buffer >>= 8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!bits_in_src_buffer && !dst_bit_off) {
|
||||
/*
|
||||
* Both src and dst pointers are byte aligned, thus we can
|
||||
* just use the optimized memcpy function.
|
||||
*/
|
||||
if (nbytes)
|
||||
memcpy(dst, src, nbytes);
|
||||
} else {
|
||||
/*
|
||||
* src buffer is not byte aligned, hence we have to copy each
|
||||
* src byte to the src_buffer variable before extracting a byte
|
||||
* to store in dst.
|
||||
*/
|
||||
for (i = 0; i < nbytes; i++) {
|
||||
src_buffer |= src[i] << bits_in_src_buffer;
|
||||
dst[i] = src_buffer;
|
||||
src_buffer >>= 8;
|
||||
}
|
||||
}
|
||||
/* Update dst and src pointers */
|
||||
dst += nbytes;
|
||||
src += nbytes;
|
||||
|
||||
/*
|
||||
* nbits is the number of remaining bits. It should not exceed 8 as
|
||||
* we've already copied as much bytes as possible.
|
||||
*/
|
||||
nbits %= 8;
|
||||
|
||||
/*
|
||||
* If there's no more bits to copy to the destination and src buffer
|
||||
* was already byte aligned, then we're done.
|
||||
*/
|
||||
if (!nbits && !bits_in_src_buffer)
|
||||
return;
|
||||
|
||||
/* Copy the remaining bits to src_buffer */
|
||||
if (nbits)
|
||||
src_buffer |= (*src & GENMASK(nbits - 1, 0)) <<
|
||||
bits_in_src_buffer;
|
||||
bits_in_src_buffer += nbits;
|
||||
|
||||
/*
|
||||
* In case there were not enough bits to get a byte aligned dst buffer
|
||||
* prepare the src_buffer variable to match the dst organization (shift
|
||||
* src_buffer by dst_bit_off and retrieve the least significant bits
|
||||
* from dst).
|
||||
*/
|
||||
if (dst_bit_off)
|
||||
src_buffer = (src_buffer << dst_bit_off) |
|
||||
(*dst & GENMASK(dst_bit_off - 1, 0));
|
||||
bits_in_src_buffer += dst_bit_off;
|
||||
|
||||
/*
|
||||
* Keep most significant bits from dst if we end up with an unaligned
|
||||
* number of bits.
|
||||
*/
|
||||
nbytes = bits_in_src_buffer / 8;
|
||||
if (bits_in_src_buffer % 8) {
|
||||
src_buffer |= (dst[nbytes] &
|
||||
GENMASK(7, bits_in_src_buffer % 8)) <<
|
||||
(nbytes * 8);
|
||||
nbytes++;
|
||||
}
|
||||
|
||||
/* Copy the remaining bytes to dst */
|
||||
for (i = 0; i < nbytes; i++) {
|
||||
dst[i] = src_buffer;
|
||||
src_buffer >>= 8;
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -103,6 +103,14 @@ struct gpmi_nfc_hardware_timing {
|
|||
u32 ctrl1n;
|
||||
};
|
||||
|
||||
#define GPMI_MAX_TRANSFERS 8
|
||||
|
||||
struct gpmi_transfer {
|
||||
u8 cmdbuf[8];
|
||||
struct scatterlist sgl;
|
||||
enum dma_data_direction direction;
|
||||
};
|
||||
|
||||
struct gpmi_nand_data {
|
||||
/* Devdata */
|
||||
const struct gpmi_devdata *devdata;
|
||||
|
@ -126,25 +134,18 @@ struct gpmi_nand_data {
|
|||
struct boot_rom_geometry rom_geometry;
|
||||
|
||||
/* MTD / NAND */
|
||||
struct nand_controller base;
|
||||
struct nand_chip nand;
|
||||
|
||||
/* General-use Variables */
|
||||
int current_chip;
|
||||
unsigned int command_length;
|
||||
struct gpmi_transfer transfers[GPMI_MAX_TRANSFERS];
|
||||
int ntransfers;
|
||||
|
||||
struct scatterlist cmd_sgl;
|
||||
char *cmd_buffer;
|
||||
bool bch;
|
||||
uint32_t bch_flashlayout0;
|
||||
uint32_t bch_flashlayout1;
|
||||
|
||||
struct scatterlist data_sgl;
|
||||
char *data_buffer_dma;
|
||||
|
||||
void *page_buffer_virt;
|
||||
dma_addr_t page_buffer_phys;
|
||||
unsigned int page_buffer_size;
|
||||
|
||||
void *payload_virt;
|
||||
dma_addr_t payload_phys;
|
||||
|
||||
void *auxiliary_virt;
|
||||
dma_addr_t auxiliary_phys;
|
||||
|
||||
|
@ -154,45 +155,8 @@ struct gpmi_nand_data {
|
|||
#define DMA_CHANS 8
|
||||
struct dma_chan *dma_chans[DMA_CHANS];
|
||||
struct completion dma_done;
|
||||
|
||||
/* private */
|
||||
void *private;
|
||||
};
|
||||
|
||||
/* Common Services */
|
||||
int common_nfc_set_geometry(struct gpmi_nand_data *);
|
||||
struct dma_chan *get_dma_chan(struct gpmi_nand_data *);
|
||||
bool prepare_data_dma(struct gpmi_nand_data *, const void *buf, int len,
|
||||
enum dma_data_direction dr);
|
||||
int start_dma_without_bch_irq(struct gpmi_nand_data *,
|
||||
struct dma_async_tx_descriptor *);
|
||||
int start_dma_with_bch_irq(struct gpmi_nand_data *,
|
||||
struct dma_async_tx_descriptor *);
|
||||
|
||||
/* GPMI-NAND helper function library */
|
||||
int gpmi_init(struct gpmi_nand_data *);
|
||||
void gpmi_clear_bch(struct gpmi_nand_data *);
|
||||
void gpmi_dump_info(struct gpmi_nand_data *);
|
||||
int bch_set_geometry(struct gpmi_nand_data *);
|
||||
int gpmi_is_ready(struct gpmi_nand_data *, unsigned chip);
|
||||
int gpmi_send_command(struct gpmi_nand_data *);
|
||||
int gpmi_enable_clk(struct gpmi_nand_data *this);
|
||||
int gpmi_disable_clk(struct gpmi_nand_data *this);
|
||||
int gpmi_setup_data_interface(struct nand_chip *chip, int chipnr,
|
||||
const struct nand_data_interface *conf);
|
||||
void gpmi_nfc_apply_timings(struct gpmi_nand_data *this);
|
||||
int gpmi_read_data(struct gpmi_nand_data *, void *buf, int len);
|
||||
int gpmi_send_data(struct gpmi_nand_data *, const void *buf, int len);
|
||||
|
||||
int gpmi_send_page(struct gpmi_nand_data *,
|
||||
dma_addr_t payload, dma_addr_t auxiliary);
|
||||
int gpmi_read_page(struct gpmi_nand_data *,
|
||||
dma_addr_t payload, dma_addr_t auxiliary);
|
||||
|
||||
void gpmi_copy_bits(u8 *dst, size_t dst_bit_off,
|
||||
const u8 *src, size_t src_bit_off,
|
||||
size_t nbits);
|
||||
|
||||
/* BCH : Status Block Completion Codes */
|
||||
#define STATUS_GOOD 0x00
|
||||
#define STATUS_ERASED 0xff
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
// SPDX-License-Identifier: GPL-2.0 OR MIT
|
||||
/*
|
||||
* MTK ECC controller driver.
|
||||
* Copyright (C) 2016 MediaTek Inc.
|
||||
|
@ -596,4 +596,4 @@ module_platform_driver(mtk_ecc_driver);
|
|||
|
||||
MODULE_AUTHOR("Xiaolei Li <xiaolei.li@mediatek.com>");
|
||||
MODULE_DESCRIPTION("MTK Nand ECC Driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_LICENSE("Dual MIT/GPL");
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/* SPDX-License-Identifier: GPL-2.0 OR MIT */
|
||||
/*
|
||||
* MTK SDG1 ECC controller
|
||||
*
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
// SPDX-License-Identifier: GPL-2.0 OR MIT
|
||||
/*
|
||||
* MTK NAND Flash controller driver.
|
||||
* Copyright (C) 2016 MediaTek Inc.
|
||||
|
@ -79,6 +79,10 @@
|
|||
#define NFI_FDMM(x) (0xA4 + (x) * sizeof(u32) * 2)
|
||||
#define NFI_FDM_MAX_SIZE (8)
|
||||
#define NFI_FDM_MIN_SIZE (1)
|
||||
#define NFI_DEBUG_CON1 (0x220)
|
||||
#define STROBE_MASK GENMASK(4, 3)
|
||||
#define STROBE_SHIFT (3)
|
||||
#define MAX_STROBE_DLY (3)
|
||||
#define NFI_MASTER_STA (0x224)
|
||||
#define MASTER_STA_MASK (0x0FFF)
|
||||
#define NFI_EMPTY_THRESH (0x23C)
|
||||
|
@ -150,6 +154,8 @@ struct mtk_nfc {
|
|||
struct list_head chips;
|
||||
|
||||
u8 *buffer;
|
||||
|
||||
unsigned long assigned_cs;
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -500,7 +506,8 @@ static int mtk_nfc_setup_data_interface(struct nand_chip *chip, int csline,
|
|||
{
|
||||
struct mtk_nfc *nfc = nand_get_controller_data(chip);
|
||||
const struct nand_sdr_timings *timings;
|
||||
u32 rate, tpoecs, tprecs, tc2r, tw2r, twh, twst, trlt;
|
||||
u32 rate, tpoecs, tprecs, tc2r, tw2r, twh, twst = 0, trlt = 0;
|
||||
u32 temp, tsel = 0;
|
||||
|
||||
timings = nand_get_sdr_timings(conf);
|
||||
if (IS_ERR(timings))
|
||||
|
@ -536,14 +543,53 @@ static int mtk_nfc_setup_data_interface(struct nand_chip *chip, int csline,
|
|||
twh = DIV_ROUND_UP(twh * rate, 1000000) - 1;
|
||||
twh &= 0xf;
|
||||
|
||||
twst = timings->tWP_min / 1000;
|
||||
/* Calculate real WE#/RE# hold time in nanosecond */
|
||||
temp = (twh + 1) * 1000000 / rate;
|
||||
/* nanosecond to picosecond */
|
||||
temp *= 1000;
|
||||
|
||||
/*
|
||||
* WE# low level time should be expaned to meet WE# pulse time
|
||||
* and WE# cycle time at the same time.
|
||||
*/
|
||||
if (temp < timings->tWC_min)
|
||||
twst = timings->tWC_min - temp;
|
||||
twst = max(timings->tWP_min, twst) / 1000;
|
||||
twst = DIV_ROUND_UP(twst * rate, 1000000) - 1;
|
||||
twst &= 0xf;
|
||||
|
||||
trlt = max(timings->tREA_max, timings->tRP_min) / 1000;
|
||||
/*
|
||||
* RE# low level time should be expaned to meet RE# pulse time
|
||||
* and RE# cycle time at the same time.
|
||||
*/
|
||||
if (temp < timings->tRC_min)
|
||||
trlt = timings->tRC_min - temp;
|
||||
trlt = max(trlt, timings->tRP_min) / 1000;
|
||||
trlt = DIV_ROUND_UP(trlt * rate, 1000000) - 1;
|
||||
trlt &= 0xf;
|
||||
|
||||
/* Calculate RE# pulse time in nanosecond. */
|
||||
temp = (trlt + 1) * 1000000 / rate;
|
||||
/* nanosecond to picosecond */
|
||||
temp *= 1000;
|
||||
/*
|
||||
* If RE# access time is bigger than RE# pulse time,
|
||||
* delay sampling data timing.
|
||||
*/
|
||||
if (temp < timings->tREA_max) {
|
||||
tsel = timings->tREA_max / 1000;
|
||||
tsel = DIV_ROUND_UP(tsel * rate, 1000000);
|
||||
tsel -= (trlt + 1);
|
||||
if (tsel > MAX_STROBE_DLY) {
|
||||
trlt += tsel - MAX_STROBE_DLY;
|
||||
tsel = MAX_STROBE_DLY;
|
||||
}
|
||||
}
|
||||
temp = nfi_readl(nfc, NFI_DEBUG_CON1);
|
||||
temp &= ~STROBE_MASK;
|
||||
temp |= tsel << STROBE_SHIFT;
|
||||
nfi_writel(nfc, temp, NFI_DEBUG_CON1);
|
||||
|
||||
/*
|
||||
* ACCON: access timing control register
|
||||
* -------------------------------------
|
||||
|
@ -835,19 +881,21 @@ static int mtk_nfc_write_oob_std(struct nand_chip *chip, int page)
|
|||
return mtk_nfc_write_page_raw(chip, NULL, 1, page);
|
||||
}
|
||||
|
||||
static int mtk_nfc_update_ecc_stats(struct mtd_info *mtd, u8 *buf, u32 sectors)
|
||||
static int mtk_nfc_update_ecc_stats(struct mtd_info *mtd, u8 *buf, u32 start,
|
||||
u32 sectors)
|
||||
{
|
||||
struct nand_chip *chip = mtd_to_nand(mtd);
|
||||
struct mtk_nfc *nfc = nand_get_controller_data(chip);
|
||||
struct mtk_nfc_nand_chip *mtk_nand = to_mtk_nand(chip);
|
||||
struct mtk_ecc_stats stats;
|
||||
u32 reg_size = mtk_nand->fdm.reg_size;
|
||||
int rc, i;
|
||||
|
||||
rc = nfi_readl(nfc, NFI_STA) & STA_EMP_PAGE;
|
||||
if (rc) {
|
||||
memset(buf, 0xff, sectors * chip->ecc.size);
|
||||
for (i = 0; i < sectors; i++)
|
||||
memset(oob_ptr(chip, i), 0xff, mtk_nand->fdm.reg_size);
|
||||
memset(oob_ptr(chip, start + i), 0xff, reg_size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -867,7 +915,7 @@ static int mtk_nfc_read_subpage(struct mtd_info *mtd, struct nand_chip *chip,
|
|||
u32 spare = mtk_nand->spare_per_sector;
|
||||
u32 column, sectors, start, end, reg;
|
||||
dma_addr_t addr;
|
||||
int bitflips;
|
||||
int bitflips = 0;
|
||||
size_t len;
|
||||
u8 *buf;
|
||||
int rc;
|
||||
|
@ -934,14 +982,11 @@ static int mtk_nfc_read_subpage(struct mtd_info *mtd, struct nand_chip *chip,
|
|||
if (rc < 0) {
|
||||
dev_err(nfc->dev, "subpage done timeout\n");
|
||||
bitflips = -EIO;
|
||||
} else {
|
||||
bitflips = 0;
|
||||
if (!raw) {
|
||||
rc = mtk_ecc_wait_done(nfc->ecc, ECC_DECODE);
|
||||
bitflips = rc < 0 ? -ETIMEDOUT :
|
||||
mtk_nfc_update_ecc_stats(mtd, buf, sectors);
|
||||
mtk_nfc_read_fdm(chip, start, sectors);
|
||||
}
|
||||
} else if (!raw) {
|
||||
rc = mtk_ecc_wait_done(nfc->ecc, ECC_DECODE);
|
||||
bitflips = rc < 0 ? -ETIMEDOUT :
|
||||
mtk_nfc_update_ecc_stats(mtd, buf, start, sectors);
|
||||
mtk_nfc_read_fdm(chip, start, sectors);
|
||||
}
|
||||
|
||||
dma_unmap_single(nfc->dev, addr, len, DMA_FROM_DEVICE);
|
||||
|
@ -1315,6 +1360,17 @@ static int mtk_nfc_nand_chip_init(struct device *dev, struct mtk_nfc *nfc,
|
|||
dev_err(dev, "reg property failure : %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (tmp >= MTK_NAND_MAX_NSELS) {
|
||||
dev_err(dev, "invalid CS: %u\n", tmp);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (test_and_set_bit(tmp, &nfc->assigned_cs)) {
|
||||
dev_err(dev, "CS %u already assigned\n", tmp);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
chip->sels[i] = tmp;
|
||||
}
|
||||
|
||||
|
@ -1589,6 +1645,6 @@ static struct platform_driver mtk_nfc_driver = {
|
|||
|
||||
module_platform_driver(mtk_nfc_driver);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_LICENSE("Dual MIT/GPL");
|
||||
MODULE_AUTHOR("Xiaolei Li <xiaolei.li@mediatek.com>");
|
||||
MODULE_DESCRIPTION("MTK Nand Flash Controller Driver");
|
||||
|
|
|
@ -2111,35 +2111,7 @@ static void nand_op_parser_trace(const struct nand_op_parser_ctx *ctx)
|
|||
if (instr == &ctx->subop.instrs[0])
|
||||
prefix = " ->";
|
||||
|
||||
switch (instr->type) {
|
||||
case NAND_OP_CMD_INSTR:
|
||||
pr_debug("%sCMD [0x%02x]\n", prefix,
|
||||
instr->ctx.cmd.opcode);
|
||||
break;
|
||||
case NAND_OP_ADDR_INSTR:
|
||||
pr_debug("%sADDR [%d cyc: %*ph]\n", prefix,
|
||||
instr->ctx.addr.naddrs,
|
||||
instr->ctx.addr.naddrs < 64 ?
|
||||
instr->ctx.addr.naddrs : 64,
|
||||
instr->ctx.addr.addrs);
|
||||
break;
|
||||
case NAND_OP_DATA_IN_INSTR:
|
||||
pr_debug("%sDATA_IN [%d B%s]\n", prefix,
|
||||
instr->ctx.data.len,
|
||||
instr->ctx.data.force_8bit ?
|
||||
", force 8-bit" : "");
|
||||
break;
|
||||
case NAND_OP_DATA_OUT_INSTR:
|
||||
pr_debug("%sDATA_OUT [%d B%s]\n", prefix,
|
||||
instr->ctx.data.len,
|
||||
instr->ctx.data.force_8bit ?
|
||||
", force 8-bit" : "");
|
||||
break;
|
||||
case NAND_OP_WAITRDY_INSTR:
|
||||
pr_debug("%sWAITRDY [max %d ms]\n", prefix,
|
||||
instr->ctx.waitrdy.timeout_ms);
|
||||
break;
|
||||
}
|
||||
nand_op_trace(prefix, instr);
|
||||
|
||||
if (instr == &ctx->subop.instrs[ctx->subop.ninstrs - 1])
|
||||
prefix = " ";
|
||||
|
@ -2152,6 +2124,22 @@ static void nand_op_parser_trace(const struct nand_op_parser_ctx *ctx)
|
|||
}
|
||||
#endif
|
||||
|
||||
static int nand_op_parser_cmp_ctx(const struct nand_op_parser_ctx *a,
|
||||
const struct nand_op_parser_ctx *b)
|
||||
{
|
||||
if (a->subop.ninstrs < b->subop.ninstrs)
|
||||
return -1;
|
||||
else if (a->subop.ninstrs > b->subop.ninstrs)
|
||||
return 1;
|
||||
|
||||
if (a->subop.last_instr_end_off < b->subop.last_instr_end_off)
|
||||
return -1;
|
||||
else if (a->subop.last_instr_end_off > b->subop.last_instr_end_off)
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* nand_op_parser_exec_op - exec_op parser
|
||||
* @chip: the NAND chip
|
||||
|
@ -2186,30 +2174,38 @@ int nand_op_parser_exec_op(struct nand_chip *chip,
|
|||
unsigned int i;
|
||||
|
||||
while (ctx.subop.instrs < op->instrs + op->ninstrs) {
|
||||
int ret;
|
||||
const struct nand_op_parser_pattern *pattern;
|
||||
struct nand_op_parser_ctx best_ctx;
|
||||
int ret, best_pattern = -1;
|
||||
|
||||
for (i = 0; i < parser->npatterns; i++) {
|
||||
const struct nand_op_parser_pattern *pattern;
|
||||
struct nand_op_parser_ctx test_ctx = ctx;
|
||||
|
||||
pattern = &parser->patterns[i];
|
||||
if (!nand_op_parser_match_pat(pattern, &ctx))
|
||||
if (!nand_op_parser_match_pat(pattern, &test_ctx))
|
||||
continue;
|
||||
|
||||
nand_op_parser_trace(&ctx);
|
||||
if (best_pattern >= 0 &&
|
||||
nand_op_parser_cmp_ctx(&test_ctx, &best_ctx) <= 0)
|
||||
continue;
|
||||
|
||||
if (check_only)
|
||||
break;
|
||||
best_pattern = i;
|
||||
best_ctx = test_ctx;
|
||||
}
|
||||
|
||||
if (best_pattern < 0) {
|
||||
pr_debug("->exec_op() parser: pattern not found!\n");
|
||||
return -ENOTSUPP;
|
||||
}
|
||||
|
||||
ctx = best_ctx;
|
||||
nand_op_parser_trace(&ctx);
|
||||
|
||||
if (!check_only) {
|
||||
pattern = &parser->patterns[best_pattern];
|
||||
ret = pattern->exec(chip, &ctx.subop);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (i == parser->npatterns) {
|
||||
pr_debug("->exec_op() parser: pattern not found!\n");
|
||||
return -ENOTSUPP;
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
@ -170,7 +170,7 @@ struct nand_bch_control *nand_bch_init(struct mtd_info *mtd)
|
|||
goto fail;
|
||||
}
|
||||
|
||||
nbc->eccmask = kmalloc(eccbytes, GFP_KERNEL);
|
||||
nbc->eccmask = kzalloc(eccbytes, GFP_KERNEL);
|
||||
nbc->errloc = kmalloc_array(t, sizeof(*nbc->errloc), GFP_KERNEL);
|
||||
if (!nbc->eccmask || !nbc->errloc)
|
||||
goto fail;
|
||||
|
@ -182,7 +182,6 @@ struct nand_bch_control *nand_bch_init(struct mtd_info *mtd)
|
|||
goto fail;
|
||||
|
||||
memset(erased_page, 0xff, eccsize);
|
||||
memset(nbc->eccmask, 0, eccbytes);
|
||||
encode_bch(nbc->bch, erased_page, eccsize, nbc->eccmask);
|
||||
kfree(erased_page);
|
||||
|
||||
|
|
|
@ -8,6 +8,50 @@
|
|||
|
||||
#include "internals.h"
|
||||
|
||||
#define MACRONIX_READ_RETRY_BIT BIT(0)
|
||||
#define MACRONIX_NUM_READ_RETRY_MODES 6
|
||||
|
||||
struct nand_onfi_vendor_macronix {
|
||||
u8 reserved;
|
||||
u8 reliability_func;
|
||||
} __packed;
|
||||
|
||||
static int macronix_nand_setup_read_retry(struct nand_chip *chip, int mode)
|
||||
{
|
||||
u8 feature[ONFI_SUBFEATURE_PARAM_LEN];
|
||||
|
||||
if (!chip->parameters.supports_set_get_features ||
|
||||
!test_bit(ONFI_FEATURE_ADDR_READ_RETRY,
|
||||
chip->parameters.set_feature_list))
|
||||
return -ENOTSUPP;
|
||||
|
||||
feature[0] = mode;
|
||||
return nand_set_features(chip, ONFI_FEATURE_ADDR_READ_RETRY, feature);
|
||||
}
|
||||
|
||||
static void macronix_nand_onfi_init(struct nand_chip *chip)
|
||||
{
|
||||
struct nand_parameters *p = &chip->parameters;
|
||||
struct nand_onfi_vendor_macronix *mxic;
|
||||
|
||||
if (!p->onfi)
|
||||
return;
|
||||
|
||||
mxic = (struct nand_onfi_vendor_macronix *)p->onfi->vendor;
|
||||
if ((mxic->reliability_func & MACRONIX_READ_RETRY_BIT) == 0)
|
||||
return;
|
||||
|
||||
chip->read_retries = MACRONIX_NUM_READ_RETRY_MODES;
|
||||
chip->setup_read_retry = macronix_nand_setup_read_retry;
|
||||
|
||||
if (p->supports_set_get_features) {
|
||||
bitmap_set(p->set_feature_list,
|
||||
ONFI_FEATURE_ADDR_READ_RETRY, 1);
|
||||
bitmap_set(p->get_feature_list,
|
||||
ONFI_FEATURE_ADDR_READ_RETRY, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Macronix AC series does not support using SET/GET_FEATURES to change
|
||||
* the timings unlike what is declared in the parameter page. Unflag
|
||||
|
@ -56,6 +100,7 @@ static int macronix_nand_init(struct nand_chip *chip)
|
|||
chip->options |= NAND_BBM_FIRSTPAGE | NAND_BBM_SECONDPAGE;
|
||||
|
||||
macronix_nand_fix_broken_get_timings(chip);
|
||||
macronix_nand_onfi_init(chip);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -37,6 +37,8 @@
|
|||
/* Max ECC buffer length */
|
||||
#define FMC2_MAX_ECC_BUF_LEN (FMC2_BCHDSRS_LEN * FMC2_MAX_SG)
|
||||
|
||||
#define FMC2_TIMEOUT_MS 1000
|
||||
|
||||
/* Timings */
|
||||
#define FMC2_THIZ 1
|
||||
#define FMC2_TIO 8000
|
||||
|
@ -530,7 +532,8 @@ static int stm32_fmc2_ham_calculate(struct nand_chip *chip, const u8 *data,
|
|||
int ret;
|
||||
|
||||
ret = readl_relaxed_poll_timeout(fmc2->io_base + FMC2_SR,
|
||||
sr, sr & FMC2_SR_NWRF, 10, 1000);
|
||||
sr, sr & FMC2_SR_NWRF, 10,
|
||||
FMC2_TIMEOUT_MS);
|
||||
if (ret) {
|
||||
dev_err(fmc2->dev, "ham timeout\n");
|
||||
return ret;
|
||||
|
@ -611,7 +614,7 @@ static int stm32_fmc2_bch_calculate(struct nand_chip *chip, const u8 *data,
|
|||
|
||||
/* Wait until the BCH code is ready */
|
||||
if (!wait_for_completion_timeout(&fmc2->complete,
|
||||
msecs_to_jiffies(1000))) {
|
||||
msecs_to_jiffies(FMC2_TIMEOUT_MS))) {
|
||||
dev_err(fmc2->dev, "bch timeout\n");
|
||||
stm32_fmc2_disable_bch_irq(fmc2);
|
||||
return -ETIMEDOUT;
|
||||
|
@ -696,7 +699,7 @@ static int stm32_fmc2_bch_correct(struct nand_chip *chip, u8 *dat,
|
|||
|
||||
/* Wait until the decoding error is ready */
|
||||
if (!wait_for_completion_timeout(&fmc2->complete,
|
||||
msecs_to_jiffies(1000))) {
|
||||
msecs_to_jiffies(FMC2_TIMEOUT_MS))) {
|
||||
dev_err(fmc2->dev, "bch timeout\n");
|
||||
stm32_fmc2_disable_bch_irq(fmc2);
|
||||
return -ETIMEDOUT;
|
||||
|
@ -969,7 +972,7 @@ static int stm32_fmc2_xfer(struct nand_chip *chip, const u8 *buf,
|
|||
|
||||
/* Wait end of sequencer transfer */
|
||||
if (!wait_for_completion_timeout(&fmc2->complete,
|
||||
msecs_to_jiffies(1000))) {
|
||||
msecs_to_jiffies(FMC2_TIMEOUT_MS))) {
|
||||
dev_err(fmc2->dev, "seq timeout\n");
|
||||
stm32_fmc2_disable_seq_irq(fmc2);
|
||||
dmaengine_terminate_all(dma_ch);
|
||||
|
@ -981,7 +984,7 @@ static int stm32_fmc2_xfer(struct nand_chip *chip, const u8 *buf,
|
|||
|
||||
/* Wait DMA data transfer completion */
|
||||
if (!wait_for_completion_timeout(&fmc2->dma_data_complete,
|
||||
msecs_to_jiffies(100))) {
|
||||
msecs_to_jiffies(FMC2_TIMEOUT_MS))) {
|
||||
dev_err(fmc2->dev, "data DMA timeout\n");
|
||||
dmaengine_terminate_all(dma_ch);
|
||||
ret = -ETIMEDOUT;
|
||||
|
@ -990,7 +993,7 @@ static int stm32_fmc2_xfer(struct nand_chip *chip, const u8 *buf,
|
|||
/* Wait DMA ECC transfer completion */
|
||||
if (!write_data && !raw) {
|
||||
if (!wait_for_completion_timeout(&fmc2->dma_ecc_complete,
|
||||
msecs_to_jiffies(100))) {
|
||||
msecs_to_jiffies(FMC2_TIMEOUT_MS))) {
|
||||
dev_err(fmc2->dev, "ECC DMA timeout\n");
|
||||
dmaengine_terminate_all(fmc2->dma_ecc_ch);
|
||||
ret = -ETIMEDOUT;
|
||||
|
@ -1909,6 +1912,12 @@ static int stm32_fmc2_probe(struct platform_device *pdev)
|
|||
}
|
||||
|
||||
irq = platform_get_irq(pdev, 0);
|
||||
if (irq < 0) {
|
||||
if (irq != -EPROBE_DEFER)
|
||||
dev_err(dev, "IRQ error missing or invalid\n");
|
||||
return irq;
|
||||
}
|
||||
|
||||
ret = devm_request_irq(dev, irq, stm32_fmc2_irq, 0,
|
||||
dev_name(dev), fmc2);
|
||||
if (ret) {
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
# SPDX-License-Identifier: GPL-2.0
|
||||
spinand-objs := core.o gigadevice.o macronix.o micron.o toshiba.o winbond.o
|
||||
spinand-objs := core.o gigadevice.o macronix.o micron.o paragon.o toshiba.o winbond.o
|
||||
obj-$(CONFIG_MTD_SPI_NAND) += spinand.o
|
||||
|
|
|
@ -511,12 +511,12 @@ static int spinand_mtd_read(struct mtd_info *mtd, loff_t from,
|
|||
if (ret == -EBADMSG) {
|
||||
ecc_failed = true;
|
||||
mtd->ecc_stats.failed++;
|
||||
ret = 0;
|
||||
} else {
|
||||
mtd->ecc_stats.corrected += ret;
|
||||
max_bitflips = max_t(unsigned int, max_bitflips, ret);
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
ops->retlen += iter.req.datalen;
|
||||
ops->oobretlen += iter.req.ooblen;
|
||||
}
|
||||
|
@ -757,6 +757,7 @@ static const struct spinand_manufacturer *spinand_manufacturers[] = {
|
|||
&gigadevice_spinand_manufacturer,
|
||||
¯onix_spinand_manufacturer,
|
||||
µn_spinand_manufacturer,
|
||||
¶gon_spinand_manufacturer,
|
||||
&toshiba_spinand_manufacturer,
|
||||
&winbond_spinand_manufacturer,
|
||||
};
|
||||
|
@ -845,7 +846,7 @@ spinand_select_op_variant(struct spinand_device *spinand,
|
|||
*/
|
||||
int spinand_match_and_init(struct spinand_device *spinand,
|
||||
const struct spinand_info *table,
|
||||
unsigned int table_size, u8 devid)
|
||||
unsigned int table_size, u16 devid)
|
||||
{
|
||||
struct nand_device *nand = spinand_to_nand(spinand);
|
||||
unsigned int i;
|
||||
|
|
|
@ -9,11 +9,17 @@
|
|||
#include <linux/mtd/spinand.h>
|
||||
|
||||
#define SPINAND_MFR_GIGADEVICE 0xC8
|
||||
|
||||
#define GD5FXGQ4XA_STATUS_ECC_1_7_BITFLIPS (1 << 4)
|
||||
#define GD5FXGQ4XA_STATUS_ECC_8_BITFLIPS (3 << 4)
|
||||
|
||||
#define GD5FXGQ4UEXXG_REG_STATUS2 0xf0
|
||||
|
||||
#define GD5FXGQ4UXFXXG_STATUS_ECC_MASK (7 << 4)
|
||||
#define GD5FXGQ4UXFXXG_STATUS_ECC_NO_BITFLIPS (0 << 4)
|
||||
#define GD5FXGQ4UXFXXG_STATUS_ECC_1_3_BITFLIPS (1 << 4)
|
||||
#define GD5FXGQ4UXFXXG_STATUS_ECC_UNCOR_ERROR (7 << 4)
|
||||
|
||||
static SPINAND_OP_VARIANTS(read_cache_variants,
|
||||
SPINAND_PAGE_READ_FROM_CACHE_QUADIO_OP(0, 2, NULL, 0),
|
||||
SPINAND_PAGE_READ_FROM_CACHE_X4_OP(0, 1, NULL, 0),
|
||||
|
@ -22,6 +28,14 @@ static SPINAND_OP_VARIANTS(read_cache_variants,
|
|||
SPINAND_PAGE_READ_FROM_CACHE_OP(true, 0, 1, NULL, 0),
|
||||
SPINAND_PAGE_READ_FROM_CACHE_OP(false, 0, 1, NULL, 0));
|
||||
|
||||
static SPINAND_OP_VARIANTS(read_cache_variants_f,
|
||||
SPINAND_PAGE_READ_FROM_CACHE_QUADIO_OP(0, 2, NULL, 0),
|
||||
SPINAND_PAGE_READ_FROM_CACHE_X4_OP_3A(0, 1, NULL, 0),
|
||||
SPINAND_PAGE_READ_FROM_CACHE_DUALIO_OP(0, 1, NULL, 0),
|
||||
SPINAND_PAGE_READ_FROM_CACHE_X2_OP_3A(0, 1, NULL, 0),
|
||||
SPINAND_PAGE_READ_FROM_CACHE_OP_3A(true, 0, 1, NULL, 0),
|
||||
SPINAND_PAGE_READ_FROM_CACHE_OP_3A(false, 0, 0, NULL, 0));
|
||||
|
||||
static SPINAND_OP_VARIANTS(write_cache_variants,
|
||||
SPINAND_PROG_LOAD_X4(true, 0, NULL, 0),
|
||||
SPINAND_PROG_LOAD(true, 0, NULL, 0));
|
||||
|
@ -59,6 +73,11 @@ static int gd5fxgq4xa_ooblayout_free(struct mtd_info *mtd, int section,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static const struct mtd_ooblayout_ops gd5fxgq4xa_ooblayout = {
|
||||
.ecc = gd5fxgq4xa_ooblayout_ecc,
|
||||
.free = gd5fxgq4xa_ooblayout_free,
|
||||
};
|
||||
|
||||
static int gd5fxgq4xa_ecc_get_status(struct spinand_device *spinand,
|
||||
u8 status)
|
||||
{
|
||||
|
@ -83,7 +102,7 @@ static int gd5fxgq4xa_ecc_get_status(struct spinand_device *spinand,
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int gd5fxgq4uexxg_ooblayout_ecc(struct mtd_info *mtd, int section,
|
||||
static int gd5fxgq4_variant2_ooblayout_ecc(struct mtd_info *mtd, int section,
|
||||
struct mtd_oob_region *region)
|
||||
{
|
||||
if (section)
|
||||
|
@ -95,7 +114,7 @@ static int gd5fxgq4uexxg_ooblayout_ecc(struct mtd_info *mtd, int section,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int gd5fxgq4uexxg_ooblayout_free(struct mtd_info *mtd, int section,
|
||||
static int gd5fxgq4_variant2_ooblayout_free(struct mtd_info *mtd, int section,
|
||||
struct mtd_oob_region *region)
|
||||
{
|
||||
if (section)
|
||||
|
@ -108,6 +127,11 @@ static int gd5fxgq4uexxg_ooblayout_free(struct mtd_info *mtd, int section,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static const struct mtd_ooblayout_ops gd5fxgq4_variant2_ooblayout = {
|
||||
.ecc = gd5fxgq4_variant2_ooblayout_ecc,
|
||||
.free = gd5fxgq4_variant2_ooblayout_free,
|
||||
};
|
||||
|
||||
static int gd5fxgq4uexxg_ecc_get_status(struct spinand_device *spinand,
|
||||
u8 status)
|
||||
{
|
||||
|
@ -150,15 +174,25 @@ static int gd5fxgq4uexxg_ecc_get_status(struct spinand_device *spinand,
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
static const struct mtd_ooblayout_ops gd5fxgq4xa_ooblayout = {
|
||||
.ecc = gd5fxgq4xa_ooblayout_ecc,
|
||||
.free = gd5fxgq4xa_ooblayout_free,
|
||||
};
|
||||
static int gd5fxgq4ufxxg_ecc_get_status(struct spinand_device *spinand,
|
||||
u8 status)
|
||||
{
|
||||
switch (status & GD5FXGQ4UXFXXG_STATUS_ECC_MASK) {
|
||||
case GD5FXGQ4UXFXXG_STATUS_ECC_NO_BITFLIPS:
|
||||
return 0;
|
||||
|
||||
static const struct mtd_ooblayout_ops gd5fxgq4uexxg_ooblayout = {
|
||||
.ecc = gd5fxgq4uexxg_ooblayout_ecc,
|
||||
.free = gd5fxgq4uexxg_ooblayout_free,
|
||||
};
|
||||
case GD5FXGQ4UXFXXG_STATUS_ECC_1_3_BITFLIPS:
|
||||
return 3;
|
||||
|
||||
case GD5FXGQ4UXFXXG_STATUS_ECC_UNCOR_ERROR:
|
||||
return -EBADMSG;
|
||||
|
||||
default: /* (2 << 4) through (6 << 4) are 4-8 corrected errors */
|
||||
return ((status & GD5FXGQ4UXFXXG_STATUS_ECC_MASK) >> 4) + 2;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static const struct spinand_info gigadevice_spinand_table[] = {
|
||||
SPINAND_INFO("GD5F1GQ4xA", 0xF1,
|
||||
|
@ -195,25 +229,40 @@ static const struct spinand_info gigadevice_spinand_table[] = {
|
|||
&write_cache_variants,
|
||||
&update_cache_variants),
|
||||
0,
|
||||
SPINAND_ECCINFO(&gd5fxgq4uexxg_ooblayout,
|
||||
SPINAND_ECCINFO(&gd5fxgq4_variant2_ooblayout,
|
||||
gd5fxgq4uexxg_ecc_get_status)),
|
||||
SPINAND_INFO("GD5F1GQ4UFxxG", 0xb148,
|
||||
NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1),
|
||||
NAND_ECCREQ(8, 512),
|
||||
SPINAND_INFO_OP_VARIANTS(&read_cache_variants_f,
|
||||
&write_cache_variants,
|
||||
&update_cache_variants),
|
||||
0,
|
||||
SPINAND_ECCINFO(&gd5fxgq4_variant2_ooblayout,
|
||||
gd5fxgq4ufxxg_ecc_get_status)),
|
||||
};
|
||||
|
||||
static int gigadevice_spinand_detect(struct spinand_device *spinand)
|
||||
{
|
||||
u8 *id = spinand->id.data;
|
||||
u16 did;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* For GD NANDs, There is an address byte needed to shift in before IDs
|
||||
* are read out, so the first byte in raw_id is dummy.
|
||||
* Earlier GDF5-series devices (A,E) return [0][MID][DID]
|
||||
* Later (F) devices return [MID][DID1][DID2]
|
||||
*/
|
||||
if (id[1] != SPINAND_MFR_GIGADEVICE)
|
||||
|
||||
if (id[0] == SPINAND_MFR_GIGADEVICE)
|
||||
did = (id[1] << 8) + id[2];
|
||||
else if (id[0] == 0 && id[1] == SPINAND_MFR_GIGADEVICE)
|
||||
did = id[2];
|
||||
else
|
||||
return 0;
|
||||
|
||||
ret = spinand_match_and_init(spinand, gigadevice_spinand_table,
|
||||
ARRAY_SIZE(gigadevice_spinand_table),
|
||||
id[2]);
|
||||
did);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
|
|
|
@ -0,0 +1,147 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (C) 2019 Jeff Kletsky
|
||||
*
|
||||
* Author: Jeff Kletsky <git-commits@allycomm.com>
|
||||
*/
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/mtd/spinand.h>
|
||||
|
||||
|
||||
#define SPINAND_MFR_PARAGON 0xa1
|
||||
|
||||
|
||||
#define PN26G0XA_STATUS_ECC_BITMASK (3 << 4)
|
||||
|
||||
#define PN26G0XA_STATUS_ECC_NONE_DETECTED (0 << 4)
|
||||
#define PN26G0XA_STATUS_ECC_1_7_CORRECTED (1 << 4)
|
||||
#define PN26G0XA_STATUS_ECC_ERRORED (2 << 4)
|
||||
#define PN26G0XA_STATUS_ECC_8_CORRECTED (3 << 4)
|
||||
|
||||
|
||||
static SPINAND_OP_VARIANTS(read_cache_variants,
|
||||
SPINAND_PAGE_READ_FROM_CACHE_QUADIO_OP(0, 2, NULL, 0),
|
||||
SPINAND_PAGE_READ_FROM_CACHE_X4_OP(0, 1, NULL, 0),
|
||||
SPINAND_PAGE_READ_FROM_CACHE_DUALIO_OP(0, 1, NULL, 0),
|
||||
SPINAND_PAGE_READ_FROM_CACHE_X2_OP(0, 1, NULL, 0),
|
||||
SPINAND_PAGE_READ_FROM_CACHE_OP(true, 0, 1, NULL, 0),
|
||||
SPINAND_PAGE_READ_FROM_CACHE_OP(false, 0, 1, NULL, 0));
|
||||
|
||||
static SPINAND_OP_VARIANTS(write_cache_variants,
|
||||
SPINAND_PROG_LOAD_X4(true, 0, NULL, 0),
|
||||
SPINAND_PROG_LOAD(true, 0, NULL, 0));
|
||||
|
||||
static SPINAND_OP_VARIANTS(update_cache_variants,
|
||||
SPINAND_PROG_LOAD_X4(false, 0, NULL, 0),
|
||||
SPINAND_PROG_LOAD(false, 0, NULL, 0));
|
||||
|
||||
|
||||
static int pn26g0xa_ooblayout_ecc(struct mtd_info *mtd, int section,
|
||||
struct mtd_oob_region *region)
|
||||
{
|
||||
if (section > 3)
|
||||
return -ERANGE;
|
||||
|
||||
region->offset = 6 + (15 * section); /* 4 BBM + 2 user bytes */
|
||||
region->length = 13;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pn26g0xa_ooblayout_free(struct mtd_info *mtd, int section,
|
||||
struct mtd_oob_region *region)
|
||||
{
|
||||
if (section > 4)
|
||||
return -ERANGE;
|
||||
|
||||
if (section == 4) {
|
||||
region->offset = 64;
|
||||
region->length = 64;
|
||||
} else {
|
||||
region->offset = 4 + (15 * section);
|
||||
region->length = 2;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pn26g0xa_ecc_get_status(struct spinand_device *spinand,
|
||||
u8 status)
|
||||
{
|
||||
switch (status & PN26G0XA_STATUS_ECC_BITMASK) {
|
||||
case PN26G0XA_STATUS_ECC_NONE_DETECTED:
|
||||
return 0;
|
||||
|
||||
case PN26G0XA_STATUS_ECC_1_7_CORRECTED:
|
||||
return 7; /* Return upper limit by convention */
|
||||
|
||||
case PN26G0XA_STATUS_ECC_8_CORRECTED:
|
||||
return 8;
|
||||
|
||||
case PN26G0XA_STATUS_ECC_ERRORED:
|
||||
return -EBADMSG;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static const struct mtd_ooblayout_ops pn26g0xa_ooblayout = {
|
||||
.ecc = pn26g0xa_ooblayout_ecc,
|
||||
.free = pn26g0xa_ooblayout_free,
|
||||
};
|
||||
|
||||
|
||||
static const struct spinand_info paragon_spinand_table[] = {
|
||||
SPINAND_INFO("PN26G01A", 0xe1,
|
||||
NAND_MEMORG(1, 2048, 128, 64, 1024, 21, 1, 1, 1),
|
||||
NAND_ECCREQ(8, 512),
|
||||
SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
|
||||
&write_cache_variants,
|
||||
&update_cache_variants),
|
||||
0,
|
||||
SPINAND_ECCINFO(&pn26g0xa_ooblayout,
|
||||
pn26g0xa_ecc_get_status)),
|
||||
SPINAND_INFO("PN26G02A", 0xe2,
|
||||
NAND_MEMORG(1, 2048, 128, 64, 2048, 41, 1, 1, 1),
|
||||
NAND_ECCREQ(8, 512),
|
||||
SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
|
||||
&write_cache_variants,
|
||||
&update_cache_variants),
|
||||
0,
|
||||
SPINAND_ECCINFO(&pn26g0xa_ooblayout,
|
||||
pn26g0xa_ecc_get_status)),
|
||||
};
|
||||
|
||||
static int paragon_spinand_detect(struct spinand_device *spinand)
|
||||
{
|
||||
u8 *id = spinand->id.data;
|
||||
int ret;
|
||||
|
||||
/* Read ID returns [0][MID][DID] */
|
||||
|
||||
if (id[1] != SPINAND_MFR_PARAGON)
|
||||
return 0;
|
||||
|
||||
ret = spinand_match_and_init(spinand, paragon_spinand_table,
|
||||
ARRAY_SIZE(paragon_spinand_table),
|
||||
id[2]);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static const struct spinand_manufacturer_ops paragon_spinand_manuf_ops = {
|
||||
.detect = paragon_spinand_detect,
|
||||
};
|
||||
|
||||
const struct spinand_manufacturer paragon_spinand_manufacturer = {
|
||||
.id = SPINAND_MFR_PARAGON,
|
||||
.name = "Paragon",
|
||||
.ops = ¶gon_spinand_manuf_ops,
|
||||
};
|
|
@ -371,8 +371,7 @@ static int parse_afs_partitions(struct mtd_info *mtd,
|
|||
|
||||
out_free_parts:
|
||||
while (i >= 0) {
|
||||
if (parts[i].name)
|
||||
kfree(parts[i].name);
|
||||
kfree(parts[i].name);
|
||||
i--;
|
||||
}
|
||||
kfree(parts);
|
||||
|
|
|
@ -105,11 +105,4 @@ config SPI_INTEL_SPI_PLATFORM
|
|||
To compile this driver as a module, choose M here: the module
|
||||
will be called intel-spi-platform.
|
||||
|
||||
config SPI_STM32_QUADSPI
|
||||
tristate "STM32 Quad SPI controller"
|
||||
depends on ARCH_STM32 || COMPILE_TEST
|
||||
help
|
||||
This enables support for the STM32 Quad SPI controller.
|
||||
We only connect the NOR to this controller.
|
||||
|
||||
endif # MTD_SPI_NOR
|
||||
|
|
|
@ -8,4 +8,3 @@ obj-$(CONFIG_SPI_NXP_SPIFI) += nxp-spifi.o
|
|||
obj-$(CONFIG_SPI_INTEL_SPI) += intel-spi.o
|
||||
obj-$(CONFIG_SPI_INTEL_SPI_PCI) += intel-spi-pci.o
|
||||
obj-$(CONFIG_SPI_INTEL_SPI_PLATFORM) += intel-spi-platform.o
|
||||
obj-$(CONFIG_SPI_STM32_QUADSPI) += stm32-quadspi.o
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
#include <linux/of.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/reset.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/spi/spi.h>
|
||||
#include <linux/timer.h>
|
||||
|
@ -1325,6 +1326,7 @@ static int cqspi_probe(struct platform_device *pdev)
|
|||
struct cqspi_st *cqspi;
|
||||
struct resource *res;
|
||||
struct resource *res_ahb;
|
||||
struct reset_control *rstc, *rstc_ocp;
|
||||
const struct cqspi_driver_platdata *ddata;
|
||||
int ret;
|
||||
int irq;
|
||||
|
@ -1391,6 +1393,25 @@ static int cqspi_probe(struct platform_device *pdev)
|
|||
goto probe_clk_failed;
|
||||
}
|
||||
|
||||
/* Obtain QSPI reset control */
|
||||
rstc = devm_reset_control_get_optional_exclusive(dev, "qspi");
|
||||
if (IS_ERR(rstc)) {
|
||||
dev_err(dev, "Cannot get QSPI reset.\n");
|
||||
return PTR_ERR(rstc);
|
||||
}
|
||||
|
||||
rstc_ocp = devm_reset_control_get_optional_exclusive(dev, "qspi-ocp");
|
||||
if (IS_ERR(rstc_ocp)) {
|
||||
dev_err(dev, "Cannot get QSPI OCP reset.\n");
|
||||
return PTR_ERR(rstc_ocp);
|
||||
}
|
||||
|
||||
reset_control_assert(rstc);
|
||||
reset_control_deassert(rstc);
|
||||
|
||||
reset_control_assert(rstc_ocp);
|
||||
reset_control_deassert(rstc_ocp);
|
||||
|
||||
cqspi->master_ref_clk_hz = clk_get_rate(cqspi->clk);
|
||||
ddata = of_device_get_match_data(dev);
|
||||
if (ddata && (ddata->quirks & CQSPI_NEEDS_WR_DELAY))
|
||||
|
|
|
@ -64,6 +64,7 @@ static const struct pci_device_id intel_spi_pci_ids[] = {
|
|||
{ PCI_VDEVICE(INTEL, 0x18e0), (unsigned long)&bxt_info },
|
||||
{ PCI_VDEVICE(INTEL, 0x19e0), (unsigned long)&bxt_info },
|
||||
{ PCI_VDEVICE(INTEL, 0x34a4), (unsigned long)&bxt_info },
|
||||
{ PCI_VDEVICE(INTEL, 0x4b24), (unsigned long)&bxt_info },
|
||||
{ PCI_VDEVICE(INTEL, 0xa1a4), (unsigned long)&bxt_info },
|
||||
{ PCI_VDEVICE(INTEL, 0xa224), (unsigned long)&bxt_info },
|
||||
{ },
|
||||
|
|
|
@ -200,7 +200,7 @@ struct sfdp_header {
|
|||
* register does not modify status register 2.
|
||||
* - 101b: QE is bit 1 of status register 2. Status register 1 is read using
|
||||
* Read Status instruction 05h. Status register2 is read using
|
||||
* instruction 35h. QE is set via Writ Status instruction 01h with
|
||||
* instruction 35h. QE is set via Write Status instruction 01h with
|
||||
* two data bytes where bit 1 of the second byte is one.
|
||||
* [...]
|
||||
*/
|
||||
|
@ -1775,6 +1775,28 @@ static int spi_nor_spansion_clear_sr_bp(struct spi_nor *nor)
|
|||
.addr_width = 3, \
|
||||
.flags = SPI_NOR_NO_FR | SPI_S3AN,
|
||||
|
||||
static int
|
||||
is25lp256_post_bfpt_fixups(struct spi_nor *nor,
|
||||
const struct sfdp_parameter_header *bfpt_header,
|
||||
const struct sfdp_bfpt *bfpt,
|
||||
struct spi_nor_flash_parameter *params)
|
||||
{
|
||||
/*
|
||||
* IS25LP256 supports 4B opcodes, but the BFPT advertises a
|
||||
* BFPT_DWORD1_ADDRESS_BYTES_3_ONLY address width.
|
||||
* Overwrite the address width advertised by the BFPT.
|
||||
*/
|
||||
if ((bfpt->dwords[BFPT_DWORD(1)] & BFPT_DWORD1_ADDRESS_BYTES_MASK) ==
|
||||
BFPT_DWORD1_ADDRESS_BYTES_3_ONLY)
|
||||
nor->addr_width = 4;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct spi_nor_fixups is25lp256_fixups = {
|
||||
.post_bfpt = is25lp256_post_bfpt_fixups,
|
||||
};
|
||||
|
||||
static int
|
||||
mx25l25635_post_bfpt_fixups(struct spi_nor *nor,
|
||||
const struct sfdp_parameter_header *bfpt_header,
|
||||
|
@ -1916,7 +1938,8 @@ static const struct flash_info spi_nor_ids[] = {
|
|||
SECT_4K | SPI_NOR_DUAL_READ) },
|
||||
{ "is25lp256", INFO(0x9d6019, 0, 64 * 1024, 512,
|
||||
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
|
||||
SPI_NOR_4B_OPCODES) },
|
||||
SPI_NOR_4B_OPCODES)
|
||||
.fixups = &is25lp256_fixups },
|
||||
{ "is25wp032", INFO(0x9d7016, 0, 64 * 1024, 64,
|
||||
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) },
|
||||
{ "is25wp064", INFO(0x9d7017, 0, 64 * 1024, 128,
|
||||
|
@ -1969,6 +1992,9 @@ static const struct flash_info spi_nor_ids[] = {
|
|||
{ "n25q512ax3", INFO(0x20ba20, 0, 64 * 1024, 1024, SECT_4K | USE_FSR | SPI_NOR_QUAD_READ) },
|
||||
{ "n25q00", INFO(0x20ba21, 0, 64 * 1024, 2048, SECT_4K | USE_FSR | SPI_NOR_QUAD_READ | NO_CHIP_ERASE) },
|
||||
{ "n25q00a", INFO(0x20bb21, 0, 64 * 1024, 2048, SECT_4K | USE_FSR | SPI_NOR_QUAD_READ | NO_CHIP_ERASE) },
|
||||
{ "mt25ql02g", INFO(0x20ba22, 0, 64 * 1024, 4096,
|
||||
SECT_4K | USE_FSR | SPI_NOR_QUAD_READ |
|
||||
NO_CHIP_ERASE) },
|
||||
{ "mt25qu02g", INFO(0x20bb22, 0, 64 * 1024, 4096, SECT_4K | USE_FSR | SPI_NOR_QUAD_READ | NO_CHIP_ERASE) },
|
||||
|
||||
/* Micron */
|
||||
|
@ -2085,6 +2111,11 @@ static const struct flash_info spi_nor_ids[] = {
|
|||
SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB)
|
||||
},
|
||||
{ "w25x32", INFO(0xef3016, 0, 64 * 1024, 64, SECT_4K) },
|
||||
{
|
||||
"w25q16jv-im/jm", INFO(0xef7015, 0, 64 * 1024, 32,
|
||||
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
|
||||
SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB)
|
||||
},
|
||||
{ "w25q20cl", INFO(0xef4012, 0, 64 * 1024, 4, SECT_4K) },
|
||||
{ "w25q20bw", INFO(0xef5012, 0, 64 * 1024, 4, SECT_4K) },
|
||||
{ "w25q20ew", INFO(0xef6012, 0, 64 * 1024, 4, SECT_4K) },
|
||||
|
@ -2151,7 +2182,7 @@ static const struct flash_info *spi_nor_read_id(struct spi_nor *nor)
|
|||
|
||||
tmp = nor->read_reg(nor, SPINOR_OP_RDID, id, SPI_NOR_MAX_ID_LEN);
|
||||
if (tmp < 0) {
|
||||
dev_dbg(nor->dev, "error %d reading JEDEC ID\n", tmp);
|
||||
dev_err(nor->dev, "error %d reading JEDEC ID\n", tmp);
|
||||
return ERR_PTR(tmp);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,707 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Driver for stm32 quadspi controller
|
||||
*
|
||||
* Copyright (C) 2017, STMicroelectronics - All Rights Reserved
|
||||
* Author(s): Ludovic Barre author <ludovic.barre@st.com>.
|
||||
*/
|
||||
#include <linux/clk.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/iopoll.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include <linux/mtd/partitions.h>
|
||||
#include <linux/mtd/spi-nor.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/reset.h>
|
||||
#include <linux/sizes.h>
|
||||
|
||||
#define QUADSPI_CR 0x00
|
||||
#define CR_EN BIT(0)
|
||||
#define CR_ABORT BIT(1)
|
||||
#define CR_DMAEN BIT(2)
|
||||
#define CR_TCEN BIT(3)
|
||||
#define CR_SSHIFT BIT(4)
|
||||
#define CR_DFM BIT(6)
|
||||
#define CR_FSEL BIT(7)
|
||||
#define CR_FTHRES_SHIFT 8
|
||||
#define CR_FTHRES_MASK GENMASK(12, 8)
|
||||
#define CR_FTHRES(n) (((n) << CR_FTHRES_SHIFT) & CR_FTHRES_MASK)
|
||||
#define CR_TEIE BIT(16)
|
||||
#define CR_TCIE BIT(17)
|
||||
#define CR_FTIE BIT(18)
|
||||
#define CR_SMIE BIT(19)
|
||||
#define CR_TOIE BIT(20)
|
||||
#define CR_PRESC_SHIFT 24
|
||||
#define CR_PRESC_MASK GENMASK(31, 24)
|
||||
#define CR_PRESC(n) (((n) << CR_PRESC_SHIFT) & CR_PRESC_MASK)
|
||||
|
||||
#define QUADSPI_DCR 0x04
|
||||
#define DCR_CSHT_SHIFT 8
|
||||
#define DCR_CSHT_MASK GENMASK(10, 8)
|
||||
#define DCR_CSHT(n) (((n) << DCR_CSHT_SHIFT) & DCR_CSHT_MASK)
|
||||
#define DCR_FSIZE_SHIFT 16
|
||||
#define DCR_FSIZE_MASK GENMASK(20, 16)
|
||||
#define DCR_FSIZE(n) (((n) << DCR_FSIZE_SHIFT) & DCR_FSIZE_MASK)
|
||||
|
||||
#define QUADSPI_SR 0x08
|
||||
#define SR_TEF BIT(0)
|
||||
#define SR_TCF BIT(1)
|
||||
#define SR_FTF BIT(2)
|
||||
#define SR_SMF BIT(3)
|
||||
#define SR_TOF BIT(4)
|
||||
#define SR_BUSY BIT(5)
|
||||
#define SR_FLEVEL_SHIFT 8
|
||||
#define SR_FLEVEL_MASK GENMASK(13, 8)
|
||||
|
||||
#define QUADSPI_FCR 0x0c
|
||||
#define FCR_CTCF BIT(1)
|
||||
|
||||
#define QUADSPI_DLR 0x10
|
||||
|
||||
#define QUADSPI_CCR 0x14
|
||||
#define CCR_INST_SHIFT 0
|
||||
#define CCR_INST_MASK GENMASK(7, 0)
|
||||
#define CCR_INST(n) (((n) << CCR_INST_SHIFT) & CCR_INST_MASK)
|
||||
#define CCR_IMODE_NONE (0U << 8)
|
||||
#define CCR_IMODE_1 (1U << 8)
|
||||
#define CCR_IMODE_2 (2U << 8)
|
||||
#define CCR_IMODE_4 (3U << 8)
|
||||
#define CCR_ADMODE_NONE (0U << 10)
|
||||
#define CCR_ADMODE_1 (1U << 10)
|
||||
#define CCR_ADMODE_2 (2U << 10)
|
||||
#define CCR_ADMODE_4 (3U << 10)
|
||||
#define CCR_ADSIZE_SHIFT 12
|
||||
#define CCR_ADSIZE_MASK GENMASK(13, 12)
|
||||
#define CCR_ADSIZE(n) (((n) << CCR_ADSIZE_SHIFT) & CCR_ADSIZE_MASK)
|
||||
#define CCR_ABMODE_NONE (0U << 14)
|
||||
#define CCR_ABMODE_1 (1U << 14)
|
||||
#define CCR_ABMODE_2 (2U << 14)
|
||||
#define CCR_ABMODE_4 (3U << 14)
|
||||
#define CCR_ABSIZE_8 (0U << 16)
|
||||
#define CCR_ABSIZE_16 (1U << 16)
|
||||
#define CCR_ABSIZE_24 (2U << 16)
|
||||
#define CCR_ABSIZE_32 (3U << 16)
|
||||
#define CCR_DCYC_SHIFT 18
|
||||
#define CCR_DCYC_MASK GENMASK(22, 18)
|
||||
#define CCR_DCYC(n) (((n) << CCR_DCYC_SHIFT) & CCR_DCYC_MASK)
|
||||
#define CCR_DMODE_NONE (0U << 24)
|
||||
#define CCR_DMODE_1 (1U << 24)
|
||||
#define CCR_DMODE_2 (2U << 24)
|
||||
#define CCR_DMODE_4 (3U << 24)
|
||||
#define CCR_FMODE_INDW (0U << 26)
|
||||
#define CCR_FMODE_INDR (1U << 26)
|
||||
#define CCR_FMODE_APM (2U << 26)
|
||||
#define CCR_FMODE_MM (3U << 26)
|
||||
|
||||
#define QUADSPI_AR 0x18
|
||||
#define QUADSPI_ABR 0x1c
|
||||
#define QUADSPI_DR 0x20
|
||||
#define QUADSPI_PSMKR 0x24
|
||||
#define QUADSPI_PSMAR 0x28
|
||||
#define QUADSPI_PIR 0x2c
|
||||
#define QUADSPI_LPTR 0x30
|
||||
#define LPTR_DFT_TIMEOUT 0x10
|
||||
|
||||
#define FSIZE_VAL(size) (__fls(size) - 1)
|
||||
|
||||
#define STM32_MAX_MMAP_SZ SZ_256M
|
||||
#define STM32_MAX_NORCHIP 2
|
||||
|
||||
#define STM32_QSPI_FIFO_SZ 32
|
||||
#define STM32_QSPI_FIFO_TIMEOUT_US 30000
|
||||
#define STM32_QSPI_BUSY_TIMEOUT_US 100000
|
||||
|
||||
struct stm32_qspi_flash {
|
||||
struct spi_nor nor;
|
||||
struct stm32_qspi *qspi;
|
||||
u32 cs;
|
||||
u32 fsize;
|
||||
u32 presc;
|
||||
u32 read_mode;
|
||||
bool registered;
|
||||
u32 prefetch_limit;
|
||||
};
|
||||
|
||||
struct stm32_qspi {
|
||||
struct device *dev;
|
||||
void __iomem *io_base;
|
||||
void __iomem *mm_base;
|
||||
resource_size_t mm_size;
|
||||
u32 nor_num;
|
||||
struct clk *clk;
|
||||
u32 clk_rate;
|
||||
struct stm32_qspi_flash flash[STM32_MAX_NORCHIP];
|
||||
struct completion cmd_completion;
|
||||
|
||||
/*
|
||||
* to protect device configuration, could be different between
|
||||
* 2 flash access (bk1, bk2)
|
||||
*/
|
||||
struct mutex lock;
|
||||
};
|
||||
|
||||
struct stm32_qspi_cmd {
|
||||
u8 addr_width;
|
||||
u8 dummy;
|
||||
bool tx_data;
|
||||
u8 opcode;
|
||||
u32 framemode;
|
||||
u32 qspimode;
|
||||
u32 addr;
|
||||
size_t len;
|
||||
void *buf;
|
||||
};
|
||||
|
||||
static int stm32_qspi_wait_cmd(struct stm32_qspi *qspi)
|
||||
{
|
||||
u32 cr;
|
||||
int err = 0;
|
||||
|
||||
if (readl_relaxed(qspi->io_base + QUADSPI_SR) & SR_TCF)
|
||||
return 0;
|
||||
|
||||
reinit_completion(&qspi->cmd_completion);
|
||||
cr = readl_relaxed(qspi->io_base + QUADSPI_CR);
|
||||
writel_relaxed(cr | CR_TCIE, qspi->io_base + QUADSPI_CR);
|
||||
|
||||
if (!wait_for_completion_interruptible_timeout(&qspi->cmd_completion,
|
||||
msecs_to_jiffies(1000)))
|
||||
err = -ETIMEDOUT;
|
||||
|
||||
writel_relaxed(cr, qspi->io_base + QUADSPI_CR);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int stm32_qspi_wait_nobusy(struct stm32_qspi *qspi)
|
||||
{
|
||||
u32 sr;
|
||||
|
||||
return readl_relaxed_poll_timeout(qspi->io_base + QUADSPI_SR, sr,
|
||||
!(sr & SR_BUSY), 10,
|
||||
STM32_QSPI_BUSY_TIMEOUT_US);
|
||||
}
|
||||
|
||||
static void stm32_qspi_set_framemode(struct spi_nor *nor,
|
||||
struct stm32_qspi_cmd *cmd, bool read)
|
||||
{
|
||||
u32 dmode = CCR_DMODE_1;
|
||||
|
||||
cmd->framemode = CCR_IMODE_1;
|
||||
|
||||
if (read) {
|
||||
switch (nor->read_proto) {
|
||||
default:
|
||||
case SNOR_PROTO_1_1_1:
|
||||
dmode = CCR_DMODE_1;
|
||||
break;
|
||||
case SNOR_PROTO_1_1_2:
|
||||
dmode = CCR_DMODE_2;
|
||||
break;
|
||||
case SNOR_PROTO_1_1_4:
|
||||
dmode = CCR_DMODE_4;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
cmd->framemode |= cmd->tx_data ? dmode : 0;
|
||||
cmd->framemode |= cmd->addr_width ? CCR_ADMODE_1 : 0;
|
||||
}
|
||||
|
||||
static void stm32_qspi_read_fifo(u8 *val, void __iomem *addr)
|
||||
{
|
||||
*val = readb_relaxed(addr);
|
||||
}
|
||||
|
||||
static void stm32_qspi_write_fifo(u8 *val, void __iomem *addr)
|
||||
{
|
||||
writeb_relaxed(*val, addr);
|
||||
}
|
||||
|
||||
static int stm32_qspi_tx_poll(struct stm32_qspi *qspi,
|
||||
const struct stm32_qspi_cmd *cmd)
|
||||
{
|
||||
void (*tx_fifo)(u8 *, void __iomem *);
|
||||
u32 len = cmd->len, sr;
|
||||
u8 *buf = cmd->buf;
|
||||
int ret;
|
||||
|
||||
if (cmd->qspimode == CCR_FMODE_INDW)
|
||||
tx_fifo = stm32_qspi_write_fifo;
|
||||
else
|
||||
tx_fifo = stm32_qspi_read_fifo;
|
||||
|
||||
while (len--) {
|
||||
ret = readl_relaxed_poll_timeout(qspi->io_base + QUADSPI_SR,
|
||||
sr, (sr & SR_FTF), 10,
|
||||
STM32_QSPI_FIFO_TIMEOUT_US);
|
||||
if (ret) {
|
||||
dev_err(qspi->dev, "fifo timeout (stat:%#x)\n", sr);
|
||||
return ret;
|
||||
}
|
||||
tx_fifo(buf++, qspi->io_base + QUADSPI_DR);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int stm32_qspi_tx_mm(struct stm32_qspi *qspi,
|
||||
const struct stm32_qspi_cmd *cmd)
|
||||
{
|
||||
memcpy_fromio(cmd->buf, qspi->mm_base + cmd->addr, cmd->len);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int stm32_qspi_tx(struct stm32_qspi *qspi,
|
||||
const struct stm32_qspi_cmd *cmd)
|
||||
{
|
||||
if (!cmd->tx_data)
|
||||
return 0;
|
||||
|
||||
if (cmd->qspimode == CCR_FMODE_MM)
|
||||
return stm32_qspi_tx_mm(qspi, cmd);
|
||||
|
||||
return stm32_qspi_tx_poll(qspi, cmd);
|
||||
}
|
||||
|
||||
static int stm32_qspi_send(struct stm32_qspi_flash *flash,
|
||||
const struct stm32_qspi_cmd *cmd)
|
||||
{
|
||||
struct stm32_qspi *qspi = flash->qspi;
|
||||
u32 ccr, dcr, cr;
|
||||
u32 last_byte;
|
||||
int err;
|
||||
|
||||
err = stm32_qspi_wait_nobusy(qspi);
|
||||
if (err)
|
||||
goto abort;
|
||||
|
||||
dcr = readl_relaxed(qspi->io_base + QUADSPI_DCR) & ~DCR_FSIZE_MASK;
|
||||
dcr |= DCR_FSIZE(flash->fsize);
|
||||
writel_relaxed(dcr, qspi->io_base + QUADSPI_DCR);
|
||||
|
||||
cr = readl_relaxed(qspi->io_base + QUADSPI_CR);
|
||||
cr &= ~CR_PRESC_MASK & ~CR_FSEL;
|
||||
cr |= CR_PRESC(flash->presc);
|
||||
cr |= flash->cs ? CR_FSEL : 0;
|
||||
writel_relaxed(cr, qspi->io_base + QUADSPI_CR);
|
||||
|
||||
if (cmd->tx_data)
|
||||
writel_relaxed(cmd->len - 1, qspi->io_base + QUADSPI_DLR);
|
||||
|
||||
ccr = cmd->framemode | cmd->qspimode;
|
||||
|
||||
if (cmd->dummy)
|
||||
ccr |= CCR_DCYC(cmd->dummy);
|
||||
|
||||
if (cmd->addr_width)
|
||||
ccr |= CCR_ADSIZE(cmd->addr_width - 1);
|
||||
|
||||
ccr |= CCR_INST(cmd->opcode);
|
||||
writel_relaxed(ccr, qspi->io_base + QUADSPI_CCR);
|
||||
|
||||
if (cmd->addr_width && cmd->qspimode != CCR_FMODE_MM)
|
||||
writel_relaxed(cmd->addr, qspi->io_base + QUADSPI_AR);
|
||||
|
||||
err = stm32_qspi_tx(qspi, cmd);
|
||||
if (err)
|
||||
goto abort;
|
||||
|
||||
if (cmd->qspimode != CCR_FMODE_MM) {
|
||||
err = stm32_qspi_wait_cmd(qspi);
|
||||
if (err)
|
||||
goto abort;
|
||||
writel_relaxed(FCR_CTCF, qspi->io_base + QUADSPI_FCR);
|
||||
} else {
|
||||
last_byte = cmd->addr + cmd->len;
|
||||
if (last_byte > flash->prefetch_limit)
|
||||
goto abort;
|
||||
}
|
||||
|
||||
return err;
|
||||
|
||||
abort:
|
||||
cr = readl_relaxed(qspi->io_base + QUADSPI_CR) | CR_ABORT;
|
||||
writel_relaxed(cr, qspi->io_base + QUADSPI_CR);
|
||||
|
||||
if (err)
|
||||
dev_err(qspi->dev, "%s abort err:%d\n", __func__, err);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int stm32_qspi_read_reg(struct spi_nor *nor,
|
||||
u8 opcode, u8 *buf, int len)
|
||||
{
|
||||
struct stm32_qspi_flash *flash = nor->priv;
|
||||
struct device *dev = flash->qspi->dev;
|
||||
struct stm32_qspi_cmd cmd;
|
||||
|
||||
dev_dbg(dev, "read_reg: cmd:%#.2x buf:%pK len:%#x\n", opcode, buf, len);
|
||||
|
||||
memset(&cmd, 0, sizeof(cmd));
|
||||
cmd.opcode = opcode;
|
||||
cmd.tx_data = true;
|
||||
cmd.len = len;
|
||||
cmd.buf = buf;
|
||||
cmd.qspimode = CCR_FMODE_INDR;
|
||||
|
||||
stm32_qspi_set_framemode(nor, &cmd, false);
|
||||
|
||||
return stm32_qspi_send(flash, &cmd);
|
||||
}
|
||||
|
||||
static int stm32_qspi_write_reg(struct spi_nor *nor, u8 opcode,
|
||||
u8 *buf, int len)
|
||||
{
|
||||
struct stm32_qspi_flash *flash = nor->priv;
|
||||
struct device *dev = flash->qspi->dev;
|
||||
struct stm32_qspi_cmd cmd;
|
||||
|
||||
dev_dbg(dev, "write_reg: cmd:%#.2x buf:%pK len:%#x\n", opcode, buf, len);
|
||||
|
||||
memset(&cmd, 0, sizeof(cmd));
|
||||
cmd.opcode = opcode;
|
||||
cmd.tx_data = !!(buf && len > 0);
|
||||
cmd.len = len;
|
||||
cmd.buf = buf;
|
||||
cmd.qspimode = CCR_FMODE_INDW;
|
||||
|
||||
stm32_qspi_set_framemode(nor, &cmd, false);
|
||||
|
||||
return stm32_qspi_send(flash, &cmd);
|
||||
}
|
||||
|
||||
static ssize_t stm32_qspi_read(struct spi_nor *nor, loff_t from, size_t len,
|
||||
u_char *buf)
|
||||
{
|
||||
struct stm32_qspi_flash *flash = nor->priv;
|
||||
struct stm32_qspi *qspi = flash->qspi;
|
||||
struct stm32_qspi_cmd cmd;
|
||||
int err;
|
||||
|
||||
dev_dbg(qspi->dev, "read(%#.2x): buf:%pK from:%#.8x len:%#zx\n",
|
||||
nor->read_opcode, buf, (u32)from, len);
|
||||
|
||||
memset(&cmd, 0, sizeof(cmd));
|
||||
cmd.opcode = nor->read_opcode;
|
||||
cmd.addr_width = nor->addr_width;
|
||||
cmd.addr = (u32)from;
|
||||
cmd.tx_data = true;
|
||||
cmd.dummy = nor->read_dummy;
|
||||
cmd.len = len;
|
||||
cmd.buf = buf;
|
||||
cmd.qspimode = flash->read_mode;
|
||||
|
||||
stm32_qspi_set_framemode(nor, &cmd, true);
|
||||
err = stm32_qspi_send(flash, &cmd);
|
||||
|
||||
return err ? err : len;
|
||||
}
|
||||
|
||||
static ssize_t stm32_qspi_write(struct spi_nor *nor, loff_t to, size_t len,
|
||||
const u_char *buf)
|
||||
{
|
||||
struct stm32_qspi_flash *flash = nor->priv;
|
||||
struct device *dev = flash->qspi->dev;
|
||||
struct stm32_qspi_cmd cmd;
|
||||
int err;
|
||||
|
||||
dev_dbg(dev, "write(%#.2x): buf:%p to:%#.8x len:%#zx\n",
|
||||
nor->program_opcode, buf, (u32)to, len);
|
||||
|
||||
memset(&cmd, 0, sizeof(cmd));
|
||||
cmd.opcode = nor->program_opcode;
|
||||
cmd.addr_width = nor->addr_width;
|
||||
cmd.addr = (u32)to;
|
||||
cmd.tx_data = true;
|
||||
cmd.len = len;
|
||||
cmd.buf = (void *)buf;
|
||||
cmd.qspimode = CCR_FMODE_INDW;
|
||||
|
||||
stm32_qspi_set_framemode(nor, &cmd, false);
|
||||
err = stm32_qspi_send(flash, &cmd);
|
||||
|
||||
return err ? err : len;
|
||||
}
|
||||
|
||||
static int stm32_qspi_erase(struct spi_nor *nor, loff_t offs)
|
||||
{
|
||||
struct stm32_qspi_flash *flash = nor->priv;
|
||||
struct device *dev = flash->qspi->dev;
|
||||
struct stm32_qspi_cmd cmd;
|
||||
|
||||
dev_dbg(dev, "erase(%#.2x):offs:%#x\n", nor->erase_opcode, (u32)offs);
|
||||
|
||||
memset(&cmd, 0, sizeof(cmd));
|
||||
cmd.opcode = nor->erase_opcode;
|
||||
cmd.addr_width = nor->addr_width;
|
||||
cmd.addr = (u32)offs;
|
||||
cmd.qspimode = CCR_FMODE_INDW;
|
||||
|
||||
stm32_qspi_set_framemode(nor, &cmd, false);
|
||||
|
||||
return stm32_qspi_send(flash, &cmd);
|
||||
}
|
||||
|
||||
static irqreturn_t stm32_qspi_irq(int irq, void *dev_id)
|
||||
{
|
||||
struct stm32_qspi *qspi = (struct stm32_qspi *)dev_id;
|
||||
u32 cr, sr, fcr = 0;
|
||||
|
||||
cr = readl_relaxed(qspi->io_base + QUADSPI_CR);
|
||||
sr = readl_relaxed(qspi->io_base + QUADSPI_SR);
|
||||
|
||||
if ((cr & CR_TCIE) && (sr & SR_TCF)) {
|
||||
/* tx complete */
|
||||
fcr |= FCR_CTCF;
|
||||
complete(&qspi->cmd_completion);
|
||||
} else {
|
||||
dev_info_ratelimited(qspi->dev, "spurious interrupt\n");
|
||||
}
|
||||
|
||||
writel_relaxed(fcr, qspi->io_base + QUADSPI_FCR);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int stm32_qspi_prep(struct spi_nor *nor, enum spi_nor_ops ops)
|
||||
{
|
||||
struct stm32_qspi_flash *flash = nor->priv;
|
||||
struct stm32_qspi *qspi = flash->qspi;
|
||||
|
||||
mutex_lock(&qspi->lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void stm32_qspi_unprep(struct spi_nor *nor, enum spi_nor_ops ops)
|
||||
{
|
||||
struct stm32_qspi_flash *flash = nor->priv;
|
||||
struct stm32_qspi *qspi = flash->qspi;
|
||||
|
||||
mutex_unlock(&qspi->lock);
|
||||
}
|
||||
|
||||
static int stm32_qspi_flash_setup(struct stm32_qspi *qspi,
|
||||
struct device_node *np)
|
||||
{
|
||||
struct spi_nor_hwcaps hwcaps = {
|
||||
.mask = SNOR_HWCAPS_READ |
|
||||
SNOR_HWCAPS_READ_FAST |
|
||||
SNOR_HWCAPS_PP,
|
||||
};
|
||||
u32 width, presc, cs_num, max_rate = 0;
|
||||
struct stm32_qspi_flash *flash;
|
||||
struct mtd_info *mtd;
|
||||
int ret;
|
||||
|
||||
of_property_read_u32(np, "reg", &cs_num);
|
||||
if (cs_num >= STM32_MAX_NORCHIP)
|
||||
return -EINVAL;
|
||||
|
||||
of_property_read_u32(np, "spi-max-frequency", &max_rate);
|
||||
if (!max_rate)
|
||||
return -EINVAL;
|
||||
|
||||
presc = DIV_ROUND_UP(qspi->clk_rate, max_rate) - 1;
|
||||
|
||||
if (of_property_read_u32(np, "spi-rx-bus-width", &width))
|
||||
width = 1;
|
||||
|
||||
if (width == 4)
|
||||
hwcaps.mask |= SNOR_HWCAPS_READ_1_1_4;
|
||||
else if (width == 2)
|
||||
hwcaps.mask |= SNOR_HWCAPS_READ_1_1_2;
|
||||
else if (width != 1)
|
||||
return -EINVAL;
|
||||
|
||||
flash = &qspi->flash[cs_num];
|
||||
flash->qspi = qspi;
|
||||
flash->cs = cs_num;
|
||||
flash->presc = presc;
|
||||
|
||||
flash->nor.dev = qspi->dev;
|
||||
spi_nor_set_flash_node(&flash->nor, np);
|
||||
flash->nor.priv = flash;
|
||||
mtd = &flash->nor.mtd;
|
||||
|
||||
flash->nor.read = stm32_qspi_read;
|
||||
flash->nor.write = stm32_qspi_write;
|
||||
flash->nor.erase = stm32_qspi_erase;
|
||||
flash->nor.read_reg = stm32_qspi_read_reg;
|
||||
flash->nor.write_reg = stm32_qspi_write_reg;
|
||||
flash->nor.prepare = stm32_qspi_prep;
|
||||
flash->nor.unprepare = stm32_qspi_unprep;
|
||||
|
||||
writel_relaxed(LPTR_DFT_TIMEOUT, qspi->io_base + QUADSPI_LPTR);
|
||||
|
||||
writel_relaxed(CR_PRESC(presc) | CR_FTHRES(3) | CR_TCEN | CR_SSHIFT
|
||||
| CR_EN, qspi->io_base + QUADSPI_CR);
|
||||
|
||||
/*
|
||||
* in stm32 qspi controller, QUADSPI_DCR register has a fsize field
|
||||
* which define the size of nor flash.
|
||||
* if fsize is NULL, the controller can't sent spi-nor command.
|
||||
* set a temporary value just to discover the nor flash with
|
||||
* "spi_nor_scan". After, the right value (mtd->size) can be set.
|
||||
*/
|
||||
flash->fsize = FSIZE_VAL(SZ_1K);
|
||||
|
||||
ret = spi_nor_scan(&flash->nor, NULL, &hwcaps);
|
||||
if (ret) {
|
||||
dev_err(qspi->dev, "device scan failed\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
flash->fsize = FSIZE_VAL(mtd->size);
|
||||
flash->prefetch_limit = mtd->size - STM32_QSPI_FIFO_SZ;
|
||||
|
||||
flash->read_mode = CCR_FMODE_MM;
|
||||
if (mtd->size > qspi->mm_size)
|
||||
flash->read_mode = CCR_FMODE_INDR;
|
||||
|
||||
writel_relaxed(DCR_CSHT(1), qspi->io_base + QUADSPI_DCR);
|
||||
|
||||
ret = mtd_device_register(mtd, NULL, 0);
|
||||
if (ret) {
|
||||
dev_err(qspi->dev, "mtd device parse failed\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
flash->registered = true;
|
||||
|
||||
dev_dbg(qspi->dev, "read mm:%s cs:%d bus:%d\n",
|
||||
flash->read_mode == CCR_FMODE_MM ? "yes" : "no", cs_num, width);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void stm32_qspi_mtd_free(struct stm32_qspi *qspi)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < STM32_MAX_NORCHIP; i++)
|
||||
if (qspi->flash[i].registered)
|
||||
mtd_device_unregister(&qspi->flash[i].nor.mtd);
|
||||
}
|
||||
|
||||
static int stm32_qspi_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct device_node *flash_np;
|
||||
struct reset_control *rstc;
|
||||
struct stm32_qspi *qspi;
|
||||
struct resource *res;
|
||||
int ret, irq;
|
||||
|
||||
qspi = devm_kzalloc(dev, sizeof(*qspi), GFP_KERNEL);
|
||||
if (!qspi)
|
||||
return -ENOMEM;
|
||||
|
||||
qspi->nor_num = of_get_child_count(dev->of_node);
|
||||
if (!qspi->nor_num || qspi->nor_num > STM32_MAX_NORCHIP)
|
||||
return -ENODEV;
|
||||
|
||||
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "qspi");
|
||||
qspi->io_base = devm_ioremap_resource(dev, res);
|
||||
if (IS_ERR(qspi->io_base))
|
||||
return PTR_ERR(qspi->io_base);
|
||||
|
||||
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "qspi_mm");
|
||||
qspi->mm_base = devm_ioremap_resource(dev, res);
|
||||
if (IS_ERR(qspi->mm_base))
|
||||
return PTR_ERR(qspi->mm_base);
|
||||
|
||||
qspi->mm_size = resource_size(res);
|
||||
|
||||
irq = platform_get_irq(pdev, 0);
|
||||
ret = devm_request_irq(dev, irq, stm32_qspi_irq, 0,
|
||||
dev_name(dev), qspi);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to request irq\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
init_completion(&qspi->cmd_completion);
|
||||
|
||||
qspi->clk = devm_clk_get(dev, NULL);
|
||||
if (IS_ERR(qspi->clk))
|
||||
return PTR_ERR(qspi->clk);
|
||||
|
||||
qspi->clk_rate = clk_get_rate(qspi->clk);
|
||||
if (!qspi->clk_rate)
|
||||
return -EINVAL;
|
||||
|
||||
ret = clk_prepare_enable(qspi->clk);
|
||||
if (ret) {
|
||||
dev_err(dev, "can not enable the clock\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
rstc = devm_reset_control_get_exclusive(dev, NULL);
|
||||
if (!IS_ERR(rstc)) {
|
||||
reset_control_assert(rstc);
|
||||
udelay(2);
|
||||
reset_control_deassert(rstc);
|
||||
}
|
||||
|
||||
qspi->dev = dev;
|
||||
platform_set_drvdata(pdev, qspi);
|
||||
mutex_init(&qspi->lock);
|
||||
|
||||
for_each_available_child_of_node(dev->of_node, flash_np) {
|
||||
ret = stm32_qspi_flash_setup(qspi, flash_np);
|
||||
if (ret) {
|
||||
dev_err(dev, "unable to setup flash chip\n");
|
||||
goto err_flash;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_flash:
|
||||
mutex_destroy(&qspi->lock);
|
||||
stm32_qspi_mtd_free(qspi);
|
||||
|
||||
clk_disable_unprepare(qspi->clk);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int stm32_qspi_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct stm32_qspi *qspi = platform_get_drvdata(pdev);
|
||||
|
||||
/* disable qspi */
|
||||
writel_relaxed(0, qspi->io_base + QUADSPI_CR);
|
||||
|
||||
stm32_qspi_mtd_free(qspi);
|
||||
mutex_destroy(&qspi->lock);
|
||||
|
||||
clk_disable_unprepare(qspi->clk);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id stm32_qspi_match[] = {
|
||||
{.compatible = "st,stm32f469-qspi"},
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, stm32_qspi_match);
|
||||
|
||||
static struct platform_driver stm32_qspi_driver = {
|
||||
.probe = stm32_qspi_probe,
|
||||
.remove = stm32_qspi_remove,
|
||||
.driver = {
|
||||
.name = "stm32-quadspi",
|
||||
.of_match_table = stm32_qspi_match,
|
||||
},
|
||||
};
|
||||
module_platform_driver(stm32_qspi_driver);
|
||||
|
||||
MODULE_AUTHOR("Ludovic Barre <ludovic.barre@st.com>");
|
||||
MODULE_DESCRIPTION("STMicroelectronics STM32 quad spi driver");
|
||||
MODULE_LICENSE("GPL v2");
|
|
@ -0,0 +1,24 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
#ifndef _MXS_DMA_H_
|
||||
#define _MXS_DMA_H_
|
||||
|
||||
#include <linux/dmaengine.h>
|
||||
|
||||
#define MXS_DMA_CTRL_WAIT4END BIT(31)
|
||||
#define MXS_DMA_CTRL_WAIT4RDY BIT(30)
|
||||
|
||||
/*
|
||||
* The mxs dmaengine can do PIO transfers. We pass a pointer to the PIO words
|
||||
* in the second argument to dmaengine_prep_slave_sg when the direction is
|
||||
* set to DMA_TRANS_NONE. To make this clear and to prevent users from doing
|
||||
* the error prone casting we have this wrapper function
|
||||
*/
|
||||
static inline struct dma_async_tx_descriptor *mxs_dmaengine_prep_pio(
|
||||
struct dma_chan *chan, u32 *pio, unsigned int npio,
|
||||
enum dma_transfer_direction dir, unsigned long flags)
|
||||
{
|
||||
return dmaengine_prep_slave_sg(chan, (struct scatterlist *)pio, npio,
|
||||
dir, flags);
|
||||
}
|
||||
|
||||
#endif /* _MXS_DMA_H_ */
|
|
@ -219,6 +219,13 @@ struct cfi_pri_amdstd {
|
|||
uint8_t VppMin;
|
||||
uint8_t VppMax;
|
||||
uint8_t TopBottom;
|
||||
/* Below field are added from version 1.5 */
|
||||
uint8_t ProgramSuspend;
|
||||
uint8_t UnlockBypass;
|
||||
uint8_t SecureSiliconSector;
|
||||
uint8_t SoftwareFeatures;
|
||||
#define CFI_POLL_STATUS_REG BIT(0)
|
||||
#define CFI_POLL_DQ BIT(1)
|
||||
} __packed;
|
||||
|
||||
/* Vendor-Specific PRI for Atmel chips (command set 0x0002) */
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0
|
||||
*
|
||||
* Copyright (C) 2019 Texas Instruments Incorporated - http://www.ti.com/
|
||||
*/
|
||||
|
||||
#ifndef __LINUX_MTD_HYPERBUS_H__
|
||||
#define __LINUX_MTD_HYPERBUS_H__
|
||||
|
||||
#include <linux/mtd/map.h>
|
||||
|
||||
enum hyperbus_memtype {
|
||||
HYPERFLASH,
|
||||
HYPERRAM,
|
||||
};
|
||||
|
||||
/**
|
||||
* struct hyperbus_device - struct representing HyperBus slave device
|
||||
* @map: map_info struct for accessing MMIO HyperBus flash memory
|
||||
* @np: pointer to HyperBus slave device node
|
||||
* @mtd: pointer to MTD struct
|
||||
* @ctlr: pointer to HyperBus controller struct
|
||||
* @memtype: type of memory device: HyperFlash or HyperRAM
|
||||
*/
|
||||
|
||||
struct hyperbus_device {
|
||||
struct map_info map;
|
||||
struct device_node *np;
|
||||
struct mtd_info *mtd;
|
||||
struct hyperbus_ctlr *ctlr;
|
||||
enum hyperbus_memtype memtype;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct hyperbus_ops - struct representing custom HyperBus operations
|
||||
* @read16: read 16 bit of data from flash in a single burst. Used to read
|
||||
* from non default address space, such as ID/CFI space
|
||||
* @write16: write 16 bit of data to flash in a single burst. Used to
|
||||
* send cmd to flash or write single 16 bit word at a time.
|
||||
* @copy_from: copy data from flash memory
|
||||
* @copy_to: copy data to flash memory
|
||||
* @calibrate: calibrate HyperBus controller
|
||||
*/
|
||||
|
||||
struct hyperbus_ops {
|
||||
u16 (*read16)(struct hyperbus_device *hbdev, unsigned long addr);
|
||||
void (*write16)(struct hyperbus_device *hbdev,
|
||||
unsigned long addr, u16 val);
|
||||
void (*copy_from)(struct hyperbus_device *hbdev, void *to,
|
||||
unsigned long from, ssize_t len);
|
||||
void (*copy_to)(struct hyperbus_device *dev, unsigned long to,
|
||||
const void *from, ssize_t len);
|
||||
int (*calibrate)(struct hyperbus_device *dev);
|
||||
};
|
||||
|
||||
/**
|
||||
* struct hyperbus_ctlr - struct representing HyperBus controller
|
||||
* @dev: pointer to HyperBus controller device
|
||||
* @calibrated: flag to indicate ctlr calibration sequence is complete
|
||||
* @ops: HyperBus controller ops
|
||||
*/
|
||||
struct hyperbus_ctlr {
|
||||
struct device *dev;
|
||||
bool calibrated;
|
||||
|
||||
const struct hyperbus_ops *ops;
|
||||
};
|
||||
|
||||
/**
|
||||
* hyperbus_register_device - probe and register a HyperBus slave memory device
|
||||
* @hbdev: hyperbus_device struct with dev, np and ctlr field populated
|
||||
*
|
||||
* Return: 0 for success, others for failure.
|
||||
*/
|
||||
int hyperbus_register_device(struct hyperbus_device *hbdev);
|
||||
|
||||
/**
|
||||
* hyperbus_unregister_device - deregister HyperBus slave memory device
|
||||
* @hbdev: hyperbus_device to be unregistered
|
||||
*
|
||||
* Return: 0 for success, others for failure.
|
||||
*/
|
||||
int hyperbus_unregister_device(struct hyperbus_device *hbdev);
|
||||
|
||||
#endif /* __LINUX_MTD_HYPERBUS_H__ */
|
|
@ -316,6 +316,12 @@ struct mtd_info {
|
|||
int (*_get_device) (struct mtd_info *mtd);
|
||||
void (*_put_device) (struct mtd_info *mtd);
|
||||
|
||||
/*
|
||||
* flag indicates a panic write, low level drivers can take appropriate
|
||||
* action if required to ensure writes go through
|
||||
*/
|
||||
bool oops_panic_write;
|
||||
|
||||
struct notifier_block reboot_notifier; /* default mode before reboot */
|
||||
|
||||
/* ECC status information */
|
||||
|
|
|
@ -77,6 +77,7 @@
|
|||
#define ONENAND_DEVICE_DENSITY_1Gb (0x003)
|
||||
#define ONENAND_DEVICE_DENSITY_2Gb (0x004)
|
||||
#define ONENAND_DEVICE_DENSITY_4Gb (0x005)
|
||||
#define ONENAND_DEVICE_DENSITY_8Gb (0x006)
|
||||
|
||||
/*
|
||||
* Version ID Register F002h (R)
|
||||
|
|
|
@ -874,6 +874,42 @@ int nand_op_parser_exec_op(struct nand_chip *chip,
|
|||
const struct nand_op_parser *parser,
|
||||
const struct nand_operation *op, bool check_only);
|
||||
|
||||
static inline void nand_op_trace(const char *prefix,
|
||||
const struct nand_op_instr *instr)
|
||||
{
|
||||
#if IS_ENABLED(CONFIG_DYNAMIC_DEBUG) || defined(DEBUG)
|
||||
switch (instr->type) {
|
||||
case NAND_OP_CMD_INSTR:
|
||||
pr_debug("%sCMD [0x%02x]\n", prefix,
|
||||
instr->ctx.cmd.opcode);
|
||||
break;
|
||||
case NAND_OP_ADDR_INSTR:
|
||||
pr_debug("%sADDR [%d cyc: %*ph]\n", prefix,
|
||||
instr->ctx.addr.naddrs,
|
||||
instr->ctx.addr.naddrs < 64 ?
|
||||
instr->ctx.addr.naddrs : 64,
|
||||
instr->ctx.addr.addrs);
|
||||
break;
|
||||
case NAND_OP_DATA_IN_INSTR:
|
||||
pr_debug("%sDATA_IN [%d B%s]\n", prefix,
|
||||
instr->ctx.data.len,
|
||||
instr->ctx.data.force_8bit ?
|
||||
", force 8-bit" : "");
|
||||
break;
|
||||
case NAND_OP_DATA_OUT_INSTR:
|
||||
pr_debug("%sDATA_OUT [%d B%s]\n", prefix,
|
||||
instr->ctx.data.len,
|
||||
instr->ctx.data.force_8bit ?
|
||||
", force 8-bit" : "");
|
||||
break;
|
||||
case NAND_OP_WAITRDY_INSTR:
|
||||
pr_debug("%sWAITRDY [max %d ms]\n", prefix,
|
||||
instr->ctx.waitrdy.timeout_ms);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* struct nand_controller_ops - Controller operations
|
||||
*
|
||||
|
|
|
@ -68,30 +68,60 @@
|
|||
SPI_MEM_OP_DUMMY(ndummy, 1), \
|
||||
SPI_MEM_OP_DATA_IN(len, buf, 1))
|
||||
|
||||
#define SPINAND_PAGE_READ_FROM_CACHE_OP_3A(fast, addr, ndummy, buf, len) \
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD(fast ? 0x0b : 0x03, 1), \
|
||||
SPI_MEM_OP_ADDR(3, addr, 1), \
|
||||
SPI_MEM_OP_DUMMY(ndummy, 1), \
|
||||
SPI_MEM_OP_DATA_IN(len, buf, 1))
|
||||
|
||||
#define SPINAND_PAGE_READ_FROM_CACHE_X2_OP(addr, ndummy, buf, len) \
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD(0x3b, 1), \
|
||||
SPI_MEM_OP_ADDR(2, addr, 1), \
|
||||
SPI_MEM_OP_DUMMY(ndummy, 1), \
|
||||
SPI_MEM_OP_DATA_IN(len, buf, 2))
|
||||
|
||||
#define SPINAND_PAGE_READ_FROM_CACHE_X2_OP_3A(addr, ndummy, buf, len) \
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD(0x3b, 1), \
|
||||
SPI_MEM_OP_ADDR(3, addr, 1), \
|
||||
SPI_MEM_OP_DUMMY(ndummy, 1), \
|
||||
SPI_MEM_OP_DATA_IN(len, buf, 2))
|
||||
|
||||
#define SPINAND_PAGE_READ_FROM_CACHE_X4_OP(addr, ndummy, buf, len) \
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD(0x6b, 1), \
|
||||
SPI_MEM_OP_ADDR(2, addr, 1), \
|
||||
SPI_MEM_OP_DUMMY(ndummy, 1), \
|
||||
SPI_MEM_OP_DATA_IN(len, buf, 4))
|
||||
|
||||
#define SPINAND_PAGE_READ_FROM_CACHE_X4_OP_3A(addr, ndummy, buf, len) \
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD(0x6b, 1), \
|
||||
SPI_MEM_OP_ADDR(3, addr, 1), \
|
||||
SPI_MEM_OP_DUMMY(ndummy, 1), \
|
||||
SPI_MEM_OP_DATA_IN(len, buf, 4))
|
||||
|
||||
#define SPINAND_PAGE_READ_FROM_CACHE_DUALIO_OP(addr, ndummy, buf, len) \
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD(0xbb, 1), \
|
||||
SPI_MEM_OP_ADDR(2, addr, 2), \
|
||||
SPI_MEM_OP_DUMMY(ndummy, 2), \
|
||||
SPI_MEM_OP_DATA_IN(len, buf, 2))
|
||||
|
||||
#define SPINAND_PAGE_READ_FROM_CACHE_DUALIO_OP_3A(addr, ndummy, buf, len) \
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD(0xbb, 1), \
|
||||
SPI_MEM_OP_ADDR(3, addr, 2), \
|
||||
SPI_MEM_OP_DUMMY(ndummy, 2), \
|
||||
SPI_MEM_OP_DATA_IN(len, buf, 2))
|
||||
|
||||
#define SPINAND_PAGE_READ_FROM_CACHE_QUADIO_OP(addr, ndummy, buf, len) \
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD(0xeb, 1), \
|
||||
SPI_MEM_OP_ADDR(2, addr, 4), \
|
||||
SPI_MEM_OP_DUMMY(ndummy, 4), \
|
||||
SPI_MEM_OP_DATA_IN(len, buf, 4))
|
||||
|
||||
#define SPINAND_PAGE_READ_FROM_CACHE_QUADIO_OP_3A(addr, ndummy, buf, len) \
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD(0xeb, 1), \
|
||||
SPI_MEM_OP_ADDR(3, addr, 4), \
|
||||
SPI_MEM_OP_DUMMY(ndummy, 4), \
|
||||
SPI_MEM_OP_DATA_IN(len, buf, 4))
|
||||
|
||||
#define SPINAND_PROG_EXEC_OP(addr) \
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD(0x10, 1), \
|
||||
SPI_MEM_OP_ADDR(3, addr, 1), \
|
||||
|
@ -197,6 +227,7 @@ struct spinand_manufacturer {
|
|||
extern const struct spinand_manufacturer gigadevice_spinand_manufacturer;
|
||||
extern const struct spinand_manufacturer macronix_spinand_manufacturer;
|
||||
extern const struct spinand_manufacturer micron_spinand_manufacturer;
|
||||
extern const struct spinand_manufacturer paragon_spinand_manufacturer;
|
||||
extern const struct spinand_manufacturer toshiba_spinand_manufacturer;
|
||||
extern const struct spinand_manufacturer winbond_spinand_manufacturer;
|
||||
|
||||
|
@ -260,7 +291,7 @@ struct spinand_ecc_info {
|
|||
*/
|
||||
struct spinand_info {
|
||||
const char *model;
|
||||
u8 devid;
|
||||
u16 devid;
|
||||
u32 flags;
|
||||
struct nand_memory_organization memorg;
|
||||
struct nand_ecc_req eccreq;
|
||||
|
@ -422,7 +453,7 @@ static inline void spinand_set_of_node(struct spinand_device *spinand,
|
|||
|
||||
int spinand_match_and_init(struct spinand_device *dev,
|
||||
const struct spinand_info *table,
|
||||
unsigned int table_size, u8 devid);
|
||||
unsigned int table_size, u16 devid);
|
||||
|
||||
int spinand_upd_cfg(struct spinand_device *spinand, u8 mask, u8 val);
|
||||
int spinand_select_target(struct spinand_device *spinand, unsigned int target);
|
||||
|
|
|
@ -113,11 +113,11 @@ struct mtd_write_req {
|
|||
#define MTD_CAP_NVRAM (MTD_WRITEABLE | MTD_BIT_WRITEABLE | MTD_NO_ERASE)
|
||||
|
||||
/* Obsolete ECC byte placement modes (used with obsolete MEMGETOOBSEL) */
|
||||
#define MTD_NANDECC_OFF 0 // Switch off ECC (Not recommended)
|
||||
#define MTD_NANDECC_PLACE 1 // Use the given placement in the structure (YAFFS1 legacy mode)
|
||||
#define MTD_NANDECC_AUTOPLACE 2 // Use the default placement scheme
|
||||
#define MTD_NANDECC_PLACEONLY 3 // Use the given placement in the structure (Do not store ecc result on read)
|
||||
#define MTD_NANDECC_AUTOPL_USR 4 // Use the given autoplacement scheme rather than using the default
|
||||
#define MTD_NANDECC_OFF 0 /* Switch off ECC (Not recommended) */
|
||||
#define MTD_NANDECC_PLACE 1 /* Use the given placement in the structure (YAFFS1 legacy mode) */
|
||||
#define MTD_NANDECC_AUTOPLACE 2 /* Use the default placement scheme */
|
||||
#define MTD_NANDECC_PLACEONLY 3 /* Use the given placement in the structure (Do not store ecc result on read) */
|
||||
#define MTD_NANDECC_AUTOPL_USR 4 /* Use the given autoplacement scheme rather than using the default */
|
||||
|
||||
/* OTP mode selection */
|
||||
#define MTD_OTP_OFF 0
|
||||
|
|
Loading…
Reference in New Issue