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,101 @@
/*
* 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 "display.h"
#include "drivers/button_id.h"
#include "stm32f4xx_gpio.h"
#include <stdint.h>
#include <stdbool.h>
#define GPIO_Port_NULL ((GPIO_TypeDef *) 0)
#define GPIO_Pin_NULL ((uint16_t)0x0000)
typedef struct {
//! One of EXTI_PortSourceGPIOX
uint8_t exti_port_source;
//! Value between 0-15
uint8_t exti_line;
} ExtiConfig;
typedef struct {
const char* const name; ///< Name for debugging purposes.
GPIO_TypeDef* const gpio; ///< One of GPIOX. For example, GPIOA.
const uint32_t gpio_pin; ///< One of GPIO_Pin_X.
ExtiConfig exti;
GPIOPuPd_TypeDef pull;
} ButtonConfig;
typedef struct {
GPIO_TypeDef* const gpio; ///< One of GPIOX. For example, GPIOA.
const uint32_t gpio_pin; ///< One of GPIO_Pin_X.
} ButtonComConfig;
typedef struct {
GPIO_TypeDef* const gpio; ///< One of GPIOX. For example, GPIOA.
const uint32_t gpio_pin; ///< One of GPIO_Pin_X.
bool active_high; ///< Pin is active high or active low
} OutputConfig;
//! Alternate function pin configuration
//! Used to configure a pin for use by a peripheral
typedef struct {
GPIO_TypeDef* const gpio; ///< One of GPIOX. For example, GPIOA.
const uint32_t gpio_pin; ///< One of GPIO_Pin_X.
const uint16_t gpio_pin_source; ///< One of GPIO_PinSourceX.
const uint8_t gpio_af; ///< One of GPIO_AF_X
} AfConfig;
typedef struct {
I2C_TypeDef *const i2c;
AfConfig i2c_scl; ///< Alternate Function configuration for SCL pin
AfConfig i2c_sda; ///< Alternate Function configuration for SDA pin
uint32_t clock_ctrl; ///< Peripheral clock control flag
uint32_t clock_speed; ///< Bus clock speed
uint32_t duty_cycle; ///< Bus clock duty cycle in fast mode
const uint8_t ev_irq_channel; ///< I2C Event interrupt (One of X_IRQn). For example, I2C1_EV_IRQn.
const uint8_t er_irq_channel; ///< I2C Error interrupt (One of X_IRQn). For example, I2C1_ER_IRQn.
void (* const rail_cfg_fn)(void); //! Configure function for pins on this rail.
void (* const rail_ctl_fn)(bool enabled); //! Control function for this rail.
} I2cBusConfig;
typedef enum I2cDevice {
I2C_DEVICE_AS3701B,
} I2cDevice;
typedef struct {
// I2C Configuration
/////////////////////////////////////////////////////////////////////////////
const I2cBusConfig *i2c_bus_configs;
const uint8_t i2c_bus_count;
const uint8_t *i2c_device_map;
const uint8_t i2c_device_count;
} BoardConfig;
// Button Configuration
/////////////////////////////////////////////////////////////////////////////
typedef struct {
const ButtonConfig buttons[NUM_BUTTONS];
const ButtonComConfig button_com;
} BoardConfigButton;
#include "board_definitions.h"

View file

@ -0,0 +1,23 @@
/*
* 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
#if BOARD_SILK
#include "board_silk.h"
#else
#error "Unknown board definition"
#endif

View 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 "util/misc.h"
#define BOARD_LSE_MODE RCC_LSE_Bypass
#define USE_PARALLEL_FLASH 1 // FIXME PBL-28872: Hack to get the "modern" flash layout.
// Fix when we add support for new flash
#define BOARD_I2C_BUS_COUNT (ARRAY_LENGTH(SILK_I2C_BUS_CONFIGS))
static const I2cBusConfig SILK_I2C_BUS_CONFIGS[] = {
// PMIC I2c
[0] = {
.i2c = I2C3,
.i2c_scl = { GPIOA, GPIO_Pin_8, GPIO_PinSource8, GPIO_AF_I2C3 },
.i2c_sda = { GPIOB, GPIO_Pin_8, GPIO_PinSource8, GPIO_AF9_I2C3 },
.clock_speed = 400000,
.duty_cycle = I2C_DutyCycle_16_9,
.clock_ctrl = RCC_APB1Periph_I2C3,
.ev_irq_channel = I2C3_EV_IRQn,
.er_irq_channel = I2C3_ER_IRQn,
},
};
static const uint8_t SILK_I2C_DEVICE_MAP[] = {
[I2C_DEVICE_AS3701B] = 0,
};
static const BoardConfig BOARD_CONFIG = {
.i2c_bus_configs = SILK_I2C_BUS_CONFIGS,
.i2c_bus_count = BOARD_I2C_BUS_COUNT,
.i2c_device_map = SILK_I2C_DEVICE_MAP,
.i2c_device_count = ARRAY_LENGTH(SILK_I2C_DEVICE_MAP),
};
static const BoardConfigButton BOARD_CONFIG_BUTTON = {
.buttons = {
[BUTTON_ID_BACK] =
{ "Back", GPIOC, GPIO_Pin_13, { EXTI_PortSourceGPIOC, 13 }, GPIO_PuPd_NOPULL },
[BUTTON_ID_UP] =
{ "Up", GPIOD, GPIO_Pin_2, { EXTI_PortSourceGPIOD, 2 }, GPIO_PuPd_DOWN },
[BUTTON_ID_SELECT] =
{ "Select", GPIOH, GPIO_Pin_0, { EXTI_PortSourceGPIOH, 0 }, GPIO_PuPd_DOWN },
[BUTTON_ID_DOWN] =
{ "Down", GPIOH, GPIO_Pin_1, { EXTI_PortSourceGPIOH, 1 }, GPIO_PuPd_DOWN },
},
.button_com = { 0 },
};
typedef enum {
QSpiPin_CS,
QSpiPin_SCLK,
QSpiPin_DQ0,
QSpiPin_DQ1,
QSpiPin_DQ2,
QSpiPin_DQ3,
QSpiPinCount,
} QSpiPin;
static const AfConfig BOARD_CONFIG_FLASH_PINS[] = {
[QSpiPin_CS] = {
.gpio = GPIOB,
.gpio_pin = GPIO_Pin_6,
.gpio_pin_source = GPIO_PinSource6,
.gpio_af = GPIO_AF10_QUADSPI,
},
[QSpiPin_SCLK] = {
.gpio = GPIOB,
.gpio_pin = GPIO_Pin_2,
.gpio_pin_source = GPIO_PinSource2,
.gpio_af = GPIO_AF9_QUADSPI,
},
[QSpiPin_DQ0] = {
.gpio = GPIOC,
.gpio_pin = GPIO_Pin_9,
.gpio_pin_source = GPIO_PinSource9,
.gpio_af = GPIO_AF9_QUADSPI,
},
[QSpiPin_DQ1] = {
.gpio = GPIOC,
.gpio_pin = GPIO_Pin_10,
.gpio_pin_source = GPIO_PinSource10,
.gpio_af = GPIO_AF9_QUADSPI,
},
[QSpiPin_DQ2] = {
.gpio = GPIOC,
.gpio_pin = GPIO_Pin_8,
.gpio_pin_source = GPIO_PinSource8,
.gpio_af = GPIO_AF9_QUADSPI,
},
[QSpiPin_DQ3] = {
.gpio = GPIOA,
.gpio_pin = GPIO_Pin_1,
.gpio_pin_source = GPIO_PinSource1,
.gpio_af = GPIO_AF9_QUADSPI,
},
};

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
#define DISP_COLS 144
#define DISP_ROWS 168
#define PBL_DISP_SHAPE_RECT

View file

@ -0,0 +1,80 @@
/*
* 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 "boot_tests.h"
#include "board/board.h"
#include "drivers/button.h"
#include "drivers/dbgserial.h"
#include "drivers/flash.h"
#include "system/bootbits.h"
#include "system/rtc_registers.h"
#include "util/misc.h"
#include "stm32f4xx.h"
#include <stdint.h>
#include <inttypes.h>
#include <stdbool.h>
static const int STUCK_BUTTON_THRESHOLD = 5;
bool is_button_stuck(void) {
// We store how many times each button has been pressed on previous boots in this
// rtc backup register. Every time when we boot without that button pressed that
// counter gets cleared. If the byte reaches 5, return a failure.
uint32_t button_counter_register = RTC_ReadBackupRegister(STUCK_BUTTON_REGISTER);
uint8_t* button_counter = (uint8_t*) (&button_counter_register);
bool result = false;
for (int button_id = 0; button_id < NUM_BUTTONS; button_id++) {
if (!button_is_pressed(button_id)) {
button_counter[button_id] = 0;
continue;
}
if (button_counter[button_id] >= STUCK_BUTTON_THRESHOLD) {
dbgserial_putstr("Stuck button register is invalid, clearing.");
dbgserial_print_hex(button_counter_register);
RTC_WriteBackupRegister(STUCK_BUTTON_REGISTER, 0);
return false;
}
button_counter[button_id] += 1;
if (button_counter[button_id] >= STUCK_BUTTON_THRESHOLD) {
dbgserial_print("Button id ");
dbgserial_print_hex(button_id);
dbgserial_putstr("is stuck!");
result = true;
}
}
if (button_counter_register != 0) {
dbgserial_print("Button was pushed on boot. Button counter: ");
dbgserial_print_hex(button_counter_register);
dbgserial_newline();
}
RTC_WriteBackupRegister(STUCK_BUTTON_REGISTER, button_counter_register);
return result;
}
bool is_flash_broken(void) {
return !flash_sanity_check();
}

View file

@ -0,0 +1,22 @@
/*
* 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>
bool is_button_stuck(void);
bool is_flash_broken(void);

View 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.
*/
#include "drivers/button.h"
#include "board/board.h"
#include "drivers/periph_config.h"
#include "drivers/gpio.h"
static void initialize_button_common(void) {
if (!BOARD_CONFIG_BUTTON.button_com.gpio) {
// This board doesn't use a button common pin.
return;
}
// Configure BUTTON_COM to drive low. When the button
// is pressed this pin will be connected to the pin for the
// button.
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_StructInit(&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = BOARD_CONFIG_BUTTON.button_com.gpio_pin;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(BOARD_CONFIG_BUTTON.button_com.gpio, &GPIO_InitStructure);
GPIO_WriteBit(BOARD_CONFIG_BUTTON.button_com.gpio, BOARD_CONFIG_BUTTON.button_com.gpio_pin, 0);
}
static void initialize_button(const ButtonConfig* config) {
// Configure the pin itself
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_StructInit(&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_PuPd = config->pull;
GPIO_InitStructure.GPIO_Pin = config->gpio_pin;
GPIO_Init(config->gpio, &GPIO_InitStructure);
}
bool button_is_pressed(ButtonId id) {
const ButtonConfig* button_config = &BOARD_CONFIG_BUTTON.buttons[id];
uint8_t bit = GPIO_ReadInputDataBit(button_config->gpio, button_config->gpio_pin);
return bit;
}
uint8_t button_get_state_bits(void) {
uint8_t button_state = 0x00;
for (int i = 0; i < NUM_BUTTONS; ++i) {
button_state |= (button_is_pressed(i) ? 0x01 : 0x00) << i;
}
return button_state;
}
void button_init(void) {
// Need to disable button wakeup functionality
// or the buttons don't register input
PWR_WakeUpPinCmd(PWR_WakeUp_Pin1, DISABLE);
PWR_WakeUpPinCmd(PWR_WakeUp_Pin2, DISABLE);
PWR_WakeUpPinCmd(PWR_WakeUp_Pin3, DISABLE);
periph_config_enable(RCC_APB2PeriphClockCmd, RCC_APB2Periph_SYSCFG);
initialize_button_common();
for (int i = 0; i < NUM_BUTTONS; ++i) {
initialize_button(&BOARD_CONFIG_BUTTON.buttons[i]);
}
periph_config_disable(RCC_APB2PeriphClockCmd, RCC_APB2Periph_SYSCFG);
}

View file

@ -0,0 +1,27 @@
/*
* 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 "button_id.h"
#include <stdbool.h>
#include <stdint.h>
void button_init(void);
bool button_is_pressed(ButtonId id);
uint8_t button_get_state_bits(void);

View file

@ -0,0 +1,41 @@
/*
* 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
//! @addtogroup UI
//! @{
//! @addtogroup Clicks
//! \brief Dealing with button input
//! @{
//! Button ID values
//! @see \ref click_recognizer_get_button_id()
typedef enum {
//! Back button
BUTTON_ID_BACK = 0,
//! Up button
BUTTON_ID_UP,
//! Select (middle) button
BUTTON_ID_SELECT,
//! Down button
BUTTON_ID_DOWN,
//! Total number of buttons
NUM_BUTTONS
} ButtonId;
//! @} // end addtogroup Clicks
//! @} // end addtogroup UI

View file

@ -0,0 +1,176 @@
/*
* 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/dbgserial.h"
#include "drivers/periph_config.h"
#include "drivers/gpio.h"
#include "stm32f4xx_rcc.h"
#include "stm32f4xx_gpio.h"
#include "stm32f4xx_usart.h"
#include "util/attributes.h"
#include "util/cobs.h"
#include "util/crc32.h"
#include "util/misc.h"
#include "util/misc.h"
#include "util/net.h"
#include <stdbool.h>
#define MAX_MESSAGE (256)
#define FRAME_DELIMITER '\x55'
#define PULSE_TRANSPORT_PUSH (0x5021)
#define PULSE_PROTOCOL_LOGGING (0x0003)
static const int SERIAL_BAUD_RATE = 1000000;
typedef struct PACKED PulseFrame {
net16 protocol;
unsigned char information[];
} PulseFrame;
typedef struct PACKED PushPacket {
net16 protocol;
net16 length;
unsigned char information[];
} PushPacket;
static const unsigned char s_message_header[] = {
// Message type: text
1,
// Source filename
'B', 'O', 'O', 'T', 'L', 'O', 'A', 'D', 'E', 'R', 0, 0, 0, 0, 0, 0,
// Log level and task
'*', '*',
// Timestamp
0, 0, 0, 0, 0, 0, 0, 0,
// Line number
0, 0,
};
static size_t s_message_length = 0;
static unsigned char s_message_buffer[MAX_MESSAGE];
void dbgserial_init(void) {
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
// Enable GPIO and UART3 peripheral clocks
periph_config_enable(RCC_APB2PeriphClockCmd, RCC_APB2Periph_USART1);
// USART_OverSampling8Cmd(USART3, ENABLE);
/* Connect PXx to USARTx_Tx*/
GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_USART1);
/* Connect PXx to USARTx_Rx*/
GPIO_PinAFConfig(GPIOB, GPIO_PinSource7, GPIO_AF_USART1);
/* Configure USART Tx as alternate function */
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/* Configure USART Rx as alternate function */
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
GPIO_Init(GPIOB, &GPIO_InitStructure);
/* USART configuration */
USART_InitStructure.USART_BaudRate = SERIAL_BAUD_RATE;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(USART1, &USART_InitStructure);
/* Enable USART */
USART_Cmd(USART1, ENABLE);
}
static void prv_putchar(uint8_t c) {
while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET) continue;
USART_SendData(USART1, c);
while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET) continue;
}
void dbgserial_print(const char* str) {
for (; *str && s_message_length < MAX_MESSAGE; ++str) {
if (*str == '\n') {
dbgserial_newline();
} else if (*str != '\r') {
s_message_buffer[s_message_length++] = *str;
}
}
}
void dbgserial_newline(void) {
uint32_t crc;
size_t raw_length = sizeof(PulseFrame) + sizeof(PushPacket) +
sizeof(s_message_header) + s_message_length + sizeof(crc);
unsigned char raw_packet[raw_length];
PulseFrame *frame = (PulseFrame *)raw_packet;
frame->protocol = hton16(PULSE_TRANSPORT_PUSH);
PushPacket *transport = (PushPacket *)frame->information;
transport->protocol = hton16(PULSE_PROTOCOL_LOGGING);
transport->length = hton16(sizeof(PushPacket) + sizeof(s_message_header) +
s_message_length);
unsigned char *app = transport->information;
memcpy(app, s_message_header, sizeof(s_message_header));
memcpy(&app[sizeof(s_message_header)], s_message_buffer,
s_message_length);
crc = crc32(CRC32_INIT, raw_packet, raw_length - sizeof(crc));
memcpy(&raw_packet[raw_length - sizeof(crc)], &crc, sizeof(crc));
unsigned char cooked_packet[MAX_SIZE_AFTER_COBS_ENCODING(raw_length)];
size_t cooked_length = cobs_encode(cooked_packet, raw_packet, raw_length);
prv_putchar(FRAME_DELIMITER);
for (size_t i = 0; i < cooked_length; ++i) {
if (cooked_packet[i] == FRAME_DELIMITER) {
prv_putchar('\0');
} else {
prv_putchar(cooked_packet[i]);
}
}
prv_putchar(FRAME_DELIMITER);
s_message_length = 0;
}
void dbgserial_putstr(const char* str) {
dbgserial_print(str);
dbgserial_newline();
}
void dbgserial_print_hex(uint32_t value) {
char buf[12];
itoa_hex(value, buf, sizeof(buf));
dbgserial_print(buf);
}

View file

