aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/spi/dw_spi_mid.c
diff options
context:
space:
mode:
authorFeng Tang <feng.tang@intel.com>2010-12-24 00:59:11 -0500
committerGrant Likely <grant.likely@secretlab.ca>2010-12-24 03:23:25 -0500
commit7063c0d942a1af2993531fbe52b4c74c1db818c4 (patch)
treefe91b01bd61449849736c47e0a05840fc47a5eb3 /drivers/spi/dw_spi_mid.c
parent79290a2aa2fd1c179a285218472092475630dc0e (diff)
spi/dw_spi: add DMA support
dw_spi driver in upstream only supports PIO mode, and this patch will support it to cowork with the Designware dma controller used on Intel Moorestown platform, at the same time it provides a general framework to support dw_spi core to cowork with dma controllers on other platforms It has been tested with a Option GTM501L 3G modem and Infenion 60x60 modem. To use DMA mode, DMA controller 2 of Moorestown has to be enabled Also change the dma interface suggested by Linus Walleij. Acked-by: Linus Walleij <linus.walleij@stericsson.com> Signed-off-by: Feng Tang <feng.tang@intel.com> [Typo fix and renames to match intel_mid_dma renaming] Signed-off-by: Vinod Koul <vinod.koul@intel.com> Signed-off-by: Alan Cox <alan@linux.intel.com> Signed-off-by: Grant Likely <grant.likely@secretlab.ca>
Diffstat (limited to 'drivers/spi/dw_spi_mid.c')
-rw-r--r--drivers/spi/dw_spi_mid.c223
1 files changed, 223 insertions, 0 deletions
diff --git a/drivers/spi/dw_spi_mid.c b/drivers/spi/dw_spi_mid.c
new file mode 100644
index 000000000000..c91c966e0717
--- /dev/null
+++ b/drivers/spi/dw_spi_mid.c
@@ -0,0 +1,223 @@
1/*
2 * dw_spi_mid.c - special handling for DW core on Intel MID platform
3 *
4 * Copyright (c) 2009, Intel Corporation.
5 *
6 * This program is free software; you can redistribute it and/or modify it
7 * under the terms and conditions of the GNU General Public License,
8 * version 2, as published by the Free Software Foundation.
9 *
10 * This program is distributed in the hope it will be useful, but WITHOUT
11 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
13 * more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20#include <linux/dma-mapping.h>
21#include <linux/dmaengine.h>
22#include <linux/interrupt.h>
23#include <linux/slab.h>
24#include <linux/spi/spi.h>
25#include <linux/spi/dw_spi.h>
26
27#ifdef CONFIG_SPI_DW_MID_DMA
28#include <linux/intel_mid_dma.h>
29#include <linux/pci.h>
30
31struct mid_dma {
32 struct intel_mid_dma_slave dmas_tx;
33 struct intel_mid_dma_slave dmas_rx;
34};
35
36static bool mid_spi_dma_chan_filter(struct dma_chan *chan, void *param)
37{
38 struct dw_spi *dws = param;
39
40 return dws->dmac && (&dws->dmac->dev == chan->device->dev);
41}
42
43static int mid_spi_dma_init(struct dw_spi *dws)
44{
45 struct mid_dma *dw_dma = dws->dma_priv;
46 struct intel_mid_dma_slave *rxs, *txs;
47 dma_cap_mask_t mask;
48
49 /*
50 * Get pci device for DMA controller, currently it could only
51 * be the DMA controller of either Moorestown or Medfield
52 */
53 dws->dmac = pci_get_device(PCI_VENDOR_ID_INTEL, 0x0813, NULL);
54 if (!dws->dmac)
55 dws->dmac = pci_get_device(PCI_VENDOR_ID_INTEL, 0x0827, NULL);
56
57 dma_cap_zero(mask);
58 dma_cap_set(DMA_SLAVE, mask);
59
60 /* 1. Init rx channel */
61 dws->rxchan = dma_request_channel(mask, mid_spi_dma_chan_filter, dws);
62 if (!dws->rxchan)
63 goto err_exit;
64 rxs = &dw_dma->dmas_rx;
65 rxs->hs_mode = LNW_DMA_HW_HS;
66 rxs->cfg_mode = LNW_DMA_PER_TO_MEM;
67 dws->rxchan->private = rxs;
68
69 /* 2. Init tx channel */
70 dws->txchan = dma_request_channel(mask, mid_spi_dma_chan_filter, dws);
71 if (!dws->txchan)
72 goto free_rxchan;
73 txs = &dw_dma->dmas_tx;
74 txs->hs_mode = LNW_DMA_HW_HS;
75 txs->cfg_mode = LNW_DMA_MEM_TO_PER;
76 dws->txchan->private = txs;
77
78 dws->dma_inited = 1;
79 return 0;
80
81free_rxchan:
82 dma_release_channel(dws->rxchan);
83err_exit:
84 return -1;
85
86}
87
88static void mid_spi_dma_exit(struct dw_spi *dws)
89{
90 dma_release_channel(dws->txchan);
91 dma_release_channel(dws->rxchan);
92}
93
94/*
95 * dws->dma_chan_done is cleared before the dma transfer starts,
96 * callback for rx/tx channel will each increment it by 1.
97 * Reaching 2 means the whole spi transaction is done.
98 */
99static void dw_spi_dma_done(void *arg)
100{
101 struct dw_spi *dws = arg;
102
103 if (++dws->dma_chan_done != 2)
104 return;
105 dw_spi_xfer_done(dws);
106}
107
108static int mid_spi_dma_transfer(struct dw_spi *dws, int cs_change)
109{
110 struct dma_async_tx_descriptor *txdesc = NULL, *rxdesc = NULL;
111 struct dma_chan *txchan, *rxchan;
112 struct dma_slave_config txconf, rxconf;
113 u16 dma_ctrl = 0;
114
115 /* 1. setup DMA related registers */
116 if (cs_change) {
117 spi_enable_chip(dws, 0);
118 dw_writew(dws, dmardlr, 0xf);
119 dw_writew(dws, dmatdlr, 0x10);
120 if (dws->tx_dma)
121 dma_ctrl |= 0x2;
122 if (dws->rx_dma)
123 dma_ctrl |= 0x1;
124 dw_writew(dws, dmacr, dma_ctrl);
125 spi_enable_chip(dws, 1);
126 }
127
128 dws->dma_chan_done = 0;
129 txchan = dws->txchan;
130 rxchan = dws->rxchan;
131
132 /* 2. Prepare the TX dma transfer */
133 txconf.direction = DMA_TO_DEVICE;
134 txconf.dst_addr = dws->dma_addr;
135 txconf.dst_maxburst = LNW_DMA_MSIZE_16;
136 txconf.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
137 txconf.dst_addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;
138
139 txchan->device->device_control(txchan, DMA_SLAVE_CONFIG,
140 (unsigned long) &txconf);
141
142 memset(&dws->tx_sgl, 0, sizeof(dws->tx_sgl));
143 dws->tx_sgl.dma_address = dws->tx_dma;
144 dws->tx_sgl.length = dws->len;
145
146 txdesc = txchan->device->device_prep_slave_sg(txchan,
147 &dws->tx_sgl,
148 1,
149 DMA_TO_DEVICE,
150 DMA_PREP_INTERRUPT | DMA_COMPL_SKIP_DEST_UNMAP);
151 txdesc->callback = dw_spi_dma_done;
152 txdesc->callback_param = dws;
153
154 /* 3. Prepare the RX dma transfer */
155 rxconf.direction = DMA_FROM_DEVICE;
156 rxconf.src_addr = dws->dma_addr;
157 rxconf.src_maxburst = LNW_DMA_MSIZE_16;
158 rxconf.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
159 rxconf.src_addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;
160
161 rxchan->device->device_control(rxchan, DMA_SLAVE_CONFIG,
162 (unsigned long) &rxconf);
163
164 memset(&dws->rx_sgl, 0, sizeof(dws->rx_sgl));
165 dws->rx_sgl.dma_address = dws->rx_dma;
166 dws->rx_sgl.length = dws->len;
167
168 rxdesc = rxchan->device->device_prep_slave_sg(rxchan,
169 &dws->rx_sgl,
170 1,
171 DMA_FROM_DEVICE,
172 DMA_PREP_INTERRUPT | DMA_COMPL_SKIP_DEST_UNMAP);
173 rxdesc->callback = dw_spi_dma_done;
174 rxdesc->callback_param = dws;
175
176 /* rx must be started before tx due to spi instinct */
177 rxdesc->tx_submit(rxdesc);
178 txdesc->tx_submit(txdesc);
179 return 0;
180}
181
182static struct dw_spi_dma_ops mid_dma_ops = {
183 .dma_init = mid_spi_dma_init,
184 .dma_exit = mid_spi_dma_exit,
185 .dma_transfer = mid_spi_dma_transfer,
186};
187#endif
188
189/* Some specific info for SPI0 controller on Moorestown */
190
191/* HW info for MRST CLk Control Unit, one 32b reg */
192#define MRST_SPI_CLK_BASE 100000000 /* 100m */
193#define MRST_CLK_SPI0_REG 0xff11d86c
194#define CLK_SPI_BDIV_OFFSET 0
195#define CLK_SPI_BDIV_MASK 0x00000007
196#define CLK_SPI_CDIV_OFFSET 9
197#define CLK_SPI_CDIV_MASK 0x00000e00
198#define CLK_SPI_DISABLE_OFFSET 8
199
200int dw_spi_mid_init(struct dw_spi *dws)
201{
202 u32 *clk_reg, clk_cdiv;
203
204 clk_reg = ioremap_nocache(MRST_CLK_SPI0_REG, 16);
205 if (!clk_reg)
206 return -ENOMEM;
207
208 /* get SPI controller operating freq info */
209 clk_cdiv = (readl(clk_reg) & CLK_SPI_CDIV_MASK) >> CLK_SPI_CDIV_OFFSET;
210 dws->max_freq = MRST_SPI_CLK_BASE / (clk_cdiv + 1);
211 iounmap(clk_reg);
212
213 dws->num_cs = 16;
214 dws->fifo_len = 40; /* FIFO has 40 words buffer */
215
216#ifdef CONFIG_SPI_DW_MID_DMA
217 dws->dma_priv = kzalloc(sizeof(struct mid_dma), GFP_KERNEL);
218 if (!dws->dma_priv)
219 return -ENOMEM;
220 dws->dma_ops = &mid_dma_ops;
221#endif
222 return 0;
223}