diff options
author | Marek Vasut <marex@denx.de> | 2012-08-03 11:26:11 -0400 |
---|---|---|
committer | Mark Brown <broonie@opensource.wolfsonmicro.com> | 2012-08-17 17:54:10 -0400 |
commit | 646781d3325cdcc648b8eeb3b7cda393bcb78659 (patch) | |
tree | f3274031ffbc47fa66b341e4cadfa857b5a20660 | |
parent | 1308239858c33feeeb67003d08c754ee181f33cf (diff) |
spi/mxs: Add SPI driver for mx233/mx28
This is slightly reworked version of the SPI driver.
Support for DT has been added and it's been converted
to queued API.
Based on previous attempt by:
Fabio Estevam <fabio.estevam@freescale.com>
Signed-off-by: Fabio Estevam <fabio.estevam@freescale.com>
Signed-off-by: Marek Vasut <marex@denx.de>
Acked-by: Chris Ball <cjb@laptop.org>
Acked-by: Shawn Guo <shawn.guo@linaro.org>
Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com>
-rw-r--r-- | drivers/spi/Kconfig | 7 | ||||
-rw-r--r-- | drivers/spi/Makefile | 1 | ||||
-rw-r--r-- | drivers/spi/spi-mxs.c | 431 |
3 files changed, 439 insertions, 0 deletions
diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 5f84b5563c2d..6992b30b4e42 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig | |||
@@ -364,6 +364,13 @@ config SPI_STMP3XXX | |||
364 | help | 364 | help |
365 | SPI driver for Freescale STMP37xx/378x SoC SSP interface | 365 | SPI driver for Freescale STMP37xx/378x SoC SSP interface |
366 | 366 | ||
367 | config SPI_MXS | ||
368 | tristate "Freescale MXS SPI controller" | ||
369 | depends on ARCH_MXS | ||
370 | select STMP_DEVICE | ||
371 | help | ||
372 | SPI driver for Freescale MXS devices. | ||
373 | |||
367 | config SPI_TEGRA | 374 | config SPI_TEGRA |
368 | tristate "Nvidia Tegra SPI controller" | 375 | tristate "Nvidia Tegra SPI controller" |
369 | depends on ARCH_TEGRA && (TEGRA_SYSTEM_DMA || TEGRA20_APB_DMA) | 376 | depends on ARCH_TEGRA && (TEGRA_SYSTEM_DMA || TEGRA20_APB_DMA) |
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index 3920dcf4c740..3b72d87b3309 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile | |||
@@ -36,6 +36,7 @@ obj-$(CONFIG_SPI_LM70_LLP) += spi-lm70llp.o | |||
36 | obj-$(CONFIG_SPI_MPC512x_PSC) += spi-mpc512x-psc.o | 36 | obj-$(CONFIG_SPI_MPC512x_PSC) += spi-mpc512x-psc.o |
37 | obj-$(CONFIG_SPI_MPC52xx_PSC) += spi-mpc52xx-psc.o | 37 | obj-$(CONFIG_SPI_MPC52xx_PSC) += spi-mpc52xx-psc.o |
38 | obj-$(CONFIG_SPI_MPC52xx) += spi-mpc52xx.o | 38 | obj-$(CONFIG_SPI_MPC52xx) += spi-mpc52xx.o |
39 | obj-$(CONFIG_SPI_MXS) += spi-mxs.o | ||
39 | obj-$(CONFIG_SPI_NUC900) += spi-nuc900.o | 40 | obj-$(CONFIG_SPI_NUC900) += spi-nuc900.o |
40 | obj-$(CONFIG_SPI_OC_TINY) += spi-oc-tiny.o | 41 | obj-$(CONFIG_SPI_OC_TINY) += spi-oc-tiny.o |
41 | obj-$(CONFIG_SPI_OMAP_UWIRE) += spi-omap-uwire.o | 42 | obj-$(CONFIG_SPI_OMAP_UWIRE) += spi-omap-uwire.o |
diff --git a/drivers/spi/spi-mxs.c b/drivers/spi/spi-mxs.c new file mode 100644 index 000000000000..7bf826f5af59 --- /dev/null +++ b/drivers/spi/spi-mxs.c | |||
@@ -0,0 +1,431 @@ | |||
1 | /* | ||
2 | * Freescale MXS SPI master driver | ||
3 | * | ||
4 | * Copyright 2012 DENX Software Engineering, GmbH. | ||
5 | * Copyright 2012 Freescale Semiconductor, Inc. | ||
6 | * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. | ||
7 | * | ||
8 | * Rework and transition to new API by: | ||
9 | * Marek Vasut <marex@denx.de> | ||
10 | * | ||
11 | * Based on previous attempt by: | ||
12 | * Fabio Estevam <fabio.estevam@freescale.com> | ||
13 | * | ||
14 | * Based on code from U-Boot bootloader by: | ||
15 | * Marek Vasut <marex@denx.de> | ||
16 | * | ||
17 | * Based on spi-stmp.c, which is: | ||
18 | * Author: Dmitry Pervushin <dimka@embeddedalley.com> | ||
19 | * | ||
20 | * This program is free software; you can redistribute it and/or modify | ||
21 | * it under the terms of the GNU General Public License as published by | ||
22 | * the Free Software Foundation; either version 2 of the License, or | ||
23 | * (at your option) any later version. | ||
24 | * | ||
25 | * This program is distributed in the hope that it will be useful, | ||
26 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
27 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
28 | * GNU General Public License for more details. | ||
29 | */ | ||
30 | |||
31 | #include <linux/kernel.h> | ||
32 | #include <linux/init.h> | ||
33 | #include <linux/ioport.h> | ||
34 | #include <linux/of.h> | ||
35 | #include <linux/of_device.h> | ||
36 | #include <linux/of_gpio.h> | ||
37 | #include <linux/platform_device.h> | ||
38 | #include <linux/delay.h> | ||
39 | #include <linux/interrupt.h> | ||
40 | #include <linux/dma-mapping.h> | ||
41 | #include <linux/dmaengine.h> | ||
42 | #include <linux/highmem.h> | ||
43 | #include <linux/clk.h> | ||
44 | #include <linux/err.h> | ||
45 | #include <linux/completion.h> | ||
46 | #include <linux/gpio.h> | ||
47 | #include <linux/regulator/consumer.h> | ||
48 | #include <linux/module.h> | ||
49 | #include <linux/fsl/mxs-dma.h> | ||
50 | #include <linux/pinctrl/consumer.h> | ||
51 | #include <linux/stmp_device.h> | ||
52 | #include <linux/spi/spi.h> | ||
53 | #include <linux/spi/mxs-spi.h> | ||
54 | |||
55 | #define DRIVER_NAME "mxs-spi" | ||
56 | |||
57 | #define SSP_TIMEOUT 1000 /* 1000 ms */ | ||
58 | |||
59 | struct mxs_spi { | ||
60 | struct mxs_ssp ssp; | ||
61 | }; | ||
62 | |||
63 | static int mxs_spi_setup_transfer(struct spi_device *dev, | ||
64 | struct spi_transfer *t) | ||
65 | { | ||
66 | struct mxs_spi *spi = spi_master_get_devdata(dev->master); | ||
67 | struct mxs_ssp *ssp = &spi->ssp; | ||
68 | uint8_t bits_per_word; | ||
69 | uint32_t hz = 0; | ||
70 | |||
71 | bits_per_word = dev->bits_per_word; | ||
72 | if (t && t->bits_per_word) | ||
73 | bits_per_word = t->bits_per_word; | ||
74 | |||
75 | if (bits_per_word != 8) { | ||
76 | dev_err(&dev->dev, "%s, unsupported bits_per_word=%d\n", | ||
77 | __func__, bits_per_word); | ||
78 | return -EINVAL; | ||
79 | } | ||
80 | |||
81 | hz = dev->max_speed_hz; | ||
82 | if (t && t->speed_hz) | ||
83 | hz = min(hz, t->speed_hz); | ||
84 | if (hz == 0) { | ||
85 | dev_err(&dev->dev, "Cannot continue with zero clock\n"); | ||
86 | return -EINVAL; | ||
87 | } | ||
88 | |||
89 | mxs_ssp_set_clk_rate(ssp, hz); | ||
90 | |||
91 | writel(BF_SSP_CTRL1_SSP_MODE(BV_SSP_CTRL1_SSP_MODE__SPI) | | ||
92 | BF_SSP_CTRL1_WORD_LENGTH | ||
93 | (BV_SSP_CTRL1_WORD_LENGTH__EIGHT_BITS) | | ||
94 | ((dev->mode & SPI_CPOL) ? BM_SSP_CTRL1_POLARITY : 0) | | ||
95 | ((dev->mode & SPI_CPHA) ? BM_SSP_CTRL1_PHASE : 0), | ||
96 | ssp->base + HW_SSP_CTRL1(ssp)); | ||
97 | |||
98 | writel(0x0, ssp->base + HW_SSP_CMD0); | ||
99 | writel(0x0, ssp->base + HW_SSP_CMD1); | ||
100 | |||
101 | return 0; | ||
102 | } | ||
103 | |||
104 | static int mxs_spi_setup(struct spi_device *dev) | ||
105 | { | ||
106 | int err = 0; | ||
107 | |||
108 | if (!dev->bits_per_word) | ||
109 | dev->bits_per_word = 8; | ||
110 | |||
111 | if (dev->mode & ~(SPI_CPOL | SPI_CPHA)) | ||
112 | return -EINVAL; | ||
113 | |||
114 | err = mxs_spi_setup_transfer(dev, NULL); | ||
115 | if (err) { | ||
116 | dev_err(&dev->dev, | ||
117 | "Failed to setup transfer, error = %d\n", err); | ||
118 | } | ||
119 | |||
120 | return err; | ||
121 | } | ||
122 | |||
123 | static uint32_t mxs_spi_cs_to_reg(unsigned cs) | ||
124 | { | ||
125 | uint32_t select = 0; | ||
126 | |||
127 | /* | ||
128 | * i.MX28 Datasheet: 17.10.1: HW_SSP_CTRL0 | ||
129 | * | ||
130 | * The bits BM_SSP_CTRL0_WAIT_FOR_CMD and BM_SSP_CTRL0_WAIT_FOR_IRQ | ||
131 | * in HW_SSP_CTRL0 register do have multiple usage, please refer to | ||
132 | * the datasheet for further details. In SPI mode, they are used to | ||
133 | * toggle the chip-select lines (nCS pins). | ||
134 | */ | ||
135 | if (cs & 1) | ||
136 | select |= BM_SSP_CTRL0_WAIT_FOR_CMD; | ||
137 | if (cs & 2) | ||
138 | select |= BM_SSP_CTRL0_WAIT_FOR_IRQ; | ||
139 | |||
140 | return select; | ||
141 | } | ||
142 | |||
143 | static void mxs_spi_set_cs(struct mxs_spi *spi, unsigned cs) | ||
144 | { | ||
145 | const uint32_t mask = | ||
146 | BM_SSP_CTRL0_WAIT_FOR_CMD | BM_SSP_CTRL0_WAIT_FOR_IRQ; | ||
147 | uint32_t select; | ||
148 | struct mxs_ssp *ssp = &spi->ssp; | ||
149 | |||
150 | writel(mask, ssp->base + HW_SSP_CTRL0 + STMP_OFFSET_REG_CLR); | ||
151 | select = mxs_spi_cs_to_reg(cs); | ||
152 | writel(select, ssp->base + HW_SSP_CTRL0 + STMP_OFFSET_REG_SET); | ||
153 | } | ||
154 | |||
155 | static inline void mxs_spi_enable(struct mxs_spi *spi) | ||
156 | { | ||
157 | struct mxs_ssp *ssp = &spi->ssp; | ||
158 | |||
159 | writel(BM_SSP_CTRL0_LOCK_CS, | ||
160 | ssp->base + HW_SSP_CTRL0 + STMP_OFFSET_REG_SET); | ||
161 | writel(BM_SSP_CTRL0_IGNORE_CRC, | ||
162 | ssp->base + HW_SSP_CTRL0 + STMP_OFFSET_REG_CLR); | ||
163 | } | ||
164 | |||
165 | static inline void mxs_spi_disable(struct mxs_spi *spi) | ||
166 | { | ||
167 | struct mxs_ssp *ssp = &spi->ssp; | ||
168 | |||
169 | writel(BM_SSP_CTRL0_LOCK_CS, | ||
170 | ssp->base + HW_SSP_CTRL0 + STMP_OFFSET_REG_CLR); | ||
171 | writel(BM_SSP_CTRL0_IGNORE_CRC, | ||
172 | ssp->base + HW_SSP_CTRL0 + STMP_OFFSET_REG_SET); | ||
173 | } | ||
174 | |||
175 | static int mxs_ssp_wait(struct mxs_spi *spi, int offset, int mask, bool set) | ||
176 | { | ||
177 | unsigned long timeout = jiffies + msecs_to_jiffies(SSP_TIMEOUT); | ||
178 | struct mxs_ssp *ssp = &spi->ssp; | ||
179 | uint32_t reg; | ||
180 | |||
181 | while (1) { | ||
182 | reg = readl_relaxed(ssp->base + offset); | ||
183 | |||
184 | if (set && ((reg & mask) == mask)) | ||
185 | break; | ||
186 | |||
187 | if (!set && ((~reg & mask) == mask)) | ||
188 | break; | ||
189 | |||
190 | udelay(1); | ||
191 | |||
192 | if (time_after(jiffies, timeout)) | ||
193 | return -ETIMEDOUT; | ||
194 | } | ||
195 | return 0; | ||
196 | } | ||
197 | |||
198 | static int mxs_spi_txrx_pio(struct mxs_spi *spi, int cs, | ||
199 | unsigned char *buf, int len, | ||
200 | int *first, int *last, int write) | ||
201 | { | ||
202 | struct mxs_ssp *ssp = &spi->ssp; | ||
203 | |||
204 | if (*first) | ||
205 | mxs_spi_enable(spi); | ||
206 | |||
207 | mxs_spi_set_cs(spi, cs); | ||
208 | |||
209 | while (len--) { | ||
210 | if (*last && len == 0) | ||
211 | mxs_spi_disable(spi); | ||
212 | |||
213 | if (ssp->devid == IMX23_SSP) { | ||
214 | writel(BM_SSP_CTRL0_XFER_COUNT, | ||
215 | ssp->base + HW_SSP_CTRL0 + STMP_OFFSET_REG_CLR); | ||
216 | writel(1, | ||
217 | ssp->base + HW_SSP_CTRL0 + STMP_OFFSET_REG_SET); | ||
218 | } else { | ||
219 | writel(1, ssp->base + HW_SSP_XFER_SIZE); | ||
220 | } | ||
221 | |||
222 | if (write) | ||
223 | writel(BM_SSP_CTRL0_READ, | ||
224 | ssp->base + HW_SSP_CTRL0 + STMP_OFFSET_REG_CLR); | ||
225 | else | ||
226 | writel(BM_SSP_CTRL0_READ, | ||
227 | ssp->base + HW_SSP_CTRL0 + STMP_OFFSET_REG_SET); | ||
228 | |||
229 | writel(BM_SSP_CTRL0_RUN, | ||
230 | ssp->base + HW_SSP_CTRL0 + STMP_OFFSET_REG_SET); | ||
231 | |||
232 | if (mxs_ssp_wait(spi, HW_SSP_CTRL0, BM_SSP_CTRL0_RUN, 1)) | ||
233 | return -ETIMEDOUT; | ||
234 | |||
235 | if (write) | ||
236 | writel(*buf, ssp->base + HW_SSP_DATA(ssp)); | ||
237 | |||
238 | writel(BM_SSP_CTRL0_DATA_XFER, | ||
239 | ssp->base + HW_SSP_CTRL0 + STMP_OFFSET_REG_SET); | ||
240 | |||
241 | if (!write) { | ||
242 | if (mxs_ssp_wait(spi, HW_SSP_STATUS(ssp), | ||
243 | BM_SSP_STATUS_FIFO_EMPTY, 0)) | ||
244 | return -ETIMEDOUT; | ||
245 | |||
246 | *buf = (readl(ssp->base + HW_SSP_DATA(ssp)) & 0xff); | ||
247 | } | ||
248 | |||
249 | if (mxs_ssp_wait(spi, HW_SSP_CTRL0, BM_SSP_CTRL0_RUN, 0)) | ||
250 | return -ETIMEDOUT; | ||
251 | |||
252 | buf++; | ||
253 | } | ||
254 | |||
255 | if (len <= 0) | ||
256 | return 0; | ||
257 | |||
258 | return -ETIMEDOUT; | ||
259 | } | ||
260 | |||
261 | static int mxs_spi_transfer_one(struct spi_master *master, | ||
262 | struct spi_message *m) | ||
263 | { | ||
264 | struct mxs_spi *spi = spi_master_get_devdata(master); | ||
265 | struct mxs_ssp *ssp = &spi->ssp; | ||
266 | int first, last; | ||
267 | struct spi_transfer *t, *tmp_t; | ||
268 | int status = 0; | ||
269 | int cs; | ||
270 | |||
271 | first = last = 0; | ||
272 | |||
273 | cs = m->spi->chip_select; | ||
274 | |||
275 | list_for_each_entry_safe(t, tmp_t, &m->transfers, transfer_list) { | ||
276 | |||
277 | status = mxs_spi_setup_transfer(m->spi, t); | ||
278 | if (status) | ||
279 | break; | ||
280 | |||
281 | if (&t->transfer_list == m->transfers.next) | ||
282 | first = 1; | ||
283 | if (&t->transfer_list == m->transfers.prev) | ||
284 | last = 1; | ||
285 | if (t->rx_buf && t->tx_buf) { | ||
286 | dev_err(ssp->dev, | ||
287 | "Cannot send and receive simultaneously\n"); | ||
288 | status = -EINVAL; | ||
289 | break; | ||
290 | } | ||
291 | |||
292 | if (t->tx_buf) | ||
293 | status = mxs_spi_txrx_pio(spi, cs, (void *)t->tx_buf, | ||
294 | t->len, &first, &last, 1); | ||
295 | if (t->rx_buf) | ||
296 | status = mxs_spi_txrx_pio(spi, cs, t->rx_buf, | ||
297 | t->len, &first, &last, 0); | ||
298 | |||
299 | m->actual_length += t->len; | ||
300 | if (status) | ||
301 | break; | ||
302 | |||
303 | first = last = 0; | ||
304 | } | ||
305 | |||
306 | m->status = 0; | ||
307 | spi_finalize_current_message(master); | ||
308 | |||
309 | return status; | ||
310 | } | ||
311 | |||
312 | static const struct of_device_id mxs_spi_dt_ids[] = { | ||
313 | { .compatible = "fsl,imx23-spi", .data = (void *) IMX23_SSP, }, | ||
314 | { .compatible = "fsl,imx28-spi", .data = (void *) IMX28_SSP, }, | ||
315 | { /* sentinel */ } | ||
316 | }; | ||
317 | MODULE_DEVICE_TABLE(of, mxs_spi_dt_ids); | ||
318 | |||
319 | static int __devinit mxs_spi_probe(struct platform_device *pdev) | ||
320 | { | ||
321 | const struct of_device_id *of_id = | ||
322 | of_match_device(mxs_spi_dt_ids, &pdev->dev); | ||
323 | struct device_node *np = pdev->dev.of_node; | ||
324 | struct spi_master *master; | ||
325 | struct mxs_spi *spi; | ||
326 | struct mxs_ssp *ssp; | ||
327 | struct resource *iores; | ||
328 | struct pinctrl *pinctrl; | ||
329 | struct clk *clk; | ||
330 | void __iomem *base; | ||
331 | int devid; | ||
332 | int ret = 0; | ||
333 | |||
334 | iores = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
335 | if (!iores) | ||
336 | return -EINVAL; | ||
337 | |||
338 | base = devm_request_and_ioremap(&pdev->dev, iores); | ||
339 | if (!base) | ||
340 | return -EADDRNOTAVAIL; | ||
341 | |||
342 | pinctrl = devm_pinctrl_get_select_default(&pdev->dev); | ||
343 | if (IS_ERR(pinctrl)) | ||
344 | return PTR_ERR(pinctrl); | ||
345 | |||
346 | clk = devm_clk_get(&pdev->dev, NULL); | ||
347 | if (IS_ERR(clk)) | ||
348 | return PTR_ERR(clk); | ||
349 | |||
350 | if (np) | ||
351 | devid = (enum mxs_ssp_id) of_id->data; | ||
352 | else | ||
353 | devid = pdev->id_entry->driver_data; | ||
354 | |||
355 | master = spi_alloc_master(&pdev->dev, sizeof(*spi)); | ||
356 | if (!master) | ||
357 | return -ENOMEM; | ||
358 | |||
359 | master->transfer_one_message = mxs_spi_transfer_one; | ||
360 | master->setup = mxs_spi_setup; | ||
361 | master->mode_bits = SPI_CPOL | SPI_CPHA; | ||
362 | master->num_chipselect = 3; | ||
363 | master->dev.of_node = np; | ||
364 | master->flags = SPI_MASTER_HALF_DUPLEX; | ||
365 | |||
366 | spi = spi_master_get_devdata(master); | ||
367 | ssp = &spi->ssp; | ||
368 | ssp->dev = &pdev->dev; | ||
369 | ssp->clk = clk; | ||
370 | ssp->base = base; | ||
371 | ssp->devid = devid; | ||
372 | |||
373 | clk_prepare_enable(ssp->clk); | ||
374 | ssp->clk_rate = clk_get_rate(ssp->clk) / 1000; | ||
375 | |||
376 | stmp_reset_block(ssp->base); | ||
377 | |||
378 | platform_set_drvdata(pdev, master); | ||
379 | |||
380 | ret = spi_register_master(master); | ||
381 | if (ret) { | ||
382 | dev_err(&pdev->dev, "Cannot register SPI master, %d\n", ret); | ||
383 | goto out_master_free; | ||
384 | } | ||
385 | |||
386 | return 0; | ||
387 | |||
388 | out_master_free: | ||
389 | platform_set_drvdata(pdev, NULL); | ||
390 | clk_disable_unprepare(ssp->clk); | ||
391 | spi_master_put(master); | ||
392 | return ret; | ||
393 | } | ||
394 | |||
395 | static int __devexit mxs_spi_remove(struct platform_device *pdev) | ||
396 | { | ||
397 | struct spi_master *master; | ||
398 | struct mxs_spi *spi; | ||
399 | struct mxs_ssp *ssp; | ||
400 | |||
401 | master = platform_get_drvdata(pdev); | ||
402 | spi = spi_master_get_devdata(master); | ||
403 | ssp = &spi->ssp; | ||
404 | |||
405 | spi_unregister_master(master); | ||
406 | |||
407 | platform_set_drvdata(pdev, NULL); | ||
408 | |||
409 | clk_disable_unprepare(ssp->clk); | ||
410 | |||
411 | spi_master_put(master); | ||
412 | |||
413 | return 0; | ||
414 | } | ||
415 | |||
416 | static struct platform_driver mxs_spi_driver = { | ||
417 | .probe = mxs_spi_probe, | ||
418 | .remove = __devexit_p(mxs_spi_remove), | ||
419 | .driver = { | ||
420 | .name = DRIVER_NAME, | ||
421 | .owner = THIS_MODULE, | ||
422 | .of_match_table = mxs_spi_dt_ids, | ||
423 | }, | ||
424 | }; | ||
425 | |||
426 | module_platform_driver(mxs_spi_driver); | ||
427 | |||
428 | MODULE_AUTHOR("Marek Vasut <marex@denx.de>"); | ||
429 | MODULE_DESCRIPTION("MXS SPI master driver"); | ||
430 | MODULE_LICENSE("GPL"); | ||
431 | MODULE_ALIAS("platform:mxs-spi"); | ||