Import of the watch repository from Pebble

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

View file

@ -0,0 +1,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.
*/
#pragma once
#include "display.h"
#include "drivers/button_id.h"
#include "stm32f7xx.h"
#include <stdint.h>
#include <stdbool.h>
#define GPIO_Port_NULL ((GPIO_TypeDef *) 0)
#define GPIO_Pin_NULL ((uint16_t)0x0000)
// This is generated in order to faciliate the check within the IRQ_MAP macro below
enum {
#define IRQ_DEF(num, irq) IS_VALID_IRQ__##irq,
#include "irq_stm32f7.def"
#undef IRQ_DEF
};
//! Creates a trampoline to the interrupt handler defined within the driver
#define IRQ_MAP(irq, handler, device) \
void irq##_IRQHandler(void) { \
handler(device); \
} \
_Static_assert(IS_VALID_IRQ__##irq || true, "(See comment below)")
/*
* The above static assert checks that the requested IRQ is valid by checking that the enum
* value (generated above) is declared. The static assert itself will not trip, but you will get
* a compilation error from that line if the IRQ does not exist within irq_stm32*.def.
*/
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 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 {
InputConfig input;
GPIOPuPd_TypeDef pupd;
} ButtonConfig;
// Button Configuration
/////////////////////////////////////////////////////////////////////////////
typedef struct {
const ButtonConfig buttons[NUM_BUTTONS];
} BoardConfigButton;
// Power Configuration
/////////////////////////////////////////////////////////////////////////////
typedef struct {
//! Voltage rail control lines
const OutputConfig rail_4V5_ctrl;
const OutputConfig rail_6V6_ctrl;
} BoardConfigPower;
typedef struct {
OutputConfig reset_gpio;
} BoardConfigFlash;
typedef struct {
const OutputConfig power_en; //< Enable power supply to the accessory connector.
} BoardConfigAccessory;
typedef const struct SPIBus SPIBus;
typedef const struct SPISlavePort SPISlavePort;
typedef const struct I2CBus I2CBus;
typedef const struct I2CSlavePort I2CSlavePort;
typedef const struct ICE40LPDevice ICE40LPDevice;
void board_init(void);
#include "board_definitions.h"

View file

@ -0,0 +1,25 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#if BOARD_ROBERT_BB || BOARD_ROBERT_BB2
#include "board_robert_bb.h"
#elif BOARD_ROBERT_EVT
#include "board_robert_evt.h"
#else
#error "Unknown board definition"
#endif

View file

@ -0,0 +1,114 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "board/board.h"
#include "drivers/i2c/i2c_definitions.h"
#include "drivers/i2c/i2c_hal_definitions.h"
#include "drivers/display/ice40lp_definitions.h"
#include "util/misc.h"
//
// iCE40LP configuration
//
static ICE40LPDevice ICE40LP_DEVICE = {
.spi = {
.periph = SPI6,
.rcc_bit = RCC_APB2Periph_SPI6,
.clk = {
.gpio = GPIOA,
.gpio_pin = GPIO_Pin_5,
.gpio_pin_source = GPIO_PinSource5,
.gpio_af = GPIO_AF8_SPI6
},
.mosi = {
.gpio = GPIOA,
.gpio_pin = GPIO_Pin_7,
.gpio_pin_source = GPIO_PinSource7,
.gpio_af = GPIO_AF8_SPI6
},
.scs = {
.gpio = GPIOA,
.gpio_pin = GPIO_Pin_4,
.active_high = false
}
},
.creset = {
.gpio = GPIOA,
.gpio_pin = GPIO_Pin_3,
.active_high = true,
},
.cdone = {
.gpio = GPIOB,
.gpio_pin = GPIO_Pin_2,
},
.busy = {
.gpio = GPIOB,
.gpio_pin = GPIO_Pin_0,
},
.use_6v6_rail = true,
};
ICE40LPDevice * const ICE40LP = &ICE40LP_DEVICE;
// I2C DEVICES
static I2CBusState I2C_PMIC_MAG_BUS_STATE = {};
static const I2CBusHal I2C_PMIC_MAG_BUS_HAL = {
.i2c = I2C4,
.clock_ctrl = RCC_APB1Periph_I2C4,
.clock_speed = 400000,
.duty_cycle = I2CDutyCycle_16_9,
.ev_irq_channel = I2C4_EV_IRQn,
.er_irq_channel = I2C4_ER_IRQn,
};
static const I2CBus I2C_PMIC_MAG_BUS = {
.state = &I2C_PMIC_MAG_BUS_STATE,
.hal = &I2C_PMIC_MAG_BUS_HAL,
.scl_gpio = {
.gpio = GPIOF,
.gpio_pin = GPIO_Pin_14,
.gpio_pin_source = GPIO_PinSource14,
.gpio_af = GPIO_AF4_I2C4
},
.sda_gpio = {
.gpio = GPIOF,
.gpio_pin = GPIO_Pin_15,
.gpio_pin_source = GPIO_PinSource15,
.gpio_af = GPIO_AF4_I2C4
},
.name = "I2C_PMIC_MAG"
};
static const I2CSlavePort I2C_SLAVE_MAX14690 = {
.bus = &I2C_PMIC_MAG_BUS,
.address = 0x50
};
I2CSlavePort * const I2C_MAX14690 = &I2C_SLAVE_MAX14690;
IRQ_MAP(I2C4_EV, i2c_hal_event_irq_handler, &I2C_PMIC_MAG_BUS);
IRQ_MAP(I2C4_ER, i2c_hal_error_irq_handler, &I2C_PMIC_MAG_BUS);
void board_init(void) {
i2c_init(&I2C_PMIC_MAG_BUS);
}

View file

@ -0,0 +1,132 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
// ----------------------------------------------
// Board definitions for Robert BB (C2 Bigboard)
// ----------------------------------------------
//
#include "util/size.h"
#define BOARD_LSE_MODE RCC_LSE_Bypass
static const BoardConfigButton BOARD_CONFIG_BUTTON = {
.buttons = {
[BUTTON_ID_BACK] = {
.input = {
.gpio = GPIOG,
.gpio_pin = GPIO_Pin_6,
},
.pupd = GPIO_PuPd_UP
},
[BUTTON_ID_UP] = {
.input = {
.gpio = GPIOG,
.gpio_pin = GPIO_Pin_3,
},
.pupd = GPIO_PuPd_NOPULL
},
[BUTTON_ID_SELECT] = {
.input = {
.gpio = GPIOG,
.gpio_pin = GPIO_Pin_5,
},
.pupd = GPIO_PuPd_UP
},
[BUTTON_ID_DOWN] = {
.input = {
.gpio = GPIOG,
.gpio_pin = GPIO_Pin_4,
},
.pupd = GPIO_PuPd_UP
},
},
};
static const BoardConfigPower BOARD_CONFIG_POWER = {
.rail_4V5_ctrl = {
.gpio = GPIOH,
.gpio_pin = GPIO_Pin_5,
.active_high = true,
},
.rail_6V6_ctrl = {
.gpio = GPIOH,
.gpio_pin = GPIO_Pin_3,
.active_high = true,
},
};
static const BoardConfigFlash BOARD_CONFIG_FLASH = {
};
static const BoardConfigAccessory BOARD_CONFIG_ACCESSORY = {
.power_en = { GPIOA, GPIO_Pin_11, true },
};
typedef enum {
QSpiPin_CS,
QSpiPin_SCLK,
QSpiPin_DQ0,
QSpiPin_DQ1,
QSpiPin_DQ2,
QSpiPin_DQ3,
QSpiPinCount,
} QSpiPin;
static const AfConfig BOARD_CONFIG_FLASH_PINS[] = {
[QSpiPin_CS] = {
.gpio = GPIOB,
.gpio_pin = GPIO_Pin_10,
.gpio_pin_source = GPIO_PinSource10,
.gpio_af = GPIO_AF9_QUADSPI,
},
[QSpiPin_SCLK] = {
.gpio = GPIOF,
.gpio_pin = GPIO_Pin_10,
.gpio_pin_source = GPIO_PinSource10,
.gpio_af = GPIO_AF9_QUADSPI,
},
[QSpiPin_DQ0] = {
.gpio = GPIOD,
.gpio_pin = GPIO_Pin_11,
.gpio_pin_source = GPIO_PinSource11,
.gpio_af = GPIO_AF9_QUADSPI,
},
[QSpiPin_DQ1] = {
.gpio = GPIOC,
.gpio_pin = GPIO_Pin_10,
.gpio_pin_source = GPIO_PinSource10,
.gpio_af = GPIO_AF9_QUADSPI,
},
[QSpiPin_DQ2] = {
.gpio = GPIOF,
.gpio_pin = GPIO_Pin_7,
.gpio_pin_source = GPIO_PinSource7,
.gpio_af = GPIO_AF9_QUADSPI,
},
[QSpiPin_DQ3] = {
.gpio = GPIOA,
.gpio_pin = GPIO_Pin_1,
.gpio_pin_source = GPIO_PinSource1,
.gpio_af = GPIO_AF9_QUADSPI,
},
};
extern I2CSlavePort * const I2C_MAX14690;
extern ICE40LPDevice * const ICE40LP;

View file

@ -0,0 +1,114 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "board/board.h"
#include "drivers/i2c/i2c_definitions.h"
#include "drivers/i2c/i2c_hal_definitions.h"
#include "drivers/display/ice40lp_definitions.h"
#include "util/misc.h"
//
// iCE40LP configuration
//
static ICE40LPDevice ICE40LP_DEVICE = {
.spi = {
.periph = SPI6,
.rcc_bit = RCC_APB2Periph_SPI6,
.clk = {
.gpio = GPIOA,
.gpio_pin = GPIO_Pin_5,
.gpio_pin_source = GPIO_PinSource5,
.gpio_af = GPIO_AF8_SPI6
},
.mosi = {
.gpio = GPIOA,
.gpio_pin = GPIO_Pin_7,
.gpio_pin_source = GPIO_PinSource7,
.gpio_af = GPIO_AF8_SPI6
},
.scs = {
.gpio = GPIOA,
.gpio_pin = GPIO_Pin_4,
.active_high = false
}
},
.creset = {
.gpio = GPIOA,
.gpio_pin = GPIO_Pin_3,
.active_high = true,
},
.cdone = {
.gpio = GPIOB,
.gpio_pin = GPIO_Pin_2,
},
.busy = {
.gpio = GPIOB,
.gpio_pin = GPIO_Pin_0,
},
.use_6v6_rail = false,
};
ICE40LPDevice * const ICE40LP = &ICE40LP_DEVICE;
// I2C DEVICES
static I2CBusState I2C_PMIC_MAG_BUS_STATE = {};
static const I2CBusHal I2C_PMIC_MAG_BUS_HAL = {
.i2c = I2C4,
.clock_ctrl = RCC_APB1Periph_I2C4,
.clock_speed = 400000,
.duty_cycle = I2CDutyCycle_16_9,
.ev_irq_channel = I2C4_EV_IRQn,
.er_irq_channel = I2C4_ER_IRQn,
};
static const I2CBus I2C_PMIC_MAG_BUS = {
.state = &I2C_PMIC_MAG_BUS_STATE,
.hal = &I2C_PMIC_MAG_BUS_HAL,
.scl_gpio = {
.gpio = GPIOF,
.gpio_pin = GPIO_Pin_14,
.gpio_pin_source = GPIO_PinSource14,
.gpio_af = GPIO_AF4_I2C4
},
.sda_gpio = {
.gpio = GPIOF,
.gpio_pin = GPIO_Pin_15,
.gpio_pin_source = GPIO_PinSource15,
.gpio_af = GPIO_AF4_I2C4
},
.name = "I2C_PMIC_MAG"
};
static const I2CSlavePort I2C_SLAVE_MAX14690 = {
.bus = &I2C_PMIC_MAG_BUS,
.address = 0x50
};
I2CSlavePort * const I2C_MAX14690 = &I2C_SLAVE_MAX14690;
IRQ_MAP(I2C4_EV, i2c_hal_event_irq_handler, &I2C_PMIC_MAG_BUS);
IRQ_MAP(I2C4_ER, i2c_hal_error_irq_handler, &I2C_PMIC_MAG_BUS);
void board_init(void) {
i2c_init(&I2C_PMIC_MAG_BUS);
}

View file

@ -0,0 +1,133 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
// ----------------------------------------------
// Board definitions for Robert EVT
// ----------------------------------------------
//
#include "util/size.h"
#define BOARD_LSE_MODE RCC_LSE_Bypass
static const BoardConfigButton BOARD_CONFIG_BUTTON = {
.buttons = {
[BUTTON_ID_BACK] = {
.input = {
.gpio = GPIOG,
.gpio_pin = GPIO_Pin_3,
},
.pupd = GPIO_PuPd_NOPULL,
},
[BUTTON_ID_UP] = {
.input = {
.gpio = GPIOG,
.gpio_pin = GPIO_Pin_4,
},
.pupd = GPIO_PuPd_UP,
},
[BUTTON_ID_SELECT] = {
.input = {
.gpio = GPIOG,
.gpio_pin = GPIO_Pin_5,
},
.pupd = GPIO_PuPd_UP,
},
[BUTTON_ID_DOWN] = {
.input = {
.gpio = GPIOG,
.gpio_pin = GPIO_Pin_6,
},
.pupd = GPIO_PuPd_UP,
},
},
};
static const BoardConfigPower BOARD_CONFIG_POWER = {
.rail_4V5_ctrl = {
.gpio = GPIOH,
.gpio_pin = GPIO_Pin_5,
.active_high = true,
},
.rail_6V6_ctrl = { GPIO_Port_NULL },
};
static const BoardConfigFlash BOARD_CONFIG_FLASH = {
.reset_gpio = {
.gpio = GPIOE,
.gpio_pin = GPIO_Pin_15,
.active_high = false,
},
};
static const BoardConfigAccessory BOARD_CONFIG_ACCESSORY = {
.power_en = { GPIOD, GPIO_Pin_2, true },
};
typedef enum {
QSpiPin_CS,
QSpiPin_SCLK,
QSpiPin_DQ0,
QSpiPin_DQ1,
QSpiPin_DQ2,
QSpiPin_DQ3,
QSpiPinCount,
} QSpiPin;
static const AfConfig BOARD_CONFIG_FLASH_PINS[] = {
[QSpiPin_CS] = {
.gpio = GPIOB,
.gpio_pin = GPIO_Pin_10,
.gpio_pin_source = GPIO_PinSource10,
.gpio_af = GPIO_AF9_QUADSPI,
},
[QSpiPin_SCLK] = {
.gpio = GPIOF,
.gpio_pin = GPIO_Pin_10,
.gpio_pin_source = GPIO_PinSource10,
.gpio_af = GPIO_AF9_QUADSPI,
},
[QSpiPin_DQ0] = {
.gpio = GPIOD,
.gpio_pin = GPIO_Pin_11,
.gpio_pin_source = GPIO_PinSource11,
.gpio_af = GPIO_AF9_QUADSPI,
},
[QSpiPin_DQ1] = {
.gpio = GPIOC,
.gpio_pin = GPIO_Pin_10,
.gpio_pin_source = GPIO_PinSource10,
.gpio_af = GPIO_AF9_QUADSPI,
},
[QSpiPin_DQ2] = {
.gpio = GPIOE,
.gpio_pin = GPIO_Pin_2,
.gpio_pin_source = GPIO_PinSource2,
.gpio_af = GPIO_AF9_QUADSPI,
},
[QSpiPin_DQ3] = {
.gpio = GPIOA,
.gpio_pin = GPIO_Pin_1,
.gpio_pin_source = GPIO_PinSource1,
.gpio_af = GPIO_AF9_QUADSPI,
},
};
extern I2CSlavePort * const I2C_MAX14690;
extern ICE40LPDevice * const ICE40LP;

View file

@ -0,0 +1,33 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#define DISP_COLS 200
#define DISP_ROWS 228
#define DISPLAY_FRAMEBUFFER_BYTES (DISP_COLS * DISP_ROWS)
#define DISPLAY_ORIENTATION_COLUMN_MAJOR_INVERTED 0
#define DISPLAY_ORIENTATION_ROTATED_180 0
#define DISPLAY_ORIENTATION_ROW_MAJOR 0
#define DISPLAY_ORIENTATION_ROW_MAJOR_INVERTED 1
#define PBL_BW 0
#define PBL_RECT 1
#define PBL_ROUND 0
#define PBL_COLOR 1

View file

@ -0,0 +1,80 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "boot_tests.h"
#include "board/board.h"
#include "drivers/button.h"
#include "drivers/dbgserial.h"
#include "drivers/flash.h"
#include "system/bootbits.h"
#include "system/rtc_registers.h"
#include "util/misc.h"
#include "stm32f7xx.h"
#include <stdint.h>
#include <inttypes.h>
#include <stdbool.h>
static const int STUCK_BUTTON_THRESHOLD = 5;
bool boot_test_is_button_stuck(void) {
// We store how many times each button has been pressed on previous boots in this
// rtc backup register. Every time when we boot without that button pressed that
// counter gets cleared. If the byte reaches 5, return a failure.
uint32_t button_counter_register = RTC_ReadBackupRegister(STUCK_BUTTON_REGISTER);
uint8_t* button_counter = (uint8_t*) (&button_counter_register);
bool result = false;
for (int button_id = 0; button_id < NUM_BUTTONS; button_id++) {
if (!button_is_pressed(button_id)) {
button_counter[button_id] = 0;
continue;
}
if (button_counter[button_id] >= STUCK_BUTTON_THRESHOLD) {
dbgserial_putstr("Stuck button register is invalid, clearing.");
dbgserial_print_hex(button_counter_register);
RTC_WriteBackupRegister(STUCK_BUTTON_REGISTER, 0);
return false;
}
button_counter[button_id] += 1;
if (button_counter[button_id] >= STUCK_BUTTON_THRESHOLD) {
dbgserial_print("Button id ");
dbgserial_print_hex(button_id);
dbgserial_putstr("is stuck!");
result = true;
}
}
if (button_counter_register != 0) {
dbgserial_print("Button was pushed on boot. Button counter: ");
dbgserial_print_hex(button_counter_register);
dbgserial_newline();
}
RTC_WriteBackupRegister(STUCK_BUTTON_REGISTER, button_counter_register);
return result;
}
bool boot_test_is_flash_broken(void) {
return !flash_sanity_check();
}

View file

@ -0,0 +1,22 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <stdbool.h>
bool boot_test_is_button_stuck(void);
bool boot_test_is_flash_broken(void);

View file

@ -0,0 +1,49 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "drivers/button.h"
#include "board/board.h"
#include "drivers/periph_config.h"
#include "drivers/gpio.h"
static void prv_initialize_button(const ButtonConfig* config) {
// Configure the pin itself
gpio_input_init_pull_up_down(&config->input, config->pupd);
}
bool button_is_pressed(ButtonId id) {
const ButtonConfig* button_config = &BOARD_CONFIG_BUTTON.buttons[id];
return !gpio_input_read(&button_config->input);
}
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();
for (int i = 0; i < NUM_BUTTONS; ++i) {
prv_initialize_button(&BOARD_CONFIG_BUTTON.buttons[i]);
}
periph_config_release_lock();
}

View file

@ -0,0 +1,27 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "button_id.h"
#include <stdbool.h>
#include <stdint.h>
void button_init(void);
bool button_is_pressed(ButtonId id);
uint8_t button_get_state_bits(void);

View file

@ -0,0 +1,41 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
//! @addtogroup UI
//! @{
//! @addtogroup Clicks
//! \brief Dealing with button input
//! @{
//! Button ID values
//! @see \ref click_recognizer_get_button_id()
typedef enum {
//! Back button
BUTTON_ID_BACK = 0,
//! Up button
BUTTON_ID_UP,
//! Select (middle) button
BUTTON_ID_SELECT,
//! Down button
BUTTON_ID_DOWN,
//! Total number of buttons
NUM_BUTTONS
} ButtonId;
//! @} // end addtogroup Clicks
//! @} // end addtogroup UI

View file

