diff options
author | Sjur Braendeland <sjur.brandeland@stericsson.com> | 2010-06-29 03:08:21 -0400 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2010-06-29 03:08:21 -0400 |
commit | 529d6dad5bc69de14cdd24831e2a14264e93daa4 (patch) | |
tree | a89d7b8cdd3692e96a64c5d7196bcefc9f1e7f24 /drivers/net/caif/caif_spi_slave.c | |
parent | 01eebb53a65996dfbfb892bf5eb21ae831fbe3a6 (diff) |
caif-driver: Add CAIF-SPI Protocol driver.
This patch introduces the CAIF SPI Protocol Driver for
CAIF Link Layer.
This driver implements a platform driver to accommodate for a
platform specific SPI device. A general platform driver is not
possible as there are no SPI Slave side Kernel API defined.
A sample CAIF SPI Platform device can be found in
.../Documentation/networking/caif/spi_porting.txt
Signed-off-by: Sjur Braendeland <sjur.brandeland@stericsson.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'drivers/net/caif/caif_spi_slave.c')
-rw-r--r-- | drivers/net/caif/caif_spi_slave.c | 252 |
1 files changed, 252 insertions, 0 deletions
diff --git a/drivers/net/caif/caif_spi_slave.c b/drivers/net/caif/caif_spi_slave.c new file mode 100644 index 00000000000..077ccf840ed --- /dev/null +++ b/drivers/net/caif/caif_spi_slave.c | |||
@@ -0,0 +1,252 @@ | |||
1 | /* | ||
2 | * Copyright (C) ST-Ericsson AB 2010 | ||
3 | * Contact: Sjur Brendeland / sjur.brandeland@stericsson.com | ||
4 | * Author: Daniel Martensson / Daniel.Martensson@stericsson.com | ||
5 | * License terms: GNU General Public License (GPL) version 2. | ||
6 | */ | ||
7 | #include <linux/version.h> | ||
8 | #include <linux/init.h> | ||
9 | #include <linux/module.h> | ||
10 | #include <linux/device.h> | ||
11 | #include <linux/platform_device.h> | ||
12 | #include <linux/string.h> | ||
13 | #include <linux/semaphore.h> | ||
14 | #include <linux/workqueue.h> | ||
15 | #include <linux/completion.h> | ||
16 | #include <linux/list.h> | ||
17 | #include <linux/interrupt.h> | ||
18 | #include <linux/dma-mapping.h> | ||
19 | #include <linux/delay.h> | ||
20 | #include <linux/sched.h> | ||
21 | #include <linux/debugfs.h> | ||
22 | #include <net/caif/caif_spi.h> | ||
23 | |||
24 | #ifndef CONFIG_CAIF_SPI_SYNC | ||
25 | #define SPI_DATA_POS SPI_CMD_SZ | ||
26 | static inline int forward_to_spi_cmd(struct cfspi *cfspi) | ||
27 | { | ||
28 | return cfspi->rx_cpck_len; | ||
29 | } | ||
30 | #else | ||
31 | #define SPI_DATA_POS 0 | ||
32 | static inline int forward_to_spi_cmd(struct cfspi *cfspi) | ||
33 | { | ||
34 | return 0; | ||
35 | } | ||
36 | #endif | ||
37 | |||
38 | int spi_frm_align = 2; | ||
39 | int spi_up_head_align = 1; | ||
40 | int spi_up_tail_align; | ||
41 | int spi_down_head_align = 3; | ||
42 | int spi_down_tail_align = 1; | ||
43 | |||
44 | #ifdef CONFIG_DEBUG_FS | ||
45 | static inline void debugfs_store_prev(struct cfspi *cfspi) | ||
46 | { | ||
47 | /* Store previous command for debugging reasons.*/ | ||
48 | cfspi->pcmd = cfspi->cmd; | ||
49 | /* Store previous transfer. */ | ||
50 | cfspi->tx_ppck_len = cfspi->tx_cpck_len; | ||
51 | cfspi->rx_ppck_len = cfspi->rx_cpck_len; | ||
52 | } | ||
53 | #else | ||
54 | static inline void debugfs_store_prev(struct cfspi *cfspi) | ||
55 | { | ||
56 | } | ||
57 | #endif | ||
58 | |||
59 | void cfspi_xfer(struct work_struct *work) | ||
60 | { | ||
61 | struct cfspi *cfspi; | ||
62 | u8 *ptr = NULL; | ||
63 | unsigned long flags; | ||
64 | int ret; | ||
65 | cfspi = container_of(work, struct cfspi, work); | ||
66 | |||
67 | /* Initialize state. */ | ||
68 | cfspi->cmd = SPI_CMD_EOT; | ||
69 | |||
70 | for (;;) { | ||
71 | |||
72 | cfspi_dbg_state(cfspi, CFSPI_STATE_WAITING); | ||
73 | |||
74 | /* Wait for master talk or transmit event. */ | ||
75 | wait_event_interruptible(cfspi->wait, | ||
76 | test_bit(SPI_XFER, &cfspi->state) || | ||
77 | test_bit(SPI_TERMINATE, &cfspi->state)); | ||
78 | |||
79 | if (test_bit(SPI_TERMINATE, &cfspi->state)) | ||
80 | return; | ||
81 | |||
82 | #if CFSPI_DBG_PREFILL | ||
83 | /* Prefill buffers for easier debugging. */ | ||
84 | memset(cfspi->xfer.va_tx, 0xFF, SPI_DMA_BUF_LEN); | ||
85 | memset(cfspi->xfer.va_rx, 0xFF, SPI_DMA_BUF_LEN); | ||
86 | #endif /* CFSPI_DBG_PREFILL */ | ||
87 | |||
88 | cfspi_dbg_state(cfspi, CFSPI_STATE_AWAKE); | ||
89 | |||
90 | /* Check whether we have a committed frame. */ | ||
91 | if (cfspi->tx_cpck_len) { | ||
92 | int len; | ||
93 | |||
94 | cfspi_dbg_state(cfspi, CFSPI_STATE_FETCH_PKT); | ||
95 | |||
96 | /* Copy commited SPI frames after the SPI indication. */ | ||
97 | ptr = (u8 *) cfspi->xfer.va_tx; | ||
98 | ptr += SPI_IND_SZ; | ||
99 | len = cfspi_xmitfrm(cfspi, ptr, cfspi->tx_cpck_len); | ||
100 | WARN_ON(len != cfspi->tx_cpck_len); | ||
101 | } | ||
102 | |||
103 | cfspi_dbg_state(cfspi, CFSPI_STATE_GET_NEXT); | ||
104 | |||
105 | /* Get length of next frame to commit. */ | ||
106 | cfspi->tx_npck_len = cfspi_xmitlen(cfspi); | ||
107 | |||
108 | WARN_ON(cfspi->tx_npck_len > SPI_DMA_BUF_LEN); | ||
109 | |||
110 | /* | ||
111 | * Add indication and length at the beginning of the frame, | ||
112 | * using little endian. | ||
113 | */ | ||
114 | ptr = (u8 *) cfspi->xfer.va_tx; | ||
115 | *ptr++ = SPI_CMD_IND; | ||
116 | *ptr++ = (SPI_CMD_IND & 0xFF00) >> 8; | ||
117 | *ptr++ = cfspi->tx_npck_len & 0x00FF; | ||
118 | *ptr++ = (cfspi->tx_npck_len & 0xFF00) >> 8; | ||
119 | |||
120 | /* Calculate length of DMAs. */ | ||
121 | cfspi->xfer.tx_dma_len = cfspi->tx_cpck_len + SPI_IND_SZ; | ||
122 | cfspi->xfer.rx_dma_len = cfspi->rx_cpck_len + SPI_CMD_SZ; | ||
123 | |||
124 | /* Add SPI TX frame alignment padding, if necessary. */ | ||
125 | if (cfspi->tx_cpck_len && | ||
126 | (cfspi->xfer.tx_dma_len % spi_frm_align)) { | ||
127 | |||
128 | cfspi->xfer.tx_dma_len += spi_frm_align - | ||
129 | (cfspi->xfer.tx_dma_len % spi_frm_align); | ||
130 | } | ||
131 | |||
132 | /* Add SPI RX frame alignment padding, if necessary. */ | ||
133 | if (cfspi->rx_cpck_len && | ||
134 | (cfspi->xfer.rx_dma_len % spi_frm_align)) { | ||
135 | |||
136 | cfspi->xfer.rx_dma_len += spi_frm_align - | ||
137 | (cfspi->xfer.rx_dma_len % spi_frm_align); | ||
138 | } | ||
139 | |||
140 | cfspi_dbg_state(cfspi, CFSPI_STATE_INIT_XFER); | ||
141 | |||
142 | /* Start transfer. */ | ||
143 | ret = cfspi->dev->init_xfer(&cfspi->xfer, cfspi->dev); | ||
144 | WARN_ON(ret); | ||
145 | |||
146 | cfspi_dbg_state(cfspi, CFSPI_STATE_WAIT_ACTIVE); | ||
147 | |||
148 | /* | ||
149 | * TODO: We might be able to make an assumption if this is the | ||
150 | * first loop. Make sure that minimum toggle time is respected. | ||
151 | */ | ||
152 | udelay(MIN_TRANSITION_TIME_USEC); | ||
153 | |||
154 | cfspi_dbg_state(cfspi, CFSPI_STATE_SIG_ACTIVE); | ||
155 | |||
156 | /* Signal that we are ready to recieve data. */ | ||
157 | cfspi->dev->sig_xfer(true, cfspi->dev); | ||
158 | |||
159 | cfspi_dbg_state(cfspi, CFSPI_STATE_WAIT_XFER_DONE); | ||
160 | |||
161 | /* Wait for transfer completion. */ | ||
162 | wait_for_completion(&cfspi->comp); | ||
163 | |||
164 | cfspi_dbg_state(cfspi, CFSPI_STATE_XFER_DONE); | ||
165 | |||
166 | if (cfspi->cmd == SPI_CMD_EOT) { | ||
167 | /* | ||
168 | * Clear the master talk bit. A xfer is always at | ||
169 | * least two bursts. | ||
170 | */ | ||
171 | clear_bit(SPI_SS_ON, &cfspi->state); | ||
172 | } | ||
173 | |||
174 | cfspi_dbg_state(cfspi, CFSPI_STATE_WAIT_INACTIVE); | ||
175 | |||
176 | /* Make sure that the minimum toggle time is respected. */ | ||
177 | if (SPI_XFER_TIME_USEC(cfspi->xfer.tx_dma_len, | ||
178 | cfspi->dev->clk_mhz) < | ||
179 | MIN_TRANSITION_TIME_USEC) { | ||
180 | |||
181 | udelay(MIN_TRANSITION_TIME_USEC - | ||
182 | SPI_XFER_TIME_USEC | ||
183 | (cfspi->xfer.tx_dma_len, cfspi->dev->clk_mhz)); | ||
184 | } | ||
185 | |||
186 | cfspi_dbg_state(cfspi, CFSPI_STATE_SIG_INACTIVE); | ||
187 | |||
188 | /* De-assert transfer signal. */ | ||
189 | cfspi->dev->sig_xfer(false, cfspi->dev); | ||
190 | |||
191 | /* Check whether we received a CAIF packet. */ | ||
192 | if (cfspi->rx_cpck_len) { | ||
193 | int len; | ||
194 | |||
195 | cfspi_dbg_state(cfspi, CFSPI_STATE_DELIVER_PKT); | ||
196 | |||
197 | /* Parse SPI frame. */ | ||
198 | ptr = ((u8 *)(cfspi->xfer.va_rx + SPI_DATA_POS)); | ||
199 | |||
200 | len = cfspi_rxfrm(cfspi, ptr, cfspi->rx_cpck_len); | ||
201 | WARN_ON(len != cfspi->rx_cpck_len); | ||
202 | } | ||
203 | |||
204 | /* Check the next SPI command and length. */ | ||
205 | ptr = (u8 *) cfspi->xfer.va_rx; | ||
206 | |||
207 | ptr += forward_to_spi_cmd(cfspi); | ||
208 | |||
209 | cfspi->cmd = *ptr++; | ||
210 | cfspi->cmd |= ((*ptr++) << 8) & 0xFF00; | ||
211 | cfspi->rx_npck_len = *ptr++; | ||
212 | cfspi->rx_npck_len |= ((*ptr++) << 8) & 0xFF00; | ||
213 | |||
214 | WARN_ON(cfspi->rx_npck_len > SPI_DMA_BUF_LEN); | ||
215 | WARN_ON(cfspi->cmd > SPI_CMD_EOT); | ||
216 | |||
217 | debugfs_store_prev(cfspi); | ||
218 | |||
219 | /* Check whether the master issued an EOT command. */ | ||
220 | if (cfspi->cmd == SPI_CMD_EOT) { | ||
221 | /* Reset state. */ | ||
222 | cfspi->tx_cpck_len = 0; | ||
223 | cfspi->rx_cpck_len = 0; | ||
224 | } else { | ||
225 | /* Update state. */ | ||
226 | cfspi->tx_cpck_len = cfspi->tx_npck_len; | ||
227 | cfspi->rx_cpck_len = cfspi->rx_npck_len; | ||
228 | } | ||
229 | |||
230 | /* | ||
231 | * Check whether we need to clear the xfer bit. | ||
232 | * Spin lock needed for packet insertion. | ||
233 | * Test and clear of different bits | ||
234 | * are not supported. | ||
235 | */ | ||
236 | spin_lock_irqsave(&cfspi->lock, flags); | ||
237 | if (cfspi->cmd == SPI_CMD_EOT && !cfspi_xmitlen(cfspi) | ||
238 | && !test_bit(SPI_SS_ON, &cfspi->state)) | ||
239 | clear_bit(SPI_XFER, &cfspi->state); | ||
240 | |||
241 | spin_unlock_irqrestore(&cfspi->lock, flags); | ||
242 | } | ||
243 | } | ||
244 | |||
245 | struct platform_driver cfspi_spi_driver = { | ||
246 | .probe = cfspi_spi_probe, | ||
247 | .remove = cfspi_spi_remove, | ||
248 | .driver = { | ||
249 | .name = "cfspi_sspi", | ||
250 | .owner = THIS_MODULE, | ||
251 | }, | ||
252 | }; | ||