Commit | Line | Data |
---|---|---|
894c3ad2 TF |
1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* | |
3 | * (C) Copyright 2018 Cisco Systems, Inc. | |
4 | * | |
5 | * Author: Thomas Fitzsimmons <fitzsim@fitzsim.org> | |
6 | */ | |
7 | ||
401d1c4f | 8 | #include <asm/global_data.h> |
894c3ad2 TF |
9 | #include <asm/io.h> |
10 | #include <command.h> | |
11 | #include <config.h> | |
12 | #include <dm.h> | |
13 | #include <errno.h> | |
14 | #include <fdtdec.h> | |
15 | #include <linux/bitops.h> | |
16 | #include <linux/delay.h> | |
17 | #include <log.h> | |
18 | #include <malloc.h> | |
19 | #include <spi.h> | |
20 | #include <time.h> | |
21 | ||
22 | DECLARE_GLOBAL_DATA_PTR; | |
23 | ||
24 | #define SPBR_MIN 8 | |
25 | #define BITS_PER_WORD 8 | |
26 | ||
27 | #define NUM_TXRAM 32 | |
28 | #define NUM_RXRAM 32 | |
29 | #define NUM_CDRAM 16 | |
30 | ||
31 | /* hif_mspi register structure. */ | |
32 | struct bcmstb_hif_mspi_regs { | |
33 | u32 spcr0_lsb; /* 0x000 */ | |
34 | u32 spcr0_msb; /* 0x004 */ | |
35 | u32 spcr1_lsb; /* 0x008 */ | |
36 | u32 spcr1_msb; /* 0x00c */ | |
37 | u32 newqp; /* 0x010 */ | |
38 | u32 endqp; /* 0x014 */ | |
39 | u32 spcr2; /* 0x018 */ | |
40 | u32 reserved0; /* 0x01c */ | |
41 | u32 mspi_status; /* 0x020 */ | |
42 | u32 cptqp; /* 0x024 */ | |
43 | u32 spcr3; /* 0x028 */ | |
44 | u32 revision; /* 0x02c */ | |
45 | u32 reserved1[4]; /* 0x030 */ | |
46 | u32 txram[NUM_TXRAM]; /* 0x040 */ | |
47 | u32 rxram[NUM_RXRAM]; /* 0x0c0 */ | |
48 | u32 cdram[NUM_CDRAM]; /* 0x140 */ | |
49 | u32 write_lock; /* 0x180 */ | |
50 | }; | |
51 | ||
52 | /* hif_mspi masks. */ | |
53 | #define HIF_MSPI_SPCR2_CONT_AFTER_CMD_MASK 0x00000080 | |
54 | #define HIF_MSPI_SPCR2_SPE_MASK 0x00000040 | |
55 | #define HIF_MSPI_SPCR2_SPIFIE_MASK 0x00000020 | |
56 | #define HIF_MSPI_WRITE_LOCK_WRITE_LOCK_MASK 0x00000001 | |
57 | ||
58 | /* bspi offsets. */ | |
59 | #define BSPI_MAST_N_BOOT_CTRL 0x008 | |
60 | ||
61 | /* bspi_raf is not used in this driver. */ | |
62 | ||
63 | /* hif_spi_intr2 offsets and masks. */ | |
64 | #define HIF_SPI_INTR2_CPU_CLEAR 0x08 | |
65 | #define HIF_SPI_INTR2_CPU_MASK_SET 0x10 | |
66 | #define HIF_SPI_INTR2_CPU_MASK_CLEAR 0x14 | |
67 | #define HIF_SPI_INTR2_CPU_SET_MSPI_DONE_MASK 0x00000020 | |
68 | ||
69 | /* SPI transfer timeout in milliseconds. */ | |
70 | #define HIF_MSPI_WAIT 10 | |
71 | ||
72 | enum bcmstb_base_type { | |
73 | HIF_MSPI, | |
74 | BSPI, | |
75 | HIF_SPI_INTR2, | |
76 | CS_REG, | |
77 | BASE_LAST, | |
78 | }; | |
79 | ||
8a8d24bd | 80 | struct bcmstb_spi_plat { |
894c3ad2 TF |
81 | void *base[4]; |
82 | }; | |
83 | ||
84 | struct bcmstb_spi_priv { | |
85 | struct bcmstb_hif_mspi_regs *regs; | |
86 | void *bspi; | |
87 | void *hif_spi_intr2; | |
88 | void *cs_reg; | |
89 | int default_cs; | |
90 | int curr_cs; | |
91 | uint tx_slot; | |
92 | uint rx_slot; | |
93 | u8 saved_cmd[NUM_CDRAM]; | |
94 | uint saved_cmd_len; | |
95 | void *saved_din_addr; | |
96 | }; | |
97 | ||
d1998a9f | 98 | static int bcmstb_spi_of_to_plat(struct udevice *bus) |
894c3ad2 | 99 | { |
8a8d24bd | 100 | struct bcmstb_spi_plat *plat = dev_get_plat(bus); |
894c3ad2 TF |
101 | const void *fdt = gd->fdt_blob; |
102 | int node = dev_of_offset(bus); | |
103 | int ret = 0; | |
104 | int i = 0; | |
105 | struct fdt_resource resource = { 0 }; | |
106 | char *names[BASE_LAST] = { "hif_mspi", "bspi", "hif_spi_intr2", | |
107 | "cs_reg" }; | |
108 | const phys_addr_t defaults[BASE_LAST] = { BCMSTB_HIF_MSPI_BASE, | |
109 | BCMSTB_BSPI_BASE, | |
110 | BCMSTB_HIF_SPI_INTR2, | |
111 | BCMSTB_CS_REG }; | |
112 | ||
113 | for (i = 0; i < BASE_LAST; i++) { | |
114 | plat->base[i] = (void *)defaults[i]; | |
115 | ||
116 | ret = fdt_get_named_resource(fdt, node, "reg", "reg-names", | |
117 | names[i], &resource); | |
118 | if (ret) { | |
119 | printf("%s: Assuming BCMSTB SPI %s address 0x0x%p\n", | |
120 | __func__, names[i], (void *)defaults[i]); | |
121 | } else { | |
122 | plat->base[i] = (void *)resource.start; | |
123 | debug("BCMSTB SPI %s address: 0x0x%p\n", | |
124 | names[i], (void *)plat->base[i]); | |
125 | } | |
126 | } | |
127 | ||
128 | return 0; | |
129 | } | |
130 | ||
131 | static void bcmstb_spi_hw_set_parms(struct bcmstb_spi_priv *priv) | |
132 | { | |
133 | writel(SPBR_MIN, &priv->regs->spcr0_lsb); | |
134 | writel(BITS_PER_WORD << 2 | SPI_MODE_3, &priv->regs->spcr0_msb); | |
135 | } | |
136 | ||
137 | static void bcmstb_spi_enable_interrupt(void *base, u32 mask) | |
138 | { | |
139 | void *reg = base + HIF_SPI_INTR2_CPU_MASK_CLEAR; | |
140 | ||
141 | writel(readl(reg) | mask, reg); | |
142 | readl(reg); | |
143 | } | |
144 | ||
145 | static void bcmstb_spi_disable_interrupt(void *base, u32 mask) | |
146 | { | |
147 | void *reg = base + HIF_SPI_INTR2_CPU_MASK_SET; | |
148 | ||
149 | writel(readl(reg) | mask, reg); | |
150 | readl(reg); | |
151 | } | |
152 | ||
153 | static void bcmstb_spi_clear_interrupt(void *base, u32 mask) | |
154 | { | |
155 | void *reg = base + HIF_SPI_INTR2_CPU_CLEAR; | |
156 | ||
157 | writel(readl(reg) | mask, reg); | |
158 | readl(reg); | |
159 | } | |
160 | ||
161 | static int bcmstb_spi_probe(struct udevice *bus) | |
162 | { | |
8a8d24bd | 163 | struct bcmstb_spi_plat *plat = dev_get_plat(bus); |
894c3ad2 TF |
164 | struct bcmstb_spi_priv *priv = dev_get_priv(bus); |
165 | ||
166 | priv->regs = plat->base[HIF_MSPI]; | |
167 | priv->bspi = plat->base[BSPI]; | |
168 | priv->hif_spi_intr2 = plat->base[HIF_SPI_INTR2]; | |
169 | priv->cs_reg = plat->base[CS_REG]; | |
170 | priv->default_cs = 0; | |
171 | priv->curr_cs = -1; | |
172 | priv->tx_slot = 0; | |
173 | priv->rx_slot = 0; | |
174 | memset(priv->saved_cmd, 0, NUM_CDRAM); | |
175 | priv->saved_cmd_len = 0; | |
176 | priv->saved_din_addr = NULL; | |
177 | ||
178 | debug("spi_xfer: tx regs: 0x%p\n", &priv->regs->txram[0]); | |
179 | debug("spi_xfer: rx regs: 0x%p\n", &priv->regs->rxram[0]); | |
180 | ||
181 | /* Disable BSPI. */ | |
182 | writel(1, priv->bspi + BSPI_MAST_N_BOOT_CTRL); | |
183 | readl(priv->bspi + BSPI_MAST_N_BOOT_CTRL); | |
184 | ||
185 | /* Set up interrupts. */ | |
186 | bcmstb_spi_disable_interrupt(priv->hif_spi_intr2, 0xffffffff); | |
187 | bcmstb_spi_clear_interrupt(priv->hif_spi_intr2, 0xffffffff); | |
188 | bcmstb_spi_enable_interrupt(priv->hif_spi_intr2, | |
189 | HIF_SPI_INTR2_CPU_SET_MSPI_DONE_MASK); | |
190 | ||
191 | /* Set up control registers. */ | |
192 | writel(0, &priv->regs->spcr1_lsb); | |
193 | writel(0, &priv->regs->spcr1_msb); | |
194 | writel(0, &priv->regs->newqp); | |
195 | writel(0, &priv->regs->endqp); | |
196 | writel(HIF_MSPI_SPCR2_SPIFIE_MASK, &priv->regs->spcr2); | |
197 | writel(0, &priv->regs->spcr3); | |
198 | ||
199 | bcmstb_spi_hw_set_parms(priv); | |
200 | ||
201 | return 0; | |
202 | } | |
203 | ||
204 | static void bcmstb_spi_submit(struct bcmstb_spi_priv *priv, bool done) | |
205 | { | |
206 | debug("WR NEWQP: %d\n", 0); | |
207 | writel(0, &priv->regs->newqp); | |
208 | ||
209 | debug("WR ENDQP: %d\n", priv->tx_slot - 1); | |
210 | writel(priv->tx_slot - 1, &priv->regs->endqp); | |
211 | ||
212 | if (done) { | |
213 | debug("WR CDRAM[%d]: %02x\n", priv->tx_slot - 1, | |
214 | readl(&priv->regs->cdram[priv->tx_slot - 1]) & ~0x80); | |
215 | writel(readl(&priv->regs->cdram[priv->tx_slot - 1]) & ~0x80, | |
216 | &priv->regs->cdram[priv->tx_slot - 1]); | |
217 | } | |
218 | ||
219 | /* Force chip select first time. */ | |
220 | if (priv->curr_cs != priv->default_cs) { | |
221 | debug("spi_xfer: switching chip select to %d\n", | |
222 | priv->default_cs); | |
223 | writel((readl(priv->cs_reg) & ~0xff) | (1 << priv->default_cs), | |
224 | priv->cs_reg); | |
225 | readl(priv->cs_reg); | |
226 | udelay(10); | |
227 | priv->curr_cs = priv->default_cs; | |
228 | } | |
229 | ||
230 | debug("WR WRITE_LOCK: %02x\n", 1); | |
231 | writel((readl(&priv->regs->write_lock) & | |
232 | ~HIF_MSPI_WRITE_LOCK_WRITE_LOCK_MASK) | 1, | |
233 | &priv->regs->write_lock); | |
234 | readl(&priv->regs->write_lock); | |
235 | ||
236 | debug("WR SPCR2: %02x\n", | |
237 | HIF_MSPI_SPCR2_SPIFIE_MASK | | |
238 | HIF_MSPI_SPCR2_SPE_MASK | | |
239 | HIF_MSPI_SPCR2_CONT_AFTER_CMD_MASK); | |
240 | writel(HIF_MSPI_SPCR2_SPIFIE_MASK | | |
241 | HIF_MSPI_SPCR2_SPE_MASK | | |
242 | HIF_MSPI_SPCR2_CONT_AFTER_CMD_MASK, | |
243 | &priv->regs->spcr2); | |
244 | } | |
245 | ||
246 | static int bcmstb_spi_wait(struct bcmstb_spi_priv *priv) | |
247 | { | |
248 | u32 start_time = get_timer(0); | |
249 | u32 status = readl(&priv->regs->mspi_status); | |
250 | ||
251 | while (!(status & 1)) { | |
252 | if (get_timer(start_time) > HIF_MSPI_WAIT) | |
253 | return -ETIMEDOUT; | |
254 | status = readl(&priv->regs->mspi_status); | |
255 | } | |
256 | ||
257 | writel(readl(&priv->regs->mspi_status) & ~1, &priv->regs->mspi_status); | |
258 | bcmstb_spi_clear_interrupt(priv->hif_spi_intr2, | |
259 | HIF_SPI_INTR2_CPU_SET_MSPI_DONE_MASK); | |
260 | ||
261 | return 0; | |
262 | } | |
263 | ||
264 | static int bcmstb_spi_xfer(struct udevice *dev, unsigned int bitlen, | |
265 | const void *dout, void *din, unsigned long flags) | |
266 | { | |
267 | uint len = bitlen / 8; | |
268 | uint tx_len = len; | |
269 | uint rx_len = len; | |
270 | const u8 *out_bytes = (u8 *)dout; | |
271 | u8 *in_bytes = (u8 *)din; | |
272 | struct udevice *bus = dev_get_parent(dev); | |
273 | struct bcmstb_spi_priv *priv = dev_get_priv(bus); | |
274 | struct bcmstb_hif_mspi_regs *regs = priv->regs; | |
275 | ||
276 | debug("spi_xfer: %d, t: 0x%p, r: 0x%p, f: %lx\n", | |
277 | len, dout, din, flags); | |
278 | debug("spi_xfer: chip select: %x\n", readl(priv->cs_reg) & 0xff); | |
279 | debug("spi_xfer: tx addr: 0x%p\n", ®s->txram[0]); | |
280 | debug("spi_xfer: rx addr: 0x%p\n", ®s->rxram[0]); | |
281 | debug("spi_xfer: cd addr: 0x%p\n", ®s->cdram[0]); | |
282 | ||
283 | if (flags & SPI_XFER_END) { | |
284 | debug("spi_xfer: clearing saved din address: 0x%p\n", | |
285 | priv->saved_din_addr); | |
286 | priv->saved_din_addr = NULL; | |
287 | priv->saved_cmd_len = 0; | |
288 | memset(priv->saved_cmd, 0, NUM_CDRAM); | |
289 | } | |
290 | ||
291 | if (bitlen == 0) | |
292 | return 0; | |
293 | ||
294 | if (bitlen % 8) { | |
295 | printf("%s: Non-byte-aligned transfer\n", __func__); | |
296 | return -EOPNOTSUPP; | |
297 | } | |
298 | ||
299 | if (flags & ~(SPI_XFER_BEGIN | SPI_XFER_END)) { | |
300 | printf("%s: Unsupported flags: %lx\n", __func__, flags); | |
301 | return -EOPNOTSUPP; | |
302 | } | |
303 | ||
304 | if (flags & SPI_XFER_BEGIN) { | |
305 | priv->tx_slot = 0; | |
306 | priv->rx_slot = 0; | |
307 | ||
308 | if (out_bytes && len > NUM_CDRAM) { | |
309 | printf("%s: Unable to save transfer\n", __func__); | |
310 | return -EOPNOTSUPP; | |
311 | } | |
312 | ||
313 | if (out_bytes && !(flags & SPI_XFER_END)) { | |
314 | /* | |
315 | * This is the start of a transmit operation | |
316 | * that will need repeating if the calling | |
317 | * code polls for the result. Save it for | |
318 | * subsequent transmission. | |
319 | */ | |
320 | debug("spi_xfer: saving command: %x, %d\n", | |
321 | out_bytes[0], len); | |
322 | priv->saved_cmd_len = len; | |
323 | memcpy(priv->saved_cmd, out_bytes, priv->saved_cmd_len); | |
324 | } | |
325 | } | |
326 | ||
327 | if (!(flags & (SPI_XFER_BEGIN | SPI_XFER_END))) { | |
328 | if (priv->saved_din_addr == din) { | |
329 | /* | |
330 | * The caller is polling for status. Repeat | |
331 | * the last transmission. | |
332 | */ | |
333 | int ret = 0; | |
334 | ||
335 | debug("spi_xfer: Making recursive call\n"); | |
336 | ret = bcmstb_spi_xfer(dev, priv->saved_cmd_len * 8, | |
337 | priv->saved_cmd, NULL, | |
338 | SPI_XFER_BEGIN); | |
339 | if (ret) { | |
340 | printf("%s: Recursive call failed\n", __func__); | |
341 | return ret; | |
342 | } | |
343 | } else { | |
344 | debug("spi_xfer: saving din address: 0x%p\n", din); | |
345 | priv->saved_din_addr = din; | |
346 | } | |
347 | } | |
348 | ||
349 | while (rx_len > 0) { | |
350 | priv->rx_slot = priv->tx_slot; | |
351 | ||
352 | while (priv->tx_slot < NUM_CDRAM && tx_len > 0) { | |
353 | bcmstb_spi_hw_set_parms(priv); | |
354 | debug("WR TXRAM[%d]: %02x\n", priv->tx_slot, | |
355 | out_bytes ? out_bytes[len - tx_len] : 0xff); | |
356 | writel(out_bytes ? out_bytes[len - tx_len] : 0xff, | |
357 | ®s->txram[priv->tx_slot << 1]); | |
358 | debug("WR CDRAM[%d]: %02x\n", priv->tx_slot, 0x8e); | |
359 | writel(0x8e, ®s->cdram[priv->tx_slot]); | |
360 | priv->tx_slot++; | |
361 | tx_len--; | |
362 | if (!in_bytes) | |
363 | rx_len--; | |
364 | } | |
365 | ||
366 | debug("spi_xfer: early return clauses: %d, %d, %d\n", | |
367 | len <= NUM_CDRAM, | |
368 | !in_bytes, | |
369 | (flags & (SPI_XFER_BEGIN | | |
370 | SPI_XFER_END)) == SPI_XFER_BEGIN); | |
371 | if (len <= NUM_CDRAM && | |
372 | !in_bytes && | |
373 | (flags & (SPI_XFER_BEGIN | SPI_XFER_END)) == SPI_XFER_BEGIN) | |
374 | return 0; | |
375 | ||
376 | bcmstb_spi_submit(priv, tx_len == 0); | |
377 | ||
378 | if (bcmstb_spi_wait(priv) == -ETIMEDOUT) { | |
379 | printf("%s: Timed out\n", __func__); | |
380 | return -ETIMEDOUT; | |
381 | } | |
382 | ||
383 | priv->tx_slot %= NUM_CDRAM; | |
384 | ||
385 | if (in_bytes) { | |
386 | while (priv->rx_slot < NUM_CDRAM && rx_len > 0) { | |
387 | in_bytes[len - rx_len] = | |
388 | readl(®s->rxram[(priv->rx_slot << 1) | |
389 | + 1]) | |
390 | & 0xff; | |
391 | debug("RD RXRAM[%d]: %02x\n", | |
392 | priv->rx_slot, in_bytes[len - rx_len]); | |
393 | priv->rx_slot++; | |
394 | rx_len--; | |
395 | } | |
396 | } | |
397 | } | |
398 | ||
399 | if (flags & SPI_XFER_END) { | |
400 | debug("WR WRITE_LOCK: %02x\n", 0); | |
401 | writel((readl(&priv->regs->write_lock) & | |
402 | ~HIF_MSPI_WRITE_LOCK_WRITE_LOCK_MASK) | 0, | |
403 | &priv->regs->write_lock); | |
404 | readl(&priv->regs->write_lock); | |
405 | } | |
406 | ||
407 | return 0; | |
408 | } | |
409 | ||
410 | static int bcmstb_spi_set_speed(struct udevice *dev, uint speed) | |
411 | { | |
412 | return 0; | |
413 | } | |
414 | ||
415 | static int bcmstb_spi_set_mode(struct udevice *dev, uint mode) | |
416 | { | |
417 | return 0; | |
418 | } | |
419 | ||
420 | static const struct dm_spi_ops bcmstb_spi_ops = { | |
421 | .xfer = bcmstb_spi_xfer, | |
422 | .set_speed = bcmstb_spi_set_speed, | |
423 | .set_mode = bcmstb_spi_set_mode, | |
424 | }; | |
425 | ||
426 | static const struct udevice_id bcmstb_spi_id[] = { | |
427 | { .compatible = "brcm,spi-brcmstb" }, | |
428 | { } | |
429 | }; | |
430 | ||
431 | U_BOOT_DRIVER(bcmstb_spi) = { | |
432 | .name = "bcmstb_spi", | |
433 | .id = UCLASS_SPI, | |
434 | .of_match = bcmstb_spi_id, | |
435 | .ops = &bcmstb_spi_ops, | |
d1998a9f | 436 | .of_to_plat = bcmstb_spi_of_to_plat, |
894c3ad2 | 437 | .probe = bcmstb_spi_probe, |
8a8d24bd | 438 | .plat_auto = sizeof(struct bcmstb_spi_plat), |
41575d8e | 439 | .priv_auto = sizeof(struct bcmstb_spi_priv), |
894c3ad2 | 440 | }; |