Import of the watch repository from Pebble

This commit is contained in:
Matthieu Jeanson 2024-12-12 16:43:03 -08:00 committed by Katharine Berry
commit 3b92768480
10334 changed files with 2564465 additions and 0 deletions

View 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, &reg_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, &reg_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