[bootloer] add minimal drivers

UART0, SPI, TWI
This commit is contained in:
stnolting 2025-03-23 17:14:43 +01:00
parent 74636ec991
commit da53cea8fc
6 changed files with 528 additions and 0 deletions

244
sw/bootloader/spi_flash.c Normal file
View file

@ -0,0 +1,244 @@
// ================================================================================ //
// The NEORV32 RISC-V Processor - https://github.com/stnolting/neorv32 //
// Copyright (c) NEORV32 contributors. //
// Copyright (c) 2020 - 2025 Stephan Nolting. All rights reserved. //
// Licensed under the BSD-3-Clause license, see LICENSE for details. //
// SPDX-License-Identifier: BSD-3-Clause //
// ================================================================================ //
/**
* @file spi_flash.c
* @brief SPI flash driver.
*/
#include <neorv32.h>
#include "config.h"
#include "spi_flash.h"
/**********************************************************************//**
* SPI flash commands
**************************************************************************/
enum SPI_FLASH_CMD_enum {
SPI_FLASH_CMD_PAGE_PROGRAM = 0x02, /**< Program page */
SPI_FLASH_CMD_READ = 0x03, /**< Read data */
SPI_FLASH_CMD_WRITE_DISABLE = 0x04, /**< Disallow write access */
SPI_FLASH_CMD_READ_STATUS = 0x05, /**< Get status register */
SPI_FLASH_CMD_WRITE_ENABLE = 0x06, /**< Allow write access */
SPI_FLASH_CMD_WAKE = 0xAB, /**< Wake up from sleep mode */
SPI_FLASH_CMD_SECTOR_ERASE = 0xD8 /**< Erase complete sector */
};
/**********************************************************************//**
* SPI flash status register bits
**************************************************************************/
enum SPI_FLASH_SREG_enum {
FLASH_SREG_BUSY = 0, /**< Busy, write/erase in progress when set, read-only */
FLASH_SREG_WEL = 1 /**< Write access enabled when set, read-only */
};
/**********************************************************************//**
* Send single command to SPI flash.
*
* @param[in] cmd Command byte.
**************************************************************************/
void spi_flash_cmd(uint8_t cmd) {
neorv32_spi_cs_en(SPI_FLASH_CS);
neorv32_spi_transfer(cmd);
neorv32_spi_cs_dis();
}
/**********************************************************************//**
* Read flash status register.
*
* @return SPI flash status register.
**************************************************************************/
uint8_t spi_flash_read_status(void) {
neorv32_spi_cs_en(SPI_FLASH_CS);
neorv32_spi_transfer(SPI_FLASH_CMD_READ_STATUS);
uint8_t res = neorv32_spi_transfer(0);
neorv32_spi_cs_dis();
return res;
}
/**********************************************************************//**
* Send address to flash.
*
* @param[in] addr Byte address.
**************************************************************************/
void spi_flash_send_addr(uint32_t addr) {
subwords32_t address;
address.uint32 = addr;
#if (FLASH_ADDR_BYTES == 2)
neorv32_spi_transfer(address.uint8[1]);
neorv32_spi_transfer(address.uint8[0]);
#elif (FLASH_ADDR_BYTES == 3)
neorv32_spi_transfer(address.uint8[2]);
neorv32_spi_transfer(address.uint8[1]);
neorv32_spi_transfer(address.uint8[0]);
#elif (FLASH_ADDR_BYTES == 4)
neorv32_spi_transfer(address.uint8[3]);
neorv32_spi_transfer(address.uint8[2]);
neorv32_spi_transfer(address.uint8[1]);
neorv32_spi_transfer(address.uint8[0]);
#else
#error "Invalid FLASH_ADDR_BYTES configuration!"
#endif
}
/**********************************************************************//**
* Write byte to SPI flash.
*
* @param[in] addr SPI flash read address.
* @param[in] wdata SPI flash read data.
**************************************************************************/
void spi_flash_write_byte(uint32_t addr, uint8_t wdata) {
spi_flash_cmd(SPI_FLASH_CMD_WRITE_ENABLE); // allow write-access
neorv32_spi_cs_en(SPI_FLASH_CS);
neorv32_spi_transfer(SPI_FLASH_CMD_PAGE_PROGRAM);
spi_flash_send_addr(addr);
neorv32_spi_transfer(wdata);
neorv32_spi_cs_dis();
while(1) {
if ((spi_flash_read_status() & (1 << FLASH_SREG_BUSY)) == 0) { // write in progress flag cleared?
break;
}
}
}
/**********************************************************************//**
* Check if SPI and flash are available/working by making sure the WEL
* flag of the flash status register can be set and cleared again.
*
* @return 0 if success, !=0 if error
**************************************************************************/
int spi_flash_check(void) {
#if (SPI_EN != 0)
if (neorv32_spi_available()) {
// the flash may have been set to sleep prior to reaching this point. Make sure it's alive
spi_flash_cmd(SPI_FLASH_CMD_WAKE);
// set WEL
spi_flash_cmd(SPI_FLASH_CMD_WRITE_ENABLE);
if ((spi_flash_read_status() & (1 << FLASH_SREG_WEL)) == 0) { // fail if WEL is cleared
return -1;
}
// clear WEL
spi_flash_cmd(SPI_FLASH_CMD_WRITE_DISABLE);
if ((spi_flash_read_status() & (1 << FLASH_SREG_WEL)) != 0) { // fail if WEL is set
return -1;
}
return 0;
}
return 1;
#else
return 1;
#endif
}
/**********************************************************************//**
* Read byte from SPI flash.
*
* @param[in] addr Word-aligned address.
* @param[in,out] rdata Pointer for returned data (uint32_t).
* @return 0 if success, !=0 if error
**************************************************************************/
int spi_flash_read_word(uint32_t addr, uint32_t* rdata) {
#if (SPI_EN != 0)
neorv32_spi_cs_en(SPI_FLASH_CS);
neorv32_spi_transfer(SPI_FLASH_CMD_READ);
spi_flash_send_addr(addr);
subwords32_t tmp;
tmp.uint8[0] = neorv32_spi_transfer(0);
tmp.uint8[1] = neorv32_spi_transfer(1);
tmp.uint8[2] = neorv32_spi_transfer(2);
tmp.uint8[3] = neorv32_spi_transfer(3);
neorv32_spi_cs_dis();
*rdata = tmp.uint32;
return 0;
#else
return 1;
#endif
}
/**********************************************************************//**
* Write word to SPI flash.
*
* @param addr SPI flash write address.
* @param wdata SPI flash write data.
* @return 0 if success, !=0 if error
**************************************************************************/
int spi_flash_write_word(uint32_t addr, uint32_t wdata) {
#if (SPI_EN != 0)
subwords32_t data;
data.uint32 = wdata;
// little-endian byte order
int i;
for (i=0; i<4; i++) {
spi_flash_write_byte(addr + i, data.uint8[i]);
}
return 0;
#else
return 1;
#endif
}
/**********************************************************************//**
* Erase sector (64kB) at base address.
*
* @param[in] addr Base address of sector to erase.
* @return 0 if success, !=0 if error
**************************************************************************/
int spi_flash_erase_sector(uint32_t addr) {
#if (SPI_EN != 0)
spi_flash_cmd(SPI_FLASH_CMD_WRITE_ENABLE); // allow write-access
neorv32_spi_cs_en(SPI_FLASH_CS);
neorv32_spi_transfer(SPI_FLASH_CMD_SECTOR_ERASE);
spi_flash_send_addr(addr);
neorv32_spi_cs_dis();
while(1) {
if ((spi_flash_read_status() & (1 << FLASH_SREG_BUSY)) == 0) { // write-in-progress flag cleared?
break;
}
}
return 0;
#else
return 1;
#endif
}

