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,39 @@
/*
* 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 "process_management/pebble_process_md.h"
#include "mfg/mfg_mode/mfg_factory_mode.h"
#include "mfg/mfg_apps/mfg_display_burnin.h"
#include "mfg/mfg_apps/mfg_button_app.h"
#include "mfg/mfg_apps/mfg_bt_test_app.h"
#include "mfg/mfg_apps/mfg_display_app.h"
#include "mfg/mfg_apps/mfg_runin_app.h"
#include "mfg/mfg_apps/mfg_func_test.h"
typedef const struct PebbleProcessMd* (*MfgInitFuncType)(void);
static const MfgInitFuncType INIT_MFG_FUNCTIONS[] = {
&mfg_app_button_test_get_info,
&mfg_app_runin_get_info,
&mfg_display_burnin_get_app_info,
&mfg_app_bt_test_get_info,
&mfg_app_lcd_test_black_get_info,
&mfg_app_lcd_test_white_get_info,
&mfg_app_lcd_test_black_white_border_get_info,
&mfg_app_lcd_test_cycle_get_info,
&mfg_func_test_get_app_info,
};

View file

@ -0,0 +1,225 @@
/*
* 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 "mfg_bt_test_app.h"
#include "applib/app.h"
#include "applib/fonts/fonts.h"
#include "applib/ui/ui.h"
#include "applib/ui/window_private.h"
#include "process_state/app_state/app_state.h"
#include "process_management/process_manager.h"
#include "kernel/pbl_malloc.h"
#include "kernel/util/sleep.h"
#include "services/common/bluetooth/bt_compliance_tests.h"
#include "services/common/new_timer/new_timer.h"
#include "services/common/system_task.h"
#include "system/passert.h"
#include "system/reboot_reason.h"
#include "system/reset.h"
EventServiceInfo bt_state_change_event_info;
typedef enum {
BtTestStateInit = 0,
BtTestStateStopped,
BtTestStateStarting,
BtTestStateStopping,
BtTestStateStarted,
BtTestStateFailed,
BtTestStateResetting,
BtTestStateNumStates,
} BtTestState;
static const char* status_text[] = {
[BtTestStateInit] = "Initializing",
[BtTestStateStopped] = "Stopped",
[BtTestStateStarting] = "Starting",
[BtTestStateStopping] = "Stopping",
[BtTestStateStarted] = "Started",
[BtTestStateFailed] = "Failed",
[BtTestStateResetting] = "Resetting",
};
typedef struct {
Window window;
TextLayer title;
TextLayer status;
BtTestState test_state;
TimerID reset_timer;
} AppData;
static void update_text_layers_callback(void *data) {
AppData *app_data = data;
TextLayer *status = &app_data->status;
text_layer_set_text(status, status_text[app_data->test_state]);
}
static void prv_bt_event_handler(PebbleEvent *e, void* data) {
AppData *app_data = (AppData*)data;
switch (app_data->test_state) {
case BtTestStateStarting: {
PBL_ASSERTN(!bt_ctl_is_bluetooth_active());
if (bt_test_bt_sig_rf_test_mode()) {
app_data->test_state = BtTestStateStarted;
} else {
app_data->test_state = BtTestStateFailed;
}
break;
}
case BtTestStateStopping: {
PBL_ASSERTN(bt_ctl_is_bluetooth_active());
app_data->test_state = BtTestStateStopped;
break;
}
case BtTestStateStopped:
case BtTestStateStarted:
break;
default:
WTF;
}
process_manager_send_callback_event_to_process(PebbleTask_App, update_text_layers_callback, (void*)data);
}
static void select_single_click_handler(ClickRecognizerRef recognizer, Window *window) {
AppData *data = app_state_get_user_data();
BtTestState new_state = data->test_state;
PBL_ASSERTN(data->test_state < BtTestStateNumStates);
switch (data->test_state) {
case BtTestStateStopped: {
new_state = BtTestStateStarting;
bt_ctl_set_override_mode(BtCtlModeOverrideStop);
break;
}
case BtTestStateStarted: {
new_state = BtTestStateStopping;
bt_ctl_set_override_mode(BtCtlModeOverrideRun);
break;
}
case BtTestStateStarting:
case BtTestStateStopping:
default:
break;
}
data->test_state = new_state;
update_text_layers_callback(data);
}
static void bt_test_reset_callback(void *timer_data) {
RebootReason reason = { RebootReasonCode_MfgShutdown, 0 };
reboot_reason_set(&reason);
system_reset();
}
static void back_single_click_handler(ClickRecognizerRef recognizer, Window *window) {
AppData *app_data = app_state_get_user_data();
app_data->test_state = BtTestStateResetting;
text_layer_set_text(&app_data->status, status_text[app_data->test_state]);
if (app_data->reset_timer == TIMER_INVALID_ID) {
bool success = false;
app_data->reset_timer = new_timer_create();
if (app_data->reset_timer == TIMER_INVALID_ID) {
success = new_timer_start(app_data->reset_timer, 500, bt_test_reset_callback, app_data, 0 /*flags*/);
}
if (app_data->reset_timer == TIMER_INVALID_ID || !success) {
bt_test_reset_callback(app_data);
}
}
}
static void config_provider(Window *window) {
// single click / repeat-on-hold config:
window_single_click_subscribe(BUTTON_ID_SELECT, (ClickHandler) select_single_click_handler);
window_single_click_subscribe(BUTTON_ID_BACK, (ClickHandler) back_single_click_handler);
}
static void handle_init() {
AppData *data = task_malloc_check(sizeof(AppData));
app_state_set_user_data(data);
*data = (AppData) {
.test_state = BtTestStateInit,
.reset_timer = TIMER_INVALID_ID,
};
Window *window = &data->window;
window_init(window, "BT Test");
// want to indicate resetting.
window_set_overrides_back_button(window, true);
TextLayer *title = &data->title;
text_layer_init(title, &window->layer.bounds);
text_layer_set_font(title, fonts_get_system_font(FONT_KEY_GOTHIC_28_BOLD));
text_layer_set_text(title, "BT Test Mode");
layer_add_child(&window->layer, &title->layer);
TextLayer *status = &data->status;
text_layer_init(status,
&GRect(0, 50, window->layer.bounds.size.w, window->layer.bounds.size.h - 30));
text_layer_set_font(status, fonts_get_system_font(FONT_KEY_GOTHIC_24_BOLD));
text_layer_set_text(status, status_text[data->test_state]);
layer_add_child(&window->layer, &status->layer);
window_set_click_config_provider(window, (ClickConfigProvider) config_provider);
app_window_stack_push(window, true /* Animated */);
bt_state_change_event_info = (EventServiceInfo) {
.type = PEBBLE_BT_STATE_EVENT,
.handler = prv_bt_event_handler,
.context = data,
};
event_service_client_subscribe(&bt_state_change_event_info);
bt_ctl_set_override_mode(BtCtlModeOverrideRun);
bt_ctl_reset_bluetooth();
}
static void handle_deinit() {
AppData *data = app_state_get_user_data();
bt_ctl_set_override_mode(BtCtlModeOverrideNone);
event_service_client_unsubscribe(&bt_state_change_event_info);
task_free(data);
}
static void s_main(void) {
handle_init();
app_event_loop();
handle_deinit();
}
static const PebbleProcessMdSystem s_mfg_bt_test_info = {
.common.main_func = &s_main,
.name = "BT Test"
};
const PebbleProcessMd* mfg_app_bt_test_get_info() {
return (const PebbleProcessMd*) &s_mfg_bt_test_info;
}