@ -0,0 +1,179 @@
/*
* 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 "dbgserial.h"
#include "drivers/gpio.h"
#include "drivers/periph_config.h"
#include "stm32f7haxx_rcc.h"
#include "stm32f7haxx_gpio.h"
#include "util/attributes.h"
#include "util/cobs.h"
#include "util/crc32.h"
#include "util/net.h"
#include "util/misc.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 const int SERIAL_BAUD_RATE = 1000000;
static USART_TypeDef *const DBGSERIAL_UART = USART3;
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) {
// Enable GPIO and UART3 peripheral clocks
periph_config_enable(GPIOD, RCC_AHB1Periph_GPIOD);
periph_config_enable(DBGSERIAL_UART, RCC_APB1Periph_USART3);
DBGSERIAL_UART->CR1 &= ~USART_CR1_UE;
AfConfig tx_cfg = {
.gpio = GPIOD,
.gpio_pin = GPIO_Pin_8,
.gpio_pin_source = GPIO_PinSource8,
.gpio_af = GPIO_AF7_USART3
};
gpio_af_init(&tx_cfg, GPIO_OType_PP, GPIO_Speed_50MHz, GPIO_PuPd_NOPULL);
AfConfig rx_cfg = {
.gpio = GPIOD,
.gpio_pin = GPIO_Pin_9,
.gpio_pin_source = GPIO_PinSource9,
.gpio_af = GPIO_AF7_USART3
};
gpio_af_init(&rx_cfg, GPIO_OType_PP, GPIO_Speed_50MHz, GPIO_PuPd_NOPULL);
// configure the UART peripheral control registers and baud rate
// - 8-bit word length
// - no parity
// - RX / TX enabled
// - 1 stop bit
// - no flow control
const int k_div_precision = 100;
RCC_ClocksTypeDef clocks;
RCC_GetClocksFreq(&clocks);
// calculate the baud rate value
const uint64_t scaled_apbclock = k_div_precision * clocks.PCLK1_Frequency;
const uint32_t div = (scaled_apbclock / SERIAL_BAUD_RATE);
const uint32_t brr = (div & 0xFFF0) | ((div & 0xF) >> 1);
DBGSERIAL_UART->BRR = brr / k_div_precision;
DBGSERIAL_UART->CR2 = 0;
DBGSERIAL_UART->CR3 = 0;
DBGSERIAL_UART->CR1 = USART_CR1_RE | USART_CR1_TE | USART_CR1_UE;
}
static void prv_putchar(uint8_t c) {
while ((DBGSERIAL_UART->ISR & USART_ISR_TXE) == 0) continue;
DBGSERIAL_UART->TDR = c;
while ((DBGSERIAL_UART->ISR & USART_ISR_TXE) == 0) continue;
}
void dbgserial_print(const char* str) {
for (; *str && s_message_length < MAX_MESSAGE; ++str) {
if (*str == '\n') {
dbgserial_newline();
} else if (*str != '\r') {
s_message_buffer[s_message_length++] = *str;
}
}
}
void dbgserial_newline(void) {
uint32_t crc;
size_t raw_length = sizeof(PulseFrame) + sizeof(PushPacket) +
sizeof(s_message_header) + s_message_length + sizeof(crc);
unsigned char raw_packet[raw_length];
PulseFrame *frame = (PulseFrame *)raw_packet;
frame->protocol = hton16(PULSE_TRANSPORT_PUSH);
PushPacket *transport = (PushPacket *)frame->information;
transport->protocol = hton16(PULSE_PROTOCOL_LOGGING);
transport->length = hton16(sizeof(PushPacket) + sizeof(s_message_header) +
s_message_length);
unsigned char *app = transport->information;
memcpy(app, s_message_header, sizeof(s_message_header));
memcpy(&app[sizeof(s_message_header)], s_message_buffer,
s_message_length);
crc = crc32(CRC32_INIT, raw_packet, raw_length - sizeof(crc));
memcpy(&raw_packet[raw_length - sizeof(crc)], &crc, sizeof(crc));
unsigned char cooked_packet[MAX_SIZE_AFTER_COBS_ENCODING(raw_length)];
size_t cooked_length = cobs_encode(cooked_packet, raw_packet, raw_length);
prv_putchar(FRAME_DELIMITER);
for (size_t i = 0; i < cooked_length; ++i) {
if (cooked_packet[i] == FRAME_DELIMITER) {
prv_putchar('\0');
} else {
prv_putchar(cooked_packet[i]);
}
}
prv_putchar(FRAME_DELIMITER);
s_message_length = 0;
}
void dbgserial_putstr(const char* str) {
dbgserial_print(str);
dbgserial_newline();
}
void dbgserial_print_hex(uint32_t value) {
char buf[12];
itoa_hex(value, buf, sizeof(buf));
dbgserial_print(buf);
}

View file

@ -0,0 +1,34 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <stdarg.h>
#include <stdint.h>
void dbgserial_init(void);
void dbgserial_putstr(const char* str);
void dbgserial_newline(void);
//! Like dbgserial_putstr, but without a terminating newline
void dbgserial_print(const char* str);
void dbgserial_print_hex(uint32_t value);
void dbgserial_putstr_fmt(char* buffer, unsigned int buffer_size, const char* fmt, ...)
__attribute__((format(printf, 3, 4)));

View file

@ -0,0 +1,34 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <stdint.h>
void display_init(void);
void display_boot_splash(void);
void display_error_code(uint32_t);
//! Do whatever is necessary to prevent visual artifacts when resetting
//! the watch.
void display_prepare_for_reset(void);
//! Display the progress of a firmware update.
//!
//! The progress is expressed as a rational number less than or equal to 1.
//! When numerator == denominator, the progress indicator shows that the update
//! is complete.
void display_firmware_update_progress(uint32_t numerator, uint32_t denominator);

View file

@ -0,0 +1,210 @@
/*
* 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/display/ice40lp_definitions.h"
#include "system/passert.h"
#include "util/delay.h"
#include "util/misc.h"
#include "util/sle.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 (47)
static uint8_t s_decoded_fpga_image[35000]; // the FPGA image is currently ~30k
static bool prv_reset_fpga(void) {
const uint32_t length = sle_decode(s_fpga_bitstream, sizeof(s_fpga_bitstream),
s_decoded_fpga_image, sizeof(s_decoded_fpga_image));
return display_program(s_decoded_fpga_image, length);
}
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) {
display_write_cmd(CMD_DISPLAY_ON, NULL, 0);
}
static void prv_screen_off(void) {
display_write_cmd(CMD_DISPLAY_OFF, NULL, 0);
}
static void prv_draw_scene(uint8_t scene) {
display_write_cmd(CMD_DRAW_SCENE, &scene, sizeof(scene));
}
static void prv_set_parameter(uint32_t param) {
display_write_cmd(CMD_SET_PARAMETER, (uint8_t *)&param, sizeof(param));
}
#ifdef DISPLAY_DEMO_LOOP
static void prv_play_demo_loop(void) {
while (1) {
for (int i = 0; i <= UPDATE_PROGRESS_MAX; ++i) {
display_firmware_update_progress(i, UPDATE_PROGRESS_MAX);
delay_ms(80);
}
for (uint32_t i = 0; i <= 0xf; ++i) {
display_error_code(i * 0x11111111);
delay_ms(200);
}
for (uint32_t i = 0; i < 8; ++i) {
for (uint32_t j = 1; j <= 0xf; ++j) {
display_error_code(j << (i * 4));
delay_ms(200);
}
}
display_error_code(0x01234567);
delay_ms(200);
display_error_code(0x89abcdef);
delay_ms(200);
display_error_code(0xcafebabe);
delay_ms(200);
display_error_code(0xfeedface);
delay_ms(200);
display_error_code(0x8badf00d);
delay_ms(200);
display_error_code(0xbad1ce40);
delay_ms(200);
display_error_code(0xbeefcace);
delay_ms(200);
display_error_code(0x0defaced);
delay_ms(200);
display_error_code(0xd15ea5e5);
delay_ms(200);
display_error_code(0xdeadbeef);
delay_ms(200);
display_boot_splash();
delay_ms(1000);
}
}
#endif
void display_init(void) {
display_start();
if (!prv_reset_fpga()) {
dbgserial_putstr("FPGA configuration failed.");
return;
}
// enable the power rails
display_power_enable();
// start with the screen off
prv_screen_off();
// 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 <= 10; ++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.");
#ifdef DISPLAY_DEMO_LOOP
prv_play_demo_loop();
#endif
return;
}
if (!prv_reset_fpga()) {
dbgserial_putstr("FPGA configuration failed.");
return;
}
}
// It's taken too many attempts and the FPGA still isn't behaving. Give up on
// showing the splash screen and keep the screen off so that the user doesn't
// see a broken-looking staticky screen on boot.
dbgserial_putstr("Display initialization failed.");
prv_screen_off();
}
void display_boot_splash(void) {
prv_wait_busy();
prv_draw_scene(SCENE_SPLASH);
// Don't turn the screen on until the boot-splash is fully drawn.
prv_wait_busy();
prv_screen_on();
}
void display_firmware_update_progress(
uint32_t numerator, uint32_t denominator) {
static uint8_t last_bar_fill = UINT8_MAX;
// Scale progress to the number of pixels in the progress bar,
// rounding half upwards.
uint8_t bar_fill =
((numerator * UPDATE_PROGRESS_MAX) + ((denominator+1)/2)) / denominator;
// Don't waste time and power redrawing the same screen repeatedly.
if (bar_fill != last_bar_fill) {
last_bar_fill = bar_fill;
prv_set_parameter(bar_fill);
prv_draw_scene(SCENE_UPDATE);
}
}
void display_error_code(uint32_t error_code) {
prv_set_parameter(error_code);
prv_draw_scene(SCENE_ERROR);
}
void display_prepare_for_reset(void) {
prv_screen_off();
}

View file

@ -0,0 +1,186 @@
/*
* 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/dbgserial.h"
#include "drivers/display/ice40lp_definitions.h"
#include "drivers/gpio.h"
#include "drivers/periph_config.h"
#include "drivers/pmic.h"
#include "system/logging.h"
#include "system/passert.h"
#include "util/delay.h"
#include "stm32f7xx.h"
#include "misc.h"
#include <string.h>
static void prv_spi_init(void) {
// Configure the GPIO (SCLK, MOSI - no MISO since the SPI is TX-only)
gpio_af_init(&ICE40LP->spi.clk, GPIO_OType_PP, GPIO_Speed_25MHz, GPIO_PuPd_NOPULL);
gpio_af_init(&ICE40LP->spi.mosi, GPIO_OType_PP, GPIO_Speed_25MHz, GPIO_PuPd_NOPULL);
// Reset the SPI peripheral and enable the clock
RCC_APB2PeriphResetCmd(ICE40LP->spi.rcc_bit, ENABLE);
RCC_APB2PeriphResetCmd(ICE40LP->spi.rcc_bit, DISABLE);
periph_config_enable(ICE40LP->spi.periph, ICE40LP->spi.rcc_bit);
// Configure CR1 first:
// * TX-only mode (BIDIMODE | BIDIOE)
// * software control NSS pin (SSM | SSI)
// * master mode (MSTR)
// * clock polarity high / 2nd edge (CPOL | CPHA)
ICE40LP->spi.periph->CR1 = SPI_CR1_BIDIMODE | SPI_CR1_BIDIOE | SPI_CR1_SSM | SPI_CR1_SSI |
SPI_CR1_MSTR | SPI_CR1_CPOL | SPI_CR1_CPHA;
// Configure CR2:
// * 8-bit data size (DS[4:0] == 0b0111)
// * 1/4 RX threshold (for 8-bit transfers)
ICE40LP->spi.periph->CR2 = SPI_CR2_DS_0 | SPI_CR2_DS_1 | SPI_CR2_DS_2 | SPI_CR2_FRXTH;
// enable the SPI
ICE40LP->spi.periph->CR1 |= SPI_CR1_SPE;
}
static void prv_spi_write(const uint8_t *data, uint32_t len) {
for (uint32_t i = 0; i < len; ++i) {
// Wait until we can transmit.
while (!(ICE40LP->spi.periph->SR & SPI_SR_TXE)) continue;
// Write a byte. STM32F7 needs to access as 8 bits in order to actually do 8 bits.
*(volatile uint8_t*)&ICE40LP->spi.periph->DR = data[i];
}
// Wait until the TX FIFO is empty plus an extra little bit for the shift-register.
while (((ICE40LP->spi.periph->SR & SPI_SR_FTLVL) >> __builtin_ctz(SPI_SR_FTLVL)) > 0) continue;
delay_us(10);
}
bool display_busy(void) {
return gpio_input_read(&ICE40LP->busy);
}
void display_start(void) {
// Configure SCS before CRESET and before configuring the SPI so that we don't end up with the
// FPGA in the "SPI Master Configuration Interface" on bigboards which don't have NVCM. If we end
// up in this mode, the FPGA will drive the clock and put the SPI peripheral in a bad state.
gpio_output_init(&ICE40LP->spi.scs, GPIO_OType_PP, GPIO_Speed_25MHz);
gpio_output_set(&ICE40LP->spi.scs, false);
gpio_input_init(&ICE40LP->cdone);
gpio_input_init(&ICE40LP->busy);
gpio_output_init(&ICE40LP->creset, GPIO_OType_OD, GPIO_Speed_25MHz);
prv_spi_init();
}
bool display_program(const uint8_t *fpga_bitstream, uint32_t bitstream_size) {
InputConfig creset_input = {
.gpio = ICE40LP->creset.gpio,
.gpio_pin = ICE40LP->creset.gpio_pin,
};
delay_ms(1);
gpio_output_set(&ICE40LP->spi.scs, true); // SCS asserted (low)
gpio_output_set(&ICE40LP->creset, false); // CRESET LOW
delay_ms(1);
if (gpio_input_read(&creset_input)) {
dbgserial_putstr("CRESET not low during reset");
return false;
}
gpio_output_set(&ICE40LP->creset, true); // CRESET -> HIGH
delay_ms(1);
if (gpio_input_read(&ICE40LP->cdone)) {
dbgserial_putstr("CDONE not low after reset");
return false;
}
if (!gpio_input_read(&creset_input)) {
dbgserial_putstr("CRESET not high after reset");
return false;
}
delay_ms(1);
// Program the FPGA
prv_spi_write(fpga_bitstream, bitstream_size);
// Set SCS high so that we don't process any of these clocks as commands.
gpio_output_set(&ICE40LP->spi.scs, false); // SCS not asserted (high)
// 49+ SCLK cycles to tell FPGA we're done configuration.
static const uint8_t spi_zeros[9] = {0, 0, 0, 0, 0, 0, 0, 0, 0};
prv_spi_write(spi_zeros, sizeof(spi_zeros));
if (!gpio_input_read(&ICE40LP->cdone)) {
dbgserial_putstr("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);
if (ICE40LP->use_6v6_rail) {
dbgserial_putstr("Enabling 6v6 (Display VDDC)");
set_6V6_power_state(true);
delay_ms(2);
}
dbgserial_putstr("Enabling 4v5 (Display VDDP)");
set_4V5_power_state(true);
}
void display_power_disable(void) {
dbgserial_putstr("Disabling 4v5 (Display VDDP)");
set_4V5_power_state(false);
delay_ms(2);
if (ICE40LP->use_6v6_rail) {
dbgserial_putstr("Disabling 6v6 (Display VDDC)");
set_6V6_power_state(false);
delay_ms(2);
}
}
void display_write_cmd(uint8_t cmd, uint8_t *arg, uint32_t arg_len) {
gpio_output_set(&ICE40LP->spi.scs, true); // SCS asserted (low)
delay_us(100);
prv_spi_write(&cmd, sizeof(cmd));
if (arg_len) {
prv_spi_write(arg, arg_len);
}
gpio_output_set(&ICE40LP->spi.scs, false); // SCS not asserted (high)
}

View file

@ -0,0 +1,27 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <stdbool.h>
#include <stdint.h>
bool display_busy(void);
void display_start(void);
bool display_program(const uint8_t *fpga_bitstream, uint32_t bitstream_size);
void display_write_cmd(uint8_t cmd, uint8_t *arg, uint32_t arg_len);
void display_power_enable(void);
void display_power_disable(void);

View file

@ -0,0 +1,38 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "board/board.h"
#include <stdbool.h>
#include <stdint.h>
typedef const struct ICE40LPDevice {
struct {
SPI_TypeDef *periph;
uint32_t rcc_bit;
AfConfig clk;
AfConfig mosi;
OutputConfig scs;
} spi;
OutputConfig creset;
InputConfig cdone;
InputConfig busy;
bool use_6v6_rail;
} ICE40LPDevice;

View file

@ -0,0 +1,46 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "board/board.h"
typedef enum {
ExtiTrigger_Rising,
ExtiTrigger_Falling,
ExtiTrigger_RisingFalling
} ExtiTrigger;
//! See section 12.2.5 "External interrupt/event line mapping" in the STM32F2 reference manual
typedef enum {
ExtiLineOther_RTCAlarm = 17,
ExtiLineOther_RTCWakeup = 22
} ExtiLineOther;
typedef void (*ExtiHandlerCallback)(void);
//! Configures the given EXTI and NVIC for the given configuration.
void exti_configure_pin(ExtiConfig cfg, ExtiTrigger trigger, ExtiHandlerCallback cb);
//! Configures the given EXTI and NVIC for the given configuration.
void exti_configure_other(ExtiLineOther exti_line, ExtiTrigger trigger);
static inline void exti_enable(ExtiConfig config);
static inline void exti_disable(ExtiConfig config);
void exti_enable_other(ExtiLineOther);
void exti_disable_other(ExtiLineOther);
#include "exti.inl.h"

View file

@ -0,0 +1,27 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
//! @file exti.inl.h
//!
//! Helper functions intended to be inlined into the calling code.
static inline void exti_enable(ExtiConfig config) {
exti_enable_other(config.exti_line);
}
static inline void exti_disable(ExtiConfig config) {
exti_disable_other(config.exti_line);
}

View file

@ -0,0 +1,40 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <stdbool.h>
#include <stdint.h>
//! Configure the micro's peripherals to communicate with the flash chip
void flash_init(void);
//! Read 1 or more bytes starting at the specified 24bit address into
//! the provided buffer. This function does no range checking, so it is
//! currently possible to run off the end of the flash.
//!
//! @param buffer A byte-buffer that will be used to store the data
//! read from flash.
//! @param start_addr The address of the first byte to be read from flash.
//! @param buffer_size The total number of bytes to be read from flash.
void flash_read_bytes(uint8_t* buffer, uint32_t start_addr, uint32_t buffer_size);
//! Check if we can talk to the flash.
//! @return true if the CFI table can be queried.
bool flash_sanity_check(void);
//! Get the checksum of a region of flash
uint32_t flash_calculate_checksum(uint32_t flash_addr, uint32_t length);

View file

@ -0,0 +1,38 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "drivers/flash.h"
#include "util/crc32.h"
#define CRC_CHUNK_SIZE 1024
uint32_t flash_calculate_checksum(uint32_t flash_addr, uint32_t num_bytes) {
uint8_t buffer[CRC_CHUNK_SIZE];
uint32_t crc = CRC32_INIT;
while (num_bytes > CRC_CHUNK_SIZE) {
flash_read_bytes(buffer, flash_addr, CRC_CHUNK_SIZE);
crc = crc32(crc, buffer, CRC_CHUNK_SIZE);
num_bytes -= CRC_CHUNK_SIZE;
flash_addr += CRC_CHUNK_SIZE;
}
flash_read_bytes(buffer, flash_addr, num_bytes);
return crc32(crc, buffer, num_bytes);
}

View file

@ -0,0 +1,220 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <stdbool.h>
#include <stdint.h>
#include "board/board.h"
#include "drivers/flash.h"
#include "drivers/gpio.h"
#include "drivers/periph_config.h"
#include "util/delay.h"
#include "stm32f7haxx_qspi.h"
#define MT25Q_FASTREAD_DUMMYCYCLES 10
typedef enum MT25QCommand {
// SPI/QSPI Commands
MT25QCommand_FastRead = 0x0B, // FAST_READ
MT25QCommand_QSPIEnable = 0x35, // QPI
MT25QCommand_ResetEnable = 0x66, // RSTEN
MT25QCommand_Reset = 0x99, // RST
// QSPI only commands
MT25QCommand_QSPI_ID = 0xAF, // QPIID
} MT25QCommand;
// Helpful Enums
typedef enum {
QSPIFlag_Retain = 0,
QSPIFlag_ClearTC = 1,
} QSPIFlag;
static void prv_enable_qspi_clock(void) {
periph_config_enable(QUADSPI, RCC_AHB3Periph_QSPI);
}
static void prv_disable_qspi_clock(void) {
periph_config_disable(QUADSPI, RCC_AHB3Periph_QSPI);
}
static void prv_set_num_data_bytes(uint32_t length) {
// From the docs: QSPI_DataLength: Number of data to be retrieved, value+1.
// so 0 is 1 byte, so we substract 1 from the length. -1 is read the entire flash length.
QSPI_SetDataLength(length - 1);
}
static void prv_wait_for_qspi_transfer_complete(QSPIFlag actions) {
while (QSPI_GetFlagStatus(QSPI_FLAG_TC) == RESET) { }
if (actions == QSPIFlag_ClearTC) {
QSPI_ClearFlag(QSPI_FLAG_TC);
}
}
static void prv_wait_for_qspi_not_busy(void) {
while (QSPI_GetFlagStatus(QSPI_FLAG_BUSY) != RESET) { }
}
static void prv_quad_enable() {
QSPI_ComConfig_InitTypeDef qspi_com_config;
QSPI_ComConfig_StructInit(&qspi_com_config);
qspi_com_config.QSPI_ComConfig_FMode = QSPI_ComConfig_FMode_Indirect_Write;
qspi_com_config.QSPI_ComConfig_IMode = QSPI_ComConfig_IMode_1Line;
qspi_com_config.QSPI_ComConfig_Ins = MT25QCommand_QSPIEnable;
QSPI_ComConfig_Init(&qspi_com_config);
prv_wait_for_qspi_transfer_complete(QSPIFlag_ClearTC);
prv_wait_for_qspi_not_busy();
}
static void prv_flash_reset(void) {
QSPI_ComConfig_InitTypeDef qspi_com_config;
QSPI_ComConfig_StructInit(&qspi_com_config);
qspi_com_config.QSPI_ComConfig_FMode = QSPI_ComConfig_FMode_Indirect_Write;
qspi_com_config.QSPI_ComConfig_IMode = QSPI_ComConfig_IMode_4Line;
qspi_com_config.QSPI_ComConfig_Ins = MT25QCommand_ResetEnable;
QSPI_ComConfig_Init(&qspi_com_config);
prv_wait_for_qspi_transfer_complete(QSPIFlag_ClearTC);
QSPI_ComConfig_StructInit(&qspi_com_config);
qspi_com_config.QSPI_ComConfig_FMode = QSPI_ComConfig_FMode_Indirect_Write;
qspi_com_config.QSPI_ComConfig_IMode = QSPI_ComConfig_IMode_4Line;
qspi_com_config.QSPI_ComConfig_Ins = MT25QCommand_Reset;
QSPI_ComConfig_Init(&qspi_com_config);
prv_wait_for_qspi_transfer_complete(QSPIFlag_ClearTC);
delay_us(50000); // 50ms reset if busy with an erase!
// Return the flash to Quad SPI mode, all our commands are quad-spi and it'll just cause
// problems/bugs for someone if it comes back in single spi mode
prv_quad_enable();
}
#include "system/passert.h"
static bool prv_flash_check_whoami(void) {
const unsigned int num_whoami_bytes = 3;
prv_set_num_data_bytes(num_whoami_bytes);
QSPI_ComConfig_InitTypeDef qspi_com_config;
QSPI_ComConfig_StructInit(&qspi_com_config);
qspi_com_config.QSPI_ComConfig_FMode = QSPI_ComConfig_FMode_Indirect_Read;
qspi_com_config.QSPI_ComConfig_DMode = QSPI_ComConfig_DMode_4Line;
qspi_com_config.QSPI_ComConfig_IMode = QSPI_ComConfig_IMode_4Line;
qspi_com_config.QSPI_ComConfig_Ins = MT25QCommand_QSPI_ID;
QSPI_ComConfig_Init(&qspi_com_config);
prv_wait_for_qspi_transfer_complete(QSPIFlag_ClearTC);
uint32_t read_whoami = 0;
for (unsigned int i = 0; i < num_whoami_bytes; ++i) {
read_whoami |= QSPI_ReceiveData8() << (8 * i);
}
prv_wait_for_qspi_not_busy();
#if BOARD_ROBERT_BB || BOARD_ROBERT_BB2
const uint32_t expected_whoami = 0x19BB20;
#elif BOARD_ROBERT_EVT
const uint32_t expected_whoami = 0x18BB20;
#else
#error "Unsupported board"
#endif
if (read_whoami == expected_whoami) {
return true;
} else {
dbgserial_print_hex(read_whoami);
return false;
}
}
void flash_init(void) {
prv_enable_qspi_clock();
// init GPIOs
for (unsigned i = 0; i < QSpiPinCount; ++i) {
gpio_af_init(&BOARD_CONFIG_FLASH_PINS[i], GPIO_OType_PP, GPIO_Speed_100MHz, GPIO_PuPd_NOPULL);
}
if (BOARD_CONFIG_FLASH.reset_gpio.gpio) {
gpio_output_init(&BOARD_CONFIG_FLASH.reset_gpio, GPIO_OType_PP, GPIO_Speed_2MHz);
gpio_output_set(&BOARD_CONFIG_FLASH.reset_gpio, false);
}
// Init QSPI peripheral
QSPI_InitTypeDef qspi_config;
QSPI_StructInit(&qspi_config);
qspi_config.QSPI_SShift = QSPI_SShift_HalfCycleShift;
qspi_config.QSPI_Prescaler = 0;
qspi_config.QSPI_CKMode = QSPI_CKMode_Mode0;
qspi_config.QSPI_CSHTime = QSPI_CSHTime_1Cycle;
qspi_config.QSPI_FSize = 23; // 2^24 = 16MB. -> 24 - 1 = 23
qspi_config.QSPI_FSelect = QSPI_FSelect_1;
qspi_config.QSPI_DFlash = QSPI_DFlash_Disable;
QSPI_Init(&qspi_config);
QSPI_Cmd(ENABLE);
// Must call quad_enable first, all commands are QSPI
prv_quad_enable();
// Reset the flash to stop any program's or erase in progress from before reboot
prv_flash_reset();
prv_disable_qspi_clock();
}
bool flash_sanity_check(void) {
prv_enable_qspi_clock();
bool result = prv_flash_check_whoami();
prv_disable_qspi_clock();
return result;
}
void flash_read_bytes(uint8_t *buffer_ptr, uint32_t start_addr, uint32_t buffer_size) {
prv_enable_qspi_clock();
prv_set_num_data_bytes(buffer_size);
QSPI_ComConfig_InitTypeDef qspi_com_config;
QSPI_ComConfig_StructInit(&qspi_com_config);
qspi_com_config.QSPI_ComConfig_FMode = QSPI_ComConfig_FMode_Indirect_Read;
qspi_com_config.QSPI_ComConfig_DMode = QSPI_ComConfig_DMode_4Line;
qspi_com_config.QSPI_ComConfig_DummyCycles = MT25Q_FASTREAD_DUMMYCYCLES;
qspi_com_config.QSPI_ComConfig_ADMode = QSPI_ComConfig_ADMode_4Line;
qspi_com_config.QSPI_ComConfig_IMode = QSPI_ComConfig_IMode_4Line;
qspi_com_config.QSPI_ComConfig_ADSize = QSPI_ComConfig_ADSize_24bit;
qspi_com_config.QSPI_ComConfig_Ins = MT25QCommand_FastRead;
QSPI_ComConfig_Init(&qspi_com_config);
QSPI_SetAddress(start_addr);
uint8_t *write_ptr = buffer_ptr;
for (unsigned i = 0; i < buffer_size; ++i) {
write_ptr[i] = QSPI_ReceiveData8();
}
QSPI_ClearFlag(QSPI_FLAG_TC);
prv_wait_for_qspi_not_busy();
prv_disable_qspi_clock();
}

View file

@ -0,0 +1,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.
*/
#include "drivers/gpio.h"
#include <stdint.h>
#include <stddef.h>
#define MAX_GPIO (11)
#define GPIO_EN_MASK ((RCC_AHB1ENR_GPIOKEN << 1) - 1)
void gpio_disable_all(void) {
RCC->AHB1ENR &= ~GPIO_EN_MASK;
}
static void prv_init_common(const InputConfig *input_config, GPIO_InitTypeDef *gpio_init) {
gpio_use(input_config->gpio);
GPIO_Init(input_config->gpio, gpio_init);
gpio_release(input_config->gpio);
}
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(const 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(const 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(const AfConfig *af_config, GPIOOType_TypeDef otype,
GPIOSpeed_TypeDef speed, GPIOPuPd_TypeDef pupd) {
GPIO_InitTypeDef init = {
.GPIO_Pin = af_config->gpio_pin,
.GPIO_Mode = GPIO_Mode_AF,
.GPIO_Speed = speed,
.GPIO_OType = otype,
.GPIO_PuPd = pupd
};
gpio_use(af_config->gpio);
GPIO_PinAFConfig(af_config->gpio, af_config->gpio_pin_source,
af_config->gpio_af);
GPIO_Init(af_config->gpio, &init);
gpio_release(af_config->gpio);
}
void gpio_af_configure_low_power(const AfConfig *af_config) {
GPIO_InitTypeDef init = {
.GPIO_Pin = af_config->gpio_pin,
.GPIO_Mode = GPIO_Mode_AN,
.GPIO_Speed = GPIO_Speed_2MHz,
.GPIO_PuPd = GPIO_PuPd_NOPULL
};
gpio_use(af_config->gpio);
GPIO_Init(af_config->gpio, &init);
gpio_release(af_config->gpio);
}
void gpio_af_configure_fixed_output(const AfConfig *af_config, bool asserted) {
GPIO_InitTypeDef init = {
.GPIO_Pin = af_config->gpio_pin,
.GPIO_Mode = GPIO_Mode_OUT,
.GPIO_Speed = GPIO_Speed_2MHz,
.GPIO_OType = GPIO_OType_PP,
.GPIO_PuPd = GPIO_PuPd_NOPULL
};
gpio_use(af_config->gpio);
GPIO_Init(af_config->gpio, &init);
GPIO_WriteBit(af_config->gpio, af_config->gpio_pin,
asserted? Bit_SET : Bit_RESET);
gpio_release(af_config->gpio);
}
void gpio_input_init(const InputConfig *input_config) {
if (input_config->gpio == NULL) {
return;
}
gpio_input_init_pull_up_down(input_config, GPIO_PuPd_NOPULL);
}
void gpio_input_init_pull_up_down(const InputConfig *input_config, GPIOPuPd_TypeDef pupd) {
GPIO_InitTypeDef gpio_init = {
.GPIO_Mode = GPIO_Mode_IN,
.GPIO_PuPd = pupd,
.GPIO_Pin = input_config->gpio_pin
};
prv_init_common(input_config, &gpio_init);
}
bool gpio_input_read(const InputConfig *input_config) {
gpio_use(input_config->gpio);
uint8_t bit = GPIO_ReadInputDataBit(input_config->gpio, input_config->gpio_pin);
gpio_release(input_config->gpio);
return bit != 0;
}
void gpio_analog_init(const InputConfig *input_config) {
GPIO_InitTypeDef gpio_init = {
.GPIO_Pin = input_config->gpio_pin,
.GPIO_Mode = GPIO_Mode_AN,
.GPIO_Speed = GPIO_Speed_2MHz,
.GPIO_PuPd = GPIO_PuPd_NOPULL
};
prv_init_common(input_config, &gpio_init);
}

View file

@ -0,0 +1,89 @@
/*
* 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 "stm32f7xx.h"
#include "board/board.h"
void gpio_disable_all(void);
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(const 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(const 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(const AfConfig *af_config, GPIOOType_TypeDef otype,
GPIOSpeed_TypeDef speed, GPIOPuPd_TypeDef pupd);
//! Configure a GPIO alternate function pin to minimize power consumption.
//!
//! Once a pin has been configured for low power, it is no longer
//! connected to its alternate function. \ref gpio_af_init will need to
//! be called again on the pin in order to configure it in alternate
//! function mode again.
void gpio_af_configure_low_power(const AfConfig *af_config);
//! Configure a GPIO alternate function pin to drive a constant output.
//!
//! Once a pin has been configured as a fixed output, it is no longer
//! connected to its alternate function. \ref gpio_af_init will need to
//! be called again on the pin in order to configure it in alternate
//! function mode again.
void gpio_af_configure_fixed_output(const AfConfig *af_config, bool asserted);
//! Configure all GPIOs in the system to optimize for power consumption.
//! At poweron most GPIOs can be configured as analog inputs instead of the
//! default digital input. This allows digital filtering logic to be shut down,
//! saving quite a bit of power.
void gpio_init_all(void);
//! Configure gpios as inputs (suitable for things like exti lines)
void gpio_input_init(const InputConfig *input_cfg);
//! Configure gpio as an input with internal pull up/pull down configured.
void gpio_input_init_pull_up_down(const InputConfig *input_cfg, GPIOPuPd_TypeDef pupd);
//! @return bool the current state of the GPIO pin
bool gpio_input_read(const InputConfig *input_cfg);
//! Configure gpios as analog inputs. Useful for unused GPIOs as this is their lowest power state.
void gpio_analog_init(const InputConfig *input_cfg);

View file

@ -0,0 +1,97 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "board/board.h"
#include <stdbool.h>
#include <stdint.h>
//! Start using the I2C bus to which \a slave is connected
//! Must be called before any other reads or writes to the slave are performed
//! @param slave I2C slave reference, which will identify the bus to use
void i2c_use(I2CSlavePort *slave);
//! Stop using the I2C bus to which \a slave is connected
//! Call when done communicating with the slave
//! @param slave I2C slave reference, which will identify the bus to release
void i2c_release(I2CSlavePort *slave);
//! Reset the slave
//! Will cycle the power to and re-initialize the bus to which \a slave is connected, if this is
//! supported for the bus.
//! @param slave I2C slave reference, which will identify the bus to be reset
void i2c_reset(I2CSlavePort *slave);
//! Manually bang out the clock on the bus to which \a slave is connected until the data line
//! recovers for a period or we timeout waiting for it to recover
//! Must not be called before \ref i2c_use has been called for the slave
//! @param slave I2C slave reference, which will identify the bus to be recovered
//! @return true if the data line recovered, false otherwise
bool i2c_bitbang_recovery(I2CSlavePort *slave);
//! Read the value of a register
//! Must not be called before \ref i2c_use has been called for the slave
//! @param slave I2C slave to communicate with
//! @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(I2CSlavePort *slave, 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 slave
//! @param slave I2C slave to communicate with
//! @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(I2CSlavePort *slave, uint8_t register_address_start,
uint32_t read_size, uint8_t* result_buffer);
//! Read a block of data without sending a register address before doing so.
//! Must not be called before \ref i2c_use has been called for the slave
//! @param slave I2C slave to communicate with
//! @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_block(I2CSlavePort *slave, uint32_t read_size, uint8_t* result_buffer);
//! Write to a register
//! Must not be called before \ref i2c_use has been called for the slave
//! @param slave I2C slave to communicate with
//! @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(I2CSlavePort *slave, 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 slave
//! @param slave I2C slave to communicate with
//! @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(I2CSlavePort *slave, uint8_t register_address_start,
uint32_t write_size, const uint8_t* buffer);
//! Write a block of data without sending a register address before doing so.
//! Must not be called before \ref i2c_use has been called for the slave
//! @param slave I2C slave to communicate with
//! @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_block(I2CSlavePort *slave, uint32_t write_size, const uint8_t* buffer);

View file

@ -0,0 +1,479 @@
/*
* 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/i2c.h"
#include "i2c_definitions.h"
#include "i2c_hal.h"
#include "board/board.h"
#include "drivers/gpio.h"
#include "drivers/periph_config.h"
#include "system/passert.h"
#include "system/logging.h"
#include "util/delay.h"
#include "util/size.h"
#include <inttypes.h>
#include "stm32f7xx.h"
#define I2C_ERROR_TIMEOUT_MS (1000)
#define I2C_TIMEOUT_ATTEMPTS_MAX (2 * 1000 * 1000)
// MFI NACKs while busy. We delay ~1ms between retries so this is approximately a 1000ms timeout.
// The longest operation of the MFi chip is "start signature generation", which seems to take
// 223-224 NACKs, but sometimes for unknown reasons it can take much longer.
#define I2C_NACK_COUNT_MAX (1000)
typedef enum {
Read,
Write
} TransferDirection;
typedef enum {
SendRegisterAddress, // Send a register address, followed by a repeat start for reads
NoRegisterAddress // Do not send a register address
} TransferType;
/*----------------SEMAPHORE/LOCKING FUNCTIONS--------------------------*/
static bool prv_semaphore_take(I2CBusState *bus) {
return true;
}
static bool prv_semaphore_wait(I2CBusState *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 prv_semaphore_give(I2CBusState *bus) {
bus->busy = false;
}
static void prv_semaphore_give_from_isr(I2CBusState *bus) {
bus->busy = false;
return;
}
/*-------------------BUS/PIN CONFIG FUNCTIONS--------------------------*/
static void prv_rail_ctl(I2CBus *bus, bool enable) {
bus->rail_ctl_fn(bus, enable);
if (enable) {
// 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);
}
}
//! Power down I2C bus power supply
//! Always lock bus and peripheral config access before use
static void prv_bus_rail_power_down(I2CBus *bus) {
if (!bus->rail_ctl_fn) {
return;
}
prv_rail_ctl(bus, false);
// Drain through pull-ups
OutputConfig out_scl = {
.gpio = bus->scl_gpio.gpio,
.gpio_pin = bus->scl_gpio.gpio_pin,
.active_high = true
};
gpio_output_init(&out_scl, GPIO_PuPd_NOPULL, GPIO_Speed_2MHz);
gpio_output_set(&out_scl, false);
OutputConfig out_sda = {
.gpio = bus->sda_gpio.gpio,
.gpio_pin = bus->sda_gpio.gpio_pin,
.active_high = true
};
gpio_output_init(&out_sda, GPIO_PuPd_NOPULL, GPIO_Speed_2MHz);
gpio_output_set(&out_sda, false);
}
//! Configure bus pins for use by I2C peripheral
//! Lock bus and peripheral config access before configuring pins
static void prv_bus_pins_cfg_i2c(I2CBus *bus) {
gpio_af_init(&bus->scl_gpio, GPIO_OType_OD, GPIO_Speed_50MHz, GPIO_PuPd_NOPULL);
gpio_af_init(&bus->sda_gpio, GPIO_OType_OD, GPIO_Speed_50MHz, GPIO_PuPd_NOPULL);
}
static void prv_bus_pins_cfg_input(I2CBus *bus) {
InputConfig in_scl = {
.gpio = bus->scl_gpio.gpio,
.gpio_pin = bus->scl_gpio.gpio_pin,
};
gpio_input_init(&in_scl);
InputConfig in_sda = {
.gpio = bus->sda_gpio.gpio,
.gpio_pin = bus->sda_gpio.gpio_pin,
};
gpio_input_init(&in_sda);
}
//! Power up I2C bus power supply
//! Always lock bus and peripheral config access before use
static void prv_bus_rail_power_up(I2CBus *bus) {
if (!bus->rail_ctl_fn) {
return;
}
static const uint32_t MIN_STOP_TIME_MS = 10;
delay_ms(MIN_STOP_TIME_MS);
prv_bus_pins_cfg_input(bus);
prv_rail_ctl(bus, true);
}
//! 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 prv_bus_enable(I2CBus *bus) {
// Don't power up rail if the bus is already in use (enable can be called to reset bus)
if (bus->state->user_count == 0) {
prv_bus_rail_power_up(bus);
}
prv_bus_pins_cfg_i2c(bus);
i2c_hal_enable(bus);
}
//! 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 prv_bus_disable(I2CBus *bus) {
i2c_hal_disable(bus);
// Do not de-power rail if there are still devices using bus (just reset peripheral and pin
// configuration during a bus reset)
if (bus->state->user_count == 0) {
prv_bus_rail_power_down(bus);
} else {
prv_bus_pins_cfg_input(bus);
}
}
//! Perform a soft reset of the bus
//! Always lock the bus before reset
static void prv_bus_reset(I2CBus *bus) {
prv_bus_disable(bus);
prv_bus_enable(bus);
}
/*---------------INIT/USE/RELEASE/RESET FUNCTIONS----------------------*/
void i2c_init(I2CBus *bus) {
PBL_ASSERTN(bus);
*bus->state = (I2CBusState) {};
i2c_hal_init(bus);
if (bus->rail_gpio.gpio) {
gpio_output_init(&bus->rail_gpio, GPIO_OType_PP, GPIO_Speed_2MHz);
}
prv_bus_rail_power_down(bus);
}
void i2c_use(I2CSlavePort *slave) {
PBL_ASSERTN(slave);
if (slave->bus->state->user_count == 0) {
prv_bus_enable(slave->bus);
}
slave->bus->state->user_count++;
}
void i2c_release(I2CSlavePort *slave) {
PBL_ASSERTN(slave);
if (slave->bus->state->user_count == 0) {
PBL_LOG(LOG_LEVEL_ERROR, "Attempted release of disabled bus %s", slave->bus->name);
return;
}
slave->bus->state->user_count--;
if (slave->bus->state->user_count == 0) {
prv_bus_disable(slave->bus);
}
}
void i2c_reset(I2CSlavePort *slave) {
PBL_ASSERTN(slave);
if (slave->bus->state->user_count == 0) {
PBL_LOG(LOG_LEVEL_ERROR, "Attempted reset of disabled bus %s when still in use by "
"another bus", slave->bus->name);
return;
}
PBL_LOG(LOG_LEVEL_WARNING, "Resetting I2C bus %s", slave->bus->name);
// decrement user count for reset so that if this user is the only user, the
// bus will be powered down during the reset
slave->bus->state->user_count--;
// Reset and reconfigure bus and pins
prv_bus_reset(slave->bus);
// Restore user count
slave->bus->state->user_count++;
}
bool i2c_bitbang_recovery(I2CSlavePort *slave) {
PBL_ASSERTN(slave);
static const int MAX_TOGGLE_COUNT = 10;
static const int TOGGLE_DELAY = 10;
if (slave->bus->state->user_count == 0) {
PBL_LOG(LOG_LEVEL_ERROR, "Attempted bitbang recovery on disabled bus %s", slave->bus->name);
return false;
}
InputConfig in_sda = {
.gpio = slave->bus->sda_gpio.gpio,
.gpio_pin = slave->bus->sda_gpio.gpio_pin,
};
gpio_input_init(&in_sda);
OutputConfig out_scl = {
.gpio = slave->bus->scl_gpio.gpio,
.gpio_pin = slave->bus->scl_gpio.gpio_pin,
.active_high = true
};
gpio_output_init(&out_scl, GPIO_PuPd_NOPULL, GPIO_Speed_2MHz);
gpio_output_set(&out_scl, true);
bool recovered = false;
for (int i = 0; i < MAX_TOGGLE_COUNT; ++i) {
gpio_output_set(&out_scl, false);
delay_ms(TOGGLE_DELAY);
gpio_output_set(&out_scl, true);
delay_ms(TOGGLE_DELAY);
if (gpio_input_read(&in_sda)) {
recovered = true;
break;
}
}
if (recovered) {
PBL_LOG(LOG_LEVEL_DEBUG, "I2C Bus %s recovered", slave->bus->name);
} else {
PBL_LOG(LOG_LEVEL_ERROR, "I2C Bus %s still hung after bitbang reset", slave->bus->name);
}
prv_bus_pins_cfg_i2c(slave->bus);
prv_bus_reset(slave->bus);
return recovered;
}
/*--------------------DATA TRANSFER FUNCTIONS--------------------------*/
//! Wait a short amount of time for busy bit to clear
static bool prv_wait_for_not_busy(I2CBus *bus) {
static const int WAIT_DELAY = 10; // milliseconds
if (i2c_hal_is_busy(bus)) {
delay_ms(WAIT_DELAY);
if (i2c_hal_is_busy(bus)) {
PBL_LOG(LOG_LEVEL_ERROR, "Timed out waiting for bus %s to become non-busy", bus->name);
return false;
}
}
return true;
}
//! Set up and start a transfer to a bus, wait for it to finish and clean up after the transfer
//! has completed
static bool prv_do_transfer(I2CBus *bus, TransferDirection direction, uint16_t device_address,
uint8_t register_address, uint32_t size, uint8_t *data,
TransferType type) {
if (bus->state->user_count == 0) {
PBL_LOG(LOG_LEVEL_ERROR, "Attempted access to disabled bus %s", bus->name);
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 (i2c_hal_is_busy(bus)) {
prv_bus_reset(bus);
if (!prv_wait_for_not_busy(bus)) {
// Bus did not recover after reset
PBL_LOG(LOG_LEVEL_ERROR, "I2C bus did not recover after reset (%s)", bus->name);
return false;
}
}
// Take binary semaphore so that next take will block
PBL_ASSERT(prv_semaphore_take(bus->state), "Could not acquire semaphore token");
// Set up transfer
bus->state->transfer = (I2CTransfer) {
.device_address = device_address,
.register_address = register_address,
.direction = direction,
.type = type,
.size = size,
.idx = 0,
.data = data,
};
i2c_hal_init_transfer(bus);
bus->state->transfer_nack_count = 0;
bool result = false;
bool complete = false;
do {
i2c_hal_start_transfer(bus);
// Wait on semaphore until it is released by interrupt or a timeout occurs
if (prv_semaphore_wait(bus->state)) {
if ((bus->state->transfer_event == I2CTransferEvent_TransferComplete) ||
(bus->state->transfer_event == I2CTransferEvent_Error)) {
// Track the max transfer duration so we can keep tabs on the MFi chip's nacking behavior
if (bus->state->transfer_event == I2CTransferEvent_Error) {
PBL_LOG(LOG_LEVEL_ERROR, "I2C Error on bus %s", bus->name);
}
complete = true;
result = (bus->state->transfer_event == I2CTransferEvent_TransferComplete);
} else if (bus->state->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->state->transfer_nack_count++;
// Wait 1-2ms:
delay_ms(2);
} else {
// Too many NACKs received, abort transfer
i2c_hal_abort_transfer(bus);
complete = true;
PBL_LOG(LOG_LEVEL_ERROR, "I2C Error: too many NACKs received on bus %s", bus->name);
break;
}
} else {
// Timeout, abort transfer
i2c_hal_abort_transfer(bus);
complete = true;
PBL_LOG(LOG_LEVEL_ERROR, "Transfer timed out on bus %s", bus->name);
break;
}
} while (!complete);
// Return semaphore token so another transfer can be started
prv_semaphore_give(bus->state);
// 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 (!prv_wait_for_not_busy(bus)) {
// Reset I2C bus if busy flag does not clear
prv_bus_reset(bus);
}
return result;
}
bool i2c_read_register(I2CSlavePort *slave, uint8_t register_address, uint8_t *result) {
return i2c_read_register_block(slave, register_address, 1, result);
}
bool i2c_read_register_block(I2CSlavePort *slave, uint8_t register_address_start,
uint32_t read_size, uint8_t* result_buffer) {
PBL_ASSERTN(slave);
PBL_ASSERTN(result_buffer);
// Do transfer locks the bus
bool result = prv_do_transfer(slave->bus, Read, slave->address, register_address_start, read_size,
result_buffer, SendRegisterAddress);
if (!result) {
PBL_LOG(LOG_LEVEL_ERROR, "Read failed on bus %s", slave->bus->name);
}
return result;
}
bool i2c_read_block(I2CSlavePort *slave, uint32_t read_size, uint8_t* result_buffer) {
PBL_ASSERTN(slave);
PBL_ASSERTN(result_buffer);
bool result = prv_do_transfer(slave->bus, Read, slave->address, 0, read_size, result_buffer,
NoRegisterAddress);
if (!result) {
PBL_LOG(LOG_LEVEL_ERROR, "Block read failed on bus %s", slave->bus->name);
}
return result;
}
bool i2c_write_register(I2CSlavePort *slave, uint8_t register_address, uint8_t value) {
return i2c_write_register_block(slave, register_address, 1, &value);
}
bool i2c_write_register_block(I2CSlavePort *slave, uint8_t register_address_start,
uint32_t write_size, const uint8_t* buffer) {
PBL_ASSERTN(slave);
PBL_ASSERTN(buffer);
// Do transfer locks the bus
bool result = prv_do_transfer(slave->bus, Write, slave->address, register_address_start,
write_size, (uint8_t*)buffer, SendRegisterAddress);
if (!result) {
PBL_LOG(LOG_LEVEL_ERROR, "Write failed on bus %s", slave->bus->name);
}
return result;
}
bool i2c_write_block(I2CSlavePort *slave, uint32_t write_size, const uint8_t* buffer) {
PBL_ASSERTN(slave);
PBL_ASSERTN(buffer);
// Do transfer locks the bus
bool result = prv_do_transfer(slave->bus, Write, slave->address, 0, write_size, (uint8_t*)buffer,
NoRegisterAddress);
if (!result) {
PBL_LOG(LOG_LEVEL_ERROR, "Block write failed on bus %s", slave->bus->name);
}
return result;
}
/*----------------------HAL INTERFACE--------------------------------*/
void i2c_handle_transfer_event(I2CBus *bus, I2CTransferEvent event) {
bus->state->transfer_event = event;
prv_semaphore_give_from_isr(bus->state);
}

View file

@ -0,0 +1,95 @@
/*
* 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>
typedef enum I2CTransferEvent {
I2CTransferEvent_Timeout,
I2CTransferEvent_TransferComplete,
I2CTransferEvent_NackReceived,
I2CTransferEvent_Error,
} I2CTransferEvent;
typedef enum {
I2CTransferDirection_Read,
I2CTransferDirection_Write
} I2CTransferDirection;
typedef enum {
// Send a register address, followed by a repeat start for reads
I2CTransferType_SendRegisterAddress,
// Do not send a register address; used for block writes/reads
I2CTransferType_NoRegisterAddress
} I2CTransferType;
typedef enum I2CTransferState {
I2CTransferState_WriteAddressTx,
I2CTransferState_WriteRegAddress,
I2CTransferState_RepeatStart,
I2CTransferState_WriteAddressRx,
I2CTransferState_WaitForData,
I2CTransferState_ReadData,
I2CTransferState_WriteData,
I2CTransferState_EndWrite,
I2CTransferState_Complete,
} I2CTransferState;
typedef struct I2CTransfer {
I2CTransferState state;
uint16_t device_address;
I2CTransferDirection direction;
I2CTransferType type;
uint8_t register_address;
uint32_t size;
uint32_t idx;
uint8_t *data;
} I2CTransfer;
typedef struct I2CBusState {
I2CTransfer transfer;
I2CTransferEvent transfer_event;
int transfer_nack_count;
int user_count;
volatile bool busy;
} I2CBusState;
struct I2CBus {
I2CBusState *const state;
const struct I2CBusHal *const hal;
AfConfig scl_gpio; ///< Alternate Function configuration for SCL pin
AfConfig sda_gpio; ///< Alternate Function configuration for SDA pin
OutputConfig rail_gpio; ///< Control pin for rail
void (* const rail_ctl_fn)(I2CBus *device, bool enabled); ///< Control function for this rail.
const char *name; //! Device ID for logging purposes
};
struct I2CSlavePort {
const I2CBus *bus;
uint16_t address;
};
//! Initialize the I2C driver.
void i2c_init(I2CBus *bus);
//! Transfer event handler implemented in i2c.c and called by HAL implementation
void i2c_handle_transfer_event(I2CBus *device, I2CTransferEvent event);
#define I2C_DEBUG(fmt, args...) \
PBL_LOG_COLOR_D(LOG_DOMAIN_I2C, LOG_LEVEL_DEBUG, LOG_COLOR_LIGHT_MAGENTA, fmt, ## args)

View file

@ -0,0 +1,336 @@
/*
* 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 "i2c_hal.h"
#include "i2c_definitions.h"
#include "i2c_hal_definitions.h"
#include "drivers/periph_config.h"
#include "system/logging.h"
#include "system/passert.h"
#include "util/attributes.h"
#include "stm32f7xx.h"
#define I2C_IRQ_PRIORITY (0xc)
#define I2C_NORMAL_MODE_CLOCK_SPEED_MAX (100000)
#define I2C_FAST_MODE_CLOCK_SPEED_MAX (400000)
#define I2C_FAST_MODE_PLUS_CLOCK_SPEED_MAX (1000000)
#define TIMINGR_MASK_PRESC (0x0F)
#define TIMINGR_MASK_SCLH (0xFF)
#define TIMINGR_MASK_SCLL (0xFF)
#define CR1_CLEAR_MASK (0x00CFE0FF)
#define CR2_CLEAR_MASK (0x07FF7FFF)
#define CR2_NBYTES_OFFSET (16)
#define CR2_TRANSFER_SETUP_MASK (I2C_CR2_SADD | I2C_CR2_NBYTES | I2C_CR2_RELOAD | \
I2C_CR2_AUTOEND | I2C_CR2_RD_WRN | I2C_CR2_START | \
I2C_CR2_STOP)
typedef union PACKED TIMINGR {
struct {
int32_t SCLL:8;
int32_t SCLH:8;
int32_t SDADEL:4;
int32_t SCLDEL:4;
int32_t reserved:4;
int32_t PRESC:4;
};
int32_t reg;
} TIMINGR;
static void prv_i2c_deinit(I2CBus *bus) {
// Reset the clock to the peripheral
RCC_APB1PeriphResetCmd(bus->hal->clock_ctrl, ENABLE);
RCC_APB1PeriphResetCmd(bus->hal->clock_ctrl, DISABLE);
}
void i2c_hal_init(I2CBus *bus) {
NVIC_SetPriority(bus->hal->ev_irq_channel, I2C_IRQ_PRIORITY);
NVIC_SetPriority(bus->hal->er_irq_channel, I2C_IRQ_PRIORITY);
NVIC_EnableIRQ(bus->hal->ev_irq_channel);
NVIC_EnableIRQ(bus->hal->er_irq_channel);
prv_i2c_deinit(bus);
}
static void prv_i2c_init(I2C_TypeDef *i2c, TIMINGR timingr) {
// Soft reset of the state machine and status bits by disabling the peripheral.
// Note: PE must be low for 3 APB cycles after this is done for the reset to be successful
i2c->CR1 &= ~I2C_CR1_PE;
i2c->CR1 &= ~CR1_CLEAR_MASK;
// Set the timing register
i2c->TIMINGR = timingr.reg;
// I2C only used as a master; disable slave address acknowledgement
i2c->OAR1 = 0;
i2c->OAR2 = 0;
// Enable i2c Peripheral; clear any configured interrupt bits; use analog filter
i2c->CR1 |= I2C_CR1_PE;
// Clear CR2, making it ready for the next transaction
i2c->CR2 &= ~CR2_CLEAR_MASK;
}
void i2c_hal_enable(I2CBus *bus) {
// We don't need to support Fast Mode Plus yet, so make sure the desired clock speed is less than
// the maximum Fast Mode clock speed.
// When Fast Mode support is added the duty-cycle settings will probably have to be re-thought.
PBL_ASSERT(bus->hal->clock_speed <= I2C_FAST_MODE_CLOCK_SPEED_MAX,
"Fast Mode Plus not yet supported");
uint32_t duty_cycle_low = 1;
uint32_t duty_cycle_high = 1;
if (bus->hal->clock_speed > I2C_NORMAL_MODE_CLOCK_SPEED_MAX) { // Fast mode
if (bus->hal->duty_cycle == I2CDutyCycle_16_9) {
duty_cycle_low = 16;
duty_cycle_high = 9;
} else if (bus->hal->duty_cycle == I2CDutyCycle_2) {
duty_cycle_low = 2;
duty_cycle_high = 1;
} else {
WTF; // It might be possible to encode a duty cycle differently from the legacy I2C, if it's
// ever necessary. Currently it's not, so just maintain the previous implementation
}
}
RCC_ClocksTypeDef rcc_clocks;
RCC_GetClocksFreq(&rcc_clocks);
uint32_t prescaler = rcc_clocks.PCLK1_Frequency /
(bus->hal->clock_speed * (duty_cycle_low + duty_cycle_high));
if ((rcc_clocks.PCLK1_Frequency %
(bus->hal->clock_speed * (duty_cycle_low + duty_cycle_high))) == 0) {
// Prescaler is PRESC + 1. This subtracts one so that exact dividers are correct, but if there
// is an integer remainder, the prescaler will ensure that the clock frequency is within spec.
prescaler -= 1;
}
// Make sure all the values fit in their corresponding fields
PBL_ASSERTN((duty_cycle_low <= TIMINGR_MASK_SCLL) &&
(duty_cycle_high <= TIMINGR_MASK_SCLH) &&
(prescaler <= TIMINGR_MASK_PRESC));
periph_config_enable(bus->hal->i2c, bus->hal->clock_ctrl);
// We currently don't need to worry about the other TIMINGR fields (they come out to 0), but might
// need to revisit this if we ever need FM+ speeds.
TIMINGR timingr = {
.PRESC = prescaler,
.SCLH = duty_cycle_high - 1, // Duty cycle high is SCLH + 1
.SCLL = duty_cycle_low - 1, // Duty cycle low is SCLL + 1
};
prv_i2c_init(bus->hal->i2c, timingr);
}
void i2c_hal_disable(I2CBus *bus) {
periph_config_disable(bus->hal->i2c, bus->hal->clock_ctrl);
prv_i2c_deinit(bus);
}
bool i2c_hal_is_busy(I2CBus *bus) {
return ((bus->hal->i2c->ISR & I2C_ISR_BUSY) != 0);
}
static void prv_disable_all_interrupts(I2CBus *bus) {
bus->hal->i2c->CR1 &= ~(I2C_CR1_TXIE |
I2C_CR1_RXIE |
I2C_CR1_TCIE |
I2C_CR1_NACKIE |
I2C_CR1_ERRIE);
}
void i2c_hal_abort_transfer(I2CBus *bus) {
// Disable all interrupts on the bus
prv_disable_all_interrupts(bus);
// Generate a stop condition
bus->hal->i2c->CR2 |= I2C_CR2_STOP;
}
void i2c_hal_init_transfer(I2CBus *bus) {
I2CTransfer *transfer = &bus->state->transfer;
if (transfer->type == I2CTransferType_SendRegisterAddress) {
transfer->state = I2CTransferState_WriteRegAddress;
} else {
if (transfer->direction == I2CTransferDirection_Read) {
transfer->state = I2CTransferState_ReadData;
} else {
transfer->state = I2CTransferState_WriteData;
}
}
}
static void prv_enable_interrupts(I2CBus *bus) {
bus->hal->i2c->CR1 |= I2C_CR1_ERRIE | // Enable error interrupt
I2C_CR1_NACKIE | // Enable NACK interrupt
I2C_CR1_TCIE | // Enable transfer complete interrupt
I2C_CR1_TXIE; // Enable transmit interrupt
if (bus->state->transfer.direction == I2CTransferDirection_Read) {
bus->hal->i2c->CR1 |= I2C_CR1_RXIE; // Enable receive interrupt
}
}
static void prv_resume_transfer(I2CBus *bus, bool generate_start) {
const I2CTransfer *transfer = &bus->state->transfer;
uint32_t cr2_value = transfer->device_address & I2C_CR2_SADD;
if ((transfer->direction == I2CTransferDirection_Read) &&
(transfer->state != I2CTransferState_WriteRegAddress)) {
cr2_value |= I2C_CR2_RD_WRN;
}
const uint32_t remaining = bus->state->transfer.size - bus->state->transfer.idx;
if (remaining > UINT8_MAX) {
cr2_value |= I2C_CR2_RELOAD;
cr2_value |= I2C_CR2_NBYTES;
} else {
cr2_value |= (remaining << CR2_NBYTES_OFFSET) & I2C_CR2_NBYTES;
}
if (generate_start) {
cr2_value |= I2C_CR2_START;
}
bus->hal->i2c->CR2 = cr2_value;
}
void i2c_hal_start_transfer(I2CBus *bus) {
prv_enable_interrupts(bus);
if (bus->state->transfer.state == I2CTransferState_WriteRegAddress) {
// For writes, we'll reload with the payload once we send the address. Otherwise, we'd need to
// send a repeated start, which we don't want to do.
const bool reload = bus->state->transfer.direction == I2CTransferDirection_Write;
bus->hal->i2c->CR2 = (bus->state->transfer.device_address & I2C_CR2_SADD) |
(1 << CR2_NBYTES_OFFSET) |
(reload ? I2C_CR2_RELOAD : 0) |
I2C_CR2_START;
} else {
prv_resume_transfer(bus, true /* generate_start */);
}
}
/*------------------------INTERRUPT FUNCTIONS--------------------------*/
static void prv_end_transfer_irq(I2CBus *bus, I2CTransferEvent event) {
prv_disable_all_interrupts(bus);
// Generate stop condition
bus->hal->i2c->CR2 |= I2C_CR2_STOP;
bus->state->transfer.state = I2CTransferState_Complete;
i2c_handle_transfer_event(bus, event);
}
//! Handle an IRQ event on the specified \a bus
static void prv_event_irq_handler(I2CBus *bus) {
I2C_TypeDef *i2c = bus->hal->i2c;
I2CTransfer *transfer = &bus->state->transfer;
switch (transfer->state) {
case I2CTransferState_WriteRegAddress:
if ((i2c->ISR & I2C_ISR_TXIS) != 0) {
i2c->TXDR = transfer->register_address;
}
if ((transfer->direction == I2CTransferDirection_Read) && (i2c->ISR & I2C_ISR_TC)) {
// done writing the register address for a read request - start a read request
transfer->state = I2CTransferState_ReadData;
prv_resume_transfer(bus, true /* generate_start */);
} else if ((transfer->direction == I2CTransferDirection_Write) && (i2c->ISR & I2C_ISR_TCR)) {
// done writing the register address for a write request - "reload" the write payload
transfer->state = I2CTransferState_WriteData;
prv_resume_transfer(bus, false /* !generate_start */);
}
if ((i2c->ISR & I2C_ISR_NACKF) != 0) {
i2c->ICR |= I2C_ICR_NACKCF;
i2c_handle_transfer_event(bus, I2CTransferEvent_NackReceived);
return;
}
break;
case I2CTransferState_ReadData:
if ((i2c->ISR & I2C_ISR_RXNE) != 0) {
transfer->data[transfer->idx++] = i2c->RXDR;
}
if ((i2c->ISR & I2C_ISR_TCR) != 0) {
prv_resume_transfer(bus, false /* !generate_start */);
}
if ((i2c->ISR & I2C_ISR_TC) != 0) {
prv_end_transfer_irq(bus, I2CTransferEvent_TransferComplete);
return;
}
break;
case I2CTransferState_WriteData:
if ((i2c->ISR & I2C_ISR_TXIS) != 0) {
i2c->TXDR = transfer->data[transfer->idx++];
}
if ((i2c->ISR & I2C_ISR_NACKF) != 0) {
i2c->ICR |= I2C_ICR_NACKCF;
return i2c_handle_transfer_event(bus, I2CTransferEvent_NackReceived);
}
if ((i2c->ISR & I2C_ISR_TCR) != 0) {
prv_resume_transfer(bus, false /* !generate_start */);
}
if ((i2c->ISR & I2C_ISR_TC) != 0) {
prv_end_transfer_irq(bus, I2CTransferEvent_TransferComplete);
return;
}
break;
case I2CTransferState_Complete:
if (i2c->ISR & I2C_ISR_TXE) {
// We seem to get a spurious interrupt after the last byte is sent
// There is no bit to specifically disable this interrupt and the interrupt may have already
// been pended when we would disable it, so just handle it silently.
break;
}
// Fallthrough
// These extra states were defined for the F4 implementation but are not necessary for the F7,
// because the interrupt scheme is a lot nicer.
case I2CTransferState_RepeatStart:
case I2CTransferState_EndWrite:
case I2CTransferState_WaitForData:
case I2CTransferState_WriteAddressRx:
case I2CTransferState_WriteAddressTx:
default:
WTF;
}
}
static void prv_error_irq_handler(I2CBus *bus) {
I2C_TypeDef *i2c = bus->hal->i2c;
if ((i2c->ISR & I2C_ISR_BERR) != 0) {
i2c->ICR |= I2C_ICR_BERRCF;
}
if ((i2c->ISR & I2C_ISR_OVR) != 0) {
i2c->ICR |= I2C_ICR_OVRCF;
}
if ((i2c->ISR & I2C_ISR_ARLO) != 0) {
i2c->ICR |= I2C_ICR_ARLOCF;
}
prv_end_transfer_irq(bus, I2CTransferEvent_Error);
}
void i2c_hal_event_irq_handler(I2CBus *bus) {
prv_event_irq_handler(bus);
}
void i2c_hal_error_irq_handler(I2CBus *bus) {
prv_error_irq_handler(bus);
}

