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

50
platform/snowy/boot/.gitignore vendored Normal file
View file

@ -0,0 +1,50 @@
build
xcode
.ycm*
.*project
*.*project
*.*workspace
.settings
*.bin
*.bin.orig
*.map
build/*.elf
*.o
cscope.*
TAGS
*.swp
*.swo
*.swn
.waf*
.lock*
*.pyc
*.gch
qemu_serial.txt
uart*.log
serial_dump.txt
a.out
tools/font/ttf
roundrect.h
log.txt
openocd.log
.DS_Store
tools/bitmaps/*.h
tools/bitmaps/*.pbi
dist
compile_commands.json
*.auto
loghash_dict.pickle
analyze_mcu_flash_usage_treemap.jsonp
**/.idea
**/CMakeLists.txt

View file

@ -0,0 +1,47 @@
#!/bin/bash
# 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.
set -o errexit -o xtrace
cd "${0%/*}"
reloutdir="../../../bin/boot"
OUTDIR=`cd "${reloutdir}"; pwd`
BOARDS=(snowy_bb snowy_dvt snowy_evt2)
# Use commit timestamp, same as the one compiled into the bootloader binary
VERSION=`git log -1 --format=%ct HEAD`
# Clear out old versions of the bootloader binaries
for board in ${BOARDS[*]}; do
git rm ${OUTDIR}/{nowatchdog_,}boot_${board}@*.{hex,elf} || true
done
# Build all bootloader variants and copy them into OUTDIR
build_and_copy () {
local variant="$1";
shift;
for board in ${BOARDS[*]}; do
pypy ./waf configure --board=${board} $@ build --progress
for ext in hex elf; do
mv build/snowy_boot.${ext} \
"${OUTDIR}/${variant}boot_${board}@${VERSION}.${ext}"
done
done
}
build_and_copy ""
build_and_copy nowatchdog_ --nowatchdog

16
platform/snowy/boot/desym.sh Executable file
View file

@ -0,0 +1,16 @@
#!/bin/sh
# 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.
arm-none-eabi-addr2line --exe=build/snowy_boot.elf $1

24
platform/snowy/boot/flash Executable file
View file

@ -0,0 +1,24 @@
#!/bin/bash
set -o errexit
SCRIPTDIR="${0%/*}"
IMGFILE="${SCRIPTDIR}/build/snowy_boot.hex"
if [ ! -f "${IMGFILE}" ]; then
echo "Cannot find bootloader binary at '${IMGFILE}'."
echo "Try running './waf build'"
exit 1
fi
OPENOCD_SCRIPT="
init
reset halt
flash write_image erase \"${IMGFILE}\"
reset
shutdown
"
openocd -f openocd.cfg -c "${OPENOCD_SCRIPT}"
# vim:filetype=sh

11
platform/snowy/boot/gdb Executable file
View file

@ -0,0 +1,11 @@
#!/bin/bash
TARGET="target extended-remote | openocd -f openocd.cfg \
-c \"gdb_port pipe; log_output openocd.log; init; reset halt;\""
arm-none-eabi-gdb build/snowy_boot.elf \
-ex "${TARGET}" \
-ex "break boot_main" \
-ex "continue"
# vim:filetype=sh

View file

@ -0,0 +1,11 @@
interface ftdi
#ftdi_device_desc "Dual RS232-HS"
ftdi_vid_pid 0x0403 0x6010 0x0403 0x6011
# output value, direction (1 for output, 0 for input)
ftdi_layout_init 0x1848 0x185b
ftdi_layout_signal nTRST -data 0x0010 -oe 0x0010
ftdi_layout_signal nSRST -data 0x0040 -oe 0x0040
# Red + Green LED (inverted output: low is on)
ftdi_layout_signal LED -ndata 0x1800 -oe 0x1800

View file

@ -0,0 +1,51 @@
#!/bin/bash
# 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.
set -o errexit
cd "${0%/*}"
CLAR_DIR=`cd ../../../tools/clar; pwd`
clar () {
local test_name=${1:?}; shift;
local test_suite=${1:?}; shift;
local test_dir="build/test/${test_name}"
mkdir -p "${test_dir}"
python "${CLAR_DIR}/clar.py" \
--file="${test_suite}" \
--clar-path="${CLAR_DIR}" \
"${test_dir}"
gcc -o "${test_dir}/do_test" \
-I"${test_dir}" -Isrc \
-Ivendor/STM32F4xx_DSP_StdPeriph_Lib_V1.3.0/Libraries/CMSIS/Include \
-Ivendor/STM32F4xx_DSP_StdPeriph_Lib_V1.3.0/Libraries/CMSIS/Device/ST/STM32F4xx/Include \
-Ivendor/STM32F4xx_DSP_StdPeriph_Lib_V1.3.0/Libraries/STM32F4xx_StdPeriph_Driver/inc \
-DMICRO_FAMILY_STM32F4 -DSTM32F429_439xx \
-ffunction-sections \
-Wl,-dead_strip \
"${test_dir}/clar_main.c" "${test_suite}" $@
# If running on a platform with GNU ld,
# replace -Wl,-dead_strip with -Wl,--gc-sections
echo "Running test ${test_suite}..."
"${test_dir}/do_test"
}
clar system_flash_algo test/test_system_flash.c \
src/drivers/stm32_common/system_flash.c

View file

@ -0,0 +1,42 @@
#!/bin/bash
# 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.
set -o errexit -o xtrace
cd "${0%/*}"
reloutdir="../../../bin/boot"
OUTDIR=`cd "${reloutdir}"; pwd`
# Use commit timestamp, same as the one compiled into the bootloader binary
VERSION=`git log -1 --format=%ct HEAD`
# Clear out old versions of the bootloader binaries
git rm ${OUTDIR}/{nowatchdog_,}boot_spalding@*.{hex,elf} || true
# Build all bootloader variants and copy them into OUTDIR
build_and_copy () {
local variant="$1";
shift;
pypy ./waf configure --board=spalding $@ build --progress
for ext in hex elf; do
mv build/snowy_boot.${ext} \
"${OUTDIR}/${variant}boot_spalding@${VERSION}.${ext}"
done
}
build_and_copy ""
build_and_copy nowatchdog_ --nowatchdog

View file

@ -0,0 +1,270 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "drivers/button_id.h"
#if defined(MICRO_FAMILY_STM32F2)
#include "stm32f2xx_gpio.h"
#elif defined(MICRO_FAMILY_STM32F4)
#include "stm32f4xx_gpio.h"
#endif
#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.
} InputConfig;
typedef struct {
GPIO_TypeDef* const gpio; ///< One of GPIOX. For example, GPIOA.
const uint16_t gpio_pin; ///< One of GPIO_Pin_*
const uint8_t adc_channel; ///< One of ADC_Channel_*
} ADCInputConfig;
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 {
int i2c_address;
int axes_offsets[3];
bool axes_inverts[3];
} AccelConfig;
typedef struct {
int i2c_address;
int axes_offsets[3];
bool axes_inverts[3];
} MagConfig;
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_LIS3DH = 0,
I2C_DEVICE_MAG3110,
I2C_DEVICE_MFI,
I2C_DEVICE_LED_CONTROLLER,
I2C_DEVICE_MAX14690,
} I2cDevice;
typedef struct {
AfConfig i2s_ck;
AfConfig i2s_sd;
DMA_Stream_TypeDef *dma_stream;
uint32_t dma_channel;
uint32_t dma_channel_irq;
uint32_t dma_clock_ctrl;
SPI_TypeDef *spi;
uint32_t spi_clock_ctrl;
//! Pin we use to control power to the microphone. Only used on certain boards.
OutputConfig mic_gpio_power;
} MicConfig;
typedef enum {
OptionNotPresent = 0, // FIXME
OptionActiveLowOpenDrain,
OptionActiveHigh
} PowerCtl5VOptions;
typedef enum {
BacklightPinNoPwm = 0,
BacklightPinPwm,
BacklightIssiI2C
} BacklightOptions;
typedef enum {
VibePinNoPwm = 0,
VibePinPwm,
} VibeOptions;
typedef struct {
TIM_TypeDef* const peripheral; ///< A TIMx peripheral
const uint32_t config_clock; ///< One of RCC_APB1Periph_TIMx. For example, RCC_APB1Periph_TIM3.
void (* const init)(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct); ///< One of TIM_OCxInit
void (* const preload)(TIM_TypeDef* TIMx, uint16_t TIM_OCPreload); ///< One of TIM_OCxPreloadConfig
} TimerConfig;
typedef enum {
CC2564A = 0,
CC2564B,
} BluetoothController;
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;
// Audio Configuration
/////////////////////////////////////////////////////////////////////////////
const bool has_mic;
const MicConfig mic_config;
// Ambient Light Configuration
/////////////////////////////////////////////////////////////////////////////
const bool has_ambient_light_sensor;
const uint32_t ambient_light_dark_threshold;
const OutputConfig photo_en;
const ADCInputConfig light_level;
// Debug Serial Configuration
/////////////////////////////////////////////////////////////////////////////
const ExtiConfig dbgserial_int;
// Accessory Configuration
/////////////////////////////////////////////////////////////////////////////
//! Enable power supply to the accessory connector.
const OutputConfig accessory_power_en;
const AfConfig accessory_rxtx_afcfg;
USART_TypeDef* const accessory_uart;
const ExtiConfig accessory_exti;
// Bluetooth Configuration
/////////////////////////////////////////////////////////////////////////////
const BluetoothController bt_controller;
const OutputConfig bt_shutdown;
const OutputConfig bt_cts_int;
const ExtiConfig bt_cts_exti;
const OutputConfig mfi_reset_pin;
// Display Configuration
/////////////////////////////////////////////////////////////////////////////
const OutputConfig lcd_com; //!< This needs to be pulsed regularly to keep the sharp display fresh.
const ExtiConfig cdone_int;
const ExtiConfig intn_int;
//! Controls power to the sharp display
const PowerCtl5VOptions power_5v0_options;
const OutputConfig power_ctl_5v0;
const BacklightOptions backlight_options;
const OutputConfig backlight_ctl;
const TimerConfig backlight_timer;
const AfConfig backlight_afcfg;
} BoardConfig;
// Button Configuration
/////////////////////////////////////////////////////////////////////////////
typedef struct {
const ButtonConfig buttons[NUM_BUTTONS];
const ButtonComConfig button_com;
} BoardConfigButton;
// Power Configuration
/////////////////////////////////////////////////////////////////////////////
typedef struct {
const ExtiConfig pmic_int;
//! Analog voltage of the battery read through an ADC.
const ADCInputConfig battery_vmon;
//! Tells us if the USB cable plugged in.
const InputConfig vusb_stat;
const ExtiConfig vusb_exti;
//! Tells us whether the charger thinks we're charging or not.
const InputConfig chg_stat;
//! Tell the charger to use 2x current to charge faster (MFG only).
const OutputConfig chg_fast;
//! Enable the charger. We may want to disable this in MFG, normally it's always on.
const OutputConfig chg_en;
//! Interrupt that fires when the USB cable is plugged in
const bool has_vusb_interrupt;
const bool wake_on_usb_power;
const int charging_status_led_voltage_compensation;
//! Percentage for watch only mode
const uint8_t low_power_threshold;
} BoardConfigPower;
typedef struct {
const AccelConfig accel_config;
const ExtiConfig accel_ints[2];
} BoardConfigAccel;
typedef struct {
const MagConfig mag_config;
const ExtiConfig mag_int;
} BoardConfigMag;
typedef struct {
const VibeOptions vibe_options;
const OutputConfig vibe_ctl;
const OutputConfig vibe_pwm;
const TimerConfig vibe_timer;
const AfConfig vibe_afcfg;
} BoardConfigVibe;
#include "board_definitions.h"

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
#if BOARD_SNOWY_BB
#include "board_snowy_bb.h" // prototypes for Snowy bigboard
#elif BOARD_SNOWY_EVT
#include "board_snowy_evt.h" // prototypes for Snowy EVT
#elif BOARD_SNOWY_EVT2
#include "board_snowy_evt2.h" // prototypes for Snowy EVT2
#elif BOARD_SPALDING
#include "board_snowy_evt2.h" // Close enough
#else
#error "Unknown board definition"
#endif

View file