View file

@ -0,0 +1,21 @@
/*
* 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 "process_management/pebble_process_md.h"
const PebbleProcessMd* mfg_app_bt_test_get_info();

View file

@ -0,0 +1,118 @@
/*
* 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 "mfg_display_burnin.h"
#include "applib/app.h"
#include "applib/app_timer.h"
#include "applib/ui/app_window_stack.h"
#include "process_state/app_state/app_state.h"
#include "drivers/display/display.h"
#include "kernel/pbl_malloc.h"
#include "util/units.h"
#include <stddef.h>
#include <string.h>
typedef struct {
Window window;
Layer background;
InverterLayer inverter_layer;
uint32_t old_display_hz;
} MfgDisplayBurninAppData;
static void draw_checkerboard(Layer* background, GContext* c) {
const int height = background->bounds.size.h;
const int width = background->bounds.size.w;
for(int y = 0; y < height; y += 4) {
for(int x = 0; x < width; x += 4) {
graphics_context_set_stroke_color(c, GColorBlack);
graphics_draw_pixel(c, GPoint(x,y));
graphics_draw_pixel(c, GPoint(x+1,y));
graphics_draw_pixel(c, GPoint(x,y+1));
graphics_draw_pixel(c, GPoint(x+1,y+1));
graphics_draw_pixel(c, GPoint(x+2,y+2));
graphics_draw_pixel(c, GPoint(x+3,y+2));
graphics_draw_pixel(c, GPoint(x+2,y+3));
graphics_draw_pixel(c, GPoint(x+3,y+3));
}
}
}
static void handle_timer(void *timer_data) {
MfgDisplayBurninAppData *data = app_state_get_user_data();
layer_set_hidden((Layer*)&data->inverter_layer,
!layer_get_hidden((Layer*)&data->inverter_layer));
app_timer_register(100 /* milliseconds */, handle_timer, NULL);
}
static void handle_init(void) {
MfgDisplayBurninAppData *data = task_malloc_check(sizeof(MfgDisplayBurninAppData));
app_state_set_user_data(data);
// Overclock the display to 4MHz make the artifacts issue more likely to happen
data->old_display_hz = display_baud_rate_change(MHZ_TO_HZ(4));
window_init(&data->window, "Display Burnin");
window_set_fullscreen(&data->window, true);
app_window_stack_push(&data->window, true /* Animated */);
layer_init(&data->background, &data->window.layer.bounds);
data->background.update_proc = (LayerUpdateProc) draw_checkerboard;
layer_add_child(&data->window.layer, (Layer*) &data->background);
inverter_layer_init(&data->inverter_layer, &data->window.layer.bounds);
layer_add_child(&data->window.layer, (Layer*) &data->inverter_layer);
app_timer_register(100 /* milliseconds */, handle_timer, NULL);
}
static void handle_deinit(void) {
MfgDisplayBurninAppData *data = app_state_get_user_data();
display_baud_rate_change(data->old_display_hz);
task_free(data);
}
static void s_main(void) {
handle_init();
app_event_loop();
handle_deinit();
}
static const PebbleProcessMdSystem s_mfg_func_test = {
.common = {
// UUID: 1bef4e93-5ec4-4af8-9eff-196eaf25b92b
.uuid = {0x1b, 0xef, 0x4e, 0x93, 0x5e, 0xc4, 0x4a, 0xf8, 0x9e, 0xff, 0x19, 0x6e, 0xaf, 0x25, 0xb9, 0x2b},
.main_func = s_main
},
.name = "Display Burn-in"
};
const Uuid* mfg_display_burnin_get_uuid() {
return &s_mfg_func_test.common.uuid;
}
const PebbleProcessMd* mfg_display_burnin_get_app_info() {
return (const PebbleProcessMd*) &s_mfg_func_test;
}

View file