View file

@ -0,0 +1,35 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "board/board.h"
#include <stdbool.h>
void i2c_hal_init(I2CBus *bus);
void i2c_hal_enable(I2CBus *bus);
void i2c_hal_disable(I2CBus *bus);
bool i2c_hal_is_busy(I2CBus *bus);
void i2c_hal_abort_transfer(I2CBus *bus);
void i2c_hal_init_transfer(I2CBus *bus);
void i2c_hal_start_transfer(I2CBus *bus);

View file

@ -0,0 +1,37 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <stdbool.h>
#include <stdint.h>
typedef enum I2CDutyCycle {
I2CDutyCycle_16_9,
I2CDutyCycle_2
} I2CDutyCycle;
typedef struct I2CBusHal {
I2C_TypeDef *const i2c;
uint32_t clock_ctrl; ///< Peripheral clock control flag
uint32_t clock_speed; ///< Bus clock speed
I2CDutyCycle duty_cycle; ///< Bus clock duty cycle in fast mode
IRQn_Type ev_irq_channel; ///< I2C Event interrupt (One of X_IRQn). For example, I2C1_EV_IRQn.
IRQn_Type er_irq_channel; ///< I2C Error interrupt (One of X_IRQn). For example, I2C1_ER_IRQn.
} I2CBusHal;
void i2c_hal_event_irq_handler(I2CBus *device);
void i2c_hal_error_irq_handler(I2CBus *device);

