Commit | Line | Data |
---|---|---|
35c9049b CM |
1 | /* |
2 | * OMAP7xx SPI 100k controller driver | |
3 | * Author: Fabrice Crohas <fcrohas@gmail.com> | |
4 | * from original omap1_mcspi driver | |
5 | * | |
6 | * Copyright (C) 2005, 2006 Nokia Corporation | |
7 | * Author: Samuel Ortiz <samuel.ortiz@nokia.com> and | |
8 | * Juha Yrj�l� <juha.yrjola@nokia.com> | |
9 | * | |
10 | * This program is free software; you can redistribute it and/or modify | |
11 | * it under the terms of the GNU General Public License as published by | |
12 | * the Free Software Foundation; either version 2 of the License, or | |
13 | * (at your option) any later version. | |
14 | * | |
15 | * This program is distributed in the hope that it will be useful, | |
16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
18 | * GNU General Public License for more details. | |
19 | * | |
20 | * You should have received a copy of the GNU General Public License | |
21 | * along with this program; if not, write to the Free Software | |
22 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
23 | * | |
24 | */ | |
25 | #include <linux/kernel.h> | |
26 | #include <linux/init.h> | |
27 | #include <linux/interrupt.h> | |
28 | #include <linux/module.h> | |
29 | #include <linux/device.h> | |
30 | #include <linux/delay.h> | |
31 | #include <linux/platform_device.h> | |
32 | #include <linux/err.h> | |
33 | #include <linux/clk.h> | |
34 | #include <linux/io.h> | |
35 | #include <linux/gpio.h> | |
5a0e3ad6 | 36 | #include <linux/slab.h> |
35c9049b CM |
37 | |
38 | #include <linux/spi/spi.h> | |
39 | ||
35c9049b CM |
40 | #define OMAP1_SPI100K_MAX_FREQ 48000000 |
41 | ||
42 | #define ICR_SPITAS (OMAP7XX_ICR_BASE + 0x12) | |
43 | ||
44 | #define SPI_SETUP1 0x00 | |
45 | #define SPI_SETUP2 0x02 | |
46 | #define SPI_CTRL 0x04 | |
47 | #define SPI_STATUS 0x06 | |
48 | #define SPI_TX_LSB 0x08 | |
49 | #define SPI_TX_MSB 0x0a | |
50 | #define SPI_RX_LSB 0x0c | |
51 | #define SPI_RX_MSB 0x0e | |
52 | ||
53 | #define SPI_SETUP1_INT_READ_ENABLE (1UL << 5) | |
54 | #define SPI_SETUP1_INT_WRITE_ENABLE (1UL << 4) | |
55 | #define SPI_SETUP1_CLOCK_DIVISOR(x) ((x) << 1) | |
56 | #define SPI_SETUP1_CLOCK_ENABLE (1UL << 0) | |
57 | ||
58 | #define SPI_SETUP2_ACTIVE_EDGE_FALLING (0UL << 0) | |
59 | #define SPI_SETUP2_ACTIVE_EDGE_RISING (1UL << 0) | |
60 | #define SPI_SETUP2_NEGATIVE_LEVEL (0UL << 5) | |
61 | #define SPI_SETUP2_POSITIVE_LEVEL (1UL << 5) | |
62 | #define SPI_SETUP2_LEVEL_TRIGGER (0UL << 10) | |
63 | #define SPI_SETUP2_EDGE_TRIGGER (1UL << 10) | |
64 | ||
65 | #define SPI_CTRL_SEN(x) ((x) << 7) | |
66 | #define SPI_CTRL_WORD_SIZE(x) (((x) - 1) << 2) | |
67 | #define SPI_CTRL_WR (1UL << 1) | |
68 | #define SPI_CTRL_RD (1UL << 0) | |
69 | ||
70 | #define SPI_STATUS_WE (1UL << 1) | |
71 | #define SPI_STATUS_RD (1UL << 0) | |
72 | ||
35c9049b CM |
73 | /* use PIO for small transfers, avoiding DMA setup/teardown overhead and |
74 | * cache operations; better heuristics consider wordsize and bitrate. | |
75 | */ | |
76 | #define DMA_MIN_BYTES 8 | |
77 | ||
78 | #define SPI_RUNNING 0 | |
79 | #define SPI_SHUTDOWN 1 | |
80 | ||
81 | struct omap1_spi100k { | |
35c9049b CM |
82 | struct clk *ick; |
83 | struct clk *fck; | |
84 | ||
85 | /* Virtual base address of the controller */ | |
86 | void __iomem *base; | |
35c9049b CM |
87 | }; |
88 | ||
89 | struct omap1_spi100k_cs { | |
90 | void __iomem *base; | |
91 | int word_len; | |
92 | }; | |
93 | ||
35c9049b CM |
94 | static void spi100k_enable_clock(struct spi_master *master) |
95 | { | |
96 | unsigned int val; | |
97 | struct omap1_spi100k *spi100k = spi_master_get_devdata(master); | |
98 | ||
99 | /* enable SPI */ | |
100 | val = readw(spi100k->base + SPI_SETUP1); | |
101 | val |= SPI_SETUP1_CLOCK_ENABLE; | |
102 | writew(val, spi100k->base + SPI_SETUP1); | |
103 | } | |
104 | ||
105 | static void spi100k_disable_clock(struct spi_master *master) | |
106 | { | |
107 | unsigned int val; | |
108 | struct omap1_spi100k *spi100k = spi_master_get_devdata(master); | |
109 | ||
110 | /* disable SPI */ | |
111 | val = readw(spi100k->base + SPI_SETUP1); | |
112 | val &= ~SPI_SETUP1_CLOCK_ENABLE; | |
113 | writew(val, spi100k->base + SPI_SETUP1); | |
114 | } | |
115 | ||
116 | static void spi100k_write_data(struct spi_master *master, int len, int data) | |
117 | { | |
118 | struct omap1_spi100k *spi100k = spi_master_get_devdata(master); | |
119 | ||
5c2818cd CM |
120 | /* write 16-bit word, shifting 8-bit data if necessary */ |
121 | if (len <= 8) { | |
122 | data <<= 8; | |
123 | len = 16; | |
124 | } | |
125 | ||
35c9049b | 126 | spi100k_enable_clock(master); |
31804f63 | 127 | writew(data , spi100k->base + SPI_TX_MSB); |
35c9049b CM |
128 | |
129 | writew(SPI_CTRL_SEN(0) | | |
130 | SPI_CTRL_WORD_SIZE(len) | | |
131 | SPI_CTRL_WR, | |
132 | spi100k->base + SPI_CTRL); | |
133 | ||
134 | /* Wait for bit ack send change */ | |
31804f63 JH |
135 | while ((readw(spi100k->base + SPI_STATUS) & SPI_STATUS_WE) != SPI_STATUS_WE) |
136 | ; | |
35c9049b CM |
137 | udelay(1000); |
138 | ||
139 | spi100k_disable_clock(master); | |
140 | } | |
141 | ||
142 | static int spi100k_read_data(struct spi_master *master, int len) | |
143 | { | |
31804f63 | 144 | int dataH, dataL; |
35c9049b CM |
145 | struct omap1_spi100k *spi100k = spi_master_get_devdata(master); |
146 | ||
5c2818cd CM |
147 | /* Always do at least 16 bits */ |
148 | if (len <= 8) | |
149 | len = 16; | |
150 | ||
35c9049b CM |
151 | spi100k_enable_clock(master); |
152 | writew(SPI_CTRL_SEN(0) | | |
153 | SPI_CTRL_WORD_SIZE(len) | | |
154 | SPI_CTRL_RD, | |
155 | spi100k->base + SPI_CTRL); | |
156 | ||
31804f63 JH |
157 | while ((readw(spi100k->base + SPI_STATUS) & SPI_STATUS_RD) != SPI_STATUS_RD) |
158 | ; | |
35c9049b CM |
159 | udelay(1000); |
160 | ||
161 | dataL = readw(spi100k->base + SPI_RX_LSB); | |
162 | dataH = readw(spi100k->base + SPI_RX_MSB); | |
163 | spi100k_disable_clock(master); | |
164 | ||
165 | return dataL; | |
166 | } | |
167 | ||
168 | static void spi100k_open(struct spi_master *master) | |
169 | { | |
170 | /* get control of SPI */ | |
171 | struct omap1_spi100k *spi100k = spi_master_get_devdata(master); | |
172 | ||
173 | writew(SPI_SETUP1_INT_READ_ENABLE | | |
174 | SPI_SETUP1_INT_WRITE_ENABLE | | |
175 | SPI_SETUP1_CLOCK_DIVISOR(0), spi100k->base + SPI_SETUP1); | |
176 | ||
177 | /* configure clock and interrupts */ | |
178 | writew(SPI_SETUP2_ACTIVE_EDGE_FALLING | | |
179 | SPI_SETUP2_NEGATIVE_LEVEL | | |
180 | SPI_SETUP2_LEVEL_TRIGGER, spi100k->base + SPI_SETUP2); | |
181 | } | |
182 | ||
183 | static void omap1_spi100k_force_cs(struct omap1_spi100k *spi100k, int enable) | |
184 | { | |
185 | if (enable) | |
186 | writew(0x05fc, spi100k->base + SPI_CTRL); | |
187 | else | |
188 | writew(0x05fd, spi100k->base + SPI_CTRL); | |
189 | } | |
190 | ||
191 | static unsigned | |
192 | omap1_spi100k_txrx_pio(struct spi_device *spi, struct spi_transfer *xfer) | |
193 | { | |
35c9049b CM |
194 | struct omap1_spi100k_cs *cs = spi->controller_state; |
195 | unsigned int count, c; | |
196 | int word_len; | |
197 | ||
35c9049b CM |
198 | count = xfer->len; |
199 | c = count; | |
200 | word_len = cs->word_len; | |
201 | ||
35c9049b CM |
202 | if (word_len <= 8) { |
203 | u8 *rx; | |
204 | const u8 *tx; | |
205 | ||
206 | rx = xfer->rx_buf; | |
207 | tx = xfer->tx_buf; | |
208 | do { | |
31804f63 | 209 | c -= 1; |
35c9049b | 210 | if (xfer->tx_buf != NULL) |
5c2818cd | 211 | spi100k_write_data(spi->master, word_len, *tx++); |
35c9049b | 212 | if (xfer->rx_buf != NULL) |
5c2818cd | 213 | *rx++ = spi100k_read_data(spi->master, word_len); |
31804f63 | 214 | } while (c); |
35c9049b CM |
215 | } else if (word_len <= 16) { |
216 | u16 *rx; | |
217 | const u16 *tx; | |
218 | ||
219 | rx = xfer->rx_buf; | |
220 | tx = xfer->tx_buf; | |
221 | do { | |
31804f63 | 222 | c -= 2; |
35c9049b | 223 | if (xfer->tx_buf != NULL) |
31804f63 | 224 | spi100k_write_data(spi->master, word_len, *tx++); |
35c9049b | 225 | if (xfer->rx_buf != NULL) |
31804f63 JH |
226 | *rx++ = spi100k_read_data(spi->master, word_len); |
227 | } while (c); | |
35c9049b CM |
228 | } else if (word_len <= 32) { |
229 | u32 *rx; | |
230 | const u32 *tx; | |
231 | ||
232 | rx = xfer->rx_buf; | |
233 | tx = xfer->tx_buf; | |
234 | do { | |
31804f63 | 235 | c -= 4; |
35c9049b | 236 | if (xfer->tx_buf != NULL) |
31804f63 | 237 | spi100k_write_data(spi->master, word_len, *tx); |
35c9049b | 238 | if (xfer->rx_buf != NULL) |
31804f63 JH |
239 | *rx = spi100k_read_data(spi->master, word_len); |
240 | } while (c); | |
35c9049b CM |
241 | } |
242 | return count - c; | |
243 | } | |
244 | ||
245 | /* called only when no transfer is active to this device */ | |
246 | static int omap1_spi100k_setup_transfer(struct spi_device *spi, | |
247 | struct spi_transfer *t) | |
248 | { | |
249 | struct omap1_spi100k *spi100k = spi_master_get_devdata(spi->master); | |
250 | struct omap1_spi100k_cs *cs = spi->controller_state; | |
251 | u8 word_len = spi->bits_per_word; | |
252 | ||
253 | if (t != NULL && t->bits_per_word) | |
254 | word_len = t->bits_per_word; | |
255 | if (!word_len) | |
256 | word_len = 8; | |
257 | ||
258 | if (spi->bits_per_word > 32) | |
259 | return -EINVAL; | |
260 | cs->word_len = word_len; | |
261 | ||
262 | /* SPI init before transfer */ | |
263 | writew(0x3e , spi100k->base + SPI_SETUP1); | |
264 | writew(0x00 , spi100k->base + SPI_STATUS); | |
265 | writew(0x3e , spi100k->base + SPI_CTRL); | |
266 | ||
267 | return 0; | |
268 | } | |
269 | ||
270 | /* the spi->mode bits understood by this driver: */ | |
271 | #define MODEBITS (SPI_CPOL | SPI_CPHA | SPI_CS_HIGH) | |
272 | ||
273 | static int omap1_spi100k_setup(struct spi_device *spi) | |
274 | { | |
275 | int ret; | |
276 | struct omap1_spi100k *spi100k; | |
277 | struct omap1_spi100k_cs *cs = spi->controller_state; | |
278 | ||
35c9049b CM |
279 | spi100k = spi_master_get_devdata(spi->master); |
280 | ||
281 | if (!cs) { | |
d1c18caa | 282 | cs = devm_kzalloc(&spi->dev, sizeof(*cs), GFP_KERNEL); |
35c9049b CM |
283 | if (!cs) |
284 | return -ENOMEM; | |
285 | cs->base = spi100k->base + spi->chip_select * 0x14; | |
286 | spi->controller_state = cs; | |
287 | } | |
288 | ||
289 | spi100k_open(spi->master); | |
290 | ||
13cd19e8 MB |
291 | clk_prepare_enable(spi100k->ick); |
292 | clk_prepare_enable(spi100k->fck); | |
35c9049b CM |
293 | |
294 | ret = omap1_spi100k_setup_transfer(spi, NULL); | |
295 | ||
13cd19e8 MB |
296 | clk_disable_unprepare(spi100k->ick); |
297 | clk_disable_unprepare(spi100k->fck); | |
35c9049b CM |
298 | |
299 | return ret; | |
300 | } | |
301 | ||
da60b855 MB |
302 | static int omap1_spi100k_prepare_hardware(struct spi_master *master) |
303 | { | |
304 | struct omap1_spi100k *spi100k = spi_master_get_devdata(master); | |
305 | ||
13cd19e8 MB |
306 | clk_prepare_enable(spi100k->ick); |
307 | clk_prepare_enable(spi100k->fck); | |
da60b855 MB |
308 | |
309 | return 0; | |
310 | } | |
311 | ||
e8153ab3 MB |
312 | static int omap1_spi100k_transfer_one_message(struct spi_master *master, |
313 | struct spi_message *m) | |
314 | { | |
315 | struct omap1_spi100k *spi100k = spi_master_get_devdata(master); | |
316 | struct spi_device *spi = m->spi; | |
317 | struct spi_transfer *t = NULL; | |
318 | int cs_active = 0; | |
319 | int par_override = 0; | |
320 | int status = 0; | |
321 | ||
322 | list_for_each_entry(t, &m->transfers, transfer_list) { | |
323 | if (t->tx_buf == NULL && t->rx_buf == NULL && t->len) { | |
324 | status = -EINVAL; | |
325 | break; | |
326 | } | |
327 | if (par_override || t->speed_hz || t->bits_per_word) { | |
328 | par_override = 1; | |
329 | status = omap1_spi100k_setup_transfer(spi, t); | |
330 | if (status < 0) | |
331 | break; | |
332 | if (!t->speed_hz && !t->bits_per_word) | |
333 | par_override = 0; | |
334 | } | |
335 | ||
336 | if (!cs_active) { | |
337 | omap1_spi100k_force_cs(spi100k, 1); | |
338 | cs_active = 1; | |
339 | } | |
340 | ||
341 | if (t->len) { | |
342 | unsigned count; | |
343 | ||
344 | count = omap1_spi100k_txrx_pio(spi, t); | |
345 | m->actual_length += count; | |
346 | ||
347 | if (count != t->len) { | |
348 | status = -EIO; | |
349 | break; | |
350 | } | |
351 | } | |
352 | ||
353 | if (t->delay_usecs) | |
354 | udelay(t->delay_usecs); | |
355 | ||
356 | /* ignore the "leave it on after last xfer" hint */ | |
357 | ||
358 | if (t->cs_change) { | |
359 | omap1_spi100k_force_cs(spi100k, 0); | |
360 | cs_active = 0; | |
361 | } | |
362 | } | |
363 | ||
364 | /* Restore defaults if they were overriden */ | |
365 | if (par_override) { | |
366 | par_override = 0; | |
367 | status = omap1_spi100k_setup_transfer(spi, NULL); | |
368 | } | |
369 | ||
370 | if (cs_active) | |
371 | omap1_spi100k_force_cs(spi100k, 0); | |
372 | ||
373 | m->status = status; | |
da60b855 MB |
374 | |
375 | spi_finalize_current_message(master); | |
e8153ab3 MB |
376 | |
377 | return status; | |
378 | } | |
379 | ||
da60b855 | 380 | static int omap1_spi100k_unprepare_hardware(struct spi_master *master) |
35c9049b | 381 | { |
da60b855 | 382 | struct omap1_spi100k *spi100k = spi_master_get_devdata(master); |
35c9049b | 383 | |
13cd19e8 MB |
384 | clk_disable_unprepare(spi100k->ick); |
385 | clk_disable_unprepare(spi100k->fck); | |
35c9049b CM |
386 | |
387 | return 0; | |
388 | } | |
389 | ||
fd4a319b | 390 | static int omap1_spi100k_probe(struct platform_device *pdev) |
35c9049b CM |
391 | { |
392 | struct spi_master *master; | |
393 | struct omap1_spi100k *spi100k; | |
394 | int status = 0; | |
395 | ||
396 | if (!pdev->id) | |
397 | return -EINVAL; | |
398 | ||
31804f63 | 399 | master = spi_alloc_master(&pdev->dev, sizeof(*spi100k)); |
35c9049b CM |
400 | if (master == NULL) { |
401 | dev_dbg(&pdev->dev, "master allocation failed\n"); | |
402 | return -ENOMEM; | |
403 | } | |
404 | ||
405 | if (pdev->id != -1) | |
31804f63 | 406 | master->bus_num = pdev->id; |
35c9049b CM |
407 | |
408 | master->setup = omap1_spi100k_setup; | |
da60b855 MB |
409 | master->transfer_one_message = omap1_spi100k_transfer_one_message; |
410 | master->prepare_transfer_hardware = omap1_spi100k_prepare_hardware; | |
411 | master->unprepare_transfer_hardware = omap1_spi100k_unprepare_hardware; | |
35c9049b CM |
412 | master->cleanup = NULL; |
413 | master->num_chipselect = 2; | |
414 | master->mode_bits = MODEBITS; | |
24778be2 | 415 | master->bits_per_word_mask = SPI_BPW_RANGE_MASK(4, 32); |
69ea672a MB |
416 | master->min_speed_hz = OMAP1_SPI100K_MAX_FREQ/(1<<16); |
417 | master->max_speed_hz = OMAP1_SPI100K_MAX_FREQ; | |
35c9049b | 418 | |
35c9049b | 419 | spi100k = spi_master_get_devdata(master); |
35c9049b CM |
420 | |
421 | /* | |
422 | * The memory region base address is taken as the platform_data. | |
423 | * You should allocate this with ioremap() before initializing | |
424 | * the SPI. | |
425 | */ | |
8074cf06 | 426 | spi100k->base = (void __iomem *)dev_get_platdata(&pdev->dev); |
35c9049b | 427 | |
022a9412 | 428 | spi100k->ick = devm_clk_get(&pdev->dev, "ick"); |
35c9049b CM |
429 | if (IS_ERR(spi100k->ick)) { |
430 | dev_dbg(&pdev->dev, "can't get spi100k_ick\n"); | |
431 | status = PTR_ERR(spi100k->ick); | |
022a9412 | 432 | goto err; |
35c9049b CM |
433 | } |
434 | ||
022a9412 | 435 | spi100k->fck = devm_clk_get(&pdev->dev, "fck"); |
35c9049b CM |
436 | if (IS_ERR(spi100k->fck)) { |
437 | dev_dbg(&pdev->dev, "can't get spi100k_fck\n"); | |
438 | status = PTR_ERR(spi100k->fck); | |
022a9412 | 439 | goto err; |
35c9049b CM |
440 | } |
441 | ||
5c4c5c7b | 442 | status = devm_spi_register_master(&pdev->dev, master); |
35c9049b | 443 | if (status < 0) |
022a9412 | 444 | goto err; |
35c9049b | 445 | |
35c9049b CM |
446 | return status; |
447 | ||
022a9412 | 448 | err: |
35c9049b CM |
449 | spi_master_put(master); |
450 | return status; | |
451 | } | |
452 | ||
35c9049b CM |
453 | static struct platform_driver omap1_spi100k_driver = { |
454 | .driver = { | |
455 | .name = "omap1_spi100k", | |
35c9049b | 456 | }, |
2d0c6148 | 457 | .probe = omap1_spi100k_probe, |
35c9049b CM |
458 | }; |
459 | ||
2d0c6148 | 460 | module_platform_driver(omap1_spi100k_driver); |
35c9049b CM |
461 | |
462 | MODULE_DESCRIPTION("OMAP7xx SPI 100k controller driver"); | |
463 | MODULE_AUTHOR("Fabrice Crohas <fcrohas@gmail.com>"); | |
464 | MODULE_LICENSE("GPL"); |