@ -0,0 +1,160 @@
/*
* 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 USE_PARALLEL_FLASH
#define HAS_ACCESSORY_CONNECTOR
#define BOARD_HAS_PMIC
#define BOARD_I2C_BUS_COUNT (ARRAY_LENGTH(SNOWY_BB_I2C_BUS_CONFIGS))
extern void snowy_i2c_rail_1_ctl_fn(bool enable);
static const I2cBusConfig SNOWY_BB_I2C_BUS_CONFIGS[] = {
// labelled 1V8_I2C on the schematic
[0] = {
.i2c = I2C1,
.i2c_scl = { GPIOB, GPIO_Pin_6, GPIO_PinSource6, GPIO_AF_I2C1 },
.i2c_sda = { GPIOB, GPIO_Pin_9, GPIO_PinSource9, GPIO_AF_I2C1 },
.clock_speed = 400000,
.duty_cycle = I2C_DutyCycle_16_9,
.clock_ctrl = RCC_APB1Periph_I2C1,
.ev_irq_channel = I2C1_EV_IRQn,
.er_irq_channel = I2C1_ER_IRQn,
},
// labelled 2V5_I2C on the schematic
[1] = {
.i2c = I2C2,
.i2c_scl = { GPIOF, GPIO_Pin_1, GPIO_PinSource1, GPIO_AF_I2C2 },
.i2c_sda = { GPIOF, GPIO_Pin_0, GPIO_PinSource0, GPIO_AF_I2C2 },
.clock_speed = 400000,
.duty_cycle = I2C_DutyCycle_2,
.clock_ctrl = RCC_APB1Periph_I2C2,
.ev_irq_channel = I2C2_EV_IRQn,
.er_irq_channel = I2C2_ER_IRQn,
.rail_ctl_fn = snowy_i2c_rail_1_ctl_fn
}
};
static const uint8_t SNOWY_BB_I2C_DEVICE_MAP[] = {
[I2C_DEVICE_LIS3DH] = 0,
[I2C_DEVICE_MAG3110] = 1,
[I2C_DEVICE_MFI] = 1,
[I2C_DEVICE_MAX14690] = 0
};
static const BoardConfig BOARD_CONFIG = {
.i2c_bus_configs = SNOWY_BB_I2C_BUS_CONFIGS,
.i2c_bus_count = BOARD_I2C_BUS_COUNT,
.i2c_device_map = SNOWY_BB_I2C_DEVICE_MAP,
.i2c_device_count = ARRAY_LENGTH(SNOWY_BB_I2C_DEVICE_MAP),
.has_ambient_light_sensor = true,
.ambient_light_dark_threshold = 3000,
.photo_en = { GPIOA, GPIO_Pin_3, true },
.light_level = { GPIOA, GPIO_Pin_2, ADC_Channel_2 },
.dbgserial_int = { EXTI_PortSourceGPIOC, 12 },
.accessory_power_en = { GPIOF, GPIO_Pin_13, true },
.accessory_rxtx_afcfg = { GPIOE, GPIO_Pin_1, GPIO_PinSource1, GPIO_AF_UART8 },
.accessory_uart = UART8,
.accessory_exti = { EXTI_PortSourceGPIOE, 0 },
.bt_controller = CC2564A,
.bt_shutdown = { GPIOC, GPIO_Pin_8, false},
.bt_cts_int = { GPIOA, GPIO_Pin_11, false},
.bt_cts_exti = { EXTI_PortSourceGPIOA, 11 },
.mfi_reset_pin = { GPIOF, GPIO_Pin_11 },
// Only used with Sharp displays
.lcd_com = { 0 },
.cdone_int = { EXTI_PortSourceGPIOG, 9 },
.intn_int = { EXTI_PortSourceGPIOG, 10 },
.power_5v0_options = OptionNotPresent,
.power_ctl_5v0 = { 0 },
.backlight_options = BacklightPinPwm,
.backlight_ctl = { GPIOB, GPIO_Pin_14, true },
.backlight_timer = {
.peripheral = TIM12,
.config_clock = RCC_APB1Periph_TIM12,
.init = TIM_OC1Init,
.preload = TIM_OC1PreloadConfig
},
.backlight_afcfg = { GPIOB, GPIO_Pin_14, GPIO_PinSource14, GPIO_AF_TIM12 },
.has_mic = true,
.mic_config = {
.i2s_ck = { GPIOB, GPIO_Pin_10, GPIO_PinSource10, GPIO_AF_SPI2 },
.i2s_sd = { GPIOB, GPIO_Pin_15, GPIO_PinSource15, GPIO_AF_SPI2 },
.dma_stream = DMA1_Stream3,
.dma_channel = DMA_Channel_0,
.dma_channel_irq = DMA1_Stream3_IRQn,
.dma_clock_ctrl = RCC_AHB1Periph_DMA1,
.spi = SPI2,
.spi_clock_ctrl = RCC_APB1Periph_SPI2
},
};
static const BoardConfigButton BOARD_CONFIG_BUTTON = {
.buttons = {
[BUTTON_ID_BACK] = { "Back", GPIOG, GPIO_Pin_4, { EXTI_PortSourceGPIOG, 4 }, GPIO_PuPd_NOPULL },
[BUTTON_ID_UP] = { "Up", GPIOG, GPIO_Pin_3, { EXTI_PortSourceGPIOG, 3 }, GPIO_PuPd_NOPULL },
[BUTTON_ID_SELECT] = { "Select", GPIOG, GPIO_Pin_1, { EXTI_PortSourceGPIOG, 1 }, GPIO_PuPd_NOPULL },
[BUTTON_ID_DOWN] = { "Down", GPIOG, GPIO_Pin_2, { EXTI_PortSourceGPIOG, 2 }, GPIO_PuPd_NOPULL },
},
.button_com = { 0 },
};
static const BoardConfigPower BOARD_CONFIG_POWER = {
.pmic_int = { EXTI_PortSourceGPIOG, 7 },
.battery_vmon = { GPIOA, GPIO_Pin_1, ADC_Channel_1 },
.vusb_stat = { .gpio = GPIO_Port_NULL, },
.chg_stat = { GPIO_Port_NULL },
.chg_fast = { GPIO_Port_NULL },
.chg_en = { GPIO_Port_NULL },
.has_vusb_interrupt = false,
.wake_on_usb_power = false,
.charging_status_led_voltage_compensation = 0,
.low_power_threshold = 5,
};
static const BoardConfigVibe BOARD_CONFIG_VIBE = {
.vibe_options = VibePinPwm,
.vibe_ctl = { GPIOF, GPIO_Pin_4, true },
.vibe_pwm = { GPIOB, GPIO_Pin_8, true },
.vibe_timer = {
.peripheral = TIM10,
.config_clock = RCC_APB2Periph_TIM10,
.init = TIM_OC1Init,
.preload = TIM_OC1PreloadConfig
},
.vibe_afcfg = { GPIOB, GPIO_Pin_8, GPIO_PinSource8, GPIO_AF_TIM10 },
};

View file

@ -0,0 +1,202 @@
/*
* 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"
#include "drivers/accel.h"
#include "drivers/imu/bmi160/bmi160.h"
#include "stm32f4xx_rcc.h"
#include "stm32f4xx_gpio.h"
#include "stm32f4xx_exti.h"
#include "stm32f4xx_syscfg.h"
#include "stm32f4xx_i2c.h"
#define USE_PARALLEL_FLASH
#define HAS_ACCESSORY_CONNECTOR
#define BOARD_HAS_PMIC
#define BOARD_I2C_BUS_COUNT (ARRAY_LENGTH(SNOWY_EVT_I2C_BUS_CONFIGS))
extern void snowy_i2c_rail_1_ctl_fn(bool enable);
static const I2cBusConfig SNOWY_EVT_I2C_BUS_CONFIGS[] = {
// Listed as I2C_PMIC on the schematic, runs at 1.8V
[0] = {
.i2c = I2C1,
.i2c_scl = { GPIOB, GPIO_Pin_6, GPIO_PinSource6, GPIO_AF_I2C1 },
.i2c_sda = { GPIOB, GPIO_Pin_9, GPIO_PinSource9, GPIO_AF_I2C1 },
.clock_speed = 400000,
.duty_cycle = I2C_DutyCycle_16_9,
.clock_ctrl = RCC_APB1Periph_I2C1,
.ev_irq_channel = I2C1_EV_IRQn,
.er_irq_channel = I2C1_ER_IRQn,
},
// Listed as I2C_MFI on the schematic, runs at 1.8V
[1] = {
.i2c = I2C2,
.i2c_scl = { GPIOF, GPIO_Pin_1, GPIO_PinSource1, GPIO_AF_I2C2 },
.i2c_sda = { GPIOF, GPIO_Pin_0, GPIO_PinSource0, GPIO_AF_I2C2 },
.clock_speed = 400000,
.duty_cycle = I2C_DutyCycle_2,
.clock_ctrl = RCC_APB1Periph_I2C2,
.ev_irq_channel = I2C2_EV_IRQn,
.er_irq_channel = I2C2_ER_IRQn,
.rail_ctl_fn = snowy_i2c_rail_1_ctl_fn
}
};
static const uint8_t SNOWY_EVT_I2C_DEVICE_MAP[] = {
[I2C_DEVICE_MFI] = 1,
[I2C_DEVICE_MAX14690] = 0
};
static const BoardConfig BOARD_CONFIG = {
.i2c_bus_configs = SNOWY_EVT_I2C_BUS_CONFIGS,
.i2c_bus_count = BOARD_I2C_BUS_COUNT,
.i2c_device_map = SNOWY_EVT_I2C_DEVICE_MAP,
.i2c_device_count = ARRAY_LENGTH(SNOWY_EVT_I2C_DEVICE_MAP),
.has_ambient_light_sensor = true,
.ambient_light_dark_threshold = 3000,
.photo_en = { GPIOA, GPIO_Pin_3, true },
.light_level = { GPIOA, GPIO_Pin_2, ADC_Channel_2 },
.dbgserial_int = { EXTI_PortSourceGPIOC, 12 },
.accessory_power_en = { GPIOF, GPIO_Pin_13, true },
.accessory_rxtx_afcfg = { GPIOE, GPIO_Pin_1, GPIO_PinSource1, GPIO_AF_UART8 },
.accessory_uart = UART8,
.accessory_exti = { EXTI_PortSourceGPIOE, 0 },
.bt_controller = CC2564B,
.bt_shutdown = { GPIOC, GPIO_Pin_8, false},
.bt_cts_int = { GPIOA, GPIO_Pin_11, false},
.bt_cts_exti = { EXTI_PortSourceGPIOA, 11 },
.mfi_reset_pin = { GPIOF, GPIO_Pin_11 },
// Only used with Sharp displays
.lcd_com = { 0 },
.cdone_int = { EXTI_PortSourceGPIOG, 9 },
.intn_int = { EXTI_PortSourceGPIOG, 10 },
.power_5v0_options = OptionNotPresent,
.power_ctl_5v0 = { 0 },
.backlight_options = BacklightPinPwm,
.backlight_ctl = { GPIOB, GPIO_Pin_14, true },
.backlight_timer = {
.peripheral = TIM12,
.config_clock = RCC_APB1Periph_TIM12,
.init = TIM_OC1Init,
.preload = TIM_OC1PreloadConfig
},
.backlight_afcfg = { GPIOB, GPIO_Pin_14, GPIO_PinSource14, GPIO_AF_TIM12 },
.has_mic = true,
.mic_config = {
.i2s_ck = { GPIOB, GPIO_Pin_10, GPIO_PinSource10, GPIO_AF_SPI2 },
.i2s_sd = { GPIOB, GPIO_Pin_15, GPIO_PinSource15, GPIO_AF_SPI2 },
.dma_stream = DMA1_Stream3,
.dma_channel = DMA_Channel_0,
.dma_channel_irq = DMA1_Stream3_IRQn,
.dma_clock_ctrl = RCC_AHB1Periph_DMA1,
.spi = SPI2,
.spi_clock_ctrl = RCC_APB1Periph_SPI2,
.mic_gpio_power = { GPIOF, GPIO_Pin_5, true }
},
};
static const BoardConfigButton BOARD_CONFIG_BUTTON = {
.buttons = {
[BUTTON_ID_BACK] = { "Back", GPIOG, GPIO_Pin_1, { EXTI_PortSourceGPIOG, 1 }, GPIO_PuPd_UP },
[BUTTON_ID_UP] = { "Up", GPIOG, GPIO_Pin_2, { EXTI_PortSourceGPIOG, 2 }, GPIO_PuPd_UP },
[BUTTON_ID_SELECT] = { "Select", GPIOG, GPIO_Pin_3, { EXTI_PortSourceGPIOG, 3 }, GPIO_PuPd_UP },
[BUTTON_ID_DOWN] = { "Down", GPIOG, GPIO_Pin_4, { EXTI_PortSourceGPIOG, 4 }, GPIO_PuPd_NOPULL },
},
.button_com = { 0 },
};
static const BoardConfigPower BOARD_CONFIG_POWER = {
.pmic_int = { EXTI_PortSourceGPIOG, 7 },
.battery_vmon = { GPIOA, GPIO_Pin_1, ADC_Channel_1 },
.vusb_stat = { .gpio = GPIO_Port_NULL, },
.chg_stat = { GPIO_Port_NULL },
.chg_fast = { GPIO_Port_NULL },
.chg_en = { GPIO_Port_NULL },
.has_vusb_interrupt = false,
.wake_on_usb_power = false,
.charging_status_led_voltage_compensation = 0,
.low_power_threshold = 5,
};
static const BoardConfigAccel BOARD_CONFIG_ACCEL = {
// FIXME: We no longer use i2c for our accel, this will need work.
.accel_config = {
.i2c_address = 0x32,
.axes_offsets[ACCEL_AXIS_X] = 1,
.axes_offsets[ACCEL_AXIS_Y] = 0,
.axes_offsets[ACCEL_AXIS_Z] = 2,
.axes_inverts[ACCEL_AXIS_X] = true,
.axes_inverts[ACCEL_AXIS_Y] = false,
.axes_inverts[ACCEL_AXIS_Z] = true,
},
.accel_ints = {
[0] = { EXTI_PortSourceGPIOG, 5 },
[1] = { EXTI_PortSourceGPIOG, 6 }
},
};
static const BoardConfigMag BOARD_CONFIG_MAG = {
// FIXME: We need to talk to the compass through the accel now as it's hanging of the accel's i2c bus.
// This will need work.
.mag_config = {
.i2c_address = 0x0e << 1,
// FIXME: ?
.axes_offsets[ACCEL_AXIS_X] = 1,
.axes_offsets[ACCEL_AXIS_Y] = 0,
.axes_offsets[ACCEL_AXIS_Z] = 2,
.axes_inverts[ACCEL_AXIS_X] = false,
.axes_inverts[ACCEL_AXIS_Y] = true,
.axes_inverts[ACCEL_AXIS_Z] = true,
},
.mag_int = { EXTI_PortSourceGPIOF, 14 },
};
static const BoardConfigVibe BOARD_CONFIG_VIBE = {
.vibe_options = VibePinPwm,
.vibe_ctl = { GPIOF, GPIO_Pin_4, true },
.vibe_pwm = { GPIOB, GPIO_Pin_8, true },
.vibe_timer = {
.peripheral = TIM10,
.config_clock = RCC_APB2Periph_TIM10,
.init = TIM_OC1Init,
.preload = TIM_OC1PreloadConfig
},
.vibe_afcfg = { GPIOB, GPIO_Pin_8, GPIO_PinSource8, GPIO_AF_TIM10 },
};

View file

@ -0,0 +1,158 @@
/*
* 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 USE_PARALLEL_FLASH
#define HAS_ACCESSORY_CONNECTOR
#define BOARD_HAS_PMIC
#define BOARD_I2C_BUS_COUNT (ARRAY_LENGTH(SNOWY_EVT2_I2C_BUS_CONFIGS))
extern void snowy_i2c_rail_1_ctl_fn(bool enable);
static const I2cBusConfig SNOWY_EVT2_I2C_BUS_CONFIGS[] = {
// Listed as I2C_PMIC_MAG on the schematic, runs at 1.8V
[0] = {
.i2c = I2C1,
.i2c_scl = { GPIOB, GPIO_Pin_6, GPIO_PinSource6, GPIO_AF_I2C1 },
.i2c_sda = { GPIOB, GPIO_Pin_9, GPIO_PinSource9, GPIO_AF_I2C1 },
.clock_speed = 400000,
.duty_cycle = I2C_DutyCycle_16_9,
.clock_ctrl = RCC_APB1Periph_I2C1,
.ev_irq_channel = I2C1_EV_IRQn,
.er_irq_channel = I2C1_ER_IRQn,
},
// Listed as I2C_MFI on the schematic, runs at 1.8V
[1] = {
.i2c = I2C2,
.i2c_scl = { GPIOF, GPIO_Pin_1, GPIO_PinSource1, GPIO_AF_I2C2 },
.i2c_sda = { GPIOF, GPIO_Pin_0, GPIO_PinSource0, GPIO_AF_I2C2 },
.clock_speed = 400000,
.duty_cycle = I2C_DutyCycle_2,
.clock_ctrl = RCC_APB1Periph_I2C2,
.ev_irq_channel = I2C2_EV_IRQn,
.er_irq_channel = I2C2_ER_IRQn,
.rail_ctl_fn = snowy_i2c_rail_1_ctl_fn
}
};
static const uint8_t SNOWY_EVT2_I2C_DEVICE_MAP[] = {
[I2C_DEVICE_MAG3110] = 0,
[I2C_DEVICE_MFI] = 1,
[I2C_DEVICE_MAX14690] = 0
};
static const BoardConfig BOARD_CONFIG = {
.i2c_bus_configs = SNOWY_EVT2_I2C_BUS_CONFIGS,
.i2c_bus_count = BOARD_I2C_BUS_COUNT,
.i2c_device_map = SNOWY_EVT2_I2C_DEVICE_MAP,
.i2c_device_count = ARRAY_LENGTH(SNOWY_EVT2_I2C_DEVICE_MAP),
.has_ambient_light_sensor = true,
.ambient_light_dark_threshold = 3000,
.photo_en = { GPIOA, GPIO_Pin_3, true },
.light_level = { GPIOA, GPIO_Pin_2, ADC_Channel_2 },
.dbgserial_int = { EXTI_PortSourceGPIOC, 12 },
.accessory_power_en = { GPIOF, GPIO_Pin_13, true },
.accessory_rxtx_afcfg = { GPIOE, GPIO_Pin_1, GPIO_PinSource1, GPIO_AF_UART8 },
.accessory_uart = UART8,
.accessory_exti = { EXTI_PortSourceGPIOE, 0 },
.bt_controller = CC2564B,
.bt_shutdown = { GPIOB, GPIO_Pin_12, false},
.bt_cts_int = { GPIOA, GPIO_Pin_11, false},
.bt_cts_exti = { EXTI_PortSourceGPIOA, 11 },
// Only used with Sharp displays
.lcd_com = { 0 },
.cdone_int = { EXTI_PortSourceGPIOG, 9 },
.intn_int = { EXTI_PortSourceGPIOG, 10 },
.power_5v0_options = OptionNotPresent,
.power_ctl_5v0 = { 0 },
.backlight_options = BacklightPinPwm,
.backlight_ctl = { GPIOB, GPIO_Pin_14, true },
.backlight_timer = {
.peripheral = TIM12,
.config_clock = RCC_APB1Periph_TIM12,
.init = TIM_OC1Init,
.preload = TIM_OC1PreloadConfig
},
.backlight_afcfg = { GPIOB, GPIO_Pin_14, GPIO_PinSource14, GPIO_AF_TIM12 },
.has_mic = true,
.mic_config = {
.i2s_ck = { GPIOB, GPIO_Pin_10, GPIO_PinSource10, GPIO_AF_SPI2 },
.i2s_sd = { GPIOB, GPIO_Pin_15, GPIO_PinSource15, GPIO_AF_SPI2 },
.dma_stream = DMA1_Stream3,
.dma_channel = DMA_Channel_0,
.dma_channel_irq = DMA1_Stream3_IRQn,
.dma_clock_ctrl = RCC_AHB1Periph_DMA1,
.spi = SPI2,
.spi_clock_ctrl = RCC_APB1Periph_SPI2,
.mic_gpio_power = { GPIOF, GPIO_Pin_5, true }
},
};
static const BoardConfigButton BOARD_CONFIG_BUTTON = {
.buttons = {
[BUTTON_ID_BACK] = { "Back", GPIOG, GPIO_Pin_4, { EXTI_PortSourceGPIOG, 4 }, GPIO_PuPd_NOPULL },
[BUTTON_ID_UP] = { "Up", GPIOG, GPIO_Pin_3, { EXTI_PortSourceGPIOG, 3 }, GPIO_PuPd_UP },
[BUTTON_ID_SELECT] = { "Select", GPIOG, GPIO_Pin_1, { EXTI_PortSourceGPIOG, 1 }, GPIO_PuPd_UP },
[BUTTON_ID_DOWN] = { "Down", GPIOG, GPIO_Pin_2, { EXTI_PortSourceGPIOG, 2 }, GPIO_PuPd_UP },
},
.button_com = { 0 },
};
static const BoardConfigPower BOARD_CONFIG_POWER = {
.pmic_int = { EXTI_PortSourceGPIOG, 7 },
.battery_vmon = { GPIOA, GPIO_Pin_1, ADC_Channel_1 },
.vusb_stat = { .gpio = GPIO_Port_NULL, },
.chg_stat = { GPIO_Port_NULL },
.chg_fast = { GPIO_Port_NULL },
.chg_en = { GPIO_Port_NULL },
.has_vusb_interrupt = false,
.wake_on_usb_power = false,
.charging_status_led_voltage_compensation = 0,
.low_power_threshold = 5,
};
static const BoardConfigVibe BOARD_CONFIG_VIBE = {
.vibe_options = VibePinPwm,
.vibe_ctl = { GPIOF, GPIO_Pin_4, true },
.vibe_pwm = { GPIOB, GPIO_Pin_8, true },
.vibe_timer = {
.peripheral = TIM10,
.config_clock = RCC_APB2Periph_TIM10,
.init = TIM_OC1Init,
.preload = TIM_OC1PreloadConfig
},
.vibe_afcfg = { GPIOB, GPIO_Pin_8, GPIO_PinSource8, GPIO_AF_TIM10 },
};

View file

@ -0,0 +1,81 @@
/*
* 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/flash.h"
#include "system/bootbits.h"
#include "system/logging.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.");
char buffer[32];
itoa(button_counter_register, buffer, sizeof(buffer));
dbgserial_putstr(buffer);
RTC_WriteBackupRegister(STUCK_BUTTON_REGISTER, 0);
return false;
}
button_counter[button_id] += 1;
if (button_counter[button_id] >= STUCK_BUTTON_THRESHOLD) {
PBL_LOG(LOG_LEVEL_ERROR, "Button id %u is stuck!", button_id);
result = true;
}
}
if (button_counter_register != 0) {
dbgserial_putstr("Button is pushed at boot");
char buffer[32];
itoa(button_counter_register, buffer, sizeof(buffer));
dbgserial_putstr(buffer);
}
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,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
#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);
void button_interrupt_handler(void* button_id);
bool button_selftest(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,44 @@
/*
* 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>
void crc_init(void);
/*
* calculate the CRC32 for a stream of bytes.
* NOTE: not safe to call from ISR
*/
uint32_t crc_calculate_bytes(const uint8_t* data, unsigned int data_length);
/*
* calculate the CRC32 for a stream of bytes from flash
* NOTE: not safe to call from ISR
*/
uint32_t crc_calculate_flash(uint32_t address, unsigned int num_bytes);
/*
* calculate a 8-bit CRC of a given byte sequence. Note that this is not using
* the standard CRC-8 polynomial, because the standard polynomial isn't very
* good.
*/
uint8_t crc8_calculate_bytes(const uint8_t *data, unsigned int data_length);
void crc_calculate_incremental_start(void);
void crc_calculate_incremental_stop(void);

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.
*/
#pragma once
#include <stdbool.h>
#include <stdint.h>
void dbgserial_init(void);
void dbgserial_putchar(uint8_t c);
//! Version of dbgserial_putchar that may return before the character is finished writing.
//! Use if you don't need a guarantee that your character will be written.
void dbgserial_putchar_lazy(uint8_t c);
void dbgserial_putstr(const char* str);
//! 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,188 @@
/*
* 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 "ice40lp.h"
#include "board/board.h"
#include "drivers/gpio.h"
#include "drivers/spi.h"
#include "drivers/pmic.h"
#include "system/logging.h"
#include "system/passert.h"
#include "util/delay.h"
#include "stm32f4xx.h"
#include "misc.h"
#include <string.h>
// We want the SPI clock to run at 16 by default
const uint32_t SPI_DEFAULT_MHZ = 16;
static uint32_t s_spi_clock_hz;
bool display_busy(void) {
bool busy = GPIO_ReadInputDataBit(DISP_GPIO, DISP_PIN_BUSY);
return busy;
}
static void prv_configure_spi(uint32_t spi_clock_hz) {
// Set up a SPI bus on SPI6
SPI_InitTypeDef spi_cfg;
SPI_I2S_DeInit(DISP_SPI);
SPI_StructInit(&spi_cfg);
spi_cfg.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
spi_cfg.SPI_Mode = SPI_Mode_Master;
spi_cfg.SPI_DataSize = SPI_DataSize_8b;
spi_cfg.SPI_CPOL = SPI_CPOL_High;
spi_cfg.SPI_CPHA = SPI_CPHA_2Edge;
spi_cfg.SPI_NSS = SPI_NSS_Soft;
spi_cfg.SPI_BaudRatePrescaler = spi_find_prescaler(spi_clock_hz, DISPLAY_SPI_CLOCK_PERIPH);
spi_cfg.SPI_FirstBit = SPI_FirstBit_MSB;
SPI_Init(DISP_SPI, &spi_cfg);
SPI_Cmd(DISP_SPI, ENABLE);
}
void display_start(void) {
// Enable the GPIOG clock; this is required before configuring the pins
gpio_use(DISP_GPIO);
GPIO_PinAFConfig(DISP_GPIO, GPIO_PINSOURCE_SCK, GPIO_AF_SPI6); // SCK
GPIO_PinAFConfig(DISP_GPIO, GPIO_PINSOURCE_MOSI, GPIO_AF_SPI6); // MOSI
GPIO_PinAFConfig(DISP_GPIO, GPIO_PINSOURCE_MISO, GPIO_AF_SPI6); // MOSI
GPIO_InitTypeDef gpio_cfg;
gpio_cfg.GPIO_OType = GPIO_OType_PP;
gpio_cfg.GPIO_PuPd = GPIO_PuPd_NOPULL;
gpio_cfg.GPIO_Mode = GPIO_Mode_AF;
gpio_cfg.GPIO_Speed = GPIO_Speed_25MHz;
gpio_cfg.GPIO_Pin = DISP_PIN_SCLK;
GPIO_Init(DISP_GPIO, &gpio_cfg);
gpio_cfg.GPIO_Pin = DISP_PIN_SI;
GPIO_Init(DISP_GPIO, &gpio_cfg);
gpio_cfg.GPIO_Pin = DISP_PIN_SO;
GPIO_Init(DISP_GPIO, &gpio_cfg);
gpio_cfg.GPIO_Mode = GPIO_Mode_IN;
gpio_cfg.GPIO_PuPd = GPIO_PuPd_UP;
gpio_cfg.GPIO_Pin = DISP_PIN_CDONE;
GPIO_Init(DISP_GPIO, &gpio_cfg);
gpio_cfg.GPIO_Mode = GPIO_Mode_IN;
gpio_cfg.GPIO_PuPd = GPIO_PuPd_NOPULL;
gpio_cfg.GPIO_Pin = DISP_PIN_BUSY;
GPIO_Init(DISP_GPIO, &gpio_cfg);
gpio_cfg.GPIO_Mode = GPIO_Mode_OUT;
gpio_cfg.GPIO_PuPd = GPIO_PuPd_NOPULL;
gpio_cfg.GPIO_Pin = DISP_PIN_SCS;
GPIO_Init(DISP_GPIO, &gpio_cfg);
gpio_cfg.GPIO_OType = GPIO_OType_OD;
gpio_cfg.GPIO_PuPd = GPIO_PuPd_NOPULL;
gpio_cfg.GPIO_Pin = DISP_PIN_CRESET;
GPIO_Init(DISP_GPIO, &gpio_cfg);
RCC_APB2PeriphClockCmd(DISPLAY_SPI_CLOCK, ENABLE);
s_spi_clock_hz = MHZ_TO_HZ(SPI_DEFAULT_MHZ);
prv_configure_spi(s_spi_clock_hz);
}
bool display_program(const uint8_t *fpga_bitstream, uint32_t bitstream_size) {
GPIO_WriteBit(DISP_GPIO, DISP_PIN_SCS, Bit_SET);
// wait a bit.
delay_ms(1);
GPIO_WriteBit(DISP_GPIO, DISP_PIN_CRESET, Bit_RESET); // CRESET LOW
GPIO_WriteBit(DISP_GPIO, DISP_PIN_SCS, Bit_RESET); // SCS LOW
delay_ms(1);
GPIO_WriteBit(DISP_GPIO, DISP_PIN_CRESET, Bit_SET); // CRESET -> HIGH
delay_ms(1);
PBL_ASSERT(!GPIO_ReadInputDataBit(DISP_GPIO, DISP_PIN_CDONE), "CDONE not low during reset");
PBL_ASSERT(GPIO_ReadInputDataBit(DISP_GPIO, DISP_PIN_CRESET), "CRESET not high during reset");
// Program the FPGA
for (unsigned int i = 0; i < bitstream_size; ++i) {
display_write_byte(fpga_bitstream[i]);
}
// Set SCS high so that we don't process any of these clocks as commands.
GPIO_WriteBit(DISP_GPIO, DISP_PIN_SCS, Bit_SET); // SCS -> HIGH
// Send dummy clocks
for (unsigned int i = 0; i < 8; ++i) {
display_write_byte(0x00);
}
if (!GPIO_ReadInputDataBit(DISP_GPIO, DISP_PIN_CDONE)) {
PBL_LOG(LOG_LEVEL_WARNING, "CDONE not high after programming!");
return false;
}
return true;
}
void display_power_enable(void) {
// The display requires us to wait 1ms between each power rail coming up. The PMIC
// initialization brings up the 3.2V rail (VLCD on the display, LD02 on the PMIC) for us, but
// we still need to wait before turning on the subsequent rails.
delay_ms(2);
PBL_LOG(LOG_LEVEL_DEBUG, "Enabling 6v6 (Display VDDC)");
set_6V6_power_state(true);
delay_ms(2);
PBL_LOG(LOG_LEVEL_DEBUG, "Enabling 4v5 (Display VDDP)");
set_4V5_power_state(true);
}
void display_power_disable(void) {
PBL_LOG(LOG_LEVEL_DEBUG, "Disabling 4v5 (Display VDDP)");
set_4V5_power_state(false);
delay_ms(2);
PBL_LOG(LOG_LEVEL_DEBUG, "Disabling 6v6 (Display VDDC)");
set_6V6_power_state(false);
delay_ms(2);
}
//!
//! Write a single byte synchronously to the display. Use this
//! sparingly, as it will tie up the micro duing the write.
//!
void display_write_byte(uint8_t d) {
// Block until the tx buffer is empty
while (!SPI_I2S_GetFlagStatus(DISP_SPI, SPI_I2S_FLAG_TXE)) continue;
SPI_I2S_SendData(DISP_SPI, d);
}
uint8_t display_write_and_read_byte(uint8_t d) {
SPI_I2S_ReceiveData(DISP_SPI);
while (!SPI_I2S_GetFlagStatus(DISP_SPI, SPI_I2S_FLAG_TXE)) continue;
SPI_I2S_SendData(DISP_SPI, d);
while (!SPI_I2S_GetFlagStatus(DISP_SPI, SPI_I2S_FLAG_RXNE)) continue;
return SPI_I2S_ReceiveData(DISP_SPI);
}

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.
*/
#pragma once
#include "drivers/gpio.h"
#include <stdbool.h>
// GPIO constants
#define DISP_SPI SPI6
#define DISP_GPIO GPIOG
#define DISPLAY_SPI_CLOCK_PERIPH SpiPeriphClockAPB2
#define DISPLAY_SPI_CLOCK RCC_APB2Periph_SPI6
#define DISP_PIN_SCS GPIO_Pin_8
#define DISP_PIN_CDONE GPIO_Pin_9
#define DISP_PIN_BUSY GPIO_Pin_10
#define DISP_PIN_SO GPIO_Pin_12
#define DISP_PIN_SCLK GPIO_Pin_13
#define DISP_PIN_SI GPIO_Pin_14
#define DISP_PIN_CRESET GPIO_Pin_15
#define GPIO_PINSOURCE_SCK GPIO_PinSource13
#define GPIO_PINSOURCE_MOSI GPIO_PinSource14
#define GPIO_PINSOURCE_MISO GPIO_PinSource12
bool display_busy(void);
void display_start(void);
bool display_program(const uint8_t *fpga_bitstream, uint32_t bitstream_size);
void display_write_byte(uint8_t d);
uint8_t display_write_and_read_byte(uint8_t d);
void display_power_enable(void);
void display_power_disable(void);

View file

@ -0,0 +1,288 @@
/*
* 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/display.h"
#include <stdbool.h>
#include "board/board.h"
#include "drivers/dbgserial.h"
#include "drivers/display/bootloader_fpga_bitstream.auto.h"
#include "drivers/display/ice40lp.h"
#include "drivers/flash/s29vs.h"
#include "drivers/gpio.h"
#include "drivers/periph_config.h"
#include "drivers/pmic.h"
#include "drivers/spi.h"
#include "flash_region.h"
#include "stm32f4xx_gpio.h"
#include "stm32f4xx_rcc.h"
#include "stm32f4xx_spi.h"
#include "system/passert.h"
#include "util/delay.h"
#include "util/misc.h"
#define CMD_NULL (0)
#define CMD_SET_PARAMETER (1)
#define CMD_DISPLAY_OFF (2)
#define CMD_DISPLAY_ON (3)
#define CMD_DRAW_SCENE (4)
#define CMD_RESET_RELEASE (8)
#define CMD_RESET_ASSERT (9)
#define SCENE_BLACK (0)
#define SCENE_SPLASH (1)
#define SCENE_UPDATE (2)
#define SCENE_ERROR (3)
#define UPDATE_PROGRESS_MAX (93)
// The FPGA bitstream stored in NVCM may be missing or defective; a replacement
// bitstream may be stored in the MFG info flash region, prefixed with a
// four-byte header. The header is composed of the bitstream length followed by
// its complement (all bits inverted).
#define FPGA_BITSTREAM_FLASH_ADDR (FMC_BANK_1_BASE_ADDRESS + \
FLASH_REGION_MFG_INFO_BEGIN + 0x10000)
struct __attribute__((packed)) FlashBitstream {
uint16_t len;
uint16_t len_complement;
uint8_t bitstream[0];
};
static bool prv_wait_programmed(void) {
// The datasheet lists the typical NVCM configuration time as 56 ms.
// Something is wrong if it takes more than twice that time.
int timeout = 100 * 10;
while (GPIO_ReadInputDataBit(DISP_GPIO, DISP_PIN_CDONE) == 0) {
if (timeout-- == 0) {
dbgserial_putstr("FPGA CDONE timeout expired!");
return false;
}
delay_us(100);
}
return true;
}
static bool prv_reset_into_nvcm(void) {
// Reset the FPGA and wait for it to program itself via NVCM.
// NVCM configuration is initiated by pulling CRESET high while SCS is high.
GPIO_WriteBit(DISP_GPIO, DISP_PIN_SCS, Bit_SET);
// CRESET needs to be low for at least 200 ns
GPIO_WriteBit(DISP_GPIO, DISP_PIN_CRESET, Bit_RESET);
delay_ms(1);
GPIO_WriteBit(DISP_GPIO, DISP_PIN_CRESET, Bit_SET);
return prv_wait_programmed();
}
static bool prv_reset_fpga(void) {
#ifdef BLANK_FPGA
return display_program(s_fpga_bitstream, sizeof(s_fpga_bitstream));
#endif
const struct FlashBitstream *bitstream = (void *)FPGA_BITSTREAM_FLASH_ADDR;
// Work around GCC bug https://gcc.gnu.org/bugzilla/show_bug.cgi?id=38341
uint16_t len_complement_complement = ~bitstream->len_complement;
if (bitstream->len != 0xffff && bitstream->len == len_complement_complement) {
dbgserial_putstr("Configuring FPGA from bitstream in flash...");
if (display_program(bitstream->bitstream, bitstream->len)) {
return true;
}
} else {
// Fall back to NVCM.
dbgserial_putstr("No FPGA bitstream in flash.");
}
dbgserial_putstr("Falling back to NVCM.");
return prv_reset_into_nvcm();
}
static void prv_start_command(uint8_t cmd) {
GPIO_WriteBit(DISP_GPIO, DISP_PIN_SCS, Bit_RESET);
delay_us(100);
display_write_byte(cmd);
}
static void prv_send_command_arg(uint8_t arg) {
display_write_byte(arg);
}
static void prv_end_command(void) {
while (SPI_I2S_GetFlagStatus(DISP_SPI, SPI_I2S_FLAG_BSY)) continue;
GPIO_WriteBit(DISP_GPIO, DISP_PIN_SCS, Bit_SET);
}
static bool prv_wait_busy(void) {
// The display should come out of busy within 35 milliseconds;
// it is a waste of time to wait more than twice that.
int timeout = 50 * 10;
while (display_busy()) {
if (timeout-- == 0) {
dbgserial_putstr("Display busy-wait timeout expired!");
return false;
}
delay_us(100);
}
return true;
}
static void prv_screen_on(void) {
prv_start_command(CMD_DISPLAY_ON);
prv_end_command();
}
static void prv_screen_off(void) {
prv_start_command(CMD_DISPLAY_OFF);
prv_end_command();
}
void prv_draw_scene(uint8_t scene) {
prv_start_command(CMD_DRAW_SCENE);
prv_send_command_arg(scene);
prv_end_command();
}
void prv_set_parameter(uint32_t param) {
prv_start_command(CMD_SET_PARAMETER);
// Send in little-endian byte order
prv_send_command_arg(param & 0xff);
prv_send_command_arg((param >> 8) & 0xff);
prv_send_command_arg((param >> 16) & 0xff);
prv_send_command_arg((param >> 24) & 0xff);
prv_end_command();
}
static uint8_t prv_read_version(void) {
GPIO_WriteBit(DISP_GPIO, DISP_PIN_SCS, Bit_RESET);
delay_us(100);
uint8_t version_num = display_write_and_read_byte(0);
GPIO_WriteBit(DISP_GPIO, DISP_PIN_SCS, Bit_SET);
return version_num;
}
void display_init(void) {
display_start();
bool program_success = prv_reset_fpga();
if (!program_success) {
dbgserial_putstr("FPGA configuration failed. Is this a bigboard?");
// Don't waste time trying to get the FPGA unstuck if it's not configured.
// It's just going to waste time and frustrate bigboard users.
return;
}
dbgserial_print("FPGA version: ");
dbgserial_print_hex(prv_read_version());
dbgserial_putstr("");
// enable the power rails
display_power_enable();
#ifdef TEST_FPGA_RESET_COMMAND
#define ASSERT_BUSY_IS(state) \
dbgserial_putstr(GPIO_ReadInputDataBit(DISP_GPIO, DISP_PIN_BUSY) == state? \
"Yes" : "No")
// Test out the FPGA soft-reset capability present in release-03 of the FPGA.
dbgserial_putstr("FPGA soft-reset test");
dbgserial_print("Precondition: BUSY asserted during scene draw? ");
prv_draw_scene(SCENE_BLACK);
ASSERT_BUSY_IS(Bit_SET);
dbgserial_print("Is BUSY cleared after the reset command? ");
prv_start_command(CMD_RESET_ASSERT);
prv_end_command();
ASSERT_BUSY_IS(Bit_RESET);
dbgserial_print("Are draw-scene commands ineffectual while in reset? ");
prv_draw_scene(SCENE_BLACK);
ASSERT_BUSY_IS(Bit_RESET);
dbgserial_print("Does releasing reset allow draw-scene commands "
"to function again? ");
prv_start_command(CMD_RESET_RELEASE);
prv_end_command();
prv_draw_scene(SCENE_BLACK);
ASSERT_BUSY_IS(Bit_SET);
dbgserial_print("Does the draw-scene command complete? ");
dbgserial_putstr(prv_wait_busy()? "Yes" : "No");
#endif
// Work around an issue which some boards exhibit where the FPGA ring
// oscillator can start up with higher harmonics, massively overclocking the
// design and causing malfunction. When this occurrs, the draw-scene command
// will not work, asserting BUSY indefinitely but never updating the display.
// Other commands such as display-on and display-off are less affected by the
// overclocking, so the display can be turned on while the FPGA is in this
// state, showing only garbage.
// FPGA malfunction can be detected in software. In an attempt to restore
// proper functioning, the FPGA can be reset and reconfigured in the hopes
// that the ring oscillator will start up and oscillate without any higher
// harmonics. Bootloader release 03 attempts to mitigate this problem by
// delaying oscillator startup until after configuration completes. Time will
// tell whether this actually fixes things.
for (int retries = 0; retries <= 20; ++retries) {
prv_draw_scene(SCENE_SPLASH);
if (prv_wait_busy()) {
prv_screen_on();
dbgserial_print("Display initialized after ");
dbgserial_print_hex(retries);
dbgserial_putstr(" retries.");
return;
}
prv_reset_fpga();
}
// It's taken too many attempts and the FPGA still isn't behaving. Give up on
// showing the splash screen and keep the screen off so that the user doesn't
// see a broken-looking staticky screen on boot.
dbgserial_putstr("Display initialization failed.");
prv_screen_off();
}
void display_boot_splash(void) {
prv_wait_busy();
prv_draw_scene(SCENE_SPLASH);
// Don't turn the screen on until the boot-splash is fully drawn.
prv_wait_busy();
prv_screen_on();
}
void display_firmware_update_progress(
uint32_t numerator, uint32_t denominator) {
static uint8_t last_bar_fill = UINT8_MAX;
// Scale progress to the number of pixels in the progress bar,
// rounding half upwards.
uint8_t bar_fill =
((numerator * UPDATE_PROGRESS_MAX) + ((denominator+1)/2)) / denominator;
// Don't waste time and power redrawing the same screen repeatedly.
if (bar_fill != last_bar_fill) {
last_bar_fill = bar_fill;
prv_set_parameter(bar_fill);
prv_draw_scene(SCENE_UPDATE);
}
}
void display_error_code(uint32_t error_code) {
prv_set_parameter(error_code);
prv_draw_scene(SCENE_ERROR);
}
void display_prepare_for_reset(void) {
prv_screen_off();
}

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,42 @@
/*
* 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 trie if the CFI table can be queried.
bool flash_sanity_check(void);

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 <stdbool.h>
#include <stdint.h>
#include "drivers/flash.h"
#include "drivers/flash/s29vs.h"
#include "drivers/gpio.h"
#include "drivers/periph_config.h"
#include "stm32f4xx_gpio.h"
#include "util/delay.h"
//! @param sector_address The address of the start of the sector to write the command to.
//! @param cmd The command to write.
static void flash_s29vs_issue_command(FlashAddress sector_address, S29VSCommand cmd) {
// The offset in the sector we write the first part of commands to. Note that this is a 16-bit
// word aligned address as opposed to a byte address.
static const uint32_t COMMAND_ADDRESS = 0x555;
((__IO uint16_t*) (FMC_BANK_1_BASE_ADDRESS + sector_address))[COMMAND_ADDRESS] = cmd;
}
static uint16_t flash_s29vs_read_short(FlashAddress addr) {
return *((__IO uint16_t*)(FMC_BANK_1_BASE_ADDRESS + addr));
}
void flash_read_bytes(uint8_t* buffer, uint32_t start_addr, uint32_t buffer_size) {
memcpy(buffer, (void*)(FMC_BANK_1_BASE_ADDRESS + start_addr), buffer_size);
}
static void flash_s29vs_software_reset(void) {
flash_s29vs_issue_command(0, S29VSCommand_SoftwareReset);
}
void flash_init(void) {
gpio_use(GPIOB);
gpio_use(GPIOD);
gpio_use(GPIOE);
// Configure the reset pin (D2)
GPIO_InitTypeDef gpio_init = {
.GPIO_Pin = GPIO_Pin_2,
.GPIO_Mode = GPIO_Mode_OUT,
.GPIO_Speed = GPIO_Speed_100MHz,
.GPIO_OType = GPIO_OType_PP,
.GPIO_PuPd = GPIO_PuPd_NOPULL
};
GPIO_Init(GPIOD, &gpio_init);
GPIO_WriteBit(GPIOD, GPIO_Pin_2, Bit_SET);
// Configure pins relating to the FMC peripheral (30 pins!)
// B7 - FMC AVD - FMC Address Valid aka Latch
// D0-D1, D8-D15, E2-15 - FMC A, AD - FMC Address and Address/Data lines
// D2 - Reset - GPIO Reset line
// D3 - FMC CLK
// D4 - FMC OE - FMC Output Enable
// D5 - FMC WE - FMC Write Enable
// D6 - FMC RDY - FMC Ready line
// D7 - FMC CE - FMC Chip Enable
gpio_init = (GPIO_InitTypeDef) {
.GPIO_Mode = GPIO_Mode_AF,
.GPIO_Speed = GPIO_Speed_100MHz,
.GPIO_OType = GPIO_OType_PP,
.GPIO_PuPd = GPIO_PuPd_NOPULL
};
GPIO_PinAFConfig(GPIOB, GPIO_PinSource7, GPIO_AF_FMC);
gpio_init.GPIO_Pin = GPIO_Pin_7;
GPIO_Init(GPIOB, &gpio_init);
for (uint8_t pin_source = 0; pin_source < 16; ++pin_source) {
if (pin_source == 2) {
continue;
}
GPIO_PinAFConfig(GPIOD, pin_source, GPIO_AF_FMC);
}
gpio_init.GPIO_Pin = GPIO_Pin_All & (~GPIO_Pin_2);
GPIO_Init(GPIOD, &gpio_init);
for (uint8_t pin_source = 2; pin_source < 16; ++pin_source) {
GPIO_PinAFConfig(GPIOE, pin_source, GPIO_AF_FMC);
}
gpio_init.GPIO_Pin = GPIO_Pin_All & (~GPIO_Pin_0) & (~GPIO_Pin_1);
GPIO_Init(GPIOE, &gpio_init);
// We have configured the pins, lets perform a full HW reset to put the chip
// in a good state
GPIO_WriteBit(GPIOD, GPIO_Pin_2, Bit_RESET);
delay_us(10); // only needs to be 50ns according to data sheet
GPIO_WriteBit(GPIOD, GPIO_Pin_2, Bit_SET);
delay_us(30); // need 200ns + 10us before CE can be pulled low
RCC_AHB3PeriphClockCmd(RCC_AHB3Periph_FMC, ENABLE);
// Setup default config for async
// Configure the FMC peripheral itself
FMC_NORSRAMTimingInitTypeDef nor_timing_init = {
// time between address write and address latch (AVD high)
// tAAVDS on datasheet, min 4 ns
//
// AVD low time
// tAVDP on datasheet, min 6 ns
.FMC_AddressSetupTime = 1,
// time between AVD high (address is available) and OE low (memory can write)
// tAVDO on the datasheet, min 4 ns
.FMC_AddressHoldTime = 1,
// time between OE low (memory can write) and valid data being available
// tOE on datasheet, max 15 ns
// 13 cycles is the default configuration in the component's configuration register
// Setup to 3 for async
.FMC_DataSetupTime = 3,
// Time between chip selects
// not on the datasheet, picked a random safe number
.FMC_BusTurnAroundDuration = 1,
.FMC_CLKDivision = 15, // Not used for async NOR
.FMC_DataLatency = 15, // Not used for async NOR
.FMC_AccessMode = FMC_AccessMode_A // Only used for ExtendedMode == FMC_ExtendedMode_Enable, which we don't use
};
FMC_NORSRAMInitTypeDef nor_init = {
.FMC_Bank = FMC_Bank1_NORSRAM1,
.FMC_DataAddressMux = FMC_DataAddressMux_Enable,
.FMC_MemoryType = FMC_MemoryType_NOR,
.FMC_MemoryDataWidth = FMC_NORSRAM_MemoryDataWidth_16b,
.FMC_BurstAccessMode = FMC_BurstAccessMode_Disable,
.FMC_AsynchronousWait = FMC_AsynchronousWait_Disable,
.FMC_WaitSignalPolarity = FMC_WaitSignalPolarity_Low,
.FMC_WrapMode = FMC_WrapMode_Disable,
.FMC_WaitSignalActive = FMC_WaitSignalActive_BeforeWaitState,
.FMC_WriteOperation = FMC_WriteOperation_Enable,
.FMC_WaitSignal = FMC_WaitSignal_Enable,
.FMC_ExtendedMode = FMC_ExtendedMode_Disable,
.FMC_WriteBurst = FMC_WriteBurst_Disable,
.FMC_ContinousClock = FMC_CClock_SyncOnly,
.FMC_ReadWriteTimingStruct = &nor_timing_init
};
FMC_NORSRAMInit(&nor_init);
// Re-enable NOR
FMC_NORSRAMCmd(FMC_Bank1_NORSRAM1, ENABLE);
}
bool flash_sanity_check(void) {
// Check that the first words of the CFI table are 'Q' 'R' 'Y'.
// This will work on any flash memory, regardless of the manufacturer.
flash_s29vs_issue_command(0, S29VSCommand_CFIEntry);
bool ok = (flash_s29vs_read_short(0x20) & 0xff) == 'Q';
ok = ok && (flash_s29vs_read_short(0x22) & 0xff) == 'R';
ok = ok && (flash_s29vs_read_short(0x24) & 0xff) == 'Y';
flash_s29vs_software_reset();
return ok;
}

View file

@ -0,0 +1,63 @@
/*
* 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 "drivers/flash.h"
//! An address in the flash address spac
typedef uint32_t FlashAddress;
//! This is the memory mapped region that's mapped to the parallel flash.
static const uintptr_t FMC_BANK_1_BASE_ADDRESS = 0x60000000;
//! This is the unit that we use for erasing
static const uint32_t SECTOR_SIZE_BYTES = 0x20000; // 128kb
//! This is the unit that we use for writing
static const uint32_t PAGE_SIZE_BYTES = 64;
//! Different commands we can send to the flash
typedef enum S29VSCommand {
S29VSCommand_WriteBufferLoad = 0x25,
S29VSCommand_BufferToFlash = 0x29,
S29VSCommand_ReadStatusRegister = 0x70,
S29VSCommand_ClearStatusRegister = 0x71,
S29VSCommand_EraseSetup = 0x80,
S29VSCommand_DeviceIDEntry = 0x90,
S29VSCommand_CFIEntry = 0x98,
S29VSCommand_ConfigureRegisterEntry = 0xD0,
S29VSCommand_SoftwareReset = 0xF0
} S29VSCommand;
//! Arguments to the S29VSCommand_EraseSetup command
typedef enum S29VSCommandEraseAguments {
S29VSCommandEraseAguments_ChipErase = 0x10,
S29VSCommandEraseAguments_SectorErase = 0x30
} S29VSCommandEraseAguments;
//! The bitset stored in the status register, see flash_s29vs_read_status_register
typedef enum S29VSStatusBit {
S29VSStatusBit_BankStatus = 0x00,
S29VSStatusBit_SectorLockStatus = 0x01,
S29VSStatusBit_ProgramSuspended = 0x02,
// 0x04 is unused
S29VSStatusBit_ProgramStatus = 0x10,
S29VSStatusBit_EraseStatus = 0x20,
S29VSStatusBit_EraseSuspended = 0x40,
S29VSStatusBit_DeviceReady = 0x80,
} S29VSStatusBit;

View file

@ -0,0 +1,57 @@
/*
* 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>
#if defined(MICRO_FAMILY_STM32F2)
#include "stm32f2xx_gpio.h"
#elif defined(MICRO_FAMILY_STM32F4)
#include "stm32f4xx_gpio.h"
#endif
#include "board/board.h"
void gpio_use(GPIO_TypeDef* GPIOx);
void gpio_release(GPIO_TypeDef* GPIOx);
//! Initialize a GPIO as an output.
//!
//! @param pin_config the BOARD_CONFIG pin configuration struct
//! @param otype the output type of the pin (GPIO_OType_PP or GPIO_OType_OD)
//! @param speed the output slew rate
//! @note The slew rate should be set as low as possible for the
//! pin function to minimize ringing and RF interference.
void gpio_output_init(OutputConfig pin_config, GPIOOType_TypeDef otype,
GPIOSpeed_TypeDef speed);
//! Assert or deassert the output pin.
//!
//! Asserting the output drives the pin high if pin_config.active_high
//! is true, and drives it low if pin_config.active_high is false.
void gpio_output_set(OutputConfig pin_config, bool asserted);
//! Configure a GPIO alternate function.
//!
//! @param pin_config the BOARD_CONFIG pin configuration struct
//! @param otype the output type of the pin (GPIO_OType_PP or GPIO_OType_OD)
//! @param speed the output slew rate
//! @param pupd pull-up or pull-down configuration
//! @note The slew rate should be set as low as possible for the
//! pin function to minimize ringing and RF interference.
void gpio_af_init(AfConfig af_config, GPIOOType_TypeDef otype,
GPIOSpeed_TypeDef speed, GPIOPuPd_TypeDef pupd);

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,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
#if defined(MICRO_FAMILY_STM32F2)
#include "stm32f2xx.h"
#elif defined(MICRO_FAMILY_STM32F4)
#include "stm32f4xx.h"
#endif
typedef void (*ClockCmd)(uint32_t periph, FunctionalState state);
static inline void periph_config_init(void) {}
static inline void periph_config_acquire_lock(void) {}
static inline void periph_config_release_lock(void) {}
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,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>
//! Initialize the PMIC driver. Call this once at startup.
bool pmic_init(void);
//! Tell the PMIC to power off the board and enter a standby-like state. All components will
//! have their power removed (except for the RTC so we'll still keep time) and the PMIC itself
//! will monitor the buttons for when to wake up.
bool pmic_power_off(void);
//! Enable the battery monitor portion of the PMIC. Remember to turn this off with
//! pmic_disable_battery_measure when immediate readings aren't required.
bool pmic_enable_battery_measure(void);
//! Disable the battery monitor portion of the PMIC.
bool pmic_disable_battery_measure(void);
//! Enable and disable the charging portion of the PMIC.
bool pmic_set_charger_state(bool enable);
//! @return true if the PMIC thinks we're charging (adding additional charge to the battery).
//! Note that once we hit full charge we'll no longer be charging, which is a different state
//! that pmic_is_usb_connected.
bool pmic_is_charging(void);
//! @return true if a usb-ish charger cable is currently connected.
bool pmic_is_usb_connected(void);
//! Read information about the chip for tracking purposes.
void pmic_read_chip_info(uint8_t *chip_id, uint8_t *chip_revision);
// FIXME: The following functions are unrelated to the PMIC and should be moved to the
// display/accessory connector drivers once we have them.
//! Enables the LDO3 power rail. Used for the MFi/Magnetometer on snowy_bb, MFi on snowy_evt.
void set_ldo3_power_state(bool enabled);
//! Enables the 4.5V power rail. Used for the display on snowy.
void set_4V5_power_state(bool enabled);
//! Enables the 6.6V power rail. Used for the display on snowy.
void set_6V6_power_state(bool enabled);
//! Enables power to the accessory connector.
void set_accessory_power_state(bool enabled);

View file

@ -0,0 +1,394 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* This file should probably go in the stm32f4 folder */
#include "drivers/pmic.h"
#include "board/board.h"
#include "drivers/gpio.h"
#include "drivers/i2c.h"
#include "drivers/exti.h"
#include "drivers/periph_config.h"
#include "drivers/display/ice40lp.h"
#include "system/logging.h"
#include "system/passert.h"
#include "util/delay.h"
#if defined(MICRO_FAMILY_STM32F2)
#include "stm32f2xx_rcc.h"
#include "stm32f2xx_gpio.h"
#include "stm32f2xx_adc.h"
#elif defined(MICRO_FAMILY_STM32F4)
#include "stm32f4xx_rcc.h"
#include "stm32f4xx_gpio.h"
#include "stm32f4xx_adc.h"
#endif
#include <stdint.h>
/* PMIC Bus Information */
#define MAX14690_ADDR 0x50
//! The addresses of the registers that we can read using i2c
typedef enum PmicRegisters {
PmicRegisters_CHIP_ID = 0x00,
PmicRegisters_CHIP_REV = 0x01,
PmicRegisters_STATUSA = 0x02,
PmicRegisters_STATUSB = 0x03,
PmicRegisters_INTA = 0x05,
PmicRegisters_INTB = 0x06,
PmicRegisters_INT_MASK_A = 0x07,
PmicRegisters_INT_MASK_B = 0x08,
PmicRegisters_CHG_CNTL_A = 0x0A,
PmicRegisters_BUCK1_CONFIG = 0x0D,
PmicRegisters_BUCK2_CONFIG = 0x0F,
PmicRegisters_LDO1_CONFIG = 0x12,
PmicRegisters_LDO2_CONFIG = 0x14,
PmicRegisters_LDO3_CONFIG = 0x16,
PmicRegisters_MON_CFG = 0x19,
PmicRegisters_HAND_SHK = 0x1D,
PmicRegisters_PWR_CFG = 0x1F
} PmicRegisters;
//! The different power rails that our PMIC controls
typedef enum PmicRail {
PmicRail_BUCK1, //!< 1.2V
PmicRail_BUCK2, //!< 1.8V
PmicRail_LDO1, //!< 2.0V - Auto - RTC
PmicRail_LDO2, //!< 3.2V - Manual - FPGA
//! snowy_bb: 2.5V - Manual - MFi, Magnetometer
//! snowy_evt: 1.8V - Manual - MFi
PmicRail_LDO3
} PmicRail;
//! Gives configuration information for reading a given rail through the monitor pin.
typedef struct {
const char* name; //!< Name for the rail.
//! What ratio we need to divide by in order to bring it into the range we can sense. We can
//! only read between 0 and 1.8Vs, so we need to use the PMIC hardware to divide it down before
//! sending it to us. Valid values are 1-4.
uint8_t ratio;
//! The binary value we need to put in the register to select the rail.
uint8_t source_config;
} PmicMonConfig;
// Using the Binary constants GCC extension here, supported in GCC and Clang
// https://gcc.gnu.org/onlinedocs/gcc/Binary-constants.html
static const PmicMonConfig MON_CONFIG[] = {
{ "+VBAT", 3, 0b001 }, // 3:1
// We only care about non-battery rails in MFG where we have the command_pmic_rails function.
#ifdef RECOVERY_FW
{ "+VSYS", 4, 0b010 }, // 4:1
{ "+1V2", 1, 0b011 }, // 1:1, BUCK1
{ "+1V8", 2, 0b100 }, // 2:1, BUCK2
{ "+2V0_RTC", 2, 0b101 }, // 2:1, LDO1
{ "+3V2", 2, 0b110 }, // 2:1, LDO2
#ifdef BOARD_SNOWY_BB
{ "+2V5", 2, 0b111 }, // 2:1, LDO3
#else
{ "+1V8_MFI_MIC", 2, 0b111 }, // 2:1, LDO3
#endif // BOARD_SNOWY_BB
#endif // RECOVERY_FW
};
static const int PMIC_MON_CONFIG_VBAT_INDEX = 0;
/* Private Function Definitions */
static bool prv_is_alive(void);
static bool prv_set_pin_config(void);
//! Request that the rail be used or released. Internally refcounted per rail so you don't have
//! to worry about turning this off on another client.
static bool prv_update_rail_state(PmicRail rail, bool enable);
static void prv_mon_config_lock(void) {
}
static void prv_mon_config_unlock(void) {
}
static bool prv_read_register(uint8_t register_address, uint8_t *result) {
return i2c_read_register(I2C_DEVICE_MAX14690, MAX14690_ADDR, register_address, result);
}
static bool prv_write_register(uint8_t register_address, uint8_t value) {
return i2c_write_register(I2C_DEVICE_MAX14690, MAX14690_ADDR, register_address, value);
}
/* Public Functions */
bool pmic_init(void) {
if (!prv_set_pin_config()) {
return false;
}
if (!prv_is_alive()) {
return false;
}
// If not written to whithin 5 seconds of power-on the PMIC will shut down.
//i2c_write_register(I2C_DEVICE_MAX14690, MAX14690_ADDR, PmicRegisters_HAND_SHK, 0x01);
// Power up 3.2V rail
prv_update_rail_state(PmicRail_LDO2, true);
return true;
}
static bool prv_update_rail_state(PmicRail rail, bool enable) {
static int8_t s_ldo2_ref_count = 0;
static int8_t s_ldo3_ref_count = 0;
int8_t *ref_count;
uint8_t rail_control_reg;
if (rail == PmicRail_LDO2) {
rail_control_reg = PmicRegisters_LDO2_CONFIG;
ref_count = &s_ldo2_ref_count;
} else if (rail == PmicRail_LDO3) {
rail_control_reg = PmicRegisters_LDO3_CONFIG;
ref_count = &s_ldo3_ref_count;
} else {
WTF;
}
uint8_t register_value;
bool success = prv_read_register(rail_control_reg, &register_value);
if (!success) {
// Failed to read the current register value
return false;
}
if (enable) {
if (*ref_count) {
(*ref_count)++;
return true;
} else {
// Set the register byte to XXXXX01X to enable the rail, mask and set
register_value = (register_value & ~0x06) | 0x02;
success = prv_write_register(rail_control_reg, register_value);
if (success) {
// We enabled the rail!
*ref_count = 1;
// We need to wait a bit for the rail to stabilize before continuing to use the device.
// It takes 2.6ms for the LDO rails to ramp.
delay_ms(3);
return true;
}
return false;
}
} else {
if (*ref_count <= 1) {
// Set the register byte to XXXXX00X to disable the rail, just mask
register_value = (register_value & ~0x06);
success = prv_write_register(rail_control_reg, register_value);
if (success) {
// We disabled the rail!
*ref_count = 0;
return true;
}
return false;
} else {
(*ref_count)--;
return true;
}
}
}
bool pmic_power_off(void) {
bool ret = prv_write_register(PmicRegisters_PWR_CFG, 0xB2);
if (ret) {
// Goodbye cruel world. The PMIC should be removing our power at any time now.
while(1);
__builtin_unreachable();
}
return false;
}
static bool prv_set_mon_config_register(uint8_t value) {
return prv_write_register(PmicRegisters_MON_CFG, value);
}
static bool prv_set_mon_config(const PmicMonConfig *config) {
const uint8_t ratio_config = 4 - config->ratio; // 4:1 is 0b00, 1:1 is 0b11.
const uint8_t register_value = (ratio_config << 4) | config->source_config;
bool result = prv_set_mon_config_register(register_value);
// Need to wait a short period of time for the reading to settle due to capacitance on the line.
delay_us(200);
return result;
}
bool pmic_enable_battery_measure(void) {
prv_mon_config_lock();
return prv_set_mon_config(&MON_CONFIG[PMIC_MON_CONFIG_VBAT_INDEX]);
// Don't prv_unlock, we don't want anyone else mucking with the mon config until
// pmic_disable_battery_measure is called.
}
bool pmic_disable_battery_measure(void) {
bool result = prv_set_mon_config_register(0);
// Releases the lock that was previously aquired in pmic_enable_battery_measure.
prv_mon_config_unlock();
return result;
}
bool pmic_set_charger_state(bool enable) {
// Defaults to ON
// Default value is 0xF7
const uint8_t register_value = enable ? 0xf7 : 0xf6;
bool result = prv_write_register(PmicRegisters_CHG_CNTL_A, register_value);
return result;
}
bool pmic_is_charging(void) {
uint8_t val;
if (!prv_read_register(PmicRegisters_STATUSA, &val)) {
// NOTE: When running on QEMU, i2c reads return false. For now, just assume a failed
// i2c read means we are charging
return true;
}
uint8_t chgstat = val & 0x07;
// TODO: Confirm that all of these values == our definition of charging
if (chgstat == 0x02 || chgstat == 0x03 || chgstat == 0x04 ||
chgstat == 0x05 || chgstat == 0x06) {
return true;
} else {
return false;
}
}
bool pmic_is_usb_connected(void) {
// TODO: Uncomment when there is i2c support in the bootloader
uint8_t val;
if (!prv_read_register(PmicRegisters_STATUSB, &val)) {
// NOTE: When running on QEMU, i2c reads return false. For now, just assume a failed
// i2c read means we are connected to a USB cable
return true;
}
bool usb_connected = (val >> 3) & 1;
return usb_connected;
}
void pmic_read_chip_info(uint8_t *chip_id, uint8_t *chip_revision) {
prv_read_register(PmicRegisters_CHIP_ID, chip_id);
prv_read_register(PmicRegisters_CHIP_REV, chip_revision);
}
/* Private Function Implementations */
static bool prv_is_alive(void) {
uint8_t val;
prv_read_register(0x00, &val);
if (val == 0x01) {
PBL_LOG(LOG_LEVEL_DEBUG, "Found the max14690");
return true;
} else {
PBL_LOG(LOG_LEVEL_DEBUG,
"Error: read max14690 whomai byte 0x%x, expecting 0x%x", val, 0x01);
return false;
}
}
static bool prv_set_pin_config(void) {
periph_config_acquire_lock();
gpio_use(GPIOB);
GPIO_InitTypeDef gpio_init_struct;
gpio_init_struct.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_9;
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(GPIOB, &gpio_init_struct);
// I2C config
GPIO_PinAFConfig(GPIOB, GPIO_PinSource6, GPIO_AF_I2C1);
GPIO_PinAFConfig(GPIOB, GPIO_PinSource9, GPIO_AF_I2C1);
gpio_release(GPIOB);
// Initialize the GPIOs for the 4V5, 6V6, and accessory rails
gpio_use(GPIOF);
gpio_init_struct.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_13;
gpio_init_struct.GPIO_Mode = GPIO_Mode_OUT;
gpio_init_struct.GPIO_Speed = GPIO_Speed_50MHz;
gpio_init_struct.GPIO_OType = GPIO_OType_PP;
gpio_init_struct.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(GPIOF, &gpio_init_struct);
gpio_release(GPIOF);
periph_config_release_lock();
// FIXME: We should probably turn this on on-demand instead of leaving it on all the time.
i2c_use(I2C_DEVICE_MAX14690);
return true;
}
void set_ldo3_power_state(bool enabled) {
i2c_use(I2C_DEVICE_MAX14690);
prv_update_rail_state(PmicRail_LDO3, enabled);
i2c_release(I2C_DEVICE_MAX14690);
}
void set_4V5_power_state(bool enabled) {
gpio_use(GPIOF);
GPIO_WriteBit(GPIOF, GPIO_Pin_2, enabled?Bit_SET:Bit_RESET);
gpio_release(GPIOF);
}
void set_6V6_power_state(bool enabled) {
gpio_use(GPIOF);
GPIO_WriteBit(GPIOF, GPIO_Pin_3, enabled?Bit_SET:Bit_RESET);
gpio_release(GPIOF);
}
void set_accessory_power_state(bool enabled) {
gpio_use(GPIOF);
GPIO_WriteBit(GPIOF, GPIO_Pin_13, enabled?Bit_SET:Bit_RESET);
gpio_release(GPIOF);
}

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.
*/
#pragma once
#include <stdint.h>
typedef enum {
SpiPeriphClockAPB1,
SpiPeriphClockAPB2
} SpiPeriphClock;
//! @internal
//! Get the nearest SPI prescaler. Updates bus_frequency with the actual frequency
//! @param bus_frequency the desired bus frequency
//! @param periph_clock The peripheral clock that is used.
uint16_t spi_find_prescaler(uint32_t bus_frequency, SpiPeriphClock periph_clock);

View file

@ -0,0 +1,97 @@
/*
* 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_use(BOARD_CONFIG_BUTTON.button_com.gpio);
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);
gpio_release(BOARD_CONFIG_BUTTON.button_com.gpio);
}
static void initialize_button(const ButtonConfig* config) {
// Configure the pin itself
gpio_use(config->gpio);
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);
gpio_release(config->gpio);
}
bool button_is_pressed(ButtonId id) {
const ButtonConfig* button_config = &BOARD_CONFIG_BUTTON.buttons[id];
gpio_use(button_config->gpio);
uint8_t bit = GPIO_ReadInputDataBit(button_config->gpio, button_config->gpio_pin);
gpio_release(button_config->gpio);
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) {
periph_config_acquire_lock();
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);
periph_config_release_lock();
}
bool button_selftest(void) {
return button_get_state_bits() == 0;
}

View file

@ -0,0 +1,160 @@
/*
* 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/crc.h"
#include "drivers/flash.h"
#include "drivers/periph_config.h"
#include "system/passert.h"
#if defined(MICRO_FAMILY_STM32F2)
#include "stm32f2xx_crc.h"
#include "stm32f2xx_rcc.h"
#elif defined(MICRO_FAMILY_STM32F4)
#include "stm32f4xx_crc.h"
#include "stm32f4xx_rcc.h"
#endif
#include <inttypes.h>
static bool s_initialized = false;
static bool s_clock_running = false;
static void enable_crc_clock(void) {
// save the state so that if stop mode interrupts things, we resume cleanly
s_clock_running = true;
periph_config_enable(RCC_AHB1PeriphClockCmd, RCC_AHB1Periph_CRC);
}
static void disable_crc_clock(void) {
// save the state so that if stop mode interrupts things, we resume cleanly
s_clock_running = false;
periph_config_disable(RCC_AHB1PeriphClockCmd, RCC_AHB1Periph_CRC);
}
void crc_init(void) {
if (s_initialized) {
return;
}
s_initialized = true;
}
void crc_calculate_incremental_start(void) {
PBL_ASSERTN(s_initialized);
enable_crc_clock();
CRC_ResetDR();
}
static void crc_calculate_incremental_words(const uint32_t* data, unsigned int data_length) {
PBL_ASSERTN(s_initialized);
CRC_CalcBlockCRC((uint32_t*) data, data_length);
}
static uint32_t crc_calculate_incremental_remaining_bytes(const uint8_t* data, unsigned int data_length) {
PBL_ASSERTN(s_initialized);
uint32_t crc_value;
if (data_length >= 4) {
const unsigned int num_words = data_length / 4;
crc_calculate_incremental_words((uint32_t*) data, num_words);
data += num_words * 4;
data_length -= num_words * 4;
}
if (data_length) {
uint32_t last_word = 0;
for (unsigned int i = 0; i < data_length; ++i) {
last_word = (last_word << 8) | data[i];
}
crc_value = CRC_CalcCRC(last_word);
} else {
crc_value = CRC_GetCRC();
}
return crc_value;
}
void crc_calculate_incremental_stop(void) {
PBL_ASSERTN(s_initialized);
disable_crc_clock();
}
uint32_t crc_calculate_bytes(const uint8_t* data, unsigned int data_length) {
crc_calculate_incremental_start();
// First calculate the CRC of the whole words, since the hardware works 4
// bytes at a time.
uint32_t* data_words = (uint32_t*) data;
const unsigned int num_words = data_length / 4;
crc_calculate_incremental_words(data_words, num_words);
const unsigned int num_remaining_bytes = data_length % 4;
const uint32_t res = crc_calculate_incremental_remaining_bytes(data + (num_words * 4), num_remaining_bytes);
crc_calculate_incremental_stop();
return (res);
}
uint32_t crc_calculate_flash(uint32_t address, unsigned int num_bytes) {
crc_calculate_incremental_start();
const unsigned int chunk_size = 128;
uint8_t buffer[chunk_size];
while (num_bytes > chunk_size) {
flash_read_bytes(buffer, address, chunk_size);
crc_calculate_incremental_words((const uint32_t*) buffer, chunk_size / 4);
num_bytes -= chunk_size;
address += chunk_size;
}
flash_read_bytes(buffer, address, num_bytes);
const uint32_t res = crc_calculate_incremental_remaining_bytes(buffer, num_bytes);
crc_calculate_incremental_stop();
return (res);
}
uint8_t crc8_calculate_bytes(const uint8_t *data, unsigned int data_len) {
// Optimal polynomial chosen based on
// http://users.ece.cmu.edu/~koopman/roses/dsn04/koopman04_crc_poly_embedded.pdf
// Note that this is different than the standard CRC-8 polynomial, because the
// standard CRC-8 polynomial is not particularly good.
// nibble lookup table for (x^8 + x^5 + x^3 + x^2 + x + 1)
static const uint8_t lookup_table[] =
{ 0, 47, 94, 113, 188, 147, 226, 205, 87, 120, 9, 38, 235, 196,
181, 154 };
uint16_t crc = 0;
for (int i = data_len * 2; i > 0; i--) {
uint8_t nibble = data[(i - 1)/ 2];
if (i % 2 == 0) {
nibble >>= 4;
}
int index = nibble ^ (crc >> 4);
crc = lookup_table[index & 0xf] ^ (crc << 4);
}
return crc;
}

View file

@ -0,0 +1,206 @@
/*
* 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 "system/passert.h"
#include "drivers/gpio.h"
#if defined(MICRO_FAMILY_STM32F2)
#include "stm32f2xx_rcc.h"
#include "stm32f2xx_gpio.h"
#include "stm32f2xx_usart.h"
#elif defined(MICRO_FAMILY_STM32F4)
#include "stm32f4xx_rcc.h"
#include "stm32f4xx_gpio.h"
#include "stm32f4xx_usart.h"
#endif
#include "util/attributes.h"
#include "util/cobs.h"
#include "util/crc32.h"
#include "util/net.h"
#include "util/misc.h"
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#define MAX_MESSAGE (256)
#define FRAME_DELIMITER '\x55'
#define PULSE_TRANSPORT_PUSH (0x5021)
#define PULSE_PROTOCOL_LOGGING (0x0003)
static bool s_initialized;
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;
periph_config_acquire_lock();
/* Enable GPIO and UART3 peripheral clocks */
gpio_use(GPIOC);
periph_config_enable(RCC_APB1PeriphClockCmd, RCC_APB1Periph_USART3);
//USART_OverSampling8Cmd(USART3, ENABLE);
/* Connect PXx to USARTx_Tx*/
GPIO_PinAFConfig(GPIOC, GPIO_PinSource10, GPIO_AF_USART3);
/* Connect PXx to USARTx_Rx*/
GPIO_PinAFConfig(GPIOC, GPIO_PinSource11, GPIO_AF_USART3);
/* 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_10;
GPIO_Init(GPIOC, &GPIO_InitStructure);
/* Configure USART Rx as alternate function */
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
GPIO_Init(GPIOC, &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(USART3, &USART_InitStructure);
/* Enable USART */
USART_Cmd(USART3, ENABLE);
periph_config_release_lock();
gpio_release(GPIOC);
s_initialized = true;
}
static void prv_putchar(uint8_t c) {
if (!s_initialized) {
return;
}
while (USART_GetFlagStatus(USART3, USART_FLAG_TC) == RESET) continue;
USART_SendData(USART3, c);
while (USART_GetFlagStatus(USART3, USART_FLAG_TC) == RESET) continue;
}
static void prv_flush(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) {
if (!s_initialized) {
return;
}
dbgserial_print(str);
prv_flush();
}
void dbgserial_print(const char* str) {
if (!s_initialized) {
return;
}
for (; *str && s_message_length < MAX_MESSAGE; ++str) {
if (*str == '\n') {
prv_flush();
} else if (*str != '\r') {
s_message_buffer[s_message_length++] = *str;
}
}
}
void dbgserial_print_hex(uint32_t value) {
char buf[12];
itoa(value, buf, sizeof(buf));
dbgserial_print(buf);
}

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 "drivers/gpio.h"
#include <stdint.h>
#define MAX_GPIO (9)
static uint8_t s_gpio_clock_count[MAX_GPIO];
void gpio_use(GPIO_TypeDef* GPIOx) {
uint8_t idx = ((((uint32_t)GPIOx) - AHB1PERIPH_BASE) / 0x0400);
if((idx < MAX_GPIO) && !(s_gpio_clock_count[idx]++)) {
SET_BIT(RCC->AHB1ENR, (0x1 << idx));
}
}
void gpio_release(GPIO_TypeDef* GPIOx) {
uint8_t idx = ((((uint32_t)GPIOx) - AHB1PERIPH_BASE) / 0x0400);
if((idx < MAX_GPIO) && s_gpio_clock_count[idx] && !(--s_gpio_clock_count[idx])) {
CLEAR_BIT(RCC->AHB1ENR, (0x1 << idx));
}
}
void gpio_output_init(OutputConfig pin_config, GPIOOType_TypeDef otype,
GPIOSpeed_TypeDef speed) {
GPIO_InitTypeDef init = {
.GPIO_Pin = pin_config.gpio_pin,
.GPIO_Mode = GPIO_Mode_OUT,
.GPIO_Speed = speed,
.GPIO_OType = otype,
.GPIO_PuPd = GPIO_PuPd_NOPULL
};
gpio_use(pin_config.gpio);
GPIO_Init(pin_config.gpio, &init);
gpio_release(pin_config.gpio);
}
void gpio_output_set(OutputConfig pin_config, bool asserted) {
if (!pin_config.active_high) {
asserted = !asserted;
}
gpio_use(pin_config.gpio);
GPIO_WriteBit(pin_config.gpio, pin_config.gpio_pin,
asserted? Bit_SET : Bit_RESET);
gpio_release(pin_config.gpio);
}
void gpio_af_init(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_use(af_config.gpio);
GPIO_Init(af_config.gpio, &init);
GPIO_PinAFConfig(af_config.gpio, af_config.gpio_pin_source,
af_config.gpio_af);
gpio_release(af_config.gpio);
}

View file

@ -0,0 +1,772 @@
/*
* 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>
#if defined(MICRO_FAMILY_STM32F2)
#include "stm32f2xx_gpio.h"
#include "stm32f2xx_rcc.h"
#include "stm32f2xx_i2c.h"
#elif defined(MICRO_FAMILY_STM32F4)
#include "stm32f4xx_gpio.h"
#include "stm32f4xx_rcc.h"
#include "stm32f4xx_i2c.h"
#include "drivers/pmic.h"
#endif
#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_use(pin_config.gpio);
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);
gpio_release(pin_config.gpio);
}
//! 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_use(pin_config.gpio);
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);
gpio_release(pin_config.gpio);
}
//! Configure bus pin as input
//! Lock bus and peripheral config access before use
static void bus_pin_cfg_input(AfConfig pin_config) {
gpio_use(pin_config.gpio);
// 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);
gpio_release(pin_config.gpio);
}
//! 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) {
gpio_use(pin_config.gpio);
// 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);
gpio_release(pin_config.gpio);
}
//! 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_acquire_lock();
periph_config_enable(RCC_APB1PeriphClockCmd, BOARD_CONFIG.i2c_bus_configs[bus_idx].clock_ctrl);
periph_config_release_lock();
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_acquire_lock();
periph_config_disable(RCC_APB1PeriphClockCmd, BOARD_CONFIG.i2c_bus_configs[bus_idx].clock_ctrl);
periph_config_release_lock();
// 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) {
#if defined(TARGET_QEMU)
PBL_LOG(LOG_LEVEL_DEBUG, "i2c reads on QEMU not supported");
return false;
#endif
// 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) {
#if defined(TARGET_QEMU)
PBL_LOG(LOG_LEVEL_DEBUG, "i2c writes on QEMU not supported");
return false;
#endif
// 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]));
}
/*------------------------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,108 @@
/*
* 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"
#if defined(MICRO_FAMILY_STM32F2)
#include "stm32f2xx_gpio.h"
#include "stm32f2xx_rcc.h"
#include "stm32f2xx_i2c.h"
#elif defined(MICRO_FAMILY_STM32F4)
#include "stm32f4xx_gpio.h"
#include "stm32f4xx_rcc.h"
#include "stm32f4xx_i2c.h"
#include "drivers/pmic.h"
#endif
extern void i2c_bus_rail_ctl_config(OutputConfig pin_config);
static void do_rail_power(bool up, GPIO_TypeDef* const gpio, const uint32_t gpio_pin, const bool active_high) {
if (up) {
gpio_use(gpio);
// enable the bus supply
GPIO_WriteBit(gpio, gpio_pin, active_high ? Bit_SET : Bit_RESET);
// wait for the bus supply to stabilize and the peripherals to start up.
// the MFI chip requires its reset pin to be stable for at least 10ms from startup.
delay_ms(20);
gpio_release(gpio);
} else {
gpio_use(gpio);
// disable the bus supply
GPIO_WriteBit(gpio, gpio_pin, active_high ? Bit_RESET : Bit_SET);
gpio_release(gpio);
}
}
// SNOWY
/////////
void snowy_i2c_rail_1_ctl_fn(bool enable) {
set_ldo3_power_state(enable);
}
// bb2
/////////
void bb2_rail_ctl_fn(bool enable) {
do_rail_power(enable, GPIOH, GPIO_Pin_0, true);
}
void bb2_rail_cfg_fn(void) {
i2c_bus_rail_ctl_config((OutputConfig){ GPIOH, GPIO_Pin_0, true});
}
// v1_5
/////////
void v1_5_rail_ctl_fn(bool enable) {
do_rail_power(enable, GPIOH, GPIO_Pin_0, true);
}
void v1_5_rail_cfg_fn(void) {
i2c_bus_rail_ctl_config((OutputConfig){ GPIOH, GPIO_Pin_0, true});
}
// v2_0
/////////
void v2_0_rail_ctl_fn(bool enable) {
do_rail_power(enable, GPIOH, GPIO_Pin_0, true);
}
void v2_0_rail_cfg_fn(void) {
i2c_bus_rail_ctl_config((OutputConfig){ GPIOH, GPIO_Pin_0, true});
}
// ev2_4
/////////
void ev2_4_rail_ctl_fn(bool enable) {
do_rail_power(enable, GPIOH, GPIO_Pin_0, true);
}
void ev2_4_rail_cfg_fn(void) {
i2c_bus_rail_ctl_config((OutputConfig){ GPIOH, GPIO_Pin_0, true});
}
// bigboard
////////////
void bigboard_rail_ctl_fn(bool enable) {
do_rail_power(enable, GPIOC, GPIO_Pin_5, true);
}
void bigboard_rail_cfg_fn(void) {
i2c_bus_rail_ctl_config((OutputConfig){ GPIOC, GPIO_Pin_5, true});
}

View file

@ -0,0 +1,60 @@
/*
* 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/spi.h"
#include "util/misc.h"
#include "system/passert.h"
#if defined(MICRO_FAMILY_STM32F2)
#include "stm32f2xx_rcc.h"
#elif defined(MICRO_FAMILY_STM32F4)
#include "stm32f4xx_rcc.h"
#endif
// Deduced by looking at the prescalers in stm32f2xx_spi.h
#define SPI_FREQ_LOG_TO_PRESCALER(LG) (((LG) - 1) * 0x8)
uint16_t spi_find_prescaler(uint32_t bus_frequency, SpiPeriphClock periph_clock) {
// Get the clocks
RCC_ClocksTypeDef clocks;
RCC_GetClocksFreq(&clocks);
uint32_t clock = 0;
// Find which peripheral clock we belong to
if (periph_clock == SpiPeriphClockAPB1) {
clock = clocks.PCLK1_Frequency;
} else if (periph_clock == SpiPeriphClockAPB2) {
clock = clocks.PCLK2_Frequency;
} else {
WTF;
}
int lg;
if (bus_frequency > (clock / 2)) {
lg = 1; // Underclock to the highest possible frequency
} else {
uint32_t divisor = clock / bus_frequency;
lg = ceil_log_two(divisor);
}
// Prescalers only exists for values in [2 - 256] range
PBL_ASSERTN(lg > 0);
PBL_ASSERTN(lg < 9);
// return prescaler
return (SPI_FREQ_LOG_TO_PRESCALER(lg));
}

View file

@ -0,0 +1,131 @@
/*
* 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"
#if defined(MICRO_FAMILY_STM32F2)
#include "stm32f2xx_flash.h"
#elif defined(MICRO_FAMILY_STM32F4)
#include "stm32f4xx_flash.h"
#endif
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_print(" is outside system flash\r\n");
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) {
dbgserial_print("system_flash_erase(");
dbgserial_print_hex(address);
dbgserial_print(", ");
dbgserial_print_hex(length);
dbgserial_print(")\r\n");
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_putstr("");
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) {
dbgserial_print("system_flash_write(");
dbgserial_print_hex(address);
dbgserial_print(", ");
dbgserial_print_hex(length);
dbgserial_print(")\r\n");
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_putstr("");
FLASH_Lock();
return false;
}
if (progress_callback && i % 128 == 0) {
progress_callback(i/128, length/128, progress_context);
}
}
FLASH_Lock();
return true;
}
uint32_t system_flash_read(uint32_t address) {
uint32_t data = *(volatile uint32_t*) address;
return data;
}

View file

@ -0,0 +1,61 @@
/*
* 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 "util/bitset.h"
#include "system/logging.h"
#if defined(MICRO_FAMILY_STM32F2)
#include "stm32f2xx_dbgmcu.h"
#include "stm32f2xx_iwdg.h"
#include "stm32f2xx_rcc.h"
#elif defined(MICRO_FAMILY_STM32F4)
#include "stm32f4xx_dbgmcu.h"
#include "stm32f4xx_iwdg.h"
#include "stm32f4xx_rcc.h"
#endif
#include <inttypes.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();
watchdog_feed();
}
// This behaves differently from the bootloader and the firmware.
void watchdog_feed(void) {
IWDG_ReloadCounter();
}
bool watchdog_check_reset_flag(void) {
return RCC_GetFlagStatus(RCC_FLAG_IWDGRST) != RESET;
}
void watchdog_clear_reset_flag(void) {
RCC_ClearFlag();
}

View file

@ -0,0 +1,74 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#if defined(MICRO_FAMILY_STM32F2)
#include "stm32f2xx_flash.h"
#elif defined(MICRO_FAMILY_STM32F4)
#include "stm32f4xx_flash.h"
#endif
#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 */
// stm32f2xx only has 512k of system flash, these sectors don't exist
#if defined(MICRO_FAMILY_STM32F4)
#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 */
#endif
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);
uint32_t system_flash_read(uint32_t address);

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 <stdbool.h>
void watchdog_init(void);
void watchdog_start(void);
void watchdog_feed(void);
bool watchdog_check_reset_flag(void);
void watchdog_clear_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,96 @@
/*
* 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 SECTOR_SIZE_BYTES 0x20000
#define SECTOR_ADDR_MASK (~(SECTOR_SIZE_BYTES - 1))
#define SUBSECTOR_SIZE_BYTES 0x20000
#define SUBSECTOR_ADDR_MASK (~(SUBSECTOR_SIZE_BYTES - 1))
// Filesystem layout
///////////////////////////////////////
// Space for our flash logs
// NOTE: This range of memory is actually in the special "bottom boot" area of our flash chip
// where the erase sectors are smaller (32k instead of 128k everywhere else).
#define FLASH_REGION_DEBUG_DB_BEGIN 0x0
#define FLASH_REGION_DEBUG_DB_END 0x20000 // 128k
// Regions after this point are in standard, 128kb sized sectors.
// 768kb gap here. We should save some space for non-filesystem things. It also aligns the
// subsequent sectors nicely.
// 1 128kb sector for storing mfg info, see fw/mfg/snowy/mfg_info.c
#define FLASH_REGION_MFG_INFO_BEGIN 0x0e0000
#define FLASH_REGION_MFG_INFO_END 0x100000
// Scratch space for firmware images (normal and recovery).
#define FLASH_REGION_FIRMWARE_SCRATCH_BEGIN 0x100000
#define FLASH_REGION_FIRMWARE_SCRATCH_END 0x200000 // 1024k
#define FLASH_REGION_SAFE_FIRMWARE_BEGIN 0x200000
#define FLASH_REGION_SAFE_FIRMWARE_END 0x300000 // 1024k
#define FLASH_REGION_NEXT_SYSTEM_RESOURCES_BEGIN 0x300000
#define FLASH_REGION_NEXT_SYSTEM_RESOURCES_END 0x380000 // 512k
#define FLASH_REGION_SYSTEM_RESOURCES_BEGIN 0x380000
#define FLASH_REGION_SYSTEM_RESOURCES_END 0x400000 // 512k
// FIXME: The addresses below here are hacky work arounds and hopefully not the final place
// for these things. Hopefully many of them can move to the filesystem. Everything above here
// should be pretty stable.
#define REGISTRY_FLASH_BEGIN 0x400000 //
#define REGISTRY_FLASH_END 0x420000 // 128k
#define FACTORY_REGISTRY_FLASH_BEGIN 0x420000 //
#define FACTORY_REGISTRY_FLASH_END 0x440000 // 128k
#define FLASH_REGION_UNUSED0_BEGIN 0x440000
#define FLASH_REGION_UNUSED0_END 0x480000 // 256k
#define FLASH_REGION_FILESYSTEM_BEGIN 0x0480000
#define FLASH_REGION_FILESYSTEM_END 0x1000000 // 8mb+, aka the rest!
// Constants used for testing flash interface
// NOTE: This purposely overlaps the file system region since the flash test requires a non-critical
// region to operate on. Data in this region will get corrupted and will not get restored after the
// test runs. Any data in this region will have to be manually restored or reinitialized.
#define FLASH_TEST_ADDR_START 0x0800000 // 8MB
#define FLASH_TEST_ADDR_END 0x1000000 // 16MB
#define FLASH_TEST_ADDR_MSK 0x1FFFFFF // test all bits in the 16MB range
#if ((FLASH_REGION_FILESYSTEM_BEGIN > FLASH_TEST_ADDR_START) || (FLASH_REGION_FILESYSTEM_END < FLASH_TEST_ADDR_END))
#error "ERROR: Flash Test space not withing expected range"
#endif
// 0x1000000 is the end of the SPI flash address space.
// FIXME: These regions are used by code paths that are never executed on this flash part. Give
// them bogus values so things still compile
#define FLASH_REGION_APP_BEGIN 0x1000000
#define FLASH_REGION_APP_END 0x1020000
#define FLASH_REGION_APP_RESOURCES_BEGIN 0x1020000
#define FLASH_REGION_APP_RESOURCES_END 0x1040000
#define FLASH_REGION_MIGRATION_BEGIN 0x1040000
#define FLASH_REGION_MIGRATION_END 0x1060000

View file

@ -0,0 +1,212 @@
/*
* 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/crc.h"
#include "drivers/dbgserial.h"
#include "drivers/display.h"
#include "drivers/flash/s29vs.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/misc.h"
#include "util/delay.h"
#include <inttypes.h>
#include <stdint.h>
static bool check_valid_firmware_crc(uint32_t flash_address, FirmwareDescription* desc) {
dbgserial_putstr("Checksumming firmware update");
uint32_t crc = crc_calculate_flash(flash_address, desc->firmware_length);
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 firmware_start_address, uint32_t firmware_length) {
dbgserial_putstr("write_new_firmware");
return system_flash_write(
FIRMWARE_BASE, (void *)(FMC_BANK_1_BASE_ADDRESS + firmware_start_address),
firmware_length, prv_display_write_progress, 0);
}
static bool check_firmware_crc(FirmwareDescription* firmware_description) {
dbgserial_print("Checksumming ");
dbgserial_print_hex(firmware_description->firmware_length);
dbgserial_print(" bytes\r\n");
uint32_t calculated_crc = crc_calculate_bytes(
(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(calculated_crc);
dbgserial_putstr("");
return calculated_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) {
crc_init();
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,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
#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,9 @@
#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,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 "drivers/dbgserial.h"
#include "util/misc.h"
#include "system/die.h"
#include "system/reset.h"
#include "misc.h"
#include "stm32f4xx.h"
#include <inttypes.h>
#include <stdarg.h>
#include <stdio.h>
void hard_fault_handler_c(unsigned int* hardfault_args) {
dbgserial_putstr("HARD FAULT");
#ifdef NO_WATCHDOG
__BKPT();
while (1) continue;
#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 hard_fault_handler_c\n");
}

View file

@ -0,0 +1,432 @@
/*
* 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/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) {
void** fw_vector_table = (void**) FIRMWARE_BASE; // Defined in wscript
*initial_stack_pointer = fw_vector_table[0];
*reset_handler = 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;
}
// Set the peripheral clock enable registers to their reset values as
// specified in the datasheet.
RCC->AHB1ENR = 0x00100000; // Core-coupled memory is enabled at reset
RCC->AHB2ENR = 0;
RCC->AHB3ENR = 0;
RCC->APB1ENR = 0;
RCC->APB2ENR = 0;
// 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_FMC;
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 check_force_boot_recovery(void) {
if (boot_bit_test(BOOT_BIT_FORCE_PRF)) {
boot_bit_clear(BOOT_BIT_FORCE_PRF);
return true;
}
if (button_is_pressed(BUTTON_ID_UP) && button_is_pressed(BUTTON_ID_BACK)) {
dbgserial_putstr("Hold down UP + BACK for 5 secs. to force-boot PRF");
for (int i = 0; i < 5000; ++i) {
if (!(button_is_pressed(BUTTON_ID_UP) && button_is_pressed(BUTTON_ID_BACK))) {
// 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(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) {
check_and_handle_resuming_from_standby();
dbgserial_init();
dbgserial_putstr("");
dbgserial_putstr(" ____ __");
dbgserial_putstr("/\\ _`\\ /'__`\\");
dbgserial_putstr("\\ \\,\\L\\_\\ ___ /\\ \\/\\ \\ __ __ __ __ __");
dbgserial_putstr(" \\/_\\__ \\ /' _ `\\ \\ \\ \\ \\/\\ \\/\\ \\/\\ \\/\\ \\/\\ \\");
dbgserial_putstr(" /\\ \\L\\ \\/\\ \\/\\ \\ \\ \\_\\ \\ \\ \\_/ \\_/ \\ \\ \\_\\ \\");
dbgserial_putstr(" \\ `\\____\\ \\_\\ \\_\\ \\____/\\ \\___x___/'\\/`____ \\");
dbgserial_putstr(" \\/_____/\\/_/\\/_/\\/___/ \\/__//__/ `/___/> \\");
dbgserial_putstr(" /\\___/");
dbgserial_putstr(" \\/__/");
// PMIC requires I2C
i2c_init();
// Enable the 3.2V rail for the benefit of the FPGA and display
pmic_init();
boot_bit_init();
boot_version_write();
// Write the bootloader version to serial-out
{
char bootloader_version_str[12];
memset(bootloader_version_str, 0, 12);
itoa(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);
}
flash_init();
button_init();
display_init();
#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);
}
watchdog_init();
#ifndef NO_WATCHDOG
watchdog_start();
#endif
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 = 128K
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,76 @@
/*
* 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/bootbits.h"
#include "system/logging.h"
#include "system/rtc_registers.h"
#include "util/version.h"
#include "stm32f4xx.h"
#include <inttypes.h>
#include <stdint.h>
void boot_bit_init(void) {
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);
PWR_BackupAccessCmd(ENABLE); // Disable write-protect on RTC_BKP_x registers
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) {
PBL_LOG(LOG_LEVEL_DEBUG, "0x%"PRIx32, RTC_ReadBackupRegister(RTC_BKP_BOOTBIT_DR));
}
uint32_t boot_bits_get(void) {
return RTC_ReadBackupRegister(RTC_BKP_BOOTBIT_DR);
}
void command_boot_bits_get(void) {
char buffer[32];
dbgserial_putstr_fmt(buffer, sizeof(buffer), "bootbits: 0x%"PRIu32, boot_bits_get());
}
void boot_version_write(void) {
if (boot_version_read() == TINTIN_METADATA.version_timestamp) {
return;
}
RTC_WriteBackupRegister(BOOTLOADER_VERSION_REGISTER, TINTIN_METADATA.version_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,
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
} 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 to PBL_LOG
void boot_bit_dump(void);
uint32_t boot_bits_get(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,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.
*/
#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,69 @@
/*
* 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...) \
dbgserial_putstr(__FILE_NAME__ ":" STRINGIFY(__LINE__) "> " fmt);
/*
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,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
#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)
extern void command_dump_malloc(void);
#define PBL_ASSERT_OOM(expr) \
do { \
if (!(expr)) { \
croak_oom(__FILE_NAME__, __LINE__, "Failed to allocate <%s> with size %u", #expr, sizeof(*expr)); \
} \
} while (0)
#define PBL_CROAK(fmt, args...) \
passert_failed(__FILE_NAME__, __LINE__, "*** CROAK: " fmt, ## args)
void croak_oom(const char *filename, int line_number, const char *fmt, ...) __attribute__((noreturn));
#define PBL_CROAK_OOM(fmt, args...) \
croak_oom(__FILE_NAME__, __LINE__, fmt, ## args)

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 "system/reset.h"
#include "drivers/display.h"
#if defined(MICRO_FAMILY_STM32F2)
#include "stm32f2xx.h"
#elif defined(MICRO_FAMILY_STM32F4)
#include "stm32f4xx.h"
#endif
void system_reset(void) {
display_prepare_for_reset();
system_hard_reset();
}
void system_hard_reset(void) {
NVIC_SystemReset();
__builtin_unreachable();
}

View file

@ -0,0 +1,32 @@
/*
* 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>
//! Shut down system services but don't actually reset.
void system_reset_prepare(bool skip_bt_teardown);
//! 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));
//! Same as system_reset() but usable as a callback.
void system_reset_callback(void *);
//! The final stage in the reset process.
void system_hard_reset(void) __attribute__((noreturn));

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
#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 MAG_XY_CORRECTION_VALS RTC_BKP_DR17
#define MAG_Z_CORRECTION_VAL RTC_BKP_DR18
#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,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.
*/
//! @file util/bitset.h
//!
//! Helper functions for dealing with a bitsets of various widths.
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include "system/passert.h"
static inline void bitset8_set(uint8_t* bitset, unsigned int index) {
bitset[index / 8] |= (1 << (index % 8));
}
static inline void bitset8_clear(uint8_t* bitset, unsigned int index) {
bitset[index / 8] &= ~(1 << (index % 8));
}
static inline void bitset8_update(uint8_t* bitset, unsigned int index, bool value) {
if (value) {
bitset8_set(bitset, index);
} else {
bitset8_clear(bitset, index);
}
}
static inline bool bitset8_get(const uint8_t* bitset, unsigned int index) {
return bitset[index / 8] & (1 << (index % 8));
}
static inline void bitset16_set(uint16_t* bitset, unsigned int index) {
bitset[index / 16] |= (1 << (index % 16));
}
static inline void bitset16_clear(uint16_t* bitset, unsigned int index) {
bitset[index / 16] &= ~(1 << (index % 16));
}
static inline void bitset16_update(uint16_t* bitset, unsigned int index, bool value) {
if (value) {
bitset16_set(bitset, index);
} else {
bitset16_clear(bitset, index);
}
}
static inline bool bitset16_get(const uint16_t* bitset, unsigned int index) {
return bitset[index / 16] & (1 << (index % 16));
}
static inline void bitset32_set(uint32_t* bitset, unsigned int index) {
bitset[index / 32] |= (1 << (index % 32));
}
static inline void bitset32_clear(uint32_t* bitset, unsigned int index) {
bitset[index / 32] &= ~(1 << (index % 32));
}
static inline void bitset32_clear_all(uint32_t* bitset, unsigned int width) {
if (width > 32) {
// TODO: revisit
WTF;
}
*bitset &= ~((1 << (width + 1)) - 1);
}
static inline void bitset32_update(uint32_t* bitset, unsigned int index, bool value) {
if (value) {
bitset32_set(bitset, index);
} else {
bitset32_clear(bitset, index);
}
}
static inline bool bitset32_get(const uint32_t* bitset, unsigned int index) {
return bitset[index / 32] & (1 << (index % 32));
}
#ifdef __arm__
#define rotl32(x, shift) \
__asm__ volatile ("ror %0,%0,%1" : "+r" (src) : "r" (32 - (shift)) :);
#else
#define rotl32(x, shift) \
uint32_t s = shift % 32; \
{x = ((x << (s)) | x >> (32 - (s)));}
#endif

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,44 @@
/*
* 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 <inttypes.h>
void delay_us(uint32_t us) {
// Empirically (measured on a bb2), 1 loop = ~47ns. (sysclk @
// 64MHz, prefetch disabled) Alignment of code will have some impact on how
// long this actually takes
uint32_t delay_loops = us * 22;
__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,201 @@
/*
* 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 "system/logging.h"
#include "system/passert.h"
#include <ctype.h>
#include <stdbool.h>
#include <string.h>
int32_t sign_extend(uint32_t a, int bits) {
if (bits == 32) {
return a;
}
// http://graphics.stanford.edu/~seander/bithacks.html#VariableSignExtend
int const m = 1U << (bits - 1); // mask can be pre-computed if b is fixed
a = a & ((1U << bits) - 1); // (Skip this if bits in x above position b are already zero.)
return (a ^ m) - m;
}
int32_t serial_distance32(uint32_t a, uint32_t b) {
return serial_distance(a, b, 32);
}
int32_t serial_distance(uint32_t a, uint32_t b, int bits) {
// See https://en.wikipedia.org/wiki/Serial_Number_Arithmetic
const int64_t a_minus_b = a - b;
const int64_t b_minus_a = b - a;
const bool a_is_earlier_than_b = (a < b && b_minus_a < (1 << (bits - 1))) || (a > b && a_minus_b > (1 << (bits - 1)));
return sign_extend(a_is_earlier_than_b ? -a_minus_b : b_minus_a, bits);
}
int ceil_log_two(uint32_t n) {
// clz stands for Count Leading Zeroes. We use it to find the MSB
int msb = 31 - __builtin_clz(n);
// popcount counts the number of set bits in a word (1's)
bool power_of_two = __builtin_popcount(n) == 1;
// if not exact power of two, use the next power of two
// we want to err on the side of caution and want to
// always round up
return ((power_of_two) ? msb : (msb + 1));
}
uint8_t count_bits_set(uint8_t *bitset_bytes, int num_bits) {
uint8_t num_bits_set = 0;
int num_bytes = (num_bits + 7) / 8;
if ((num_bits % 8) != 0) {
bitset_bytes[num_bytes] &= ((0x1 << (num_bits)) - 1);
}
for (int i = 0; i < num_bytes; i++) {
num_bits_set += __builtin_popcount(bitset_bytes[i]);
}
return (num_bits_set);
}
void itoa(uint32_t num, char *buffer, int buffer_length) {
if (buffer_length < 11) {
PBL_LOG(LOG_LEVEL_WARNING, "ito 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';
}
static int8_t ascii_hex_to_int(const uint8_t c) {
if (isdigit(c)) return c - '0';
if (isupper(c)) return (c - 'A') + 10;
if (islower(c)) return (c - 'a') + 10;
return -1;
}
static uint8_t ascii_hex_to_uint(const uint8_t msb, const uint8_t lsb) {
return 16 * ascii_hex_to_int(msb) + ascii_hex_to_int(lsb);
}
uintptr_t str_to_address(const char *address_str) {
unsigned int address_str_length = strlen(address_str);
if (address_str_length < 3) return -1; // Invalid address, must be at least 0x[0-9a-f]+
if (address_str[0] != '0' || address_str[1] != 'x') return -1; // Incorrect address prefix.
address_str += 2;
uintptr_t address = 0;
for (; *address_str; ++address_str) {
int8_t c = ascii_hex_to_int(*address_str);
if (c == -1) return -1; // Unexpected character
address = (address * 16) + c;
}
return address;
}
bool convert_bt_addr_hex_str_to_bd_addr(const char *hex_str, uint8_t *bd_addr, const unsigned int bd_addr_size) {
const int len = strlen(hex_str);
if (len != 12) {
return false;
}
uint8_t* src = (uint8_t*) hex_str;
uint8_t* dest = bd_addr + bd_addr_size - 1;
for (unsigned int i = 0; i < bd_addr_size; ++i, src += 2, --dest) {
*dest = ascii_hex_to_uint(src[0], src[1]);
}
return true;
}
uint32_t next_exponential_backoff(uint32_t *attempt, uint32_t initial_value, uint32_t max_value) {
if (*attempt > 31) {
return max_value;
}
uint32_t backoff_multiplier = 0x1 << (*attempt)++;
uint32_t next_value = initial_value * backoff_multiplier;
return MIN(next_value, max_value);
}
// Based on DJB2 Hash
uint32_t hash(const uint8_t *bytes, const uint32_t length) {
uint32_t hash = 5381;
if (length == 0) {
return hash;
}
uint8_t c;
const uint8_t *last_byte = bytes + length;
while (bytes++ != last_byte) {
c = *bytes;
hash = ((hash << 5) + hash) + c;
}
return hash;
}
const char *bool_to_str(bool b) {
if (b) {
return "yes";
} else {
return "no";
}
}
// Override libgcc's table-driven __builtin_popcount implementation
#ifdef __arm__
int __popcountsi2(uint32_t val) {
// Adapted from http://www.sciencezero.org/index.php?title=ARM%3a_Count_ones_%28bit_count%29
uint32_t tmp;
__asm("and %[tmp], %[val], #0xaaaaaaaa\n\t"
"sub %[val], %[val], %[tmp], lsr #1\n\t"
"and %[tmp], %[val], #0xcccccccc\n\t"
"and %[val], %[val], #0x33333333\n\t"
"add %[val], %[val], %[tmp], lsr #2\n\t"
"add %[val], %[val], %[val], lsr #4\n\t"
"and %[val], %[val], #0x0f0f0f0f\n\t"
"add %[val], %[val], %[val], lsr #8\n\t"
"add %[val], %[val], %[val], lsr #16\n\t"
"and %[val], %[val], #63\n\t"
: [val] "+l" (val), [tmp] "=&l" (tmp));
return val;
}
#endif // __arm__

View file

@ -0,0 +1,107 @@
/*
* 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 ABS(a) (((a) > 0) ? (a) : -1 * (a))
#define CLIP(n, min, max) ((n)<(min)?(min):((n)>(max)?(max):(n)))
#define MHZ_TO_HZ(hz) (((uint32_t)(hz)) * 1000000)
#define KiBYTES(k) ((k) * 1024) // Bytes to Kibibytes
#define MiBYTES(m) ((m) * 1024 * 1024) // Bytes to Mebibytes
#define EiBYTES(e) ((e) * 1024 * 1024 * 1024 * 1024 * 1024 * 1024) // Bytes to Exbibytes
// Stolen from http://stackoverflow.com/a/8488201
#define GET_FILE_NAME(file) (strrchr(file, '/') ? (strrchr(file, '/') + 1) : (file))
#define ARRAY_LENGTH(array) (sizeof((array))/sizeof((array)[0]))
#define MEMBER_SIZE(type, member) sizeof(((type *)0)->member)
static inline void swap16(int16_t *a, int16_t *b) {
int16_t t = *a;
*a = *b;
*b = t;
}
int32_t sign_extend(uint32_t a, int bits);
//! Calculates the distance (end - start), taking a roll-over into account as good as it can get.
int32_t serial_distance32(uint32_t start, uint32_t end);
//! Calculates the distance (end - start), taking a roll-over into account as good as it can get.
//! @param bits the number of bits that are valid in start and end.
int32_t serial_distance(uint32_t start, uint32_t end, int bits);
void itoa(uint32_t num, char *buffer, int buffer_length);
/*
* find the log base two of a number rounded up
*/
int ceil_log_two(uint32_t n);
//! Count the number of bits that are set to 1 in a multi-byte bitset.
//! @param bitset_bytes The bytes of the bitset
//! @param num_bits The width of the bitset
//! @note this function zeroes out any bits in the last byte if there
//! are more bits than num_bits.
uint8_t count_bits_set(uint8_t *bitset_bytes, int num_bits);
uintptr_t str_to_address(const char *address_str);
uint32_t hash(const uint8_t *bytes, const uint32_t length);
const char *bool_to_str(bool b);
//! @param hex 12-digit hex string representing a BT address
//! @param addr Points to a SS1 BD_ADDR_t as defined in BTBTypes.h
//! @return True on success
bool convert_bt_addr_hex_str_to_bd_addr(const char *hex_str, uint8_t *bd_addr, const unsigned int bd_addr_size);
/**
* Compute the next backoff interval using a bounded binary expoential backoff formula.
*
* @param[in,out] attempt The number of retries performed so far. This count will be incremented by
* the function.
* @param[in] initial_value The inital backoff interval. Subsequent backoff attempts will be this
* number multiplied by a power of 2.
* @param[in] max_value The maximum backoff interval that returned by the function.
* @return The next backoff interval.
*/
uint32_t next_exponential_backoff(uint32_t *attempt, uint32_t initial_value, uint32_t max_value);
/*
* The -Wtype-limits flag generated an error with the previous IS_SIGNED maco.
* If an unsigned number was passed in the macro would check if the unsigned number was less than 0.
*/
#define IS_SIGNED(var) (__builtin_choose_expr( \
__builtin_types_compatible_p(__typeof__(var), unsigned char), false, \
__builtin_choose_expr( \
__builtin_types_compatible_p(__typeof__(var), unsigned short), false, \
__builtin_choose_expr( \
__builtin_types_compatible_p(__typeof__(var), unsigned int), false, \
__builtin_choose_expr( \
__builtin_types_compatible_p(__typeof__(var), unsigned long), false, \
__builtin_choose_expr( \
__builtin_types_compatible_p(__typeof__(var), unsigned long long), false, true))))) \
)

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) };
}