@ -0,0 +1,26 @@
/*
* 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 "process_management/pebble_process_md.h"
#include "applib/ui/ui.h"
#include <stdbool.h>
const PebbleProcessMd* mfg_display_burnin_get_app_info();
const Uuid* mfg_display_burnin_get_uuid();

View file

@ -0,0 +1,768 @@
/*
* 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.
*/
// This app only makes sense on Snowy, as it uses addresses and sector sizes that only make sense
// on our parallel flash hardware
#if CAPABILITY_USE_PARALLEL_FLASH
#include <inttypes.h>
#include "mfg_flash_test.h"
#include "drivers/flash.h"
#include "drivers/task_watchdog.h"
#include "flash_region/flash_region.h"
#include "system/logging.h"
#include "kernel/pbl_malloc.h"
static const uint8_t data_pattern = 0xAA;
static const uint8_t test_pattern = 0x55;
static volatile bool enable_flash_test = false;
static FlashTestErrorType prv_read_verify_byte(uint32_t read_addr,
uint8_t expected_val,
FlashTestErrorType err_code,
uint8_t bitpos,
bool disp_logs) {
uint8_t read_buffer = 0;
flash_read_bytes((uint8_t *)&read_buffer, read_addr, sizeof(read_buffer));
if (disp_logs) {
PBL_LOG(LOG_LEVEL_DEBUG, ">> Reading Addr 0x%"PRIx32" value is 0x%"PRIx8,
read_addr, read_buffer);
}
if (read_buffer != expected_val) {
switch (err_code) {
case FLASH_TEST_ERR_ERASE:
PBL_LOG(LOG_LEVEL_DEBUG, "ERROR: Did not successfully erase the sector");
break;
case FLASH_TEST_ERR_STUCK_AT_HIGH:
PBL_LOG(LOG_LEVEL_DEBUG, "ERROR: Address bit %d stuck at high", bitpos);
break;
case FLASH_TEST_ERR_STUCK_AT_LOW:
PBL_LOG(LOG_LEVEL_DEBUG, "ERROR: Address bit %d stuck at low or shorted", bitpos);
break;
default:
break;
}
return err_code;
}
return FLASH_TEST_SUCCESS;
}
static FlashTestErrorType prv_read_verify_halfword(uint32_t read_addr,
uint16_t expected_val,
FlashTestErrorType err_code,
bool disp_logs) {
uint16_t read_buffer = 0;
flash_read_bytes((uint8_t *)&read_buffer, read_addr, sizeof(read_buffer));
if (disp_logs) {
PBL_LOG(LOG_LEVEL_DEBUG, ">> Reading Addr 0x%"PRIx32" value is 0x%"PRIx16,
read_addr, read_buffer);
}
if (read_buffer != expected_val) {
switch (err_code) {
case FLASH_TEST_ERR_ERASE:
PBL_LOG(LOG_LEVEL_DEBUG, "ERROR: Did not successfully erase the sector");
break;
case FLASH_TEST_ERR_DATA_WRITE:
PBL_LOG(LOG_LEVEL_DEBUG, "ERROR: Did not successfully write the data");
break;
default:
break;
}
return err_code;
}
return FLASH_TEST_SUCCESS;
}
static FlashTestErrorType prv_write_read_verify_byte(uint32_t write_addr,
uint8_t write_val,
uint8_t expected_val,
bool disp_logs) {
uint8_t write_buffer = write_val;
uint8_t read_buffer = 0;
// Write test pattern
if (disp_logs) {
PBL_LOG(LOG_LEVEL_DEBUG, ">> Writing Addr 0x%"PRIx32" to value 0x%"PRIx8,
write_addr, write_val);
}
flash_write_bytes((uint8_t *)&write_buffer, write_addr, sizeof(write_buffer));
// Confirm write took place
read_buffer = 0;
flash_read_bytes((uint8_t*) &read_buffer, write_addr, sizeof(read_buffer));
if (disp_logs) {
PBL_LOG(LOG_LEVEL_DEBUG, ">> Reading Addr 0x%"PRIx32" value is 0x%"PRIx8,
write_addr, read_buffer);
}
if (read_buffer != expected_val) {
PBL_LOG(LOG_LEVEL_DEBUG, "ERROR: Did not successfully write the data");
return FLASH_TEST_ERR_DATA_WRITE;
}
return FLASH_TEST_SUCCESS;
}
static FlashTestErrorType prv_write_read_verify_halfword(uint32_t write_addr,
uint16_t write_val,
uint16_t expected_val,
bool disp_logs) {
uint16_t write_buffer = write_val;
uint16_t read_buffer = 0;
// Write test pattern
if (disp_logs) {
PBL_LOG(LOG_LEVEL_DEBUG, ">> Writing Addr 0x%"PRIx32" to value 0x%"PRIx16,
write_addr, write_val);
}
flash_write_bytes((uint8_t *)&write_buffer, write_addr, sizeof(write_buffer));
// Confirm write took place
read_buffer = 0;
flash_read_bytes((uint8_t*) &read_buffer, write_addr, sizeof(read_buffer));
if (disp_logs) {
PBL_LOG(LOG_LEVEL_DEBUG, ">> Reading Addr 0x%"PRIx32" value is 0x%"PRIx16,
write_addr, read_buffer);
}
if (read_buffer != expected_val) {
PBL_LOG(LOG_LEVEL_DEBUG, "ERROR: Did not successfully write the data");
return FLASH_TEST_ERR_DATA_WRITE;
}
return FLASH_TEST_SUCCESS;
}
#define VERIFY_TEST_STATUS(status) \
do { \
if (status != FLASH_TEST_SUCCESS) { return status; } \
} while(0)
/***********************************************************/
/******************* DATA Test Functions *******************/
/***********************************************************/
static FlashTestErrorType prv_run_data_test(void) {
FlashTestErrorType status = FLASH_TEST_SUCCESS;
// Test each data bit using walking 1's method by toggling each data line
PBL_LOG(LOG_LEVEL_DEBUG, ">START - DATA TEST 1: Data bus test");
// Initialize region that is to be written
uint16_t data_buffer = 0x0;
uint8_t bitpos = 0;
uint16_t read_buffer = 0x0;
// Ensure within test data region and aligned to sector boundary
uint32_t addr_region = (FLASH_TEST_ADDR_START + SECTOR_SIZE_BYTES) & SECTOR_ADDR_MASK;
// Loop on each data bit - erase the sector, then write the next data value and verify that data
// was written
for (data_buffer = 1; data_buffer != 0; data_buffer <<= 1) {
read_buffer = 0;
flash_read_bytes((uint8_t*) &read_buffer, addr_region, sizeof(read_buffer));
PBL_LOG(LOG_LEVEL_DEBUG, ">> Reading Addr 0x%"PRIx32" value is 0x%"PRIx16,
addr_region, read_buffer);
if (read_buffer != 0xFFFF) {
// Erase sector only if necessary
flash_erase_sector_blocking(addr_region);
flash_read_bytes((uint8_t*) &read_buffer, addr_region, sizeof(read_buffer));
PBL_LOG(LOG_LEVEL_DEBUG, ">> Reading Addr 0x%"PRIx32" value is 0x%"PRIx16,
addr_region, read_buffer);
}
// All data should be set to 0xFFFF upon erase
if (read_buffer != 0xFFFF) {
PBL_LOG(LOG_LEVEL_DEBUG, "ERROR: Did not successfully erase the sector");
return FLASH_TEST_ERR_ERASE;
}
// Read and compare data that was written
status = prv_write_read_verify_halfword(addr_region, data_buffer, data_buffer, true);
if (status != 0) {
PBL_LOG(LOG_LEVEL_DEBUG, "ERROR: Data bit %d not returning correct data value", bitpos);
return status;
}
bitpos++;
addr_region += 4; // increment to the next address to avoid extra erases
}
PBL_LOG(LOG_LEVEL_DEBUG, ">PASS - DATA TEST 1: Data bus test");
return status;
}
/***********************************************************/
/******************* Addr Test Functions *******************/
/***********************************************************/
// Write the test byte 0xAA at each power-of-two offset within the flash test range. If necessary,
// erase the sector that the byte resides in first.
// The base address always gets erased. If skip_base_addr is true, we leave it at 0xFF (erased)
// otherwise we write 0xAA to that address as well.
static FlashTestErrorType write_initial_pattern(bool display_logs, bool skip_base_addr,
uint32_t* erase_addr) {
FlashTestErrorType status = FLASH_TEST_SUCCESS;
uint32_t base_addr = FLASH_TEST_ADDR_START;
uint32_t test_addr = base_addr;
uint32_t addr_mask = FLASH_TEST_ADDR_MSK;
uint8_t read_buffer = 0;
uint32_t bit_offset;
if (display_logs) { PBL_LOG(LOG_LEVEL_DEBUG, ">>> Initializing data patterns..."); }
if (display_logs) { PBL_LOG(LOG_LEVEL_DEBUG, ">>> Erasing sectors..."); }
if (erase_addr) {
// only erase the specific erase address
if (display_logs) {
PBL_LOG(LOG_LEVEL_DEBUG, ">> Erasing Addr 0x%"PRIx32, *erase_addr);
}
flash_erase_sector_blocking(*erase_addr);
} else {
// Erase the addresses within test range that we will touch. These are addresses with
// power-of-two offsets
for (bit_offset = 0; (bit_offset == 0) || (bit_offset & addr_mask); bit_offset <<= 1) {
if (bit_offset > base_addr) {
test_addr = bit_offset;
} else {
test_addr = base_addr + bit_offset;
}
if (test_addr >= FLASH_TEST_ADDR_END) {
break;
}
// skip erasing of unnecessary overlapping sectors
if ((test_addr >= base_addr + SECTOR_SIZE_BYTES) ||
(test_addr == base_addr + 1) || (test_addr == base_addr)) {
// check if byte location is already 0xFF or default data pattern, then skip erase
// Always erase base address
flash_read_bytes((uint8_t*)&read_buffer, test_addr, sizeof(read_buffer));
PBL_LOG(LOG_LEVEL_DEBUG, ">> Testing Addr 0x%"PRIx32", value:0x%x", test_addr, read_buffer);
if (((read_buffer != 0xFF) && (read_buffer != 0xAA)) || (test_addr == base_addr)) {
if (display_logs) {
PBL_LOG(LOG_LEVEL_DEBUG, ">> Erasing Addr 0x%"PRIx32, test_addr);
}
flash_erase_sector_blocking(test_addr);
// Verify data was erased
status = prv_read_verify_byte(test_addr, 0xFF, FLASH_TEST_ERR_ERASE, 0, display_logs);
VERIFY_TEST_STATUS(status);
}
}
// After the base address, go up by power of 2's
if (bit_offset == 0) {
bit_offset = 1;
}
}
}
if (display_logs) { PBL_LOG(LOG_LEVEL_DEBUG, ">>> Erasing sectors...complete"); }
// Write default data pattern to each power-of-two offset within the test region
for (bit_offset = 1; (bit_offset & addr_mask) != 0; bit_offset <<= 1) {
if (bit_offset > base_addr) {
test_addr = bit_offset;
} else {
test_addr = base_addr + bit_offset;
}
if (test_addr >= FLASH_TEST_ADDR_END) {
break;
}
// Write default data pattern to address if necessary
status = prv_read_verify_byte(test_addr, data_pattern, FLASH_TEST_ERR_SKIP, 0, display_logs);
if (status != FLASH_TEST_SUCCESS) {
// Write default data pattern to address
status = prv_write_read_verify_byte(test_addr, data_pattern, data_pattern, display_logs);
VERIFY_TEST_STATUS(status);
}
}
if (!skip_base_addr) {
test_addr = base_addr;
// Read initial value
read_buffer = 0;
flash_read_bytes((uint8_t*) &read_buffer, test_addr, sizeof(read_buffer));
if (display_logs) {
PBL_LOG(LOG_LEVEL_DEBUG, ">> Reading Addr 0x%"PRIx32" value is 0x%"PRIx8,
test_addr, read_buffer);
}
// Write data pattern
status = prv_write_read_verify_byte(test_addr, data_pattern, data_pattern, display_logs);
VERIFY_TEST_STATUS(status);
}
if (display_logs) { PBL_LOG(LOG_LEVEL_DEBUG, ">>> Initializing data patterns...complete"); }
return FLASH_TEST_SUCCESS;
}
static FlashTestErrorType prv_run_addr_test (void) {
uint32_t base_addr = FLASH_TEST_ADDR_START;
uint32_t test_addr = base_addr;
uint32_t addr_mask = FLASH_TEST_ADDR_MSK;
uint8_t read_buffer = 0;
uint32_t bit_offset;
uint32_t test_offset = 0;
FlashTestErrorType status = FLASH_TEST_SUCCESS;
///////////////////////////////////////////////////
/// Test 1: Check for address bits stuck at high
///////////////////////////////////////////////////
PBL_LOG(LOG_LEVEL_DEBUG, ">START - ADDR TEST 1: Check for address bits stuck at high");
// Write data pattern (0xAA) to each power-of-2 offset within the flash
status = write_initial_pattern(true /*display_logs*/, false /*skip_base_addr*/,
NULL /*erase_addr*/);
VERIFY_TEST_STATUS(status);
// offset of 0
test_addr = base_addr + test_offset;
// Read initial value
read_buffer = 0;
flash_read_bytes((uint8_t*) &read_buffer, test_addr, sizeof(read_buffer));
PBL_LOG(LOG_LEVEL_DEBUG, ">> Reading Addr 0x%"PRIx32" value is 0x%"PRIx8,
test_addr, read_buffer);
// Write test pattern to address 0
// After writing test pattern, data should be 0x00 since initial value was 0xAA and 0x55 was written
status = prv_write_read_verify_byte(test_addr, test_pattern, 0x00, true /*display_logs*/);
VERIFY_TEST_STATUS(status);
// Check if any of the address bits are stuck at high. If they are, then the previous write to
// address 0 would have trashed the data at one of the other addresses
uint8_t base_addr_pos = 0;
uint8_t bitpos;
bool stuck_at_high = false;
for (bit_offset = 1, bitpos = 0; bit_offset & addr_mask; bit_offset <<= 1, bitpos++) {
if (bit_offset > base_addr) {
test_addr = bit_offset;
} else if (bit_offset == base_addr) {
base_addr_pos = bitpos;
// Skip base address check - that is done later
PBL_LOG(LOG_LEVEL_DEBUG, "Skip base address bit position %d", bitpos);
bitpos++;
continue;
} else {
test_addr = base_addr + bit_offset;
}
if (test_addr >= FLASH_TEST_ADDR_END) {
PBL_LOG(LOG_LEVEL_DEBUG, "Skipping test address 0x%"PRIx32" which is out of range",
test_addr);
break;
}
// If test_pattern was written over the data_pattern, then return data should be 0 since
// data cannot transition from 0 to 1 without an erase; else it will be initial data_pattern
status = prv_read_verify_byte(test_addr, data_pattern, FLASH_TEST_ERR_STUCK_AT_HIGH, bitpos,
true);
if (status != FLASH_TEST_SUCCESS) {
stuck_at_high = true;
}
}
// Special case - test bit for base address
// - Use an address between FLASH_TEST_ADDR_START and base_addr
PBL_LOG(LOG_LEVEL_DEBUG, ">> Testing special case for base address bit %d", base_addr_pos);
// Read initial value
test_addr = FLASH_REGION_FILESYSTEM_BEGIN;
uint32_t special_case_addr = test_addr | base_addr;
if ((test_addr >= base_addr) || (special_case_addr > FLASH_TEST_ADDR_END)) {
PBL_LOG(LOG_LEVEL_DEBUG, "ERROR: Cannot test address bit for base_addr");
return FLASH_TEST_ERR_ADDR_RANGE;
}
// erase (base_addr | test_addr) and start of test space
flash_erase_sector_blocking(test_addr);
flash_erase_sector_blocking(special_case_addr);
// Verify erase took place
status = prv_read_verify_byte(test_addr, 0xFF, FLASH_TEST_ERR_ERASE, 0, true);
VERIFY_TEST_STATUS(status);
// Verify erase took place
status = prv_read_verify_byte(special_case_addr, 0xFF, FLASH_TEST_ERR_ERASE, 0, true);
VERIFY_TEST_STATUS(status);
// Write test pattern to the test address
// Data should be set to test_pattern since existing data should be 0xFF and we are writing
// test_pattern
status = prv_write_read_verify_byte(test_addr, test_pattern, test_pattern, true);
VERIFY_TEST_STATUS(status);
// Confirm write into base_addr did not take place
// If test_pattern was written over the data_pattern, then return data should be 0 since
// data cannot transition from 0 to 1 without an erase
status = prv_read_verify_byte(special_case_addr, 0xFF, FLASH_TEST_ERR_STUCK_AT_HIGH,
base_addr_pos, true);
if (status != FLASH_TEST_SUCCESS) {
stuck_at_high = true;
}
// If any bits are stuck at high, return error
if (stuck_at_high) {
return FLASH_TEST_ERR_STUCK_AT_HIGH;
}
PBL_LOG(LOG_LEVEL_DEBUG, ">PASS - ADDR TEST 1: Check for address bits stuck at high");
/////////////////////////////////////////////////////////////
/// Test 2: Check for address bits stuck at low or shorted
/////////////////////////////////////////////////////////////
PBL_LOG(LOG_LEVEL_DEBUG, ">START - ADDR TEST 2: Check for address bits stuck at low or shorted");
// NOTE that the previous test only modified the data at base_addr and left all other
// power-of-2 addresses with the data pattern in them. The write_initial_pattern() method
// will skip erasing a sector if all of the power of 2 addresses within it still have the
// data pattern, so only the first sector will end up being re-erased.
status = write_initial_pattern(true /*display_logs*/, false /*skip_base_addr*/,
NULL /*erase_addr*/);
VERIFY_TEST_STATUS(status);
bool stuck_at_low = false;
for (test_offset = 1, bitpos=0; test_offset & addr_mask; test_offset <<= 1, bitpos++) {
if (test_offset >= base_addr) {
test_addr = test_offset;
} else {
test_addr = base_addr + test_offset;
}
if (test_addr >= FLASH_TEST_ADDR_END) {
break;
}
// Skip base address
if (test_addr == base_addr) {
continue;
}
PBL_LOG(LOG_LEVEL_DEBUG, ">> Testing Stuck at Low at Addr 0x%"PRIx32, test_addr);
// After we write test_pattern, data should be set to 0x00 since existing data should be 0xAA
// and we are writing 0x55
status = prv_write_read_verify_byte(test_addr, test_pattern, 0x00, false);
VERIFY_TEST_STATUS(status);
// read base address to insure that it wasn't modified due to a stuck at zero in an address
// bit
status = prv_read_verify_byte(base_addr, data_pattern, FLASH_TEST_ERR_STUCK_AT_LOW, bitpos,
false);
if (status != FLASH_TEST_SUCCESS) {
stuck_at_low = true;
}
// Check if any other address bits are shorted with our test bit. If shorted, then we would
// read 0 from the address bit which is shorted with the test one.
// We only have to check shorts with higher address bits, since we've already checked for
// shorts from the lower address bits to this one.
uint8_t bitpos2 = bitpos+1;
for (bit_offset = test_offset << 1; bit_offset & addr_mask; bit_offset <<= 1, bitpos2++) {
// skip same offset
if (bit_offset == test_offset) {
continue;
}
uint32_t test_addr2;
if (bit_offset >= base_addr) {
test_addr2 = bit_offset;
} else {
test_addr2 = base_addr + bit_offset;
}
if (test_addr2 >= FLASH_TEST_ADDR_END) {
break;
}
status = prv_read_verify_byte(test_addr2, data_pattern, FLASH_TEST_ERR_STUCK_AT_LOW, bitpos2,
false /*display_logs*/);
if (status != FLASH_TEST_SUCCESS) {
stuck_at_low = true;
}
}
if (stuck_at_low) {
// Restore data back to original if stuck at low occurred
status = write_initial_pattern(false /*display_logs*/, false /*skip_base_addr*/,
NULL /*base_addr*/);
}
VERIFY_TEST_STATUS(status);
}
if (stuck_at_low) {
return FLASH_TEST_ERR_STUCK_AT_LOW;
}
PBL_LOG(LOG_LEVEL_DEBUG, ">PASS - ADDR TEST 2: Check for address bits stuck at low or shorted");
return FLASH_TEST_SUCCESS;
}
/***********************************************************/
/******************* Stress Test Functions *****************/
/***********************************************************/
#define FLASH_TEST_STRESS_ADDR1 0x00A5A5A5
#define FLASH_TEST_STRESS_DATA1 0x5A5A
#define FLASH_TEST_STRESS_ADDR2 0x00CA5A5A
#define FLASH_TEST_STRESS_DATA2 0xA5A5
static FlashTestErrorType setup_stress_addr_test(void) {
FlashTestErrorType status = FLASH_TEST_SUCCESS;
// Read/Write from address 1
uint32_t stress_addr1 = FLASH_TEST_STRESS_ADDR1;
uint16_t stress_data1 = FLASH_TEST_STRESS_DATA1;
// Read/Write from address 2
uint32_t stress_addr2 = FLASH_TEST_STRESS_ADDR2;
uint16_t stress_data2 = FLASH_TEST_STRESS_DATA2;
if ((stress_addr1 < FLASH_REGION_FILESYSTEM_BEGIN) ||
(stress_addr1 >= FLASH_REGION_FILESYSTEM_END)) {
PBL_LOG(LOG_LEVEL_DEBUG, "ERROR: Invalid range");
return FLASH_TEST_ERR_ADDR_RANGE;
}
if ((stress_addr2 < FLASH_REGION_FILESYSTEM_BEGIN) ||
(stress_addr2 >= FLASH_REGION_FILESYSTEM_END)) {
PBL_LOG(LOG_LEVEL_DEBUG, "ERROR: Invalid range");
return FLASH_TEST_ERR_ADDR_RANGE;
}
// Erase sectors
flash_erase_sector_blocking(stress_addr1);
status = prv_read_verify_halfword(stress_addr1, 0xFFFF, FLASH_TEST_ERR_ERASE, false);
VERIFY_TEST_STATUS(status);
flash_erase_sector_blocking(stress_addr2);
status = prv_read_verify_halfword(stress_addr2, 0xFFFF, FLASH_TEST_ERR_ERASE, false);
VERIFY_TEST_STATUS(status);
// Write data to stress address locations
PBL_LOG(LOG_LEVEL_DEBUG, ">> Writing Addr 0x%"PRIx32" to value 0x%"PRIx16,
stress_addr1, stress_data1);
flash_write_bytes((uint8_t *)&stress_data1, stress_addr1, sizeof(stress_data1));
PBL_LOG(LOG_LEVEL_DEBUG, ">> Writing Addr 0x%"PRIx32" to value 0x%"PRIx16,
stress_addr2, stress_data2);
flash_write_bytes((uint8_t *)&stress_data2, stress_addr2, sizeof(stress_data2));
return FLASH_TEST_SUCCESS;
}
// Run address read/write stess test - if iterations is 0, then stop only when button is pushed;
// else go until iterations hit
static FlashTestErrorType prv_run_stress_addr_test(uint32_t iterations) {
PBL_LOG(LOG_LEVEL_DEBUG, ">START - STRESS TEST 1");
uint16_t halfwordcount = 0;
unsigned int iteration_count = 0;
// Read/Write from address 1
uint32_t stress_addr1 = FLASH_TEST_STRESS_ADDR1;
uint16_t stress_data1 = FLASH_TEST_STRESS_DATA1;
// Read/Write from adress 2
uint32_t stress_addr2 = FLASH_TEST_STRESS_ADDR2;
uint16_t stress_data2 = FLASH_TEST_STRESS_DATA2;
FlashTestErrorType status = setup_stress_addr_test();
if (status != FLASH_TEST_SUCCESS) {
return status;
}
// Keep going until DOWN button is pushed or iterations reached
while(((iterations == 0) && enable_flash_test) ||
((iterations > 0) && (iteration_count < iterations))) {
// Confirm write took place - data should now be set to stress_data1
status = prv_read_verify_halfword(stress_addr1, stress_data1, FLASH_TEST_ERR_DATA_WRITE, false);
VERIFY_TEST_STATUS(status);
halfwordcount++;
// Confirm write took place - data should now be set to stress_data2
status = prv_read_verify_halfword(stress_addr2, stress_data2, FLASH_TEST_ERR_DATA_WRITE, false);
VERIFY_TEST_STATUS(status);
halfwordcount++;
if (halfwordcount*2 % (256*1024) == 0) {
// Reading flash words (which are 16 bits) hence double
if (iterations) {
PBL_LOG(LOG_LEVEL_DEBUG, ">> Read 256KB, iteration: %d of %"PRId32,
iteration_count, iterations);
} else {
PBL_LOG(LOG_LEVEL_DEBUG, ">> Read 256KB, iteration: %d", iteration_count);
}
}
iteration_count++;
}
PBL_LOG(LOG_LEVEL_DEBUG, "Ran %d iterations", iteration_count);
PBL_LOG(LOG_LEVEL_DEBUG, ">PASS - STRESS TEST 1");
return FLASH_TEST_SUCCESS;
}
/***********************************************************/
/******************* Perf Data Test Functions **************/
/***********************************************************/
#define COUNTER_START \
uint32_t _start = *((volatile uint32_t *)0xE0001004);\
uint32_t _tot = 0
#define COUNTER_STOP \
uint32_t _end = *((volatile uint32_t *)0xE0001004)
#define COUNTER_PRINT(x) \
do { \
if (_end > _start) { \
_tot += (_end - _start); \
} else { \
_tot += (UINT32_MAX - _start) + _end; \
} \
PBL_LOG(LOG_LEVEL_DEBUG, "Read %lu bytes %lu ticks %lu us", x, _tot, _tot / 64); \
} while (0)
#define SWAP(a, b) \
do { \
uint32_t temp = a; \
a = b; \
b = temp; \
} while (0)
// Run performance test to measure data access times
#define DWT_CTRL_ADDR 0xE0001000
#define DWT_CYCCNT_ADDR 0xE0001004
#define MAX_READ_BUFF_SIZE 4096 // 4KB
static FlashTestErrorType prv_run_perf_data_test(void) {
uint8_t *read_buffer = (uint8_t *) app_malloc(MAX_READ_BUFF_SIZE);
if (!read_buffer) {
PBL_LOG(LOG_LEVEL_DEBUG, "ERROR: Not enough memory to run test");
return FLASH_TEST_ERR_OOM;
}
uint32_t addr = FLASH_TEST_ADDR_START;
volatile uint32_t *ptr = (uint32_t *) DWT_CTRL_ADDR;
for (uint32_t num_bytes = 1; num_bytes <= MAX_READ_BUFF_SIZE; num_bytes<<=1) {
// Run test three times and print out the median throughput
uint32_t ticks[3] = {0, 0, 0};
for (uint8_t repeat = 0; repeat < 3; repeat++) {
*ptr = *ptr & 0xFFFFFFFE;
*((volatile uint32_t *)DWT_CYCCNT_ADDR) = 0;
*ptr = *ptr | 0x1;
COUNTER_START;
flash_read_bytes((uint8_t *)&read_buffer[0], addr, num_bytes);
COUNTER_STOP;
COUNTER_PRINT(num_bytes);
ticks[repeat] = _tot;
}
// Do a simple sort
if (ticks[0] > ticks[1]) {
SWAP(ticks[0], ticks[1]);
}
if (ticks[1] > ticks[2]) {
SWAP(ticks[1], ticks[2]);
if (ticks[0] > ticks[1]) {
SWAP(ticks[0], ticks[1]);
}
}
PBL_LOG(LOG_LEVEL_DEBUG, "Read %lu bytes, median throughput %lu KBps", num_bytes, (num_bytes * 1000 * 64 / ticks[1]));
}
app_free(read_buffer);
return FLASH_TEST_SUCCESS;
}
/***********************************************************/
/******************* Wrapper Functions *********************/
/***********************************************************/
void stop_flash_test_case( void ) {
enable_flash_test = false;
}
FlashTestErrorType run_flash_test_case(FlashTestCaseType test_case_num, uint32_t iterations) {
FlashTestErrorType status = FLASH_TEST_SUCCESS;
// Disable watchdog if enabled
bool previous_task_watchdog_state = task_watchdog_mask_get(pebble_task_get_current());
if (previous_task_watchdog_state) {
task_watchdog_mask_clear(pebble_task_get_current());
}
enable_flash_test = true;
// Schedule test to run
switch (test_case_num) {
case FLASH_TEST_CASE_RUN_DATA_TEST:
status = prv_run_data_test();
break;
case FLASH_TEST_CASE_RUN_ADDR_TEST:
status = prv_run_addr_test();
break;
case FLASH_TEST_CASE_RUN_STRESS_ADDR_TEST:
status = prv_run_stress_addr_test(iterations);
break;
case FLASH_TEST_CASE_RUN_PERF_DATA_TEST:
status = prv_run_perf_data_test();
break;
case FLASH_TEST_CASE_RUN_SWITCH_MODE_ASYNC:
case FLASH_TEST_CASE_RUN_SWITCH_MODE_SYNC_BURST:
flash_switch_mode(test_case_num - FLASH_TEST_CASE_RUN_SWITCH_MODE_ASYNC);
status = FLASH_TEST_SUCCESS;
break;
default:
status = FLASH_TEST_ERR_UNSUPPORTED;
break;
}
enable_flash_test = false;
if (status == FLASH_TEST_SUCCESS) {
PBL_LOG(LOG_LEVEL_DEBUG, ">>>>>PASS FLASH TEST CASE %d<<<<<", test_case_num);
}
else {
PBL_LOG(LOG_LEVEL_DEBUG, ">>>>>FAIL FLASH TEST CASE %d, Status: %d<<<<<", test_case_num, status);
}
// Re-enable watchdog state if previously enabled
if (previous_task_watchdog_state) {
task_watchdog_bit_set(pebble_task_get_current());
task_watchdog_mask_set(pebble_task_get_current());
}
return status;
}
#endif // CAPABILITY_USE_PARALLEL_FLASH

