]>
Commit | Line | Data |
---|---|---|
83d290c5 | 1 | // SPDX-License-Identifier: GPL-2.0+ |
5ac07d29 ÁFR |
2 | /* |
3 | * Copyright (C) 2017 Álvaro Fernández Rojas <noltari@gmail.com> | |
4 | * | |
5 | * Derived from linux/drivers/spi/spi-bcm63xx.c: | |
6 | * Copyright (C) 2009-2012 Florian Fainelli <florian@openwrt.org> | |
7 | * Copyright (C) 2010 Tanguy Bouzeloc <tanguy.bouzeloc@efixo.com> | |
5ac07d29 ÁFR |
8 | */ |
9 | ||
10 | #include <common.h> | |
11 | #include <clk.h> | |
12 | #include <dm.h> | |
13 | #include <spi.h> | |
14 | #include <reset.h> | |
15 | #include <wait_bit.h> | |
16 | #include <asm/io.h> | |
17 | ||
5ac07d29 ÁFR |
18 | /* BCM6348 SPI core */ |
19 | #define SPI_6348_CLK 0x06 | |
20 | #define SPI_6348_CMD 0x00 | |
21 | #define SPI_6348_CTL 0x40 | |
22 | #define SPI_6348_CTL_SHIFT 6 | |
23 | #define SPI_6348_FILL 0x07 | |
24 | #define SPI_6348_IR_MASK 0x04 | |
25 | #define SPI_6348_IR_STAT 0x02 | |
26 | #define SPI_6348_RX 0x80 | |
27 | #define SPI_6348_RX_SIZE 0x3f | |
28 | #define SPI_6348_TX 0x41 | |
29 | #define SPI_6348_TX_SIZE 0x3f | |
30 | ||
31 | /* BCM6358 SPI core */ | |
32 | #define SPI_6358_CLK 0x706 | |
33 | #define SPI_6358_CMD 0x700 | |
34 | #define SPI_6358_CTL 0x000 | |
35 | #define SPI_6358_CTL_SHIFT 14 | |
36 | #define SPI_6358_FILL 0x707 | |
37 | #define SPI_6358_IR_MASK 0x702 | |
38 | #define SPI_6358_IR_STAT 0x704 | |
39 | #define SPI_6358_RX 0x400 | |
40 | #define SPI_6358_RX_SIZE 0x220 | |
41 | #define SPI_6358_TX 0x002 | |
42 | #define SPI_6358_TX_SIZE 0x21e | |
43 | ||
44 | /* SPI Clock register */ | |
45 | #define SPI_CLK_SHIFT 0 | |
46 | #define SPI_CLK_20MHZ (0 << SPI_CLK_SHIFT) | |
47 | #define SPI_CLK_0_391MHZ (1 << SPI_CLK_SHIFT) | |
48 | #define SPI_CLK_0_781MHZ (2 << SPI_CLK_SHIFT) | |
49 | #define SPI_CLK_1_563MHZ (3 << SPI_CLK_SHIFT) | |
50 | #define SPI_CLK_3_125MHZ (4 << SPI_CLK_SHIFT) | |
51 | #define SPI_CLK_6_250MHZ (5 << SPI_CLK_SHIFT) | |
52 | #define SPI_CLK_12_50MHZ (6 << SPI_CLK_SHIFT) | |
53 | #define SPI_CLK_25MHZ (7 << SPI_CLK_SHIFT) | |
54 | #define SPI_CLK_MASK (7 << SPI_CLK_SHIFT) | |
55 | #define SPI_CLK_SSOFF_SHIFT 3 | |
56 | #define SPI_CLK_SSOFF_2 (2 << SPI_CLK_SSOFF_SHIFT) | |
57 | #define SPI_CLK_SSOFF_MASK (7 << SPI_CLK_SSOFF_SHIFT) | |
58 | #define SPI_CLK_BSWAP_SHIFT 7 | |
59 | #define SPI_CLK_BSWAP_MASK (1 << SPI_CLK_BSWAP_SHIFT) | |
60 | ||
61 | /* SPI Command register */ | |
62 | #define SPI_CMD_OP_SHIFT 0 | |
63 | #define SPI_CMD_OP_START (0x3 << SPI_CMD_OP_SHIFT) | |
64 | #define SPI_CMD_SLAVE_SHIFT 4 | |
65 | #define SPI_CMD_SLAVE_MASK (0xf << SPI_CMD_SLAVE_SHIFT) | |
66 | #define SPI_CMD_PREPEND_SHIFT 8 | |
67 | #define SPI_CMD_PREPEND_BYTES 0xf | |
68 | #define SPI_CMD_3WIRE_SHIFT 12 | |
69 | #define SPI_CMD_3WIRE_MASK (1 << SPI_CMD_3WIRE_SHIFT) | |
70 | ||
71 | /* SPI Control register */ | |
72 | #define SPI_CTL_TYPE_FD_RW 0 | |
73 | #define SPI_CTL_TYPE_HD_W 1 | |
74 | #define SPI_CTL_TYPE_HD_R 2 | |
75 | ||
76 | /* SPI Interrupt registers */ | |
77 | #define SPI_IR_DONE_SHIFT 0 | |
78 | #define SPI_IR_DONE_MASK (1 << SPI_IR_DONE_SHIFT) | |
79 | #define SPI_IR_RXOVER_SHIFT 1 | |
80 | #define SPI_IR_RXOVER_MASK (1 << SPI_IR_RXOVER_SHIFT) | |
81 | #define SPI_IR_TXUNDER_SHIFT 2 | |
82 | #define SPI_IR_TXUNDER_MASK (1 << SPI_IR_TXUNDER_SHIFT) | |
83 | #define SPI_IR_TXOVER_SHIFT 3 | |
84 | #define SPI_IR_TXOVER_MASK (1 << SPI_IR_TXOVER_SHIFT) | |
85 | #define SPI_IR_RXUNDER_SHIFT 4 | |
86 | #define SPI_IR_RXUNDER_MASK (1 << SPI_IR_RXUNDER_SHIFT) | |
87 | #define SPI_IR_CLEAR_MASK (SPI_IR_DONE_MASK |\ | |
88 | SPI_IR_RXOVER_MASK |\ | |
89 | SPI_IR_TXUNDER_MASK |\ | |
90 | SPI_IR_TXOVER_MASK |\ | |
91 | SPI_IR_RXUNDER_MASK) | |
92 | ||
93 | enum bcm63xx_regs_spi { | |
94 | SPI_CLK, | |
95 | SPI_CMD, | |
96 | SPI_CTL, | |
97 | SPI_CTL_SHIFT, | |
98 | SPI_FILL, | |
99 | SPI_IR_MASK, | |
100 | SPI_IR_STAT, | |
101 | SPI_RX, | |
102 | SPI_RX_SIZE, | |
103 | SPI_TX, | |
104 | SPI_TX_SIZE, | |
105 | }; | |
106 | ||
107 | struct bcm63xx_spi_priv { | |
108 | const unsigned long *regs; | |
109 | void __iomem *base; | |
110 | size_t tx_bytes; | |
111 | uint8_t num_cs; | |
112 | }; | |
113 | ||
114 | #define SPI_CLK_CNT 8 | |
115 | static const unsigned bcm63xx_spi_freq_table[SPI_CLK_CNT][2] = { | |
116 | { 25000000, SPI_CLK_25MHZ }, | |
117 | { 20000000, SPI_CLK_20MHZ }, | |
118 | { 12500000, SPI_CLK_12_50MHZ }, | |
119 | { 6250000, SPI_CLK_6_250MHZ }, | |
120 | { 3125000, SPI_CLK_3_125MHZ }, | |
121 | { 1563000, SPI_CLK_1_563MHZ }, | |
122 | { 781000, SPI_CLK_0_781MHZ }, | |
123 | { 391000, SPI_CLK_0_391MHZ } | |
124 | }; | |
125 | ||
126 | static int bcm63xx_spi_cs_info(struct udevice *bus, uint cs, | |
127 | struct spi_cs_info *info) | |
128 | { | |
129 | struct bcm63xx_spi_priv *priv = dev_get_priv(bus); | |
130 | ||
131 | if (cs >= priv->num_cs) { | |
132 | printf("no cs %u\n", cs); | |
133 | return -ENODEV; | |
134 | } | |
135 | ||
136 | return 0; | |
137 | } | |
138 | ||
139 | static int bcm63xx_spi_set_mode(struct udevice *bus, uint mode) | |
140 | { | |
141 | struct bcm63xx_spi_priv *priv = dev_get_priv(bus); | |
142 | const unsigned long *regs = priv->regs; | |
143 | ||
144 | if (mode & SPI_LSB_FIRST) | |
145 | setbits_8(priv->base + regs[SPI_CLK], SPI_CLK_BSWAP_MASK); | |
146 | else | |
147 | clrbits_8(priv->base + regs[SPI_CLK], SPI_CLK_BSWAP_MASK); | |
148 | ||
149 | return 0; | |
150 | } | |
151 | ||
152 | static int bcm63xx_spi_set_speed(struct udevice *bus, uint speed) | |
153 | { | |
154 | struct bcm63xx_spi_priv *priv = dev_get_priv(bus); | |
155 | const unsigned long *regs = priv->regs; | |
156 | uint8_t clk_cfg; | |
157 | int i; | |
158 | ||
159 | /* default to lowest clock configuration */ | |
160 | clk_cfg = SPI_CLK_0_391MHZ; | |
161 | ||
162 | /* find the closest clock configuration */ | |
163 | for (i = 0; i < SPI_CLK_CNT; i++) { | |
164 | if (speed >= bcm63xx_spi_freq_table[i][0]) { | |
165 | clk_cfg = bcm63xx_spi_freq_table[i][1]; | |
166 | break; | |
167 | } | |
168 | } | |
169 | ||
170 | /* write clock configuration */ | |
171 | clrsetbits_8(priv->base + regs[SPI_CLK], | |
172 | SPI_CLK_SSOFF_MASK | SPI_CLK_MASK, | |
173 | clk_cfg | SPI_CLK_SSOFF_2); | |
174 | ||
175 | return 0; | |
176 | } | |
177 | ||
178 | /* | |
179 | * BCM63xx SPI driver doesn't allow keeping CS active between transfers since | |
180 | * they are HW controlled. | |
181 | * However, it provides a mechanism to prepend write transfers prior to read | |
182 | * transfers (with a maximum prepend of 15 bytes), which is usually enough for | |
183 | * SPI-connected flashes since reading requires prepending a write transfer of | |
184 | * 5 bytes. | |
185 | * | |
186 | * This implementation takes advantage of the prepend mechanism and combines | |
187 | * multiple transfers into a single one where possible (single/multiple write | |
188 | * transfer(s) followed by a final read/write transfer). | |
189 | * However, it's not possible to buffer reads, which means that read transfers | |
190 | * should always be done as the final ones. | |
191 | * On the other hand, take into account that combining write transfers into | |
192 | * a single one is just buffering and doesn't require prepend mechanism. | |
193 | */ | |
194 | static int bcm63xx_spi_xfer(struct udevice *dev, unsigned int bitlen, | |
195 | const void *dout, void *din, unsigned long flags) | |
196 | { | |
197 | struct bcm63xx_spi_priv *priv = dev_get_priv(dev->parent); | |
198 | const unsigned long *regs = priv->regs; | |
199 | size_t data_bytes = bitlen / 8; | |
200 | ||
201 | if (flags & SPI_XFER_BEGIN) { | |
202 | /* clear prepends */ | |
203 | priv->tx_bytes = 0; | |
204 | ||
205 | /* initialize hardware */ | |
206 | writeb_be(0, priv->base + regs[SPI_IR_MASK]); | |
207 | } | |
208 | ||
209 | if (din) { | |
210 | /* buffering reads not possible since cs is hw controlled */ | |
211 | if (!(flags & SPI_XFER_END)) { | |
212 | printf("unable to buffer reads\n"); | |
213 | return -EINVAL; | |
214 | } | |
215 | ||
216 | /* check rx size */ | |
217 | if (data_bytes > regs[SPI_RX_SIZE]) { | |
218 | printf("max rx bytes exceeded\n"); | |
219 | return -EMSGSIZE; | |
220 | } | |
221 | } | |
222 | ||
223 | if (dout) { | |
224 | /* check tx size */ | |
225 | if (priv->tx_bytes + data_bytes > regs[SPI_TX_SIZE]) { | |
226 | printf("max tx bytes exceeded\n"); | |
227 | return -EMSGSIZE; | |
228 | } | |
229 | ||
230 | /* copy tx data */ | |
231 | memcpy_toio(priv->base + regs[SPI_TX] + priv->tx_bytes, | |
232 | dout, data_bytes); | |
233 | priv->tx_bytes += data_bytes; | |
234 | } | |
235 | ||
236 | if (flags & SPI_XFER_END) { | |
237 | struct dm_spi_slave_platdata *plat = | |
238 | dev_get_parent_platdata(dev); | |
239 | uint16_t val, cmd; | |
240 | int ret; | |
241 | ||
242 | /* determine control config */ | |
243 | if (dout && !din) { | |
244 | /* buffered write transfers */ | |
245 | val = priv->tx_bytes; | |
246 | val |= (SPI_CTL_TYPE_HD_W << regs[SPI_CTL_SHIFT]); | |
247 | priv->tx_bytes = 0; | |
248 | } else { | |
249 | if (dout && din && (flags & SPI_XFER_ONCE)) { | |
250 | /* full duplex read/write */ | |
251 | val = data_bytes; | |
252 | val |= (SPI_CTL_TYPE_FD_RW << | |
253 | regs[SPI_CTL_SHIFT]); | |
254 | priv->tx_bytes = 0; | |
255 | } else { | |
256 | /* prepended write transfer */ | |
257 | val = data_bytes; | |
258 | val |= (SPI_CTL_TYPE_HD_R << | |
259 | regs[SPI_CTL_SHIFT]); | |
260 | if (priv->tx_bytes > SPI_CMD_PREPEND_BYTES) { | |
261 | printf("max prepend bytes exceeded\n"); | |
262 | return -EMSGSIZE; | |
263 | } | |
264 | } | |
265 | } | |
266 | ||
267 | if (regs[SPI_CTL_SHIFT] >= 8) | |
268 | writew_be(val, priv->base + regs[SPI_CTL]); | |
269 | else | |
270 | writeb_be(val, priv->base + regs[SPI_CTL]); | |
271 | ||
272 | /* clear interrupts */ | |
273 | writeb_be(SPI_IR_CLEAR_MASK, priv->base + regs[SPI_IR_STAT]); | |
274 | ||
275 | /* issue the transfer */ | |
276 | cmd = SPI_CMD_OP_START; | |
277 | cmd |= (plat->cs << SPI_CMD_SLAVE_SHIFT) & SPI_CMD_SLAVE_MASK; | |
278 | cmd |= (priv->tx_bytes << SPI_CMD_PREPEND_SHIFT); | |
279 | if (plat->mode & SPI_3WIRE) | |
280 | cmd |= SPI_CMD_3WIRE_MASK; | |
281 | writew_be(cmd, priv->base + regs[SPI_CMD]); | |
282 | ||
283 | /* enable interrupts */ | |
284 | writeb_be(SPI_IR_DONE_MASK, priv->base + regs[SPI_IR_MASK]); | |
285 | ||
286 | ret = wait_for_bit_8(priv->base + regs[SPI_IR_STAT], | |
287 | SPI_IR_DONE_MASK, true, 1000, false); | |
288 | if (ret) { | |
289 | printf("interrupt timeout\n"); | |
290 | return ret; | |
291 | } | |
292 | ||
293 | /* copy rx data */ | |
294 | if (din) | |
295 | memcpy_fromio(din, priv->base + regs[SPI_RX], | |
296 | data_bytes); | |
297 | } | |
298 | ||
299 | return 0; | |
300 | } | |
301 | ||
302 | static const struct dm_spi_ops bcm63xx_spi_ops = { | |
303 | .cs_info = bcm63xx_spi_cs_info, | |
304 | .set_mode = bcm63xx_spi_set_mode, | |
305 | .set_speed = bcm63xx_spi_set_speed, | |
306 | .xfer = bcm63xx_spi_xfer, | |
307 | }; | |
308 | ||
309 | static const unsigned long bcm6348_spi_regs[] = { | |
310 | [SPI_CLK] = SPI_6348_CLK, | |
311 | [SPI_CMD] = SPI_6348_CMD, | |
312 | [SPI_CTL] = SPI_6348_CTL, | |
313 | [SPI_CTL_SHIFT] = SPI_6348_CTL_SHIFT, | |
314 | [SPI_FILL] = SPI_6348_FILL, | |
315 | [SPI_IR_MASK] = SPI_6348_IR_MASK, | |
316 | [SPI_IR_STAT] = SPI_6348_IR_STAT, | |
317 | [SPI_RX] = SPI_6348_RX, | |
318 | [SPI_RX_SIZE] = SPI_6348_RX_SIZE, | |
319 | [SPI_TX] = SPI_6348_TX, | |
320 | [SPI_TX_SIZE] = SPI_6348_TX_SIZE, | |
321 | }; | |
322 | ||
323 | static const unsigned long bcm6358_spi_regs[] = { | |
324 | [SPI_CLK] = SPI_6358_CLK, | |
325 | [SPI_CMD] = SPI_6358_CMD, | |
326 | [SPI_CTL] = SPI_6358_CTL, | |
327 | [SPI_CTL_SHIFT] = SPI_6358_CTL_SHIFT, | |
328 | [SPI_FILL] = SPI_6358_FILL, | |
329 | [SPI_IR_MASK] = SPI_6358_IR_MASK, | |
330 | [SPI_IR_STAT] = SPI_6358_IR_STAT, | |
331 | [SPI_RX] = SPI_6358_RX, | |
332 | [SPI_RX_SIZE] = SPI_6358_RX_SIZE, | |
333 | [SPI_TX] = SPI_6358_TX, | |
334 | [SPI_TX_SIZE] = SPI_6358_TX_SIZE, | |
335 | }; | |
336 | ||
337 | static const struct udevice_id bcm63xx_spi_ids[] = { | |
338 | { | |
339 | .compatible = "brcm,bcm6348-spi", | |
340 | .data = (ulong)&bcm6348_spi_regs, | |
341 | }, { | |
342 | .compatible = "brcm,bcm6358-spi", | |
343 | .data = (ulong)&bcm6358_spi_regs, | |
344 | }, { /* sentinel */ } | |
345 | }; | |
346 | ||
347 | static int bcm63xx_spi_child_pre_probe(struct udevice *dev) | |
348 | { | |
349 | struct bcm63xx_spi_priv *priv = dev_get_priv(dev->parent); | |
350 | const unsigned long *regs = priv->regs; | |
351 | struct spi_slave *slave = dev_get_parent_priv(dev); | |
352 | struct dm_spi_slave_platdata *plat = dev_get_parent_platdata(dev); | |
353 | ||
354 | /* check cs */ | |
355 | if (plat->cs >= priv->num_cs) { | |
356 | printf("no cs %u\n", plat->cs); | |
357 | return -ENODEV; | |
358 | } | |
359 | ||
360 | /* max read/write sizes */ | |
361 | slave->max_read_size = regs[SPI_RX_SIZE]; | |
362 | slave->max_write_size = regs[SPI_TX_SIZE]; | |
363 | ||
364 | return 0; | |
365 | } | |
366 | ||
367 | static int bcm63xx_spi_probe(struct udevice *dev) | |
368 | { | |
369 | struct bcm63xx_spi_priv *priv = dev_get_priv(dev); | |
370 | const unsigned long *regs = | |
371 | (const unsigned long *)dev_get_driver_data(dev); | |
372 | struct reset_ctl rst_ctl; | |
373 | struct clk clk; | |
5ac07d29 ÁFR |
374 | int ret; |
375 | ||
85e1ddba ÁFR |
376 | priv->base = dev_remap_addr(dev); |
377 | if (!priv->base) | |
5ac07d29 ÁFR |
378 | return -EINVAL; |
379 | ||
380 | priv->regs = regs; | |
85e1ddba | 381 | priv->num_cs = dev_read_u32_default(dev, "num-cs", 8); |
5ac07d29 ÁFR |
382 | |
383 | /* enable clock */ | |
384 | ret = clk_get_by_index(dev, 0, &clk); | |
385 | if (ret < 0) | |
386 | return ret; | |
387 | ||
388 | ret = clk_enable(&clk); | |
389 | if (ret < 0) | |
390 | return ret; | |
391 | ||
392 | ret = clk_free(&clk); | |
393 | if (ret < 0) | |
394 | return ret; | |
395 | ||
396 | /* perform reset */ | |
397 | ret = reset_get_by_index(dev, 0, &rst_ctl); | |
398 | if (ret < 0) | |
399 | return ret; | |
400 | ||
401 | ret = reset_deassert(&rst_ctl); | |
402 | if (ret < 0) | |
403 | return ret; | |
404 | ||
405 | ret = reset_free(&rst_ctl); | |
406 | if (ret < 0) | |
407 | return ret; | |
408 | ||
409 | /* initialize hardware */ | |
410 | writeb_be(0, priv->base + regs[SPI_IR_MASK]); | |
411 | ||
412 | /* set fill register */ | |
413 | writeb_be(0xff, priv->base + regs[SPI_FILL]); | |
414 | ||
415 | return 0; | |
416 | } | |
417 | ||
418 | U_BOOT_DRIVER(bcm63xx_spi) = { | |
419 | .name = "bcm63xx_spi", | |
420 | .id = UCLASS_SPI, | |
421 | .of_match = bcm63xx_spi_ids, | |
422 | .ops = &bcm63xx_spi_ops, | |
423 | .priv_auto_alloc_size = sizeof(struct bcm63xx_spi_priv), | |
424 | .child_pre_probe = bcm63xx_spi_child_pre_probe, | |
425 | .probe = bcm63xx_spi_probe, | |
426 | }; |