1 // SPDX-License-Identifier: GPL-2.0-only
3 * OpenCores tiny SPI host driver
5 * https://opencores.org/project,tiny_spi
7 * Copyright (C) 2011 Thomas Chou <thomas@wytron.com.tw>
9 * Based on spi_s3c24xx.c, which is:
10 * Copyright (c) 2006 Ben Dooks
11 * Copyright (c) 2006 Simtec Electronics
12 * Ben Dooks <ben@simtec.co.uk>
15 #include <linux/interrupt.h>
16 #include <linux/errno.h>
17 #include <linux/module.h>
18 #include <linux/platform_device.h>
19 #include <linux/spi/spi.h>
20 #include <linux/spi/spi_bitbang.h>
21 #include <linux/spi/spi_oc_tiny.h>
25 #define DRV_NAME "spi_oc_tiny"
27 #define TINY_SPI_RXDATA 0
28 #define TINY_SPI_TXDATA 4
29 #define TINY_SPI_STATUS 8
30 #define TINY_SPI_CONTROL 12
31 #define TINY_SPI_BAUD 16
33 #define TINY_SPI_STATUS_TXE 0x1
34 #define TINY_SPI_STATUS_TXR 0x2
37 /* bitbang has to be first */
38 struct spi_bitbang bitbang
;
39 struct completion done
;
44 unsigned int baudwidth
;
46 unsigned int speed_hz
;
49 unsigned int txc
, rxc
;
54 static inline struct tiny_spi
*tiny_spi_to_hw(struct spi_device
*sdev
)
56 return spi_controller_get_devdata(sdev
->controller
);
59 static unsigned int tiny_spi_baud(struct spi_device
*spi
, unsigned int hz
)
61 struct tiny_spi
*hw
= tiny_spi_to_hw(spi
);
63 return min(DIV_ROUND_UP(hw
->freq
, hz
* 2), (1U << hw
->baudwidth
)) - 1;
66 static int tiny_spi_setup_transfer(struct spi_device
*spi
,
67 struct spi_transfer
*t
)
69 struct tiny_spi
*hw
= tiny_spi_to_hw(spi
);
70 unsigned int baud
= hw
->baud
;
73 if (t
->speed_hz
&& t
->speed_hz
!= hw
->speed_hz
)
74 baud
= tiny_spi_baud(spi
, t
->speed_hz
);
76 writel(baud
, hw
->base
+ TINY_SPI_BAUD
);
77 writel(hw
->mode
, hw
->base
+ TINY_SPI_CONTROL
);
81 static int tiny_spi_setup(struct spi_device
*spi
)
83 struct tiny_spi
*hw
= tiny_spi_to_hw(spi
);
85 if (spi
->max_speed_hz
!= hw
->speed_hz
) {
86 hw
->speed_hz
= spi
->max_speed_hz
;
87 hw
->baud
= tiny_spi_baud(spi
, hw
->speed_hz
);
89 hw
->mode
= spi
->mode
& SPI_MODE_X_MASK
;
93 static inline void tiny_spi_wait_txr(struct tiny_spi
*hw
)
95 while (!(readb(hw
->base
+ TINY_SPI_STATUS
) &
100 static inline void tiny_spi_wait_txe(struct tiny_spi
*hw
)
102 while (!(readb(hw
->base
+ TINY_SPI_STATUS
) &
103 TINY_SPI_STATUS_TXE
))
107 static int tiny_spi_txrx_bufs(struct spi_device
*spi
, struct spi_transfer
*t
)
109 struct tiny_spi
*hw
= tiny_spi_to_hw(spi
);
110 const u8
*txp
= t
->tx_buf
;
115 /* use interrupt driven data transfer */
122 /* send the first byte */
124 writeb(hw
->txp
? *hw
->txp
++ : 0,
125 hw
->base
+ TINY_SPI_TXDATA
);
127 writeb(hw
->txp
? *hw
->txp
++ : 0,
128 hw
->base
+ TINY_SPI_TXDATA
);
130 writeb(TINY_SPI_STATUS_TXR
, hw
->base
+ TINY_SPI_STATUS
);
132 writeb(hw
->txp
? *hw
->txp
++ : 0,
133 hw
->base
+ TINY_SPI_TXDATA
);
135 writeb(TINY_SPI_STATUS_TXE
, hw
->base
+ TINY_SPI_STATUS
);
138 wait_for_completion(&hw
->done
);
140 /* we need to tighten the transfer loop */
141 writeb(txp
? *txp
++ : 0, hw
->base
+ TINY_SPI_TXDATA
);
142 for (i
= 1; i
< t
->len
; i
++) {
143 writeb(txp
? *txp
++ : 0, hw
->base
+ TINY_SPI_TXDATA
);
145 if (rxp
|| (i
!= t
->len
- 1))
146 tiny_spi_wait_txr(hw
);
148 *rxp
++ = readb(hw
->base
+ TINY_SPI_TXDATA
);
150 tiny_spi_wait_txe(hw
);
152 *rxp
++ = readb(hw
->base
+ TINY_SPI_RXDATA
);
158 static irqreturn_t
tiny_spi_irq(int irq
, void *dev
)
160 struct tiny_spi
*hw
= dev
;
162 writeb(0, hw
->base
+ TINY_SPI_STATUS
);
163 if (hw
->rxc
+ 1 == hw
->len
) {
165 *hw
->rxp
++ = readb(hw
->base
+ TINY_SPI_RXDATA
);
170 *hw
->rxp
++ = readb(hw
->base
+ TINY_SPI_TXDATA
);
172 if (hw
->txc
< hw
->len
) {
173 writeb(hw
->txp
? *hw
->txp
++ : 0,
174 hw
->base
+ TINY_SPI_TXDATA
);
176 writeb(TINY_SPI_STATUS_TXR
,
177 hw
->base
+ TINY_SPI_STATUS
);
179 writeb(TINY_SPI_STATUS_TXE
,
180 hw
->base
+ TINY_SPI_STATUS
);
187 static int tiny_spi_of_probe(struct platform_device
*pdev
)
189 struct tiny_spi
*hw
= platform_get_drvdata(pdev
);
190 struct device_node
*np
= pdev
->dev
.of_node
;
195 hw
->bitbang
.ctlr
->dev
.of_node
= pdev
->dev
.of_node
;
196 if (!of_property_read_u32(np
, "clock-frequency", &val
))
198 if (!of_property_read_u32(np
, "baud-width", &val
))
202 #else /* !CONFIG_OF */
203 static int tiny_spi_of_probe(struct platform_device
*pdev
)
207 #endif /* CONFIG_OF */
209 static int tiny_spi_probe(struct platform_device
*pdev
)
211 struct tiny_spi_platform_data
*platp
= dev_get_platdata(&pdev
->dev
);
213 struct spi_controller
*host
;
216 host
= spi_alloc_host(&pdev
->dev
, sizeof(struct tiny_spi
));
220 /* setup the host state. */
221 host
->bus_num
= pdev
->id
;
222 host
->mode_bits
= SPI_CPOL
| SPI_CPHA
| SPI_CS_HIGH
;
223 host
->setup
= tiny_spi_setup
;
224 host
->use_gpio_descriptors
= true;
226 hw
= spi_controller_get_devdata(host
);
227 platform_set_drvdata(pdev
, hw
);
229 /* setup the state for the bitbang driver */
230 hw
->bitbang
.ctlr
= host
;
231 hw
->bitbang
.setup_transfer
= tiny_spi_setup_transfer
;
232 hw
->bitbang
.txrx_bufs
= tiny_spi_txrx_bufs
;
234 /* find and map our resources */
235 hw
->base
= devm_platform_ioremap_resource(pdev
, 0);
236 if (IS_ERR(hw
->base
)) {
237 err
= PTR_ERR(hw
->base
);
240 /* irq is optional */
241 hw
->irq
= platform_get_irq(pdev
, 0);
243 init_completion(&hw
->done
);
244 err
= devm_request_irq(&pdev
->dev
, hw
->irq
, tiny_spi_irq
, 0,
249 /* find platform data */
251 hw
->freq
= platp
->freq
;
252 hw
->baudwidth
= platp
->baudwidth
;
254 err
= tiny_spi_of_probe(pdev
);
259 /* register our spi controller */
260 err
= spi_bitbang_start(&hw
->bitbang
);
263 dev_info(&pdev
->dev
, "base %p, irq %d\n", hw
->base
, hw
->irq
);
268 spi_controller_put(host
);
272 static void tiny_spi_remove(struct platform_device
*pdev
)
274 struct tiny_spi
*hw
= platform_get_drvdata(pdev
);
275 struct spi_controller
*host
= hw
->bitbang
.ctlr
;
277 spi_bitbang_stop(&hw
->bitbang
);
278 spi_controller_put(host
);
282 static const struct of_device_id tiny_spi_match
[] = {
283 { .compatible
= "opencores,tiny-spi-rtlsvn2", },
286 MODULE_DEVICE_TABLE(of
, tiny_spi_match
);
287 #endif /* CONFIG_OF */
289 static struct platform_driver tiny_spi_driver
= {
290 .probe
= tiny_spi_probe
,
291 .remove
= tiny_spi_remove
,
295 .of_match_table
= of_match_ptr(tiny_spi_match
),
298 module_platform_driver(tiny_spi_driver
);
300 MODULE_DESCRIPTION("OpenCores tiny SPI driver");
301 MODULE_AUTHOR("Thomas Chou <thomas@wytron.com.tw>");
302 MODULE_LICENSE("GPL");
303 MODULE_ALIAS("platform:" DRV_NAME
);