| /* SPDX-License-Identifier: GPL-2.0-only */ |
| |
| /* |
| * Derived from Cavium's BSD-3 Clause OCTEONTX-SDK-6.2.0. |
| */ |
| |
| #include <device/mmio.h> |
| #include <assert.h> |
| #include <console/console.h> |
| #include <endian.h> |
| #include <soc/addressmap.h> |
| #include <soc/spi.h> |
| #include <soc/clock.h> |
| #include <spi-generic.h> |
| #include <spi_flash.h> |
| #include <timer.h> |
| |
| union cavium_spi_cfg { |
| u64 u; |
| struct { |
| u64 enable : 1; |
| u64 idlelow : 1; |
| u64 clk_cont : 1; |
| u64 wireor : 1; |
| u64 lsbfirst : 1; |
| u64 : 2; |
| u64 cshi : 1; |
| u64 idleclks : 2; |
| u64 tristate : 1; |
| u64 cslate : 1; |
| u64 csena : 4; /* Must be one */ |
| u64 clkdiv : 13; |
| u64 : 35; |
| } s; |
| }; |
| |
| union cavium_spi_sts { |
| u64 u; |
| struct { |
| u64 busy : 1; |
| u64 mpi_intr : 1; |
| u64 : 6; |
| u64 rxnum : 5; |
| u64 : 51; |
| } s; |
| }; |
| |
| union cavium_spi_tx { |
| u64 u; |
| struct { |
| u64 totnum : 5; |
| u64 : 3; |
| u64 txnum : 5; |
| u64 : 3; |
| u64 leavecs : 1; |
| u64 : 3; |
| u64 csid : 2; |
| u64 : 42; |
| } s; |
| }; |
| |
| struct cavium_spi { |
| union cavium_spi_cfg cfg; |
| union cavium_spi_sts sts; |
| union cavium_spi_tx tx; |
| u64 rsvd1; |
| u64 sts_w1s; |
| u64 rsvd2; |
| u64 int_ena_w1c; |
| u64 int_ena_w1s; |
| u64 wide_dat; |
| u8 rsvd4[0x38]; |
| u64 dat[8]; |
| }; |
| |
| check_member(cavium_spi, cfg, 0); |
| check_member(cavium_spi, sts, 0x8); |
| check_member(cavium_spi, tx, 0x10); |
| check_member(cavium_spi, dat[7], 0xb8); |
| |
| struct cavium_spi_slave { |
| struct cavium_spi *regs; |
| int cs; |
| }; |
| |
| #define SPI_TIMEOUT_US 5000 |
| |
| static struct cavium_spi_slave cavium_spi_slaves[] = { |
| { |
| .regs = (struct cavium_spi *)MPI_PF_BAR0, |
| .cs = 0, |
| }, |
| }; |
| |
| static struct cavium_spi_slave *to_cavium_spi(const struct spi_slave *slave) |
| { |
| assert(slave->bus < ARRAY_SIZE(cavium_spi_slaves)); |
| return &cavium_spi_slaves[slave->bus]; |
| } |
| |
| /** |
| * Enable the SPI controller. Pins are driven. |
| * |
| * @param bus The SPI bus to operate on |
| */ |
| void spi_enable(const size_t bus) |
| { |
| union cavium_spi_cfg cfg; |
| |
| assert(bus < ARRAY_SIZE(cavium_spi_slaves)); |
| if (bus >= ARRAY_SIZE(cavium_spi_slaves)) |
| return; |
| |
| struct cavium_spi *regs = cavium_spi_slaves[bus].regs; |
| |
| cfg.u = read64(®s->cfg); |
| cfg.s.csena = 0xf; |
| cfg.s.enable = 1; |
| write64(®s->cfg, cfg.u); |
| } |
| |
| /** |
| * Disable the SPI controller. Pins are tristated. |
| * |
| * @param bus The SPI bus to operate on |
| */ |
| void spi_disable(const size_t bus) |
| { |
| union cavium_spi_cfg cfg; |
| |
| assert(bus < ARRAY_SIZE(cavium_spi_slaves)); |
| if (bus >= ARRAY_SIZE(cavium_spi_slaves)) |
| return; |
| |
| struct cavium_spi *regs = cavium_spi_slaves[bus].regs; |
| |
| cfg.u = read64(®s->cfg); |
| cfg.s.csena = 0xf; |
| cfg.s.enable = 0; |
| write64(®s->cfg, cfg.u); |
| } |
| |
| /** |
| * Set SPI Chip select line and level if asserted. |
| * |
| * @param bus The SPI bus to operate on |
| * @param chip_select The chip select pin to use (0 - 3) |
| * @param assert_is_low CS pin state is low when asserted |
| */ |
| void spi_set_cs(const size_t bus, |
| const size_t chip_select, |
| const size_t assert_is_low) |
| { |
| union cavium_spi_cfg cfg; |
| |
| assert(bus < ARRAY_SIZE(cavium_spi_slaves)); |
| if (bus >= ARRAY_SIZE(cavium_spi_slaves)) |
| return; |
| |
| cavium_spi_slaves[bus].cs = chip_select & 0x3; |
| struct cavium_spi *regs = cavium_spi_slaves[bus].regs; |
| |
| cfg.u = read64(®s->cfg); |
| cfg.s.csena = 0xf; |
| cfg.s.cshi = !assert_is_low; |
| write64(®s->cfg, cfg.u); |
| |
| //FIXME: CS2/3: Change pin mux here |
| } |
| |
| /** |
| * Set SPI clock frequency. |
| * |
| * @param bus The SPI bus to operate on |
| * @param speed_hz The SPI frequency in Hz |
| * @param idle_low The SPI clock idles low |
| * @param idle_cycles Number of CLK cycles between two commands (0 - 3) |
| |
| */ |
| void spi_set_clock(const size_t bus, |
| const size_t speed_hz, |
| const size_t idle_low, |
| const size_t idle_cycles) |
| { |
| union cavium_spi_cfg cfg; |
| |
| assert(bus < ARRAY_SIZE(cavium_spi_slaves)); |
| if (bus >= ARRAY_SIZE(cavium_spi_slaves)) |
| return; |
| |
| struct cavium_spi *regs = cavium_spi_slaves[bus].regs; |
| const uint64_t sclk = thunderx_get_io_clock(); |
| |
| cfg.u = read64(®s->cfg); |
| cfg.s.csena = 0xf; |
| cfg.s.clk_cont = 0; |
| cfg.s.idlelow = !!idle_low; |
| cfg.s.idleclks = idle_cycles & 0x3; |
| cfg.s.clkdiv = MIN(sclk / (2ULL * speed_hz), 0x1fff); |
| write64(®s->cfg, cfg.u); |
| |
| printk(BIOS_DEBUG, "SPI: set clock to %lld kHz\n", |
| (sclk / (2ULL * cfg.s.clkdiv)) >> 10); |
| } |
| |
| /** |
| * Get current SPI clock frequency in Hz. |
| * |
| * @param bus The SPI bus to operate on |
| */ |
| uint64_t spi_get_clock(const size_t bus) |
| { |
| union cavium_spi_cfg cfg; |
| |
| assert(bus < ARRAY_SIZE(cavium_spi_slaves)); |
| if (bus >= ARRAY_SIZE(cavium_spi_slaves)) |
| return 0; |
| |
| struct cavium_spi *regs = cavium_spi_slaves[bus].regs; |
| const uint64_t sclk = thunderx_get_io_clock(); |
| |
| cfg.u = read64(®s->cfg); |
| |
| return (sclk / (2ULL * cfg.s.clkdiv)); |
| } |
| |
| /** |
| * Set SPI LSB/MSB first. |
| * |
| * @param bus The SPI bus to operate on |
| * @param lsb_first The SPI operates LSB first |
| * |
| */ |
| void spi_set_lsbmsb(const size_t bus, const size_t lsb_first) |
| { |
| union cavium_spi_cfg cfg; |
| |
| assert(bus < ARRAY_SIZE(cavium_spi_slaves)); |
| if (bus >= ARRAY_SIZE(cavium_spi_slaves)) |
| return; |
| |
| struct cavium_spi *regs = cavium_spi_slaves[bus].regs; |
| |
| cfg.u = read64(®s->cfg); |
| cfg.s.csena = 0xf; |
| cfg.s.lsbfirst = !!lsb_first; |
| write64(®s->cfg, cfg.u); |
| } |
| |
| /** |
| * Init SPI with custom parameters and enable SPI controller. |
| * |
| * @param bus The SPI bus to operate on |
| * @param speed_hz The SPI frequency in Hz |
| * @param idle_low The SPI clock idles low |
| * @param idle_cycles Number of CLK cycles between two commands (0 - 3) |
| * @param lsb_first The SPI operates LSB first |
| * @param chip_select The chip select pin to use (0 - 3) |
| * @param assert_is_low CS pin state is low when asserted |
| */ |
| void spi_init_custom(const size_t bus, |
| const size_t speed_hz, |
| const size_t idle_low, |
| const size_t idle_cycles, |
| const size_t lsb_first, |
| const size_t chip_select, |
| const size_t assert_is_low) |
| { |
| spi_disable(bus); |
| spi_set_clock(bus, speed_hz, idle_low, idle_cycles); |
| spi_set_lsbmsb(bus, lsb_first); |
| spi_set_cs(bus, chip_select, assert_is_low); |
| spi_enable(bus); |
| } |
| |
| /** |
| * Init all SPI controllers with default values and enable all SPI controller. |
| * |
| */ |
| void spi_init(void) |
| { |
| for (size_t i = 0; i < ARRAY_SIZE(cavium_spi_slaves); i++) { |
| spi_disable(i); |
| spi_set_clock(i, 12500000, 0, 0); |
| spi_set_lsbmsb(i, 0); |
| spi_set_cs(i, 0, 1); |
| spi_enable(i); |
| } |
| } |
| |
| static int cavium_spi_wait(struct cavium_spi *regs) |
| { |
| struct stopwatch sw; |
| union cavium_spi_sts sts; |
| |
| stopwatch_init_usecs_expire(&sw, SPI_TIMEOUT_US); |
| do { |
| sts.u = read64(®s->sts); |
| if (!sts.s.busy) |
| return 0; |
| } while (!stopwatch_expired(&sw)); |
| printk(BIOS_DEBUG, "SPI: Timed out after %uus\n", SPI_TIMEOUT_US); |
| return -1; |
| } |
| |
| static int do_xfer(const struct spi_slave *slave, struct spi_op *vector, |
| int leavecs) |
| { |
| struct cavium_spi *regs = to_cavium_spi(slave)->regs; |
| uint8_t *out_buf = (uint8_t *)vector->dout; |
| size_t bytesout = vector->bytesout; |
| uint8_t *in_buf = (uint8_t *)vector->din; |
| size_t bytesin = vector->bytesin; |
| union cavium_spi_sts sts; |
| union cavium_spi_tx tx; |
| |
| /** |
| * The CN81xx SPI controller is half-duplex and has 8 data registers. |
| * If >8 bytes remain in the transfer then we must set LEAVECS = 1 so |
| * that the /CS remains asserted. Once <=8 bytes remain we must set |
| * LEAVECS = 0 so that /CS is de-asserted, thus completing the transfer. |
| */ |
| while (bytesout) { |
| size_t out_now = MIN(bytesout, 8); |
| unsigned int i; |
| |
| for (i = 0; i < out_now; i++) |
| write64(®s->dat[i], out_buf[i] & 0xff); |
| |
| tx.u = 0; |
| tx.s.csid = to_cavium_spi(slave)->cs; |
| if (leavecs || ((bytesout > 8) || bytesin)) |
| tx.s.leavecs = 1; |
| /* number of bytes to transmit goes in both TXNUM and TOTNUM */ |
| tx.s.totnum = out_now; |
| tx.s.txnum = out_now; |
| write64(®s->tx, tx.u); |
| |
| /* check status */ |
| if (cavium_spi_wait(regs) < 0) |
| return -1; |
| |
| bytesout -= out_now; |
| out_buf += out_now; |
| } |
| |
| while (bytesin) { |
| size_t in_now = MIN(bytesin, 8); |
| unsigned int i; |
| |
| tx.u = 0; |
| tx.s.csid = to_cavium_spi(slave)->cs; |
| if (leavecs || (bytesin > 8)) |
| tx.s.leavecs = 1; |
| tx.s.totnum = in_now; |
| write64(®s->tx, tx.u); |
| |
| /* check status */ |
| if (cavium_spi_wait(regs) < 0) |
| return -1; |
| |
| sts.u = read64(®s->sts); |
| if (sts.s.rxnum != in_now) { |
| printk(BIOS_ERR, |
| "SPI: Incorrect number of bytes received: %u.\n", |
| sts.s.rxnum); |
| return -1; |
| } |
| |
| for (i = 0; i < in_now; i++) { |
| *in_buf = (uint8_t)((read64(®s->dat[i]) & 0xff)); |
| in_buf++; |
| } |
| bytesin -= in_now; |
| } |
| |
| return 0; |
| } |
| |
| static int spi_ctrlr_xfer_vector(const struct spi_slave *slave, |
| struct spi_op vectors[], size_t count) |
| { |
| int i; |
| |
| for (i = 0; i < count; i++) { |
| if (do_xfer(slave, &vectors[i], count - 1 == i ? 0 : 1)) { |
| printk(BIOS_ERR, |
| "SPI: Failed to transfer %zu vectors.\n", count); |
| return -1; |
| } |
| } |
| |
| return 0; |
| } |
| static const struct spi_ctrlr spi_ctrlr = { |
| |
| .xfer_vector = spi_ctrlr_xfer_vector, |
| .max_xfer_size = SPI_CTRLR_DEFAULT_MAX_XFER_SIZE, |
| }; |
| |
| const struct spi_ctrlr_buses spi_ctrlr_bus_map[] = { |
| { |
| .ctrlr = &spi_ctrlr, |
| .bus_start = 0, |
| .bus_end = ARRAY_SIZE(cavium_spi_slaves) - 1, |
| }, |
| }; |
| const size_t spi_ctrlr_bus_map_count = ARRAY_SIZE(spi_ctrlr_bus_map); |