View file

@ -0,0 +1,331 @@
/*
* 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/dbgserial.h"
#include "drivers/gpio.h"
#include "drivers/i2c.h"
#include "drivers/periph_config.h"
#include "system/logging.h"
#include "system/passert.h"
#include "util/delay.h"
#include "stm32f7xx.h"
#include <stdint.h>
#define MAX14690_ADDR (0x50)
#define MAX14690_WHOAMI (0x01)
//! 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_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
};
static const int PMIC_MON_CONFIG_VBAT_INDEX = 0;
/* Private Function Definitions */
static bool prv_is_alive(void);
static void 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) {
i2c_use(I2C_MAX14690);
bool rv = i2c_read_register(I2C_MAX14690, register_address, result);
i2c_release(I2C_MAX14690);
return (rv);
}
static bool prv_write_register(uint8_t register_address, uint8_t value) {
i2c_use(I2C_MAX14690);
bool rv = i2c_write_register(I2C_MAX14690, register_address, value);
i2c_release(I2C_MAX14690);
return (rv);
}
/* Public Functions */
bool pmic_init(void) {
prv_set_pin_config();
if (!prv_is_alive()) {
return false;
}
// 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 = 0;
if (rail == PmicRail_LDO2) {
rail_control_reg = PmicRegisters_LDO2_CONFIG;
ref_count = &s_ldo2_ref_count;
} else if (rail == PmicRail_LDO3) {
rail_control_reg = PmicRegisters_LDO3_CONFIG;
ref_count = &s_ldo3_ref_count;
} else {
WTF;
}
uint8_t register_value;
bool success = prv_read_register(rail_control_reg, &register_value);
if (!success) {
// Failed to read the current register value
return false;
}
if (enable) {
if (*ref_count) {
(*ref_count)++;
return true;
} else {
// Set the register byte to XXXXX01X to enable the rail, mask and set
register_value = (register_value & ~0x06) | 0x02;
success = prv_write_register(rail_control_reg, register_value);
if (success) {
// We enabled the rail!
*ref_count = 1;
// We need to wait a bit for the rail to stabilize before continuing to use the device.
// It takes 2.6ms for the LDO rails to ramp.
delay_ms(3);
return true;
}
return false;
}
} else {
if (*ref_count <= 1) {
// Set the register byte to XXXXX00X to disable the rail, just mask
register_value = (register_value & ~0x06);
success = prv_write_register(rail_control_reg, register_value);
if (success) {
// We disabled the rail!
*ref_count = 0;
return true;
}
return false;
} else {
(*ref_count)--;
return true;
}
}
}
bool pmic_power_off(void) {
bool ret = prv_write_register(PmicRegisters_PWR_CFG, 0xB2);
if (ret) {
// Goodbye cruel world. The PMIC should be removing our power at any time now.
while (1) {}
__builtin_unreachable();
}
return false;
}
static bool prv_set_mon_config_register(uint8_t value) {
return prv_write_register(PmicRegisters_MON_CFG, value);
}
static bool prv_set_mon_config(const PmicMonConfig *config) {
const uint8_t ratio_config = 4 - config->ratio; // 4:1 is 0b00, 1:1 is 0b11.
const uint8_t register_value = (ratio_config << 4) | config->source_config;
bool result = prv_set_mon_config_register(register_value);
// Need to wait a short period of time for the reading to settle due to capacitance on the line.
delay_us(200);
return result;
}
bool pmic_enable_battery_measure(void) {
prv_mon_config_lock();
return prv_set_mon_config(&MON_CONFIG[PMIC_MON_CONFIG_VBAT_INDEX]);
// Don't prv_unlock, we don't want anyone else mucking with the mon config until
// pmic_disable_battery_measure is called.
}
bool pmic_disable_battery_measure(void) {
bool result = prv_set_mon_config_register(0);
// Releases the lock that was previously aquired in pmic_enable_battery_measure.
prv_mon_config_unlock();
return result;
}
bool pmic_set_charger_state(bool enable) {
// Defaults to ON
// Default value is 0xF7
const uint8_t register_value = enable ? 0xf7 : 0xf6;
bool result = prv_write_register(PmicRegisters_CHG_CNTL_A, register_value);
return result;
}
bool pmic_is_charging(void) {
uint8_t val;
if (!prv_read_register(PmicRegisters_STATUSA, &val)) {
// NOTE: When running on QEMU, i2c reads return false. For now, just assume a failed
// i2c read means we are charging
return true;
}
uint8_t chgstat = val & 0x07; // Mask off only charging status
if (chgstat == 0x02 || // Pre-charge in progress
chgstat == 0x03 || // Fast charge, CC
chgstat == 0x04 || // Fast charge, CV
chgstat == 0x05) { // Maintain charge
return true;
} else {
return false;
}
}
bool pmic_is_usb_connected(void) {
uint8_t val;
if (!prv_read_register(PmicRegisters_STATUSB, &val)) {
return false;
}
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 == MAX14690_WHOAMI) {
PBL_LOG(LOG_LEVEL_DEBUG, "Found the max14690");
return true;
} else {
PBL_LOG(LOG_LEVEL_DEBUG, "Error reading max14690 WHOAMI byte");
return false;
}
}
static void prv_set_pin_config(void) {
periph_config_acquire_lock();
// Initialize the GPIOs for the 4V5 & 6V6 rails
gpio_output_init(&BOARD_CONFIG_POWER.rail_4V5_ctrl, GPIO_OType_OD, GPIO_Speed_50MHz);
if (BOARD_CONFIG_POWER.rail_6V6_ctrl.gpio) {
gpio_output_init(&BOARD_CONFIG_POWER.rail_6V6_ctrl, GPIO_OType_OD, GPIO_Speed_50MHz);
}
gpio_output_init(&BOARD_CONFIG_ACCESSORY.power_en, GPIO_OType_OD, GPIO_Speed_50MHz);
periph_config_release_lock();
}
void set_4V5_power_state(bool enabled) {
gpio_output_set(&BOARD_CONFIG_POWER.rail_4V5_ctrl, enabled);
}
void set_6V6_power_state(bool enabled) {
if (BOARD_CONFIG_POWER.rail_6V6_ctrl.gpio) {
gpio_output_set(&BOARD_CONFIG_POWER.rail_6V6_ctrl, enabled);
}
}

