diff options
Diffstat (limited to 'target/linux/pistachio/patches-4.14')
16 files changed, 2080 insertions, 0 deletions
diff --git a/target/linux/pistachio/patches-4.14/101-dmaengine-img-mdc-Handle-early-status-read.patch b/target/linux/pistachio/patches-4.14/101-dmaengine-img-mdc-Handle-early-status-read.patch new file mode 100644 index 0000000000..92293f8796 --- /dev/null +++ b/target/linux/pistachio/patches-4.14/101-dmaengine-img-mdc-Handle-early-status-read.patch @@ -0,0 +1,68 @@ +From a2dd154377c9aa6ddda00d39b8c7c334e4fa16ff Mon Sep 17 00:00:00 2001 +From: Damien Horsley <damien.horsley@imgtec.com> +Date: Tue, 22 Mar 2016 12:46:09 +0000 +Subject: dmaengine: img-mdc: Handle early status read + +It is possible that mdc_tx_status may be called before the first +node has been read from memory. + +In this case, the residue value stored in the register is undefined. +Return the transfer size instead. + +Signed-off-by: Damien Horsley <damien.horsley@imgtec.com> +--- + drivers/dma/img-mdc-dma.c | 40 ++++++++++++++++++++++++---------------- + 1 file changed, 24 insertions(+), 16 deletions(-) + +--- a/drivers/dma/img-mdc-dma.c ++++ b/drivers/dma/img-mdc-dma.c +@@ -620,25 +620,33 @@ static enum dma_status mdc_tx_status(str + (MDC_CMDS_PROCESSED_CMDS_DONE_MASK + 1); + + /* +- * If the command loaded event hasn't been processed yet, then +- * the difference above includes an extra command. ++ * If the first node has not yet been read from memory, ++ * the residue register value is undefined + */ +- if (!mdesc->cmd_loaded) +- cmds--; +- else +- cmds += mdesc->list_cmds_done; +- +- bytes = mdesc->list_xfer_size; +- ldesc = mdesc->list; +- for (i = 0; i < cmds; i++) { +- bytes -= ldesc->xfer_size + 1; +- ldesc = ldesc->next_desc; +- } +- if (ldesc) { +- if (residue != MDC_TRANSFER_SIZE_MASK) +- bytes -= ldesc->xfer_size - residue; ++ if (!mdesc->cmd_loaded && !cmds) { ++ bytes = mdesc->list_xfer_size; ++ } else { ++ /* ++ * If the command loaded event hasn't been processed yet, then ++ * the difference above includes an extra command. ++ */ ++ if (!mdesc->cmd_loaded) ++ cmds--; + else ++ cmds += mdesc->list_cmds_done; ++ ++ bytes = mdesc->list_xfer_size; ++ ldesc = mdesc->list; ++ for (i = 0; i < cmds; i++) { + bytes -= ldesc->xfer_size + 1; ++ ldesc = ldesc->next_desc; ++ } ++ if (ldesc) { ++ if (residue != MDC_TRANSFER_SIZE_MASK) ++ bytes -= ldesc->xfer_size - residue; ++ else ++ bytes -= ldesc->xfer_size + 1; ++ } + } + } + spin_unlock_irqrestore(&mchan->vc.lock, flags); diff --git a/target/linux/pistachio/patches-4.14/102-spi-img-spfi-Implement-dual-and-quad-mode.patch b/target/linux/pistachio/patches-4.14/102-spi-img-spfi-Implement-dual-and-quad-mode.patch new file mode 100644 index 0000000000..15a5d3c806 --- /dev/null +++ b/target/linux/pistachio/patches-4.14/102-spi-img-spfi-Implement-dual-and-quad-mode.patch @@ -0,0 +1,198 @@ +From cd2a6af51553d38072cd31699b58d16ca6176ef5 Mon Sep 17 00:00:00 2001 +From: Ionela Voinescu <ionela.voinescu@imgtec.com> +Date: Thu, 2 Feb 2017 16:46:14 +0000 +Subject: spi: img-spfi: Implement dual and quad mode + +For dual and quad modes to work the SPFI controller needs +to have information about command/address/dummy bytes in the +transaction register. This information is not relevant for +single mode, and therefore it can have any value in the +allowed range. Therefore, for any read or write transfers of less +than 8 bytes (cmd = 1 byte, addr up to 7 bytes), SPFI will be +configured, but not enabled (unless it is the last transfer in +the queue). The transfer will be enabled by the subsequent tranfer. +A pending transfer is determined by the content of the transaction +register: if command part is set and tsize is not. + +This way we ensure that for dual and quad transactions +the command request size will apear in the command/address part +of the transaction register, while the data size will be in +tsize, all data being sent/received in the same transaction (as +set up in the transaction register). + +Signed-off-by: Ionela Voinescu <ionela.voinescu@imgtec.com> +Signed-off-by: Ezequiel Garcia <ezequiel.garcia@imgtec.com> +--- + drivers/spi/spi-img-spfi.c | 96 ++++++++++++++++++++++++++++++++++++++++------ + 1 file changed, 85 insertions(+), 11 deletions(-) + +--- a/drivers/spi/spi-img-spfi.c ++++ b/drivers/spi/spi-img-spfi.c +@@ -40,7 +40,8 @@ + #define SPFI_CONTROL_SOFT_RESET BIT(11) + #define SPFI_CONTROL_SEND_DMA BIT(10) + #define SPFI_CONTROL_GET_DMA BIT(9) +-#define SPFI_CONTROL_SE BIT(8) ++#define SPFI_CONTROL_SE BIT(8) ++#define SPFI_CONTROL_TX_RX BIT(1) + #define SPFI_CONTROL_TMODE_SHIFT 5 + #define SPFI_CONTROL_TMODE_MASK 0x7 + #define SPFI_CONTROL_TMODE_SINGLE 0 +@@ -51,6 +52,10 @@ + #define SPFI_TRANSACTION 0x18 + #define SPFI_TRANSACTION_TSIZE_SHIFT 16 + #define SPFI_TRANSACTION_TSIZE_MASK 0xffff ++#define SPFI_TRANSACTION_CMD_SHIFT 13 ++#define SPFI_TRANSACTION_CMD_MASK 0x7 ++#define SPFI_TRANSACTION_ADDR_SHIFT 10 ++#define SPFI_TRANSACTION_ADDR_MASK 0x7 + + #define SPFI_PORT_STATE 0x1c + #define SPFI_PORT_STATE_DEV_SEL_SHIFT 20 +@@ -87,6 +92,7 @@ + */ + #define SPFI_32BIT_FIFO_SIZE 64 + #define SPFI_8BIT_FIFO_SIZE 16 ++#define SPFI_DATA_REQUEST_MAX_SIZE 8 + + struct img_spfi { + struct device *dev; +@@ -103,6 +109,8 @@ struct img_spfi { + struct dma_chan *tx_ch; + bool tx_dma_busy; + bool rx_dma_busy; ++ ++ bool complete; + }; + + struct img_spfi_device_data { +@@ -123,9 +131,11 @@ static inline void spfi_start(struct img + { + u32 val; + +- val = spfi_readl(spfi, SPFI_CONTROL); +- val |= SPFI_CONTROL_SPFI_EN; +- spfi_writel(spfi, val, SPFI_CONTROL); ++ if (spfi->complete) { ++ val = spfi_readl(spfi, SPFI_CONTROL); ++ val |= SPFI_CONTROL_SPFI_EN; ++ spfi_writel(spfi, val, SPFI_CONTROL); ++ } + } + + static inline void spfi_reset(struct img_spfi *spfi) +@@ -138,12 +148,21 @@ static int spfi_wait_all_done(struct img + { + unsigned long timeout = jiffies + msecs_to_jiffies(50); + ++ if (!(spfi->complete)) ++ return 0; ++ + while (time_before(jiffies, timeout)) { + u32 status = spfi_readl(spfi, SPFI_INTERRUPT_STATUS); + + if (status & SPFI_INTERRUPT_ALLDONETRIG) { + spfi_writel(spfi, SPFI_INTERRUPT_ALLDONETRIG, + SPFI_INTERRUPT_CLEAR); ++ /* ++ * Disable SPFI for it not to interfere with ++ * pending transactions ++ */ ++ spfi_writel(spfi, spfi_readl(spfi, SPFI_CONTROL) ++ & ~SPFI_CONTROL_SPFI_EN, SPFI_CONTROL); + return 0; + } + cpu_relax(); +@@ -494,9 +513,32 @@ static void img_spfi_config(struct spi_m + struct spi_transfer *xfer) + { + struct img_spfi *spfi = spi_master_get_devdata(spi->master); +- u32 val, div; ++ u32 val, div, transact; ++ bool is_pending; + + /* ++ * For read or write transfers of less than 8 bytes (cmd = 1 byte, ++ * addr up to 7 bytes), SPFI will be configured, but not enabled ++ * (unless it is the last transfer in the queue).The transfer will ++ * be enabled by the subsequent transfer. ++ * A pending transfer is determined by the content of the ++ * transaction register: if command part is set and tsize ++ * is not ++ */ ++ transact = spfi_readl(spfi, SPFI_TRANSACTION); ++ is_pending = ((transact >> SPFI_TRANSACTION_CMD_SHIFT) & ++ SPFI_TRANSACTION_CMD_MASK) && ++ (!((transact >> SPFI_TRANSACTION_TSIZE_SHIFT) & ++ SPFI_TRANSACTION_TSIZE_MASK)); ++ ++ /* If there are no pending transactions it's OK to soft reset */ ++ if (!is_pending) { ++ /* Start the transaction from a known (reset) state */ ++ spfi_reset(spfi); ++ } ++ ++ /* ++ * Before anything else, set up parameters. + * output = spfi_clk * (BITCLK / 512), where BITCLK must be a + * power of 2 up to 128 + */ +@@ -509,20 +551,52 @@ static void img_spfi_config(struct spi_m + val |= div << SPFI_DEVICE_PARAMETER_BITCLK_SHIFT; + spfi_writel(spfi, val, SPFI_DEVICE_PARAMETER(spi->chip_select)); + +- spfi_writel(spfi, xfer->len << SPFI_TRANSACTION_TSIZE_SHIFT, +- SPFI_TRANSACTION); ++ if (!list_is_last(&xfer->transfer_list, &master->cur_msg->transfers) && ++ /* ++ * For duplex mode (both the tx and rx buffers are !NULL) the ++ * CMD, ADDR, and DUMMY byte parts of the transaction register ++ * should always be 0 and therefore the pending transfer ++ * technique cannot be used. ++ */ ++ (xfer->tx_buf) && (!xfer->rx_buf) && ++ (xfer->len <= SPFI_DATA_REQUEST_MAX_SIZE) && !is_pending) { ++ transact = (1 & SPFI_TRANSACTION_CMD_MASK) << ++ SPFI_TRANSACTION_CMD_SHIFT; ++ transact |= ((xfer->len - 1) & SPFI_TRANSACTION_ADDR_MASK) << ++ SPFI_TRANSACTION_ADDR_SHIFT; ++ spfi->complete = false; ++ } else { ++ spfi->complete = true; ++ if (is_pending) { ++ /* Keep setup from pending transfer */ ++ transact |= ((xfer->len & SPFI_TRANSACTION_TSIZE_MASK) << ++ SPFI_TRANSACTION_TSIZE_SHIFT); ++ } else { ++ transact = ((xfer->len & SPFI_TRANSACTION_TSIZE_MASK) << ++ SPFI_TRANSACTION_TSIZE_SHIFT); ++ } ++ } ++ spfi_writel(spfi, transact, SPFI_TRANSACTION); + + val = spfi_readl(spfi, SPFI_CONTROL); + val &= ~(SPFI_CONTROL_SEND_DMA | SPFI_CONTROL_GET_DMA); +- if (xfer->tx_buf) ++ /* ++ * We set up send DMA for pending transfers also, as ++ * those are always send transfers ++ */ ++ if ((xfer->tx_buf) || is_pending) + val |= SPFI_CONTROL_SEND_DMA; +- if (xfer->rx_buf) ++ if (xfer->tx_buf) ++ val |= SPFI_CONTROL_TX_RX; ++ if (xfer->rx_buf) { + val |= SPFI_CONTROL_GET_DMA; ++ val &= ~SPFI_CONTROL_TX_RX; ++ } + val &= ~(SPFI_CONTROL_TMODE_MASK << SPFI_CONTROL_TMODE_SHIFT); +- if (xfer->tx_nbits == SPI_NBITS_DUAL && ++ if (xfer->tx_nbits == SPI_NBITS_DUAL || + xfer->rx_nbits == SPI_NBITS_DUAL) + val |= SPFI_CONTROL_TMODE_DUAL << SPFI_CONTROL_TMODE_SHIFT; +- else if (xfer->tx_nbits == SPI_NBITS_QUAD && ++ else if (xfer->tx_nbits == SPI_NBITS_QUAD || + xfer->rx_nbits == SPI_NBITS_QUAD) + val |= SPFI_CONTROL_TMODE_QUAD << SPFI_CONTROL_TMODE_SHIFT; + val |= SPFI_CONTROL_SE; diff --git a/target/linux/pistachio/patches-4.14/103-spi-img-spfi-set-device-select-bits-for-SPFI-port-st.patch b/target/linux/pistachio/patches-4.14/103-spi-img-spfi-set-device-select-bits-for-SPFI-port-st.patch new file mode 100644 index 0000000000..ba70348da9 --- /dev/null +++ b/target/linux/pistachio/patches-4.14/103-spi-img-spfi-set-device-select-bits-for-SPFI-port-st.patch @@ -0,0 +1,27 @@ +From 145f5369510b86cd55c659388a26a0cc267f8874 Mon Sep 17 00:00:00 2001 +From: Ionela Voinescu <ionela.voinescu@imgtec.com> +Date: Mon, 1 Feb 2016 10:58:08 +0000 +Subject: spi: img-spfi: set device select bits for SPFI port state + +Even if the chip select line is not controlled by the SPFI +hardware, the device select bits need to be set to specify +the chip select line in use for the hardware to know what +parameters to use for the current transfer. + +Signed-off-by: Ionela Voinescu <ionela.voinescu@imgtec.com> +--- + drivers/spi/spi-img-spfi.c | 3 +++ + 1 file changed, 3 insertions(+) + +--- a/drivers/spi/spi-img-spfi.c ++++ b/drivers/spi/spi-img-spfi.c +@@ -438,6 +438,9 @@ static int img_spfi_prepare(struct spi_m + u32 val; + + val = spfi_readl(spfi, SPFI_PORT_STATE); ++ val &= ~(SPFI_PORT_STATE_DEV_SEL_MASK << ++ SPFI_PORT_STATE_DEV_SEL_SHIFT); ++ val |= msg->spi->chip_select << SPFI_PORT_STATE_DEV_SEL_SHIFT; + if (msg->spi->mode & SPI_CPHA) + val |= SPFI_PORT_STATE_CK_PHASE(msg->spi->chip_select); + else diff --git a/target/linux/pistachio/patches-4.14/104-spi-img-spfi-use-device-0-configuration-for-all-devi.patch b/target/linux/pistachio/patches-4.14/104-spi-img-spfi-use-device-0-configuration-for-all-devi.patch new file mode 100644 index 0000000000..6c9e6b5a76 --- /dev/null +++ b/target/linux/pistachio/patches-4.14/104-spi-img-spfi-use-device-0-configuration-for-all-devi.patch @@ -0,0 +1,64 @@ +From 905ee06a9966113fe51d6bad1819759cb30fd0bd Mon Sep 17 00:00:00 2001 +From: Ionela Voinescu <ionela.voinescu@imgtec.com> +Date: Tue, 9 Feb 2016 10:18:31 +0000 +Subject: spi: img-spfi: use device 0 configuration for all devices + +Given that we control the chip select line externally +we can use only one parameter register (device 0 parameter +register) and one set of configuration bits (port configuration +bits for device 0) for all devices (all chip select lines). + +Signed-off-by: Ionela Voinescu <ionela.voinescu@imgtec.com> +--- + drivers/spi/spi-img-spfi.c | 23 ++++++++++++++++------- + 1 file changed, 16 insertions(+), 7 deletions(-) + +--- a/drivers/spi/spi-img-spfi.c ++++ b/drivers/spi/spi-img-spfi.c +@@ -437,18 +437,23 @@ static int img_spfi_prepare(struct spi_m + struct img_spfi *spfi = spi_master_get_devdata(master); + u32 val; + ++ /* ++ * The chip select line is controlled externally so ++ * we can use the CS0 configuration for all devices ++ */ + val = spfi_readl(spfi, SPFI_PORT_STATE); ++ ++ /* 0 for device selection */ + val &= ~(SPFI_PORT_STATE_DEV_SEL_MASK << + SPFI_PORT_STATE_DEV_SEL_SHIFT); +- val |= msg->spi->chip_select << SPFI_PORT_STATE_DEV_SEL_SHIFT; + if (msg->spi->mode & SPI_CPHA) +- val |= SPFI_PORT_STATE_CK_PHASE(msg->spi->chip_select); ++ val |= SPFI_PORT_STATE_CK_PHASE(0); + else +- val &= ~SPFI_PORT_STATE_CK_PHASE(msg->spi->chip_select); ++ val &= ~SPFI_PORT_STATE_CK_PHASE(0); + if (msg->spi->mode & SPI_CPOL) +- val |= SPFI_PORT_STATE_CK_POL(msg->spi->chip_select); ++ val |= SPFI_PORT_STATE_CK_POL(0); + else +- val &= ~SPFI_PORT_STATE_CK_POL(msg->spi->chip_select); ++ val &= ~SPFI_PORT_STATE_CK_POL(0); + spfi_writel(spfi, val, SPFI_PORT_STATE); + + return 0; +@@ -548,11 +553,15 @@ static void img_spfi_config(struct spi_m + div = DIV_ROUND_UP(clk_get_rate(spfi->spfi_clk), xfer->speed_hz); + div = clamp(512 / (1 << get_count_order(div)), 1, 128); + +- val = spfi_readl(spfi, SPFI_DEVICE_PARAMETER(spi->chip_select)); ++ /* ++ * The chip select line is controlled externally so ++ * we can use the CS0 parameters for all devices ++ */ ++ val = spfi_readl(spfi, SPFI_DEVICE_PARAMETER(0)); + val &= ~(SPFI_DEVICE_PARAMETER_BITCLK_MASK << + SPFI_DEVICE_PARAMETER_BITCLK_SHIFT); + val |= div << SPFI_DEVICE_PARAMETER_BITCLK_SHIFT; +- spfi_writel(spfi, val, SPFI_DEVICE_PARAMETER(spi->chip_select)); ++ spfi_writel(spfi, val, SPFI_DEVICE_PARAMETER(0)); + + if (!list_is_last(&xfer->transfer_list, &master->cur_msg->transfers) && + /* diff --git a/target/linux/pistachio/patches-4.14/105-spi-img-spfi-RX-maximum-burst-size-for-DMA-is-8.patch b/target/linux/pistachio/patches-4.14/105-spi-img-spfi-RX-maximum-burst-size-for-DMA-is-8.patch new file mode 100644 index 0000000000..0067b0ea4a --- /dev/null +++ b/target/linux/pistachio/patches-4.14/105-spi-img-spfi-RX-maximum-burst-size-for-DMA-is-8.patch @@ -0,0 +1,59 @@ +From 56466f505f58f44b69feb7eaed3b506842800456 Mon Sep 17 00:00:00 2001 +From: Ionela Voinescu <ionela.voinescu@imgtec.com> +Date: Tue, 1 Mar 2016 17:49:45 +0000 +Subject: spi: img-spfi: RX maximum burst size for DMA is 8 + +The depth of the FIFOs is 16 bytes. The DMA request line is tied +to the half full/empty (depending on the use of the TX or RX FIFO) +threshold. For the TX FIFO, if you set a burst size of 8 (equal to +half the depth) the first burst goes into FIFO without any issues, +but due the latency involved (the time the data leaves the DMA +engine to the time it arrives at the FIFO), the DMA might trigger +another burst of 8. But given that there is no space for 2 additonal +bursts of 8, this would result in a failure. Therefore, we have to +keep the burst size for TX to 4 to accomodate for an extra burst. + +For the read (RX) scenario, the DMA request line goes high when +there is at least 8 entries in the FIFO (half full), and we can +program the burst size to be 8 because the risk of accidental burst +does not exist. The DMA engine will not trigger another read until +the read data for all the burst it has sent out has been received. + +While here, move the burst size setting outside of the if/else branches +as they have the same value for both 8 and 32 bit data widths. + +Signed-off-by: Ionela Voinescu <ionela.voinescu@imgtec.com> +--- + drivers/spi/spi-img-spfi.c | 6 ++---- + 1 file changed, 2 insertions(+), 4 deletions(-) + +--- a/drivers/spi/spi-img-spfi.c ++++ b/drivers/spi/spi-img-spfi.c +@@ -346,12 +346,11 @@ static int img_spfi_start_dma(struct spi + if (xfer->len % 4 == 0) { + rxconf.src_addr = spfi->phys + SPFI_RX_32BIT_VALID_DATA; + rxconf.src_addr_width = 4; +- rxconf.src_maxburst = 4; + } else { + rxconf.src_addr = spfi->phys + SPFI_RX_8BIT_VALID_DATA; + rxconf.src_addr_width = 1; +- rxconf.src_maxburst = 4; + } ++ rxconf.src_maxburst = 8; + dmaengine_slave_config(spfi->rx_ch, &rxconf); + + rxdesc = dmaengine_prep_slave_sg(spfi->rx_ch, xfer->rx_sg.sgl, +@@ -370,12 +369,11 @@ static int img_spfi_start_dma(struct spi + if (xfer->len % 4 == 0) { + txconf.dst_addr = spfi->phys + SPFI_TX_32BIT_VALID_DATA; + txconf.dst_addr_width = 4; +- txconf.dst_maxburst = 4; + } else { + txconf.dst_addr = spfi->phys + SPFI_TX_8BIT_VALID_DATA; + txconf.dst_addr_width = 1; +- txconf.dst_maxburst = 4; + } ++ txconf.dst_maxburst = 4; + dmaengine_slave_config(spfi->tx_ch, &txconf); + + txdesc = dmaengine_prep_slave_sg(spfi->tx_ch, xfer->tx_sg.sgl, diff --git a/target/linux/pistachio/patches-4.14/106-spi-img-spfi-finish-every-transfer-cleanly.patch b/target/linux/pistachio/patches-4.14/106-spi-img-spfi-finish-every-transfer-cleanly.patch new file mode 100644 index 0000000000..0f958314e3 --- /dev/null +++ b/target/linux/pistachio/patches-4.14/106-spi-img-spfi-finish-every-transfer-cleanly.patch @@ -0,0 +1,120 @@ +From 5fcca3fd4b621d7b5bdeca18d36dfc6ca6cfe383 Mon Sep 17 00:00:00 2001 +From: Ionela Voinescu <ionela.voinescu@imgtec.com> +Date: Wed, 10 Aug 2016 11:42:26 +0100 +Subject: spi: img-spfi: finish every transfer cleanly + +Before this change, the interrupt status bit that signaled +the end of a tranfers was cleared in the wait_all_done +function. That functionality triggered issues for DMA +duplex transactions where the wait function was called +twice, in both the TX and RX callbacks. + +In order to fix the issue, clear all interrupt data bits +at the end of a PIO transfer or at the end of both TX and RX +duplex transfers, if the transfer is not a pending tranfer +(command waiting for data). After that, the status register +is checked for new incoming data or new data requests to be +signaled. If SPFI finished cleanly, no new interrupt data +bits should be set. + +Signed-off-by: Ionela Voinescu <ionela.voinescu@imgtec.com> +--- + drivers/spi/spi-img-spfi.c | 49 +++++++++++++++++++++++++++++++++------------- + 1 file changed, 35 insertions(+), 14 deletions(-) + +--- a/drivers/spi/spi-img-spfi.c ++++ b/drivers/spi/spi-img-spfi.c +@@ -83,6 +83,14 @@ + #define SPFI_INTERRUPT_SDE BIT(1) + #define SPFI_INTERRUPT_SDTRIG BIT(0) + ++#define SPFI_INTERRUPT_DATA_BITS (SPFI_INTERRUPT_SDHF |\ ++ SPFI_INTERRUPT_SDFUL |\ ++ SPFI_INTERRUPT_GDEX32BIT |\ ++ SPFI_INTERRUPT_GDHF |\ ++ SPFI_INTERRUPT_GDFUL |\ ++ SPFI_INTERRUPT_ALLDONETRIG |\ ++ SPFI_INTERRUPT_GDEX8BIT) ++ + /* + * There are four parallel FIFOs of 16 bytes each. The word buffer + * (*_32BIT_VALID_DATA) accesses all four FIFOs at once, resulting in an +@@ -144,6 +152,23 @@ static inline void spfi_reset(struct img + spfi_writel(spfi, 0, SPFI_CONTROL); + } + ++static inline void spfi_finish(struct img_spfi *spfi) ++{ ++ if (!(spfi->complete)) ++ return; ++ ++ /* Clear data bits as all transfers(TX and RX) have finished */ ++ spfi_writel(spfi, SPFI_INTERRUPT_DATA_BITS, SPFI_INTERRUPT_CLEAR); ++ if (spfi_readl(spfi, SPFI_INTERRUPT_STATUS) & SPFI_INTERRUPT_DATA_BITS) { ++ dev_err(spfi->dev, "SPFI did not finish transfer cleanly.\n"); ++ spfi_reset(spfi); ++ } ++ /* Disable SPFI for it not to interfere with pending transactions */ ++ spfi_writel(spfi, ++ spfi_readl(spfi, SPFI_CONTROL) & ~SPFI_CONTROL_SPFI_EN, ++ SPFI_CONTROL); ++} ++ + static int spfi_wait_all_done(struct img_spfi *spfi) + { + unsigned long timeout = jiffies + msecs_to_jiffies(50); +@@ -152,19 +177,9 @@ static int spfi_wait_all_done(struct img + return 0; + + while (time_before(jiffies, timeout)) { +- u32 status = spfi_readl(spfi, SPFI_INTERRUPT_STATUS); +- +- if (status & SPFI_INTERRUPT_ALLDONETRIG) { +- spfi_writel(spfi, SPFI_INTERRUPT_ALLDONETRIG, +- SPFI_INTERRUPT_CLEAR); +- /* +- * Disable SPFI for it not to interfere with +- * pending transactions +- */ +- spfi_writel(spfi, spfi_readl(spfi, SPFI_CONTROL) +- & ~SPFI_CONTROL_SPFI_EN, SPFI_CONTROL); ++ if (spfi_readl(spfi, SPFI_INTERRUPT_STATUS) & ++ SPFI_INTERRUPT_ALLDONETRIG) + return 0; +- } + cpu_relax(); + } + +@@ -296,6 +311,8 @@ static int img_spfi_start_pio(struct spi + } + + ret = spfi_wait_all_done(spfi); ++ spfi_finish(spfi); ++ + if (ret < 0) + return ret; + +@@ -311,8 +328,10 @@ static void img_spfi_dma_rx_cb(void *dat + + spin_lock_irqsave(&spfi->lock, flags); + spfi->rx_dma_busy = false; +- if (!spfi->tx_dma_busy) ++ if (!spfi->tx_dma_busy) { ++ spfi_finish(spfi); + spi_finalize_current_transfer(spfi->master); ++ } + spin_unlock_irqrestore(&spfi->lock, flags); + } + +@@ -325,8 +344,10 @@ static void img_spfi_dma_tx_cb(void *dat + + spin_lock_irqsave(&spfi->lock, flags); + spfi->tx_dma_busy = false; +- if (!spfi->rx_dma_busy) ++ if (!spfi->rx_dma_busy) { ++ spfi_finish(spfi); + spi_finalize_current_transfer(spfi->master); ++ } + spin_unlock_irqrestore(&spfi->lock, flags); + } + diff --git a/target/linux/pistachio/patches-4.14/107-clockevents-Retry-programming-min-delta-up-to-10-tim.patch b/target/linux/pistachio/patches-4.14/107-clockevents-Retry-programming-min-delta-up-to-10-tim.patch new file mode 100644 index 0000000000..857823b783 --- /dev/null +++ b/target/linux/pistachio/patches-4.14/107-clockevents-Retry-programming-min-delta-up-to-10-tim.patch @@ -0,0 +1,66 @@ +From b46f8c74afdd30cd52bfdcc2231470a0bab04416 Mon Sep 17 00:00:00 2001 +From: James Hogan <james.hogan@imgtec.com> +Date: Fri, 22 Apr 2016 18:22:45 +0100 +Subject: clockevents: Retry programming min delta up to 10 times + +Under virtualisation it is possible to get unexpected latency during a +clockevent device's set_next_event() callback which can make it return +-ETIME even for a delta based on min_delta_ns. + +The clockevents_program_min_delta() implementation for +CONFIG_GENERIC_CLOCKEVENTS_MIN_ADJUST=n doesn't handle retries when this +happens, nor does clockevents_program_event() or its callers when force +is true (for example hrtimer_reprogram()). This can result in hangs +until the clock event device does a full period. + +It isn't appropriate to use MIN_ADJUST in this case as occasional +hypervisor induced high latency will cause min_delta_ns to quickly +increase to the maximum. +Instead, borrow the retry pattern from the MIN_ADJUST case, but without +making adjustments. We retry up to 10 times before giving up. + +(picked https://patchwork.kernel.org/patch/8909491/) + +Signed-off-by: James Hogan <james.hogan@imgtec.com> +--- + kernel/time/clockevents.c | 26 +++++++++++++++++++------- + 1 file changed, 19 insertions(+), 7 deletions(-) + +--- a/kernel/time/clockevents.c ++++ b/kernel/time/clockevents.c +@@ -281,16 +281,28 @@ static int clockevents_program_min_delta + { + unsigned long long clc; + int64_t delta; ++ int i; + +- delta = dev->min_delta_ns; +- dev->next_event = ktime_add_ns(ktime_get(), delta); ++ for (i = 0;;) { ++ delta = dev->min_delta_ns; ++ dev->next_event = ktime_add_ns(ktime_get(), delta); + +- if (clockevent_state_shutdown(dev)) +- return 0; ++ if (clockevent_state_shutdown(dev)) ++ return 0; + +- dev->retries++; +- clc = ((unsigned long long) delta * dev->mult) >> dev->shift; +- return dev->set_next_event((unsigned long) clc, dev); ++ dev->retries++; ++ clc = ((unsigned long long) delta * dev->mult) >> dev->shift; ++ if (dev->set_next_event((unsigned long) clc, dev) == 0) ++ return 0; ++ ++ if (++i > 9) { ++ /* ++ * We tried 10 times to program the device with the ++ * given min_delta_ns. Get out of here. ++ */ ++ return -ETIME; ++ } ++ } + } + + #endif /* CONFIG_GENERIC_CLOCKEVENTS_MIN_ADJUST */ diff --git a/target/linux/pistachio/patches-4.14/108-clk-pistachio-Fix-wrong-SDHost-card-speed.patch b/target/linux/pistachio/patches-4.14/108-clk-pistachio-Fix-wrong-SDHost-card-speed.patch new file mode 100644 index 0000000000..5329ad62d0 --- /dev/null +++ b/target/linux/pistachio/patches-4.14/108-clk-pistachio-Fix-wrong-SDHost-card-speed.patch @@ -0,0 +1,49 @@ +From 3642843a06025ec333d7e92580cf52cb8db2a652 Mon Sep 17 00:00:00 2001 +From: Govindraj Raja <Govindraj.Raja@imgtec.com> +Date: Fri, 8 Jan 2016 16:36:07 +0000 +Subject: clk: pistachio: Fix wrong SDHost card speed + +The SDHost currently clocks the card 4x slower than it +should do, because there is fixed divide by 4 in the +sdhost wrapper that is not present in the clock tree. +To model this add a fixed divide by 4 clock node in +the SDHost clock path. + +This will ensure the right clock frequency is selected when +the mmc driver tries to configure frequency on card insert. + +Signed-off-by: Govindraj Raja <Govindraj.Raja@imgtec.com> +--- + drivers/clk/pistachio/clk-pistachio.c | 3 ++- + include/dt-bindings/clock/pistachio-clk.h | 1 + + 2 files changed, 3 insertions(+), 1 deletion(-) + +--- a/drivers/clk/pistachio/clk-pistachio.c ++++ b/drivers/clk/pistachio/clk-pistachio.c +@@ -44,7 +44,7 @@ static struct pistachio_gate pistachio_g + GATE(CLK_AUX_ADC_INTERNAL, "aux_adc_internal", "sys_internal_div", + 0x104, 22), + GATE(CLK_AUX_ADC, "aux_adc", "aux_adc_div", 0x104, 23), +- GATE(CLK_SD_HOST, "sd_host", "sd_host_div", 0x104, 24), ++ GATE(CLK_SD_HOST, "sd_host", "sd_host_div4", 0x104, 24), + GATE(CLK_BT, "bt", "bt_div", 0x104, 25), + GATE(CLK_BT_DIV4, "bt_div4", "bt_div4_div", 0x104, 26), + GATE(CLK_BT_DIV8, "bt_div8", "bt_div8_div", 0x104, 27), +@@ -54,6 +54,7 @@ static struct pistachio_gate pistachio_g + static struct pistachio_fixed_factor pistachio_ffs[] __initdata = { + FIXED_FACTOR(CLK_WIFI_DIV4, "wifi_div4", "wifi_pll", 4), + FIXED_FACTOR(CLK_WIFI_DIV8, "wifi_div8", "wifi_pll", 8), ++ FIXED_FACTOR(CLK_SDHOST_DIV4, "sd_host_div4", "sd_host_div", 4), + }; + + static struct pistachio_div pistachio_divs[] __initdata = { +--- a/include/dt-bindings/clock/pistachio-clk.h ++++ b/include/dt-bindings/clock/pistachio-clk.h +@@ -21,6 +21,7 @@ + /* Fixed-factor clocks */ + #define CLK_WIFI_DIV4 16 + #define CLK_WIFI_DIV8 17 ++#define CLK_SDHOST_DIV4 18 + + /* Gate clocks */ + #define CLK_MIPS 32 diff --git a/target/linux/pistachio/patches-4.14/109-MIPS-DTS-img-marduk-switch-mmc-to-1-bit-mode.patch b/target/linux/pistachio/patches-4.14/109-MIPS-DTS-img-marduk-switch-mmc-to-1-bit-mode.patch new file mode 100644 index 0000000000..22fc42b988 --- /dev/null +++ b/target/linux/pistachio/patches-4.14/109-MIPS-DTS-img-marduk-switch-mmc-to-1-bit-mode.patch @@ -0,0 +1,47 @@ +From 981c1d416af45eff207227aec106381ac23aac99 Mon Sep 17 00:00:00 2001 +From: Ian Pozella <Ian.Pozella@imgtec.com> +Date: Mon, 20 Feb 2017 10:00:52 +0000 +Subject: MIPS: DTS: img: marduk: switch mmc to 1 bit mode + +The mmc block in Pistachio allows 1 to 8 data bits to be used. +Marduk uses 4 bits allowing the upper 4 bits to be allocated +to the Mikrobus ports. However these bits are still connected +internally meaning the mmc block recieves signals on all data lines +and seems the internal HW CRC checks get corrupted by this erroneous +data. + +We cannot control what data is sent on these lines because they go +to external ports. 1 bit mode does not exhibit the issue hence the +safe default is to use this. If a user knows that in their use case +they will not use the upper bits then they can set to 4 bit mode in +order to improve performance. + +Also make sure that the upper 4 bits don't get allocated to the mmc +driver (the default is to assign all 8 pins) so they can be allocated +to other drivers. Allocating all 4 despite setting 1 bit mode as this +matches what is there in hardware. + +Signed-off-by: Ian Pozella <Ian.Pozella@imgtec.com> +--- + arch/mips/boot/dts/img/pistachio_marduk.dts | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +--- a/arch/mips/boot/dts/img/pistachio_marduk.dts ++++ b/arch/mips/boot/dts/img/pistachio_marduk.dts +@@ -120,7 +120,7 @@ + + &sdhost { + status = "okay"; +- bus-width = <4>; ++ bus-width = <1>; + disable-wp; + }; + +@@ -130,6 +130,7 @@ + + &pin_sdhost_data { + drive-strength = <2>; ++ pins = "mfio17", "mfio18", "mfio19", "mfio20"; + }; + + &pwm { diff --git a/target/linux/pistachio/patches-4.14/401-mtd-nor-support-mtd-name-from-device-tree.patch b/target/linux/pistachio/patches-4.14/401-mtd-nor-support-mtd-name-from-device-tree.patch new file mode 100644 index 0000000000..00686aaf37 --- /dev/null +++ b/target/linux/pistachio/patches-4.14/401-mtd-nor-support-mtd-name-from-device-tree.patch @@ -0,0 +1,34 @@ +From f32bc2aa01edcba2f2ed5db151cf183eac9ef919 Mon Sep 17 00:00:00 2001 +From: Abhimanyu Vishwakarma <Abhimanyu.Vishwakarma@imgtec.com> +Date: Sat, 25 Feb 2017 16:42:50 +0000 +Subject: mtd: nor: support mtd name from device tree + +Signed-off-by: Abhimanyu Vishwakarma <Abhimanyu.Vishwakarma@imgtec.com> +--- + drivers/mtd/spi-nor/spi-nor.c | 8 +++++++- + 1 file changed, 7 insertions(+), 1 deletion(-) + +--- a/drivers/mtd/spi-nor/spi-nor.c ++++ b/drivers/mtd/spi-nor/spi-nor.c +@@ -2646,6 +2646,7 @@ int spi_nor_scan(struct spi_nor *nor, co + struct device *dev = nor->dev; + struct mtd_info *mtd = &nor->mtd; + struct device_node *np = spi_nor_get_flash_node(nor); ++ const char __maybe_unused *of_mtd_name = NULL; + int ret; + int i; + +@@ -2721,7 +2722,12 @@ int spi_nor_scan(struct spi_nor *nor, co + spi_nor_wait_till_ready(nor); + } + +- if (!mtd->name) ++#ifdef CONFIG_MTD_OF_PARTS ++ of_property_read_string(np, "linux,mtd-name", &of_mtd_name); ++#endif ++ if (of_mtd_name) ++ mtd->name = of_mtd_name; ++ else if (!mtd->name) + mtd->name = dev_name(dev); + mtd->priv = nor; + mtd->type = MTD_NORFLASH; diff --git a/target/linux/pistachio/patches-4.14/411-mtd-nand-Check-length-of-ID-before-reading-bits-per-.patch b/target/linux/pistachio/patches-4.14/411-mtd-nand-Check-length-of-ID-before-reading-bits-per-.patch new file mode 100644 index 0000000000..3311de645f --- /dev/null +++ b/target/linux/pistachio/patches-4.14/411-mtd-nand-Check-length-of-ID-before-reading-bits-per-.patch @@ -0,0 +1,33 @@ +From 42ebff638003be18fab503b37de4ad7853244e95 Mon Sep 17 00:00:00 2001 +From: Ezequiel Garcia <ezequiel.garcia@imgtec.com> +Date: Sat, 25 Feb 2017 15:58:22 +0000 +Subject: mtd: nand: Check length of ID before reading bits per cell + +The table-based NAND identification currently reads the number +of bits per cell from the 3rd byte of the extended ID. This is done +for the so-called 'full ID' devices; i.e. devices that have a known +length ID. + +However, if the ID length is shorter than three, there's no 3rd byte, +and so it's wrong to read the bits per cell from there. Fix this by +adding a check for the ID length. + +(picked from http://lists.infradead.org/pipermail/linux-mtd/2014-December/056764.html) + +Signed-off-by: Ezequiel Garcia <ezequiel.garcia@imgtec.com> +--- + drivers/mtd/nand/nand_base.c | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +--- a/drivers/mtd/nand/nand_base.c ++++ b/drivers/mtd/nand/nand_base.c +@@ -3803,7 +3803,8 @@ static bool find_full_id_nand(struct nan + mtd->erasesize = type->erasesize; + mtd->oobsize = type->oobsize; + +- chip->bits_per_cell = nand_get_bits_per_cell(id_data[2]); ++ if (type->id_len > 2) ++ chip->bits_per_cell = nand_get_bits_per_cell(id_data[2]); + chip->chipsize = (uint64_t)type->chipsize << 20; + chip->options |= type->options; + chip->ecc_strength_ds = NAND_ECC_STRENGTH(type); diff --git a/target/linux/pistachio/patches-4.14/412-mtd-nand-Add-JEDEC-manufacturer-ID-for-Gigadevice.patch b/target/linux/pistachio/patches-4.14/412-mtd-nand-Add-JEDEC-manufacturer-ID-for-Gigadevice.patch new file mode 100644 index 0000000000..cb629a4122 --- /dev/null +++ b/target/linux/pistachio/patches-4.14/412-mtd-nand-Add-JEDEC-manufacturer-ID-for-Gigadevice.patch @@ -0,0 +1,35 @@ +From a4bc33b205fd9b1db862f1e45173dba57b0fa57f Mon Sep 17 00:00:00 2001 +From: Ezequiel Garcia <ezequiel.garcia@imgtec.com> +Date: Sat, 25 Feb 2017 15:43:09 +0000 +Subject: mtd: nand: Add JEDEC manufacturer ID for Gigadevice + +This commit adds Gigadevice to the list of manufacturer ID and name strings. + +(picked from http://lists.infradead.org/pipermail/linux-mtd/2014-December/056765.html) + +Signed-off-by: Ezequiel Garcia <ezequiel.garcia@imgtec.com> +--- + drivers/mtd/nand/nand_ids.c | 1 + + include/linux/mtd/rawnand.h | 1 + + 2 files changed, 2 insertions(+) + +--- a/drivers/mtd/nand/nand_ids.c ++++ b/drivers/mtd/nand/nand_ids.c +@@ -184,6 +184,7 @@ static const struct nand_manufacturer na + {NAND_MFR_SANDISK, "SanDisk"}, + {NAND_MFR_INTEL, "Intel"}, + {NAND_MFR_ATO, "ATO"}, ++ {NAND_MFR_GIGADEVICE, "Gigadevice"}, + {NAND_MFR_WINBOND, "Winbond"}, + }; + +--- a/include/linux/mtd/rawnand.h ++++ b/include/linux/mtd/rawnand.h +@@ -1016,6 +1016,7 @@ static inline void *nand_get_manufacture + #define NAND_MFR_SANDISK 0x45 + #define NAND_MFR_INTEL 0x89 + #define NAND_MFR_ATO 0x9b ++#define NAND_MFR_GIGADEVICE 0xc8 + #define NAND_MFR_WINBOND 0xef + + diff --git a/target/linux/pistachio/patches-4.14/413-mtd-Introduce-SPI-NAND-framework.patch b/target/linux/pistachio/patches-4.14/413-mtd-Introduce-SPI-NAND-framework.patch new file mode 100644 index 0000000000..fe93b2114a --- /dev/null +++ b/target/linux/pistachio/patches-4.14/413-mtd-Introduce-SPI-NAND-framework.patch @@ -0,0 +1,706 @@ +From 082a89a78e29b15008284df90441747cb742f149 Mon Sep 17 00:00:00 2001 +From: Ezequiel Garcia <ezequiel.garcia@imgtec.com> +Date: Tue, 2 Dec 2014 09:58:52 -0300 +Subject: mtd: Introduce SPI NAND framework + +Add a new framework, to support SPI NAND devices. The framework registers +a NAND chip and handles the generic SPI NAND protocol, calling device-specific +hooks for each SPI NAND command. + +The following is the stack design, from userspace to hardware. This commit +adds the "SPI NAND core" layer. + + Userspace + ------------------ + MTD + ------------------ + NAND core + ------------------ + SPI NAND core + ------------------ + SPI NAND device + ------------------ + SPI core + ------------------ + SPI master + ------------------ + Hardware + +(based on http://lists.infradead.org/pipermail/linux-mtd/2014-December/056763.html) + +Signed-off-by: Ionela Voinescu <ionela.voinescu@imgtec.com> +Signed-off-by: Ezequiel Garcia <ezequiel.garcia@imgtec.com> +Signed-off-by: Ian Pozella <Ian.Pozella@imgtec.com> +--- + drivers/mtd/Kconfig | 2 + + drivers/mtd/Makefile | 1 + + drivers/mtd/spi-nand/Kconfig | 7 + + drivers/mtd/spi-nand/Makefile | 1 + + drivers/mtd/spi-nand/spi-nand-base.c | 566 +++++++++++++++++++++++++++++++++++ + include/linux/mtd/spi-nand.h | 54 ++++ + 6 files changed, 631 insertions(+) + create mode 100644 drivers/mtd/spi-nand/Kconfig + create mode 100644 drivers/mtd/spi-nand/Makefile + create mode 100644 drivers/mtd/spi-nand/spi-nand-base.c + create mode 100644 include/linux/mtd/spi-nand.h + +--- a/drivers/mtd/Kconfig ++++ b/drivers/mtd/Kconfig +@@ -373,6 +373,8 @@ source "drivers/mtd/onenand/Kconfig" + + source "drivers/mtd/lpddr/Kconfig" + ++source "drivers/mtd/spi-nand/Kconfig" ++ + source "drivers/mtd/spi-nor/Kconfig" + + source "drivers/mtd/ubi/Kconfig" +--- a/drivers/mtd/Makefile ++++ b/drivers/mtd/Makefile +@@ -37,5 +37,6 @@ inftl-objs := inftlcore.o inftlmount.o + + obj-y += chips/ lpddr/ maps/ devices/ nand/ onenand/ tests/ + ++obj-$(CONFIG_MTD_SPI_NAND) += spi-nand/ + obj-$(CONFIG_MTD_SPI_NOR) += spi-nor/ + obj-$(CONFIG_MTD_UBI) += ubi/ +--- /dev/null ++++ b/drivers/mtd/spi-nand/Kconfig +@@ -0,0 +1,7 @@ ++menuconfig MTD_SPI_NAND ++ tristate "SPI NAND device support" ++ depends on MTD ++ select MTD_NAND ++ help ++ This is the framework for the SPI NAND. ++ +--- /dev/null ++++ b/drivers/mtd/spi-nand/Makefile +@@ -0,0 +1 @@ ++obj-$(CONFIG_MTD_SPI_NAND) += spi-nand-base.o +--- /dev/null ++++ b/drivers/mtd/spi-nand/spi-nand-base.c +@@ -0,0 +1,566 @@ ++/* ++ * Copyright (C) 2014 Imagination Technologies Ltd. ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; version 2 of the License. ++ * ++ * Notes: ++ * 1. Erase and program operations need to call write_enable() first, ++ * to clear the enable bit. This bit is cleared automatically after ++ * the erase or program operation. ++ * ++ */ ++ ++#include <linux/device.h> ++#include <linux/err.h> ++#include <linux/errno.h> ++#include <linux/kernel.h> ++#include <linux/module.h> ++#include <linux/mtd/rawnand.h> ++#include <linux/mtd/mtd.h> ++#include <linux/mtd/partitions.h> ++#include <linux/mtd/spi-nand.h> ++#include <linux/of.h> ++#include <linux/slab.h> ++ ++/* Registers common to all devices */ ++#define SPI_NAND_LOCK_REG 0xa0 ++#define SPI_NAND_PROT_UNLOCK_ALL 0x0 ++ ++#define SPI_NAND_FEATURE_REG 0xb0 ++#define SPI_NAND_ECC_EN BIT(4) ++#define SPI_NAND_QUAD_EN BIT(0) ++ ++#define SPI_NAND_STATUS_REG 0xc0 ++#define SPI_NAND_STATUS_REG_ECC_MASK 0x3 ++#define SPI_NAND_STATUS_REG_ECC_SHIFT 4 ++#define SPI_NAND_STATUS_REG_PROG_FAIL BIT(3) ++#define SPI_NAND_STATUS_REG_ERASE_FAIL BIT(2) ++#define SPI_NAND_STATUS_REG_WREN BIT(1) ++#define SPI_NAND_STATUS_REG_BUSY BIT(0) ++ ++#define SPI_NAND_CMD_BUF_LEN 8 ++ ++/* Rewind and fill the buffer with 0xff */ ++static void spi_nand_clear_buffer(struct spi_nand *snand) ++{ ++ snand->buf_start = 0; ++ memset(snand->data_buf, 0xff, snand->buf_size); ++} ++ ++static int spi_nand_enable_ecc(struct spi_nand *snand) ++{ ++ int ret; ++ ++ ret = snand->read_reg(snand, SPI_NAND_FEATURE_REG, snand->buf); ++ if (ret) ++ return ret; ++ ++ snand->buf[0] |= SPI_NAND_ECC_EN; ++ ret = snand->write_reg(snand, SPI_NAND_FEATURE_REG, snand->buf); ++ if (ret) ++ return ret; ++ snand->ecc = true; ++ ++ return 0; ++} ++ ++static int spi_nand_disable_ecc(struct spi_nand *snand) ++{ ++ int ret; ++ ++ ret = snand->read_reg(snand, SPI_NAND_FEATURE_REG, snand->buf); ++ if (ret) ++ return ret; ++ ++ snand->buf[0] &= ~SPI_NAND_ECC_EN; ++ ret = snand->write_reg(snand, SPI_NAND_FEATURE_REG, snand->buf); ++ if (ret) ++ return ret; ++ snand->ecc = false; ++ ++ return 0; ++} ++ ++static int spi_nand_enable_quad(struct spi_nand *snand) ++{ ++ int ret; ++ ++ ret = snand->read_reg(snand, SPI_NAND_FEATURE_REG, snand->buf); ++ if (ret) ++ return ret; ++ ++ snand->buf[0] |= SPI_NAND_QUAD_EN; ++ ret = snand->write_reg(snand, SPI_NAND_FEATURE_REG, snand->buf); ++ if (ret) ++ return ret; ++ ++ return 0; ++} ++/* ++ * Wait until the status register busy bit is cleared. ++ * Returns a negatie errno on error or time out, and a non-negative status ++ * value if the device is ready. ++ */ ++static int spi_nand_wait_till_ready(struct spi_nand *snand) ++{ ++ unsigned long deadline = jiffies + msecs_to_jiffies(100); ++ bool timeout = false; ++ int ret; ++ ++ /* ++ * Perhaps we should set a different timeout for each ++ * operation (reset, read, write, erase). ++ */ ++ while (!timeout) { ++ if (time_after_eq(jiffies, deadline)) ++ timeout = true; ++ ++ ret = snand->read_reg(snand, SPI_NAND_STATUS_REG, snand->buf); ++ if (ret < 0) { ++ dev_err(snand->dev, "error reading status register\n"); ++ return ret; ++ } else if (!(snand->buf[0] & SPI_NAND_STATUS_REG_BUSY)) { ++ return snand->buf[0]; ++ } ++ ++ cond_resched(); ++ } ++ ++ dev_err(snand->dev, "operation timed out\n"); ++ ++ return -ETIMEDOUT; ++} ++ ++static int spi_nand_reset(struct spi_nand *snand) ++{ ++ int ret; ++ ++ ret = snand->reset(snand); ++ if (ret < 0) { ++ dev_err(snand->dev, "reset command failed\n"); ++ return ret; ++ } ++ ++ /* ++ * The NAND core won't wait after a device reset, so we need ++ * to do that here. ++ */ ++ ret = spi_nand_wait_till_ready(snand); ++ if (ret < 0) ++ return ret; ++ return 0; ++} ++ ++static int spi_nand_status(struct spi_nand *snand) ++{ ++ int ret, status; ++ ++ ret = snand->read_reg(snand, SPI_NAND_STATUS_REG, snand->buf); ++ if (ret < 0) { ++ dev_err(snand->dev, "error reading status register\n"); ++ return ret; ++ } ++ status = snand->buf[0]; ++ ++ /* Convert this into standard NAND_STATUS values */ ++ if (status & SPI_NAND_STATUS_REG_BUSY) ++ snand->buf[0] = 0; ++ else ++ snand->buf[0] = NAND_STATUS_READY; ++ ++ if (status & SPI_NAND_STATUS_REG_PROG_FAIL || ++ status & SPI_NAND_STATUS_REG_ERASE_FAIL) ++ snand->buf[0] |= NAND_STATUS_FAIL; ++ ++ /* ++ * Since we unlock the entire device at initialization, unconditionally ++ * set the WP bit to indicate it's not protected. ++ */ ++ snand->buf[0] |= NAND_STATUS_WP; ++ return 0; ++} ++ ++static int spi_nand_erase(struct spi_nand *snand, int page_addr) ++{ ++ int ret; ++ ++ ret = snand->write_enable(snand); ++ if (ret < 0) { ++ dev_err(snand->dev, "write enable command failed\n"); ++ return ret; ++ } ++ ++ ret = snand->block_erase(snand, page_addr); ++ if (ret < 0) { ++ dev_err(snand->dev, "block erase command failed\n"); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static int spi_nand_write(struct spi_nand *snand) ++{ ++ int ret; ++ ++ /* Enable quad mode */ ++ ret = spi_nand_enable_quad(snand); ++ if (ret) { ++ dev_err(snand->dev, "error %d enabling quad mode\n", ret); ++ return ret; ++ } ++ /* Store the page to cache */ ++ ret = snand->store_cache(snand, 0, snand->buf_size, snand->data_buf); ++ if (ret < 0) { ++ dev_err(snand->dev, "error %d storing page 0x%x to cache\n", ++ ret, snand->page_addr); ++ return ret; ++ } ++ ++ ret = snand->write_enable(snand); ++ if (ret < 0) { ++ dev_err(snand->dev, "write enable command failed\n"); ++ return ret; ++ } ++ ++ /* Get page from the device cache into our internal buffer */ ++ ret = snand->write_page(snand, snand->page_addr); ++ if (ret < 0) { ++ dev_err(snand->dev, "error %d reading page 0x%x from cache\n", ++ ret, snand->page_addr); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static int spi_nand_read_id(struct spi_nand *snand) ++{ ++ int ret; ++ ++ ret = snand->read_id(snand, snand->data_buf); ++ if (ret < 0) { ++ dev_err(snand->dev, "error %d reading ID\n", ret); ++ return ret; ++ } ++ return 0; ++} ++ ++static int spi_nand_read_page(struct spi_nand *snand, unsigned int page_addr, ++ unsigned int page_offset, size_t length) ++{ ++ unsigned int corrected = 0, ecc_error = 0; ++ int ret; ++ ++ /* Load a page into the cache register */ ++ ret = snand->load_page(snand, page_addr); ++ if (ret < 0) { ++ dev_err(snand->dev, "error %d loading page 0x%x to cache\n", ++ ret, page_addr); ++ return ret; ++ } ++ ++ ret = spi_nand_wait_till_ready(snand); ++ if (ret < 0) ++ return ret; ++ ++ if (snand->ecc) { ++ snand->get_ecc_status(ret, &corrected, &ecc_error); ++ snand->bitflips = corrected; ++ ++ /* ++ * If there's an ECC error, print a message and notify MTD ++ * about it. Then complete the read, to load actual data on ++ * the buffer (instead of the status result). ++ */ ++ if (ecc_error) { ++ dev_err(snand->dev, ++ "internal ECC error reading page 0x%x\n", ++ page_addr); ++ snand->nand_chip.mtd.ecc_stats.failed++; ++ } else { ++ snand->nand_chip.mtd.ecc_stats.corrected += corrected; ++ } ++ } ++ ++ /* Enable quad mode */ ++ ret = spi_nand_enable_quad(snand); ++ if (ret) { ++ dev_err(snand->dev, "error %d enabling quad mode\n", ret); ++ return ret; ++ } ++ /* Get page from the device cache into our internal buffer */ ++ ret = snand->read_cache(snand, page_offset, length, snand->data_buf); ++ if (ret < 0) { ++ dev_err(snand->dev, "error %d reading page 0x%x from cache\n", ++ ret, page_addr); ++ return ret; ++ } ++ return 0; ++} ++ ++static u8 spi_nand_read_byte(struct mtd_info *mtd) ++{ ++ struct nand_chip *chip = mtd_to_nand(mtd); ++ struct spi_nand *snand = nand_get_controller_data(chip); ++ char val = 0xff; ++ ++ if (snand->buf_start < snand->buf_size) ++ val = snand->data_buf[snand->buf_start++]; ++ return val; ++} ++ ++static void spi_nand_write_buf(struct mtd_info *mtd, const u8 *buf, int len) ++{ ++ struct nand_chip *chip = mtd_to_nand(mtd); ++ struct spi_nand *snand = nand_get_controller_data(chip); ++ size_t n = min_t(size_t, len, snand->buf_size - snand->buf_start); ++ ++ memcpy(snand->data_buf + snand->buf_start, buf, n); ++ snand->buf_start += n; ++} ++ ++static void spi_nand_read_buf(struct mtd_info *mtd, u8 *buf, int len) ++{ ++ struct nand_chip *chip = mtd_to_nand(mtd); ++ struct spi_nand *snand = nand_get_controller_data(chip); ++ size_t n = min_t(size_t, len, snand->buf_size - snand->buf_start); ++ ++ memcpy(buf, snand->data_buf + snand->buf_start, n); ++ snand->buf_start += n; ++} ++ ++static int spi_nand_write_page_hwecc(struct mtd_info *mtd, ++ struct nand_chip *chip, const uint8_t *buf, int oob_required, ++ int page) ++{ ++ chip->write_buf(mtd, buf, mtd->writesize); ++ chip->write_buf(mtd, chip->oob_poi, mtd->oobsize); ++ ++ return 0; ++} ++ ++static int spi_nand_read_page_hwecc(struct mtd_info *mtd, ++ struct nand_chip *chip, uint8_t *buf, int oob_required, ++ int page) ++{ ++ struct spi_nand *snand = nand_get_controller_data(chip); ++ ++ chip->read_buf(mtd, buf, mtd->writesize); ++ chip->read_buf(mtd, chip->oob_poi, mtd->oobsize); ++ ++ return snand->bitflips; ++} ++ ++static int spi_nand_waitfunc(struct mtd_info *mtd, struct nand_chip *chip) ++{ ++ struct spi_nand *snand = nand_get_controller_data(chip); ++ int ret; ++ ++ ret = spi_nand_wait_till_ready(snand); ++ ++ if (ret < 0) { ++ return NAND_STATUS_FAIL; ++ } else if (ret & SPI_NAND_STATUS_REG_PROG_FAIL) { ++ dev_err(snand->dev, "page program failed\n"); ++ return NAND_STATUS_FAIL; ++ } else if (ret & SPI_NAND_STATUS_REG_ERASE_FAIL) { ++ dev_err(snand->dev, "block erase failed\n"); ++ return NAND_STATUS_FAIL; ++ } ++ ++ return NAND_STATUS_READY; ++} ++ ++static void spi_nand_cmdfunc(struct mtd_info *mtd, unsigned int command, ++ int column, int page_addr) ++{ ++ struct nand_chip *chip = mtd_to_nand(mtd); ++ struct spi_nand *snand = nand_get_controller_data(chip); ++ ++ /* ++ * In case there's any unsupported command, let's make sure ++ * we don't keep garbage around in the buffer. ++ */ ++ if (command != NAND_CMD_PAGEPROG) { ++ spi_nand_clear_buffer(snand); ++ snand->page_addr = 0; ++ } ++ ++ switch (command) { ++ case NAND_CMD_READ0: ++ spi_nand_read_page(snand, page_addr, 0, mtd->writesize); ++ break; ++ case NAND_CMD_READOOB: ++ spi_nand_disable_ecc(snand); ++ spi_nand_read_page(snand, page_addr, mtd->writesize, ++ mtd->oobsize); ++ spi_nand_enable_ecc(snand); ++ break; ++ case NAND_CMD_READID: ++ spi_nand_read_id(snand); ++ break; ++ case NAND_CMD_ERASE1: ++ spi_nand_erase(snand, page_addr); ++ break; ++ case NAND_CMD_ERASE2: ++ /* There's nothing to do here, as the erase is one-step */ ++ break; ++ case NAND_CMD_SEQIN: ++ snand->buf_start = column; ++ snand->page_addr = page_addr; ++ break; ++ case NAND_CMD_PAGEPROG: ++ spi_nand_write(snand); ++ break; ++ case NAND_CMD_STATUS: ++ spi_nand_status(snand); ++ break; ++ case NAND_CMD_RESET: ++ spi_nand_reset(snand); ++ break; ++ default: ++ dev_err(&mtd->dev, "unknown command 0x%x\n", command); ++ } ++} ++ ++static void spi_nand_select_chip(struct mtd_info *mtd, int chip) ++{ ++ /* We need this to override the default */ ++} ++ ++int spi_nand_check(struct spi_nand *snand) ++{ ++ if (!snand->dev) ++ return -ENODEV; ++ if (!snand->read_cache) ++ return -ENODEV; ++ if (!snand->load_page) ++ return -ENODEV; ++ if (!snand->store_cache) ++ return -ENODEV; ++ if (!snand->write_page) ++ return -ENODEV; ++ if (!snand->write_reg) ++ return -ENODEV; ++ if (!snand->read_reg) ++ return -ENODEV; ++ if (!snand->block_erase) ++ return -ENODEV; ++ if (!snand->reset) ++ return -ENODEV; ++ if (!snand->write_enable) ++ return -ENODEV; ++ if (!snand->write_disable) ++ return -ENODEV; ++ if (!snand->get_ecc_status) ++ return -ENODEV; ++ return 0; ++} ++ ++int spi_nand_register(struct spi_nand *snand, struct nand_flash_dev *flash_ids) ++{ ++ struct nand_chip *chip = &snand->nand_chip; ++ struct mtd_info *mtd = nand_to_mtd(chip); ++ struct device_node *np = snand->dev->of_node; ++ const char __maybe_unused *of_mtd_name = NULL; ++ int ret; ++ ++ /* Let's check all the hooks are in-place so we don't panic later */ ++ ret = spi_nand_check(snand); ++ if (ret) ++ return ret; ++ ++ nand_set_controller_data(chip, snand); ++ nand_set_flash_node(chip, np); ++ chip->read_buf = spi_nand_read_buf; ++ chip->write_buf = spi_nand_write_buf; ++ chip->read_byte = spi_nand_read_byte; ++ chip->cmdfunc = spi_nand_cmdfunc; ++ chip->waitfunc = spi_nand_waitfunc; ++ chip->select_chip = spi_nand_select_chip; ++ chip->options |= NAND_NO_SUBPAGE_WRITE; ++ chip->bits_per_cell = 1; ++ ++ mtd_set_ooblayout(mtd, snand->ooblayout); ++ chip->ecc.read_page = spi_nand_read_page_hwecc; ++ chip->ecc.write_page = spi_nand_write_page_hwecc; ++ chip->ecc.mode = NAND_ECC_HW; ++ ++ if (of_property_read_bool(np, "nand-on-flash-bbt")) ++ chip->bbt_options |= NAND_BBT_USE_FLASH | NAND_BBT_NO_OOB; ++ ++#ifdef CONFIG_MTD_OF_PARTS ++ of_property_read_string(np, "linux,mtd-name", &of_mtd_name); ++#endif ++ if (of_mtd_name) ++ mtd->name = of_mtd_name; ++ else ++ mtd->name = snand->name; ++ mtd->owner = THIS_MODULE; ++ ++ /* Allocate buffer to be used to read/write the internal registers */ ++ snand->buf = kmalloc(SPI_NAND_CMD_BUF_LEN, GFP_KERNEL); ++ if (!snand->buf) ++ return -ENOMEM; ++ ++ /* This is enabled at device power up but we'd better make sure */ ++ ret = spi_nand_enable_ecc(snand); ++ if (ret) ++ return ret; ++ ++ /* Preallocate buffer for flash identification (NAND_CMD_READID) */ ++ snand->buf_size = SPI_NAND_CMD_BUF_LEN; ++ snand->data_buf = kmalloc(snand->buf_size, GFP_KERNEL); ++ ++ ret = nand_scan_ident(mtd, 1, flash_ids); ++ if (ret) ++ return ret; ++ ++ /* ++ * SPI NAND has on-die ECC, which means we can correct as much as ++ * we are required to. This must be done after identification of ++ * the device. ++ */ ++ chip->ecc.strength = chip->ecc_strength_ds; ++ chip->ecc.size = chip->ecc_step_ds; ++ ++ /* ++ * Unlock all the device before calling nand_scan_tail. This is needed ++ * in case the in-flash bad block table needs to be created. ++ * We could override __nand_unlock(), but since it's not currently used ++ * by the NAND core we call this explicitly. ++ */ ++ snand->buf[0] = SPI_NAND_PROT_UNLOCK_ALL; ++ ret = snand->write_reg(snand, SPI_NAND_LOCK_REG, snand->buf); ++ if (ret) ++ return ret; ++ ++ /* Free the buffer and allocate a good one, to fit a page plus OOB */ ++ kfree(snand->data_buf); ++ ++ snand->buf_size = mtd->writesize + mtd->oobsize; ++ snand->data_buf = kmalloc(snand->buf_size, GFP_KERNEL); ++ if (!snand->data_buf) ++ return -ENOMEM; ++ ++ ret = nand_scan_tail(mtd); ++ if (ret) ++ return ret; ++ ++ return mtd_device_register(mtd, NULL, 0); ++} ++EXPORT_SYMBOL_GPL(spi_nand_register); ++ ++void spi_nand_unregister(struct spi_nand *snand) ++{ ++ kfree(snand->buf); ++ kfree(snand->data_buf); ++} ++EXPORT_SYMBOL_GPL(spi_nand_unregister); ++ ++MODULE_AUTHOR("Ezequiel Garcia <ezequiel.garcia@imgtec.com>"); ++MODULE_DESCRIPTION("Framework for SPI NAND"); ++MODULE_LICENSE("GPL v2"); +--- /dev/null ++++ b/include/linux/mtd/spi-nand.h +@@ -0,0 +1,54 @@ ++/* ++ * Copyright (C) 2014 Imagination Technologies Ltd. ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; version 2 of the License. ++ */ ++ ++#ifndef __LINUX_MTD_SPI_NAND_H ++#define __LINUX_MTD_SPI_NAND_H ++ ++#include <linux/mtd/mtd.h> ++#include <linux/mtd/rawnand.h> ++ ++struct spi_nand { ++ struct nand_chip nand_chip; ++ struct device *dev; ++ const char *name; ++ ++ u8 *buf, *data_buf; ++ size_t buf_size; ++ off_t buf_start; ++ unsigned int page_addr; ++ unsigned int bitflips; ++ bool ecc; ++ struct mtd_ooblayout_ops *ooblayout; ++ ++ int (*reset)(struct spi_nand *snand); ++ int (*read_id)(struct spi_nand *snand, u8 *buf); ++ ++ int (*write_disable)(struct spi_nand *snand); ++ int (*write_enable)(struct spi_nand *snand); ++ ++ int (*read_reg)(struct spi_nand *snand, u8 opcode, u8 *buf); ++ int (*write_reg)(struct spi_nand *snand, u8 opcode, u8 *buf); ++ void (*get_ecc_status)(unsigned int status, ++ unsigned int *corrected, ++ unsigned int *ecc_errors); ++ ++ int (*store_cache)(struct spi_nand *snand, unsigned int page_offset, ++ size_t length, u8 *write_buf); ++ int (*write_page)(struct spi_nand *snand, unsigned int page_addr); ++ int (*load_page)(struct spi_nand *snand, unsigned int page_addr); ++ int (*read_cache)(struct spi_nand *snand, unsigned int page_offset, ++ size_t length, u8 *read_buf); ++ int (*block_erase)(struct spi_nand *snand, unsigned int page_addr); ++ ++ void *priv; ++}; ++ ++int spi_nand_register(struct spi_nand *snand, struct nand_flash_dev *flash_ids); ++void spi_nand_unregister(struct spi_nand *snand); ++ ++#endif diff --git a/target/linux/pistachio/patches-4.14/414-mtd-spi-nand-Support-Gigadevice-GD5F.patch b/target/linux/pistachio/patches-4.14/414-mtd-spi-nand-Support-Gigadevice-GD5F.patch new file mode 100644 index 0000000000..1f1461061c --- /dev/null +++ b/target/linux/pistachio/patches-4.14/414-mtd-spi-nand-Support-Gigadevice-GD5F.patch @@ -0,0 +1,524 @@ +From 7723e59d483a883578115a73eb87eb7fff0ff724 Mon Sep 17 00:00:00 2001 +From: Ezequiel Garcia <ezequiel.garcia@imgtec.com> +Date: Tue, 28 Feb 2017 10:37:24 +0000 +Subject: mtd: spi-nand: Support Gigadevice GD5F + +This commit uses the recently introduced SPI NAND framework to support +the Gigadevice GD5F serial NAND device. + +The current support includes: + + * Page read and page program operations (using on-die ECC) + * Page out-of-band read + * Erase + * Reset + * Device status retrieval + * Device ID retrieval + +(based on http://lists.infradead.org/pipermail/linux-mtd/2014-December/056769.html) + +Signed-off-by: Ezequiel Garcia <ezequiel.garcia@imgtec.com> +Signed-off-by: Ian Pozella <Ian.Pozella@imgtec.com> +--- + drivers/mtd/spi-nand/Kconfig | 10 + + drivers/mtd/spi-nand/Makefile | 1 + + drivers/mtd/spi-nand/spi-nand-device.c | 472 +++++++++++++++++++++++++++++++++ + 3 files changed, 483 insertions(+) + create mode 100644 drivers/mtd/spi-nand/spi-nand-device.c + +--- a/drivers/mtd/spi-nand/Kconfig ++++ b/drivers/mtd/spi-nand/Kconfig +@@ -5,3 +5,13 @@ menuconfig MTD_SPI_NAND + help + This is the framework for the SPI NAND. + ++if MTD_SPI_NAND ++ ++config MTD_SPI_NAND_DEVICES ++ tristate "Support for SPI NAND devices" ++ default y ++ depends on MTD_SPI_NAND ++ help ++ Select this option if you require support for SPI NAND devices. ++ ++endif # MTD_SPI_NAND +--- a/drivers/mtd/spi-nand/Makefile ++++ b/drivers/mtd/spi-nand/Makefile +@@ -1 +1,2 @@ + obj-$(CONFIG_MTD_SPI_NAND) += spi-nand-base.o ++obj-$(CONFIG_MTD_SPI_NAND_DEVICES) += spi-nand-device.o +--- /dev/null ++++ b/drivers/mtd/spi-nand/spi-nand-device.c +@@ -0,0 +1,472 @@ ++/* ++ * Copyright (C) 2014 Imagination Technologies Ltd. ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; version 2 of the License. ++ * ++ * Notes: ++ * 1. We avoid using a stack-allocated buffer for SPI messages. Using ++ * a kmalloced buffer is probably better, given we shouldn't assume ++ * any particular usage by SPI core. ++ */ ++ ++#include <linux/device.h> ++#include <linux/err.h> ++#include <linux/errno.h> ++#include <linux/module.h> ++#include <linux/mtd/mtd.h> ++#include <linux/mtd/partitions.h> ++#include <linux/mtd/spi-nand.h> ++#include <linux/sizes.h> ++#include <linux/spi/spi.h> ++ ++/* SPI NAND commands */ ++#define SPI_NAND_WRITE_ENABLE 0x06 ++#define SPI_NAND_WRITE_DISABLE 0x04 ++#define SPI_NAND_GET_FEATURE 0x0f ++#define SPI_NAND_SET_FEATURE 0x1f ++#define SPI_NAND_PAGE_READ 0x13 ++#define SPI_NAND_READ_CACHE 0x03 ++#define SPI_NAND_FAST_READ_CACHE 0x0b ++#define SPI_NAND_READ_CACHE_X2 0x3b ++#define SPI_NAND_READ_CACHE_X4 0x6b ++#define SPI_NAND_READ_CACHE_DUAL_IO 0xbb ++#define SPI_NAND_READ_CACHE_QUAD_IO 0xeb ++#define SPI_NAND_READ_ID 0x9f ++#define SPI_NAND_PROGRAM_LOAD 0x02 ++#define SPI_NAND_PROGRAM_LOAD4 0x32 ++#define SPI_NAND_PROGRAM_EXEC 0x10 ++#define SPI_NAND_PROGRAM_LOAD_RANDOM 0x84 ++#define SPI_NAND_PROGRAM_LOAD_RANDOM4 0xc4 ++#define SPI_NAND_BLOCK_ERASE 0xd8 ++#define SPI_NAND_RESET 0xff ++ ++#define SPI_NAND_GD5F_READID_LEN 2 ++ ++#define SPI_NAND_GD5F_ECC_MASK (BIT(0) | BIT(1) | BIT(2)) ++#define SPI_NAND_GD5F_ECC_UNCORR (BIT(0) | BIT(1) | BIT(2)) ++#define SPI_NAND_GD5F_ECC_SHIFT 4 ++ ++static int spi_nand_gd5f_ooblayout_256_ecc(struct mtd_info *mtd, int section, ++ struct mtd_oob_region *oobregion) ++{ ++ if (section) ++ return -ERANGE; ++ ++ oobregion->offset = 128; ++ oobregion->length = 128; ++ ++ return 0; ++} ++ ++static int spi_nand_gd5f_ooblayout_256_free(struct mtd_info *mtd, int section, ++ struct mtd_oob_region *oobregion) ++{ ++ if (section) ++ return -ERANGE; ++ ++ oobregion->offset = 1; ++ oobregion->length = 127; ++ ++ return 0; ++} ++ ++static const struct mtd_ooblayout_ops spi_nand_gd5f_oob_256_ops = { ++ .ecc = spi_nand_gd5f_ooblayout_256_ecc, ++ .free = spi_nand_gd5f_ooblayout_256_free, ++}; ++ ++static struct nand_flash_dev spi_nand_flash_ids[] = { ++ { ++ .name = "SPI NAND 512MiB 3,3V", ++ .id = { NAND_MFR_GIGADEVICE, 0xb4 }, ++ .chipsize = 512, ++ .pagesize = SZ_4K, ++ .erasesize = SZ_256K, ++ .id_len = 2, ++ .oobsize = 256, ++ .ecc.strength_ds = 8, ++ .ecc.step_ds = 512, ++ }, ++ { ++ .name = "SPI NAND 512MiB 1,8V", ++ .id = { NAND_MFR_GIGADEVICE, 0xa4 }, ++ .chipsize = 512, ++ .pagesize = SZ_4K, ++ .erasesize = SZ_256K, ++ .id_len = 2, ++ .oobsize = 256, ++ .ecc.strength_ds = 8, ++ .ecc.step_ds = 512, ++ }, ++}; ++ ++enum spi_nand_device_variant { ++ SPI_NAND_GENERIC, ++ SPI_NAND_GD5F, ++}; ++ ++struct spi_nand_device_cmd { ++ ++ /* ++ * Command and address. I/O errors have been observed if a ++ * separate spi_transfer is used for command and address, ++ * so keep them together. ++ */ ++ u32 n_cmd; ++ u8 cmd[5]; ++ ++ /* Tx data */ ++ u32 n_tx; ++ u8 *tx_buf; ++ ++ /* Rx data */ ++ u32 n_rx; ++ u8 *rx_buf; ++ u8 rx_nbits; ++ u8 tx_nbits; ++}; ++ ++struct spi_nand_device { ++ struct spi_nand spi_nand; ++ struct spi_device *spi; ++ ++ struct spi_nand_device_cmd cmd; ++}; ++ ++static int spi_nand_send_command(struct spi_device *spi, ++ struct spi_nand_device_cmd *cmd) ++{ ++ struct spi_message message; ++ struct spi_transfer x[2]; ++ ++ if (!cmd->n_cmd) { ++ dev_err(&spi->dev, "cannot send an empty command\n"); ++ return -EINVAL; ++ } ++ ++ if (cmd->n_tx && cmd->n_rx) { ++ dev_err(&spi->dev, "cannot send and receive data at the same time\n"); ++ return -EINVAL; ++ } ++ ++ spi_message_init(&message); ++ memset(x, 0, sizeof(x)); ++ ++ /* Command and address */ ++ x[0].len = cmd->n_cmd; ++ x[0].tx_buf = cmd->cmd; ++ x[0].tx_nbits = cmd->tx_nbits; ++ spi_message_add_tail(&x[0], &message); ++ ++ /* Data to be transmitted */ ++ if (cmd->n_tx) { ++ x[1].len = cmd->n_tx; ++ x[1].tx_buf = cmd->tx_buf; ++ x[1].tx_nbits = cmd->tx_nbits; ++ spi_message_add_tail(&x[1], &message); ++ } ++ ++ /* Data to be received */ ++ if (cmd->n_rx) { ++ x[1].len = cmd->n_rx; ++ x[1].rx_buf = cmd->rx_buf; ++ x[1].rx_nbits = cmd->rx_nbits; ++ spi_message_add_tail(&x[1], &message); ++ } ++ ++ return spi_sync(spi, &message); ++} ++ ++static int spi_nand_device_reset(struct spi_nand *snand) ++{ ++ struct spi_nand_device *snand_dev = snand->priv; ++ struct spi_nand_device_cmd *cmd = &snand_dev->cmd; ++ ++ memset(cmd, 0, sizeof(struct spi_nand_device_cmd)); ++ cmd->n_cmd = 1; ++ cmd->cmd[0] = SPI_NAND_RESET; ++ ++ dev_dbg(snand->dev, "%s\n", __func__); ++ ++ return spi_nand_send_command(snand_dev->spi, cmd); ++} ++ ++static int spi_nand_device_read_reg(struct spi_nand *snand, u8 opcode, u8 *buf) ++{ ++ struct spi_nand_device *snand_dev = snand->priv; ++ struct spi_nand_device_cmd *cmd = &snand_dev->cmd; ++ ++ memset(cmd, 0, sizeof(struct spi_nand_device_cmd)); ++ cmd->n_cmd = 2; ++ cmd->cmd[0] = SPI_NAND_GET_FEATURE; ++ cmd->cmd[1] = opcode; ++ cmd->n_rx = 1; ++ cmd->rx_buf = buf; ++ ++ dev_dbg(snand->dev, "%s: reg 0%x\n", __func__, opcode); ++ ++ return spi_nand_send_command(snand_dev->spi, cmd); ++} ++ ++static int spi_nand_device_write_reg(struct spi_nand *snand, u8 opcode, u8 *buf) ++{ ++ struct spi_nand_device *snand_dev = snand->priv; ++ struct spi_nand_device_cmd *cmd = &snand_dev->cmd; ++ ++ memset(cmd, 0, sizeof(struct spi_nand_device_cmd)); ++ cmd->n_cmd = 2; ++ cmd->cmd[0] = SPI_NAND_SET_FEATURE; ++ cmd->cmd[1] = opcode; ++ cmd->n_tx = 1; ++ cmd->tx_buf = buf; ++ ++ dev_dbg(snand->dev, "%s: reg 0%x\n", __func__, opcode); ++ ++ return spi_nand_send_command(snand_dev->spi, cmd); ++} ++ ++static int spi_nand_device_write_enable(struct spi_nand *snand) ++{ ++ struct spi_nand_device *snand_dev = snand->priv; ++ struct spi_nand_device_cmd *cmd = &snand_dev->cmd; ++ ++ memset(cmd, 0, sizeof(struct spi_nand_device_cmd)); ++ cmd->n_cmd = 1; ++ cmd->cmd[0] = SPI_NAND_WRITE_ENABLE; ++ ++ dev_dbg(snand->dev, "%s\n", __func__); ++ ++ return spi_nand_send_command(snand_dev->spi, cmd); ++} ++ ++static int spi_nand_device_write_disable(struct spi_nand *snand) ++{ ++ struct spi_nand_device *snand_dev = snand->priv; ++ struct spi_nand_device_cmd *cmd = &snand_dev->cmd; ++ ++ memset(cmd, 0, sizeof(struct spi_nand_device_cmd)); ++ cmd->n_cmd = 1; ++ cmd->cmd[0] = SPI_NAND_WRITE_DISABLE; ++ ++ dev_dbg(snand->dev, "%s\n", __func__); ++ ++ return spi_nand_send_command(snand_dev->spi, cmd); ++} ++ ++static int spi_nand_device_write_page(struct spi_nand *snand, ++ unsigned int page_addr) ++{ ++ struct spi_nand_device *snand_dev = snand->priv; ++ struct spi_nand_device_cmd *cmd = &snand_dev->cmd; ++ ++ memset(cmd, 0, sizeof(struct spi_nand_device_cmd)); ++ cmd->n_cmd = 4; ++ cmd->cmd[0] = SPI_NAND_PROGRAM_EXEC; ++ cmd->cmd[1] = (u8)((page_addr & 0xff0000) >> 16); ++ cmd->cmd[2] = (u8)((page_addr & 0xff00) >> 8); ++ cmd->cmd[3] = (u8)(page_addr & 0xff); ++ ++ dev_dbg(snand->dev, "%s: page 0x%x\n", __func__, page_addr); ++ ++ return spi_nand_send_command(snand_dev->spi, cmd); ++} ++ ++static int spi_nand_device_store_cache(struct spi_nand *snand, ++ unsigned int page_offset, size_t length, ++ u8 *write_buf) ++{ ++ struct spi_nand_device *snand_dev = snand->priv; ++ struct spi_nand_device_cmd *cmd = &snand_dev->cmd; ++ struct spi_device *spi = snand_dev->spi; ++ ++ memset(cmd, 0, sizeof(struct spi_nand_device_cmd)); ++ cmd->n_cmd = 3; ++ cmd->cmd[0] = spi->mode & SPI_TX_QUAD ? SPI_NAND_PROGRAM_LOAD4 : ++ SPI_NAND_PROGRAM_LOAD; ++ cmd->cmd[1] = (u8)((page_offset & 0xff00) >> 8); ++ cmd->cmd[2] = (u8)(page_offset & 0xff); ++ cmd->n_tx = length; ++ cmd->tx_buf = write_buf; ++ cmd->tx_nbits = spi->mode & SPI_TX_QUAD ? 4 : 1; ++ ++ dev_dbg(snand->dev, "%s: offset 0x%x\n", __func__, page_offset); ++ ++ return spi_nand_send_command(snand_dev->spi, cmd); ++} ++ ++static int spi_nand_device_load_page(struct spi_nand *snand, ++ unsigned int page_addr) ++{ ++ struct spi_nand_device *snand_dev = snand->priv; ++ struct spi_nand_device_cmd *cmd = &snand_dev->cmd; ++ ++ memset(cmd, 0, sizeof(struct spi_nand_device_cmd)); ++ cmd->n_cmd = 4; ++ cmd->cmd[0] = SPI_NAND_PAGE_READ; ++ cmd->cmd[1] = (u8)((page_addr & 0xff0000) >> 16); ++ cmd->cmd[2] = (u8)((page_addr & 0xff00) >> 8); ++ cmd->cmd[3] = (u8)(page_addr & 0xff); ++ ++ dev_dbg(snand->dev, "%s: page 0x%x\n", __func__, page_addr); ++ ++ return spi_nand_send_command(snand_dev->spi, cmd); ++} ++ ++static int spi_nand_device_read_cache(struct spi_nand *snand, ++ unsigned int page_offset, size_t length, ++ u8 *read_buf) ++{ ++ struct spi_nand_device *snand_dev = snand->priv; ++ struct spi_nand_device_cmd *cmd = &snand_dev->cmd; ++ struct spi_device *spi = snand_dev->spi; ++ ++ memset(cmd, 0, sizeof(struct spi_nand_device_cmd)); ++ if ((spi->mode & SPI_RX_DUAL) || (spi->mode & SPI_RX_QUAD)) ++ cmd->n_cmd = 5; ++ else ++ cmd->n_cmd = 4; ++ cmd->cmd[0] = (spi->mode & SPI_RX_QUAD) ? SPI_NAND_READ_CACHE_X4 : ++ ((spi->mode & SPI_RX_DUAL) ? SPI_NAND_READ_CACHE_X2 : ++ SPI_NAND_READ_CACHE); ++ cmd->cmd[1] = 0; /* dummy byte */ ++ cmd->cmd[2] = (u8)((page_offset & 0xff00) >> 8); ++ cmd->cmd[3] = (u8)(page_offset & 0xff); ++ cmd->cmd[4] = 0; /* dummy byte */ ++ cmd->n_rx = length; ++ cmd->rx_buf = read_buf; ++ cmd->rx_nbits = (spi->mode & SPI_RX_QUAD) ? 4 : ++ ((spi->mode & SPI_RX_DUAL) ? 2 : 1); ++ ++ dev_dbg(snand->dev, "%s: offset 0x%x\n", __func__, page_offset); ++ ++ return spi_nand_send_command(snand_dev->spi, cmd); ++} ++ ++static int spi_nand_device_block_erase(struct spi_nand *snand, ++ unsigned int page_addr) ++{ ++ struct spi_nand_device *snand_dev = snand->priv; ++ struct spi_nand_device_cmd *cmd = &snand_dev->cmd; ++ ++ memset(cmd, 0, sizeof(struct spi_nand_device_cmd)); ++ cmd->n_cmd = 4; ++ cmd->cmd[0] = SPI_NAND_BLOCK_ERASE; ++ cmd->cmd[1] = (u8)((page_addr & 0xff0000) >> 16); ++ cmd->cmd[2] = (u8)((page_addr & 0xff00) >> 8); ++ cmd->cmd[3] = (u8)(page_addr & 0xff); ++ ++ dev_dbg(snand->dev, "%s: block 0x%x\n", __func__, page_addr); ++ ++ return spi_nand_send_command(snand_dev->spi, cmd); ++} ++ ++static int spi_nand_gd5f_read_id(struct spi_nand *snand, u8 *buf) ++{ ++ struct spi_nand_device *snand_dev = snand->priv; ++ struct spi_nand_device_cmd *cmd = &snand_dev->cmd; ++ ++ memset(cmd, 0, sizeof(struct spi_nand_device_cmd)); ++ cmd->n_cmd = 1; ++ cmd->cmd[0] = SPI_NAND_READ_ID; ++ cmd->n_rx = SPI_NAND_GD5F_READID_LEN; ++ cmd->rx_buf = buf; ++ ++ dev_dbg(snand->dev, "%s\n", __func__); ++ ++ return spi_nand_send_command(snand_dev->spi, cmd); ++} ++ ++static void spi_nand_gd5f_ecc_status(unsigned int status, ++ unsigned int *corrected, ++ unsigned int *ecc_error) ++{ ++ unsigned int ecc_status = (status >> SPI_NAND_GD5F_ECC_SHIFT) & ++ SPI_NAND_GD5F_ECC_MASK; ++ ++ *ecc_error = (ecc_status == SPI_NAND_GD5F_ECC_UNCORR) ? 1 : 0; ++ if (*ecc_error == 0) ++ *corrected = (ecc_status > 1) ? (2 + ecc_status) : 0; ++} ++ ++static int spi_nand_device_probe(struct spi_device *spi) ++{ ++ enum spi_nand_device_variant variant; ++ struct spi_nand_device *priv; ++ struct spi_nand *snand; ++ int ret; ++ ++ priv = devm_kzalloc(&spi->dev, sizeof(*priv), GFP_KERNEL); ++ if (!priv) ++ return -ENOMEM; ++ ++ snand = &priv->spi_nand; ++ ++ snand->read_cache = spi_nand_device_read_cache; ++ snand->load_page = spi_nand_device_load_page; ++ snand->store_cache = spi_nand_device_store_cache; ++ snand->write_page = spi_nand_device_write_page; ++ snand->write_reg = spi_nand_device_write_reg; ++ snand->read_reg = spi_nand_device_read_reg; ++ snand->block_erase = spi_nand_device_block_erase; ++ snand->reset = spi_nand_device_reset; ++ snand->write_enable = spi_nand_device_write_enable; ++ snand->write_disable = spi_nand_device_write_disable; ++ snand->dev = &spi->dev; ++ snand->priv = priv; ++ ++ /* This'll mean we won't need to specify any specific compatible string ++ * for a given device, and instead just support spi-nand. ++ */ ++ variant = spi_get_device_id(spi)->driver_data; ++ switch (variant) { ++ case SPI_NAND_GD5F: ++ snand->read_id = spi_nand_gd5f_read_id; ++ snand->get_ecc_status = spi_nand_gd5f_ecc_status; ++ snand->ooblayout = &spi_nand_gd5f_oob_256_ops; ++ break; ++ default: ++ dev_err(snand->dev, "unknown device\n"); ++ return -ENODEV; ++ } ++ ++ spi_set_drvdata(spi, snand); ++ priv->spi = spi; ++ ++ ret = spi_nand_register(snand, spi_nand_flash_ids); ++ if (ret) ++ return ret; ++ return 0; ++} ++ ++static int spi_nand_device_remove(struct spi_device *spi) ++{ ++ struct spi_nand *snand = spi_get_drvdata(spi); ++ ++ spi_nand_unregister(snand); ++ ++ return 0; ++} ++ ++const struct spi_device_id spi_nand_id_table[] = { ++ { "spi-nand", SPI_NAND_GENERIC }, ++ { "gd5f", SPI_NAND_GD5F }, ++ { }, ++}; ++MODULE_DEVICE_TABLE(spi, spi_nand_id_table); ++ ++static struct spi_driver spi_nand_device_driver = { ++ .driver = { ++ .name = "spi_nand_device", ++ .owner = THIS_MODULE, ++ }, ++ .id_table = spi_nand_id_table, ++ .probe = spi_nand_device_probe, ++ .remove = spi_nand_device_remove, ++}; ++module_spi_driver(spi_nand_device_driver); ++ ++MODULE_AUTHOR("Ezequiel Garcia <ezequiel.garcia@imgtec.com>"); ++MODULE_DESCRIPTION("SPI NAND device support"); ++MODULE_LICENSE("GPL v2"); diff --git a/target/linux/pistachio/patches-4.14/901-MIPS-DTS-img-marduk-add-nor-partition-name.patch b/target/linux/pistachio/patches-4.14/901-MIPS-DTS-img-marduk-add-nor-partition-name.patch new file mode 100644 index 0000000000..6af11eaa36 --- /dev/null +++ b/target/linux/pistachio/patches-4.14/901-MIPS-DTS-img-marduk-add-nor-partition-name.patch @@ -0,0 +1,20 @@ +From b5f49b448cd3c7ab930f1a53c88f739a86071df8 Mon Sep 17 00:00:00 2001 +From: Ian Pozella <Ian.Pozella@imgtec.com> +Date: Mon, 20 Feb 2017 10:38:07 +0000 +Subject: MIPS: DTS: img: marduk: add nor partition name + +Signed-off-by: Ian Pozella <Ian.Pozella@imgtec.com> +--- + arch/mips/boot/dts/img/pistachio_marduk.dts | 1 + + 1 file changed, 1 insertion(+) + +--- a/arch/mips/boot/dts/img/pistachio_marduk.dts ++++ b/arch/mips/boot/dts/img/pistachio_marduk.dts +@@ -90,6 +90,7 @@ + compatible = "spansion,s25fl016k", "jedec,spi-nor"; + reg = <0>; + spi-max-frequency = <50000000>; ++ linux,mtd-name = "spi-nor"; + }; + }; + diff --git a/target/linux/pistachio/patches-4.14/902-MIPS-DTS-img-marduk-add-nand-device-support.patch b/target/linux/pistachio/patches-4.14/902-MIPS-DTS-img-marduk-add-nand-device-support.patch new file mode 100644 index 0000000000..bd03f08126 --- /dev/null +++ b/target/linux/pistachio/patches-4.14/902-MIPS-DTS-img-marduk-add-nand-device-support.patch @@ -0,0 +1,30 @@ +From c13cfb3a49cf1578bb85f19a7066f262503a39ba Mon Sep 17 00:00:00 2001 +From: Abhimanyu Vishwakarma <Abhimanyu.Vishwakarma@imgtec.com> +Date: Thu, 24 Nov 2016 19:26:46 +0530 +Subject: MIPS: DTS: img: marduk: add nand device support + +Signed-off-by: Abhimanyu Vishwakarma <Abhimanyu.Vishwakarma@imgtec.com> +--- + arch/mips/boot/dts/img/pistachio_marduk.dts | 11 +++++++++++ + 1 file changed, 11 insertions(+) + +--- a/arch/mips/boot/dts/img/pistachio_marduk.dts ++++ b/arch/mips/boot/dts/img/pistachio_marduk.dts +@@ -92,6 +92,17 @@ + spi-max-frequency = <50000000>; + linux,mtd-name = "spi-nor"; + }; ++ flash@1 { ++ compatible = "gigadevice,gd5f"; ++ reg = <1>; ++ spi-max-frequency = <50000000>; ++ nand-on-flash-bbt; ++ spi-rx-bus-width = <2>; ++ spi-tx-bus-width = <4>; ++ #address-cells = <1>; ++ #size-cells = <1>; ++ linux,mtd-name = "spi-nand"; ++ }; + }; + + &uart0 { |