24
sw/bootloader/spi_flash.h Normal file
View file

@ -0,0 +1,24 @@
// ================================================================================ //
// The NEORV32 RISC-V Processor - https://github.com/stnolting/neorv32 //
// Copyright (c) NEORV32 contributors. //
// Copyright (c) 2020 - 2025 Stephan Nolting. All rights reserved. //
// Licensed under the BSD-3-Clause license, see LICENSE for details. //
// SPDX-License-Identifier: BSD-3-Clause //
// ================================================================================ //
/**
* @file spi_flash.h
* @brief SPI flash driver.
*/
#ifndef SPI_FLASH_H
#define SPI_FLASH_H
#include <stdint.h>
int spi_flash_check(void) ;
int spi_flash_read_word(uint32_t addr, uint32_t* rdata) ;
int spi_flash_write_word(uint32_t addr, uint32_t wdata);
int spi_flash_erase_sector(uint32_t addr);
#endif // SPI_FLASH_H

103
sw/bootloader/twi_flash.c Normal file
View file

@ -0,0 +1,103 @@
// ================================================================================ //
// The NEORV32 RISC-V Processor - https://github.com/stnolting/neorv32 //
// Copyright (c) NEORV32 contributors. //
// Copyright (c) 2020 - 2025 Stephan Nolting. All rights reserved. //
// Licensed under the BSD-3-Clause license, see LICENSE for details. //
// SPDX-License-Identifier: BSD-3-Clause //
// ================================================================================ //
/**
* @file twi_flash.c
* @brief TWI flash driver.
*/
#include <neorv32.h>
#include "config.h"
#include "twi_flash.h"
/**********************************************************************//**
* Read 32-bit word from word-aligned TWI flash address.
*
* @param[in] addr Word-aligned address.
* @param[in,out] rdata Pointer for returned data (uint32_t).
* @return 0 if success, != 0 if error
**************************************************************************/
int twi_flash_read_word(uint32_t addr, uint32_t* rdata) {
#if (TWI_EN != 0)
int i;
int device_nack = 0;
uint8_t transfer;
subwords32_t data, address;
address.uint32 = addr;
/***********************
* set address to read
***********************/
neorv32_twi_generate_start();
// send device address
uint8_t device_id = address.uint8[FLASH_ADDR_BYTES] + TWI_DEVICE_ID;
transfer = device_id << 1;
device_nack |= neorv32_twi_transfer(&transfer, 0);
// send read access address
#if (FLASH_ADDR_BYTES == 1)
transfer = address.uint8[0];
device_nack |= neorv32_twi_transfer(&transfer, 0);
#elif (FLASH_ADDR_BYTES == 2)
transfer = address.uint8[1];
device_nack |= neorv32_twi_transfer(&transfer, 0);
transfer = address.uint8[0];
device_nack |= neorv32_twi_transfer(&transfer, 0);
#elif (FLASH_ADDR_BYTES == 3)
transfer = address.uint8[2];
device_nack |= neorv32_twi_transfer(&transfer, 0);
transfer = address.uint8[1];
device_nack |= neorv32_twi_transfer(&transfer, 0);
transfer = address.uint8[0];
device_nack |= neorv32_twi_transfer(&transfer, 0);
#else
#error "Invalid FLASH_ADDR_BYTES configuration!"
#endif
/***********************
* read data
***********************/
neorv32_twi_generate_start();
// send device address with read flag
transfer = device_id << 1;
transfer |= 0x01;
device_nack |= neorv32_twi_transfer(&transfer, 0);
if (device_nack) {
neorv32_twi_generate_stop();
return 1;
}
// read
for (i = 0; i < 3; i++) {
transfer = 0xFF;
neorv32_twi_transfer(&transfer, 1); // ACK by host
data.uint8[i] = transfer;
}
// last read with NACK by host
transfer = 0xFF;
neorv32_twi_transfer(&transfer, 0); // NACK by host
data.uint8[3] = transfer;
*rdata = data.uint32;
neorv32_twi_generate_stop();
return 0;
#else
return 1;
#endif
}