@ -0,0 +1,34 @@
/*
* 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>
void dbgserial_init(void);
void dbgserial_putstr(const char* str);
void dbgserial_newline(void);
//! Like dbgserial_putstr, but without a terminating newline
void dbgserial_print(const char* str);
void dbgserial_print_hex(uint32_t value);
void dbgserial_putstr_fmt(char* buffer, unsigned int buffer_size, const char* fmt, ...)
__attribute__((format(printf, 3, 4)));

View file

@ -0,0 +1,34 @@
/*
* 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 <stdint.h>
void display_init(void);
void display_boot_splash(void);
void display_error_code(uint32_t);
//! Do whatever is necessary to prevent visual artifacts when resetting
//! the watch.
void display_prepare_for_reset(void);
//! Display the progress of a firmware update.
//!
//! The progress is expressed as a rational number less than or equal to 1.
//! When numerator == denominator, the progress indicator shows that the update
//! is complete.
void display_firmware_update_progress(uint32_t numerator, uint32_t denominator);

View file

@ -0,0 +1,47 @@
#define dead_face_width 92
#define dead_face_height 44
static const unsigned char dead_face_bits[] = {
0x00, 0x0c, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x30, 0x00,
0x00, 0x18, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x18, 0x00,
0x00, 0x30, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x0c, 0x00,
0x00, 0x60, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x06, 0x00,
0x00, 0xc0, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x03, 0x00,
0x00, 0x80, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8c, 0x01, 0x00,
0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd8, 0x00, 0x00,
0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00,
0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00,
0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd8, 0x00, 0x00,
0x00, 0x80, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8c, 0x01, 0x00,
0x00, 0xc0, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x03, 0x00,
0x00, 0x60, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x06, 0x00,
0x00, 0x30, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x0c, 0x00,
0x00, 0x18, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x18, 0x00,
0x00, 0x0c, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x30, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x80, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xfe, 0xff, 0xff, 0x7f, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0xe0, 0x7f, 0x00, 0x00, 0xfe, 0x07, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0xfe, 0x01, 0x00, 0x00, 0x80, 0x7f, 0x00, 0x00, 0x00,
0x00, 0x00, 0x80, 0x1f, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x01, 0x00, 0x00,
0x00, 0x00, 0xf0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x80, 0x0f, 0x00, 0x00,
0x00, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00,
0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00,
0x00, 0x80, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x01, 0x00,
0x00, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00,
0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00,
0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00,
0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00,
0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00,
0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00,
0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01,
0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03,
0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06,
0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c,
0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08 };

View file

@ -0,0 +1,11 @@
#define empty_bar_width 96
#define empty_bar_height 8
static const unsigned char empty_bar_bits[] = {
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };

View file

@ -0,0 +1,20 @@
#define error_url_width 109
#define error_url_height 14
static const unsigned char error_url_bits[] = {
0x00, 0x00, 0x06, 0x83, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x06, 0x83, 0x01, 0x00, 0x00, 0x00, 0x00, 0x0c,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x83, 0x01, 0x00, 0x00, 0x00,
0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0xd8, 0x78, 0x36, 0x9b, 0x79, 0xc0,
0xe3, 0xd9, 0x0c, 0x8c, 0x67, 0xdb, 0x3c, 0x1b, 0xf8, 0xfd, 0x7e, 0xbf,
0xfd, 0xe0, 0xf7, 0xfb, 0x1f, 0xc6, 0xef, 0xfb, 0x7e, 0x1f, 0x98, 0xcd,
0x66, 0xb3, 0xcd, 0x60, 0x36, 0x9b, 0x19, 0xc6, 0x6c, 0x18, 0x66, 0x03,
0x98, 0xfd, 0x66, 0xb3, 0xfd, 0x60, 0x30, 0x9b, 0x19, 0xc6, 0x6f, 0x18,
0x66, 0x03, 0x98, 0xfd, 0x66, 0xb3, 0xfd, 0x60, 0x30, 0x9b, 0x19, 0xc3,
0x6f, 0x18, 0x66, 0x03, 0x98, 0x0d, 0x66, 0xb3, 0x0d, 0x60, 0x36, 0x9b,
0x19, 0xc3, 0x60, 0x18, 0x66, 0x03, 0xf8, 0xfd, 0x7e, 0xbf, 0xfd, 0xec,
0xf7, 0x9b, 0x19, 0xc3, 0x6f, 0x18, 0x7e, 0x03, 0xd8, 0x78, 0x36, 0x9b,
0x79, 0xcc, 0xe3, 0x99, 0x99, 0x81, 0x67, 0x18, 0x3c, 0x03, 0x18, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00,
0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00 };

View file

@ -0,0 +1,103 @@
/*
* 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
// hex_digits_bits is indexed on the digit:
// ie: hex_digits_bits[0] is the bits to display 0 on
// the screen stored in xbm format
static const uint8_t hex_digits_bits[][36] = {
{
0x7C, 0x00, 0xFE, 0x00, 0xFF, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xC7, 0x01,
0xC7, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xC7, 0x01,
0xC7, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xFF, 0x01, 0xFE, 0x00, 0x7C, 0x00,
},
{
0x38, 0x00, 0x3C, 0x00, 0x3E, 0x00, 0x3E, 0x00, 0x38, 0x00, 0x38, 0x00,
0x38, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00,
0x38, 0x00, 0x38, 0x00, 0x38, 0x00, 0xFE, 0x00, 0xFE, 0x00, 0xFE, 0x00,
},
{
0x7C, 0x00, 0xFE, 0x00, 0xFF, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xC0, 0x01,
0xC0, 0x01, 0xE0, 0x00, 0xF0, 0x00, 0x78, 0x00, 0x3C, 0x00, 0x1E, 0x00,
0x0E, 0x00, 0x0F, 0x00, 0x07, 0x00, 0xFF, 0x01, 0xFF, 0x01, 0xFF, 0x01,
},
{
0x7C, 0x00, 0xFE, 0x00, 0xFF, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xC0, 0x01,
0xC0, 0x01, 0xF8, 0x00, 0x78, 0x00, 0xF8, 0x00, 0xC0, 0x01, 0xC0, 0x01,
0xC0, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xFF, 0x01, 0xFE, 0x00, 0x7C, 0x00,
},
{
0xE0, 0x00, 0xE0, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0xF8, 0x00, 0xF8, 0x00,
0xF8, 0x00, 0xFC, 0x00, 0xEC, 0x00, 0xEE, 0x00, 0xE6, 0x00, 0xFF, 0x01,
0xFF, 0x01, 0xFF, 0x01, 0xE0, 0x00, 0xE0, 0x00, 0xE0, 0x00, 0xE0, 0x00,
},
{
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x07, 0x00, 0x07, 0x00, 0x07, 0x00,
0x7F, 0x00, 0xFF, 0x00, 0xFF, 0x01, 0xC7, 0x01, 0xC0, 0x01, 0xC0, 0x01,
0xC7, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xFF, 0x01, 0xFE, 0x00, 0x7C, 0x00,
},
{
0x7C, 0x00, 0xFE, 0x00, 0xFF, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0x07, 0x00,
0x07, 0x00, 0x77, 0x00, 0xFF, 0x00, 0xFF, 0x01, 0xC7, 0x01, 0xC7, 0x01,
0xC7, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xFF, 0x01, 0xFE, 0x00, 0x7C, 0x00,
},
{
0xFF, 0x01, 0xFF, 0x01, 0xFF, 0x01, 0xE0, 0x00, 0xE0, 0x00, 0x70, 0x00,
0x70, 0x00, 0x70, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00,
0x1C, 0x00, 0x1C, 0x00, 0x1C, 0x00, 0x1C, 0x00, 0x1C, 0x00, 0x1C, 0x00,
},
{
0x7C, 0x00, 0xFE, 0x00, 0xFF, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xC7, 0x01,
0xC7, 0x01, 0xFE, 0x00, 0x7C, 0x00, 0xFE, 0x00, 0xC7, 0x01, 0xC7, 0x01,
0xC7, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xFF, 0x01, 0xFE, 0x00, 0x7C, 0x00,
},
{
0x7C, 0x00, 0xFE, 0x00, 0xFF, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xC7, 0x01,
0xC7, 0x01, 0xC7, 0x01, 0xFF, 0x01, 0xFE, 0x01, 0xDC, 0x01, 0xC0, 0x01,
0xC0, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xFF, 0x01, 0xFE, 0x00, 0x7C, 0x00,
},
{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0x00, 0xFE, 0x00,
0xFF, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xF0, 0x01, 0xFC, 0x01, 0xCE, 0x01,
0xC7, 0x01, 0xC7, 0x01, 0xE7, 0x01, 0xFF, 0x01, 0xDF, 0x01, 0xCE, 0x01,
},
{
0x07, 0x00, 0x07, 0x00, 0x07, 0x00, 0x07, 0x00, 0xE7, 0x00, 0xF7, 0x01,
0xFF, 0x01, 0xCF, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xC7, 0x01,
0xC7, 0x01, 0xC7, 0x01, 0xCF, 0x01, 0xFF, 0x01, 0xF7, 0x01, 0xE7, 0x00,
},
{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0x00, 0xFE, 0x00,
0xFF, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0x07, 0x00, 0x07, 0x00, 0x07, 0x00,
0x07, 0x00, 0xC7, 0x01, 0xC7, 0x01, 0xFF, 0x01, 0xFE, 0x00, 0x7C, 0x00,
},
{
0xC0, 0x01, 0xC0, 0x01, 0xC0, 0x01, 0xC0, 0x01, 0xCE, 0x01, 0xDF, 0x01,
0xFF, 0x01, 0xE7, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xC7, 0x01,
0xC7, 0x01, 0xC7, 0x01, 0xE7, 0x01, 0xFF, 0x01, 0xDF, 0x01, 0xCE, 0x01,
},
{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0x00, 0xFE, 0x00,
0xFF, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xFF, 0x01, 0xFF, 0x01,
0x07, 0x00, 0xC7, 0x01, 0xC7, 0x01, 0xFF, 0x01, 0xFE, 0x00, 0x7C, 0x00,
},
{
0xE0, 0x00, 0xF0, 0x00, 0xF8, 0x00, 0x38, 0x00, 0xFE, 0x00, 0xFE, 0x00,
0xFE, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00,
0x38, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00,
}
};

View file

@ -0,0 +1,35 @@
#define pebble_logo_width 105
#define pebble_logo_height 27
static const unsigned char pebble_logo_bits[] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x03, 0x00, 0x18, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x03, 0x00,
0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00,
0x03, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60,
0x00, 0x00, 0x03, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x60, 0x00, 0x00, 0x03, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0xE0,
0x03, 0x00, 0x1F, 0x60, 0xF8, 0x00, 0xC3, 0x07, 0x18, 0xC0, 0x07, 0x00,
0x00, 0xF8, 0x0F, 0xC0, 0x7F, 0x60, 0xFE, 0x03, 0xF3, 0x1F, 0x18, 0xF0,
0x1F, 0x00, 0x00, 0x0C, 0x18, 0x60, 0xC0, 0x60, 0x03, 0x06, 0x1B, 0x30,
0x18, 0x18, 0x30, 0x00, 0x00, 0x06, 0x30, 0x30, 0x80, 0xE1, 0x01, 0x0C,
0x0F, 0x60, 0x18, 0x0C, 0x60, 0x00, 0x00, 0x03, 0x60, 0x18, 0x00, 0xE3,
0x00, 0x18, 0x07, 0xC0, 0x18, 0x06, 0xC0, 0x00, 0x80, 0x01, 0x40, 0x0C,
0x00, 0x62, 0x00, 0x10, 0x03, 0x80, 0x18, 0x03, 0x80, 0x00, 0x80, 0x01,
0xC0, 0x0C, 0x00, 0x66, 0x00, 0x30, 0x03, 0x80, 0x19, 0x03, 0x80, 0x01,
0x80, 0x01, 0xC0, 0xFC, 0xFF, 0x67, 0x00, 0x30, 0x03, 0x80, 0x19, 0xFF,
0xFF, 0x01, 0x80, 0x01, 0xC0, 0xFC, 0xFF, 0x63, 0x00, 0x30, 0x03, 0x80,
0x19, 0xFF, 0xFF, 0x00, 0x80, 0x01, 0xC0, 0x0C, 0x00, 0x60, 0x00, 0x30,
0x03, 0x80, 0x19, 0x03, 0x00, 0x00, 0x80, 0x01, 0xC0, 0x0C, 0x00, 0x60,
0x00, 0x30, 0x03, 0x80, 0x19, 0x03, 0x00, 0x00, 0x80, 0x01, 0x40, 0x0C,
0x00, 0x62, 0x00, 0x10, 0x03, 0x80, 0x18, 0x03, 0x80, 0x00, 0x80, 0x03,
0x60, 0x18, 0x00, 0xC3, 0x00, 0x18, 0x06, 0xC0, 0x18, 0x06, 0xC0, 0x00,
0x80, 0x07, 0x30, 0x30, 0x80, 0x81, 0x01, 0x0C, 0x0C, 0x60, 0x18, 0x0C,
0x60, 0x00, 0x80, 0x0D, 0x18, 0x60, 0xC0, 0x00, 0x03, 0x06, 0x18, 0x30,
0x38, 0x18, 0x30, 0x00, 0x80, 0xF9, 0x0F, 0xC0, 0x7F, 0x00, 0xFE, 0x03,
0xF0, 0x1F, 0x70, 0xF0, 0x1F, 0x00, 0x80, 0xE1, 0x03, 0x00, 0x1F, 0x00,
0xF8, 0x00, 0xC0, 0x07, 0x60, 0xC0, 0x07, 0x00, 0x80, 0x01, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, };

View file

@ -0,0 +1,365 @@
/*
* 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/display.h"
#include "drivers/periph_config.h"
#include "drivers/gpio.h"
#include "drivers/dbgserial.h"
#include "util/attributes.h"
#include "util/delay.h"
#include "stm32f4xx_dma.h"
#include "stm32f4xx_gpio.h"
#include "stm32f4xx_rcc.h"
#include "stm32f4xx_spi.h"
#include <stdbool.h>
#include <stdint.h>
// Bootloader images
#include "drivers/display/resources/hex_digits.h"
#include "drivers/display/resources/dead_face.xbm"
#include "drivers/display/resources/empty_bar.xbm"
#include "drivers/display/resources/error_url.xbm"
#include "drivers/display/resources/pebble_logo.xbm"
#define DISP_LINE_BYTES (DISP_COLS / 8)
#define DISP_LINE_WORDS (((DISP_COLS - 1) / 32) + 1)
// GPIO constants
#define DISP_SPI (SPI2)
#define DISP_GPIO (GPIOB)
#define DISPLAY_SPI_CLOCK (RCC_APB1Periph_SPI2)
#define DISP_PIN_SCS (GPIO_Pin_9)
#define DISP_PINSOURCE_SCS (GPIO_PinSource9)
#define DISP_PIN_SCLK (GPIO_Pin_10)
#define DISP_PINSOURCE_SCKL (GPIO_PinSource10)
#define DISP_PIN_SI (GPIO_Pin_15)
#define DISP_PINSOURCE_SI (GPIO_PinSource15)
#define DISP_LCD_GPIO (GPIOA)
#define DISP_PIN_LCD (GPIO_Pin_0)
#define DISP_PINSOURCE_LCD (GPIO_PinSource0)
#define DISP_MODE_STATIC (0x00)
#define DISP_MODE_WRITE (0x80)
#define DISP_MODE_CLEAR (0x20)
// The bootloader leaves SYSCLK at defaults (connected to HSI at 16 Mhz),
// and there are no prescalers on any of the buses. Since the display
// can handle a max of 2 Mhz, we want to divide by 8
#define DISPLAY_PERIPH_PRESCALER (SPI_BaudRatePrescaler_8)
static void prv_enable_display_spi_clock() {
periph_config_enable(RCC_APB1PeriphClockCmd, DISPLAY_SPI_CLOCK);
}
static void prv_disable_display_spi_clock() {
periph_config_disable(RCC_APB1PeriphClockCmd, DISPLAY_SPI_CLOCK);
}
static void prv_enable_chip_select(void) {
GPIO_WriteBit(DISP_GPIO, DISP_PIN_SCS, Bit_SET);
// required setup time > 3us
delay_us(7);
}
static void prv_disable_chip_select(void) {
// delay while last byte is emitted by the SPI peripheral
delay_us(7);
GPIO_WriteBit(DISP_GPIO, DISP_PIN_SCS, Bit_RESET);
// hold time > 1us
// produce a delay 4ms
delay_us(4);
}
//! These functions needed to be called around any commands that
//! are sent to the display. NOINLINE only for code size savings.
static NOINLINE void prv_enable_display_access(void) {
prv_enable_display_spi_clock();
prv_enable_chip_select();
}
static NOINLINE void prv_disable_display_access(void) {
prv_disable_chip_select();
prv_disable_display_spi_clock();
}
//! Write a single byte synchronously to the display. This is the only practical
//! way to write to the display in the bootloader since we don't have interrupts.
static void prv_display_write_byte(uint8_t d) {
// Block until the tx buffer is empty
SPI_I2S_SendData(DISP_SPI, d);
while (!SPI_I2S_GetFlagStatus(DISP_SPI, SPI_I2S_FLAG_TXE)) {}
}
// Since all these values are constant we can save code space
// by storing the initialized struct in memory rather than
// needing to copy in each value
static GPIO_InitTypeDef s_disp_gpio_init = {
.GPIO_OType = GPIO_OType_PP,
.GPIO_PuPd = GPIO_PuPd_NOPULL,
.GPIO_Mode = GPIO_Mode_AF,
.GPIO_Speed = GPIO_Speed_50MHz,
.GPIO_Pin = DISP_PIN_SCLK | DISP_PIN_SI
};
static SPI_InitTypeDef s_disp_spi_init = {
.SPI_Direction = SPI_Direction_1Line_Tx, // Write-only SPI
.SPI_Mode = SPI_Mode_Master,
.SPI_DataSize = SPI_DataSize_8b,
.SPI_CPOL = SPI_CPOL_Low,
.SPI_CPHA = SPI_CPHA_1Edge,
.SPI_NSS = SPI_NSS_Soft,
// We want the SPI clock to run at 2MHz
.SPI_BaudRatePrescaler = DISPLAY_PERIPH_PRESCALER,
// MSB order allows us to write pixels out without reversing bytes, but command bytes
// have to be reversed
.SPI_FirstBit = SPI_FirstBit_MSB,
.SPI_CRCPolynomial = 7 // default
};
static void prv_display_start(void) {
// Setup alternate functions
GPIO_PinAFConfig(DISP_GPIO, DISP_PINSOURCE_SCKL, GPIO_AF_SPI2);
GPIO_PinAFConfig(DISP_GPIO, DISP_PINSOURCE_SI, GPIO_AF_SPI2);
// Init display GPIOs
GPIO_Init(DISP_GPIO, &s_disp_gpio_init);
// Init CS
s_disp_gpio_init.GPIO_Mode = GPIO_Mode_OUT;
s_disp_gpio_init.GPIO_OType = GPIO_OType_PP;
s_disp_gpio_init.GPIO_Pin = DISP_PIN_SCS;
GPIO_Init(DISP_GPIO, &s_disp_gpio_init);
// Init LCD Control
s_disp_gpio_init.GPIO_Mode = GPIO_Mode_OUT;
s_disp_gpio_init.GPIO_OType = GPIO_OType_PP;
s_disp_gpio_init.GPIO_Pin = DISP_PIN_LCD;
GPIO_Init(DISP_LCD_GPIO, &s_disp_gpio_init);
// Set up a SPI bus on SPI2
SPI_I2S_DeInit(DISP_SPI);
SPI_Init(DISP_SPI, &s_disp_spi_init);
SPI_Cmd(DISP_SPI, ENABLE);
// Hold LCD on
GPIO_WriteBit(DISP_LCD_GPIO, DISP_PIN_LCD, Bit_SET);
}
// Clear-all mode is entered by sending 0x04 to the panel
void display_clear(void) {
prv_enable_display_access();
prv_display_write_byte(DISP_MODE_CLEAR);
prv_display_write_byte(0x00);
prv_disable_display_access();
}
//! Static mode is entered by sending 0x00 to the panel
//! This stops any further updates being registered by
//! the display, preventing corruption on shutdown / boot
static void prv_display_enter_static(void) {
prv_enable_display_access();
prv_display_write_byte(DISP_MODE_STATIC);
prv_display_write_byte(0x00);
prv_display_write_byte(0x00);
prv_disable_display_access();
}
// Helper to reverse command bytes
static uint8_t prv_reverse_bits(uint8_t input) {
uint8_t result;
__asm__ ("rev %[result], %[input]\n\t"
"rbit %[result], %[result]"
: [result] "=r" (result)
: [input] "r" (input));
return result;
}
static void prv_display_start_write(void) {
prv_enable_display_access();
prv_display_write_byte(DISP_MODE_WRITE);
}
static void prv_display_write_line(uint8_t line_addr, const uint8_t *line) {
// 1-indexed (ugh) 8bit line address (1-168)
prv_display_write_byte(prv_reverse_bits(line_addr + 1));
for (int i = 0; i < DISP_LINE_BYTES; ++i) {
prv_display_write_byte(prv_reverse_bits(line[i]));
}
prv_display_write_byte(0x00);
}
static void prv_display_end_write(void) {
prv_display_write_byte(0x00);
prv_disable_display_access();
}
// Round a bit offset to a byte offset
static unsigned prv_round_to_byte(unsigned x) {
return (x + 7) >> 3;
}
// Draw bitmap onto buffer.
static void prv_draw_bitmap(const uint8_t *bitmap, unsigned x_offset, unsigned y_offset,
unsigned width, unsigned height,
uint8_t buffer[DISP_ROWS][DISP_LINE_BYTES]) {
// Need to convert offsets to bytes for the horizontal dimensions
x_offset = prv_round_to_byte(x_offset);
width = prv_round_to_byte(width);
for (unsigned i = 0; i < height; i++) {
memcpy(buffer[i + y_offset] + x_offset, bitmap + i * width, width);
}
}
static void prv_display_buffer(uint8_t buffer[DISP_ROWS][DISP_LINE_BYTES]) {
prv_display_start_write();
for (int i = 0; i < DISP_ROWS; i++) {
prv_display_write_line(i, buffer[i]);
}
prv_display_end_write();
}
void display_boot_splash(void) {
uint8_t buffer[DISP_ROWS][DISP_LINE_BYTES];
// Draw black
memset(buffer, 0x00, sizeof(buffer));
prv_draw_bitmap(pebble_logo_bits, 16, 64, pebble_logo_width, pebble_logo_height, buffer);
prv_display_buffer(buffer);
}
static void prv_set_bit(uint8_t x, uint8_t y, uint8_t buffer[DISP_ROWS][DISP_LINE_BYTES]) {
buffer[y][x / 8] |= (1 << (x % 8));
}
static void prv_render_char(unsigned digit, uint8_t x_offset_bits, uint8_t y_offset,
uint8_t buffer[DISP_ROWS][DISP_LINE_BYTES]) {
const unsigned char_rows = 18, char_cols = 9;
const uint8_t * char_data = hex_digits_bits[digit];
// Each character requires 2 bytes of storage
for (unsigned y = 0; y < char_rows; y++) {
unsigned cur_y = y_offset + y;
uint8_t first_byte = char_data[2 * y];
for (unsigned x = 0; x < char_cols; x++) {
bool pixel;
if (x < 8) { // Pixel is in first byte
pixel = first_byte & (1 << x);
}
else { // Last pixel is in second byte
pixel = char_data[2 * y + 1] & 1;
}
// The buffer is already all black, so just set the white pixels
if (pixel) {
prv_set_bit(x_offset_bits + x, cur_y, buffer);
}
}
}
}
static void prv_draw_code(uint32_t code, uint8_t buffer[DISP_ROWS][DISP_LINE_BYTES]) {
const unsigned y_offset = 116; // beneath sad face, above url
unsigned x_offset = 28; // Aligned with sad face
// Extract and print digits
for (int i = 7; i >= 0; i--) {
// Mask off 4 bits at a time
uint32_t mask = (0xf << (i * 4));
unsigned digit = ((code & mask) >> (i * 4));
prv_render_char(digit, x_offset, y_offset, buffer);
// Each character is 9px wide plus 2px of padding
x_offset += 11;
}
}
void display_error_code(uint32_t code) {
uint8_t buffer[DISP_ROWS][DISP_LINE_BYTES];
memset(buffer, 0x00, sizeof(buffer));
prv_draw_bitmap(dead_face_bits, 24, 32, dead_face_width, dead_face_height, buffer);
prv_draw_code(code, buffer);
prv_draw_bitmap(error_url_bits, 16, 144, error_url_width, error_url_height, buffer);
prv_display_buffer(buffer);
}
//! Do whatever is necessary to prevent visual artifacts when resetting
//! the watch.
void display_prepare_for_reset(void) {
prv_display_enter_static();
}
//! Display the progress of a firmware update.
//!
//! The progress is expressed as a rational number less than or equal to 1.
//! When numerator == denominator, the progress indicator shows that the update
//! is complete.
void display_firmware_update_progress(uint32_t numerator, uint32_t denominator) {
// Dimensions for progress bar
const unsigned x_offset = 24, y_offset = 106,
inner_bar_width = 94, inner_bar_height = 6;
static unsigned s_prev_num_pixels = -1;
// Calculate number of pixels to fill in
unsigned num_pixels = inner_bar_width * numerator / denominator;
if (num_pixels == s_prev_num_pixels) {
return;
}
s_prev_num_pixels = num_pixels;
uint8_t buffer[DISP_ROWS][DISP_LINE_BYTES];
memset(buffer, 0x00, sizeof(buffer));
prv_draw_bitmap(pebble_logo_bits, 16, 64, pebble_logo_width, pebble_logo_height, buffer);
prv_draw_bitmap(empty_bar_bits, x_offset, y_offset, empty_bar_width, empty_bar_height, buffer);
for (unsigned y = 0; y < inner_bar_height; y++) {
for (unsigned x = 0; x < num_pixels; x++) {
// Add 1 to offsets so we don't write into outer box
prv_set_bit(x + x_offset + 1, y_offset + y + 1, buffer);
}
}
prv_display_buffer(buffer);
}
void display_init(void) {
prv_enable_display_spi_clock();
prv_display_start();
prv_disable_display_spi_clock();
}

View file

@ -0,0 +1,46 @@
/*
* 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"
typedef enum {
ExtiTrigger_Rising,
ExtiTrigger_Falling,
ExtiTrigger_RisingFalling
} ExtiTrigger;
//! See section 12.2.5 "External interrupt/event line mapping" in the STM32F2 reference manual
typedef enum {
ExtiLineOther_RTCAlarm = 17,
ExtiLineOther_RTCWakeup = 22
} ExtiLineOther;
typedef void (*ExtiHandlerCallback)(void);
//! Configures the given EXTI and NVIC for the given configuration.
void exti_configure_pin(ExtiConfig cfg, ExtiTrigger trigger, ExtiHandlerCallback cb);
//! Configures the given EXTI and NVIC for the given configuration.
void exti_configure_other(ExtiLineOther exti_line, ExtiTrigger trigger);
static inline void exti_enable(ExtiConfig config);
static inline void exti_disable(ExtiConfig config);
void exti_enable_other(ExtiLineOther);
void exti_disable_other(ExtiLineOther);
#include "exti.inl.h"

View file

@ -0,0 +1,27 @@
/*
* 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.
*/
//! @file exti.inl.h
//!
//! Helper functions intended to be inlined into the calling code.
static inline void exti_enable(ExtiConfig config) {
exti_enable_other(config.exti_line);
}
static inline void exti_disable(ExtiConfig config) {
exti_disable_other(config.exti_line);
}

View file

@ -0,0 +1,40 @@
/*
* 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>
//! Configure the micro's peripherals to communicate with the flash chip
void flash_init(void);
//! Read 1 or more bytes starting at the specified 24bit address into
//! the provided buffer. This function does no range checking, so it is
//! currently possible to run off the end of the flash.
//!
//! @param buffer A byte-buffer that will be used to store the data
//! read from flash.
//! @param start_addr The address of the first byte to be read from flash.
//! @param buffer_size The total number of bytes to be read from flash.
void flash_read_bytes(uint8_t* buffer, uint32_t start_addr, uint32_t buffer_size);
//! Check if we can talk to the flash.
//! @return true if the CFI table can be queried.
bool flash_sanity_check(void);
//! Get the checksum of a region of flash
uint32_t flash_calculate_checksum(uint32_t flash_addr, uint32_t length);

View file

@ -0,0 +1,38 @@
/*
* 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 "util/crc32.h"
#define CRC_CHUNK_SIZE 1024
uint32_t flash_calculate_checksum(uint32_t flash_addr, uint32_t num_bytes) {
uint8_t buffer[CRC_CHUNK_SIZE];
uint32_t crc = CRC32_INIT;
while (num_bytes > CRC_CHUNK_SIZE) {
flash_read_bytes(buffer, flash_addr, CRC_CHUNK_SIZE);
crc = crc32(crc, buffer, CRC_CHUNK_SIZE);
num_bytes -= CRC_CHUNK_SIZE;
flash_addr += CRC_CHUNK_SIZE;
}
flash_read_bytes(buffer, flash_addr, num_bytes);
return crc32(crc, buffer, num_bytes);
}

View file

@ -0,0 +1,209 @@
/*
* 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 <stdbool.h>
#include <stdint.h>
#include "board/board.h"
#include "drivers/flash.h"
#include "drivers/gpio.h"
#include "drivers/periph_config.h"
#include "util/delay.h"
#include "stm32f4xx_gpio.h"
#include "stm32f4xx_qspi.h"
#define MX25U_FASTREAD_DUMMYCYCLES 4
typedef enum MX25UCommand {
// SPI/QSPI Commands
MX25UCommand_FastRead = 0x0B, // FAST_READ
MX25UCommand_QSPIEnable = 0x35, // QPI
MX25UCommand_ResetEnable = 0x66, // RSTEN
MX25UCommand_Reset = 0x99, // RST
// QSPI only commands
MX25UCommand_QSPI_ID = 0xAF, // QPIID
} MX25UCommand;
// Helpful Enums
typedef enum {
QSPIFlag_Retain = 0,
QSPIFlag_ClearTC = 1,
} QSPIFlag;
static void prv_enable_qspi_clock(void) {
periph_config_enable(RCC_AHB3PeriphClockCmd, RCC_AHB3Periph_QSPI);
}
static void prv_disable_qspi_clock(void) {
periph_config_disable(RCC_AHB3PeriphClockCmd, RCC_AHB3Periph_QSPI);
}
static void prv_set_num_data_bytes(uint32_t length) {
// From the docs: QSPI_DataLength: Number of data to be retrieved, value+1.
// so 0 is 1 byte, so we substract 1 from the length. -1 is read the entire flash length.
QSPI_SetDataLength(length - 1);
}
static void prv_wait_for_qspi_transfer_complete(QSPIFlag actions) {
while (QSPI_GetFlagStatus(QSPI_FLAG_TC) == RESET) { }
if (actions == QSPIFlag_ClearTC) {
QSPI_ClearFlag(QSPI_FLAG_TC);
}
}
static void prv_wait_for_qspi_not_busy(void) {
while (QSPI_GetFlagStatus(QSPI_FLAG_BUSY) != RESET) { }
}
static void prv_quad_enable() {
QSPI_ComConfig_InitTypeDef qspi_com_config;
QSPI_ComConfig_StructInit(&qspi_com_config);
qspi_com_config.QSPI_ComConfig_FMode = QSPI_ComConfig_FMode_Indirect_Write;
qspi_com_config.QSPI_ComConfig_IMode = QSPI_ComConfig_IMode_1Line;
qspi_com_config.QSPI_ComConfig_Ins = MX25UCommand_QSPIEnable;
QSPI_ComConfig_Init(&qspi_com_config);
prv_wait_for_qspi_transfer_complete(QSPIFlag_ClearTC);
prv_wait_for_qspi_not_busy();
}
static void prv_flash_reset(void) {
QSPI_ComConfig_InitTypeDef qspi_com_config;
QSPI_ComConfig_StructInit(&qspi_com_config);
qspi_com_config.QSPI_ComConfig_FMode = QSPI_ComConfig_FMode_Indirect_Write;
qspi_com_config.QSPI_ComConfig_IMode = QSPI_ComConfig_IMode_4Line;
qspi_com_config.QSPI_ComConfig_Ins = MX25UCommand_ResetEnable;
QSPI_ComConfig_Init(&qspi_com_config);
prv_wait_for_qspi_transfer_complete(QSPIFlag_ClearTC);
QSPI_ComConfig_StructInit(&qspi_com_config);
qspi_com_config.QSPI_ComConfig_FMode = QSPI_ComConfig_FMode_Indirect_Write;
qspi_com_config.QSPI_ComConfig_IMode = QSPI_ComConfig_IMode_4Line;
qspi_com_config.QSPI_ComConfig_Ins = MX25UCommand_Reset;
QSPI_ComConfig_Init(&qspi_com_config);
prv_wait_for_qspi_transfer_complete(QSPIFlag_ClearTC);
delay_us(12000); // 12ms reset if busy with an erase!
// 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
prv_quad_enable();
}
static bool prv_flash_check_whoami(void) {
const unsigned int num_whoami_bytes = 3;
prv_set_num_data_bytes(num_whoami_bytes);
QSPI_ComConfig_InitTypeDef qspi_com_config;
QSPI_ComConfig_StructInit(&qspi_com_config);
qspi_com_config.QSPI_ComConfig_FMode = QSPI_ComConfig_FMode_Indirect_Read;
qspi_com_config.QSPI_ComConfig_DMode = QSPI_ComConfig_DMode_4Line;
qspi_com_config.QSPI_ComConfig_IMode = QSPI_ComConfig_IMode_4Line;
qspi_com_config.QSPI_ComConfig_Ins = MX25UCommand_QSPI_ID;
QSPI_ComConfig_Init(&qspi_com_config);
prv_wait_for_qspi_transfer_complete(QSPIFlag_ClearTC);
uint32_t read_whoami = 0;
for (unsigned int i = 0; i < num_whoami_bytes; ++i) {
read_whoami |= QSPI_ReceiveData8() << (8 * i);
}
prv_wait_for_qspi_not_busy();
const uint32_t expected_whoami = 0x3725c2;
if (read_whoami == expected_whoami) {
return true;
} else {
return false;
}
}
void flash_init(void) {
prv_enable_qspi_clock();
// init GPIOs
for (unsigned i = 0; i < QSpiPinCount; ++i) {
gpio_af_init(&BOARD_CONFIG_FLASH_PINS[i], GPIO_OType_PP, GPIO_Speed_100MHz, GPIO_PuPd_NOPULL);
}
// Init QSPI peripheral
QSPI_InitTypeDef qspi_config;
QSPI_StructInit(&qspi_config);
qspi_config.QSPI_SShift = QSPI_SShift_HalfCycleShift;
qspi_config.QSPI_Prescaler = 0x0;
qspi_config.QSPI_CKMode = QSPI_CKMode_Mode0;
qspi_config.QSPI_CSHTime = QSPI_CSHTime_1Cycle;
qspi_config.QSPI_FSize = 22; // 2^23 = 8MB. -> 23 - 1 = 22
qspi_config.QSPI_FSelect = QSPI_FSelect_1;
qspi_config.QSPI_DFlash = QSPI_DFlash_Disable;
QSPI_Init(&qspi_config);
QSPI_Cmd(ENABLE);
// Must call quad_enable first, all commands are QSPI
prv_quad_enable();
// Reset the flash to stop any program's or erase in progress from before reboot
prv_flash_reset();
prv_disable_qspi_clock();
}
bool flash_sanity_check(void) {
prv_enable_qspi_clock();
bool result = prv_flash_check_whoami();
prv_disable_qspi_clock();
return result;
}
void flash_read_bytes(uint8_t *buffer_ptr, uint32_t start_addr, uint32_t buffer_size) {
prv_enable_qspi_clock();
prv_set_num_data_bytes(buffer_size);
QSPI_ComConfig_InitTypeDef qspi_com_config;
QSPI_ComConfig_StructInit(&qspi_com_config);
qspi_com_config.QSPI_ComConfig_FMode = QSPI_ComConfig_FMode_Indirect_Read;
qspi_com_config.QSPI_ComConfig_DMode = QSPI_ComConfig_DMode_4Line;
qspi_com_config.QSPI_ComConfig_DummyCycles = MX25U_FASTREAD_DUMMYCYCLES;
qspi_com_config.QSPI_ComConfig_ADMode = QSPI_ComConfig_ADMode_4Line;
qspi_com_config.QSPI_ComConfig_IMode = QSPI_ComConfig_IMode_4Line;
qspi_com_config.QSPI_ComConfig_ADSize = QSPI_ComConfig_ADSize_24bit;
qspi_com_config.QSPI_ComConfig_Ins = MX25UCommand_FastRead;
QSPI_ComConfig_Init(&qspi_com_config);
QSPI_SetAddress(start_addr);
uint8_t *write_ptr = buffer_ptr;
for (unsigned i = 0; i < buffer_size; ++i) {
write_ptr[i] = QSPI_ReceiveData8();
}
QSPI_ClearFlag(QSPI_FLAG_TC);
prv_wait_for_qspi_not_busy();
prv_disable_qspi_clock();
}

View file

@ -0,0 +1,43 @@
/*
* 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/gpio.h"
#include <stdint.h>
#define GPIO_EN_MASK ((RCC_AHB1ENR_GPIOHEN << 1) - 1)
void gpio_enable_all(void) {
RCC->AHB1ENR |= GPIO_EN_MASK;
}
void gpio_disable_all(void) {
RCC->AHB1ENR &= ~GPIO_EN_MASK;
}
void gpio_af_init(const AfConfig *af_config, GPIOOType_TypeDef otype, GPIOSpeed_TypeDef speed,
GPIOPuPd_TypeDef pupd) {
GPIO_InitTypeDef init = {
.GPIO_Pin = af_config->gpio_pin,
.GPIO_Mode = GPIO_Mode_AF,
.GPIO_Speed = speed,
.GPIO_OType = otype,
.GPIO_PuPd = pupd
};
GPIO_PinAFConfig(af_config->gpio, af_config->gpio_pin_source, af_config->gpio_af);
GPIO_Init(af_config->gpio, &init);
}

View file

@ -0,0 +1,28 @@
/*
* 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 "stm32f4xx_gpio.h"
#include "board/board.h"
void gpio_enable_all(void);
void gpio_disable_all(void);
void gpio_af_init(const AfConfig *af_config, GPIOOType_TypeDef otype, GPIOSpeed_TypeDef speed,
GPIOPuPd_TypeDef pupd);

View file

@ -0,0 +1,743 @@
/*
* 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 "util/misc.h"
#include "drivers/i2c.h"
#include "drivers/periph_config.h"
#include "drivers/gpio.h"
#include "system/passert.h"
#include "system/logging.h"
#include "util/delay.h"
#include <inttypes.h>
#include "stm32f4xx_gpio.h"
#include "stm32f4xx_rcc.h"
#include "stm32f4xx_i2c.h"
#define portBASE_TYPE int
#define pdFALSE 0
#define portEND_SWITCHING_ISR(expr) (void)(expr)
#define I2C_ERROR_TIMEOUT_MS (1000)
#define I2C_TIMEOUT_ATTEMPTS_MAX (2 * 1000 * 1000)
#define I2C_NORMAL_MODE_CLOCK_SPEED_MAX (100000)
#define I2C_NACK_COUNT_MAX (1000) // MFI NACKs while busy. We delay ~1ms between retries so this is approximately a 1s timeout
#define I2C_READ_WRITE_BIT (0x01)
typedef struct I2cTransfer {
uint8_t device_address;
bool read_not_write; //True for read, false for write
uint8_t register_address;
uint8_t size;
uint8_t idx;
uint8_t *data;
enum TransferState {
TRANSFER_STATE_WRITE_ADDRESS_TX,
TRANSFER_STATE_WRITE_REG_ADDRESS,
TRANSFER_STATE_REPEAT_START,
TRANSFER_STATE_WRITE_ADDRESS_RX,
TRANSFER_STATE_WAIT_FOR_DATA,
TRANSFER_STATE_READ_DATA,
TRANSFER_STATE_WRITE_DATA,
TRANSFER_STATE_END_WRITE,
TRANSFER_STATE_INVALID,
} state;
bool result;
uint16_t nack_count;
}I2cTransfer;
typedef struct I2cBus{
I2C_TypeDef *i2c;
uint8_t user_count;
I2cTransfer transfer;
volatile bool busy;
}I2cBus;
static I2cBus i2c_buses[BOARD_I2C_BUS_COUNT];
static uint32_t s_guard_events[] = {
I2C_EVENT_MASTER_MODE_SELECT,
I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED,
I2C_EVENT_MASTER_BYTE_TRANSMITTED,
I2C_EVENT_MASTER_MODE_SELECT,
I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED,
I2C_EVENT_MASTER_BYTE_RECEIVED,
I2C_EVENT_MASTER_BYTE_TRANSMITTING,
I2C_EVENT_MASTER_BYTE_TRANSMITTED,
};
static bool s_initialized = false;
/*----------------SEMAPHORE/LOCKING FUNCTIONS--------------------------*/
static void bus_lock(I2cBus *bus) {
}
static void bus_unlock(I2cBus *bus) {
}
static bool semaphore_take(I2cBus *bus) {
return true;
}
static bool semaphore_wait(I2cBus *bus) {
bus->busy = true;
volatile uint32_t timeout_attempts = I2C_TIMEOUT_ATTEMPTS_MAX;
while ((timeout_attempts-- > 0) && (bus->busy));
bus->busy = false;
return (timeout_attempts != 0);
}
static void semaphore_give(I2cBus *bus) {
}
/*-------------------BUS/PIN CONFIG FUNCTIONS--------------------------*/
//! Configure bus power supply control pin as output
//! Lock bus and peripheral config access before configuring pins
void i2c_bus_rail_ctl_config(OutputConfig pin_config) {
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_StructInit(&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = pin_config.gpio_pin;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(pin_config.gpio, &GPIO_InitStructure);
}
//! Configure bus pins for use by I2C peripheral
//! Lock bus and peripheral config access before configuring pins
static void bus_pin_cfg_i2c(AfConfig pin_config) {
GPIO_InitTypeDef gpio_init_struct;
gpio_init_struct.GPIO_Pin = pin_config.gpio_pin;
gpio_init_struct.GPIO_Mode = GPIO_Mode_AF;
gpio_init_struct.GPIO_Speed = GPIO_Speed_50MHz;
gpio_init_struct.GPIO_OType = GPIO_OType_OD;
gpio_init_struct.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(pin_config.gpio, &gpio_init_struct);
GPIO_PinAFConfig(pin_config.gpio, pin_config.gpio_pin_source, pin_config.gpio_af);
}
//! Configure bus pin as input
//! Lock bus and peripheral config access before use
static void bus_pin_cfg_input(AfConfig pin_config) {
// Configure pin as high impedance input
GPIO_InitTypeDef gpio_init_struct;
gpio_init_struct.GPIO_Pin = pin_config.gpio_pin;
gpio_init_struct.GPIO_Mode = GPIO_Mode_IN;
gpio_init_struct.GPIO_Speed = GPIO_Speed_2MHz;
gpio_init_struct.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(pin_config.gpio, &gpio_init_struct);
}
//! Configure bus pin as output
//! Lock bus and peripheral config access before use
static void bus_pin_cfg_output(AfConfig pin_config, bool pin_state) {
// Configure pin as output
GPIO_InitTypeDef gpio_init_struct;
gpio_init_struct.GPIO_Pin = pin_config.gpio_pin;
gpio_init_struct.GPIO_Mode = GPIO_Mode_OUT;
gpio_init_struct.GPIO_Speed = GPIO_Speed_2MHz;
gpio_init_struct.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(pin_config.gpio, &gpio_init_struct);
// Set bit high or low
GPIO_WriteBit(pin_config.gpio, pin_config.gpio_pin, (pin_state) ? Bit_SET : Bit_RESET);
}
//! Power down I2C bus power supply
//! Always lock bus and peripheral config access before use
static void bus_rail_power_down(uint8_t bus_idx) {
if (BOARD_CONFIG.i2c_bus_configs[bus_idx].rail_ctl_fn == NULL) {
return;
}
BOARD_CONFIG.i2c_bus_configs[bus_idx].rail_ctl_fn(false);
// Drain through pull-ups
bus_pin_cfg_output(BOARD_CONFIG.i2c_bus_configs[bus_idx].i2c_scl, false);
bus_pin_cfg_output(BOARD_CONFIG.i2c_bus_configs[bus_idx].i2c_sda, false);
}
//! Power up I2C bus power supply
//! Always lock bus and peripheral config access before use
static void bus_rail_power_up(uint8_t bus_idx) {
if (BOARD_CONFIG.i2c_bus_configs[bus_idx].rail_ctl_fn == NULL) {
return;
}
// check that at least enough time has elapsed since the last turn-off
// TODO: is this necessary in bootloader?
static const uint32_t MIN_STOP_TIME_MS = 10;
delay_ms(MIN_STOP_TIME_MS);
bus_pin_cfg_input(BOARD_CONFIG.i2c_bus_configs[bus_idx].i2c_scl);
bus_pin_cfg_input(BOARD_CONFIG.i2c_bus_configs[bus_idx].i2c_sda);
BOARD_CONFIG.i2c_bus_configs[bus_idx].rail_ctl_fn(true);
}
//! Initialize the I2C peripheral
//! Lock the bus and peripheral config access before initialization
static void bus_init(uint8_t bus_idx) {
// Initialize peripheral
I2C_InitTypeDef i2c_init_struct;
I2C_StructInit(&i2c_init_struct);
if (BOARD_CONFIG.i2c_bus_configs[bus_idx].clock_speed > I2C_NORMAL_MODE_CLOCK_SPEED_MAX) { //Fast mode
i2c_init_struct.I2C_DutyCycle = BOARD_CONFIG.i2c_bus_configs[bus_idx].duty_cycle;
}
i2c_init_struct.I2C_ClockSpeed = BOARD_CONFIG.i2c_bus_configs[bus_idx].clock_speed;
i2c_init_struct.I2C_Ack = I2C_Ack_Enable;
I2C_Init(i2c_buses[bus_idx].i2c, &i2c_init_struct);
I2C_Cmd(i2c_buses[bus_idx].i2c, ENABLE);
}
//! Configure the bus pins, enable the peripheral clock and initialize the I2C peripheral.
//! Always lock the bus and peripheral config access before enabling it
static void bus_enable(uint8_t bus_idx) {
// Don't power up rail if the bus is already in use (enable can be called to reset bus)
if (i2c_buses[bus_idx].user_count == 0) {
bus_rail_power_up(bus_idx);
}
bus_pin_cfg_i2c(BOARD_CONFIG.i2c_bus_configs[bus_idx].i2c_scl);
bus_pin_cfg_i2c(BOARD_CONFIG.i2c_bus_configs[bus_idx].i2c_sda);
// Enable peripheral clock
periph_config_enable(RCC_APB1PeriphClockCmd, BOARD_CONFIG.i2c_bus_configs[bus_idx].clock_ctrl);
bus_init(bus_idx);
}
//! De-initialize and gate the clock to the peripheral
//! Power down rail if the bus supports that and no devices are using it
//! Always lock the bus and peripheral config access before disabling it
static void bus_disable(uint8_t bus_idx) {
I2C_DeInit(i2c_buses[bus_idx].i2c);
periph_config_disable(RCC_APB1PeriphClockCmd, BOARD_CONFIG.i2c_bus_configs[bus_idx].clock_ctrl);
// Do not de-power rail if there are still devices using bus (just reset peripheral and pin configuration during a bus reset)
if (i2c_buses[bus_idx].user_count == 0) {
bus_rail_power_down(bus_idx);
}
else {
bus_pin_cfg_input(BOARD_CONFIG.i2c_bus_configs[bus_idx].i2c_scl);
bus_pin_cfg_input(BOARD_CONFIG.i2c_bus_configs[bus_idx].i2c_sda);
}
}
//! Perform a soft reset of the bus
//! Always lock the bus before reset
static void bus_reset(uint8_t bus_idx) {
bus_disable(bus_idx);
bus_enable(bus_idx);
}
/*---------------INIT/USE/RELEASE/RESET FUNCTIONS----------------------*/
void i2c_init(void) {
for (uint32_t i = 0; i < ARRAY_LENGTH(i2c_buses); i++) {
i2c_buses[i].i2c = BOARD_CONFIG.i2c_bus_configs[i].i2c;
i2c_buses[i].user_count = 0;
i2c_buses[i].busy = false;
i2c_buses[i].transfer.idx = 0;
i2c_buses[i].transfer.size = 0;
i2c_buses[i].transfer.data = NULL;
i2c_buses[i].transfer.state = TRANSFER_STATE_INVALID;
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = BOARD_CONFIG.i2c_bus_configs[i].ev_irq_channel;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x0c;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x00;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
NVIC_InitStructure.NVIC_IRQChannel = BOARD_CONFIG.i2c_bus_configs[i].er_irq_channel;
NVIC_Init(&NVIC_InitStructure);
I2C_DeInit(i2c_buses[i].i2c);
}
s_initialized = true;
for (uint32_t i = 0; i < ARRAY_LENGTH(i2c_buses); i++) {
if (BOARD_CONFIG.i2c_bus_configs[i].rail_cfg_fn) {
BOARD_CONFIG.i2c_bus_configs[i].rail_cfg_fn();
}
if (BOARD_CONFIG.i2c_bus_configs[i].rail_ctl_fn) {
bus_rail_power_down(i);
}
}
}
void i2c_use(I2cDevice device_id) {
PBL_ASSERTN(s_initialized);
PBL_ASSERT(device_id < BOARD_CONFIG.i2c_device_count, "I2C device ID out of bounds %d (max: %d)",
device_id, BOARD_CONFIG.i2c_device_count);
uint8_t bus_idx = BOARD_CONFIG.i2c_device_map[device_id];
I2cBus *bus = &i2c_buses[bus_idx];
bus_lock(bus);
if (bus->user_count == 0) {
bus_enable(bus_idx);
}
bus->user_count++;
bus_unlock(bus);
}
void i2c_release(I2cDevice device_id) {
PBL_ASSERTN(s_initialized);
PBL_ASSERTN(device_id < BOARD_CONFIG.i2c_device_count);
uint8_t bus_idx = BOARD_CONFIG.i2c_device_map[device_id];
I2cBus *bus = &i2c_buses[bus_idx];
bus_lock(bus);
if (bus->user_count == 0) {
PBL_LOG(LOG_LEVEL_ERROR, "Attempted release of disabled bus %d by device %d", bus_idx, device_id);
bus_unlock(bus);
return;
}
bus->user_count--;
if (bus->user_count == 0) {
bus_disable(bus_idx);
}
bus_unlock(bus);
}
void i2c_reset(I2cDevice device_id) {
PBL_ASSERTN(s_initialized);
PBL_ASSERTN(device_id < BOARD_CONFIG.i2c_device_count);
uint8_t bus_idx = BOARD_CONFIG.i2c_device_map[device_id];
I2cBus *bus = &i2c_buses[bus_idx];
// Take control of bus; only one task may use bus at a time
bus_lock(bus);
if (bus->user_count == 0) {
PBL_LOG(LOG_LEVEL_ERROR, "Attempted reset of disabled bus %d by device %d", bus_idx, device_id);
bus_unlock(bus);
return;
}
PBL_LOG(LOG_LEVEL_WARNING, "Resetting I2C bus %" PRId8, bus_idx);
// decrement user count for reset so that if this user is the only user, the
// bus will be powered down during the reset
bus->user_count--;
// Reset and reconfigure bus and pins
bus_reset(bus_idx);
//Restore user count
bus->user_count++;
bus_unlock(bus);
}
/*--------------------DATA TRANSFER FUNCTIONS--------------------------*/
//! Wait a short amount of time for busy bit to clear
static bool wait_for_busy_clear(uint8_t bus_idx) {
unsigned int attempts = I2C_TIMEOUT_ATTEMPTS_MAX;
while((i2c_buses[bus_idx].i2c->SR2 & I2C_SR2_BUSY) != 0) {
--attempts;
if (!attempts) {
return false;
}
}
return true;
}
//! Abort the transfer
//! Should only be called when the bus is locked
static void abort_transfer(I2cBus *bus) {
// Disable all interrupts on the bus
bus->i2c->CR2 &= ~(I2C_CR2_ITEVTEN | I2C_CR2_ITERREN | I2C_CR2_ITBUFEN);
// Generate a stop condition
bus->i2c->CR1 |= I2C_CR1_STOP;
bus->transfer.state = TRANSFER_STATE_INVALID;
}
//! Set up and start a transfer to a device, wait for it to finish and clean up after the transfer has completed
static bool do_transfer(I2cDevice device_id, bool read_not_write, uint8_t device_address, uint8_t register_address, uint8_t size, uint8_t *data) {
PBL_ASSERTN(s_initialized);
PBL_ASSERTN(device_id < BOARD_CONFIG.i2c_device_count);
uint8_t bus_idx = BOARD_CONFIG.i2c_device_map[device_id];
I2cBus *bus = &i2c_buses[bus_idx];
// Take control of bus; only one task may use bus at a time
bus_lock(bus);
if (bus->user_count == 0) {
PBL_LOG(LOG_LEVEL_ERROR, "Attempted access to disabled bus %d by device %d", bus_idx, device_id);
bus_unlock(bus);
return false;
}
// If bus is busy (it shouldn't be as this function waits for the bus to report a non-idle state
// before exiting) reset the bus and wait for it to become not-busy
// Exit if bus remains busy. User module should reset the I2C module at this point
if((bus->i2c->SR2 & I2C_SR2_BUSY) != 0) {
bus_reset(bus_idx);
if (!wait_for_busy_clear(bus_idx)) {
// Bus did not recover after reset
bus_unlock(bus);
return false;
}
}
// Take binary semaphore so that next take will block
PBL_ASSERT(semaphore_take(bus), "Could not acquire semaphore token");
// Set up transfer
bus->transfer.device_address = device_address;
bus->transfer.register_address = register_address;
bus->transfer.read_not_write = read_not_write;
bus->transfer.size = size;
bus->transfer.idx = 0;
bus->transfer.state = TRANSFER_STATE_WRITE_ADDRESS_TX;
bus->transfer.data = data;
bus->transfer.nack_count = 0;
// Ack received bytes
I2C_AcknowledgeConfig(bus->i2c, ENABLE);
bool result = false;
do {
// Generate start event
bus->i2c->CR1 |= I2C_CR1_START;
//Enable event and error interrupts
bus->i2c->CR2 |= I2C_CR2_ITEVTEN | I2C_CR2_ITERREN;
// Wait on semaphore until it is released by interrupt or a timeout occurs
if (semaphore_wait(bus)) {
if (bus->transfer.state == TRANSFER_STATE_INVALID) {
// Transfer is complete
result = bus->transfer.result;
if (!result) {
PBL_LOG(LOG_LEVEL_ERROR, "I2C Error on bus %" PRId8, bus_idx);
}
} else if (bus->transfer.nack_count < I2C_NACK_COUNT_MAX) {
// NACK received after start condition sent: the MFI chip NACKs start conditions whilst it is busy
// Retry start condition after a short delay.
// A NACK count is incremented for each NACK received, so that legitimate NACK
// errors cause the transfer to be aborted (after the NACK count max has been reached).
bus->transfer.nack_count++;
delay_ms(1);
} else {
// Too many NACKs received, abort transfer
abort_transfer(bus);
break;
PBL_LOG(LOG_LEVEL_ERROR, "I2C Error: too many NACKs received on bus %" PRId8, bus_idx);
}
} else {
// Timeout, abort transfer
abort_transfer(bus);
break;
PBL_LOG(LOG_LEVEL_ERROR, "Transfer timed out on bus %" PRId8, bus_idx);
}
} while (bus->transfer.state != TRANSFER_STATE_INVALID);
// Return semaphore token so another transfer can be started
semaphore_give(bus);
// Wait for bus to to clear the busy flag before a new transfer starts
// Theoretically a transfer could complete successfully, but the busy flag never clears,
// which would cause the next transfer to fail
if (!wait_for_busy_clear(bus_idx)) {
// Reset I2C bus if busy flag does not clear
bus_reset(bus_idx);
}
bus_unlock(bus);
return result;
}
bool i2c_read_register(I2cDevice device_id, uint8_t i2c_device_address, uint8_t register_address, uint8_t *result) {
return i2c_read_register_block(device_id, i2c_device_address, register_address, 1, result);
}
bool i2c_read_register_block(I2cDevice device_id, uint8_t i2c_device_address, uint8_t
register_address_start, uint8_t read_size, uint8_t* result_buffer) {
// Do transfer locks the bus
bool result = do_transfer(device_id, true, i2c_device_address, register_address_start, read_size, result_buffer);
if (!result) {
PBL_LOG(LOG_LEVEL_ERROR, "Read failed on bus %" PRId8, BOARD_CONFIG.i2c_device_map[device_id]);
}
return result;
}
bool i2c_write_register(I2cDevice device_id, uint8_t i2c_device_address, uint8_t register_address,
uint8_t value) {
return i2c_write_register_block(device_id, i2c_device_address, register_address, 1, &value);
}
bool i2c_write_register_block(I2cDevice device_id, uint8_t i2c_device_address, uint8_t
register_address_start, uint8_t write_size, const uint8_t* buffer) {
// Do transfer locks the bus
bool result = do_transfer(device_id, false, i2c_device_address, register_address_start, write_size, (uint8_t*)buffer);
if (!result) {
PBL_LOG(LOG_LEVEL_ERROR, "Write failed on bus %" PRId8, BOARD_CONFIG.i2c_device_map[device_id]);
}
return result;
}
/*------------------------INTERRUPT FUNCTIONS--------------------------*/
//! End a transfer and disable further interrupts
//! Only call from interrupt functions
static portBASE_TYPE end_transfer_irq(I2cBus *bus, bool result) {
bus->i2c->CR2 &= ~(I2C_CR2_ITEVTEN | I2C_CR2_ITERREN | I2C_CR2_ITBUFEN);
bus->i2c->CR1 |= I2C_CR1_STOP;
bus->transfer.result = result;
bus->transfer.state = TRANSFER_STATE_INVALID;
bus->busy = false;
return pdFALSE;
}
//! Pause a transfer, disabling interrupts during the pause
//! Only call from interrupt functions
static portBASE_TYPE pause_transfer_irq(I2cBus *bus) {
bus->i2c->CR2 &= ~(I2C_CR2_ITEVTEN | I2C_CR2_ITERREN | I2C_CR2_ITBUFEN);
bus->busy = false;
return pdFALSE;
}
//! Handle an IRQ event on the specified \a bus
static portBASE_TYPE irq_event_handler(I2cBus *bus) {
if (bus->transfer.state == TRANSFER_STATE_INVALID) {
// Disable interrupts if spurious interrupt received
bus->i2c->CR2 &= ~(I2C_CR2_ITEVTEN | I2C_CR2_ITBUFEN);
return pdFALSE;
}
// Check that the expected event occurred
if (I2C_CheckEvent(bus->i2c, s_guard_events[bus->transfer.state]) == ERROR) {
// Ignore interrupt - A spurious byte transmitted event as well as an interrupt with no
// discernible event associated with it occur after repeat start events are generated
return pdFALSE;
}
portBASE_TYPE should_context_switch = pdFALSE;
switch (bus->transfer.state) {
case TRANSFER_STATE_WRITE_ADDRESS_TX:
// Write the i2c device address to the bus to select it in write mode.
bus->i2c->DR = bus->transfer.device_address & ~I2C_READ_WRITE_BIT;
bus->transfer.state = TRANSFER_STATE_WRITE_REG_ADDRESS;
break;
case TRANSFER_STATE_WRITE_REG_ADDRESS:
// Write the register address
bus->i2c->DR = bus->transfer.register_address;
if (bus->transfer.read_not_write) {
bus->transfer.state = TRANSFER_STATE_REPEAT_START;
} else {
// Enable TXE interrupt for writing
bus->i2c->CR2 |= I2C_CR2_ITBUFEN;
bus->transfer.state = TRANSFER_STATE_WRITE_DATA;
}
break;
case TRANSFER_STATE_REPEAT_START:
// Generate a repeat start
bus->i2c->CR1 |= I2C_CR1_START;
bus->transfer.state = TRANSFER_STATE_WRITE_ADDRESS_RX;
break;
case TRANSFER_STATE_WRITE_ADDRESS_RX:
// Write the I2C device address again, but this time in read mode.
bus->i2c->DR = bus->transfer.device_address | I2C_READ_WRITE_BIT;
if (bus->transfer.size == 1) {
// Last byte, we want to NACK this one to tell the slave to stop sending us bytes.
bus->i2c->CR1 &= ~I2C_CR1_ACK;
}
bus->transfer.state = TRANSFER_STATE_WAIT_FOR_DATA;
break;
case TRANSFER_STATE_WAIT_FOR_DATA:
//This state just ensures that the transition to receive mode event happened
// Enable RXNE interrupt for writing
bus->i2c->CR2 |= I2C_CR2_ITBUFEN;
bus->transfer.state = TRANSFER_STATE_READ_DATA;
break;
case TRANSFER_STATE_READ_DATA:
bus->transfer.data[bus->transfer.idx] = bus->i2c->DR;
bus->transfer.idx++;
if (bus->transfer.idx + 1 == bus->transfer.size) {
// Last byte, we want to NACK this one to tell the slave to stop sending us bytes.
bus->i2c->CR1 &= ~I2C_CR1_ACK;
}
else if (bus->transfer.idx == bus->transfer.size) {
// End transfer after all bytes have been received
bus->i2c->CR2 &= ~I2C_CR2_ITBUFEN;
should_context_switch = end_transfer_irq(bus, true);
break;
}
break;
case TRANSFER_STATE_WRITE_DATA:
bus->i2c->DR = bus->transfer.data[bus->transfer.idx];
bus->transfer.idx++;
if (bus->transfer.idx == bus->transfer.size) {
bus->i2c->CR2 &= ~I2C_CR2_ITBUFEN;
bus->transfer.state = TRANSFER_STATE_END_WRITE;
break;
}
break;
case TRANSFER_STATE_END_WRITE:
// End transfer after all bytes have been sent
should_context_switch = end_transfer_irq(bus, true);
break;
default:
// Abort transfer from invalid state - should never reach here (state machine logic broken)
should_context_switch = end_transfer_irq(bus, false);
break;
}
return should_context_switch;
}
//! Handle error interrupt on the specified \a bus
static portBASE_TYPE irq_error_handler(I2cBus *bus) {
if (bus->transfer.state == TRANSFER_STATE_INVALID) {
// Disable interrupts if spurious interrupt received
bus->i2c->CR2 &= ~I2C_CR2_ITERREN;
return pdFALSE;
}
// Data overrun and bus errors can only really be handled by terminating the transfer and
// trying to recover bus to an idle state. Each error will be logged. In each case a stop
// condition will be sent and then we will wait on the busy flag to clear (if it doesn't,
// a soft reset of the bus will be performed (handled in wait i2c_do_transfer).
if ((bus->i2c->SR1 & I2C_SR1_OVR) != 0) {
bus->i2c->SR1 &= ~I2C_SR1_OVR;
// Data overrun
PBL_LOG(LOG_LEVEL_ERROR, "Data overrun during I2C transaction; Bus: 0x%p", bus->i2c);
}
if ((bus->i2c->SR1 & I2C_SR1_BERR) != 0) {
bus->i2c->SR1 &= ~I2C_SR1_BERR;
// Bus error: invalid start or stop condition detected
PBL_LOG(LOG_LEVEL_ERROR, "Bus error detected during I2C transaction; Bus: 0x%p", bus->i2c);
}
if ((bus->i2c->SR1 & I2C_SR1_AF) != 0) {
bus->i2c->SR1 &= ~I2C_SR1_AF;
// NACK received.
//
// The MFI chip will cause NACK errors during read operations after writing a start bit (first start
// or repeat start indicating that it is busy. The transfer must be paused and the start condition sent
// again after a delay and the state machine set back a step.
//
// If the NACK is received after any other action log an error and abort the transfer
if (bus->transfer.state == TRANSFER_STATE_WAIT_FOR_DATA) {
bus->transfer.state = TRANSFER_STATE_WRITE_ADDRESS_RX;
return pause_transfer_irq(bus);
}
else if (bus->transfer.state == TRANSFER_STATE_WRITE_REG_ADDRESS){
bus->transfer.state = TRANSFER_STATE_WRITE_ADDRESS_TX;
return pause_transfer_irq(bus);
}
else {
PBL_LOG(LOG_LEVEL_ERROR, "NACK received during I2C transfer; Bus: 0x%p", bus->i2c);
}
}
return end_transfer_irq(bus, false);
}
void I2C1_EV_IRQHandler(void) {
portEND_SWITCHING_ISR(irq_event_handler(&i2c_buses[0]));
}
void I2C1_ER_IRQHandler(void) {
portEND_SWITCHING_ISR(irq_error_handler(&i2c_buses[0]));
}
void I2C2_EV_IRQHandler(void) {
portEND_SWITCHING_ISR(irq_event_handler(&i2c_buses[1]));
}
void I2C2_ER_IRQHandler(void) {
portEND_SWITCHING_ISR(irq_error_handler(&i2c_buses[1]));
}
void I2C3_EV_IRQHandler(void) {
portEND_SWITCHING_ISR(irq_event_handler(&i2c_buses[0]));
}
void I2C3_ER_IRQHandler(void) {
portEND_SWITCHING_ISR(irq_error_handler(&i2c_buses[0]));
}
/*------------------------COMMAND FUNCTIONS--------------------------*/
void command_power_2v5(char *arg) {
// Intentionally ignore the s_running_count and make it so!
// This is intended for low level electrical test only
if(!strcmp("on", arg)) {
bus_rail_power_up(1);
} else {
bus_rail_power_down(1);
}
}

View file

@ -0,0 +1,86 @@
/*
* 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 <stdbool.h>
#include <stdint.h>
//! Initialize the I2C driver. Must be called before first use
void i2c_init(void);
//! Start using the I2C bus connected to the device specified by \a device_id
//! Must be called before any other use of the bus is performed
//! @param device_id ID of device
void i2c_use(I2cDevice device_id);
//! Stop using the I2C bus connected to the device specified by \a device_id
//! Call when done with the bus
//! @param device_id ID of device
void i2c_release(I2cDevice device_id);
//! Reset the bus
//! Will re-initialize the bus and cycle the power to the bus if this is
//! supported for the bus the device specified by \a device_id is connected to)
//! @param device_id ID of device, this will identify the bus to be reset
void i2c_reset(I2cDevice device_id);
//! Manually bang out the clock on the bus specified by \a device_id for a period
//! of time or until the data line recovers
//! Must not be called before \ref i2c_use has been called for the device
//! @param device_id ID of device, this will identify the bus to be recovered
//! @return true if the data line recovered, false otherwise
bool i2c_bitbang_recovery(I2cDevice device_id);
//! Read the value of a register
//! Must not be called before \ref i2c_use has been called for the device
//! @param device_id ID of device to communicate with
//! @param i2c_device_address Device bus address
//! @param register_address Address of register to read
//! @param result Pointer to destination buffer
//! @return true if transfer succeeded, false if error occurred
bool i2c_read_register(I2cDevice device_id, uint8_t i2c_device_address, uint8_t register_address, uint8_t *result);
//! Read a sequence of registers starting from \a register_address_start
//! Must not be called before \ref i2c_use has been called for the device
//! @param device_id ID of device to communicate with
//! @param i2c_device_address Device bus address
//! @param register_address_start Address of first register to read
//! @param read_size Number of bytes to read
//! @param result_buffer Pointer to destination buffer
//! @return true if transfer succeeded, false if error occurred
bool i2c_read_register_block(I2cDevice device_id, uint8_t i2c_device_address, uint8_t register_address_start, uint8_t read_size, uint8_t* result_buffer);
//! Write to a register
//! Must not be called before \ref i2c_use has been called for the device
//! @param device_id ID of device to communicate with
//! @param i2c_device_address Device bus address
//! @param register_address Address of register to write to
//! @param value Data value to write
//! @return true if transfer succeeded, false if error occurred
bool i2c_write_register(I2cDevice device_id, uint8_t i2c_device_address, uint8_t register_address, uint8_t value);
//! Write to a sequence of registers starting from \a register_address_start
//! Must not be called before \ref i2c_use has been called for the device
//! @param device_id ID of device to communicate with
//! @param i2c_device_address Device bus address
//! @param register_address_start Address of first register to read
//! @param write_size Number of bytes to write
//! @param buffer Pointer to source buffer
//! @return true if transfer succeeded, false if error occurred
bool i2c_write_register_block(I2cDevice device_id, uint8_t i2c_device_address, uint8_t register_address_start, uint8_t write_size, const uint8_t* buffer);

View file

@ -0,0 +1,35 @@
/*
* 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 <stdbool.h>
#include "drivers/pmic.h"
#include "drivers/gpio.h"
#include "util/delay.h"
#include "board/board.h"
#include "stm32f4xx_gpio.h"
#include "stm32f4xx_rcc.h"
#include "stm32f4xx_i2c.h"
void silk_rail_ctl_fn(bool enable) {
// NYI
return;
}
void silk_rail_cfg_fn(void) {
// NYI
return;
}

View file

@ -0,0 +1,29 @@
/*
* 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 "stm32f4xx.h"
typedef void (*ClockCmd)(uint32_t periph, FunctionalState state);
static inline void periph_config_enable(ClockCmd clock_cmd, uint32_t periph) {
clock_cmd(periph, ENABLE);
}
static inline void periph_config_disable(ClockCmd clock_cmd, uint32_t periph) {
clock_cmd(periph, DISABLE);
}

View 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 <stdbool.h>
#include <stdint.h>
//! Initialize the PMIC driver. Call this once at startup.
bool pmic_init(void);
//! Whether or not the usb charger is detected by the pmic.
bool pmic_is_usb_connected(void);
//! Returns true if this boot was caused by a charger event from standby, but
//! there is no charger connected.
bool pmic_boot_due_to_charger_disconnect(void);
//! Enter what we consider to be a "powered off" state, which is the as3701
//! standby state, where we keep the RTC powered.
bool pmic_power_off(void);
//! Fully shut down the as3701, which does not power the RTC.
bool pmic_full_power_off(void);

View file

@ -0,0 +1,311 @@
/*
* 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/pmic.h"
#include "board/board.h"
#include "drivers/gpio.h"
#include "drivers/i2c.h"
#include "system/passert.h"
#include "stm32f4xx_rcc.h"
#include "stm32f4xx_gpio.h"
#include "stm32f4xx_adc.h"
typedef enum {
PmicRegisters_SD1_VOLTAGE = 0x01,
PmicRegisters_LDO1_VOLTAGE = 0x02,
PmicRegisters_LDO2_VOLTAGE = 0x03,
PmicRegisters_GPIO1_CNTL = 0x09,
PmicRegisters_GPIO2_CNTL = 0x0a,
PmicRegisters_GPIO3_CNTL = 0x0b,
PmicRegisters_GPIO4_CNTL = 0x0c,
PmicRegisters_GPIO5_CNTL = 0x0d,
PmicRegisters_GPIO_SIG_OUT = 0x20,
PmicRegisters_GPIO_SIG_IN = 0x21,
PmicRegisters_REG1_VOLTAGE = 0x22,
PmicRegisters_REG2_VOLTAGE = 0x23,
PmicRegisters_REG_CNTL = 0x24,
PmicRegisters_GPIO_CNTL1 = 0x25,
PmicRegisters_GPIO_CNTL2 = 0x26,
PmicRegisters_SD_CNTL1 = 0x30,
PmicRegisters_BATT_VOLTAGE_MON = 0x32,
PmicRegisters_STARTUP_CNTL = 0x33,
PmicRegisters_REFERENCE_CNTL = 0x35,
PmicRegisters_RESET_CNTL = 0x36,
PmicRegisters_OVERTEMP_CNTL = 0x37,
PmicRegisters_REG_STANDBY_MOD1 = 0x39,
PmicRegisters_PWM_CNTL_L = 0x41,
PmicRegisters_PWM_CNTL_H = 0x42,
PmicRegisters_CURR1_VAL = 0x43,
PmicRegisters_CURR2_VAL = 0x44,
PmicRegisters_REG_STATUS = 0x73,
PmicRegisters_INT_MASK_1 = 0x74,
PmicRegisters_INT_MASK_2 = 0x75,
PmicRegisters_INT_STATUS_1 = 0x77,
PmicRegisters_INT_STATUS_2 = 0x78,
PmicRegisters_CHARGE_CNTL = 0x80,
PmicRegisters_CHARGE_VOLTAGE_CNTL = 0x81,
PmicRegisters_CHARGE_CURRENT_CNTL = 0x82,
PmicRegisters_CHARGE_CONFIG_1 = 0x83,
PmicRegisters_CHARGE_CONFIG_2 = 0x84,
PmicRegisters_CHARGE_SUPERVISION = 0x85,
PmicRegisters_CHARGE_STATUS_1 = 0x86,
PmicRegisters_CHARGE_STATUS_2 = 0x87,
PmicRegisters_LOCK_REG = 0x8e,
PmicRegisters_CHIP_ID = 0x90,
PmicRegisters_CHIP_REV = 0x91,
PmicRegisters_FUSE_5 = 0xa5,
PmicRegisters_FUSE_6 = 0xa6,
PmicRegisters_FUSE_7 = 0xa7,
PmicRegisters_FUSE_8 = 0xa8,
PmicRegisters_FUSE_9 = 0xa9,
PmicRegisters_FUSE_10 = 0xaa,
PmicRegisters_FUSE_11 = 0xab,
PmicRegisters_FUSE_12 = 0xac,
PmicRegisters_FUSE_13 = 0xad,
PmicRegisters_FUSE_14 = 0xae,
PmicRegisters_FUSE_15 = 0xaf,
} PmicRegisters;
// These are values for the reset_reason field of the ResetControl register.
// None of these values should ever be changed, as conversions are done on
// readings dont directly out of the ResetControl register.
// See Figure 79 of the AS3701B datasheet for more information.
typedef enum {
PmicResetReason_PowerUpFromScratch = 0x00, //!< Battery or charger insertion from scratch
PmicResetReason_ResVoltFall = 0x01, //!< Battery voltage drop below 2.75V
PmicResetReason_ForcedReset = 0x02, //!< sw force_reset
PmicResetReason_OnPulledHigh = 0x03, //!< Force sw power_off, ON pulled high
PmicResetReason_Charger = 0x04, //!< Forced sw power_off, Charger detected.
PmicResetReason_XRES = 0x05, //!< External trigger through XRES
PmicResetReason_OverTemperature = 0x06, //!< Reset caused by overtemperature
PmicResetReason_OnKeyHold = 0x08, //!< Reset for holding down on key
PmicResetReason_StandbyInterrupt = 0x0b, //!< Reset for interrupt in standby
PmicResetReason_StandbyOnPulledHigh = 0x0c, //!< Reset for ON pulled high in standby
PmicResetReason_Unknown,
} PmicResetReason;
static void prv_start_120hz_clock(void);
static bool prv_init_gpio(void) {
return true;
}
//! Interrupt masks for InterruptStatus1 and InterruptMask1 registers
enum PmicInt1 {
PmicInt1_Trickle = (1 << 0), //!< Trickle charge
PmicInt1_NoBat = (1 << 1), //!< Battery detached
PmicInt1_Resume = (1 << 2), //!< Resuming charge on drop after full
PmicInt1_EOC = (1 << 3), //!< End of charge
PmicInt1_ChDet = (1 << 4), //!< Charger detected
PmicInt1_OnKey = (1 << 5), //!< On Key held
PmicInt1_OvTemp = (1 << 6), //!< Set when 110deg is exceeded
PmicInt1_LowBat = (1 << 7), //!< Low Battery detected. Set when BSUP drops below ResVoltFall
};
enum PmicRail {
PmicRail_SD1, //!< 1.8V
PmicRail_LDO1, //!< 3.0V
PmicRail_LDO2, //!< 2.0V
};
#define AS3701B_CHIP_ID 0x11
#define AS3701B_WRITE_ADDR 0x80
#define AS3701B_READ_ADDR 0x81
static bool prv_read_register(uint8_t register_address, uint8_t *result) {
i2c_use(I2C_DEVICE_AS3701B);
bool rv = i2c_read_register(I2C_DEVICE_AS3701B, AS3701B_READ_ADDR, register_address, result);
i2c_release(I2C_DEVICE_AS3701B);
return rv;
}
static bool prv_write_register(uint8_t register_address, uint8_t value) {
i2c_use(I2C_DEVICE_AS3701B);
bool rv = i2c_write_register(I2C_DEVICE_AS3701B, AS3701B_WRITE_ADDR, register_address, value);
i2c_release(I2C_DEVICE_AS3701B);
return rv;
}
static bool prv_register_set_bit(uint8_t register_address, uint8_t bit) {
uint8_t val;
if (!prv_read_register(register_address, &val)) {
return false;
}
val |= (1 << bit);
return prv_write_register(register_address, val);
}
static bool prv_register_clear_bit(uint8_t register_address, uint8_t bit) {
uint8_t val;
if (!prv_read_register(register_address, &val)) {
return false;
}
val &= ~(1 << bit);
return prv_write_register(register_address, val);
}
// Read the interrupt status registers to clear pending bits.
static void prv_clear_pending_interrupts(void) {
uint8_t throwaway_read_result;
prv_read_register(PmicRegisters_INT_STATUS_1, &throwaway_read_result);
prv_read_register(PmicRegisters_INT_STATUS_2, &throwaway_read_result);
}
// Set up 120Hz clock which is used for VCOM.
// Slowest possible setting, with divisor of 16 and high/low duration of 256us.
void prv_start_120hz_clock(void) {
const uint8_t pwm_high_low_time_us = (256 - 1);
prv_write_register(PmicRegisters_PWM_CNTL_H, pwm_high_low_time_us);
prv_write_register(PmicRegisters_PWM_CNTL_L, pwm_high_low_time_us);
bool success = false;
uint8_t ref_cntl;
if (prv_read_register(PmicRegisters_REFERENCE_CNTL, &ref_cntl)) {
ref_cntl |= 0x3; // Divisor of 16
prv_write_register(PmicRegisters_REFERENCE_CNTL, ref_cntl);
// Enable PWM Output on GPIO2 (Fig. 64)
// Bits 6-4: Mode, 0x1 = Output
// Bits 0-3: iosf, 0xe = PWM
uint8_t val = (1 << 4) | 0x0e;
success = prv_write_register(PmicRegisters_GPIO2_CNTL, val);
}
PBL_ASSERT(success, "Failed to start PMIC 120Hz PWM");
}
static bool prv_is_alive(void) {
uint8_t chip_id;
if (!prv_read_register(PmicRegisters_CHIP_ID, &chip_id)) {
return false;
}
const bool found = (chip_id == AS3701B_CHIP_ID);
if (found) {
PBL_LOG(LOG_LEVEL_DEBUG, "Found the as3701b");
} else {
PBL_LOG(LOG_LEVEL_DEBUG, "Error: read as3701b whoami byte 0x%x, expecting 0x11", chip_id);
}
return found;
}
bool pmic_init(void) {
prv_init_gpio();
if (!prv_is_alive()) {
return false;
}
prv_start_120hz_clock();
return true;
}
bool pmic_is_usb_connected(void) {
uint8_t status;
if (!prv_read_register(PmicRegisters_CHARGE_STATUS_2, &status)) {
return false;
}
// ChargerStatus2 (Fig. 98)
// Bit 2: Charger detected
return !!(status & (1 << 2));
}
static PmicResetReason prv_reset_reason(void) {
uint8_t val;
if (!prv_read_register(PmicRegisters_RESET_CNTL, &val)) {
return PmicResetReason_Unknown;
}
return (val & 0xf0) >> 4;
}
// If the pmic indicates that we were reset due to a charger interrupt, but the
// charger is currently disconnected, then we know we were woken by a disconnect event.
bool pmic_boot_due_to_charger_disconnect(void) {
if (prv_reset_reason() != PmicResetReason_StandbyInterrupt) {
return false;
}
uint8_t int_status;
if (!prv_read_register(PmicRegisters_INT_STATUS_1, &int_status)) {
return false;
}
return ((int_status & PmicInt1_ChDet) && !pmic_is_usb_connected());
}
// This is a hard power off, resulting in all rails being disabled.
bool pmic_full_power_off(void) {
// ResetControl (Fig. 79)
// Bit 1: power_off - Start a reset cycle, and wait for ON or charger to complete the reset.
if (prv_register_set_bit(PmicRegisters_RESET_CNTL, 1)) {
while (1) {}
__builtin_unreachable();
}
return false;
}
// On the as3701b, a power_off will cut power to all rails. We want to keep the
// RTC alive, so rather than performing a sw_power_off, enter the pmic's standby
// mode, powering down all but LDO2.
bool pmic_power_off(void) {
// Only enable interrupts that should be able to wake us out of standby
// - Wake on charger detect
const uint8_t int_mask = ~((uint8_t)PmicInt1_ChDet);
prv_write_register(PmicRegisters_INT_MASK_1, int_mask);
prv_write_register(PmicRegisters_INT_MASK_2, 0xff);
// Clear interrupt status so we're not woken immediately (read the regs)
prv_clear_pending_interrupts();
// Set Reg_Standby_mod1 to specify which rails to turn off / keep on
// - SD1, LDO1 off
// - LDO2 on
// - Disable regulator pulldowns
prv_write_register(PmicRegisters_REG_STANDBY_MOD1, 0xa);
// Set standby_mode_on (bit 4) in ReferenceControl to 1 (See Fig. 78)
if (prv_register_set_bit(PmicRegisters_REFERENCE_CNTL, 4)) {
while (1) {}
__builtin_unreachable();
}
return false;
}
void set_ldo3_power_state(bool enabled) {
}
void set_4V5_power_state(bool enabled) {
}
void set_6V6_power_state(bool enabled) {
}

View file

@ -0,0 +1,111 @@
/*
* 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/system_flash.h"
#include "drivers/dbgserial.h"
#include "util/misc.h"
#include "stm32f4xx_flash.h"
static uint16_t s_sectors[] = {
FLASH_Sector_0, FLASH_Sector_1, FLASH_Sector_2, FLASH_Sector_3,
FLASH_Sector_4, FLASH_Sector_5, FLASH_Sector_6, FLASH_Sector_7,
FLASH_Sector_8, FLASH_Sector_9, FLASH_Sector_10, FLASH_Sector_11
};
static uint32_t s_sector_addresses[] = {
ADDR_FLASH_SECTOR_0, ADDR_FLASH_SECTOR_1, ADDR_FLASH_SECTOR_2, ADDR_FLASH_SECTOR_3,
ADDR_FLASH_SECTOR_4, ADDR_FLASH_SECTOR_5, ADDR_FLASH_SECTOR_6, ADDR_FLASH_SECTOR_7,
ADDR_FLASH_SECTOR_8, ADDR_FLASH_SECTOR_9, ADDR_FLASH_SECTOR_10, ADDR_FLASH_SECTOR_11
};
int prv_get_sector_num_for_address(uint32_t address) {
if (address < s_sector_addresses[0]) {
dbgserial_print("address ");
dbgserial_print_hex(address);
dbgserial_putstr(" is outside system flash");
return -1;
}
for (size_t i=0; i < ARRAY_LENGTH(s_sector_addresses)-1; ++i) {
if (s_sector_addresses[i] <= address
&& address < s_sector_addresses[i+1]) {
return i;
}
}
return ARRAY_LENGTH(s_sector_addresses)-1;
}
bool system_flash_erase(
uint32_t address, size_t length,
SystemFlashProgressCb progress_callback, void *progress_context) {
if (length == 0) {
// Nothing to do
return true;
}
int first_sector = prv_get_sector_num_for_address(address);
int last_sector = prv_get_sector_num_for_address(address + length - 1);
if (first_sector < 0 || last_sector < 0) {
return false;
}
int count = last_sector - first_sector + 1;
if (progress_callback) {
progress_callback(0, count, progress_context);
}
FLASH_Unlock();
for (int sector = first_sector; sector <= last_sector; ++sector) {
FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_OPERR | FLASH_FLAG_WRPERR |
FLASH_FLAG_PGAERR | FLASH_FLAG_PGPERR | FLASH_FLAG_PGSERR);
if (FLASH_EraseSector(
s_sectors[sector], VoltageRange_1) != FLASH_COMPLETE) {
dbgserial_print("failed to erase sector ");
dbgserial_print_hex(sector);
dbgserial_newline();
FLASH_Lock();
return false;
}
if (progress_callback) {
progress_callback(sector - first_sector + 1, count, progress_context);
}
}
FLASH_Lock();
return true;
}
bool system_flash_write(
uint32_t address, const void *data, size_t length,
SystemFlashProgressCb progress_callback, void *progress_context) {
FLASH_Unlock();
FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_OPERR | FLASH_FLAG_WRPERR |
FLASH_FLAG_PGAERR | FLASH_FLAG_PGPERR|FLASH_FLAG_PGSERR);
const uint8_t *data_array = data;
for (uint32_t i = 0; i < length; ++i) {
if (FLASH_ProgramByte(address + i, data_array[i]) != FLASH_COMPLETE) {
dbgserial_print("failed to write address ");
dbgserial_print_hex(address);
dbgserial_newline();
FLASH_Lock();
return false;
}
if (progress_callback && i % 128 == 0) {
progress_callback(i/128, length/128, progress_context);
}
}
FLASH_Lock();
return true;
}

View file

@ -0,0 +1,64 @@
/*
* 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 <string.h>
#include "stm32f4xx_flash.h"
#define ADDR_FLASH_SECTOR_0 ((uint32_t)0x08000000) /* Base @ of Sector 0, 16 Kbytes */
#define ADDR_FLASH_SECTOR_1 ((uint32_t)0x08004000) /* Base @ of Sector 1, 16 Kbytes */
#define ADDR_FLASH_SECTOR_2 ((uint32_t)0x08008000) /* Base @ of Sector 2, 16 Kbytes */
#define ADDR_FLASH_SECTOR_3 ((uint32_t)0x0800C000) /* Base @ of Sector 3, 16 Kbytes */
#define ADDR_FLASH_SECTOR_4 ((uint32_t)0x08010000) /* Base @ of Sector 4, 64 Kbytes */
#define ADDR_FLASH_SECTOR_5 ((uint32_t)0x08020000) /* Base @ of Sector 5, 128 Kbytes */
#define ADDR_FLASH_SECTOR_6 ((uint32_t)0x08040000) /* Base @ of Sector 6, 128 Kbytes */
#define ADDR_FLASH_SECTOR_7 ((uint32_t)0x08060000) /* Base @ of Sector 7, 128 Kbytes */
#define ADDR_FLASH_SECTOR_8 ((uint32_t)0x08080000) /* Base @ of Sector 8, 128 Kbytes */
#define ADDR_FLASH_SECTOR_9 ((uint32_t)0x080A0000) /* Base @ of Sector 9, 128 Kbytes */
#define ADDR_FLASH_SECTOR_10 ((uint32_t)0x080C0000) /* Base @ of Sector 10, 128 Kbytes */
#define ADDR_FLASH_SECTOR_11 ((uint32_t)0x080E0000) /* Base @ of Sector 11, 128 Kbytes */
typedef void (*SystemFlashProgressCb)(
uint32_t progress, uint32_t total, void *context);
// Erase the sectors of flash which lie within the given address range.
//
// If the address range overlaps even one single byte of a sector, the entire
// sector is erased.
//
// If progress_callback is not NULL, it is called at the beginning of the erase
// process and after each sector is erased. The rational number (progress/total)
// increases monotonically as the sector erasue procedure progresses.
//
// Returns true if successful, false if an error occurred.
bool system_flash_erase(
uint32_t address, size_t length,
SystemFlashProgressCb progress_callback, void *progress_context);
// Write data into flash. The flash must already be erased.
//
// If progress_callback is not NULL, it is called at the beginning of the
// writing process and periodically thereafter. The rational number
// (progress/total) increases monotonically as the data is written.
//
// Returns true if successful, false if an error occurred.
bool system_flash_write(
uint32_t address, const void *data, size_t length,
SystemFlashProgressCb progress_callback, void *progress_context);

View file

@ -0,0 +1,41 @@
/*
* 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/watchdog.h"
#include "stm32f4xx_dbgmcu.h"
#include "stm32f4xx_iwdg.h"
#include "stm32f4xx_rcc.h"
void watchdog_init(void) {
IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable);
IWDG_SetPrescaler(IWDG_Prescaler_64); // ~8 seconds
IWDG_SetReload(0xfff);
IWDG_WriteAccessCmd(IWDG_WriteAccess_Disable);
DBGMCU_APB1PeriphConfig(DBGMCU_IWDG_STOP, ENABLE);
}
void watchdog_start(void) {
IWDG_Enable();
IWDG_ReloadCounter();
}
bool watchdog_check_reset_flag(void) {
return RCC_GetFlagStatus(RCC_FLAG_IWDGRST) != RESET;
}

View file

@ -0,0 +1,24 @@
/*
* 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>
void watchdog_init(void);
void watchdog_start(void);
bool watchdog_check_reset_flag(void);

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 "stm32f4xx.h"
#define FIRMWARE_BASE (FLASH_BASE + (BOOTLOADER_LENGTH))

View file

@ -0,0 +1,25 @@
/*
* 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
// Scratch space for firmware images (normal and recovery).
// We assume this is 64k aligned...
#define FLASH_REGION_FIRMWARE_SCRATCH_BEGIN 0x000000
#define FLASH_REGION_FIRMWARE_SCRATCH_END 0x100000 // 1024k
#define FLASH_REGION_SAFE_FIRMWARE_BEGIN 0x200000
#define FLASH_REGION_SAFE_FIRMWARE_END 0x280000 // 512k

View file

@ -0,0 +1,226 @@
/*
* 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 "fw_copy.h"
#include "drivers/dbgserial.h"
#include "drivers/display.h"
#include "drivers/flash.h"
#include "drivers/system_flash.h"
#include "firmware.h"
#include "flash_region.h"
#include "system/bootbits.h"
#include "system/firmware_storage.h"
#include "system/reset.h"
#include "util/crc32.h"
#include "util/misc.h"
#include "util/delay.h"
#include <inttypes.h>
#include <stdint.h>
#define MAX_CHUNK_SIZE 65536
static bool check_valid_firmware_crc(uint32_t flash_address, FirmwareDescription* desc) {
dbgserial_putstr("Checksumming firmware update");
const uint32_t crc = flash_calculate_checksum(flash_address, desc->firmware_length);
dbgserial_print("Calculated checksum: ");
dbgserial_print_hex(crc);
dbgserial_newline();
return crc == desc->checksum;
}
static void prv_display_erase_progress(
uint32_t progress, uint32_t total, void *ctx) {
display_firmware_update_progress(progress, total * 2);
}
static bool erase_old_firmware(uint32_t firmware_length) {
dbgserial_putstr("erase_old_firmware");
return system_flash_erase(
FIRMWARE_BASE, firmware_length, prv_display_erase_progress, 0);
}
static void prv_display_write_progress(
uint32_t progress, uint32_t total, void *ctx) {
display_firmware_update_progress(progress/2 + total/2, total);
}
static bool write_new_firmware(uint32_t flash_new_fw_start,
uint32_t firmware_length) {
dbgserial_putstr("write_new_firmware");
// We can't just read the flash like memory, so we gotta lift everything ourselves.
// buffer is static so it goes in BSS, since stack is only 8192 bytes
static uint8_t buffer[MAX_CHUNK_SIZE];
uint32_t chunk_size;
for (uint32_t i = 0; i < firmware_length; i += chunk_size) {
chunk_size = MIN(MAX_CHUNK_SIZE, firmware_length - i);
flash_read_bytes(buffer, flash_new_fw_start + i, chunk_size);
if (!system_flash_write(FIRMWARE_BASE + i, buffer, chunk_size, NULL, NULL)) {
dbgserial_putstr("We're dead");
return false;
}
prv_display_write_progress(i + chunk_size, firmware_length, NULL);
}
return true;
}
static bool check_firmware_crc(FirmwareDescription* firmware_description) {
dbgserial_print("Checksumming ");
dbgserial_print_hex(firmware_description->firmware_length);
dbgserial_print(" bytes\r\n");
const uint32_t crc = crc32(CRC32_INIT, (const uint8_t *)FIRMWARE_BASE,
firmware_description->firmware_length);
dbgserial_print("Checksum - wanted ");
dbgserial_print_hex(firmware_description->checksum);
dbgserial_print(" got ");
dbgserial_print_hex(crc);
dbgserial_newline();
return crc == firmware_description->checksum;
}
typedef enum UpdateFirmwareResult {
UPDATE_FW_SUCCESS = 0,
UPDATE_FW_ERROR_MICRO_FLASH_UNTOUCHED = 1,
UPDATE_FW_ERROR_MICRO_FLASH_MANGLED = 2
} UpdateFirmwareResult;
static UpdateFirmwareResult update_fw(uint32_t flash_address) {
display_firmware_update_progress(0, 1);
boot_bit_set(BOOT_BIT_NEW_FW_UPDATE_IN_PROGRESS);
FirmwareDescription firmware_description =
firmware_storage_read_firmware_description(flash_address);
if (!firmware_storage_check_valid_firmware_description(&firmware_description)) {
dbgserial_putstr("Invalid firmware description!");
return UPDATE_FW_ERROR_MICRO_FLASH_UNTOUCHED;
}
if (!check_valid_firmware_crc(
flash_address + sizeof(FirmwareDescription), &firmware_description)) {
dbgserial_putstr("Invalid firmware CRC in SPI flash!");
return UPDATE_FW_ERROR_MICRO_FLASH_UNTOUCHED;
}
erase_old_firmware(firmware_description.firmware_length);
write_new_firmware(
flash_address + sizeof(FirmwareDescription),
firmware_description.firmware_length);
if (!check_firmware_crc(&firmware_description)) {
dbgserial_putstr(
"Our internal flash contents are bad (checksum failed)! "
"This is really bad!");
return UPDATE_FW_ERROR_MICRO_FLASH_MANGLED;
}
return UPDATE_FW_SUCCESS;
}
void check_update_fw(void) {
if (!boot_bit_test(BOOT_BIT_NEW_FW_AVAILABLE)) {
return;
}
if (boot_bit_test(BOOT_BIT_NEW_FW_UPDATE_IN_PROGRESS)) {
dbgserial_putstr("Our previous firmware update failed, aborting update.");
// Pretend like the new firmware bit wasn't set afterall. We'll just run the
// previous code, whether that was normal firmware or the recovery firmware.
boot_bit_clear(BOOT_BIT_NEW_FW_UPDATE_IN_PROGRESS);
boot_bit_clear(BOOT_BIT_NEW_FW_AVAILABLE);
boot_bit_clear(BOOT_BIT_NEW_FW_INSTALLED);
return;
}
dbgserial_putstr("New firmware is available!");
boot_bit_clear(BOOT_BIT_FW_START_FAIL_STRIKE_ONE);
boot_bit_clear(BOOT_BIT_FW_START_FAIL_STRIKE_TWO);
boot_bit_clear(BOOT_BIT_RECOVERY_LOAD_FAIL_STRIKE_ONE);
boot_bit_clear(BOOT_BIT_RECOVERY_LOAD_FAIL_STRIKE_TWO);
UpdateFirmwareResult result = update_fw(FLASH_REGION_FIRMWARE_SCRATCH_BEGIN);
switch (result) {
case UPDATE_FW_SUCCESS:
break;
case UPDATE_FW_ERROR_MICRO_FLASH_UNTOUCHED:
// Our firmware update failed in a way that didn't break our previous
// firmware. Just run the previous code, whether that was normal firmware
// or the recovery firmware.
break;
case UPDATE_FW_ERROR_MICRO_FLASH_MANGLED:
// We've broken our internal flash when trying to update our normal
// firmware. Fall back immediately to the recovery firmare.
boot_bit_set(BOOT_BIT_FW_START_FAIL_STRIKE_ONE);
boot_bit_set(BOOT_BIT_FW_START_FAIL_STRIKE_TWO);
system_reset();
return;
}
// Done, we're ready to boot.
boot_bit_clear(BOOT_BIT_NEW_FW_UPDATE_IN_PROGRESS);
boot_bit_clear(BOOT_BIT_NEW_FW_AVAILABLE);
boot_bit_set(BOOT_BIT_NEW_FW_INSTALLED);
}
bool switch_to_recovery_fw() {
dbgserial_putstr("Loading recovery firmware");
UpdateFirmwareResult result = update_fw(FLASH_REGION_SAFE_FIRMWARE_BEGIN);
bool recovery_fw_ok = true;
switch (result) {
case UPDATE_FW_SUCCESS:
boot_bit_clear(BOOT_BIT_RECOVERY_LOAD_FAIL_STRIKE_ONE);
boot_bit_clear(BOOT_BIT_RECOVERY_LOAD_FAIL_STRIKE_TWO);
boot_bit_set(BOOT_BIT_RECOVERY_START_IN_PROGRESS);
break;
case UPDATE_FW_ERROR_MICRO_FLASH_UNTOUCHED:
case UPDATE_FW_ERROR_MICRO_FLASH_MANGLED:
// Keep us booting into recovery firmware.
boot_bit_set(BOOT_BIT_FW_START_FAIL_STRIKE_ONE);
boot_bit_set(BOOT_BIT_FW_START_FAIL_STRIKE_TWO);
if (!boot_bit_test(BOOT_BIT_RECOVERY_LOAD_FAIL_STRIKE_ONE)) {
dbgserial_putstr("Failed to load recovery firmware, strike one. Try again.");
boot_bit_set(BOOT_BIT_RECOVERY_LOAD_FAIL_STRIKE_ONE);
boot_bit_set(BOOT_BIT_SOFTWARE_FAILURE_OCCURRED);
system_reset();
} else if (!boot_bit_test(BOOT_BIT_RECOVERY_LOAD_FAIL_STRIKE_TWO)) {
dbgserial_putstr("Failed to load recovery firmware, strike two. Try again.");
boot_bit_set(BOOT_BIT_RECOVERY_LOAD_FAIL_STRIKE_TWO);
boot_bit_set(BOOT_BIT_SOFTWARE_FAILURE_OCCURRED);
system_reset();
} else {
dbgserial_putstr("Failed to load recovery firmware, strike three. SAD WATCH");
boot_bit_clear(BOOT_BIT_FW_START_FAIL_STRIKE_ONE);
boot_bit_clear(BOOT_BIT_FW_START_FAIL_STRIKE_TWO);
boot_bit_clear(BOOT_BIT_RECOVERY_LOAD_FAIL_STRIKE_ONE);
boot_bit_clear(BOOT_BIT_RECOVERY_LOAD_FAIL_STRIKE_TWO);
recovery_fw_ok = false;
}
break;
}
boot_bit_clear(BOOT_BIT_NEW_FW_UPDATE_IN_PROGRESS);
return recovery_fw_ok;
}

View file

@ -0,0 +1,24 @@
/*
* 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>
void check_update_fw(void);
//! @return false if we've failed to load recovery mode too many times and we should just sadwatch
bool switch_to_recovery_fw(void);

View file

@ -0,0 +1,8 @@
#pragma once
#define GIT_TIMESTAMP @TIMESTAMP@
#define GIT_TAG "@TAG@"
#define GIT_MAJOR_VERSION @MAJOR_VERSION@
#define GIT_MINOR_VERSION @MINOR_VERSION@
#define GIT_PATCH_VERSION @PATCH_VERSION@
#define GIT_REVISION "@COMMIT@"

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 "drivers/dbgserial.h"
#include "system/die.h"
#include "system/reset.h"
static void prv_hard_fault_handler_c(unsigned int *hardfault_args) {
dbgserial_putstr("HARD FAULT");
#ifdef NO_WATCHDOG
reset_due_to_software_failure();
#else
system_hard_reset();
#endif
}
void HardFault_Handler(void) {
// Grab the stack pointer, shove it into a register and call
// the c function above.
__asm("tst lr, #4\n"
"ite eq\n"
"mrseq r0, msp\n"
"mrsne r0, psp\n"
"b %0\n" :: "i" (prv_hard_fault_handler_c));
}

View file

@ -0,0 +1,474 @@
/*
* 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 <stdint.h>
#include <inttypes.h>
#include "board/board.h"
#include "boot_tests.h"
#include "drivers/button.h"
#include "drivers/dbgserial.h"
#include "drivers/display.h"
#include "drivers/flash.h"
#include "drivers/gpio.h"
#include "drivers/i2c.h"
#include "drivers/periph_config.h"
#include "drivers/pmic.h"
#include "drivers/watchdog.h"
#include "firmware.h"
#include "fw_copy.h"
#include "pebble_errors.h"
#include "stm32f4xx.h"
#include "system/bootbits.h"
#include "system/logging.h"
#include "system/passert.h"
#include "system/reset.h"
#include "util/delay.h"
static const uint8_t SELECT_BUTTON_MASK = 0x4;
static void prv_get_fw_reset_vector(void **reset_handler,
void **initial_stack_pointer) {
uintptr_t** fw_vector_table = (uintptr_t**) FIRMWARE_BASE; // Defined in wscript
*initial_stack_pointer = (void *)fw_vector_table[0];
*reset_handler = (void *)fw_vector_table[1];
}
static void __attribute__((noreturn)) jump_to_fw(void) {
void *initial_stack_pointer, *reset_handler;
prv_get_fw_reset_vector(&reset_handler, &initial_stack_pointer);
dbgserial_print("Booting firmware @ ");
dbgserial_print_hex((uintptr_t)reset_handler);
dbgserial_print("...\r\n\r\n");
// Disable all interrupts, just in case.
for (int i = 0; i < 8; ++i) {
// Interrupt Clear-Enable Register
NVIC->ICER[i] = 0xFFFFFFFF;
// Interrupt Clear-Pending Register
NVIC->ICPR[i] = 0xFFFFFFFF;
}
// Disable all of the peripheral clocks we may have on, but don't touch any of
// the reserved bits. The reset values in the stm32f412 reference manual are
// not to be trusted, so make sure those bits never change.
RCC->AHB1ENR &= ~(0x006010ff);
RCC->AHB2ENR &= ~(0x000000c0);
RCC->AHB3ENR &= ~(0x00000003);
RCC->APB1ENR &= ~(0x17e6c9ff);
RCC->APB2ENR &= ~(0x01177933);
// Reset most peripherals used by the bootloader. We want to minimize the
// chances that the firmware unintentionally relies on some state that the
// bootloader leaves behind. This includes disabling the PLL.
// GPIOs are not reset here: resetting them would change their output values,
// which could unintentionally turn of e.g. PMIC power rails.
// The backup domain is not reset; that would be foolish.
const uint32_t ahb1_periphs =
RCC_AHB1Periph_CRC | RCC_AHB1Periph_DMA1 | RCC_AHB1Periph_DMA2
| RCC_AHB1Periph_DMA2D | RCC_AHB1Periph_ETH_MAC | RCC_AHB1Periph_OTG_HS;
const uint32_t ahb2_periphs =
RCC_AHB2Periph_DCMI | RCC_AHB2Periph_CRYP | RCC_AHB2Periph_HASH
| RCC_AHB2Periph_RNG | RCC_AHB2Periph_OTG_FS;
const uint32_t ahb3_periphs = RCC_AHB3Periph_FSMC;
const uint32_t apb1_periphs =
RCC_APB1Periph_TIM2 | RCC_APB1Periph_TIM3 | RCC_APB1Periph_TIM4
| RCC_APB1Periph_TIM5 | RCC_APB1Periph_TIM6 | RCC_APB1Periph_TIM7
| RCC_APB1Periph_TIM12 | RCC_APB1Periph_TIM13 | RCC_APB1Periph_TIM14
| RCC_APB1Periph_WWDG | RCC_APB1Periph_SPI2 | RCC_APB1Periph_SPI3
| RCC_APB1Periph_USART2 | RCC_APB1Periph_USART3 | RCC_APB1Periph_UART4
| RCC_APB1Periph_UART5 | RCC_APB1Periph_I2C1 | RCC_APB1Periph_I2C2
| RCC_APB1Periph_I2C3 | RCC_APB1Periph_CAN1 | RCC_APB1Periph_CAN2
| RCC_APB1Periph_PWR | RCC_APB1Periph_DAC | RCC_APB1Periph_UART7
| RCC_APB1Periph_UART8;
const uint32_t apb2_periphs =
RCC_APB2Periph_TIM1 | RCC_APB2Periph_TIM8 | RCC_APB2Periph_USART1 |
RCC_APB2Periph_USART6 | RCC_APB2Periph_ADC | RCC_APB2Periph_ADC1 |
RCC_APB2Periph_ADC2 | RCC_APB2Periph_ADC3 | RCC_APB2Periph_SDIO |
RCC_APB2Periph_SPI1 | RCC_APB2Periph_SPI4 | RCC_APB2Periph_SYSCFG |
RCC_APB2Periph_TIM9 | RCC_APB2Periph_TIM10 | RCC_APB2Periph_TIM11 |
RCC_APB2Periph_SPI5 | RCC_APB2Periph_SPI6 | RCC_APB2Periph_SAI1 |
RCC_APB2Periph_LTDC;
RCC_DeInit();
RCC_AHB1PeriphResetCmd(ahb1_periphs, ENABLE);
RCC_AHB1PeriphResetCmd(ahb1_periphs, DISABLE);
RCC_AHB2PeriphResetCmd(ahb2_periphs, ENABLE);
RCC_AHB2PeriphResetCmd(ahb2_periphs, DISABLE);
RCC_AHB3PeriphResetCmd(ahb3_periphs, ENABLE);
RCC_AHB3PeriphResetCmd(ahb3_periphs, DISABLE);
RCC_APB1PeriphResetCmd(apb1_periphs, ENABLE);
RCC_APB1PeriphResetCmd(apb1_periphs, DISABLE);
RCC_APB2PeriphResetCmd(apb2_periphs, ENABLE);
RCC_APB2PeriphResetCmd(apb2_periphs, DISABLE);
// The Cortex-M user guide states that the reset values for the core registers
// are as follows:
// R0-R12 = Unknown
// MSP = VECTOR_TABLE[0] (main stack pointer)
// PSP = Unknown (process stack pointer)
// LR = 0xFFFFFFFF
// PC = VECTOR_TABLE[1]
// PRIMASK = 0x0
// FAULTMASK = 0x0
// BASEPRI = 0x0
// CONTROL = 0x0
//
// Attempt to put the processor into as close to the reset state as possible
// before passing control to the firmware.
//
// No attempt is made to set CONTROL to zero as it should already be set to
// the reset value when this code executes.
__asm volatile (
"cpsie if\n" // Clear PRIMASK and FAULTMASK
"mov lr, 0xFFFFFFFF\n"
"mov sp, %[initial_sp]\n"
"bx %[reset_handler]\n"
: : [initial_sp] "r" (initial_stack_pointer),
[reset_handler] "r" (reset_handler)
);
__builtin_unreachable();
}
static bool check_and_increment_reset_loop_detection_bits(void) {
uint8_t counter =
(boot_bit_test(BOOT_BIT_RESET_LOOP_DETECT_THREE) << 2) |
(boot_bit_test(BOOT_BIT_RESET_LOOP_DETECT_TWO) << 1) |
boot_bit_test(BOOT_BIT_RESET_LOOP_DETECT_ONE);
if (counter == 7) {
boot_bit_clear(BOOT_BIT_RESET_LOOP_DETECT_ONE);
boot_bit_clear(BOOT_BIT_RESET_LOOP_DETECT_TWO);
boot_bit_clear(BOOT_BIT_RESET_LOOP_DETECT_THREE);
return true;
}
switch (++counter) {
case 1:
boot_bit_set(BOOT_BIT_RESET_LOOP_DETECT_ONE);
break;
case 2:
boot_bit_clear(BOOT_BIT_RESET_LOOP_DETECT_ONE);
boot_bit_set(BOOT_BIT_RESET_LOOP_DETECT_TWO);
break;
case 3:
boot_bit_set(BOOT_BIT_RESET_LOOP_DETECT_ONE);
break;
case 4:
boot_bit_clear(BOOT_BIT_RESET_LOOP_DETECT_ONE);
boot_bit_clear(BOOT_BIT_RESET_LOOP_DETECT_TWO);
boot_bit_set(BOOT_BIT_RESET_LOOP_DETECT_THREE);
break;
case 5:
boot_bit_set(BOOT_BIT_RESET_LOOP_DETECT_ONE);
break;
case 6:
boot_bit_clear(BOOT_BIT_RESET_LOOP_DETECT_ONE);
boot_bit_set(BOOT_BIT_RESET_LOOP_DETECT_TWO);
break;
case 7:
boot_bit_set(BOOT_BIT_RESET_LOOP_DETECT_ONE);
break;
default:
PBL_CROAK("reset loop boot bits overrun");
break;
}
return false;
}
static bool check_for_recovery_start_failure() {
return boot_bit_test(BOOT_BIT_RECOVERY_START_IN_PROGRESS);
}
static bool check_for_fw_start_failure() {
// Add more failure conditions here.
if (!watchdog_check_reset_flag() && !boot_bit_test(BOOT_BIT_SOFTWARE_FAILURE_OCCURRED)) {
// We're good, we're just starting normally.
PBL_LOG_VERBOSE("We're good, we're just starting normally.");
boot_bit_clear(BOOT_BIT_FW_START_FAIL_STRIKE_ONE);
boot_bit_clear(BOOT_BIT_FW_START_FAIL_STRIKE_TWO);
return false;
}
// We failed to start our firmware successfully!
if (watchdog_check_reset_flag()) {
dbgserial_putstr("Watchdog caused a reset");
}
if (boot_bit_test(BOOT_BIT_SOFTWARE_FAILURE_OCCURRED)) {
dbgserial_putstr("Software failure caused a reset");
}
// Clean up after the last failure.
boot_bit_clear(BOOT_BIT_SOFTWARE_FAILURE_OCCURRED);
// We have a "three strikes" algorithm: if the watch fails three times, return true
// to tell the parent we should load the recovery firmware. A reset for any other reason
// will reset this algorithm.
if (boot_bit_test(BOOT_BIT_FW_START_FAIL_STRIKE_TWO)) {
// Yikes, our firmware is screwed. Boot into recovery mode.
dbgserial_putstr("Failed to start firmware, strike three.");
boot_bit_clear(BOOT_BIT_FW_START_FAIL_STRIKE_ONE);
boot_bit_clear(BOOT_BIT_FW_START_FAIL_STRIKE_TWO);
return true;
} else if (boot_bit_test(BOOT_BIT_FW_START_FAIL_STRIKE_ONE)) {
dbgserial_putstr("Failed to start firmware, strike two.");
boot_bit_set(BOOT_BIT_FW_START_FAIL_STRIKE_TWO);
} else {
dbgserial_putstr("Failed to start firmware, strike one.");
boot_bit_set(BOOT_BIT_FW_START_FAIL_STRIKE_ONE);
}
return false;
}
static bool prv_prf_button_combination_is_pressed(void) {
return (button_is_pressed(BUTTON_ID_UP) && button_is_pressed(BUTTON_ID_BACK)
&& button_is_pressed(BUTTON_ID_SELECT) && !button_is_pressed(BUTTON_ID_DOWN));
}
static bool check_force_boot_recovery(void) {
if (boot_bit_test(BOOT_BIT_FORCE_PRF)) {
boot_bit_clear(BOOT_BIT_FORCE_PRF);
return true;
}
if (prv_prf_button_combination_is_pressed()) {
dbgserial_putstr("Hold down UP + BACK + SELECT for 5 secs. to force-boot PRF");
for (int i = 0; i < 5000; ++i) {
if (!prv_prf_button_combination_is_pressed()) {
// stop waiting if not held down any longer
return false;
}
delay_ms(1);
}
return true;
}
void *reset_vector, *initial_sp;
prv_get_fw_reset_vector(&reset_vector, &initial_sp);
if ((uintptr_t)reset_vector == 0xffffffff ||
(uintptr_t)initial_sp == 0xffffffff) {
dbgserial_putstr("Firmware is erased");
return true;
}
return false;
}
static void sad_watch(uint32_t error_code) {
dbgserial_putstr("SAD WATCH");
char error_code_buffer[12];
itoa_hex(error_code, error_code_buffer, sizeof(error_code_buffer));
dbgserial_putstr(error_code_buffer);
display_error_code(error_code);
static uint8_t prev_button_state = 0;
prev_button_state = button_get_state_bits() & ~SELECT_BUTTON_MASK;
while (1) {
// See if we should restart
uint8_t button_state = button_get_state_bits() & ~SELECT_BUTTON_MASK;
if (button_state != prev_button_state) {
system_reset();
}
delay_ms(10);
}
}
static void check_and_handle_resuming_from_standby(void) {
periph_config_enable(RCC_APB1PeriphClockCmd, RCC_APB1Periph_PWR);
if (PWR_GetFlagStatus(PWR_FLAG_SB) == SET) {
// We just woke up from standby. For some reason this leaves the system in a funny state,
// so clear the flag and reboot again to really clear things up.
PWR_ClearFlag(PWR_FLAG_SB);
dbgserial_putstr("exit standby");
system_hard_reset();
}
periph_config_disable(RCC_APB1PeriphClockCmd, RCC_APB1Periph_PWR);
}
void boot_main(void) {
gpio_enable_all();
check_and_handle_resuming_from_standby();
dbgserial_init();
dbgserial_putstr("");
dbgserial_putstr(" ___ _ _ _ ");
dbgserial_putstr("/ __><_>| || |__");
dbgserial_putstr("\\__ \\| || || / /");
dbgserial_putstr("<___/|_||_||_\\_\\");
dbgserial_putstr("");
// PMIC requires I2C
i2c_init();
dbgserial_putstr("i2c inited");
pmic_init();
dbgserial_putstr("pmic inited");
boot_bit_init();
dbgserial_putstr("boot bit");
boot_version_write();
// Write the bootloader version to serial-out
{
char bootloader_version_str[12];
memset(bootloader_version_str, 0, 12);
itoa_hex(boot_version_read(), bootloader_version_str, 12);
dbgserial_putstr(bootloader_version_str);
}
dbgserial_putstr("");
dbgserial_putstr("");
if (boot_bit_test(BOOT_BIT_FW_STABLE)) {
dbgserial_putstr("Last firmware boot was stable; clear strikes");
boot_bit_clear(BOOT_BIT_FW_STABLE);
boot_bit_clear(BOOT_BIT_FW_START_FAIL_STRIKE_ONE);
boot_bit_clear(BOOT_BIT_FW_START_FAIL_STRIKE_TWO);
boot_bit_clear(BOOT_BIT_RECOVERY_LOAD_FAIL_STRIKE_ONE);
boot_bit_clear(BOOT_BIT_RECOVERY_LOAD_FAIL_STRIKE_TWO);
}
if (pmic_boot_due_to_charger_disconnect()) {
dbgserial_putstr("PMIC woke from standby due to charger disconnect");
dbgserial_putstr("Putting PMIC back into standby");
pmic_power_off();
}
flash_init();
button_init();
display_init();
display_boot_splash();
#ifdef DISPLAY_DEMO_LOOP
while (1) {
for (int i=0; i < 92; ++i) {
display_firmware_update_progress(i, 91);
delay_us(80000);
}
for (uint32_t i=0; i <= 0xf; ++i) {
display_error_code(i * 0x11111111);
delay_us(200000);
}
for (uint32_t i=0; i < 8; ++i) {
for (uint32_t j=1; j<=0xf; ++j) {
display_error_code(j << (i*4));
delay_us(200000);
}
}
display_error_code(0x01234567);
delay_us(200000);
display_error_code(0x89abcdef);
delay_us(200000);
display_error_code(0xcafebabe);
delay_us(200000);
display_error_code(0xfeedface);
delay_us(200000);
display_error_code(0x8badf00d);
delay_us(200000);
display_error_code(0xbad1ce40);
delay_us(200000);
display_error_code(0xbeefcace);
delay_us(200000);
display_error_code(0x0defaced);
delay_us(200000);
display_error_code(0xd15ea5e5);
delay_us(200000);
display_error_code(0xdeadbeef);
delay_us(200000);
display_boot_splash();
delay_us(1000000);
}
#endif
if (is_button_stuck()) {
sad_watch(ERROR_STUCK_BUTTON);
}
if (is_flash_broken()) {
sad_watch(ERROR_BAD_SPI_FLASH);
}
boot_bit_dump();
// If the recovery firmware crashed at start-up, the watch is now a
// $150 brick. That's life!
if (check_for_recovery_start_failure()) {
boot_bit_clear(BOOT_BIT_RECOVERY_START_IN_PROGRESS);
sad_watch(ERROR_CANT_LOAD_FW);
}
bool force_boot_recovery_mode = check_force_boot_recovery();
if (force_boot_recovery_mode) {
dbgserial_putstr("Force-booting recovery mode...");
}
if (force_boot_recovery_mode || check_for_fw_start_failure()) {
if (!switch_to_recovery_fw()) {
// We've failed to load recovery mode too many times.
sad_watch(ERROR_CANT_LOAD_FW);
}
} else {
check_update_fw();
}
if (check_and_increment_reset_loop_detection_bits()) {
sad_watch(ERROR_RESET_LOOP);
}
if (boot_bit_test(BOOT_BIT_SHUTDOWN_REQUESTED)) {
boot_bit_clear(BOOT_BIT_SHUTDOWN_REQUESTED);
dbgserial_putstr("Shutdown requested.");
// A full shutdown has been requested instead of booting.
// This needs to be done prior to watchdog init: we may need to spin on the charger status.
// It is OK to spin indefinitely here. We are connected to a charger and won't die.
int i = 0;
while (pmic_is_usb_connected()) {
if (i == 4) {
dbgserial_putstr("Remove charger to shutdown");
i = 0;
}
delay_ms(500);
++i;
}
dbgserial_putstr("Shutting down.");
pmic_full_power_off();
}
watchdog_init();
#ifndef NO_WATCHDOG
watchdog_start();
#endif
gpio_disable_all();
jump_to_fw();
}

View file

@ -0,0 +1,34 @@
/*
* 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>
static const uint32_t ERROR_NO_ACTIVE_ERROR = 0;
// FW Errors
static const uint32_t ERROR_STUCK_BUTTON = 0xfe504501;
static const uint32_t ERROR_BAD_SPI_FLASH = 0xfe504502;
static const uint32_t ERROR_CANT_LOAD_FW = 0xfe504503;
static const uint32_t ERROR_RESET_LOOP = 0xfe504504;
// BT Errors
static const uint32_t ERROR_CANT_LOAD_BT = 0xfe504510;
static const uint32_t ERROR_CANT_START_LSE = 0xfe504511;
static const uint32_t ERROR_CANT_START_ACCEL = 0xfe504512;
static const uint32_t ERROR_LOW_BATTERY = 0xfe504520;

View file

@ -0,0 +1,180 @@
/*
Linker script for STM32F2xx_1024K_128K
*/
/* include the common STM32F2xx sub-script */
/* Common part of the linker scripts for STM32 devices*/
/* default stack sizes.
These are used by the startup in order to allocate stacks for the different modes.
*/
__Stack_Size = 8192;
PROVIDE ( _Stack_Size = __Stack_Size ) ;
__Stack_Init = _estack - __Stack_Size ;
/*"PROVIDE" allows to easily override these values from an object file or the commmand line.*/
PROVIDE ( _Stack_Init = __Stack_Init ) ;
/*
There will be a link error if there is not this amount of RAM free at the end.
*/
_Minimum_Stack_Size = 0x100 ;
/* include the memory spaces definitions sub-script */
/*
Linker subscript for STM32F2xx definitions with 1024K Flash and 1024K External SRAM */
/* Memory Spaces Definitions */
MEMORY
{
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 256K
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = @BOOTLOADER_LENGTH@
}
__end_heap = ORIGIN(RAM) + LENGTH(RAM);
PROVIDE(_heap_end = __end_heap);
/* include the sections management sub-script for FLASH mode */
/* Sections Definitions */
SECTIONS
{
/* for Cortex devices, the beginning of the startup code is stored in the .isr_vector section, which goes to FLASH */
.isr_vector :
{
. = ALIGN(4);
KEEP(*(.isr_vector)) /* Startup code */
. = ALIGN(4);
} >FLASH
/* for some STRx devices, the beginning of the startup code is stored in the .flashtext section, which goes to FLASH */
.flashtext :
{
. = ALIGN(4);
*(.flashtext) /* Startup code */
. = ALIGN(4);
} >FLASH
/* Exception handling sections. "contains index entries for section unwinding" */
.ARM.exidx :
{
. = ALIGN(4);
*(.ARM.exidx)
. = ALIGN(4);
} >FLASH
/* the program code is stored in the .text section, which goes to Flash */
.text :
{
. = ALIGN(4);
*(.text) /* remaining code */
*(.text.*) /* remaining code */
*(.rodata) /* read-only data (constants) */
*(.rodata*)
*(.constdata) /* read-only data (constants) */
*(.constdata*)
*(.glue_7)
*(.glue_7t)
*(i.*)
. = ALIGN(4);
_etext = .;
_sidata = _etext;
} >FLASH
/* This is the initialized data section
The program executes knowing that the data is in the RAM
but the loader puts the initial values in the FLASH (inidata).
It is one task of the startup to copy the initial values from FLASH to RAM. */
.data : AT ( _sidata )
{
. = ALIGN(4);
/* This is used by the startup in order to initialize the .data secion */
_sdata = .;
*(.data)
*(.data.*)
. = ALIGN(4);
_edata = .; /* This is used by the startup in order to initialize the .data secion */
} >RAM
/* This is the uninitialized data section */
.bss :
{
. = ALIGN(4);
_sbss = .; /* This is used by the startup in order to initialize the .bss secion */
*(.bss)
*(.bss.*)
*(COMMON)
. = ALIGN(4);
_ebss = .; /* This is used by the startup in order to initialize the .bss secion */
} >RAM
.stack :
{
. = ALIGN(8);
_sstack = .;
. = . + __Stack_Size;
. = ALIGN(8);
_endstack = .;
} >RAM
PROVIDE ( _estack = _endstack);
PROVIDE ( end = _endstack );
PROVIDE ( _end = _endstack );
PROVIDE ( _heap_start = _endstack );
/* after that it's only debugging information. */
/* remove the debugging information from the standard libraries */
DISCARD :
{
libc.a ( * )
libm.a ( * )
libgcc.a ( * )
}
/* Stabs debugging sections. */
.stab 0 : { *(.stab) }
.stabstr 0 : { *(.stabstr) }
.stab.excl 0 : { *(.stab.excl) }
.stab.exclstr 0 : { *(.stab.exclstr) }
.stab.index 0 : { *(.stab.index) }
.stab.indexstr 0 : { *(.stab.indexstr) }
.comment 0 : { *(.comment) }
/* DWARF debug sections.
Symbols in the DWARF debugging sections are relative to the beginning
of the section so we begin them at 0. */
/* DWARF 1 */
.debug 0 : { *(.debug) }
.line 0 : { *(.line) }
/* GNU DWARF 1 extensions */
.debug_srcinfo 0 : { *(.debug_srcinfo) }
.debug_sfnames 0 : { *(.debug_sfnames) }
/* DWARF 1.1 and DWARF 2 */
.debug_aranges 0 : { *(.debug_aranges) }
.debug_pubnames 0 : { *(.debug_pubnames) }
/* DWARF 2 */
.debug_info 0 : { *(.debug_info .gnu.linkonce.wi.*) }
.debug_abbrev 0 : { *(.debug_abbrev) }
.debug_line 0 : { *(.debug_line) }
.debug_frame 0 : { *(.debug_frame) }
.debug_str 0 : { *(.debug_str) }
.debug_loc 0 : { *(.debug_loc) }
.debug_macinfo 0 : { *(.debug_macinfo) }
/* SGI/MIPS DWARF 2 extensions */
.debug_weaknames 0 : { *(.debug_weaknames) }
.debug_funcnames 0 : { *(.debug_funcnames) }
.debug_typenames 0 : { *(.debug_typenames) }
.debug_varnames 0 : { *(.debug_varnames) }
}

View file

@ -0,0 +1,72 @@
/*
* 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/dbgserial.h"
#include "system/bootbits.h"
#include "system/rtc_registers.h"
#include "git_version.auto.h"
#include "stm32f4xx.h"
#include <inttypes.h>
#include <stdint.h>
static const uint32_t s_bootloader_timestamp = GIT_TIMESTAMP;
void boot_bit_init(void) {
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);
PWR_BackupAccessCmd(ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, DISABLE);
if (!boot_bit_test(BOOT_BIT_INITIALIZED)) {
RTC_WriteBackupRegister(RTC_BKP_BOOTBIT_DR, BOOT_BIT_INITIALIZED);
}
}
void boot_bit_set(BootBitValue bit) {
uint32_t current_value = RTC_ReadBackupRegister(RTC_BKP_BOOTBIT_DR);
current_value |= bit;
RTC_WriteBackupRegister(RTC_BKP_BOOTBIT_DR, current_value);
}
void boot_bit_clear(BootBitValue bit) {
uint32_t current_value = RTC_ReadBackupRegister(RTC_BKP_BOOTBIT_DR);
current_value &= ~bit;
RTC_WriteBackupRegister(RTC_BKP_BOOTBIT_DR, current_value);
}
bool boot_bit_test(BootBitValue bit) {
uint32_t current_value = RTC_ReadBackupRegister(RTC_BKP_BOOTBIT_DR);
return (current_value & bit);
}
void boot_bit_dump(void) {
dbgserial_print("Boot bits: ");
dbgserial_print_hex(RTC_ReadBackupRegister(RTC_BKP_BOOTBIT_DR));
dbgserial_newline();
}
void boot_version_write(void) {
if (boot_version_read() == s_bootloader_timestamp) {
return;
}
RTC_WriteBackupRegister(BOOTLOADER_VERSION_REGISTER, s_bootloader_timestamp);
}
uint32_t boot_version_read(void) {
return RTC_ReadBackupRegister(BOOTLOADER_VERSION_REGISTER);
}

View file

@ -0,0 +1,54 @@
/*
* 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>
#include <stdbool.h>
typedef enum BootBitValue {
BOOT_BIT_INITIALIZED = 0x1 << 0,
BOOT_BIT_NEW_FW_AVAILABLE = 0x1 << 1,
BOOT_BIT_NEW_FW_UPDATE_IN_PROGRESS = 0x1 << 2,
BOOT_BIT_FW_START_FAIL_STRIKE_ONE = 0x1 << 3,
BOOT_BIT_FW_START_FAIL_STRIKE_TWO = 0x1 << 4,
BOOT_BIT_RECOVERY_LOAD_FAIL_STRIKE_ONE = 0x1 << 5,
BOOT_BIT_RECOVERY_LOAD_FAIL_STRIKE_TWO = 0x1 << 6,
BOOT_BIT_RECOVERY_START_IN_PROGRESS = 0x1 << 7,
BOOT_BIT_STANDBY_MODE_REQUESTED = 0x1 << 8, //!< Bootloader enter standby immediately after reset.
BOOT_BIT_SOFTWARE_FAILURE_OCCURRED = 0x1 << 9,
BOOT_BIT_NEW_SYSTEM_RESOURCES_AVAILABLE = 0x1 << 10,
BOOT_BIT_RESET_LOOP_DETECT_ONE = 0x1 << 11,
BOOT_BIT_RESET_LOOP_DETECT_TWO = 0x1 << 12,
BOOT_BIT_RESET_LOOP_DETECT_THREE = 0x1 << 13,
BOOT_BIT_FW_STABLE = 0x1 << 14,
BOOT_BIT_NEW_FW_INSTALLED = 0x1 << 15,
BOOT_BIT_STANDBY_MODE_ENTERED = 0x1 << 16,
BOOT_BIT_FORCE_PRF = 0x1 << 17,
BOOT_BIT_NEW_PRF_AVAILABLE = 0x1 << 18,
BOOT_BIT_SHUTDOWN_REQUESTED = 0x1 << 19, //!< Bootloader hard power-off instead of jumping to fw.
} BootBitValue;
void boot_bit_init();
void boot_bit_set(BootBitValue bit);
void boot_bit_clear(BootBitValue bit);
bool boot_bit_test(BootBitValue bit);
// Dump the contents through dbgserial
void boot_bit_dump(void);
void boot_version_write(void);
uint32_t boot_version_read(void);

View file

@ -0,0 +1,34 @@
/*
* 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/dbgserial.h"
#include "system/reset.h"
#include "system/passert.h"
#include "misc.h"
void reset_due_to_software_failure(void) {
#if defined(NO_WATCHDOG)
// Don't reset right away, leave it in a state we can inspect
while (1) {
BREAKPOINT;
}
#endif
dbgserial_putstr("Software failure; resetting!");
system_reset();
}

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
//! Does not call reboot_reason_set, only calls reboot_reason_set_restarted_safely if we were
//! able shut everything down nicely before rebooting.
void reset_due_to_software_failure(void) __attribute__((noreturn));

View file

@ -0,0 +1,30 @@
/*
* 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 "firmware_storage.h"
#include "drivers/flash.h"
FirmwareDescription firmware_storage_read_firmware_description(uint32_t firmware_start_address) {
FirmwareDescription firmware_description;
flash_read_bytes((uint8_t*)&firmware_description, firmware_start_address,
sizeof(FirmwareDescription));
return firmware_description;
}
bool firmware_storage_check_valid_firmware_description(FirmwareDescription* desc) {
return desc->description_length == sizeof(FirmwareDescription);
}

View file

@ -0,0 +1,33 @@
/*
* 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
//! @file firmware_storage.h
//! Utilities for reading a firmware image stored in flash.
#include <stdbool.h>
#include <stdint.h>
typedef struct __attribute__((__packed__)) FirmwareDescription {
uint32_t description_length;
uint32_t firmware_length;
uint32_t checksum;
} FirmwareDescription;
FirmwareDescription firmware_storage_read_firmware_description(uint32_t firmware_start_address);
bool firmware_storage_check_valid_firmware_description(FirmwareDescription* desc);

View file

@ -0,0 +1,66 @@
/*
* 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/dbgserial.h"
#include "system/die.h"
#include <stdarg.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#ifndef __FILE_NAME__
#define __FILE_NAME__ __FILE__
#endif
#define LOG_LEVEL_ALWAYS 0
#define LOG_LEVEL_ERROR 1
#define LOG_LEVEL_WARNING 50
#define LOG_LEVEL_INFO 100
#define LOG_LEVEL_DEBUG 200
#define LOG_LEVEL_DEBUG_VERBOSE 255
#ifndef STRINGIFY
#define STRINGIFY_NX(a) #a
#define STRINGIFY(a) STRINGIFY_NX(a)
#endif // STRINGIFY
#define STATUS_STRING(s) STRINGIFY(s)
#ifdef PBL_LOG_ENABLED
#define PBL_LOG(level, fmt, args...) \
do { \
char _pbl_log_buffer[128]; \
dbgserial_putstr_fmt(_pbl_log_buffer, sizeof(_pbl_log_buffer), \
__FILE_NAME__ ":" STRINGIFY(__LINE__) "> " fmt, ## args); \
} while (0)
#ifdef VERBOSE_LOGGING
#define PBL_LOG_VERBOSE(fmt, args...) \
PBL_LOG(LOG_LEVEL_DEBUG, fmt, ## args)
#else // VERBOSE_LOGGING
#define PBL_LOG_VERBOSE(fmt, args...)
#endif // VERBOSE_LOGGING
#else // PBL_LOG_ENABLED
#define PBL_LOG(level, fmt, args...)
#define PBL_LOG_VERBOSE(fmt, args...)
#endif // PBL_LOG_ENABLED

View file

@ -0,0 +1,105 @@
/*
* 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 "passert.h"
#include "system/die.h"
#include "drivers/dbgserial.h"
#include "util/misc.h"
#include <string.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
static __attribute__((noreturn)) void handle_passert_failed_vargs(const char* filename, int line_number,
uintptr_t lr, const char* expr, const char* fmt, va_list fmt_args) {
dbgserial_print("ASSERT: ");
dbgserial_print(expr);
dbgserial_print(" ");
dbgserial_print(filename);
dbgserial_print(":");
dbgserial_print_hex(line_number);
if (fmt) {
dbgserial_print(" ");
dbgserial_print(fmt);
}
dbgserial_putstr("");
reset_due_to_software_failure();
}
static __attribute__((noreturn)) void handle_passert_failed(const char* filename, int line_number,
uintptr_t lr, const char *expr, const char* fmt, ...) {
va_list fmt_args;
va_start(fmt_args, fmt);
handle_passert_failed_vargs(filename, line_number, lr, expr, fmt, fmt_args);
va_end(fmt_args);
}
void passert_failed(const char* filename, int line_number, const char* message, ...) {
va_list fmt_args;
va_start(fmt_args, message);
handle_passert_failed_vargs(filename, line_number,
(uintptr_t)__builtin_return_address(0), "ASSERT", message, fmt_args);
va_end(fmt_args);
}
void passert_failed_no_message(const char* filename, int line_number) {
handle_passert_failed(filename, line_number,
(uintptr_t)__builtin_return_address(0), "ASSERTN", NULL);
}
void wtf(void) {
uintptr_t saved_lr = (uintptr_t) __builtin_return_address(0);
dbgserial_print("*** WTF ");
dbgserial_print_hex(saved_lr);
dbgserial_putstr("");
reset_due_to_software_failure();
}
//! Assert function called by the STM peripheral library's
//! 'assert_param' method. See stm32f2xx_conf.h for more information.
void assert_failed(uint8_t* file, uint32_t line) {
register uintptr_t lr __asm("lr");
uintptr_t saved_lr = lr;
handle_passert_failed((const char*) file, line, saved_lr, "STM32", "STM32 peripheral library tripped an assert");
}
extern void command_dump_malloc_kernel(void);
void croak_oom(const char *filename, int line_number, const char *fmt, ...) {
register uintptr_t lr __asm("lr");
uintptr_t saved_lr = lr;
#ifdef MALLOC_INSTRUMENTATION
command_dump_malloc_kernel();
#endif
va_list fmt_args;
va_start(fmt_args, fmt);
handle_passert_failed_vargs(filename, line_number, saved_lr, "CROAK OOM", fmt, fmt_args);
va_end(fmt_args);
}

View file

@ -0,0 +1,53 @@
/*
* 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 "logging.h"
void passert_failed(const char* filename, int line_number, const char* message, ...)
__attribute__((noreturn));
#define PBL_ASSERT(expr, ...) \
do { \
if (!(expr)) { \
passert_failed(__FILE_NAME__, __LINE__, __VA_ARGS__); \
} \
} while (0)
#define PBL_ASSERTN(expr) \
do { \
if (!(expr)) { \
passert_failed_no_message(__FILE_NAME__, __LINE__); \
} \
} while (0)
void passert_failed_no_message(const char* filename, int line_number)
__attribute__((noreturn));
void wtf(void) __attribute__((noreturn));
#define WTF wtf()
// Insert a compiled-in breakpoint
#define BREAKPOINT __asm("bkpt")
#define PBL_ASSERT_PRIVILEGED()
#define PBL_ASSERT_TASK(task)
#define PBL_CROAK(fmt, args...) \
passert_failed(__FILE_NAME__, __LINE__, "*** CROAK: " fmt, ## args)

View file

@ -0,0 +1,36 @@
/*
* 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 "system/reset.h"
#include "drivers/display.h"
#include "stm32f4xx.h"
void system_reset(void) {
display_prepare_for_reset();
// Clear the reset reason since it will no longer
// apply after this bootloader reset
RCC_ClearFlag();
system_hard_reset();
}
void system_hard_reset(void) {
NVIC_SystemReset();
__builtin_unreachable();
}

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 <stdbool.h>
//! Reset nicely after shutting down system services. Does not set the reboot_reason other than
//! calling reboot_reason_set_restarted_safely just before the reset occurs.
void system_reset(void)__attribute__((noreturn));
//! The final stage in the reset process.
void system_hard_reset(void) __attribute__((noreturn));

View file

@ -0,0 +1,31 @@
/*
* 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
#define RTC_BKP_BOOTBIT_DR RTC_BKP_DR0
#define STUCK_BUTTON_REGISTER RTC_BKP_DR1
#define BOOTLOADER_VERSION_REGISTER RTC_BKP_DR2
#define CURRENT_TIME_REGISTER RTC_BKP_DR3
#define CURRENT_INTERVAL_TICKS_REGISTER RTC_BKP_DR4
#define REBOOT_REASON_REGISTER_1 RTC_BKP_DR5
#define REBOOT_REASON_REGISTER_2 RTC_BKP_DR6
#define REBOOT_REASON_STUCK_TASK_PC RTC_BKP_DR7
#define REBOOT_REASON_STUCK_TASK_LR RTC_BKP_DR8
#define REBOOT_REASON_STUCK_TASK_CALLBACK RTC_BKP_DR9
#define REBOOT_REASON_MUTEX_LR RTC_BKP_DR10 // Now REBOOT_REASON_DROPPED_EVENT
#define REBOOT_REASON_MUTEX_PC RTC_BKP_DR11 // Deprecated
#define SLOT_OF_LAST_LAUNCHED_APP RTC_BKP_DR19

View file

@ -0,0 +1,50 @@
/*
* 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
#if defined(__clang__)
#define GCC_ONLY(x)
#else
#define GCC_ONLY(x) x
#endif
// Function attributes
#define FORMAT_FUNC(TYPE, STR_IDX, FIRST) __attribute__((__format__(TYPE, STR_IDX, FIRST)))
#define FORMAT_PRINTF(STR_IDX, FIRST) FORMAT_FUNC(__printf__, STR_IDX, FIRST)
#define ALWAYS_INLINE __attribute__((__always_inline__)) inline
#define NOINLINE __attribute__((__noinline__))
#define NORETURN __attribute__((__noreturn__)) void
#define NAKED_FUNC __attribute__((__naked__))
#define OPTIMIZE_FUNC(LVL) __attribute__((__optimize__(LVL)))
#define CONST_FUNC __attribute__((__const__))
#define PURE_FUNC __attribute__((__pure__))
// Variable attributes
#define ATTR_CLEANUP(FUNC) __attribute__((__cleanup__(FUNC)))
// Structure attributes
#define PACKED __attribute__((__packed__))
// General attributes
#define USED __attribute__((__used__))
#define UNUSED __attribute__((__unused__))
#define WEAK __attribute__((__weak__))
#define ALIAS(sym) __attribute__((__weak__, __alias__(sym)))
#define SECTION(SEC) GCC_ONLY(__attribute__((__section__(SEC))))
#define EXTERNALLY_VISIBLE GCC_ONLY(__attribute__((__externally_visible__)))

View file

@ -0,0 +1,51 @@
/*
* 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 "cobs.h"
size_t cobs_encode(void *dst_ptr, const void *src_ptr, size_t length) {
const char *src = src_ptr;
char *dst = dst_ptr;
uint8_t code = 0x01;
size_t code_idx = 0;
size_t dst_idx = 1;
for (size_t src_idx = 0; src_idx < length; ++src_idx) {
if (src[src_idx] == '\0') {
dst[code_idx] = code;
code_idx = dst_idx++;
code = 0x01;
} else {
dst[dst_idx++] = src[src_idx];
code++;
if (code == 0xff) {
if (src_idx == length - 1) {
// Special case: the final encoded block is 254 bytes long with no
// zero after it. While it's technically a valid encoding if a
// trailing zero is appended, it causes the output to be one byte
// longer than it needs to be. This violates consistent overhead
// contract and could overflow a carefully sized buffer.
break;
}
dst[code_idx] = code;
code_idx = dst_idx++;
code = 0x01;
}
}
}
dst[code_idx] = code;
return dst_idx;
}

View file

@ -0,0 +1,40 @@
/*
* 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 <stddef.h>
#include <stdint.h>
//! An implementation of Consistent Overhead Byte Stuffing
//!
//! http://conferences.sigcomm.org/sigcomm/1997/papers/p062.pdf
//! http://en.wikipedia.org/wiki/Consistent_Overhead_Byte_Stuffing
//! Evaluates to the offset required when encoding in-place
#define COBS_OVERHEAD(n) (((n) + 253) / 254)
//! Evaluates to the maximum buffer size required to hold n bytes of data
//! after COBS encoding.
#define MAX_SIZE_AFTER_COBS_ENCODING(n) ((n) + COBS_OVERHEAD(n))
//! COBS-encode a buffer out to another buffer.
//!
//! @param [out] dst destination buffer. The buffer must be at least
//! MAX_SIZE_AFTER_COBS_ENCODING(length) bytes long.
//! @param [in] src source buffer
//! @param length length of src
size_t cobs_encode(void * restrict dst, const void * restrict src,
size_t length);

View file

@ -0,0 +1,48 @@
/*
* 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 "util/crc32.h"
// Nybble-wide table driven CRC-32 algorithm
//
// A compromise between speed and size, this algorithm uses a lookup table to
// calculate the CRC four bits at a time with a size cost of only 64 bytes. By
// contrast, a byte-wide algorithm requires a lookup table sixteen times larger!
//
// The lookup table is generated by the crc32_lut.py
static const uint32_t s_lookup_table[] = {
0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac,
0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c,
0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c,
0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c,
};
uint32_t crc32(uint32_t crc, const void * restrict data, size_t length) {
if (data == 0) {
return 0;
}
const uint8_t * restrict bytes = data;
crc ^= 0xffffffff;
while (length--) {
crc = (crc >> 4) ^ s_lookup_table[(crc ^ *bytes) & 0xf];
crc = (crc >> 4) ^ s_lookup_table[(crc ^ (*bytes >> 4)) & 0xf];
bytes++;
}
crc ^= 0xffffffff;
return crc;
}

View file

@ -0,0 +1,67 @@
/*
* 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
//! \file
//! Calculate the CRC-32 checksum of data.
//!
//! The checksum is the standard CRC-32 used by zlib, PNG and others.
//! The model parameters for the algorithm, as described in A Painless Guide to
//! CRC Error Detection Algorithms (http://www.zlib.net/crc_v3.txt), are:
//! Name: "CRC-32"
//! Width: 32
//! Poly: 04C11DB7
//! Init: FFFFFFFF
//! RefIn: True
//! RefOut: True
//! XorOut: FFFFFFFF
//! Check: CBF43926
#include <stdint.h>
#include <string.h>
//! Update a running CRC-32 checksum with the bytes of data and return the
//! updated CRC-32. If data is NULL, the function returns the required initial
//! value for the CRC.
//!
//! This function is drop-in compatible with zlib's crc32 function.
//!
//! \par Usage
//! \code
//! uint32_t crc = crc32(0, NULL, 0);
//! while (read_buffer(data, length)) {
//! crc = crc32(crc, data, length);
//! }
//! \endcode
uint32_t crc32(uint32_t crc, const void * restrict data, size_t length);
//! The initial CRC register value for a standard CRC-32 checksum.
//!
//! It is the same value as is returned by the `crc32` function when data is
//! NULL.
//!
//! \code
//! assert(CRC32_INIT == crc32(0, NULL, 0));
//! \endcode
#define CRC32_INIT (0)
//! The residue constant of the CRC-32 algorithm.
//!
//! If the CRC-32 value of a message is appended (little-endian) onto the
//! end of the message, the CRC-32 of the concatenated message and CRC will be
//! equal to CRC32_RESIDUE if the message has not been corrupted in transit.
#define CRC32_RESIDUE (0x2144DF1C)

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.
*/
#include "delay.h"
#include "stm32f4xx.h"
#include "util/attributes.h"
#include <inttypes.h>
// Calculated using the formula from the firmware's delay_init:
// ceil( NS_PER_US / ( clock_period * INSTRUCTIONS_PER_LOOP )
// where NS_PER_US = 1000 and INSTRUCTIONS_PER_LOOP = 3,
// and clock_period = 62.5
#define LOOPS_PER_US (6)
void NOINLINE delay_us(uint32_t us) {
uint32_t delay_loops = us * LOOPS_PER_US;
__asm volatile (
"spinloop: \n"
" subs %[delay_loops], #1 \n"
" bne spinloop \n"
: [delay_loops] "+r" (delay_loops) // read-write operand
:
: "cc"
);
}
void delay_ms(uint32_t millis) {
// delay_us(millis*1000) is not used because a long delay could easily
// overflow the veriable. Without the outer loop, a delay of even five
// seconds would overflow.
while (millis--) {
delay_us(1000);
}
}

View file

@ -0,0 +1,28 @@
/*
* 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 <inttypes.h>
//! Carefully timed spinloop that allows one to delay at a microsecond
//! granularity.
void delay_us(uint32_t us);
//! Waits for a certain amount of milliseconds by busy-waiting.
//!
//! @param millis The number of milliseconds to wait for
void delay_ms(uint32_t millis);

View file

@ -0,0 +1,46 @@
/*
* 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 "misc.h"
#include "drivers/dbgserial.h"
#include <stdint.h>
void itoa_hex(uint32_t num, char *buffer, int buffer_length) {
if (buffer_length < 11) {
dbgserial_putstr("itoa buffer too small");
return;
}
*buffer++ = '0';
*buffer++ = 'x';
for (int i = 7; i >= 0; --i) {
uint32_t digit = (num & (0xf << (i * 4))) >> (i * 4);
char c;
if (digit < 0xa) {
c = '0' + digit;
} else if (digit < 0x10) {
c = 'a' + (digit - 0xa);
} else {
c = ' ';
}
*buffer++ = c;
}
*buffer = '\0';
}

View file

@ -0,0 +1,34 @@
/*
* 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 <string.h>
#define MIN(a, b) (((a) < (b)) ? (a) : (b))
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
#define MHZ_TO_HZ(hz) (((uint32_t)(hz)) * 1000000)
#define ARRAY_LENGTH(array) (sizeof((array))/sizeof((array)[0]))
//! Find the log base two of a number rounded up
int ceil_log_two(uint32_t n);
//! Convert num to a hex string and put in buffer
void itoa_hex(uint32_t num, char *buffer, int buffer_length);

View file

@ -0,0 +1,80 @@
/*
* 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>
// When compiling test, the host OS might have conflicting defines for this:
#undef ntohs
#undef htons
#undef ntohl
#undef htonl
#undef ltohs
#undef ltohl
static inline uint16_t ntohs(uint16_t v) {
// return ((v & 0x00ff) << 8) | ((v & 0xff00) >> 8);
return __builtin_bswap16(v);
}
static inline uint16_t htons(uint16_t v) {
return ntohs(v);
}
static inline uint32_t ntohl(uint32_t v) {
// return ((v & 0x000000ff) << 24) |
// ((v & 0x0000ff00) << 8) |
// ((v & 0x00ff0000) >> 8) |
// ((v & 0xff000000) >> 24);
return __builtin_bswap32(v);
}
static inline uint32_t htonl(uint32_t v) {
return ntohl(v);
}
#define ltohs(v) (v)
#define ltohl(v) (v)
// Types for values in network byte-order. They are wrapped in structs so that
// the compiler will disallow implicit casting of these types to or from
// integral types. This way it is a compile error to try using variables of
// these types without first performing a byte-order conversion.
// There is no overhead for wrapping the values in structs.
typedef struct net16 {
uint16_t v;
} net16;
typedef struct net32 {
uint32_t v;
} net32;
static inline uint16_t ntoh16(net16 net) {
return ntohs(net.v);
}
static inline net16 hton16(uint16_t v) {
return (net16){ htons(v) };
}
static inline uint32_t ntoh32(net32 net) {
return ntohl(net.v);
}
static inline net32 hton32(uint32_t v) {
return (net32){ htonl(v) };
}