View file

@ -0,0 +1,103 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "drivers/periph_config.h"
#include "system/logging.h"
#include "system/passert.h"
#include "stm32f7xx.h"
#define PERIPH_CONFIG_DEBUG 0
#if PERIPH_CONFIG_DEBUG
#define PERIPH_CONFIG_LOG(fmt, args...) \
PBL_LOG(LOG_LEVEL_DEBUG, fmt, ## args)
#else
#define PERIPH_CONFIG_LOG(fmt, args...)
#endif
typedef void (*ClockCmd)(uint32_t periph, FunctionalState state);
#if PERIPH_CONFIG_DEBUG
static const char *prv_string_for_cmd(ClockCmd cmd) {
if (cmd == RCC_APB1PeriphClockCmd) {
return "APB1";
} else if (cmd == RCC_APB2PeriphClockCmd) {
return "APB2";
} else if (cmd == RCC_AHB1PeriphClockCmd) {
return "AHB1";
} else if (cmd == RCC_AHB2PeriphClockCmd) {
return "AHB2";
} else {
return NULL;
}
}
#endif
// F(S)MC is the only AHB3 peripheral
#ifdef FMC_R_BASE
#define AHB3_BASE FMC_R_BASE
#else
#define AHB3_BASE FSMC_R_BASE
#endif
_Static_assert(APB1PERIPH_BASE < APB2PERIPH_BASE, "Clock mapping assumptions don't hold");
_Static_assert(APB2PERIPH_BASE < AHB1PERIPH_BASE, "Clock mapping assumptions don't hold");
_Static_assert(AHB1PERIPH_BASE < AHB2PERIPH_BASE, "Clock mapping assumptions don't hold");
_Static_assert(AHB2PERIPH_BASE < AHB3_BASE, "Clock mapping assumptions don't hold");
// Note: this works only with peripheral (<...>Typedef_t *) defines, not with RCC defines
static ClockCmd prv_get_clock_cmd(uintptr_t periph_addr) {
PBL_ASSERTN(periph_addr >= APB1PERIPH_BASE);
if (periph_addr < APB2PERIPH_BASE) {
return RCC_APB1PeriphClockCmd;
} else if (periph_addr < AHB1PERIPH_BASE) {
return RCC_APB2PeriphClockCmd;
} else if (periph_addr < AHB2PERIPH_BASE) {
return RCC_AHB1PeriphClockCmd;
} else if (periph_addr < AHB3_BASE) {
return RCC_AHB2PeriphClockCmd;
} else {
return RCC_AHB3PeriphClockCmd;
}
}
void periph_config_init(void) {
}
void periph_config_acquire_lock(void) {
}
void periph_config_release_lock(void) {
}
void periph_config_enable(void *periph, uint32_t rcc_bit) {
ClockCmd clock_cmd = prv_get_clock_cmd((uintptr_t)periph);
#if PERIPH_CONFIG_DEBUG
if (prv_string_for_cmd(clock_cmd))
PERIPH_CONFIG_LOG("Enabling clock %s", prv_string_for_cmd(clock_cmd));
#endif
clock_cmd(rcc_bit, ENABLE);
}
void periph_config_disable(void *periph, uint32_t rcc_bit) {
ClockCmd clock_cmd = prv_get_clock_cmd((uintptr_t)periph);
#if PERIPH_CONFIG_DEBUG
if (prv_string_for_cmd(clock_cmd))
PERIPH_CONFIG_LOG("Disabling clock %s", prv_string_for_cmd(clock_cmd));
#endif
clock_cmd(rcc_bit, DISABLE);
}

View file

@ -0,0 +1,25 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <stdint.h>
void periph_config_init(void);
void periph_config_acquire_lock(void);
void periph_config_release_lock(void);
void periph_config_enable(void *periph, uint32_t rcc_bit);
void periph_config_disable(void *periph, uint32_t rcc_bit);

View file

@ -0,0 +1,61 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#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);
//! Enables the LDO3 power rail. Used for the MFi/Magnetometer on snowy_bb, MFi on snowy_evt.
void set_ldo3_power_state(bool enabled);
//! Enables the 4.5V power rail. Used for the display on snowy.
void set_4V5_power_state(bool enabled);
//! Enables the 6.6V power rail. Used for the display on snowy.
void set_6V6_power_state(bool enabled);
//! Enables power to the accessory connector.
void set_accessory_power_state(bool enabled);

View file

@ -0,0 +1,41 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "drivers/pwr.h"
#include "drivers/periph_config.h"
#include "stm32f7xx.h"
void pwr_access_backup_domain(bool enable_access) {
periph_config_enable(PWR, RCC_APB1Periph_PWR);
if (enable_access) {
__atomic_or_fetch(&PWR->CR1, PWR_CR1_DBP, __ATOMIC_RELAXED);
} else {
__atomic_and_fetch(&PWR->CR1, ~PWR_CR1_DBP, __ATOMIC_RELAXED);
}
periph_config_disable(PWR, RCC_APB1Periph_PWR);
}
bool pwr_did_boot_from_standby(void) {
bool result = (PWR->CSR1 & PWR_CSR1_SBF) != 0;
return result;
}
void pwr_clear_boot_from_standby_flag(void) {
PWR->CR1 |= PWR_CR1_CSBF;
}

View file

@ -0,0 +1,25 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <stdbool.h>
void pwr_access_backup_domain(bool enable_access);
bool pwr_did_boot_from_standby(void);
void pwr_clear_boot_from_standby_flag(void);

View file

@ -0,0 +1,109 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "drivers/system_flash.h"
#include "drivers/dbgserial.h"
#include "util/misc.h"
#include "stm32f7xx.h"
static uint16_t s_sectors[] = {
FLASH_Sector_0, FLASH_Sector_1, FLASH_Sector_2, FLASH_Sector_3,
FLASH_Sector_4, FLASH_Sector_5, FLASH_Sector_6, FLASH_Sector_7,
FLASH_Sector_8, FLASH_Sector_9, FLASH_Sector_10, FLASH_Sector_11
};
static uint32_t s_sector_addresses[] = {
ADDR_FLASH_SECTOR_0, ADDR_FLASH_SECTOR_1, ADDR_FLASH_SECTOR_2, ADDR_FLASH_SECTOR_3,
ADDR_FLASH_SECTOR_4, ADDR_FLASH_SECTOR_5, ADDR_FLASH_SECTOR_6, ADDR_FLASH_SECTOR_7,
ADDR_FLASH_SECTOR_8, ADDR_FLASH_SECTOR_9, ADDR_FLASH_SECTOR_10, ADDR_FLASH_SECTOR_11
};
static int prv_get_sector_num_for_address(uint32_t address) {
if (address < s_sector_addresses[0]) {
dbgserial_print("address ");
dbgserial_print_hex(address);
dbgserial_putstr(" is outside system flash");
return -1;
}
for (size_t i=0; i < ARRAY_LENGTH(s_sector_addresses)-1; ++i) {
if (s_sector_addresses[i] <= address
&& address < s_sector_addresses[i+1]) {
return i;
}
}
return ARRAY_LENGTH(s_sector_addresses)-1;
}
bool system_flash_erase(uint32_t address, size_t length, SystemFlashProgressCb progress_callback,
void *progress_context) {
if (length == 0) {
// Nothing to do
return true;
}
int first_sector = prv_get_sector_num_for_address(address);
int last_sector = prv_get_sector_num_for_address(address + length - 1);
if (first_sector < 0 || last_sector < 0) {
return false;
}
int count = last_sector - first_sector + 1;
if (progress_callback) {
progress_callback(0, count, progress_context);
}
FLASH_Unlock();
for (int sector = first_sector; sector <= last_sector; ++sector) {
FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_OPERR | FLASH_FLAG_WRPERR |
FLASH_FLAG_PGAERR | FLASH_FLAG_PGPERR | FLASH_FLAG_PGSERR);
if (FLASH_EraseSector(s_sectors[sector], VoltageRange_1) != FLASH_COMPLETE) {
dbgserial_print("failed to erase sector ");
dbgserial_print_hex(sector);
dbgserial_newline();
FLASH_Lock();
return false;
}
if (progress_callback) {
progress_callback(sector - first_sector + 1, count, progress_context);
}
}
FLASH_Lock();
return true;
}
bool system_flash_write(uint32_t address, const void *data, size_t length) {
// enable programming of flash
FLASH_Unlock();
// clear errors
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) {
// wait till the previous operation finished
if (FLASH_ProgramByte(address + i, data_array[i]) != FLASH_COMPLETE) {
dbgserial_print("Program failed @");
dbgserial_print_hex(address + i);
dbgserial_newline();
FLASH_Lock();
return false;
}
}
// disable programming of flash
FLASH_Lock();
return true;
}