21
sw/bootloader/twi_flash.h Normal file
View file

@ -0,0 +1,21 @@
// ================================================================================ //
// The NEORV32 RISC-V Processor - https://github.com/stnolting/neorv32 //
// Copyright (c) NEORV32 contributors. //
// Copyright (c) 2020 - 2025 Stephan Nolting. All rights reserved. //
// Licensed under the BSD-3-Clause license, see LICENSE for details. //
// SPDX-License-Identifier: BSD-3-Clause //
// ================================================================================ //
/**
* @file twi_flash.h
* @brief TWI flash driver.
*/
#ifndef TWI_FLASH_H
#define TWI_FLASH_H
#include <stdint.h>
int twi_flash_read_word(uint32_t addr, uint32_t* rdata);
#endif // TWI_FLASH_H

111
sw/bootloader/uart.c Normal file
View file

@ -0,0 +1,111 @@
// ================================================================================ //
// The NEORV32 RISC-V Processor - https://github.com/stnolting/neorv32 //
// Copyright (c) NEORV32 contributors. //
// Copyright (c) 2020 - 2025 Stephan Nolting. All rights reserved. //
// Licensed under the BSD-3-Clause license, see LICENSE for details. //
// SPDX-License-Identifier: BSD-3-Clause //
// ================================================================================ //
/**
* @file uart.c
* @brief Minimal UART0 driver.
*/
#include <neorv32.h>
#include "config.h"
#include "uart.h"
/**********************************************************************//**
* Read single char from UART0.
*
* @return Received char.
**************************************************************************/
char uart_getc(void) {
#if (UART_EN != 0)
if (neorv32_uart_available(NEORV32_UART0)) {
return neorv32_uart_getc(NEORV32_UART0);
}
#endif
return 0;
}
/**********************************************************************//**
* Print single char via UART0.
*
* @note Converts LF ("\n") to CR+LF ("\r\n").
*
* @param[in] c Character to print.
**************************************************************************/
void uart_putc(char c) {
#if (UART_EN != 0)
if (neorv32_uart_available(NEORV32_UART0)) {
if (c == '\n') {
neorv32_uart_putc(NEORV32_UART0, '\r');
}
neorv32_uart_putc(NEORV32_UART0, c);
}
#endif
}
/**********************************************************************//**
* Print zero-terminated string via UART0.
*
* @param[in] s Pointer to string.
**************************************************************************/
void uart_puts(const char *s) {
#if (UART_EN != 0)
char c = 0;
while ((c = *s++)) {
uart_putc(c);
}
#endif
}
/**********************************************************************//**
* Print 32-bit number as 8-digit hexadecimal value with "0x" suffix via UART0.
*
* @param[in] num Number to print as hexadecimal.
**************************************************************************/
void uart_puth(uint32_t num) {
#if (UART_EN != 0)
static const char hex_symbols[16] = "0123456789abcdef";
uart_putc('0');
uart_putc('x');
int i;
for (i=28; i>=0; i-=4) {
uart_putc(hex_symbols[(num >> i) & 0xf]);
}
#endif
}
/**********************************************************************//**
* Read 32-bit binary word from UART0.
*
* @param[in,out] rdata Pointer for returned data (uint32_t).
* @return 0 if success, != 0 if error
**************************************************************************/
int uart_getw(uint32_t* rdata) {
#if (UART_EN != 0)
int i;
subwords32_t tmp;
for (i=0; i<4; i++) {
tmp.uint8[i] = (uint8_t)uart_getc();
}
*rdata = tmp.uint32;
return 0;
#else
return 1;
#endif
}

25
sw/bootloader/uart.h Normal file
View file

@ -0,0 +1,25 @@
// ================================================================================ //
// The NEORV32 RISC-V Processor - https://github.com/stnolting/neorv32 //
// Copyright (c) NEORV32 contributors. //
// Copyright (c) 2020 - 2025 Stephan Nolting. All rights reserved. //
// Licensed under the BSD-3-Clause license, see LICENSE for details. //
// SPDX-License-Identifier: BSD-3-Clause //
// ================================================================================ //
/**
* @file uart.h
* @brief Minimal UART0 driver.
*/
#ifndef UART_H
#define UART_H
#include <stdint.h>
char uart_getc(void);
void uart_putc(char c);
void uart_puts(const char *s);
void uart_puth(uint32_t num);
int uart_getw(uint32_t* rdata);
#endif // UART_H