]> asedeno.scripts.mit.edu Git - linux.git/blobdiff - drivers/dma/tegra210-adma.c
nvme: fix regression upon hot device removal and insertion
[linux.git] / drivers / dma / tegra210-adma.c
index f26c458d584e25c18dbbbd7b7a1bce3eb53c5567..2805853e963fcf6f9076e8360ebd519585efa94e 100644 (file)
@@ -1,19 +1,8 @@
+// SPDX-License-Identifier: GPL-2.0-only
 /*
  * ADMA driver for Nvidia's Tegra210 ADMA controller.
  *
  * Copyright (c) 2016, NVIDIA CORPORATION.  All rights reserved.
- *
- * This program is free software; you can redistribute it and/or modify it
- * under the terms and conditions of the GNU General Public License,
- * version 2, as published by the Free Software Foundation.
- *
- * This program is distributed in the hope it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
- * more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
 #include <linux/clk.h>
 #define ADMA_CH_CONFIG_MAX_BUFS                                8
 
 #define ADMA_CH_FIFO_CTRL                              0x2c
-#define ADMA_CH_FIFO_CTRL_OVRFW_THRES(val)             (((val) & 0xf) << 24)
-#define ADMA_CH_FIFO_CTRL_STARV_THRES(val)             (((val) & 0xf) << 16)
-#define ADMA_CH_FIFO_CTRL_TX_FIFO_SIZE_SHIFT           8
-#define ADMA_CH_FIFO_CTRL_RX_FIFO_SIZE_SHIFT           0
+#define TEGRA210_ADMA_CH_FIFO_CTRL_OFLWTHRES(val)      (((val) & 0xf) << 24)
+#define TEGRA210_ADMA_CH_FIFO_CTRL_STRVTHRES(val)      (((val) & 0xf) << 16)
+#define TEGRA210_ADMA_CH_FIFO_CTRL_TXSIZE(val)         (((val) & 0xf) << 8)
+#define TEGRA210_ADMA_CH_FIFO_CTRL_RXSIZE(val)         ((val) & 0xf)
+#define TEGRA186_ADMA_CH_FIFO_CTRL_OFLWTHRES(val)      (((val) & 0x1f) << 24)
+#define TEGRA186_ADMA_CH_FIFO_CTRL_STRVTHRES(val)      (((val) & 0x1f) << 16)
+#define TEGRA186_ADMA_CH_FIFO_CTRL_TXSIZE(val)         (((val) & 0x1f) << 8)
+#define TEGRA186_ADMA_CH_FIFO_CTRL_RXSIZE(val)         ((val) & 0x1f)
 
 #define ADMA_CH_LOWER_SRC_ADDR                         0x34
 #define ADMA_CH_LOWER_TRG_ADDR                         0x3c
 
 #define TEGRA_ADMA_BURST_COMPLETE_TIME                 20
 
-#define ADMA_CH_FIFO_CTRL_DEFAULT      (ADMA_CH_FIFO_CTRL_OVRFW_THRES(1) | \
-                                        ADMA_CH_FIFO_CTRL_STARV_THRES(1))
+#define TEGRA210_FIFO_CTRL_DEFAULT (TEGRA210_ADMA_CH_FIFO_CTRL_OFLWTHRES(1) | \
+                                   TEGRA210_ADMA_CH_FIFO_CTRL_STRVTHRES(1) | \
+                                   TEGRA210_ADMA_CH_FIFO_CTRL_TXSIZE(3)    | \
+                                   TEGRA210_ADMA_CH_FIFO_CTRL_RXSIZE(3))
+
+#define TEGRA186_FIFO_CTRL_DEFAULT (TEGRA186_ADMA_CH_FIFO_CTRL_OFLWTHRES(1) | \
+                                   TEGRA186_ADMA_CH_FIFO_CTRL_STRVTHRES(1) | \
+                                   TEGRA186_ADMA_CH_FIFO_CTRL_TXSIZE(3)    | \
+                                   TEGRA186_ADMA_CH_FIFO_CTRL_RXSIZE(3))
 
 #define ADMA_CH_REG_FIELD_VAL(val, mask, shift)        (((val) & mask) << shift)
 
@@ -84,7 +84,8 @@ struct tegra_adma;
  * @global_int_clear: Register offset of DMA global interrupt clear.
  * @ch_req_tx_shift: Register offset for AHUB transmit channel select.
  * @ch_req_rx_shift: Register offset for AHUB receive channel select.
- * @ch_base_offset: Reister offset of DMA channel registers.
+ * @ch_base_offset: Register offset of DMA channel registers.
+ * @ch_fifo_ctrl: Default value for channel FIFO CTRL register.
  * @ch_req_mask: Mask for Tx or Rx channel select.
  * @ch_req_max: Maximum number of Tx or Rx channels available.
  * @ch_reg_size: Size of DMA channel register space.
@@ -97,6 +98,7 @@ struct tegra_adma_chip_data {
        unsigned int ch_req_tx_shift;
        unsigned int ch_req_rx_shift;
        unsigned int ch_base_offset;
+       unsigned int ch_fifo_ctrl;
        unsigned int ch_req_mask;
        unsigned int ch_req_max;
        unsigned int ch_reg_size;
@@ -112,6 +114,7 @@ struct tegra_adma_chan_regs {
        unsigned int src_addr;
        unsigned int trg_addr;
        unsigned int fifo_ctrl;
+       unsigned int cmd;
        unsigned int tc;
 };
 
@@ -141,6 +144,7 @@ struct tegra_adma_chan {
        enum dma_transfer_direction     sreq_dir;
        unsigned int                    sreq_index;
        bool                            sreq_reserved;
+       struct tegra_adma_chan_regs     ch_regs;
 
        /* Transfer count and position info */
        unsigned int                    tx_buf_count;