View file

@ -0,0 +1,60 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include "stm32f7xx.h"
#define ADDR_FLASH_SECTOR_0 ((uint32_t)0x08000000) /* Base @ of Sector 0, 32 Kbytes */
#define ADDR_FLASH_SECTOR_1 ((uint32_t)0x08008000) /* Base @ of Sector 1, 32 Kbytes */
#define ADDR_FLASH_SECTOR_2 ((uint32_t)0x08010000) /* Base @ of Sector 2, 32 Kbytes */
#define ADDR_FLASH_SECTOR_3 ((uint32_t)0x08018000) /* Base @ of Sector 3, 32 Kbytes */
#define ADDR_FLASH_SECTOR_4 ((uint32_t)0x08020000) /* Base @ of Sector 4, 128 Kbytes */
#define ADDR_FLASH_SECTOR_5 ((uint32_t)0x08040000) /* Base @ of Sector 5, 256 Kbytes */
#define ADDR_FLASH_SECTOR_6 ((uint32_t)0x08080000) /* Base @ of Sector 6, 256 Kbytes */
#define ADDR_FLASH_SECTOR_7 ((uint32_t)0x080C0000) /* Base @ of Sector 7, 256 Kbytes */
#define ADDR_FLASH_SECTOR_8 ((uint32_t)0x08100000) /* Base @ of Sector 8, 256 Kbytes */
#define ADDR_FLASH_SECTOR_9 ((uint32_t)0x08140000) /* Base @ of Sector 9, 256 Kbytes */
#define ADDR_FLASH_SECTOR_10 ((uint32_t)0x08180000) /* Base @ of Sector 10, 256 Kbytes */
#define ADDR_FLASH_SECTOR_11 ((uint32_t)0x081C0000) /* Base @ of Sector 11, 256 Kbytes */
typedef void (*SystemFlashProgressCb)(uint32_t progress, uint32_t total, void *context);
// Erase the sectors of flash which lie within the given address range.
//
// If the address range overlaps even one single byte of a sector, the entire
// sector is erased.
//
// If progress_callback is not NULL, it is called at the beginning of the erase
// process and after each sector is erased. The rational number (progress/total)
// increases monotonically as the sector erasue procedure progresses.
//
// Returns true if successful, false if an error occurred.
bool system_flash_erase(uint32_t address, size_t length, SystemFlashProgressCb progress_callback,
void *progress_context);
// Write data into flash. The flash must already be erased.
//
// If progress_callback is not NULL, it is called at the beginning of the
// writing process and periodically thereafter. The rational number
// (progress/total) increases monotonically as the data is written.
//
// Returns true if successful, false if an error occurred.
bool system_flash_write(uint32_t address, const void *data, size_t length);

View file

@ -0,0 +1,39 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "drivers/watchdog.h"
#include "stm32f7xx.h"
void watchdog_init(void) {
IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable);
IWDG_SetPrescaler(IWDG_Prescaler_64); // ~8 seconds
IWDG_SetReload(0xfff);
IWDG_WriteAccessCmd(IWDG_WriteAccess_Disable);
DBGMCU_APB1PeriphConfig(DBGMCU_IWDG_STOP, ENABLE);
}
void watchdog_start(void) {
IWDG_Enable();
IWDG_ReloadCounter();
}
bool watchdog_check_reset_flag(void) {
return RCC_GetFlagStatus(RCC_FLAG_IWDGRST) != RESET;
}

View file

@ -0,0 +1,24 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <stdbool.h>
void watchdog_init(void);
void watchdog_start(void);
bool watchdog_check_reset_flag(void);

View file

@ -0,0 +1,24 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "stm32f7xx.h"
extern const uint32_t __BOOTLOADER_size__[];
#define BOOTLOADER_SIZE ((uint32_t) __BOOTLOADER_size__)
#define FIRMWARE_BASE (FLASH_BASE + BOOTLOADER_SIZE)

View file

@ -0,0 +1,67 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#define PAGE_SIZE_BYTES (0x100)
#define SECTOR_SIZE_BYTES (0x10000)
#define SECTOR_ADDR_MASK (~(SECTOR_SIZE_BYTES - 1))
#define SUBSECTOR_SIZE_BYTES (0x1000)
#define SUBSECTOR_ADDR_MASK (~(SUBSECTOR_SIZE_BYTES - 1))
// A bit of preprocessor magic to help with automatically calculating flash region addresses
//////////////////////////////////////////////////////////////////////////////
#define FLASH_REGION_DEF(MACRO, arg) \
MACRO(FIRMWARE_SCRATCH, 0x200000 /* 2048k */, arg) \
MACRO(SYSTEM_RESOURCES_BANK_0, 0x100000 /* 1024k */, arg) \
MACRO(SYSTEM_RESOURCES_BANK_1, 0x100000 /* 1024k */, arg) \
MACRO(SAFE_FIRMWARE, 0x080000 /* 512k */, arg) \
MACRO(DEBUG_DB, 0x020000 /* 128k */, arg) \
MACRO(MFG_INFO, 0x020000 /* 128k */, arg) \
MACRO(FILESYSTEM, 0xB30000 /* 11456k */, arg) \
MACRO(RSVD, 0x00F000 /* 60k */, arg) \
MACRO(SHARED_PRF_STORAGE, 0x001000 /* 4k */, arg)
#include "flash_region_def_helper.h"
// Flash region _BEGIN and _END addresses
//////////////////////////////////////////////////////////////////////////////
#define FLASH_REGION_FIRMWARE_SCRATCH_BEGIN FLASH_REGION_START_ADDR(FIRMWARE_SCRATCH)
#define FLASH_REGION_FIRMWARE_SCRATCH_END FLASH_REGION_END_ADDR(FIRMWARE_SCRATCH)
#define FLASH_REGION_SAFE_FIRMWARE_BEGIN FLASH_REGION_START_ADDR(SAFE_FIRMWARE)
#define FLASH_REGION_SAFE_FIRMWARE_END FLASH_REGION_END_ADDR(SAFE_FIRMWARE)
#define FLASH_REGION_MFG_INFO_BEGIN FLASH_REGION_START_ADDR(MFG_INFO)
#define FLASH_REGION_MFG_INFO_END FLASH_REGION_END_ADDR(MFG_INFO)
#define BOARD_NOR_FLASH_SIZE FLASH_REGION_START_ADDR(_COUNT)
// Static asserts to make sure everything worked out
//////////////////////////////////////////////////////////////////////////////
// make sure all the sizes are multiples of the subsector size (4k)
FLASH_REGION_SIZE_CHECK(SUBSECTOR_SIZE_BYTES)
// make sure the total size is what we expect (16MB for robert)
_Static_assert(BOARD_NOR_FLASH_SIZE == 0x1000000, "Flash size should be 16MB");

View file

