]> nv-tegra.nvidia Code Review - linux-2.6.git/blob - drivers/spi/spi_altera.c
spi/s3c64xx: Bug fix for SPI with different FIFO level
[linux-2.6.git] / drivers / spi / spi_altera.c
1 /*
2  * Altera SPI driver
3  *
4  * Copyright (C) 2008 Thomas Chou <thomas@wytron.com.tw>
5  *
6  * Based on spi_s3c24xx.c, which is:
7  * Copyright (c) 2006 Ben Dooks
8  * Copyright (c) 2006 Simtec Electronics
9  *      Ben Dooks <ben@simtec.co.uk>
10  *
11  * This program is free software; you can redistribute it and/or modify
12  * it under the terms of the GNU General Public License version 2 as
13  * published by the Free Software Foundation.
14  */
15
16 #include <linux/init.h>
17 #include <linux/interrupt.h>
18 #include <linux/errno.h>
19 #include <linux/platform_device.h>
20 #include <linux/spi/spi.h>
21 #include <linux/spi/spi_bitbang.h>
22 #include <linux/io.h>
23 #include <linux/of.h>
24
25 #define DRV_NAME "spi_altera"
26
27 #define ALTERA_SPI_RXDATA       0
28 #define ALTERA_SPI_TXDATA       4
29 #define ALTERA_SPI_STATUS       8
30 #define ALTERA_SPI_CONTROL      12
31 #define ALTERA_SPI_SLAVE_SEL    20
32
33 #define ALTERA_SPI_STATUS_ROE_MSK       0x8
34 #define ALTERA_SPI_STATUS_TOE_MSK       0x10
35 #define ALTERA_SPI_STATUS_TMT_MSK       0x20
36 #define ALTERA_SPI_STATUS_TRDY_MSK      0x40
37 #define ALTERA_SPI_STATUS_RRDY_MSK      0x80
38 #define ALTERA_SPI_STATUS_E_MSK         0x100
39
40 #define ALTERA_SPI_CONTROL_IROE_MSK     0x8
41 #define ALTERA_SPI_CONTROL_ITOE_MSK     0x10
42 #define ALTERA_SPI_CONTROL_ITRDY_MSK    0x40
43 #define ALTERA_SPI_CONTROL_IRRDY_MSK    0x80
44 #define ALTERA_SPI_CONTROL_IE_MSK       0x100
45 #define ALTERA_SPI_CONTROL_SSO_MSK      0x400
46
47 struct altera_spi {
48         /* bitbang has to be first */
49         struct spi_bitbang bitbang;
50         struct completion done;
51
52         void __iomem *base;
53         int irq;
54         int len;
55         int count;
56         int bytes_per_word;
57         unsigned long imr;
58
59         /* data buffers */
60         const unsigned char *tx;
61         unsigned char *rx;
62 };
63
64 static inline struct altera_spi *altera_spi_to_hw(struct spi_device *sdev)
65 {
66         return spi_master_get_devdata(sdev->master);
67 }
68
69 static void altera_spi_chipsel(struct spi_device *spi, int value)
70 {
71         struct altera_spi *hw = altera_spi_to_hw(spi);
72
73         if (spi->mode & SPI_CS_HIGH) {
74                 switch (value) {
75                 case BITBANG_CS_INACTIVE:
76                         writel(1 << spi->chip_select,
77                                hw->base + ALTERA_SPI_SLAVE_SEL);
78                         hw->imr |= ALTERA_SPI_CONTROL_SSO_MSK;
79                         writel(hw->imr, hw->base + ALTERA_SPI_CONTROL);
80                         break;
81
82                 case BITBANG_CS_ACTIVE:
83                         hw->imr &= ~ALTERA_SPI_CONTROL_SSO_MSK;
84                         writel(hw->imr, hw->base + ALTERA_SPI_CONTROL);
85                         writel(0, hw->base + ALTERA_SPI_SLAVE_SEL);
86                         break;
87                 }
88         } else {
89                 switch (value) {
90                 case BITBANG_CS_INACTIVE:
91                         hw->imr &= ~ALTERA_SPI_CONTROL_SSO_MSK;
92                         writel(hw->imr, hw->base + ALTERA_SPI_CONTROL);
93                         break;
94
95                 case BITBANG_CS_ACTIVE:
96                         writel(1 << spi->chip_select,
97                                hw->base + ALTERA_SPI_SLAVE_SEL);
98                         hw->imr |= ALTERA_SPI_CONTROL_SSO_MSK;
99                         writel(hw->imr, hw->base + ALTERA_SPI_CONTROL);
100                         break;
101                 }
102         }
103 }
104
105 static int altera_spi_setupxfer(struct spi_device *spi, struct spi_transfer *t)
106 {
107         return 0;
108 }
109
110 static int altera_spi_setup(struct spi_device *spi)
111 {
112         return 0;
113 }
114
115 static inline unsigned int hw_txbyte(struct altera_spi *hw, int count)
116 {
117         if (hw->tx) {
118                 switch (hw->bytes_per_word) {
119                 case 1:
120                         return hw->tx[count];
121                 case 2:
122                         return (hw->tx[count * 2]
123                                 | (hw->tx[count * 2 + 1] << 8));
124                 }
125         }
126         return 0;
127 }
128
129 static int altera_spi_txrx(struct spi_device *spi, struct spi_transfer *t)
130 {
131         struct altera_spi *hw = altera_spi_to_hw(spi);
132
133         hw->tx = t->tx_buf;
134         hw->rx = t->rx_buf;
135         hw->count = 0;
136         hw->bytes_per_word = (t->bits_per_word ? : spi->bits_per_word) / 8;
137         hw->len = t->len / hw->bytes_per_word;
138
139         if (hw->irq >= 0) {
140                 /* enable receive interrupt */
141                 hw->imr |= ALTERA_SPI_CONTROL_IRRDY_MSK;
142                 writel(hw->imr, hw->base + ALTERA_SPI_CONTROL);
143
144                 /* send the first byte */
145                 writel(hw_txbyte(hw, 0), hw->base + ALTERA_SPI_TXDATA);
146
147                 wait_for_completion(&hw->done);
148                 /* disable receive interrupt */
149                 hw->imr &= ~ALTERA_SPI_CONTROL_IRRDY_MSK;
150                 writel(hw->imr, hw->base + ALTERA_SPI_CONTROL);
151         } else {
152                 /* send the first byte */
153                 writel(hw_txbyte(hw, 0), hw->base + ALTERA_SPI_TXDATA);
154
155                 while (1) {
156                         unsigned int rxd;
157
158                         while (!(readl(hw->base + ALTERA_SPI_STATUS) &
159                                  ALTERA_SPI_STATUS_RRDY_MSK))
160                                 cpu_relax();
161
162                         rxd = readl(hw->base + ALTERA_SPI_RXDATA);
163                         if (hw->rx) {
164                                 switch (hw->bytes_per_word) {
165                                 case 1:
166                                         hw->rx[hw->count] = rxd;
167                                         break;
168                                 case 2:
169                                         hw->rx[hw->count * 2] = rxd;
170                                         hw->rx[hw->count * 2 + 1] = rxd >> 8;
171                                         break;
172                                 }
173                         }
174
175                         hw->count++;
176
177                         if (hw->count < hw->len)
178                                 writel(hw_txbyte(hw, hw->count),
179                                        hw->base + ALTERA_SPI_TXDATA);
180                         else
181                                 break;
182                 }
183
184         }
185
186         return hw->count * hw->bytes_per_word;
187 }
188
189 static irqreturn_t altera_spi_irq(int irq, void *dev)
190 {
191         struct altera_spi *hw = dev;
192         unsigned int rxd;
193
194         rxd = readl(hw->base + ALTERA_SPI_RXDATA);
195         if (hw->rx) {
196                 switch (hw->bytes_per_word) {
197                 case 1:
198                         hw->rx[hw->count] = rxd;
199                         break;
200                 case 2:
201                         hw->rx[hw->count * 2] = rxd;
202                         hw->rx[hw->count * 2 + 1] = rxd >> 8;
203                         break;
204                 }
205         }
206
207         hw->count++;
208
209         if (hw->count < hw->len)
210                 writel(hw_txbyte(hw, hw->count), hw->base + ALTERA_SPI_TXDATA);
211         else
212                 complete(&hw->done);
213
214         return IRQ_HANDLED;
215 }
216
217 static int __devinit altera_spi_probe(struct platform_device *pdev)
218 {
219         struct altera_spi_platform_data *platp = pdev->dev.platform_data;
220         struct altera_spi *hw;
221         struct spi_master *master;
222         struct resource *res;
223         int err = -ENODEV;
224
225         master = spi_alloc_master(&pdev->dev, sizeof(struct altera_spi));
226         if (!master)
227                 return err;
228
229         /* setup the master state. */
230         master->bus_num = pdev->id;
231         master->num_chipselect = 16;
232         master->mode_bits = SPI_CS_HIGH;
233         master->setup = altera_spi_setup;
234
235         hw = spi_master_get_devdata(master);
236         platform_set_drvdata(pdev, hw);
237
238         /* setup the state for the bitbang driver */
239         hw->bitbang.master = spi_master_get(master);
240         if (!hw->bitbang.master)
241                 return err;
242         hw->bitbang.setup_transfer = altera_spi_setupxfer;
243         hw->bitbang.chipselect = altera_spi_chipsel;
244         hw->bitbang.txrx_bufs = altera_spi_txrx;
245
246         /* find and map our resources */
247         res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
248         if (!res)
249                 goto exit_busy;
250         if (!devm_request_mem_region(&pdev->dev, res->start, resource_size(res),
251                                      pdev->name))
252                 goto exit_busy;
253         hw->base = devm_ioremap_nocache(&pdev->dev, res->start,
254                                         resource_size(res));
255         if (!hw->base)
256                 goto exit_busy;
257         /* program defaults into the registers */
258         hw->imr = 0;            /* disable spi interrupts */
259         writel(hw->imr, hw->base + ALTERA_SPI_CONTROL);
260         writel(0, hw->base + ALTERA_SPI_STATUS);        /* clear status reg */
261         if (readl(hw->base + ALTERA_SPI_STATUS) & ALTERA_SPI_STATUS_RRDY_MSK)
262                 readl(hw->base + ALTERA_SPI_RXDATA);    /* flush rxdata */
263         /* irq is optional */
264         hw->irq = platform_get_irq(pdev, 0);
265         if (hw->irq >= 0) {
266                 init_completion(&hw->done);
267                 err = devm_request_irq(&pdev->dev, hw->irq, altera_spi_irq, 0,
268                                        pdev->name, hw);
269                 if (err)
270                         goto exit;
271         }
272         /* find platform data */
273         if (!platp)
274                 hw->bitbang.master->dev.of_node = pdev->dev.of_node;
275
276         /* register our spi controller */
277         err = spi_bitbang_start(&hw->bitbang);
278         if (err)
279                 goto exit;
280         dev_info(&pdev->dev, "base %p, irq %d\n", hw->base, hw->irq);
281
282         return 0;
283
284 exit_busy:
285         err = -EBUSY;
286 exit:
287         platform_set_drvdata(pdev, NULL);
288         spi_master_put(master);
289         return err;
290 }
291
292 static int __devexit altera_spi_remove(struct platform_device *dev)
293 {
294         struct altera_spi *hw = platform_get_drvdata(dev);
295         struct spi_master *master = hw->bitbang.master;
296
297         spi_bitbang_stop(&hw->bitbang);
298         platform_set_drvdata(dev, NULL);
299         spi_master_put(master);
300         return 0;
301 }
302
303 #ifdef CONFIG_OF
304 static const struct of_device_id altera_spi_match[] = {
305         { .compatible = "ALTR,spi-1.0", },
306         {},
307 };
308 MODULE_DEVICE_TABLE(of, altera_spi_match);
309 #else /* CONFIG_OF */
310 #define altera_spi_match NULL
311 #endif /* CONFIG_OF */
312
313 static struct platform_driver altera_spi_driver = {
314         .probe = altera_spi_probe,
315         .remove = __devexit_p(altera_spi_remove),
316         .driver = {
317                 .name = DRV_NAME,
318                 .owner = THIS_MODULE,
319                 .pm = NULL,
320                 .of_match_table = altera_spi_match,
321         },
322 };
323
324 static int __init altera_spi_init(void)
325 {
326         return platform_driver_register(&altera_spi_driver);
327 }
328 module_init(altera_spi_init);
329
330 static void __exit altera_spi_exit(void)
331 {
332         platform_driver_unregister(&altera_spi_driver);
333 }
334 module_exit(altera_spi_exit);
335
336 MODULE_DESCRIPTION("Altera SPI driver");
337 MODULE_AUTHOR("Thomas Chou <thomas@wytron.com.tw>");
338 MODULE_LICENSE("GPL");
339 MODULE_ALIAS("platform:" DRV_NAME);