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