| // SPDX-License-Identifier: BSD-2-Clause |
| /* |
| * Copyright (c) 2016, Linaro Limited |
| * |
| */ |
| |
| #include <assert.h> |
| #include <drivers/pl022_spi.h> |
| #include <initcall.h> |
| #include <io.h> |
| #include <keep.h> |
| #include <kernel/panic.h> |
| #include <kernel/tee_time.h> |
| #include <platform_config.h> |
| #include <trace.h> |
| #include <util.h> |
| |
| /* SPI register offsets */ |
| #define SSPCR0 0x000 |
| #define SSPCR1 0x004 |
| #define SSPDR 0x008 |
| #define SSPSR 0x00C |
| #define SSPCPSR 0x010 |
| #define SSPIMSC 0x014 |
| #define SSPRIS 0x018 |
| #define SSPMIS 0x01C |
| #define SSPICR 0x020 |
| #define SSPDMACR 0x024 |
| |
| #ifdef PLATFORM_hikey |
| /* HiKey extensions */ |
| #define SSPTXFIFOCR 0x028 |
| #define SSPRXFIFOCR 0x02C |
| #define SSPB2BTRANS 0x030 |
| #endif |
| |
| /* test registers */ |
| #define SSPTCR 0x080 |
| #define SSPITIP 0x084 |
| #define SSPITOP 0x088 |
| #define SSPTDR 0x08C |
| |
| #define SSPPeriphID0 0xFE0 |
| #define SSPPeriphID1 0xFE4 |
| #define SSPPeriphID2 0xFE8 |
| #define SSPPeriphID3 0xFEC |
| |
| #define SSPPCellID0 0xFF0 |
| #define SSPPCellID1 0xFF4 |
| #define SSPPCellID2 0xFF8 |
| #define SSPPCellID3 0xFFC |
| |
| /* SPI register masks */ |
| #define SSPCR0_SCR SHIFT_U32(0xFF, 8) |
| #define SSPCR0_SPH SHIFT_U32(1, 7) |
| #define SSPCR0_SPH1 SHIFT_U32(1, 7) |
| #define SSPCR0_SPH0 SHIFT_U32(0, 7) |
| #define SSPCR0_SPO SHIFT_U32(1, 6) |
| #define SSPCR0_SPO1 SHIFT_U32(1, 6) |
| #define SSPCR0_SPO0 SHIFT_U32(0, 6) |
| #define SSPCR0_FRF SHIFT_U32(3, 4) |
| #define SSPCR0_FRF_SPI SHIFT_U32(0, 4) |
| #define SSPCR0_DSS SHIFT_U32(0xFF, 0) |
| #define SSPCR0_DSS_16BIT SHIFT_U32(0xF, 0) |
| #define SSPCR0_DSS_8BIT SHIFT_U32(7, 0) |
| |
| #define SSPCR1_SOD SHIFT_U32(1, 3) |
| #define SSPCR1_SOD_ENABLE SHIFT_U32(1, 3) |
| #define SSPCR1_SOD_DISABLE SHIFT_U32(0, 3) |
| #define SSPCR1_MS SHIFT_U32(1, 2) |
| #define SSPCR1_MS_SLAVE SHIFT_U32(1, 2) |
| #define SSPCR1_MS_MASTER SHIFT_U32(0, 2) |
| #define SSPCR1_SSE SHIFT_U32(1, 1) |
| #define SSPCR1_SSE_ENABLE SHIFT_U32(1, 1) |
| #define SSPCR1_SSE_DISABLE SHIFT_U32(0, 1) |
| #define SSPCR1_LBM SHIFT_U32(1, 0) |
| #define SSPCR1_LBM_YES SHIFT_U32(1, 0) |
| #define SSPCR1_LBM_NO SHIFT_U32(0, 0) |
| |
| #define SSPDR_DATA SHIFT_U32(0xFFFF, 0) |
| |
| #define SSPSR_BSY SHIFT_U32(1, 4) |
| #define SSPSR_RNF SHIFT_U32(1, 3) |
| #define SSPSR_RNE SHIFT_U32(1, 2) |
| #define SSPSR_TNF SHIFT_U32(1, 1) |
| #define SSPSR_TFE SHIFT_U32(1, 0) |
| |
| #define SSPCPSR_CPSDVR SHIFT_U32(0xFF, 0) |
| |
| #define SSPIMSC_TXIM SHIFT_U32(1, 3) |
| #define SSPIMSC_RXIM SHIFT_U32(1, 2) |
| #define SSPIMSC_RTIM SHIFT_U32(1, 1) |
| #define SSPIMSC_RORIM SHIFT_U32(1, 0) |
| |
| #define SSPRIS_TXRIS SHIFT_U32(1, 3) |
| #define SSPRIS_RXRIS SHIFT_U32(1, 2) |
| #define SSPRIS_RTRIS SHIFT_U32(1, 1) |
| #define SSPRIS_RORRIS SHIFT_U32(1, 0) |
| |
| #define SSPMIS_TXMIS SHIFT_U32(1, 3) |
| #define SSPMIS_RXMIS SHIFT_U32(1, 2) |
| #define SSPMIS_RTMIS SHIFT_U32(1, 1) |
| #define SSPMIS_RORMIS SHIFT_U32(1, 0) |
| |
| #define SSPICR_RTIC SHIFT_U32(1, 1) |
| #define SSPICR_RORIC SHIFT_U32(1, 0) |
| |
| #define SSPDMACR_TXDMAE SHIFT_U32(1, 1) |
| #define SSPDMACR_RXDMAE SHIFT_U32(1, 0) |
| |
| #define SSPPeriphID0_PartNumber0 SHIFT_U32(0xFF, 0) /* 0x22 */ |
| #define SSPPeriphID1_Designer0 SHIFT_U32(0xF, 4) /* 0x1 */ |
| #define SSPPeriphID1_PartNumber1 SHIFT_U32(0xF, 0) /* 0x0 */ |
| #define SSPPeriphID2_Revision SHIFT_U32(0xF, 4) |
| #define SSPPeriphID2_Designer1 SHIFT_U32(0xF, 0) /* 0x4 */ |
| #define SSPPeriphID3_Configuration SHIFT_U32(0xFF, 0) /* 0x00 */ |
| |
| #define SSPPCellID_0 SHIFT_U32(0xFF, 0) /* 0x0D */ |
| #define SSPPCellID_1 SHIFT_U32(0xFF, 0) /* 0xF0 */ |
| #define SSPPPCellID_2 SHIFT_U32(0xFF, 0) /* 0x05 */ |
| #define SSPPPCellID_3 SHIFT_U32(0xFF, 0) /* 0xB1 */ |
| |
| #define MASK_32 0xFFFFFFFF |
| #define MASK_28 0xFFFFFFF |
| #define MASK_24 0xFFFFFF |
| #define MASK_20 0xFFFFF |
| #define MASK_16 0xFFFF |
| #define MASK_12 0xFFF |
| #define MASK_8 0xFF |
| #define MASK_4 0xF |
| /* SPI register masks */ |
| |
| #define SSP_CPSDVR_MAX 254 |
| #define SSP_CPSDVR_MIN 2 |
| #define SSP_SCR_MAX 255 |
| #define SSP_SCR_MIN 0 |
| #define SSP_DATASIZE_MAX 16 |
| |
| static enum spi_result pl022_txrx8(struct spi_chip *chip, uint8_t *wdat, |
| uint8_t *rdat, size_t num_pkts) |
| { |
| size_t i = 0; |
| size_t j = 0; |
| struct pl022_data *pd = container_of(chip, struct pl022_data, chip); |
| |
| |
| if (pd->data_size_bits != 8) { |
| EMSG("data_size_bits should be 8, not %u", |
| pd->data_size_bits); |
| return SPI_ERR_CFG; |
| } |
| |
| if (wdat) |
| while (i < num_pkts) { |
| if (io_read8(pd->base + SSPSR) & SSPSR_TNF) { |
| /* tx 1 packet */ |
| io_write8(pd->base + SSPDR, wdat[i++]); |
| } |
| |
| if (rdat) |
| if (io_read8(pd->base + SSPSR) & SSPSR_RNE) { |
| /* rx 1 packet */ |
| rdat[j++] = io_read8(pd->base + SSPDR); |
| } |
| } |
| |
| /* Capture remaining rdat not read above */ |
| if (rdat) { |
| while ((j < num_pkts) && |
| (io_read8(pd->base + SSPSR) & SSPSR_RNE)) { |
| /* rx 1 packet */ |
| rdat[j++] = io_read8(pd->base + SSPDR); |
| } |
| |
| if (j < num_pkts) { |
| EMSG("Packets requested %zu, received %zu", |
| num_pkts, j); |
| return SPI_ERR_PKTCNT; |
| } |
| } |
| |
| return SPI_OK; |
| } |
| |
| static enum spi_result pl022_txrx16(struct spi_chip *chip, uint16_t *wdat, |
| uint16_t *rdat, size_t num_pkts) |
| { |
| size_t i = 0; |
| size_t j = 0; |
| struct pl022_data *pd = container_of(chip, struct pl022_data, chip); |
| |
| if (pd->data_size_bits != 16) { |
| EMSG("data_size_bits should be 16, not %u", |
| pd->data_size_bits); |
| return SPI_ERR_CFG; |
| } |
| |
| if (wdat) |
| while (i < num_pkts) { |
| if (io_read8(pd->base + SSPSR) & SSPSR_TNF) { |
| /* tx 1 packet */ |
| io_write16(pd->base + SSPDR, wdat[i++]); |
| } |
| |
| if (rdat) |
| if (io_read8(pd->base + SSPSR) & SSPSR_RNE) { |
| /* rx 1 packet */ |
| rdat[j++] = io_read16(pd->base + SSPDR); |
| } |
| } |
| |
| /* Capture remaining rdat not read above */ |
| if (rdat) { |
| while ((j < num_pkts) && |
| (io_read8(pd->base + SSPSR) & SSPSR_RNE)) { |
| /* rx 1 packet */ |
| rdat[j++] = io_read16(pd->base + SSPDR); |
| } |
| |
| if (j < num_pkts) { |
| EMSG("Packets requested %zu, received %zu", |
| num_pkts, j); |
| return SPI_ERR_PKTCNT; |
| } |
| } |
| |
| return SPI_OK; |
| } |
| |
| static void pl022_print_peri_id(struct pl022_data *pd __maybe_unused) |
| { |
| DMSG("Expected: 0x 22 10 ?4 00"); |
| DMSG("Read: 0x %02x %02x %02x %02x", |
| io_read8(pd->base + SSPPeriphID0), |
| io_read8(pd->base + SSPPeriphID1), |
| io_read8(pd->base + SSPPeriphID2), |
| io_read8(pd->base + SSPPeriphID3)); |
| } |
| |
| static void pl022_print_cell_id(struct pl022_data *pd __maybe_unused) |
| { |
| DMSG("Expected: 0x 0d f0 05 b1"); |
| DMSG("Read: 0x %02x %02x %02x %02x", |
| io_read8(pd->base + SSPPCellID0), |
| io_read8(pd->base + SSPPCellID1), |
| io_read8(pd->base + SSPPCellID2), |
| io_read8(pd->base + SSPPCellID3)); |
| } |
| |
| static void pl022_sanity_check(struct pl022_data *pd) |
| { |
| assert(pd); |
| assert(pd->chip.ops); |
| assert(pd->cs_control <= PL022_CS_CTRL_MANUAL); |
| switch (pd->cs_control) { |
| case PL022_CS_CTRL_AUTO_GPIO: |
| assert(pd->cs_data.gpio_data.chip); |
| assert(pd->cs_data.gpio_data.chip->ops); |
| break; |
| case PL022_CS_CTRL_CB: |
| assert(pd->cs_data.cs_cb); |
| break; |
| default: |
| break; |
| } |
| assert(pd->clk_hz); |
| assert(pd->speed_hz && pd->speed_hz <= pd->clk_hz/2); |
| assert(pd->mode <= SPI_MODE3); |
| assert(pd->data_size_bits == 8 || pd->data_size_bits == 16); |
| |
| #ifdef PLATFORM_hikey |
| DMSG("SSPB2BTRANS: Expected: 0x2. Read: 0x%x", |
| io_read8(pd->base + SSPB2BTRANS)); |
| #endif |
| pl022_print_peri_id(pd); |
| pl022_print_cell_id(pd); |
| } |
| |
| static inline uint32_t pl022_calc_freq(struct pl022_data *pd, |
| uint8_t cpsdvr, uint8_t scr) |
| { |
| return pd->clk_hz / (cpsdvr * (1 + scr)); |
| } |
| |
| static void pl022_control_cs(struct spi_chip *chip, enum gpio_level value) |
| { |
| struct pl022_data *pd = container_of(chip, struct pl022_data, chip); |
| |
| switch (pd->cs_control) { |
| case PL022_CS_CTRL_AUTO_GPIO: |
| if (io_read8(pd->base + SSPSR) & SSPSR_BSY) |
| DMSG("pl022 busy - do NOT set CS!"); |
| while (io_read8(pd->base + SSPSR) & SSPSR_BSY) |
| ; |
| DMSG("pl022 done - set CS!"); |
| |
| pd->cs_data.gpio_data.chip->ops->set_value( |
| pd->cs_data.gpio_data.pin_num, value); |
| break; |
| case PL022_CS_CTRL_CB: |
| pd->cs_data.cs_cb(value); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| static void pl022_calc_clk_divisors(struct pl022_data *pd, |
| uint8_t *cpsdvr, uint8_t *scr) |
| { |
| unsigned int freq1 = 0; |
| unsigned int freq2 = 0; |
| uint8_t tmp_cpsdvr1; |
| uint8_t tmp_scr1; |
| uint8_t tmp_cpsdvr2 = 0; |
| uint8_t tmp_scr2 = 0; |
| |
| for (tmp_scr1 = SSP_SCR_MIN; tmp_scr1 < SSP_SCR_MAX; tmp_scr1++) { |
| for (tmp_cpsdvr1 = SSP_CPSDVR_MIN; tmp_cpsdvr1 < SSP_CPSDVR_MAX; |
| tmp_cpsdvr1++) { |
| freq1 = pl022_calc_freq(pd, tmp_cpsdvr1, tmp_scr1); |
| if (freq1 == pd->speed_hz) |
| goto done; |
| else if (freq1 < pd->speed_hz) |
| goto stage2; |
| } |
| } |
| |
| stage2: |
| for (tmp_cpsdvr2 = SSP_CPSDVR_MIN; tmp_cpsdvr2 < SSP_CPSDVR_MAX; |
| tmp_cpsdvr2++) { |
| for (tmp_scr2 = SSP_SCR_MIN; tmp_scr2 < SSP_SCR_MAX; |
| tmp_scr2++) { |
| freq2 = pl022_calc_freq(pd, tmp_cpsdvr2, tmp_scr2); |
| if (freq2 <= pd->speed_hz) |
| goto done; |
| } |
| } |
| |
| done: |
| if (freq1 >= freq2) { |
| *cpsdvr = tmp_cpsdvr1; |
| *scr = tmp_scr1; |
| DMSG("speed: requested: %u, closest1: %u", |
| pd->speed_hz, freq1); |
| } else { |
| *cpsdvr = tmp_cpsdvr2; |
| *scr = tmp_scr2; |
| DMSG("speed: requested: %u, closest2: %u", |
| pd->speed_hz, freq2); |
| } |
| DMSG("CPSDVR: %u (0x%x), SCR: %u (0x%x)", |
| *cpsdvr, *cpsdvr, *scr, *scr); |
| } |
| |
| static void pl022_flush_fifo(struct pl022_data *pd) |
| { |
| uint32_t __maybe_unused rdat; |
| |
| do { |
| while (io_read32(pd->base + SSPSR) & SSPSR_RNE) { |
| rdat = io_read32(pd->base + SSPDR); |
| DMSG("rdat: 0x%x", rdat); |
| } |
| } while (io_read32(pd->base + SSPSR) & SSPSR_BSY); |
| } |
| |
| static void pl022_configure(struct spi_chip *chip) |
| { |
| uint16_t mode; |
| uint16_t data_size; |
| uint8_t cpsdvr; |
| uint8_t scr; |
| uint8_t lbm; |
| struct pl022_data *pd = container_of(chip, struct pl022_data, chip); |
| |
| pl022_sanity_check(pd); |
| |
| switch (pd->cs_control) { |
| case PL022_CS_CTRL_AUTO_GPIO: |
| DMSG("Use auto GPIO CS control"); |
| DMSG("Mask/disable interrupt for CS GPIO"); |
| pd->cs_data.gpio_data.chip->ops->set_interrupt( |
| pd->cs_data.gpio_data.pin_num, |
| GPIO_INTERRUPT_DISABLE); |
| DMSG("Set CS GPIO dir to out"); |
| pd->cs_data.gpio_data.chip->ops->set_direction( |
| pd->cs_data.gpio_data.pin_num, |
| GPIO_DIR_OUT); |
| break; |
| case PL022_CS_CTRL_CB: |
| DMSG("Use registered CS callback"); |
| break; |
| case PL022_CS_CTRL_MANUAL: |
| DMSG("Use manual CS control"); |
| break; |
| default: |
| EMSG("Invalid CS control type: %d", pd->cs_control); |
| panic(); |
| } |
| |
| DMSG("Pull CS high"); |
| pl022_control_cs(chip, GPIO_LEVEL_HIGH); |
| |
| pl022_calc_clk_divisors(pd, &cpsdvr, &scr); |
| |
| /* configure ssp based on platform settings */ |
| switch (pd->mode) { |
| case SPI_MODE0: |
| DMSG("SPI mode 0"); |
| mode = SSPCR0_SPO0 | SSPCR0_SPH0; |
| break; |
| case SPI_MODE1: |
| DMSG("SPI mode 1"); |
| mode = SSPCR0_SPO0 | SSPCR0_SPH1; |
| break; |
| case SPI_MODE2: |
| DMSG("SPI mode 2"); |
| mode = SSPCR0_SPO1 | SSPCR0_SPH0; |
| break; |
| case SPI_MODE3: |
| DMSG("SPI mode 3"); |
| mode = SSPCR0_SPO1 | SSPCR0_SPH1; |
| break; |
| default: |
| EMSG("Invalid SPI mode: %u", pd->mode); |
| panic(); |
| } |
| |
| switch (pd->data_size_bits) { |
| case 8: |
| DMSG("Data size: 8"); |
| data_size = SSPCR0_DSS_8BIT; |
| break; |
| case 16: |
| DMSG("Data size: 16"); |
| data_size = SSPCR0_DSS_16BIT; |
| break; |
| default: |
| EMSG("Unsupported data size: %u bits", pd->data_size_bits); |
| panic(); |
| } |
| |
| if (pd->loopback) { |
| DMSG("Starting in loopback mode!"); |
| lbm = SSPCR1_LBM_YES; |
| } else { |
| DMSG("Starting in regular (non-loopback) mode!"); |
| lbm = SSPCR1_LBM_NO; |
| } |
| |
| DMSG("Set Serial Clock Rate (SCR), SPI mode (phase and clock)"); |
| DMSG("Set frame format (SPI) and data size (8- or 16-bit)"); |
| io_mask16(pd->base + SSPCR0, SHIFT_U32(scr, 8) | mode | SSPCR0_FRF_SPI | |
| data_size, MASK_16); |
| |
| DMSG("Set master mode, disable SSP, set loopback mode"); |
| io_mask8(pd->base + SSPCR1, SSPCR1_SOD_DISABLE | SSPCR1_MS_MASTER | |
| SSPCR1_SSE_DISABLE | lbm, MASK_4); |
| |
| DMSG("Set clock prescale"); |
| io_mask8(pd->base + SSPCPSR, cpsdvr, SSPCPSR_CPSDVR); |
| |
| DMSG("Disable interrupts"); |
| io_mask8(pd->base + SSPIMSC, 0, MASK_4); |
| |
| DMSG("Clear interrupts"); |
| io_mask8(pd->base + SSPICR, SSPICR_RORIC | SSPICR_RTIC, |
| SSPICR_RORIC | SSPICR_RTIC); |
| |
| DMSG("Empty FIFO before starting"); |
| pl022_flush_fifo(pd); |
| } |
| |
| static void pl022_start(struct spi_chip *chip) |
| { |
| struct pl022_data *pd = container_of(chip, struct pl022_data, chip); |
| |
| DMSG("Enable SSP"); |
| io_mask8(pd->base + SSPCR1, SSPCR1_SSE_ENABLE, SSPCR1_SSE); |
| |
| pl022_control_cs(chip, GPIO_LEVEL_LOW); |
| } |
| |
| static void pl022_end(struct spi_chip *chip) |
| { |
| struct pl022_data *pd = container_of(chip, struct pl022_data, chip); |
| |
| pl022_control_cs(chip, GPIO_LEVEL_HIGH); |
| |
| DMSG("Disable SSP"); |
| io_mask8(pd->base + SSPCR1, SSPCR1_SSE_DISABLE, SSPCR1_SSE); |
| } |
| |
| static const struct spi_ops pl022_ops = { |
| .configure = pl022_configure, |
| .start = pl022_start, |
| .txrx8 = pl022_txrx8, |
| .txrx16 = pl022_txrx16, |
| .end = pl022_end, |
| }; |
| KEEP_PAGER(pl022_ops); |
| |
| void pl022_init(struct pl022_data *pd) |
| { |
| assert(pd); |
| pd->chip.ops = &pl022_ops; |
| } |