View file

@ -0,0 +1,178 @@
/*
* 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 <stddef.h>
#include <stdio.h>
#include <string.h>
#include "drivers/crc.h"
#include "drivers/flash.h"
#include "flash_region.h"
#include "git_version.auto.h"
#include "system/firmware_storage.h"
#include "system/passert.h"
#include "version.h"
//! The linker inserts the build id as an "elf external note" structure:
struct ElfExternalNote {
uint32_t name_length;
uint32_t data_length;
uint32_t type; // NT_GNU_BUILD_ID = 3
uint8_t data[]; // concatenated name ('GNU') + data (build id)
};
//! This symbol and its contents are provided by the linker script, see the
//! .note.gnu.build-id section in src/fw/stm32f2xx_flash_fw.ld
extern const struct ElfExternalNote TINTIN_BUILD_ID;
const FirmwareMetadata TINTIN_METADATA __attribute__ ((section (".pbl_fw_version"))) = {
.version_timestamp = GIT_TIMESTAMP,
.version_tag = GIT_TAG,
.version_short = GIT_REVISION,
#ifdef RECOVERY_FW
.is_recovery_firmware = true,
#else
.is_recovery_firmware = false,
#endif
#if defined(BOARD_BIGBOARD)
.hw_platform = FirmwareMetadataPlatformPebbleOneBigboard,
#elif defined(BOARD_BB2)
.hw_platform = FirmwareMetadataPlatformPebbleOneBigboard2,
#elif defined(BOARD_V2_0)
.hw_platform = FirmwareMetadataPlatformPebbleTwoPointZero,
#elif defined(BOARD_V1_5)
.hw_platform = FirmwareMetadataPlatformPebbleOnePointFive,
#elif defined(BOARD_EV2_4)
.hw_platform = FirmwareMetadataPlatformPebbleOneEV2_4,
#else
.hw_platform = FirmwareMetadataPlatformUnknown,
#endif
.metadata_version = FW_METADATA_CURRENT_STRUCT_VERSION,
};
bool version_copy_running_fw_metadata(FirmwareMetadata *out_metadata) {
if (out_metadata == NULL) {
return false;
}
memcpy(out_metadata, &TINTIN_METADATA, sizeof(FirmwareMetadata));
return true;
}
static bool prv_version_copy_flash_fw_metadata(FirmwareMetadata *out_metadata, uint32_t flash_address) {
if (out_metadata == NULL) {
return false;
}
FirmwareDescription firmware_description = firmware_storage_read_firmware_description(flash_address);
if (!firmware_storage_check_valid_firmware_description(&firmware_description)) {
memset(out_metadata, 0, sizeof(FirmwareMetadata));
return false;
}
// The FirmwareMetadata is stored at the end of the binary:
uint32_t offset = firmware_description.description_length + firmware_description.firmware_length - sizeof(FirmwareMetadata);
flash_read_bytes((uint8_t*)out_metadata, flash_address + offset, sizeof(FirmwareMetadata));
return true;
}
bool version_copy_recovery_fw_metadata(FirmwareMetadata *out_metadata) {
return prv_version_copy_flash_fw_metadata(out_metadata, FLASH_REGION_SAFE_FIRMWARE_BEGIN);
}
bool version_copy_update_fw_metadata(FirmwareMetadata *out_metadata) {
return prv_version_copy_flash_fw_metadata(out_metadata, FLASH_REGION_FIRMWARE_SCRATCH_BEGIN);
}
bool version_copy_recovery_fw_version(char* dest, const int dest_len_bytes) {
FirmwareMetadata out_metadata;
bool success = version_copy_recovery_fw_metadata(&out_metadata);
if (success) {
strncpy(dest, out_metadata.version_tag, dest_len_bytes);
}
return success;
}
bool version_is_prf_installed(void) {
FirmwareDescription firmware_description =
firmware_storage_read_firmware_description(FLASH_REGION_SAFE_FIRMWARE_BEGIN);
if (!firmware_storage_check_valid_firmware_description(&firmware_description)) {
return false;
}
uint32_t flash_address = FLASH_REGION_SAFE_FIRMWARE_BEGIN + firmware_description.description_length;
uint32_t crc = crc_calculate_flash(flash_address, firmware_description.firmware_length);
return crc == firmware_description.checksum;
}
const uint8_t * version_get_build_id(size_t *out_len) {
if (out_len) {
*out_len = TINTIN_BUILD_ID.data_length;
}
return &TINTIN_BUILD_ID.data[TINTIN_BUILD_ID.name_length];
}
void version_copy_build_id_hex_string(char *buffer, size_t buffer_bytes_left) {
size_t build_id_bytes_left;
const uint8_t *build_id = version_get_build_id(&build_id_bytes_left);
while (buffer_bytes_left >= 3 /* 2 hex digits, plus zero terminator */
&& build_id_bytes_left > 0) {
snprintf(buffer, buffer_bytes_left, "%02x", *build_id);
buffer += 2;
buffer_bytes_left -= 2;
build_id += 1;
build_id_bytes_left -= 1;
}
}
static void version_fw_version_to_major_minor(unsigned int *major, unsigned int *minor,
char *version_str) {
// read in the two X's (vX.X)
sscanf(version_str, "v%u.%u", major, minor);
}
//! Compares its two arguments for order. Returns a negative integer, zero, or a positive integer
//! if the first argument is less than, equal to, or greater than the second.
static int8_t prv_version_compare_fw_version_tags(char *fw1_version, char *fw2_version) {
unsigned int major1, minor1, major2, minor2;
version_fw_version_to_major_minor(&major1, &minor1, fw1_version);
version_fw_version_to_major_minor(&major2, &minor2, fw2_version);
if (major1 != major2) { // do the major versions differ?
return (major1 - major2);
}
if (minor1 != minor2) { // do the minor versions differ?
return (minor1 - minor2);
}
return (0); // versions are the same
}
bool version_fw_downgrade_detected(void) {
FirmwareMetadata running_meta_data, update_meta_data;
version_copy_running_fw_metadata(&running_meta_data);
version_copy_update_fw_metadata(&update_meta_data);
int rv = prv_version_compare_fw_version_tags(update_meta_data.version_tag,
running_meta_data.version_tag);
// return true is the new firmware to be updated to is a version less than the old one.
return (rv < 0);
}

