1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * OpenCores tiny SPI host driver |
4 | * |
5 | * https://opencores.org/project,tiny_spi |
6 | * |
7 | * Copyright (C) 2011 Thomas Chou <thomas@wytron.com.tw> |
8 | * |
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> |
13 | */ |
14 | |
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> |
22 | #include <linux/io.h> |
23 | #include <linux/of.h> |
24 | |
25 | #define DRV_NAME "spi_oc_tiny" |
26 | |
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 |
32 | |
33 | #define TINY_SPI_STATUS_TXE 0x1 |
34 | #define TINY_SPI_STATUS_TXR 0x2 |
35 | |
36 | struct tiny_spi { |
37 | /* bitbang has to be first */ |
38 | struct spi_bitbang bitbang; |
39 | struct completion done; |
40 | |
41 | void __iomem *base; |
42 | int irq; |
43 | unsigned int freq; |
44 | unsigned int baudwidth; |
45 | unsigned int baud; |
46 | unsigned int speed_hz; |
47 | unsigned int mode; |
48 | unsigned int len; |
49 | unsigned int txc, rxc; |
50 | const u8 *txp; |
51 | u8 *rxp; |
52 | }; |
53 | |
54 | static inline struct tiny_spi *tiny_spi_to_hw(struct spi_device *sdev) |
55 | { |
56 | return spi_controller_get_devdata(ctlr: sdev->controller); |
57 | } |
58 | |
59 | static unsigned int tiny_spi_baud(struct spi_device *spi, unsigned int hz) |
60 | { |
61 | struct tiny_spi *hw = tiny_spi_to_hw(sdev: spi); |
62 | |
63 | return min(DIV_ROUND_UP(hw->freq, hz * 2), (1U << hw->baudwidth)) - 1; |
64 | } |
65 | |
66 | static int tiny_spi_setup_transfer(struct spi_device *spi, |
67 | struct spi_transfer *t) |
68 | { |
69 | struct tiny_spi *hw = tiny_spi_to_hw(sdev: spi); |
70 | unsigned int baud = hw->baud; |
71 | |
72 | if (t) { |
73 | if (t->speed_hz && t->speed_hz != hw->speed_hz) |
74 | baud = tiny_spi_baud(spi, hz: t->speed_hz); |
75 | } |
76 | writel(val: baud, addr: hw->base + TINY_SPI_BAUD); |
77 | writel(val: hw->mode, addr: hw->base + TINY_SPI_CONTROL); |
78 | return 0; |
79 | } |
80 | |
81 | static int tiny_spi_setup(struct spi_device *spi) |
82 | { |
83 | struct tiny_spi *hw = tiny_spi_to_hw(sdev: spi); |
84 | |
85 | if (spi->max_speed_hz != hw->speed_hz) { |
86 | hw->speed_hz = spi->max_speed_hz; |
87 | hw->baud = tiny_spi_baud(spi, hz: hw->speed_hz); |
88 | } |
89 | hw->mode = spi->mode & SPI_MODE_X_MASK; |
90 | return 0; |
91 | } |
92 | |
93 | static inline void tiny_spi_wait_txr(struct tiny_spi *hw) |
94 | { |
95 | while (!(readb(addr: hw->base + TINY_SPI_STATUS) & |
96 | TINY_SPI_STATUS_TXR)) |
97 | cpu_relax(); |
98 | } |
99 | |
100 | static inline void tiny_spi_wait_txe(struct tiny_spi *hw) |
101 | { |
102 | while (!(readb(addr: hw->base + TINY_SPI_STATUS) & |
103 | TINY_SPI_STATUS_TXE)) |
104 | cpu_relax(); |
105 | } |
106 | |
107 | static int tiny_spi_txrx_bufs(struct spi_device *spi, struct spi_transfer *t) |
108 | { |
109 | struct tiny_spi *hw = tiny_spi_to_hw(sdev: spi); |
110 | const u8 *txp = t->tx_buf; |
111 | u8 *rxp = t->rx_buf; |
112 | unsigned int i; |
113 | |
114 | if (hw->irq >= 0) { |
115 | /* use interrupt driven data transfer */ |
116 | hw->len = t->len; |
117 | hw->txp = t->tx_buf; |
118 | hw->rxp = t->rx_buf; |
119 | hw->txc = 0; |
120 | hw->rxc = 0; |
121 | |
122 | /* send the first byte */ |
123 | if (t->len > 1) { |
124 | writeb(val: hw->txp ? *hw->txp++ : 0, |
125 | addr: hw->base + TINY_SPI_TXDATA); |
126 | hw->txc++; |
127 | writeb(val: hw->txp ? *hw->txp++ : 0, |
128 | addr: hw->base + TINY_SPI_TXDATA); |
129 | hw->txc++; |
130 | writeb(TINY_SPI_STATUS_TXR, addr: hw->base + TINY_SPI_STATUS); |
131 | } else { |
132 | writeb(val: hw->txp ? *hw->txp++ : 0, |
133 | addr: hw->base + TINY_SPI_TXDATA); |
134 | hw->txc++; |
135 | writeb(TINY_SPI_STATUS_TXE, addr: hw->base + TINY_SPI_STATUS); |
136 | } |
137 | |
138 | wait_for_completion(&hw->done); |
139 | } else { |
140 | /* we need to tighten the transfer loop */ |
141 | writeb(val: txp ? *txp++ : 0, addr: hw->base + TINY_SPI_TXDATA); |
142 | for (i = 1; i < t->len; i++) { |
143 | writeb(val: txp ? *txp++ : 0, addr: hw->base + TINY_SPI_TXDATA); |
144 | |
145 | if (rxp || (i != t->len - 1)) |
146 | tiny_spi_wait_txr(hw); |
147 | if (rxp) |
148 | *rxp++ = readb(addr: hw->base + TINY_SPI_TXDATA); |
149 | } |
150 | tiny_spi_wait_txe(hw); |
151 | if (rxp) |
152 | *rxp++ = readb(addr: hw->base + TINY_SPI_RXDATA); |
153 | } |
154 | |
155 | return t->len; |
156 | } |
157 | |
158 | static irqreturn_t tiny_spi_irq(int irq, void *dev) |
159 | { |
160 | struct tiny_spi *hw = dev; |
161 | |
162 | writeb(val: 0, addr: hw->base + TINY_SPI_STATUS); |
163 | if (hw->rxc + 1 == hw->len) { |
164 | if (hw->rxp) |
165 | *hw->rxp++ = readb(addr: hw->base + TINY_SPI_RXDATA); |
166 | hw->rxc++; |
167 | complete(&hw->done); |
168 | } else { |
169 | if (hw->rxp) |
170 | *hw->rxp++ = readb(addr: hw->base + TINY_SPI_TXDATA); |
171 | hw->rxc++; |
172 | if (hw->txc < hw->len) { |
173 | writeb(val: hw->txp ? *hw->txp++ : 0, |
174 | addr: hw->base + TINY_SPI_TXDATA); |
175 | hw->txc++; |
176 | writeb(TINY_SPI_STATUS_TXR, |
177 | addr: hw->base + TINY_SPI_STATUS); |
178 | } else { |
179 | writeb(TINY_SPI_STATUS_TXE, |
180 | addr: hw->base + TINY_SPI_STATUS); |
181 | } |
182 | } |
183 | return IRQ_HANDLED; |
184 | } |
185 | |
186 | #ifdef CONFIG_OF |
187 | #include <linux/of_gpio.h> |
188 | |
189 | static int tiny_spi_of_probe(struct platform_device *pdev) |
190 | { |
191 | struct tiny_spi *hw = platform_get_drvdata(pdev); |
192 | struct device_node *np = pdev->dev.of_node; |
193 | u32 val; |
194 | |
195 | if (!np) |
196 | return 0; |
197 | hw->bitbang.ctlr->dev.of_node = pdev->dev.of_node; |
198 | if (!of_property_read_u32(np, propname: "clock-frequency" , out_value: &val)) |
199 | hw->freq = val; |
200 | if (!of_property_read_u32(np, propname: "baud-width" , out_value: &val)) |
201 | hw->baudwidth = val; |
202 | return 0; |
203 | } |
204 | #else /* !CONFIG_OF */ |
205 | static int tiny_spi_of_probe(struct platform_device *pdev) |
206 | { |
207 | return 0; |
208 | } |
209 | #endif /* CONFIG_OF */ |
210 | |
211 | static int tiny_spi_probe(struct platform_device *pdev) |
212 | { |
213 | struct tiny_spi_platform_data *platp = dev_get_platdata(dev: &pdev->dev); |
214 | struct tiny_spi *hw; |
215 | struct spi_controller *host; |
216 | int err = -ENODEV; |
217 | |
218 | host = spi_alloc_host(dev: &pdev->dev, size: sizeof(struct tiny_spi)); |
219 | if (!host) |
220 | return err; |
221 | |
222 | /* setup the host state. */ |
223 | host->bus_num = pdev->id; |
224 | host->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH; |
225 | host->setup = tiny_spi_setup; |
226 | host->use_gpio_descriptors = true; |
227 | |
228 | hw = spi_controller_get_devdata(ctlr: host); |
229 | platform_set_drvdata(pdev, data: hw); |
230 | |
231 | /* setup the state for the bitbang driver */ |
232 | hw->bitbang.ctlr = host; |
233 | hw->bitbang.setup_transfer = tiny_spi_setup_transfer; |
234 | hw->bitbang.txrx_bufs = tiny_spi_txrx_bufs; |
235 | |
236 | /* find and map our resources */ |
237 | hw->base = devm_platform_ioremap_resource(pdev, index: 0); |
238 | if (IS_ERR(ptr: hw->base)) { |
239 | err = PTR_ERR(ptr: hw->base); |
240 | goto exit; |
241 | } |
242 | /* irq is optional */ |
243 | hw->irq = platform_get_irq(pdev, 0); |
244 | if (hw->irq >= 0) { |
245 | init_completion(x: &hw->done); |
246 | err = devm_request_irq(dev: &pdev->dev, irq: hw->irq, handler: tiny_spi_irq, irqflags: 0, |
247 | devname: pdev->name, dev_id: hw); |
248 | if (err) |
249 | goto exit; |
250 | } |
251 | /* find platform data */ |
252 | if (platp) { |
253 | hw->freq = platp->freq; |
254 | hw->baudwidth = platp->baudwidth; |
255 | } else { |
256 | err = tiny_spi_of_probe(pdev); |
257 | if (err) |
258 | goto exit; |
259 | } |
260 | |
261 | /* register our spi controller */ |
262 | err = spi_bitbang_start(spi: &hw->bitbang); |
263 | if (err) |
264 | goto exit; |
265 | dev_info(&pdev->dev, "base %p, irq %d\n" , hw->base, hw->irq); |
266 | |
267 | return 0; |
268 | |
269 | exit: |
270 | spi_controller_put(ctlr: host); |
271 | return err; |
272 | } |
273 | |
274 | static void tiny_spi_remove(struct platform_device *pdev) |
275 | { |
276 | struct tiny_spi *hw = platform_get_drvdata(pdev); |
277 | struct spi_controller *host = hw->bitbang.ctlr; |
278 | |
279 | spi_bitbang_stop(spi: &hw->bitbang); |
280 | spi_controller_put(ctlr: host); |
281 | } |
282 | |
283 | #ifdef CONFIG_OF |
284 | static const struct of_device_id tiny_spi_match[] = { |
285 | { .compatible = "opencores,tiny-spi-rtlsvn2" , }, |
286 | {}, |
287 | }; |
288 | MODULE_DEVICE_TABLE(of, tiny_spi_match); |
289 | #endif /* CONFIG_OF */ |
290 | |
291 | static struct platform_driver tiny_spi_driver = { |
292 | .probe = tiny_spi_probe, |
293 | .remove_new = tiny_spi_remove, |
294 | .driver = { |
295 | .name = DRV_NAME, |
296 | .pm = NULL, |
297 | .of_match_table = of_match_ptr(tiny_spi_match), |
298 | }, |
299 | }; |
300 | module_platform_driver(tiny_spi_driver); |
301 | |
302 | MODULE_DESCRIPTION("OpenCores tiny SPI driver" ); |
303 | MODULE_AUTHOR("Thomas Chou <thomas@wytron.com.tw>" ); |
304 | MODULE_LICENSE("GPL" ); |
305 | MODULE_ALIAS("platform:" DRV_NAME); |
306 | |