| /* |
| * spi.c |
| * |
| * Copyright (C) 2019-2020 Sylvain Munaut <tnt@246tNt.com> |
| * SPDX-License-Identifier: LGPL-3.0-or-later |
| */ |
| |
| #include <stdbool.h> |
| #include <stdint.h> |
| |
| #include "config.h" |
| #include "spi.h" |
| |
| |
| struct spi { |
| uint32_t _rsvd0[6]; |
| uint32_t irq; /* 0110 - SPIIRQ - Interrupt Status Register */ |
| uint32_t irqen; /* 0111 - SPIIRQEN - Interrupt Control Register */ |
| uint32_t cr0; /* 1000 - CR0 - Control Register 0 */ |
| uint32_t cr1; /* 1001 - CR1 - Control Register 1 */ |
| uint32_t cr2; /* 1010 - CR2 - Control Register 2 */ |
| uint32_t br; /* 1011 - BR - Baud Rate Register */ |
| uint32_t sr; /* 1100 - SR - Status Register */ |
| uint32_t txdr; /* 1101 - TXDR - Transmit Data Register */ |
| uint32_t rxdr; /* 1110 - RXDR - Receive Data Register */ |
| uint32_t csr; /* 1111 - CSR - Chip Select Register */ |
| } __attribute__((packed,aligned(4))); |
| |
| #define SPI_CR0_TIDLE(xcnt) (((xcnt) & 3) << 6) |
| #define SPI_CR0_TTRAIL(xcnt) (((xcnt) & 7) << 3) |
| #define SPI_CR0_TLEAD(xcnt) (((xcnt) & 7) << 0) |
| |
| #define SPI_CR1_ENABLE (1 << 7) |
| #define SPI_CR1_WKUPEN_USER (1 << 6) |
| #define SPI_CR1_TXEDGE (1 << 4) |
| |
| #define SPI_CR2_MASTER (1 << 7) |
| #define SPI_CR2_MCSH (1 << 6) |
| #define SPI_CR2_SDBRE (1 << 5) |
| #define SPI_CR2_CPOL (1 << 2) |
| #define SPI_CR2_CPHA (1 << 1) |
| #define SPI_CR2_LSBF (1 << 0) |
| |
| #define SPI_SR_TIP (1 << 7) |
| #define SPI_SR_BUSY (1 << 6) |
| #define SPI_SR_TRDY (1 << 4) |
| #define SPI_SR_RRDY (1 << 3) |
| #define SPI_SR_TOE (1 << 2) |
| #define SPI_SR_ROE (1 << 1) |
| #define SPI_SR_MDF (1 << 0) |
| |
| |
| static volatile struct spi * const spi_regs[] = { |
| (void*)(SPI_FLASH_BASE), |
| #ifdef BOARD_E1_TRACER |
| (void*)(SPI_LIU_BASE), |
| #endif |
| }; |
| |
| |
| void |
| spi_init(void) |
| { |
| /* Channel 0: Flash */ |
| spi_regs[0]->cr0 = SPI_CR0_TIDLE(3) | |
| SPI_CR0_TTRAIL(7) | |
| SPI_CR0_TLEAD(7); |
| |
| spi_regs[0]->cr1 = SPI_CR1_ENABLE; |
| spi_regs[0]->cr2 = SPI_CR2_MASTER | SPI_CR2_MCSH; |
| spi_regs[0]->br = 3; |
| spi_regs[0]->csr = 0xf; |
| |
| #ifdef BOARD_E1_TRACER |
| /* Channel 1: LIU */ |
| spi_regs[1]->cr0 = SPI_CR0_TIDLE(3) | |
| SPI_CR0_TTRAIL(7) | |
| SPI_CR0_TLEAD(7); |
| spi_regs[1]->cr1 = SPI_CR1_ENABLE | SPI_CR1_TXEDGE; |
| spi_regs[1]->cr2 = SPI_CR2_MASTER | SPI_CR2_LSBF | SPI_CR2_MCSH | SPI_CR2_CPHA; |
| spi_regs[1]->br = 3; |
| spi_regs[1]->csr = 0xf; |
| #endif |
| } |
| |
| void |
| spi_xfer(unsigned cs, const struct spi_xfer_chunk *xfer, unsigned n) |
| { |
| uint8_t rxd; |
| unsigned chan = (cs >> 2); |
| cs &= 3; |
| |
| /* Setup CS */ |
| spi_regs[chan]->csr = 0xf ^ (1 << cs); |
| |
| /* Run the chunks */ |
| while (n--) { |
| for (int i=0; i<xfer->len; i++) |
| { |
| spi_regs[chan]->txdr = xfer->write ? xfer->data[i] : 0x00; |
| while (!(spi_regs[chan]->sr & SPI_SR_RRDY)); |
| rxd = spi_regs[chan]->rxdr; |
| if (xfer->read) |
| xfer->data[i] = rxd; |
| } |
| xfer++; |
| } |
| |
| /* Clear CS */ |
| spi_regs[chan]->csr = 0xf; |
| } |
| |
| |
| #define FLASH_CMD_DEEP_POWER_DOWN 0xb9 |
| #define FLASH_CMD_WAKE_UP 0xab |
| #define FLASH_CMD_WRITE_ENABLE 0x06 |
| #define FLASH_CMD_WRITE_ENABLE_VOLATILE 0x50 |
| #define FLASH_CMD_WRITE_DISABLE 0x04 |
| |
| #define FLASH_CMD_READ_MANUF_ID 0x9f |
| #define FLASH_CMD_READ_UNIQUE_ID 0x4b |
| |
| #define FLASH_CMD_READ_SR1 0x05 |
| #define FLASH_CMD_WRITE_SR1 0x01 |
| |
| #define FLASH_CMD_READ_DATA 0x03 |
| #define FLASH_CMD_PAGE_PROGRAM 0x02 |
| #define FLASH_CMD_CHIP_ERASE 0x60 |
| #define FLASH_CMD_SECTOR_ERASE 0x20 |
| |
| void |
| flash_cmd(uint8_t cmd) |
| { |
| struct spi_xfer_chunk xfer[1] = { |
| { .data = (void*)&cmd, .len = 1, .read = false, .write = true, }, |
| }; |
| spi_xfer(SPI_CS_FLASH, xfer, 1); |
| } |
| |
| void |
| flash_deep_power_down(void) |
| { |
| flash_cmd(FLASH_CMD_DEEP_POWER_DOWN); |
| } |
| |
| void |
| flash_wake_up(void) |
| { |
| flash_cmd(FLASH_CMD_WAKE_UP); |
| } |
| |
| void |
| flash_write_enable(void) |
| { |
| flash_cmd(FLASH_CMD_WRITE_ENABLE); |
| } |
| |
| void |
| flash_write_enable_volatile(void) |
| { |
| flash_cmd(FLASH_CMD_WRITE_ENABLE_VOLATILE); |
| } |
| |
| void |
| flash_write_disable(void) |
| { |
| flash_cmd(FLASH_CMD_WRITE_DISABLE); |
| } |
| |
| void |
| flash_manuf_id(void *manuf) |
| { |
| uint8_t cmd = FLASH_CMD_READ_MANUF_ID; |
| struct spi_xfer_chunk xfer[2] = { |
| { .data = (void*)&cmd, .len = 1, .read = false, .write = true, }, |
| { .data = (void*)manuf, .len = 3, .read = true, .write = false, }, |
| }; |
| spi_xfer(SPI_CS_FLASH, xfer, 2); |
| } |
| |
| void |
| flash_unique_id(void *id) |
| { |
| uint8_t cmd = FLASH_CMD_READ_UNIQUE_ID; |
| struct spi_xfer_chunk xfer[3] = { |
| { .data = (void*)&cmd, .len = 1, .read = false, .write = true, }, |
| { .data = (void*)0, .len = 4, .read = false, .write = false, }, |
| { .data = (void*)id, .len = 8, .read = true, .write = false, }, |
| }; |
| spi_xfer(SPI_CS_FLASH, xfer, 3); |
| } |
| |
| uint8_t |
| flash_read_sr(void) |
| { |
| uint8_t cmd = FLASH_CMD_READ_SR1; |
| uint8_t rv; |
| struct spi_xfer_chunk xfer[2] = { |
| { .data = (void*)&cmd, .len = 1, .read = false, .write = true, }, |
| { .data = (void*)&rv, .len = 1, .read = true, .write = false, }, |
| }; |
| spi_xfer(SPI_CS_FLASH, xfer, 2); |
| return rv; |
| } |
| |
| void |
| flash_write_sr(uint8_t sr) |
| { |
| uint8_t cmd[2] = { FLASH_CMD_WRITE_SR1, sr }; |
| struct spi_xfer_chunk xfer[1] = { |
| { .data = (void*)cmd, .len = 2, .read = false, .write = true, }, |
| }; |
| spi_xfer(SPI_CS_FLASH, xfer, 1); |
| } |
| |
| void |
| flash_read(void *dst, uint32_t addr, unsigned len) |
| { |
| uint8_t cmd[4] = { FLASH_CMD_READ_DATA, ((addr >> 16) & 0xff), ((addr >> 8) & 0xff), (addr & 0xff) }; |
| struct spi_xfer_chunk xfer[2] = { |
| { .data = (void*)cmd, .len = 4, .read = false, .write = true, }, |
| { .data = (void*)dst, .len = len, .read = true, .write = false, }, |
| }; |
| spi_xfer(SPI_CS_FLASH, xfer, 2); |
| } |
| |
| void |
| flash_page_program(const void *src, uint32_t addr, unsigned len) |
| { |
| uint8_t cmd[4] = { FLASH_CMD_PAGE_PROGRAM, ((addr >> 16) & 0xff), ((addr >> 8) & 0xff), (addr & 0xff) }; |
| struct spi_xfer_chunk xfer[2] = { |
| { .data = (void*)cmd, .len = 4, .read = false, .write = true, }, |
| { .data = (void*)src, .len = len, .read = false, .write = true, }, |
| }; |
| spi_xfer(SPI_CS_FLASH, xfer, 2); |
| } |
| |
| void |
| flash_sector_erase(uint32_t addr) |
| { |
| uint8_t cmd[4] = { FLASH_CMD_SECTOR_ERASE, ((addr >> 16) & 0xff), ((addr >> 8) & 0xff), (addr & 0xff) }; |
| struct spi_xfer_chunk xfer[1] = { |
| { .data = (void*)cmd, .len = 4, .read = false, .write = true, }, |
| }; |
| spi_xfer(SPI_CS_FLASH, xfer, 1); |
| } |