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 | static int tiny_spi_of_probe(struct platform_device *pdev) |
188 | { |
189 | struct tiny_spi *hw = platform_get_drvdata(pdev); |
190 | struct device_node *np = pdev->dev.of_node; |
191 | u32 val; |
192 | |
193 | if (!np) |
194 | return 0; |
195 | hw->bitbang.ctlr->dev.of_node = pdev->dev.of_node; |
196 | if (!of_property_read_u32(np, propname: "clock-frequency" , out_value: &val)) |
197 | hw->freq = val; |
198 | if (!of_property_read_u32(np, propname: "baud-width" , out_value: &val)) |
199 | hw->baudwidth = val; |
200 | return 0; |
201 | } |
202 | #else /* !CONFIG_OF */ |
203 | static int tiny_spi_of_probe(struct platform_device *pdev) |
204 | { |
205 | return 0; |
206 | } |
207 | #endif /* CONFIG_OF */ |
208 | |
209 | static int tiny_spi_probe(struct platform_device *pdev) |
210 | { |
211 | struct tiny_spi_platform_data *platp = dev_get_platdata(dev: &pdev->dev); |
212 | struct tiny_spi *hw; |
213 | struct spi_controller *host; |
214 | int err = -ENODEV; |
215 | |
216 | host = spi_alloc_host(dev: &pdev->dev, size: sizeof(struct tiny_spi)); |
217 | if (!host) |
218 | return err; |
219 | |
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; |
225 | |
226 | hw = spi_controller_get_devdata(ctlr: host); |
227 | platform_set_drvdata(pdev, data: hw); |
228 | |
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; |
233 | |
234 | /* find and map our resources */ |
235 | hw->base = devm_platform_ioremap_resource(pdev, index: 0); |
236 | if (IS_ERR(ptr: hw->base)) { |
237 | err = PTR_ERR(ptr: hw->base); |
238 | goto exit; |
239 | } |
240 | /* irq is optional */ |
241 | hw->irq = platform_get_irq(pdev, 0); |
242 | if (hw->irq >= 0) { |
243 | init_completion(x: &hw->done); |
244 | err = devm_request_irq(dev: &pdev->dev, irq: hw->irq, handler: tiny_spi_irq, irqflags: 0, |
245 | devname: pdev->name, dev_id: hw); |
246 | if (err) |
247 | goto exit; |
248 | } |
249 | /* find platform data */ |
250 | if (platp) { |
251 | hw->freq = platp->freq; |
252 | hw->baudwidth = platp->baudwidth; |
253 | } else { |
254 | err = tiny_spi_of_probe(pdev); |
255 | if (err) |
256 | goto exit; |
257 | } |
258 | |
259 | /* register our spi controller */ |
260 | err = spi_bitbang_start(spi: &hw->bitbang); |
261 | if (err) |
262 | goto exit; |
263 | dev_info(&pdev->dev, "base %p, irq %d\n" , hw->base, hw->irq); |
264 | |
265 | return 0; |
266 | |
267 | exit: |
268 | spi_controller_put(ctlr: host); |
269 | return err; |
270 | } |
271 | |
272 | static void tiny_spi_remove(struct platform_device *pdev) |
273 | { |
274 | struct tiny_spi *hw = platform_get_drvdata(pdev); |
275 | struct spi_controller *host = hw->bitbang.ctlr; |
276 | |
277 | spi_bitbang_stop(spi: &hw->bitbang); |
278 | spi_controller_put(ctlr: host); |
279 | } |
280 | |
281 | #ifdef CONFIG_OF |
282 | static const struct of_device_id tiny_spi_match[] = { |
283 | { .compatible = "opencores,tiny-spi-rtlsvn2" , }, |
284 | {}, |
285 | }; |
286 | MODULE_DEVICE_TABLE(of, tiny_spi_match); |
287 | #endif /* CONFIG_OF */ |
288 | |
289 | static struct platform_driver tiny_spi_driver = { |
290 | .probe = tiny_spi_probe, |
291 | .remove = tiny_spi_remove, |
292 | .driver = { |
293 | .name = DRV_NAME, |
294 | .pm = NULL, |
295 | .of_match_table = of_match_ptr(tiny_spi_match), |
296 | }, |
297 | }; |
298 | module_platform_driver(tiny_spi_driver); |
299 | |
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); |
304 | |