mirror of
https://github.com/google/pebble.git
synced 2025-06-02 16:23:11 +00:00
Import of the watch repository from Pebble
This commit is contained in:
commit
3b92768480
10334 changed files with 2564465 additions and 0 deletions
16
src/fw/drivers/flash/README.md
Normal file
16
src/fw/drivers/flash/README.md
Normal file
|
@ -0,0 +1,16 @@
|
|||
Flash Memory Drivers
|
||||
--------------------
|
||||
|
||||
Flash memory access is exposed through two separate APIs. The main API, defined
|
||||
in `flash.h` and implemented in `flash_api.c`, is used almost everywhere. There
|
||||
is also an alternate API used exclusively for performing core dumps, implemented
|
||||
in `cd_flash_driver.c`. The alternate API is carefully implemented to be usable
|
||||
regardless of how messed up the system state is in by not relying on any OS
|
||||
services.
|
||||
|
||||
The flash APIs are written to be agnostic to the specific type of flash used.
|
||||
They are written against the generic low-level driver interface defined in
|
||||
`flash_impl.h`. Each low-level driver is specific to a combination of
|
||||
flash memory part and microcontroller interface. The low-level drivers make no
|
||||
use of OS services so that they can be used for both the main and core dump
|
||||
driver APIs.
|
100
src/fw/drivers/flash/cd_flash_driver.c
Normal file
100
src/fw/drivers/flash/cd_flash_driver.c
Normal file
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "drivers/flash.h"
|
||||
#include "drivers/flash/flash_impl.h"
|
||||
#include "drivers/watchdog.h"
|
||||
#include "flash_region/flash_region.h"
|
||||
#include "kernel/util/delay.h"
|
||||
|
||||
#include "kernel/core_dump_private.h"
|
||||
|
||||
//! We have our own flash driver for coredump support because it must not use
|
||||
//! any FreeRTOS constructs & we want to keep it as simple as possible. In
|
||||
//! addition we want the flexibility to be able to reset the flash driver to
|
||||
//! get it into a working state
|
||||
|
||||
void cd_flash_init(void) {
|
||||
// Reset & (re)initialize the flash HW
|
||||
flash_impl_init(true /* coredump_mode */);
|
||||
|
||||
// Protect the PRF region from writes
|
||||
flash_impl_write_protect(
|
||||
FLASH_REGION_SAFE_FIRMWARE_BEGIN,
|
||||
(FLASH_REGION_SAFE_FIRMWARE_END - SECTOR_SIZE_BYTES));
|
||||
}
|
||||
|
||||
void cd_flash_erase_region(uint32_t start_addr, uint32_t total_bytes) {
|
||||
CD_ASSERTN(((start_addr & SUBSECTOR_ADDR_MASK) == start_addr) &&
|
||||
((total_bytes & SUBSECTOR_ADDR_MASK) == total_bytes));
|
||||
|
||||
while (total_bytes > 0) {
|
||||
watchdog_feed();
|
||||
|
||||
uint32_t erase_size = 0;
|
||||
if (((start_addr & SECTOR_ADDR_MASK) == start_addr) &&
|
||||
(total_bytes >= SECTOR_SIZE_BYTES)) {
|
||||
erase_size = SECTOR_SIZE_BYTES;
|
||||
flash_impl_erase_sector_begin(start_addr);
|
||||
} else if ((start_addr & SUBSECTOR_ADDR_MASK) == start_addr &&
|
||||
(total_bytes >= SUBSECTOR_SIZE_BYTES)) {
|
||||
erase_size = SUBSECTOR_SIZE_BYTES;
|
||||
flash_impl_erase_subsector_begin(start_addr);
|
||||
} else {
|
||||
// Unaligned start address or unaligned erase size
|
||||
CD_ASSERTN(0);
|
||||
}
|
||||
|
||||
status_t status;
|
||||
while ((status = flash_impl_get_erase_status()) == E_BUSY) delay_us(100);
|
||||
CD_ASSERTN(status == S_SUCCESS);
|
||||
|
||||
total_bytes -= erase_size;
|
||||
start_addr += erase_size;
|
||||
}
|
||||
|
||||
watchdog_feed();
|
||||
}
|
||||
|
||||
uint32_t cd_flash_write_bytes(const void *buffer_ptr, uint32_t start_addr,
|
||||
const uint32_t buffer_size) {
|
||||
CD_ASSERTN(((start_addr + buffer_size) <= CORE_DUMP_FLASH_END) &&
|
||||
(int)start_addr >= CORE_DUMP_FLASH_START);
|
||||
|
||||
const uint8_t *buffer = buffer_ptr;
|
||||
uint32_t remaining = buffer_size;
|
||||
|
||||
while (remaining) {
|
||||
int written = flash_impl_write_page_begin(buffer, start_addr, remaining);
|
||||
CD_ASSERTN(PASSED(written));
|
||||
status_t status;
|
||||
while ((status = flash_impl_get_write_status()) == E_BUSY) {
|
||||
delay_us(10);
|
||||
}
|
||||
CD_ASSERTN(PASSED(status));
|
||||
buffer += written;
|
||||
start_addr += written;
|
||||
remaining -= written;
|
||||
}
|
||||
return buffer_size;
|
||||
}
|
||||
|
||||
void cd_flash_read_bytes(void* buffer_ptr, uint32_t start_addr,
|
||||
uint32_t buffer_size) {
|
||||
flash_impl_read_sync(buffer_ptr, start_addr, buffer_size);
|
||||
}
|
599
src/fw/drivers/flash/flash_api.c
Normal file
599
src/fw/drivers/flash/flash_api.c
Normal file
|
@ -0,0 +1,599 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "drivers/flash.h"
|
||||
#include "drivers/flash/flash_internal.h"
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "drivers/flash/flash_impl.h"
|
||||
#include "drivers/task_watchdog.h"
|
||||
#include "drivers/watchdog.h"
|
||||
#include "flash_region/flash_region.h"
|
||||
#include "kernel/util/stop.h"
|
||||
#include "os/mutex.h"
|
||||
#include "os/tick.h"
|
||||
#include "process_management/worker_manager.h"
|
||||
#include "services/common/analytics/analytics.h"
|
||||
#include "services/common/new_timer/new_timer.h"
|
||||
#include "system/logging.h"
|
||||
#include "system/passert.h"
|
||||
#include "kernel/util/sleep.h"
|
||||
|
||||
#include "FreeRTOS.h"
|
||||
#include "semphr.h"
|
||||
|
||||
#define MAX_ERASE_RETRIES (3)
|
||||
|
||||
static PebbleMutex *s_flash_lock;
|
||||
static SemaphoreHandle_t s_erase_semphr;
|
||||
|
||||
static struct FlashEraseContext {
|
||||
bool in_progress;
|
||||
bool suspended;
|
||||
bool is_subsector;
|
||||
uint8_t retries;
|
||||
PebbleTask task;
|
||||
uint32_t address;
|
||||
FlashOperationCompleteCb on_complete_cb;
|
||||
void *cb_context;
|
||||
uint32_t expected_duration;
|
||||
} s_erase = { 0 };
|
||||
|
||||
static TimerID s_erase_poll_timer;
|
||||
static TimerID s_erase_suspend_timer;
|
||||
|
||||
static uint32_t s_analytics_read_count;
|
||||
static uint32_t s_analytics_read_bytes_count;
|
||||
static uint32_t s_analytics_write_bytes_count;
|
||||
|
||||
static uint32_t s_system_analytics_read_bytes_count = 0;
|
||||
static uint32_t s_system_analytics_write_bytes_count = 0;
|
||||
static uint8_t s_system_analytics_erase_count = 0;
|
||||
|
||||
void analytics_external_collect_system_flash_statistics(void) {
|
||||
analytics_set(ANALYTICS_DEVICE_METRIC_FLASH_READ_BYTES_COUNT,
|
||||
s_system_analytics_read_bytes_count, AnalyticsClient_System);
|
||||
analytics_set(ANALYTICS_DEVICE_METRIC_FLASH_WRITE_BYTES_COUNT,
|
||||
s_system_analytics_write_bytes_count, AnalyticsClient_System);
|
||||
analytics_set(ANALYTICS_DEVICE_METRIC_FLASH_ERASE_COUNT,
|
||||
s_system_analytics_erase_count, AnalyticsClient_System);
|
||||
|
||||
s_system_analytics_read_bytes_count = 0;
|
||||
s_system_analytics_write_bytes_count = 0;
|
||||
s_system_analytics_erase_count = 0;
|
||||
}
|
||||
|
||||
void analytics_external_collect_app_flash_read_stats(void) {
|
||||
analytics_set(ANALYTICS_APP_METRIC_FLASH_READ_COUNT,
|
||||
s_analytics_read_count, AnalyticsClient_App);
|
||||
analytics_set(ANALYTICS_APP_METRIC_FLASH_READ_BYTES_COUNT,
|
||||
s_analytics_read_bytes_count, AnalyticsClient_App);
|
||||
analytics_set(ANALYTICS_APP_METRIC_FLASH_WRITE_BYTES_COUNT,
|
||||
s_analytics_write_bytes_count, AnalyticsClient_App);
|
||||
|
||||
// The overhead cost of tracking whether each flash read was due to the foreground
|
||||
// or background app is large, so the best we can do is to attribute to both of them
|
||||
if (worker_manager_get_current_worker_md() != NULL) {
|
||||
analytics_set(ANALYTICS_APP_METRIC_FLASH_READ_COUNT,
|
||||
s_analytics_read_count, AnalyticsClient_Worker);
|
||||
analytics_set(ANALYTICS_APP_METRIC_FLASH_READ_BYTES_COUNT,
|
||||
s_analytics_read_bytes_count, AnalyticsClient_Worker);
|
||||
analytics_set(ANALYTICS_APP_METRIC_FLASH_WRITE_BYTES_COUNT,
|
||||
s_analytics_write_bytes_count, AnalyticsClient_Worker);
|
||||
}
|
||||
|
||||
s_analytics_read_count = 0;
|
||||
s_analytics_read_bytes_count = 0;
|
||||
s_analytics_write_bytes_count = 0;
|
||||
}
|
||||
|
||||
//! Assumes that s_flash_lock is held.
|
||||
static status_t prv_try_restart_interrupted_erase(bool is_subsector,
|
||||
uint32_t addr) {
|
||||
status_t status = is_subsector? flash_impl_erase_subsector_begin(addr)
|
||||
: flash_impl_erase_sector_begin(addr);
|
||||
if (FAILED(status)) {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Got error trying to reissue interrupted erase: "
|
||||
"%"PRIi32, status);
|
||||
return status;
|
||||
}
|
||||
|
||||
// Hopefully the task watchdog isn't enabled; this could take a while.
|
||||
while (1) {
|
||||
psleep(10);
|
||||
status = flash_impl_get_erase_status();
|
||||
if (status != E_BUSY && status != E_AGAIN) {
|
||||
// Success or failure
|
||||
return status;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void flash_init(void) {
|
||||
if (s_flash_lock) {
|
||||
return; // Already initialized
|
||||
}
|
||||
s_flash_lock = mutex_create();
|
||||
s_erase_semphr = xSemaphoreCreateBinary();
|
||||
xSemaphoreGive(s_erase_semphr);
|
||||
s_erase_poll_timer = new_timer_create();
|
||||
s_erase_suspend_timer = new_timer_create();
|
||||
flash_erase_init();
|
||||
|
||||
mutex_lock(s_flash_lock);
|
||||
flash_impl_init(false /* coredump_mode */);
|
||||
|
||||
uint32_t erase_in_progress_address = 0;
|
||||
bool is_subsector = false;
|
||||
if (flash_impl_get_nvram_erase_status(
|
||||
&is_subsector, &erase_in_progress_address) == S_TRUE) {
|
||||
// An erase was interrupted by e.g. a crash. Retry the erase so the
|
||||
// incompletely-erased sector doesn't cause us trouble later on.
|
||||
PBL_LOG(LOG_LEVEL_WARNING, "Flash erase to 0x%"PRIx32" was interrupted "
|
||||
"last boot. Restarting the erase...", erase_in_progress_address);
|
||||
|
||||
// When an erase is interrupted, subsequent erases of the same sector tend to
|
||||
// take an exceptionally long time and may even outright fail. Try the erase a
|
||||
// few times before giving up.
|
||||
int attempt = 1;
|
||||
while (true) {
|
||||
status_t status = prv_try_restart_interrupted_erase(
|
||||
is_subsector, erase_in_progress_address);
|
||||
if (PASSED(status)) {
|
||||
break;
|
||||
}
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Flash erase failed, status %"PRIi32, status);
|
||||
if (attempt++ >= MAX_ERASE_RETRIES) {
|
||||
// We've tried all we can. No point in croaking; it's not like a reboot
|
||||
// is going to fix anything. Best we can do is pretend like nothing is
|
||||
// wrong and hope for the best.
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Giving up on restarting the flash erase.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
flash_impl_clear_nvram_erase_status();
|
||||
mutex_unlock(s_flash_lock);
|
||||
}
|
||||
|
||||
#if UNITTEST
|
||||
void flash_api_reset_for_test(void) {
|
||||
s_erase = (struct FlashEraseContext) {0};
|
||||
s_flash_lock = NULL;
|
||||
}
|
||||
|
||||
TimerID flash_api_get_erase_poll_timer_for_test(void) {
|
||||
return s_erase_poll_timer;
|
||||
}
|
||||
#endif
|
||||
|
||||
//! Assumes that s_flash_lock is held.
|
||||
static void prv_erase_pause(void) {
|
||||
if (s_erase.in_progress && !s_erase.suspended) {
|
||||
// If an erase is in progress, make sure it gets at least a mininum time slice to progress.
|
||||
// If not, the successive kicking of the suspend timer could starve it out completely
|
||||
psleep(100);
|
||||
task_watchdog_bit_set(s_erase.task);
|
||||
status_t status = flash_impl_erase_suspend(s_erase.address);
|
||||
PBL_ASSERT(PASSED(status), "Erase suspend failure: %" PRId32, status);
|
||||
if (status == S_NO_ACTION_REQUIRED) {
|
||||
// The erase has already completed. No need to resume.
|
||||
s_erase.in_progress = false;
|
||||
} else {
|
||||
s_erase.suspended = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//! Assumes that s_flash_lock is held.
|
||||
static void prv_erase_resume(void) {
|
||||
if (s_erase.suspended) {
|
||||
status_t status = flash_impl_erase_resume(s_erase.address);
|
||||
PBL_ASSERT(PASSED(status), "Erase resume failure: %" PRId32, status);
|
||||
s_erase.suspended = false;
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_erase_suspend_timer_cb(void *unused) {
|
||||
mutex_lock(s_flash_lock);
|
||||
prv_erase_resume();
|
||||
mutex_unlock(s_flash_lock);
|
||||
}
|
||||
|
||||
void flash_read_bytes(uint8_t* buffer, uint32_t start_addr,
|
||||
uint32_t buffer_size) {
|
||||
mutex_lock(s_flash_lock);
|
||||
s_analytics_read_count++;
|
||||
s_analytics_read_bytes_count += buffer_size;
|
||||
s_system_analytics_read_bytes_count += buffer_size;
|
||||
// TODO: use DMA when possible
|
||||
// TODO: be smarter about pausing erases. Some flash chips allow concurrent
|
||||
// reads while an erase is in progress, as long as the read is to another bank
|
||||
// than the one being erased.
|
||||
prv_erase_pause();
|
||||
new_timer_start(s_erase_suspend_timer, 5, prv_erase_suspend_timer_cb, NULL, 0);
|
||||
flash_impl_read_sync(buffer, start_addr, buffer_size);
|
||||
mutex_unlock(s_flash_lock);
|
||||
}
|
||||
|
||||
#ifdef TEST_FLASH_LOCK_PROTECTION
|
||||
static bool s_assert_write_error = false;
|
||||
void flash_expect_program_failure(bool expect_failure) {
|
||||
s_assert_write_error = expect_failure;
|
||||
}
|
||||
#endif
|
||||
|
||||
void flash_write_bytes(const uint8_t *buffer, uint32_t start_addr,
|
||||
uint32_t buffer_size) {
|
||||
mutex_lock(s_flash_lock);
|
||||
stop_mode_disable(InhibitorFlash); // FIXME: PBL-18028
|
||||
s_analytics_write_bytes_count += buffer_size;
|
||||
s_system_analytics_write_bytes_count += buffer_size;
|
||||
prv_erase_pause();
|
||||
new_timer_start(s_erase_suspend_timer, 50, prv_erase_suspend_timer_cb, NULL, 0);
|
||||
while (buffer_size) {
|
||||
int written = flash_impl_write_page_begin(buffer, start_addr, buffer_size);
|
||||
PBL_ASSERT(
|
||||
#ifdef TEST_FLASH_LOCK_PROTECTION
|
||||
s_assert_write_error ||
|
||||
#endif
|
||||
PASSED(written),
|
||||
"flash_impl_write_page_begin failed: %d", written);
|
||||
status_t status;
|
||||
while ((status = flash_impl_get_write_status()) == E_BUSY) {
|
||||
psleep(0);
|
||||
}
|
||||
#ifdef TEST_FLASH_LOCK_PROTECTION
|
||||
if (s_assert_write_error) {
|
||||
PBL_ASSERT(FAILED(status), "flash write unexpectedly succeeded: %" PRId32,
|
||||
status);
|
||||
} else {
|
||||
#endif
|
||||
PBL_ASSERT(PASSED(status), "flash_impl_get_write_status returned %" PRId32,
|
||||
status);
|
||||
#ifdef TEST_FLASH_LOCK_PROTECTION
|
||||
}
|
||||
#endif
|
||||
buffer += written;
|
||||
start_addr += written;
|
||||
buffer_size -= written;
|
||||
// Give higher-priority tasks a chance to access the flash in between
|
||||
// each page write.
|
||||
// TODO: uncomment the lines below to resolve PBL-17503
|
||||
// if (buffer_size) {
|
||||
// mutex_unlock(s_flash_lock);
|
||||
// mutex_lock(s_flash_lock);
|
||||
// }
|
||||
}
|
||||
stop_mode_enable(InhibitorFlash);
|
||||
mutex_unlock(s_flash_lock);
|
||||
}
|
||||
|
||||
// Returns 0 if the erase has completed, or a non-zero expected duration (in
|
||||
// ms) if not. If the erase has not finished (non-zero has been returned), the
|
||||
// caller is responsible for calling the prv_flash_erase_poll() method until
|
||||
// the erase completes.
|
||||
static uint32_t prv_flash_erase_start(uint32_t addr,
|
||||
FlashOperationCompleteCb on_complete_cb,
|
||||
void *context,
|
||||
bool is_subsector,
|
||||
uint8_t retries) {
|
||||
xSemaphoreTake(s_erase_semphr, portMAX_DELAY);
|
||||
mutex_lock(s_flash_lock);
|
||||
PBL_ASSERTN(s_erase.in_progress == false);
|
||||
s_erase = (struct FlashEraseContext) {
|
||||
.in_progress = true,
|
||||
.task = pebble_task_get_current(),
|
||||
.retries = retries,
|
||||
// FIXME: We should just assert that the address is already aligned. If
|
||||
// someone is depending on this behaviour without already knowing the range
|
||||
// that's being erased they're going to have a bad time. This will probably
|
||||
// cause some client fallout though, so tackle this later.
|
||||
.is_subsector = is_subsector,
|
||||
.address = is_subsector? flash_impl_get_subsector_base_address(addr)
|
||||
: flash_impl_get_sector_base_address(addr),
|
||||
.on_complete_cb = on_complete_cb,
|
||||
.cb_context = context,
|
||||
.expected_duration = is_subsector?
|
||||
flash_impl_get_typical_subsector_erase_duration_ms() :
|
||||
flash_impl_get_typical_sector_erase_duration_ms(),
|
||||
};
|
||||
stop_mode_disable(InhibitorFlash); // FIXME: PBL-18028
|
||||
status_t status = is_subsector? flash_impl_blank_check_subsector(addr)
|
||||
: flash_impl_blank_check_sector(addr);
|
||||
PBL_ASSERT(PASSED(status), "Blank check error: %" PRId32, status);
|
||||
if (status != S_FALSE) {
|
||||
stop_mode_enable(InhibitorFlash);
|
||||
s_erase.in_progress = false;
|
||||
mutex_unlock(s_flash_lock);
|
||||
xSemaphoreGive(s_erase_semphr);
|
||||
// Only run the callback with no locks held so that the callback won't
|
||||
// deadlock if it kicks off another sector erase.
|
||||
on_complete_cb(context, S_NO_ACTION_REQUIRED);
|
||||
return 0;
|
||||
}
|
||||
|
||||
analytics_inc(ANALYTICS_APP_METRIC_FLASH_SUBSECTOR_ERASE_COUNT,
|
||||
AnalyticsClient_CurrentTask);
|
||||
s_system_analytics_erase_count++;
|
||||
status = is_subsector? flash_impl_erase_subsector_begin(addr)
|
||||
: flash_impl_erase_sector_begin(addr);
|
||||
|
||||
if (PASSED(status)) {
|
||||
flash_impl_set_nvram_erase_status(is_subsector, addr);
|
||||
mutex_unlock(s_flash_lock);
|
||||
return (s_erase.expected_duration * 7 / 8);
|
||||
} else {
|
||||
stop_mode_enable(InhibitorFlash);
|
||||
s_erase.in_progress = false;
|
||||
mutex_unlock(s_flash_lock);
|
||||
xSemaphoreGive(s_erase_semphr);
|
||||
// Only run the callback with no locks held so that the callback won't
|
||||
// deadlock if it kicks off another sector erase.
|
||||
on_complete_cb(context, status);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Returns non-zero expected remaining time if the erase has not finished. If the erase
|
||||
// has finished it will re-enable stop-mode, clear the in_progress flag and call the
|
||||
// completed callback before returning 0.
|
||||
static uint32_t prv_flash_erase_poll(void) {
|
||||
mutex_lock(s_flash_lock);
|
||||
status_t status = flash_impl_get_erase_status();
|
||||
bool erase_finished;
|
||||
struct FlashEraseContext saved_ctx = s_erase;
|
||||
switch (status) {
|
||||
case E_BUSY:
|
||||
case E_AGAIN:
|
||||
erase_finished = false;
|
||||
break;
|
||||
case S_SUCCESS:
|
||||
default:
|
||||
// Success or failure; the erase has finished either way.
|
||||
erase_finished = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (erase_finished) {
|
||||
stop_mode_enable(InhibitorFlash);
|
||||
s_erase.in_progress = false;
|
||||
flash_impl_clear_nvram_erase_status();
|
||||
}
|
||||
mutex_unlock(s_flash_lock);
|
||||
|
||||
if (!erase_finished) {
|
||||
return s_erase.expected_duration / 8;
|
||||
}
|
||||
|
||||
xSemaphoreGive(s_erase_semphr);
|
||||
if (status == E_ERROR && saved_ctx.retries < MAX_ERASE_RETRIES) {
|
||||
// Try issuing the erase again. It might succeed this time around.
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Erase of 0x%"PRIx32" failed (attempt %d)."
|
||||
" Trying again...", saved_ctx.address, saved_ctx.retries);
|
||||
return prv_flash_erase_start(
|
||||
saved_ctx.address, saved_ctx.on_complete_cb, saved_ctx.cb_context,
|
||||
saved_ctx.is_subsector, saved_ctx.retries + 1);
|
||||
} else {
|
||||
// Only run the callback with no locks held so that the callback won't
|
||||
// deadlock if it kicks off another sector erase.
|
||||
saved_ctx.on_complete_cb(saved_ctx.cb_context, status);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Timer callback that checks to see if the erase has finished. Used by the non-blocking
|
||||
// routines.
|
||||
static void prv_flash_erase_timer_cb(void *context) {
|
||||
uint32_t remaining_ms = prv_flash_erase_poll();
|
||||
if (remaining_ms) {
|
||||
// Erase is in progress or suspended; poll again later.
|
||||
new_timer_start(s_erase_poll_timer, remaining_ms, prv_flash_erase_timer_cb, NULL, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_flash_erase_async(
|
||||
uint32_t sector_addr, bool is_subsector, FlashOperationCompleteCb on_complete_cb,
|
||||
void *context) {
|
||||
uint32_t remaining_ms = prv_flash_erase_start(sector_addr, on_complete_cb,
|
||||
context, is_subsector, 0);
|
||||
if (remaining_ms) {
|
||||
// Start timer that will periodically check for the erase to complete
|
||||
new_timer_start(s_erase_poll_timer, remaining_ms, prv_flash_erase_timer_cb, NULL, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_blocking_erase_complete(void *context, status_t status) {
|
||||
PBL_ASSERT(PASSED(status), "Flash erase failure: %" PRId32, status);
|
||||
}
|
||||
|
||||
static void prv_flash_erase_blocking(uint32_t sector_addr, bool is_subsector) {
|
||||
uint32_t total_time_spent_waiting_ms = 0;
|
||||
|
||||
uint32_t remaining_ms = prv_flash_erase_start(
|
||||
sector_addr, prv_blocking_erase_complete, NULL, is_subsector, 0);
|
||||
while (remaining_ms) {
|
||||
psleep(remaining_ms);
|
||||
total_time_spent_waiting_ms += remaining_ms;
|
||||
|
||||
remaining_ms = prv_flash_erase_poll();
|
||||
|
||||
// check to see if the cb responsible for resuming erases should have run
|
||||
// but is blocked. See PBL-25741 for details
|
||||
uint32_t erase_suspend_time_remaining;
|
||||
if (new_timer_scheduled(s_erase_suspend_timer, &erase_suspend_time_remaining) &&
|
||||
(erase_suspend_time_remaining == 0)) {
|
||||
prv_erase_suspend_timer_cb(NULL);
|
||||
}
|
||||
|
||||
|
||||
// An erase can take a long time, especially if the erase needs to be
|
||||
// retried. Appease the watchdog so that it doesn't get angry when an
|
||||
// erase takes >6 seconds.
|
||||
//
|
||||
// After a certain amount of time passes, stop kicking the watchdog. This is to handle a case
|
||||
// where the erase never finishes or takes an unheard of amount of time to complete. Just let
|
||||
// the watchdog kill us in this case.
|
||||
static const uint32_t FLASH_ERASE_BLOCKING_TIMEOUT_MS = 5000;
|
||||
if (total_time_spent_waiting_ms < FLASH_ERASE_BLOCKING_TIMEOUT_MS) {
|
||||
#if IS_BIGBOARD
|
||||
// Our bigboards have had a hard life and they have some fairly abused flash chips, and we
|
||||
// run into 5+ second erases pretty regularly. We're not holding the flash lock while we're
|
||||
// doing this, so other threads are allowed to use flash, but it's pretty common to hold
|
||||
// other locks while waiting for a flash operation to complete, leading to other tasks
|
||||
// triggering their task watchdogs before this erase completes. Let's kick all watchdogs
|
||||
// instead. The downside to this is that it may take us longer to detect another thread is
|
||||
// stuck, but we should still detect it eventually as long as we're not constantly erasing.
|
||||
task_watchdog_bit_set_all();
|
||||
#else
|
||||
// Just kick the watchdog for the current task. This should give us more accurate watchdog
|
||||
// behaviour and sealed watches haven't been abused as much and shouldn't have extremely
|
||||
// long erase problems.
|
||||
task_watchdog_bit_set(pebble_task_get_current());
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void flash_erase_sector(uint32_t sector_addr,
|
||||
FlashOperationCompleteCb on_complete_cb,
|
||||
void *context) {
|
||||
prv_flash_erase_async(sector_addr, false /* is_subsector */, on_complete_cb, context);
|
||||
}
|
||||
|
||||
void flash_erase_subsector(uint32_t sector_addr,
|
||||
FlashOperationCompleteCb on_complete_cb,
|
||||
void *context) {
|
||||
prv_flash_erase_async(sector_addr, true /* is_subsector */, on_complete_cb, context);
|
||||
}
|
||||
|
||||
void flash_erase_sector_blocking(uint32_t sector_addr) {
|
||||
prv_flash_erase_blocking(sector_addr, false /* is_subsector */);
|
||||
}
|
||||
|
||||
void flash_erase_subsector_blocking(uint32_t subsector_addr) {
|
||||
prv_flash_erase_blocking(subsector_addr, true /* is_subsector */);
|
||||
}
|
||||
|
||||
void flash_enable_write_protection(void) {
|
||||
flash_impl_enable_write_protection();
|
||||
}
|
||||
|
||||
void flash_prf_set_protection(bool do_protect) {
|
||||
status_t status;
|
||||
mutex_lock(s_flash_lock);
|
||||
if (do_protect) {
|
||||
status = flash_impl_write_protect(
|
||||
FLASH_REGION_SAFE_FIRMWARE_BEGIN,
|
||||
(FLASH_REGION_SAFE_FIRMWARE_END - SECTOR_SIZE_BYTES));
|
||||
} else {
|
||||
status = flash_impl_unprotect();
|
||||
}
|
||||
PBL_ASSERT(PASSED(status), "flash_prf_set_protection failed: %" PRId32, status);
|
||||
mutex_unlock(s_flash_lock);
|
||||
}
|
||||
|
||||
#if 0
|
||||
void flash_erase_bulk(void) {
|
||||
mutex_lock(s_flash_lock);
|
||||
flash_impl_erase_bulk_begin();
|
||||
while (flash_impl_erase_is_in_progress()) {
|
||||
psleep(10);
|
||||
}
|
||||
mutex_unlock(s_flash_lock);
|
||||
}
|
||||
#endif
|
||||
|
||||
void flash_sleep_when_idle(bool enable) {
|
||||
// the S29VS flash automatically enters and exits standby
|
||||
}
|
||||
|
||||
bool flash_get_sleep_when_idle(void) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool flash_is_initialized(void) {
|
||||
return (s_flash_lock != NULL);
|
||||
}
|
||||
|
||||
void flash_stop(void) {
|
||||
if (!flash_is_initialized()) {
|
||||
// Not yet initialized, nothing to do.
|
||||
return;
|
||||
}
|
||||
|
||||
mutex_lock(s_flash_lock);
|
||||
if (s_erase.in_progress) {
|
||||
new_timer_stop(s_erase_suspend_timer);
|
||||
prv_erase_resume();
|
||||
mutex_unlock(s_flash_lock);
|
||||
while (__atomic_load_n(&s_erase.in_progress, __ATOMIC_SEQ_CST)) {
|
||||
psleep(10);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void flash_switch_mode(FlashModeType mode) {
|
||||
mutex_lock(s_flash_lock);
|
||||
flash_impl_set_burst_mode(mode == FLASH_MODE_SYNC_BURST);
|
||||
mutex_unlock(s_flash_lock);
|
||||
}
|
||||
|
||||
uint32_t flash_get_sector_base_address(uint32_t flash_addr) {
|
||||
return flash_impl_get_sector_base_address(flash_addr);
|
||||
}
|
||||
|
||||
uint32_t flash_get_subsector_base_address(uint32_t flash_addr) {
|
||||
return flash_impl_get_subsector_base_address(flash_addr);
|
||||
}
|
||||
|
||||
void flash_power_down_for_stop_mode(void) {
|
||||
flash_impl_enter_low_power_mode();
|
||||
}
|
||||
|
||||
void flash_power_up_after_stop_mode(void) {
|
||||
flash_impl_exit_low_power_mode();
|
||||
}
|
||||
|
||||
bool flash_sector_is_erased(uint32_t sector_addr) {
|
||||
return flash_impl_blank_check_sector(flash_impl_get_sector_base_address(sector_addr));
|
||||
}
|
||||
|
||||
bool flash_subsector_is_erased(uint32_t sector_addr) {
|
||||
return flash_impl_blank_check_subsector(flash_impl_get_subsector_base_address(sector_addr));
|
||||
}
|
||||
|
||||
void flash_use(void) {
|
||||
mutex_lock(s_flash_lock);
|
||||
flash_impl_use();
|
||||
mutex_unlock(s_flash_lock);
|
||||
}
|
||||
|
||||
void flash_release_many(uint32_t num_locks) {
|
||||
mutex_lock(s_flash_lock);
|
||||
flash_impl_release_many(num_locks);
|
||||
mutex_unlock(s_flash_lock);
|
||||
}
|
||||
|
||||
#include "console/prompt.h"
|
||||
void command_flash_unprotect(void) {
|
||||
flash_impl_unprotect();
|
||||
prompt_send_response("OK");
|
||||
}
|
83
src/fw/drivers/flash/flash_crc.c
Normal file
83
src/fw/drivers/flash/flash_crc.c
Normal file
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "drivers/flash.h"
|
||||
|
||||
#include "kernel/pbl_malloc.h"
|
||||
#include "system/logging.h"
|
||||
#include "util/crc32.h"
|
||||
#include "util/legacy_checksum.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
static size_t prv_allocate_crc_buffer(void **buffer) {
|
||||
// Try to allocate a big buffer for reading flash data. If we can't,
|
||||
// use a smaller one.
|
||||
unsigned int chunk_size = 1024;
|
||||
*buffer = kernel_malloc(chunk_size);
|
||||
if (!*buffer) {
|
||||
PBL_LOG(LOG_LEVEL_WARNING, "Insufficient memory for a large CRC buffer, going slow");
|
||||
|
||||
chunk_size = 128;
|
||||
*buffer = kernel_malloc_check(chunk_size);
|
||||
}
|
||||
return chunk_size;
|
||||
}
|
||||
|
||||
uint32_t flash_crc32(uint32_t flash_addr, uint32_t num_bytes) {
|
||||
void *buffer;
|
||||
unsigned int chunk_size = prv_allocate_crc_buffer(&buffer);
|
||||
|
||||
uint32_t crc = CRC32_INIT;
|
||||
while (num_bytes > chunk_size) {
|
||||
flash_read_bytes(buffer, flash_addr, chunk_size);
|
||||
crc = crc32(crc, buffer, chunk_size);
|
||||
|
||||
num_bytes -= chunk_size;
|
||||
flash_addr += chunk_size;
|
||||
}
|
||||
|
||||
flash_read_bytes(buffer, flash_addr, num_bytes);
|
||||
crc = crc32(crc, buffer, num_bytes);
|
||||
|
||||
kernel_free(buffer);
|
||||
|
||||
return crc;
|
||||
}
|
||||
|
||||
uint32_t flash_calculate_legacy_defective_checksum(uint32_t flash_addr,
|
||||
uint32_t num_bytes) {
|
||||
void *buffer;
|
||||
unsigned int chunk_size = prv_allocate_crc_buffer(&buffer);
|
||||
|
||||
LegacyChecksum checksum;
|
||||
legacy_defective_checksum_init(&checksum);
|
||||
|
||||
while (num_bytes > chunk_size) {
|
||||
flash_read_bytes(buffer, flash_addr, chunk_size);
|
||||
legacy_defective_checksum_update(&checksum, buffer, chunk_size);
|
||||
|
||||
num_bytes -= chunk_size;
|
||||
flash_addr += chunk_size;
|
||||
}
|
||||
|
||||
flash_read_bytes(buffer, flash_addr, num_bytes);
|
||||
legacy_defective_checksum_update(&checksum, buffer, num_bytes);
|
||||
|
||||
kernel_free(buffer);
|
||||
|
||||
return legacy_defective_checksum_finish(&checksum);
|
||||
}
|
143
src/fw/drivers/flash/flash_erase.c
Normal file
143
src/fw/drivers/flash/flash_erase.c
Normal file
|
@ -0,0 +1,143 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "drivers/flash.h"
|
||||
#include "drivers/flash/flash_internal.h"
|
||||
|
||||
#include "flash_region/flash_region.h"
|
||||
#include "services/common/new_timer/new_timer.h"
|
||||
#include "system/passert.h"
|
||||
#include "util/attributes.h"
|
||||
#include "util/math.h"
|
||||
|
||||
#include "FreeRTOS.h"
|
||||
#include "semphr.h"
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
|
||||
static SemaphoreHandle_t s_erase_mutex = NULL;
|
||||
static struct FlashRegionEraseState {
|
||||
uint32_t next_erase_addr;
|
||||
uint32_t end_addr;
|
||||
FlashOperationCompleteCb on_complete;
|
||||
void *on_complete_context;
|
||||
} s_erase_state;
|
||||
|
||||
static void prv_erase_next_async(void *ignored);
|
||||
|
||||
T_STATIC void prv_lock_erase_mutex(void);
|
||||
T_STATIC void prv_unlock_erase_mutex(void);
|
||||
#if !UNITTEST
|
||||
void flash_erase_init(void) {
|
||||
s_erase_mutex = xSemaphoreCreateBinary();
|
||||
xSemaphoreGive(s_erase_mutex);
|
||||
}
|
||||
|
||||
static void prv_lock_erase_mutex(void) {
|
||||
xSemaphoreTake(s_erase_mutex, portMAX_DELAY);
|
||||
}
|
||||
|
||||
static void prv_unlock_erase_mutex(void) {
|
||||
xSemaphoreGive(s_erase_mutex);
|
||||
}
|
||||
#endif
|
||||
|
||||
static void prv_async_erase_done_cb(void *ignored, status_t result) {
|
||||
if (PASSED(result) &&
|
||||
s_erase_state.next_erase_addr < s_erase_state.end_addr) {
|
||||
// Chain the next erase from a new callback to prevent recursion (and the
|
||||
// potential for a stack overflow) if the flash_erase_sector calls the
|
||||
// completion callback asynchronously.
|
||||
if (!new_timer_add_work_callback(prv_erase_next_async, NULL)) {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Failed to enqueue callback; aborting erase");
|
||||
prv_unlock_erase_mutex();
|
||||
s_erase_state.on_complete(s_erase_state.on_complete_context, E_INTERNAL);
|
||||
}
|
||||
} else {
|
||||
prv_unlock_erase_mutex();
|
||||
s_erase_state.on_complete(s_erase_state.on_complete_context, result);
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_erase_next_async(void *ignored) {
|
||||
uint32_t addr = s_erase_state.next_erase_addr;
|
||||
if ((addr & ~SECTOR_ADDR_MASK) == 0 &&
|
||||
addr + SECTOR_SIZE_BYTES <= s_erase_state.end_addr) {
|
||||
s_erase_state.next_erase_addr += SECTOR_SIZE_BYTES;
|
||||
flash_erase_sector(addr, prv_async_erase_done_cb, NULL);
|
||||
} else {
|
||||
// Fall back to a subsector erase
|
||||
s_erase_state.next_erase_addr += SUBSECTOR_SIZE_BYTES;
|
||||
PBL_ASSERTN(s_erase_state.next_erase_addr <= s_erase_state.end_addr);
|
||||
flash_erase_subsector(addr, prv_async_erase_done_cb, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
void flash_erase_optimal_range(
|
||||
uint32_t min_start, uint32_t max_start, uint32_t min_end, uint32_t max_end,
|
||||
FlashOperationCompleteCb on_complete, void *context) {
|
||||
PBL_ASSERTN(((min_start & (~SUBSECTOR_ADDR_MASK)) == 0) &&
|
||||
((max_end & (~SUBSECTOR_ADDR_MASK)) == 0) &&
|
||||
(min_start <= max_start) &&
|
||||
(max_start <= min_end) &&
|
||||
(min_end <= max_end));
|
||||
|
||||
// We want to erase the sector that starts immediately below max_start but
|
||||
// after min_start. If no sector boundary exists between the two, we need to
|
||||
// start erasing sectors after min_start and backfill with subsector erases.
|
||||
int32_t sector_start = (max_start & SECTOR_ADDR_MASK);
|
||||
int32_t subsector_start = (max_start & SUBSECTOR_ADDR_MASK);
|
||||
if (sector_start < (int32_t) min_start) {
|
||||
sector_start += SECTOR_SIZE_BYTES;
|
||||
}
|
||||
|
||||
// We want to erase ending after min_end but before max_end. If that ends
|
||||
// running past the end of max_end, we need to erase starting with the sector
|
||||
// before and fill in with subsector erases.
|
||||
int32_t sector_end = ((min_end - 1) & SECTOR_ADDR_MASK) + SECTOR_SIZE_BYTES;
|
||||
int32_t subsector_end =
|
||||
((min_end - 1) & SUBSECTOR_ADDR_MASK) + SUBSECTOR_SIZE_BYTES;
|
||||
if (sector_end > (int32_t) max_end) {
|
||||
sector_end -= SECTOR_SIZE_BYTES;
|
||||
}
|
||||
|
||||
int32_t start_addr = MIN(sector_start, subsector_start);
|
||||
int32_t end_addr = MAX(sector_end, subsector_end);
|
||||
|
||||
if (sector_start >= sector_end) {
|
||||
// Can't erase any full sectors; just erase subsectors the whole way.
|
||||
start_addr = subsector_start;
|
||||
end_addr = subsector_end;
|
||||
}
|
||||
|
||||
if (start_addr == end_addr) {
|
||||
// Nothing to do!
|
||||
on_complete(context, S_NO_ACTION_REQUIRED);
|
||||
return;
|
||||
}
|
||||
|
||||
prv_lock_erase_mutex();
|
||||
|
||||
s_erase_state = (struct FlashRegionEraseState) {
|
||||
.next_erase_addr = start_addr,
|
||||
.end_addr = end_addr,
|
||||
.on_complete = on_complete,
|
||||
.on_complete_context = context,
|
||||
};
|
||||
|
||||
prv_erase_next_async(NULL);
|
||||
}
|
322
src/fw/drivers/flash/flash_impl.h
Normal file
322
src/fw/drivers/flash/flash_impl.h
Normal file
|
@ -0,0 +1,322 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "system/status_codes.h"
|
||||
|
||||
//! Flash Low-Level API
|
||||
//!
|
||||
//! Unless otherwise specified, this API is non-reentrant. It is unsafe to
|
||||
//! call a function in one thread while another function is being executed in a
|
||||
//! second thread, and it is unsafe to call these functions from within a
|
||||
//! flash_impl callback.
|
||||
|
||||
typedef uint32_t FlashAddress;
|
||||
|
||||
//! Initialize the low-level flash implementation and hardware into a known
|
||||
//! state where it is ready to accept commands.
|
||||
//!
|
||||
//! This function configures microcontroller peripherals. It should be guarded
|
||||
//! with periph_config_acquire_lock/periph_config_release_lock.
|
||||
//!
|
||||
//! @param coredump_mode True if we need this flash driver to not rely on any other system
|
||||
//! services such as FreeRTOS being available because we're in the middle
|
||||
//! of a core dump. This may result in slower operations.
|
||||
status_t flash_impl_init(bool coredump_mode);
|
||||
|
||||
//! Enable or disable synchronous burst mode, if supported.
|
||||
//!
|
||||
//! Burst mode is disabled whenever \ref flash_impl_init is called.
|
||||
//!
|
||||
//! The result is undefined if this function is called while any other flash
|
||||
//! operation is in progress.
|
||||
status_t flash_impl_set_burst_mode(bool enable);
|
||||
|
||||
//! Return the base address of the sector overlapping the given address.
|
||||
//!
|
||||
//! This function is reentrant.
|
||||
FlashAddress flash_impl_get_sector_base_address(FlashAddress addr);
|
||||
|
||||
//! Return the base address of the subsector overlapping the given address.
|
||||
//!
|
||||
//! This function is reentrant.
|
||||
FlashAddress flash_impl_get_subsector_base_address(FlashAddress addr);
|
||||
|
||||
//! Query the flash hardware for its capacity in bytes.
|
||||
size_t flash_impl_get_capacity(void);
|
||||
|
||||
//! Enter a low-power state.
|
||||
//!
|
||||
//! Once in a low-power mode, all operations may fail until
|
||||
//! \ref flash_impl_exit_low_power_mode is called. This function is idempotent.
|
||||
status_t flash_impl_enter_low_power_mode(void);
|
||||
|
||||
//! Exit a low-power state.
|
||||
//!
|
||||
//! Return the flash to a fully operational mode. This may be a time-intensive
|
||||
//! operation. This function is idempotent.
|
||||
status_t flash_impl_exit_low_power_mode(void);
|
||||
|
||||
//! Read data into a buffer.
|
||||
//!
|
||||
//! The result is undefined if this function is called while a write or erase is
|
||||
//! in progress.
|
||||
status_t flash_impl_read_sync(void *buffer, FlashAddress addr, size_t len);
|
||||
|
||||
//! Initiate a DMA-accelerated flash read.
|
||||
//!
|
||||
//! The caller must ensure that the DMA transfer will not be interfered with
|
||||
//! by any clock changes or stoppages externally. (read: inhibit stop mode)
|
||||
//!
|
||||
//! This function will return immediately once the transfer has begun.
|
||||
//! \ref flash_impl_on_read_dma_complete_from_isr will be called from an
|
||||
//! interrupt context to signal that the transfer has completed. The effect of
|
||||
//! calling flash_impl_read_dma_begin a second time while another DMA transfer
|
||||
//! is currently in progress is undefined.
|
||||
//!
|
||||
//! The result is undefined if this function is called while a write or erase is
|
||||
//! in progress.
|
||||
status_t flash_impl_read_dma_begin(void *buffer, FlashAddress addr,
|
||||
size_t len);
|
||||
|
||||
//! Called from an interrupt context when the DMA read has completed. It is
|
||||
//! guaranteed that the call is made from an interrupt of low enough priority
|
||||
//! that RTOS API calls are safe to use, and that it is a tail-call from the end
|
||||
//! of the implementation's ISR (read: portEND_SWITCHING_ISR is permissible).
|
||||
//!
|
||||
//! @param result S_SUCCESS iff the read completed successfully.
|
||||
extern void flash_impl_on_read_dma_complete_from_isr(status_t result);
|
||||
|
||||
//! If the flash part requires write protection to be explicitly enabled, enable it.
|
||||
void flash_impl_enable_write_protection(void);
|
||||
|
||||
//! Write protect a region of flash. Only one region may be protected at any
|
||||
//! given time.
|
||||
//!
|
||||
//! The result is undefined if this function is called while a write or erase is
|
||||
//! in progress.
|
||||
status_t flash_impl_write_protect(FlashAddress start_sector,
|
||||
FlashAddress end_sector);
|
||||
|
||||
//! Remove write protection.
|
||||
//!
|
||||
//! The result is undefined if this function is called while a write or erase is
|
||||
//! in progress.
|
||||
status_t flash_impl_unprotect(void);
|
||||
|
||||
//! Write a page of bytes to flash.
|
||||
//!
|
||||
//! @param buffer The source buffer.
|
||||
//! @param addr Destination flash address.
|
||||
//! @param len Length to write.
|
||||
//! @return The number of bytes that will be written to flash, assuming that the
|
||||
//! write completes successfully, or a StatusCode error if there was an
|
||||
//! error starting the write operation.
|
||||
//!
|
||||
//! Each call to flash_impl_write_page_begin begins a single flash write
|
||||
//! operation, writing the maximum amount of data supported by the hardware in
|
||||
//! a single operation. Multiple page writes may be required to write a complete
|
||||
//! buffer to flash.
|
||||
//!
|
||||
//! Example usage:
|
||||
//! \code
|
||||
//! while (len) {
|
||||
//! int written = flash_impl_write_page_begin(buffer, addr, len));
|
||||
//! if (written < 0) {
|
||||
//! // Handle error
|
||||
//! }
|
||||
//! status_t status;
|
||||
//! while ((status = flash_impl_get_write_status()) == E_AGAIN) {
|
||||
//! continue;
|
||||
//! }
|
||||
//! if (status != S_SUCCESS) {
|
||||
//! // Handle error
|
||||
//! }
|
||||
//! buffer += written;
|
||||
//! addr += written;
|
||||
//! len -= written;
|
||||
//! }
|
||||
//! \endcode
|
||||
//!
|
||||
//! The result is undefined if this function is called while a read or erase is
|
||||
//! in progress. It is an error to call this function while a write is
|
||||
//! in progress or suspended.
|
||||
int flash_impl_write_page_begin(const void *buffer, FlashAddress addr,
|
||||
size_t len);
|
||||
|
||||
//! Poll the status of a flash page write.
|
||||
//!
|
||||
//! @return S_SUCCESS if the write has succeeded, E_ERROR if the write has
|
||||
//! failed, E_BUSY if the write is still in progress or E_AGAIN if the
|
||||
//! write is suspended.
|
||||
status_t flash_impl_get_write_status(void);
|
||||
|
||||
//! Suspend an in-progress write so that reads and erases are permitted.
|
||||
//!
|
||||
//! @param addr The address passed to the \ref flash_impl_write_page_begin
|
||||
//! call which initiated the write being suspended.
|
||||
status_t flash_impl_write_suspend(FlashAddress addr);
|
||||
|
||||
//! Resume a previously-suspended write.
|
||||
//!
|
||||
//! @param addr The address passed to \ref flash_impl_write_suspend.
|
||||
//!
|
||||
//! The result is undefined if this function is called while a read or write is
|
||||
//! in progress.
|
||||
status_t flash_impl_write_resume(FlashAddress addr);
|
||||
|
||||
//! Erase the subsector which overlaps the given address.
|
||||
//!
|
||||
//! The result is undefined if this function is called while a read or write is
|
||||
//! in progress. It is an error to call this function while an erase is
|
||||
//! suspended.
|
||||
status_t flash_impl_erase_subsector_begin(FlashAddress subsector_addr);
|
||||
|
||||
//! Erase the sector which overlaps the given address.
|
||||
//!
|
||||
//! The result is undefined if this function is called while a read or write is
|
||||
//! in progress. It is an error to call this function while an erase is
|
||||
//! suspended.
|
||||
status_t flash_impl_erase_sector_begin(FlashAddress sector_addr);
|
||||
|
||||
//! Erase the entire flash.
|
||||
//!
|
||||
//! The result is undefined if this function is called while a read or write is
|
||||
//! in progress. It is an error to call this function while an erase is
|
||||
//! suspended.
|
||||
status_t flash_impl_erase_bulk_begin(void);
|
||||
|
||||
//! Poll the status of a flash erase.
|
||||
//!
|
||||
//! @return S_SUCCESS if the erase has succeeded, E_ERROR if the erase has
|
||||
//! failed, E_BUSY if the erase is still in progress or E_AGAIN if the
|
||||
//! erase is suspended.
|
||||
status_t flash_impl_get_erase_status(void);
|
||||
|
||||
//! Returns the typical duration of a subsector erase, in milliseconds.
|
||||
//!
|
||||
//! This function is reentrant.
|
||||
uint32_t flash_impl_get_typical_subsector_erase_duration_ms(void);
|
||||
|
||||
//! Returns the typical duration of a sector erase, in milliseconds.
|
||||
//!
|
||||
//! This function is reentrant.
|
||||
uint32_t flash_impl_get_typical_sector_erase_duration_ms(void);
|
||||
|
||||
//! Suspend an in-progress erase so that reads and writes are permitted.
|
||||
//!
|
||||
//! @param addr The sector address passed to the
|
||||
//! \ref flash_impl_erase_subsector_begin or
|
||||
//! \ref flash_impl_erase_sector_begin call which initiated the erase being
|
||||
//! suspended.
|
||||
//!
|
||||
//! @return S_SUCCESS if the erase has been suspended, S_NO_ACTION_REQUIRED if
|
||||
//! there was no erase in progress at the time, or an error code.
|
||||
status_t flash_impl_erase_suspend(FlashAddress addr);
|
||||
|
||||
//! Resume a previously-suspended erase.
|
||||
//!
|
||||
//! @param addr The address passed to \ref flash_impl_erase_suspend.
|
||||
//!
|
||||
//! The result is undefined if this function is called while a read or write is
|
||||
//! in progress.
|
||||
status_t flash_impl_erase_resume(FlashAddress addr);
|
||||
|
||||
//! Check whether the subsector overlapping the specified address is blank
|
||||
//! (reads as all 1's).
|
||||
//!
|
||||
//! @param addr An address within the subsector being checked.
|
||||
//!
|
||||
//! @return S_TRUE if blank, S_FALSE if any bit in the sector has been
|
||||
//! programmed, or E_BUSY if another flash operation is in progress.
|
||||
//!
|
||||
//! This operation is hardware-accelerated if possible. This operation may not
|
||||
//! be performed if any reads, writes, or erases are in progress or suspended,
|
||||
//! and this operation cannot be suspended once initiated. The result is
|
||||
//! undefined if any other flash operation is initiated or in progress while a
|
||||
//! blank check operation is in progress.
|
||||
//!
|
||||
//! @warning This function may return S_TRUE on a subsector where an erase
|
||||
//! operation was terminated prematurely. While such a subsector may
|
||||
//! read back as blank, data loss may occur and writes may fail if the
|
||||
//! subsector is not erased fully before it is written to.
|
||||
status_t flash_impl_blank_check_subsector(FlashAddress addr);
|
||||
|
||||
//! Check whether the sector overlapping the specified address is blank (reads
|
||||
//! as all 1's).
|
||||
//!
|
||||
//! @param addr An address within the sector being checked.
|
||||
//!
|
||||
//! @return S_TRUE if blank, S_FALSE if any bit in the sector has been
|
||||
//! programmed, or E_BUSY if another flash operation is in progress.
|
||||
//!
|
||||
//! This operation is hardware-accelerated if possible. This operation may not
|
||||
//! be performed if any reads, writes, or erases are in progress or suspended,
|
||||
//! and this operation cannot be suspended once initiated. The result is
|
||||
//! undefined if any other flash operation is initiated or in progress while a
|
||||
//! blank check operation is in progress.
|
||||
//!
|
||||
//! @warning This function may return S_TRUE on a sector where an erase
|
||||
//! operation was terminated prematurely. While such a sector may read
|
||||
//! back as blank, data loss may occur and writes may fail if the
|
||||
//! sector is not erased fully before it is written to.
|
||||
status_t flash_impl_blank_check_sector(FlashAddress addr);
|
||||
|
||||
//! Save the address of an erase in progress to a nonvolatile location. The
|
||||
//! erase address, along with the fact that an erase is in progress, must be
|
||||
//! able to survive a system crash and reboot.
|
||||
//!
|
||||
//! @note Writing this data to the same flash array that is being erased is
|
||||
//! almost certainly a bad idea.
|
||||
//!
|
||||
//! @param is_subsector True if the erase is a subsector.
|
||||
//!
|
||||
//! @param addr The address being erased.
|
||||
//!
|
||||
//! @return S_SUCCESS if the data was successfully stored.
|
||||
status_t flash_impl_set_nvram_erase_status(bool is_subsector,
|
||||
FlashAddress addr);
|
||||
|
||||
//! Save to a nonvolatile location the fact that no erase is in progress.
|
||||
//!
|
||||
//! @return S_SUCCESS if the status was successfully stored.
|
||||
status_t flash_impl_clear_nvram_erase_status(void);
|
||||
|
||||
//! Retrieve the erase status previously set by
|
||||
//! flash_impl_set_nvram_erase_status or flash_impl_clear_nvram_erase_status.
|
||||
//!
|
||||
//! @param [out] is_subsector The value of is_subsector passed to the most
|
||||
//! most recent call to flash_impl_set_nvram_erase_status if the status was
|
||||
//! not subsequently cleared by flash_impl_clear_nvram_erase_status. The
|
||||
//! pointer should not be written to if the erase status was cleared.
|
||||
//!
|
||||
//! @param [out] addr The address passed to the most recent call to
|
||||
//! flash_impl_set_nvram_erase_status if the status was not subsequently
|
||||
//! cleared by flash_impl_clear_nvram_erase_status. The address should not be
|
||||
//! written to if the erase status was cleared.
|
||||
//!
|
||||
//! @return S_TRUE if an erase was in progress; S_FALSE otherwise.
|
||||
status_t flash_impl_get_nvram_erase_status(bool *is_subsector,
|
||||
FlashAddress *addr);
|
||||
|
||||
void flash_impl_use(void);
|
||||
void flash_impl_release(void);
|
||||
void flash_impl_release_many(uint32_t num_locks);
|
19
src/fw/drivers/flash/flash_internal.h
Normal file
19
src/fw/drivers/flash/flash_internal.h
Normal file
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
void flash_erase_init(void);
|
236
src/fw/drivers/flash/micron_n25q/cd_flash_driver.c
Normal file
236
src/fw/drivers/flash/micron_n25q/cd_flash_driver.c
Normal file
|
@ -0,0 +1,236 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "drivers/flash.h"
|
||||
#include "drivers/flash/micron_n25q/flash_private.h"
|
||||
#include "drivers/watchdog.h"
|
||||
#include "flash_region/flash_region.h"
|
||||
#include "kernel/util/delay.h"
|
||||
#include "util/math.h"
|
||||
|
||||
#include "kernel/core_dump_private.h"
|
||||
|
||||
//! We have our own flash driver for coredump support because it must not use
|
||||
//! any FreeRTOS constructs & we want to keep it as simple as possible. In
|
||||
//! addition we want the flexibility to be able to reset the flash driver to
|
||||
//! get it into a working state
|
||||
|
||||
static void prv_flash_start_cmd(void) {
|
||||
GPIO_ResetBits(FLASH_GPIO, FLASH_PIN_SCS);
|
||||
}
|
||||
|
||||
static void prv_flash_end_cmd(void) {
|
||||
GPIO_SetBits(FLASH_GPIO, FLASH_PIN_SCS);
|
||||
|
||||
// 50ns required between SCS going high and low again, so just delay here to be safe
|
||||
delay_us(1);
|
||||
}
|
||||
|
||||
static uint8_t prv_flash_send_and_receive_byte(uint8_t byte) {
|
||||
// Ensure that there are no other write operations in progress
|
||||
while (SPI_I2S_GetFlagStatus(FLASH_SPI, SPI_I2S_FLAG_TXE) == RESET) {
|
||||
continue;
|
||||
}
|
||||
// Send the byte on the SPI bus
|
||||
SPI_I2S_SendData(FLASH_SPI, byte);
|
||||
|
||||
// Wait for the response byte to be received
|
||||
while (SPI_I2S_GetFlagStatus(FLASH_SPI, SPI_I2S_FLAG_RXNE) == RESET) {
|
||||
continue;
|
||||
}
|
||||
// Return the byte
|
||||
return SPI_I2S_ReceiveData(FLASH_SPI);
|
||||
}
|
||||
|
||||
static uint8_t prv_flash_read_next_byte(void) {
|
||||
uint8_t result = prv_flash_send_and_receive_byte(FLASH_CMD_DUMMY);
|
||||
return result;
|
||||
}
|
||||
|
||||
static void prv_flash_wait_for_write_bounded(volatile int cycles_to_wait) {
|
||||
prv_flash_start_cmd();
|
||||
|
||||
prv_flash_send_and_receive_byte(FLASH_CMD_READ_STATUS_REG);
|
||||
|
||||
uint8_t status_register = 0;
|
||||
do {
|
||||
if (cycles_to_wait-- < 1) {
|
||||
break;
|
||||
}
|
||||
status_register = prv_flash_read_next_byte();
|
||||
} while (status_register & 0x1);
|
||||
|
||||
prv_flash_end_cmd();
|
||||
}
|
||||
|
||||
// Init the flash hardware
|
||||
void cd_flash_init(void) {
|
||||
// Enable the SPI clock
|
||||
RCC_APB2PeriphClockCmd(FLASH_SPI_CLOCK, ENABLE);
|
||||
|
||||
// Enable the GPIO clock
|
||||
uint8_t idx = ((((uint32_t)FLASH_GPIO) - AHB1PERIPH_BASE) / 0x0400);
|
||||
SET_BIT(RCC->AHB1ENR, (0x1 << idx));
|
||||
|
||||
// Init the flash hardware
|
||||
flash_hw_init();
|
||||
|
||||
// Make sure we are not in deep sleep
|
||||
prv_flash_start_cmd();
|
||||
prv_flash_send_and_receive_byte(FLASH_CMD_WAKE);
|
||||
prv_flash_end_cmd();
|
||||
|
||||
// See if we can successfully access the flash
|
||||
// TODO: Will we successfully recover if the flash HW was left midstream in a command from
|
||||
// before?
|
||||
prv_flash_wait_for_write_bounded(64000000);
|
||||
prv_flash_start_cmd();
|
||||
prv_flash_send_and_receive_byte(FLASH_CMD_READ_ID);
|
||||
uint32_t manufacturer = prv_flash_read_next_byte();
|
||||
uint32_t type = prv_flash_read_next_byte();
|
||||
uint32_t capacity = prv_flash_read_next_byte();
|
||||
prv_flash_end_cmd();
|
||||
|
||||
// If we can't ready the flash info correctly, bail
|
||||
CD_ASSERTN(manufacturer == 0x20 && type == 0xbb && (capacity >= 0x16));
|
||||
}
|
||||
|
||||
static void prv_flash_write_enable(void) {
|
||||
prv_flash_start_cmd();
|
||||
prv_flash_send_and_receive_byte(FLASH_CMD_WRITE_ENABLE);
|
||||
prv_flash_end_cmd();
|
||||
}
|
||||
|
||||
static void prv_flash_send_24b_address(uint32_t start_addr) {
|
||||
// Ensure the high bits are not set.
|
||||
prv_flash_send_and_receive_byte((start_addr & 0xFF0000) >> 16);
|
||||
prv_flash_send_and_receive_byte((start_addr & 0x00FF00) >> 8);
|
||||
prv_flash_send_and_receive_byte((start_addr & 0x0000FF));
|
||||
}
|
||||
|
||||
static void prv_flash_wait_for_write(void) {
|
||||
prv_flash_start_cmd();
|
||||
|
||||
prv_flash_send_and_receive_byte(FLASH_CMD_READ_STATUS_REG);
|
||||
|
||||
uint8_t status_register = 0;
|
||||
do {
|
||||
status_register = prv_flash_read_next_byte();
|
||||
} while (status_register & 0x1);
|
||||
|
||||
prv_flash_end_cmd();
|
||||
}
|
||||
|
||||
static void prv_flash_write_page(const uint8_t* buffer, uint32_t start_addr, uint16_t buffer_size) {
|
||||
// Ensure that we're not trying to write more data than a single page (256 bytes)
|
||||
CD_ASSERTN(buffer_size <= FLASH_PAGE_SIZE);
|
||||
CD_ASSERTN(buffer_size);
|
||||
|
||||
// Writing a zero-length buffer is a no-op.
|
||||
if (buffer_size < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
prv_flash_write_enable();
|
||||
prv_flash_start_cmd();
|
||||
prv_flash_send_and_receive_byte(FLASH_CMD_PAGE_PROGRAM);
|
||||
prv_flash_send_24b_address(start_addr);
|
||||
|
||||
while (buffer_size--) {
|
||||
prv_flash_send_and_receive_byte(*buffer);
|
||||
buffer++;
|
||||
}
|
||||
prv_flash_end_cmd();
|
||||
prv_flash_wait_for_write();
|
||||
}
|
||||
|
||||
void cd_flash_read_bytes(void* buffer_ptr, uint32_t start_addr, uint32_t buffer_size) {
|
||||
uint8_t* buffer = buffer_ptr;
|
||||
prv_flash_wait_for_write();
|
||||
prv_flash_start_cmd();
|
||||
prv_flash_send_and_receive_byte(FLASH_CMD_READ);
|
||||
prv_flash_send_24b_address(start_addr);
|
||||
|
||||
while (buffer_size--) {
|
||||
*buffer = prv_flash_read_next_byte();
|
||||
buffer++;
|
||||
}
|
||||
prv_flash_end_cmd();
|
||||
}
|
||||
|
||||
uint32_t cd_flash_write_bytes(const void* buffer_ptr, uint32_t start_addr, uint32_t buffer_size) {
|
||||
CD_ASSERTN((start_addr + buffer_size <= CORE_DUMP_FLASH_END) &&
|
||||
(int)start_addr >= CORE_DUMP_FLASH_START);
|
||||
|
||||
const uint8_t* buffer = buffer_ptr;
|
||||
const uint32_t total_bytes = buffer_size;
|
||||
uint32_t first_page_available_bytes = FLASH_PAGE_SIZE - (start_addr % FLASH_PAGE_SIZE);
|
||||
uint32_t bytes_to_write = MIN(buffer_size, first_page_available_bytes);
|
||||
|
||||
while (bytes_to_write) {
|
||||
prv_flash_write_page(buffer, start_addr, bytes_to_write);
|
||||
start_addr += bytes_to_write;
|
||||
buffer += bytes_to_write;
|
||||
buffer_size -= bytes_to_write;
|
||||
bytes_to_write = MIN(buffer_size, FLASH_PAGE_SIZE);
|
||||
}
|
||||
watchdog_feed();
|
||||
return total_bytes;
|
||||
}
|
||||
|
||||
static void prv_flash_erase_sector(uint32_t sector_addr) {
|
||||
prv_flash_write_enable();
|
||||
|
||||
prv_flash_start_cmd();
|
||||
prv_flash_send_and_receive_byte(FLASH_CMD_ERASE_SECTOR);
|
||||
prv_flash_send_24b_address(sector_addr);
|
||||
prv_flash_end_cmd();
|
||||
|
||||
prv_flash_wait_for_write();
|
||||
}
|
||||
|
||||
static void prv_flash_erase_subsector(uint32_t sector_addr) {
|
||||
prv_flash_write_enable();
|
||||
|
||||
prv_flash_start_cmd();
|
||||
prv_flash_send_and_receive_byte(FLASH_CMD_ERASE_SUBSECTOR);
|
||||
prv_flash_send_24b_address(sector_addr);
|
||||
prv_flash_end_cmd();
|
||||
|
||||
prv_flash_wait_for_write();
|
||||
}
|
||||
|
||||
// Erase a region comprised of 1 or more sub-sectors. This will erase sectors at a time if
|
||||
// the address and size allow.
|
||||
void cd_flash_erase_region(uint32_t start_addr, uint32_t total_bytes) {
|
||||
CD_ASSERTN(((start_addr & SUBSECTOR_ADDR_MASK) == start_addr)
|
||||
&& ((total_bytes & SUBSECTOR_ADDR_MASK) == total_bytes));
|
||||
|
||||
while (total_bytes > 0) {
|
||||
if (((start_addr & SECTOR_ADDR_MASK) == start_addr) && (total_bytes >= SECTOR_SIZE_BYTES)) {
|
||||
prv_flash_erase_sector(start_addr);
|
||||
total_bytes -= SECTOR_SIZE_BYTES;
|
||||
start_addr += SECTOR_SIZE_BYTES;
|
||||
} else {
|
||||
prv_flash_erase_subsector(start_addr);
|
||||
total_bytes -= SUBSECTOR_SIZE_BYTES;
|
||||
start_addr += SUBSECTOR_SIZE_BYTES;
|
||||
}
|
||||
watchdog_feed();
|
||||
}
|
||||
}
|
700
src/fw/drivers/flash/micron_n25q/flash.c
Normal file
700
src/fw/drivers/flash/micron_n25q/flash.c
Normal file
|
@ -0,0 +1,700 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "board/board.h"
|
||||
#include "drivers/dma.h"
|
||||
#include "drivers/flash.h"
|
||||
#include "drivers/flash/micron_n25q/flash_private.h"
|
||||
#include "kernel/util/stop.h"
|
||||
#include "process_management/worker_manager.h"
|
||||
#include "services/common/analytics/analytics.h"
|
||||
#include "os/mutex.h"
|
||||
#include "kernel/util/delay.h"
|
||||
#include "system/logging.h"
|
||||
#include "system/passert.h"
|
||||
#include "util/math.h"
|
||||
|
||||
#include "FreeRTOS.h"
|
||||
#include "semphr.h"
|
||||
|
||||
/*
|
||||
* Each peripheral has a dma channel / stream it works with
|
||||
* c.f. section 9.3.3 in stm32 reference manual
|
||||
*/
|
||||
/* RX DMA */
|
||||
static DMA_Stream_TypeDef* FLASH_DMA_STREAM = DMA2_Stream0;
|
||||
static const uint32_t FLASH_DMA_CHANNEL = DMA_Channel_3;
|
||||
static const uint32_t FLASH_DMA_IRQn = DMA2_Stream0_IRQn;
|
||||
static const uint32_t FLASH_DATA_REGISTER_ADDR = (uint32_t)&(SPI1->DR);
|
||||
/* TX DMA */
|
||||
static DMA_Stream_TypeDef* FLASH_TX_DMA_STREAM = DMA2_Stream3;
|
||||
static const uint32_t FLASH_TX_DMA_CHANNEL = DMA_Channel_3;
|
||||
|
||||
static uint32_t analytics_read_count;
|
||||
static uint32_t analytics_read_bytes_count;
|
||||
static uint32_t analytics_write_bytes_count;
|
||||
|
||||
void analytics_external_collect_system_flash_statistics(void) {
|
||||
// TODO: Add support back to tintin
|
||||
}
|
||||
|
||||
void analytics_external_collect_app_flash_read_stats(void) {
|
||||
analytics_set(ANALYTICS_APP_METRIC_FLASH_READ_COUNT, analytics_read_count, AnalyticsClient_App);
|
||||
analytics_set(ANALYTICS_APP_METRIC_FLASH_READ_BYTES_COUNT, analytics_read_bytes_count, AnalyticsClient_App);
|
||||
analytics_set(ANALYTICS_APP_METRIC_FLASH_WRITE_BYTES_COUNT, analytics_write_bytes_count, AnalyticsClient_App);
|
||||
|
||||
// The overhead cost of tracking whether each flash read was due to the foreground
|
||||
// or background app is large, so the best we can do is to attribute to both of them
|
||||
if (worker_manager_get_current_worker_md() != NULL) {
|
||||
analytics_set(ANALYTICS_APP_METRIC_FLASH_READ_COUNT, analytics_read_count, AnalyticsClient_Worker);
|
||||
analytics_set(ANALYTICS_APP_METRIC_FLASH_READ_BYTES_COUNT, analytics_read_bytes_count, AnalyticsClient_Worker);
|
||||
analytics_set(ANALYTICS_APP_METRIC_FLASH_WRITE_BYTES_COUNT, analytics_write_bytes_count, AnalyticsClient_Worker);
|
||||
}
|
||||
|
||||
analytics_read_count = 0;
|
||||
analytics_read_bytes_count = 0;
|
||||
analytics_write_bytes_count = 0;
|
||||
}
|
||||
|
||||
struct FlashState {
|
||||
bool enabled;
|
||||
bool sleep_when_idle;
|
||||
bool deep_sleep;
|
||||
PebbleMutex * mutex;
|
||||
SemaphoreHandle_t dma_semaphore;
|
||||
} s_flash_state;
|
||||
|
||||
static void flash_deep_sleep_enter(void);
|
||||
static void flash_deep_sleep_exit(void);
|
||||
|
||||
|
||||
void assert_usable_state(void) {
|
||||
PBL_ASSERTN(s_flash_state.mutex != 0);
|
||||
}
|
||||
|
||||
static void enable_flash_dma_clock(void) {
|
||||
// TINTINHACK: Rather than update this file to use the new DMA driver, just rely on the fact that
|
||||
// this is the only consumer of DMA2.
|
||||
periph_config_enable(DMA2, RCC_AHB1Periph_DMA2);
|
||||
}
|
||||
|
||||
static void disable_flash_dma_clock(void) {
|
||||
// TINTINHACK: Rather than update this file to use the new DMA driver, just rely on the fact that
|
||||
// this is the only consumer of DMA2.
|
||||
periph_config_disable(DMA2, RCC_AHB1Periph_DMA2);
|
||||
}
|
||||
|
||||
static void setup_dma_read(uint8_t *buffer, int size) {
|
||||
DMA_InitTypeDef dma_config;
|
||||
|
||||
DMA_DeInit(FLASH_DMA_STREAM);
|
||||
DMA_DeInit(FLASH_TX_DMA_STREAM);
|
||||
|
||||
/* RX DMA config */
|
||||
DMA_StructInit(&dma_config);
|
||||
dma_config.DMA_Channel = FLASH_DMA_CHANNEL;
|
||||
dma_config.DMA_DIR = DMA_DIR_PeripheralToMemory;
|
||||
dma_config.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
|
||||
dma_config.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
|
||||
dma_config.DMA_Mode = DMA_Mode_Normal;
|
||||
dma_config.DMA_PeripheralBaseAddr = FLASH_DATA_REGISTER_ADDR;
|
||||
dma_config.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
|
||||
dma_config.DMA_MemoryInc = DMA_MemoryInc_Enable;
|
||||
dma_config.DMA_Priority = DMA_Priority_High;
|
||||
dma_config.DMA_FIFOMode = DMA_FIFOMode_Disable;
|
||||
dma_config.DMA_MemoryBurst = DMA_MemoryBurst_Single;
|
||||
dma_config.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
|
||||
dma_config.DMA_Memory0BaseAddr = (uint32_t)buffer;
|
||||
dma_config.DMA_BufferSize = size;
|
||||
|
||||
DMA_Init(FLASH_DMA_STREAM, &dma_config);
|
||||
|
||||
/* TX DMA config */
|
||||
dma_config.DMA_Channel = FLASH_TX_DMA_CHANNEL;
|
||||
dma_config.DMA_DIR = DMA_DIR_MemoryToPeripheral;
|
||||
dma_config.DMA_PeripheralBaseAddr = FLASH_DATA_REGISTER_ADDR;
|
||||
dma_config.DMA_MemoryInc = DMA_MemoryInc_Disable;
|
||||
dma_config.DMA_Priority = DMA_Priority_High;
|
||||
dma_config.DMA_Memory0BaseAddr = (uint32_t)&FLASH_CMD_DUMMY;
|
||||
dma_config.DMA_BufferSize = size;
|
||||
|
||||
DMA_Init(FLASH_TX_DMA_STREAM, &dma_config);
|
||||
|
||||
/* Setup DMA interrupts */
|
||||
NVIC_InitTypeDef nvic_config;
|
||||
nvic_config.NVIC_IRQChannel = FLASH_DMA_IRQn;
|
||||
nvic_config.NVIC_IRQChannelPreemptionPriority = 0x0f;
|
||||
nvic_config.NVIC_IRQChannelSubPriority = 0x00;
|
||||
nvic_config.NVIC_IRQChannelCmd = ENABLE;
|
||||
NVIC_Init(&nvic_config);
|
||||
|
||||
DMA_ITConfig(FLASH_DMA_STREAM, DMA_IT_TC, ENABLE);
|
||||
|
||||
// enable the DMA stream to start the transfer
|
||||
SPI_I2S_DMACmd(FLASH_SPI, SPI_I2S_DMAReq_Tx | SPI_I2S_DMAReq_Rx, ENABLE);
|
||||
}
|
||||
|
||||
static void do_dma_transfer(void) {
|
||||
xSemaphoreTake(s_flash_state.dma_semaphore, portMAX_DELAY);
|
||||
stop_mode_disable(InhibitorFlash);
|
||||
DMA_Cmd(FLASH_DMA_STREAM, ENABLE);
|
||||
DMA_Cmd(FLASH_TX_DMA_STREAM, ENABLE);
|
||||
xSemaphoreTake(s_flash_state.dma_semaphore, portMAX_DELAY);
|
||||
stop_mode_enable(InhibitorFlash);
|
||||
xSemaphoreGive(s_flash_state.dma_semaphore);
|
||||
}
|
||||
|
||||
void DMA2_Stream0_IRQHandler(void) {
|
||||
if (DMA_GetITStatus(FLASH_DMA_STREAM, DMA_IT_TCIF3)) {
|
||||
DMA_ClearITPendingBit(FLASH_DMA_STREAM, DMA_IT_TCIF3);
|
||||
NVIC_DisableIRQ(FLASH_DMA_IRQn);
|
||||
signed portBASE_TYPE was_higher_priority_task_woken = pdFALSE;
|
||||
xSemaphoreGiveFromISR(s_flash_state.dma_semaphore, &was_higher_priority_task_woken);
|
||||
portEND_SWITCHING_ISR(was_higher_priority_task_woken);
|
||||
return; //notreached
|
||||
}
|
||||
}
|
||||
|
||||
static void flash_deep_sleep_enter(void) {
|
||||
assert_usable_state();
|
||||
|
||||
if (!s_flash_state.deep_sleep) {
|
||||
flash_start_cmd();
|
||||
flash_send_and_receive_byte(FLASH_CMD_DEEP_SLEEP);
|
||||
flash_end_cmd();
|
||||
|
||||
// guarantee we have actually transitioned to deep sleep
|
||||
delay_us(5);
|
||||
s_flash_state.deep_sleep = true;
|
||||
}
|
||||
}
|
||||
|
||||
static void flash_deep_sleep_exit(void) {
|
||||
assert_usable_state();
|
||||
|
||||
if (s_flash_state.deep_sleep) {
|
||||
flash_start_cmd();
|
||||
flash_send_and_receive_byte(FLASH_CMD_WAKE);
|
||||
flash_end_cmd();
|
||||
|
||||
// wait a sufficient amount of time to enter standby mode
|
||||
// It appears violating these timing conditions can lead to
|
||||
// random bit corruptions on flash writes!
|
||||
delay_us(100);
|
||||
s_flash_state.deep_sleep = false;
|
||||
}
|
||||
}
|
||||
|
||||
void handle_sleep_when_idle_begin(void) {
|
||||
if (s_flash_state.sleep_when_idle) {
|
||||
flash_deep_sleep_exit();
|
||||
}
|
||||
}
|
||||
|
||||
void flash_power_down_for_stop_mode(void) {
|
||||
if (s_flash_state.sleep_when_idle) {
|
||||
if (s_flash_state.enabled) {
|
||||
enable_flash_spi_clock();
|
||||
flash_deep_sleep_enter();
|
||||
disable_flash_spi_clock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void flash_power_up_after_stop_mode(void) {
|
||||
// no need here as this platform doesn't support memory-mappable flash
|
||||
}
|
||||
|
||||
uint32_t flash_get_sector_base_address(uint32_t addr) {
|
||||
return addr & ~(SECTOR_SIZE_BYTES - 1);
|
||||
}
|
||||
|
||||
// This simply issues a command to read a specific register
|
||||
static uint8_t prv_flash_get_register(uint8_t command) {
|
||||
flash_start_cmd();
|
||||
flash_send_and_receive_byte(command);
|
||||
uint8_t reg = flash_read_next_byte();
|
||||
flash_end_cmd();
|
||||
return reg;
|
||||
}
|
||||
|
||||
// This will read the flag status register and check it for the SectorLockStatus flag
|
||||
void prv_check_protection_flag() {
|
||||
uint8_t flag_status_register = prv_flash_get_register(FLASH_CMD_READ_FLAG_STATUS_REG);
|
||||
// assert if we found the flag to be enabled
|
||||
PBL_ASSERTN(!(flag_status_register & N25QFlagStatusBit_SectorLockStatus));
|
||||
}
|
||||
|
||||
// This will clear the protection flag error from a previous error.
|
||||
// We call this because the error bits persist across reboots
|
||||
static void prv_clear_flag_status_register(void) {
|
||||
flash_start_cmd();
|
||||
flash_send_and_receive_byte(FLASH_CMD_CLEAR_FLAG_STATUS_REG);
|
||||
flash_end_cmd();
|
||||
}
|
||||
|
||||
/**
|
||||
* Write up to 1 page (256B) of data to flash. start_addr DOES NOT
|
||||
* need to be paged aligned. When writing into the middle of a page
|
||||
* (addr & 0xFFF > 0), overrunning the length of the page will cause
|
||||
* the write to "wrap around" and will modify (i.e. corrupt) data
|
||||
* stored before the starting address within the page.
|
||||
*
|
||||
*/
|
||||
static void flash_write_page(const uint8_t* buffer, uint32_t start_addr, uint16_t buffer_size) {
|
||||
// Ensure that we're not trying to write more data than a single page (256 bytes)
|
||||
PBL_ASSERTN(buffer_size <= FLASH_PAGE_SIZE);
|
||||
PBL_ASSERTN(buffer_size);
|
||||
mutex_assert_held_by_curr_task(s_flash_state.mutex, true /* is_held */);
|
||||
|
||||
// Writing a zero-length buffer is a no-op.
|
||||
if (buffer_size < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
flash_write_enable();
|
||||
|
||||
flash_start_cmd();
|
||||
|
||||
flash_send_and_receive_byte(FLASH_CMD_PAGE_PROGRAM);
|
||||
flash_send_24b_address(start_addr);
|
||||
|
||||
while (buffer_size--) {
|
||||
flash_send_and_receive_byte(*buffer);
|
||||
buffer++;
|
||||
}
|
||||
|
||||
flash_end_cmd();
|
||||
flash_wait_for_write();
|
||||
|
||||
prv_check_protection_flag();
|
||||
}
|
||||
|
||||
// Public interface
|
||||
// From here on down, make sure you're taking the s_flash_state.mutex before doing anything to the SPI peripheral.
|
||||
|
||||
void flash_enable_write_protection(void) {
|
||||
return;
|
||||
}
|
||||
|
||||
void flash_lock(void) {
|
||||
mutex_lock(s_flash_state.mutex);
|
||||
}
|
||||
|
||||
void flash_unlock(void) {
|
||||
mutex_unlock(s_flash_state.mutex);
|
||||
}
|
||||
|
||||
bool flash_is_enabled(void) {
|
||||
return (s_flash_state.enabled);
|
||||
}
|
||||
|
||||
void flash_init(void) {
|
||||
if (s_flash_state.mutex != 0) {
|
||||
return; // Already initialized.
|
||||
}
|
||||
|
||||
s_flash_state.mutex = mutex_create();
|
||||
vSemaphoreCreateBinary(s_flash_state.dma_semaphore);
|
||||
flash_lock();
|
||||
|
||||
enable_flash_spi_clock();
|
||||
|
||||
flash_start();
|
||||
|
||||
s_flash_state.enabled = true;
|
||||
s_flash_state.sleep_when_idle = false;
|
||||
|
||||
// Assume that last time we shut down we were asleep. Come back out.
|
||||
s_flash_state.deep_sleep = true;
|
||||
flash_deep_sleep_exit();
|
||||
|
||||
prv_clear_flag_status_register();
|
||||
|
||||
disable_flash_spi_clock();
|
||||
flash_unlock();
|
||||
|
||||
flash_whoami();
|
||||
|
||||
PBL_LOG_VERBOSE("Detected SPI Flash Size: %u bytes", flash_get_size());
|
||||
}
|
||||
|
||||
void flash_stop(void) {
|
||||
if (s_flash_state.mutex == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
flash_lock();
|
||||
s_flash_state.enabled = false;
|
||||
flash_unlock();
|
||||
}
|
||||
|
||||
void flash_read_bytes(uint8_t* buffer, uint32_t start_addr, uint32_t buffer_size) {
|
||||
if (!buffer_size) {
|
||||
return;
|
||||
}
|
||||
|
||||
assert_usable_state();
|
||||
|
||||
flash_lock();
|
||||
|
||||
if (!s_flash_state.enabled) {
|
||||
flash_unlock();
|
||||
return;
|
||||
}
|
||||
|
||||
analytics_read_count++;
|
||||
analytics_read_bytes_count += buffer_size;
|
||||
power_tracking_start(PowerSystemFlashRead);
|
||||
|
||||
enable_flash_spi_clock();
|
||||
handle_sleep_when_idle_begin();
|
||||
|
||||
flash_wait_for_write();
|
||||
|
||||
flash_start_cmd();
|
||||
|
||||
flash_send_and_receive_byte(FLASH_CMD_READ);
|
||||
flash_send_24b_address(start_addr);
|
||||
|
||||
// There is delay associated with setting up the stm32 dma, using FreeRTOS
|
||||
// sempahores, handling ISRs, etc. Thus for short reads, the cost of using
|
||||
// DMA is far more expensive than the read being performed. Reads greater
|
||||
// than 34 was empirically determined to be the point at which using the DMA
|
||||
// engine is advantageous
|
||||
#if !defined(TARGET_QEMU)
|
||||
const uint32_t num_reads_dma_cutoff = 34;
|
||||
#else
|
||||
// We are disabling DMA reads when running under QEMU for now because they are not reliable.
|
||||
const uint32_t num_reads_dma_cutoff = buffer_size + 1;
|
||||
#endif
|
||||
if (buffer_size < num_reads_dma_cutoff) {
|
||||
while (buffer_size--) {
|
||||
*buffer = flash_read_next_byte();
|
||||
buffer++;
|
||||
}
|
||||
} else {
|
||||
enable_flash_dma_clock();
|
||||
setup_dma_read(buffer, buffer_size);
|
||||
do_dma_transfer();
|
||||
disable_flash_dma_clock();
|
||||
}
|
||||
|
||||
flash_end_cmd();
|
||||
|
||||
disable_flash_spi_clock();
|
||||
|
||||
power_tracking_stop(PowerSystemFlashRead);
|
||||
flash_unlock();
|
||||
}
|
||||
|
||||
void flash_write_bytes(const uint8_t* buffer, uint32_t start_addr, uint32_t buffer_size) {
|
||||
if (!buffer_size) {
|
||||
return;
|
||||
}
|
||||
|
||||
PBL_ASSERTN((start_addr + buffer_size) <= BOARD_NOR_FLASH_SIZE);
|
||||
|
||||
assert_usable_state();
|
||||
|
||||
flash_lock();
|
||||
|
||||
if (!s_flash_state.enabled) {
|
||||
flash_unlock();
|
||||
return;
|
||||
}
|
||||
|
||||
analytics_write_bytes_count += buffer_size;
|
||||
power_tracking_start(PowerSystemFlashWrite);
|
||||
|
||||
enable_flash_spi_clock();
|
||||
handle_sleep_when_idle_begin();
|
||||
|
||||
uint32_t first_page_available_bytes = FLASH_PAGE_SIZE - (start_addr % FLASH_PAGE_SIZE);
|
||||
uint32_t bytes_to_write = MIN(buffer_size, first_page_available_bytes);
|
||||
|
||||
if (first_page_available_bytes < FLASH_PAGE_SIZE) {
|
||||
PBL_LOG_VERBOSE("Address is not page-aligned; first write will be %"PRId32"B at address 0x%"PRIX32,
|
||||
first_page_available_bytes, start_addr);
|
||||
}
|
||||
|
||||
while (bytes_to_write) {
|
||||
flash_write_page(buffer, start_addr, bytes_to_write);
|
||||
|
||||
start_addr += bytes_to_write;
|
||||
buffer += bytes_to_write;
|
||||
buffer_size -= bytes_to_write;
|
||||
bytes_to_write = MIN(buffer_size, FLASH_PAGE_SIZE);
|
||||
}
|
||||
|
||||
disable_flash_spi_clock();
|
||||
|
||||
power_tracking_stop(PowerSystemFlashWrite);
|
||||
flash_unlock();
|
||||
}
|
||||
|
||||
void flash_erase_subsector_blocking(uint32_t subsector_addr) {
|
||||
assert_usable_state();
|
||||
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Erasing subsector 0x%"PRIx32" (0x%"PRIx32" - 0x%"PRIx32")",
|
||||
subsector_addr,
|
||||
subsector_addr & SUBSECTOR_ADDR_MASK,
|
||||
(subsector_addr & SUBSECTOR_ADDR_MASK) + SUBSECTOR_SIZE_BYTES);
|
||||
|
||||
flash_lock();
|
||||
|
||||
if (!s_flash_state.enabled) {
|
||||
flash_unlock();
|
||||
return;
|
||||
}
|
||||
|
||||
analytics_inc(ANALYTICS_APP_METRIC_FLASH_SUBSECTOR_ERASE_COUNT, AnalyticsClient_CurrentTask);
|
||||
power_tracking_start(PowerSystemFlashErase);
|
||||
|
||||
enable_flash_spi_clock();
|
||||
handle_sleep_when_idle_begin();
|
||||
|
||||
flash_write_enable();
|
||||
|
||||
flash_start_cmd();
|
||||
flash_send_and_receive_byte(FLASH_CMD_ERASE_SUBSECTOR);
|
||||
flash_send_24b_address(subsector_addr);
|
||||
flash_end_cmd();
|
||||
|
||||
flash_wait_for_write();
|
||||
|
||||
prv_check_protection_flag();
|
||||
|
||||
disable_flash_spi_clock();
|
||||
|
||||
power_tracking_stop(PowerSystemFlashErase);
|
||||
flash_unlock();
|
||||
}
|
||||
|
||||
void flash_erase_sector_blocking(uint32_t sector_addr) {
|
||||
assert_usable_state();
|
||||
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Erasing sector 0x%"PRIx32" (0x%"PRIx32" - 0x%"PRIx32")",
|
||||
sector_addr,
|
||||
sector_addr & SECTOR_ADDR_MASK,
|
||||
(sector_addr & SECTOR_ADDR_MASK) + SECTOR_SIZE_BYTES);
|
||||
|
||||
if (flash_sector_is_erased(sector_addr)) {
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Sector %#"PRIx32" already erased", sector_addr);
|
||||
return;
|
||||
}
|
||||
|
||||
flash_lock();
|
||||
|
||||
if (!flash_is_enabled()) {
|
||||
flash_unlock();
|
||||
return;
|
||||
}
|
||||
|
||||
power_tracking_start(PowerSystemFlashErase);
|
||||
|
||||
enable_flash_spi_clock();
|
||||
handle_sleep_when_idle_begin();
|
||||
|
||||
flash_write_enable();
|
||||
|
||||
flash_start_cmd();
|
||||
flash_send_and_receive_byte(FLASH_CMD_ERASE_SECTOR);
|
||||
flash_send_24b_address(sector_addr);
|
||||
flash_end_cmd();
|
||||
|
||||
flash_wait_for_write();
|
||||
|
||||
prv_check_protection_flag();
|
||||
|
||||
disable_flash_spi_clock();
|
||||
|
||||
power_tracking_stop(PowerSystemFlashErase);
|
||||
flash_unlock();
|
||||
}
|
||||
|
||||
// It is dangerous to leave this built in by default.
|
||||
#if 0
|
||||
void flash_erase_bulk(void) {
|
||||
assert_usable_state();
|
||||
|
||||
flash_lock();
|
||||
|
||||
if (!s_flash_state.enabled) {
|
||||
flash_unlock();
|
||||
return;
|
||||
}
|
||||
|
||||
flash_prf_set_protection(false);
|
||||
|
||||
power_tracking_start(PowerSystemFlashErase);
|
||||
|
||||
enable_flash_spi_clock();
|
||||
handle_sleep_when_idle_begin();
|
||||
|
||||
flash_write_enable();
|
||||
|
||||
flash_start_cmd();
|
||||
flash_send_and_receive_byte(FLASH_CMD_ERASE_BULK);
|
||||
flash_end_cmd();
|
||||
|
||||
flash_wait_for_write();
|
||||
|
||||
flash_prf_set_protection(true);
|
||||
|
||||
disable_flash_spi_clock();
|
||||
|
||||
power_tracking_stop(PowerSystemFlashErase);
|
||||
flash_unlock();
|
||||
}
|
||||
#endif
|
||||
|
||||
void flash_sleep_when_idle(bool enable) {
|
||||
if (enable == s_flash_state.sleep_when_idle) {
|
||||
return;
|
||||
}
|
||||
|
||||
flash_lock();
|
||||
|
||||
if (!s_flash_state.enabled) {
|
||||
flash_unlock();
|
||||
return;
|
||||
}
|
||||
|
||||
enable_flash_spi_clock();
|
||||
|
||||
s_flash_state.sleep_when_idle = enable;
|
||||
|
||||
if (enable) {
|
||||
if (!s_flash_state.deep_sleep) {
|
||||
flash_deep_sleep_enter();
|
||||
}
|
||||
} else {
|
||||
if (s_flash_state.deep_sleep) {
|
||||
flash_deep_sleep_exit();
|
||||
}
|
||||
}
|
||||
|
||||
disable_flash_spi_clock();
|
||||
flash_unlock();
|
||||
}
|
||||
|
||||
bool flash_get_sleep_when_idle(void) {
|
||||
bool result;
|
||||
flash_lock();
|
||||
result = s_flash_state.deep_sleep;
|
||||
flash_unlock();
|
||||
return result;
|
||||
}
|
||||
|
||||
void debug_flash_dump_registers(void) {
|
||||
#ifdef PBL_LOG_ENABLED
|
||||
flash_lock();
|
||||
|
||||
if (!s_flash_state.enabled) {
|
||||
flash_unlock();
|
||||
return;
|
||||
}
|
||||
|
||||
enable_flash_spi_clock();
|
||||
handle_sleep_when_idle_begin();
|
||||
|
||||
uint8_t status_register = prv_flash_get_register(FLASH_CMD_READ_STATUS_REG);
|
||||
uint8_t lock_register = prv_flash_get_register(FLASH_CMD_READ_LOCK_REGISTER);
|
||||
uint8_t flag_status_register = prv_flash_get_register(FLASH_CMD_READ_FLAG_STATUS_REG);
|
||||
uint8_t nonvolatile_config_register =
|
||||
prv_flash_get_register(FLASH_CMD_READ_NONVOLATILE_CONFIG_REGISTER);
|
||||
uint8_t volatile_config_register =
|
||||
prv_flash_get_register(FLASH_CMD_READ_VOLATILE_CONFIG_REGISTER);
|
||||
|
||||
disable_flash_spi_clock();
|
||||
flash_unlock();
|
||||
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Status Register: 0x%x", status_register);
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Lock Register: 0x%x", lock_register);
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Flag Status Register: 0x%x", flag_status_register);
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Nonvolatile Configuration Register: 0x%x", nonvolatile_config_register);
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Volatile Configuration Register: 0x%x", volatile_config_register);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool flash_is_initialized(void) {
|
||||
return (s_flash_state.mutex != 0);
|
||||
}
|
||||
|
||||
size_t flash_get_size(void) {
|
||||
uint32_t spi_flash_id = flash_whoami();
|
||||
if (!check_whoami(spi_flash_id)) {
|
||||
// Zero bytes is the best size to report if the flash is corrupted
|
||||
return 0;
|
||||
}
|
||||
|
||||
// capcity_megabytes = 2^(capacity in whoami)
|
||||
uint32_t capacity = spi_flash_id & 0x000000FF;
|
||||
// get the capacity of the flash in bytes
|
||||
return 1 << capacity;
|
||||
}
|
||||
|
||||
void flash_prf_set_protection(bool do_protect) {
|
||||
assert_usable_state();
|
||||
|
||||
flash_lock();
|
||||
|
||||
if (!s_flash_state.enabled) {
|
||||
flash_unlock();
|
||||
return;
|
||||
}
|
||||
|
||||
enable_flash_spi_clock();
|
||||
handle_sleep_when_idle_begin();
|
||||
|
||||
flash_write_enable();
|
||||
|
||||
const uint32_t start_addr = FLASH_REGION_SAFE_FIRMWARE_BEGIN;
|
||||
const uint32_t end_addr = FLASH_REGION_SAFE_FIRMWARE_END;
|
||||
const uint8_t lock_bits = do_protect ? N25QLockBit_SectorWriteLock : 0;
|
||||
for (uint32_t addr = start_addr; addr < end_addr; addr += SECTOR_SIZE_BYTES) {
|
||||
flash_start_cmd();
|
||||
flash_send_and_receive_byte(FLASH_CMD_WRITE_LOCK_REGISTER);
|
||||
flash_send_24b_address(addr);
|
||||
flash_send_and_receive_byte(lock_bits);
|
||||
flash_end_cmd();
|
||||
}
|
||||
|
||||
disable_flash_spi_clock();
|
||||
|
||||
flash_unlock();
|
||||
}
|
||||
|
||||
void flash_erase_sector(uint32_t sector_addr,
|
||||
FlashOperationCompleteCb on_complete_cb,
|
||||
void *context) {
|
||||
// TODO: implement nonblocking erase
|
||||
flash_erase_sector_blocking(sector_addr);
|
||||
on_complete_cb(context, S_SUCCESS);
|
||||
}
|
||||
|
||||
void flash_erase_subsector(uint32_t sector_addr,
|
||||
FlashOperationCompleteCb on_complete_cb,
|
||||
void *context) {
|
||||
// TODO: implement nonblocking erase
|
||||
flash_erase_subsector_blocking(sector_addr);
|
||||
on_complete_cb(context, S_SUCCESS);
|
||||
}
|
228
src/fw/drivers/flash/micron_n25q/flash_core.c
Normal file
228
src/fw/drivers/flash/micron_n25q/flash_core.c
Normal file
|
@ -0,0 +1,228 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "drivers/flash.h"
|
||||
#include "kernel/util/delay.h"
|
||||
#include "system/passert.h"
|
||||
#include "util/units.h"
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "drivers/flash/micron_n25q/flash_private.h"
|
||||
|
||||
void enable_flash_spi_clock(void) {
|
||||
periph_config_enable(FLASH_SPI, FLASH_SPI_CLOCK);
|
||||
}
|
||||
|
||||
void disable_flash_spi_clock(void) {
|
||||
periph_config_disable(FLASH_SPI, FLASH_SPI_CLOCK);
|
||||
}
|
||||
|
||||
// IMPORTANT: This method is also used by the core dump logic in order to re-initialize the flash hardware
|
||||
// to prepare for writing the core dump. For this reason, it can NOT use any FreeRTOS functions, mess with
|
||||
// the interrupt priority, primask, etc.
|
||||
void flash_hw_init(void) {
|
||||
// Connect PA5 to SPI1_SCLK
|
||||
GPIO_PinAFConfig(FLASH_GPIO, GPIO_PinSource5, GPIO_AF_SPI1);
|
||||
|
||||
// Connect PA6 to SPI1_MISO
|
||||
GPIO_PinAFConfig(FLASH_GPIO, GPIO_PinSource6, GPIO_AF_SPI1);
|
||||
|
||||
// Connect PA7 to SPI1_MOSI
|
||||
GPIO_PinAFConfig(FLASH_GPIO, GPIO_PinSource7, GPIO_AF_SPI1);
|
||||
|
||||
GPIO_InitTypeDef gpio_cfg;
|
||||
gpio_cfg.GPIO_OType = GPIO_OType_PP;
|
||||
gpio_cfg.GPIO_PuPd = GPIO_PuPd_NOPULL;
|
||||
gpio_cfg.GPIO_Mode = GPIO_Mode_AF;
|
||||
gpio_cfg.GPIO_Speed = GPIO_Speed_50MHz;
|
||||
gpio_cfg.GPIO_Pin = FLASH_PIN_MISO | FLASH_PIN_MOSI;
|
||||
GPIO_Init(FLASH_GPIO, &gpio_cfg);
|
||||
|
||||
// Configure the SCLK pin to have a weak pull-down to put it in a known state
|
||||
// when SCS is toggled
|
||||
gpio_cfg.GPIO_PuPd = GPIO_PuPd_DOWN;
|
||||
gpio_cfg.GPIO_Pin = FLASH_PIN_SCLK;
|
||||
GPIO_Init(FLASH_GPIO, &gpio_cfg);
|
||||
|
||||
// Configure SCS to be controlled in software; pull up to high when inactive
|
||||
gpio_cfg.GPIO_Mode = GPIO_Mode_OUT;
|
||||
gpio_cfg.GPIO_Pin = FLASH_PIN_SCS;
|
||||
gpio_cfg.GPIO_PuPd = GPIO_PuPd_UP;
|
||||
GPIO_Init(FLASH_GPIO, &gpio_cfg);
|
||||
|
||||
// Set up a SPI bus on SPI1
|
||||
SPI_InitTypeDef spi_cfg;
|
||||
SPI_I2S_DeInit(FLASH_SPI);
|
||||
spi_cfg.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
|
||||
spi_cfg.SPI_Mode = SPI_Mode_Master;
|
||||
spi_cfg.SPI_DataSize = SPI_DataSize_8b;
|
||||
spi_cfg.SPI_CPOL = SPI_CPOL_Low;
|
||||
spi_cfg.SPI_CPHA = SPI_CPHA_1Edge;
|
||||
spi_cfg.SPI_NSS = SPI_NSS_Soft;
|
||||
spi_cfg.SPI_BaudRatePrescaler = spi_find_prescaler(MHZ_TO_HZ(54), FLASH_SPI_CLOCK_PERIPH); // max read freq for the flash
|
||||
spi_cfg.SPI_FirstBit = SPI_FirstBit_MSB;
|
||||
spi_cfg.SPI_CRCPolynomial = 7;
|
||||
SPI_Init(FLASH_SPI, &spi_cfg);
|
||||
|
||||
SPI_Cmd(FLASH_SPI, ENABLE);
|
||||
}
|
||||
|
||||
void flash_start(void) {
|
||||
periph_config_acquire_lock();
|
||||
gpio_use(FLASH_GPIO);
|
||||
|
||||
// Init the hardware
|
||||
flash_hw_init();
|
||||
|
||||
gpio_release(FLASH_GPIO);
|
||||
periph_config_release_lock();
|
||||
}
|
||||
|
||||
void flash_start_cmd(void) {
|
||||
gpio_use(FLASH_GPIO);
|
||||
GPIO_ResetBits(FLASH_GPIO, FLASH_PIN_SCS);
|
||||
gpio_release(FLASH_GPIO);
|
||||
}
|
||||
|
||||
void flash_end_cmd(void) {
|
||||
gpio_use(FLASH_GPIO);
|
||||
GPIO_SetBits(FLASH_GPIO, FLASH_PIN_SCS);
|
||||
gpio_release(FLASH_GPIO);
|
||||
|
||||
// 50ns required between SCS going high and low again, so just delay here to be safe
|
||||
delay_us(1);
|
||||
}
|
||||
|
||||
uint8_t flash_send_and_receive_byte(uint8_t byte) {
|
||||
// Ensure that there are no other write operations in progress
|
||||
while (SPI_I2S_GetFlagStatus(FLASH_SPI, SPI_I2S_FLAG_TXE) == RESET);
|
||||
// Send the byte on the SPI bus
|
||||
SPI_I2S_SendData(FLASH_SPI, byte);
|
||||
|
||||
// Wait for the response byte to be received
|
||||
while (SPI_I2S_GetFlagStatus(FLASH_SPI, SPI_I2S_FLAG_RXNE) == RESET);
|
||||
// Return the byte
|
||||
return SPI_I2S_ReceiveData(FLASH_SPI);
|
||||
}
|
||||
|
||||
void flash_write_enable(void) {
|
||||
flash_start_cmd();
|
||||
flash_send_and_receive_byte(FLASH_CMD_WRITE_ENABLE);
|
||||
flash_end_cmd();
|
||||
}
|
||||
|
||||
void flash_send_24b_address(uint32_t start_addr) {
|
||||
// Ensure the high bits are not set.
|
||||
PBL_ASSERTN(!(start_addr & 0xFF000000));
|
||||
|
||||
flash_send_and_receive_byte((start_addr & 0xFF0000) >> 16);
|
||||
flash_send_and_receive_byte((start_addr & 0x00FF00) >> 8);
|
||||
flash_send_and_receive_byte((start_addr & 0x0000FF));
|
||||
}
|
||||
|
||||
uint8_t flash_read_next_byte(void) {
|
||||
uint8_t result = flash_send_and_receive_byte(FLASH_CMD_DUMMY);
|
||||
return result;
|
||||
}
|
||||
|
||||
void flash_wait_for_write_bounded(volatile int cycles_to_wait) {
|
||||
flash_start_cmd();
|
||||
|
||||
flash_send_and_receive_byte(FLASH_CMD_READ_STATUS_REG);
|
||||
|
||||
uint8_t status_register = 0;
|
||||
do {
|
||||
if (cycles_to_wait-- < 1) {
|
||||
break;
|
||||
}
|
||||
status_register = flash_read_next_byte();
|
||||
} while (status_register & N25QStatusBit_WriteInProgress);
|
||||
|
||||
flash_end_cmd();
|
||||
}
|
||||
|
||||
void flash_wait_for_write(void) {
|
||||
flash_start_cmd();
|
||||
|
||||
flash_send_and_receive_byte(FLASH_CMD_READ_STATUS_REG);
|
||||
|
||||
uint8_t status_register = 0;
|
||||
do {
|
||||
status_register = flash_read_next_byte();
|
||||
} while (status_register & N25QStatusBit_WriteInProgress);
|
||||
|
||||
flash_end_cmd();
|
||||
}
|
||||
|
||||
bool flash_sector_is_erased(uint32_t sector_addr) {
|
||||
const uint32_t bufsize = 128;
|
||||
uint8_t buffer[bufsize];
|
||||
sector_addr &= SECTOR_ADDR_MASK;
|
||||
for (uint32_t offset = 0; offset < SECTOR_SIZE_BYTES; offset += bufsize) {
|
||||
flash_read_bytes(buffer, sector_addr + offset, bufsize);
|
||||
for (uint32_t i = 0; i < bufsize; i++) {
|
||||
if (buffer[i] != 0xff) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t flash_whoami(void) {
|
||||
assert_usable_state();
|
||||
|
||||
flash_lock();
|
||||
|
||||
if (!flash_is_enabled()) {
|
||||
flash_unlock();
|
||||
return 0;
|
||||
}
|
||||
|
||||
enable_flash_spi_clock();
|
||||
handle_sleep_when_idle_begin();
|
||||
|
||||
flash_wait_for_write_bounded(64000000);
|
||||
|
||||
flash_start_cmd();
|
||||
flash_send_and_receive_byte(FLASH_CMD_READ_ID);
|
||||
uint32_t manufacturer = flash_read_next_byte();
|
||||
uint32_t type = flash_read_next_byte();
|
||||
uint32_t capacity = flash_read_next_byte();
|
||||
flash_end_cmd();
|
||||
|
||||
disable_flash_spi_clock();
|
||||
flash_unlock();
|
||||
|
||||
return ((manufacturer << 16) | (type << 8) | capacity);
|
||||
}
|
||||
|
||||
bool check_whoami(uint32_t spi_flash_id) {
|
||||
return spi_flash_id == EXPECTED_SPI_FLASH_ID_32MBIT ||
|
||||
spi_flash_id == EXPECTED_SPI_FLASH_ID_64MBIT;
|
||||
}
|
||||
|
||||
bool flash_is_whoami_correct(void) {
|
||||
uint32_t spi_flash_id = flash_whoami();
|
||||
return check_whoami(spi_flash_id);
|
||||
}
|
||||
|
||||
void flash_switch_mode(FlashModeType mode) {
|
||||
}
|
||||
|
115
src/fw/drivers/flash/micron_n25q/flash_private.h
Normal file
115
src/fw/drivers/flash/micron_n25q/flash_private.h
Normal file
|
@ -0,0 +1,115 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "board/board.h"
|
||||
#include "drivers/gpio.h"
|
||||
#include "drivers/periph_config.h"
|
||||
#include "drivers/spi.h"
|
||||
#include "flash_region/flash_region.h"
|
||||
#include "system/passert.h"
|
||||
#include "system/logging.h"
|
||||
|
||||
#define STM32F2_COMPATIBLE
|
||||
#define STM32F4_COMPATIBLE
|
||||
#include <mcu.h>
|
||||
|
||||
#include "debug/power_tracking.h"
|
||||
|
||||
/* GPIO */
|
||||
static GPIO_TypeDef* const FLASH_GPIO = GPIOA;
|
||||
/* SPI */
|
||||
static SPI_TypeDef* const FLASH_SPI = SPI1;
|
||||
static const uint32_t FLASH_SPI_CLOCK = RCC_APB2Periph_SPI1;
|
||||
static const SpiPeriphClock FLASH_SPI_CLOCK_PERIPH = SpiPeriphClockAPB2;
|
||||
|
||||
|
||||
/* Pin Defintions */
|
||||
static const uint32_t FLASH_PIN_SCS = GPIO_Pin_4;
|
||||
static const uint32_t FLASH_PIN_SCLK = GPIO_Pin_5;
|
||||
static const uint32_t FLASH_PIN_MISO = GPIO_Pin_6;
|
||||
static const uint32_t FLASH_PIN_MOSI = GPIO_Pin_7;
|
||||
|
||||
/* Flash SPI commands */
|
||||
static const uint8_t FLASH_CMD_WRITE_ENABLE = 0x06;
|
||||
static const uint8_t FLASH_CMD_WRITE_DISABLE = 0x04;
|
||||
static const uint8_t FLASH_CMD_READ_STATUS_REG = 0x05;
|
||||
static const uint8_t FLASH_CMD_READ_FLAG_STATUS_REG = 0x70;
|
||||
static const uint8_t FLASH_CMD_CLEAR_FLAG_STATUS_REG = 0x50;
|
||||
static const uint8_t FLASH_CMD_READ = 0x03;
|
||||
static const uint8_t FLASH_CMD_READ_ID = 0x9F;
|
||||
static const uint8_t FLASH_CMD_PAGE_PROGRAM = 0x02;
|
||||
static const uint8_t FLASH_CMD_ERASE_SUBSECTOR = 0x20;
|
||||
static const uint8_t FLASH_CMD_ERASE_SECTOR = 0xD8;
|
||||
static const uint8_t FLASH_CMD_ERASE_BULK = 0xC7;
|
||||
static const uint8_t FLASH_CMD_DEEP_SLEEP = 0xB9;
|
||||
static const uint8_t FLASH_CMD_WAKE = 0xAB;
|
||||
static const uint8_t FLASH_CMD_DUMMY = 0xA9;
|
||||
static const uint8_t FLASH_CMD_WRITE_LOCK_REGISTER = 0xE5;
|
||||
static const uint8_t FLASH_CMD_READ_LOCK_REGISTER = 0xE8;
|
||||
static const uint8_t FLASH_CMD_READ_NONVOLATILE_CONFIG_REGISTER = 0xB5;
|
||||
static const uint8_t FLASH_CMD_READ_VOLATILE_CONFIG_REGISTER = 0x85;
|
||||
|
||||
static const uint16_t FLASH_PAGE_SIZE = 0x100;
|
||||
|
||||
typedef enum N25QFlagStatusBit {
|
||||
// Bit 0 is reserved
|
||||
N25QFlagStatusBit_SectorLockStatus = (1 << 1),
|
||||
N25QFlagStatusBit_ProgramSuspended = (1 << 2),
|
||||
N25QFlagStatusBit_VppStatus = (1 << 3),
|
||||
N25QFlagStatusBit_ProgramStatus = (1 << 4),
|
||||
N25QFlagStatusBit_EraseStatus = (1 << 5),
|
||||
N25QFlagStatusBit_EraseSuspended = (1 << 6),
|
||||
N25QFlagStatusBit_DeviceReady = (1 << 7),
|
||||
} N25QFlagStatusBit;
|
||||
|
||||
typedef enum N25QStatusBit {
|
||||
N25QStatusBit_WriteInProgress = (1 << 0),
|
||||
N25QStatusBit_WriteEnableLatch = (1 << 1),
|
||||
N25QStatusBit_BlockProtect0 = (1 << 2),
|
||||
N25QStatusBit_BlockProtect1 = (1 << 3),
|
||||
N25QStatusBit_BlockProtect2 = (1 << 4),
|
||||
N25QStatusBit_ProtectTopBottom = (1 << 5),
|
||||
// Bit 6 is reserved
|
||||
N25QStatusBit_StatusRegisterWrite = (1 << 7),
|
||||
} N25QStatusBit;
|
||||
|
||||
typedef enum N25QLockBit {
|
||||
N25QLockBit_SectorWriteLock = (1 << 0),
|
||||
N25QLockBit_SectorLockDown = (1 << 1),
|
||||
// Bits 2-7 are reserved
|
||||
} N25QLockBit;
|
||||
|
||||
// Method shared with flash.c and the core dump logic in core_dump.c
|
||||
void flash_hw_init(void);
|
||||
void assert_usable_state(void);
|
||||
void flash_lock(void);
|
||||
void flash_unlock(void);
|
||||
bool flash_is_enabled(void);
|
||||
void handle_sleep_when_idle_begin(void);
|
||||
void enable_flash_spi_clock(void);
|
||||
void disable_flash_spi_clock(void);
|
||||
void flash_start(void);
|
||||
void flash_start_cmd(void);
|
||||
void flash_end_cmd(void);
|
||||
uint8_t flash_send_and_receive_byte(uint8_t byte);
|
||||
void flash_write_enable(void);
|
||||
void flash_send_24b_address(uint32_t start_addr);
|
||||
uint8_t flash_read_next_byte(void);
|
||||
void flash_wait_for_write_bounded(volatile int cycles_to_wait);
|
||||
void flash_wait_for_write(void);
|
||||
bool check_whoami(uint32_t spi_flash_id);
|
||||
bool flash_is_whoami_correct(void);
|
210
src/fw/drivers/flash/mt25q.c
Normal file
210
src/fw/drivers/flash/mt25q.c
Normal file
|
@ -0,0 +1,210 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "board/board.h"
|
||||
#include "drivers/flash/flash_impl.h"
|
||||
#include "drivers/flash/qspi_flash.h"
|
||||
#include "drivers/flash/qspi_flash_part_definitions.h"
|
||||
#include "flash_region/flash_region.h"
|
||||
#include "system/passert.h"
|
||||
#include "system/status_codes.h"
|
||||
#include "util/math.h"
|
||||
|
||||
#define STM32F4_COMPATIBLE
|
||||
#define STM32F7_COMPATIBLE
|
||||
#include <mcu.h>
|
||||
|
||||
|
||||
static QSPIFlashPart QSPI_FLASH_PART = {
|
||||
.instructions = {
|
||||
.fast_read = 0x0B,
|
||||
.fast_read_ddr = 0x0D,
|
||||
.page_program = 0x02,
|
||||
.erase_sector_4k = 0x20,
|
||||
.erase_block_64k = 0xD8,
|
||||
.write_enable = 0x06,
|
||||
.write_disable = 0x04,
|
||||
.read_status = 0x05,
|
||||
.read_flag_status = 0x70,
|
||||
.erase_suspend = 0x75,
|
||||
.erase_resume = 0x7A,
|
||||
.enter_low_power = 0xB9,
|
||||
.exit_low_power = 0xAB,
|
||||
.enter_quad_mode = 0x35,
|
||||
.reset_enable = 0x66,
|
||||
.reset = 0x99,
|
||||
.qspi_id = 0xAF,
|
||||
|
||||
.block_lock = 0xE5,
|
||||
.block_lock_status = 0xE8,
|
||||
},
|
||||
.status_bit_masks = {
|
||||
.busy = 1 << 0,
|
||||
.write_enable = 1 << 1,
|
||||
},
|
||||
.flag_status_bit_masks = {
|
||||
.erase_suspend = 1 << 6,
|
||||
},
|
||||
.dummy_cycles = {
|
||||
.fast_read = 10,
|
||||
.fast_read_ddr = 8,
|
||||
},
|
||||
.block_lock = {
|
||||
.has_lock_data = true,
|
||||
.lock_data = 0x1,
|
||||
.locked_check = 0x1,
|
||||
},
|
||||
.reset_latency_ms = 51,
|
||||
.suspend_to_read_latency_us = 0,
|
||||
.standby_to_low_power_latency_us = 3,
|
||||
.low_power_to_standby_latency_us = 30,
|
||||
.supports_fast_read_ddr = true,
|
||||
#if BOARD_ROBERT_BB || BOARD_ROBERT_BB2 || BOARD_CUTTS_BB
|
||||
.qspi_id_value = 0x19BB20,
|
||||
.name = "MT25Q256",
|
||||
#elif BOARD_ROBERT_EVT
|
||||
.qspi_id_value = 0x18BB20,
|
||||
.name = "MT25Q128",
|
||||
#else
|
||||
#error "Unsupported board"
|
||||
#endif
|
||||
};
|
||||
|
||||
bool flash_check_whoami(void) {
|
||||
return qspi_flash_check_whoami(QSPI_FLASH);
|
||||
}
|
||||
|
||||
FlashAddress flash_impl_get_sector_base_address(FlashAddress addr) {
|
||||
return (addr & SECTOR_ADDR_MASK);
|
||||
}
|
||||
|
||||
FlashAddress flash_impl_get_subsector_base_address(FlashAddress addr) {
|
||||
return (addr & SUBSECTOR_ADDR_MASK);
|
||||
}
|
||||
|
||||
void flash_impl_enable_write_protection(void) {
|
||||
}
|
||||
|
||||
status_t flash_impl_write_protect(FlashAddress start_sector, FlashAddress end_sector) {
|
||||
FlashAddress block_addr = start_sector;
|
||||
while (block_addr <= end_sector) {
|
||||
uint32_t block_size;
|
||||
if (WITHIN(block_addr, SECTOR_SIZE_BYTES, BOARD_NOR_FLASH_SIZE - SECTOR_SIZE_BYTES - 1)) {
|
||||
// Middle of flash has 64k lock units
|
||||
block_addr = flash_impl_get_sector_base_address(block_addr);
|
||||
block_size = SECTOR_SIZE_BYTES;
|
||||
} else {
|
||||
// Start and end of flash have 1 sector of 4k lock units
|
||||
block_addr = flash_impl_get_subsector_base_address(block_addr);
|
||||
block_size = SUBSECTOR_SIZE_BYTES;
|
||||
}
|
||||
const status_t sc = qspi_flash_lock_sector(QSPI_FLASH, block_addr);
|
||||
if (FAILED(sc)) {
|
||||
return sc;
|
||||
}
|
||||
block_addr += block_size;
|
||||
}
|
||||
|
||||
return S_SUCCESS;
|
||||
}
|
||||
|
||||
status_t flash_impl_unprotect(void) {
|
||||
// No way to unprotect all of flash. This requires a full reset of the mt25q
|
||||
qspi_flash_init(QSPI_FLASH, &QSPI_FLASH_PART, qspi_flash_is_in_coredump_mode(QSPI_FLASH));
|
||||
return S_SUCCESS;
|
||||
}
|
||||
|
||||
static void prv_set_high_drive_strength(void) {
|
||||
// Match the impedance of the traces (~50 ohms) by configuring the drive strength of the data
|
||||
// output pins on the MT25Q to the 45 ohm setting This avoids signal integreity issues. This is
|
||||
// done by setting bits 2:0 in the "Enhanced Volatile Configuration Register" to 101b.
|
||||
const uint8_t read_instruction = 0x65;
|
||||
const uint8_t write_instruction = 0x61;
|
||||
const uint8_t mask = 0x7;
|
||||
const uint8_t value = 0x5;
|
||||
qspi_flash_ll_set_register_bits(QSPI_FLASH, read_instruction, write_instruction, value, mask);
|
||||
}
|
||||
|
||||
status_t flash_impl_init(bool coredump_mode) {
|
||||
qspi_flash_init(QSPI_FLASH, &QSPI_FLASH_PART, coredump_mode);
|
||||
#if BOARD_CUTTS_BB || BOARD_ROBERT_BB || BOARD_ROBERT_EVT
|
||||
prv_set_high_drive_strength();
|
||||
#endif
|
||||
return S_SUCCESS;
|
||||
}
|
||||
|
||||
status_t flash_impl_get_erase_status(void) {
|
||||
return qspi_flash_is_erase_complete(QSPI_FLASH);
|
||||
}
|
||||
|
||||
status_t flash_impl_erase_subsector_begin(FlashAddress subsector_addr) {
|
||||
return qspi_flash_erase_begin(QSPI_FLASH, subsector_addr, true /* is_subsector */);
|
||||
}
|
||||
status_t flash_impl_erase_sector_begin(FlashAddress sector_addr) {
|
||||
return qspi_flash_erase_begin(QSPI_FLASH, sector_addr, false /* !is_subsector */);
|
||||
}
|
||||
|
||||
status_t flash_impl_erase_suspend(FlashAddress sector_addr) {
|
||||
return qspi_flash_erase_suspend(QSPI_FLASH, sector_addr);
|
||||
}
|
||||
|
||||
status_t flash_impl_erase_resume(FlashAddress sector_addr) {
|
||||
qspi_flash_erase_resume(QSPI_FLASH, sector_addr);
|
||||
return S_SUCCESS;
|
||||
}
|
||||
|
||||
status_t flash_impl_read_sync(void *buffer_ptr, FlashAddress start_addr, size_t buffer_size) {
|
||||
PBL_ASSERT(buffer_size > 0, "flash_impl_read_sync() called with 0 bytes to read");
|
||||
qspi_flash_read_blocking(QSPI_FLASH, start_addr, buffer_ptr, buffer_size);
|
||||
return S_SUCCESS;
|
||||
}
|
||||
|
||||
int flash_impl_write_page_begin(const void *buffer, const FlashAddress start_addr, size_t len) {
|
||||
return qspi_flash_write_page_begin(QSPI_FLASH, buffer, start_addr, len);
|
||||
}
|
||||
|
||||
status_t flash_impl_get_write_status(void) {
|
||||
return qspi_flash_get_write_status(QSPI_FLASH);
|
||||
}
|
||||
|
||||
status_t flash_impl_enter_low_power_mode(void) {
|
||||
qspi_flash_set_lower_power_mode(QSPI_FLASH, true);
|
||||
return S_SUCCESS;
|
||||
}
|
||||
status_t flash_impl_exit_low_power_mode(void) {
|
||||
qspi_flash_set_lower_power_mode(QSPI_FLASH, false);
|
||||
return S_SUCCESS;
|
||||
}
|
||||
|
||||
status_t flash_impl_set_burst_mode(bool burst_mode) {
|
||||
// NYI
|
||||
return S_SUCCESS;
|
||||
}
|
||||
|
||||
status_t flash_impl_blank_check_sector(FlashAddress addr) {
|
||||
return qspi_flash_blank_check(QSPI_FLASH, addr, false /* !is_subsector */);
|
||||
}
|
||||
status_t flash_impl_blank_check_subsector(FlashAddress addr) {
|
||||
return qspi_flash_blank_check(QSPI_FLASH, addr, true /* is_subsector */);
|
||||
}
|
||||
|
||||
uint32_t flash_impl_get_typical_sector_erase_duration_ms(void) {
|
||||
return 150;
|
||||
}
|
||||
|
||||
uint32_t flash_impl_get_typical_subsector_erase_duration_ms(void) {
|
||||
return 50;
|
||||
}
|
223
src/fw/drivers/flash/mx25u.c
Normal file
223
src/fw/drivers/flash/mx25u.c
Normal file
|
@ -0,0 +1,223 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "board/board.h"
|
||||
#include "drivers/flash/flash_impl.h"
|
||||
#include "drivers/flash/qspi_flash.h"
|
||||
#include "drivers/flash/qspi_flash_part_definitions.h"
|
||||
#include "flash_region/flash_region.h"
|
||||
#include "system/passert.h"
|
||||
#include "system/status_codes.h"
|
||||
#include "system/version.h"
|
||||
#include "util/math.h"
|
||||
|
||||
#define STM32F4_COMPATIBLE
|
||||
#define STM32F7_COMPATIBLE
|
||||
#include <mcu.h>
|
||||
|
||||
static QSPIFlashPart QSPI_FLASH_PART = {
|
||||
.instructions = {
|
||||
.fast_read = 0x0B,
|
||||
.page_program = 0x02,
|
||||
.erase_sector_4k = 0x20,
|
||||
.erase_block_64k = 0xD8,
|
||||
.write_enable = 0x06,
|
||||
.write_disable = 0x04,
|
||||
.read_status = 0x05,
|
||||
.read_flag_status = 0x2B,
|
||||
.erase_suspend = 0xB0,
|
||||
.erase_resume = 0x30,
|
||||
.enter_low_power = 0xB9,
|
||||
.exit_low_power = 0xAB,
|
||||
.enter_quad_mode = 0x35,
|
||||
.reset_enable = 0x66,
|
||||
.reset = 0x99,
|
||||
.qspi_id = 0xAF,
|
||||
|
||||
.block_lock = 0x36,
|
||||
.block_lock_status = 0x3C,
|
||||
.block_unlock_all = 0x98,
|
||||
|
||||
.write_protection_enable = 0x68,
|
||||
.read_protection_status = 0x2B,
|
||||
},
|
||||
.status_bit_masks = {
|
||||
.busy = 1 << 0,
|
||||
.write_enable = 1 << 1,
|
||||
},
|
||||
.flag_status_bit_masks = {
|
||||
.erase_suspend = 1 << 3,
|
||||
},
|
||||
.dummy_cycles = {
|
||||
.fast_read = 4,
|
||||
},
|
||||
.block_lock = {
|
||||
.has_lock_data = false,
|
||||
.locked_check = 0xff,
|
||||
|
||||
.protection_enabled_mask = (1 << 7),
|
||||
},
|
||||
.reset_latency_ms = 13,
|
||||
.suspend_to_read_latency_us = 20,
|
||||
.standby_to_low_power_latency_us = 10,
|
||||
.low_power_to_standby_latency_us = 30,
|
||||
.supports_fast_read_ddr = false,
|
||||
.qspi_id_value = 0x3725c2,
|
||||
.name = "MX25U64",
|
||||
};
|
||||
|
||||
//! Any PRF built after this timestamp supports mx25u flash protection
|
||||
#define MIN_PRF_TIMESTAMP_SUPPORTING_PROTECTION (1466531458)
|
||||
|
||||
//! True if the installed PRF version supports flash protection
|
||||
static bool s_flash_protection_supported = false;
|
||||
|
||||
bool flash_check_whoami(void) {
|
||||
return qspi_flash_check_whoami(QSPI_FLASH);
|
||||
}
|
||||
|
||||
FlashAddress flash_impl_get_sector_base_address(FlashAddress addr) {
|
||||
return (addr & SECTOR_ADDR_MASK);
|
||||
}
|
||||
|
||||
FlashAddress flash_impl_get_subsector_base_address(FlashAddress addr) {
|
||||
return (addr & SUBSECTOR_ADDR_MASK);
|
||||
}
|
||||
|
||||
static bool prv_prf_supports_flash_protection(void) {
|
||||
#if IS_BIGBOARD
|
||||
// Bigboards should always exercise flash protection
|
||||
return true;
|
||||
#else
|
||||
FirmwareMetadata prf;
|
||||
if (!version_copy_recovery_fw_metadata(&prf)) {
|
||||
return false;
|
||||
}
|
||||
return (prf.version_timestamp > MIN_PRF_TIMESTAMP_SUPPORTING_PROTECTION);
|
||||
#endif
|
||||
}
|
||||
|
||||
void flash_impl_enable_write_protection(void) {
|
||||
s_flash_protection_supported = prv_prf_supports_flash_protection();
|
||||
|
||||
if (s_flash_protection_supported) {
|
||||
// Ensure that write protection is enabled on the mx25u
|
||||
if (qspi_flash_write_protection_enable(QSPI_FLASH) == S_SUCCESS) {
|
||||
// after flash protection is enabled, full array is locked. Unlock it.
|
||||
qspi_flash_unlock_all(QSPI_FLASH);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
status_t flash_impl_write_protect(FlashAddress start_sector, FlashAddress end_sector) {
|
||||
if (!s_flash_protection_supported) {
|
||||
return S_SUCCESS; // If not supported, pretend protection succeeded.
|
||||
}
|
||||
|
||||
FlashAddress block_addr = start_sector;
|
||||
while (block_addr <= end_sector) {
|
||||
uint32_t block_size;
|
||||
if (WITHIN(block_addr, SECTOR_SIZE_BYTES, BOARD_NOR_FLASH_SIZE - SECTOR_SIZE_BYTES - 1)) {
|
||||
// Middle of flash has 64k lock units
|
||||
block_addr = flash_impl_get_sector_base_address(block_addr);
|
||||
block_size = SECTOR_SIZE_BYTES;
|
||||
} else {
|
||||
// Start and end of flash have 1 sector of 4k lock units
|
||||
block_addr = flash_impl_get_subsector_base_address(block_addr);
|
||||
block_size = SUBSECTOR_SIZE_BYTES;
|
||||
}
|
||||
const status_t sc = qspi_flash_lock_sector(QSPI_FLASH, block_addr);
|
||||
if (FAILED(sc)) {
|
||||
return sc;
|
||||
}
|
||||
block_addr += block_size;
|
||||
}
|
||||
|
||||
return S_SUCCESS;
|
||||
}
|
||||
|
||||
status_t flash_impl_unprotect(void) {
|
||||
return qspi_flash_unlock_all(QSPI_FLASH);
|
||||
}
|
||||
|
||||
status_t flash_impl_init(bool coredump_mode) {
|
||||
qspi_flash_init(QSPI_FLASH, &QSPI_FLASH_PART, coredump_mode);
|
||||
qspi_flash_unlock_all(QSPI_FLASH);
|
||||
return S_SUCCESS;
|
||||
}
|
||||
|
||||
status_t flash_impl_get_erase_status(void) {
|
||||
return qspi_flash_is_erase_complete(QSPI_FLASH);
|
||||
}
|
||||
|
||||
status_t flash_impl_erase_subsector_begin(FlashAddress subsector_addr) {
|
||||
return qspi_flash_erase_begin(QSPI_FLASH, subsector_addr, true /* is_subsector */);
|
||||
}
|
||||
status_t flash_impl_erase_sector_begin(FlashAddress sector_addr) {
|
||||
return qspi_flash_erase_begin(QSPI_FLASH, sector_addr, false /* !is_subsector */);
|
||||
}
|
||||
|
||||
status_t flash_impl_erase_suspend(FlashAddress sector_addr) {
|
||||
return qspi_flash_erase_suspend(QSPI_FLASH, sector_addr);
|
||||
}
|
||||
|
||||
status_t flash_impl_erase_resume(FlashAddress sector_addr) {
|
||||
qspi_flash_erase_resume(QSPI_FLASH, sector_addr);
|
||||
return S_SUCCESS;
|
||||
}
|
||||
|
||||
status_t flash_impl_read_sync(void *buffer_ptr, FlashAddress start_addr, size_t buffer_size) {
|
||||
PBL_ASSERT(buffer_size > 0, "flash_impl_read_sync() called with 0 bytes to read");
|
||||
qspi_flash_read_blocking(QSPI_FLASH, start_addr, buffer_ptr, buffer_size);
|
||||
return S_SUCCESS;
|
||||
}
|
||||
|
||||
int flash_impl_write_page_begin(const void *buffer, const FlashAddress start_addr, size_t len) {
|
||||
return qspi_flash_write_page_begin(QSPI_FLASH, buffer, start_addr, len);
|
||||
}
|
||||
|
||||
status_t flash_impl_get_write_status(void) {
|
||||
return qspi_flash_get_write_status(QSPI_FLASH);
|
||||
}
|
||||
|
||||
status_t flash_impl_enter_low_power_mode(void) {
|
||||
qspi_flash_set_lower_power_mode(QSPI_FLASH, true);
|
||||
return S_SUCCESS;
|
||||
}
|
||||
status_t flash_impl_exit_low_power_mode(void) {
|
||||
qspi_flash_set_lower_power_mode(QSPI_FLASH, false);
|
||||
return S_SUCCESS;
|
||||
}
|
||||
|
||||
status_t flash_impl_set_burst_mode(bool burst_mode) {
|
||||
// NYI
|
||||
return S_SUCCESS;
|
||||
}
|
||||
|
||||
status_t flash_impl_blank_check_sector(FlashAddress addr) {
|
||||
return qspi_flash_blank_check(QSPI_FLASH, addr, false /* !is_subsector */);
|
||||
}
|
||||
status_t flash_impl_blank_check_subsector(FlashAddress addr) {
|
||||
return qspi_flash_blank_check(QSPI_FLASH, addr, true /* is_subsector */);
|
||||
}
|
||||
|
||||
uint32_t flash_impl_get_typical_sector_erase_duration_ms(void) {
|
||||
return 400;
|
||||
}
|
||||
|
||||
uint32_t flash_impl_get_typical_subsector_erase_duration_ms(void) {
|
||||
return 40;
|
||||
}
|
74
src/fw/drivers/flash/nvram_bkp.c
Normal file
74
src/fw/drivers/flash/nvram_bkp.c
Normal file
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "drivers/flash/flash_impl.h"
|
||||
|
||||
#include "system/passert.h"
|
||||
#include "system/rtc_registers.h"
|
||||
|
||||
#define STM32F2_COMPATIBLE
|
||||
#define STM32F4_COMPATIBLE
|
||||
#define STM32F7_COMPATIBLE
|
||||
#include <mcu.h>
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
// RTC backup registers are reset to zero on a cold boot (power up from a dead
|
||||
// battery) so the value stored in the backup register corresponding to no erase
|
||||
// in progress must also be zero. We need to use a nonzero value to store an
|
||||
// erase to sector zero, so we can't simply store just the address.
|
||||
//
|
||||
// We want to store whether an erase is in progress (1 bit), whether the erase
|
||||
// is for a sector or a subsector (1 bit), and the address being erased (32
|
||||
// bits) in a single 32-bit RTC register. Since we can't magically compress 34
|
||||
// bits into 32, we'll need to play some tricks. The address is going to almost
|
||||
// certainly be less than 32 bits long; we aren't going to be using
|
||||
// gigabyte-sized flash memories any time soon (at least not with this
|
||||
// homegrown API), leaving bits free on the high end.
|
||||
|
||||
#define ERASE_IN_PROGRESS 0x80000000
|
||||
#define ERASE_IS_SUBSECTOR 0x40000000
|
||||
#define ERASE_FLAGS_MASK (ERASE_IN_PROGRESS | ERASE_IS_SUBSECTOR)
|
||||
#define ERASE_ADDRESS_MASK 0x3FFFFFFF
|
||||
|
||||
status_t flash_impl_set_nvram_erase_status(bool is_subsector,
|
||||
FlashAddress addr) {
|
||||
PBL_ASSERTN((addr & ERASE_FLAGS_MASK) == 0); // "Flash address too large to store"
|
||||
uint32_t reg = addr | ERASE_IN_PROGRESS;
|
||||
if (is_subsector) {
|
||||
reg |= ERASE_IS_SUBSECTOR;
|
||||
}
|
||||
RTC_WriteBackupRegister(RTC_BKP_FLASH_ERASE_PROGRESS, reg);
|
||||
return S_SUCCESS;
|
||||
}
|
||||
|
||||
status_t flash_impl_clear_nvram_erase_status(void) {
|
||||
RTC_WriteBackupRegister(RTC_BKP_FLASH_ERASE_PROGRESS, 0);
|
||||
return S_SUCCESS;
|
||||
}
|
||||
|
||||
status_t flash_impl_get_nvram_erase_status(bool *is_subsector,
|
||||
FlashAddress *addr) {
|
||||
uint32_t reg = RTC_ReadBackupRegister(RTC_BKP_FLASH_ERASE_PROGRESS);
|
||||
if (reg == 0) {
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
*addr = reg & ERASE_ADDRESS_MASK;
|
||||
*is_subsector = (reg & ERASE_IS_SUBSECTOR) != 0;
|
||||
return S_TRUE;
|
||||
}
|
761
src/fw/drivers/flash/qspi_flash.c
Normal file
761
src/fw/drivers/flash/qspi_flash.c
Normal file
|
@ -0,0 +1,761 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "qspi_flash.h"
|
||||
#include "qspi_flash_definitions.h"
|
||||
|
||||
#include "board/board.h"
|
||||
#include "drivers/flash/flash_impl.h"
|
||||
#include "drivers/gpio.h"
|
||||
#include "drivers/qspi.h"
|
||||
#include "flash_region/flash_region.h"
|
||||
#include "kernel/util/delay.h"
|
||||
#include "kernel/util/sleep.h"
|
||||
#include "mcu/cache.h"
|
||||
#include "system/logging.h"
|
||||
#include "system/passert.h"
|
||||
#include "system/status_codes.h"
|
||||
#include "util/math.h"
|
||||
|
||||
#define STM32F4_COMPATIBLE
|
||||
#define STM32F7_COMPATIBLE
|
||||
#include <mcu.h>
|
||||
|
||||
|
||||
#define FLASH_RESET_WORD_VALUE (0xffffffff)
|
||||
|
||||
|
||||
static void prv_read_register(QSPIFlash *dev, uint8_t instruction, uint8_t *data, uint32_t length) {
|
||||
qspi_indirect_read_no_addr(dev->qspi, instruction, 0, data, length, false /* !is_ddr */);
|
||||
}
|
||||
|
||||
static void prv_write_cmd_no_addr(QSPIFlash *dev, uint8_t cmd) {
|
||||
qspi_indirect_write_no_addr(dev->qspi, cmd, NULL, 0);
|
||||
}
|
||||
|
||||
static void prv_write_enable(QSPIFlash *dev) {
|
||||
prv_write_cmd_no_addr(dev, dev->state->part->instructions.write_enable);
|
||||
// wait for writing to be enabled
|
||||
qspi_poll_bit(dev->qspi, dev->state->part->instructions.read_status,
|
||||
dev->state->part->status_bit_masks.write_enable, true /* set */, QSPI_NO_TIMEOUT);
|
||||
}
|
||||
|
||||
static bool prv_check_whoami(QSPIFlash *dev) {
|
||||
// The WHOAMI is 3 bytes
|
||||
const uint32_t whoami_length = 3;
|
||||
uint32_t read_whoami = 0;
|
||||
prv_read_register(dev, dev->state->part->instructions.qspi_id, (uint8_t *)&read_whoami,
|
||||
whoami_length);
|
||||
|
||||
if (read_whoami == dev->state->part->qspi_id_value) {
|
||||
PBL_LOG(LOG_LEVEL_INFO, "Flash is %s", dev->state->part->name);
|
||||
return true;
|
||||
} else {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Flash isn't expected %s (whoami: 0x%"PRIx32")",
|
||||
dev->state->part->name, read_whoami);
|
||||
return false;
|
||||
}
|
||||
qspi_release(dev->qspi);
|
||||
}
|
||||
|
||||
bool qspi_flash_check_whoami(QSPIFlash *dev) {
|
||||
qspi_use(dev->qspi);
|
||||
bool result = prv_check_whoami(dev);
|
||||
qspi_release(dev->qspi);
|
||||
return result;
|
||||
}
|
||||
|
||||
static void prv_set_fast_read_ddr_enabled(QSPIFlash *dev, bool enabled) {
|
||||
// If we're supposed to use DDR for fast read, make sure the part can support it
|
||||
PBL_ASSERTN(!enabled || dev->state->part->supports_fast_read_ddr);
|
||||
dev->state->fast_read_ddr_enabled = enabled;
|
||||
}
|
||||
|
||||
bool qspi_flash_is_in_coredump_mode(QSPIFlash *dev) {
|
||||
return dev->state->coredump_mode;
|
||||
}
|
||||
|
||||
void qspi_flash_init(QSPIFlash *dev, QSPIFlashPart *part, bool coredump_mode) {
|
||||
dev->state->part = part;
|
||||
dev->state->coredump_mode = coredump_mode;
|
||||
prv_set_fast_read_ddr_enabled(dev, dev->default_fast_read_ddr_enabled);
|
||||
qspi_use(dev->qspi);
|
||||
|
||||
if (dev->reset_gpio.gpio) {
|
||||
gpio_output_init(&dev->reset_gpio, GPIO_OType_PP, GPIO_Speed_2MHz);
|
||||
gpio_output_set(&dev->reset_gpio, false);
|
||||
}
|
||||
|
||||
// Must call quad_enable first, all commands are QSPI
|
||||
qspi_indirect_write_no_addr_1line(dev->qspi, dev->state->part->instructions.enter_quad_mode);
|
||||
|
||||
// Reset the flash to stop any program's or erase in progress from before reboot
|
||||
prv_write_cmd_no_addr(dev, dev->state->part->instructions.reset_enable);
|
||||
prv_write_cmd_no_addr(dev, dev->state->part->instructions.reset);
|
||||
|
||||
if (coredump_mode) {
|
||||
delay_us(dev->state->part->reset_latency_ms * 1000);
|
||||
} else {
|
||||
psleep(dev->state->part->reset_latency_ms);
|
||||
}
|
||||
|
||||
// Return the flash to Quad SPI mode, all our commands are quad-spi and it'll just cause
|
||||
// problems/bugs for someone if it comes back in single spi mode
|
||||
qspi_indirect_write_no_addr_1line(dev->qspi, dev->state->part->instructions.enter_quad_mode);
|
||||
|
||||
if (!coredump_mode) {
|
||||
prv_check_whoami(dev);
|
||||
}
|
||||
|
||||
qspi_release(dev->qspi);
|
||||
}
|
||||
|
||||
status_t qspi_flash_is_erase_complete(QSPIFlash *dev) {
|
||||
qspi_use(dev->qspi);
|
||||
|
||||
uint8_t status_reg;
|
||||
uint8_t flag_status_reg;
|
||||
prv_read_register(dev, dev->state->part->instructions.read_status, &status_reg, 1);
|
||||
prv_read_register(dev, dev->state->part->instructions.read_flag_status, &flag_status_reg, 1);
|
||||
|
||||
qspi_release(dev->qspi);
|
||||
|
||||
if (status_reg & dev->state->part->status_bit_masks.busy) {
|
||||
return E_BUSY;
|
||||
} else if (flag_status_reg & dev->state->part->flag_status_bit_masks.erase_suspend) {
|
||||
return E_AGAIN;
|
||||
} else {
|
||||
return S_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
status_t qspi_flash_erase_begin(QSPIFlash *dev, uint32_t addr, bool is_subsector) {
|
||||
uint8_t instruction;
|
||||
if (is_subsector) {
|
||||
instruction = dev->state->part->instructions.erase_sector_4k;
|
||||
} else {
|
||||
instruction = dev->state->part->instructions.erase_block_64k;
|
||||
}
|
||||
|
||||
qspi_use(dev->qspi);
|
||||
prv_write_enable(dev);
|
||||
qspi_indirect_write(dev->qspi, instruction, addr, NULL, 0);
|
||||
// wait for busy to be set indicating the erase has started
|
||||
const uint32_t busy_timeout_us = 500;
|
||||
const bool result = qspi_poll_bit(dev->qspi, dev->state->part->instructions.read_status,
|
||||
dev->state->part->status_bit_masks.busy, true /* set */,
|
||||
busy_timeout_us);
|
||||
qspi_release(QSPI);
|
||||
|
||||
return result ? S_SUCCESS : E_ERROR;
|
||||
}
|
||||
|
||||
status_t qspi_flash_erase_suspend(QSPIFlash *dev, uint32_t addr) {
|
||||
qspi_use(dev->qspi);
|
||||
|
||||
uint8_t status_reg;
|
||||
prv_read_register(dev, dev->state->part->instructions.read_status, &status_reg, 1);
|
||||
if (!(status_reg & dev->state->part->status_bit_masks.busy)) {
|
||||
// no erase in progress
|
||||
qspi_release(QSPI);
|
||||
return S_NO_ACTION_REQUIRED;
|
||||
}
|
||||
|
||||
prv_write_cmd_no_addr(dev, dev->state->part->instructions.erase_suspend);
|
||||
|
||||
qspi_release(QSPI);
|
||||
|
||||
if (dev->state->part->suspend_to_read_latency_us) {
|
||||
delay_us(dev->state->part->suspend_to_read_latency_us);
|
||||
}
|
||||
|
||||
return S_SUCCESS;
|
||||
}
|
||||
|
||||
void qspi_flash_erase_resume(QSPIFlash *dev, uint32_t addr) {
|
||||
qspi_use(dev->qspi);
|
||||
prv_write_cmd_no_addr(dev, dev->state->part->instructions.erase_resume);
|
||||
// wait for the erase_suspend bit to be cleared
|
||||
qspi_poll_bit(dev->qspi, dev->state->part->instructions.read_flag_status,
|
||||
dev->state->part->flag_status_bit_masks.erase_suspend, false /* !set */,
|
||||
QSPI_NO_TIMEOUT);
|
||||
qspi_release(dev->qspi);
|
||||
}
|
||||
|
||||
static void prv_get_fast_read_params(QSPIFlash *dev, uint8_t *instruction, uint8_t *dummy_cycles,
|
||||
bool *is_ddr) {
|
||||
if (dev->state->fast_read_ddr_enabled) {
|
||||
*instruction = dev->state->part->instructions.fast_read_ddr;
|
||||
*dummy_cycles = dev->state->part->dummy_cycles.fast_read_ddr;
|
||||
*is_ddr = true;
|
||||
} else {
|
||||
*instruction = dev->state->part->instructions.fast_read;
|
||||
*dummy_cycles = dev->state->part->dummy_cycles.fast_read;
|
||||
*is_ddr = false;
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_read_mmap_with_params(QSPIFlash *dev, uint32_t addr, void *buffer, uint32_t length,
|
||||
uint8_t instruction, uint8_t dummy_cycles, bool is_ddr) {
|
||||
qspi_mmap_start(dev->qspi, instruction, addr, dummy_cycles, length, is_ddr);
|
||||
|
||||
// Point the buffer at the QSPI region
|
||||
memcpy(buffer, (uint32_t *)(QSPI_MMAP_BASE_ADDRESS + addr), length);
|
||||
|
||||
// stop memory mapped mode
|
||||
qspi_mmap_stop(dev->qspi);
|
||||
}
|
||||
|
||||
static void prv_read_mmap(QSPIFlash *dev, uint32_t addr, void *buffer, uint32_t length) {
|
||||
uint8_t instruction;
|
||||
uint8_t dummy_cycles;
|
||||
bool is_ddr;
|
||||
prv_get_fast_read_params(dev, &instruction, &dummy_cycles, &is_ddr);
|
||||
|
||||
prv_read_mmap_with_params(dev, addr, buffer, length, instruction,
|
||||
dummy_cycles, is_ddr);
|
||||
}
|
||||
|
||||
void qspi_flash_read_blocking(QSPIFlash *dev, uint32_t addr, void *buffer, uint32_t length) {
|
||||
// TODO: Figure out what thresholds we should use when switching between memory mapping, DMA, &
|
||||
// polling PBL-37438
|
||||
bool should_use_dma = length > 128 && !dev->state->coredump_mode;
|
||||
bool should_use_memmap = length > 128;
|
||||
|
||||
#if QSPI_DMA_DISABLE
|
||||
// Known issues with some platforms, see PBL-37278 as an example
|
||||
should_use_dma = false;
|
||||
#endif
|
||||
|
||||
#if TARGET_QEMU
|
||||
// QEMU doesn't yet support DMA or memory-mapping
|
||||
should_use_dma = should_use_memmap = false;
|
||||
#endif
|
||||
|
||||
qspi_use(dev->qspi);
|
||||
uint8_t instruction;
|
||||
uint8_t dummy_cycles;
|
||||
bool is_ddr;
|
||||
prv_get_fast_read_params(dev, &instruction, &dummy_cycles, &is_ddr);
|
||||
if (should_use_dma) {
|
||||
qspi_indirect_read_dma(dev->qspi, instruction, addr, dummy_cycles, buffer, length, is_ddr);
|
||||
} else if (should_use_memmap) {
|
||||
prv_read_mmap_with_params(dev, addr, buffer, length, instruction, dummy_cycles, is_ddr);
|
||||
} else {
|
||||
qspi_indirect_read(dev->qspi, instruction, addr, dummy_cycles, buffer, length, is_ddr);
|
||||
}
|
||||
qspi_release(dev->qspi);
|
||||
}
|
||||
|
||||
int qspi_flash_write_page_begin(QSPIFlash *dev, const void *buffer, uint32_t addr,
|
||||
uint32_t length) {
|
||||
const uint32_t offset_in_page = addr % PAGE_SIZE_BYTES;
|
||||
const uint32_t bytes_in_page = MIN(PAGE_SIZE_BYTES - offset_in_page, length);
|
||||
|
||||
qspi_use(dev->qspi);
|
||||
prv_write_enable(dev);
|
||||
qspi_indirect_write(dev->qspi, dev->state->part->instructions.page_program, addr, buffer,
|
||||
bytes_in_page);
|
||||
qspi_poll_bit(dev->qspi, dev->state->part->instructions.read_status,
|
||||
dev->state->part->status_bit_masks.busy, false /* !set */, QSPI_NO_TIMEOUT);
|
||||
qspi_release(dev->qspi);
|
||||
|
||||
return bytes_in_page;
|
||||
}
|
||||
|
||||
status_t qspi_flash_get_write_status(QSPIFlash *dev) {
|
||||
qspi_use(dev->qspi);
|
||||
uint8_t status_reg;
|
||||
prv_read_register(dev, dev->state->part->instructions.read_status, &status_reg, 1);
|
||||
qspi_release(dev->qspi);
|
||||
return (status_reg & dev->state->part->status_bit_masks.busy) ? E_BUSY : S_SUCCESS;
|
||||
}
|
||||
|
||||
void qspi_flash_set_lower_power_mode(QSPIFlash *dev, bool active) {
|
||||
qspi_use(dev->qspi);
|
||||
uint8_t instruction;
|
||||
uint32_t delay;
|
||||
if (active) {
|
||||
instruction = dev->state->part->instructions.enter_low_power;
|
||||
delay = dev->state->part->standby_to_low_power_latency_us;
|
||||
} else {
|
||||
instruction = dev->state->part->instructions.exit_low_power;
|
||||
delay = dev->state->part->low_power_to_standby_latency_us;
|
||||
}
|
||||
prv_write_cmd_no_addr(dev, instruction);
|
||||
qspi_release(dev->qspi);
|
||||
if (delay) {
|
||||
delay_us(delay);
|
||||
}
|
||||
}
|
||||
|
||||
#if TARGET_QEMU
|
||||
// While this works with normal hardware, it has a large stack requirment and I can't
|
||||
// see a compelling reason to use it over the mmap blank check variant
|
||||
static bool prv_blank_check_poll(QSPIFlash *dev, uint32_t addr, bool is_subsector) {
|
||||
const uint32_t size_bytes = is_subsector ? SUBSECTOR_SIZE_BYTES : SECTOR_SIZE_BYTES;
|
||||
const uint32_t BUF_SIZE_BYTES = 128;
|
||||
const uint32_t BUF_SIZE_WORDS = BUF_SIZE_BYTES / sizeof(uint32_t);
|
||||
uint32_t buffer[BUF_SIZE_WORDS];
|
||||
for (uint32_t offset = 0; offset < size_bytes; offset += BUF_SIZE_BYTES) {
|
||||
flash_impl_read_sync(buffer, addr + offset, BUF_SIZE_BYTES);
|
||||
for (uint32_t i = 0; i < BUF_SIZE_WORDS; ++i) {
|
||||
if (buffer[i] != FLASH_RESET_WORD_VALUE) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
static bool prv_blank_check_mmap(QSPIFlash *dev, uint32_t addr, bool is_subsector) {
|
||||
const uint32_t size_bytes = is_subsector ? SUBSECTOR_SIZE_BYTES : SECTOR_SIZE_BYTES;
|
||||
bool result = true;
|
||||
uint8_t instruction;
|
||||
uint8_t dummy_cycles;
|
||||
bool is_ddr;
|
||||
prv_get_fast_read_params(dev, &instruction, &dummy_cycles, &is_ddr);
|
||||
qspi_mmap_start(dev->qspi, instruction, addr, dummy_cycles, size_bytes, is_ddr);
|
||||
|
||||
// Point the buffer at the QSPI region
|
||||
uint32_t const volatile * const buffer = (uint32_t *)(QSPI_MMAP_BASE_ADDRESS + addr);
|
||||
uint32_t size_words = size_bytes / sizeof(uint32_t);
|
||||
for (uint32_t i = 0; i < size_words; ++i) {
|
||||
if (buffer[i] != FLASH_RESET_WORD_VALUE) {
|
||||
result = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// stop memory mapped mode
|
||||
qspi_mmap_stop(QSPI);
|
||||
return result;
|
||||
}
|
||||
status_t qspi_flash_blank_check(QSPIFlash *dev, uint32_t addr, bool is_subsector) {
|
||||
qspi_use(dev->qspi);
|
||||
#if TARGET_QEMU
|
||||
// QEMU doesn't support memory-mapping the FLASH
|
||||
const bool result = prv_blank_check_poll(dev, addr, is_subsector);
|
||||
#else
|
||||
const bool result = prv_blank_check_mmap(dev, addr, is_subsector);
|
||||
#endif
|
||||
qspi_release(dev->qspi);
|
||||
return result ? S_TRUE : S_FALSE;
|
||||
}
|
||||
|
||||
void qspi_flash_ll_set_register_bits(QSPIFlash *dev, uint8_t read_instruction,
|
||||
uint8_t write_instruction, uint8_t value, uint8_t mask) {
|
||||
// make sure we're not trying to set any bits not within the mask
|
||||
PBL_ASSERTN((value & mask) == value);
|
||||
|
||||
qspi_use(dev->qspi);
|
||||
|
||||
// first read the register
|
||||
uint8_t reg_value;
|
||||
prv_read_register(dev, read_instruction, ®_value, 1);
|
||||
|
||||
// set the desired bits
|
||||
reg_value = (reg_value & ~mask) | value;
|
||||
|
||||
// enable writing and write the register value
|
||||
prv_write_cmd_no_addr(dev, dev->state->part->instructions.write_enable);
|
||||
qspi_indirect_write_no_addr(dev->qspi, write_instruction, ®_value, 1);
|
||||
|
||||
qspi_release(dev->qspi);
|
||||
}
|
||||
|
||||
static bool prv_protection_is_enabled(QSPIFlash *dev) {
|
||||
uint8_t status;
|
||||
prv_read_register(dev, dev->state->part->instructions.read_protection_status, &status, 1);
|
||||
return (status & dev->state->part->block_lock.protection_enabled_mask);
|
||||
}
|
||||
|
||||
status_t qspi_flash_write_protection_enable(QSPIFlash *dev) {
|
||||
#if TARGET_QEMU
|
||||
return S_NO_ACTION_REQUIRED;
|
||||
#endif
|
||||
qspi_use(dev->qspi);
|
||||
prv_write_enable(dev);
|
||||
const bool already_enabled = prv_protection_is_enabled(dev);
|
||||
if (already_enabled == false) {
|
||||
PBL_LOG(LOG_LEVEL_INFO, "Enabling flash protection");
|
||||
// Enable write protection
|
||||
prv_write_cmd_no_addr(dev, dev->state->part->instructions.write_protection_enable);
|
||||
|
||||
// Poll busy status until done
|
||||
qspi_poll_bit(dev->qspi, dev->state->part->instructions.read_status,
|
||||
dev->state->part->status_bit_masks.busy, false /* !set */, QSPI_NO_TIMEOUT);
|
||||
|
||||
}
|
||||
qspi_release(dev->qspi);
|
||||
|
||||
return (already_enabled) ? S_NO_ACTION_REQUIRED : S_SUCCESS;
|
||||
}
|
||||
|
||||
status_t qspi_flash_lock_sector(QSPIFlash *dev, uint32_t addr) {
|
||||
#if TARGET_QEMU
|
||||
return S_SUCCESS;
|
||||
#endif
|
||||
qspi_use(dev->qspi);
|
||||
|
||||
prv_write_enable(dev);
|
||||
|
||||
// Lock or unlock the sector
|
||||
const uint8_t instruction = dev->state->part->instructions.block_lock;
|
||||
if (dev->state->part->block_lock.has_lock_data) {
|
||||
qspi_indirect_write(dev->qspi, instruction, addr, &dev->state->part->block_lock.lock_data, 1);
|
||||
} else {
|
||||
qspi_indirect_write(dev->qspi, instruction, addr, NULL, 0);
|
||||
}
|
||||
|
||||
// Poll busy status until done
|
||||
qspi_poll_bit(dev->qspi, dev->state->part->instructions.read_status,
|
||||
dev->state->part->status_bit_masks.busy, false /* !set */, QSPI_NO_TIMEOUT);
|
||||
|
||||
// Read lock status
|
||||
uint8_t status;
|
||||
qspi_indirect_read(dev->qspi, dev->state->part->instructions.block_lock_status,
|
||||
addr, 0, &status, sizeof(status), false);
|
||||
|
||||
qspi_release(dev->qspi);
|
||||
|
||||
return (status == dev->state->part->block_lock.locked_check) ? S_SUCCESS : E_ERROR;
|
||||
}
|
||||
|
||||
status_t qspi_flash_unlock_all(QSPIFlash *dev) {
|
||||
#if TARGET_QEMU
|
||||
return S_SUCCESS;
|
||||
#endif
|
||||
qspi_use(dev->qspi);
|
||||
prv_write_enable(dev);
|
||||
prv_write_cmd_no_addr(dev, dev->state->part->instructions.block_unlock_all);
|
||||
qspi_release(dev->qspi);
|
||||
return S_SUCCESS;
|
||||
}
|
||||
|
||||
#if !RELEASE
|
||||
#include "console/prompt.h"
|
||||
#include "drivers/flash.h"
|
||||
#include "kernel/pbl_malloc.h"
|
||||
#include "system/profiler.h"
|
||||
#include "util/size.h"
|
||||
|
||||
static bool prv_flash_read_verify(QSPIFlash *dev, int size, int offset) {
|
||||
bool success = true;
|
||||
char *buffer_dma_ptr = kernel_malloc_check(size + offset + 3);
|
||||
char *buffer_pol = kernel_malloc_check(size + 3);
|
||||
char *buffer_mmap = kernel_malloc_check(size + 3);
|
||||
|
||||
char *buffer_dma = buffer_dma_ptr + offset;
|
||||
|
||||
// The buffers need to be different, so when compared against each other we can make
|
||||
// sure the write functions wrote the same thing.
|
||||
memset(buffer_dma, 0xA5, size);
|
||||
memset(buffer_pol, 0xCC, size);
|
||||
memset(buffer_mmap, 0x33, size);
|
||||
|
||||
profiler_start();
|
||||
prv_read_mmap(dev, 0, buffer_mmap, size);
|
||||
profiler_stop();
|
||||
uint32_t mmap_time = profiler_get_total_duration(true);
|
||||
|
||||
profiler_start();
|
||||
uint8_t instruction;
|
||||
uint8_t dummy_cycles;
|
||||
bool is_ddr;
|
||||
prv_get_fast_read_params(dev, &instruction, &dummy_cycles, &is_ddr);
|
||||
qspi_indirect_read_dma(dev->qspi, instruction, 0, dummy_cycles, buffer_dma, size, is_ddr);
|
||||
profiler_stop();
|
||||
uint32_t dma_time = profiler_get_total_duration(true);
|
||||
|
||||
profiler_start();
|
||||
qspi_indirect_read(dev->qspi, instruction, 0, dummy_cycles, buffer_pol, size, is_ddr);
|
||||
profiler_stop();
|
||||
uint32_t pol_time = profiler_get_total_duration(true);
|
||||
|
||||
if (memcmp(buffer_dma, buffer_pol, size) != 0) {
|
||||
prompt_send_response("FAILURE: buffer_dma != buffer_pol");
|
||||
success = false;
|
||||
}
|
||||
if (memcmp(buffer_dma, buffer_mmap, size) != 0) {
|
||||
prompt_send_response("FAILURE: buffer_dma != buffer_mmap");
|
||||
success = false;
|
||||
}
|
||||
|
||||
const int buf_size = 64;
|
||||
char buf[buf_size];
|
||||
prompt_send_response_fmt(buf, buf_size, "Size: %d DMA: %"PRIu32 " POL: %"PRIu32 " MMP: %"PRIu32,
|
||||
size, dma_time, pol_time, mmap_time);
|
||||
|
||||
|
||||
kernel_free(buffer_dma_ptr);
|
||||
kernel_free(buffer_pol);
|
||||
kernel_free(buffer_mmap);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
struct FlashReadTestValues {
|
||||
int size;
|
||||
int offset;
|
||||
};
|
||||
|
||||
const struct FlashReadTestValues FLASH_READ_TEST_TABLE[] = {
|
||||
{ .size = 1024, .offset = 0 },
|
||||
{ .size = 1025, .offset = 0 },
|
||||
{ .size = 1026, .offset = 0 },
|
||||
{ .size = 1027, .offset = 0 },
|
||||
{ .size = 1024, .offset = 1 },
|
||||
{ .size = 1025, .offset = 2 },
|
||||
{ .size = 1026, .offset = 3 },
|
||||
{ .size = 4, .offset = 0 },
|
||||
{ .size = 20, .offset = 0 },
|
||||
{ .size = 60, .offset = 0 },
|
||||
{ .size = 127, .offset = 0 },
|
||||
{ .size = 128, .offset = 0 },
|
||||
};
|
||||
|
||||
|
||||
void command_flash_apicheck(const char *len_str) {
|
||||
QSPIFlash *dev = QSPI_FLASH;
|
||||
const int buf_size = 64;
|
||||
char buf[buf_size];
|
||||
int failures = 0;
|
||||
int passes = 0;
|
||||
|
||||
profiler_init();
|
||||
|
||||
prompt_send_response("Check whoami");
|
||||
if (!qspi_flash_check_whoami(dev)) {
|
||||
++failures;
|
||||
prompt_send_response("ERROR: Who am I failed");
|
||||
} else {
|
||||
++passes;
|
||||
}
|
||||
|
||||
prompt_send_response("Enter low power mode");
|
||||
flash_impl_enter_low_power_mode();
|
||||
|
||||
// WHOAMI should fail in low-power mode
|
||||
prompt_send_response("Check whoami, should fail in low power mode");
|
||||
if (qspi_flash_check_whoami(dev)) {
|
||||
++failures;
|
||||
prompt_send_response("ERROR: Who am I failed");
|
||||
} else {
|
||||
++passes;
|
||||
}
|
||||
|
||||
prompt_send_response("Exit low power mode");
|
||||
flash_impl_exit_low_power_mode();
|
||||
|
||||
prompt_send_response("Start flash_read_verify test");
|
||||
qspi_use(QSPI);
|
||||
|
||||
const int final_size = atoi(len_str);
|
||||
|
||||
// If size is 0 run through a pre-defined table
|
||||
if (final_size == 0) {
|
||||
for (unsigned int i = 0; i < ARRAY_LENGTH(FLASH_READ_TEST_TABLE); ++i) {
|
||||
bool result = prv_flash_read_verify(dev, FLASH_READ_TEST_TABLE[i].size,
|
||||
FLASH_READ_TEST_TABLE[i].offset);
|
||||
if (!result) {
|
||||
++failures;
|
||||
} else {
|
||||
++passes;
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
if (prv_flash_read_verify(dev, final_size, 3)) {
|
||||
++passes;
|
||||
} else {
|
||||
++failures;
|
||||
prompt_send_response("ERROR: flash_read_verify failed");
|
||||
}
|
||||
}
|
||||
|
||||
qspi_release(QSPI);
|
||||
|
||||
bool was_busy = false;
|
||||
|
||||
// write a few bytes to the sector we're going to erase so it's not empty
|
||||
uint8_t dummy_data = 0x55;
|
||||
flash_write_bytes(&dummy_data, FLASH_REGION_FIRMWARE_SCRATCH_BEGIN, sizeof(dummy_data));
|
||||
profiler_start();
|
||||
status_t result = flash_impl_erase_sector_begin(FLASH_REGION_FIRMWARE_SCRATCH_BEGIN);
|
||||
flash_impl_get_erase_status();
|
||||
if (result == S_SUCCESS) {
|
||||
while (flash_impl_get_erase_status() == E_BUSY) {
|
||||
was_busy = true;
|
||||
}
|
||||
}
|
||||
profiler_stop();
|
||||
uint32_t duration = profiler_get_total_duration(true);
|
||||
prompt_send_response_fmt(buf, buf_size, "Erase took: %"PRIu32, duration);
|
||||
|
||||
// Fash erases take at least ~100ms, if we're too short we probably didn't erase
|
||||
const uint32_t min_erase_time = 10000;
|
||||
if (result != S_SUCCESS) {
|
||||
++failures;
|
||||
prompt_send_response_fmt(buf, buf_size,
|
||||
"FAILURE: erase did not report success %"PRIi32, result);
|
||||
} else if (was_busy == false) {
|
||||
++failures;
|
||||
prompt_send_response("FAILURE: Flash never became busy, but we should be busy for 300ms.");
|
||||
prompt_send_response("FAILURE: Flash probably never did an erase.");
|
||||
} else if (duration < min_erase_time) {
|
||||
++failures;
|
||||
prompt_send_response("FAILURE: Flash erase completed way to quickly to have succeeded.");
|
||||
} else {
|
||||
++passes;
|
||||
}
|
||||
|
||||
// must call blank_check_poll by hand, otherwise we'll get the dma version
|
||||
profiler_start();
|
||||
qspi_use(QSPI);
|
||||
bool is_blank = qspi_flash_blank_check(QSPI_FLASH, FLASH_REGION_FIRMWARE_SCRATCH_BEGIN,
|
||||
SUBSECTOR_SIZE_BYTES);
|
||||
qspi_release(QSPI);
|
||||
profiler_stop();
|
||||
|
||||
uint32_t blank = profiler_get_total_duration(true);
|
||||
prompt_send_response_fmt(buf, buf_size, "Sector blank check via read took: %"PRIu32, blank);
|
||||
if (is_blank != S_TRUE) {
|
||||
++failures;
|
||||
prompt_send_response("FAILURE: sector not blank!?!");
|
||||
} else {
|
||||
++passes;
|
||||
}
|
||||
|
||||
profiler_start();
|
||||
is_blank = flash_impl_blank_check_subsector(FLASH_REGION_FIRMWARE_SCRATCH_BEGIN);
|
||||
profiler_stop();
|
||||
|
||||
blank = profiler_get_total_duration(true);
|
||||
prompt_send_response_fmt(buf, buf_size, "Subsector blank check via read took: %"PRIu32,
|
||||
blank);
|
||||
if (is_blank != S_TRUE) {
|
||||
++failures;
|
||||
prompt_send_response("FAILURE: sector not blank!?!");
|
||||
} else {
|
||||
++passes;
|
||||
}
|
||||
|
||||
if (failures == 0) {
|
||||
prompt_send_response_fmt(buf, buf_size, "SUCCESS: run %d tests and all passeed", passes);
|
||||
}
|
||||
else {
|
||||
prompt_send_response_fmt(buf, buf_size, "FAILED: run %d tests and %d failed",
|
||||
passes + failures,
|
||||
failures);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#if RECOVERY_FW
|
||||
#include "console/prompt.h"
|
||||
#include "drivers/flash.h"
|
||||
|
||||
#define SIGNAL_TEST_MAGIC_PATTERN (0xA5)
|
||||
#define TEST_BUFFER_SIZE (1024)
|
||||
static uint8_t s_test_buffer[TEST_BUFFER_SIZE];
|
||||
static const uint32_t s_test_addr = FLASH_REGION_FIRMWARE_SCRATCH_END - SECTOR_SIZE_BYTES;
|
||||
static bool s_signal_test_initialized;
|
||||
|
||||
void command_flash_signal_test_init(void) {
|
||||
// just test one sector, which is probably less than the size of the region
|
||||
|
||||
// erase the sector
|
||||
flash_erase_sector_blocking(s_test_addr);
|
||||
|
||||
// set the contents of the sector such that we will end up reading alternating 1s and 0s
|
||||
memset(s_test_buffer, SIGNAL_TEST_MAGIC_PATTERN, sizeof(s_test_buffer));
|
||||
flash_write_bytes(s_test_buffer, s_test_addr, sizeof(s_test_buffer));
|
||||
|
||||
QSPIFlash *dev = QSPI_FLASH;
|
||||
// Ensure DDR is disabled for write check
|
||||
prv_set_fast_read_ddr_enabled(dev, false);
|
||||
uint8_t instruction;
|
||||
uint8_t dummy_cycles;
|
||||
bool is_ddr;
|
||||
prv_get_fast_read_params(dev, &instruction, &dummy_cycles, &is_ddr);
|
||||
PBL_ASSERTN(!is_ddr);
|
||||
|
||||
qspi_use(QSPI);
|
||||
qspi_indirect_read(dev->qspi, instruction, s_test_addr, dummy_cycles, s_test_buffer,
|
||||
sizeof(s_test_buffer), is_ddr);
|
||||
|
||||
prv_set_fast_read_ddr_enabled(dev, dev->default_fast_read_ddr_enabled);
|
||||
qspi_release(QSPI);
|
||||
|
||||
bool success = true;
|
||||
for (uint32_t i = 0; i < sizeof(s_test_buffer); ++i) {
|
||||
if (s_test_buffer[i] != SIGNAL_TEST_MAGIC_PATTERN) {
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (success) {
|
||||
prompt_send_response("Done!");
|
||||
s_signal_test_initialized = true;
|
||||
} else {
|
||||
prompt_send_response("ERROR: Data read (SDR mode) did not match data written!");
|
||||
}
|
||||
}
|
||||
|
||||
void command_flash_signal_test_run(void) {
|
||||
if (!s_signal_test_initialized) {
|
||||
prompt_send_response("ERROR: 'flash signal test init' must be run first!");
|
||||
return;
|
||||
}
|
||||
|
||||
QSPIFlash *dev = QSPI_FLASH;
|
||||
qspi_use(QSPI);
|
||||
|
||||
// set to DDR
|
||||
prv_set_fast_read_ddr_enabled(dev, true);
|
||||
|
||||
// issue the read
|
||||
uint8_t instruction;
|
||||
uint8_t dummy_cycles;
|
||||
bool is_ddr;
|
||||
prv_get_fast_read_params(dev, &instruction, &dummy_cycles, &is_ddr);
|
||||
PBL_ASSERTN(is_ddr);
|
||||
qspi_indirect_read(dev->qspi, instruction, s_test_addr, dummy_cycles, s_test_buffer,
|
||||
sizeof(s_test_buffer), is_ddr);
|
||||
|
||||
bool success = true;
|
||||
for (uint32_t i = 0; i < sizeof(s_test_buffer); ++i) {
|
||||
if (s_test_buffer[i] != SIGNAL_TEST_MAGIC_PATTERN) {
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// set back to default mode
|
||||
prv_set_fast_read_ddr_enabled(dev, dev->default_fast_read_ddr_enabled);
|
||||
qspi_release(QSPI);
|
||||
|
||||
if (success) {
|
||||
prompt_send_response("Ok");
|
||||
} else {
|
||||
prompt_send_response("ERROR: Read value didn't match!");
|
||||
}
|
||||
}
|
||||
#endif
|
85
src/fw/drivers/flash/qspi_flash.h
Normal file
85
src/fw/drivers/flash/qspi_flash.h
Normal file
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "system/status_codes.h"
|
||||
|
||||
typedef const struct QSPIFlash QSPIFlash;
|
||||
typedef const struct QSPIFlashPart QSPIFlashPart;
|
||||
|
||||
//! Initialize the QSPI flash
|
||||
//! @param coredump_mode If true, don't use anything that might not be available mid-crash, such
|
||||
//! as FreeRTOS calls or other system services.
|
||||
void qspi_flash_init(QSPIFlash *dev, QSPIFlashPart *part, bool coredump_mode);
|
||||
|
||||
bool qspi_flash_is_in_coredump_mode(QSPIFlash *dev);
|
||||
|
||||
//! Check if the WHOAMI matches the expected value
|
||||
bool qspi_flash_check_whoami(QSPIFlash *dev);
|
||||
|
||||
//! Check if an in-progress erase is complete
|
||||
status_t qspi_flash_is_erase_complete(QSPIFlash *dev);
|
||||
|
||||
//! Begin an erase
|
||||
status_t qspi_flash_erase_begin(QSPIFlash *dev, uint32_t addr, bool is_subsector);
|
||||
|
||||
//! Suspend an erase
|
||||
status_t qspi_flash_erase_suspend(QSPIFlash *dev, uint32_t addr);
|
||||
|
||||
//! Resume a suspended erase
|
||||
void qspi_flash_erase_resume(QSPIFlash *dev, uint32_t addr);
|
||||
|
||||
//! Performs a blocking read
|
||||
void qspi_flash_read_blocking(QSPIFlash *dev, uint32_t addr, void *buffer, uint32_t length);
|
||||
|
||||
// Begins a write operation
|
||||
int qspi_flash_write_page_begin(QSPIFlash *dev, const void *buffer, uint32_t addr, uint32_t length);
|
||||
|
||||
//! Gets the status of an in-progress write operation
|
||||
status_t qspi_flash_get_write_status(QSPIFlash *dev);
|
||||
|
||||
//! Sets whether or not the QSPI flash is in low-power mode
|
||||
void qspi_flash_set_lower_power_mode(QSPIFlash *dev, bool active);
|
||||
|
||||
//! Check whether a sector/subsector is blank
|
||||
status_t qspi_flash_blank_check(QSPIFlash *dev, uint32_t addr, bool is_subsector);
|
||||
|
||||
//! Sets the values of the bits (masked by `mask`) in the register (read by `read_instruction` and
|
||||
//! written via `write_instruction`) to `value`
|
||||
void qspi_flash_ll_set_register_bits(QSPIFlash *dev, uint8_t read_instruction,
|
||||
uint8_t write_instruction, uint8_t value, uint8_t mask);
|
||||
|
||||
//! Enable write/erase protection on the given QSPI flash part.
|
||||
//! Requires the `write_protection_enable` and `read_protection_status` instructions.
|
||||
//! Return value of the `read_protection_status` instruction is checked against
|
||||
//! `block_lock.protection_enabled_mask` to test for success.
|
||||
status_t qspi_flash_write_protection_enable(QSPIFlash *dev);
|
||||
|
||||
//! Lock the given sector from write/erase operations.
|
||||
//! Sector locked with the `block_lock` instruction, and confirmed with `block_lock_status`
|
||||
//! If the `block_lock` instruction requires extra data, `block_lock.has_lock_data`
|
||||
//! and `block_lock.lock_data` can be used.
|
||||
//! When checking `block_lock_status`, the returned status value is
|
||||
//! compared against `block_lock.locked_check`
|
||||
status_t qspi_flash_lock_sector(QSPIFlash *dev, uint32_t addr);
|
||||
|
||||
//! Unlock all sectors so they can be written/erased.
|
||||
//! Operation is performed by the `block_unlock_all` instruction.
|
||||
status_t qspi_flash_unlock_all(QSPIFlash *dev);
|
37
src/fw/drivers/flash/qspi_flash_definitions.h
Normal file
37
src/fw/drivers/flash/qspi_flash_definitions.h
Normal file
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "qspi_flash_part_definitions.h"
|
||||
|
||||
#include "board/board.h"
|
||||
#include "drivers/qspi.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
typedef struct QSPIFlashState {
|
||||
QSPIFlashPart *part;
|
||||
bool coredump_mode;
|
||||
bool fast_read_ddr_enabled;
|
||||
} QSPIFlashState;
|
||||
|
||||
typedef const struct QSPIFlash {
|
||||
QSPIFlashState *state;
|
||||
QSPIPort *qspi;
|
||||
bool default_fast_read_ddr_enabled;
|
||||
OutputConfig reset_gpio;
|
||||
} QSPIFlash;
|
73
src/fw/drivers/flash/qspi_flash_part_definitions.h
Normal file
73
src/fw/drivers/flash/qspi_flash_part_definitions.h
Normal file
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
typedef const struct QSPIFlashPart {
|
||||
struct {
|
||||
uint8_t fast_read;
|
||||
uint8_t fast_read_ddr;
|
||||
uint8_t page_program;
|
||||
uint8_t erase_sector_4k;
|
||||
uint8_t erase_block_64k;
|
||||
uint8_t write_enable;
|
||||
uint8_t write_disable;
|
||||
uint8_t read_status;
|
||||
uint8_t read_flag_status;
|
||||
uint8_t erase_suspend;
|
||||
uint8_t erase_resume;
|
||||
uint8_t enter_low_power;
|
||||
uint8_t exit_low_power;
|
||||
uint8_t enter_quad_mode;
|
||||
uint8_t exit_quad_mode;
|
||||
uint8_t reset_enable;
|
||||
uint8_t reset;
|
||||
uint8_t qspi_id;
|
||||
uint8_t block_lock;
|
||||
uint8_t block_lock_status;
|
||||
uint8_t block_unlock_all;
|
||||
uint8_t write_protection_enable;
|
||||
uint8_t read_protection_status;
|
||||
} instructions;
|
||||
struct {
|
||||
uint8_t busy;
|
||||
uint8_t write_enable;
|
||||
} status_bit_masks;
|
||||
struct {
|
||||
uint8_t erase_suspend;
|
||||
} flag_status_bit_masks;
|
||||
struct {
|
||||
uint8_t fast_read;
|
||||
uint8_t fast_read_ddr;
|
||||
} dummy_cycles;
|
||||
struct {
|
||||
bool has_lock_data; //<! true ifdata needs to be send along with the block_lock instruction
|
||||
uint8_t lock_data; //<! The data to be sent on a block_lock command, if has_lock_data is true
|
||||
uint8_t locked_check; //<! Value block_lock_status instruction should return if sector is locked
|
||||
uint8_t protection_enabled_mask; //<! Mask read_protection_status instr to check if enabled
|
||||
} block_lock;
|
||||
uint32_t reset_latency_ms;
|
||||
uint32_t suspend_to_read_latency_us;
|
||||
uint32_t standby_to_low_power_latency_us;
|
||||
uint32_t low_power_to_standby_latency_us;
|
||||
bool supports_fast_read_ddr;
|
||||
uint32_t qspi_id_value;
|
||||
const char *name;
|
||||
} QSPIFlashPart;
|
||||
|
936
src/fw/drivers/flash/spansion_s29vs.c
Normal file
936
src/fw/drivers/flash/spansion_s29vs.c
Normal file
|
@ -0,0 +1,936 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "drivers/flash/flash_impl.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "drivers/gpio.h"
|
||||
#include "drivers/periph_config.h"
|
||||
#include "flash_region/flash_region.h"
|
||||
#include "system/passert.h"
|
||||
#include "system/logging.h"
|
||||
#include "kernel/util/delay.h"
|
||||
#include "util/math.h"
|
||||
#include "util/size.h"
|
||||
#include "util/units.h"
|
||||
|
||||
#define STM32F4_COMPATIBLE
|
||||
#include <mcu.h>
|
||||
|
||||
//! This is the memory mapped region that's mapped to the parallel flash.
|
||||
static const uintptr_t FMC_BANK_1_BASE_ADDRESS = 0x60000000;
|
||||
|
||||
//! This is the unit that we use for writing
|
||||
static const uint32_t PAGE_SIZE_BYTES = 64;
|
||||
|
||||
|
||||
//! Different commands we can send to the flash
|
||||
typedef enum S29VSCommand {
|
||||
S29VSCommand_WriteBufferLoad = 0x25,
|
||||
S29VSCommand_BufferToFlash = 0x29,
|
||||
S29VSCommand_EraseResume = 0x30,
|
||||
S29VSCommand_SectorBlank = 0x33,
|
||||
S29VSCommand_SectorLock = 0x60,
|
||||
S29VSCommand_SectorLockRangeArg = 0x61,
|
||||
S29VSCommand_ReadStatusRegister = 0x70,
|
||||
S29VSCommand_ClearStatusRegister = 0x71,
|
||||
S29VSCommand_EraseSetup = 0x80,
|
||||
S29VSCommand_DeviceIDEntry = 0x90,
|
||||
S29VSCommand_EraseSuspend = 0xB0,
|
||||
S29VSCommand_ConfigureRegisterEntry = 0xD0,
|
||||
S29VSCommand_SoftwareReset = 0xF0
|
||||
} S29VSCommand;
|
||||
|
||||
//! Arguments to the S29VSCommand_EraseSetup command
|
||||
typedef enum S29VSCommandEraseAguments {
|
||||
S29VSCommandEraseAguments_ChipErase = 0x10,
|
||||
S29VSCommandEraseAguments_SectorErase = 0x30
|
||||
} S29VSCommandEraseAguments;
|
||||
|
||||
//! The bitset stored in the status register, see prv_read_status_register
|
||||
typedef enum S29VSStatusBit {
|
||||
S29VSStatusBit_BankStatus = (1 << 0),
|
||||
S29VSStatusBit_SectorLockStatus = (1 << 1),
|
||||
S29VSStatusBit_ProgramSuspended = (1 << 2),
|
||||
// Bit 3 is reserved
|
||||
S29VSStatusBit_ProgramStatus = (1 << 4),
|
||||
S29VSStatusBit_EraseStatus = (1 << 5),
|
||||
S29VSStatusBit_EraseSuspended = (1 << 6),
|
||||
S29VSStatusBit_DeviceReady = (1 << 7),
|
||||
} S29VSStatusBit;
|
||||
static const uint16_t SPANSION_MANUFACTURER_ID = 0x01;
|
||||
static const uint16_t MACRONIX_MANUFACTURER_ID = 0xc2;
|
||||
static const GPIO_InitTypeDef s_default_at_flash_cfg = {
|
||||
.GPIO_Mode = GPIO_Mode_AF,
|
||||
.GPIO_Speed = GPIO_Speed_100MHz,
|
||||
.GPIO_OType = GPIO_OType_PP,
|
||||
.GPIO_PuPd = GPIO_PuPd_NOPULL
|
||||
};
|
||||
|
||||
static void prv_issue_command_argument(FlashAddress sector_address, uint16_t cmd_arg);
|
||||
static void prv_issue_command(FlashAddress sector_address, S29VSCommand cmd);
|
||||
|
||||
// puts gpios into or out of analog to save power when idle/in use respectively
|
||||
static void prv_flash_idle_gpios(bool enable_gpios) {
|
||||
static bool gpios_idled = false;
|
||||
if (gpios_idled == enable_gpios) {
|
||||
return;
|
||||
}
|
||||
gpios_idled = enable_gpios;
|
||||
|
||||
gpio_use(GPIOB);
|
||||
gpio_use(GPIOD);
|
||||
gpio_use(GPIOE);
|
||||
|
||||
GPIO_InitTypeDef gpio_init;
|
||||
if (enable_gpios) {
|
||||
gpio_init = s_default_at_flash_cfg;
|
||||
} else {
|
||||
gpio_init = (GPIO_InitTypeDef) {
|
||||
.GPIO_Mode = GPIO_Mode_AN,
|
||||
.GPIO_Speed = GPIO_Speed_2MHz,
|
||||
.GPIO_PuPd = GPIO_PuPd_NOPULL
|
||||
};
|
||||
}
|
||||
|
||||
// leave RESET_N and CE: they need to retain their state
|
||||
// Configure the rest as analog inputs to save as much power as possible
|
||||
// D2 - Reset - GPIO Reset line
|
||||
// D7 - FMC CE - FMC Chip Enable
|
||||
gpio_init.GPIO_Pin = GPIO_Pin_7;
|
||||
GPIO_Init(GPIOB, &gpio_init);
|
||||
|
||||
gpio_init.GPIO_Pin = GPIO_Pin_All & (~GPIO_Pin_2) & (~GPIO_Pin_7);
|
||||
GPIO_Init(GPIOD, &gpio_init);
|
||||
|
||||
gpio_init.GPIO_Pin = GPIO_Pin_All & (~GPIO_Pin_0) & (~GPIO_Pin_1);
|
||||
GPIO_Init(GPIOE, &gpio_init);
|
||||
|
||||
gpio_release(GPIOE);
|
||||
gpio_release(GPIOD);
|
||||
gpio_release(GPIOB);
|
||||
}
|
||||
|
||||
static uint32_t s_num_flash_uses = 0;
|
||||
|
||||
void flash_impl_use(void) {
|
||||
if (s_num_flash_uses == 0) {
|
||||
periph_config_enable(FMC_Bank1, RCC_AHB3Periph_FMC); // FIXME
|
||||
prv_flash_idle_gpios(true);
|
||||
}
|
||||
s_num_flash_uses++;
|
||||
}
|
||||
|
||||
void flash_impl_release_many(uint32_t num_locks) {
|
||||
PBL_ASSERTN(s_num_flash_uses >= num_locks);
|
||||
s_num_flash_uses -= num_locks;
|
||||
if (s_num_flash_uses == 0) {
|
||||
periph_config_disable(FMC_Bank1, RCC_AHB3Periph_FMC); // FIXME
|
||||
}
|
||||
}
|
||||
|
||||
void flash_impl_release(void) {
|
||||
flash_impl_release_many(1);
|
||||
}
|
||||
|
||||
static uint16_t flash_s29vs_read_short(FlashAddress addr) {
|
||||
return *((__IO uint16_t*)(FMC_BANK_1_BASE_ADDRESS + addr));
|
||||
}
|
||||
|
||||
FlashAddress flash_impl_get_sector_base_address(FlashAddress addr) {
|
||||
if (addr < BOTTOM_BOOT_REGION_END) {
|
||||
return addr & ~(BOTTOM_BOOT_SECTOR_SIZE - 1);
|
||||
}
|
||||
|
||||
return addr & ~(SECTOR_SIZE_BYTES - 1);
|
||||
}
|
||||
|
||||
FlashAddress flash_impl_get_subsector_base_address(FlashAddress addr) {
|
||||
return flash_impl_get_sector_base_address(addr);
|
||||
}
|
||||
|
||||
static uint8_t prv_read_status_register(FlashAddress sector_base_addr) {
|
||||
prv_issue_command(sector_base_addr, S29VSCommand_ReadStatusRegister);
|
||||
return flash_s29vs_read_short(sector_base_addr);
|
||||
}
|
||||
|
||||
static uint8_t prv_poll_for_ready(FlashAddress sector_base_addr) {
|
||||
// TODO: We should probably just assert if this takes too long
|
||||
uint8_t status;
|
||||
while (((status = prv_read_status_register(sector_base_addr)) &
|
||||
S29VSStatusBit_DeviceReady) == 0) {
|
||||
delay_us(10);
|
||||
}
|
||||
|
||||
return (status);
|
||||
}
|
||||
|
||||
//! Issue the second part of a two-cycle command. This is not merged with the
|
||||
//! prv_issue_command as not all commands have an argument.
|
||||
//!
|
||||
//! @param sector_address The address of the start of the sector to write the command to.
|
||||
//! @param cmd_arg The command argument to write.
|
||||
static void prv_issue_command_argument(FlashAddress sector_address, uint16_t cmd_arg) {
|
||||
// The offset in the sector we write the second part of commands to. Note that this is a 16-bit
|
||||
// word aligned address as opposed to a byte address.
|
||||
static const uint32_t COMMAND_ARGUMENT_ADDRESS = 0x2AA;
|
||||
|
||||
((__IO uint16_t*) (FMC_BANK_1_BASE_ADDRESS + sector_address))[COMMAND_ARGUMENT_ADDRESS] = cmd_arg;
|
||||
}
|
||||
|
||||
//! @param sector_address The address of the start of the sector to write the command to.
|
||||
//! @param cmd The command to write.
|
||||
static void prv_issue_command(FlashAddress sector_address, S29VSCommand cmd) {
|
||||
// The offset in the sector we write the first part of commands to. Note that this is a 16-bit
|
||||
// word aligned address as opposed to a byte address.
|
||||
static const uint32_t COMMAND_ADDRESS = 0x555;
|
||||
|
||||
((__IO uint16_t*) (FMC_BANK_1_BASE_ADDRESS + sector_address))[COMMAND_ADDRESS] = cmd;
|
||||
}
|
||||
|
||||
static void prv_software_reset(void) {
|
||||
prv_issue_command(0, S29VSCommand_SoftwareReset);
|
||||
}
|
||||
|
||||
// Note: If this command has been executed at least once, all sectors are
|
||||
// locked. They then must be unlocked before and relocked after each program
|
||||
// operation (i.e write or erase). The chip only allows for one sector to be
|
||||
// unlocked at any given time. For sector ranges which have been protected using
|
||||
// the "Sector Lock Range Command", this function will have no effect.
|
||||
static void prv_allow_write_if_sector_is_not_protected(bool lock, uint32_t sector_addr) {
|
||||
prv_issue_command(0, S29VSCommand_SectorLock);
|
||||
prv_issue_command_argument(0, S29VSCommand_SectorLock);
|
||||
|
||||
int lock_flag = (lock ? 0 : 1) << 7; // set A6 to 0 to lock and 1 to unlock
|
||||
((__IO uint16_t*) (FMC_BANK_1_BASE_ADDRESS + sector_addr + lock_flag))[0] =
|
||||
S29VSCommand_SectorLock;
|
||||
}
|
||||
|
||||
static uint16_t prv_read_manufacturer_id(void) {
|
||||
// Issue the DeviceIDEntry command to change to the ID-CFI Address Map. This means that reading from the bank will
|
||||
// give us ID-CFI information instead of the normal flash contents. See Table 11.2 (ID/CFI Data) for all the
|
||||
// content you can read here. Reset the state afterwards to return to the default address map.
|
||||
|
||||
flash_impl_use();
|
||||
prv_issue_command(0, S29VSCommand_DeviceIDEntry);
|
||||
uint16_t result = flash_s29vs_read_short(0x0);
|
||||
prv_software_reset();
|
||||
flash_impl_release();
|
||||
return result;
|
||||
}
|
||||
|
||||
static uint16_t prv_read_configuration_register(void) {
|
||||
prv_issue_command(0, S29VSCommand_ConfigureRegisterEntry);
|
||||
uint16_t result = flash_s29vs_read_short(0x0);
|
||||
prv_software_reset();
|
||||
return result;
|
||||
}
|
||||
|
||||
static void prv_write_configuration_register(uint16_t data) {
|
||||
// See section 5.8.1 of data sheet for command sequence
|
||||
prv_issue_command(0, S29VSCommand_ConfigureRegisterEntry);
|
||||
|
||||
// Cycle 1: SA+Address 555h & Data 25h
|
||||
// Cycle 2: SA+Address 2AAh & Data 00h
|
||||
// Cycle 3: SA+Address X00h & PD
|
||||
// Cycle 4: SA+ Address 555h & Data 29h
|
||||
prv_issue_command(0, S29VSCommand_WriteBufferLoad);
|
||||
prv_issue_command_argument(0, 0);
|
||||
((__IO uint16_t*) (FMC_BANK_1_BASE_ADDRESS))[0] = data;
|
||||
prv_issue_command(0, S29VSCommand_BufferToFlash);
|
||||
|
||||
prv_software_reset();
|
||||
}
|
||||
|
||||
// Use the "Sector Lock Range Command" (section 8.2 of data sheet) to block
|
||||
// writes or erases to the PRF image residing on the flash. The only way to undo
|
||||
// this is to issue a HW reset or pull power
|
||||
static void prv_flash_protect_range(uint32_t start_sector, uint32_t end_sector) {
|
||||
PBL_ASSERTN(start_sector <= end_sector);
|
||||
|
||||
flash_impl_use();
|
||||
|
||||
prv_issue_command(0, S29VSCommand_SectorLock);
|
||||
prv_issue_command_argument(0, S29VSCommand_SectorLock);
|
||||
|
||||
start_sector = flash_impl_get_sector_base_address(start_sector);
|
||||
end_sector = flash_impl_get_sector_base_address(end_sector);
|
||||
|
||||
((__IO uint16_t*) (FMC_BANK_1_BASE_ADDRESS + start_sector))[0] =
|
||||
S29VSCommand_SectorLockRangeArg;
|
||||
((__IO uint16_t*) (FMC_BANK_1_BASE_ADDRESS + end_sector))[0] =
|
||||
S29VSCommand_SectorLockRangeArg;
|
||||
|
||||
flash_impl_release();
|
||||
}
|
||||
|
||||
void flash_s29vs_hw_init(void) {
|
||||
// Configure the reset pin (D2)
|
||||
GPIO_InitTypeDef gpio_init = {
|
||||
.GPIO_Pin = GPIO_Pin_2,
|
||||
.GPIO_Mode = GPIO_Mode_OUT,
|
||||
.GPIO_Speed = GPIO_Speed_100MHz,
|
||||
.GPIO_OType = GPIO_OType_PP,
|
||||
.GPIO_PuPd = GPIO_PuPd_NOPULL
|
||||
};
|
||||
GPIO_Init(GPIOD, &gpio_init);
|
||||
|
||||
GPIO_WriteBit(GPIOD, GPIO_Pin_2, Bit_SET);
|
||||
|
||||
// Configure pins relating to the FMC peripheral (30 pins!)
|
||||
|
||||
// B7 - FMC AVD - FMC Address Valid aka Latch
|
||||
// D0-D1, D8-D15, E2-15 - FMC A, AD - FMC Address and Address/Data lines
|
||||
// D2 - Reset - GPIO Reset line
|
||||
// D3 - FMC CLK
|
||||
// D4 - FMC OE - FMC Output Enable
|
||||
// D5 - FMC WE - FMC Write Enable
|
||||
// D6 - FMC RDY - FMC Ready line
|
||||
// D7 - FMC CE - FMC Chip Enable
|
||||
|
||||
GPIO_PinAFConfig(GPIOB, GPIO_PinSource7, GPIO_AF_FMC);
|
||||
gpio_init = s_default_at_flash_cfg;
|
||||
gpio_init.GPIO_Pin = GPIO_Pin_7;
|
||||
GPIO_Init(GPIOB, &gpio_init);
|
||||
|
||||
for (uint8_t pin_source = 0; pin_source < 16; ++pin_source) {
|
||||
if (pin_source == 2) {
|
||||
continue;
|
||||
}
|
||||
GPIO_PinAFConfig(GPIOD, pin_source, GPIO_AF_FMC);
|
||||
}
|
||||
gpio_init.GPIO_Pin = GPIO_Pin_All & (~GPIO_Pin_2);
|
||||
GPIO_Init(GPIOD, &gpio_init);
|
||||
|
||||
for (uint8_t pin_source = 2; pin_source < 16; ++pin_source) {
|
||||
GPIO_PinAFConfig(GPIOE, pin_source, GPIO_AF_FMC);
|
||||
}
|
||||
gpio_init.GPIO_Pin = GPIO_Pin_All & (~GPIO_Pin_0) & (~GPIO_Pin_1);
|
||||
GPIO_Init(GPIOE, &gpio_init);
|
||||
|
||||
// We have configured the pins, lets perform a full HW reset to put the chip
|
||||
// in a good state
|
||||
GPIO_WriteBit(GPIOD, GPIO_Pin_2, Bit_RESET);
|
||||
delay_us(10); // only needs to be 50ns according to data sheet
|
||||
GPIO_WriteBit(GPIOD, GPIO_Pin_2, Bit_SET);
|
||||
delay_us(30); // need 200ns + 10us before CE can be pulled low
|
||||
|
||||
flash_impl_set_burst_mode(false);
|
||||
}
|
||||
|
||||
static void prv_flash_reset(void) {
|
||||
s_num_flash_uses = 0;
|
||||
gpio_use(GPIOB);
|
||||
gpio_use(GPIOD);
|
||||
gpio_use(GPIOE);
|
||||
flash_impl_use();
|
||||
|
||||
flash_s29vs_hw_init();
|
||||
|
||||
flash_impl_release();
|
||||
gpio_release(GPIOE);
|
||||
gpio_release(GPIOD);
|
||||
gpio_release(GPIOB);
|
||||
}
|
||||
|
||||
void flash_impl_enable_write_protection(void) {
|
||||
}
|
||||
|
||||
// Protects start_sector - end_sector, inclusive, from any kind of program
|
||||
// operation
|
||||
status_t flash_impl_write_protect(FlashAddress start_sector,
|
||||
FlashAddress end_sector) {
|
||||
prv_flash_reset();
|
||||
prv_flash_protect_range(start_sector, end_sector);
|
||||
return S_SUCCESS;
|
||||
}
|
||||
|
||||
status_t flash_impl_unprotect(void) {
|
||||
// The only way to undo sector protection is to pull power from the chip or
|
||||
// issue a hardware reset
|
||||
prv_flash_reset();
|
||||
return S_SUCCESS;
|
||||
}
|
||||
|
||||
status_t flash_impl_init(bool coredump_mode) {
|
||||
// Don't need to do anything to enable coredump mode.
|
||||
|
||||
prv_flash_reset();
|
||||
return S_SUCCESS;
|
||||
}
|
||||
|
||||
status_t flash_impl_get_erase_status(void) {
|
||||
flash_impl_use();
|
||||
uint8_t status = prv_read_status_register(0);
|
||||
flash_impl_release();
|
||||
|
||||
if ((status & S29VSStatusBit_DeviceReady) == 0) return E_BUSY;
|
||||
if ((status & S29VSStatusBit_EraseSuspended) != 0) return E_AGAIN;
|
||||
if ((status & S29VSStatusBit_EraseStatus) != 0) return E_ERROR;
|
||||
return S_SUCCESS;
|
||||
}
|
||||
|
||||
status_t flash_impl_erase_subsector_begin(FlashAddress subsector_addr) {
|
||||
return flash_impl_erase_sector_begin(subsector_addr);
|
||||
}
|
||||
|
||||
status_t flash_impl_erase_sector_begin(FlashAddress sector_addr) {
|
||||
status_t result = E_UNKNOWN;
|
||||
|
||||
// FIXME: We should just assert that the address is already aligned. If
|
||||
// someone is depending on this behaviour without already knowing the range
|
||||
// that's being erased they're going to have a bad time. This will probably
|
||||
// cause some client fallout though, so tackle this later.
|
||||
sector_addr = flash_impl_get_sector_base_address(sector_addr);
|
||||
|
||||
flash_impl_use();
|
||||
prv_issue_command(sector_addr, S29VSCommand_ClearStatusRegister);
|
||||
|
||||
// Some sanity checks
|
||||
{
|
||||
status_t error = S_SUCCESS;
|
||||
const uint8_t sr = prv_read_status_register(sector_addr);
|
||||
if ((sr & S29VSStatusBit_DeviceReady) == 0) {
|
||||
// Another operation is already in progress.
|
||||
error = E_BUSY;
|
||||
} else if (sr & S29VSStatusBit_EraseSuspended) {
|
||||
// Cannot program while another program operation is suspended.
|
||||
error = E_INVALID_OPERATION;
|
||||
}
|
||||
if (FAILED(error)) {
|
||||
result = error;
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
|
||||
prv_allow_write_if_sector_is_not_protected(false, sector_addr);
|
||||
|
||||
prv_issue_command(sector_addr, S29VSCommand_EraseSetup);
|
||||
prv_issue_command_argument(sector_addr,
|
||||
S29VSCommandEraseAguments_SectorErase);
|
||||
prv_allow_write_if_sector_is_not_protected(true, sector_addr);
|
||||
|
||||
// Check the status register to make sure that the erase has started.
|
||||
const uint8_t sr = prv_read_status_register(sector_addr);
|
||||
if ((sr & S29VSStatusBit_DeviceReady) == 0) {
|
||||
// Program or erase operation in progress. Is it in the current bank?
|
||||
result = ((sr & S29VSStatusBit_BankStatus) == 0)? S_SUCCESS : E_BUSY;
|
||||
} else {
|
||||
// Operation hasn't started. Something is wrong.
|
||||
if (sr & S29VSStatusBit_SectorLockStatus) {
|
||||
// Sector is write-protected.
|
||||
result = E_INVALID_OPERATION;
|
||||
} else if (sr & S29VSStatusBit_EraseStatus) {
|
||||
// Erase failed for some reason.
|
||||
result = E_ERROR;
|
||||
} else {
|
||||
// The erase has either completed in the time between starting the erase
|
||||
// and polling the status register, or the erase was never started. The
|
||||
// former case could be due to a context switch at the worst time and
|
||||
// subsequent task starvation, or being run in QEMU. The latter could be
|
||||
// due to a software bug or hardware failure. It would be possible to tell
|
||||
// the two situations apart by performing a blank check, but that takes
|
||||
// more time than a nonblocking erase should require. Let the upper layers
|
||||
// verify that the erase succeeded if they care about it.
|
||||
result = S_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
done:
|
||||
flash_impl_release();
|
||||
return result;
|
||||
}
|
||||
|
||||
status_t flash_impl_erase_suspend(FlashAddress sector_addr) {
|
||||
status_t status = E_INTERNAL;
|
||||
sector_addr = flash_impl_get_sector_base_address(sector_addr);
|
||||
flash_impl_use();
|
||||
const uint8_t sr = prv_read_status_register(sector_addr);
|
||||
// Is an operation in progress?
|
||||
if ((sr & S29VSStatusBit_DeviceReady) != 0) {
|
||||
// No erase in progress to suspend. Maybe the erase completed before this
|
||||
// call.
|
||||
status = S_NO_ACTION_REQUIRED;
|
||||
} else if ((sr & S29VSStatusBit_BankStatus) != 0) {
|
||||
// Operation is in a different bank than the given address.
|
||||
status = E_INVALID_ARGUMENT;
|
||||
} else {
|
||||
// All clear.
|
||||
prv_issue_command(sector_addr, S29VSCommand_EraseSuspend);
|
||||
if (prv_poll_for_ready(sector_addr) & S29VSStatusBit_EraseSuspended) {
|
||||
status = S_SUCCESS;
|
||||
} else {
|
||||
// The erase must have completed between the status register read and
|
||||
// the EraseSuspend command.
|
||||
status = S_NO_ACTION_REQUIRED;
|
||||
}
|
||||
}
|
||||
flash_impl_release();
|
||||
return status;
|
||||
}
|
||||
|
||||
status_t flash_impl_erase_resume(FlashAddress sector_addr) {
|
||||
status_t status = E_INTERNAL;
|
||||
sector_addr = flash_impl_get_sector_base_address(sector_addr);
|
||||
flash_impl_use();
|
||||
uint8_t sr = prv_read_status_register(sector_addr);
|
||||
if ((sr & S29VSStatusBit_DeviceReady) != 0 &&
|
||||
(sr & S29VSStatusBit_EraseSuspended) != 0) {
|
||||
prv_issue_command(sector_addr, S29VSCommand_EraseResume);
|
||||
status = S_SUCCESS;
|
||||
} else {
|
||||
// Device busy or no suspended erase to resume.
|
||||
status = E_INVALID_OPERATION;
|
||||
}
|
||||
flash_impl_release();
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
// It is dangerous to leave this built in by default.
|
||||
#if 0
|
||||
status_t flash_impl_erase_bulk_begin(void) {
|
||||
flash_s29vs_use();
|
||||
|
||||
prv_issue_command(0, S29VSCommand_EraseSetup);
|
||||
prv_issue_command_argument(0, S29VSCommandEraseAguments_ChipErase);
|
||||
|
||||
flash_s29vs_release();
|
||||
}
|
||||
#endif
|
||||
|
||||
static void prv_read_words_pio(uint16_t* buffer, uint16_t* flash_data_region,
|
||||
uint32_t num_words) {
|
||||
for (uint32_t words_read = 0; words_read < num_words; words_read++) {
|
||||
buffer[words_read] = flash_data_region[words_read];
|
||||
}
|
||||
}
|
||||
|
||||
// Currently this implementation reads halfwords at a time (16-bits). Burst
|
||||
// length is currently 1 for synchronous reads. This can be optimized in future
|
||||
// to do larger burst sizes and/or unrolling larger transfer sizes into 32-bit
|
||||
// reads.
|
||||
status_t flash_impl_read_sync(void *buffer_ptr, FlashAddress start_addr,
|
||||
size_t buffer_size) {
|
||||
uint8_t *buffer = buffer_ptr;
|
||||
flash_impl_use();
|
||||
|
||||
uint32_t flash_data_addr = (FMC_BANK_1_BASE_ADDRESS + start_addr);
|
||||
bool odd_start_addr = ((start_addr % 2) == 1);
|
||||
uint32_t bytes_read = 0;
|
||||
|
||||
uint16_t* buff_ptr = (uint16_t *)&buffer[bytes_read];
|
||||
if (odd_start_addr) {
|
||||
// read first byte into a temporary buffer but read from source on aligned word boundary
|
||||
uint16_t temp_buffer = *(__IO uint16_t *)(flash_data_addr-1);
|
||||
buffer[bytes_read++] = (uint8_t)((temp_buffer >> 8) & 0xFF);
|
||||
}
|
||||
|
||||
// At this point, flash_data_addr is now halfword aligned
|
||||
buff_ptr = (uint16_t *)&buffer[bytes_read];
|
||||
bool odd_buff_addr = ((((uint32_t)buff_ptr) % 2) == 1);
|
||||
if (buffer_size - bytes_read >= 2) {
|
||||
// if at least one halfword to read
|
||||
if (!odd_buff_addr) {
|
||||
// Both flash_data_addr and buffer are aligned
|
||||
uint32_t num_words = (buffer_size - bytes_read) / 2;
|
||||
prv_read_words_pio(buff_ptr, (uint16_t*)(flash_data_addr + bytes_read), num_words);
|
||||
bytes_read += num_words*2;
|
||||
} else {
|
||||
// Not aligned - read into temporary buffer and copy over
|
||||
__IO uint16_t *flash_data_region = (__IO uint16_t*)(flash_data_addr + bytes_read);
|
||||
uint32_t num_words = (buffer_size - bytes_read) / 2;
|
||||
for (uint32_t words_read = 0; words_read < num_words; words_read++) {
|
||||
uint16_t temp_buffer = flash_data_region[words_read];
|
||||
buffer[bytes_read++] = (uint8_t)(temp_buffer & 0xFF);
|
||||
buffer[bytes_read++] = (uint8_t)((temp_buffer >> 8) & 0xFF);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buff_ptr = (uint16_t *)&buffer[bytes_read];
|
||||
// See if there are any remaining bytes left - at this point - flash_data_addr is still halfword aligned
|
||||
if (buffer_size - bytes_read == 1) {
|
||||
uint16_t temp_buffer = *(__IO uint16_t *)(flash_data_addr + bytes_read);
|
||||
buffer[bytes_read++] = (uint8_t)(temp_buffer & 0xFF);
|
||||
} else if (buffer_size - bytes_read != 0) {
|
||||
// Should not reach here
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Invalid data length read");
|
||||
}
|
||||
|
||||
flash_impl_release();
|
||||
|
||||
return S_SUCCESS;
|
||||
}
|
||||
|
||||
int flash_impl_write_page_begin(const void *vp_buffer,
|
||||
const FlashAddress start_addr, size_t len) {
|
||||
if (!len) {
|
||||
return E_INVALID_ARGUMENT;
|
||||
}
|
||||
const uint8_t *buffer = vp_buffer;
|
||||
// Flash write transactions can only write one page at a time, where each
|
||||
// page is 64 bytes in size. Split up our transactions into pages and then
|
||||
// write one page.
|
||||
const uint32_t offset_in_page = start_addr % PAGE_SIZE_BYTES;
|
||||
const uint32_t bytes_in_page = MIN(PAGE_SIZE_BYTES - offset_in_page, len);
|
||||
|
||||
// We're only allowed to write whole 16-bit words during a write operation.
|
||||
// Therefore we'll need to pad out our write if it's not perfectly aligned at
|
||||
// the start or the end.
|
||||
int num_shorts = bytes_in_page / 2;
|
||||
|
||||
// 4 cases
|
||||
// Perfectly aligned - No additional writes
|
||||
// Unaligned start, even length - Need to pad both ends
|
||||
// Unaligned start, odd length - Pad the start
|
||||
// Aligned start, odd length - Pad the end
|
||||
if (start_addr & 0x1 || bytes_in_page & 0x1) {
|
||||
++num_shorts;
|
||||
}
|
||||
|
||||
const FlashAddress sector_addr =
|
||||
flash_impl_get_sector_base_address(start_addr);
|
||||
|
||||
flash_impl_use();
|
||||
prv_issue_command(sector_addr, S29VSCommand_ClearStatusRegister);
|
||||
|
||||
// Some sanity checks
|
||||
{
|
||||
status_t error = S_SUCCESS;
|
||||
const uint8_t sr = prv_read_status_register(sector_addr);
|
||||
if ((sr & S29VSStatusBit_DeviceReady) == 0) {
|
||||
// Another operation is already in progress.
|
||||
error = E_BUSY;
|
||||
} else if (sr & S29VSStatusBit_ProgramSuspended) {
|
||||
// Cannot program while another program operation is suspended.
|
||||
error = E_INVALID_OPERATION;
|
||||
}
|
||||
if (FAILED(error)) {
|
||||
flash_impl_release();
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
prv_allow_write_if_sector_is_not_protected(false, sector_addr);
|
||||
prv_issue_command(sector_addr, S29VSCommand_WriteBufferLoad);
|
||||
prv_issue_command_argument(sector_addr, num_shorts - 1);
|
||||
|
||||
// We're now ready to write the words. Subsequent writes to the sector will
|
||||
// actually write the data through to the write buffer.
|
||||
|
||||
__IO uint16_t *flash_write_dest = (__IO uint16_t*)
|
||||
(FMC_BANK_1_BASE_ADDRESS + (start_addr & ~0x1));
|
||||
uint32_t bytes_remaining = bytes_in_page;
|
||||
|
||||
// Handle leading byte
|
||||
if (start_addr & 0x1) {
|
||||
// Handle a buffer with an unaligned start. Write 0xff for the first byte
|
||||
// since flash can only flip ones to zeros, and no data will be lost.
|
||||
const uint16_t first_short_value = 0xFF | ((*buffer) << 8);
|
||||
*flash_write_dest = first_short_value;
|
||||
|
||||
// Now for the rest of the function let's pretend this never happened.
|
||||
++flash_write_dest;
|
||||
++buffer;
|
||||
--bytes_remaining;
|
||||
}
|
||||
|
||||
// Handle body words
|
||||
for (; bytes_remaining >= 2; bytes_remaining -= 2, buffer += 2) {
|
||||
uint16_t buffer_word;
|
||||
memcpy(&buffer_word, buffer, sizeof buffer_word);
|
||||
*flash_write_dest++ = buffer_word;
|
||||
}
|
||||
|
||||
// Handle trailing byte if present. This will be present if we started out
|
||||
// aligned and we wrote an odd number of bytes or if we started out unaligned
|
||||
// and wrote an even number of bytes.
|
||||
if (bytes_remaining) {
|
||||
// We need to write only a single byte, but we're only allowed to write
|
||||
// words. If we write a single byte followed by 0xFFFF, we won't modify the
|
||||
// second byte as bits are only allowed to be written from 1 -> 0. 1s will
|
||||
// stay 1s, and 0s will stay 0s.
|
||||
const uint16_t trailing_short_value = *buffer | 0xFF00;
|
||||
*flash_write_dest = trailing_short_value;
|
||||
}
|
||||
|
||||
// Buffer writing is complete, issue the buffer to flash command to actually
|
||||
// commit the changes to memory.
|
||||
prv_issue_command(sector_addr, S29VSCommand_BufferToFlash);
|
||||
|
||||
// Check the status register to make sure that the write has started.
|
||||
status_t result = E_UNKNOWN;
|
||||
const uint8_t sr = prv_read_status_register(sector_addr);
|
||||
if ((sr & S29VSStatusBit_DeviceReady) == 0) {
|
||||
// Program or erase operation in progress. Is it in the current bank?
|
||||
result = ((sr & S29VSStatusBit_BankStatus) == 0)? S_SUCCESS : E_BUSY;
|
||||
} else {
|
||||
// Operation hasn't started. Something is wrong.
|
||||
if (sr & S29VSStatusBit_SectorLockStatus) {
|
||||
// Sector is write-protected.
|
||||
result = E_INVALID_OPERATION;
|
||||
} else if (sr & S29VSStatusBit_ProgramStatus) {
|
||||
// Programming failed for some reason.
|
||||
result = E_ERROR;
|
||||
} else {
|
||||
// The flash never appeared to go busy and there is no error. Either the
|
||||
// flash write completed between the write command and the status register
|
||||
// read (inopportune context switch or running in QEMU), or the write
|
||||
// never started. It's possible to tell them apart by validating that the
|
||||
// data was actually written to flash, but that adds even more complexity
|
||||
// to this function. Let the upper layers verify that the write succeeded
|
||||
// if they are concerned about reliability.
|
||||
result = S_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
prv_allow_write_if_sector_is_not_protected(true, sector_addr);
|
||||
flash_impl_release();
|
||||
return FAILED(result) ? result : (int)bytes_in_page;
|
||||
}
|
||||
|
||||
status_t flash_impl_get_write_status(void) {
|
||||
flash_impl_use();
|
||||
const uint8_t status = prv_read_status_register(0);
|
||||
flash_impl_release();
|
||||
|
||||
if ((status & S29VSStatusBit_DeviceReady) == 0) return E_BUSY;
|
||||
if ((status & S29VSStatusBit_ProgramSuspended) != 0) return E_AGAIN;
|
||||
if ((status & S29VSStatusBit_ProgramStatus) != 0) return E_ERROR;
|
||||
return S_SUCCESS;
|
||||
}
|
||||
|
||||
uint8_t pbl_28517_flash_impl_get_status_register(uint32_t sector_addr) {
|
||||
flash_impl_use();
|
||||
|
||||
const FlashAddress base_addr = flash_impl_get_sector_base_address(sector_addr);
|
||||
const uint8_t status = prv_read_status_register(base_addr);
|
||||
|
||||
flash_impl_release();
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
status_t flash_impl_enter_low_power_mode(void) {
|
||||
prv_flash_idle_gpios(false);
|
||||
return S_SUCCESS;
|
||||
}
|
||||
|
||||
status_t flash_impl_exit_low_power_mode(void) {
|
||||
// it's ok to access s_num_flash_uses here directly, as only caller enter_stop_mode() is called
|
||||
// only while interrupts are disabled
|
||||
prv_flash_idle_gpios(s_num_flash_uses > 0);
|
||||
return S_SUCCESS;
|
||||
}
|
||||
|
||||
static void prv_switch_flash_mode(FMC_NORSRAMInitTypeDef *nor_init) {
|
||||
FMC_NORSRAMCmd(FMC_Bank1_NORSRAM1, DISABLE);
|
||||
FMC_NORSRAMInit(nor_init);
|
||||
FMC_NORSRAMCmd(FMC_Bank1_NORSRAM1, ENABLE);
|
||||
}
|
||||
|
||||
static uint16_t prv_get_num_wait_cycles(uint32_t flash_clock_freq) {
|
||||
// wait_cycle table based on frequency (table 7.1)
|
||||
// NOTE: 27MHZ frequency skipped due to data latency being 4 smaller than the wait_cycle
|
||||
uint32_t wait_cycle[] = {
|
||||
40000000 ,
|
||||
54000000 ,
|
||||
66000000 ,
|
||||
80000000 ,
|
||||
95000000 ,
|
||||
104000000 ,
|
||||
120000000
|
||||
};
|
||||
// find number wait states based on table
|
||||
uint32_t wait_state;
|
||||
for (wait_state = 4; wait_state < (ARRAY_LENGTH(wait_cycle) + 4); wait_state++) {
|
||||
if (flash_clock_freq < wait_cycle[wait_state-4]) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return wait_state;
|
||||
}
|
||||
|
||||
status_t flash_impl_set_burst_mode(bool burst_mode) {
|
||||
const uint32_t MAX_FREQ = MHZ_TO_HZ(108); // max frequency of the flash 108MHZ
|
||||
const uint32_t TAVDP_MIN = 60; // min addr setup time in tenths of ns
|
||||
const uint32_t TADVO_MIN = 40; // min addr hold time in tenths
|
||||
|
||||
const uint32_t SETUP_STEP = MHZ_TO_HZ(16); // for data setup equation
|
||||
|
||||
const uint16_t WAIT_STATE_MASK = 0x7800; // mask for wait state binary for sync burst
|
||||
|
||||
flash_impl_use();
|
||||
|
||||
// get system clock tick speed
|
||||
RCC_ClocksTypeDef clocks;
|
||||
RCC_GetClocksFreq(&clocks);
|
||||
uint32_t h_clock = clocks.HCLK_Frequency; // frequency in hertz
|
||||
uint32_t time_per_cycle = ((uint64_t)(10000000000)) / h_clock; // period in 1/10th ns
|
||||
|
||||
FMC_NORSRAMTimingInitTypeDef nor_timing_init = {
|
||||
// time between address write and address latch (AVD high)
|
||||
// tAAVDS on datasheet, min 4 ns
|
||||
//
|
||||
// AVD low time
|
||||
// tAVDP on datasheet, min 6 ns
|
||||
.FMC_AddressSetupTime = (TAVDP_MIN / time_per_cycle) + 1, // give setup of min 6ns
|
||||
|
||||
// time between AVD high (address is available) and OE low (memory can write)
|
||||
// tAVDO on the datasheet, min 4 ns
|
||||
.FMC_AddressHoldTime = (TADVO_MIN / time_per_cycle) + 1, // gives hold of min 4ns
|
||||
|
||||
// time between OE low (memory can write) and valid data being available
|
||||
// FIXME: optimize this equation
|
||||
// current linear equation has slope of 1 cycle/SETUP_STEP, with initial value 1
|
||||
// setupTime based on h_clock frequency
|
||||
// equation derived from existing working values; 5 at 64Mhz, 8 at 128 Mhz
|
||||
// the data was then interpolated into a line, with a padded value of 1
|
||||
.FMC_DataSetupTime = (h_clock / SETUP_STEP) + 1,
|
||||
|
||||
// Time between chip selects
|
||||
// not on the datasheet, picked a random safe number
|
||||
// FIXME: at high bus frequencies, more than one cycle may be needed
|
||||
.FMC_BusTurnAroundDuration = 1, // TODO: actually ok? See back-to-back Read/Write Cycle
|
||||
|
||||
.FMC_CLKDivision = 15, // Not used for async NOR
|
||||
.FMC_DataLatency = 15, // Not used for async NOR
|
||||
.FMC_AccessMode = FMC_AccessMode_A // Only used for ExtendedMode == FMC_ExtendedMode_Enable, which we don't use
|
||||
};
|
||||
|
||||
FMC_NORSRAMInitTypeDef nor_init = {
|
||||
.FMC_Bank = FMC_Bank1_NORSRAM1,
|
||||
.FMC_DataAddressMux = FMC_DataAddressMux_Enable,
|
||||
.FMC_MemoryType = FMC_MemoryType_NOR,
|
||||
.FMC_MemoryDataWidth = FMC_NORSRAM_MemoryDataWidth_16b,
|
||||
.FMC_BurstAccessMode = FMC_BurstAccessMode_Disable,
|
||||
.FMC_AsynchronousWait = FMC_AsynchronousWait_Disable,
|
||||
.FMC_WaitSignalPolarity = FMC_WaitSignalPolarity_Low,
|
||||
.FMC_WrapMode = FMC_WrapMode_Disable,
|
||||
.FMC_WaitSignalActive = FMC_WaitSignalActive_BeforeWaitState,
|
||||
.FMC_WriteOperation = FMC_WriteOperation_Enable,
|
||||
.FMC_WaitSignal = FMC_WaitSignal_Enable,
|
||||
.FMC_ExtendedMode = FMC_ExtendedMode_Disable,
|
||||
.FMC_WriteBurst = FMC_WriteBurst_Disable,
|
||||
.FMC_ContinousClock = FMC_CClock_SyncOnly,
|
||||
.FMC_ReadWriteTimingStruct = &nor_timing_init
|
||||
};
|
||||
|
||||
// configure the peripheral before we try to read from it
|
||||
prv_switch_flash_mode(&nor_init);
|
||||
|
||||
uint16_t configuration_register = prv_read_configuration_register();
|
||||
// clear bits that are about to be set
|
||||
configuration_register &= 0x0278; // clear bits [15:10], [8:7], [2:0]
|
||||
|
||||
// add one. This way, if (h_clock < MAX_FREQ), only divide by one (use h_clock as is)
|
||||
// else divide by whatever is needed to be under MAX_FREQ
|
||||
uint32_t clk_division = (h_clock / (MAX_FREQ + 1)) + 1;
|
||||
|
||||
// Update necessary parameters for synchronous modes
|
||||
if (burst_mode) {
|
||||
nor_init.FMC_BurstAccessMode = FMC_BurstAccessMode_Enable;
|
||||
nor_init.FMC_WaitSignalActive = FMC_WaitSignalActive_DuringWaitState;
|
||||
|
||||
nor_timing_init.FMC_BusTurnAroundDuration = 1;
|
||||
|
||||
// nor_timing_init.FMC_DataSetupTime = 1; // FIXME: originally set to 1 for 64Mhz
|
||||
// but sync burst was not working at this value;
|
||||
// commented out so the DataSetupTime for ASYNC (up above) is used instead
|
||||
// this is to ensure sync_burst works with dynamic changes to h_clk frequency
|
||||
|
||||
nor_timing_init.FMC_CLKDivision = clk_division; // divide h_clock if h_clock > 108MHZ
|
||||
|
||||
uint16_t wait_state = prv_get_num_wait_cycles(h_clock / clk_division);
|
||||
// testing shows that a difference of 4 needs to be maintained between wait_state and latency
|
||||
nor_timing_init.FMC_DataLatency = wait_state - 4;
|
||||
|
||||
// Set bits according to value needed - see Table 7.11 in data sheet
|
||||
// [15] Device Read Mode 0b0 Synchronous Read Mode
|
||||
// [14:11] Programmable Read Wait States 0bXXXX N wait cycles, wait states set to (N - 2)
|
||||
// [10] RDY Polarity 0b1 RDY signal is active high (default)
|
||||
// [8] RDY Timing 0b0 RDY active once cycle before data (default)
|
||||
// [7] Output Drive Strength 0b0 Full Drive=Current Driver Strength (default)
|
||||
// [2:0] Burst Length 0b000 Continuous (default)
|
||||
configuration_register |= 0x400 | (((wait_state - 2) << 11) & (WAIT_STATE_MASK));
|
||||
} else {
|
||||
// Set bits according to value needed - see Table 7.11 in data sheet
|
||||
// [15] Device Read Mode 0b1 Asynchronous Read Mode
|
||||
// [14:11] Programmable Read Wait States 0b1011 13 wait cycles (default)
|
||||
// [10] RDY Polarity 0b1 RDY signal is active high (default)
|
||||
// [8] RDY Timing 0b1 RDY active with data (default)
|
||||
// [7] Output Drive Strength 0b0 Full Drive=Current Driver Strength (default)
|
||||
// [2:0] Burst Length 0b000 Continuous (default)
|
||||
configuration_register |= 0xDD00;
|
||||
}
|
||||
|
||||
prv_write_configuration_register(configuration_register);
|
||||
|
||||
prv_switch_flash_mode(&nor_init);
|
||||
|
||||
prv_poll_for_ready(0);
|
||||
flash_impl_release();
|
||||
|
||||
return S_SUCCESS;
|
||||
}
|
||||
|
||||
status_t flash_impl_blank_check_sector(FlashAddress addr) {
|
||||
// FIXME: Blank check operation is only allowed in asynchronous mode. Fall
|
||||
// back to a software blank check in synchronous mode.
|
||||
const FlashAddress base_addr = flash_impl_get_sector_base_address(addr);
|
||||
status_t ret = E_INTERNAL;
|
||||
|
||||
flash_impl_use();
|
||||
uint8_t status = prv_read_status_register(base_addr);
|
||||
if ((status & S29VSStatusBit_DeviceReady) == 0 ||
|
||||
(status & (S29VSStatusBit_EraseSuspended |
|
||||
S29VSStatusBit_ProgramSuspended)) != 0) {
|
||||
ret = E_BUSY;
|
||||
goto done;
|
||||
}
|
||||
|
||||
prv_issue_command(base_addr, S29VSCommand_SectorBlank);
|
||||
status = prv_poll_for_ready(base_addr);
|
||||
ret = ((status & S29VSStatusBit_EraseStatus) == 0)? S_TRUE : S_FALSE;
|
||||
|
||||
done:
|
||||
flash_impl_release();
|
||||
return ret;
|
||||
}
|
||||
|
||||
status_t flash_impl_blank_check_subsector(FlashAddress addr) {
|
||||
return flash_impl_blank_check_sector(addr);
|
||||
}
|
||||
|
||||
|
||||
bool flash_check_whoami(void) {
|
||||
uint16_t manufacturer_id = prv_read_manufacturer_id();
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Flash Manufacturer ID: 0x%"PRIx16, manufacturer_id);
|
||||
|
||||
return manufacturer_id == SPANSION_MANUFACTURER_ID ||
|
||||
manufacturer_id == MACRONIX_MANUFACTURER_ID;
|
||||
}
|
||||
|
||||
uint32_t flash_impl_get_typical_sector_erase_duration_ms(void) {
|
||||
return 800;
|
||||
}
|
||||
|
||||
uint32_t flash_impl_get_typical_subsector_erase_duration_ms(void) {
|
||||
return 800;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue