RP2040 emulated EEPROM. (#17519)
parent
9f1c4f304d
commit
5846b40f74
@ -0,0 +1,117 @@ |
|||||||
|
/* |
||||||
|
ChibiOS - Copyright (C) 2006..2021 Giovanni Di Sirio |
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
you may not use this file except in compliance with the License. |
||||||
|
You may obtain a copy of the License at |
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software |
||||||
|
distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
See the License for the specific language governing permissions and |
||||||
|
limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
/* |
||||||
|
* RP2040 memory setup. |
||||||
|
*/ |
||||||
|
MEMORY |
||||||
|
{ |
||||||
|
flash0 (rx) : org = 0x00000000, len = 16k /* ROM */ |
||||||
|
flash1 (rx) : org = 0x10000000, len = DEFINED(FLASH_LEN) ? FLASH_LEN : 2048k /* XIP */ |
||||||
|
flash2 (rx) : org = 0x00000000, len = 0 |
||||||
|
flash3 (rx) : org = 0x00000000, len = 0 |
||||||
|
flash4 (rx) : org = 0x00000000, len = 0 |
||||||
|
flash5 (rx) : org = 0x00000000, len = 0 |
||||||
|
flash6 (rx) : org = 0x00000000, len = 0 |
||||||
|
flash7 (rx) : org = 0x00000000, len = 0 |
||||||
|
ram0 (wx) : org = 0x20000000, len = 256k /* SRAM0 striped */ |
||||||
|
ram1 (wx) : org = 0x00000000, len = 256k /* SRAM0 non striped */ |
||||||
|
ram2 (wx) : org = 0x00000000, len = 0 |
||||||
|
ram3 (wx) : org = 0x00000000, len = 0 |
||||||
|
ram4 (wx) : org = 0x20040000, len = 4k /* SRAM4 */ |
||||||
|
ram5 (wx) : org = 0x20041000, len = 4k /* SRAM5 */ |
||||||
|
ram6 (wx) : org = 0x00000000, len = 0 |
||||||
|
ram7 (wx) : org = 0x20041f00, len = 256 /* SRAM5 boot */ |
||||||
|
} |
||||||
|
|
||||||
|
/* For each data/text section two region are defined, a virtual region |
||||||
|
and a load region (_LMA suffix).*/ |
||||||
|
|
||||||
|
/* Flash region to be used for exception vectors.*/ |
||||||
|
REGION_ALIAS("VECTORS_FLASH", flash1); |
||||||
|
REGION_ALIAS("VECTORS_FLASH_LMA", flash1); |
||||||
|
|
||||||
|
/* Flash region to be used for constructors and destructors.*/ |
||||||
|
REGION_ALIAS("XTORS_FLASH", flash1); |
||||||
|
REGION_ALIAS("XTORS_FLASH_LMA", flash1); |
||||||
|
|
||||||
|
/* Flash region to be used for code text.*/ |
||||||
|
REGION_ALIAS("TEXT_FLASH", flash1); |
||||||
|
REGION_ALIAS("TEXT_FLASH_LMA", flash1); |
||||||
|
|
||||||
|
/* Flash region to be used for read only data.*/ |
||||||
|
REGION_ALIAS("RODATA_FLASH", flash1); |
||||||
|
REGION_ALIAS("RODATA_FLASH_LMA", flash1); |
||||||
|
|
||||||
|
/* Flash region to be used for various.*/ |
||||||
|
REGION_ALIAS("VARIOUS_FLASH", flash1); |
||||||
|
REGION_ALIAS("VARIOUS_FLASH_LMA", flash1); |
||||||
|
|
||||||
|
/* Flash region to be used for RAM(n) initialization data.*/ |
||||||
|
REGION_ALIAS("RAM_INIT_FLASH_LMA", flash1); |
||||||
|
|
||||||
|
/* RAM region to be used for Main stack. This stack accommodates the processing |
||||||
|
of all exceptions and interrupts.*/ |
||||||
|
REGION_ALIAS("MAIN_STACK_RAM", ram4); |
||||||
|
|
||||||
|
/* RAM region to be used for the process stack. This is the stack used by |
||||||
|
the main() function.*/ |
||||||
|
REGION_ALIAS("PROCESS_STACK_RAM", ram4); |
||||||
|
|
||||||
|
/* RAM region to be used for Main stack. This stack accommodates the processing |
||||||
|
of all exceptions and interrupts.*/ |
||||||
|
REGION_ALIAS("C1_MAIN_STACK_RAM", ram5); |
||||||
|
|
||||||
|
/* RAM region to be used for the process stack. This is the stack used by |
||||||
|
the main() function.*/ |
||||||
|
REGION_ALIAS("C1_PROCESS_STACK_RAM", ram5); |
||||||
|
|
||||||
|
/* RAM region to be used for data segment.*/ |
||||||
|
REGION_ALIAS("DATA_RAM", ram0); |
||||||
|
REGION_ALIAS("DATA_RAM_LMA", flash1); |
||||||
|
|
||||||
|
/* RAM region to be used for BSS segment.*/ |
||||||
|
REGION_ALIAS("BSS_RAM", ram0); |
||||||
|
|
||||||
|
/* RAM region to be used for the default heap.*/ |
||||||
|
REGION_ALIAS("HEAP_RAM", ram0); |
||||||
|
|
||||||
|
SECTIONS |
||||||
|
{ |
||||||
|
.flash_begin : { |
||||||
|
__flash_binary_start = .; |
||||||
|
} > flash1 |
||||||
|
|
||||||
|
.boot2 : { |
||||||
|
__boot2_start__ = .; |
||||||
|
KEEP (*(.boot2)) |
||||||
|
__boot2_end__ = .; |
||||||
|
} > flash1 |
||||||
|
} |
||||||
|
|
||||||
|
/* Generic rules inclusion.*/ |
||||||
|
INCLUDE rules_stacks.ld |
||||||
|
INCLUDE rules_stacks_c1.ld |
||||||
|
INCLUDE RP2040_rules_code_with_boot2.ld |
||||||
|
INCLUDE RP2040_rules_data_with_timecrit.ld |
||||||
|
INCLUDE rules_memory.ld |
||||||
|
|
||||||
|
SECTIONS |
||||||
|
{ |
||||||
|
.flash_end : { |
||||||
|
__flash_binary_end = .; |
||||||
|
} > flash1 |
||||||
|
} |
@ -0,0 +1,46 @@ |
|||||||
|
/* |
||||||
|
ChibiOS - Copyright (C) 2006..2018 Giovanni Di Sirio |
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
you may not use this file except in compliance with the License. |
||||||
|
You may obtain a copy of the License at |
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software |
||||||
|
distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
See the License for the specific language governing permissions and |
||||||
|
limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
SECTIONS |
||||||
|
{ |
||||||
|
.data : ALIGN(4) |
||||||
|
{ |
||||||
|
PROVIDE(_textdata = LOADADDR(.data)); |
||||||
|
PROVIDE(_data = .); |
||||||
|
__textdata_base__ = LOADADDR(.data); |
||||||
|
__data_base__ = .; |
||||||
|
*(vtable) |
||||||
|
*(.time_critical*) |
||||||
|
. = ALIGN(4); |
||||||
|
*(.data) |
||||||
|
*(.data.*) |
||||||
|
*(.ramtext) |
||||||
|
. = ALIGN(4); |
||||||
|
PROVIDE(_edata = .); |
||||||
|
__data_end__ = .; |
||||||
|
} > DATA_RAM AT > DATA_RAM_LMA |
||||||
|
|
||||||
|
.bss (NOLOAD) : ALIGN(4) |
||||||
|
{ |
||||||
|
__bss_base__ = .; |
||||||
|
*(.bss) |
||||||
|
*(.bss.*) |
||||||
|
*(COMMON) |
||||||
|
. = ALIGN(4); |
||||||
|
__bss_end__ = .; |
||||||
|
PROVIDE(end = .); |
||||||
|
} > BSS_RAM |
||||||
|
} |
@ -0,0 +1,221 @@ |
|||||||
|
/**
|
||||||
|
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd. |
||||||
|
* Copyright (c) 2022 Nick Brassel (@tzarc) |
||||||
|
* Copyright (c) 2022 Stefan Kerkmann (@KarlK90) |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: BSD-3-Clause |
||||||
|
*/ |
||||||
|
|
||||||
|
#include "pico/bootrom.h" |
||||||
|
#include "hardware/flash.h" |
||||||
|
#include "hardware/sync.h" |
||||||
|
#include "hardware/structs/ssi.h" |
||||||
|
#include "hardware/structs/ioqspi.h" |
||||||
|
|
||||||
|
#include <stdbool.h> |
||||||
|
#include "timer.h" |
||||||
|
#include "wear_leveling.h" |
||||||
|
#include "wear_leveling_internal.h" |
||||||
|
|
||||||
|
#ifndef WEAR_LEVELING_RP2040_FLASH_BULK_COUNT |
||||||
|
# define WEAR_LEVELING_RP2040_FLASH_BULK_COUNT 64 |
||||||
|
#endif // WEAR_LEVELING_RP2040_FLASH_BULK_COUNT
|
||||||
|
|
||||||
|
#define FLASHCMD_PAGE_PROGRAM 0x02 |
||||||
|
#define FLASHCMD_READ_STATUS 0x05 |
||||||
|
#define FLASHCMD_WRITE_ENABLE 0x06 |
||||||
|
|
||||||
|
extern uint8_t BOOT2_ROM[256]; |
||||||
|
static uint32_t BOOT2_ROM_RAM[64]; |
||||||
|
|
||||||
|
static ssi_hw_t *const ssi = (ssi_hw_t *)XIP_SSI_BASE; |
||||||
|
|
||||||
|
// Sanity check
|
||||||
|
check_hw_layout(ssi_hw_t, ssienr, SSI_SSIENR_OFFSET); |
||||||
|
check_hw_layout(ssi_hw_t, spi_ctrlr0, SSI_SPI_CTRLR0_OFFSET); |
||||||
|
|
||||||
|
static void __no_inline_not_in_flash_func(flash_enable_xip_via_boot2)(void) { |
||||||
|
((void (*)(void))BOOT2_ROM_RAM + 1)(); |
||||||
|
} |
||||||
|
|
||||||
|
// Bitbanging the chip select using IO overrides, in case RAM-resident IRQs
|
||||||
|
// are still running, and the FIFO bottoms out. (the bootrom does the same)
|
||||||
|
static void __no_inline_not_in_flash_func(flash_cs_force)(bool high) { |
||||||
|
uint32_t field_val = high ? IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_VALUE_HIGH : IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_VALUE_LOW; |
||||||
|
hw_write_masked(&ioqspi_hw->io[1].ctrl, field_val << IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_LSB, IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_BITS); |
||||||
|
} |
||||||
|
|
||||||
|
// Also allow any unbounded loops to check whether the above abort condition
|
||||||
|
// was asserted, and terminate early
|
||||||
|
static int __no_inline_not_in_flash_func(flash_was_aborted)(void) { |
||||||
|
return *(io_rw_32 *)(IO_QSPI_BASE + IO_QSPI_GPIO_QSPI_SD1_CTRL_OFFSET) & IO_QSPI_GPIO_QSPI_SD1_CTRL_INOVER_BITS; |
||||||
|
} |
||||||
|
|
||||||
|
// Put bytes from one buffer, and get bytes into another buffer.
|
||||||
|
// These can be the same buffer.
|
||||||
|
// If tx is NULL then send zeroes.
|
||||||
|
// If rx is NULL then all read data will be dropped.
|
||||||
|
//
|
||||||
|
// If rx_skip is nonzero, this many bytes will first be consumed from the FIFO,
|
||||||
|
// before reading a further count bytes into *rx.
|
||||||
|
// E.g. if you have written a command+address just before calling this function.
|
||||||
|
static void __no_inline_not_in_flash_func(flash_put_get)(const uint8_t *tx, uint8_t *rx, size_t count, size_t rx_skip) { |
||||||
|
// Make sure there is never more data in flight than the depth of the RX
|
||||||
|
// FIFO. Otherwise, when we are interrupted for long periods, hardware
|
||||||
|
// will overflow the RX FIFO.
|
||||||
|
const uint max_in_flight = 16 - 2; // account for data internal to SSI
|
||||||
|
size_t tx_count = count; |
||||||
|
size_t rx_count = count; |
||||||
|
while (tx_count || rx_skip || rx_count) { |
||||||
|
// NB order of reads, for pessimism rather than optimism
|
||||||
|
uint32_t tx_level = ssi_hw->txflr; |
||||||
|
uint32_t rx_level = ssi_hw->rxflr; |
||||||
|
bool did_something = false; // Expect this to be folded into control flow, not register
|
||||||
|
if (tx_count && tx_level + rx_level < max_in_flight) { |
||||||
|
ssi->dr0 = (uint32_t)(tx ? *tx++ : 0); |
||||||
|
--tx_count; |
||||||
|
did_something = true; |
||||||
|
} |
||||||
|
if (rx_level) { |
||||||
|
uint8_t rxbyte = ssi->dr0; |
||||||
|
did_something = true; |
||||||
|
if (rx_skip) { |
||||||
|
--rx_skip; |
||||||
|
} else { |
||||||
|
if (rx) *rx++ = rxbyte; |
||||||
|
--rx_count; |
||||||
|
} |
||||||
|
} |
||||||
|
// APB load costs 4 cycles, so only do it on idle loops (our budget is
|
||||||
|
// 48 cyc/byte)
|
||||||
|
if (!did_something && __builtin_expect(flash_was_aborted(), 0)) break; |
||||||
|
} |
||||||
|
flash_cs_force(1); |
||||||
|
} |
||||||
|
|
||||||
|
// Convenience wrapper for above
|
||||||
|
// (And it's hard for the debug host to get the tight timing between
|
||||||
|
// cmd DR0 write and the remaining data)
|
||||||
|
static void __no_inline_not_in_flash_func(_flash_do_cmd)(uint8_t cmd, const uint8_t *tx, uint8_t *rx, size_t count) { |
||||||
|
flash_cs_force(0); |
||||||
|
ssi->dr0 = cmd; |
||||||
|
flash_put_get(tx, rx, count, 1); |
||||||
|
} |
||||||
|
|
||||||
|
// Timing of this one is critical, so do not expose the symbol to debugger etc
|
||||||
|
static void __no_inline_not_in_flash_func(flash_put_cmd_addr)(uint8_t cmd, uint32_t addr) { |
||||||
|
flash_cs_force(0); |
||||||
|
addr |= cmd << 24; |
||||||
|
for (int i = 0; i < 4; ++i) { |
||||||
|
ssi->dr0 = addr >> 24; |
||||||
|
addr <<= 8; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Poll the flash status register until the busy bit (LSB) clears
|
||||||
|
static void __no_inline_not_in_flash_func(flash_wait_ready)(void) { |
||||||
|
uint8_t stat; |
||||||
|
do { |
||||||
|
_flash_do_cmd(FLASHCMD_READ_STATUS, NULL, &stat, 1); |
||||||
|
} while (stat & 0x1 && !flash_was_aborted()); |
||||||
|
} |
||||||
|
|
||||||
|
// Set the WEL bit (needed before any program/erase operation)
|
||||||
|
static void __no_inline_not_in_flash_func(flash_enable_write)(void) { |
||||||
|
_flash_do_cmd(FLASHCMD_WRITE_ENABLE, NULL, NULL, 0); |
||||||
|
} |
||||||
|
|
||||||
|
static void __no_inline_not_in_flash_func(pico_program_bulk)(uint32_t flash_address, backing_store_int_t *values, size_t item_count) { |
||||||
|
rom_connect_internal_flash_fn connect_internal_flash = (rom_connect_internal_flash_fn)rom_func_lookup_inline(ROM_FUNC_CONNECT_INTERNAL_FLASH); |
||||||
|
rom_flash_exit_xip_fn flash_exit_xip = (rom_flash_exit_xip_fn)rom_func_lookup_inline(ROM_FUNC_FLASH_EXIT_XIP); |
||||||
|
rom_flash_flush_cache_fn flash_flush_cache = (rom_flash_flush_cache_fn)rom_func_lookup_inline(ROM_FUNC_FLASH_FLUSH_CACHE); |
||||||
|
assert(connect_internal_flash && flash_exit_xip && flash_flush_cache); |
||||||
|
|
||||||
|
static backing_store_int_t bulk_write_buffer[WEAR_LEVELING_RP2040_FLASH_BULK_COUNT]; |
||||||
|
|
||||||
|
while (item_count) { |
||||||
|
size_t batch_size = MIN(item_count, WEAR_LEVELING_RP2040_FLASH_BULK_COUNT); |
||||||
|
for (size_t i = 0; i < batch_size; i++, values++, item_count--) { |
||||||
|
bulk_write_buffer[i] = ~(*values); |
||||||
|
} |
||||||
|
__compiler_memory_barrier(); |
||||||
|
|
||||||
|
connect_internal_flash(); |
||||||
|
flash_exit_xip(); |
||||||
|
flash_enable_write(); |
||||||
|
|
||||||
|
flash_put_cmd_addr(FLASHCMD_PAGE_PROGRAM, flash_address); |
||||||
|
flash_put_get((uint8_t *)bulk_write_buffer, NULL, batch_size * sizeof(backing_store_int_t), 4); |
||||||
|
flash_wait_ready(); |
||||||
|
flash_address += batch_size * sizeof(backing_store_int_t); |
||||||
|
|
||||||
|
flash_flush_cache(); |
||||||
|
flash_enable_xip_via_boot2(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// QMK Wear-Leveling Backing Store implementation
|
||||||
|
|
||||||
|
static int interrupts; |
||||||
|
|
||||||
|
bool backing_store_init(void) { |
||||||
|
bs_dprintf("Init\n"); |
||||||
|
memcpy(BOOT2_ROM_RAM, BOOT2_ROM, sizeof(BOOT2_ROM)); |
||||||
|
__compiler_memory_barrier(); |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
bool backing_store_unlock(void) { |
||||||
|
bs_dprintf("Unlock\n"); |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
bool backing_store_erase(void) { |
||||||
|
#ifdef WEAR_LEVELING_DEBUG_OUTPUT |
||||||
|
uint32_t start = timer_read32(); |
||||||
|
#endif |
||||||
|
|
||||||
|
// Ensure the backing size can be cleanly subtracted from the flash size without alignment issues.
|
||||||
|
_Static_assert((WEAR_LEVELING_BACKING_SIZE) % (FLASH_SECTOR_SIZE) == 0, "Backing size must be a multiple of FLASH_SECTOR_SIZE"); |
||||||
|
|
||||||
|
interrupts = save_and_disable_interrupts(); |
||||||
|
flash_range_erase((WEAR_LEVELING_RP2040_FLASH_BASE), (WEAR_LEVELING_BACKING_SIZE)); |
||||||
|
restore_interrupts(interrupts); |
||||||
|
|
||||||
|
bs_dprintf("Backing store erase took %ldms to complete\n", ((long)(timer_read32() - start))); |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
bool backing_store_write(uint32_t address, backing_store_int_t value) { |
||||||
|
return backing_store_write_bulk(address, &value, 1); |
||||||
|
} |
||||||
|
|
||||||
|
bool backing_store_write_bulk(uint32_t address, backing_store_int_t *values, size_t item_count) { |
||||||
|
uint32_t offset = (WEAR_LEVELING_RP2040_FLASH_BASE) + address; |
||||||
|
bs_dprintf("Write "); |
||||||
|
wl_dump(offset, values, sizeof(backing_store_int_t) * item_count); |
||||||
|
interrupts = save_and_disable_interrupts(); |
||||||
|
pico_program_bulk(offset, values, item_count); |
||||||
|
restore_interrupts(interrupts); |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
bool backing_store_lock(void) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
bool backing_store_read(uint32_t address, backing_store_int_t *value) { |
||||||
|
return backing_store_read_bulk(address, value, 1); |
||||||
|
} |
||||||
|
|
||||||
|
bool backing_store_read_bulk(uint32_t address, backing_store_int_t *values, size_t item_count) { |
||||||
|
uint32_t offset = (WEAR_LEVELING_RP2040_FLASH_BASE) + address; |
||||||
|
backing_store_int_t *loc = (backing_store_int_t *)((XIP_BASE) + offset); |
||||||
|
for (size_t i = 0; i < item_count; ++i) { |
||||||
|
values[i] = ~loc[i]; |
||||||
|
} |
||||||
|
bs_dprintf("Read "); |
||||||
|
wl_dump(offset, values, item_count * sizeof(backing_store_int_t)); |
||||||
|
return true; |
||||||
|
} |
@ -0,0 +1,32 @@ |
|||||||
|
// Copyright 2022 Nick Brassel (@tzarc)
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
#pragma once |
||||||
|
|
||||||
|
#ifndef __ASSEMBLER__ |
||||||
|
# include "hardware/flash.h" |
||||||
|
#endif |
||||||
|
|
||||||
|
// 2-byte writes
|
||||||
|
#ifndef BACKING_STORE_WRITE_SIZE |
||||||
|
# define BACKING_STORE_WRITE_SIZE 2 |
||||||
|
#endif |
||||||
|
|
||||||
|
// 64kB backing space allocated
|
||||||
|
#ifndef WEAR_LEVELING_BACKING_SIZE |
||||||
|
# define WEAR_LEVELING_BACKING_SIZE 8192 |
||||||
|
#endif // WEAR_LEVELING_BACKING_SIZE
|
||||||
|
|
||||||
|
// 32kB logical EEPROM
|
||||||
|
#ifndef WEAR_LEVELING_LOGICAL_SIZE |
||||||
|
# define WEAR_LEVELING_LOGICAL_SIZE 4096 |
||||||
|
#endif // WEAR_LEVELING_LOGICAL_SIZE
|
||||||
|
|
||||||
|
// Define how much flash space we have (defaults to lib/pico-sdk/src/boards/include/boards/***)
|
||||||
|
#ifndef WEAR_LEVELING_RP2040_FLASH_SIZE |
||||||
|
# define WEAR_LEVELING_RP2040_FLASH_SIZE (PICO_FLASH_SIZE_BYTES) |
||||||
|
#endif |
||||||
|
|
||||||
|
// Define the location of emulated EEPROM
|
||||||
|
#ifndef WEAR_LEVELING_RP2040_FLASH_BASE |
||||||
|
# define WEAR_LEVELING_RP2040_FLASH_BASE ((WEAR_LEVELING_RP2040_FLASH_SIZE) - (WEAR_LEVELING_BACKING_SIZE)) |
||||||
|
#endif |
Loading…
Reference in new issue