diff --git a/hw/application_fpga/fw/tk1/flash.c b/hw/application_fpga/fw/tk1/flash.c new file mode 100644 index 0000000..3be9431 --- /dev/null +++ b/hw/application_fpga/fw/tk1/flash.c @@ -0,0 +1,217 @@ +// Copyright (C) 2024 - Tillitis AB +// SPDX-License-Identifier: GPL-2.0-only + +#include "flash.h" +#include "../tk1_mem.h" +#include "spi.h" + +#include +#include +#include + +// clang-format off +static volatile uint32_t *timer = (volatile uint32_t *)TK1_MMIO_TIMER_TIMER; +static volatile uint32_t *timer_prescaler = (volatile uint32_t *)TK1_MMIO_TIMER_PRESCALER; +static volatile uint32_t *timer_status = (volatile uint32_t *)TK1_MMIO_TIMER_STATUS; +static volatile uint32_t *timer_ctrl = (volatile uint32_t *)TK1_MMIO_TIMER_CTRL; +// clang-format on + +// CPU clock frequency in Hz +#define CPUFREQ 21000000 +#define PAGE_SIZE 256 + +static void delay(int timeout_ms) +{ + // Tick once every centisecond + *timer_prescaler = CPUFREQ / 100; + *timer = timeout_ms / 10; + + *timer_ctrl |= (1 << TK1_MMIO_TIMER_CTRL_START_BIT); + + while (*timer_status != 0) { + } + + // Stop timer + *timer_ctrl |= (1 << TK1_MMIO_TIMER_CTRL_STOP_BIT); +} + +bool flash_is_busy(void) +{ + uint8_t tx_buf = READ_STATUS_REG_1; + uint8_t rx_buf = {0x00}; + + spi_transfer(&tx_buf, sizeof(tx_buf), NULL, 0, &rx_buf, sizeof(rx_buf)); + + if (rx_buf & (1 << STATUS_REG_BUSY_BIT)) { + return true; + } + + return false; +} + +// Blocking until !busy +void flash_wait_busy(void) +{ + while (flash_is_busy()) { + delay(10); + } +} + +void flash_write_enable(void) +{ + uint8_t tx_buf = WRITE_ENABLE; + + spi_transfer(&tx_buf, sizeof(tx_buf), NULL, 0, NULL, 0); +} + +void flash_write_disable(void) +{ + uint8_t tx_buf = WRITE_DISABLE; + + spi_transfer(&tx_buf, sizeof(tx_buf), NULL, 0, NULL, 0); +} + +void flash_sector_erase(uint32_t address) +{ + uint8_t tx_buf[4] = {0x00}; + tx_buf[0] = SECTOR_ERASE; + tx_buf[1] = (address >> ADDR_BYTE_3_BIT) & 0xFF; + tx_buf[2] = (address >> ADDR_BYTE_2_BIT) & 0xFF; + /* tx_buf[3] is within a sector, and hence does not make a difference */ + + flash_write_enable(); + spi_transfer(tx_buf, sizeof(tx_buf), NULL, 0, NULL, 0); + flash_wait_busy(); +} + +void flash_block_32_erase(uint32_t address) +{ + uint8_t tx_buf[4] = {0x00}; + tx_buf[0] = BLOCK_ERASE_32K; + tx_buf[1] = (address >> ADDR_BYTE_3_BIT) & 0xFF; + tx_buf[2] = (address >> ADDR_BYTE_2_BIT) & 0xFF; + tx_buf[3] = (address >> ADDR_BYTE_1_BIT) & 0xFF; + + flash_write_enable(); + spi_transfer(tx_buf, sizeof(tx_buf), NULL, 0, NULL, 0); + flash_wait_busy(); +} + +// 64 KiB block erase, only cares about address bits 16 and above. +void flash_block_64_erase(uint32_t address) +{ + uint8_t tx_buf[4] = {0x00}; + tx_buf[0] = BLOCK_ERASE_64K; + tx_buf[1] = (address >> ADDR_BYTE_3_BIT) & 0xFF; + /* tx_buf[2] and tx_buf[3] is within a block, and hence does not make a + * difference */ + + flash_write_enable(); + spi_transfer(tx_buf, sizeof(tx_buf), NULL, 0, NULL, 0); + flash_wait_busy(); +} + +void flash_release_powerdown(void) +{ + uint8_t tx_buf[4] = {0x00}; + tx_buf[0] = RELEASE_POWER_DOWN; + + spi_transfer(tx_buf, sizeof(tx_buf), NULL, 0, NULL, 0); +} + +void flash_powerdown(void) +{ + uint8_t tx_buf = POWER_DOWN; + + spi_transfer(&tx_buf, sizeof(tx_buf), NULL, 0, NULL, 0); +} + +void flash_read_manufacturer_device_id(uint8_t *device_id) +{ + uint8_t tx_buf[4] = {0x00}; + tx_buf[0] = READ_MANUFACTURER_ID; + + spi_transfer(tx_buf, sizeof(tx_buf), NULL, 0, device_id, 2); +} + +void flash_read_jedec_id(uint8_t *jedec_id) +{ + uint8_t tx_buf = READ_JEDEC_ID; + + spi_transfer(&tx_buf, sizeof(tx_buf), NULL, 0, jedec_id, 3); +} + +void flash_read_unique_id(uint8_t *unique_id) +{ + uint8_t tx_buf[5] = {0x00}; + tx_buf[0] = READ_UNIQUE_ID; + + spi_transfer(tx_buf, sizeof(tx_buf), NULL, 0, unique_id, 8); +} + +void flash_read_status(uint8_t *status_reg) +{ + uint8_t tx_buf = READ_STATUS_REG_1; + + spi_transfer(&tx_buf, sizeof(tx_buf), NULL, 0, status_reg, 1); + + tx_buf = READ_STATUS_REG_2; + spi_transfer(&tx_buf, sizeof(tx_buf), NULL, 0, status_reg + 1, 1); +} + +int flash_read_data(uint32_t address, uint8_t *dest_buf, size_t size) +{ + uint8_t tx_buf[4] = {0x00}; + tx_buf[0] = READ_DATA; + tx_buf[1] = (address >> ADDR_BYTE_3_BIT) & 0xFF; + tx_buf[2] = (address >> ADDR_BYTE_2_BIT) & 0xFF; + tx_buf[3] = (address >> ADDR_BYTE_1_BIT) & 0xFF; + + return spi_transfer(tx_buf, sizeof(tx_buf), NULL, 0, dest_buf, size); +} + +// Only handles writes where the least significant byte of the start address is +// zero. +int flash_write_data(uint32_t address, uint8_t *data, size_t size) +{ + if (size <= 0 || size > 4096) { + return -1; + } + + size_t left = size; + uint8_t *p_data = data; + size_t n_bytes = 0; + + uint8_t tx_buf[4] = { + PAGE_PROGRAM, /* tx_buf[0] */ + (address >> ADDR_BYTE_3_BIT) & 0xFF, /* tx_buf[1] */ + (address >> ADDR_BYTE_2_BIT) & 0xFF, /* tx_buf[2] */ + 0x00, /* tx_buf[3] */ + }; + + while (left > 0) { + if (left >= PAGE_SIZE) { + n_bytes = PAGE_SIZE; + } else { + n_bytes = left; + } + + flash_write_enable(); + + if (spi_transfer(tx_buf, sizeof(tx_buf), p_data, n_bytes, NULL, + 0) != 0) { + return -1; + } + + left -= n_bytes; + p_data += n_bytes; + + address += n_bytes; + tx_buf[1] = (address >> ADDR_BYTE_3_BIT) & 0xFF; + tx_buf[2] = (address >> ADDR_BYTE_2_BIT) & 0xFF; + + flash_wait_busy(); + } + + return 0; +} diff --git a/hw/application_fpga/fw/tk1/flash.h b/hw/application_fpga/fw/tk1/flash.h new file mode 100644 index 0000000..cad6f07 --- /dev/null +++ b/hw/application_fpga/fw/tk1/flash.h @@ -0,0 +1,58 @@ +// Copyright (C) 2024 - Tillitis AB +// SPDX-License-Identifier: GPL-2.0-only + +#ifndef TKEY_FLASH_H +#define TKEY_FLASH_H + +#include +#include +#include + +#define WRITE_ENABLE 0x06 +#define WRITE_DISABLE 0x04 + +#define READ_STATUS_REG_1 0x05 +#define READ_STATUS_REG_2 0x35 +#define WRITE_STATUS_REG 0x01 + +#define PAGE_PROGRAM 0x02 +#define SECTOR_ERASE 0x20 +#define BLOCK_ERASE_32K 0x52 +#define BLOCK_ERASE_64K 0xD8 +#define CHIP_ERASE 0xC7 + +#define POWER_DOWN 0xB9 +#define READ_DATA 0x03 +#define RELEASE_POWER_DOWN 0xAB + +#define READ_MANUFACTURER_ID 0x90 +#define READ_JEDEC_ID 0x9F +#define READ_UNIQUE_ID 0x4B + +#define ENABLE_RESET 0x66 +#define RESET 0x99 + +#define ADDR_BYTE_3_BIT 16 +#define ADDR_BYTE_2_BIT 8 +#define ADDR_BYTE_1_BIT 0 + +#define STATUS_REG_BUSY_BIT 0 +#define STATUS_REG_WEL_BIT 1 + +bool flash_is_busy(void); +void flash_wait_busy(void); +void flash_write_enable(void); +void flash_write_disable(void); +void flash_sector_erase(uint32_t address); +void flash_block_32_erase(uint32_t address); +void flash_block_64_erase(uint32_t address); +void flash_release_powerdown(void); +void flash_powerdown(void); +void flash_read_manufacturer_device_id(uint8_t *device_id); +void flash_read_jedec_id(uint8_t *jedec_id); +void flash_read_unique_id(uint8_t *unique_id); +void flash_read_status(uint8_t *status_reg); +int flash_read_data(uint32_t address, uint8_t *dest_buf, size_t size); +int flash_write_data(uint32_t address, uint8_t *data, size_t size); + +#endif diff --git a/hw/application_fpga/fw/tk1/spi.c b/hw/application_fpga/fw/tk1/spi.c new file mode 100644 index 0000000..4246ccb --- /dev/null +++ b/hw/application_fpga/fw/tk1/spi.c @@ -0,0 +1,91 @@ +// Copyright (C) 2024 - Tillitis AB +// SPDX-License-Identifier: GPL-2.0-only + +#include "spi.h" +#include "../tk1_mem.h" + +#include +#include + +// clang-format off +static volatile uint32_t *spi_en = (volatile uint32_t *)(TK1_MMIO_TK1_BASE | 0x200); +static volatile uint32_t *spi_xfer = (volatile uint32_t *)(TK1_MMIO_TK1_BASE | 0x204); +static volatile uint32_t *spi_data = (volatile uint32_t *)(TK1_MMIO_TK1_BASE | 0x208); +// clang-format on + +// returns non-zero when the SPI-master is ready, and zero if not +// ready. This can be used to check if the SPI-master is available +// in the hardware. +int spi_ready(void) +{ + return *spi_xfer; +} + +static void spi_enable(void) +{ + *spi_en = 1; +} + +static void spi_disable(void) +{ + *spi_en = 0; +} + +static void _spi_write(uint8_t *cmd, size_t size) +{ + for (size_t i = 0; i < size; i++) { + while (!spi_ready()) { + } + + *spi_data = cmd[i]; + *spi_xfer = 1; + } + + while (!spi_ready()) { + } +} + +static void _spi_read(uint8_t *buf, size_t size) +{ + + while (!spi_ready()) { + } + + for (size_t i = 0; i < size; i++) { + + *spi_data = 0x00; + *spi_xfer = 1; + + // wait until spi master is done + while (!spi_ready()) { + } + + buf[i] = (*spi_data & 0xff); + } +} + + +// Function to both read and write data to the connected SPI flash. +int spi_transfer(uint8_t *cmd, size_t cmd_size, uint8_t *tx_buf, size_t tx_size, + uint8_t *rx_buf, size_t rx_size) +{ + if (cmd == NULL || cmd_size == 0) { + return -1; + } + + spi_enable(); + + _spi_write(cmd, cmd_size); + + if (tx_buf != NULL || tx_size != 0) { + _spi_write(tx_buf, tx_size); + } + + if (rx_buf != NULL && rx_size != 0) { + _spi_read(rx_buf, rx_size); + } + + spi_disable(); + + return 0; +} diff --git a/hw/application_fpga/fw/tk1/spi.h b/hw/application_fpga/fw/tk1/spi.h new file mode 100644 index 0000000..2c59f4c --- /dev/null +++ b/hw/application_fpga/fw/tk1/spi.h @@ -0,0 +1,14 @@ +// Copyright (C) 2024 - Tillitis AB +// SPDX-License-Identifier: GPL-2.0-only + +#ifndef TKEY_SPI_H +#define TKEY_SPI_H + +#include +#include + +int spi_ready(void); +int spi_transfer(uint8_t *cmd, size_t cmd_size, uint8_t *tx_buf, size_t tx_size, + uint8_t *rx_buf, size_t rx_size); + +#endif