/* SPDX-License-Identifier: GPL-2.0-only */ /* * Derived from Cavium's BSD-3 Clause OCTEONTX-SDK-6.2.0. */ #include #include #include #include #include #include #include #include #include #include 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);