@@ -598,7 +602,7 @@ static int tegra_adma_set_xfer_params(struct tegra_adma_chan *tdc,
                         ADMA_CH_CTRL_FLOWCTRL_EN;
        ch_regs->config |= cdata->adma_get_burst_config(burst_size);
        ch_regs->config |= ADMA_CH_CONFIG_WEIGHT_FOR_WRR(1);
-       ch_regs->fifo_ctrl = ADMA_CH_FIFO_CTRL_DEFAULT;
+       ch_regs->fifo_ctrl = cdata->ch_fifo_ctrl;
        ch_regs->tc = desc->period_len & ADMA_CH_TC_COUNT_MASK;
 
        return tegra_adma_request_alloc(tdc, direction);
@@ -711,8 +715,30 @@ static struct dma_chan *tegra_dma_of_xlate(struct of_phandle_args *dma_spec,
 static int tegra_adma_runtime_suspend(struct device *dev)
 {
        struct tegra_adma *tdma = dev_get_drvdata(dev);
+       struct tegra_adma_chan_regs *ch_reg;
+       struct tegra_adma_chan *tdc;
+       int i;
 
        tdma->global_cmd = tdma_read(tdma, ADMA_GLOBAL_CMD);
+       if (!tdma->global_cmd)
+               goto clk_disable;
+
+       for (i = 0; i < tdma->nr_channels; i++) {
+               tdc = &tdma->channels[i];
+               ch_reg = &tdc->ch_regs;
+               ch_reg->cmd = tdma_ch_read(tdc, ADMA_CH_CMD);
+               /* skip if channel is not active */
+               if (!ch_reg->cmd)
+                       continue;
+               ch_reg->tc = tdma_ch_read(tdc, ADMA_CH_TC);
+               ch_reg->src_addr = tdma_ch_read(tdc, ADMA_CH_LOWER_SRC_ADDR);
+               ch_reg->trg_addr = tdma_ch_read(tdc, ADMA_CH_LOWER_TRG_ADDR);
+               ch_reg->ctrl = tdma_ch_read(tdc, ADMA_CH_CTRL);
+               ch_reg->fifo_ctrl = tdma_ch_read(tdc, ADMA_CH_FIFO_CTRL);
+               ch_reg->config = tdma_ch_read(tdc, ADMA_CH_CONFIG);
+       }
+
+clk_disable:
        clk_disable_unprepare(tdma->ahub_clk);
 
        return 0;
@@ -721,7 +747,9 @@ static int tegra_adma_runtime_suspend(struct device *dev)
 static int tegra_adma_runtime_resume(struct device *dev)
 {
        struct tegra_adma *tdma = dev_get_drvdata(dev);
-       int ret;
+       struct tegra_adma_chan_regs *ch_reg;
+       struct tegra_adma_chan *tdc;
+       int ret, i;
 
        ret = clk_prepare_enable(tdma->ahub_clk);
        if (ret) {
@@ -730,6 +758,24 @@ static int tegra_adma_runtime_resume(struct device *dev)
        }
        tdma_write(tdma, ADMA_GLOBAL_CMD, tdma->global_cmd);
 
+       if (!tdma->global_cmd)
+               return 0;
+
+       for (i = 0; i < tdma->nr_channels; i++) {
+               tdc = &tdma->channels[i];
+               ch_reg = &tdc->ch_regs;
+               /* skip if channel was not active earlier */
+               if (!ch_reg->cmd)
+                       continue;
+               tdma_ch_write(tdc, ADMA_CH_TC, ch_reg->tc);
+               tdma_ch_write(tdc, ADMA_CH_LOWER_SRC_ADDR, ch_reg->src_addr);
+               tdma_ch_write(tdc, ADMA_CH_LOWER_TRG_ADDR, ch_reg->trg_addr);
+               tdma_ch_write(tdc, ADMA_CH_CTRL, ch_reg->ctrl);
+               tdma_ch_write(tdc, ADMA_CH_FIFO_CTRL, ch_reg->fifo_ctrl);
+               tdma_ch_write(tdc, ADMA_CH_CONFIG, ch_reg->config);
+               tdma_ch_write(tdc, ADMA_CH_CMD, ch_reg->cmd);
+       }
+
        return 0;
 }
 
@@ -740,6 +786,7 @@ static const struct tegra_adma_chip_data tegra210_chip_data = {
        .ch_req_tx_shift        = 28,
        .ch_req_rx_shift        = 24,
        .ch_base_offset         = 0,
+       .ch_fifo_ctrl           = TEGRA210_FIFO_CTRL_DEFAULT,
        .ch_req_mask            = 0xf,
        .ch_req_max             = 10,
        .ch_reg_size            = 0x80,
@@ -753,6 +800,7 @@ static const struct tegra_adma_chip_data tegra186_chip_data = {
        .ch_req_tx_shift        = 27,
        .ch_req_rx_shift        = 22,
        .ch_base_offset         = 0x10000,
+       .ch_fifo_ctrl           = TEGRA186_FIFO_CTRL_DEFAULT,
        .ch_req_mask            = 0x1f,
        .ch_req_max             = 20,
        .ch_reg_size            = 0x100,
@@ -801,16 +849,6 @@ static int tegra_adma_probe(struct platform_device *pdev)
                return PTR_ERR(tdma->ahub_clk);
        }
 
-       pm_runtime_enable(&pdev->dev);
-
-       ret = pm_runtime_get_sync(&pdev->dev);
-       if (ret < 0)
-               goto rpm_disable;
-
-       ret = tegra_adma_init(tdma);
-       if (ret)
-               goto rpm_put;
-
        INIT_LIST_HEAD(&tdma->dma_dev.channels);
        for (i = 0; i < tdma->nr_channels; i++) {
                struct tegra_adma_chan *tdc = &tdma->channels[i];
@@ -829,6 +867,16 @@ static int tegra_adma_probe(struct platform_device *pdev)
                tdc->tdma = tdma;
        }
 
+       pm_runtime_enable(&pdev->dev);
+
+       ret = pm_runtime_get_sync(&pdev->dev);
+       if (ret < 0)
+               goto rpm_disable;
+
+       ret = tegra_adma_init(tdma);
+       if (ret)
+               goto rpm_put;
+
        dma_cap_set(DMA_SLAVE, tdma->dma_dev.cap_mask);
        dma_cap_set(DMA_PRIVATE, tdma->dma_dev.cap_mask);
        dma_cap_set(DMA_CYCLIC, tdma->dma_dev.cap_mask);
@@ -872,13 +920,13 @@ static int tegra_adma_probe(struct platform_device *pdev)
 
 dma_remove:
        dma_async_device_unregister(&tdma->dma_dev);
-irq_dispose:
-       while (--i >= 0)
-               irq_dispose_mapping(tdma->channels[i].irq);
 rpm_put:
        pm_runtime_put_sync(&pdev->dev);
 rpm_disable:
        pm_runtime_disable(&pdev->dev);
+irq_dispose:
+       while (--i >= 0)
+               irq_dispose_mapping(tdma->channels[i].irq);
 
        return ret;
 }
@@ -888,6 +936,7 @@ static int tegra_adma_remove(struct platform_device *pdev)
        struct tegra_adma *tdma = platform_get_drvdata(pdev);
        int i;
 
+       of_dma_controller_free(pdev->dev.of_node);
        dma_async_device_unregister(&tdma->dma_dev);
 
        for (i = 0; i < tdma->nr_channels; ++i)