@ -0,0 +1,37 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
enum {
#define FLASH_REGION_LIST(name, size, arg) FlashRegion_##name,
FLASH_REGION_DEF(FLASH_REGION_LIST, NULL)
FlashRegion__COUNT
};
#define FLASH_REGION_ADDR_HELPER(name, size, tgt) \
+ (FlashRegion_##name < (tgt) ? (size) : 0)
// These macros add up all the sizes of the flash regions that come before (and including in the
// case of the _END_ADDR macro) the specified one to determine the proper flash address value.
#define FLASH_REGION_START_ADDR(region) \
((0) FLASH_REGION_DEF(FLASH_REGION_ADDR_HELPER, FlashRegion_##region))
#define FLASH_REGION_END_ADDR(region) \
((0) FLASH_REGION_DEF(FLASH_REGION_ADDR_HELPER, FlashRegion_##region + 1))
// Checks that all regions are a multiple of the specified size (usually sector or subsector size)
#define FLASH_REGION_SIZE_CHECK_HELPER(name, size, arg) \
&& ((size) % (arg) == 0)
#define FLASH_REGION_SIZE_CHECK(size) \
_Static_assert((1) FLASH_REGION_DEF(FLASH_REGION_SIZE_CHECK_HELPER, size), "Invalid region size");

View file

@ -0,0 +1,221 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "fw_copy.h"
#include "drivers/dbgserial.h"
#include "drivers/display.h"
#include "drivers/flash.h"
#include "drivers/system_flash.h"
#include "firmware.h"
#include "flash_region.h"
#include "system/bootbits.h"
#include "system/firmware_storage.h"
#include "system/reset.h"
#include "util/crc32.h"
#include "util/misc.h"
#include "util/delay.h"
#include <inttypes.h>
#include <stdint.h>
#define MAX_CHUNK_SIZE 65536
static bool prv_check_valid_firmware_crc(uint32_t flash_address, FirmwareDescription* desc) {
dbgserial_putstr("Checksumming firmware update");
const uint32_t crc = flash_calculate_checksum(flash_address, desc->firmware_length);
dbgserial_print("Calculated checksum: ");
dbgserial_print_hex(crc);
dbgserial_newline();
return crc == desc->checksum;
}
static void prv_display_erase_progress(uint32_t progress, uint32_t total, void *ctx) {
display_firmware_update_progress(progress, total * 2);
}
static bool prv_erase_old_firmware(uint32_t firmware_length) {
dbgserial_putstr("prv_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 prv_write_new_firmware(uint32_t flash_new_fw_start, uint32_t firmware_length) {
dbgserial_putstr("prv_write_new_firmware");
// We can't just read the flash like memory, so we gotta lift everything ourselves.
// buffer is static so it goes in BSS, since stack is only 8192 bytes
static uint8_t buffer[MAX_CHUNK_SIZE];
uint32_t chunk_size;
for (uint32_t i = 0; i < firmware_length; i += chunk_size) {
chunk_size = MIN(MAX_CHUNK_SIZE, firmware_length - i);
flash_read_bytes(buffer, flash_new_fw_start + i, chunk_size);
if (!system_flash_write(FIRMWARE_BASE + i, buffer, chunk_size)) {
dbgserial_putstr("We're dead");
return false;
}
prv_display_write_progress(i, firmware_length, NULL);
}
return true;
}
static bool prv_check_firmware_crc(FirmwareDescription* firmware_description) {
dbgserial_print("Checksumming ");
dbgserial_print_hex(firmware_description->firmware_length);
dbgserial_print(" bytes\r\n");
const uint32_t crc = crc32(CRC32_INIT, (const uint8_t *)FIRMWARE_BASE,
firmware_description->firmware_length);
dbgserial_print("Checksum - wanted ");
dbgserial_print_hex(firmware_description->checksum);
dbgserial_print(" got ");
dbgserial_print_hex(crc);
dbgserial_newline();
return crc == firmware_description->checksum;
}
typedef enum UpdateFirmwareResult {
UPDATE_FW_SUCCESS = 0,
UPDATE_FW_ERROR_MICRO_FLASH_UNTOUCHED = 1,
UPDATE_FW_ERROR_MICRO_FLASH_MANGLED = 2
} UpdateFirmwareResult;
static UpdateFirmwareResult prv_update_fw(uint32_t flash_address) {
display_firmware_update_progress(0, 1);
boot_bit_set(BOOT_BIT_NEW_FW_UPDATE_IN_PROGRESS);
FirmwareDescription firmware_description =
firmware_storage_read_firmware_description(flash_address);
if (!firmware_storage_check_valid_firmware_description(&firmware_description)) {
dbgserial_putstr("Invalid firmware description!");
return UPDATE_FW_ERROR_MICRO_FLASH_UNTOUCHED;
}
if (!prv_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;
}
prv_erase_old_firmware(firmware_description.firmware_length);
prv_write_new_firmware(flash_address + sizeof(FirmwareDescription),
firmware_description.firmware_length);
if (!prv_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 fw_copy_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 = prv_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 fw_copy_switch_to_recovery_fw() {
dbgserial_putstr("Loading recovery firmware");
UpdateFirmwareResult result = prv_update_fw(FLASH_REGION_SAFE_FIRMWARE_BEGIN);
bool recovery_fw_ok = true;
switch (result) {
case UPDATE_FW_SUCCESS:
boot_bit_clear(BOOT_BIT_RECOVERY_LOAD_FAIL_STRIKE_ONE);
boot_bit_clear(BOOT_BIT_RECOVERY_LOAD_FAIL_STRIKE_TWO);
boot_bit_set(BOOT_BIT_RECOVERY_START_IN_PROGRESS);
break;
case UPDATE_FW_ERROR_MICRO_FLASH_UNTOUCHED:
case UPDATE_FW_ERROR_MICRO_FLASH_MANGLED:
// Keep us booting into recovery firmware.
boot_bit_set(BOOT_BIT_FW_START_FAIL_STRIKE_ONE);
boot_bit_set(BOOT_BIT_FW_START_FAIL_STRIKE_TWO);
if (!boot_bit_test(BOOT_BIT_RECOVERY_LOAD_FAIL_STRIKE_ONE)) {
dbgserial_putstr("Failed to load recovery firmware, strike one. Try again.");
boot_bit_set(BOOT_BIT_RECOVERY_LOAD_FAIL_STRIKE_ONE);
boot_bit_set(BOOT_BIT_SOFTWARE_FAILURE_OCCURRED);
system_reset();
} else if (!boot_bit_test(BOOT_BIT_RECOVERY_LOAD_FAIL_STRIKE_TWO)) {
dbgserial_putstr("Failed to load recovery firmware, strike two. Try again.");
boot_bit_set(BOOT_BIT_RECOVERY_LOAD_FAIL_STRIKE_TWO);
boot_bit_set(BOOT_BIT_SOFTWARE_FAILURE_OCCURRED);
system_reset();
} else {
dbgserial_putstr("Failed to load recovery firmware, strike three. SAD WATCH");
boot_bit_clear(BOOT_BIT_FW_START_FAIL_STRIKE_ONE);
boot_bit_clear(BOOT_BIT_FW_START_FAIL_STRIKE_TWO);
boot_bit_clear(BOOT_BIT_RECOVERY_LOAD_FAIL_STRIKE_ONE);
boot_bit_clear(BOOT_BIT_RECOVERY_LOAD_FAIL_STRIKE_TWO);
recovery_fw_ok = false;
}
break;
}
boot_bit_clear(BOOT_BIT_NEW_FW_UPDATE_IN_PROGRESS);
return recovery_fw_ok;
}

View file

@ -0,0 +1,24 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <stdbool.h>
void fw_copy_check_update_fw(void);
//! @return false if we've failed to load recovery mode too many times and we should just sadwatch
bool fw_copy_switch_to_recovery_fw(void);

View file

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

View file

@ -0,0 +1,39 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "drivers/dbgserial.h"
#include "system/die.h"
#include "system/reset.h"
static void prv_hard_fault_handler_c(unsigned int *hardfault_args) {
dbgserial_putstr("HARD FAULT");
#ifdef NO_WATCHDOG
reset_due_to_software_failure();
#else
system_hard_reset();
#endif
}
void HardFault_Handler(void) {
// Grab the stack pointer, shove it into a register and call
// the c function above.
__asm("tst lr, #4\n"
"ite eq\n"
"mrseq r0, msp\n"
"mrsne r0, psp\n"
"b %0\n" :: "i" (prv_hard_fault_handler_c));
}

View file

@ -0,0 +1,109 @@
IRQ_DEF(0, WWDG) // Window WatchDog
IRQ_DEF(1, PVD) // PVD through EXTI Line detection
IRQ_DEF(2, TAMP_STAMP) // Tamper and TimeStamps through the EXTI line
IRQ_DEF(3, RTC_WKUP) // RTC Wakeup through the EXTI line
IRQ_DEF(4, FLASH) // FLASH
IRQ_DEF(5, RCC) // RCC
IRQ_DEF(6, EXTI0) // EXTI Line0
IRQ_DEF(7, EXTI1) // EXTI Line1
IRQ_DEF(8, EXTI2) // EXTI Line2
IRQ_DEF(9, EXTI3) // EXTI Line3
IRQ_DEF(10, EXTI4) // EXTI Line4
IRQ_DEF(11, DMA1_Stream0) // DMA1 Stream 0
IRQ_DEF(12, DMA1_Stream1) // DMA1 Stream 1
IRQ_DEF(13, DMA1_Stream2) // DMA1 Stream 2
IRQ_DEF(14, DMA1_Stream3) // DMA1 Stream 3
IRQ_DEF(15, DMA1_Stream4) // DMA1 Stream 4
IRQ_DEF(16, DMA1_Stream5) // DMA1 Stream 5
IRQ_DEF(17, DMA1_Stream6) // DMA1 Stream 6
IRQ_DEF(18, ADC) // ADC1, ADC2 and ADC3s
IRQ_DEF(19, CAN1_TX) // CAN1 TX
IRQ_DEF(20, CAN1_RX0) // CAN1 RX0
IRQ_DEF(21, CAN1_RX1) // CAN1 RX1
IRQ_DEF(22, CAN1_SCE) // CAN1 SCE
IRQ_DEF(23, EXTI9_5) // External Line[9:5]s
IRQ_DEF(24, TIM1_BRK_TIM9) // TIM1 Break and TIM9
IRQ_DEF(25, TIM1_UP_TIM10) // TIM1 Update and TIM10
IRQ_DEF(26, TIM1_TRG_COM_TIM11) // TIM1 Trigger and Commutation and TIM11
IRQ_DEF(27, TIM1_CC) // TIM1 Capture Compare
IRQ_DEF(28, TIM2) // TIM2
IRQ_DEF(29, TIM3) // TIM3
IRQ_DEF(30, TIM4) // TIM4
IRQ_DEF(31, I2C1_EV) // I2C1 Event
IRQ_DEF(32, I2C1_ER) // I2C1 Error
IRQ_DEF(33, I2C2_EV) // I2C2 Event
IRQ_DEF(34, I2C2_ER) // I2C2 Error
IRQ_DEF(35, SPI1) // SPI1
IRQ_DEF(36, SPI2) // SPI2
IRQ_DEF(37, USART1) // USART1
IRQ_DEF(38, USART2) // USART2
IRQ_DEF(39, USART3) // USART3
IRQ_DEF(40, EXTI15_10) // External Line[15:10]s
IRQ_DEF(41, RTC_Alarm) // RTC Alarm (A and B) through EXTI Line
IRQ_DEF(42, OTG_FS_WKUP) // USB OTG FS Wakeup through EXTI line
IRQ_DEF(43, TIM8_BRK_TIM12) // TIM8 Break and TIM12
IRQ_DEF(44, TIM8_UP_TIM13) // TIM8 Update and TIM13
IRQ_DEF(45, TIM8_TRG_COM_TIM14) // TIM8 Trigger and Commutation and TIM14
IRQ_DEF(46, TIM8_CC) // TIM8 Capture Compare
IRQ_DEF(47, DMA1_Stream7) // DMA1 Stream7
IRQ_DEF(48, FMC) // FMC
IRQ_DEF(49, SDMMC1) // SDMMC1
IRQ_DEF(50, TIM5) // TIM5
IRQ_DEF(51, SPI3) // SPI3
IRQ_DEF(52, UART4) // UART4
IRQ_DEF(53, UART5) // UART5
IRQ_DEF(54, TIM6_DAC) // TIM6 and DAC1&2 underrun errors
IRQ_DEF(55, TIM7) // TIM7
IRQ_DEF(56, DMA2_Stream0) // DMA2 Stream 0
IRQ_DEF(57, DMA2_Stream1) // DMA2 Stream 1
IRQ_DEF(58, DMA2_Stream2) // DMA2 Stream 2
IRQ_DEF(59, DMA2_Stream3) // DMA2 Stream 3
IRQ_DEF(60, DMA2_Stream4) // DMA2 Stream 4
IRQ_DEF(61, ETH) // Ethernet
IRQ_DEF(62, ETH_WKUP) // Ethernet Wakeup through EXTI line
IRQ_DEF(63, CAN2_TX) // CAN2 TX
IRQ_DEF(64, CAN2_RX0) // CAN2 RX0
IRQ_DEF(65, CAN2_RX1) // CAN2 RX1
IRQ_DEF(66, CAN2_SCE) // CAN2 SCE
IRQ_DEF(67, OTG_FS) // USB OTG FS
IRQ_DEF(68, DMA2_Stream5) // DMA2 Stream 5
IRQ_DEF(69, DMA2_Stream6) // DMA2 Stream 6
IRQ_DEF(70, DMA2_Stream7) // DMA2 Stream 7
IRQ_DEF(71, USART6) // USART6
IRQ_DEF(72, I2C3_EV) // I2C3 event
IRQ_DEF(73, I2C3_ER) // I2C3 error
IRQ_DEF(74, OTG_HS_EP1_OUT) // USB OTG HS End Point 1 Out
IRQ_DEF(75, OTG_HS_EP1_IN) // USB OTG HS End Point 1 In
IRQ_DEF(76, OTG_HS_WKUP) // USB OTG HS Wakeup through EXTI
IRQ_DEF(77, OTG_HS) // USB OTG HS
IRQ_DEF(78, DCMI) // DCMI
IRQ_DEF(79, CRYP) // CRYP crypto
IRQ_DEF(80, HASH_RNG) // Hash and Rng
IRQ_DEF(81, FPU) // FPU
IRQ_DEF(82, UART7) // UART7
IRQ_DEF(83, UART8) // UART8
IRQ_DEF(84, SPI4) // SPI4
IRQ_DEF(85, SPI5) // SPI5
IRQ_DEF(86, SPI6) // SPI6
IRQ_DEF(87, SAI1) // SAI1
IRQ_DEF(88, LTDC) // LTDC
IRQ_DEF(89, LTDC_ER) // LTDC_ER
IRQ_DEF(90, DMA2D) // DMA2D
IRQ_DEF(91, SAI2) // SAI2
IRQ_DEF(92, QUADSPI) // Quad SPI
IRQ_DEF(93, LPTIM1) // LP TIM1
IRQ_DEF(94, CEC) // HDMI-CEC
IRQ_DEF(95, I2C4_EV) // I2C4 Event
IRQ_DEF(96, I2C4_ER) // I2C4 Error
IRQ_DEF(97, SPDIF_RX) // SPDIF-RX
IRQ_DEF(99, DFSDM0) // DFSDM Filter1
IRQ_DEF(100, DFSDM1) // DFSDM Filter2
IRQ_DEF(101, DFSDM2) // DFSDM Filter3
IRQ_DEF(102, DFSDM3) // DFSDM Filter4
IRQ_DEF(103, SDMMC2) // SDMMC2
IRQ_DEF(104, CAN3_TX) // CAN3 TX
IRQ_DEF(105, CAN3_RX0) // CAN3 RX0
IRQ_DEF(106, CAN3_RX1) // CAN3 RX1
IRQ_DEF(107, CAN3_SCE) // CAN3 SCE
IRQ_DEF(108, JPEG) // JPEG
IRQ_DEF(109, MDIOS) // MDIO Slave

View file

@ -0,0 +1,404 @@
/*
* 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 "board/board.h"
#include "drivers/button.h"
#include "drivers/dbgserial.h"
#include "drivers/display.h"
#include "drivers/flash.h"
#include "drivers/gpio.h"
#include "drivers/periph_config.h"
#include "drivers/pmic.h"
#include "drivers/pwr.h"
#include "drivers/watchdog.h"
#include "boot_tests.h"
#include "firmware.h"
#include "fw_copy.h"
#include "pebble_errors.h"
#include "system/bootbits.h"
#include "system/logging.h"
#include "system/passert.h"
#include "system/reset.h"
#include "util/delay.h"
#include "util/misc.h"
#include "stm32f7xx.h"
static void prv_get_fw_reset_vector(void **reset_handler,
void **initial_stack_pointer) {
void** fw_vector_table = (void**) FIRMWARE_BASE;
*initial_stack_pointer = fw_vector_table[0];
*reset_handler = fw_vector_table[1];
}
static void prv_hw_reset(void) {
// 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 reference manual.
RCC->AHB1ENR = 0x00100000;
RCC->AHB2ENR = 0;
RCC->AHB3ENR = 0;
RCC->APB1ENR = 0x00000400; // Reserved bit needs to be set to enable RTC!
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_ETHMAC | RCC_AHB1Periph_OTGHS;
const uint32_t ahb2_periphs =
RCC_AHB2Periph_DCMI | RCC_AHB2Periph_JPEG | RCC_AHB2Periph_CRYP | RCC_AHB2Periph_HASH
| RCC_AHB2Periph_RNG | RCC_AHB2Periph_OTGFS;
const uint32_t ahb3_periphs = RCC_AHB3Periph_FMC | RCC_AHB3Periph_QSPI;
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_LPTIM1 | RCC_APB1Periph_WWDG | RCC_APB1Periph_CAN3
| RCC_APB1Periph_SPI2 | RCC_APB1Periph_SPI3 | RCC_APB1Periph_SPDIFRX | RCC_APB1Periph_USART2
| RCC_APB1Periph_USART3 | RCC_APB1Periph_UART4 | RCC_APB1Periph_UART5 | RCC_APB1Periph_I2C1
| RCC_APB1Periph_I2C2 | RCC_APB1Periph_I2C3 | RCC_APB1Periph_I2C4 | RCC_APB1Periph_CAN1
| RCC_APB1Periph_CAN2 | RCC_APB1Periph_CEC | 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_SDMMC2 | RCC_APB2Periph_ADC | RCC_APB2Periph_SDMMC1
| 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_SAI2
| RCC_APB2Periph_DFSDM | RCC_APB2Periph_MDIO | 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);
}
static void __attribute__((noreturn)) prv_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");
prv_hw_reset();
// 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 prv_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 prv_check_for_recovery_start_failure() {
return boot_bit_test(BOOT_BIT_RECOVERY_START_IN_PROGRESS);
}
static bool prv_check_for_fw_start_failure() {
// Add more failure conditions here.
if (!watchdog_check_reset_flag() && !boot_bit_test(BOOT_BIT_SOFTWARE_FAILURE_OCCURRED)) {
// We're good, we're just starting normally.
PBL_LOG_VERBOSE("We're good, we're just starting normally.");
boot_bit_clear(BOOT_BIT_FW_START_FAIL_STRIKE_ONE);
boot_bit_clear(BOOT_BIT_FW_START_FAIL_STRIKE_TWO);
return false;
}
// We failed to start our firmware successfully!
if (watchdog_check_reset_flag()) {
dbgserial_putstr("Watchdog caused a reset");
}
if (boot_bit_test(BOOT_BIT_SOFTWARE_FAILURE_OCCURRED)) {
dbgserial_putstr("Software failure caused a reset");
}
// Clean up after the last failure.
boot_bit_clear(BOOT_BIT_SOFTWARE_FAILURE_OCCURRED);
// We have a "three strikes" algorithm: if the watch fails three times, return true
// to tell the parent we should load the recovery firmware. A reset for any other reason
// will reset this algorithm.
if (boot_bit_test(BOOT_BIT_FW_START_FAIL_STRIKE_TWO)) {
// Yikes, our firmware is screwed. Boot into recovery mode.
dbgserial_putstr("Failed to start firmware, strike three.");
boot_bit_clear(BOOT_BIT_FW_START_FAIL_STRIKE_ONE);
boot_bit_clear(BOOT_BIT_FW_START_FAIL_STRIKE_TWO);
return true;
} else if (boot_bit_test(BOOT_BIT_FW_START_FAIL_STRIKE_ONE)) {
dbgserial_putstr("Failed to start firmware, strike two.");
boot_bit_set(BOOT_BIT_FW_START_FAIL_STRIKE_TWO);
} else {
dbgserial_putstr("Failed to start firmware, strike one.");
boot_bit_set(BOOT_BIT_FW_START_FAIL_STRIKE_ONE);
}
return false;
}
static bool prv_prf_button_combination_is_pressed(void) {
return (button_is_pressed(BUTTON_ID_UP) && button_is_pressed(BUTTON_ID_BACK)
&& button_is_pressed(BUTTON_ID_SELECT) && !button_is_pressed(BUTTON_ID_DOWN));
}
static bool prv_check_force_boot_recovery(void) {
if (boot_bit_test(BOOT_BIT_FORCE_PRF)) {
boot_bit_clear(BOOT_BIT_FORCE_PRF);
return true;
}
if (prv_prf_button_combination_is_pressed()) {
dbgserial_putstr("Hold down UP + BACK + SELECT for 5 secs. to force-boot PRF");
for (int i = 0; i < 5000; ++i) {
if (!prv_prf_button_combination_is_pressed()) {
// stop waiting if not held down any longer
return false;
}
delay_ms(1);
}
return true;
}
void *reset_vector, *initial_sp;
prv_get_fw_reset_vector(&reset_vector, &initial_sp);
if ((uintptr_t)reset_vector == 0xffffffff ||
(uintptr_t)initial_sp == 0xffffffff) {
dbgserial_putstr("Firmware is erased");
return true;
}
return false;
}
static void prv_sad_watch(uint32_t error_code) {
dbgserial_putstr("SAD WATCH");
char error_code_buffer[12];
itoa_hex(error_code, error_code_buffer, sizeof(error_code_buffer));
dbgserial_putstr(error_code_buffer);
display_error_code(error_code);
bool prev_select_state = button_is_pressed(BUTTON_ID_SELECT);
while (1) {
// See if we should restart
bool select_state = button_is_pressed(BUTTON_ID_SELECT);
if (select_state != prev_select_state) {
system_reset();
}
delay_ms(10);
}
}
static void prv_check_and_handle_resuming_from_standby(void) {
periph_config_enable(PWR, RCC_APB1Periph_PWR);
if (pwr_did_boot_from_standby()) {
// 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_clear_boot_from_standby_flag();
dbgserial_putstr("exit standby");
system_hard_reset();
}
periph_config_disable(PWR, RCC_APB1Periph_PWR);
}
static void prv_print_bootloader_version(void) {
char bootloader_version_str[12];
memset(bootloader_version_str, 0, 12);
itoa_hex(boot_version_read(), bootloader_version_str, 12);
dbgserial_putstr(bootloader_version_str);
dbgserial_putstr("");
dbgserial_putstr("");
}
int main(void) {
prv_check_and_handle_resuming_from_standby();
board_init();
dbgserial_init();
dbgserial_putstr("\r\n\r\n\r\n");
dbgserial_putstr("██████╗ ██████╗ ██████╗ ███████╗██████╗ ████████╗");
dbgserial_putstr("██╔══██╗██╔═══██╗██╔══██╗██╔════╝██╔══██╗╚══██╔══╝");
dbgserial_putstr("██████╔╝██║ ██║██████╔╝█████╗ ██████╔╝ ██║ ");
dbgserial_putstr("██╔══██╗██║ ██║██╔══██╗██╔══╝ ██╔══██╗ ██║ ");
dbgserial_putstr("██║ ██║╚██████╔╝██████╔╝███████╗██║ ██║ ██║ ");
dbgserial_putstr("╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚══════╝╚═╝ ╚═╝ ╚═╝ ");
// Enable the 3.2V rail for the benefit of the FPGA and display
pmic_init();
boot_bit_init();
boot_version_write();
prv_print_bootloader_version();
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();
pmic_init();
display_init();
display_boot_splash();
if (boot_test_is_button_stuck()) {
prv_sad_watch(ERROR_STUCK_BUTTON);
}
if (boot_test_is_flash_broken()) {
prv_sad_watch(ERROR_BAD_SPI_FLASH);
}
boot_bit_dump();
// If the recovery firmware crashed at start-up, the watch is now a $199 brick. That's life!
if (prv_check_for_recovery_start_failure()) {
boot_bit_clear(BOOT_BIT_RECOVERY_START_IN_PROGRESS);
prv_sad_watch(ERROR_CANT_LOAD_FW);
}
bool force_boot_recovery_mode = prv_check_force_boot_recovery();
if (force_boot_recovery_mode) {
dbgserial_putstr("Force-booting recovery mode...");
}
if (force_boot_recovery_mode || prv_check_for_fw_start_failure()) {
if (!fw_copy_switch_to_recovery_fw()) {
// We've failed to load recovery mode too many times.
prv_sad_watch(ERROR_CANT_LOAD_FW);
}
} else {
fw_copy_check_update_fw();
}
if (prv_check_and_increment_reset_loop_detection_bits()) {
prv_sad_watch(ERROR_RESET_LOOP);
}
#if !NO_WATCHDOG
dbgserial_putstr("Enabling watchdog");
watchdog_init();
watchdog_start();
#endif
gpio_disable_all();
prv_jump_to_fw();
}
// Stubs for libg_s.a, which is our libc implementation from nano-newlib
void _exit(int status) {
}

View file

@ -0,0 +1,34 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <stdint.h>
static const uint32_t ERROR_NO_ACTIVE_ERROR = 0;
// FW Errors
static const uint32_t ERROR_STUCK_BUTTON = 0xfe504501;
static const uint32_t ERROR_BAD_SPI_FLASH = 0xfe504502;
static const uint32_t ERROR_CANT_LOAD_FW = 0xfe504503;
static const uint32_t ERROR_RESET_LOOP = 0xfe504504;
// BT Errors
static const uint32_t ERROR_CANT_LOAD_BT = 0xfe504510;
static const uint32_t ERROR_CANT_START_LSE = 0xfe504511;
static const uint32_t ERROR_CANT_START_ACCEL = 0xfe504512;
static const uint32_t ERROR_LOW_BATTERY = 0xfe504520;

View file

@ -0,0 +1,147 @@
__Stack_Size = 8192;
PROVIDE ( _Stack_Size = __Stack_Size ) ;
__Stack_Init = _estack - __Stack_Size ;
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 ;
MEMORY
{
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 128K
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 32K
}
__BOOTLOADER_size__ = LENGTH(FLASH);
__end_heap = ORIGIN(RAM) + LENGTH(RAM);
PROVIDE(_heap_end = __end_heap);
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);
} >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 : {
. = ALIGN(4);
/* This is used by the startup in order to initialize the .data secion */
__data_start = .;
*(.data)
*(.data.*)
. = ALIGN(4);
__data_end = .; /* This is used by the startup in order to initialize the .data secion */
} >RAM AT>FLASH
__data_load_start = LOADADDR(.data);
/* This is the uninitialized data section */
.bss (NOLOAD) : {
. = ALIGN(4);
__bss_start = .; /* This is used by the startup in order to initialize the .bss secion */
*(.bss)
*(.bss.*)
*(COMMON)
. = ALIGN(4);
__bss_end = .; /* This is used by the startup in order to initialize the .bss secion */
} >RAM
.stack (NOLOAD) : {
. = ALIGN(8);
_sstack = .;
. = . + __Stack_Size;
. = ALIGN(8);
_estack = .;
} >RAM
/* after that it's only debugging information. */
/* remove the debugging information from the standard libraries */
DISCARD : {
libc.a ( * )
libm.a ( * )
libgcc.a ( * )
}
/* Stabs debugging sections. */
.stab 0 : { *(.stab) }
.stabstr 0 : { *(.stabstr) }
.stab.excl 0 : { *(.stab.excl) }
.stab.exclstr 0 : { *(.stab.exclstr) }
.stab.index 0 : { *(.stab.index) }
.stab.indexstr 0 : { *(.stab.indexstr) }
.comment 0 : { *(.comment) }
/* DWARF debug sections.
Symbols in the DWARF debugging sections are relative to the beginning
of the section so we begin them at 0. */
/* DWARF 1 */
.debug 0 : { *(.debug) }
.line 0 : { *(.line) }
/* GNU DWARF 1 extensions */
.debug_srcinfo 0 : { *(.debug_srcinfo) }
.debug_sfnames 0 : { *(.debug_sfnames) }
/* DWARF 1.1 and DWARF 2 */
.debug_aranges 0 : { *(.debug_aranges) }
.debug_pubnames 0 : { *(.debug_pubnames) }
/* DWARF 2 */
.debug_info 0 : { *(.debug_info .gnu.linkonce.wi.*) }
.debug_abbrev 0 : { *(.debug_abbrev) }
.debug_line 0 : { *(.debug_line) }
.debug_frame 0 : { *(.debug_frame) }
.debug_str 0 : { *(.debug_str) }
.debug_loc 0 : { *(.debug_loc) }
.debug_macinfo 0 : { *(.debug_macinfo) }
/* SGI/MIPS DWARF 2 extensions */
.debug_weaknames 0 : { *(.debug_weaknames) }
.debug_funcnames 0 : { *(.debug_funcnames) }
.debug_typenames 0 : { *(.debug_typenames) }
.debug_varnames 0 : { *(.debug_varnames) }
}

View file

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

View file

@ -0,0 +1,54 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <stdint.h>
#include <stdbool.h>
typedef enum BootBitValue {
BOOT_BIT_INITIALIZED = 0x1 << 0,
BOOT_BIT_NEW_FW_AVAILABLE = 0x1 << 1,
BOOT_BIT_NEW_FW_UPDATE_IN_PROGRESS = 0x1 << 2,
BOOT_BIT_FW_START_FAIL_STRIKE_ONE = 0x1 << 3,
BOOT_BIT_FW_START_FAIL_STRIKE_TWO = 0x1 << 4,
BOOT_BIT_RECOVERY_LOAD_FAIL_STRIKE_ONE = 0x1 << 5,
BOOT_BIT_RECOVERY_LOAD_FAIL_STRIKE_TWO = 0x1 << 6,
BOOT_BIT_RECOVERY_START_IN_PROGRESS = 0x1 << 7,
BOOT_BIT_STANDBY_MODE_REQUESTED = 0x1 << 8, //!< Bootloader enter standby immediately after reset.
BOOT_BIT_SOFTWARE_FAILURE_OCCURRED = 0x1 << 9,
BOOT_BIT_NEW_SYSTEM_RESOURCES_AVAILABLE = 0x1 << 10,
BOOT_BIT_RESET_LOOP_DETECT_ONE = 0x1 << 11,
BOOT_BIT_RESET_LOOP_DETECT_TWO = 0x1 << 12,
BOOT_BIT_RESET_LOOP_DETECT_THREE = 0x1 << 13,
BOOT_BIT_FW_STABLE = 0x1 << 14,
BOOT_BIT_NEW_FW_INSTALLED = 0x1 << 15,
BOOT_BIT_STANDBY_MODE_ENTERED = 0x1 << 16,
BOOT_BIT_FORCE_PRF = 0x1 << 17,
BOOT_BIT_NEW_PRF_AVAILABLE = 0x1 << 18,
BOOT_BIT_SHUTDOWN_REQUESTED = 0x1 << 19, //!< Bootloader hard power-off instead of jumping to fw.
} BootBitValue;
void boot_bit_init();
void boot_bit_set(BootBitValue bit);
void boot_bit_clear(BootBitValue bit);
bool boot_bit_test(BootBitValue bit);
// Dump the contents through dbgserial
void boot_bit_dump(void);
void boot_version_write(void);
uint32_t boot_version_read(void);

View file

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

View file

@ -0,0 +1,23 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "util/attributes.h"
//! Does not call reboot_reason_set, only calls reboot_reason_set_restarted_safely if we were
//! able shut everything down nicely before rebooting.
NORETURN reset_due_to_software_failure(void);

View file

@ -0,0 +1,30 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "firmware_storage.h"
#include "drivers/flash.h"
FirmwareDescription firmware_storage_read_firmware_description(uint32_t firmware_start_address) {
FirmwareDescription firmware_description;
flash_read_bytes((uint8_t*)&firmware_description, firmware_start_address,
sizeof(FirmwareDescription));
return firmware_description;
}
bool firmware_storage_check_valid_firmware_description(FirmwareDescription* desc) {
return desc->description_length == sizeof(FirmwareDescription);
}

View file

@ -0,0 +1,33 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
//! @file firmware_storage.h
//! Utilities for reading a firmware image stored in flash.
#include <stdbool.h>
#include <stdint.h>
typedef struct __attribute__((__packed__)) FirmwareDescription {
uint32_t description_length;
uint32_t firmware_length;
uint32_t checksum;
} FirmwareDescription;
FirmwareDescription firmware_storage_read_firmware_description(uint32_t firmware_start_address);
bool firmware_storage_check_valid_firmware_description(FirmwareDescription* desc);

View file

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

View file

@ -0,0 +1,87 @@
/*
* 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 "drivers/dbgserial.h"
#include "system/die.h"
#include "util/attributes.h"
#include <string.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
static NORETURN prv_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 NORETURN prv_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);
prv_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);
prv_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) {
prv_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;
prv_handle_passert_failed((const char*) file, line, saved_lr, "STM32", "STM32 peripheral library "
"tripped an assert");
}

View file

@ -0,0 +1,53 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "logging.h"
void passert_failed(const char* filename, int line_number, const char* message, ...)
__attribute__((noreturn));
#define PBL_ASSERT(expr, ...) \
do { \
if (!(expr)) { \
passert_failed(__FILE_NAME__, __LINE__, __VA_ARGS__); \
} \
} while (0)
#define PBL_ASSERTN(expr) \
do { \
if (!(expr)) { \
passert_failed_no_message(__FILE_NAME__, __LINE__); \
} \
} while (0)
void passert_failed_no_message(const char* filename, int line_number)
__attribute__((noreturn));
void wtf(void) __attribute__((noreturn));
#define WTF wtf()
// Insert a compiled-in breakpoint
#define BREAKPOINT __asm("bkpt")
#define PBL_ASSERT_PRIVILEGED()
#define PBL_ASSERT_TASK(task)
#define PBL_CROAK(fmt, args...) \
passert_failed(__FILE_NAME__, __LINE__, "*** CROAK: " fmt, ## args)

View file

@ -0,0 +1,36 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "system/reset.h"
#include "drivers/display.h"
#include "stm32f7xx.h"
void system_reset(void) {
display_prepare_for_reset();
// Clear the reset reason since it will no longer
// apply after this bootloader reset
RCC_ClearFlag();
system_hard_reset();
}
void system_hard_reset(void) {
NVIC_SystemReset();
__builtin_unreachable();
}

View file

@ -0,0 +1,26 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <stdbool.h>
//! Reset nicely after shutting down system services. Does not set the reboot_reason other than
//! calling reboot_reason_set_restarted_safely just before the reset occurs.
void system_reset(void)__attribute__((noreturn));
//! The final stage in the reset process.
void system_hard_reset(void) __attribute__((noreturn));

View file

@ -0,0 +1,31 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#define RTC_BKP_BOOTBIT_DR RTC_BKP_DR0
#define STUCK_BUTTON_REGISTER RTC_BKP_DR1
#define BOOTLOADER_VERSION_REGISTER RTC_BKP_DR2
#define CURRENT_TIME_REGISTER RTC_BKP_DR3
#define CURRENT_INTERVAL_TICKS_REGISTER RTC_BKP_DR4
#define REBOOT_REASON_REGISTER_1 RTC_BKP_DR5
#define REBOOT_REASON_REGISTER_2 RTC_BKP_DR6
#define REBOOT_REASON_STUCK_TASK_PC RTC_BKP_DR7
#define REBOOT_REASON_STUCK_TASK_LR RTC_BKP_DR8
#define REBOOT_REASON_STUCK_TASK_CALLBACK RTC_BKP_DR9
#define REBOOT_REASON_MUTEX_LR RTC_BKP_DR10 // Now REBOOT_REASON_DROPPED_EVENT
#define REBOOT_REASON_MUTEX_PC RTC_BKP_DR11 // Deprecated
#define SLOT_OF_LAST_LAUNCHED_APP RTC_BKP_DR19

View file

@ -0,0 +1,50 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#if defined(__clang__)
#define GCC_ONLY(x)
#else
#define GCC_ONLY(x) x
#endif
// Function attributes
#define FORMAT_FUNC(TYPE, STR_IDX, FIRST) __attribute__((__format__(TYPE, STR_IDX, FIRST)))
#define FORMAT_PRINTF(STR_IDX, FIRST) FORMAT_FUNC(__printf__, STR_IDX, FIRST)
#define ALWAYS_INLINE __attribute__((__always_inline__)) inline
#define NOINLINE __attribute__((__noinline__))
#define NORETURN __attribute__((__noreturn__)) void
#define NAKED_FUNC __attribute__((__naked__))
#define OPTIMIZE_FUNC(LVL) __attribute__((__optimize__(LVL)))
#define CONST_FUNC __attribute__((__const__))
#define PURE_FUNC __attribute__((__pure__))
// Variable attributes
#define ATTR_CLEANUP(FUNC) __attribute__((__cleanup__(FUNC)))
// Structure attributes
#define PACKED __attribute__((__packed__))
// General attributes
#define USED __attribute__((__used__))
#define UNUSED __attribute__((__unused__))
#define WEAK __attribute__((__weak__))
#define ALIAS(sym) __attribute__((__weak__, __alias__(sym)))
#define SECTION(SEC) GCC_ONLY(__attribute__((__section__(SEC))))
#define EXTERNALLY_VISIBLE GCC_ONLY(__attribute__((__externally_visible__)))

View file

@ -0,0 +1,51 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "cobs.h"
size_t cobs_encode(void *dst_ptr, const void *src_ptr, size_t length) {
const char *src = src_ptr;
char *dst = dst_ptr;
uint8_t code = 0x01;
size_t code_idx = 0;
size_t dst_idx = 1;
for (size_t src_idx = 0; src_idx < length; ++src_idx) {
if (src[src_idx] == '\0') {
dst[code_idx] = code;
code_idx = dst_idx++;
code = 0x01;
} else {
dst[dst_idx++] = src[src_idx];
code++;
if (code == 0xff) {
if (src_idx == length - 1) {
// Special case: the final encoded block is 254 bytes long with no
// zero after it. While it's technically a valid encoding if a
// trailing zero is appended, it causes the output to be one byte
// longer than it needs to be. This violates consistent overhead
// contract and could overflow a carefully sized buffer.
break;
}
dst[code_idx] = code;
code_idx = dst_idx++;
code = 0x01;
}
}
}
dst[code_idx] = code;
return dst_idx;
}

View file

@ -0,0 +1,40 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <stddef.h>
#include <stdint.h>
//! An implementation of Consistent Overhead Byte Stuffing
//!
//! http://conferences.sigcomm.org/sigcomm/1997/papers/p062.pdf
//! http://en.wikipedia.org/wiki/Consistent_Overhead_Byte_Stuffing
//! Evaluates to the offset required when encoding in-place
#define COBS_OVERHEAD(n) (((n) + 253) / 254)
//! Evaluates to the maximum buffer size required to hold n bytes of data
//! after COBS encoding.
#define MAX_SIZE_AFTER_COBS_ENCODING(n) ((n) + COBS_OVERHEAD(n))
//! COBS-encode a buffer out to another buffer.
//!
//! @param [out] dst destination buffer. The buffer must be at least
//! MAX_SIZE_AFTER_COBS_ENCODING(length) bytes long.
//! @param [in] src source buffer
//! @param length length of src
size_t cobs_encode(void * restrict dst, const void * restrict src,
size_t length);

View file

@ -0,0 +1,48 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "util/crc32.h"
// Nybble-wide table driven CRC-32 algorithm
//
// A compromise between speed and size, this algorithm uses a lookup table to
// calculate the CRC four bits at a time with a size cost of only 64 bytes. By
// contrast, a byte-wide algorithm requires a lookup table sixteen times larger!
//
// The lookup table is generated by the crc32_lut.py
static const uint32_t s_lookup_table[] = {
0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac,
0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c,
0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c,
0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c,
};
uint32_t crc32(uint32_t crc, const void * restrict data, size_t length) {
if (data == 0) {
return 0;
}
const uint8_t * restrict bytes = data;
crc ^= 0xffffffff;
while (length--) {
crc = (crc >> 4) ^ s_lookup_table[(crc ^ *bytes) & 0xf];
crc = (crc >> 4) ^ s_lookup_table[(crc ^ (*bytes >> 4)) & 0xf];
bytes++;
}
crc ^= 0xffffffff;
return crc;
}

View file

@ -0,0 +1,67 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
//! \file
//! Calculate the CRC-32 checksum of data.
//!
//! The checksum is the standard CRC-32 used by zlib, PNG and others.
//! The model parameters for the algorithm, as described in A Painless Guide to
//! CRC Error Detection Algorithms (http://www.zlib.net/crc_v3.txt), are:
//! Name: "CRC-32"
//! Width: 32
//! Poly: 04C11DB7
//! Init: FFFFFFFF
//! RefIn: True
//! RefOut: True
//! XorOut: FFFFFFFF
//! Check: CBF43926
#include <stdint.h>
#include <string.h>
//! Update a running CRC-32 checksum with the bytes of data and return the
//! updated CRC-32. If data is NULL, the function returns the required initial
//! value for the CRC.
//!
//! This function is drop-in compatible with zlib's crc32 function.
//!
//! \par Usage
//! \code
//! uint32_t crc = crc32(0, NULL, 0);
//! while (read_buffer(data, length)) {
//! crc = crc32(crc, data, length);
//! }
//! \endcode
uint32_t crc32(uint32_t crc, const void * restrict data, size_t length);
//! The initial CRC register value for a standard CRC-32 checksum.
//!
//! It is the same value as is returned by the `crc32` function when data is
//! NULL.
//!
//! \code
//! assert(CRC32_INIT == crc32(0, NULL, 0));
//! \endcode
#define CRC32_INIT (0)
//! The residue constant of the CRC-32 algorithm.
//!
//! If the CRC-32 value of a message is appended (little-endian) onto the
//! end of the message, the CRC-32 of the concatenated message and CRC will be
//! equal to CRC32_RESIDUE if the message has not been corrupted in transit.
#define CRC32_RESIDUE (0x2144DF1C)

View file

@ -0,0 +1,47 @@
/*
* 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 "util/attributes.h"
#include "stm32f7xx.h"
#include <inttypes.h>
void delay_us(uint32_t us) {
// Empirically (measured on a C2 bb), 1 loop = 1 cycle. (sysclk @
// 16MHz, I-Cache disabled) Alignment of code will have some impact on how
// long this actually takes
uint32_t delay_loops = us * 16;
__asm volatile (
"spinloop: \n"
" subs %[delay_loops], #1 \n"
" bne spinloop \n"
: [delay_loops] "+r" (delay_loops) // read-write operand
:
: "cc"
);
}
void delay_ms(uint32_t millis) {
// delay_us(millis*1000) is not used because a long delay could easily
// overflow the veriable. Without the outer loop, a delay of even five
// seconds would overflow.
while (millis--) {
delay_us(1000);
}
}

View file

@ -0,0 +1,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 <inttypes.h>
//! Carefully timed spinloop that allows one to delay at a microsecond
//! granularity.
void delay_us(uint32_t us);
void delay_ms(uint32_t millis);

View file

@ -0,0 +1,31 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "util/math.h"
#include <stdint.h>
#include <stdbool.h>
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));
}

View file

@ -0,0 +1,79 @@
/*
* 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>
#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 ROUND(num, denom) (((num) + ((denom) / 2))/(denom))
#define WITHIN(n, min, max) ((n) >= (min) && (n) <= (max))
#define RANGE_WITHIN(n_min, n_max, min, max) ((n_min) >= (min) && (n_max) <= (max))
// Divide num by denom, rounding up (ceil(0.5) is 1.0, and ceil(-0.5) is 0.0)
// ex. 3, 4 (ie. 3/4) : returns 1
// ex. -3, 4 : returns 0
#define DIVIDE_CEIL(num, denom) ((num + (denom - 1)) / denom)
// Round value up to the next increment of modulus
// ex. val = 152 mod = 32 : returns 160
// val = -32 mod = 90 : returns -90
#define ROUND_TO_MOD_CEIL(val, mod) \
((val >= 0) ? \
((((val) + ABS(ABS(mod) - 1)) / ABS(mod)) * ABS(mod)) : \
-((((-val) + ABS(ABS(mod) - 1)) / ABS(mod)) * ABS(mod)))
/*
* find the log base two of a number rounded up
*/
int ceil_log_two(uint32_t n);
/*
* 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.
*/
//! Determine whether a variable is signed or not.
//! @param var The variable to evaluate.
//! @return true if the variable is signed.
#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))))) \
)
/**
* 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);
//! Find the greatest common divisor of two numbers.
uint32_t gcd(uint32_t a, uint32_t b);

View file

@ -0,0 +1,46 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "misc.h"
#include "drivers/dbgserial.h"
#include <stdint.h>
void itoa_hex(uint32_t num, char *buffer, int buffer_length) {
if (buffer_length < 11) {
dbgserial_putstr("itoa buffer too small");
return;
}
*buffer++ = '0';
*buffer++ = 'x';
for (int i = 7; i >= 0; --i) {
uint32_t digit = (num & (0xf << (i * 4))) >> (i * 4);
char c;
if (digit < 0xa) {
c = '0' + digit;
} else if (digit < 0x10) {
c = 'a' + (digit - 0xa);
} else {
c = ' ';
}
*buffer++ = c;
}
*buffer = '\0';
}

View file

@ -0,0 +1,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 <stdbool.h>
#include <stdint.h>
#include <string.h>
#define MIN(a, b) (((a) < (b)) ? (a) : (b))
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
#define MHZ_TO_HZ(hz) (((uint32_t)(hz)) * 1000000)
#define ARRAY_LENGTH(array) (sizeof((array))/sizeof((array)[0]))
//! Convert num to a hex string and put in buffer
void itoa_hex(uint32_t num, char *buffer, int buffer_length);

View file

@ -0,0 +1,80 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <stdint.h>
// When compiling test, the host OS might have conflicting defines for this:
#undef ntohs
#undef htons
#undef ntohl
#undef htonl
#undef ltohs
#undef ltohl
static inline uint16_t ntohs(uint16_t v) {
// return ((v & 0x00ff) << 8) | ((v & 0xff00) >> 8);
return __builtin_bswap16(v);
}
static inline uint16_t htons(uint16_t v) {
return ntohs(v);
}
static inline uint32_t ntohl(uint32_t v) {
// return ((v & 0x000000ff) << 24) |
// ((v & 0x0000ff00) << 8) |
// ((v & 0x00ff0000) >> 8) |
// ((v & 0xff000000) >> 24);
return __builtin_bswap32(v);
}
static inline uint32_t htonl(uint32_t v) {
return ntohl(v);
}
#define ltohs(v) (v)
#define ltohl(v) (v)
// Types for values in network byte-order. They are wrapped in structs so that
// the compiler will disallow implicit casting of these types to or from
// integral types. This way it is a compile error to try using variables of
// these types without first performing a byte-order conversion.
// There is no overhead for wrapping the values in structs.
typedef struct net16 {
uint16_t v;
} net16;
typedef struct net32 {
uint32_t v;
} net32;
static inline uint16_t ntoh16(net16 net) {
return ntohs(net.v);
}
static inline net16 hton16(uint16_t v) {
return (net16){ htons(v) };
}
static inline uint32_t ntoh32(net32 net) {
return ntohl(net.v);
}
static inline net32 hton32(uint32_t v) {
return (net32){ htonl(v) };
}

View file

@ -0,0 +1,31 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
//! Calculate the length of an array, based on the size of the element type.
//! @param array The array to be evaluated.
//! @return The length of the array.
#define ARRAY_LENGTH(array) (sizeof((array))/sizeof((array)[0]))
//! Calculate the length of a literal array based on the size of the given type
//! This is usable in contexts that require compile time constants
//! @param type Type of the elements
//! @param array Literal definition of the array
//! @return Length of the array in bytes
#define STATIC_ARRAY_LENGTH(type, array) (sizeof((type[]) array) / sizeof(type))
#define MEMBER_SIZE(type, member) sizeof(((type *)0)->member)

View file

@ -0,0 +1,88 @@
/*
* 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 "sle.h"
#include "system/passert.h"
// See waftools/sparse_length_encoding.py for more info on SLE encoding/decoding
typedef struct {
const uint8_t *data;
uint32_t index;
uint32_t length;
} ReadByteStream;
typedef struct {
uint8_t *data;
uint32_t index;
uint32_t length;
} WriteByteStream;
static uint8_t prv_byte_stream_read(ReadByteStream *stream) {
PBL_ASSERTN(stream->index < stream->length);
return stream->data[stream->index++];
}
static void prv_byte_stream_write(WriteByteStream *stream, uint8_t data) {
PBL_ASSERTN(stream->index < stream->length);
stream->data[stream->index++] = data;
}
uint32_t sle_decode(const uint8_t *in, uint32_t in_len, uint8_t *out, uint32_t out_len) {
ReadByteStream in_stream = {
.data = in,
.length = in_len
};
WriteByteStream out_stream = {
.data = out,
.length = out_len
};
const uint8_t escape = prv_byte_stream_read(&in_stream);
while (true) {
const uint8_t byte = prv_byte_stream_read(&in_stream);
if (byte != escape) {
// simply write the byte into the output stream
prv_byte_stream_write(&out_stream, byte);
continue;
}
// read the escape code
const uint8_t code = prv_byte_stream_read(&in_stream);
if (code == 0) {
// end of stream
break;
} else if (code == 1) {
// literal escape byte
prv_byte_stream_write(&out_stream, escape);
} else {
// a sequence of zeros
uint16_t count;
if ((code & 0x80) == 0) {
// the count is only 1 byte (1-127)
count = code;
} else {
// the count is 2 bytes
count = (((uint16_t)(code & 0x7f) << 8) | prv_byte_stream_read(&in_stream)) + 0x80;
}
for (int i = 0; i < count; ++i) {
prv_byte_stream_write(&out_stream, 0);
}
}
}
return out_stream.index;
}

View file

@ -0,0 +1,21 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <stdint.h>
uint32_t sle_decode(const uint8_t *in, uint32_t in_len, uint8_t *out, uint32_t out_len);

View file

@ -0,0 +1,96 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
extern int main(void);
//! These symbols are defined in the linker script for use in initializing
//! the data sections. uint8_t since we do arithmetic with section lengths.
//! These are arrays to avoid the need for an & when dealing with linker symbols.
extern uint8_t __data_load_start[];
extern uint8_t __data_start[];
extern uint8_t __data_end[];
extern uint8_t __bss_start[];
extern uint8_t __bss_end[];
extern uint8_t _estack[];
__attribute__((__noreturn__)) void Reset_Handler(void) {
memcpy(__data_start, __data_load_start, __data_end - __data_start);
// Clear the bss section, assumes .bss goes directly after .data
memset(__bss_start, 0, __bss_end - __bss_start);
main();
__builtin_unreachable();
}
__attribute__((__noreturn__)) void Default_Handler(void) {
// This handler is only called if we haven't defined a specific
// handler for the interrupt. This means the interrupt is unexpected,
// so we loop infinitely to preserve the system state for examination
// by a debugger
while (true) {}
}
// All these functions are weak references to the Default_Handler,
// so if we define a handler in elsewhere in the firmware, these
// will be overriden
#define ALIAS(sym) __attribute__((__weak__, __alias__(sym)))
ALIAS("Default_Handler") void NMI_Handler(void);
ALIAS("Default_Handler") void HardFault_Handler(void);
ALIAS("Default_Handler") void MemManage_Handler(void);
ALIAS("Default_Handler") void BusFault_Handler(void);
ALIAS("Default_Handler") void UsageFault_Handler(void);
ALIAS("Default_Handler") void SVC_Handler(void);
ALIAS("Default_Handler") void DebugMon_Handler(void);
ALIAS("Default_Handler") void PendSV_Handler(void);
ALIAS("Default_Handler") void SysTick_Handler(void);
// External Interrupts
#define IRQ_DEF(idx, irq) ALIAS("Default_Handler") void irq##_IRQHandler(void);
#include "irq_stm32f7.def"
#undef IRQ_DEF
__attribute__((__section__(".isr_vector"))) const void * const vector_table[] = {
_estack,
Reset_Handler,
NMI_Handler,
HardFault_Handler,
MemManage_Handler,
BusFault_Handler,
UsageFault_Handler,
0,
0,
0,
0,
SVC_Handler,
DebugMon_Handler,
0,
PendSV_Handler,
SysTick_Handler,
// External Interrupts
#define IRQ_DEF(idx, irq) [idx + 16] = irq##_IRQHandler,
#include "irq_stm32f7.def"
#undef IRQ_DEF
};