View file

@ -0,0 +1,97 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#define FW_METADATA_CURRENT_STRUCT_VERSION 0x1
#define FW_METADATA_VERSION_SHORT_BYTES 8
#define FW_METADATA_VERSION_TAG_BYTES 32
typedef enum {
FirmwareMetadataPlatformUnknown = 0,
FirmwareMetadataPlatformPebbleOneEV1 = 1,
FirmwareMetadataPlatformPebbleOneEV2 = 2,
FirmwareMetadataPlatformPebbleOneEV2_3 = 3,
FirmwareMetadataPlatformPebbleOneEV2_4 = 4,
FirmwareMetadataPlatformPebbleOnePointFive = 5,
FirmwareMetadataPlatformPebbleTwoPointZero = 6,
FirmwareMetadataPlatformPebbleOneBigboard = 0xff,
FirmwareMetadataPlatformPebbleOneBigboard2 = 0xfe,
} FirmwareMetadataPlatform;
// WARNING: changes in this struct must be reflected in:
// - iOS/PebblePrivateKit/PebblePrivateKit/PBBundle.m
struct FirmwareMetadata {
uint32_t version_timestamp;
char version_tag[FW_METADATA_VERSION_TAG_BYTES];
char version_short[FW_METADATA_VERSION_SHORT_BYTES];
const bool is_recovery_firmware;
const uint8_t hw_platform;
const uint8_t metadata_version; //!< This should be the last field, since we put the meta data struct at the end of the fw binary.
} __attribute__((__packed__));
typedef struct FirmwareMetadata FirmwareMetadata;
extern const FirmwareMetadata TINTIN_METADATA;
//! Copies the version metadata of the running firmware in the provided struct.
//! @param[out] out_metadata pointer to a FirmwareMetadata to which to copy the version metadata
//! @returns true if it successfully copied the version metadata.
bool version_copy_running_fw_metadata(FirmwareMetadata *out_metadata);
//! Copies the version metadata of the recovery firmware in the provided struct.
//! If there is no valid metadata available, the struct will be wiped to be all zeroes.
//! @param[out] out_metadata pointer to a FirmwareMetadata to which to copy the version metadata
//! @returns true if it successfully copied the version metadata.
bool version_copy_recovery_fw_metadata(FirmwareMetadata *out_metadata);
//! Copies the version metadata of the update firmware located in
//! FLASH_REGION_FIRMWARE_SCRATCH_BEGIN into the provided struct.
//! If there is no valid metadata available, the struct will be wiped to be all zeroes.
//! @param[out] out_metadata pointer to a FirmwareMetadata to which to copy the version metadata
//! @returns true if it successfully copied the version metadata.
bool version_copy_update_fw_metadata(FirmwareMetadata *out_metadata);
//! Read recovery version_short from flash and copy to dest; copy at most
//! dest_len_bytes - 1 before being null-terminated via strncpy()
//!
//! @param dest: char[dest_len_bytes]
//! @returns true on success, false otherwise
bool version_copy_recovery_fw_version(char* dest, const int dest_len_bytes);
//! Checks to see if a valid PRF is installed with a correct checksum.
//! @return true if a PRF is installed, false otherwise.
bool version_is_prf_installed(void);
//! @return Pointer to the GNU build id data. This is a hash of the firmware
//! that is generated by the linker and uniquely identifies the binary.
//! @param[out] out_len The length of the build id in bytes.
const uint8_t * version_get_build_id(size_t *out_len);
//! Copies a hex C-string of the build id into the supplied buffer.
//! Get the build id from an elf, using `arm-none-eabi-readelf -n tintin_fw.elf`
//! @param[out] buffer The buffer into which the string should be copied.
//! @param max_length The length of buffer.
void version_copy_build_id_hex_string(char *buffer, size_t max_length);
//! Checks the firmware stored in FLASH_REGION_FIRMWARE_SCRATCH_BEGIN and compares it to the
//! currently running firmware.
//! @returns true if a downgrade is detected.
bool version_fw_downgrade_detected(void);

View file

