162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * ADMA driver for Nvidia's Tegra210 ADMA controller. 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved. 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci#include <linux/clk.h> 962306a36Sopenharmony_ci#include <linux/iopoll.h> 1062306a36Sopenharmony_ci#include <linux/module.h> 1162306a36Sopenharmony_ci#include <linux/of.h> 1262306a36Sopenharmony_ci#include <linux/of_dma.h> 1362306a36Sopenharmony_ci#include <linux/of_irq.h> 1462306a36Sopenharmony_ci#include <linux/platform_device.h> 1562306a36Sopenharmony_ci#include <linux/pm_runtime.h> 1662306a36Sopenharmony_ci#include <linux/slab.h> 1762306a36Sopenharmony_ci 1862306a36Sopenharmony_ci#include "virt-dma.h" 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_ci#define ADMA_CH_CMD 0x00 2162306a36Sopenharmony_ci#define ADMA_CH_STATUS 0x0c 2262306a36Sopenharmony_ci#define ADMA_CH_STATUS_XFER_EN BIT(0) 2362306a36Sopenharmony_ci#define ADMA_CH_STATUS_XFER_PAUSED BIT(1) 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_ci#define ADMA_CH_INT_STATUS 0x10 2662306a36Sopenharmony_ci#define ADMA_CH_INT_STATUS_XFER_DONE BIT(0) 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_ci#define ADMA_CH_INT_CLEAR 0x1c 2962306a36Sopenharmony_ci#define ADMA_CH_CTRL 0x24 3062306a36Sopenharmony_ci#define ADMA_CH_CTRL_DIR(val) (((val) & 0xf) << 12) 3162306a36Sopenharmony_ci#define ADMA_CH_CTRL_DIR_AHUB2MEM 2 3262306a36Sopenharmony_ci#define ADMA_CH_CTRL_DIR_MEM2AHUB 4 3362306a36Sopenharmony_ci#define ADMA_CH_CTRL_MODE_CONTINUOUS (2 << 8) 3462306a36Sopenharmony_ci#define ADMA_CH_CTRL_FLOWCTRL_EN BIT(1) 3562306a36Sopenharmony_ci#define ADMA_CH_CTRL_XFER_PAUSE_SHIFT 0 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_ci#define ADMA_CH_CONFIG 0x28 3862306a36Sopenharmony_ci#define ADMA_CH_CONFIG_SRC_BUF(val) (((val) & 0x7) << 28) 3962306a36Sopenharmony_ci#define ADMA_CH_CONFIG_TRG_BUF(val) (((val) & 0x7) << 24) 4062306a36Sopenharmony_ci#define ADMA_CH_CONFIG_BURST_SIZE_SHIFT 20 4162306a36Sopenharmony_ci#define ADMA_CH_CONFIG_MAX_BURST_SIZE 16 4262306a36Sopenharmony_ci#define ADMA_CH_CONFIG_WEIGHT_FOR_WRR(val) ((val) & 0xf) 4362306a36Sopenharmony_ci#define ADMA_CH_CONFIG_MAX_BUFS 8 4462306a36Sopenharmony_ci#define TEGRA186_ADMA_CH_CONFIG_OUTSTANDING_REQS(reqs) (reqs << 4) 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_ci#define ADMA_CH_FIFO_CTRL 0x2c 4762306a36Sopenharmony_ci#define ADMA_CH_TX_FIFO_SIZE_SHIFT 8 4862306a36Sopenharmony_ci#define ADMA_CH_RX_FIFO_SIZE_SHIFT 0 4962306a36Sopenharmony_ci 5062306a36Sopenharmony_ci#define ADMA_CH_LOWER_SRC_ADDR 0x34 5162306a36Sopenharmony_ci#define ADMA_CH_LOWER_TRG_ADDR 0x3c 5262306a36Sopenharmony_ci#define ADMA_CH_TC 0x44 5362306a36Sopenharmony_ci#define ADMA_CH_TC_COUNT_MASK 0x3ffffffc 5462306a36Sopenharmony_ci 5562306a36Sopenharmony_ci#define ADMA_CH_XFER_STATUS 0x54 5662306a36Sopenharmony_ci#define ADMA_CH_XFER_STATUS_COUNT_MASK 0xffff 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_ci#define ADMA_GLOBAL_CMD 0x00 5962306a36Sopenharmony_ci#define ADMA_GLOBAL_SOFT_RESET 0x04 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_ci#define TEGRA_ADMA_BURST_COMPLETE_TIME 20 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_ci#define ADMA_CH_REG_FIELD_VAL(val, mask, shift) (((val) & mask) << shift) 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_cistruct tegra_adma; 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_ci/* 6862306a36Sopenharmony_ci * struct tegra_adma_chip_data - Tegra chip specific data 6962306a36Sopenharmony_ci * @adma_get_burst_config: Function callback used to set DMA burst size. 7062306a36Sopenharmony_ci * @global_reg_offset: Register offset of DMA global register. 7162306a36Sopenharmony_ci * @global_int_clear: Register offset of DMA global interrupt clear. 7262306a36Sopenharmony_ci * @ch_req_tx_shift: Register offset for AHUB transmit channel select. 7362306a36Sopenharmony_ci * @ch_req_rx_shift: Register offset for AHUB receive channel select. 7462306a36Sopenharmony_ci * @ch_base_offset: Register offset of DMA channel registers. 7562306a36Sopenharmony_ci * @ch_fifo_ctrl: Default value for channel FIFO CTRL register. 7662306a36Sopenharmony_ci * @ch_req_mask: Mask for Tx or Rx channel select. 7762306a36Sopenharmony_ci * @ch_req_max: Maximum number of Tx or Rx channels available. 7862306a36Sopenharmony_ci * @ch_reg_size: Size of DMA channel register space. 7962306a36Sopenharmony_ci * @nr_channels: Number of DMA channels available. 8062306a36Sopenharmony_ci * @ch_fifo_size_mask: Mask for FIFO size field. 8162306a36Sopenharmony_ci * @sreq_index_offset: Slave channel index offset. 8262306a36Sopenharmony_ci * @has_outstanding_reqs: If DMA channel can have outstanding requests. 8362306a36Sopenharmony_ci */ 8462306a36Sopenharmony_cistruct tegra_adma_chip_data { 8562306a36Sopenharmony_ci unsigned int (*adma_get_burst_config)(unsigned int burst_size); 8662306a36Sopenharmony_ci unsigned int global_reg_offset; 8762306a36Sopenharmony_ci unsigned int global_int_clear; 8862306a36Sopenharmony_ci unsigned int ch_req_tx_shift; 8962306a36Sopenharmony_ci unsigned int ch_req_rx_shift; 9062306a36Sopenharmony_ci unsigned int ch_base_offset; 9162306a36Sopenharmony_ci unsigned int ch_fifo_ctrl; 9262306a36Sopenharmony_ci unsigned int ch_req_mask; 9362306a36Sopenharmony_ci unsigned int ch_req_max; 9462306a36Sopenharmony_ci unsigned int ch_reg_size; 9562306a36Sopenharmony_ci unsigned int nr_channels; 9662306a36Sopenharmony_ci unsigned int ch_fifo_size_mask; 9762306a36Sopenharmony_ci unsigned int sreq_index_offset; 9862306a36Sopenharmony_ci bool has_outstanding_reqs; 9962306a36Sopenharmony_ci}; 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_ci/* 10262306a36Sopenharmony_ci * struct tegra_adma_chan_regs - Tegra ADMA channel registers 10362306a36Sopenharmony_ci */ 10462306a36Sopenharmony_cistruct tegra_adma_chan_regs { 10562306a36Sopenharmony_ci unsigned int ctrl; 10662306a36Sopenharmony_ci unsigned int config; 10762306a36Sopenharmony_ci unsigned int src_addr; 10862306a36Sopenharmony_ci unsigned int trg_addr; 10962306a36Sopenharmony_ci unsigned int fifo_ctrl; 11062306a36Sopenharmony_ci unsigned int cmd; 11162306a36Sopenharmony_ci unsigned int tc; 11262306a36Sopenharmony_ci}; 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_ci/* 11562306a36Sopenharmony_ci * struct tegra_adma_desc - Tegra ADMA descriptor to manage transfer requests. 11662306a36Sopenharmony_ci */ 11762306a36Sopenharmony_cistruct tegra_adma_desc { 11862306a36Sopenharmony_ci struct virt_dma_desc vd; 11962306a36Sopenharmony_ci struct tegra_adma_chan_regs ch_regs; 12062306a36Sopenharmony_ci size_t buf_len; 12162306a36Sopenharmony_ci size_t period_len; 12262306a36Sopenharmony_ci size_t num_periods; 12362306a36Sopenharmony_ci}; 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_ci/* 12662306a36Sopenharmony_ci * struct tegra_adma_chan - Tegra ADMA channel information 12762306a36Sopenharmony_ci */ 12862306a36Sopenharmony_cistruct tegra_adma_chan { 12962306a36Sopenharmony_ci struct virt_dma_chan vc; 13062306a36Sopenharmony_ci struct tegra_adma_desc *desc; 13162306a36Sopenharmony_ci struct tegra_adma *tdma; 13262306a36Sopenharmony_ci int irq; 13362306a36Sopenharmony_ci void __iomem *chan_addr; 13462306a36Sopenharmony_ci 13562306a36Sopenharmony_ci /* Slave channel configuration info */ 13662306a36Sopenharmony_ci struct dma_slave_config sconfig; 13762306a36Sopenharmony_ci enum dma_transfer_direction sreq_dir; 13862306a36Sopenharmony_ci unsigned int sreq_index; 13962306a36Sopenharmony_ci bool sreq_reserved; 14062306a36Sopenharmony_ci struct tegra_adma_chan_regs ch_regs; 14162306a36Sopenharmony_ci 14262306a36Sopenharmony_ci /* Transfer count and position info */ 14362306a36Sopenharmony_ci unsigned int tx_buf_count; 14462306a36Sopenharmony_ci unsigned int tx_buf_pos; 14562306a36Sopenharmony_ci}; 14662306a36Sopenharmony_ci 14762306a36Sopenharmony_ci/* 14862306a36Sopenharmony_ci * struct tegra_adma - Tegra ADMA controller information 14962306a36Sopenharmony_ci */ 15062306a36Sopenharmony_cistruct tegra_adma { 15162306a36Sopenharmony_ci struct dma_device dma_dev; 15262306a36Sopenharmony_ci struct device *dev; 15362306a36Sopenharmony_ci void __iomem *base_addr; 15462306a36Sopenharmony_ci struct clk *ahub_clk; 15562306a36Sopenharmony_ci unsigned int nr_channels; 15662306a36Sopenharmony_ci unsigned long rx_requests_reserved; 15762306a36Sopenharmony_ci unsigned long tx_requests_reserved; 15862306a36Sopenharmony_ci 15962306a36Sopenharmony_ci /* Used to store global command register state when suspending */ 16062306a36Sopenharmony_ci unsigned int global_cmd; 16162306a36Sopenharmony_ci 16262306a36Sopenharmony_ci const struct tegra_adma_chip_data *cdata; 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_ci /* Last member of the structure */ 16562306a36Sopenharmony_ci struct tegra_adma_chan channels[]; 16662306a36Sopenharmony_ci}; 16762306a36Sopenharmony_ci 16862306a36Sopenharmony_cistatic inline void tdma_write(struct tegra_adma *tdma, u32 reg, u32 val) 16962306a36Sopenharmony_ci{ 17062306a36Sopenharmony_ci writel(val, tdma->base_addr + tdma->cdata->global_reg_offset + reg); 17162306a36Sopenharmony_ci} 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_cistatic inline u32 tdma_read(struct tegra_adma *tdma, u32 reg) 17462306a36Sopenharmony_ci{ 17562306a36Sopenharmony_ci return readl(tdma->base_addr + tdma->cdata->global_reg_offset + reg); 17662306a36Sopenharmony_ci} 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_cistatic inline void tdma_ch_write(struct tegra_adma_chan *tdc, u32 reg, u32 val) 17962306a36Sopenharmony_ci{ 18062306a36Sopenharmony_ci writel(val, tdc->chan_addr + reg); 18162306a36Sopenharmony_ci} 18262306a36Sopenharmony_ci 18362306a36Sopenharmony_cistatic inline u32 tdma_ch_read(struct tegra_adma_chan *tdc, u32 reg) 18462306a36Sopenharmony_ci{ 18562306a36Sopenharmony_ci return readl(tdc->chan_addr + reg); 18662306a36Sopenharmony_ci} 18762306a36Sopenharmony_ci 18862306a36Sopenharmony_cistatic inline struct tegra_adma_chan *to_tegra_adma_chan(struct dma_chan *dc) 18962306a36Sopenharmony_ci{ 19062306a36Sopenharmony_ci return container_of(dc, struct tegra_adma_chan, vc.chan); 19162306a36Sopenharmony_ci} 19262306a36Sopenharmony_ci 19362306a36Sopenharmony_cistatic inline struct tegra_adma_desc *to_tegra_adma_desc( 19462306a36Sopenharmony_ci struct dma_async_tx_descriptor *td) 19562306a36Sopenharmony_ci{ 19662306a36Sopenharmony_ci return container_of(td, struct tegra_adma_desc, vd.tx); 19762306a36Sopenharmony_ci} 19862306a36Sopenharmony_ci 19962306a36Sopenharmony_cistatic inline struct device *tdc2dev(struct tegra_adma_chan *tdc) 20062306a36Sopenharmony_ci{ 20162306a36Sopenharmony_ci return tdc->tdma->dev; 20262306a36Sopenharmony_ci} 20362306a36Sopenharmony_ci 20462306a36Sopenharmony_cistatic void tegra_adma_desc_free(struct virt_dma_desc *vd) 20562306a36Sopenharmony_ci{ 20662306a36Sopenharmony_ci kfree(container_of(vd, struct tegra_adma_desc, vd)); 20762306a36Sopenharmony_ci} 20862306a36Sopenharmony_ci 20962306a36Sopenharmony_cistatic int tegra_adma_slave_config(struct dma_chan *dc, 21062306a36Sopenharmony_ci struct dma_slave_config *sconfig) 21162306a36Sopenharmony_ci{ 21262306a36Sopenharmony_ci struct tegra_adma_chan *tdc = to_tegra_adma_chan(dc); 21362306a36Sopenharmony_ci 21462306a36Sopenharmony_ci memcpy(&tdc->sconfig, sconfig, sizeof(*sconfig)); 21562306a36Sopenharmony_ci 21662306a36Sopenharmony_ci return 0; 21762306a36Sopenharmony_ci} 21862306a36Sopenharmony_ci 21962306a36Sopenharmony_cistatic int tegra_adma_init(struct tegra_adma *tdma) 22062306a36Sopenharmony_ci{ 22162306a36Sopenharmony_ci u32 status; 22262306a36Sopenharmony_ci int ret; 22362306a36Sopenharmony_ci 22462306a36Sopenharmony_ci /* Clear any interrupts */ 22562306a36Sopenharmony_ci tdma_write(tdma, tdma->cdata->ch_base_offset + tdma->cdata->global_int_clear, 0x1); 22662306a36Sopenharmony_ci 22762306a36Sopenharmony_ci /* Assert soft reset */ 22862306a36Sopenharmony_ci tdma_write(tdma, ADMA_GLOBAL_SOFT_RESET, 0x1); 22962306a36Sopenharmony_ci 23062306a36Sopenharmony_ci /* Wait for reset to clear */ 23162306a36Sopenharmony_ci ret = readx_poll_timeout(readl, 23262306a36Sopenharmony_ci tdma->base_addr + 23362306a36Sopenharmony_ci tdma->cdata->global_reg_offset + 23462306a36Sopenharmony_ci ADMA_GLOBAL_SOFT_RESET, 23562306a36Sopenharmony_ci status, status == 0, 20, 10000); 23662306a36Sopenharmony_ci if (ret) 23762306a36Sopenharmony_ci return ret; 23862306a36Sopenharmony_ci 23962306a36Sopenharmony_ci /* Enable global ADMA registers */ 24062306a36Sopenharmony_ci tdma_write(tdma, ADMA_GLOBAL_CMD, 1); 24162306a36Sopenharmony_ci 24262306a36Sopenharmony_ci return 0; 24362306a36Sopenharmony_ci} 24462306a36Sopenharmony_ci 24562306a36Sopenharmony_cistatic int tegra_adma_request_alloc(struct tegra_adma_chan *tdc, 24662306a36Sopenharmony_ci enum dma_transfer_direction direction) 24762306a36Sopenharmony_ci{ 24862306a36Sopenharmony_ci struct tegra_adma *tdma = tdc->tdma; 24962306a36Sopenharmony_ci unsigned int sreq_index = tdc->sreq_index; 25062306a36Sopenharmony_ci 25162306a36Sopenharmony_ci if (tdc->sreq_reserved) 25262306a36Sopenharmony_ci return tdc->sreq_dir == direction ? 0 : -EINVAL; 25362306a36Sopenharmony_ci 25462306a36Sopenharmony_ci if (sreq_index > tdma->cdata->ch_req_max) { 25562306a36Sopenharmony_ci dev_err(tdma->dev, "invalid DMA request\n"); 25662306a36Sopenharmony_ci return -EINVAL; 25762306a36Sopenharmony_ci } 25862306a36Sopenharmony_ci 25962306a36Sopenharmony_ci switch (direction) { 26062306a36Sopenharmony_ci case DMA_MEM_TO_DEV: 26162306a36Sopenharmony_ci if (test_and_set_bit(sreq_index, &tdma->tx_requests_reserved)) { 26262306a36Sopenharmony_ci dev_err(tdma->dev, "DMA request reserved\n"); 26362306a36Sopenharmony_ci return -EINVAL; 26462306a36Sopenharmony_ci } 26562306a36Sopenharmony_ci break; 26662306a36Sopenharmony_ci 26762306a36Sopenharmony_ci case DMA_DEV_TO_MEM: 26862306a36Sopenharmony_ci if (test_and_set_bit(sreq_index, &tdma->rx_requests_reserved)) { 26962306a36Sopenharmony_ci dev_err(tdma->dev, "DMA request reserved\n"); 27062306a36Sopenharmony_ci return -EINVAL; 27162306a36Sopenharmony_ci } 27262306a36Sopenharmony_ci break; 27362306a36Sopenharmony_ci 27462306a36Sopenharmony_ci default: 27562306a36Sopenharmony_ci dev_WARN(tdma->dev, "channel %s has invalid transfer type\n", 27662306a36Sopenharmony_ci dma_chan_name(&tdc->vc.chan)); 27762306a36Sopenharmony_ci return -EINVAL; 27862306a36Sopenharmony_ci } 27962306a36Sopenharmony_ci 28062306a36Sopenharmony_ci tdc->sreq_dir = direction; 28162306a36Sopenharmony_ci tdc->sreq_reserved = true; 28262306a36Sopenharmony_ci 28362306a36Sopenharmony_ci return 0; 28462306a36Sopenharmony_ci} 28562306a36Sopenharmony_ci 28662306a36Sopenharmony_cistatic void tegra_adma_request_free(struct tegra_adma_chan *tdc) 28762306a36Sopenharmony_ci{ 28862306a36Sopenharmony_ci struct tegra_adma *tdma = tdc->tdma; 28962306a36Sopenharmony_ci 29062306a36Sopenharmony_ci if (!tdc->sreq_reserved) 29162306a36Sopenharmony_ci return; 29262306a36Sopenharmony_ci 29362306a36Sopenharmony_ci switch (tdc->sreq_dir) { 29462306a36Sopenharmony_ci case DMA_MEM_TO_DEV: 29562306a36Sopenharmony_ci clear_bit(tdc->sreq_index, &tdma->tx_requests_reserved); 29662306a36Sopenharmony_ci break; 29762306a36Sopenharmony_ci 29862306a36Sopenharmony_ci case DMA_DEV_TO_MEM: 29962306a36Sopenharmony_ci clear_bit(tdc->sreq_index, &tdma->rx_requests_reserved); 30062306a36Sopenharmony_ci break; 30162306a36Sopenharmony_ci 30262306a36Sopenharmony_ci default: 30362306a36Sopenharmony_ci dev_WARN(tdma->dev, "channel %s has invalid transfer type\n", 30462306a36Sopenharmony_ci dma_chan_name(&tdc->vc.chan)); 30562306a36Sopenharmony_ci return; 30662306a36Sopenharmony_ci } 30762306a36Sopenharmony_ci 30862306a36Sopenharmony_ci tdc->sreq_reserved = false; 30962306a36Sopenharmony_ci} 31062306a36Sopenharmony_ci 31162306a36Sopenharmony_cistatic u32 tegra_adma_irq_status(struct tegra_adma_chan *tdc) 31262306a36Sopenharmony_ci{ 31362306a36Sopenharmony_ci u32 status = tdma_ch_read(tdc, ADMA_CH_INT_STATUS); 31462306a36Sopenharmony_ci 31562306a36Sopenharmony_ci return status & ADMA_CH_INT_STATUS_XFER_DONE; 31662306a36Sopenharmony_ci} 31762306a36Sopenharmony_ci 31862306a36Sopenharmony_cistatic u32 tegra_adma_irq_clear(struct tegra_adma_chan *tdc) 31962306a36Sopenharmony_ci{ 32062306a36Sopenharmony_ci u32 status = tegra_adma_irq_status(tdc); 32162306a36Sopenharmony_ci 32262306a36Sopenharmony_ci if (status) 32362306a36Sopenharmony_ci tdma_ch_write(tdc, ADMA_CH_INT_CLEAR, status); 32462306a36Sopenharmony_ci 32562306a36Sopenharmony_ci return status; 32662306a36Sopenharmony_ci} 32762306a36Sopenharmony_ci 32862306a36Sopenharmony_cistatic void tegra_adma_stop(struct tegra_adma_chan *tdc) 32962306a36Sopenharmony_ci{ 33062306a36Sopenharmony_ci unsigned int status; 33162306a36Sopenharmony_ci 33262306a36Sopenharmony_ci /* Disable ADMA */ 33362306a36Sopenharmony_ci tdma_ch_write(tdc, ADMA_CH_CMD, 0); 33462306a36Sopenharmony_ci 33562306a36Sopenharmony_ci /* Clear interrupt status */ 33662306a36Sopenharmony_ci tegra_adma_irq_clear(tdc); 33762306a36Sopenharmony_ci 33862306a36Sopenharmony_ci if (readx_poll_timeout_atomic(readl, tdc->chan_addr + ADMA_CH_STATUS, 33962306a36Sopenharmony_ci status, !(status & ADMA_CH_STATUS_XFER_EN), 34062306a36Sopenharmony_ci 20, 10000)) { 34162306a36Sopenharmony_ci dev_err(tdc2dev(tdc), "unable to stop DMA channel\n"); 34262306a36Sopenharmony_ci return; 34362306a36Sopenharmony_ci } 34462306a36Sopenharmony_ci 34562306a36Sopenharmony_ci kfree(tdc->desc); 34662306a36Sopenharmony_ci tdc->desc = NULL; 34762306a36Sopenharmony_ci} 34862306a36Sopenharmony_ci 34962306a36Sopenharmony_cistatic void tegra_adma_start(struct tegra_adma_chan *tdc) 35062306a36Sopenharmony_ci{ 35162306a36Sopenharmony_ci struct virt_dma_desc *vd = vchan_next_desc(&tdc->vc); 35262306a36Sopenharmony_ci struct tegra_adma_chan_regs *ch_regs; 35362306a36Sopenharmony_ci struct tegra_adma_desc *desc; 35462306a36Sopenharmony_ci 35562306a36Sopenharmony_ci if (!vd) 35662306a36Sopenharmony_ci return; 35762306a36Sopenharmony_ci 35862306a36Sopenharmony_ci list_del(&vd->node); 35962306a36Sopenharmony_ci 36062306a36Sopenharmony_ci desc = to_tegra_adma_desc(&vd->tx); 36162306a36Sopenharmony_ci 36262306a36Sopenharmony_ci if (!desc) { 36362306a36Sopenharmony_ci dev_warn(tdc2dev(tdc), "unable to start DMA, no descriptor\n"); 36462306a36Sopenharmony_ci return; 36562306a36Sopenharmony_ci } 36662306a36Sopenharmony_ci 36762306a36Sopenharmony_ci ch_regs = &desc->ch_regs; 36862306a36Sopenharmony_ci 36962306a36Sopenharmony_ci tdc->tx_buf_pos = 0; 37062306a36Sopenharmony_ci tdc->tx_buf_count = 0; 37162306a36Sopenharmony_ci tdma_ch_write(tdc, ADMA_CH_TC, ch_regs->tc); 37262306a36Sopenharmony_ci tdma_ch_write(tdc, ADMA_CH_CTRL, ch_regs->ctrl); 37362306a36Sopenharmony_ci tdma_ch_write(tdc, ADMA_CH_LOWER_SRC_ADDR, ch_regs->src_addr); 37462306a36Sopenharmony_ci tdma_ch_write(tdc, ADMA_CH_LOWER_TRG_ADDR, ch_regs->trg_addr); 37562306a36Sopenharmony_ci tdma_ch_write(tdc, ADMA_CH_FIFO_CTRL, ch_regs->fifo_ctrl); 37662306a36Sopenharmony_ci tdma_ch_write(tdc, ADMA_CH_CONFIG, ch_regs->config); 37762306a36Sopenharmony_ci 37862306a36Sopenharmony_ci /* Start ADMA */ 37962306a36Sopenharmony_ci tdma_ch_write(tdc, ADMA_CH_CMD, 1); 38062306a36Sopenharmony_ci 38162306a36Sopenharmony_ci tdc->desc = desc; 38262306a36Sopenharmony_ci} 38362306a36Sopenharmony_ci 38462306a36Sopenharmony_cistatic unsigned int tegra_adma_get_residue(struct tegra_adma_chan *tdc) 38562306a36Sopenharmony_ci{ 38662306a36Sopenharmony_ci struct tegra_adma_desc *desc = tdc->desc; 38762306a36Sopenharmony_ci unsigned int max = ADMA_CH_XFER_STATUS_COUNT_MASK + 1; 38862306a36Sopenharmony_ci unsigned int pos = tdma_ch_read(tdc, ADMA_CH_XFER_STATUS); 38962306a36Sopenharmony_ci unsigned int periods_remaining; 39062306a36Sopenharmony_ci 39162306a36Sopenharmony_ci /* 39262306a36Sopenharmony_ci * Handle wrap around of buffer count register 39362306a36Sopenharmony_ci */ 39462306a36Sopenharmony_ci if (pos < tdc->tx_buf_pos) 39562306a36Sopenharmony_ci tdc->tx_buf_count += pos + (max - tdc->tx_buf_pos); 39662306a36Sopenharmony_ci else 39762306a36Sopenharmony_ci tdc->tx_buf_count += pos - tdc->tx_buf_pos; 39862306a36Sopenharmony_ci 39962306a36Sopenharmony_ci periods_remaining = tdc->tx_buf_count % desc->num_periods; 40062306a36Sopenharmony_ci tdc->tx_buf_pos = pos; 40162306a36Sopenharmony_ci 40262306a36Sopenharmony_ci return desc->buf_len - (periods_remaining * desc->period_len); 40362306a36Sopenharmony_ci} 40462306a36Sopenharmony_ci 40562306a36Sopenharmony_cistatic irqreturn_t tegra_adma_isr(int irq, void *dev_id) 40662306a36Sopenharmony_ci{ 40762306a36Sopenharmony_ci struct tegra_adma_chan *tdc = dev_id; 40862306a36Sopenharmony_ci unsigned long status; 40962306a36Sopenharmony_ci 41062306a36Sopenharmony_ci spin_lock(&tdc->vc.lock); 41162306a36Sopenharmony_ci 41262306a36Sopenharmony_ci status = tegra_adma_irq_clear(tdc); 41362306a36Sopenharmony_ci if (status == 0 || !tdc->desc) { 41462306a36Sopenharmony_ci spin_unlock(&tdc->vc.lock); 41562306a36Sopenharmony_ci return IRQ_NONE; 41662306a36Sopenharmony_ci } 41762306a36Sopenharmony_ci 41862306a36Sopenharmony_ci vchan_cyclic_callback(&tdc->desc->vd); 41962306a36Sopenharmony_ci 42062306a36Sopenharmony_ci spin_unlock(&tdc->vc.lock); 42162306a36Sopenharmony_ci 42262306a36Sopenharmony_ci return IRQ_HANDLED; 42362306a36Sopenharmony_ci} 42462306a36Sopenharmony_ci 42562306a36Sopenharmony_cistatic void tegra_adma_issue_pending(struct dma_chan *dc) 42662306a36Sopenharmony_ci{ 42762306a36Sopenharmony_ci struct tegra_adma_chan *tdc = to_tegra_adma_chan(dc); 42862306a36Sopenharmony_ci unsigned long flags; 42962306a36Sopenharmony_ci 43062306a36Sopenharmony_ci spin_lock_irqsave(&tdc->vc.lock, flags); 43162306a36Sopenharmony_ci 43262306a36Sopenharmony_ci if (vchan_issue_pending(&tdc->vc)) { 43362306a36Sopenharmony_ci if (!tdc->desc) 43462306a36Sopenharmony_ci tegra_adma_start(tdc); 43562306a36Sopenharmony_ci } 43662306a36Sopenharmony_ci 43762306a36Sopenharmony_ci spin_unlock_irqrestore(&tdc->vc.lock, flags); 43862306a36Sopenharmony_ci} 43962306a36Sopenharmony_ci 44062306a36Sopenharmony_cistatic bool tegra_adma_is_paused(struct tegra_adma_chan *tdc) 44162306a36Sopenharmony_ci{ 44262306a36Sopenharmony_ci u32 csts; 44362306a36Sopenharmony_ci 44462306a36Sopenharmony_ci csts = tdma_ch_read(tdc, ADMA_CH_STATUS); 44562306a36Sopenharmony_ci csts &= ADMA_CH_STATUS_XFER_PAUSED; 44662306a36Sopenharmony_ci 44762306a36Sopenharmony_ci return csts ? true : false; 44862306a36Sopenharmony_ci} 44962306a36Sopenharmony_ci 45062306a36Sopenharmony_cistatic int tegra_adma_pause(struct dma_chan *dc) 45162306a36Sopenharmony_ci{ 45262306a36Sopenharmony_ci struct tegra_adma_chan *tdc = to_tegra_adma_chan(dc); 45362306a36Sopenharmony_ci struct tegra_adma_desc *desc = tdc->desc; 45462306a36Sopenharmony_ci struct tegra_adma_chan_regs *ch_regs = &desc->ch_regs; 45562306a36Sopenharmony_ci int dcnt = 10; 45662306a36Sopenharmony_ci 45762306a36Sopenharmony_ci ch_regs->ctrl = tdma_ch_read(tdc, ADMA_CH_CTRL); 45862306a36Sopenharmony_ci ch_regs->ctrl |= (1 << ADMA_CH_CTRL_XFER_PAUSE_SHIFT); 45962306a36Sopenharmony_ci tdma_ch_write(tdc, ADMA_CH_CTRL, ch_regs->ctrl); 46062306a36Sopenharmony_ci 46162306a36Sopenharmony_ci while (dcnt-- && !tegra_adma_is_paused(tdc)) 46262306a36Sopenharmony_ci udelay(TEGRA_ADMA_BURST_COMPLETE_TIME); 46362306a36Sopenharmony_ci 46462306a36Sopenharmony_ci if (dcnt < 0) { 46562306a36Sopenharmony_ci dev_err(tdc2dev(tdc), "unable to pause DMA channel\n"); 46662306a36Sopenharmony_ci return -EBUSY; 46762306a36Sopenharmony_ci } 46862306a36Sopenharmony_ci 46962306a36Sopenharmony_ci return 0; 47062306a36Sopenharmony_ci} 47162306a36Sopenharmony_ci 47262306a36Sopenharmony_cistatic int tegra_adma_resume(struct dma_chan *dc) 47362306a36Sopenharmony_ci{ 47462306a36Sopenharmony_ci struct tegra_adma_chan *tdc = to_tegra_adma_chan(dc); 47562306a36Sopenharmony_ci struct tegra_adma_desc *desc = tdc->desc; 47662306a36Sopenharmony_ci struct tegra_adma_chan_regs *ch_regs = &desc->ch_regs; 47762306a36Sopenharmony_ci 47862306a36Sopenharmony_ci ch_regs->ctrl = tdma_ch_read(tdc, ADMA_CH_CTRL); 47962306a36Sopenharmony_ci ch_regs->ctrl &= ~(1 << ADMA_CH_CTRL_XFER_PAUSE_SHIFT); 48062306a36Sopenharmony_ci tdma_ch_write(tdc, ADMA_CH_CTRL, ch_regs->ctrl); 48162306a36Sopenharmony_ci 48262306a36Sopenharmony_ci return 0; 48362306a36Sopenharmony_ci} 48462306a36Sopenharmony_ci 48562306a36Sopenharmony_cistatic int tegra_adma_terminate_all(struct dma_chan *dc) 48662306a36Sopenharmony_ci{ 48762306a36Sopenharmony_ci struct tegra_adma_chan *tdc = to_tegra_adma_chan(dc); 48862306a36Sopenharmony_ci unsigned long flags; 48962306a36Sopenharmony_ci LIST_HEAD(head); 49062306a36Sopenharmony_ci 49162306a36Sopenharmony_ci spin_lock_irqsave(&tdc->vc.lock, flags); 49262306a36Sopenharmony_ci 49362306a36Sopenharmony_ci if (tdc->desc) 49462306a36Sopenharmony_ci tegra_adma_stop(tdc); 49562306a36Sopenharmony_ci 49662306a36Sopenharmony_ci tegra_adma_request_free(tdc); 49762306a36Sopenharmony_ci vchan_get_all_descriptors(&tdc->vc, &head); 49862306a36Sopenharmony_ci spin_unlock_irqrestore(&tdc->vc.lock, flags); 49962306a36Sopenharmony_ci vchan_dma_desc_free_list(&tdc->vc, &head); 50062306a36Sopenharmony_ci 50162306a36Sopenharmony_ci return 0; 50262306a36Sopenharmony_ci} 50362306a36Sopenharmony_ci 50462306a36Sopenharmony_cistatic enum dma_status tegra_adma_tx_status(struct dma_chan *dc, 50562306a36Sopenharmony_ci dma_cookie_t cookie, 50662306a36Sopenharmony_ci struct dma_tx_state *txstate) 50762306a36Sopenharmony_ci{ 50862306a36Sopenharmony_ci struct tegra_adma_chan *tdc = to_tegra_adma_chan(dc); 50962306a36Sopenharmony_ci struct tegra_adma_desc *desc; 51062306a36Sopenharmony_ci struct virt_dma_desc *vd; 51162306a36Sopenharmony_ci enum dma_status ret; 51262306a36Sopenharmony_ci unsigned long flags; 51362306a36Sopenharmony_ci unsigned int residual; 51462306a36Sopenharmony_ci 51562306a36Sopenharmony_ci ret = dma_cookie_status(dc, cookie, txstate); 51662306a36Sopenharmony_ci if (ret == DMA_COMPLETE || !txstate) 51762306a36Sopenharmony_ci return ret; 51862306a36Sopenharmony_ci 51962306a36Sopenharmony_ci spin_lock_irqsave(&tdc->vc.lock, flags); 52062306a36Sopenharmony_ci 52162306a36Sopenharmony_ci vd = vchan_find_desc(&tdc->vc, cookie); 52262306a36Sopenharmony_ci if (vd) { 52362306a36Sopenharmony_ci desc = to_tegra_adma_desc(&vd->tx); 52462306a36Sopenharmony_ci residual = desc->ch_regs.tc; 52562306a36Sopenharmony_ci } else if (tdc->desc && tdc->desc->vd.tx.cookie == cookie) { 52662306a36Sopenharmony_ci residual = tegra_adma_get_residue(tdc); 52762306a36Sopenharmony_ci } else { 52862306a36Sopenharmony_ci residual = 0; 52962306a36Sopenharmony_ci } 53062306a36Sopenharmony_ci 53162306a36Sopenharmony_ci spin_unlock_irqrestore(&tdc->vc.lock, flags); 53262306a36Sopenharmony_ci 53362306a36Sopenharmony_ci dma_set_residue(txstate, residual); 53462306a36Sopenharmony_ci 53562306a36Sopenharmony_ci return ret; 53662306a36Sopenharmony_ci} 53762306a36Sopenharmony_ci 53862306a36Sopenharmony_cistatic unsigned int tegra210_adma_get_burst_config(unsigned int burst_size) 53962306a36Sopenharmony_ci{ 54062306a36Sopenharmony_ci if (!burst_size || burst_size > ADMA_CH_CONFIG_MAX_BURST_SIZE) 54162306a36Sopenharmony_ci burst_size = ADMA_CH_CONFIG_MAX_BURST_SIZE; 54262306a36Sopenharmony_ci 54362306a36Sopenharmony_ci return fls(burst_size) << ADMA_CH_CONFIG_BURST_SIZE_SHIFT; 54462306a36Sopenharmony_ci} 54562306a36Sopenharmony_ci 54662306a36Sopenharmony_cistatic unsigned int tegra186_adma_get_burst_config(unsigned int burst_size) 54762306a36Sopenharmony_ci{ 54862306a36Sopenharmony_ci if (!burst_size || burst_size > ADMA_CH_CONFIG_MAX_BURST_SIZE) 54962306a36Sopenharmony_ci burst_size = ADMA_CH_CONFIG_MAX_BURST_SIZE; 55062306a36Sopenharmony_ci 55162306a36Sopenharmony_ci return (burst_size - 1) << ADMA_CH_CONFIG_BURST_SIZE_SHIFT; 55262306a36Sopenharmony_ci} 55362306a36Sopenharmony_ci 55462306a36Sopenharmony_cistatic int tegra_adma_set_xfer_params(struct tegra_adma_chan *tdc, 55562306a36Sopenharmony_ci struct tegra_adma_desc *desc, 55662306a36Sopenharmony_ci dma_addr_t buf_addr, 55762306a36Sopenharmony_ci enum dma_transfer_direction direction) 55862306a36Sopenharmony_ci{ 55962306a36Sopenharmony_ci struct tegra_adma_chan_regs *ch_regs = &desc->ch_regs; 56062306a36Sopenharmony_ci const struct tegra_adma_chip_data *cdata = tdc->tdma->cdata; 56162306a36Sopenharmony_ci unsigned int burst_size, adma_dir, fifo_size_shift; 56262306a36Sopenharmony_ci 56362306a36Sopenharmony_ci if (desc->num_periods > ADMA_CH_CONFIG_MAX_BUFS) 56462306a36Sopenharmony_ci return -EINVAL; 56562306a36Sopenharmony_ci 56662306a36Sopenharmony_ci switch (direction) { 56762306a36Sopenharmony_ci case DMA_MEM_TO_DEV: 56862306a36Sopenharmony_ci fifo_size_shift = ADMA_CH_TX_FIFO_SIZE_SHIFT; 56962306a36Sopenharmony_ci adma_dir = ADMA_CH_CTRL_DIR_MEM2AHUB; 57062306a36Sopenharmony_ci burst_size = tdc->sconfig.dst_maxburst; 57162306a36Sopenharmony_ci ch_regs->config = ADMA_CH_CONFIG_SRC_BUF(desc->num_periods - 1); 57262306a36Sopenharmony_ci ch_regs->ctrl = ADMA_CH_REG_FIELD_VAL(tdc->sreq_index, 57362306a36Sopenharmony_ci cdata->ch_req_mask, 57462306a36Sopenharmony_ci cdata->ch_req_tx_shift); 57562306a36Sopenharmony_ci ch_regs->src_addr = buf_addr; 57662306a36Sopenharmony_ci break; 57762306a36Sopenharmony_ci 57862306a36Sopenharmony_ci case DMA_DEV_TO_MEM: 57962306a36Sopenharmony_ci fifo_size_shift = ADMA_CH_RX_FIFO_SIZE_SHIFT; 58062306a36Sopenharmony_ci adma_dir = ADMA_CH_CTRL_DIR_AHUB2MEM; 58162306a36Sopenharmony_ci burst_size = tdc->sconfig.src_maxburst; 58262306a36Sopenharmony_ci ch_regs->config = ADMA_CH_CONFIG_TRG_BUF(desc->num_periods - 1); 58362306a36Sopenharmony_ci ch_regs->ctrl = ADMA_CH_REG_FIELD_VAL(tdc->sreq_index, 58462306a36Sopenharmony_ci cdata->ch_req_mask, 58562306a36Sopenharmony_ci cdata->ch_req_rx_shift); 58662306a36Sopenharmony_ci ch_regs->trg_addr = buf_addr; 58762306a36Sopenharmony_ci break; 58862306a36Sopenharmony_ci 58962306a36Sopenharmony_ci default: 59062306a36Sopenharmony_ci dev_err(tdc2dev(tdc), "DMA direction is not supported\n"); 59162306a36Sopenharmony_ci return -EINVAL; 59262306a36Sopenharmony_ci } 59362306a36Sopenharmony_ci 59462306a36Sopenharmony_ci ch_regs->ctrl |= ADMA_CH_CTRL_DIR(adma_dir) | 59562306a36Sopenharmony_ci ADMA_CH_CTRL_MODE_CONTINUOUS | 59662306a36Sopenharmony_ci ADMA_CH_CTRL_FLOWCTRL_EN; 59762306a36Sopenharmony_ci ch_regs->config |= cdata->adma_get_burst_config(burst_size); 59862306a36Sopenharmony_ci ch_regs->config |= ADMA_CH_CONFIG_WEIGHT_FOR_WRR(1); 59962306a36Sopenharmony_ci if (cdata->has_outstanding_reqs) 60062306a36Sopenharmony_ci ch_regs->config |= TEGRA186_ADMA_CH_CONFIG_OUTSTANDING_REQS(8); 60162306a36Sopenharmony_ci 60262306a36Sopenharmony_ci /* 60362306a36Sopenharmony_ci * 'sreq_index' represents the current ADMAIF channel number and as per 60462306a36Sopenharmony_ci * HW recommendation its FIFO size should match with the corresponding 60562306a36Sopenharmony_ci * ADMA channel. 60662306a36Sopenharmony_ci * 60762306a36Sopenharmony_ci * ADMA FIFO size is set as per below (based on default ADMAIF channel 60862306a36Sopenharmony_ci * FIFO sizes): 60962306a36Sopenharmony_ci * fifo_size = 0x2 (sreq_index > sreq_index_offset) 61062306a36Sopenharmony_ci * fifo_size = 0x3 (sreq_index <= sreq_index_offset) 61162306a36Sopenharmony_ci * 61262306a36Sopenharmony_ci */ 61362306a36Sopenharmony_ci if (tdc->sreq_index > cdata->sreq_index_offset) 61462306a36Sopenharmony_ci ch_regs->fifo_ctrl = 61562306a36Sopenharmony_ci ADMA_CH_REG_FIELD_VAL(2, cdata->ch_fifo_size_mask, 61662306a36Sopenharmony_ci fifo_size_shift); 61762306a36Sopenharmony_ci else 61862306a36Sopenharmony_ci ch_regs->fifo_ctrl = 61962306a36Sopenharmony_ci ADMA_CH_REG_FIELD_VAL(3, cdata->ch_fifo_size_mask, 62062306a36Sopenharmony_ci fifo_size_shift); 62162306a36Sopenharmony_ci 62262306a36Sopenharmony_ci ch_regs->tc = desc->period_len & ADMA_CH_TC_COUNT_MASK; 62362306a36Sopenharmony_ci 62462306a36Sopenharmony_ci return tegra_adma_request_alloc(tdc, direction); 62562306a36Sopenharmony_ci} 62662306a36Sopenharmony_ci 62762306a36Sopenharmony_cistatic struct dma_async_tx_descriptor *tegra_adma_prep_dma_cyclic( 62862306a36Sopenharmony_ci struct dma_chan *dc, dma_addr_t buf_addr, size_t buf_len, 62962306a36Sopenharmony_ci size_t period_len, enum dma_transfer_direction direction, 63062306a36Sopenharmony_ci unsigned long flags) 63162306a36Sopenharmony_ci{ 63262306a36Sopenharmony_ci struct tegra_adma_chan *tdc = to_tegra_adma_chan(dc); 63362306a36Sopenharmony_ci struct tegra_adma_desc *desc = NULL; 63462306a36Sopenharmony_ci 63562306a36Sopenharmony_ci if (!buf_len || !period_len || period_len > ADMA_CH_TC_COUNT_MASK) { 63662306a36Sopenharmony_ci dev_err(tdc2dev(tdc), "invalid buffer/period len\n"); 63762306a36Sopenharmony_ci return NULL; 63862306a36Sopenharmony_ci } 63962306a36Sopenharmony_ci 64062306a36Sopenharmony_ci if (buf_len % period_len) { 64162306a36Sopenharmony_ci dev_err(tdc2dev(tdc), "buf_len not a multiple of period_len\n"); 64262306a36Sopenharmony_ci return NULL; 64362306a36Sopenharmony_ci } 64462306a36Sopenharmony_ci 64562306a36Sopenharmony_ci if (!IS_ALIGNED(buf_addr, 4)) { 64662306a36Sopenharmony_ci dev_err(tdc2dev(tdc), "invalid buffer alignment\n"); 64762306a36Sopenharmony_ci return NULL; 64862306a36Sopenharmony_ci } 64962306a36Sopenharmony_ci 65062306a36Sopenharmony_ci desc = kzalloc(sizeof(*desc), GFP_NOWAIT); 65162306a36Sopenharmony_ci if (!desc) 65262306a36Sopenharmony_ci return NULL; 65362306a36Sopenharmony_ci 65462306a36Sopenharmony_ci desc->buf_len = buf_len; 65562306a36Sopenharmony_ci desc->period_len = period_len; 65662306a36Sopenharmony_ci desc->num_periods = buf_len / period_len; 65762306a36Sopenharmony_ci 65862306a36Sopenharmony_ci if (tegra_adma_set_xfer_params(tdc, desc, buf_addr, direction)) { 65962306a36Sopenharmony_ci kfree(desc); 66062306a36Sopenharmony_ci return NULL; 66162306a36Sopenharmony_ci } 66262306a36Sopenharmony_ci 66362306a36Sopenharmony_ci return vchan_tx_prep(&tdc->vc, &desc->vd, flags); 66462306a36Sopenharmony_ci} 66562306a36Sopenharmony_ci 66662306a36Sopenharmony_cistatic int tegra_adma_alloc_chan_resources(struct dma_chan *dc) 66762306a36Sopenharmony_ci{ 66862306a36Sopenharmony_ci struct tegra_adma_chan *tdc = to_tegra_adma_chan(dc); 66962306a36Sopenharmony_ci int ret; 67062306a36Sopenharmony_ci 67162306a36Sopenharmony_ci ret = request_irq(tdc->irq, tegra_adma_isr, 0, dma_chan_name(dc), tdc); 67262306a36Sopenharmony_ci if (ret) { 67362306a36Sopenharmony_ci dev_err(tdc2dev(tdc), "failed to get interrupt for %s\n", 67462306a36Sopenharmony_ci dma_chan_name(dc)); 67562306a36Sopenharmony_ci return ret; 67662306a36Sopenharmony_ci } 67762306a36Sopenharmony_ci 67862306a36Sopenharmony_ci ret = pm_runtime_resume_and_get(tdc2dev(tdc)); 67962306a36Sopenharmony_ci if (ret < 0) { 68062306a36Sopenharmony_ci free_irq(tdc->irq, tdc); 68162306a36Sopenharmony_ci return ret; 68262306a36Sopenharmony_ci } 68362306a36Sopenharmony_ci 68462306a36Sopenharmony_ci dma_cookie_init(&tdc->vc.chan); 68562306a36Sopenharmony_ci 68662306a36Sopenharmony_ci return 0; 68762306a36Sopenharmony_ci} 68862306a36Sopenharmony_ci 68962306a36Sopenharmony_cistatic void tegra_adma_free_chan_resources(struct dma_chan *dc) 69062306a36Sopenharmony_ci{ 69162306a36Sopenharmony_ci struct tegra_adma_chan *tdc = to_tegra_adma_chan(dc); 69262306a36Sopenharmony_ci 69362306a36Sopenharmony_ci tegra_adma_terminate_all(dc); 69462306a36Sopenharmony_ci vchan_free_chan_resources(&tdc->vc); 69562306a36Sopenharmony_ci tasklet_kill(&tdc->vc.task); 69662306a36Sopenharmony_ci free_irq(tdc->irq, tdc); 69762306a36Sopenharmony_ci pm_runtime_put(tdc2dev(tdc)); 69862306a36Sopenharmony_ci 69962306a36Sopenharmony_ci tdc->sreq_index = 0; 70062306a36Sopenharmony_ci tdc->sreq_dir = DMA_TRANS_NONE; 70162306a36Sopenharmony_ci} 70262306a36Sopenharmony_ci 70362306a36Sopenharmony_cistatic struct dma_chan *tegra_dma_of_xlate(struct of_phandle_args *dma_spec, 70462306a36Sopenharmony_ci struct of_dma *ofdma) 70562306a36Sopenharmony_ci{ 70662306a36Sopenharmony_ci struct tegra_adma *tdma = ofdma->of_dma_data; 70762306a36Sopenharmony_ci struct tegra_adma_chan *tdc; 70862306a36Sopenharmony_ci struct dma_chan *chan; 70962306a36Sopenharmony_ci unsigned int sreq_index; 71062306a36Sopenharmony_ci 71162306a36Sopenharmony_ci if (dma_spec->args_count != 1) 71262306a36Sopenharmony_ci return NULL; 71362306a36Sopenharmony_ci 71462306a36Sopenharmony_ci sreq_index = dma_spec->args[0]; 71562306a36Sopenharmony_ci 71662306a36Sopenharmony_ci if (sreq_index == 0) { 71762306a36Sopenharmony_ci dev_err(tdma->dev, "DMA request must not be 0\n"); 71862306a36Sopenharmony_ci return NULL; 71962306a36Sopenharmony_ci } 72062306a36Sopenharmony_ci 72162306a36Sopenharmony_ci chan = dma_get_any_slave_channel(&tdma->dma_dev); 72262306a36Sopenharmony_ci if (!chan) 72362306a36Sopenharmony_ci return NULL; 72462306a36Sopenharmony_ci 72562306a36Sopenharmony_ci tdc = to_tegra_adma_chan(chan); 72662306a36Sopenharmony_ci tdc->sreq_index = sreq_index; 72762306a36Sopenharmony_ci 72862306a36Sopenharmony_ci return chan; 72962306a36Sopenharmony_ci} 73062306a36Sopenharmony_ci 73162306a36Sopenharmony_cistatic int __maybe_unused tegra_adma_runtime_suspend(struct device *dev) 73262306a36Sopenharmony_ci{ 73362306a36Sopenharmony_ci struct tegra_adma *tdma = dev_get_drvdata(dev); 73462306a36Sopenharmony_ci struct tegra_adma_chan_regs *ch_reg; 73562306a36Sopenharmony_ci struct tegra_adma_chan *tdc; 73662306a36Sopenharmony_ci int i; 73762306a36Sopenharmony_ci 73862306a36Sopenharmony_ci tdma->global_cmd = tdma_read(tdma, ADMA_GLOBAL_CMD); 73962306a36Sopenharmony_ci if (!tdma->global_cmd) 74062306a36Sopenharmony_ci goto clk_disable; 74162306a36Sopenharmony_ci 74262306a36Sopenharmony_ci for (i = 0; i < tdma->nr_channels; i++) { 74362306a36Sopenharmony_ci tdc = &tdma->channels[i]; 74462306a36Sopenharmony_ci ch_reg = &tdc->ch_regs; 74562306a36Sopenharmony_ci ch_reg->cmd = tdma_ch_read(tdc, ADMA_CH_CMD); 74662306a36Sopenharmony_ci /* skip if channel is not active */ 74762306a36Sopenharmony_ci if (!ch_reg->cmd) 74862306a36Sopenharmony_ci continue; 74962306a36Sopenharmony_ci ch_reg->tc = tdma_ch_read(tdc, ADMA_CH_TC); 75062306a36Sopenharmony_ci ch_reg->src_addr = tdma_ch_read(tdc, ADMA_CH_LOWER_SRC_ADDR); 75162306a36Sopenharmony_ci ch_reg->trg_addr = tdma_ch_read(tdc, ADMA_CH_LOWER_TRG_ADDR); 75262306a36Sopenharmony_ci ch_reg->ctrl = tdma_ch_read(tdc, ADMA_CH_CTRL); 75362306a36Sopenharmony_ci ch_reg->fifo_ctrl = tdma_ch_read(tdc, ADMA_CH_FIFO_CTRL); 75462306a36Sopenharmony_ci ch_reg->config = tdma_ch_read(tdc, ADMA_CH_CONFIG); 75562306a36Sopenharmony_ci } 75662306a36Sopenharmony_ci 75762306a36Sopenharmony_ciclk_disable: 75862306a36Sopenharmony_ci clk_disable_unprepare(tdma->ahub_clk); 75962306a36Sopenharmony_ci 76062306a36Sopenharmony_ci return 0; 76162306a36Sopenharmony_ci} 76262306a36Sopenharmony_ci 76362306a36Sopenharmony_cistatic int __maybe_unused tegra_adma_runtime_resume(struct device *dev) 76462306a36Sopenharmony_ci{ 76562306a36Sopenharmony_ci struct tegra_adma *tdma = dev_get_drvdata(dev); 76662306a36Sopenharmony_ci struct tegra_adma_chan_regs *ch_reg; 76762306a36Sopenharmony_ci struct tegra_adma_chan *tdc; 76862306a36Sopenharmony_ci int ret, i; 76962306a36Sopenharmony_ci 77062306a36Sopenharmony_ci ret = clk_prepare_enable(tdma->ahub_clk); 77162306a36Sopenharmony_ci if (ret) { 77262306a36Sopenharmony_ci dev_err(dev, "ahub clk_enable failed: %d\n", ret); 77362306a36Sopenharmony_ci return ret; 77462306a36Sopenharmony_ci } 77562306a36Sopenharmony_ci tdma_write(tdma, ADMA_GLOBAL_CMD, tdma->global_cmd); 77662306a36Sopenharmony_ci 77762306a36Sopenharmony_ci if (!tdma->global_cmd) 77862306a36Sopenharmony_ci return 0; 77962306a36Sopenharmony_ci 78062306a36Sopenharmony_ci for (i = 0; i < tdma->nr_channels; i++) { 78162306a36Sopenharmony_ci tdc = &tdma->channels[i]; 78262306a36Sopenharmony_ci ch_reg = &tdc->ch_regs; 78362306a36Sopenharmony_ci /* skip if channel was not active earlier */ 78462306a36Sopenharmony_ci if (!ch_reg->cmd) 78562306a36Sopenharmony_ci continue; 78662306a36Sopenharmony_ci tdma_ch_write(tdc, ADMA_CH_TC, ch_reg->tc); 78762306a36Sopenharmony_ci tdma_ch_write(tdc, ADMA_CH_LOWER_SRC_ADDR, ch_reg->src_addr); 78862306a36Sopenharmony_ci tdma_ch_write(tdc, ADMA_CH_LOWER_TRG_ADDR, ch_reg->trg_addr); 78962306a36Sopenharmony_ci tdma_ch_write(tdc, ADMA_CH_CTRL, ch_reg->ctrl); 79062306a36Sopenharmony_ci tdma_ch_write(tdc, ADMA_CH_FIFO_CTRL, ch_reg->fifo_ctrl); 79162306a36Sopenharmony_ci tdma_ch_write(tdc, ADMA_CH_CONFIG, ch_reg->config); 79262306a36Sopenharmony_ci tdma_ch_write(tdc, ADMA_CH_CMD, ch_reg->cmd); 79362306a36Sopenharmony_ci } 79462306a36Sopenharmony_ci 79562306a36Sopenharmony_ci return 0; 79662306a36Sopenharmony_ci} 79762306a36Sopenharmony_ci 79862306a36Sopenharmony_cistatic const struct tegra_adma_chip_data tegra210_chip_data = { 79962306a36Sopenharmony_ci .adma_get_burst_config = tegra210_adma_get_burst_config, 80062306a36Sopenharmony_ci .global_reg_offset = 0xc00, 80162306a36Sopenharmony_ci .global_int_clear = 0x20, 80262306a36Sopenharmony_ci .ch_req_tx_shift = 28, 80362306a36Sopenharmony_ci .ch_req_rx_shift = 24, 80462306a36Sopenharmony_ci .ch_base_offset = 0, 80562306a36Sopenharmony_ci .ch_req_mask = 0xf, 80662306a36Sopenharmony_ci .ch_req_max = 10, 80762306a36Sopenharmony_ci .ch_reg_size = 0x80, 80862306a36Sopenharmony_ci .nr_channels = 22, 80962306a36Sopenharmony_ci .ch_fifo_size_mask = 0xf, 81062306a36Sopenharmony_ci .sreq_index_offset = 2, 81162306a36Sopenharmony_ci .has_outstanding_reqs = false, 81262306a36Sopenharmony_ci}; 81362306a36Sopenharmony_ci 81462306a36Sopenharmony_cistatic const struct tegra_adma_chip_data tegra186_chip_data = { 81562306a36Sopenharmony_ci .adma_get_burst_config = tegra186_adma_get_burst_config, 81662306a36Sopenharmony_ci .global_reg_offset = 0, 81762306a36Sopenharmony_ci .global_int_clear = 0x402c, 81862306a36Sopenharmony_ci .ch_req_tx_shift = 27, 81962306a36Sopenharmony_ci .ch_req_rx_shift = 22, 82062306a36Sopenharmony_ci .ch_base_offset = 0x10000, 82162306a36Sopenharmony_ci .ch_req_mask = 0x1f, 82262306a36Sopenharmony_ci .ch_req_max = 20, 82362306a36Sopenharmony_ci .ch_reg_size = 0x100, 82462306a36Sopenharmony_ci .nr_channels = 32, 82562306a36Sopenharmony_ci .ch_fifo_size_mask = 0x1f, 82662306a36Sopenharmony_ci .sreq_index_offset = 4, 82762306a36Sopenharmony_ci .has_outstanding_reqs = true, 82862306a36Sopenharmony_ci}; 82962306a36Sopenharmony_ci 83062306a36Sopenharmony_cistatic const struct of_device_id tegra_adma_of_match[] = { 83162306a36Sopenharmony_ci { .compatible = "nvidia,tegra210-adma", .data = &tegra210_chip_data }, 83262306a36Sopenharmony_ci { .compatible = "nvidia,tegra186-adma", .data = &tegra186_chip_data }, 83362306a36Sopenharmony_ci { }, 83462306a36Sopenharmony_ci}; 83562306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, tegra_adma_of_match); 83662306a36Sopenharmony_ci 83762306a36Sopenharmony_cistatic int tegra_adma_probe(struct platform_device *pdev) 83862306a36Sopenharmony_ci{ 83962306a36Sopenharmony_ci const struct tegra_adma_chip_data *cdata; 84062306a36Sopenharmony_ci struct tegra_adma *tdma; 84162306a36Sopenharmony_ci int ret, i; 84262306a36Sopenharmony_ci 84362306a36Sopenharmony_ci cdata = of_device_get_match_data(&pdev->dev); 84462306a36Sopenharmony_ci if (!cdata) { 84562306a36Sopenharmony_ci dev_err(&pdev->dev, "device match data not found\n"); 84662306a36Sopenharmony_ci return -ENODEV; 84762306a36Sopenharmony_ci } 84862306a36Sopenharmony_ci 84962306a36Sopenharmony_ci tdma = devm_kzalloc(&pdev->dev, 85062306a36Sopenharmony_ci struct_size(tdma, channels, cdata->nr_channels), 85162306a36Sopenharmony_ci GFP_KERNEL); 85262306a36Sopenharmony_ci if (!tdma) 85362306a36Sopenharmony_ci return -ENOMEM; 85462306a36Sopenharmony_ci 85562306a36Sopenharmony_ci tdma->dev = &pdev->dev; 85662306a36Sopenharmony_ci tdma->cdata = cdata; 85762306a36Sopenharmony_ci tdma->nr_channels = cdata->nr_channels; 85862306a36Sopenharmony_ci platform_set_drvdata(pdev, tdma); 85962306a36Sopenharmony_ci 86062306a36Sopenharmony_ci tdma->base_addr = devm_platform_ioremap_resource(pdev, 0); 86162306a36Sopenharmony_ci if (IS_ERR(tdma->base_addr)) 86262306a36Sopenharmony_ci return PTR_ERR(tdma->base_addr); 86362306a36Sopenharmony_ci 86462306a36Sopenharmony_ci tdma->ahub_clk = devm_clk_get(&pdev->dev, "d_audio"); 86562306a36Sopenharmony_ci if (IS_ERR(tdma->ahub_clk)) { 86662306a36Sopenharmony_ci dev_err(&pdev->dev, "Error: Missing ahub controller clock\n"); 86762306a36Sopenharmony_ci return PTR_ERR(tdma->ahub_clk); 86862306a36Sopenharmony_ci } 86962306a36Sopenharmony_ci 87062306a36Sopenharmony_ci INIT_LIST_HEAD(&tdma->dma_dev.channels); 87162306a36Sopenharmony_ci for (i = 0; i < tdma->nr_channels; i++) { 87262306a36Sopenharmony_ci struct tegra_adma_chan *tdc = &tdma->channels[i]; 87362306a36Sopenharmony_ci 87462306a36Sopenharmony_ci tdc->chan_addr = tdma->base_addr + cdata->ch_base_offset 87562306a36Sopenharmony_ci + (cdata->ch_reg_size * i); 87662306a36Sopenharmony_ci 87762306a36Sopenharmony_ci tdc->irq = of_irq_get(pdev->dev.of_node, i); 87862306a36Sopenharmony_ci if (tdc->irq <= 0) { 87962306a36Sopenharmony_ci ret = tdc->irq ?: -ENXIO; 88062306a36Sopenharmony_ci goto irq_dispose; 88162306a36Sopenharmony_ci } 88262306a36Sopenharmony_ci 88362306a36Sopenharmony_ci vchan_init(&tdc->vc, &tdma->dma_dev); 88462306a36Sopenharmony_ci tdc->vc.desc_free = tegra_adma_desc_free; 88562306a36Sopenharmony_ci tdc->tdma = tdma; 88662306a36Sopenharmony_ci } 88762306a36Sopenharmony_ci 88862306a36Sopenharmony_ci pm_runtime_enable(&pdev->dev); 88962306a36Sopenharmony_ci 89062306a36Sopenharmony_ci ret = pm_runtime_resume_and_get(&pdev->dev); 89162306a36Sopenharmony_ci if (ret < 0) 89262306a36Sopenharmony_ci goto rpm_disable; 89362306a36Sopenharmony_ci 89462306a36Sopenharmony_ci ret = tegra_adma_init(tdma); 89562306a36Sopenharmony_ci if (ret) 89662306a36Sopenharmony_ci goto rpm_put; 89762306a36Sopenharmony_ci 89862306a36Sopenharmony_ci dma_cap_set(DMA_SLAVE, tdma->dma_dev.cap_mask); 89962306a36Sopenharmony_ci dma_cap_set(DMA_PRIVATE, tdma->dma_dev.cap_mask); 90062306a36Sopenharmony_ci dma_cap_set(DMA_CYCLIC, tdma->dma_dev.cap_mask); 90162306a36Sopenharmony_ci 90262306a36Sopenharmony_ci tdma->dma_dev.dev = &pdev->dev; 90362306a36Sopenharmony_ci tdma->dma_dev.device_alloc_chan_resources = 90462306a36Sopenharmony_ci tegra_adma_alloc_chan_resources; 90562306a36Sopenharmony_ci tdma->dma_dev.device_free_chan_resources = 90662306a36Sopenharmony_ci tegra_adma_free_chan_resources; 90762306a36Sopenharmony_ci tdma->dma_dev.device_issue_pending = tegra_adma_issue_pending; 90862306a36Sopenharmony_ci tdma->dma_dev.device_prep_dma_cyclic = tegra_adma_prep_dma_cyclic; 90962306a36Sopenharmony_ci tdma->dma_dev.device_config = tegra_adma_slave_config; 91062306a36Sopenharmony_ci tdma->dma_dev.device_tx_status = tegra_adma_tx_status; 91162306a36Sopenharmony_ci tdma->dma_dev.device_terminate_all = tegra_adma_terminate_all; 91262306a36Sopenharmony_ci tdma->dma_dev.src_addr_widths = BIT(DMA_SLAVE_BUSWIDTH_4_BYTES); 91362306a36Sopenharmony_ci tdma->dma_dev.dst_addr_widths = BIT(DMA_SLAVE_BUSWIDTH_4_BYTES); 91462306a36Sopenharmony_ci tdma->dma_dev.directions = BIT(DMA_DEV_TO_MEM) | BIT(DMA_MEM_TO_DEV); 91562306a36Sopenharmony_ci tdma->dma_dev.residue_granularity = DMA_RESIDUE_GRANULARITY_SEGMENT; 91662306a36Sopenharmony_ci tdma->dma_dev.device_pause = tegra_adma_pause; 91762306a36Sopenharmony_ci tdma->dma_dev.device_resume = tegra_adma_resume; 91862306a36Sopenharmony_ci 91962306a36Sopenharmony_ci ret = dma_async_device_register(&tdma->dma_dev); 92062306a36Sopenharmony_ci if (ret < 0) { 92162306a36Sopenharmony_ci dev_err(&pdev->dev, "ADMA registration failed: %d\n", ret); 92262306a36Sopenharmony_ci goto rpm_put; 92362306a36Sopenharmony_ci } 92462306a36Sopenharmony_ci 92562306a36Sopenharmony_ci ret = of_dma_controller_register(pdev->dev.of_node, 92662306a36Sopenharmony_ci tegra_dma_of_xlate, tdma); 92762306a36Sopenharmony_ci if (ret < 0) { 92862306a36Sopenharmony_ci dev_err(&pdev->dev, "ADMA OF registration failed %d\n", ret); 92962306a36Sopenharmony_ci goto dma_remove; 93062306a36Sopenharmony_ci } 93162306a36Sopenharmony_ci 93262306a36Sopenharmony_ci pm_runtime_put(&pdev->dev); 93362306a36Sopenharmony_ci 93462306a36Sopenharmony_ci dev_info(&pdev->dev, "Tegra210 ADMA driver registered %d channels\n", 93562306a36Sopenharmony_ci tdma->nr_channels); 93662306a36Sopenharmony_ci 93762306a36Sopenharmony_ci return 0; 93862306a36Sopenharmony_ci 93962306a36Sopenharmony_cidma_remove: 94062306a36Sopenharmony_ci dma_async_device_unregister(&tdma->dma_dev); 94162306a36Sopenharmony_cirpm_put: 94262306a36Sopenharmony_ci pm_runtime_put_sync(&pdev->dev); 94362306a36Sopenharmony_cirpm_disable: 94462306a36Sopenharmony_ci pm_runtime_disable(&pdev->dev); 94562306a36Sopenharmony_ciirq_dispose: 94662306a36Sopenharmony_ci while (--i >= 0) 94762306a36Sopenharmony_ci irq_dispose_mapping(tdma->channels[i].irq); 94862306a36Sopenharmony_ci 94962306a36Sopenharmony_ci return ret; 95062306a36Sopenharmony_ci} 95162306a36Sopenharmony_ci 95262306a36Sopenharmony_cistatic int tegra_adma_remove(struct platform_device *pdev) 95362306a36Sopenharmony_ci{ 95462306a36Sopenharmony_ci struct tegra_adma *tdma = platform_get_drvdata(pdev); 95562306a36Sopenharmony_ci int i; 95662306a36Sopenharmony_ci 95762306a36Sopenharmony_ci of_dma_controller_free(pdev->dev.of_node); 95862306a36Sopenharmony_ci dma_async_device_unregister(&tdma->dma_dev); 95962306a36Sopenharmony_ci 96062306a36Sopenharmony_ci for (i = 0; i < tdma->nr_channels; ++i) 96162306a36Sopenharmony_ci irq_dispose_mapping(tdma->channels[i].irq); 96262306a36Sopenharmony_ci 96362306a36Sopenharmony_ci pm_runtime_disable(&pdev->dev); 96462306a36Sopenharmony_ci 96562306a36Sopenharmony_ci return 0; 96662306a36Sopenharmony_ci} 96762306a36Sopenharmony_ci 96862306a36Sopenharmony_cistatic const struct dev_pm_ops tegra_adma_dev_pm_ops = { 96962306a36Sopenharmony_ci SET_RUNTIME_PM_OPS(tegra_adma_runtime_suspend, 97062306a36Sopenharmony_ci tegra_adma_runtime_resume, NULL) 97162306a36Sopenharmony_ci SET_LATE_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, 97262306a36Sopenharmony_ci pm_runtime_force_resume) 97362306a36Sopenharmony_ci}; 97462306a36Sopenharmony_ci 97562306a36Sopenharmony_cistatic struct platform_driver tegra_admac_driver = { 97662306a36Sopenharmony_ci .driver = { 97762306a36Sopenharmony_ci .name = "tegra-adma", 97862306a36Sopenharmony_ci .pm = &tegra_adma_dev_pm_ops, 97962306a36Sopenharmony_ci .of_match_table = tegra_adma_of_match, 98062306a36Sopenharmony_ci }, 98162306a36Sopenharmony_ci .probe = tegra_adma_probe, 98262306a36Sopenharmony_ci .remove = tegra_adma_remove, 98362306a36Sopenharmony_ci}; 98462306a36Sopenharmony_ci 98562306a36Sopenharmony_cimodule_platform_driver(tegra_admac_driver); 98662306a36Sopenharmony_ci 98762306a36Sopenharmony_ciMODULE_ALIAS("platform:tegra210-adma"); 98862306a36Sopenharmony_ciMODULE_DESCRIPTION("NVIDIA Tegra ADMA driver"); 98962306a36Sopenharmony_ciMODULE_AUTHOR("Dara Ramesh <dramesh@nvidia.com>"); 99062306a36Sopenharmony_ciMODULE_AUTHOR("Jon Hunter <jonathanh@nvidia.com>"); 99162306a36Sopenharmony_ciMODULE_LICENSE("GPL v2"); 992