| /* |
| * Copyright 2017, Data61, CSIRO (ABN 41 687 119 230) |
| * |
| * SPDX-License-Identifier: BSD-2-Clause |
| */ |
| |
| /* SPI driver */ |
| |
| #include <autoconf.h> |
| #include <platsupport/gen_config.h> |
| #include <stdio.h> |
| #include <stdint.h> |
| #include <platsupport/spi.h> |
| #include <platsupport/plat/mux.h> |
| #include "../../services.h" |
| |
| //#define DEBUG_SPI |
| #ifdef DEBUG_SPI |
| #define DSPI(args...) \ |
| do { \ |
| printf("SPI %s(%d):", __func__, __LINE__); \ |
| printf(args); \ |
| printf("\n"); \ |
| } while(0) |
| #else |
| #define DSPI(...) do{}while(0) |
| #endif |
| |
| /* SPI configuration */ |
| #define CH_CFG_HIGH_SPEED_EN BIT(6) |
| #define CH_CFG_SW_RST BIT(5) |
| #define CH_CFG_SLAVE BIT(4) |
| #define CH_CFG_CPOL BIT(3) |
| #define CH_CFG_CPHA BIT(2) |
| #define CH_CFG_RX_CH_ON BIT(1) |
| #define CH_CFG_TX_CH_ON BIT(0) |
| |
| /* FIFO control */ |
| #define FIFO_SIZE 64 |
| #define MODE_CFG_CH_WIDTH_SHF (29) |
| #define MODE_CFG_TRAILING_CNT_SHF (19) |
| #define MODE_CFG_BUS_WIDTH_SHF (17) |
| #define MODE_CFG_RX_RDY_LVL_SHF (11) |
| #define MODE_CFG_TX_RDY_LVL_SHF (5) |
| #define MODE_CFG_RX_DMA_SW BIT(2) |
| #define MODE_CFG_TX_DMA_SW BIT(1) |
| #define MODE_CFG_DMA_TYPE BIT(0) |
| |
| /* Slave selection control */ |
| #define CS_REG_NCS_TIME_COUNT_SHF (4) |
| #define CS_REG_AUTO_N_MANUAL BIT(1) |
| #define CS_REG_NSSOUT BIT(0) |
| |
| /* Interrupt enable */ |
| #define INT_EN_TRAILING BIT(6) |
| #define INT_EN_RX_OVERRUN BIT(5) |
| #define INT_EN_RX_UNDERRUN BIT(4) |
| #define INT_EN_TX_OVERRUN BIT(3) |
| #define INT_EN_TX_UNDERRUN BIT(2) |
| #define INT_EN_RX_FIFO_RDY BIT(1) |
| #define INT_EN_TX_FIFO_RDY BIT(0) |
| |
| /* SPI status */ |
| #define STATUS_TX_DONE BIT(25) |
| #define STATUS_TRAILING_BYTE BIT(24) |
| #define STATUS_RX_FIFO_LVL_SHF (15) |
| #define STATUS_TX_FIFO_LVL_SHF (6) |
| #define STATUS_RX_OVERRUN BIT(5) |
| #define STATUS_RX_UNDERRUN BIT(4) |
| #define STATUS_TX_OVERRUN BIT(3) |
| #define STATUS_TX_UNDERRUN BIT(2) |
| #define STATUS_RX_FIFO_RDY BIT(1) |
| #define STATUS_TX_FIFO_RDY BIT(0) |
| |
| /* Packet count */ |
| #define PACKET_CNT_EN BIT(16) |
| #define PACKET_CNT_VALUE_SHF BIT(0) |
| |
| /* Interrupt pending clear */ |
| #define PENDING_CLR_TX_UNDERRUN BIT(4) |
| #define PENDING_CLR_TX_OVERRUN BIT(3) |
| #define PENDING_CLR_RX_UNDERRUN BIT(2) |
| #define PENDING_CLR_RX_OVERRUN BIT(1) |
| #define PENDING_CLR_TRAILING BIT(0) |
| |
| /* Swap configuration */ |
| #define SWAP_CFG_RX_HWORD BIT(7) |
| #define SWAP_CFG_RX_BYTE BIT(6) |
| #define SWAP_CFG_RX_BIT BIT(5) |
| #define SWAP_CFG_RX_EN BIT(4) |
| #define SWAP_CFG_TX_HWORD BIT(3) |
| #define SWAP_CFG_TX_BYTE BIT(2) |
| #define SWAP_CFG_TX_BIT BIT(1) |
| #define SWAP_CFG_TX_EN BIT(0) |
| |
| /* Feedback clock selection */ |
| #define FB_CLK_SEL_SHF (0) |
| |
| struct spi_regs { |
| uint32_t ch_cfg; |
| uint32_t res; |
| uint32_t mode_cfg; |
| uint32_t cs_reg; |
| uint32_t int_en; |
| uint32_t status; |
| uint32_t tx_data; |
| uint32_t rx_data; |
| uint32_t packet_cnt; |
| uint32_t pending_clr; |
| uint32_t swap_cfg; |
| uint32_t fb_clk_sel; |
| }; |
| |
| enum spi_mode { |
| MASTER_MODE = 0, |
| SLAVE_MODE |
| }; |
| |
| struct spi_bus { |
| volatile struct spi_regs* regs; |
| enum mux_feature mux; |
| enum clk_id clkid; |
| int mode: 1; //0 -- Master, 1 -- Slave |
| int high_speed: 1; //High speed operation in slave mode. |
| int cs_auto: 1; //Auto chip selection. |
| clk_t *clk; //Clock for changing the bus speed. |
| |
| /* Transfer management */ |
| const char *txbuf; |
| char *rxbuf; |
| size_t txcnt, rxcnt; |
| size_t txsize, rxsize; |
| spi_callback_fn cb; |
| void* token; |
| }; |
| |
| static spi_bus_t _spi[NSPI] = { |
| { .regs = NULL, .mux = MUX_SPI0, .clkid = CLK_SPI0 }, |
| { .regs = NULL, .mux = MUX_SPI1, .clkid = CLK_SPI1 }, |
| { .regs = NULL, .mux = MUX_SPI2, .clkid = CLK_SPI2 }, |
| #if defined(CONFIG_PLAT_EXYNOS5) |
| { .regs = NULL, .mux = MUX_SPI0_ISP, .clkid = CLK_SPI0_ISP }, |
| { .regs = NULL, .mux = MUX_SPI1_ISP, .clkid = CLK_SPI1_ISP }, |
| #endif /* EXYNOSX */ |
| }; |
| |
| static void |
| spi_reset(spi_bus_t *spi_bus) |
| { |
| uint32_t v; |
| |
| /* Turn off the channel */ |
| v = spi_bus->regs->ch_cfg; |
| v &= ~(CH_CFG_RX_CH_ON | CH_CFG_TX_CH_ON); |
| spi_bus->regs->ch_cfg = v; |
| |
| /* Write to reset bit */ |
| v = spi_bus->regs->ch_cfg; |
| v |= CH_CFG_SW_RST; |
| spi_bus->regs->ch_cfg = v; |
| |
| /* Clear reset bit */ |
| v = spi_bus->regs->ch_cfg; |
| v &= ~CH_CFG_SW_RST; |
| spi_bus->regs->ch_cfg = v; |
| |
| /* Turn on the channel */ |
| v = spi_bus->regs->ch_cfg; |
| v |= (CH_CFG_RX_CH_ON | CH_CFG_TX_CH_ON); |
| spi_bus->regs->ch_cfg = v; |
| } |
| |
| static void |
| spi_config(spi_bus_t *spi_bus) |
| { |
| uint32_t v; |
| |
| /* Step1: SPI configuration */ |
| v = 0; |
| /* Master/Slave mode */ |
| if (spi_bus->mode == SLAVE_MODE) { |
| v |= CH_CFG_SLAVE; |
| |
| /* High speed mode is slave mode only */ |
| if (spi_bus->high_speed) { |
| v |= CH_CFG_HIGH_SPEED_EN; |
| v &= ~CH_CFG_CPHA; |
| } |
| } |
| spi_bus->regs->ch_cfg = v; |
| |
| /* |
| * Step2: Feedback clock |
| * The default clock is 33MHz, so we use a 180 degree phase feedback. |
| * The feedback clock only works in the master mode. |
| */ |
| if (spi_bus->mode == MASTER_MODE) { |
| v = (0x2 << FB_CLK_SEL_SHF); |
| spi_bus->regs->fb_clk_sel = v; |
| } |
| |
| /* |
| * Step3: FIFO control |
| * Channel width to Byte, No DMA, only need to set trigger level. |
| */ |
| v = (0x20 << MODE_CFG_RX_RDY_LVL_SHF) | (0x20 << MODE_CFG_TX_RDY_LVL_SHF); |
| spi_bus->regs->mode_cfg = v; |
| |
| /* Step4: Interrupts */ |
| spi_bus->regs->int_en = 0x0; |
| spi_bus->regs->pending_clr = 0xff; |
| |
| /* Step5: Packet control */ |
| spi_bus->regs->packet_cnt = 0; |
| |
| /* Step6: Turn on the channel */ |
| v = spi_bus->regs->ch_cfg; |
| v |= (CH_CFG_RX_CH_ON | CH_CFG_TX_CH_ON); |
| spi_bus->regs->ch_cfg = v; |
| |
| /* Step7: Chip selection */ |
| v = (0x0 << CS_REG_NCS_TIME_COUNT_SHF); |
| if (spi_bus->cs_auto) { |
| v |= CS_REG_AUTO_N_MANUAL; |
| } else { |
| v |= CS_REG_NSSOUT; |
| } |
| spi_bus->regs->cs_reg = v; |
| } |
| |
| static void |
| spi_cs(spi_bus_t* spi_bus, enum spi_cs_state state) |
| { |
| if (!spi_bus->cs_auto) { |
| uint32_t v; |
| v = spi_bus->regs->cs_reg; |
| if (state == SPI_CS_ASSERT) { |
| v &= ~CS_REG_NSSOUT; |
| } else { |
| v |= CS_REG_NSSOUT; |
| } |
| spi_bus->regs->cs_reg = v; |
| } |
| } |
| |
| static int |
| spi_init_common(spi_bus_t* spi_bus, mux_sys_t* mux_sys, clock_sys_t* clock_sys) |
| { |
| if (mux_sys && mux_sys_valid(mux_sys)) { |
| mux_feature_enable(mux_sys, spi_bus->mux, MUX_DIR_NOT_A_GPIO); |
| } else { |
| // LOG_INFO("SPI: Skipping MUX initialisation as no mux subsystem was provided\n"); |
| } |
| |
| if (clock_sys && clock_sys_valid(clock_sys)) { |
| spi_bus->clk = clk_get_clock(clock_sys, spi_bus->clkid); |
| } else { |
| // LOG_INFO("SPI: Assuming default clock frequency as no clock subsystem was provided\n"); |
| spi_bus->clk = NULL; |
| } |
| |
| spi_bus->mode = 0; |
| spi_bus->high_speed = 0; |
| spi_bus->cs_auto = 0; |
| |
| spi_reset(spi_bus); |
| spi_config(spi_bus); |
| return 0; |
| } |
| |
| int |
| transfer_data(spi_bus_t* spi_bus) |
| { |
| int rxfifo_cnt, txfifo_cnt, txfifo_space; |
| uint32_t stat; |
| stat = spi_bus->regs->status; |
| rxfifo_cnt = (stat >> STATUS_RX_FIFO_LVL_SHF) & 0x1FF; |
| txfifo_cnt = (stat >> STATUS_TX_FIFO_LVL_SHF) & 0x1FF; |
| txfifo_space = FIFO_SIZE - txfifo_cnt - 1; |
| |
| /* Check for fatal events */ |
| if (stat & STATUS_RX_OVERRUN) { |
| printf("SPI RX overrun\n"); |
| } |
| if (stat & STATUS_RX_UNDERRUN) { |
| printf("SPI RX underrun\n"); |
| } |
| if (stat & STATUS_TX_OVERRUN) { |
| printf("SPI TX overrun\n"); |
| } |
| if (stat & STATUS_TX_UNDERRUN) { |
| printf("SPI TX underrun\n"); |
| } |
| |
| /* Drain the RX FIFO */ |
| while (rxfifo_cnt--) { |
| uint32_t d; |
| d = spi_bus->regs->rx_data; |
| /* Store the data only if we are in RX phase */ |
| if (spi_bus->rxcnt >= spi_bus->txsize) { |
| assert(spi_bus->rxcnt - spi_bus->txsize < spi_bus->rxsize); |
| } |
| *spi_bus->rxbuf++ = d; |
| spi_bus->rxcnt++; |
| } |
| |
| /* Fill the TX FIFO */ |
| if (txfifo_space > spi_bus->txsize + spi_bus->rxsize - spi_bus->rxcnt) { |
| txfifo_space = spi_bus->txsize + spi_bus->rxsize - spi_bus->txcnt; |
| } |
| while (txfifo_space--) { |
| uint32_t d; |
| if (spi_bus->txcnt < spi_bus->txsize) { |
| d = *spi_bus->txbuf++; |
| } else { |
| d = 0; |
| } |
| spi_bus->regs->tx_data = d; |
| spi_bus->txcnt++; |
| } |
| |
| /* Check for completion */ |
| if ((stat & STATUS_TX_DONE) && spi_bus->rxcnt == spi_bus->rxsize + spi_bus->txsize) { |
| spi_bus->regs->int_en = 0; |
| spi_cs(spi_bus, SPI_CS_RELAX); |
| if (spi_bus->cb) { |
| spi_bus->cb(spi_bus, spi_bus->txcnt, spi_bus->token); |
| } |
| |
| return 0; |
| } |
| |
| return 1; |
| } |
| |
| void |
| spi_handle_irq(spi_bus_t* spi_bus) |
| { |
| transfer_data(spi_bus); |
| } |
| |
| int |
| spi_xfer(spi_bus_t* spi_bus, const void* txdata, size_t txcnt, |
| void* rxdata, size_t rxcnt, spi_callback_fn cb, void* token) |
| { |
| spi_bus->txbuf = (const char*)txdata; |
| spi_bus->rxbuf = (char*)rxdata; |
| spi_bus->rxsize = rxcnt; |
| spi_bus->txsize = txcnt; |
| spi_bus->rxcnt = 0; |
| spi_bus->txcnt = 0; |
| |
| DSPI("Starting transfer: TX: from 0x%x, %d bytes. RX to 0x%x, %d bytes\n", |
| (uint32_t)txdata, txcnt, (uint32_t)rxdata, rxcnt); |
| |
| transfer_data(spi_bus); |
| spi_cs(spi_bus, SPI_CS_ASSERT); |
| if (cb == NULL) { |
| while (transfer_data(spi_bus)); |
| } else { |
| uint32_t v; |
| spi_bus->cb = cb; |
| spi_bus->token = token; |
| v = INT_EN_RX_OVERRUN | INT_EN_RX_UNDERRUN |
| | INT_EN_TX_OVERRUN | INT_EN_TX_UNDERRUN |
| | INT_EN_RX_FIFO_RDY | INT_EN_TX_FIFO_RDY |
| | INT_EN_TRAILING; |
| spi_bus->regs->pending_clr = 0xFFFFFFFF; |
| spi_bus->regs->int_en = v; |
| } |
| |
| return spi_bus->rxcnt; |
| } |
| |
| long |
| spi_set_speed(spi_bus_t* spi_bus, long bps) |
| { |
| return 0; |
| } |
| |
| void |
| spi_prepare_transfer(spi_bus_t* spi_bus, const spi_slave_config_t* cfg) |
| { |
| if (spi_bus->clk && clk_get_freq(spi_bus->clk) != cfg->speed_hz) { |
| clk_set_freq(spi_bus->clk, cfg->speed_hz); |
| } |
| |
| /* Set feedback clock, only when SPI runs at a high frequency */ |
| if (cfg->speed_hz >= 1 * MHZ) { |
| spi_bus->regs->fb_clk_sel = (cfg->fb_delay << FB_CLK_SEL_SHF); |
| } |
| } |
| |
| int |
| exynos_spi_init(enum spi_id id, void* base, |
| mux_sys_t* mux_sys, clock_sys_t* clock_sys, |
| spi_bus_t** ret_spi_bus) |
| { |
| if (id >= 0 && id < NSPI) { |
| spi_bus_t* spi_bus = _spi + id; |
| *ret_spi_bus = spi_bus; |
| spi_bus->regs = base; |
| return spi_init_common(spi_bus, mux_sys, clock_sys); |
| } else { |
| return -1; |
| } |
| } |
| |
| int |
| spi_init(enum spi_id id, ps_io_ops_t* io_ops, spi_bus_t** ret_spi_bus) |
| { |
| spi_bus_t* spi_bus = _spi + id; |
| *ret_spi_bus = spi_bus; |
| /* Map memory */ |
| DSPI("Mapping spi %d\n", id); |
| switch (id) { |
| case SPI0: |
| MAP_IF_NULL(io_ops, EXYNOS_SPI0, spi_bus->regs); |
| break; |
| case SPI1: |
| MAP_IF_NULL(io_ops, EXYNOS_SPI1, spi_bus->regs); |
| break; |
| case SPI2: |
| MAP_IF_NULL(io_ops, EXYNOS_SPI2, spi_bus->regs); |
| break; |
| #ifdef CONFIG_PLAT_EXYNOS5 |
| case SPI0_ISP: |
| MAP_IF_NULL(io_ops, EXYNOS_SPI0_ISP, spi_bus->regs); |
| break; |
| case SPI1_ISP: |
| MAP_IF_NULL(io_ops, EXYNOS_SPI1_ISP, spi_bus->regs); |
| break; |
| #endif |
| default: |
| return -1; |
| } |
| |
| return spi_init_common(spi_bus, &io_ops->mux_sys, &io_ops->clock_sys); |
| } |