@ -0,0 +1,251 @@
/*
* 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 <stdio.h>
#include <string.h>
#include "clar.h"
#include "drivers/system_flash.h"
#define KiB *1024
// Set bits n..0 in a bit-vector (zero-indexed)
#define BITS(n) ((((uint32_t)(1<<((n)+1)))) - 1)
// Set bits y..x in a bit-vector (x <= y; x, y >= 0)
#define BITS_BETWEEN(x,y) (BITS(y) & ~BITS(x-1))
// Yo dawg, I heard you like tests so I put tests in your tests so you can test
// your tests while you test!
void test_system_flash__bit_range_macros(void) {
cl_assert_equal_i(0b1, BITS(0));
cl_assert_equal_i(0b00011111, BITS(4));
cl_assert_equal_i(0b00111111, BITS_BETWEEN(0,5));
cl_assert_equal_i(0b00111000, BITS_BETWEEN(3,5));
cl_assert_equal_i(0b00010000, BITS_BETWEEN(4,4));
}
// Flash memory is organized into twelve sectors of unequal sizes.
// Sectors 0-3 are 16 KiB. Sector 4 is 64 KiB.
// The remaining sectors are 128 KiB.
// Bitset of sectors that have been "erased"
static uint32_t erased_sector;
static bool flash_locked, flash_flags_set;
static FLASH_Status return_status;
uint8_t *flash_written_data;
bool *flash_written_flag;
void *source_buffer;
uint32_t flash_data_start, flash_data_length;
bool callback_called;
void test_system_flash__initialize(void) {
erased_sector = 0;
flash_locked = true;
flash_flags_set = false;
return_status = FLASH_COMPLETE;
flash_written_data = NULL;
flash_written_flag = NULL;
flash_data_start = 0;
flash_data_length = 0;
callback_called = false;
}
void test_system_flash__cleanup(void) {
free(flash_written_data);
free(flash_written_flag);
}
void test_system_flash__erase_zero_bytes(void) {
cl_assert(system_flash_erase(FLASH_BASE, 0, NULL, NULL));
cl_assert_equal_i(0, erased_sector);
cl_assert(flash_locked);
}
void test_system_flash__erase_one_byte(void) {
cl_assert(system_flash_erase(FLASH_BASE, 1, NULL, NULL));
cl_assert_equal_i(BITS_BETWEEN(0, 0), erased_sector);
cl_assert(flash_locked);
}
void test_system_flash__erase_one_byte_in_middle_of_sector(void) {
cl_assert(system_flash_erase(FLASH_BASE + 12345, 1, NULL, NULL));
cl_assert_equal_i(BITS_BETWEEN(0, 0), erased_sector);
cl_assert(flash_locked);
}
void test_system_flash__erase_some_sectors_from_beginning(void) {
cl_assert(system_flash_erase(FLASH_BASE, 128 KiB, NULL, NULL));
cl_assert_equal_i(BITS_BETWEEN(0, 4), erased_sector);
cl_assert(flash_locked);
}
void test_system_flash__erase_full_flash(void) {
cl_assert(system_flash_erase(FLASH_BASE, 1024 KiB, NULL, NULL));
cl_assert_equal_i(BITS_BETWEEN(0, 11), erased_sector);
cl_assert(flash_locked);
}
void test_system_flash__erase_sector_0(void) {
cl_assert(system_flash_erase(FLASH_BASE, 16 KiB, NULL, NULL));
cl_assert_equal_i(BITS_BETWEEN(0, 0), erased_sector);
cl_assert(flash_locked);
}
void test_system_flash__erase_16KB_sectors(void) {
cl_assert(system_flash_erase(FLASH_BASE, 48 KiB, NULL, NULL));
cl_assert_equal_i(BITS_BETWEEN(0, 2), erased_sector);
cl_assert(flash_locked);
}
void test_system_flash__erase_sectors_1_thru_10(void) {
cl_assert(system_flash_erase(FLASH_BASE + 25 KiB, 871 KiB - 1, NULL, NULL));
cl_assert_equal_i(BITS_BETWEEN(1, 10), erased_sector);
cl_assert(flash_locked);
}
void test_system_flash__erase_sectors_1_thru_11(void) {
cl_assert(system_flash_erase(FLASH_BASE + 25 KiB, 871 KiB, NULL, NULL));
cl_assert_equal_i(BITS_BETWEEN(1, 10), erased_sector);
cl_assert(flash_locked);
}
void callback_is_called_cb(uint32_t num, uint32_t den, void *context) {
callback_called = true;
cl_assert_equal_i(8675309, (uintptr_t)context);
}
void test_system_flash__callback_is_called(void) {
cl_assert(system_flash_erase(FLASH_BASE, 16 KiB, callback_is_called_cb,
(void *)8675309));
cl_assert(callback_called);
cl_assert(flash_locked);
}
void test_system_flash__handle_erase_error(void) {
return_status = FLASH_ERROR_OPERATION;
cl_assert(!system_flash_erase(FLASH_BASE, 16 KiB, NULL, NULL));
cl_assert(flash_locked);
}
void error_in_middle_cb(uint32_t num, uint32_t den, void *context) {
int *countdown = context;
if ((*countdown)-- == 0) {
return_status = FLASH_ERROR_OPERATION;
}
}
void test_system_flash__handle_erase_error_mid_operation(void) {
int countdown = 3;
cl_assert(!system_flash_erase(FLASH_BASE, 512 KiB, error_in_middle_cb,
&countdown));
cl_assert(flash_locked);
cl_assert_(countdown <= 0, "Callback not called enough times");
}
void malloc_flash_data(uint32_t size) {
flash_data_length = size;
flash_written_data = malloc(size * sizeof(uint8_t));
cl_assert(flash_written_data);
flash_written_flag = malloc(size * sizeof(bool));
cl_assert(flash_written_flag);
}
void assert_flash_unwritten(uint32_t start, uint32_t length) {
for (uint32_t i = 0; i < length; ++i) {
cl_assert(flash_written_flag[start + i] == false);
}
}
void test_system_flash__write_simple(void) {
const char testdata[] = "The quick brown fox jumps over the lazy dog.";
malloc_flash_data(100);
flash_data_start = FLASH_BASE;
cl_assert(system_flash_write(
FLASH_BASE + 10, testdata, sizeof(testdata), callback_is_called_cb,
(void *)8675309));
cl_assert(flash_locked);
cl_assert_equal_s(testdata, (const char *)&flash_written_data[10]);
assert_flash_unwritten(0, 10);
assert_flash_unwritten(10 + sizeof(testdata), 90 - sizeof(testdata));
cl_assert(callback_called);
}
void test_system_flash__write_error(void) {
return_status = FLASH_ERROR_OPERATION;
malloc_flash_data(10);
flash_data_start = FLASH_BASE;
cl_assert(!system_flash_write(FLASH_BASE, "abc", 3, NULL, NULL));
cl_assert(flash_locked);
assert_flash_unwritten(0, 10);
}
extern void FLASH_Lock(void) {
flash_locked = true;
}
extern void FLASH_Unlock(void) {
flash_locked = false;
}
extern void FLASH_ClearFlag(uint32_t FLASH_FLAG) {
flash_flags_set = false;
}
extern FLASH_Status FLASH_EraseSector(uint32_t sector, uint8_t voltage_range) {
// Pretty sure FLASH_Sector_N defines are simply 8*N, at least for the first
// twelve sectors.
cl_assert_(!flash_locked, "Attempted to erase a locked flash");
cl_assert_(IS_FLASH_SECTOR(sector), "Sector number out of range");
cl_assert(IS_VOLTAGERANGE(voltage_range));
cl_check_(flash_flags_set == false, "Forgot to clear flags before erasing");
cl_check_((erased_sector & (1 << sector/8)) == 0,
"Re-erasing an already erased sector");
flash_flags_set = true;
if (return_status == FLASH_COMPLETE) {
erased_sector |= (1 << sector/8);
}
return return_status;
}
extern FLASH_Status FLASH_ProgramByte(uint32_t address, uint8_t data) {
cl_assert_(!flash_locked, "Attempted to write to a locked flash");
cl_assert_(address >= flash_data_start &&
address < flash_data_start + flash_data_length,
"Address out of range");
cl_assert_(flash_written_flag[address - flash_data_start] == false,
"Overwriting an already-written byte");
if (return_status == FLASH_COMPLETE) {
flash_written_data[address - flash_data_start] = data;
}
return return_status;
}
extern void dbgserial_print(char *str) {
fprintf(stderr, "%s", str);
}
extern void dbgserial_print_hex(uint32_t num) {
fprintf(stderr, "0x%.08x", num);
}
extern void dbgserial_putstr(char *str) {
fprintf(stderr, "%s\n", str);
}

25
platform/snowy/boot/vendor/wscript vendored Normal file
View file

@ -0,0 +1,25 @@
def configure(conf):
conf.env.append_unique('DEFINES', 'STM32F429_439xx')
def build(bld):
stm32_basedir = 'STM32F4xx_DSP_StdPeriph_Lib_V1.3.0/'
stm32_srcdirs = [stm32_basedir + subpath for subpath in (
'', 'Libraries/STM32F4xx_StdPeriph_Driver/src',
'Libraries/CMSIS/CM3/CoreSupport')]
stm32_sources = sum(
[bld.path.ant_glob('%s/*.c' % d, excl=['**/stm32f4xx_fsmc.c'])
for d in stm32_srcdirs], [])
stm32_incpath_base = stm32_basedir + 'Libraries/'
stm32_includes = [ stm32_incpath_base + subpath for subpath in (
'CMSIS/Include', 'CMSIS/Device/ST/STM32F4xx/Include',
'STM32F4xx_StdPeriph_Driver/inc')]
stm32_includes += ['stm32_conf']
bld.stlib(source=stm32_sources,
target='stm32_stdlib',
includes=stm32_includes,
export_includes=stm32_includes)
# vim:filetype=python

View file

@ -0,0 +1,137 @@
# 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.
import binascii
from waflib import Task, TaskGen, Utils, Node, Errors
class binary_header(Task.Task):
"""
Create a header file containing an array with contents from a binary file.
"""
def run(self):
if getattr(self.generator, 'hex', False):
# Input file is hexadecimal ASCII characters with whitespace
text = self.inputs[0].read(
encoding=getattr(self.generator, 'encoding', 'ISO8859-1'))
# Strip all whitespace so that binascii is happy
text = ''.join(text.split())
code = binascii.unhexlify(text)
else:
code = self.inputs[0].read('rb')
array_name = getattr(self.generator, 'array_name', None)
if not array_name:
array_name = re.sub(r'[^A-Za-z0-9]', '_', self.inputs[0].name)
output = ['#pragma once', '#include <stdint.h>']
output += ['static const uint8_t %s[] = {' % array_name]
line = []
for n, b in enumerate(code):
line += ['0x%.2x,' % ord(b)]
if n % 16 == 15:
output += [''.join(line)]
line = []
if line:
output += [''.join(line)]
output += ['};', '']
self.outputs[0].write(
'\n'.join(output),
encoding=getattr(self.generator, 'encoding', 'ISO8859-1'))
self.generator.bld.raw_deps[self.uid()] = self.dep_vars = 'array_name'
if getattr(self.generator, 'chmod', None):
os.chmod(self.outputs[0].abspath(), self.generator.chmod)
@TaskGen.feature('binary_header')
@TaskGen.before_method('process_source', 'process_rule')
def process_binary_header(self):
"""
Define a transformation that substitutes the contents of *source* files to
*target* files::
def build(bld):
bld(
features='binary_header',
source='foo.bin',
target='foo.auto.h',
array_name='s_some_array'
)
bld(
features='binary_header',
source='bar.hex',
target='bar.auto.h',
hex=True
)
If the *hex* parameter is True, the *source* files are read in an ASCII
hexadecimal format, where each byte is represented by a pair of hexadecimal
digits with optional whitespace. If *hex* is False or not specified, the
file is treated as a raw binary file.
The name of the array variable defaults to the source file name with all
characters that are invaid C identifiers replaced with underscores. The name
can be explicitly specified by setting the *array_name* parameter.
This method overrides the processing by
:py:meth:`waflib.TaskGen.process_source`.
"""
src = Utils.to_list(getattr(self, 'source', []))
if isinstance(src, Node.Node):
src = [src]
tgt = Utils.to_list(getattr(self, 'target', []))
if isinstance(tgt, Node.Node):
tgt = [tgt]
if len(src) != len(tgt):
raise Errors.WafError('invalid number of source/target for %r' % self)
for x, y in zip(src, tgt):
if not x or not y:
raise Errors.WafError('null source or target for %r' % self)
a, b = None, None
if isinstance(x, str) and isinstance(y, str) and x == y:
a = self.path.find_node(x)
b = self.path.get_bld().make_node(y)
if not os.path.isfile(b.abspath()):
b.sig = None
b.parent.mkdir()
else:
if isinstance(x, str):
a = self.path.find_resource(x)
elif isinstance(x, Node.Node):
a = x
if isinstance(y, str):
b = self.path.find_or_declare(y)
elif isinstance(y, Node.Node):
b = y
if not a:
raise Errors.WafError('could not find %r for %r' % (x, self))
has_constraints = False
tsk = self.create_task('binary_header', a, b)
for k in ('after', 'before', 'ext_in', 'ext_out'):
val = getattr(self, k, None)
if val:
has_constraints = True
setattr(tsk, k, val)
tsk.before = [k for k in ('c', 'cxx') if k in Task.classes]
self.source = []

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.
"""
Define a __FILE_NAME__ macro to expand to the filename of the C/C++ source,
stripping the other path components.
"""
from waflib.TaskGen import feature, after_method
@feature('c')
@after_method('create_compiled_task')
def file_name_c_define(self):
for task in self.tasks:
task.env.append_value(
'DEFINES', '__FILE_NAME__="%s"' % task.inputs[0].name)

View file

@ -0,0 +1,45 @@
# 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.
import re
import waflib.Context
import waflib.Logs
def get_git_revision(ctx):
try:
tag = ctx.cmd_and_log(['git', 'describe'], quiet=waflib.Context.BOTH).strip()
commit = ctx.cmd_and_log(['git', 'rev-parse', '--short', 'HEAD'], quiet=waflib.Context.BOTH).strip()
timestamp = ctx.cmd_and_log(['git', 'log', '-1', '--format=%ct', 'HEAD'], quiet=waflib.Context.BOTH).strip()
except Exception:
waflib.Logs.warn('get_git_version: unable to determine git revision')
tag, commit, timestamp = ("?", "?", "1")
# Validate that git tag follows the required form:
# See https://github.com/pebble/tintin/wiki/Firmware,-PRF-&-Bootloader-Versions
# Note: version_regex.groups() returns sequence ('0', '0', '0', 'suffix'):
version_regex = re.search("^v(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:(?:-)(.+))?$", tag)
if version_regex:
# Get version numbers from version_regex.groups() sequence and replace None values with 0
# e.g. v2-beta11 => ('2', None, None, 'beta11') => ('2', '0', '0')
version = [x if x else '0' for x in version_regex.groups()[:3]]
else:
waflib.Logs.warn('get_git_revision: Invalid git tag! '
'Must follow this form: `v0[.0[.0]][-suffix]`')
version = ['0', '0', '0', 'unknown']
return {'TAG': tag,
'COMMIT': commit,
'TIMESTAMP': timestamp,
'MAJOR_VERSION': version[0],
'MINOR_VERSION': version[1],
'PATCH_VERSION': version[2]}

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.
from waflib import Utils, Errors
from waflib.TaskGen import after, feature
@after('apply_link')
@feature('cprogram', 'cshlib')
def process_ldscript(self):
if not getattr(self, 'ldscript', None) or self.env.CC_NAME != 'gcc':
return
node = self.path.find_resource(self.ldscript)
if not node:
raise Errors.WafError('could not find %r' % self.ldscript)
self.link_task.env.append_value('LINKFLAGS', '-T%s' % node.abspath())
self.link_task.dep_nodes.append(node)

View file

@ -0,0 +1,67 @@
#!/usr/bin/python
# 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.
# Grygoriy Fuchedzhy 2010
"""
Support for converting linked targets to ihex, srec or binary files using
objcopy. Use the 'objcopy' feature in conjuction with the 'cc' or 'cxx'
feature. The 'objcopy' feature uses the following attributes:
objcopy_bfdname Target object format name (eg. ihex, srec, binary).
Defaults to ihex.
objcopy_target File name used for objcopy output. This defaults to the
target name with objcopy_bfdname as extension.
objcopy_install_path Install path for objcopy_target file. Defaults to ${PREFIX}/fw.
objcopy_flags Additional flags passed to objcopy.
"""
from waflib.Utils import def_attrs
from waflib import Task
from waflib.TaskGen import feature, after_method
class objcopy(Task.Task):
run_str = '${OBJCOPY} -O ${TARGET_BFDNAME} ${OBJCOPYFLAGS} ${SRC} ${TGT}'
color = 'CYAN'
@feature('objcopy')
@after_method('apply_link')
def objcopy(self):
def_attrs(self,
objcopy_bfdname = 'ihex',
objcopy_target = None,
objcopy_install_path = "${PREFIX}/firmware",
objcopy_flags = '')
link_output = self.link_task.outputs[0]
if not self.objcopy_target:
self.objcopy_target = link_output.change_ext('.' + self.objcopy_bfdname).name
task = self.create_task('objcopy',
src=link_output,
tgt=self.path.find_or_declare(self.objcopy_target))
task.env.append_unique('TARGET_BFDNAME', self.objcopy_bfdname)
try:
task.env.append_unique('OBJCOPYFLAGS', getattr(self, 'objcopy_flags'))
except AttributeError:
pass
if self.objcopy_install_path:
self.bld.install_files(self.objcopy_install_path,
task.outputs[0],
env=task.env.derive())
def configure(ctx):
objcopy = ctx.find_program('objcopy', var='OBJCOPY', mandatory=True)

134
platform/snowy/boot/wscript Normal file
View file

@ -0,0 +1,134 @@
import os
import sys
import waflib.Logs
sys.path.append(os.path.abspath('waftools'))
import gitinfo
def options(opt):
opt.load('compiler_c')
opt.add_option('--board', action='store', default='snowy_bb',
help='Which board to build for '
'(snowy_bb|snowy_evt|snowy_evt2|spalding)',
choices=[ 'snowy_bb',
'snowy_bb2', # Alias for snowy_evt2
'snowy_dvt', # Alias for snowy_evt2
'snowy_s3',
'snowy_evt',
'snowy_evt2',
'spalding'])
opt.add_option('--nowatchdog', action='store_true',
help='Do not enable watchdog timer or reset upon failure')
opt.add_option('--loglevel', type='int', default=0,
help='Set the logging verbosity [default: %default]')
opt.add_option('--big-bootloader', action='store_true',
help='Build for boards with an unprogrammed FPGA, loading '
'and launching firmware at 0x08010000')
def configure(conf):
if conf.options.board in ('snowy_bb2', 'snowy_dvt', 'snowy_s3'):
conf.options.board = 'snowy_evt2'
CPU_FLAGS = [
'-mcpu=cortex-m4', '-mthumb',
'-mfpu=fpv4-sp-d16', '-mfloat-abi=softfp',
]
OPT_FLAGS = [ '-Os' ,'-g' ]
C_FLAGS = [
'-std=c11', '-ffunction-sections',
'-Wall', '-Wextra', '-Werror', '-Wpointer-arith',
'-Wno-unused-parameter', '-Wno-missing-field-initializers',
'-Wno-error=unused-function', '-Wno-error=unused-variable',
'-Wno-error=unused-parameter', '-Wno-error=unused-but-set-variable',
'-Wno-packed-bitfield-compat'
]
conf.find_program('arm-none-eabi-gcc', var='CC', mandatory=True)
conf.env.AS = conf.env.CC
for tool in 'ar objcopy'.split():
conf.find_program('arm-none-eabi-' + tool, var=tool.upper(),
mandatory=True)
conf.env.BOARD = conf.options.board
conf.env.MICRO_FAMILY = 'STM32F4'
conf.env.append_unique('CFLAGS', CPU_FLAGS + OPT_FLAGS + C_FLAGS)
conf.env.append_unique('DEFINES', [
'_REENT_SMALL=1',
'USE_STDPERIPH_DRIVER=1',
'BOARD_' + conf.options.board.upper(),
'MICRO_FAMILY_' + conf.env.MICRO_FAMILY,
])
conf.env.append_unique('LINKFLAGS',
['-Wl,--cref', '-Wl,--gc-sections', '-specs=nano.specs']
+ CPU_FLAGS + OPT_FLAGS)
if conf.options.nowatchdog:
conf.env.append_unique('DEFINES', 'NO_WATCHDOG')
if conf.options.loglevel >= 1:
conf.env.append_unique('DEFINES', 'PBL_LOG_ENABLED')
if conf.options.loglevel >= 2:
conf.env.append_unique('DEFINES', 'VERBOSE_LOGGING')
if conf.options.big_bootloader:
conf.env.append_unique('DEFINES', 'BLANK_FPGA')
conf.env.BOOTLOADER_LENGTH = '65536'
else:
conf.env.BOOTLOADER_LENGTH = '16384'
conf.env.append_unique(
'DEFINES', 'BOOTLOADER_LENGTH=' + conf.env.BOOTLOADER_LENGTH)
conf.load('gcc gas objcopy ldscript')
conf.load('binary_header')
conf.load('file_name_c_define')
conf.recurse('vendor')
def build(bld):
if bld.cmd == 'install':
raise Exception("install isn't a supported command. Did you mean flash?")
linkflags = ['-Wl,-Map,snowy_boot.map']
sources = (
bld.path.ant_glob('src/*.S') + bld.path.ant_glob('src/**/*.c') )
bld(features='subst',
source='src/git_version.auto.h.in',
target='src/git_version.auto.h',
**gitinfo.get_git_revision(bld))
_generate_fpga_header(bld)
bld.recurse('vendor')
bld(features='subst',
source='src/stm32f_flash_boot.ld.in',
target='src/stm32f_flash_boot.ld')
bld(features='c asm cprogram objcopy',
source=sources,
includes='src',
target='snowy_boot.elf',
ldscript='src/stm32f_flash_boot.ld',
linkflags=linkflags,
objcopy_bfdname='ihex',
objcopy_target='snowy_boot.hex',
use='stm32_stdlib')
def _generate_fpga_header(bld):
# This is temporary. The bootloader FPGA image will not need to be included
# in the bootloader once it is burned into the FPGA's NVCM.
if bld.env.BOARD in ('snowy_bb', 'snowy_evt'):
fpga_src = 'snowy_bb1_boot.fpga'
elif bld.env.BOARD in ('snowy_evt2',):
fpga_src = 'snowy_boot.fpga'
elif bld.env.BOARD in ('spalding',):
fpga_src = 'spalding_boot.fpga'
else:
fpga_src = None
if fpga_src:
bld(features='binary_header',
source='../' + fpga_src,
target='src/drivers/display/bootloader_fpga_bitstream.auto.h',
array_name='s_fpga_bitstream')
# vim:filetype=python