diff options
author | John Crispin <john@openwrt.org> | 2015-04-01 08:32:03 +0000 |
---|---|---|
committer | John Crispin <john@openwrt.org> | 2015-04-01 08:32:03 +0000 |
commit | d3a9fc4586f05f5baffec6eedab5e9884348aa97 (patch) | |
tree | 2599794a0587a65b620a1450a54c6cf470903640 /target/linux/ipq806x/patches-4.0 | |
parent | 2d74104b524e51d9ded11ea5b3cb6f39a08e5711 (diff) | |
download | mtk-20170518-d3a9fc4586f05f5baffec6eedab5e9884348aa97.zip mtk-20170518-d3a9fc4586f05f5baffec6eedab5e9884348aa97.tar.gz mtk-20170518-d3a9fc4586f05f5baffec6eedab5e9884348aa97.tar.bz2 |
ipq806x: add support for 4.0 kernel
Signed-off-by: Mathieu Olivari <mathieu@codeaurora.org>
SVN-Revision: 45210
Diffstat (limited to 'target/linux/ipq806x/patches-4.0')
9 files changed, 1382 insertions, 0 deletions
diff --git a/target/linux/ipq806x/patches-4.0/001-spi-qup-Add-DMA-capabilities.patch b/target/linux/ipq806x/patches-4.0/001-spi-qup-Add-DMA-capabilities.patch new file mode 100644 index 0000000..62badd5 --- /dev/null +++ b/target/linux/ipq806x/patches-4.0/001-spi-qup-Add-DMA-capabilities.patch @@ -0,0 +1,522 @@ +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Subject: spi: qup: Add DMA capabilities +From: Andy Gross <agross@codeaurora.org> +X-Patchwork-Id: 4432401 +Message-Id: <1403816781-31008-1-git-send-email-agross@codeaurora.org> +To: Mark Brown <broonie@kernel.org> +Cc: linux-spi@vger.kernel.org, Sagar Dharia <sdharia@codeaurora.org>, + Daniel Sneddon <dsneddon@codeaurora.org>, + Bjorn Andersson <bjorn.andersson@sonymobile.com>, + "Ivan T. Ivanov" <iivanov@mm-sol.com>, + linux-kernel@vger.kernel.org, linux-arm-kernel@lists.infradead.org, + linux-arm-msm@vger.kernel.org, Andy Gross <agross@codeaurora.org> +Date: Thu, 26 Jun 2014 16:06:21 -0500 + +This patch adds DMA capabilities to the spi-qup driver. If DMA channels are +present, the QUP will use DMA instead of block mode for transfers to/from SPI +peripherals for transactions larger than the length of a block. + +Signed-off-by: Andy Gross <agross@codeaurora.org> + +--- +.../devicetree/bindings/spi/qcom,spi-qup.txt | 10 + + drivers/spi/spi-qup.c | 361 ++++++++++++++++++-- + 2 files changed, 350 insertions(+), 21 deletions(-) + +--- a/Documentation/devicetree/bindings/spi/qcom,spi-qup.txt ++++ b/Documentation/devicetree/bindings/spi/qcom,spi-qup.txt +@@ -27,6 +27,11 @@ Optional properties: + - spi-max-frequency: Specifies maximum SPI clock frequency, + Units - Hz. Definition as per + Documentation/devicetree/bindings/spi/spi-bus.txt ++- dmas : Two DMA channel specifiers following the convention outlined ++ in bindings/dma/dma.txt ++- dma-names: Names for the dma channels, if present. There must be at ++ least one channel named "tx" for transmit and named "rx" for ++ receive. + - num-cs: total number of chipselects + - cs-gpios: should specify GPIOs used for chipselects. + The gpios will be referred to as reg = <index> in the SPI child +@@ -51,6 +56,10 @@ Example: + clocks = <&gcc GCC_BLSP2_QUP2_SPI_APPS_CLK>, <&gcc GCC_BLSP2_AHB_CLK>; + clock-names = "core", "iface"; + ++ dmas = <&blsp2_bam 2>, ++ <&blsp2_bam 3>; ++ dma-names = "rx", "tx"; ++ + pinctrl-names = "default"; + pinctrl-0 = <&spi8_default>; + +--- a/drivers/spi/spi-qup.c ++++ b/drivers/spi/spi-qup.c +@@ -22,6 +22,8 @@ + #include <linux/platform_device.h> + #include <linux/pm_runtime.h> + #include <linux/spi/spi.h> ++#include <linux/dmaengine.h> ++#include <linux/dma-mapping.h> + + #define QUP_CONFIG 0x0000 + #define QUP_STATE 0x0004 +@@ -116,6 +118,8 @@ + + #define SPI_NUM_CHIPSELECTS 4 + ++#define SPI_MAX_XFER (SZ_64K - 64) ++ + /* high speed mode is when bus rate is greater then 26MHz */ + #define SPI_HS_MIN_RATE 26000000 + #define SPI_MAX_RATE 50000000 +@@ -143,6 +147,17 @@ struct spi_qup { + int tx_bytes; + int rx_bytes; + int qup_v1; ++ ++ int use_dma; ++ ++ struct dma_chan *rx_chan; ++ struct dma_slave_config rx_conf; ++ struct dma_chan *tx_chan; ++ struct dma_slave_config tx_conf; ++ dma_addr_t rx_dma; ++ dma_addr_t tx_dma; ++ void *dummy; ++ atomic_t dma_outstanding; + }; + + +@@ -266,6 +281,221 @@ static void spi_qup_fifo_write(struct sp + } + } + ++static void qup_dma_callback(void *data) ++{ ++ struct spi_qup *controller = data; ++ ++ if (atomic_dec_and_test(&controller->dma_outstanding)) ++ complete(&controller->done); ++} ++ ++static int spi_qup_do_dma(struct spi_qup *controller, struct spi_transfer *xfer) ++{ ++ struct dma_async_tx_descriptor *rxd, *txd; ++ dma_cookie_t rx_cookie, tx_cookie; ++ u32 xfer_len, rx_align = 0, tx_align = 0, n_words; ++ struct scatterlist tx_sg[2], rx_sg[2]; ++ int ret = 0; ++ u32 bytes_to_xfer = xfer->len; ++ u32 offset = 0; ++ u32 rx_nents = 0, tx_nents = 0; ++ dma_addr_t rx_dma = 0, tx_dma = 0, rx_dummy_dma = 0, tx_dummy_dma = 0; ++ ++ ++ if (xfer->rx_buf) { ++ rx_dma = dma_map_single(controller->dev, xfer->rx_buf, ++ xfer->len, DMA_FROM_DEVICE); ++ ++ if (dma_mapping_error(controller->dev, rx_dma)) { ++ ret = -ENOMEM; ++ return ret; ++ } ++ ++ /* check to see if we need dummy buffer for leftover bytes */ ++ rx_align = xfer->len % controller->in_blk_sz; ++ if (rx_align) { ++ rx_dummy_dma = dma_map_single(controller->dev, ++ controller->dummy, controller->in_fifo_sz, ++ DMA_FROM_DEVICE); ++ ++ if (dma_mapping_error(controller->dev, rx_dummy_dma)) { ++ ret = -ENOMEM; ++ goto err_map_rx_dummy; ++ } ++ } ++ } ++ ++ if (xfer->tx_buf) { ++ tx_dma = dma_map_single(controller->dev, ++ (void *)xfer->tx_buf, xfer->len, DMA_TO_DEVICE); ++ ++ if (dma_mapping_error(controller->dev, tx_dma)) { ++ ret = -ENOMEM; ++ goto err_map_tx; ++ } ++ ++ /* check to see if we need dummy buffer for leftover bytes */ ++ tx_align = xfer->len % controller->out_blk_sz; ++ if (tx_align) { ++ memcpy(controller->dummy + SZ_1K, ++ xfer->tx_buf + xfer->len - tx_align, ++ tx_align); ++ memset(controller->dummy + SZ_1K + tx_align, 0, ++ controller->out_blk_sz - tx_align); ++ ++ tx_dummy_dma = dma_map_single(controller->dev, ++ controller->dummy + SZ_1K, ++ controller->out_blk_sz, DMA_TO_DEVICE); ++ ++ if (dma_mapping_error(controller->dev, tx_dummy_dma)) { ++ ret = -ENOMEM; ++ goto err_map_tx_dummy; ++ } ++ } ++ } ++ ++ atomic_set(&controller->dma_outstanding, 0); ++ ++ while (bytes_to_xfer > 0) { ++ xfer_len = min_t(u32, bytes_to_xfer, SPI_MAX_XFER); ++ n_words = DIV_ROUND_UP(xfer_len, controller->w_size); ++ ++ /* write out current word count to controller */ ++ writel_relaxed(n_words, controller->base + QUP_MX_INPUT_CNT); ++ writel_relaxed(n_words, controller->base + QUP_MX_OUTPUT_CNT); ++ ++ reinit_completion(&controller->done); ++ ++ if (xfer->tx_buf) { ++ /* recalc align for each transaction */ ++ tx_align = xfer_len % controller->out_blk_sz; ++ ++ if (tx_align) ++ tx_nents = 2; ++ else ++ tx_nents = 1; ++ ++ /* initialize scatterlists */ ++ sg_init_table(tx_sg, tx_nents); ++ sg_dma_len(&tx_sg[0]) = xfer_len - tx_align; ++ sg_dma_address(&tx_sg[0]) = tx_dma + offset; ++ ++ /* account for non block size transfer */ ++ if (tx_align) { ++ sg_dma_len(&tx_sg[1]) = controller->out_blk_sz; ++ sg_dma_address(&tx_sg[1]) = tx_dummy_dma; ++ } ++ ++ txd = dmaengine_prep_slave_sg(controller->tx_chan, ++ tx_sg, tx_nents, DMA_MEM_TO_DEV, 0); ++ if (!txd) { ++ ret = -ENOMEM; ++ goto err_unmap; ++ } ++ ++ atomic_inc(&controller->dma_outstanding); ++ ++ txd->callback = qup_dma_callback; ++ txd->callback_param = controller; ++ ++ tx_cookie = dmaengine_submit(txd); ++ ++ dma_async_issue_pending(controller->tx_chan); ++ } ++ ++ if (xfer->rx_buf) { ++ /* recalc align for each transaction */ ++ rx_align = xfer_len % controller->in_blk_sz; ++ ++ if (rx_align) ++ rx_nents = 2; ++ else ++ rx_nents = 1; ++ ++ /* initialize scatterlists */ ++ sg_init_table(rx_sg, rx_nents); ++ sg_dma_address(&rx_sg[0]) = rx_dma + offset; ++ sg_dma_len(&rx_sg[0]) = xfer_len - rx_align; ++ ++ /* account for non block size transfer */ ++ if (rx_align) { ++ sg_dma_len(&rx_sg[1]) = controller->in_blk_sz; ++ sg_dma_address(&rx_sg[1]) = rx_dummy_dma; ++ } ++ ++ rxd = dmaengine_prep_slave_sg(controller->rx_chan, ++ rx_sg, rx_nents, DMA_DEV_TO_MEM, 0); ++ if (!rxd) { ++ ret = -ENOMEM; ++ goto err_unmap; ++ } ++ ++ atomic_inc(&controller->dma_outstanding); ++ ++ rxd->callback = qup_dma_callback; ++ rxd->callback_param = controller; ++ ++ rx_cookie = dmaengine_submit(rxd); ++ ++ dma_async_issue_pending(controller->rx_chan); ++ } ++ ++ if (spi_qup_set_state(controller, QUP_STATE_RUN)) { ++ dev_warn(controller->dev, "cannot set EXECUTE state\n"); ++ goto err_unmap; ++ } ++ ++ if (!wait_for_completion_timeout(&controller->done, ++ msecs_to_jiffies(1000))) { ++ ret = -ETIMEDOUT; ++ ++ /* clear out all the DMA transactions */ ++ if (xfer->tx_buf) ++ dmaengine_terminate_all(controller->tx_chan); ++ if (xfer->rx_buf) ++ dmaengine_terminate_all(controller->rx_chan); ++ ++ goto err_unmap; ++ } ++ ++ if (rx_align) ++ memcpy(xfer->rx_buf + offset + xfer->len - rx_align, ++ controller->dummy, rx_align); ++ ++ /* adjust remaining bytes to transfer */ ++ bytes_to_xfer -= xfer_len; ++ offset += xfer_len; ++ ++ ++ /* reset mini-core state so we can program next transaction */ ++ if (spi_qup_set_state(controller, QUP_STATE_RESET)) { ++ dev_err(controller->dev, "cannot set RESET state\n"); ++ goto err_unmap; ++ } ++ } ++ ++ ret = 0; ++ ++err_unmap: ++ if (tx_align) ++ dma_unmap_single(controller->dev, tx_dummy_dma, ++ controller->out_fifo_sz, DMA_TO_DEVICE); ++err_map_tx_dummy: ++ if (xfer->tx_buf) ++ dma_unmap_single(controller->dev, tx_dma, xfer->len, ++ DMA_TO_DEVICE); ++err_map_tx: ++ if (rx_align) ++ dma_unmap_single(controller->dev, rx_dummy_dma, ++ controller->in_fifo_sz, DMA_FROM_DEVICE); ++err_map_rx_dummy: ++ if (xfer->rx_buf) ++ dma_unmap_single(controller->dev, rx_dma, xfer->len, ++ DMA_FROM_DEVICE); ++ ++ return ret; ++} ++ + static irqreturn_t spi_qup_qup_irq(int irq, void *dev_id) + { + struct spi_qup *controller = dev_id; +@@ -315,11 +545,13 @@ static irqreturn_t spi_qup_qup_irq(int i + error = -EIO; + } + +- if (opflags & QUP_OP_IN_SERVICE_FLAG) +- spi_qup_fifo_read(controller, xfer); ++ if (!controller->use_dma) { ++ if (opflags & QUP_OP_IN_SERVICE_FLAG) ++ spi_qup_fifo_read(controller, xfer); + +- if (opflags & QUP_OP_OUT_SERVICE_FLAG) +- spi_qup_fifo_write(controller, xfer); ++ if (opflags & QUP_OP_OUT_SERVICE_FLAG) ++ spi_qup_fifo_write(controller, xfer); ++ } + + spin_lock_irqsave(&controller->lock, flags); + controller->error = error; +@@ -339,6 +571,8 @@ static int spi_qup_io_config(struct spi_ + struct spi_qup *controller = spi_master_get_devdata(spi->master); + u32 config, iomode, mode; + int ret, n_words, w_size; ++ size_t dma_align = dma_get_cache_alignment(); ++ u32 dma_available = 0; + + if (spi->mode & SPI_LOOP && xfer->len > controller->in_fifo_sz) { + dev_err(controller->dev, "too big size for loopback %d > %d\n", +@@ -367,6 +601,11 @@ static int spi_qup_io_config(struct spi_ + n_words = xfer->len / w_size; + controller->w_size = w_size; + ++ if (controller->rx_chan && ++ IS_ALIGNED((size_t)xfer->tx_buf, dma_align) && ++ IS_ALIGNED((size_t)xfer->rx_buf, dma_align)) ++ dma_available = 1; ++ + if (n_words <= (controller->in_fifo_sz / sizeof(u32))) { + mode = QUP_IO_M_MODE_FIFO; + writel_relaxed(n_words, controller->base + QUP_MX_READ_CNT); +@@ -374,19 +613,31 @@ static int spi_qup_io_config(struct spi_ + /* must be zero for FIFO */ + writel_relaxed(0, controller->base + QUP_MX_INPUT_CNT); + writel_relaxed(0, controller->base + QUP_MX_OUTPUT_CNT); +- } else { ++ controller->use_dma = 0; ++ } else if (!dma_available) { + mode = QUP_IO_M_MODE_BLOCK; + writel_relaxed(n_words, controller->base + QUP_MX_INPUT_CNT); + writel_relaxed(n_words, controller->base + QUP_MX_OUTPUT_CNT); + /* must be zero for BLOCK and BAM */ + writel_relaxed(0, controller->base + QUP_MX_READ_CNT); + writel_relaxed(0, controller->base + QUP_MX_WRITE_CNT); ++ controller->use_dma = 0; ++ } else { ++ mode = QUP_IO_M_MODE_DMOV; ++ writel_relaxed(0, controller->base + QUP_MX_READ_CNT); ++ writel_relaxed(0, controller->base + QUP_MX_WRITE_CNT); ++ controller->use_dma = 1; + } + + iomode = readl_relaxed(controller->base + QUP_IO_M_MODES); + /* Set input and output transfer mode */ + iomode &= ~(QUP_IO_M_INPUT_MODE_MASK | QUP_IO_M_OUTPUT_MODE_MASK); +- iomode &= ~(QUP_IO_M_PACK_EN | QUP_IO_M_UNPACK_EN); ++ ++ if (!controller->use_dma) ++ iomode &= ~(QUP_IO_M_PACK_EN | QUP_IO_M_UNPACK_EN); ++ else ++ iomode |= QUP_IO_M_PACK_EN | QUP_IO_M_UNPACK_EN; ++ + iomode |= (mode << QUP_IO_M_OUTPUT_MODE_MASK_SHIFT); + iomode |= (mode << QUP_IO_M_INPUT_MODE_MASK_SHIFT); + +@@ -419,6 +670,14 @@ static int spi_qup_io_config(struct spi_ + config &= ~(QUP_CONFIG_NO_INPUT | QUP_CONFIG_NO_OUTPUT | QUP_CONFIG_N); + config |= xfer->bits_per_word - 1; + config |= QUP_CONFIG_SPI_MODE; ++ ++ if (controller->use_dma) { ++ if (!xfer->tx_buf) ++ config |= QUP_CONFIG_NO_OUTPUT; ++ if (!xfer->rx_buf) ++ config |= QUP_CONFIG_NO_INPUT; ++ } ++ + writel_relaxed(config, controller->base + QUP_CONFIG); + + /* only write to OPERATIONAL_MASK when register is present */ +@@ -452,25 +711,29 @@ static int spi_qup_transfer_one(struct s + controller->tx_bytes = 0; + spin_unlock_irqrestore(&controller->lock, flags); + +- if (spi_qup_set_state(controller, QUP_STATE_RUN)) { +- dev_warn(controller->dev, "cannot set RUN state\n"); +- goto exit; +- } ++ if (controller->use_dma) { ++ ret = spi_qup_do_dma(controller, xfer); ++ } else { ++ if (spi_qup_set_state(controller, QUP_STATE_RUN)) { ++ dev_warn(controller->dev, "cannot set RUN state\n"); ++ goto exit; ++ } + +- if (spi_qup_set_state(controller, QUP_STATE_PAUSE)) { +- dev_warn(controller->dev, "cannot set PAUSE state\n"); +- goto exit; +- } ++ if (spi_qup_set_state(controller, QUP_STATE_PAUSE)) { ++ dev_warn(controller->dev, "cannot set PAUSE state\n"); ++ goto exit; ++ } + +- spi_qup_fifo_write(controller, xfer); ++ spi_qup_fifo_write(controller, xfer); + +- if (spi_qup_set_state(controller, QUP_STATE_RUN)) { +- dev_warn(controller->dev, "cannot set EXECUTE state\n"); +- goto exit; +- } ++ if (spi_qup_set_state(controller, QUP_STATE_RUN)) { ++ dev_warn(controller->dev, "cannot set EXECUTE state\n"); ++ goto exit; ++ } + +- if (!wait_for_completion_timeout(&controller->done, timeout)) +- ret = -ETIMEDOUT; ++ if (!wait_for_completion_timeout(&controller->done, timeout)) ++ ret = -ETIMEDOUT; ++ } + exit: + spi_qup_set_state(controller, QUP_STATE_RESET); + spin_lock_irqsave(&controller->lock, flags); +@@ -553,6 +816,7 @@ static int spi_qup_probe(struct platform + master->transfer_one = spi_qup_transfer_one; + master->dev.of_node = pdev->dev.of_node; + master->auto_runtime_pm = true; ++ master->dma_alignment = dma_get_cache_alignment(); + + platform_set_drvdata(pdev, master); + +@@ -618,6 +882,56 @@ static int spi_qup_probe(struct platform + QUP_ERROR_INPUT_UNDER_RUN | QUP_ERROR_OUTPUT_UNDER_RUN, + base + QUP_ERROR_FLAGS_EN); + ++ /* allocate dma resources, if available */ ++ controller->rx_chan = dma_request_slave_channel(&pdev->dev, "rx"); ++ if (controller->rx_chan) { ++ controller->tx_chan = ++ dma_request_slave_channel(&pdev->dev, "tx"); ++ ++ if (!controller->tx_chan) { ++ dev_err(&pdev->dev, "Failed to allocate dma tx chan"); ++ dma_release_channel(controller->rx_chan); ++ } ++ ++ /* set DMA parameters */ ++ controller->rx_conf.device_fc = 1; ++ controller->rx_conf.src_addr = res->start + QUP_INPUT_FIFO; ++ controller->rx_conf.src_maxburst = controller->in_blk_sz; ++ ++ controller->tx_conf.device_fc = 1; ++ controller->tx_conf.dst_addr = res->start + QUP_OUTPUT_FIFO; ++ controller->tx_conf.dst_maxburst = controller->out_blk_sz; ++ ++ if (dmaengine_slave_config(controller->rx_chan, ++ &controller->rx_conf)) { ++ dev_err(&pdev->dev, "failed to configure RX channel\n"); ++ ++ dma_release_channel(controller->rx_chan); ++ dma_release_channel(controller->tx_chan); ++ controller->tx_chan = NULL; ++ controller->rx_chan = NULL; ++ } else if (dmaengine_slave_config(controller->tx_chan, ++ &controller->tx_conf)) { ++ dev_err(&pdev->dev, "failed to configure TX channel\n"); ++ ++ dma_release_channel(controller->rx_chan); ++ dma_release_channel(controller->tx_chan); ++ controller->tx_chan = NULL; ++ controller->rx_chan = NULL; ++ } ++ ++ controller->dummy = devm_kmalloc(controller->dev, PAGE_SIZE, ++ GFP_KERNEL); ++ ++ if (!controller->dummy) { ++ dma_release_channel(controller->rx_chan); ++ dma_release_channel(controller->tx_chan); ++ controller->tx_chan = NULL; ++ controller->rx_chan = NULL; ++ } ++ } ++ ++ + writel_relaxed(0, base + SPI_CONFIG); + writel_relaxed(SPI_IO_C_NO_TRI_STATE, base + SPI_IO_CONTROL); + +@@ -730,6 +1044,11 @@ static int spi_qup_remove(struct platfor + if (ret) + return ret; + ++ if (controller->rx_chan) ++ dma_release_channel(controller->rx_chan); ++ if (controller->tx_chan) ++ dma_release_channel(controller->tx_chan); ++ + clk_disable_unprepare(controller->cclk); + clk_disable_unprepare(controller->iclk); + diff --git a/target/linux/ipq806x/patches-4.0/002-v3-spi-qup-Fix-incorrect-block-transfers.patch b/target/linux/ipq806x/patches-4.0/002-v3-spi-qup-Fix-incorrect-block-transfers.patch new file mode 100644 index 0000000..c8c6e4f --- /dev/null +++ b/target/linux/ipq806x/patches-4.0/002-v3-spi-qup-Fix-incorrect-block-transfers.patch @@ -0,0 +1,376 @@ +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Subject: [v3] spi: qup: Fix incorrect block transfers +From: Andy Gross <agross@codeaurora.org> +X-Patchwork-Id: 5007321 +Message-Id: <1412112088-25928-1-git-send-email-agross@codeaurora.org> +To: Mark Brown <broonie@kernel.org> +Cc: linux-spi@vger.kernel.org, linux-kernel@vger.kernel.org, + linux-arm-kernel@lists.infradead.org, linux-arm-msm@vger.kernel.org, + "Ivan T. Ivanov" <iivanov@mm-sol.com>, + Bjorn Andersson <bjorn.andersson@sonymobile.com>, + Kumar Gala <galak@codeaurora.org>, Andy Gross <agross@codeaurora.org> +Date: Tue, 30 Sep 2014 16:21:28 -0500 + +This patch fixes a number of errors with the QUP block transfer mode. Errors +manifested themselves as input underruns, output overruns, and timed out +transactions. + +The block mode does not require the priming that occurs in FIFO mode. At the +moment that the QUP is placed into the RUN state, the QUP will immediately raise +an interrupt if the request is a write. Therefore, there is no need to prime +the pump. + +In addition, the block transfers require that whole blocks of data are +read/written at a time. The last block of data that completes a transaction may +contain less than a full blocks worth of data. + +Each block of data results in an input/output service interrupt accompanied with +a input/output block flag set. Additional block reads/writes require clearing +of the service flag. It is ok to check for additional blocks of data in the +ISR, but you have to ack every block you transfer. Imbalanced acks result in +early return from complete transactions with pending interrupts that still have +to be ack'd. The next transaction can be affected by these interrupts. +Transactions are deemed complete when the MAX_INPUT or MAX_OUTPUT flag are set. + +Changes from v2: +- Added in additional completion check so that transaction done is not + prematurely signaled. +- Fixed various review comments. + +Changes from v1: +- Split out read/write block function. +- Removed extraneous checks for transfer length + +Signed-off-by: Andy Gross <agross@codeaurora.org> + +--- +drivers/spi/spi-qup.c | 201 ++++++++++++++++++++++++++++++++++++------------- + 1 file changed, 148 insertions(+), 53 deletions(-) + +--- a/drivers/spi/spi-qup.c ++++ b/drivers/spi/spi-qup.c +@@ -82,6 +82,8 @@ + #define QUP_IO_M_MODE_BAM 3 + + /* QUP_OPERATIONAL fields */ ++#define QUP_OP_IN_BLOCK_READ_REQ BIT(13) ++#define QUP_OP_OUT_BLOCK_WRITE_REQ BIT(12) + #define QUP_OP_MAX_INPUT_DONE_FLAG BIT(11) + #define QUP_OP_MAX_OUTPUT_DONE_FLAG BIT(10) + #define QUP_OP_IN_SERVICE_FLAG BIT(9) +@@ -147,6 +149,7 @@ struct spi_qup { + int tx_bytes; + int rx_bytes; + int qup_v1; ++ int mode; + + int use_dma; + +@@ -213,30 +216,14 @@ static int spi_qup_set_state(struct spi_ + return 0; + } + +- +-static void spi_qup_fifo_read(struct spi_qup *controller, +- struct spi_transfer *xfer) ++static void spi_qup_fill_read_buffer(struct spi_qup *controller, ++ struct spi_transfer *xfer, u32 data) + { + u8 *rx_buf = xfer->rx_buf; +- u32 word, state; +- int idx, shift, w_size; +- +- w_size = controller->w_size; +- +- while (controller->rx_bytes < xfer->len) { +- +- state = readl_relaxed(controller->base + QUP_OPERATIONAL); +- if (0 == (state & QUP_OP_IN_FIFO_NOT_EMPTY)) +- break; +- +- word = readl_relaxed(controller->base + QUP_INPUT_FIFO); +- +- if (!rx_buf) { +- controller->rx_bytes += w_size; +- continue; +- } ++ int idx, shift; + +- for (idx = 0; idx < w_size; idx++, controller->rx_bytes++) { ++ if (rx_buf) ++ for (idx = 0; idx < controller->w_size; idx++) { + /* + * The data format depends on bytes per SPI word: + * 4 bytes: 0x12345678 +@@ -244,41 +231,139 @@ static void spi_qup_fifo_read(struct spi + * 1 byte : 0x00000012 + */ + shift = BITS_PER_BYTE; +- shift *= (w_size - idx - 1); +- rx_buf[controller->rx_bytes] = word >> shift; ++ shift *= (controller->w_size - idx - 1); ++ rx_buf[controller->rx_bytes + idx] = data >> shift; ++ } ++ ++ controller->rx_bytes += controller->w_size; ++} ++ ++static void spi_qup_prepare_write_data(struct spi_qup *controller, ++ struct spi_transfer *xfer, u32 *data) ++{ ++ const u8 *tx_buf = xfer->tx_buf; ++ u32 val; ++ int idx; ++ ++ *data = 0; ++ ++ if (tx_buf) ++ for (idx = 0; idx < controller->w_size; idx++) { ++ val = tx_buf[controller->tx_bytes + idx]; ++ *data |= val << (BITS_PER_BYTE * (3 - idx)); + } ++ ++ controller->tx_bytes += controller->w_size; ++} ++ ++static void spi_qup_fifo_read(struct spi_qup *controller, ++ struct spi_transfer *xfer) ++{ ++ u32 data; ++ ++ /* clear service request */ ++ writel_relaxed(QUP_OP_IN_SERVICE_FLAG, ++ controller->base + QUP_OPERATIONAL); ++ ++ while (controller->rx_bytes < xfer->len) { ++ if (!(readl_relaxed(controller->base + QUP_OPERATIONAL) & ++ QUP_OP_IN_FIFO_NOT_EMPTY)) ++ break; ++ ++ data = readl_relaxed(controller->base + QUP_INPUT_FIFO); ++ ++ spi_qup_fill_read_buffer(controller, xfer, data); + } + } + + static void spi_qup_fifo_write(struct spi_qup *controller, +- struct spi_transfer *xfer) ++ struct spi_transfer *xfer) + { +- const u8 *tx_buf = xfer->tx_buf; +- u32 word, state, data; +- int idx, w_size; ++ u32 data; + +- w_size = controller->w_size; ++ /* clear service request */ ++ writel_relaxed(QUP_OP_OUT_SERVICE_FLAG, ++ controller->base + QUP_OPERATIONAL); + + while (controller->tx_bytes < xfer->len) { + +- state = readl_relaxed(controller->base + QUP_OPERATIONAL); +- if (state & QUP_OP_OUT_FIFO_FULL) ++ if (readl_relaxed(controller->base + QUP_OPERATIONAL) & ++ QUP_OP_OUT_FIFO_FULL) + break; + +- word = 0; +- for (idx = 0; idx < w_size; idx++, controller->tx_bytes++) { ++ spi_qup_prepare_write_data(controller, xfer, &data); ++ writel_relaxed(data, controller->base + QUP_OUTPUT_FIFO); + +- if (!tx_buf) { +- controller->tx_bytes += w_size; +- break; +- } ++ } ++} + +- data = tx_buf[controller->tx_bytes]; +- word |= data << (BITS_PER_BYTE * (3 - idx)); +- } ++static void spi_qup_block_read(struct spi_qup *controller, ++ struct spi_transfer *xfer) ++{ ++ u32 data; ++ u32 reads_per_blk = controller->in_blk_sz >> 2; ++ u32 num_words = (xfer->len - controller->rx_bytes) / controller->w_size; ++ int i; ++ ++ do { ++ /* ACK by clearing service flag */ ++ writel_relaxed(QUP_OP_IN_SERVICE_FLAG, ++ controller->base + QUP_OPERATIONAL); ++ ++ /* transfer up to a block size of data in a single pass */ ++ for (i = 0; num_words && i < reads_per_blk; i++, num_words--) { ++ ++ /* read data and fill up rx buffer */ ++ data = readl_relaxed(controller->base + QUP_INPUT_FIFO); ++ spi_qup_fill_read_buffer(controller, xfer, data); ++ } ++ ++ /* check to see if next block is ready */ ++ if (!(readl_relaxed(controller->base + QUP_OPERATIONAL) & ++ QUP_OP_IN_BLOCK_READ_REQ)) ++ break; + +- writel_relaxed(word, controller->base + QUP_OUTPUT_FIFO); +- } ++ } while (num_words); ++ ++ /* ++ * Due to extra stickiness of the QUP_OP_IN_SERVICE_FLAG during block ++ * reads, it has to be cleared again at the very end ++ */ ++ if (readl_relaxed(controller->base + QUP_OPERATIONAL) & ++ QUP_OP_MAX_INPUT_DONE_FLAG) ++ writel_relaxed(QUP_OP_IN_SERVICE_FLAG, ++ controller->base + QUP_OPERATIONAL); ++ ++} ++ ++static void spi_qup_block_write(struct spi_qup *controller, ++ struct spi_transfer *xfer) ++{ ++ u32 data; ++ u32 writes_per_blk = controller->out_blk_sz >> 2; ++ u32 num_words = (xfer->len - controller->tx_bytes) / controller->w_size; ++ int i; ++ ++ do { ++ /* ACK by clearing service flag */ ++ writel_relaxed(QUP_OP_OUT_SERVICE_FLAG, ++ controller->base + QUP_OPERATIONAL); ++ ++ /* transfer up to a block size of data in a single pass */ ++ for (i = 0; num_words && i < writes_per_blk; i++, num_words--) { ++ ++ /* swizzle the bytes for output and write out */ ++ spi_qup_prepare_write_data(controller, xfer, &data); ++ writel_relaxed(data, ++ controller->base + QUP_OUTPUT_FIFO); ++ } ++ ++ /* check to see if next block is ready */ ++ if (!(readl_relaxed(controller->base + QUP_OPERATIONAL) & ++ QUP_OP_OUT_BLOCK_WRITE_REQ)) ++ break; ++ ++ } while (num_words); + } + + static void qup_dma_callback(void *data) +@@ -515,9 +600,9 @@ static irqreturn_t spi_qup_qup_irq(int i + + writel_relaxed(qup_err, controller->base + QUP_ERROR_FLAGS); + writel_relaxed(spi_err, controller->base + SPI_ERROR_FLAGS); +- writel_relaxed(opflags, controller->base + QUP_OPERATIONAL); + + if (!xfer) { ++ writel_relaxed(opflags, controller->base + QUP_OPERATIONAL); + dev_err_ratelimited(controller->dev, "unexpected irq %08x %08x %08x\n", + qup_err, spi_err, opflags); + return IRQ_HANDLED; +@@ -546,11 +631,19 @@ static irqreturn_t spi_qup_qup_irq(int i + } + + if (!controller->use_dma) { +- if (opflags & QUP_OP_IN_SERVICE_FLAG) +- spi_qup_fifo_read(controller, xfer); ++ if (opflags & QUP_OP_IN_SERVICE_FLAG) { ++ if (opflags & QUP_OP_IN_BLOCK_READ_REQ) ++ spi_qup_block_read(controller, xfer); ++ else ++ spi_qup_fifo_read(controller, xfer); ++ } + +- if (opflags & QUP_OP_OUT_SERVICE_FLAG) +- spi_qup_fifo_write(controller, xfer); ++ if (opflags & QUP_OP_OUT_SERVICE_FLAG) { ++ if (opflags & QUP_OP_OUT_BLOCK_WRITE_REQ) ++ spi_qup_block_write(controller, xfer); ++ else ++ spi_qup_fifo_write(controller, xfer); ++ } + } + + spin_lock_irqsave(&controller->lock, flags); +@@ -558,7 +651,8 @@ static irqreturn_t spi_qup_qup_irq(int i + controller->xfer = xfer; + spin_unlock_irqrestore(&controller->lock, flags); + +- if (controller->rx_bytes == xfer->len || error) ++ if ((controller->rx_bytes == xfer->len && ++ (opflags & QUP_OP_MAX_INPUT_DONE_FLAG)) || error) + complete(&controller->done); + + return IRQ_HANDLED; +@@ -569,7 +663,7 @@ static irqreturn_t spi_qup_qup_irq(int i + static int spi_qup_io_config(struct spi_device *spi, struct spi_transfer *xfer) + { + struct spi_qup *controller = spi_master_get_devdata(spi->master); +- u32 config, iomode, mode, control; ++ u32 config, iomode, control; + int ret, n_words, w_size; + size_t dma_align = dma_get_cache_alignment(); + u32 dma_available = 0; +@@ -607,7 +701,7 @@ static int spi_qup_io_config(struct spi_ + dma_available = 1; + + if (n_words <= (controller->in_fifo_sz / sizeof(u32))) { +- mode = QUP_IO_M_MODE_FIFO; ++ controller->mode = QUP_IO_M_MODE_FIFO; + writel_relaxed(n_words, controller->base + QUP_MX_READ_CNT); + writel_relaxed(n_words, controller->base + QUP_MX_WRITE_CNT); + /* must be zero for FIFO */ +@@ -615,7 +709,7 @@ static int spi_qup_io_config(struct spi_ + writel_relaxed(0, controller->base + QUP_MX_OUTPUT_CNT); + controller->use_dma = 0; + } else if (!dma_available) { +- mode = QUP_IO_M_MODE_BLOCK; ++ controller->mode = QUP_IO_M_MODE_BLOCK; + writel_relaxed(n_words, controller->base + QUP_MX_INPUT_CNT); + writel_relaxed(n_words, controller->base + QUP_MX_OUTPUT_CNT); + /* must be zero for BLOCK and BAM */ +@@ -623,7 +717,7 @@ static int spi_qup_io_config(struct spi_ + writel_relaxed(0, controller->base + QUP_MX_WRITE_CNT); + controller->use_dma = 0; + } else { +- mode = QUP_IO_M_MODE_DMOV; ++ controller->mode = QUP_IO_M_MODE_DMOV; + writel_relaxed(0, controller->base + QUP_MX_READ_CNT); + writel_relaxed(0, controller->base + QUP_MX_WRITE_CNT); + controller->use_dma = 1; +@@ -638,8 +732,8 @@ static int spi_qup_io_config(struct spi_ + else + iomode |= QUP_IO_M_PACK_EN | QUP_IO_M_UNPACK_EN; + +- iomode |= (mode << QUP_IO_M_OUTPUT_MODE_MASK_SHIFT); +- iomode |= (mode << QUP_IO_M_INPUT_MODE_MASK_SHIFT); ++ iomode |= (controller->mode << QUP_IO_M_OUTPUT_MODE_MASK_SHIFT); ++ iomode |= (controller->mode << QUP_IO_M_INPUT_MODE_MASK_SHIFT); + + writel_relaxed(iomode, controller->base + QUP_IO_M_MODES); + +@@ -733,7 +827,8 @@ static int spi_qup_transfer_one(struct s + goto exit; + } + +- spi_qup_fifo_write(controller, xfer); ++ if (controller->mode == QUP_IO_M_MODE_FIFO) ++ spi_qup_fifo_write(controller, xfer); + + if (spi_qup_set_state(controller, QUP_STATE_RUN)) { + dev_warn(controller->dev, "cannot set EXECUTE state\n"); +@@ -750,6 +845,7 @@ exit: + if (!ret) + ret = controller->error; + spin_unlock_irqrestore(&controller->lock, flags); ++ + return ret; + } + diff --git a/target/linux/ipq806x/patches-4.0/003-spi-qup-Ensure-done-detection.patch b/target/linux/ipq806x/patches-4.0/003-spi-qup-Ensure-done-detection.patch new file mode 100644 index 0000000..7052227 --- /dev/null +++ b/target/linux/ipq806x/patches-4.0/003-spi-qup-Ensure-done-detection.patch @@ -0,0 +1,56 @@ +From 4faba89e3ffbb1c5f6232651375b9b3212b50f02 Mon Sep 17 00:00:00 2001 +From: Andy Gross <agross@codeaurora.org> +Date: Thu, 15 Jan 2015 17:56:02 -0800 +Subject: [PATCH] spi: qup: Ensure done detection + +This patch fixes an issue where a SPI transaction has completed, but the done +condition is missed. This occurs because at the time of interrupt the +MAX_INPUT_DONE_FLAG is not asserted. However, in the process of reading blocks +of data from the FIFO, the last portion of data comes in. + +The opflags read at the beginning of the irq handler no longer matches the +current opflag state. To get around this condition, the block read function +should update the opflags so that done detection is correct after the return. + +Change-Id: If109e0eeb432f96000d765c4b34dbb2269f8093f +Signed-off-by: Andy Gross <agross@codeaurora.org> +--- + drivers/spi/spi-qup.c | 12 +++++++----- + 1 file changed, 7 insertions(+), 5 deletions(-) + +--- a/drivers/spi/spi-qup.c ++++ b/drivers/spi/spi-qup.c +@@ -298,7 +298,7 @@ static void spi_qup_fifo_write(struct sp + } + + static void spi_qup_block_read(struct spi_qup *controller, +- struct spi_transfer *xfer) ++ struct spi_transfer *xfer, u32 *opflags) + { + u32 data; + u32 reads_per_blk = controller->in_blk_sz >> 2; +@@ -327,10 +327,12 @@ static void spi_qup_block_read(struct sp + + /* + * Due to extra stickiness of the QUP_OP_IN_SERVICE_FLAG during block +- * reads, it has to be cleared again at the very end ++ * reads, it has to be cleared again at the very end. However, be sure ++ * to refresh opflags value because MAX_INPUT_DONE_FLAG may now be ++ * present and this is used to determine if transaction is complete + */ +- if (readl_relaxed(controller->base + QUP_OPERATIONAL) & +- QUP_OP_MAX_INPUT_DONE_FLAG) ++ *opflags = readl_relaxed(controller->base + QUP_OPERATIONAL); ++ if (*opflags & QUP_OP_MAX_INPUT_DONE_FLAG) + writel_relaxed(QUP_OP_IN_SERVICE_FLAG, + controller->base + QUP_OPERATIONAL); + +@@ -633,7 +635,7 @@ static irqreturn_t spi_qup_qup_irq(int i + if (!controller->use_dma) { + if (opflags & QUP_OP_IN_SERVICE_FLAG) { + if (opflags & QUP_OP_IN_BLOCK_READ_REQ) +- spi_qup_block_read(controller, xfer); ++ spi_qup_block_read(controller, xfer, &opflags); + else + spi_qup_fifo_read(controller, xfer); + } diff --git a/target/linux/ipq806x/patches-4.0/011-watchdog-qcom-use-timer-devicetree-binding.patch b/target/linux/ipq806x/patches-4.0/011-watchdog-qcom-use-timer-devicetree-binding.patch new file mode 100644 index 0000000..0cd7da1 --- /dev/null +++ b/target/linux/ipq806x/patches-4.0/011-watchdog-qcom-use-timer-devicetree-binding.patch @@ -0,0 +1,72 @@ +From fded70251b1b58f68de1d3757ece9965f0b75452 Mon Sep 17 00:00:00 2001 +From: Mathieu Olivari <mathieu@codeaurora.org> +Date: Thu, 19 Feb 2015 20:19:30 -0800 +Subject: [PATCH 1/3] watchdog: qcom: use timer devicetree binding + +MSM watchdog configuration happens in the same register block as the +timer, so we'll use the same binding as the existing timer. + +The qcom-wdt will now be probed when devicetree has an entry compatible +with "qcom,kpss-timer" or "qcom-scss-timer". + +Signed-off-by: Mathieu Olivari <mathieu@codeaurora.org> +--- + drivers/watchdog/qcom-wdt.c | 21 +++++++++++++++------ + 1 file changed, 15 insertions(+), 6 deletions(-) + +diff --git a/drivers/watchdog/qcom-wdt.c b/drivers/watchdog/qcom-wdt.c +index aa85618..aa03ca8 100644 +--- a/drivers/watchdog/qcom-wdt.c ++++ b/drivers/watchdog/qcom-wdt.c +@@ -20,9 +20,9 @@ + #include <linux/reboot.h> + #include <linux/watchdog.h> + +-#define WDT_RST 0x0 +-#define WDT_EN 0x8 +-#define WDT_BITE_TIME 0x24 ++#define WDT_RST 0x38 ++#define WDT_EN 0x40 ++#define WDT_BITE_TIME 0x5C + + struct qcom_wdt { + struct watchdog_device wdd; +@@ -117,6 +117,8 @@ static int qcom_wdt_probe(struct platform_device *pdev) + { + struct qcom_wdt *wdt; + struct resource *res; ++ struct device_node *np = pdev->dev.of_node; ++ u32 percpu_offset; + int ret; + + wdt = devm_kzalloc(&pdev->dev, sizeof(*wdt), GFP_KERNEL); +@@ -124,6 +126,14 @@ static int qcom_wdt_probe(struct platform_device *pdev) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ ++ /* We use CPU0's DGT for the watchdog */ ++ if (of_property_read_u32(np, "cpu-offset", &percpu_offset)) ++ percpu_offset = 0; ++ ++ res->start += percpu_offset; ++ res->end += percpu_offset; ++ + wdt->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(wdt->base)) + return PTR_ERR(wdt->base); +@@ -203,9 +213,8 @@ static int qcom_wdt_remove(struct platform_device *pdev) + } + + static const struct of_device_id qcom_wdt_of_table[] = { +- { .compatible = "qcom,kpss-wdt-msm8960", }, +- { .compatible = "qcom,kpss-wdt-apq8064", }, +- { .compatible = "qcom,kpss-wdt-ipq8064", }, ++ { .compatible = "qcom,kpss-timer" }, ++ { .compatible = "qcom,scss-timer" }, + { }, + }; + MODULE_DEVICE_TABLE(of, qcom_wdt_of_table); +-- +1.9.1 + diff --git a/target/linux/ipq806x/patches-4.0/012-ARM-qcom-add-description-of-KPSS-WDT-for-IPQ8064.patch b/target/linux/ipq806x/patches-4.0/012-ARM-qcom-add-description-of-KPSS-WDT-for-IPQ8064.patch new file mode 100644 index 0000000..24a093a --- /dev/null +++ b/target/linux/ipq806x/patches-4.0/012-ARM-qcom-add-description-of-KPSS-WDT-for-IPQ8064.patch @@ -0,0 +1,53 @@ +From 297cf8136ecd6a56520888fd28948393766b8ee7 Mon Sep 17 00:00:00 2001 +From: Mathieu Olivari <mathieu@codeaurora.org> +Date: Thu, 19 Feb 2015 20:27:39 -0800 +Subject: [PATCH 2/3] ARM: qcom: add description of KPSS WDT for IPQ8064 + +Add the watchdog related entries to the Krait Processor Sub-system +(KPSS) timer IPQ8064 devicetree section. Also, add a fixed-clock +description of SLEEP_CLK, which will do for now. + +Signed-off-by: Josh Cartwright <joshc@codeaurora.org> +Signed-off-by: Mathieu Olivari <mathieu@codeaurora.org> +--- + arch/arm/boot/dts/qcom-ipq8064.dtsi | 14 +++++++++++++- + 1 file changed, 13 insertions(+), 1 deletion(-) + +diff --git a/arch/arm/boot/dts/qcom-ipq8064.dtsi b/arch/arm/boot/dts/qcom-ipq8064.dtsi +index cb225da..d01f618 100644 +--- a/arch/arm/boot/dts/qcom-ipq8064.dtsi ++++ b/arch/arm/boot/dts/qcom-ipq8064.dtsi +@@ -60,6 +60,14 @@ + }; + }; + ++ clocks { ++ sleep_clk: sleep_clk { ++ compatible = "fixed-clock"; ++ clock-frequency = <32768>; ++ #clock-cells = <0>; ++ }; ++ }; ++ + soc: soc { + #address-cells = <1>; + #size-cells = <1>; +@@ -89,10 +97,14 @@ + compatible = "qcom,kpss-timer", "qcom,msm-timer"; + interrupts = <1 1 0x301>, + <1 2 0x301>, +- <1 3 0x301>; ++ <1 3 0x301>, ++ <1 4 0x301>, ++ <1 5 0x301>; + reg = <0x0200a000 0x100>; + clock-frequency = <25000000>, + <32768>; ++ clocks = <&sleep_clk>; ++ clock-names = "sleep"; + cpu-offset = <0x80000>; + }; + +-- +1.9.1 + diff --git a/target/linux/ipq806x/patches-4.0/013-ARM-msm-add-watchdog-entries-to-DT-timer-binding-doc.patch b/target/linux/ipq806x/patches-4.0/013-ARM-msm-add-watchdog-entries-to-DT-timer-binding-doc.patch new file mode 100644 index 0000000..e775f12 --- /dev/null +++ b/target/linux/ipq806x/patches-4.0/013-ARM-msm-add-watchdog-entries-to-DT-timer-binding-doc.patch @@ -0,0 +1,50 @@ +From e535f01dffb6dd9e09934fa219be52af3437a8f6 Mon Sep 17 00:00:00 2001 +From: Mathieu Olivari <mathieu@codeaurora.org> +Date: Thu, 19 Feb 2015 20:36:27 -0800 +Subject: [PATCH 3/3] ARM: msm: add watchdog entries to DT timer binding doc + +The watchdog has been reworked to use the same DT node as the timer. +This change is updating the device tree doc accordingly. + +Signed-off-by: Mathieu Olivari <mathieu@codeaurora.org> +--- + Documentation/devicetree/bindings/arm/msm/timer.txt | 16 +++++++++++++--- + 1 file changed, 13 insertions(+), 3 deletions(-) + +--- a/Documentation/devicetree/bindings/arm/msm/timer.txt ++++ b/Documentation/devicetree/bindings/arm/msm/timer.txt +@@ -9,11 +9,17 @@ Properties: + "qcom,scss-timer" - scorpion subsystem + + - interrupts : Interrupts for the debug timer, the first general purpose +- timer, and optionally a second general purpose timer in that +- order. ++ timer, and optionally a second general purpose timer, and ++ optionally as well, 2 watchdog interrupts, in that order. + + - reg : Specifies the base address of the timer registers. + ++- clocks: Reference to the parent clocks, one per output clock. The parents ++ must appear in the same order as the clock names. ++ ++- clock-names: The name of the clocks as free-form strings. They should be in ++ the same order as the clocks. ++ + - clock-frequency : The frequency of the debug timer and the general purpose + timer(s) in Hz in that order. + +@@ -29,9 +35,13 @@ Example: + compatible = "qcom,scss-timer", "qcom,msm-timer"; + interrupts = <1 1 0x301>, + <1 2 0x301>, +- <1 3 0x301>; ++ <1 3 0x301>, ++ <1 4 0x301>, ++ <1 5 0x301>; + reg = <0x0200a000 0x100>; + clock-frequency = <19200000>, + <32768>; ++ clocks = <&sleep_clk>; ++ clock-names = "sleep"; + cpu-offset = <0x40000>; + }; diff --git a/target/linux/ipq806x/patches-4.0/020-add-ap148-bootargs.patch b/target/linux/ipq806x/patches-4.0/020-add-ap148-bootargs.patch new file mode 100644 index 0000000..a61481e --- /dev/null +++ b/target/linux/ipq806x/patches-4.0/020-add-ap148-bootargs.patch @@ -0,0 +1,46 @@ +--- a/arch/arm/boot/dts/qcom-ipq8064-ap148.dts ++++ b/arch/arm/boot/dts/qcom-ipq8064-ap148.dts +@@ -14,6 +14,14 @@ + }; + }; + ++ alias { ++ serial0 = &uart4; ++ }; ++ ++ chosen { ++ linux,stdout-path = "serial0:115200n8"; ++ }; ++ + soc { + pinmux@800000 { + i2c4_pins: i2c4_pinmux { +--- a/arch/arm/boot/dts/qcom-ipq8064.dtsi ++++ b/arch/arm/boot/dts/qcom-ipq8064.dtsi +@@ -140,7 +140,7 @@ + ranges; + status = "disabled"; + +- serial@12490000 { ++ uart2: serial@12490000 { + compatible = "qcom,msm-uartdm-v1.3", "qcom,msm-uartdm"; + reg = <0x12490000 0x1000>, + <0x12480000 0x1000>; +@@ -175,7 +175,7 @@ + ranges; + status = "disabled"; + +- serial@16340000 { ++ uart4: serial@16340000 { + compatible = "qcom,msm-uartdm-v1.3", "qcom,msm-uartdm"; + reg = <0x16340000 0x1000>, + <0x16300000 0x1000>; +@@ -209,7 +209,7 @@ + ranges; + status = "disabled"; + +- serial@1a240000 { ++ uart5: serial@1a240000 { + compatible = "qcom,msm-uartdm-v1.3", "qcom,msm-uartdm"; + reg = <0x1a240000 0x1000>, + <0x1a200000 0x1000>; diff --git a/target/linux/ipq806x/patches-4.0/021-add-ap148-partitions.patch b/target/linux/ipq806x/patches-4.0/021-add-ap148-partitions.patch new file mode 100644 index 0000000..34eb9c0 --- /dev/null +++ b/target/linux/ipq806x/patches-4.0/021-add-ap148-partitions.patch @@ -0,0 +1,35 @@ +--- a/arch/arm/boot/dts/qcom-ipq8064-ap148.dts ++++ b/arch/arm/boot/dts/qcom-ipq8064-ap148.dts +@@ -78,13 +78,28 @@ + reg = <0>; + + partition@0 { +- label = "rootfs"; +- reg = <0x0 0x1000000>; ++ label = "lowlevel_init"; ++ reg = <0x0 0x1b0000>; + }; + + partition@1 { +- label = "scratch"; +- reg = <0x1000000 0x1000000>; ++ label = "u-boot"; ++ reg = <0x1b0000 0x80000>; ++ }; ++ ++ partition@2 { ++ label = "u-boot-env"; ++ reg = <0x230000 0x40000>; ++ }; ++ ++ partition@3 { ++ label = "caldata"; ++ reg = <0x270000 0x40000>; ++ }; ++ ++ partition@4 { ++ label = "firmware"; ++ reg = <0x2b0000 0x1d50000>; + }; + }; + }; diff --git a/target/linux/ipq806x/patches-4.0/700-add-gmac-dts-suport.patch b/target/linux/ipq806x/patches-4.0/700-add-gmac-dts-suport.patch new file mode 100644 index 0000000..89ebe66 --- /dev/null +++ b/target/linux/ipq806x/patches-4.0/700-add-gmac-dts-suport.patch @@ -0,0 +1,172 @@ +--- a/arch/arm/boot/dts/qcom-ipq8064-ap148.dts ++++ b/arch/arm/boot/dts/qcom-ipq8064-ap148.dts +@@ -18,8 +18,15 @@ + bootargs = "console=ttyMSM0,115200 root=/dev/mtdblock12 rootfstype=squashfs,jffs2"; + }; + ++ aliases { ++ mdio-gpio0 = &mdio0; ++ }; ++ + soc { + pinmux@800000 { ++ pinctrl-0 = <&mdio0_pins &rgmii2_pins>; ++ pinctrl-names = "default"; ++ + i2c4_pins: i2c4_pinmux { + pins = "gpio12", "gpio13"; + function = "gsbi4"; +@@ -34,6 +41,25 @@ + bias-none; + }; + }; ++ ++ mdio0_pins: mdio0_pins { ++ mux { ++ pins = "gpio0", "gpio1"; ++ function = "gpio"; ++ drive-strength = <8>; ++ bias-disable; ++ }; ++ }; ++ ++ rgmii2_pins: rgmii2_pins { ++ mux { ++ pins = "gpio27", "gpio28", "gpio29", "gpio30", "gpio31", "gpio32", ++ "gpio51", "gpio52", "gpio59", "gpio60", "gpio61", "gpio62" ; ++ function = "rgmii2"; ++ drive-strength = <8>; ++ bias-disable; ++ }; ++ }; + }; + + gsbi@16300000 { +@@ -72,6 +98,7 @@ + #size-cells = <1>; + spi-max-frequency = <50000000>; + reg = <0>; ++ m25p,fast-read; + + partition@0 { + label = "0:SBL1"; +@@ -148,5 +175,66 @@ + sata@29000000 { + status = "ok"; + }; ++ ++ mdio0: mdio { ++ compatible = "virtual,mdio-gpio"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ gpios = <&qcom_pinmux 1 0 &qcom_pinmux 0 0>; ++ ++ phy0: ethernet-phy@0 { ++ device_type = "ethernet-phy"; ++ reg = <0>; ++ qca,ar8327-initvals = < ++ 0x00004 0x7600000 /* PAD0_MODE */ ++ 0x00008 0x1000000 /* PAD5_MODE */ ++ 0x0000c 0x80 /* PAD6_MODE */ ++ 0x000e4 0xaa545 /* MAC_POWER_SEL */ ++ 0x000e0 0xc74164de /* SGMII_CTRL */ ++ 0x0007c 0x4e /* PORT0_STATUS */ ++ 0x00094 0x4e /* PORT6_STATUS */ ++ >; ++ }; ++ ++ phy4: ethernet-phy@4 { ++ device_type = "ethernet-phy"; ++ reg = <4>; ++ }; ++ }; ++ ++ nss-gmac-common { ++ reg = <0x03000000 0x0000FFFF 0x1bb00000 0x0000FFFF 0x00900000 0x00004000>; ++ reg-names = "nss_reg_base" , "qsgmii_reg_base", "clk_ctl_base"; ++ }; ++ ++ gmac1: ethernet@37200000 { ++ status = "ok"; ++ phy-mode = "rgmii"; ++ qcom,id = <1>; ++ qcom,phy_mdio_addr = <4>; ++ qcom,poll_required = <1>; ++ qcom,rgmii_delay = <0>; ++ qcom,emulation = <0>; ++ qcom,forced_speed = <1000>; ++ qcom,forced_duplex = <1>; ++ qcom,socver = <0>; ++ local-mac-address = [000000000000]; ++ mdiobus = <&mdio0>; ++ }; ++ ++ gmac2: ethernet@37400000 { ++ status = "ok"; ++ phy-mode = "sgmii"; ++ qcom,id = <2>; ++ qcom,phy_mdio_addr = <0>; ++ qcom,poll_required = <0>; ++ qcom,rgmii_delay = <0>; ++ qcom,emulation = <0>; ++ qcom,forced_speed = <1000>; ++ qcom,forced_duplex = <1>; ++ qcom,socver = <0>; ++ local-mac-address = [000000000000]; ++ mdiobus = <&mdio0>; ++ }; + }; + }; +--- a/arch/arm/boot/dts/qcom-ipq8064.dtsi ++++ b/arch/arm/boot/dts/qcom-ipq8064.dtsi +@@ -3,6 +3,7 @@ + #include "skeleton.dtsi" + #include <dt-bindings/clock/qcom,gcc-ipq806x.h> + #include <dt-bindings/soc/qcom,gsbi.h> ++#include <dt-bindings/interrupt-controller/arm-gic.h> + + / { + model = "Qualcomm IPQ8064"; +@@ -279,5 +280,42 @@ + #clock-cells = <1>; + #reset-cells = <1>; + }; ++ ++ nss-gmac-common { ++ reg = <0x03000000 0x0000FFFF 0x1bb00000 0x0000FFFF 0x00900000 0x00004000>; ++ reg-names = "nss_reg_base" , "qsgmii_reg_base", "clk_ctl_base"; ++ }; ++ ++ gmac0: ethernet@37000000 { ++ device_type = "network"; ++ compatible = "qcom,nss-gmac"; ++ reg = <0x37000000 0x200000>; ++ interrupts = <GIC_SPI 220 IRQ_TYPE_LEVEL_HIGH>; ++ status = "disabled"; ++ }; ++ ++ gmac1: ethernet@37200000 { ++ device_type = "network"; ++ compatible = "qcom,nss-gmac"; ++ reg = <0x37200000 0x200000>; ++ interrupts = <GIC_SPI 223 IRQ_TYPE_LEVEL_HIGH>; ++ status = "disabled"; ++ }; ++ ++ gmac2: ethernet@37400000 { ++ device_type = "network"; ++ compatible = "qcom,nss-gmac"; ++ reg = <0x37400000 0x200000>; ++ interrupts = <GIC_SPI 226 IRQ_TYPE_LEVEL_HIGH>; ++ status = "disabled"; ++ }; ++ ++ gmac3: ethernet@37600000 { ++ device_type = "network"; ++ compatible = "qcom,nss-gmac"; ++ reg = <0x37600000 0x200000>; ++ interrupts = <GIC_SPI 229 IRQ_TYPE_LEVEL_HIGH>; ++ status = "disabled"; ++ }; + }; + }; |