mirror of
https://github.com/google/pebble.git
synced 2025-06-20 00:00:36 +00:00
Import of the watch repository from Pebble
This commit is contained in:
commit
3b92768480
10334 changed files with 2564465 additions and 0 deletions
50
platform/snowy/boot/.gitignore
vendored
Normal file
50
platform/snowy/boot/.gitignore
vendored
Normal 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
|
47
platform/snowy/boot/boot-bin-update.sh
Executable file
47
platform/snowy/boot/boot-bin-update.sh
Executable 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
16
platform/snowy/boot/desym.sh
Executable 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
24
platform/snowy/boot/flash
Executable 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
11
platform/snowy/boot/gdb
Executable 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
|
11
platform/snowy/boot/openocd_bb2_ftdi.cfg
Normal file
11
platform/snowy/boot/openocd_bb2_ftdi.cfg
Normal 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
|
51
platform/snowy/boot/run-tests.sh
Executable file
51
platform/snowy/boot/run-tests.sh
Executable 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
|
42
platform/snowy/boot/spalding-bin-update.sh
Executable file
42
platform/snowy/boot/spalding-bin-update.sh
Executable 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
|
270
platform/snowy/boot/src/board/board.h
Normal file
270
platform/snowy/boot/src/board/board.h
Normal 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"
|
29
platform/snowy/boot/src/board/board_definitions.h
Normal file
29
platform/snowy/boot/src/board/board_definitions.h
Normal 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
|
160
platform/snowy/boot/src/board/board_snowy_bb.h
Normal file
160
platform/snowy/boot/src/board/board_snowy_bb.h
Normal 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 },
|
||||
};
|
202
platform/snowy/boot/src/board/board_snowy_evt.h
Normal file
202
platform/snowy/boot/src/board/board_snowy_evt.h
Normal 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 },
|
||||
};
|
158
platform/snowy/boot/src/board/board_snowy_evt2.h
Normal file
158
platform/snowy/boot/src/board/board_snowy_evt2.h
Normal 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 },
|
||||
};
|
81
platform/snowy/boot/src/boot_tests.c
Normal file
81
platform/snowy/boot/src/boot_tests.c
Normal 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();
|
||||
}
|
22
platform/snowy/boot/src/boot_tests.h
Normal file
22
platform/snowy/boot/src/boot_tests.h
Normal 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);
|
31
platform/snowy/boot/src/drivers/button.h
Normal file
31
platform/snowy/boot/src/drivers/button.h
Normal 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);
|
41
platform/snowy/boot/src/drivers/button_id.h
Normal file
41
platform/snowy/boot/src/drivers/button_id.h
Normal 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
|
44
platform/snowy/boot/src/drivers/crc.h
Normal file
44
platform/snowy/boot/src/drivers/crc.h
Normal 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);
|
38
platform/snowy/boot/src/drivers/dbgserial.h
Normal file
38
platform/snowy/boot/src/drivers/dbgserial.h
Normal 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)));
|
34
platform/snowy/boot/src/drivers/display.h
Normal file
34
platform/snowy/boot/src/drivers/display.h
Normal 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);
|
188
platform/snowy/boot/src/drivers/display/ice40lp.c
Normal file
188
platform/snowy/boot/src/drivers/display/ice40lp.c
Normal 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);
|
||||
}
|
48
platform/snowy/boot/src/drivers/display/ice40lp.h
Normal file
48
platform/snowy/boot/src/drivers/display/ice40lp.h
Normal 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);
|
288
platform/snowy/boot/src/drivers/display/snowy_boot_fpga.c
Normal file
288
platform/snowy/boot/src/drivers/display/snowy_boot_fpga.c
Normal 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();
|
||||
}
|
46
platform/snowy/boot/src/drivers/exti.h
Normal file
46
platform/snowy/boot/src/drivers/exti.h
Normal 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"
|
27
platform/snowy/boot/src/drivers/exti.inl.h
Normal file
27
platform/snowy/boot/src/drivers/exti.inl.h
Normal 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);
|
||||
}
|
42
platform/snowy/boot/src/drivers/flash.h
Normal file
42
platform/snowy/boot/src/drivers/flash.h
Normal 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);
|
176
platform/snowy/boot/src/drivers/flash/s29vs.c
Normal file
176
platform/snowy/boot/src/drivers/flash/s29vs.c
Normal 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;
|
||||
}
|
63
platform/snowy/boot/src/drivers/flash/s29vs.h
Normal file
63
platform/snowy/boot/src/drivers/flash/s29vs.h
Normal 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;
|
57
platform/snowy/boot/src/drivers/gpio.h
Normal file
57
platform/snowy/boot/src/drivers/gpio.h
Normal 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);
|
86
platform/snowy/boot/src/drivers/i2c.h
Normal file
86
platform/snowy/boot/src/drivers/i2c.h
Normal 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);
|
37
platform/snowy/boot/src/drivers/periph_config.h
Normal file
37
platform/snowy/boot/src/drivers/periph_config.h
Normal file
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#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);
|
||||
}
|
64
platform/snowy/boot/src/drivers/pmic.h
Normal file
64
platform/snowy/boot/src/drivers/pmic.h
Normal 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);
|
394
platform/snowy/boot/src/drivers/pmic/max14690_pmic.c
Normal file
394
platform/snowy/boot/src/drivers/pmic/max14690_pmic.c
Normal 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, ®ister_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);
|
||||
}
|
30
platform/snowy/boot/src/drivers/spi.h
Normal file
30
platform/snowy/boot/src/drivers/spi.h
Normal 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);
|
97
platform/snowy/boot/src/drivers/stm32_common/button.c
Normal file
97
platform/snowy/boot/src/drivers/stm32_common/button.c
Normal 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;
|
||||
}
|
160
platform/snowy/boot/src/drivers/stm32_common/crc.c
Normal file
160
platform/snowy/boot/src/drivers/stm32_common/crc.c
Normal 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;
|
||||
}
|
206
platform/snowy/boot/src/drivers/stm32_common/dbgserial.c
Normal file
206
platform/snowy/boot/src/drivers/stm32_common/dbgserial.c
Normal 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);
|
||||
}
|
||||
|
80
platform/snowy/boot/src/drivers/stm32_common/gpio.c
Normal file
80
platform/snowy/boot/src/drivers/stm32_common/gpio.c
Normal 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);
|
||||
}
|
772
platform/snowy/boot/src/drivers/stm32_common/i2c.c
Normal file
772
platform/snowy/boot/src/drivers/stm32_common/i2c.c
Normal 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);
|
||||
}
|
||||
}
|
108
platform/snowy/boot/src/drivers/stm32_common/i2c_private.c
Normal file
108
platform/snowy/boot/src/drivers/stm32_common/i2c_private.c
Normal 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});
|
||||
}
|
60
platform/snowy/boot/src/drivers/stm32_common/spi.c
Normal file
60
platform/snowy/boot/src/drivers/stm32_common/spi.c
Normal 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));
|
||||
}
|
131
platform/snowy/boot/src/drivers/stm32_common/system_flash.c
Normal file
131
platform/snowy/boot/src/drivers/stm32_common/system_flash.c
Normal 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;
|
||||
}
|
61
platform/snowy/boot/src/drivers/stm32_common/watchdog.c
Normal file
61
platform/snowy/boot/src/drivers/stm32_common/watchdog.c
Normal 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();
|
||||
}
|
74
platform/snowy/boot/src/drivers/system_flash.h
Normal file
74
platform/snowy/boot/src/drivers/system_flash.h
Normal file
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#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);
|
27
platform/snowy/boot/src/drivers/watchdog.h
Normal file
27
platform/snowy/boot/src/drivers/watchdog.h
Normal 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);
|
21
platform/snowy/boot/src/firmware.h
Normal file
21
platform/snowy/boot/src/firmware.h
Normal 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))
|
96
platform/snowy/boot/src/flash_region.h
Normal file
96
platform/snowy/boot/src/flash_region.h
Normal 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
|
212
platform/snowy/boot/src/fw_copy.c
Normal file
212
platform/snowy/boot/src/fw_copy.c
Normal 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;
|
||||
}
|
25
platform/snowy/boot/src/fw_copy.h
Normal file
25
platform/snowy/boot/src/fw_copy.h
Normal 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);
|
||||
|
9
platform/snowy/boot/src/git_version.auto.h.in
Normal file
9
platform/snowy/boot/src/git_version.auto.h.in
Normal 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@"
|
||||
|
48
platform/snowy/boot/src/hardfault_handler.c
Normal file
48
platform/snowy/boot/src/hardfault_handler.c
Normal 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");
|
||||
}
|
432
platform/snowy/boot/src/main.c
Normal file
432
platform/snowy/boot/src/main.c
Normal 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();
|
||||
}
|
34
platform/snowy/boot/src/pebble_errors.h
Normal file
34
platform/snowy/boot/src/pebble_errors.h
Normal 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;
|
180
platform/snowy/boot/src/stm32f_flash_boot.ld.in
Normal file
180
platform/snowy/boot/src/stm32f_flash_boot.ld.in
Normal 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) }
|
||||
}
|
76
platform/snowy/boot/src/system/bootbits.c
Normal file
76
platform/snowy/boot/src/system/bootbits.c
Normal 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);
|
||||
}
|
54
platform/snowy/boot/src/system/bootbits.h
Normal file
54
platform/snowy/boot/src/system/bootbits.h
Normal 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);
|
34
platform/snowy/boot/src/system/die.c
Normal file
34
platform/snowy/boot/src/system/die.c
Normal 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();
|
||||
}
|
21
platform/snowy/boot/src/system/die.h
Normal file
21
platform/snowy/boot/src/system/die.h
Normal 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));
|
29
platform/snowy/boot/src/system/firmware_storage.c
Normal file
29
platform/snowy/boot/src/system/firmware_storage.c
Normal 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);
|
||||
}
|
33
platform/snowy/boot/src/system/firmware_storage.h
Normal file
33
platform/snowy/boot/src/system/firmware_storage.h
Normal 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);
|
69
platform/snowy/boot/src/system/logging.h
Normal file
69
platform/snowy/boot/src/system/logging.h
Normal 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
|
105
platform/snowy/boot/src/system/passert.c
Normal file
105
platform/snowy/boot/src/system/passert.c
Normal 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);
|
||||
}
|
67
platform/snowy/boot/src/system/passert.h
Normal file
67
platform/snowy/boot/src/system/passert.h
Normal 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)
|
35
platform/snowy/boot/src/system/reset.c
Normal file
35
platform/snowy/boot/src/system/reset.c
Normal 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();
|
||||
}
|
32
platform/snowy/boot/src/system/reset.h
Normal file
32
platform/snowy/boot/src/system/reset.h
Normal 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));
|
33
platform/snowy/boot/src/system/rtc_registers.h
Normal file
33
platform/snowy/boot/src/system/rtc_registers.h
Normal 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
|
50
platform/snowy/boot/src/util/attributes.h
Normal file
50
platform/snowy/boot/src/util/attributes.h
Normal 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__)))
|
103
platform/snowy/boot/src/util/bitset.h
Normal file
103
platform/snowy/boot/src/util/bitset.h
Normal 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
|
51
platform/snowy/boot/src/util/cobs.c
Normal file
51
platform/snowy/boot/src/util/cobs.c
Normal 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;
|
||||
}
|
40
platform/snowy/boot/src/util/cobs.h
Normal file
40
platform/snowy/boot/src/util/cobs.h
Normal 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);
|
48
platform/snowy/boot/src/util/crc32.c
Normal file
48
platform/snowy/boot/src/util/crc32.c
Normal 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;
|
||||
}
|
67
platform/snowy/boot/src/util/crc32.h
Normal file
67
platform/snowy/boot/src/util/crc32.h
Normal 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)
|
44
platform/snowy/boot/src/util/delay.c
Normal file
44
platform/snowy/boot/src/util/delay.c
Normal 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);
|
||||
}
|
||||
}
|
28
platform/snowy/boot/src/util/delay.h
Normal file
28
platform/snowy/boot/src/util/delay.h
Normal 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);
|
201
platform/snowy/boot/src/util/misc.c
Normal file
201
platform/snowy/boot/src/util/misc.c
Normal 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__
|
107
platform/snowy/boot/src/util/misc.h
Normal file
107
platform/snowy/boot/src/util/misc.h
Normal 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))))) \
|
||||
)
|
80
platform/snowy/boot/src/util/net.h
Normal file
80
platform/snowy/boot/src/util/net.h
Normal 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) };
|
||||
}
|
178
platform/snowy/boot/src/util/version.c
Normal file
178
platform/snowy/boot/src/util/version.c
Normal 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);
|
||||
}
|
97
platform/snowy/boot/src/util/version.h
Normal file
97
platform/snowy/boot/src/util/version.h
Normal 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);
|
251
platform/snowy/boot/test/test_system_flash.c
Normal file
251
platform/snowy/boot/test/test_system_flash.c
Normal 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
25
platform/snowy/boot/vendor/wscript
vendored
Normal 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
|
137
platform/snowy/boot/waftools/binary_header.py
Normal file
137
platform/snowy/boot/waftools/binary_header.py
Normal 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 = []
|
26
platform/snowy/boot/waftools/file_name_c_define.py
Normal file
26
platform/snowy/boot/waftools/file_name_c_define.py
Normal 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)
|
45
platform/snowy/boot/waftools/gitinfo.py
Normal file
45
platform/snowy/boot/waftools/gitinfo.py
Normal 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]}
|
28
platform/snowy/boot/waftools/ldscript.py
Normal file
28
platform/snowy/boot/waftools/ldscript.py
Normal 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)
|
67
platform/snowy/boot/waftools/objcopy.py
Normal file
67
platform/snowy/boot/waftools/objcopy.py
Normal 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
134
platform/snowy/boot/wscript
Normal 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
|
Loading…
Add table
Add a link
Reference in a new issue