diff options
author | Kuninori Morimoto <kuninori.morimoto.gx@renesas.com> | 2012-03-01 20:10:17 -0500 |
---|---|---|
committer | Grant Likely <grant.likely@secretlab.ca> | 2012-03-09 11:50:09 -0500 |
commit | d1c8bbd793e4c2f346f8788ad312f5b5b530aff5 (patch) | |
tree | 136cf736f0a21f4414fa26ea480848e48e9aadbe /drivers/spi | |
parent | ffbbdd21329f3e15eeca6df2d4bc11c04d9d91c0 (diff) |
spi: Add SuperH HSPI prototype driver
This patch adds SuperH HSPI driver.
It is still prototype driver, but has enough function at this point.
Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Signed-off-by: Grant Likely <grant.likely@secretlab.ca>
Diffstat (limited to 'drivers/spi')
-rw-r--r-- | drivers/spi/Kconfig | 6 | ||||
-rw-r--r-- | drivers/spi/Makefile | 1 | ||||
-rw-r--r-- | drivers/spi/spi-sh-hspi.c | 364 |
3 files changed, 371 insertions, 0 deletions
diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 7609c9cecd35..e2083f6d2026 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig | |||
@@ -330,6 +330,12 @@ config SPI_SH_SCI | |||
330 | help | 330 | help |
331 | SPI driver for SuperH SCI blocks. | 331 | SPI driver for SuperH SCI blocks. |
332 | 332 | ||
333 | config SPI_SH_HSPI | ||
334 | tristate "SuperH HSPI controller" | ||
335 | depends on ARCH_SHMOBILE | ||
336 | help | ||
337 | SPI driver for SuperH HSPI blocks. | ||
338 | |||
333 | config SPI_STMP3XXX | 339 | config SPI_STMP3XXX |
334 | tristate "Freescale STMP37xx/378x SPI/SSP controller" | 340 | tristate "Freescale STMP37xx/378x SPI/SSP controller" |
335 | depends on ARCH_STMP3XXX && SPI_MASTER | 341 | depends on ARCH_STMP3XXX && SPI_MASTER |
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index 326e6a1ac0ca..d9a7aed4ae9a 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile | |||
@@ -50,6 +50,7 @@ spi-s3c24xx-hw-y := spi-s3c24xx.o | |||
50 | spi-s3c24xx-hw-$(CONFIG_SPI_S3C24XX_FIQ) += spi-s3c24xx-fiq.o | 50 | spi-s3c24xx-hw-$(CONFIG_SPI_S3C24XX_FIQ) += spi-s3c24xx-fiq.o |
51 | obj-$(CONFIG_SPI_S3C64XX) += spi-s3c64xx.o | 51 | obj-$(CONFIG_SPI_S3C64XX) += spi-s3c64xx.o |
52 | obj-$(CONFIG_SPI_SH) += spi-sh.o | 52 | obj-$(CONFIG_SPI_SH) += spi-sh.o |
53 | obj-$(CONFIG_SPI_SH_HSPI) += spi-sh-hspi.o | ||
53 | obj-$(CONFIG_SPI_SH_MSIOF) += spi-sh-msiof.o | 54 | obj-$(CONFIG_SPI_SH_MSIOF) += spi-sh-msiof.o |
54 | obj-$(CONFIG_SPI_SH_SCI) += spi-sh-sci.o | 55 | obj-$(CONFIG_SPI_SH_SCI) += spi-sh-sci.o |
55 | obj-$(CONFIG_SPI_STMP3XXX) += spi-stmp.o | 56 | obj-$(CONFIG_SPI_STMP3XXX) += spi-stmp.o |
diff --git a/drivers/spi/spi-sh-hspi.c b/drivers/spi/spi-sh-hspi.c new file mode 100644 index 000000000000..8356ec8cfda2 --- /dev/null +++ b/drivers/spi/spi-sh-hspi.c | |||
@@ -0,0 +1,364 @@ | |||
1 | /* | ||
2 | * SuperH HSPI bus driver | ||
3 | * | ||
4 | * Copyright (C) 2011 Kuninori Morimoto | ||
5 | * | ||
6 | * Based on spi-sh.c: | ||
7 | * Based on pxa2xx_spi.c: | ||
8 | * Copyright (C) 2011 Renesas Solutions Corp. | ||
9 | * Copyright (C) 2005 Stephen Street / StreetFire Sound Labs | ||
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 as published by | ||
13 | * the Free Software Foundation; version 2 of the License. | ||
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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | ||
23 | * | ||
24 | */ | ||
25 | #include <linux/module.h> | ||
26 | #include <linux/kernel.h> | ||
27 | #include <linux/timer.h> | ||
28 | #include <linux/delay.h> | ||
29 | #include <linux/list.h> | ||
30 | #include <linux/workqueue.h> | ||
31 | #include <linux/interrupt.h> | ||
32 | #include <linux/platform_device.h> | ||
33 | #include <linux/pm_runtime.h> | ||
34 | #include <linux/io.h> | ||
35 | #include <linux/spi/spi.h> | ||
36 | #include <linux/spi/sh_hspi.h> | ||
37 | |||
38 | #define SPCR 0x00 | ||
39 | #define SPSR 0x04 | ||
40 | #define SPSCR 0x08 | ||
41 | #define SPTBR 0x0C | ||
42 | #define SPRBR 0x10 | ||
43 | #define SPCR2 0x14 | ||
44 | |||
45 | /* SPSR */ | ||
46 | #define RXFL (1 << 2) | ||
47 | |||
48 | #define hspi2info(h) (h->dev->platform_data) | ||
49 | |||
50 | struct hspi_priv { | ||
51 | void __iomem *addr; | ||
52 | struct spi_master *master; | ||
53 | struct list_head queue; | ||
54 | struct workqueue_struct *workqueue; | ||
55 | struct work_struct ws; | ||
56 | struct device *dev; | ||
57 | spinlock_t lock; | ||
58 | }; | ||
59 | |||
60 | /* | ||
61 | * basic function | ||
62 | */ | ||
63 | static void hspi_write(struct hspi_priv *hspi, int reg, u32 val) | ||
64 | { | ||
65 | iowrite32(val, hspi->addr + reg); | ||
66 | } | ||
67 | |||
68 | static u32 hspi_read(struct hspi_priv *hspi, int reg) | ||
69 | { | ||
70 | return ioread32(hspi->addr + reg); | ||
71 | } | ||
72 | |||
73 | /* | ||
74 | * transfer function | ||
75 | */ | ||
76 | static int hspi_status_check_timeout(struct hspi_priv *hspi, u32 mask, u32 val) | ||
77 | { | ||
78 | int t = 256; | ||
79 | |||
80 | while (t--) { | ||
81 | if ((mask & hspi_read(hspi, SPSR)) == val) | ||
82 | return 0; | ||
83 | |||
84 | msleep(20); | ||
85 | } | ||
86 | |||
87 | dev_err(hspi->dev, "timeout\n"); | ||
88 | return -ETIMEDOUT; | ||
89 | } | ||
90 | |||
91 | static int hspi_push(struct hspi_priv *hspi, struct spi_message *msg, | ||
92 | struct spi_transfer *t) | ||
93 | { | ||
94 | int i, ret; | ||
95 | u8 *data = (u8 *)t->tx_buf; | ||
96 | |||
97 | /* | ||
98 | * FIXME | ||
99 | * very simple, but polling transfer | ||
100 | */ | ||
101 | for (i = 0; i < t->len; i++) { | ||
102 | /* wait remains */ | ||
103 | ret = hspi_status_check_timeout(hspi, 0x1, 0x0); | ||
104 | if (ret < 0) | ||
105 | return ret; | ||
106 | |||
107 | hspi_write(hspi, SPTBR, (u32)data[i]); | ||
108 | |||
109 | /* wait recive */ | ||
110 | ret = hspi_status_check_timeout(hspi, 0x4, 0x4); | ||
111 | if (ret < 0) | ||
112 | return ret; | ||
113 | |||
114 | /* dummy read */ | ||
115 | hspi_read(hspi, SPRBR); | ||
116 | } | ||
117 | |||
118 | return 0; | ||
119 | } | ||
120 | |||
121 | static int hspi_pop(struct hspi_priv *hspi, struct spi_message *msg, | ||
122 | struct spi_transfer *t) | ||
123 | { | ||
124 | int i, ret; | ||
125 | u8 *data = (u8 *)t->rx_buf; | ||
126 | |||
127 | /* | ||
128 | * FIXME | ||
129 | * very simple, but polling receive | ||
130 | */ | ||
131 | for (i = 0; i < t->len; i++) { | ||
132 | /* wait remains */ | ||
133 | ret = hspi_status_check_timeout(hspi, 0x1, 0); | ||
134 | if (ret < 0) | ||
135 | return ret; | ||
136 | |||
137 | /* dummy write */ | ||
138 | hspi_write(hspi, SPTBR, 0x0); | ||
139 | |||
140 | /* wait recive */ | ||
141 | ret = hspi_status_check_timeout(hspi, 0x4, 0x4); | ||
142 | if (ret < 0) | ||
143 | return ret; | ||
144 | |||
145 | data[i] = (u8)hspi_read(hspi, SPRBR); | ||
146 | } | ||
147 | |||
148 | return 0; | ||
149 | } | ||
150 | |||
151 | static void hspi_work(struct work_struct *work) | ||
152 | { | ||
153 | struct hspi_priv *hspi = container_of(work, struct hspi_priv, ws); | ||
154 | struct sh_hspi_info *info = hspi2info(hspi); | ||
155 | struct spi_message *msg; | ||
156 | struct spi_transfer *t; | ||
157 | unsigned long flags; | ||
158 | u32 data; | ||
159 | int ret; | ||
160 | |||
161 | dev_dbg(hspi->dev, "%s\n", __func__); | ||
162 | |||
163 | /************************ pm enable ************************/ | ||
164 | pm_runtime_get_sync(hspi->dev); | ||
165 | |||
166 | /* setup first of all in under pm_runtime */ | ||
167 | data = SH_HSPI_CLK_DIVC(info->flags); | ||
168 | |||
169 | if (info->flags & SH_HSPI_FBS) | ||
170 | data |= 1 << 7; | ||
171 | if (info->flags & SH_HSPI_CLKP_HIGH) | ||
172 | data |= 1 << 6; | ||
173 | if (info->flags & SH_HSPI_IDIV_DIV128) | ||
174 | data |= 1 << 5; | ||
175 | |||
176 | hspi_write(hspi, SPCR, data); | ||
177 | hspi_write(hspi, SPSR, 0x0); | ||
178 | hspi_write(hspi, SPSCR, 0x1); /* master mode */ | ||
179 | |||
180 | while (1) { | ||
181 | msg = NULL; | ||
182 | |||
183 | /************************ spin lock ************************/ | ||
184 | spin_lock_irqsave(&hspi->lock, flags); | ||
185 | if (!list_empty(&hspi->queue)) { | ||
186 | msg = list_entry(hspi->queue.next, | ||
187 | struct spi_message, queue); | ||
188 | list_del_init(&msg->queue); | ||
189 | } | ||
190 | spin_unlock_irqrestore(&hspi->lock, flags); | ||
191 | /************************ spin unlock ************************/ | ||
192 | if (!msg) | ||
193 | break; | ||
194 | |||
195 | ret = 0; | ||
196 | list_for_each_entry(t, &msg->transfers, transfer_list) { | ||
197 | if (t->tx_buf) { | ||
198 | ret = hspi_push(hspi, msg, t); | ||
199 | if (ret < 0) | ||
200 | goto error; | ||
201 | } | ||
202 | if (t->rx_buf) { | ||
203 | ret = hspi_pop(hspi, msg, t); | ||
204 | if (ret < 0) | ||
205 | goto error; | ||
206 | } | ||
207 | msg->actual_length += t->len; | ||
208 | } | ||
209 | error: | ||
210 | msg->status = ret; | ||
211 | msg->complete(msg->context); | ||
212 | } | ||
213 | |||
214 | pm_runtime_put_sync(hspi->dev); | ||
215 | /************************ pm disable ************************/ | ||
216 | |||
217 | return; | ||
218 | } | ||
219 | |||
220 | /* | ||
221 | * spi master function | ||
222 | */ | ||
223 | static int hspi_setup(struct spi_device *spi) | ||
224 | { | ||
225 | struct hspi_priv *hspi = spi_master_get_devdata(spi->master); | ||
226 | struct device *dev = hspi->dev; | ||
227 | |||
228 | if (8 != spi->bits_per_word) { | ||
229 | dev_err(dev, "bits_per_word should be 8\n"); | ||
230 | return -EIO; | ||
231 | } | ||
232 | |||
233 | dev_dbg(dev, "%s setup\n", spi->modalias); | ||
234 | |||
235 | return 0; | ||
236 | } | ||
237 | |||
238 | static void hspi_cleanup(struct spi_device *spi) | ||
239 | { | ||
240 | struct hspi_priv *hspi = spi_master_get_devdata(spi->master); | ||
241 | struct device *dev = hspi->dev; | ||
242 | |||
243 | dev_dbg(dev, "%s cleanup\n", spi->modalias); | ||
244 | } | ||
245 | |||
246 | static int hspi_transfer(struct spi_device *spi, struct spi_message *msg) | ||
247 | { | ||
248 | struct hspi_priv *hspi = spi_master_get_devdata(spi->master); | ||
249 | unsigned long flags; | ||
250 | |||
251 | /************************ spin lock ************************/ | ||
252 | spin_lock_irqsave(&hspi->lock, flags); | ||
253 | |||
254 | msg->actual_length = 0; | ||
255 | msg->status = -EINPROGRESS; | ||
256 | list_add_tail(&msg->queue, &hspi->queue); | ||
257 | |||
258 | spin_unlock_irqrestore(&hspi->lock, flags); | ||
259 | /************************ spin unlock ************************/ | ||
260 | |||
261 | queue_work(hspi->workqueue, &hspi->ws); | ||
262 | |||
263 | return 0; | ||
264 | } | ||
265 | |||
266 | static int __devinit hspi_probe(struct platform_device *pdev) | ||
267 | { | ||
268 | struct resource *res; | ||
269 | struct spi_master *master; | ||
270 | struct hspi_priv *hspi; | ||
271 | int ret; | ||
272 | |||
273 | /* get base addr */ | ||
274 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
275 | if (!res) { | ||
276 | dev_err(&pdev->dev, "invalid resource\n"); | ||
277 | return -EINVAL; | ||
278 | } | ||
279 | |||
280 | master = spi_alloc_master(&pdev->dev, sizeof(*hspi)); | ||
281 | if (!master) { | ||
282 | dev_err(&pdev->dev, "spi_alloc_master error.\n"); | ||
283 | return -ENOMEM; | ||
284 | } | ||
285 | |||
286 | hspi = spi_master_get_devdata(master); | ||
287 | dev_set_drvdata(&pdev->dev, hspi); | ||
288 | |||
289 | /* init hspi */ | ||
290 | hspi->master = master; | ||
291 | hspi->dev = &pdev->dev; | ||
292 | hspi->addr = devm_ioremap(hspi->dev, | ||
293 | res->start, resource_size(res)); | ||
294 | if (!hspi->addr) { | ||
295 | dev_err(&pdev->dev, "ioremap error.\n"); | ||
296 | ret = -ENOMEM; | ||
297 | goto error1; | ||
298 | } | ||
299 | hspi->workqueue = create_singlethread_workqueue(dev_name(&pdev->dev)); | ||
300 | if (!hspi->workqueue) { | ||
301 | dev_err(&pdev->dev, "create workqueue error\n"); | ||
302 | ret = -EBUSY; | ||
303 | goto error2; | ||
304 | } | ||
305 | |||
306 | spin_lock_init(&hspi->lock); | ||
307 | INIT_LIST_HEAD(&hspi->queue); | ||
308 | INIT_WORK(&hspi->ws, hspi_work); | ||
309 | |||
310 | master->num_chipselect = 1; | ||
311 | master->bus_num = pdev->id; | ||
312 | master->setup = hspi_setup; | ||
313 | master->transfer = hspi_transfer; | ||
314 | master->cleanup = hspi_cleanup; | ||
315 | master->mode_bits = SPI_CPOL | SPI_CPHA; | ||
316 | ret = spi_register_master(master); | ||
317 | if (ret < 0) { | ||
318 | dev_err(&pdev->dev, "spi_register_master error.\n"); | ||
319 | goto error3; | ||
320 | } | ||
321 | |||
322 | pm_runtime_enable(&pdev->dev); | ||
323 | |||
324 | dev_info(&pdev->dev, "probed\n"); | ||
325 | |||
326 | return 0; | ||
327 | |||
328 | error3: | ||
329 | destroy_workqueue(hspi->workqueue); | ||
330 | error2: | ||
331 | devm_iounmap(hspi->dev, hspi->addr); | ||
332 | error1: | ||
333 | spi_master_put(master); | ||
334 | |||
335 | return ret; | ||
336 | } | ||
337 | |||
338 | static int __devexit hspi_remove(struct platform_device *pdev) | ||
339 | { | ||
340 | struct hspi_priv *hspi = dev_get_drvdata(&pdev->dev); | ||
341 | |||
342 | pm_runtime_disable(&pdev->dev); | ||
343 | |||
344 | spi_unregister_master(hspi->master); | ||
345 | destroy_workqueue(hspi->workqueue); | ||
346 | devm_iounmap(hspi->dev, hspi->addr); | ||
347 | |||
348 | return 0; | ||
349 | } | ||
350 | |||
351 | static struct platform_driver hspi_driver = { | ||
352 | .probe = hspi_probe, | ||
353 | .remove = __devexit_p(hspi_remove), | ||
354 | .driver = { | ||
355 | .name = "sh-hspi", | ||
356 | .owner = THIS_MODULE, | ||
357 | }, | ||
358 | }; | ||
359 | module_platform_driver(hspi_driver); | ||
360 | |||
361 | MODULE_DESCRIPTION("SuperH HSPI bus driver"); | ||
362 | MODULE_LICENSE("GPL"); | ||
363 | MODULE_AUTHOR("Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>"); | ||
364 | MODULE_ALIAS("platform:sh_spi"); | ||