View file

@ -0,0 +1,55 @@
/*
* 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 <stdint.h>
// To add new tests, update the following:
// 1. FlashTestCaseType enum
// 2. Update run_flash_test_case to include test
// 3. Update test_window_load in flash_test.c with test case name
// 4. Update flash_test_window_load in flash_test.c with test case menu item
// 5. Update prompt_commands.c if necessary
typedef enum {
FLASH_TEST_CASE_RUN_DATA_TEST = 0,
FLASH_TEST_CASE_RUN_ADDR_TEST = 1,
FLASH_TEST_CASE_RUN_STRESS_ADDR_TEST = 2,
FLASH_TEST_CASE_RUN_PERF_DATA_TEST = 3,
FLASH_TEST_CASE_RUN_SWITCH_MODE_ASYNC = 4,
FLASH_TEST_CASE_RUN_SWITCH_MODE_SYNC_BURST = 5,
// Add new test cases above this line
FLASH_TEST_CASE_NUM_MENU_ITEMS
} FlashTestCaseType;
typedef enum {
FLASH_TEST_SUCCESS = 0,
FLASH_TEST_ERR_OTHER = -1,
FLASH_TEST_ERR_ERASE = -2,
FLASH_TEST_ERR_DATA_WRITE = -3,
FLASH_TEST_ERR_ADDR_RANGE = -4,
FLASH_TEST_ERR_STUCK_AT_HIGH = -5,
FLASH_TEST_ERR_STUCK_AT_LOW = -6,
FLASH_TEST_ERR_OOM = -7,
FLASH_TEST_ERR_UNSUPPORTED = -8,
FLASH_TEST_ERR_SKIP = -9,
} FlashTestErrorType;
// This function explicitly stop a test case if it is currently running. Currently this only affects
// the stress test.
extern void stop_flash_test_case( void );
extern FlashTestErrorType run_flash_test_case(FlashTestCaseType test_case_num, uint32_t iterations);

View file

@ -0,0 +1,173 @@
/*
* 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
// FIXME: Wha? We don't use this file directly.
#include "mfg_func_test_buttons.h"
#include "kernel/events.h"
#include "applib/fonts/fonts.h"
#include "applib/ui/ui.h"
#include "drivers/battery.h"
#include "services/common/battery/battery_monitor.h"
#include "services/common/new_timer/new_timer.h"
#include <stdio.h>
typedef struct {
MfgFuncTestData *app_data;
char text_top[32];
TextLayer text_layer_top;
TextLayer text_layer_center;
char text_volt[16];
TextLayer text_layer_volt;
PathLayer bolt;
TimerID poll_timer_id;
} BatteryTestData;
static void battery_polling_callback(void *timer_data) {
BatteryTestData *data = (BatteryTestData*)timer_data;
sprintf(data->text_volt, "%i mV", battery_get_millivolts());
text_layer_set_text(&data->text_layer_volt, data->text_volt);
const bool is_discharging = !battery_get_charge_state().is_charging;
layer_set_hidden(&data->bolt.layer, is_discharging);
layer_set_hidden(&data->text_layer_center.layer, !is_discharging);
}
static void stop_battery_polling(BatteryTestData *data) {
if (data == NULL || data->poll_timer_id == TIMER_INVALID_ID) {
return;
}
new_timer_delete(data->poll_timer_id);
data->poll_timer_id = TIMER_INVALID_ID;
}
static void start_battery_polling(BatteryTestData *data) {
if (data == NULL || data->poll_timer_id != TIMER_INVALID_ID) {
stop_battery_polling(data);
}
const uint32_t poll_interval_ms = 300;
if (data->poll_timer_id == TIMER_INVALID_ID) {
data->poll_timer_id = new_timer_create();
}
new_timer_start(data->poll_timer_id, poll_interval_ms, battery_polling_callback, data, TIMER_START_FLAG_REPEATING);
}
static void battery_window_button_up(ClickRecognizerRef recognizer, void *context) {
BatteryTestData *data = window_get_user_data(context);
if (data->app_data->charge_test_done) {
const bool animated = false;
window_stack_pop(animated);
} else {
if (battery_get_charge_state().is_charging) {
mfg_func_test_append_bits(MfgFuncTestBitChargeTestPassed);
data->app_data->charge_test_done = true;
stop_battery_polling(data);
layer_set_hidden(&data->text_layer_volt.layer, true);
layer_set_hidden(&data->bolt.layer, true);
text_layer_set_text(&data->text_layer_center, "QC OK!");
layer_set_hidden(&data->text_layer_center.layer, false);
}
}
}
static void battery_window_click_config_provider(void *context) {
for (ButtonId button_id = BUTTON_ID_BACK; button_id < NUM_BUTTONS; ++button_id) {
window_raw_click_subscribe(button_id, NULL, (ClickHandler) battery_window_button_up, context);
}
}
static void battery_window_load(Window *window) {
BatteryTestData *data = window_get_user_data(window);
// Layout Battery Test Window:
Layer *root = &window->layer;
char addr_hex_str[BT_ADDR_FMT_BUFFER_SIZE_BYTES];
bt_local_id_copy_address_hex_string(addr_hex_str);
sprintf(data->text_top, "Quality Test\nMAC: %s", addr_hex_str);
TextLayer *text_layer_top = &data->text_layer_top;
text_layer_init(text_layer_top, &GRect(0, 0, 144, 168));
text_layer_set_background_color(text_layer_top, GColorWhite);
text_layer_set_text_color(text_layer_top, GColorBlack);
text_layer_set_text(text_layer_top, data->text_top);
text_layer_set_font(text_layer_top, fonts_get_system_font(FONT_KEY_GOTHIC_18_BOLD));
layer_add_child(root, &text_layer_top->layer);
TextLayer *text_layer_center = &data->text_layer_center;
text_layer_init(text_layer_center, &GRect(0, 60, 144, 40));
text_layer_set_background_color(text_layer_center, GColorClear);
text_layer_set_text_color(text_layer_center, GColorBlack);
text_layer_set_text(text_layer_center, "Plug Charger");
text_layer_set_font(text_layer_center, fonts_get_system_font(FONT_KEY_GOTHIC_28_BOLD));
layer_add_child(root, &text_layer_center->layer);
TextLayer *text_layer_volt = &data->text_layer_volt;
text_layer_init(text_layer_volt, &GRect(0, 128, 144, 40));
text_layer_set_background_color(text_layer_volt, GColorBlack);
text_layer_set_text_color(text_layer_volt, GColorWhite);
text_layer_set_font(text_layer_volt, fonts_get_system_font(FONT_KEY_GOTHIC_28_BOLD));
layer_add_child(root, &text_layer_volt->layer);
PathLayer *bolt = &data->bolt;
path_layer_init(bolt, &BOLT_PATH_INFO);
path_layer_set_fill_color(bolt, GColorBlack);
path_layer_set_stroke_color(bolt, GColorClear);
layer_set_frame(&bolt->layer, &GRect(58, 48, 28, 60));
layer_set_hidden(&bolt->layer, true);
layer_add_child(root, &bolt->layer);
}
static void battery_window_appear(Window *window) {
BatteryTestData *data = window_get_user_data(window);
start_battery_polling(data);
}
static void battery_window_disappear(Window *window) {
BatteryTestData *data = window_get_user_data(window);
stop_battery_polling(data);
}
static void push_battery_test_window(MfgFuncTestData *app_data) {
static BatteryTestData s_battery_test_data;
s_battery_test_data = (BatteryTestData) {
.app_data = app_data,
.poll_timer_id = TIMER_INVALID_ID,
};
// Battery Charge test window:
Window *battery_window = &app_data->battery_window;
window_init(battery_window, WINDOW_NAME("Mfg Func Test Battery"));
window_set_overrides_back_button(battery_window, true);
window_set_user_data(battery_window, app_data);
window_set_click_config_provider_with_context(battery_window,
(ClickConfigProvider) battery_window_click_config_provider, battery_window);
window_set_window_handlers(battery_window, &(WindowHandlers) {
.load = battery_window_load,
.appear = battery_window_appear,
.disappear = battery_window_disappear
});
window_set_user_data(battery_window, &s_battery_test_data);
window_set_fullscreen(battery_window, true);
const bool animated = false;
window_stack_push(battery_window, animated);
}

View file

@ -0,0 +1,49 @@
/*
* 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 "mfg_func_test_buttons.h"
#include "applib/ui/ui.h"
static void black_window_button_up(ClickRecognizerRef recognizer, Window *window) {
MfgFuncTestData *app_data = (MfgFuncTestData*)window_get_user_data(window);
mfg_func_test_append_bits(MfgFuncTestBitBlackTestPassed);
app_data->black_test_done = true;
vibes_short_pulse();
const bool animated = false;
window_stack_pop(animated);
}
static void black_window_click_config_provider(void *context) {
for (ButtonId button_id = BUTTON_ID_BACK; button_id < NUM_BUTTONS; ++button_id) {
window_raw_click_subscribe(button_id, NULL, (ClickHandler) black_window_button_up, context);
}
}
static void push_black_test_window(MfgFuncTestData *app_data) {
Window *black_window = &app_data->black_window;
window_init(black_window, WINDOW_NAME("Mfg Func Test Black"));
window_set_background_color(black_window, GColorBlack);
window_set_click_config_provider_with_context(black_window,
(ClickConfigProvider) black_window_click_config_provider, black_window);
window_set_user_data(black_window, app_data);
window_set_fullscreen(black_window, true);
const bool animated = false;
window_stack_push(black_window, animated);
}

View file

@ -0,0 +1,132 @@
/*
* 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 "drivers/button.h"
#include "drivers/backlight.h"
#include "util/trig.h"
#include "applib/ui/ui.h"
#include "applib/ui/window_private.h"
#include "applib/fonts/fonts.h"
typedef struct {
MfgFuncTestData *app_data;
ButtonId button_id;
TextLayer label;
PathLayer arrow;
} ButtonTestData;
static const GPathInfo ARROW_PATH_INFO = {
.num_points = 7,
.points = (GPoint []) {{0, 14}, {29, 14}, {29, 0}, {54, 25}, {29, 50}, {29, 36}, {0, 36}}
};
static const GPathInfo BOLT_PATH_INFO = {
.num_points = 6,
.points = (GPoint []) {{21, 0}, {14, 26}, {28, 26}, {7, 60}, {14, 34}, {0, 34}}
};
static void move_arrow_to_button(ButtonTestData *data, ButtonId id) {
#define ARROW_SIZE {54, 50}
static const GRect ARROW_RECTS[] = {
{{2, 30}, ARROW_SIZE}, // BACK
{{88, 2}, ARROW_SIZE}, // UP
{{88, 59}, ARROW_SIZE}, // SELECT
{{88, 116}, ARROW_SIZE}, // DOWN
};
layer_set_frame(&data->arrow.layer, &ARROW_RECTS[id]);
gpath_rotate_to(&data->arrow.path, id == BUTTON_ID_BACK ? (TRIG_MAX_ANGLE / 2) : 0);
gpath_move_to(&data->arrow.path, id == BUTTON_ID_BACK ? GPoint(54, 50) : GPoint(0, 0));
}
static void button_window_button_up(ClickRecognizerRef recognizer, Window *window) {
ButtonTestData *data = window_get_user_data(window);
ButtonId button_id = click_recognizer_get_button_id(recognizer);
if (data->button_id == button_id) {
++(data->button_id);
if (data->button_id > BUTTON_ID_DOWN) {
mfg_func_test_append_bits(MfgFuncTestBitButtonTestPassed);
data->app_data->button_test_done = true;
data->button_id = BUTTON_ID_BACK;
const bool animated = false;
window_stack_pop(animated);
} else {
move_arrow_to_button(data, data->button_id);
}
backlight_set_brightness(0);
}
}
static void button_window_button_down(ClickRecognizerRef recognizer, Window *window) {
ButtonTestData *data = window_get_user_data(window);
if (data->button_id == click_recognizer_get_button_id(recognizer)) {
backlight_set_brightness(0xffff);
}
}
static void button_window_click_config_provider(void *context) {
for (ButtonId button_id = BUTTON_ID_BACK; button_id < NUM_BUTTONS; ++button_id) {
window_raw_click_subscribe(button_id,
(ClickHandler) button_window_button_down, (ClickHandler) button_window_button_up, context);
}
}
static void button_window_load(Window *window) {
ButtonTestData *data = window_get_user_data(window);
Layer *root = &window->layer;
TextLayer *label = &data->label;
text_layer_init(label, GRect(0, 0, 144, 40));
text_layer_set_background_color(label, GColorClear);
text_layer_set_text_color(label, GColorBlack);
text_layer_set_text(label, "Press Button");
text_layer_set_font(label, fonts_get_system_font(FONT_KEY_GOTHIC_28_BOLD));
layer_add_child(root, &label->layer);
PathLayer *arrow = &data->arrow;
path_layer_init(arrow, &ARROW_PATH_INFO);
path_layer_set_fill_color(arrow, GColorBlack);
path_layer_set_stroke_color(arrow, GColorClear);
layer_add_child(root, &arrow->layer);
move_arrow_to_button(data, data->button_id);
}
static void push_button_test_window(MfgFuncTestData *app_data) {
static ButtonTestData s_button_test_data = {
.button_id = BUTTON_ID_BACK,
};
s_button_test_data.app_data = app_data;
Window *button_window = &app_data->button_window;
window_init(button_window, WINDOW_NAME("Mfg Func Test Buttons"));
window_set_overrides_back_button(button_window, true);
window_set_click_config_provider_with_context(button_window,
(ClickConfigProvider) button_window_click_config_provider, button_window);
window_set_window_handlers(button_window, &(WindowHandlers) {
.load = button_window_load
});
window_set_user_data(button_window, &s_button_test_data);
window_set_fullscreen(button_window, true);
const bool animated = false;
window_stack_push(button_window, animated);
}

View file

@ -0,0 +1,133 @@
/*
* 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 "drivers/backlight.h"
#include "drivers/button.h"
#include "applib/fonts/fonts.h"
#include "git_version.auto.h"
#include "applib/graphics/utf8.h"
#include "resource/resource.h"
#include "resource/resource_ids.auto.h"
#include "system/bootbits.h"
#include "system/logging.h"
#include "applib/ui/ui.h"
#include "applib/ui/window_private.h"
#include "system/version.h"
#include <stdlib.h>
#include <stdio.h>
#include <inttypes.h>
typedef struct {
MfgFuncTestData *app_data;
TextLayer label;
} VersionData;
static int s_click_count;
static char* s_version_str;
static void version_window_button_up(ClickRecognizerRef recognizer, Window *window) {
// The button-up event from launching the QC app will propagate to
// this window, so instead we wait for two button-up events before
// proceeding
if (s_click_count++ > 0) {
const bool animated = false;
window_stack_pop(animated);
}
}
static void version_window_click_config_provider(void *context) {
for (ButtonId button_id = BUTTON_ID_BACK; button_id < NUM_BUTTONS; ++button_id) {
window_raw_click_subscribe(button_id, NULL, (ClickHandler) version_window_button_up, context);
}
}
static void version_get_version_str(char* buf, int size) {
FirmwareMetadata normal_fw;
FirmwareMetadata recovery_fw;
version_copy_running_fw_metadata(&normal_fw);
version_copy_recovery_fw_metadata(&recovery_fw);
ResourceVersion system_res = resource_get_system_version();
uint32_t bootloader_version = boot_version_read();
uint32_t system_res_version = system_res.crc;
char* normal_version = (char*) normal_fw.version_short;
if (!utf8_is_valid_string(normal_version)) {
normal_version = "???";
}
char* recovery_version = (char*) recovery_fw.version_short;
if (!utf8_is_valid_string(recovery_version)) {
recovery_version = "???";
}
sniprintf(buf, size,
"n:%s\nr:%s\nb:0x%"PRIx32"\ns:0x%"PRIx32,
normal_version,
recovery_version,
bootloader_version,
system_res_version);
}
static void version_window_load(Window *window) {
VersionData *data = window_get_user_data(window);
Layer *root = &window->layer;
s_version_str = (char*) malloc(64);
version_get_version_str(s_version_str, 64);
TextLayer *label = &data->label;
text_layer_init(label, GRect(2, 2, 142, 164));
text_layer_set_background_color(label, GColorClear);
text_layer_set_text_color(label, GColorBlack);
text_layer_set_text(label, s_version_str);
text_layer_set_font(label, fonts_get_system_font(FONT_KEY_GOTHIC_28_BOLD));
layer_add_child(root, &label->layer);
s_click_count = 0;
}
static void version_window_unload(Window* window) {
free(s_version_str);
(void)window;
}
static void push_version_window(MfgFuncTestData *app_data) {
static VersionData s_version_data;
s_version_data.app_data = app_data;
Window* version_window = &app_data->version_window;
window_init(version_window, WINDOW_NAME("Mfg Func Test Version"));
window_set_overrides_back_button(version_window, true);
window_set_click_config_provider_with_context(version_window,
(ClickConfigProvider) version_window_click_config_provider, version_window);
window_set_window_handlers(version_window, &(WindowHandlers) {
.load = version_window_load,
.unload = version_window_unload
});
window_set_user_data(version_window, &s_version_data);
window_set_fullscreen(version_window, true);
const bool animated = false;
window_stack_push(version_window, animated);
}