mirror of
https://github.com/google/pebble.git
synced 2025-05-28 14:03:12 +00:00
Import of the watch repository from Pebble
This commit is contained in:
commit
3b92768480
10334 changed files with 2564465 additions and 0 deletions
101
platform/silk/boot/src/board/board.h
Normal file
101
platform/silk/boot/src/board/board.h
Normal file
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "display.h"
|
||||
|
||||
#include "drivers/button_id.h"
|
||||
|
||||
#include "stm32f4xx_gpio.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#define GPIO_Port_NULL ((GPIO_TypeDef *) 0)
|
||||
#define GPIO_Pin_NULL ((uint16_t)0x0000)
|
||||
|
||||
|
||||
typedef struct {
|
||||
//! One of EXTI_PortSourceGPIOX
|
||||
uint8_t exti_port_source;
|
||||
|
||||
//! Value between 0-15
|
||||
uint8_t exti_line;
|
||||
} ExtiConfig;
|
||||
|
||||
typedef struct {
|
||||
const char* const name; ///< Name for debugging purposes.
|
||||
GPIO_TypeDef* const gpio; ///< One of GPIOX. For example, GPIOA.
|
||||
const uint32_t gpio_pin; ///< One of GPIO_Pin_X.
|
||||
ExtiConfig exti;
|
||||
GPIOPuPd_TypeDef pull;
|
||||
} ButtonConfig;
|
||||
|
||||
typedef struct {
|
||||
GPIO_TypeDef* const gpio; ///< One of GPIOX. For example, GPIOA.
|
||||
const uint32_t gpio_pin; ///< One of GPIO_Pin_X.
|
||||
} ButtonComConfig;
|
||||
|
||||
typedef struct {
|
||||
GPIO_TypeDef* const gpio; ///< One of GPIOX. For example, GPIOA.
|
||||
const uint32_t gpio_pin; ///< One of GPIO_Pin_X.
|
||||
bool active_high; ///< Pin is active high or active low
|
||||
} OutputConfig;
|
||||
|
||||
//! Alternate function pin configuration
|
||||
//! Used to configure a pin for use by a peripheral
|
||||
typedef struct {
|
||||
GPIO_TypeDef* const gpio; ///< One of GPIOX. For example, GPIOA.
|
||||
const uint32_t gpio_pin; ///< One of GPIO_Pin_X.
|
||||
const uint16_t gpio_pin_source; ///< One of GPIO_PinSourceX.
|
||||
const uint8_t gpio_af; ///< One of GPIO_AF_X
|
||||
} AfConfig;
|
||||
|
||||
typedef struct {
|
||||
I2C_TypeDef *const i2c;
|
||||
AfConfig i2c_scl; ///< Alternate Function configuration for SCL pin
|
||||
AfConfig i2c_sda; ///< Alternate Function configuration for SDA pin
|
||||
uint32_t clock_ctrl; ///< Peripheral clock control flag
|
||||
uint32_t clock_speed; ///< Bus clock speed
|
||||
uint32_t duty_cycle; ///< Bus clock duty cycle in fast mode
|
||||
const uint8_t ev_irq_channel; ///< I2C Event interrupt (One of X_IRQn). For example, I2C1_EV_IRQn.
|
||||
const uint8_t er_irq_channel; ///< I2C Error interrupt (One of X_IRQn). For example, I2C1_ER_IRQn.
|
||||
void (* const rail_cfg_fn)(void); //! Configure function for pins on this rail.
|
||||
void (* const rail_ctl_fn)(bool enabled); //! Control function for this rail.
|
||||
} I2cBusConfig;
|
||||
|
||||
typedef enum I2cDevice {
|
||||
I2C_DEVICE_AS3701B,
|
||||
} I2cDevice;
|
||||
|
||||
typedef struct {
|
||||
// I2C Configuration
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
const I2cBusConfig *i2c_bus_configs;
|
||||
const uint8_t i2c_bus_count;
|
||||
const uint8_t *i2c_device_map;
|
||||
const uint8_t i2c_device_count;
|
||||
} BoardConfig;
|
||||
|
||||
// Button Configuration
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
typedef struct {
|
||||
const ButtonConfig buttons[NUM_BUTTONS];
|
||||
const ButtonComConfig button_com;
|
||||
} BoardConfigButton;
|
||||
|
||||
#include "board_definitions.h"
|
23
platform/silk/boot/src/board/board_definitions.h
Normal file
23
platform/silk/boot/src/board/board_definitions.h
Normal file
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#if BOARD_SILK
|
||||
#include "board_silk.h"
|
||||
#else
|
||||
#error "Unknown board definition"
|
||||
#endif
|
115
platform/silk/boot/src/board/board_silk.h
Normal file
115
platform/silk/boot/src/board/board_silk.h
Normal file
|
@ -0,0 +1,115 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "util/misc.h"
|
||||
|
||||
#define BOARD_LSE_MODE RCC_LSE_Bypass
|
||||
|
||||
#define USE_PARALLEL_FLASH 1 // FIXME PBL-28872: Hack to get the "modern" flash layout.
|
||||
// Fix when we add support for new flash
|
||||
|
||||
#define BOARD_I2C_BUS_COUNT (ARRAY_LENGTH(SILK_I2C_BUS_CONFIGS))
|
||||
|
||||
static const I2cBusConfig SILK_I2C_BUS_CONFIGS[] = {
|
||||
// PMIC I2c
|
||||
[0] = {
|
||||
.i2c = I2C3,
|
||||
.i2c_scl = { GPIOA, GPIO_Pin_8, GPIO_PinSource8, GPIO_AF_I2C3 },
|
||||
.i2c_sda = { GPIOB, GPIO_Pin_8, GPIO_PinSource8, GPIO_AF9_I2C3 },
|
||||
.clock_speed = 400000,
|
||||
.duty_cycle = I2C_DutyCycle_16_9,
|
||||
.clock_ctrl = RCC_APB1Periph_I2C3,
|
||||
.ev_irq_channel = I2C3_EV_IRQn,
|
||||
.er_irq_channel = I2C3_ER_IRQn,
|
||||
},
|
||||
};
|
||||
|
||||
static const uint8_t SILK_I2C_DEVICE_MAP[] = {
|
||||
[I2C_DEVICE_AS3701B] = 0,
|
||||
};
|
||||
|
||||
static const BoardConfig BOARD_CONFIG = {
|
||||
.i2c_bus_configs = SILK_I2C_BUS_CONFIGS,
|
||||
.i2c_bus_count = BOARD_I2C_BUS_COUNT,
|
||||
.i2c_device_map = SILK_I2C_DEVICE_MAP,
|
||||
.i2c_device_count = ARRAY_LENGTH(SILK_I2C_DEVICE_MAP),
|
||||
};
|
||||
|
||||
static const BoardConfigButton BOARD_CONFIG_BUTTON = {
|
||||
.buttons = {
|
||||
[BUTTON_ID_BACK] =
|
||||
{ "Back", GPIOC, GPIO_Pin_13, { EXTI_PortSourceGPIOC, 13 }, GPIO_PuPd_NOPULL },
|
||||
[BUTTON_ID_UP] =
|
||||
{ "Up", GPIOD, GPIO_Pin_2, { EXTI_PortSourceGPIOD, 2 }, GPIO_PuPd_DOWN },
|
||||
[BUTTON_ID_SELECT] =
|
||||
{ "Select", GPIOH, GPIO_Pin_0, { EXTI_PortSourceGPIOH, 0 }, GPIO_PuPd_DOWN },
|
||||
[BUTTON_ID_DOWN] =
|
||||
{ "Down", GPIOH, GPIO_Pin_1, { EXTI_PortSourceGPIOH, 1 }, GPIO_PuPd_DOWN },
|
||||
},
|
||||
|
||||
.button_com = { 0 },
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
QSpiPin_CS,
|
||||
QSpiPin_SCLK,
|
||||
QSpiPin_DQ0,
|
||||
QSpiPin_DQ1,
|
||||
QSpiPin_DQ2,
|
||||
QSpiPin_DQ3,
|
||||
QSpiPinCount,
|
||||
} QSpiPin;
|
||||
|
||||
static const AfConfig BOARD_CONFIG_FLASH_PINS[] = {
|
||||
[QSpiPin_CS] = {
|
||||
.gpio = GPIOB,
|
||||
.gpio_pin = GPIO_Pin_6,
|
||||
.gpio_pin_source = GPIO_PinSource6,
|
||||
.gpio_af = GPIO_AF10_QUADSPI,
|
||||
},
|
||||
[QSpiPin_SCLK] = {
|
||||
.gpio = GPIOB,
|
||||
.gpio_pin = GPIO_Pin_2,
|
||||
.gpio_pin_source = GPIO_PinSource2,
|
||||
.gpio_af = GPIO_AF9_QUADSPI,
|
||||
},
|
||||
[QSpiPin_DQ0] = {
|
||||
.gpio = GPIOC,
|
||||
.gpio_pin = GPIO_Pin_9,
|
||||
.gpio_pin_source = GPIO_PinSource9,
|
||||
.gpio_af = GPIO_AF9_QUADSPI,
|
||||
},
|
||||
[QSpiPin_DQ1] = {
|
||||
.gpio = GPIOC,
|
||||
.gpio_pin = GPIO_Pin_10,
|
||||
.gpio_pin_source = GPIO_PinSource10,
|
||||
.gpio_af = GPIO_AF9_QUADSPI,
|
||||
},
|
||||
[QSpiPin_DQ2] = {
|
||||
.gpio = GPIOC,
|
||||
.gpio_pin = GPIO_Pin_8,
|
||||
.gpio_pin_source = GPIO_PinSource8,
|
||||
.gpio_af = GPIO_AF9_QUADSPI,
|
||||
},
|
||||
[QSpiPin_DQ3] = {
|
||||
.gpio = GPIOA,
|
||||
.gpio_pin = GPIO_Pin_1,
|
||||
.gpio_pin_source = GPIO_PinSource1,
|
||||
.gpio_af = GPIO_AF9_QUADSPI,
|
||||
},
|
||||
};
|
21
platform/silk/boot/src/board/display.h
Normal file
21
platform/silk/boot/src/board/display.h
Normal file
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#define DISP_COLS 144
|
||||
#define DISP_ROWS 168
|
||||
#define PBL_DISP_SHAPE_RECT
|
80
platform/silk/boot/src/boot_tests.c
Normal file
80
platform/silk/boot/src/boot_tests.c
Normal file
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "boot_tests.h"
|
||||
|
||||
#include "board/board.h"
|
||||
#include "drivers/button.h"
|
||||
#include "drivers/dbgserial.h"
|
||||
#include "drivers/flash.h"
|
||||
#include "system/bootbits.h"
|
||||
#include "system/rtc_registers.h"
|
||||
#include "util/misc.h"
|
||||
|
||||
#include "stm32f4xx.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
static const int STUCK_BUTTON_THRESHOLD = 5;
|
||||
|
||||
bool is_button_stuck(void) {
|
||||
// We store how many times each button has been pressed on previous boots in this
|
||||
// rtc backup register. Every time when we boot without that button pressed that
|
||||
// counter gets cleared. If the byte reaches 5, return a failure.
|
||||
|
||||
uint32_t button_counter_register = RTC_ReadBackupRegister(STUCK_BUTTON_REGISTER);
|
||||
uint8_t* button_counter = (uint8_t*) (&button_counter_register);
|
||||
bool result = false;
|
||||
|
||||
for (int button_id = 0; button_id < NUM_BUTTONS; button_id++) {
|
||||
if (!button_is_pressed(button_id)) {
|
||||
button_counter[button_id] = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (button_counter[button_id] >= STUCK_BUTTON_THRESHOLD) {
|
||||
dbgserial_putstr("Stuck button register is invalid, clearing.");
|
||||
dbgserial_print_hex(button_counter_register);
|
||||
|
||||
RTC_WriteBackupRegister(STUCK_BUTTON_REGISTER, 0);
|
||||
return false;
|
||||
}
|
||||
|
||||
button_counter[button_id] += 1;
|
||||
|
||||
if (button_counter[button_id] >= STUCK_BUTTON_THRESHOLD) {
|
||||
dbgserial_print("Button id ");
|
||||
dbgserial_print_hex(button_id);
|
||||
dbgserial_putstr("is stuck!");
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (button_counter_register != 0) {
|
||||
dbgserial_print("Button was pushed on boot. Button counter: ");
|
||||
dbgserial_print_hex(button_counter_register);
|
||||
dbgserial_newline();
|
||||
}
|
||||
|
||||
RTC_WriteBackupRegister(STUCK_BUTTON_REGISTER, button_counter_register);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool is_flash_broken(void) {
|
||||
return !flash_sanity_check();
|
||||
}
|
22
platform/silk/boot/src/boot_tests.h
Normal file
22
platform/silk/boot/src/boot_tests.h
Normal file
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
bool is_button_stuck(void);
|
||||
bool is_flash_broken(void);
|
85
platform/silk/boot/src/drivers/button.c
Normal file
85
platform/silk/boot/src/drivers/button.c
Normal file
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "drivers/button.h"
|
||||
|
||||
#include "board/board.h"
|
||||
#include "drivers/periph_config.h"
|
||||
#include "drivers/gpio.h"
|
||||
|
||||
static void initialize_button_common(void) {
|
||||
if (!BOARD_CONFIG_BUTTON.button_com.gpio) {
|
||||
// This board doesn't use a button common pin.
|
||||
return;
|
||||
}
|
||||
|
||||
// Configure BUTTON_COM to drive low. When the button
|
||||
// is pressed this pin will be connected to the pin for the
|
||||
// button.
|
||||
GPIO_InitTypeDef GPIO_InitStructure;
|
||||
GPIO_StructInit(&GPIO_InitStructure);
|
||||
|
||||
GPIO_InitStructure.GPIO_Pin = BOARD_CONFIG_BUTTON.button_com.gpio_pin;
|
||||
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
|
||||
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
|
||||
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
|
||||
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
|
||||
GPIO_Init(BOARD_CONFIG_BUTTON.button_com.gpio, &GPIO_InitStructure);
|
||||
GPIO_WriteBit(BOARD_CONFIG_BUTTON.button_com.gpio, BOARD_CONFIG_BUTTON.button_com.gpio_pin, 0);
|
||||
}
|
||||
|
||||
static void initialize_button(const ButtonConfig* config) {
|
||||
// Configure the pin itself
|
||||
GPIO_InitTypeDef GPIO_InitStructure;
|
||||
GPIO_StructInit(&GPIO_InitStructure);
|
||||
|
||||
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
|
||||
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
|
||||
GPIO_InitStructure.GPIO_PuPd = config->pull;
|
||||
GPIO_InitStructure.GPIO_Pin = config->gpio_pin;
|
||||
GPIO_Init(config->gpio, &GPIO_InitStructure);
|
||||
}
|
||||
|
||||
bool button_is_pressed(ButtonId id) {
|
||||
const ButtonConfig* button_config = &BOARD_CONFIG_BUTTON.buttons[id];
|
||||
uint8_t bit = GPIO_ReadInputDataBit(button_config->gpio, button_config->gpio_pin);
|
||||
return bit;
|
||||
}
|
||||
|
||||
uint8_t button_get_state_bits(void) {
|
||||
uint8_t button_state = 0x00;
|
||||
for (int i = 0; i < NUM_BUTTONS; ++i) {
|
||||
button_state |= (button_is_pressed(i) ? 0x01 : 0x00) << i;
|
||||
}
|
||||
return button_state;
|
||||
}
|
||||
|
||||
void button_init(void) {
|
||||
// Need to disable button wakeup functionality
|
||||
// or the buttons don't register input
|
||||
PWR_WakeUpPinCmd(PWR_WakeUp_Pin1, DISABLE);
|
||||
PWR_WakeUpPinCmd(PWR_WakeUp_Pin2, DISABLE);
|
||||
PWR_WakeUpPinCmd(PWR_WakeUp_Pin3, DISABLE);
|
||||
|
||||
periph_config_enable(RCC_APB2PeriphClockCmd, RCC_APB2Periph_SYSCFG);
|
||||
|
||||
initialize_button_common();
|
||||
for (int i = 0; i < NUM_BUTTONS; ++i) {
|
||||
initialize_button(&BOARD_CONFIG_BUTTON.buttons[i]);
|
||||
}
|
||||
|
||||
periph_config_disable(RCC_APB2PeriphClockCmd, RCC_APB2Periph_SYSCFG);
|
||||
}
|
27
platform/silk/boot/src/drivers/button.h
Normal file
27
platform/silk/boot/src/drivers/button.h
Normal file
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "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);
|
41
platform/silk/boot/src/drivers/button_id.h
Normal file
41
platform/silk/boot/src/drivers/button_id.h
Normal file
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
//! @addtogroup UI
|
||||
//! @{
|
||||
//! @addtogroup Clicks
|
||||
//! \brief Dealing with button input
|
||||
//! @{
|
||||
|
||||
//! Button ID values
|
||||
//! @see \ref click_recognizer_get_button_id()
|
||||
typedef enum {
|
||||
//! Back button
|
||||
BUTTON_ID_BACK = 0,
|
||||
//! Up button
|
||||
BUTTON_ID_UP,
|
||||
//! Select (middle) button
|
||||
BUTTON_ID_SELECT,
|
||||
//! Down button
|
||||
BUTTON_ID_DOWN,
|
||||
//! Total number of buttons
|
||||
NUM_BUTTONS
|
||||
} ButtonId;
|
||||
|
||||
//! @} // end addtogroup Clicks
|
||||
//! @} // end addtogroup UI
|
176
platform/silk/boot/src/drivers/dbgserial.c
Normal file
176
platform/silk/boot/src/drivers/dbgserial.c
Normal file
|
@ -0,0 +1,176 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "drivers/dbgserial.h"
|
||||
|
||||
#include "drivers/periph_config.h"
|
||||
|
||||
#include "drivers/gpio.h"
|
||||
|
||||
#include "stm32f4xx_rcc.h"
|
||||
#include "stm32f4xx_gpio.h"
|
||||
#include "stm32f4xx_usart.h"
|
||||
#include "util/attributes.h"
|
||||
#include "util/cobs.h"
|
||||
#include "util/crc32.h"
|
||||
#include "util/misc.h"
|
||||
#include "util/misc.h"
|
||||
#include "util/net.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#define MAX_MESSAGE (256)
|
||||
#define FRAME_DELIMITER '\x55'
|
||||
#define PULSE_TRANSPORT_PUSH (0x5021)
|
||||
#define PULSE_PROTOCOL_LOGGING (0x0003)
|
||||
|
||||
static const int SERIAL_BAUD_RATE = 1000000;
|
||||
|
||||
typedef struct PACKED PulseFrame {
|
||||
net16 protocol;
|
||||
unsigned char information[];
|
||||
} PulseFrame;
|
||||
|
||||
typedef struct PACKED PushPacket {
|
||||
net16 protocol;
|
||||
net16 length;
|
||||
unsigned char information[];
|
||||
} PushPacket;
|
||||
|
||||
static const unsigned char s_message_header[] = {
|
||||
// Message type: text
|
||||
1,
|
||||
// Source filename
|
||||
'B', 'O', 'O', 'T', 'L', 'O', 'A', 'D', 'E', 'R', 0, 0, 0, 0, 0, 0,
|
||||
// Log level and task
|
||||
'*', '*',
|
||||
// Timestamp
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
// Line number
|
||||
0, 0,
|
||||
};
|
||||
|
||||
static size_t s_message_length = 0;
|
||||
static unsigned char s_message_buffer[MAX_MESSAGE];
|
||||
|
||||
|
||||
void dbgserial_init(void) {
|
||||
GPIO_InitTypeDef GPIO_InitStructure;
|
||||
USART_InitTypeDef USART_InitStructure;
|
||||
|
||||
// Enable GPIO and UART3 peripheral clocks
|
||||
periph_config_enable(RCC_APB2PeriphClockCmd, RCC_APB2Periph_USART1);
|
||||
|
||||
// USART_OverSampling8Cmd(USART3, ENABLE);
|
||||
|
||||
/* Connect PXx to USARTx_Tx*/
|
||||
GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_USART1);
|
||||
|
||||
/* Connect PXx to USARTx_Rx*/
|
||||
GPIO_PinAFConfig(GPIOB, GPIO_PinSource7, GPIO_AF_USART1);
|
||||
|
||||
/* Configure USART Tx as alternate function */
|
||||
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
|
||||
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
|
||||
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
|
||||
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
|
||||
|
||||
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
|
||||
GPIO_Init(GPIOA, &GPIO_InitStructure);
|
||||
|
||||
/* Configure USART Rx as alternate function */
|
||||
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
|
||||
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
|
||||
GPIO_Init(GPIOB, &GPIO_InitStructure);
|
||||
|
||||
/* USART configuration */
|
||||
USART_InitStructure.USART_BaudRate = SERIAL_BAUD_RATE;
|
||||
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
|
||||
USART_InitStructure.USART_StopBits = USART_StopBits_1;
|
||||
USART_InitStructure.USART_Parity = USART_Parity_No;
|
||||
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
|
||||
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
|
||||
USART_Init(USART1, &USART_InitStructure);
|
||||
|
||||
/* Enable USART */
|
||||
USART_Cmd(USART1, ENABLE);
|
||||
}
|
||||
|
||||
static void prv_putchar(uint8_t c) {
|
||||
while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET) continue;
|
||||
USART_SendData(USART1, c);
|
||||
while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET) continue;
|
||||
}
|
||||
|
||||
void dbgserial_print(const char* str) {
|
||||
for (; *str && s_message_length < MAX_MESSAGE; ++str) {
|
||||
if (*str == '\n') {
|
||||
dbgserial_newline();
|
||||
} else if (*str != '\r') {
|
||||
s_message_buffer[s_message_length++] = *str;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void dbgserial_newline(void) {
|
||||
uint32_t crc;
|
||||
size_t raw_length = sizeof(PulseFrame) + sizeof(PushPacket) +
|
||||
sizeof(s_message_header) + s_message_length + sizeof(crc);
|
||||
unsigned char raw_packet[raw_length];
|
||||
|
||||
PulseFrame *frame = (PulseFrame *)raw_packet;
|
||||
frame->protocol = hton16(PULSE_TRANSPORT_PUSH);
|
||||
|
||||
PushPacket *transport = (PushPacket *)frame->information;
|
||||
transport->protocol = hton16(PULSE_PROTOCOL_LOGGING);
|
||||
transport->length = hton16(sizeof(PushPacket) + sizeof(s_message_header) +
|
||||
s_message_length);
|
||||
|
||||
unsigned char *app = transport->information;
|
||||
memcpy(app, s_message_header, sizeof(s_message_header));
|
||||
memcpy(&app[sizeof(s_message_header)], s_message_buffer,
|
||||
s_message_length);
|
||||
|
||||
crc = crc32(CRC32_INIT, raw_packet, raw_length - sizeof(crc));
|
||||
memcpy(&raw_packet[raw_length - sizeof(crc)], &crc, sizeof(crc));
|
||||
|
||||
unsigned char cooked_packet[MAX_SIZE_AFTER_COBS_ENCODING(raw_length)];
|
||||
size_t cooked_length = cobs_encode(cooked_packet, raw_packet, raw_length);
|
||||
|
||||
prv_putchar(FRAME_DELIMITER);
|
||||
for (size_t i = 0; i < cooked_length; ++i) {
|
||||
if (cooked_packet[i] == FRAME_DELIMITER) {
|
||||
prv_putchar('\0');
|
||||
} else {
|
||||
prv_putchar(cooked_packet[i]);
|
||||
}
|
||||
}
|
||||
prv_putchar(FRAME_DELIMITER);
|
||||
|
||||
s_message_length = 0;
|
||||
}
|
||||
|
||||
void dbgserial_putstr(const char* str) {
|
||||
dbgserial_print(str);
|
||||
|
||||
dbgserial_newline();
|
||||
}
|
||||
|
||||
void dbgserial_print_hex(uint32_t value) {
|
||||
char buf[12];
|
||||
itoa_hex(value, buf, sizeof(buf));
|
||||
dbgserial_print(buf);
|
||||
}
|
34
platform/silk/boot/src/drivers/dbgserial.h
Normal file
34
platform/silk/boot/src/drivers/dbgserial.h
Normal file
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
void dbgserial_init(void);
|
||||
|
||||
void dbgserial_putstr(const char* str);
|
||||
|
||||
void dbgserial_newline(void);
|
||||
|
||||
//! Like dbgserial_putstr, but without a terminating newline
|
||||
void dbgserial_print(const char* str);
|
||||
|
||||
void dbgserial_print_hex(uint32_t value);
|
||||
|
||||
void dbgserial_putstr_fmt(char* buffer, unsigned int buffer_size, const char* fmt, ...)
|
||||
__attribute__((format(printf, 3, 4)));
|
34
platform/silk/boot/src/drivers/display.h
Normal file
34
platform/silk/boot/src/drivers/display.h
Normal file
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
void display_init(void);
|
||||
|
||||
void display_boot_splash(void);
|
||||
|
||||
void display_error_code(uint32_t);
|
||||
|
||||
//! Do whatever is necessary to prevent visual artifacts when resetting
|
||||
//! the watch.
|
||||
void display_prepare_for_reset(void);
|
||||
|
||||
//! Display the progress of a firmware update.
|
||||
//!
|
||||
//! The progress is expressed as a rational number less than or equal to 1.
|
||||
//! When numerator == denominator, the progress indicator shows that the update
|
||||
//! is complete.
|
||||
void display_firmware_update_progress(uint32_t numerator, uint32_t denominator);
|
|
@ -0,0 +1,47 @@
|
|||
#define dead_face_width 92
|
||||
#define dead_face_height 44
|
||||
static const unsigned char dead_face_bits[] = {
|
||||
0x00, 0x0c, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x30, 0x00,
|
||||
0x00, 0x18, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x18, 0x00,
|
||||
0x00, 0x30, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x0c, 0x00,
|
||||
0x00, 0x60, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x06, 0x00,
|
||||
0x00, 0xc0, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x03, 0x00,
|
||||
0x00, 0x80, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8c, 0x01, 0x00,
|
||||
0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd8, 0x00, 0x00,
|
||||
0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00,
|
||||
0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00,
|
||||
0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd8, 0x00, 0x00,
|
||||
0x00, 0x80, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8c, 0x01, 0x00,
|
||||
0x00, 0xc0, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x03, 0x00,
|
||||
0x00, 0x60, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x06, 0x00,
|
||||
0x00, 0x30, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x0c, 0x00,
|
||||
0x00, 0x18, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x18, 0x00,
|
||||
0x00, 0x0c, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x30, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x80, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0xfe, 0xff, 0xff, 0x7f, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0xe0, 0x7f, 0x00, 0x00, 0xfe, 0x07, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0xfe, 0x01, 0x00, 0x00, 0x80, 0x7f, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x80, 0x1f, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x01, 0x00, 0x00,
|
||||
0x00, 0x00, 0xf0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x80, 0x0f, 0x00, 0x00,
|
||||
0x00, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00,
|
||||
0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00,
|
||||
0x00, 0x80, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x01, 0x00,
|
||||
0x00, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00,
|
||||
0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00,
|
||||
0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00,
|
||||
0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00,
|
||||
0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00,
|
||||
0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00,
|
||||
0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01,
|
||||
0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03,
|
||||
0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06,
|
||||
0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c,
|
||||
0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08 };
|
|
@ -0,0 +1,11 @@
|
|||
#define empty_bar_width 96
|
||||
#define empty_bar_height 8
|
||||
static const unsigned char empty_bar_bits[] = {
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
|
||||
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
|
||||
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
|
||||
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
|
||||
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
|
||||
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
|
|
@ -0,0 +1,20 @@
|
|||
#define error_url_width 109
|
||||
#define error_url_height 14
|
||||
static const unsigned char error_url_bits[] = {
|
||||
0x00, 0x00, 0x06, 0x83, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x06, 0x83, 0x01, 0x00, 0x00, 0x00, 0x00, 0x0c,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x83, 0x01, 0x00, 0x00, 0x00,
|
||||
0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0xd8, 0x78, 0x36, 0x9b, 0x79, 0xc0,
|
||||
0xe3, 0xd9, 0x0c, 0x8c, 0x67, 0xdb, 0x3c, 0x1b, 0xf8, 0xfd, 0x7e, 0xbf,
|
||||
0xfd, 0xe0, 0xf7, 0xfb, 0x1f, 0xc6, 0xef, 0xfb, 0x7e, 0x1f, 0x98, 0xcd,
|
||||
0x66, 0xb3, 0xcd, 0x60, 0x36, 0x9b, 0x19, 0xc6, 0x6c, 0x18, 0x66, 0x03,
|
||||
0x98, 0xfd, 0x66, 0xb3, 0xfd, 0x60, 0x30, 0x9b, 0x19, 0xc6, 0x6f, 0x18,
|
||||
0x66, 0x03, 0x98, 0xfd, 0x66, 0xb3, 0xfd, 0x60, 0x30, 0x9b, 0x19, 0xc3,
|
||||
0x6f, 0x18, 0x66, 0x03, 0x98, 0x0d, 0x66, 0xb3, 0x0d, 0x60, 0x36, 0x9b,
|
||||
0x19, 0xc3, 0x60, 0x18, 0x66, 0x03, 0xf8, 0xfd, 0x7e, 0xbf, 0xfd, 0xec,
|
||||
0xf7, 0x9b, 0x19, 0xc3, 0x6f, 0x18, 0x7e, 0x03, 0xd8, 0x78, 0x36, 0x9b,
|
||||
0x79, 0xcc, 0xe3, 0x99, 0x99, 0x81, 0x67, 0x18, 0x3c, 0x03, 0x18, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00,
|
||||
0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00 };
|
103
platform/silk/boot/src/drivers/display/resources/hex_digits.h
Normal file
103
platform/silk/boot/src/drivers/display/resources/hex_digits.h
Normal file
|
@ -0,0 +1,103 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
// hex_digits_bits is indexed on the digit:
|
||||
// ie: hex_digits_bits[0] is the bits to display 0 on
|
||||
// the screen stored in xbm format
|
||||
static const uint8_t hex_digits_bits[][36] = {
|
||||
{
|
||||
0x7C, 0x00, 0xFE, 0x00, 0xFF, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xC7, 0x01,
|
||||
0xC7, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xC7, 0x01,
|
||||
0xC7, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xFF, 0x01, 0xFE, 0x00, 0x7C, 0x00,
|
||||
},
|
||||
{
|
||||
0x38, 0x00, 0x3C, 0x00, 0x3E, 0x00, 0x3E, 0x00, 0x38, 0x00, 0x38, 0x00,
|
||||
0x38, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00,
|
||||
0x38, 0x00, 0x38, 0x00, 0x38, 0x00, 0xFE, 0x00, 0xFE, 0x00, 0xFE, 0x00,
|
||||
},
|
||||
{
|
||||
0x7C, 0x00, 0xFE, 0x00, 0xFF, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xC0, 0x01,
|
||||
0xC0, 0x01, 0xE0, 0x00, 0xF0, 0x00, 0x78, 0x00, 0x3C, 0x00, 0x1E, 0x00,
|
||||
0x0E, 0x00, 0x0F, 0x00, 0x07, 0x00, 0xFF, 0x01, 0xFF, 0x01, 0xFF, 0x01,
|
||||
},
|
||||
{
|
||||
0x7C, 0x00, 0xFE, 0x00, 0xFF, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xC0, 0x01,
|
||||
0xC0, 0x01, 0xF8, 0x00, 0x78, 0x00, 0xF8, 0x00, 0xC0, 0x01, 0xC0, 0x01,
|
||||
0xC0, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xFF, 0x01, 0xFE, 0x00, 0x7C, 0x00,
|
||||
},
|
||||
{
|
||||
0xE0, 0x00, 0xE0, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0xF8, 0x00, 0xF8, 0x00,
|
||||
0xF8, 0x00, 0xFC, 0x00, 0xEC, 0x00, 0xEE, 0x00, 0xE6, 0x00, 0xFF, 0x01,
|
||||
0xFF, 0x01, 0xFF, 0x01, 0xE0, 0x00, 0xE0, 0x00, 0xE0, 0x00, 0xE0, 0x00,
|
||||
},
|
||||
{
|
||||
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x07, 0x00, 0x07, 0x00, 0x07, 0x00,
|
||||
0x7F, 0x00, 0xFF, 0x00, 0xFF, 0x01, 0xC7, 0x01, 0xC0, 0x01, 0xC0, 0x01,
|
||||
0xC7, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xFF, 0x01, 0xFE, 0x00, 0x7C, 0x00,
|
||||
},
|
||||
{
|
||||
0x7C, 0x00, 0xFE, 0x00, 0xFF, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0x07, 0x00,
|
||||
0x07, 0x00, 0x77, 0x00, 0xFF, 0x00, 0xFF, 0x01, 0xC7, 0x01, 0xC7, 0x01,
|
||||
0xC7, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xFF, 0x01, 0xFE, 0x00, 0x7C, 0x00,
|
||||
},
|
||||
{
|
||||
0xFF, 0x01, 0xFF, 0x01, 0xFF, 0x01, 0xE0, 0x00, 0xE0, 0x00, 0x70, 0x00,
|
||||
0x70, 0x00, 0x70, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00,
|
||||
0x1C, 0x00, 0x1C, 0x00, 0x1C, 0x00, 0x1C, 0x00, 0x1C, 0x00, 0x1C, 0x00,
|
||||
},
|
||||
{
|
||||
0x7C, 0x00, 0xFE, 0x00, 0xFF, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xC7, 0x01,
|
||||
0xC7, 0x01, 0xFE, 0x00, 0x7C, 0x00, 0xFE, 0x00, 0xC7, 0x01, 0xC7, 0x01,
|
||||
0xC7, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xFF, 0x01, 0xFE, 0x00, 0x7C, 0x00,
|
||||
},
|
||||
{
|
||||
0x7C, 0x00, 0xFE, 0x00, 0xFF, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xC7, 0x01,
|
||||
0xC7, 0x01, 0xC7, 0x01, 0xFF, 0x01, 0xFE, 0x01, 0xDC, 0x01, 0xC0, 0x01,
|
||||
0xC0, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xFF, 0x01, 0xFE, 0x00, 0x7C, 0x00,
|
||||
},
|
||||
{
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0x00, 0xFE, 0x00,
|
||||
0xFF, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xF0, 0x01, 0xFC, 0x01, 0xCE, 0x01,
|
||||
0xC7, 0x01, 0xC7, 0x01, 0xE7, 0x01, 0xFF, 0x01, 0xDF, 0x01, 0xCE, 0x01,
|
||||
},
|
||||
{
|
||||
0x07, 0x00, 0x07, 0x00, 0x07, 0x00, 0x07, 0x00, 0xE7, 0x00, 0xF7, 0x01,
|
||||
0xFF, 0x01, 0xCF, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xC7, 0x01,
|
||||
0xC7, 0x01, 0xC7, 0x01, 0xCF, 0x01, 0xFF, 0x01, 0xF7, 0x01, 0xE7, 0x00,
|
||||
},
|
||||
{
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0x00, 0xFE, 0x00,
|
||||
0xFF, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0x07, 0x00, 0x07, 0x00, 0x07, 0x00,
|
||||
0x07, 0x00, 0xC7, 0x01, 0xC7, 0x01, 0xFF, 0x01, 0xFE, 0x00, 0x7C, 0x00,
|
||||
},
|
||||
{
|
||||
0xC0, 0x01, 0xC0, 0x01, 0xC0, 0x01, 0xC0, 0x01, 0xCE, 0x01, 0xDF, 0x01,
|
||||
0xFF, 0x01, 0xE7, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xC7, 0x01,
|
||||
0xC7, 0x01, 0xC7, 0x01, 0xE7, 0x01, 0xFF, 0x01, 0xDF, 0x01, 0xCE, 0x01,
|
||||
},
|
||||
{
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0x00, 0xFE, 0x00,
|
||||
0xFF, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xFF, 0x01, 0xFF, 0x01,
|
||||
0x07, 0x00, 0xC7, 0x01, 0xC7, 0x01, 0xFF, 0x01, 0xFE, 0x00, 0x7C, 0x00,
|
||||
},
|
||||
{
|
||||
0xE0, 0x00, 0xF0, 0x00, 0xF8, 0x00, 0x38, 0x00, 0xFE, 0x00, 0xFE, 0x00,
|
||||
0xFE, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00,
|
||||
0x38, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00,
|
||||
}
|
||||
};
|
|
@ -0,0 +1,35 @@
|
|||
#define pebble_logo_width 105
|
||||
#define pebble_logo_height 27
|
||||
static const unsigned char pebble_logo_bits[] = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x03, 0x00, 0x18, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x03, 0x00,
|
||||
0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00,
|
||||
0x03, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60,
|
||||
0x00, 0x00, 0x03, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x60, 0x00, 0x00, 0x03, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0xE0,
|
||||
0x03, 0x00, 0x1F, 0x60, 0xF8, 0x00, 0xC3, 0x07, 0x18, 0xC0, 0x07, 0x00,
|
||||
0x00, 0xF8, 0x0F, 0xC0, 0x7F, 0x60, 0xFE, 0x03, 0xF3, 0x1F, 0x18, 0xF0,
|
||||
0x1F, 0x00, 0x00, 0x0C, 0x18, 0x60, 0xC0, 0x60, 0x03, 0x06, 0x1B, 0x30,
|
||||
0x18, 0x18, 0x30, 0x00, 0x00, 0x06, 0x30, 0x30, 0x80, 0xE1, 0x01, 0x0C,
|
||||
0x0F, 0x60, 0x18, 0x0C, 0x60, 0x00, 0x00, 0x03, 0x60, 0x18, 0x00, 0xE3,
|
||||
0x00, 0x18, 0x07, 0xC0, 0x18, 0x06, 0xC0, 0x00, 0x80, 0x01, 0x40, 0x0C,
|
||||
0x00, 0x62, 0x00, 0x10, 0x03, 0x80, 0x18, 0x03, 0x80, 0x00, 0x80, 0x01,
|
||||
0xC0, 0x0C, 0x00, 0x66, 0x00, 0x30, 0x03, 0x80, 0x19, 0x03, 0x80, 0x01,
|
||||
0x80, 0x01, 0xC0, 0xFC, 0xFF, 0x67, 0x00, 0x30, 0x03, 0x80, 0x19, 0xFF,
|
||||
0xFF, 0x01, 0x80, 0x01, 0xC0, 0xFC, 0xFF, 0x63, 0x00, 0x30, 0x03, 0x80,
|
||||
0x19, 0xFF, 0xFF, 0x00, 0x80, 0x01, 0xC0, 0x0C, 0x00, 0x60, 0x00, 0x30,
|
||||
0x03, 0x80, 0x19, 0x03, 0x00, 0x00, 0x80, 0x01, 0xC0, 0x0C, 0x00, 0x60,
|
||||
0x00, 0x30, 0x03, 0x80, 0x19, 0x03, 0x00, 0x00, 0x80, 0x01, 0x40, 0x0C,
|
||||
0x00, 0x62, 0x00, 0x10, 0x03, 0x80, 0x18, 0x03, 0x80, 0x00, 0x80, 0x03,
|
||||
0x60, 0x18, 0x00, 0xC3, 0x00, 0x18, 0x06, 0xC0, 0x18, 0x06, 0xC0, 0x00,
|
||||
0x80, 0x07, 0x30, 0x30, 0x80, 0x81, 0x01, 0x0C, 0x0C, 0x60, 0x18, 0x0C,
|
||||
0x60, 0x00, 0x80, 0x0D, 0x18, 0x60, 0xC0, 0x00, 0x03, 0x06, 0x18, 0x30,
|
||||
0x38, 0x18, 0x30, 0x00, 0x80, 0xF9, 0x0F, 0xC0, 0x7F, 0x00, 0xFE, 0x03,
|
||||
0xF0, 0x1F, 0x70, 0xF0, 0x1F, 0x00, 0x80, 0xE1, 0x03, 0x00, 0x1F, 0x00,
|
||||
0xF8, 0x00, 0xC0, 0x07, 0x60, 0xC0, 0x07, 0x00, 0x80, 0x01, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, };
|
365
platform/silk/boot/src/drivers/display/sharp_ls013b7dh01.c
Normal file
365
platform/silk/boot/src/drivers/display/sharp_ls013b7dh01.c
Normal file
|
@ -0,0 +1,365 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "board/display.h"
|
||||
#include "drivers/periph_config.h"
|
||||
#include "drivers/gpio.h"
|
||||
#include "drivers/dbgserial.h"
|
||||
#include "util/attributes.h"
|
||||
#include "util/delay.h"
|
||||
|
||||
#include "stm32f4xx_dma.h"
|
||||
#include "stm32f4xx_gpio.h"
|
||||
#include "stm32f4xx_rcc.h"
|
||||
#include "stm32f4xx_spi.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
// Bootloader images
|
||||
#include "drivers/display/resources/hex_digits.h"
|
||||
#include "drivers/display/resources/dead_face.xbm"
|
||||
#include "drivers/display/resources/empty_bar.xbm"
|
||||
#include "drivers/display/resources/error_url.xbm"
|
||||
#include "drivers/display/resources/pebble_logo.xbm"
|
||||
|
||||
#define DISP_LINE_BYTES (DISP_COLS / 8)
|
||||
#define DISP_LINE_WORDS (((DISP_COLS - 1) / 32) + 1)
|
||||
|
||||
// GPIO constants
|
||||
#define DISP_SPI (SPI2)
|
||||
#define DISP_GPIO (GPIOB)
|
||||
#define DISPLAY_SPI_CLOCK (RCC_APB1Periph_SPI2)
|
||||
#define DISP_PIN_SCS (GPIO_Pin_9)
|
||||
#define DISP_PINSOURCE_SCS (GPIO_PinSource9)
|
||||
#define DISP_PIN_SCLK (GPIO_Pin_10)
|
||||
#define DISP_PINSOURCE_SCKL (GPIO_PinSource10)
|
||||
#define DISP_PIN_SI (GPIO_Pin_15)
|
||||
#define DISP_PINSOURCE_SI (GPIO_PinSource15)
|
||||
#define DISP_LCD_GPIO (GPIOA)
|
||||
#define DISP_PIN_LCD (GPIO_Pin_0)
|
||||
#define DISP_PINSOURCE_LCD (GPIO_PinSource0)
|
||||
#define DISP_MODE_STATIC (0x00)
|
||||
#define DISP_MODE_WRITE (0x80)
|
||||
#define DISP_MODE_CLEAR (0x20)
|
||||
|
||||
// The bootloader leaves SYSCLK at defaults (connected to HSI at 16 Mhz),
|
||||
// and there are no prescalers on any of the buses. Since the display
|
||||
// can handle a max of 2 Mhz, we want to divide by 8
|
||||
#define DISPLAY_PERIPH_PRESCALER (SPI_BaudRatePrescaler_8)
|
||||
|
||||
static void prv_enable_display_spi_clock() {
|
||||
periph_config_enable(RCC_APB1PeriphClockCmd, DISPLAY_SPI_CLOCK);
|
||||
}
|
||||
|
||||
static void prv_disable_display_spi_clock() {
|
||||
periph_config_disable(RCC_APB1PeriphClockCmd, DISPLAY_SPI_CLOCK);
|
||||
}
|
||||
|
||||
static void prv_enable_chip_select(void) {
|
||||
GPIO_WriteBit(DISP_GPIO, DISP_PIN_SCS, Bit_SET);
|
||||
// required setup time > 3us
|
||||
delay_us(7);
|
||||
}
|
||||
|
||||
static void prv_disable_chip_select(void) {
|
||||
// delay while last byte is emitted by the SPI peripheral
|
||||
delay_us(7);
|
||||
|
||||
GPIO_WriteBit(DISP_GPIO, DISP_PIN_SCS, Bit_RESET);
|
||||
// hold time > 1us
|
||||
// produce a delay 4ms
|
||||
delay_us(4);
|
||||
}
|
||||
|
||||
//! These functions needed to be called around any commands that
|
||||
//! are sent to the display. NOINLINE only for code size savings.
|
||||
static NOINLINE void prv_enable_display_access(void) {
|
||||
prv_enable_display_spi_clock();
|
||||
prv_enable_chip_select();
|
||||
}
|
||||
|
||||
static NOINLINE void prv_disable_display_access(void) {
|
||||
prv_disable_chip_select();
|
||||
prv_disable_display_spi_clock();
|
||||
}
|
||||
|
||||
//! Write a single byte synchronously to the display. This is the only practical
|
||||
//! way to write to the display in the bootloader since we don't have interrupts.
|
||||
static void prv_display_write_byte(uint8_t d) {
|
||||
// Block until the tx buffer is empty
|
||||
SPI_I2S_SendData(DISP_SPI, d);
|
||||
while (!SPI_I2S_GetFlagStatus(DISP_SPI, SPI_I2S_FLAG_TXE)) {}
|
||||
}
|
||||
|
||||
// Since all these values are constant we can save code space
|
||||
// by storing the initialized struct in memory rather than
|
||||
// needing to copy in each value
|
||||
static GPIO_InitTypeDef s_disp_gpio_init = {
|
||||
.GPIO_OType = GPIO_OType_PP,
|
||||
.GPIO_PuPd = GPIO_PuPd_NOPULL,
|
||||
.GPIO_Mode = GPIO_Mode_AF,
|
||||
.GPIO_Speed = GPIO_Speed_50MHz,
|
||||
.GPIO_Pin = DISP_PIN_SCLK | DISP_PIN_SI
|
||||
};
|
||||
|
||||
static SPI_InitTypeDef s_disp_spi_init = {
|
||||
.SPI_Direction = SPI_Direction_1Line_Tx, // Write-only SPI
|
||||
.SPI_Mode = SPI_Mode_Master,
|
||||
.SPI_DataSize = SPI_DataSize_8b,
|
||||
.SPI_CPOL = SPI_CPOL_Low,
|
||||
.SPI_CPHA = SPI_CPHA_1Edge,
|
||||
.SPI_NSS = SPI_NSS_Soft,
|
||||
// We want the SPI clock to run at 2MHz
|
||||
.SPI_BaudRatePrescaler = DISPLAY_PERIPH_PRESCALER,
|
||||
// MSB order allows us to write pixels out without reversing bytes, but command bytes
|
||||
// have to be reversed
|
||||
.SPI_FirstBit = SPI_FirstBit_MSB,
|
||||
.SPI_CRCPolynomial = 7 // default
|
||||
};
|
||||
|
||||
static void prv_display_start(void) {
|
||||
// Setup alternate functions
|
||||
GPIO_PinAFConfig(DISP_GPIO, DISP_PINSOURCE_SCKL, GPIO_AF_SPI2);
|
||||
GPIO_PinAFConfig(DISP_GPIO, DISP_PINSOURCE_SI, GPIO_AF_SPI2);
|
||||
|
||||
// Init display GPIOs
|
||||
GPIO_Init(DISP_GPIO, &s_disp_gpio_init);
|
||||
|
||||
// Init CS
|
||||
s_disp_gpio_init.GPIO_Mode = GPIO_Mode_OUT;
|
||||
s_disp_gpio_init.GPIO_OType = GPIO_OType_PP;
|
||||
s_disp_gpio_init.GPIO_Pin = DISP_PIN_SCS;
|
||||
GPIO_Init(DISP_GPIO, &s_disp_gpio_init);
|
||||
|
||||
// Init LCD Control
|
||||
s_disp_gpio_init.GPIO_Mode = GPIO_Mode_OUT;
|
||||
s_disp_gpio_init.GPIO_OType = GPIO_OType_PP;
|
||||
s_disp_gpio_init.GPIO_Pin = DISP_PIN_LCD;
|
||||
GPIO_Init(DISP_LCD_GPIO, &s_disp_gpio_init);
|
||||
|
||||
// Set up a SPI bus on SPI2
|
||||
SPI_I2S_DeInit(DISP_SPI);
|
||||
SPI_Init(DISP_SPI, &s_disp_spi_init);
|
||||
|
||||
SPI_Cmd(DISP_SPI, ENABLE);
|
||||
|
||||
|
||||
// Hold LCD on
|
||||
GPIO_WriteBit(DISP_LCD_GPIO, DISP_PIN_LCD, Bit_SET);
|
||||
}
|
||||
|
||||
// Clear-all mode is entered by sending 0x04 to the panel
|
||||
void display_clear(void) {
|
||||
prv_enable_display_access();
|
||||
|
||||
prv_display_write_byte(DISP_MODE_CLEAR);
|
||||
prv_display_write_byte(0x00);
|
||||
|
||||
prv_disable_display_access();
|
||||
}
|
||||
|
||||
//! Static mode is entered by sending 0x00 to the panel
|
||||
//! This stops any further updates being registered by
|
||||
//! the display, preventing corruption on shutdown / boot
|
||||
static void prv_display_enter_static(void) {
|
||||
prv_enable_display_access();
|
||||
|
||||
prv_display_write_byte(DISP_MODE_STATIC);
|
||||
prv_display_write_byte(0x00);
|
||||
prv_display_write_byte(0x00);
|
||||
|
||||
prv_disable_display_access();
|
||||
}
|
||||
|
||||
// Helper to reverse command bytes
|
||||
static uint8_t prv_reverse_bits(uint8_t input) {
|
||||
uint8_t result;
|
||||
__asm__ ("rev %[result], %[input]\n\t"
|
||||
"rbit %[result], %[result]"
|
||||
: [result] "=r" (result)
|
||||
: [input] "r" (input));
|
||||
return result;
|
||||
}
|
||||
|
||||
static void prv_display_start_write(void) {
|
||||
prv_enable_display_access();
|
||||
|
||||
prv_display_write_byte(DISP_MODE_WRITE);
|
||||
}
|
||||
|
||||
static void prv_display_write_line(uint8_t line_addr, const uint8_t *line) {
|
||||
// 1-indexed (ugh) 8bit line address (1-168)
|
||||
prv_display_write_byte(prv_reverse_bits(line_addr + 1));
|
||||
|
||||
for (int i = 0; i < DISP_LINE_BYTES; ++i) {
|
||||
prv_display_write_byte(prv_reverse_bits(line[i]));
|
||||
}
|
||||
|
||||
prv_display_write_byte(0x00);
|
||||
}
|
||||
|
||||
static void prv_display_end_write(void) {
|
||||
prv_display_write_byte(0x00);
|
||||
|
||||
|
||||
prv_disable_display_access();
|
||||
}
|
||||
|
||||
// Round a bit offset to a byte offset
|
||||
static unsigned prv_round_to_byte(unsigned x) {
|
||||
return (x + 7) >> 3;
|
||||
}
|
||||
|
||||
// Draw bitmap onto buffer.
|
||||
static void prv_draw_bitmap(const uint8_t *bitmap, unsigned x_offset, unsigned y_offset,
|
||||
unsigned width, unsigned height,
|
||||
uint8_t buffer[DISP_ROWS][DISP_LINE_BYTES]) {
|
||||
// Need to convert offsets to bytes for the horizontal dimensions
|
||||
x_offset = prv_round_to_byte(x_offset);
|
||||
width = prv_round_to_byte(width);
|
||||
|
||||
for (unsigned i = 0; i < height; i++) {
|
||||
memcpy(buffer[i + y_offset] + x_offset, bitmap + i * width, width);
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_display_buffer(uint8_t buffer[DISP_ROWS][DISP_LINE_BYTES]) {
|
||||
prv_display_start_write();
|
||||
for (int i = 0; i < DISP_ROWS; i++) {
|
||||
prv_display_write_line(i, buffer[i]);
|
||||
}
|
||||
prv_display_end_write();
|
||||
}
|
||||
|
||||
void display_boot_splash(void) {
|
||||
uint8_t buffer[DISP_ROWS][DISP_LINE_BYTES];
|
||||
// Draw black
|
||||
memset(buffer, 0x00, sizeof(buffer));
|
||||
|
||||
prv_draw_bitmap(pebble_logo_bits, 16, 64, pebble_logo_width, pebble_logo_height, buffer);
|
||||
|
||||
prv_display_buffer(buffer);
|
||||
}
|
||||
|
||||
static void prv_set_bit(uint8_t x, uint8_t y, uint8_t buffer[DISP_ROWS][DISP_LINE_BYTES]) {
|
||||
buffer[y][x / 8] |= (1 << (x % 8));
|
||||
}
|
||||
|
||||
static void prv_render_char(unsigned digit, uint8_t x_offset_bits, uint8_t y_offset,
|
||||
uint8_t buffer[DISP_ROWS][DISP_LINE_BYTES]) {
|
||||
const unsigned char_rows = 18, char_cols = 9;
|
||||
const uint8_t * char_data = hex_digits_bits[digit];
|
||||
|
||||
// Each character requires 2 bytes of storage
|
||||
for (unsigned y = 0; y < char_rows; y++) {
|
||||
unsigned cur_y = y_offset + y;
|
||||
uint8_t first_byte = char_data[2 * y];
|
||||
|
||||
for (unsigned x = 0; x < char_cols; x++) {
|
||||
bool pixel;
|
||||
if (x < 8) { // Pixel is in first byte
|
||||
pixel = first_byte & (1 << x);
|
||||
}
|
||||
else { // Last pixel is in second byte
|
||||
pixel = char_data[2 * y + 1] & 1;
|
||||
}
|
||||
|
||||
// The buffer is already all black, so just set the white pixels
|
||||
if (pixel) {
|
||||
prv_set_bit(x_offset_bits + x, cur_y, buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_draw_code(uint32_t code, uint8_t buffer[DISP_ROWS][DISP_LINE_BYTES]) {
|
||||
const unsigned y_offset = 116; // beneath sad face, above url
|
||||
unsigned x_offset = 28; // Aligned with sad face
|
||||
|
||||
// Extract and print digits
|
||||
for (int i = 7; i >= 0; i--) {
|
||||
// Mask off 4 bits at a time
|
||||
uint32_t mask = (0xf << (i * 4));
|
||||
unsigned digit = ((code & mask) >> (i * 4));
|
||||
prv_render_char(digit, x_offset, y_offset, buffer);
|
||||
|
||||
// Each character is 9px wide plus 2px of padding
|
||||
x_offset += 11;
|
||||
}
|
||||
}
|
||||
|
||||
void display_error_code(uint32_t code) {
|
||||
uint8_t buffer[DISP_ROWS][DISP_LINE_BYTES];
|
||||
memset(buffer, 0x00, sizeof(buffer));
|
||||
|
||||
prv_draw_bitmap(dead_face_bits, 24, 32, dead_face_width, dead_face_height, buffer);
|
||||
|
||||
prv_draw_code(code, buffer);
|
||||
|
||||
prv_draw_bitmap(error_url_bits, 16, 144, error_url_width, error_url_height, buffer);
|
||||
|
||||
prv_display_buffer(buffer);
|
||||
}
|
||||
|
||||
//! Do whatever is necessary to prevent visual artifacts when resetting
|
||||
//! the watch.
|
||||
void display_prepare_for_reset(void) {
|
||||
prv_display_enter_static();
|
||||
}
|
||||
|
||||
//! Display the progress of a firmware update.
|
||||
//!
|
||||
//! The progress is expressed as a rational number less than or equal to 1.
|
||||
//! When numerator == denominator, the progress indicator shows that the update
|
||||
//! is complete.
|
||||
void display_firmware_update_progress(uint32_t numerator, uint32_t denominator) {
|
||||
// Dimensions for progress bar
|
||||
const unsigned x_offset = 24, y_offset = 106,
|
||||
inner_bar_width = 94, inner_bar_height = 6;
|
||||
|
||||
static unsigned s_prev_num_pixels = -1;
|
||||
// Calculate number of pixels to fill in
|
||||
unsigned num_pixels = inner_bar_width * numerator / denominator;
|
||||
|
||||
if (num_pixels == s_prev_num_pixels) {
|
||||
return;
|
||||
}
|
||||
s_prev_num_pixels = num_pixels;
|
||||
|
||||
uint8_t buffer[DISP_ROWS][DISP_LINE_BYTES];
|
||||
memset(buffer, 0x00, sizeof(buffer));
|
||||
|
||||
prv_draw_bitmap(pebble_logo_bits, 16, 64, pebble_logo_width, pebble_logo_height, buffer);
|
||||
|
||||
|
||||
prv_draw_bitmap(empty_bar_bits, x_offset, y_offset, empty_bar_width, empty_bar_height, buffer);
|
||||
|
||||
for (unsigned y = 0; y < inner_bar_height; y++) {
|
||||
for (unsigned x = 0; x < num_pixels; x++) {
|
||||
// Add 1 to offsets so we don't write into outer box
|
||||
prv_set_bit(x + x_offset + 1, y_offset + y + 1, buffer);
|
||||
}
|
||||
}
|
||||
|
||||
prv_display_buffer(buffer);
|
||||
}
|
||||
|
||||
void display_init(void) {
|
||||
prv_enable_display_spi_clock();
|
||||
prv_display_start();
|
||||
prv_disable_display_spi_clock();
|
||||
}
|
46
platform/silk/boot/src/drivers/exti.h
Normal file
46
platform/silk/boot/src/drivers/exti.h
Normal file
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "board/board.h"
|
||||
|
||||
typedef enum {
|
||||
ExtiTrigger_Rising,
|
||||
ExtiTrigger_Falling,
|
||||
ExtiTrigger_RisingFalling
|
||||
} ExtiTrigger;
|
||||
|
||||
//! See section 12.2.5 "External interrupt/event line mapping" in the STM32F2 reference manual
|
||||
typedef enum {
|
||||
ExtiLineOther_RTCAlarm = 17,
|
||||
ExtiLineOther_RTCWakeup = 22
|
||||
} ExtiLineOther;
|
||||
|
||||
typedef void (*ExtiHandlerCallback)(void);
|
||||
|
||||
//! Configures the given EXTI and NVIC for the given configuration.
|
||||
void exti_configure_pin(ExtiConfig cfg, ExtiTrigger trigger, ExtiHandlerCallback cb);
|
||||
//! Configures the given EXTI and NVIC for the given configuration.
|
||||
void exti_configure_other(ExtiLineOther exti_line, ExtiTrigger trigger);
|
||||
|
||||
static inline void exti_enable(ExtiConfig config);
|
||||
static inline void exti_disable(ExtiConfig config);
|
||||
|
||||
void exti_enable_other(ExtiLineOther);
|
||||
void exti_disable_other(ExtiLineOther);
|
||||
|
||||
#include "exti.inl.h"
|
27
platform/silk/boot/src/drivers/exti.inl.h
Normal file
27
platform/silk/boot/src/drivers/exti.inl.h
Normal file
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
//! @file exti.inl.h
|
||||
//!
|
||||
//! Helper functions intended to be inlined into the calling code.
|
||||
|
||||
static inline void exti_enable(ExtiConfig config) {
|
||||
exti_enable_other(config.exti_line);
|
||||
}
|
||||
|
||||
static inline void exti_disable(ExtiConfig config) {
|
||||
exti_disable_other(config.exti_line);
|
||||
}
|
40
platform/silk/boot/src/drivers/flash.h
Normal file
40
platform/silk/boot/src/drivers/flash.h
Normal file
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <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);
|
38
platform/silk/boot/src/drivers/flash/flash_crc.c
Normal file
38
platform/silk/boot/src/drivers/flash/flash_crc.c
Normal file
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#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);
|
||||
}
|
209
platform/silk/boot/src/drivers/flash/mx25u.c
Normal file
209
platform/silk/boot/src/drivers/flash/mx25u.c
Normal file
|
@ -0,0 +1,209 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "board/board.h"
|
||||
#include "drivers/flash.h"
|
||||
#include "drivers/gpio.h"
|
||||
#include "drivers/periph_config.h"
|
||||
#include "util/delay.h"
|
||||
|
||||
#include "stm32f4xx_gpio.h"
|
||||
#include "stm32f4xx_qspi.h"
|
||||
|
||||
#define MX25U_FASTREAD_DUMMYCYCLES 4
|
||||
|
||||
typedef enum MX25UCommand {
|
||||
// SPI/QSPI Commands
|
||||
MX25UCommand_FastRead = 0x0B, // FAST_READ
|
||||
MX25UCommand_QSPIEnable = 0x35, // QPI
|
||||
MX25UCommand_ResetEnable = 0x66, // RSTEN
|
||||
MX25UCommand_Reset = 0x99, // RST
|
||||
|
||||
// QSPI only commands
|
||||
MX25UCommand_QSPI_ID = 0xAF, // QPIID
|
||||
} MX25UCommand;
|
||||
|
||||
// Helpful Enums
|
||||
typedef enum {
|
||||
QSPIFlag_Retain = 0,
|
||||
QSPIFlag_ClearTC = 1,
|
||||
} QSPIFlag;
|
||||
|
||||
static void prv_enable_qspi_clock(void) {
|
||||
periph_config_enable(RCC_AHB3PeriphClockCmd, RCC_AHB3Periph_QSPI);
|
||||
}
|
||||
|
||||
static void prv_disable_qspi_clock(void) {
|
||||
periph_config_disable(RCC_AHB3PeriphClockCmd, RCC_AHB3Periph_QSPI);
|
||||
}
|
||||
|
||||
static void prv_set_num_data_bytes(uint32_t length) {
|
||||
// From the docs: QSPI_DataLength: Number of data to be retrieved, value+1.
|
||||
// so 0 is 1 byte, so we substract 1 from the length. -1 is read the entire flash length.
|
||||
QSPI_SetDataLength(length - 1);
|
||||
}
|
||||
|
||||
static void prv_wait_for_qspi_transfer_complete(QSPIFlag actions) {
|
||||
while (QSPI_GetFlagStatus(QSPI_FLAG_TC) == RESET) { }
|
||||
|
||||
if (actions == QSPIFlag_ClearTC) {
|
||||
QSPI_ClearFlag(QSPI_FLAG_TC);
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_wait_for_qspi_not_busy(void) {
|
||||
while (QSPI_GetFlagStatus(QSPI_FLAG_BUSY) != RESET) { }
|
||||
}
|
||||
|
||||
static void prv_quad_enable() {
|
||||
QSPI_ComConfig_InitTypeDef qspi_com_config;
|
||||
QSPI_ComConfig_StructInit(&qspi_com_config);
|
||||
qspi_com_config.QSPI_ComConfig_FMode = QSPI_ComConfig_FMode_Indirect_Write;
|
||||
qspi_com_config.QSPI_ComConfig_IMode = QSPI_ComConfig_IMode_1Line;
|
||||
qspi_com_config.QSPI_ComConfig_Ins = MX25UCommand_QSPIEnable;
|
||||
QSPI_ComConfig_Init(&qspi_com_config);
|
||||
|
||||
prv_wait_for_qspi_transfer_complete(QSPIFlag_ClearTC);
|
||||
|
||||
prv_wait_for_qspi_not_busy();
|
||||
}
|
||||
|
||||
static void prv_flash_reset(void) {
|
||||
QSPI_ComConfig_InitTypeDef qspi_com_config;
|
||||
QSPI_ComConfig_StructInit(&qspi_com_config);
|
||||
qspi_com_config.QSPI_ComConfig_FMode = QSPI_ComConfig_FMode_Indirect_Write;
|
||||
qspi_com_config.QSPI_ComConfig_IMode = QSPI_ComConfig_IMode_4Line;
|
||||
qspi_com_config.QSPI_ComConfig_Ins = MX25UCommand_ResetEnable;
|
||||
QSPI_ComConfig_Init(&qspi_com_config);
|
||||
|
||||
prv_wait_for_qspi_transfer_complete(QSPIFlag_ClearTC);
|
||||
|
||||
QSPI_ComConfig_StructInit(&qspi_com_config);
|
||||
qspi_com_config.QSPI_ComConfig_FMode = QSPI_ComConfig_FMode_Indirect_Write;
|
||||
qspi_com_config.QSPI_ComConfig_IMode = QSPI_ComConfig_IMode_4Line;
|
||||
qspi_com_config.QSPI_ComConfig_Ins = MX25UCommand_Reset;
|
||||
QSPI_ComConfig_Init(&qspi_com_config);
|
||||
|
||||
prv_wait_for_qspi_transfer_complete(QSPIFlag_ClearTC);
|
||||
|
||||
delay_us(12000); // 12ms reset if busy with an erase!
|
||||
|
||||
// Return the flash to Quad SPI mode, all our commands are quad-spi and it'll just cause
|
||||
// problems/bugs for someone if it comes back in single spi mode
|
||||
prv_quad_enable();
|
||||
}
|
||||
|
||||
static bool prv_flash_check_whoami(void) {
|
||||
const unsigned int num_whoami_bytes = 3;
|
||||
|
||||
prv_set_num_data_bytes(num_whoami_bytes);
|
||||
|
||||
QSPI_ComConfig_InitTypeDef qspi_com_config;
|
||||
QSPI_ComConfig_StructInit(&qspi_com_config);
|
||||
qspi_com_config.QSPI_ComConfig_FMode = QSPI_ComConfig_FMode_Indirect_Read;
|
||||
qspi_com_config.QSPI_ComConfig_DMode = QSPI_ComConfig_DMode_4Line;
|
||||
qspi_com_config.QSPI_ComConfig_IMode = QSPI_ComConfig_IMode_4Line;
|
||||
qspi_com_config.QSPI_ComConfig_Ins = MX25UCommand_QSPI_ID;
|
||||
QSPI_ComConfig_Init(&qspi_com_config);
|
||||
|
||||
prv_wait_for_qspi_transfer_complete(QSPIFlag_ClearTC);
|
||||
|
||||
uint32_t read_whoami = 0;
|
||||
for (unsigned int i = 0; i < num_whoami_bytes; ++i) {
|
||||
read_whoami |= QSPI_ReceiveData8() << (8 * i);
|
||||
}
|
||||
|
||||
prv_wait_for_qspi_not_busy();
|
||||
|
||||
const uint32_t expected_whoami = 0x3725c2;
|
||||
if (read_whoami == expected_whoami) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void flash_init(void) {
|
||||
prv_enable_qspi_clock();
|
||||
// init GPIOs
|
||||
for (unsigned i = 0; i < QSpiPinCount; ++i) {
|
||||
gpio_af_init(&BOARD_CONFIG_FLASH_PINS[i], GPIO_OType_PP, GPIO_Speed_100MHz, GPIO_PuPd_NOPULL);
|
||||
}
|
||||
|
||||
// Init QSPI peripheral
|
||||
QSPI_InitTypeDef qspi_config;
|
||||
QSPI_StructInit(&qspi_config);
|
||||
qspi_config.QSPI_SShift = QSPI_SShift_HalfCycleShift;
|
||||
qspi_config.QSPI_Prescaler = 0x0;
|
||||
qspi_config.QSPI_CKMode = QSPI_CKMode_Mode0;
|
||||
qspi_config.QSPI_CSHTime = QSPI_CSHTime_1Cycle;
|
||||
qspi_config.QSPI_FSize = 22; // 2^23 = 8MB. -> 23 - 1 = 22
|
||||
qspi_config.QSPI_FSelect = QSPI_FSelect_1;
|
||||
qspi_config.QSPI_DFlash = QSPI_DFlash_Disable;
|
||||
QSPI_Init(&qspi_config);
|
||||
|
||||
QSPI_Cmd(ENABLE);
|
||||
|
||||
// Must call quad_enable first, all commands are QSPI
|
||||
prv_quad_enable();
|
||||
|
||||
// Reset the flash to stop any program's or erase in progress from before reboot
|
||||
prv_flash_reset();
|
||||
|
||||
prv_disable_qspi_clock();
|
||||
}
|
||||
|
||||
bool flash_sanity_check(void) {
|
||||
prv_enable_qspi_clock();
|
||||
|
||||
bool result = prv_flash_check_whoami();
|
||||
|
||||
prv_disable_qspi_clock();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void flash_read_bytes(uint8_t *buffer_ptr, uint32_t start_addr, uint32_t buffer_size) {
|
||||
prv_enable_qspi_clock();
|
||||
|
||||
prv_set_num_data_bytes(buffer_size);
|
||||
|
||||
QSPI_ComConfig_InitTypeDef qspi_com_config;
|
||||
QSPI_ComConfig_StructInit(&qspi_com_config);
|
||||
qspi_com_config.QSPI_ComConfig_FMode = QSPI_ComConfig_FMode_Indirect_Read;
|
||||
qspi_com_config.QSPI_ComConfig_DMode = QSPI_ComConfig_DMode_4Line;
|
||||
qspi_com_config.QSPI_ComConfig_DummyCycles = MX25U_FASTREAD_DUMMYCYCLES;
|
||||
qspi_com_config.QSPI_ComConfig_ADMode = QSPI_ComConfig_ADMode_4Line;
|
||||
qspi_com_config.QSPI_ComConfig_IMode = QSPI_ComConfig_IMode_4Line;
|
||||
qspi_com_config.QSPI_ComConfig_ADSize = QSPI_ComConfig_ADSize_24bit;
|
||||
qspi_com_config.QSPI_ComConfig_Ins = MX25UCommand_FastRead;
|
||||
QSPI_ComConfig_Init(&qspi_com_config);
|
||||
|
||||
QSPI_SetAddress(start_addr);
|
||||
|
||||
uint8_t *write_ptr = buffer_ptr;
|
||||
for (unsigned i = 0; i < buffer_size; ++i) {
|
||||
write_ptr[i] = QSPI_ReceiveData8();
|
||||
}
|
||||
|
||||
QSPI_ClearFlag(QSPI_FLAG_TC);
|
||||
prv_wait_for_qspi_not_busy();
|
||||
|
||||
prv_disable_qspi_clock();
|
||||
}
|
43
platform/silk/boot/src/drivers/gpio.c
Normal file
43
platform/silk/boot/src/drivers/gpio.c
Normal file
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "drivers/gpio.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define GPIO_EN_MASK ((RCC_AHB1ENR_GPIOHEN << 1) - 1)
|
||||
|
||||
void gpio_enable_all(void) {
|
||||
RCC->AHB1ENR |= GPIO_EN_MASK;
|
||||
}
|
||||
|
||||
void gpio_disable_all(void) {
|
||||
RCC->AHB1ENR &= ~GPIO_EN_MASK;
|
||||
}
|
||||
|
||||
void gpio_af_init(const AfConfig *af_config, GPIOOType_TypeDef otype, GPIOSpeed_TypeDef speed,
|
||||
GPIOPuPd_TypeDef pupd) {
|
||||
GPIO_InitTypeDef init = {
|
||||
.GPIO_Pin = af_config->gpio_pin,
|
||||
.GPIO_Mode = GPIO_Mode_AF,
|
||||
.GPIO_Speed = speed,
|
||||
.GPIO_OType = otype,
|
||||
.GPIO_PuPd = pupd
|
||||
};
|
||||
|
||||
GPIO_PinAFConfig(af_config->gpio, af_config->gpio_pin_source, af_config->gpio_af);
|
||||
GPIO_Init(af_config->gpio, &init);
|
||||
}
|
28
platform/silk/boot/src/drivers/gpio.h
Normal file
28
platform/silk/boot/src/drivers/gpio.h
Normal file
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "stm32f4xx_gpio.h"
|
||||
|
||||
#include "board/board.h"
|
||||
|
||||
void gpio_enable_all(void);
|
||||
void gpio_disable_all(void);
|
||||
void gpio_af_init(const AfConfig *af_config, GPIOOType_TypeDef otype, GPIOSpeed_TypeDef speed,
|
||||
GPIOPuPd_TypeDef pupd);
|
743
platform/silk/boot/src/drivers/i2c.c
Normal file
743
platform/silk/boot/src/drivers/i2c.c
Normal file
|
@ -0,0 +1,743 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "util/misc.h"
|
||||
#include "drivers/i2c.h"
|
||||
#include "drivers/periph_config.h"
|
||||
#include "drivers/gpio.h"
|
||||
#include "system/passert.h"
|
||||
#include "system/logging.h"
|
||||
#include "util/delay.h"
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "stm32f4xx_gpio.h"
|
||||
#include "stm32f4xx_rcc.h"
|
||||
#include "stm32f4xx_i2c.h"
|
||||
|
||||
#define portBASE_TYPE int
|
||||
#define pdFALSE 0
|
||||
#define portEND_SWITCHING_ISR(expr) (void)(expr)
|
||||
|
||||
#define I2C_ERROR_TIMEOUT_MS (1000)
|
||||
#define I2C_TIMEOUT_ATTEMPTS_MAX (2 * 1000 * 1000)
|
||||
#define I2C_NORMAL_MODE_CLOCK_SPEED_MAX (100000)
|
||||
#define I2C_NACK_COUNT_MAX (1000) // MFI NACKs while busy. We delay ~1ms between retries so this is approximately a 1s timeout
|
||||
|
||||
#define I2C_READ_WRITE_BIT (0x01)
|
||||
|
||||
typedef struct I2cTransfer {
|
||||
uint8_t device_address;
|
||||
bool read_not_write; //True for read, false for write
|
||||
uint8_t register_address;
|
||||
uint8_t size;
|
||||
uint8_t idx;
|
||||
uint8_t *data;
|
||||
enum TransferState {
|
||||
TRANSFER_STATE_WRITE_ADDRESS_TX,
|
||||
TRANSFER_STATE_WRITE_REG_ADDRESS,
|
||||
TRANSFER_STATE_REPEAT_START,
|
||||
TRANSFER_STATE_WRITE_ADDRESS_RX,
|
||||
TRANSFER_STATE_WAIT_FOR_DATA,
|
||||
TRANSFER_STATE_READ_DATA,
|
||||
TRANSFER_STATE_WRITE_DATA,
|
||||
TRANSFER_STATE_END_WRITE,
|
||||
|
||||
TRANSFER_STATE_INVALID,
|
||||
} state;
|
||||
bool result;
|
||||
uint16_t nack_count;
|
||||
}I2cTransfer;
|
||||
|
||||
typedef struct I2cBus{
|
||||
I2C_TypeDef *i2c;
|
||||
uint8_t user_count;
|
||||
I2cTransfer transfer;
|
||||
volatile bool busy;
|
||||
}I2cBus;
|
||||
|
||||
static I2cBus i2c_buses[BOARD_I2C_BUS_COUNT];
|
||||
|
||||
static uint32_t s_guard_events[] = {
|
||||
I2C_EVENT_MASTER_MODE_SELECT,
|
||||
I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED,
|
||||
I2C_EVENT_MASTER_BYTE_TRANSMITTED,
|
||||
I2C_EVENT_MASTER_MODE_SELECT,
|
||||
I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED,
|
||||
I2C_EVENT_MASTER_BYTE_RECEIVED,
|
||||
I2C_EVENT_MASTER_BYTE_TRANSMITTING,
|
||||
I2C_EVENT_MASTER_BYTE_TRANSMITTED,
|
||||
};
|
||||
|
||||
static bool s_initialized = false;
|
||||
|
||||
/*----------------SEMAPHORE/LOCKING FUNCTIONS--------------------------*/
|
||||
|
||||
static void bus_lock(I2cBus *bus) {
|
||||
}
|
||||
|
||||
static void bus_unlock(I2cBus *bus) {
|
||||
}
|
||||
|
||||
static bool semaphore_take(I2cBus *bus) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool semaphore_wait(I2cBus *bus) {
|
||||
bus->busy = true;
|
||||
volatile uint32_t timeout_attempts = I2C_TIMEOUT_ATTEMPTS_MAX;
|
||||
while ((timeout_attempts-- > 0) && (bus->busy));
|
||||
bus->busy = false;
|
||||
return (timeout_attempts != 0);
|
||||
}
|
||||
|
||||
static void semaphore_give(I2cBus *bus) {
|
||||
}
|
||||
|
||||
/*-------------------BUS/PIN CONFIG FUNCTIONS--------------------------*/
|
||||
|
||||
//! Configure bus power supply control pin as output
|
||||
//! Lock bus and peripheral config access before configuring pins
|
||||
void i2c_bus_rail_ctl_config(OutputConfig pin_config) {
|
||||
GPIO_InitTypeDef GPIO_InitStructure;
|
||||
GPIO_StructInit(&GPIO_InitStructure);
|
||||
|
||||
GPIO_InitStructure.GPIO_Pin = pin_config.gpio_pin;
|
||||
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
|
||||
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
|
||||
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
|
||||
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
|
||||
GPIO_Init(pin_config.gpio, &GPIO_InitStructure);
|
||||
}
|
||||
|
||||
//! Configure bus pins for use by I2C peripheral
|
||||
//! Lock bus and peripheral config access before configuring pins
|
||||
static void bus_pin_cfg_i2c(AfConfig pin_config) {
|
||||
GPIO_InitTypeDef gpio_init_struct;
|
||||
gpio_init_struct.GPIO_Pin = pin_config.gpio_pin;
|
||||
gpio_init_struct.GPIO_Mode = GPIO_Mode_AF;
|
||||
gpio_init_struct.GPIO_Speed = GPIO_Speed_50MHz;
|
||||
gpio_init_struct.GPIO_OType = GPIO_OType_OD;
|
||||
gpio_init_struct.GPIO_PuPd = GPIO_PuPd_NOPULL;
|
||||
GPIO_Init(pin_config.gpio, &gpio_init_struct);
|
||||
|
||||
GPIO_PinAFConfig(pin_config.gpio, pin_config.gpio_pin_source, pin_config.gpio_af);
|
||||
}
|
||||
|
||||
//! Configure bus pin as input
|
||||
//! Lock bus and peripheral config access before use
|
||||
static void bus_pin_cfg_input(AfConfig pin_config) {
|
||||
// Configure pin as high impedance input
|
||||
GPIO_InitTypeDef gpio_init_struct;
|
||||
gpio_init_struct.GPIO_Pin = pin_config.gpio_pin;
|
||||
gpio_init_struct.GPIO_Mode = GPIO_Mode_IN;
|
||||
gpio_init_struct.GPIO_Speed = GPIO_Speed_2MHz;
|
||||
gpio_init_struct.GPIO_PuPd = GPIO_PuPd_NOPULL;
|
||||
GPIO_Init(pin_config.gpio, &gpio_init_struct);
|
||||
}
|
||||
|
||||
//! Configure bus pin as output
|
||||
//! Lock bus and peripheral config access before use
|
||||
static void bus_pin_cfg_output(AfConfig pin_config, bool pin_state) {
|
||||
// Configure pin as output
|
||||
GPIO_InitTypeDef gpio_init_struct;
|
||||
gpio_init_struct.GPIO_Pin = pin_config.gpio_pin;
|
||||
gpio_init_struct.GPIO_Mode = GPIO_Mode_OUT;
|
||||
gpio_init_struct.GPIO_Speed = GPIO_Speed_2MHz;
|
||||
gpio_init_struct.GPIO_PuPd = GPIO_PuPd_NOPULL;
|
||||
GPIO_Init(pin_config.gpio, &gpio_init_struct);
|
||||
|
||||
// Set bit high or low
|
||||
GPIO_WriteBit(pin_config.gpio, pin_config.gpio_pin, (pin_state) ? Bit_SET : Bit_RESET);
|
||||
}
|
||||
|
||||
//! Power down I2C bus power supply
|
||||
//! Always lock bus and peripheral config access before use
|
||||
static void bus_rail_power_down(uint8_t bus_idx) {
|
||||
if (BOARD_CONFIG.i2c_bus_configs[bus_idx].rail_ctl_fn == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
BOARD_CONFIG.i2c_bus_configs[bus_idx].rail_ctl_fn(false);
|
||||
|
||||
// Drain through pull-ups
|
||||
bus_pin_cfg_output(BOARD_CONFIG.i2c_bus_configs[bus_idx].i2c_scl, false);
|
||||
bus_pin_cfg_output(BOARD_CONFIG.i2c_bus_configs[bus_idx].i2c_sda, false);
|
||||
}
|
||||
|
||||
//! Power up I2C bus power supply
|
||||
//! Always lock bus and peripheral config access before use
|
||||
static void bus_rail_power_up(uint8_t bus_idx) {
|
||||
if (BOARD_CONFIG.i2c_bus_configs[bus_idx].rail_ctl_fn == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
// check that at least enough time has elapsed since the last turn-off
|
||||
// TODO: is this necessary in bootloader?
|
||||
static const uint32_t MIN_STOP_TIME_MS = 10;
|
||||
delay_ms(MIN_STOP_TIME_MS);
|
||||
|
||||
bus_pin_cfg_input(BOARD_CONFIG.i2c_bus_configs[bus_idx].i2c_scl);
|
||||
bus_pin_cfg_input(BOARD_CONFIG.i2c_bus_configs[bus_idx].i2c_sda);
|
||||
|
||||
BOARD_CONFIG.i2c_bus_configs[bus_idx].rail_ctl_fn(true);
|
||||
}
|
||||
|
||||
//! Initialize the I2C peripheral
|
||||
//! Lock the bus and peripheral config access before initialization
|
||||
static void bus_init(uint8_t bus_idx) {
|
||||
// Initialize peripheral
|
||||
I2C_InitTypeDef i2c_init_struct;
|
||||
I2C_StructInit(&i2c_init_struct);
|
||||
|
||||
if (BOARD_CONFIG.i2c_bus_configs[bus_idx].clock_speed > I2C_NORMAL_MODE_CLOCK_SPEED_MAX) { //Fast mode
|
||||
i2c_init_struct.I2C_DutyCycle = BOARD_CONFIG.i2c_bus_configs[bus_idx].duty_cycle;
|
||||
}
|
||||
i2c_init_struct.I2C_ClockSpeed = BOARD_CONFIG.i2c_bus_configs[bus_idx].clock_speed;
|
||||
i2c_init_struct.I2C_Ack = I2C_Ack_Enable;
|
||||
|
||||
I2C_Init(i2c_buses[bus_idx].i2c, &i2c_init_struct);
|
||||
I2C_Cmd(i2c_buses[bus_idx].i2c, ENABLE);
|
||||
}
|
||||
|
||||
//! Configure the bus pins, enable the peripheral clock and initialize the I2C peripheral.
|
||||
//! Always lock the bus and peripheral config access before enabling it
|
||||
static void bus_enable(uint8_t bus_idx) {
|
||||
// Don't power up rail if the bus is already in use (enable can be called to reset bus)
|
||||
if (i2c_buses[bus_idx].user_count == 0) {
|
||||
bus_rail_power_up(bus_idx);
|
||||
}
|
||||
|
||||
bus_pin_cfg_i2c(BOARD_CONFIG.i2c_bus_configs[bus_idx].i2c_scl);
|
||||
bus_pin_cfg_i2c(BOARD_CONFIG.i2c_bus_configs[bus_idx].i2c_sda);
|
||||
|
||||
// Enable peripheral clock
|
||||
periph_config_enable(RCC_APB1PeriphClockCmd, BOARD_CONFIG.i2c_bus_configs[bus_idx].clock_ctrl);
|
||||
|
||||
bus_init(bus_idx);
|
||||
}
|
||||
|
||||
//! De-initialize and gate the clock to the peripheral
|
||||
//! Power down rail if the bus supports that and no devices are using it
|
||||
//! Always lock the bus and peripheral config access before disabling it
|
||||
static void bus_disable(uint8_t bus_idx) {
|
||||
I2C_DeInit(i2c_buses[bus_idx].i2c);
|
||||
|
||||
periph_config_disable(RCC_APB1PeriphClockCmd, BOARD_CONFIG.i2c_bus_configs[bus_idx].clock_ctrl);
|
||||
|
||||
// Do not de-power rail if there are still devices using bus (just reset peripheral and pin configuration during a bus reset)
|
||||
if (i2c_buses[bus_idx].user_count == 0) {
|
||||
bus_rail_power_down(bus_idx);
|
||||
}
|
||||
else {
|
||||
bus_pin_cfg_input(BOARD_CONFIG.i2c_bus_configs[bus_idx].i2c_scl);
|
||||
bus_pin_cfg_input(BOARD_CONFIG.i2c_bus_configs[bus_idx].i2c_sda);
|
||||
}
|
||||
}
|
||||
|
||||
//! Perform a soft reset of the bus
|
||||
//! Always lock the bus before reset
|
||||
static void bus_reset(uint8_t bus_idx) {
|
||||
bus_disable(bus_idx);
|
||||
bus_enable(bus_idx);
|
||||
}
|
||||
|
||||
/*---------------INIT/USE/RELEASE/RESET FUNCTIONS----------------------*/
|
||||
|
||||
void i2c_init(void) {
|
||||
for (uint32_t i = 0; i < ARRAY_LENGTH(i2c_buses); i++) {
|
||||
i2c_buses[i].i2c = BOARD_CONFIG.i2c_bus_configs[i].i2c;
|
||||
i2c_buses[i].user_count = 0;
|
||||
i2c_buses[i].busy = false;
|
||||
i2c_buses[i].transfer.idx = 0;
|
||||
i2c_buses[i].transfer.size = 0;
|
||||
i2c_buses[i].transfer.data = NULL;
|
||||
i2c_buses[i].transfer.state = TRANSFER_STATE_INVALID;
|
||||
|
||||
NVIC_InitTypeDef NVIC_InitStructure;
|
||||
NVIC_InitStructure.NVIC_IRQChannel = BOARD_CONFIG.i2c_bus_configs[i].ev_irq_channel;
|
||||
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x0c;
|
||||
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x00;
|
||||
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
|
||||
NVIC_Init(&NVIC_InitStructure);
|
||||
|
||||
NVIC_InitStructure.NVIC_IRQChannel = BOARD_CONFIG.i2c_bus_configs[i].er_irq_channel;
|
||||
NVIC_Init(&NVIC_InitStructure);
|
||||
|
||||
I2C_DeInit(i2c_buses[i].i2c);
|
||||
}
|
||||
|
||||
s_initialized = true;
|
||||
|
||||
for (uint32_t i = 0; i < ARRAY_LENGTH(i2c_buses); i++) {
|
||||
if (BOARD_CONFIG.i2c_bus_configs[i].rail_cfg_fn) {
|
||||
BOARD_CONFIG.i2c_bus_configs[i].rail_cfg_fn();
|
||||
}
|
||||
if (BOARD_CONFIG.i2c_bus_configs[i].rail_ctl_fn) {
|
||||
bus_rail_power_down(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void i2c_use(I2cDevice device_id) {
|
||||
PBL_ASSERTN(s_initialized);
|
||||
PBL_ASSERT(device_id < BOARD_CONFIG.i2c_device_count, "I2C device ID out of bounds %d (max: %d)",
|
||||
device_id, BOARD_CONFIG.i2c_device_count);
|
||||
|
||||
uint8_t bus_idx = BOARD_CONFIG.i2c_device_map[device_id];
|
||||
I2cBus *bus = &i2c_buses[bus_idx];
|
||||
|
||||
bus_lock(bus);
|
||||
|
||||
if (bus->user_count == 0) {
|
||||
bus_enable(bus_idx);
|
||||
}
|
||||
bus->user_count++;
|
||||
|
||||
bus_unlock(bus);
|
||||
}
|
||||
|
||||
void i2c_release(I2cDevice device_id) {
|
||||
PBL_ASSERTN(s_initialized);
|
||||
PBL_ASSERTN(device_id < BOARD_CONFIG.i2c_device_count);
|
||||
|
||||
uint8_t bus_idx = BOARD_CONFIG.i2c_device_map[device_id];
|
||||
I2cBus *bus = &i2c_buses[bus_idx];
|
||||
|
||||
bus_lock(bus);
|
||||
|
||||
if (bus->user_count == 0) {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Attempted release of disabled bus %d by device %d", bus_idx, device_id);
|
||||
bus_unlock(bus);
|
||||
return;
|
||||
}
|
||||
|
||||
bus->user_count--;
|
||||
if (bus->user_count == 0) {
|
||||
bus_disable(bus_idx);
|
||||
}
|
||||
|
||||
bus_unlock(bus);
|
||||
}
|
||||
|
||||
void i2c_reset(I2cDevice device_id) {
|
||||
PBL_ASSERTN(s_initialized);
|
||||
PBL_ASSERTN(device_id < BOARD_CONFIG.i2c_device_count);
|
||||
|
||||
uint8_t bus_idx = BOARD_CONFIG.i2c_device_map[device_id];
|
||||
I2cBus *bus = &i2c_buses[bus_idx];
|
||||
|
||||
// Take control of bus; only one task may use bus at a time
|
||||
bus_lock(bus);
|
||||
|
||||
if (bus->user_count == 0) {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Attempted reset of disabled bus %d by device %d", bus_idx, device_id);
|
||||
bus_unlock(bus);
|
||||
return;
|
||||
}
|
||||
|
||||
PBL_LOG(LOG_LEVEL_WARNING, "Resetting I2C bus %" PRId8, bus_idx);
|
||||
|
||||
// decrement user count for reset so that if this user is the only user, the
|
||||
// bus will be powered down during the reset
|
||||
bus->user_count--;
|
||||
|
||||
// Reset and reconfigure bus and pins
|
||||
bus_reset(bus_idx);
|
||||
|
||||
//Restore user count
|
||||
bus->user_count++;
|
||||
|
||||
bus_unlock(bus);
|
||||
}
|
||||
|
||||
/*--------------------DATA TRANSFER FUNCTIONS--------------------------*/
|
||||
|
||||
//! Wait a short amount of time for busy bit to clear
|
||||
static bool wait_for_busy_clear(uint8_t bus_idx) {
|
||||
unsigned int attempts = I2C_TIMEOUT_ATTEMPTS_MAX;
|
||||
while((i2c_buses[bus_idx].i2c->SR2 & I2C_SR2_BUSY) != 0) {
|
||||
--attempts;
|
||||
if (!attempts) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//! Abort the transfer
|
||||
//! Should only be called when the bus is locked
|
||||
static void abort_transfer(I2cBus *bus) {
|
||||
// Disable all interrupts on the bus
|
||||
bus->i2c->CR2 &= ~(I2C_CR2_ITEVTEN | I2C_CR2_ITERREN | I2C_CR2_ITBUFEN);
|
||||
// Generate a stop condition
|
||||
bus->i2c->CR1 |= I2C_CR1_STOP;
|
||||
bus->transfer.state = TRANSFER_STATE_INVALID;
|
||||
}
|
||||
|
||||
//! Set up and start a transfer to a device, wait for it to finish and clean up after the transfer has completed
|
||||
static bool do_transfer(I2cDevice device_id, bool read_not_write, uint8_t device_address, uint8_t register_address, uint8_t size, uint8_t *data) {
|
||||
PBL_ASSERTN(s_initialized);
|
||||
PBL_ASSERTN(device_id < BOARD_CONFIG.i2c_device_count);
|
||||
|
||||
uint8_t bus_idx = BOARD_CONFIG.i2c_device_map[device_id];
|
||||
I2cBus *bus = &i2c_buses[bus_idx];
|
||||
|
||||
// Take control of bus; only one task may use bus at a time
|
||||
bus_lock(bus);
|
||||
|
||||
if (bus->user_count == 0) {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Attempted access to disabled bus %d by device %d", bus_idx, device_id);
|
||||
bus_unlock(bus);
|
||||
return false;
|
||||
}
|
||||
|
||||
// If bus is busy (it shouldn't be as this function waits for the bus to report a non-idle state
|
||||
// before exiting) reset the bus and wait for it to become not-busy
|
||||
// Exit if bus remains busy. User module should reset the I2C module at this point
|
||||
if((bus->i2c->SR2 & I2C_SR2_BUSY) != 0) {
|
||||
bus_reset(bus_idx);
|
||||
|
||||
if (!wait_for_busy_clear(bus_idx)) {
|
||||
// Bus did not recover after reset
|
||||
bus_unlock(bus);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Take binary semaphore so that next take will block
|
||||
PBL_ASSERT(semaphore_take(bus), "Could not acquire semaphore token");
|
||||
|
||||
// Set up transfer
|
||||
bus->transfer.device_address = device_address;
|
||||
bus->transfer.register_address = register_address;
|
||||
bus->transfer.read_not_write = read_not_write;
|
||||
bus->transfer.size = size;
|
||||
bus->transfer.idx = 0;
|
||||
bus->transfer.state = TRANSFER_STATE_WRITE_ADDRESS_TX;
|
||||
bus->transfer.data = data;
|
||||
bus->transfer.nack_count = 0;
|
||||
|
||||
// Ack received bytes
|
||||
I2C_AcknowledgeConfig(bus->i2c, ENABLE);
|
||||
|
||||
bool result = false;
|
||||
|
||||
do {
|
||||
// Generate start event
|
||||
bus->i2c->CR1 |= I2C_CR1_START;
|
||||
//Enable event and error interrupts
|
||||
bus->i2c->CR2 |= I2C_CR2_ITEVTEN | I2C_CR2_ITERREN;
|
||||
|
||||
// Wait on semaphore until it is released by interrupt or a timeout occurs
|
||||
if (semaphore_wait(bus)) {
|
||||
|
||||
if (bus->transfer.state == TRANSFER_STATE_INVALID) {
|
||||
// Transfer is complete
|
||||
result = bus->transfer.result;
|
||||
if (!result) {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "I2C Error on bus %" PRId8, bus_idx);
|
||||
}
|
||||
|
||||
} else if (bus->transfer.nack_count < I2C_NACK_COUNT_MAX) {
|
||||
// NACK received after start condition sent: the MFI chip NACKs start conditions whilst it is busy
|
||||
// Retry start condition after a short delay.
|
||||
// A NACK count is incremented for each NACK received, so that legitimate NACK
|
||||
// errors cause the transfer to be aborted (after the NACK count max has been reached).
|
||||
|
||||
bus->transfer.nack_count++;
|
||||
|
||||
delay_ms(1);
|
||||
|
||||
} else {
|
||||
// Too many NACKs received, abort transfer
|
||||
abort_transfer(bus);
|
||||
break;
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "I2C Error: too many NACKs received on bus %" PRId8, bus_idx);
|
||||
}
|
||||
|
||||
} else {
|
||||
// Timeout, abort transfer
|
||||
abort_transfer(bus);
|
||||
break;
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Transfer timed out on bus %" PRId8, bus_idx);
|
||||
}
|
||||
} while (bus->transfer.state != TRANSFER_STATE_INVALID);
|
||||
|
||||
// Return semaphore token so another transfer can be started
|
||||
semaphore_give(bus);
|
||||
|
||||
// Wait for bus to to clear the busy flag before a new transfer starts
|
||||
// Theoretically a transfer could complete successfully, but the busy flag never clears,
|
||||
// which would cause the next transfer to fail
|
||||
if (!wait_for_busy_clear(bus_idx)) {
|
||||
// Reset I2C bus if busy flag does not clear
|
||||
bus_reset(bus_idx);
|
||||
}
|
||||
|
||||
bus_unlock(bus);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool i2c_read_register(I2cDevice device_id, uint8_t i2c_device_address, uint8_t register_address, uint8_t *result) {
|
||||
return i2c_read_register_block(device_id, i2c_device_address, register_address, 1, result);
|
||||
}
|
||||
|
||||
bool i2c_read_register_block(I2cDevice device_id, uint8_t i2c_device_address, uint8_t
|
||||
register_address_start, uint8_t read_size, uint8_t* result_buffer) {
|
||||
// Do transfer locks the bus
|
||||
bool result = do_transfer(device_id, true, i2c_device_address, register_address_start, read_size, result_buffer);
|
||||
|
||||
if (!result) {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Read failed on bus %" PRId8, BOARD_CONFIG.i2c_device_map[device_id]);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool i2c_write_register(I2cDevice device_id, uint8_t i2c_device_address, uint8_t register_address,
|
||||
uint8_t value) {
|
||||
return i2c_write_register_block(device_id, i2c_device_address, register_address, 1, &value);
|
||||
}
|
||||
|
||||
bool i2c_write_register_block(I2cDevice device_id, uint8_t i2c_device_address, uint8_t
|
||||
register_address_start, uint8_t write_size, const uint8_t* buffer) {
|
||||
// Do transfer locks the bus
|
||||
bool result = do_transfer(device_id, false, i2c_device_address, register_address_start, write_size, (uint8_t*)buffer);
|
||||
|
||||
if (!result) {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Write failed on bus %" PRId8, BOARD_CONFIG.i2c_device_map[device_id]);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*------------------------INTERRUPT FUNCTIONS--------------------------*/
|
||||
|
||||
//! End a transfer and disable further interrupts
|
||||
//! Only call from interrupt functions
|
||||
static portBASE_TYPE end_transfer_irq(I2cBus *bus, bool result) {
|
||||
bus->i2c->CR2 &= ~(I2C_CR2_ITEVTEN | I2C_CR2_ITERREN | I2C_CR2_ITBUFEN);
|
||||
bus->i2c->CR1 |= I2C_CR1_STOP;
|
||||
bus->transfer.result = result;
|
||||
bus->transfer.state = TRANSFER_STATE_INVALID;
|
||||
|
||||
bus->busy = false;
|
||||
return pdFALSE;
|
||||
}
|
||||
|
||||
//! Pause a transfer, disabling interrupts during the pause
|
||||
//! Only call from interrupt functions
|
||||
static portBASE_TYPE pause_transfer_irq(I2cBus *bus) {
|
||||
bus->i2c->CR2 &= ~(I2C_CR2_ITEVTEN | I2C_CR2_ITERREN | I2C_CR2_ITBUFEN);
|
||||
bus->busy = false;
|
||||
return pdFALSE;
|
||||
}
|
||||
|
||||
//! Handle an IRQ event on the specified \a bus
|
||||
static portBASE_TYPE irq_event_handler(I2cBus *bus) {
|
||||
if (bus->transfer.state == TRANSFER_STATE_INVALID) {
|
||||
|
||||
// Disable interrupts if spurious interrupt received
|
||||
bus->i2c->CR2 &= ~(I2C_CR2_ITEVTEN | I2C_CR2_ITBUFEN);
|
||||
return pdFALSE;
|
||||
}
|
||||
|
||||
// Check that the expected event occurred
|
||||
if (I2C_CheckEvent(bus->i2c, s_guard_events[bus->transfer.state]) == ERROR) {
|
||||
// Ignore interrupt - A spurious byte transmitted event as well as an interrupt with no
|
||||
// discernible event associated with it occur after repeat start events are generated
|
||||
return pdFALSE;
|
||||
}
|
||||
portBASE_TYPE should_context_switch = pdFALSE;
|
||||
|
||||
switch (bus->transfer.state) {
|
||||
case TRANSFER_STATE_WRITE_ADDRESS_TX:
|
||||
// Write the i2c device address to the bus to select it in write mode.
|
||||
bus->i2c->DR = bus->transfer.device_address & ~I2C_READ_WRITE_BIT;
|
||||
bus->transfer.state = TRANSFER_STATE_WRITE_REG_ADDRESS;
|
||||
break;
|
||||
|
||||
case TRANSFER_STATE_WRITE_REG_ADDRESS:
|
||||
// Write the register address
|
||||
bus->i2c->DR = bus->transfer.register_address;
|
||||
|
||||
if (bus->transfer.read_not_write) {
|
||||
bus->transfer.state = TRANSFER_STATE_REPEAT_START;
|
||||
} else {
|
||||
// Enable TXE interrupt for writing
|
||||
bus->i2c->CR2 |= I2C_CR2_ITBUFEN;
|
||||
bus->transfer.state = TRANSFER_STATE_WRITE_DATA;
|
||||
}
|
||||
break;
|
||||
|
||||
case TRANSFER_STATE_REPEAT_START:
|
||||
// Generate a repeat start
|
||||
bus->i2c->CR1 |= I2C_CR1_START;
|
||||
bus->transfer.state = TRANSFER_STATE_WRITE_ADDRESS_RX;
|
||||
break;
|
||||
|
||||
case TRANSFER_STATE_WRITE_ADDRESS_RX:
|
||||
// Write the I2C device address again, but this time in read mode.
|
||||
bus->i2c->DR = bus->transfer.device_address | I2C_READ_WRITE_BIT;
|
||||
if (bus->transfer.size == 1) {
|
||||
// Last byte, we want to NACK this one to tell the slave to stop sending us bytes.
|
||||
bus->i2c->CR1 &= ~I2C_CR1_ACK;
|
||||
}
|
||||
bus->transfer.state = TRANSFER_STATE_WAIT_FOR_DATA;
|
||||
break;
|
||||
|
||||
case TRANSFER_STATE_WAIT_FOR_DATA:
|
||||
//This state just ensures that the transition to receive mode event happened
|
||||
|
||||
// Enable RXNE interrupt for writing
|
||||
bus->i2c->CR2 |= I2C_CR2_ITBUFEN;
|
||||
bus->transfer.state = TRANSFER_STATE_READ_DATA;
|
||||
break;
|
||||
|
||||
case TRANSFER_STATE_READ_DATA:
|
||||
bus->transfer.data[bus->transfer.idx] = bus->i2c->DR;
|
||||
bus->transfer.idx++;
|
||||
|
||||
if (bus->transfer.idx + 1 == bus->transfer.size) {
|
||||
// Last byte, we want to NACK this one to tell the slave to stop sending us bytes.
|
||||
bus->i2c->CR1 &= ~I2C_CR1_ACK;
|
||||
}
|
||||
else if (bus->transfer.idx == bus->transfer.size) {
|
||||
// End transfer after all bytes have been received
|
||||
bus->i2c->CR2 &= ~I2C_CR2_ITBUFEN;
|
||||
should_context_switch = end_transfer_irq(bus, true);
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case TRANSFER_STATE_WRITE_DATA:
|
||||
bus->i2c->DR = bus->transfer.data[bus->transfer.idx];
|
||||
bus->transfer.idx++;
|
||||
if (bus->transfer.idx == bus->transfer.size) {
|
||||
bus->i2c->CR2 &= ~I2C_CR2_ITBUFEN;
|
||||
bus->transfer.state = TRANSFER_STATE_END_WRITE;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case TRANSFER_STATE_END_WRITE:
|
||||
// End transfer after all bytes have been sent
|
||||
should_context_switch = end_transfer_irq(bus, true);
|
||||
break;
|
||||
|
||||
default:
|
||||
// Abort transfer from invalid state - should never reach here (state machine logic broken)
|
||||
should_context_switch = end_transfer_irq(bus, false);
|
||||
break;
|
||||
}
|
||||
|
||||
return should_context_switch;
|
||||
}
|
||||
|
||||
//! Handle error interrupt on the specified \a bus
|
||||
static portBASE_TYPE irq_error_handler(I2cBus *bus) {
|
||||
if (bus->transfer.state == TRANSFER_STATE_INVALID) {
|
||||
|
||||
// Disable interrupts if spurious interrupt received
|
||||
bus->i2c->CR2 &= ~I2C_CR2_ITERREN;
|
||||
return pdFALSE;
|
||||
}
|
||||
|
||||
// Data overrun and bus errors can only really be handled by terminating the transfer and
|
||||
// trying to recover bus to an idle state. Each error will be logged. In each case a stop
|
||||
// condition will be sent and then we will wait on the busy flag to clear (if it doesn't,
|
||||
// a soft reset of the bus will be performed (handled in wait i2c_do_transfer).
|
||||
|
||||
if ((bus->i2c->SR1 & I2C_SR1_OVR) != 0) {
|
||||
bus->i2c->SR1 &= ~I2C_SR1_OVR;
|
||||
|
||||
// Data overrun
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Data overrun during I2C transaction; Bus: 0x%p", bus->i2c);
|
||||
}
|
||||
if ((bus->i2c->SR1 & I2C_SR1_BERR) != 0) {
|
||||
bus->i2c->SR1 &= ~I2C_SR1_BERR;
|
||||
|
||||
// Bus error: invalid start or stop condition detected
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Bus error detected during I2C transaction; Bus: 0x%p", bus->i2c);
|
||||
}
|
||||
if ((bus->i2c->SR1 & I2C_SR1_AF) != 0) {
|
||||
bus->i2c->SR1 &= ~I2C_SR1_AF;
|
||||
|
||||
// NACK received.
|
||||
//
|
||||
// The MFI chip will cause NACK errors during read operations after writing a start bit (first start
|
||||
// or repeat start indicating that it is busy. The transfer must be paused and the start condition sent
|
||||
// again after a delay and the state machine set back a step.
|
||||
//
|
||||
// If the NACK is received after any other action log an error and abort the transfer
|
||||
|
||||
|
||||
if (bus->transfer.state == TRANSFER_STATE_WAIT_FOR_DATA) {
|
||||
bus->transfer.state = TRANSFER_STATE_WRITE_ADDRESS_RX;
|
||||
return pause_transfer_irq(bus);
|
||||
}
|
||||
else if (bus->transfer.state == TRANSFER_STATE_WRITE_REG_ADDRESS){
|
||||
bus->transfer.state = TRANSFER_STATE_WRITE_ADDRESS_TX;
|
||||
return pause_transfer_irq(bus);
|
||||
}
|
||||
else {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "NACK received during I2C transfer; Bus: 0x%p", bus->i2c);
|
||||
}
|
||||
}
|
||||
|
||||
return end_transfer_irq(bus, false);
|
||||
|
||||
}
|
||||
|
||||
void I2C1_EV_IRQHandler(void) {
|
||||
portEND_SWITCHING_ISR(irq_event_handler(&i2c_buses[0]));
|
||||
}
|
||||
|
||||
void I2C1_ER_IRQHandler(void) {
|
||||
portEND_SWITCHING_ISR(irq_error_handler(&i2c_buses[0]));
|
||||
}
|
||||
|
||||
void I2C2_EV_IRQHandler(void) {
|
||||
portEND_SWITCHING_ISR(irq_event_handler(&i2c_buses[1]));
|
||||
}
|
||||
|
||||
void I2C2_ER_IRQHandler(void) {
|
||||
portEND_SWITCHING_ISR(irq_error_handler(&i2c_buses[1]));
|
||||
}
|
||||
|
||||
void I2C3_EV_IRQHandler(void) {
|
||||
portEND_SWITCHING_ISR(irq_event_handler(&i2c_buses[0]));
|
||||
}
|
||||
|
||||
void I2C3_ER_IRQHandler(void) {
|
||||
portEND_SWITCHING_ISR(irq_error_handler(&i2c_buses[0]));
|
||||
}
|
||||
|
||||
/*------------------------COMMAND FUNCTIONS--------------------------*/
|
||||
|
||||
void command_power_2v5(char *arg) {
|
||||
// Intentionally ignore the s_running_count and make it so!
|
||||
// This is intended for low level electrical test only
|
||||
if(!strcmp("on", arg)) {
|
||||
bus_rail_power_up(1);
|
||||
} else {
|
||||
bus_rail_power_down(1);
|
||||
}
|
||||
}
|
86
platform/silk/boot/src/drivers/i2c.h
Normal file
86
platform/silk/boot/src/drivers/i2c.h
Normal file
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "board/board.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
//! Initialize the I2C driver. Must be called before first use
|
||||
void i2c_init(void);
|
||||
|
||||
//! Start using the I2C bus connected to the device specified by \a device_id
|
||||
//! Must be called before any other use of the bus is performed
|
||||
//! @param device_id ID of device
|
||||
void i2c_use(I2cDevice device_id);
|
||||
|
||||
//! Stop using the I2C bus connected to the device specified by \a device_id
|
||||
//! Call when done with the bus
|
||||
//! @param device_id ID of device
|
||||
void i2c_release(I2cDevice device_id);
|
||||
|
||||
//! Reset the bus
|
||||
//! Will re-initialize the bus and cycle the power to the bus if this is
|
||||
//! supported for the bus the device specified by \a device_id is connected to)
|
||||
//! @param device_id ID of device, this will identify the bus to be reset
|
||||
void i2c_reset(I2cDevice device_id);
|
||||
|
||||
//! Manually bang out the clock on the bus specified by \a device_id for a period
|
||||
//! of time or until the data line recovers
|
||||
//! Must not be called before \ref i2c_use has been called for the device
|
||||
//! @param device_id ID of device, this will identify the bus to be recovered
|
||||
//! @return true if the data line recovered, false otherwise
|
||||
bool i2c_bitbang_recovery(I2cDevice device_id);
|
||||
|
||||
//! Read the value of a register
|
||||
//! Must not be called before \ref i2c_use has been called for the device
|
||||
//! @param device_id ID of device to communicate with
|
||||
//! @param i2c_device_address Device bus address
|
||||
//! @param register_address Address of register to read
|
||||
//! @param result Pointer to destination buffer
|
||||
//! @return true if transfer succeeded, false if error occurred
|
||||
bool i2c_read_register(I2cDevice device_id, uint8_t i2c_device_address, uint8_t register_address, uint8_t *result);
|
||||
|
||||
//! Read a sequence of registers starting from \a register_address_start
|
||||
//! Must not be called before \ref i2c_use has been called for the device
|
||||
//! @param device_id ID of device to communicate with
|
||||
//! @param i2c_device_address Device bus address
|
||||
//! @param register_address_start Address of first register to read
|
||||
//! @param read_size Number of bytes to read
|
||||
//! @param result_buffer Pointer to destination buffer
|
||||
//! @return true if transfer succeeded, false if error occurred
|
||||
bool i2c_read_register_block(I2cDevice device_id, uint8_t i2c_device_address, uint8_t register_address_start, uint8_t read_size, uint8_t* result_buffer);
|
||||
|
||||
//! Write to a register
|
||||
//! Must not be called before \ref i2c_use has been called for the device
|
||||
//! @param device_id ID of device to communicate with
|
||||
//! @param i2c_device_address Device bus address
|
||||
//! @param register_address Address of register to write to
|
||||
//! @param value Data value to write
|
||||
//! @return true if transfer succeeded, false if error occurred
|
||||
bool i2c_write_register(I2cDevice device_id, uint8_t i2c_device_address, uint8_t register_address, uint8_t value);
|
||||
|
||||
//! Write to a sequence of registers starting from \a register_address_start
|
||||
//! Must not be called before \ref i2c_use has been called for the device
|
||||
//! @param device_id ID of device to communicate with
|
||||
//! @param i2c_device_address Device bus address
|
||||
//! @param register_address_start Address of first register to read
|
||||
//! @param write_size Number of bytes to write
|
||||
//! @param buffer Pointer to source buffer
|
||||
//! @return true if transfer succeeded, false if error occurred
|
||||
bool i2c_write_register_block(I2cDevice device_id, uint8_t i2c_device_address, uint8_t register_address_start, uint8_t write_size, const uint8_t* buffer);
|
35
platform/silk/boot/src/drivers/i2c_private.c
Normal file
35
platform/silk/boot/src/drivers/i2c_private.c
Normal file
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
#include "drivers/pmic.h"
|
||||
#include "drivers/gpio.h"
|
||||
#include "util/delay.h"
|
||||
#include "board/board.h"
|
||||
|
||||
#include "stm32f4xx_gpio.h"
|
||||
#include "stm32f4xx_rcc.h"
|
||||
#include "stm32f4xx_i2c.h"
|
||||
|
||||
void silk_rail_ctl_fn(bool enable) {
|
||||
// NYI
|
||||
return;
|
||||
}
|
||||
|
||||
void silk_rail_cfg_fn(void) {
|
||||
// NYI
|
||||
return;
|
||||
}
|
29
platform/silk/boot/src/drivers/periph_config.h
Normal file
29
platform/silk/boot/src/drivers/periph_config.h
Normal file
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "stm32f4xx.h"
|
||||
|
||||
typedef void (*ClockCmd)(uint32_t periph, FunctionalState state);
|
||||
|
||||
static inline void periph_config_enable(ClockCmd clock_cmd, uint32_t periph) {
|
||||
clock_cmd(periph, ENABLE);
|
||||
}
|
||||
|
||||
static inline void periph_config_disable(ClockCmd clock_cmd, uint32_t periph) {
|
||||
clock_cmd(periph, DISABLE);
|
||||
}
|
37
platform/silk/boot/src/drivers/pmic.h
Normal file
37
platform/silk/boot/src/drivers/pmic.h
Normal file
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
//! Initialize the PMIC driver. Call this once at startup.
|
||||
bool pmic_init(void);
|
||||
|
||||
//! Whether or not the usb charger is detected by the pmic.
|
||||
bool pmic_is_usb_connected(void);
|
||||
|
||||
//! Returns true if this boot was caused by a charger event from standby, but
|
||||
//! there is no charger connected.
|
||||
bool pmic_boot_due_to_charger_disconnect(void);
|
||||
|
||||
//! Enter what we consider to be a "powered off" state, which is the as3701
|
||||
//! standby state, where we keep the RTC powered.
|
||||
bool pmic_power_off(void);
|
||||
|
||||
//! Fully shut down the as3701, which does not power the RTC.
|
||||
bool pmic_full_power_off(void);
|
311
platform/silk/boot/src/drivers/pmic/as3701b.c
Normal file
311
platform/silk/boot/src/drivers/pmic/as3701b.c
Normal file
|
@ -0,0 +1,311 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "drivers/pmic.h"
|
||||
|
||||
#include "board/board.h"
|
||||
#include "drivers/gpio.h"
|
||||
#include "drivers/i2c.h"
|
||||
#include "system/passert.h"
|
||||
|
||||
#include "stm32f4xx_rcc.h"
|
||||
#include "stm32f4xx_gpio.h"
|
||||
#include "stm32f4xx_adc.h"
|
||||
|
||||
typedef enum {
|
||||
PmicRegisters_SD1_VOLTAGE = 0x01,
|
||||
PmicRegisters_LDO1_VOLTAGE = 0x02,
|
||||
PmicRegisters_LDO2_VOLTAGE = 0x03,
|
||||
|
||||
PmicRegisters_GPIO1_CNTL = 0x09,
|
||||
PmicRegisters_GPIO2_CNTL = 0x0a,
|
||||
PmicRegisters_GPIO3_CNTL = 0x0b,
|
||||
PmicRegisters_GPIO4_CNTL = 0x0c,
|
||||
PmicRegisters_GPIO5_CNTL = 0x0d,
|
||||
PmicRegisters_GPIO_SIG_OUT = 0x20,
|
||||
PmicRegisters_GPIO_SIG_IN = 0x21,
|
||||
|
||||
PmicRegisters_REG1_VOLTAGE = 0x22,
|
||||
PmicRegisters_REG2_VOLTAGE = 0x23,
|
||||
PmicRegisters_REG_CNTL = 0x24,
|
||||
|
||||
PmicRegisters_GPIO_CNTL1 = 0x25,
|
||||
PmicRegisters_GPIO_CNTL2 = 0x26,
|
||||
PmicRegisters_SD_CNTL1 = 0x30,
|
||||
|
||||
PmicRegisters_BATT_VOLTAGE_MON = 0x32,
|
||||
PmicRegisters_STARTUP_CNTL = 0x33,
|
||||
PmicRegisters_REFERENCE_CNTL = 0x35,
|
||||
PmicRegisters_RESET_CNTL = 0x36,
|
||||
PmicRegisters_OVERTEMP_CNTL = 0x37,
|
||||
PmicRegisters_REG_STANDBY_MOD1 = 0x39,
|
||||
|
||||
PmicRegisters_PWM_CNTL_L = 0x41,
|
||||
PmicRegisters_PWM_CNTL_H = 0x42,
|
||||
|
||||
PmicRegisters_CURR1_VAL = 0x43,
|
||||
PmicRegisters_CURR2_VAL = 0x44,
|
||||
|
||||
PmicRegisters_REG_STATUS = 0x73,
|
||||
PmicRegisters_INT_MASK_1 = 0x74,
|
||||
PmicRegisters_INT_MASK_2 = 0x75,
|
||||
PmicRegisters_INT_STATUS_1 = 0x77,
|
||||
PmicRegisters_INT_STATUS_2 = 0x78,
|
||||
PmicRegisters_CHARGE_CNTL = 0x80,
|
||||
PmicRegisters_CHARGE_VOLTAGE_CNTL = 0x81,
|
||||
PmicRegisters_CHARGE_CURRENT_CNTL = 0x82,
|
||||
PmicRegisters_CHARGE_CONFIG_1 = 0x83,
|
||||
PmicRegisters_CHARGE_CONFIG_2 = 0x84,
|
||||
PmicRegisters_CHARGE_SUPERVISION = 0x85,
|
||||
PmicRegisters_CHARGE_STATUS_1 = 0x86,
|
||||
PmicRegisters_CHARGE_STATUS_2 = 0x87,
|
||||
|
||||
PmicRegisters_LOCK_REG = 0x8e,
|
||||
|
||||
PmicRegisters_CHIP_ID = 0x90,
|
||||
PmicRegisters_CHIP_REV = 0x91,
|
||||
|
||||
PmicRegisters_FUSE_5 = 0xa5,
|
||||
PmicRegisters_FUSE_6 = 0xa6,
|
||||
PmicRegisters_FUSE_7 = 0xa7,
|
||||
PmicRegisters_FUSE_8 = 0xa8,
|
||||
PmicRegisters_FUSE_9 = 0xa9,
|
||||
PmicRegisters_FUSE_10 = 0xaa,
|
||||
PmicRegisters_FUSE_11 = 0xab,
|
||||
PmicRegisters_FUSE_12 = 0xac,
|
||||
PmicRegisters_FUSE_13 = 0xad,
|
||||
PmicRegisters_FUSE_14 = 0xae,
|
||||
PmicRegisters_FUSE_15 = 0xaf,
|
||||
} PmicRegisters;
|
||||
|
||||
// These are values for the reset_reason field of the ResetControl register.
|
||||
// None of these values should ever be changed, as conversions are done on
|
||||
// readings dont directly out of the ResetControl register.
|
||||
// See Figure 79 of the AS3701B datasheet for more information.
|
||||
typedef enum {
|
||||
PmicResetReason_PowerUpFromScratch = 0x00, //!< Battery or charger insertion from scratch
|
||||
PmicResetReason_ResVoltFall = 0x01, //!< Battery voltage drop below 2.75V
|
||||
|
||||
PmicResetReason_ForcedReset = 0x02, //!< sw force_reset
|
||||
|
||||
PmicResetReason_OnPulledHigh = 0x03, //!< Force sw power_off, ON pulled high
|
||||
PmicResetReason_Charger = 0x04, //!< Forced sw power_off, Charger detected.
|
||||
|
||||
PmicResetReason_XRES = 0x05, //!< External trigger through XRES
|
||||
PmicResetReason_OverTemperature = 0x06, //!< Reset caused by overtemperature
|
||||
|
||||
PmicResetReason_OnKeyHold = 0x08, //!< Reset for holding down on key
|
||||
|
||||
PmicResetReason_StandbyInterrupt = 0x0b, //!< Reset for interrupt in standby
|
||||
PmicResetReason_StandbyOnPulledHigh = 0x0c, //!< Reset for ON pulled high in standby
|
||||
|
||||
PmicResetReason_Unknown,
|
||||
} PmicResetReason;
|
||||
|
||||
static void prv_start_120hz_clock(void);
|
||||
|
||||
static bool prv_init_gpio(void) {
|
||||
return true;
|
||||
}
|
||||
|
||||
//! Interrupt masks for InterruptStatus1 and InterruptMask1 registers
|
||||
enum PmicInt1 {
|
||||
PmicInt1_Trickle = (1 << 0), //!< Trickle charge
|
||||
PmicInt1_NoBat = (1 << 1), //!< Battery detached
|
||||
PmicInt1_Resume = (1 << 2), //!< Resuming charge on drop after full
|
||||
PmicInt1_EOC = (1 << 3), //!< End of charge
|
||||
PmicInt1_ChDet = (1 << 4), //!< Charger detected
|
||||
PmicInt1_OnKey = (1 << 5), //!< On Key held
|
||||
PmicInt1_OvTemp = (1 << 6), //!< Set when 110deg is exceeded
|
||||
PmicInt1_LowBat = (1 << 7), //!< Low Battery detected. Set when BSUP drops below ResVoltFall
|
||||
};
|
||||
|
||||
enum PmicRail {
|
||||
PmicRail_SD1, //!< 1.8V
|
||||
PmicRail_LDO1, //!< 3.0V
|
||||
PmicRail_LDO2, //!< 2.0V
|
||||
};
|
||||
|
||||
#define AS3701B_CHIP_ID 0x11
|
||||
#define AS3701B_WRITE_ADDR 0x80
|
||||
#define AS3701B_READ_ADDR 0x81
|
||||
|
||||
static bool prv_read_register(uint8_t register_address, uint8_t *result) {
|
||||
i2c_use(I2C_DEVICE_AS3701B);
|
||||
bool rv = i2c_read_register(I2C_DEVICE_AS3701B, AS3701B_READ_ADDR, register_address, result);
|
||||
i2c_release(I2C_DEVICE_AS3701B);
|
||||
return rv;
|
||||
}
|
||||
|
||||
static bool prv_write_register(uint8_t register_address, uint8_t value) {
|
||||
i2c_use(I2C_DEVICE_AS3701B);
|
||||
bool rv = i2c_write_register(I2C_DEVICE_AS3701B, AS3701B_WRITE_ADDR, register_address, value);
|
||||
i2c_release(I2C_DEVICE_AS3701B);
|
||||
return rv;
|
||||
}
|
||||
|
||||
static bool prv_register_set_bit(uint8_t register_address, uint8_t bit) {
|
||||
uint8_t val;
|
||||
if (!prv_read_register(register_address, &val)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
val |= (1 << bit);
|
||||
return prv_write_register(register_address, val);
|
||||
}
|
||||
|
||||
static bool prv_register_clear_bit(uint8_t register_address, uint8_t bit) {
|
||||
uint8_t val;
|
||||
if (!prv_read_register(register_address, &val)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
val &= ~(1 << bit);
|
||||
return prv_write_register(register_address, val);
|
||||
}
|
||||
|
||||
// Read the interrupt status registers to clear pending bits.
|
||||
static void prv_clear_pending_interrupts(void) {
|
||||
uint8_t throwaway_read_result;
|
||||
prv_read_register(PmicRegisters_INT_STATUS_1, &throwaway_read_result);
|
||||
prv_read_register(PmicRegisters_INT_STATUS_2, &throwaway_read_result);
|
||||
}
|
||||
|
||||
// Set up 120Hz clock which is used for VCOM.
|
||||
// Slowest possible setting, with divisor of 16 and high/low duration of 256us.
|
||||
void prv_start_120hz_clock(void) {
|
||||
const uint8_t pwm_high_low_time_us = (256 - 1);
|
||||
prv_write_register(PmicRegisters_PWM_CNTL_H, pwm_high_low_time_us);
|
||||
prv_write_register(PmicRegisters_PWM_CNTL_L, pwm_high_low_time_us);
|
||||
|
||||
bool success = false;
|
||||
uint8_t ref_cntl;
|
||||
if (prv_read_register(PmicRegisters_REFERENCE_CNTL, &ref_cntl)) {
|
||||
ref_cntl |= 0x3; // Divisor of 16
|
||||
prv_write_register(PmicRegisters_REFERENCE_CNTL, ref_cntl);
|
||||
|
||||
// Enable PWM Output on GPIO2 (Fig. 64)
|
||||
// Bits 6-4: Mode, 0x1 = Output
|
||||
// Bits 0-3: iosf, 0xe = PWM
|
||||
uint8_t val = (1 << 4) | 0x0e;
|
||||
success = prv_write_register(PmicRegisters_GPIO2_CNTL, val);
|
||||
}
|
||||
PBL_ASSERT(success, "Failed to start PMIC 120Hz PWM");
|
||||
}
|
||||
|
||||
static bool prv_is_alive(void) {
|
||||
uint8_t chip_id;
|
||||
if (!prv_read_register(PmicRegisters_CHIP_ID, &chip_id)) {
|
||||
return false;
|
||||
}
|
||||
const bool found = (chip_id == AS3701B_CHIP_ID);
|
||||
if (found) {
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Found the as3701b");
|
||||
} else {
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Error: read as3701b whoami byte 0x%x, expecting 0x11", chip_id);
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
bool pmic_init(void) {
|
||||
prv_init_gpio();
|
||||
if (!prv_is_alive()) {
|
||||
return false;
|
||||
}
|
||||
prv_start_120hz_clock();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool pmic_is_usb_connected(void) {
|
||||
uint8_t status;
|
||||
if (!prv_read_register(PmicRegisters_CHARGE_STATUS_2, &status)) {
|
||||
return false;
|
||||
}
|
||||
// ChargerStatus2 (Fig. 98)
|
||||
// Bit 2: Charger detected
|
||||
return !!(status & (1 << 2));
|
||||
}
|
||||
|
||||
static PmicResetReason prv_reset_reason(void) {
|
||||
uint8_t val;
|
||||
if (!prv_read_register(PmicRegisters_RESET_CNTL, &val)) {
|
||||
return PmicResetReason_Unknown;
|
||||
}
|
||||
return (val & 0xf0) >> 4;
|
||||
}
|
||||
|
||||
// If the pmic indicates that we were reset due to a charger interrupt, but the
|
||||
// charger is currently disconnected, then we know we were woken by a disconnect event.
|
||||
bool pmic_boot_due_to_charger_disconnect(void) {
|
||||
if (prv_reset_reason() != PmicResetReason_StandbyInterrupt) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t int_status;
|
||||
if (!prv_read_register(PmicRegisters_INT_STATUS_1, &int_status)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ((int_status & PmicInt1_ChDet) && !pmic_is_usb_connected());
|
||||
}
|
||||
|
||||
// This is a hard power off, resulting in all rails being disabled.
|
||||
bool pmic_full_power_off(void) {
|
||||
// ResetControl (Fig. 79)
|
||||
// Bit 1: power_off - Start a reset cycle, and wait for ON or charger to complete the reset.
|
||||
if (prv_register_set_bit(PmicRegisters_RESET_CNTL, 1)) {
|
||||
while (1) {}
|
||||
__builtin_unreachable();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// On the as3701b, a power_off will cut power to all rails. We want to keep the
|
||||
// RTC alive, so rather than performing a sw_power_off, enter the pmic's standby
|
||||
// mode, powering down all but LDO2.
|
||||
bool pmic_power_off(void) {
|
||||
// Only enable interrupts that should be able to wake us out of standby
|
||||
// - Wake on charger detect
|
||||
const uint8_t int_mask = ~((uint8_t)PmicInt1_ChDet);
|
||||
prv_write_register(PmicRegisters_INT_MASK_1, int_mask);
|
||||
prv_write_register(PmicRegisters_INT_MASK_2, 0xff);
|
||||
|
||||
// Clear interrupt status so we're not woken immediately (read the regs)
|
||||
prv_clear_pending_interrupts();
|
||||
|
||||
// Set Reg_Standby_mod1 to specify which rails to turn off / keep on
|
||||
// - SD1, LDO1 off
|
||||
// - LDO2 on
|
||||
// - Disable regulator pulldowns
|
||||
prv_write_register(PmicRegisters_REG_STANDBY_MOD1, 0xa);
|
||||
|
||||
// Set standby_mode_on (bit 4) in ReferenceControl to 1 (See Fig. 78)
|
||||
if (prv_register_set_bit(PmicRegisters_REFERENCE_CNTL, 4)) {
|
||||
while (1) {}
|
||||
__builtin_unreachable();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void set_ldo3_power_state(bool enabled) {
|
||||
}
|
||||
|
||||
void set_4V5_power_state(bool enabled) {
|
||||
}
|
||||
|
||||
void set_6V6_power_state(bool enabled) {
|
||||
}
|
111
platform/silk/boot/src/drivers/system_flash.c
Normal file
111
platform/silk/boot/src/drivers/system_flash.c
Normal file
|
@ -0,0 +1,111 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "drivers/system_flash.h"
|
||||
|
||||
#include "drivers/dbgserial.h"
|
||||
#include "util/misc.h"
|
||||
|
||||
#include "stm32f4xx_flash.h"
|
||||
|
||||
static uint16_t s_sectors[] = {
|
||||
FLASH_Sector_0, FLASH_Sector_1, FLASH_Sector_2, FLASH_Sector_3,
|
||||
FLASH_Sector_4, FLASH_Sector_5, FLASH_Sector_6, FLASH_Sector_7,
|
||||
FLASH_Sector_8, FLASH_Sector_9, FLASH_Sector_10, FLASH_Sector_11
|
||||
};
|
||||
static uint32_t s_sector_addresses[] = {
|
||||
ADDR_FLASH_SECTOR_0, ADDR_FLASH_SECTOR_1, ADDR_FLASH_SECTOR_2, ADDR_FLASH_SECTOR_3,
|
||||
ADDR_FLASH_SECTOR_4, ADDR_FLASH_SECTOR_5, ADDR_FLASH_SECTOR_6, ADDR_FLASH_SECTOR_7,
|
||||
ADDR_FLASH_SECTOR_8, ADDR_FLASH_SECTOR_9, ADDR_FLASH_SECTOR_10, ADDR_FLASH_SECTOR_11
|
||||
};
|
||||
|
||||
int prv_get_sector_num_for_address(uint32_t address) {
|
||||
if (address < s_sector_addresses[0]) {
|
||||
dbgserial_print("address ");
|
||||
dbgserial_print_hex(address);
|
||||
dbgserial_putstr(" is outside system flash");
|
||||
return -1;
|
||||
}
|
||||
for (size_t i=0; i < ARRAY_LENGTH(s_sector_addresses)-1; ++i) {
|
||||
if (s_sector_addresses[i] <= address
|
||||
&& address < s_sector_addresses[i+1]) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return ARRAY_LENGTH(s_sector_addresses)-1;
|
||||
}
|
||||
|
||||
bool system_flash_erase(
|
||||
uint32_t address, size_t length,
|
||||
SystemFlashProgressCb progress_callback, void *progress_context) {
|
||||
if (length == 0) {
|
||||
// Nothing to do
|
||||
return true;
|
||||
}
|
||||
|
||||
int first_sector = prv_get_sector_num_for_address(address);
|
||||
int last_sector = prv_get_sector_num_for_address(address + length - 1);
|
||||
if (first_sector < 0 || last_sector < 0) {
|
||||
return false;
|
||||
}
|
||||
int count = last_sector - first_sector + 1;
|
||||
if (progress_callback) {
|
||||
progress_callback(0, count, progress_context);
|
||||
}
|
||||
|
||||
FLASH_Unlock();
|
||||
for (int sector = first_sector; sector <= last_sector; ++sector) {
|
||||
FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_OPERR | FLASH_FLAG_WRPERR |
|
||||
FLASH_FLAG_PGAERR | FLASH_FLAG_PGPERR | FLASH_FLAG_PGSERR);
|
||||
if (FLASH_EraseSector(
|
||||
s_sectors[sector], VoltageRange_1) != FLASH_COMPLETE) {
|
||||
dbgserial_print("failed to erase sector ");
|
||||
dbgserial_print_hex(sector);
|
||||
dbgserial_newline();
|
||||
FLASH_Lock();
|
||||
return false;
|
||||
}
|
||||
if (progress_callback) {
|
||||
progress_callback(sector - first_sector + 1, count, progress_context);
|
||||
}
|
||||
}
|
||||
FLASH_Lock();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool system_flash_write(
|
||||
uint32_t address, const void *data, size_t length,
|
||||
SystemFlashProgressCb progress_callback, void *progress_context) {
|
||||
FLASH_Unlock();
|
||||
FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_OPERR | FLASH_FLAG_WRPERR |
|
||||
FLASH_FLAG_PGAERR | FLASH_FLAG_PGPERR|FLASH_FLAG_PGSERR);
|
||||
|
||||
const uint8_t *data_array = data;
|
||||
for (uint32_t i = 0; i < length; ++i) {
|
||||
if (FLASH_ProgramByte(address + i, data_array[i]) != FLASH_COMPLETE) {
|
||||
dbgserial_print("failed to write address ");
|
||||
dbgserial_print_hex(address);
|
||||
dbgserial_newline();
|
||||
FLASH_Lock();
|
||||
return false;
|
||||
}
|
||||
if (progress_callback && i % 128 == 0) {
|
||||
progress_callback(i/128, length/128, progress_context);
|
||||
}
|
||||
}
|
||||
FLASH_Lock();
|
||||
return true;
|
||||
}
|
64
platform/silk/boot/src/drivers/system_flash.h
Normal file
64
platform/silk/boot/src/drivers/system_flash.h
Normal file
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "stm32f4xx_flash.h"
|
||||
|
||||
#define ADDR_FLASH_SECTOR_0 ((uint32_t)0x08000000) /* Base @ of Sector 0, 16 Kbytes */
|
||||
#define ADDR_FLASH_SECTOR_1 ((uint32_t)0x08004000) /* Base @ of Sector 1, 16 Kbytes */
|
||||
#define ADDR_FLASH_SECTOR_2 ((uint32_t)0x08008000) /* Base @ of Sector 2, 16 Kbytes */
|
||||
#define ADDR_FLASH_SECTOR_3 ((uint32_t)0x0800C000) /* Base @ of Sector 3, 16 Kbytes */
|
||||
#define ADDR_FLASH_SECTOR_4 ((uint32_t)0x08010000) /* Base @ of Sector 4, 64 Kbytes */
|
||||
#define ADDR_FLASH_SECTOR_5 ((uint32_t)0x08020000) /* Base @ of Sector 5, 128 Kbytes */
|
||||
#define ADDR_FLASH_SECTOR_6 ((uint32_t)0x08040000) /* Base @ of Sector 6, 128 Kbytes */
|
||||
#define ADDR_FLASH_SECTOR_7 ((uint32_t)0x08060000) /* Base @ of Sector 7, 128 Kbytes */
|
||||
#define ADDR_FLASH_SECTOR_8 ((uint32_t)0x08080000) /* Base @ of Sector 8, 128 Kbytes */
|
||||
#define ADDR_FLASH_SECTOR_9 ((uint32_t)0x080A0000) /* Base @ of Sector 9, 128 Kbytes */
|
||||
#define ADDR_FLASH_SECTOR_10 ((uint32_t)0x080C0000) /* Base @ of Sector 10, 128 Kbytes */
|
||||
#define ADDR_FLASH_SECTOR_11 ((uint32_t)0x080E0000) /* Base @ of Sector 11, 128 Kbytes */
|
||||
|
||||
typedef void (*SystemFlashProgressCb)(
|
||||
uint32_t progress, uint32_t total, void *context);
|
||||
|
||||
// Erase the sectors of flash which lie within the given address range.
|
||||
//
|
||||
// If the address range overlaps even one single byte of a sector, the entire
|
||||
// sector is erased.
|
||||
//
|
||||
// If progress_callback is not NULL, it is called at the beginning of the erase
|
||||
// process and after each sector is erased. The rational number (progress/total)
|
||||
// increases monotonically as the sector erasue procedure progresses.
|
||||
//
|
||||
// Returns true if successful, false if an error occurred.
|
||||
bool system_flash_erase(
|
||||
uint32_t address, size_t length,
|
||||
SystemFlashProgressCb progress_callback, void *progress_context);
|
||||
|
||||
// Write data into flash. The flash must already be erased.
|
||||
//
|
||||
// If progress_callback is not NULL, it is called at the beginning of the
|
||||
// writing process and periodically thereafter. The rational number
|
||||
// (progress/total) increases monotonically as the data is written.
|
||||
//
|
||||
// Returns true if successful, false if an error occurred.
|
||||
bool system_flash_write(
|
||||
uint32_t address, const void *data, size_t length,
|
||||
SystemFlashProgressCb progress_callback, void *progress_context);
|
41
platform/silk/boot/src/drivers/watchdog.c
Normal file
41
platform/silk/boot/src/drivers/watchdog.c
Normal file
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "drivers/watchdog.h"
|
||||
|
||||
#include "stm32f4xx_dbgmcu.h"
|
||||
#include "stm32f4xx_iwdg.h"
|
||||
#include "stm32f4xx_rcc.h"
|
||||
|
||||
void watchdog_init(void) {
|
||||
IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable);
|
||||
|
||||
IWDG_SetPrescaler(IWDG_Prescaler_64); // ~8 seconds
|
||||
IWDG_SetReload(0xfff);
|
||||
|
||||
IWDG_WriteAccessCmd(IWDG_WriteAccess_Disable);
|
||||
|
||||
DBGMCU_APB1PeriphConfig(DBGMCU_IWDG_STOP, ENABLE);
|
||||
}
|
||||
|
||||
void watchdog_start(void) {
|
||||
IWDG_Enable();
|
||||
IWDG_ReloadCounter();
|
||||
}
|
||||
|
||||
bool watchdog_check_reset_flag(void) {
|
||||
return RCC_GetFlagStatus(RCC_FLAG_IWDGRST) != RESET;
|
||||
}
|
24
platform/silk/boot/src/drivers/watchdog.h
Normal file
24
platform/silk/boot/src/drivers/watchdog.h
Normal 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);
|
21
platform/silk/boot/src/firmware.h
Normal file
21
platform/silk/boot/src/firmware.h
Normal file
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "stm32f4xx.h"
|
||||
|
||||
#define FIRMWARE_BASE (FLASH_BASE + (BOOTLOADER_LENGTH))
|
25
platform/silk/boot/src/flash_region.h
Normal file
25
platform/silk/boot/src/flash_region.h
Normal file
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
// Scratch space for firmware images (normal and recovery).
|
||||
// We assume this is 64k aligned...
|
||||
#define FLASH_REGION_FIRMWARE_SCRATCH_BEGIN 0x000000
|
||||
#define FLASH_REGION_FIRMWARE_SCRATCH_END 0x100000 // 1024k
|
||||
|
||||
#define FLASH_REGION_SAFE_FIRMWARE_BEGIN 0x200000
|
||||
#define FLASH_REGION_SAFE_FIRMWARE_END 0x280000 // 512k
|
226
platform/silk/boot/src/fw_copy.c
Normal file
226
platform/silk/boot/src/fw_copy.c
Normal file
|
@ -0,0 +1,226 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "fw_copy.h"
|
||||
|
||||
#include "drivers/dbgserial.h"
|
||||
#include "drivers/display.h"
|
||||
#include "drivers/flash.h"
|
||||
#include "drivers/system_flash.h"
|
||||
#include "firmware.h"
|
||||
#include "flash_region.h"
|
||||
#include "system/bootbits.h"
|
||||
#include "system/firmware_storage.h"
|
||||
#include "system/reset.h"
|
||||
#include "util/crc32.h"
|
||||
#include "util/misc.h"
|
||||
#include "util/delay.h"
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define MAX_CHUNK_SIZE 65536
|
||||
|
||||
static bool check_valid_firmware_crc(uint32_t flash_address, FirmwareDescription* desc) {
|
||||
dbgserial_putstr("Checksumming firmware update");
|
||||
const uint32_t crc = flash_calculate_checksum(flash_address, desc->firmware_length);
|
||||
dbgserial_print("Calculated checksum: ");
|
||||
dbgserial_print_hex(crc);
|
||||
dbgserial_newline();
|
||||
return crc == desc->checksum;
|
||||
}
|
||||
|
||||
static void prv_display_erase_progress(
|
||||
uint32_t progress, uint32_t total, void *ctx) {
|
||||
display_firmware_update_progress(progress, total * 2);
|
||||
}
|
||||
|
||||
static bool erase_old_firmware(uint32_t firmware_length) {
|
||||
dbgserial_putstr("erase_old_firmware");
|
||||
return system_flash_erase(
|
||||
FIRMWARE_BASE, firmware_length, prv_display_erase_progress, 0);
|
||||
}
|
||||
|
||||
static void prv_display_write_progress(
|
||||
uint32_t progress, uint32_t total, void *ctx) {
|
||||
display_firmware_update_progress(progress/2 + total/2, total);
|
||||
}
|
||||
|
||||
static bool write_new_firmware(uint32_t flash_new_fw_start,
|
||||
uint32_t firmware_length) {
|
||||
dbgserial_putstr("write_new_firmware");
|
||||
// We can't just read the flash like memory, so we gotta lift everything ourselves.
|
||||
// buffer is static so it goes in BSS, since stack is only 8192 bytes
|
||||
static uint8_t buffer[MAX_CHUNK_SIZE];
|
||||
uint32_t chunk_size;
|
||||
for (uint32_t i = 0; i < firmware_length; i += chunk_size) {
|
||||
chunk_size = MIN(MAX_CHUNK_SIZE, firmware_length - i);
|
||||
flash_read_bytes(buffer, flash_new_fw_start + i, chunk_size);
|
||||
if (!system_flash_write(FIRMWARE_BASE + i, buffer, chunk_size, NULL, NULL)) {
|
||||
dbgserial_putstr("We're dead");
|
||||
return false;
|
||||
}
|
||||
prv_display_write_progress(i + chunk_size, firmware_length, NULL);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool check_firmware_crc(FirmwareDescription* firmware_description) {
|
||||
dbgserial_print("Checksumming ");
|
||||
dbgserial_print_hex(firmware_description->firmware_length);
|
||||
dbgserial_print(" bytes\r\n");
|
||||
|
||||
const uint32_t crc = crc32(CRC32_INIT, (const uint8_t *)FIRMWARE_BASE,
|
||||
firmware_description->firmware_length);
|
||||
|
||||
dbgserial_print("Checksum - wanted ");
|
||||
dbgserial_print_hex(firmware_description->checksum);
|
||||
dbgserial_print(" got ");
|
||||
dbgserial_print_hex(crc);
|
||||
dbgserial_newline();
|
||||
|
||||
return crc == firmware_description->checksum;
|
||||
}
|
||||
|
||||
typedef enum UpdateFirmwareResult {
|
||||
UPDATE_FW_SUCCESS = 0,
|
||||
UPDATE_FW_ERROR_MICRO_FLASH_UNTOUCHED = 1,
|
||||
UPDATE_FW_ERROR_MICRO_FLASH_MANGLED = 2
|
||||
} UpdateFirmwareResult;
|
||||
|
||||
static UpdateFirmwareResult update_fw(uint32_t flash_address) {
|
||||
display_firmware_update_progress(0, 1);
|
||||
boot_bit_set(BOOT_BIT_NEW_FW_UPDATE_IN_PROGRESS);
|
||||
|
||||
FirmwareDescription firmware_description =
|
||||
firmware_storage_read_firmware_description(flash_address);
|
||||
|
||||
if (!firmware_storage_check_valid_firmware_description(&firmware_description)) {
|
||||
dbgserial_putstr("Invalid firmware description!");
|
||||
return UPDATE_FW_ERROR_MICRO_FLASH_UNTOUCHED;
|
||||
}
|
||||
|
||||
if (!check_valid_firmware_crc(
|
||||
flash_address + sizeof(FirmwareDescription), &firmware_description)) {
|
||||
dbgserial_putstr("Invalid firmware CRC in SPI flash!");
|
||||
return UPDATE_FW_ERROR_MICRO_FLASH_UNTOUCHED;
|
||||
}
|
||||
|
||||
erase_old_firmware(firmware_description.firmware_length);
|
||||
|
||||
write_new_firmware(
|
||||
flash_address + sizeof(FirmwareDescription),
|
||||
firmware_description.firmware_length);
|
||||
|
||||
if (!check_firmware_crc(&firmware_description)) {
|
||||
dbgserial_putstr(
|
||||
"Our internal flash contents are bad (checksum failed)! "
|
||||
"This is really bad!");
|
||||
return UPDATE_FW_ERROR_MICRO_FLASH_MANGLED;
|
||||
}
|
||||
|
||||
return UPDATE_FW_SUCCESS;
|
||||
}
|
||||
|
||||
void check_update_fw(void) {
|
||||
if (!boot_bit_test(BOOT_BIT_NEW_FW_AVAILABLE)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (boot_bit_test(BOOT_BIT_NEW_FW_UPDATE_IN_PROGRESS)) {
|
||||
dbgserial_putstr("Our previous firmware update failed, aborting update.");
|
||||
|
||||
// Pretend like the new firmware bit wasn't set afterall. We'll just run the
|
||||
// previous code, whether that was normal firmware or the recovery firmware.
|
||||
boot_bit_clear(BOOT_BIT_NEW_FW_UPDATE_IN_PROGRESS);
|
||||
boot_bit_clear(BOOT_BIT_NEW_FW_AVAILABLE);
|
||||
boot_bit_clear(BOOT_BIT_NEW_FW_INSTALLED);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
dbgserial_putstr("New firmware is available!");
|
||||
|
||||
boot_bit_clear(BOOT_BIT_FW_START_FAIL_STRIKE_ONE);
|
||||
boot_bit_clear(BOOT_BIT_FW_START_FAIL_STRIKE_TWO);
|
||||
boot_bit_clear(BOOT_BIT_RECOVERY_LOAD_FAIL_STRIKE_ONE);
|
||||
boot_bit_clear(BOOT_BIT_RECOVERY_LOAD_FAIL_STRIKE_TWO);
|
||||
|
||||
UpdateFirmwareResult result = update_fw(FLASH_REGION_FIRMWARE_SCRATCH_BEGIN);
|
||||
switch (result) {
|
||||
case UPDATE_FW_SUCCESS:
|
||||
break;
|
||||
case UPDATE_FW_ERROR_MICRO_FLASH_UNTOUCHED:
|
||||
// Our firmware update failed in a way that didn't break our previous
|
||||
// firmware. Just run the previous code, whether that was normal firmware
|
||||
// or the recovery firmware.
|
||||
break;
|
||||
case UPDATE_FW_ERROR_MICRO_FLASH_MANGLED:
|
||||
// We've broken our internal flash when trying to update our normal
|
||||
// firmware. Fall back immediately to the recovery firmare.
|
||||
boot_bit_set(BOOT_BIT_FW_START_FAIL_STRIKE_ONE);
|
||||
boot_bit_set(BOOT_BIT_FW_START_FAIL_STRIKE_TWO);
|
||||
system_reset();
|
||||
return;
|
||||
}
|
||||
|
||||
// Done, we're ready to boot.
|
||||
boot_bit_clear(BOOT_BIT_NEW_FW_UPDATE_IN_PROGRESS);
|
||||
boot_bit_clear(BOOT_BIT_NEW_FW_AVAILABLE);
|
||||
boot_bit_set(BOOT_BIT_NEW_FW_INSTALLED);
|
||||
}
|
||||
|
||||
bool switch_to_recovery_fw() {
|
||||
dbgserial_putstr("Loading recovery firmware");
|
||||
|
||||
UpdateFirmwareResult result = update_fw(FLASH_REGION_SAFE_FIRMWARE_BEGIN);
|
||||
bool recovery_fw_ok = true;
|
||||
switch (result) {
|
||||
case UPDATE_FW_SUCCESS:
|
||||
boot_bit_clear(BOOT_BIT_RECOVERY_LOAD_FAIL_STRIKE_ONE);
|
||||
boot_bit_clear(BOOT_BIT_RECOVERY_LOAD_FAIL_STRIKE_TWO);
|
||||
boot_bit_set(BOOT_BIT_RECOVERY_START_IN_PROGRESS);
|
||||
break;
|
||||
case UPDATE_FW_ERROR_MICRO_FLASH_UNTOUCHED:
|
||||
case UPDATE_FW_ERROR_MICRO_FLASH_MANGLED:
|
||||
// Keep us booting into recovery firmware.
|
||||
boot_bit_set(BOOT_BIT_FW_START_FAIL_STRIKE_ONE);
|
||||
boot_bit_set(BOOT_BIT_FW_START_FAIL_STRIKE_TWO);
|
||||
|
||||
if (!boot_bit_test(BOOT_BIT_RECOVERY_LOAD_FAIL_STRIKE_ONE)) {
|
||||
dbgserial_putstr("Failed to load recovery firmware, strike one. Try again.");
|
||||
boot_bit_set(BOOT_BIT_RECOVERY_LOAD_FAIL_STRIKE_ONE);
|
||||
boot_bit_set(BOOT_BIT_SOFTWARE_FAILURE_OCCURRED);
|
||||
system_reset();
|
||||
} else if (!boot_bit_test(BOOT_BIT_RECOVERY_LOAD_FAIL_STRIKE_TWO)) {
|
||||
dbgserial_putstr("Failed to load recovery firmware, strike two. Try again.");
|
||||
boot_bit_set(BOOT_BIT_RECOVERY_LOAD_FAIL_STRIKE_TWO);
|
||||
boot_bit_set(BOOT_BIT_SOFTWARE_FAILURE_OCCURRED);
|
||||
system_reset();
|
||||
} else {
|
||||
dbgserial_putstr("Failed to load recovery firmware, strike three. SAD WATCH");
|
||||
boot_bit_clear(BOOT_BIT_FW_START_FAIL_STRIKE_ONE);
|
||||
boot_bit_clear(BOOT_BIT_FW_START_FAIL_STRIKE_TWO);
|
||||
boot_bit_clear(BOOT_BIT_RECOVERY_LOAD_FAIL_STRIKE_ONE);
|
||||
boot_bit_clear(BOOT_BIT_RECOVERY_LOAD_FAIL_STRIKE_TWO);
|
||||
recovery_fw_ok = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
boot_bit_clear(BOOT_BIT_NEW_FW_UPDATE_IN_PROGRESS);
|
||||
return recovery_fw_ok;
|
||||
}
|
24
platform/silk/boot/src/fw_copy.h
Normal file
24
platform/silk/boot/src/fw_copy.h
Normal file
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
void check_update_fw(void);
|
||||
|
||||
//! @return false if we've failed to load recovery mode too many times and we should just sadwatch
|
||||
bool switch_to_recovery_fw(void);
|
8
platform/silk/boot/src/git_version.auto.h.in
Normal file
8
platform/silk/boot/src/git_version.auto.h.in
Normal 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@"
|
39
platform/silk/boot/src/hardfault_handler.c
Normal file
39
platform/silk/boot/src/hardfault_handler.c
Normal 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));
|
||||
}
|
474
platform/silk/boot/src/main.c
Normal file
474
platform/silk/boot/src/main.c
Normal file
|
@ -0,0 +1,474 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "board/board.h"
|
||||
#include "boot_tests.h"
|
||||
#include "drivers/button.h"
|
||||
#include "drivers/dbgserial.h"
|
||||
#include "drivers/display.h"
|
||||
#include "drivers/flash.h"
|
||||
#include "drivers/gpio.h"
|
||||
#include "drivers/i2c.h"
|
||||
#include "drivers/periph_config.h"
|
||||
#include "drivers/pmic.h"
|
||||
#include "drivers/watchdog.h"
|
||||
#include "firmware.h"
|
||||
#include "fw_copy.h"
|
||||
#include "pebble_errors.h"
|
||||
#include "stm32f4xx.h"
|
||||
#include "system/bootbits.h"
|
||||
#include "system/logging.h"
|
||||
#include "system/passert.h"
|
||||
#include "system/reset.h"
|
||||
#include "util/delay.h"
|
||||
|
||||
|
||||
static const uint8_t SELECT_BUTTON_MASK = 0x4;
|
||||
|
||||
static void prv_get_fw_reset_vector(void **reset_handler,
|
||||
void **initial_stack_pointer) {
|
||||
uintptr_t** fw_vector_table = (uintptr_t**) FIRMWARE_BASE; // Defined in wscript
|
||||
*initial_stack_pointer = (void *)fw_vector_table[0];
|
||||
*reset_handler = (void *)fw_vector_table[1];
|
||||
}
|
||||
|
||||
static void __attribute__((noreturn)) jump_to_fw(void) {
|
||||
void *initial_stack_pointer, *reset_handler;
|
||||
prv_get_fw_reset_vector(&reset_handler, &initial_stack_pointer);
|
||||
|
||||
dbgserial_print("Booting firmware @ ");
|
||||
dbgserial_print_hex((uintptr_t)reset_handler);
|
||||
dbgserial_print("...\r\n\r\n");
|
||||
|
||||
// Disable all interrupts, just in case.
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
// Interrupt Clear-Enable Register
|
||||
NVIC->ICER[i] = 0xFFFFFFFF;
|
||||
// Interrupt Clear-Pending Register
|
||||
NVIC->ICPR[i] = 0xFFFFFFFF;
|
||||
}
|
||||
|
||||
// Disable all of the peripheral clocks we may have on, but don't touch any of
|
||||
// the reserved bits. The reset values in the stm32f412 reference manual are
|
||||
// not to be trusted, so make sure those bits never change.
|
||||
RCC->AHB1ENR &= ~(0x006010ff);
|
||||
RCC->AHB2ENR &= ~(0x000000c0);
|
||||
RCC->AHB3ENR &= ~(0x00000003);
|
||||
RCC->APB1ENR &= ~(0x17e6c9ff);
|
||||
RCC->APB2ENR &= ~(0x01177933);
|
||||
|
||||
// Reset most peripherals used by the bootloader. We want to minimize the
|
||||
// chances that the firmware unintentionally relies on some state that the
|
||||
// bootloader leaves behind. This includes disabling the PLL.
|
||||
// GPIOs are not reset here: resetting them would change their output values,
|
||||
// which could unintentionally turn of e.g. PMIC power rails.
|
||||
// The backup domain is not reset; that would be foolish.
|
||||
const uint32_t ahb1_periphs =
|
||||
RCC_AHB1Periph_CRC | RCC_AHB1Periph_DMA1 | RCC_AHB1Periph_DMA2
|
||||
| RCC_AHB1Periph_DMA2D | RCC_AHB1Periph_ETH_MAC | RCC_AHB1Periph_OTG_HS;
|
||||
const uint32_t ahb2_periphs =
|
||||
RCC_AHB2Periph_DCMI | RCC_AHB2Periph_CRYP | RCC_AHB2Periph_HASH
|
||||
| RCC_AHB2Periph_RNG | RCC_AHB2Periph_OTG_FS;
|
||||
const uint32_t ahb3_periphs = RCC_AHB3Periph_FSMC;
|
||||
const uint32_t apb1_periphs =
|
||||
RCC_APB1Periph_TIM2 | RCC_APB1Periph_TIM3 | RCC_APB1Periph_TIM4
|
||||
| RCC_APB1Periph_TIM5 | RCC_APB1Periph_TIM6 | RCC_APB1Periph_TIM7
|
||||
| RCC_APB1Periph_TIM12 | RCC_APB1Periph_TIM13 | RCC_APB1Periph_TIM14
|
||||
| RCC_APB1Periph_WWDG | RCC_APB1Periph_SPI2 | RCC_APB1Periph_SPI3
|
||||
| RCC_APB1Periph_USART2 | RCC_APB1Periph_USART3 | RCC_APB1Periph_UART4
|
||||
| RCC_APB1Periph_UART5 | RCC_APB1Periph_I2C1 | RCC_APB1Periph_I2C2
|
||||
| RCC_APB1Periph_I2C3 | RCC_APB1Periph_CAN1 | RCC_APB1Periph_CAN2
|
||||
| RCC_APB1Periph_PWR | RCC_APB1Periph_DAC | RCC_APB1Periph_UART7
|
||||
| RCC_APB1Periph_UART8;
|
||||
const uint32_t apb2_periphs =
|
||||
RCC_APB2Periph_TIM1 | RCC_APB2Periph_TIM8 | RCC_APB2Periph_USART1 |
|
||||
RCC_APB2Periph_USART6 | RCC_APB2Periph_ADC | RCC_APB2Periph_ADC1 |
|
||||
RCC_APB2Periph_ADC2 | RCC_APB2Periph_ADC3 | RCC_APB2Periph_SDIO |
|
||||
RCC_APB2Periph_SPI1 | RCC_APB2Periph_SPI4 | RCC_APB2Periph_SYSCFG |
|
||||
RCC_APB2Periph_TIM9 | RCC_APB2Periph_TIM10 | RCC_APB2Periph_TIM11 |
|
||||
RCC_APB2Periph_SPI5 | RCC_APB2Periph_SPI6 | RCC_APB2Periph_SAI1 |
|
||||
RCC_APB2Periph_LTDC;
|
||||
RCC_DeInit();
|
||||
RCC_AHB1PeriphResetCmd(ahb1_periphs, ENABLE);
|
||||
RCC_AHB1PeriphResetCmd(ahb1_periphs, DISABLE);
|
||||
RCC_AHB2PeriphResetCmd(ahb2_periphs, ENABLE);
|
||||
RCC_AHB2PeriphResetCmd(ahb2_periphs, DISABLE);
|
||||
RCC_AHB3PeriphResetCmd(ahb3_periphs, ENABLE);
|
||||
RCC_AHB3PeriphResetCmd(ahb3_periphs, DISABLE);
|
||||
RCC_APB1PeriphResetCmd(apb1_periphs, ENABLE);
|
||||
RCC_APB1PeriphResetCmd(apb1_periphs, DISABLE);
|
||||
RCC_APB2PeriphResetCmd(apb2_periphs, ENABLE);
|
||||
RCC_APB2PeriphResetCmd(apb2_periphs, DISABLE);
|
||||
|
||||
// The Cortex-M user guide states that the reset values for the core registers
|
||||
// are as follows:
|
||||
// R0-R12 = Unknown
|
||||
// MSP = VECTOR_TABLE[0] (main stack pointer)
|
||||
// PSP = Unknown (process stack pointer)
|
||||
// LR = 0xFFFFFFFF
|
||||
// PC = VECTOR_TABLE[1]
|
||||
// PRIMASK = 0x0
|
||||
// FAULTMASK = 0x0
|
||||
// BASEPRI = 0x0
|
||||
// CONTROL = 0x0
|
||||
//
|
||||
// Attempt to put the processor into as close to the reset state as possible
|
||||
// before passing control to the firmware.
|
||||
//
|
||||
// No attempt is made to set CONTROL to zero as it should already be set to
|
||||
// the reset value when this code executes.
|
||||
__asm volatile (
|
||||
"cpsie if\n" // Clear PRIMASK and FAULTMASK
|
||||
"mov lr, 0xFFFFFFFF\n"
|
||||
"mov sp, %[initial_sp]\n"
|
||||
"bx %[reset_handler]\n"
|
||||
: : [initial_sp] "r" (initial_stack_pointer),
|
||||
[reset_handler] "r" (reset_handler)
|
||||
);
|
||||
__builtin_unreachable();
|
||||
}
|
||||
|
||||
static bool check_and_increment_reset_loop_detection_bits(void) {
|
||||
uint8_t counter =
|
||||
(boot_bit_test(BOOT_BIT_RESET_LOOP_DETECT_THREE) << 2) |
|
||||
(boot_bit_test(BOOT_BIT_RESET_LOOP_DETECT_TWO) << 1) |
|
||||
boot_bit_test(BOOT_BIT_RESET_LOOP_DETECT_ONE);
|
||||
|
||||
if (counter == 7) {
|
||||
boot_bit_clear(BOOT_BIT_RESET_LOOP_DETECT_ONE);
|
||||
boot_bit_clear(BOOT_BIT_RESET_LOOP_DETECT_TWO);
|
||||
boot_bit_clear(BOOT_BIT_RESET_LOOP_DETECT_THREE);
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (++counter) {
|
||||
case 1:
|
||||
boot_bit_set(BOOT_BIT_RESET_LOOP_DETECT_ONE);
|
||||
break;
|
||||
case 2:
|
||||
boot_bit_clear(BOOT_BIT_RESET_LOOP_DETECT_ONE);
|
||||
boot_bit_set(BOOT_BIT_RESET_LOOP_DETECT_TWO);
|
||||
break;
|
||||
case 3:
|
||||
boot_bit_set(BOOT_BIT_RESET_LOOP_DETECT_ONE);
|
||||
break;
|
||||
case 4:
|
||||
boot_bit_clear(BOOT_BIT_RESET_LOOP_DETECT_ONE);
|
||||
boot_bit_clear(BOOT_BIT_RESET_LOOP_DETECT_TWO);
|
||||
boot_bit_set(BOOT_BIT_RESET_LOOP_DETECT_THREE);
|
||||
break;
|
||||
case 5:
|
||||
boot_bit_set(BOOT_BIT_RESET_LOOP_DETECT_ONE);
|
||||
break;
|
||||
case 6:
|
||||
boot_bit_clear(BOOT_BIT_RESET_LOOP_DETECT_ONE);
|
||||
boot_bit_set(BOOT_BIT_RESET_LOOP_DETECT_TWO);
|
||||
break;
|
||||
case 7:
|
||||
boot_bit_set(BOOT_BIT_RESET_LOOP_DETECT_ONE);
|
||||
break;
|
||||
default:
|
||||
PBL_CROAK("reset loop boot bits overrun");
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool check_for_recovery_start_failure() {
|
||||
return boot_bit_test(BOOT_BIT_RECOVERY_START_IN_PROGRESS);
|
||||
}
|
||||
|
||||
static bool check_for_fw_start_failure() {
|
||||
// Add more failure conditions here.
|
||||
if (!watchdog_check_reset_flag() && !boot_bit_test(BOOT_BIT_SOFTWARE_FAILURE_OCCURRED)) {
|
||||
// We're good, we're just starting normally.
|
||||
PBL_LOG_VERBOSE("We're good, we're just starting normally.");
|
||||
|
||||
boot_bit_clear(BOOT_BIT_FW_START_FAIL_STRIKE_ONE);
|
||||
boot_bit_clear(BOOT_BIT_FW_START_FAIL_STRIKE_TWO);
|
||||
return false;
|
||||
}
|
||||
|
||||
// We failed to start our firmware successfully!
|
||||
if (watchdog_check_reset_flag()) {
|
||||
dbgserial_putstr("Watchdog caused a reset");
|
||||
}
|
||||
if (boot_bit_test(BOOT_BIT_SOFTWARE_FAILURE_OCCURRED)) {
|
||||
dbgserial_putstr("Software failure caused a reset");
|
||||
}
|
||||
|
||||
// Clean up after the last failure.
|
||||
boot_bit_clear(BOOT_BIT_SOFTWARE_FAILURE_OCCURRED);
|
||||
|
||||
// We have a "three strikes" algorithm: if the watch fails three times, return true
|
||||
// to tell the parent we should load the recovery firmware. A reset for any other reason
|
||||
// will reset this algorithm.
|
||||
if (boot_bit_test(BOOT_BIT_FW_START_FAIL_STRIKE_TWO)) {
|
||||
// Yikes, our firmware is screwed. Boot into recovery mode.
|
||||
dbgserial_putstr("Failed to start firmware, strike three.");
|
||||
boot_bit_clear(BOOT_BIT_FW_START_FAIL_STRIKE_ONE);
|
||||
boot_bit_clear(BOOT_BIT_FW_START_FAIL_STRIKE_TWO);
|
||||
return true;
|
||||
} else if (boot_bit_test(BOOT_BIT_FW_START_FAIL_STRIKE_ONE)) {
|
||||
dbgserial_putstr("Failed to start firmware, strike two.");
|
||||
boot_bit_set(BOOT_BIT_FW_START_FAIL_STRIKE_TWO);
|
||||
} else {
|
||||
dbgserial_putstr("Failed to start firmware, strike one.");
|
||||
boot_bit_set(BOOT_BIT_FW_START_FAIL_STRIKE_ONE);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool prv_prf_button_combination_is_pressed(void) {
|
||||
return (button_is_pressed(BUTTON_ID_UP) && button_is_pressed(BUTTON_ID_BACK)
|
||||
&& button_is_pressed(BUTTON_ID_SELECT) && !button_is_pressed(BUTTON_ID_DOWN));
|
||||
}
|
||||
|
||||
static bool check_force_boot_recovery(void) {
|
||||
if (boot_bit_test(BOOT_BIT_FORCE_PRF)) {
|
||||
boot_bit_clear(BOOT_BIT_FORCE_PRF);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (prv_prf_button_combination_is_pressed()) {
|
||||
dbgserial_putstr("Hold down UP + BACK + SELECT for 5 secs. to force-boot PRF");
|
||||
for (int i = 0; i < 5000; ++i) {
|
||||
if (!prv_prf_button_combination_is_pressed()) {
|
||||
// stop waiting if not held down any longer
|
||||
return false;
|
||||
}
|
||||
delay_ms(1);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void *reset_vector, *initial_sp;
|
||||
prv_get_fw_reset_vector(&reset_vector, &initial_sp);
|
||||
if ((uintptr_t)reset_vector == 0xffffffff ||
|
||||
(uintptr_t)initial_sp == 0xffffffff) {
|
||||
dbgserial_putstr("Firmware is erased");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void sad_watch(uint32_t error_code) {
|
||||
dbgserial_putstr("SAD WATCH");
|
||||
|
||||
char error_code_buffer[12];
|
||||
itoa_hex(error_code, error_code_buffer, sizeof(error_code_buffer));
|
||||
dbgserial_putstr(error_code_buffer);
|
||||
|
||||
display_error_code(error_code);
|
||||
|
||||
static uint8_t prev_button_state = 0;
|
||||
prev_button_state = button_get_state_bits() & ~SELECT_BUTTON_MASK;
|
||||
while (1) {
|
||||
// See if we should restart
|
||||
uint8_t button_state = button_get_state_bits() & ~SELECT_BUTTON_MASK;
|
||||
if (button_state != prev_button_state) {
|
||||
system_reset();
|
||||
}
|
||||
delay_ms(10);
|
||||
}
|
||||
}
|
||||
|
||||
static void check_and_handle_resuming_from_standby(void) {
|
||||
periph_config_enable(RCC_APB1PeriphClockCmd, RCC_APB1Periph_PWR);
|
||||
if (PWR_GetFlagStatus(PWR_FLAG_SB) == SET) {
|
||||
// We just woke up from standby. For some reason this leaves the system in a funny state,
|
||||
// so clear the flag and reboot again to really clear things up.
|
||||
|
||||
PWR_ClearFlag(PWR_FLAG_SB);
|
||||
dbgserial_putstr("exit standby");
|
||||
system_hard_reset();
|
||||
}
|
||||
periph_config_disable(RCC_APB1PeriphClockCmd, RCC_APB1Periph_PWR);
|
||||
}
|
||||
|
||||
void boot_main(void) {
|
||||
gpio_enable_all();
|
||||
|
||||
check_and_handle_resuming_from_standby();
|
||||
|
||||
dbgserial_init();
|
||||
|
||||
dbgserial_putstr("");
|
||||
dbgserial_putstr(" ___ _ _ _ ");
|
||||
dbgserial_putstr("/ __><_>| || |__");
|
||||
dbgserial_putstr("\\__ \\| || || / /");
|
||||
dbgserial_putstr("<___/|_||_||_\\_\\");
|
||||
dbgserial_putstr("");
|
||||
|
||||
|
||||
// PMIC requires I2C
|
||||
i2c_init();
|
||||
|
||||
dbgserial_putstr("i2c inited");
|
||||
|
||||
pmic_init();
|
||||
|
||||
dbgserial_putstr("pmic inited");
|
||||
|
||||
boot_bit_init();
|
||||
|
||||
dbgserial_putstr("boot bit");
|
||||
|
||||
boot_version_write();
|
||||
|
||||
// Write the bootloader version to serial-out
|
||||
{
|
||||
char bootloader_version_str[12];
|
||||
memset(bootloader_version_str, 0, 12);
|
||||
itoa_hex(boot_version_read(), bootloader_version_str, 12);
|
||||
dbgserial_putstr(bootloader_version_str);
|
||||
}
|
||||
dbgserial_putstr("");
|
||||
dbgserial_putstr("");
|
||||
|
||||
if (boot_bit_test(BOOT_BIT_FW_STABLE)) {
|
||||
dbgserial_putstr("Last firmware boot was stable; clear strikes");
|
||||
|
||||
boot_bit_clear(BOOT_BIT_FW_STABLE);
|
||||
|
||||
boot_bit_clear(BOOT_BIT_FW_START_FAIL_STRIKE_ONE);
|
||||
boot_bit_clear(BOOT_BIT_FW_START_FAIL_STRIKE_TWO);
|
||||
boot_bit_clear(BOOT_BIT_RECOVERY_LOAD_FAIL_STRIKE_ONE);
|
||||
boot_bit_clear(BOOT_BIT_RECOVERY_LOAD_FAIL_STRIKE_TWO);
|
||||
}
|
||||
|
||||
if (pmic_boot_due_to_charger_disconnect()) {
|
||||
dbgserial_putstr("PMIC woke from standby due to charger disconnect");
|
||||
dbgserial_putstr("Putting PMIC back into standby");
|
||||
pmic_power_off();
|
||||
}
|
||||
|
||||
flash_init();
|
||||
button_init();
|
||||
display_init();
|
||||
display_boot_splash();
|
||||
|
||||
#ifdef DISPLAY_DEMO_LOOP
|
||||
while (1) {
|
||||
for (int i=0; i < 92; ++i) {
|
||||
display_firmware_update_progress(i, 91);
|
||||
delay_us(80000);
|
||||
}
|
||||
|
||||
for (uint32_t i=0; i <= 0xf; ++i) {
|
||||
display_error_code(i * 0x11111111);
|
||||
delay_us(200000);
|
||||
}
|
||||
for (uint32_t i=0; i < 8; ++i) {
|
||||
for (uint32_t j=1; j<=0xf; ++j) {
|
||||
display_error_code(j << (i*4));
|
||||
delay_us(200000);
|
||||
}
|
||||
}
|
||||
display_error_code(0x01234567);
|
||||
delay_us(200000);
|
||||
display_error_code(0x89abcdef);
|
||||
delay_us(200000);
|
||||
display_error_code(0xcafebabe);
|
||||
delay_us(200000);
|
||||
display_error_code(0xfeedface);
|
||||
delay_us(200000);
|
||||
display_error_code(0x8badf00d);
|
||||
delay_us(200000);
|
||||
display_error_code(0xbad1ce40);
|
||||
delay_us(200000);
|
||||
display_error_code(0xbeefcace);
|
||||
delay_us(200000);
|
||||
display_error_code(0x0defaced);
|
||||
delay_us(200000);
|
||||
display_error_code(0xd15ea5e5);
|
||||
delay_us(200000);
|
||||
display_error_code(0xdeadbeef);
|
||||
delay_us(200000);
|
||||
display_boot_splash();
|
||||
delay_us(1000000);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (is_button_stuck()) {
|
||||
sad_watch(ERROR_STUCK_BUTTON);
|
||||
}
|
||||
|
||||
if (is_flash_broken()) {
|
||||
sad_watch(ERROR_BAD_SPI_FLASH);
|
||||
}
|
||||
|
||||
boot_bit_dump();
|
||||
|
||||
// If the recovery firmware crashed at start-up, the watch is now a
|
||||
// $150 brick. That's life!
|
||||
if (check_for_recovery_start_failure()) {
|
||||
boot_bit_clear(BOOT_BIT_RECOVERY_START_IN_PROGRESS);
|
||||
sad_watch(ERROR_CANT_LOAD_FW);
|
||||
}
|
||||
|
||||
bool force_boot_recovery_mode = check_force_boot_recovery();
|
||||
if (force_boot_recovery_mode) {
|
||||
dbgserial_putstr("Force-booting recovery mode...");
|
||||
}
|
||||
|
||||
if (force_boot_recovery_mode || check_for_fw_start_failure()) {
|
||||
if (!switch_to_recovery_fw()) {
|
||||
// We've failed to load recovery mode too many times.
|
||||
sad_watch(ERROR_CANT_LOAD_FW);
|
||||
}
|
||||
} else {
|
||||
check_update_fw();
|
||||
}
|
||||
|
||||
if (check_and_increment_reset_loop_detection_bits()) {
|
||||
sad_watch(ERROR_RESET_LOOP);
|
||||
}
|
||||
|
||||
if (boot_bit_test(BOOT_BIT_SHUTDOWN_REQUESTED)) {
|
||||
boot_bit_clear(BOOT_BIT_SHUTDOWN_REQUESTED);
|
||||
dbgserial_putstr("Shutdown requested.");
|
||||
// A full shutdown has been requested instead of booting.
|
||||
// This needs to be done prior to watchdog init: we may need to spin on the charger status.
|
||||
// It is OK to spin indefinitely here. We are connected to a charger and won't die.
|
||||
|
||||
int i = 0;
|
||||
while (pmic_is_usb_connected()) {
|
||||
if (i == 4) {
|
||||
dbgserial_putstr("Remove charger to shutdown");
|
||||
i = 0;
|
||||
}
|
||||
delay_ms(500);
|
||||
++i;
|
||||
}
|
||||
|
||||
dbgserial_putstr("Shutting down.");
|
||||
pmic_full_power_off();
|
||||
}
|
||||
|
||||
watchdog_init();
|
||||
#ifndef NO_WATCHDOG
|
||||
watchdog_start();
|
||||
#endif
|
||||
|
||||
gpio_disable_all();
|
||||
jump_to_fw();
|
||||
}
|
34
platform/silk/boot/src/pebble_errors.h
Normal file
34
platform/silk/boot/src/pebble_errors.h
Normal file
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
static const uint32_t ERROR_NO_ACTIVE_ERROR = 0;
|
||||
|
||||
// FW Errors
|
||||
static const uint32_t ERROR_STUCK_BUTTON = 0xfe504501;
|
||||
static const uint32_t ERROR_BAD_SPI_FLASH = 0xfe504502;
|
||||
static const uint32_t ERROR_CANT_LOAD_FW = 0xfe504503;
|
||||
static const uint32_t ERROR_RESET_LOOP = 0xfe504504;
|
||||
|
||||
// BT Errors
|
||||
static const uint32_t ERROR_CANT_LOAD_BT = 0xfe504510;
|
||||
static const uint32_t ERROR_CANT_START_LSE = 0xfe504511;
|
||||
static const uint32_t ERROR_CANT_START_ACCEL = 0xfe504512;
|
||||
|
||||
static const uint32_t ERROR_LOW_BATTERY = 0xfe504520;
|
180
platform/silk/boot/src/stm32f_flash_boot.ld.in
Normal file
180
platform/silk/boot/src/stm32f_flash_boot.ld.in
Normal file
|
@ -0,0 +1,180 @@
|
|||
/*
|
||||
Linker script for STM32F2xx_1024K_128K
|
||||
*/
|
||||
|
||||
/* include the common STM32F2xx sub-script */
|
||||
|
||||
/* Common part of the linker scripts for STM32 devices*/
|
||||
|
||||
/* default stack sizes.
|
||||
These are used by the startup in order to allocate stacks for the different modes.
|
||||
*/
|
||||
|
||||
__Stack_Size = 8192;
|
||||
|
||||
PROVIDE ( _Stack_Size = __Stack_Size ) ;
|
||||
|
||||
__Stack_Init = _estack - __Stack_Size ;
|
||||
|
||||
/*"PROVIDE" allows to easily override these values from an object file or the commmand line.*/
|
||||
PROVIDE ( _Stack_Init = __Stack_Init ) ;
|
||||
|
||||
/*
|
||||
There will be a link error if there is not this amount of RAM free at the end.
|
||||
*/
|
||||
_Minimum_Stack_Size = 0x100 ;
|
||||
|
||||
/* include the memory spaces definitions sub-script */
|
||||
/*
|
||||
Linker subscript for STM32F2xx definitions with 1024K Flash and 1024K External SRAM */
|
||||
|
||||
/* Memory Spaces Definitions */
|
||||
|
||||
MEMORY
|
||||
{
|
||||
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 256K
|
||||
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = @BOOTLOADER_LENGTH@
|
||||
}
|
||||
|
||||
__end_heap = ORIGIN(RAM) + LENGTH(RAM);
|
||||
PROVIDE(_heap_end = __end_heap);
|
||||
|
||||
/* include the sections management sub-script for FLASH mode */
|
||||
|
||||
/* Sections Definitions */
|
||||
|
||||
SECTIONS
|
||||
{
|
||||
/* for Cortex devices, the beginning of the startup code is stored in the .isr_vector section, which goes to FLASH */
|
||||
.isr_vector :
|
||||
{
|
||||
. = ALIGN(4);
|
||||
KEEP(*(.isr_vector)) /* Startup code */
|
||||
. = ALIGN(4);
|
||||
} >FLASH
|
||||
|
||||
/* for some STRx devices, the beginning of the startup code is stored in the .flashtext section, which goes to FLASH */
|
||||
.flashtext :
|
||||
{
|
||||
. = ALIGN(4);
|
||||
*(.flashtext) /* Startup code */
|
||||
. = ALIGN(4);
|
||||
} >FLASH
|
||||
|
||||
/* Exception handling sections. "contains index entries for section unwinding" */
|
||||
.ARM.exidx :
|
||||
{
|
||||
. = ALIGN(4);
|
||||
*(.ARM.exidx)
|
||||
. = ALIGN(4);
|
||||
} >FLASH
|
||||
|
||||
/* the program code is stored in the .text section, which goes to Flash */
|
||||
.text :
|
||||
{
|
||||
. = ALIGN(4);
|
||||
|
||||
*(.text) /* remaining code */
|
||||
*(.text.*) /* remaining code */
|
||||
*(.rodata) /* read-only data (constants) */
|
||||
*(.rodata*)
|
||||
*(.constdata) /* read-only data (constants) */
|
||||
*(.constdata*)
|
||||
*(.glue_7)
|
||||
*(.glue_7t)
|
||||
*(i.*)
|
||||
|
||||
. = ALIGN(4);
|
||||
_etext = .;
|
||||
_sidata = _etext;
|
||||
} >FLASH
|
||||
|
||||
/* This is the initialized data section
|
||||
The program executes knowing that the data is in the RAM
|
||||
but the loader puts the initial values in the FLASH (inidata).
|
||||
It is one task of the startup to copy the initial values from FLASH to RAM. */
|
||||
.data : AT ( _sidata )
|
||||
{
|
||||
. = ALIGN(4);
|
||||
/* This is used by the startup in order to initialize the .data secion */
|
||||
_sdata = .;
|
||||
|
||||
*(.data)
|
||||
*(.data.*)
|
||||
|
||||
. = ALIGN(4);
|
||||
_edata = .; /* This is used by the startup in order to initialize the .data secion */
|
||||
} >RAM
|
||||
|
||||
/* This is the uninitialized data section */
|
||||
.bss :
|
||||
{
|
||||
. = ALIGN(4);
|
||||
_sbss = .; /* This is used by the startup in order to initialize the .bss secion */
|
||||
|
||||
*(.bss)
|
||||
*(.bss.*)
|
||||
*(COMMON)
|
||||
|
||||
. = ALIGN(4);
|
||||
_ebss = .; /* This is used by the startup in order to initialize the .bss secion */
|
||||
} >RAM
|
||||
|
||||
.stack :
|
||||
{
|
||||
. = ALIGN(8);
|
||||
_sstack = .;
|
||||
. = . + __Stack_Size;
|
||||
. = ALIGN(8);
|
||||
_endstack = .;
|
||||
} >RAM
|
||||
|
||||
PROVIDE ( _estack = _endstack);
|
||||
PROVIDE ( end = _endstack );
|
||||
PROVIDE ( _end = _endstack );
|
||||
PROVIDE ( _heap_start = _endstack );
|
||||
|
||||
/* after that it's only debugging information. */
|
||||
|
||||
/* remove the debugging information from the standard libraries */
|
||||
DISCARD :
|
||||
{
|
||||
libc.a ( * )
|
||||
libm.a ( * )
|
||||
libgcc.a ( * )
|
||||
}
|
||||
|
||||
/* Stabs debugging sections. */
|
||||
.stab 0 : { *(.stab) }
|
||||
.stabstr 0 : { *(.stabstr) }
|
||||
.stab.excl 0 : { *(.stab.excl) }
|
||||
.stab.exclstr 0 : { *(.stab.exclstr) }
|
||||
.stab.index 0 : { *(.stab.index) }
|
||||
.stab.indexstr 0 : { *(.stab.indexstr) }
|
||||
.comment 0 : { *(.comment) }
|
||||
/* DWARF debug sections.
|
||||
Symbols in the DWARF debugging sections are relative to the beginning
|
||||
of the section so we begin them at 0. */
|
||||
/* DWARF 1 */
|
||||
.debug 0 : { *(.debug) }
|
||||
.line 0 : { *(.line) }
|
||||
/* GNU DWARF 1 extensions */
|
||||
.debug_srcinfo 0 : { *(.debug_srcinfo) }
|
||||
.debug_sfnames 0 : { *(.debug_sfnames) }
|
||||
/* DWARF 1.1 and DWARF 2 */
|
||||
.debug_aranges 0 : { *(.debug_aranges) }
|
||||
.debug_pubnames 0 : { *(.debug_pubnames) }
|
||||
/* DWARF 2 */
|
||||
.debug_info 0 : { *(.debug_info .gnu.linkonce.wi.*) }
|
||||
.debug_abbrev 0 : { *(.debug_abbrev) }
|
||||
.debug_line 0 : { *(.debug_line) }
|
||||
.debug_frame 0 : { *(.debug_frame) }
|
||||
.debug_str 0 : { *(.debug_str) }
|
||||
.debug_loc 0 : { *(.debug_loc) }
|
||||
.debug_macinfo 0 : { *(.debug_macinfo) }
|
||||
/* SGI/MIPS DWARF 2 extensions */
|
||||
.debug_weaknames 0 : { *(.debug_weaknames) }
|
||||
.debug_funcnames 0 : { *(.debug_funcnames) }
|
||||
.debug_typenames 0 : { *(.debug_typenames) }
|
||||
.debug_varnames 0 : { *(.debug_varnames) }
|
||||
}
|
72
platform/silk/boot/src/system/bootbits.c
Normal file
72
platform/silk/boot/src/system/bootbits.c
Normal file
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "drivers/dbgserial.h"
|
||||
#include "system/bootbits.h"
|
||||
#include "system/rtc_registers.h"
|
||||
|
||||
#include "git_version.auto.h"
|
||||
|
||||
#include "stm32f4xx.h"
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdint.h>
|
||||
|
||||
static const uint32_t s_bootloader_timestamp = GIT_TIMESTAMP;
|
||||
|
||||
void boot_bit_init(void) {
|
||||
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);
|
||||
PWR_BackupAccessCmd(ENABLE);
|
||||
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, DISABLE);
|
||||
|
||||
if (!boot_bit_test(BOOT_BIT_INITIALIZED)) {
|
||||
RTC_WriteBackupRegister(RTC_BKP_BOOTBIT_DR, BOOT_BIT_INITIALIZED);
|
||||
}
|
||||
}
|
||||
|
||||
void boot_bit_set(BootBitValue bit) {
|
||||
uint32_t current_value = RTC_ReadBackupRegister(RTC_BKP_BOOTBIT_DR);
|
||||
current_value |= bit;
|
||||
RTC_WriteBackupRegister(RTC_BKP_BOOTBIT_DR, current_value);
|
||||
}
|
||||
|
||||
void boot_bit_clear(BootBitValue bit) {
|
||||
uint32_t current_value = RTC_ReadBackupRegister(RTC_BKP_BOOTBIT_DR);
|
||||
current_value &= ~bit;
|
||||
RTC_WriteBackupRegister(RTC_BKP_BOOTBIT_DR, current_value);
|
||||
}
|
||||
|
||||
bool boot_bit_test(BootBitValue bit) {
|
||||
uint32_t current_value = RTC_ReadBackupRegister(RTC_BKP_BOOTBIT_DR);
|
||||
return (current_value & bit);
|
||||
}
|
||||
|
||||
void boot_bit_dump(void) {
|
||||
dbgserial_print("Boot bits: ");
|
||||
dbgserial_print_hex(RTC_ReadBackupRegister(RTC_BKP_BOOTBIT_DR));
|
||||
dbgserial_newline();
|
||||
}
|
||||
|
||||
void boot_version_write(void) {
|
||||
if (boot_version_read() == s_bootloader_timestamp) {
|
||||
return;
|
||||
}
|
||||
RTC_WriteBackupRegister(BOOTLOADER_VERSION_REGISTER, s_bootloader_timestamp);
|
||||
}
|
||||
|
||||
uint32_t boot_version_read(void) {
|
||||
return RTC_ReadBackupRegister(BOOTLOADER_VERSION_REGISTER);
|
||||
}
|
54
platform/silk/boot/src/system/bootbits.h
Normal file
54
platform/silk/boot/src/system/bootbits.h
Normal file
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef enum BootBitValue {
|
||||
BOOT_BIT_INITIALIZED = 0x1 << 0,
|
||||
BOOT_BIT_NEW_FW_AVAILABLE = 0x1 << 1,
|
||||
BOOT_BIT_NEW_FW_UPDATE_IN_PROGRESS = 0x1 << 2,
|
||||
BOOT_BIT_FW_START_FAIL_STRIKE_ONE = 0x1 << 3,
|
||||
BOOT_BIT_FW_START_FAIL_STRIKE_TWO = 0x1 << 4,
|
||||
BOOT_BIT_RECOVERY_LOAD_FAIL_STRIKE_ONE = 0x1 << 5,
|
||||
BOOT_BIT_RECOVERY_LOAD_FAIL_STRIKE_TWO = 0x1 << 6,
|
||||
BOOT_BIT_RECOVERY_START_IN_PROGRESS = 0x1 << 7,
|
||||
BOOT_BIT_STANDBY_MODE_REQUESTED = 0x1 << 8, //!< 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);
|
34
platform/silk/boot/src/system/die.c
Normal file
34
platform/silk/boot/src/system/die.c
Normal file
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "drivers/dbgserial.h"
|
||||
#include "system/reset.h"
|
||||
#include "system/passert.h"
|
||||
|
||||
#include "misc.h"
|
||||
|
||||
void reset_due_to_software_failure(void) {
|
||||
#if defined(NO_WATCHDOG)
|
||||
// Don't reset right away, leave it in a state we can inspect
|
||||
|
||||
while (1) {
|
||||
BREAKPOINT;
|
||||
}
|
||||
#endif
|
||||
|
||||
dbgserial_putstr("Software failure; resetting!");
|
||||
system_reset();
|
||||
}
|
21
platform/silk/boot/src/system/die.h
Normal file
21
platform/silk/boot/src/system/die.h
Normal file
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
//! Does not call reboot_reason_set, only calls reboot_reason_set_restarted_safely if we were
|
||||
//! able shut everything down nicely before rebooting.
|
||||
void reset_due_to_software_failure(void) __attribute__((noreturn));
|
30
platform/silk/boot/src/system/firmware_storage.c
Normal file
30
platform/silk/boot/src/system/firmware_storage.c
Normal file
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "firmware_storage.h"
|
||||
|
||||
#include "drivers/flash.h"
|
||||
|
||||
FirmwareDescription firmware_storage_read_firmware_description(uint32_t firmware_start_address) {
|
||||
FirmwareDescription firmware_description;
|
||||
flash_read_bytes((uint8_t*)&firmware_description, firmware_start_address,
|
||||
sizeof(FirmwareDescription));
|
||||
return firmware_description;
|
||||
}
|
||||
|
||||
bool firmware_storage_check_valid_firmware_description(FirmwareDescription* desc) {
|
||||
return desc->description_length == sizeof(FirmwareDescription);
|
||||
}
|
33
platform/silk/boot/src/system/firmware_storage.h
Normal file
33
platform/silk/boot/src/system/firmware_storage.h
Normal file
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
//! @file firmware_storage.h
|
||||
//! Utilities for reading a firmware image stored in flash.
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
typedef struct __attribute__((__packed__)) FirmwareDescription {
|
||||
uint32_t description_length;
|
||||
uint32_t firmware_length;
|
||||
uint32_t checksum;
|
||||
} FirmwareDescription;
|
||||
|
||||
FirmwareDescription firmware_storage_read_firmware_description(uint32_t firmware_start_address);
|
||||
|
||||
bool firmware_storage_check_valid_firmware_description(FirmwareDescription* desc);
|
66
platform/silk/boot/src/system/logging.h
Normal file
66
platform/silk/boot/src/system/logging.h
Normal file
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "drivers/dbgserial.h"
|
||||
#include "system/die.h"
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifndef __FILE_NAME__
|
||||
#define __FILE_NAME__ __FILE__
|
||||
#endif
|
||||
|
||||
#define LOG_LEVEL_ALWAYS 0
|
||||
#define LOG_LEVEL_ERROR 1
|
||||
#define LOG_LEVEL_WARNING 50
|
||||
#define LOG_LEVEL_INFO 100
|
||||
#define LOG_LEVEL_DEBUG 200
|
||||
#define LOG_LEVEL_DEBUG_VERBOSE 255
|
||||
|
||||
#ifndef STRINGIFY
|
||||
#define STRINGIFY_NX(a) #a
|
||||
#define STRINGIFY(a) STRINGIFY_NX(a)
|
||||
#endif // STRINGIFY
|
||||
|
||||
#define STATUS_STRING(s) STRINGIFY(s)
|
||||
|
||||
#ifdef PBL_LOG_ENABLED
|
||||
#define PBL_LOG(level, fmt, args...) \
|
||||
do { \
|
||||
char _pbl_log_buffer[128]; \
|
||||
dbgserial_putstr_fmt(_pbl_log_buffer, sizeof(_pbl_log_buffer), \
|
||||
__FILE_NAME__ ":" STRINGIFY(__LINE__) "> " fmt, ## args); \
|
||||
} while (0)
|
||||
|
||||
#ifdef VERBOSE_LOGGING
|
||||
|
||||
#define PBL_LOG_VERBOSE(fmt, args...) \
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, fmt, ## args)
|
||||
|
||||
#else // VERBOSE_LOGGING
|
||||
#define PBL_LOG_VERBOSE(fmt, args...)
|
||||
#endif // VERBOSE_LOGGING
|
||||
|
||||
#else // PBL_LOG_ENABLED
|
||||
#define PBL_LOG(level, fmt, args...)
|
||||
#define PBL_LOG_VERBOSE(fmt, args...)
|
||||
#endif // PBL_LOG_ENABLED
|
105
platform/silk/boot/src/system/passert.c
Normal file
105
platform/silk/boot/src/system/passert.c
Normal file
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "passert.h"
|
||||
|
||||
#include "system/die.h"
|
||||
|
||||
#include "drivers/dbgserial.h"
|
||||
#include "util/misc.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
static __attribute__((noreturn)) void handle_passert_failed_vargs(const char* filename, int line_number,
|
||||
uintptr_t lr, const char* expr, const char* fmt, va_list fmt_args) {
|
||||
dbgserial_print("ASSERT: ");
|
||||
dbgserial_print(expr);
|
||||
dbgserial_print(" ");
|
||||
dbgserial_print(filename);
|
||||
dbgserial_print(":");
|
||||
dbgserial_print_hex(line_number);
|
||||
if (fmt) {
|
||||
dbgserial_print(" ");
|
||||
dbgserial_print(fmt);
|
||||
}
|
||||
dbgserial_putstr("");
|
||||
|
||||
reset_due_to_software_failure();
|
||||
}
|
||||
|
||||
static __attribute__((noreturn)) void handle_passert_failed(const char* filename, int line_number,
|
||||
uintptr_t lr, const char *expr, const char* fmt, ...) {
|
||||
va_list fmt_args;
|
||||
va_start(fmt_args, fmt);
|
||||
|
||||
handle_passert_failed_vargs(filename, line_number, lr, expr, fmt, fmt_args);
|
||||
|
||||
va_end(fmt_args);
|
||||
}
|
||||
|
||||
void passert_failed(const char* filename, int line_number, const char* message, ...) {
|
||||
va_list fmt_args;
|
||||
va_start(fmt_args, message);
|
||||
|
||||
handle_passert_failed_vargs(filename, line_number,
|
||||
(uintptr_t)__builtin_return_address(0), "ASSERT", message, fmt_args);
|
||||
|
||||
va_end(fmt_args);
|
||||
}
|
||||
|
||||
void passert_failed_no_message(const char* filename, int line_number) {
|
||||
handle_passert_failed(filename, line_number,
|
||||
(uintptr_t)__builtin_return_address(0), "ASSERTN", NULL);
|
||||
}
|
||||
|
||||
void wtf(void) {
|
||||
uintptr_t saved_lr = (uintptr_t) __builtin_return_address(0);
|
||||
dbgserial_print("*** WTF ");
|
||||
dbgserial_print_hex(saved_lr);
|
||||
dbgserial_putstr("");
|
||||
reset_due_to_software_failure();
|
||||
}
|
||||
|
||||
//! Assert function called by the STM peripheral library's
|
||||
//! 'assert_param' method. See stm32f2xx_conf.h for more information.
|
||||
void assert_failed(uint8_t* file, uint32_t line) {
|
||||
register uintptr_t lr __asm("lr");
|
||||
uintptr_t saved_lr = lr;
|
||||
|
||||
handle_passert_failed((const char*) file, line, saved_lr, "STM32", "STM32 peripheral library tripped an assert");
|
||||
}
|
||||
|
||||
extern void command_dump_malloc_kernel(void);
|
||||
|
||||
void croak_oom(const char *filename, int line_number, const char *fmt, ...) {
|
||||
register uintptr_t lr __asm("lr");
|
||||
uintptr_t saved_lr = lr;
|
||||
|
||||
#ifdef MALLOC_INSTRUMENTATION
|
||||
command_dump_malloc_kernel();
|
||||
#endif
|
||||
|
||||
va_list fmt_args;
|
||||
va_start(fmt_args, fmt);
|
||||
|
||||
handle_passert_failed_vargs(filename, line_number, saved_lr, "CROAK OOM", fmt, fmt_args);
|
||||
|
||||
va_end(fmt_args);
|
||||
}
|
53
platform/silk/boot/src/system/passert.h
Normal file
53
platform/silk/boot/src/system/passert.h
Normal 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)
|
36
platform/silk/boot/src/system/reset.c
Normal file
36
platform/silk/boot/src/system/reset.c
Normal file
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "system/reset.h"
|
||||
|
||||
#include "drivers/display.h"
|
||||
|
||||
#include "stm32f4xx.h"
|
||||
|
||||
void system_reset(void) {
|
||||
display_prepare_for_reset();
|
||||
|
||||
// Clear the reset reason since it will no longer
|
||||
// apply after this bootloader reset
|
||||
RCC_ClearFlag();
|
||||
|
||||
system_hard_reset();
|
||||
}
|
||||
|
||||
void system_hard_reset(void) {
|
||||
NVIC_SystemReset();
|
||||
__builtin_unreachable();
|
||||
}
|
26
platform/silk/boot/src/system/reset.h
Normal file
26
platform/silk/boot/src/system/reset.h
Normal file
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#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));
|
31
platform/silk/boot/src/system/rtc_registers.h
Normal file
31
platform/silk/boot/src/system/rtc_registers.h
Normal file
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#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
|
50
platform/silk/boot/src/util/attributes.h
Normal file
50
platform/silk/boot/src/util/attributes.h
Normal file
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#if defined(__clang__)
|
||||
#define GCC_ONLY(x)
|
||||
#else
|
||||
#define GCC_ONLY(x) x
|
||||
#endif
|
||||
|
||||
// Function attributes
|
||||
#define FORMAT_FUNC(TYPE, STR_IDX, FIRST) __attribute__((__format__(TYPE, STR_IDX, FIRST)))
|
||||
|
||||
#define FORMAT_PRINTF(STR_IDX, FIRST) FORMAT_FUNC(__printf__, STR_IDX, FIRST)
|
||||
|
||||
#define ALWAYS_INLINE __attribute__((__always_inline__)) inline
|
||||
#define NOINLINE __attribute__((__noinline__))
|
||||
#define NORETURN __attribute__((__noreturn__)) void
|
||||
#define NAKED_FUNC __attribute__((__naked__))
|
||||
#define OPTIMIZE_FUNC(LVL) __attribute__((__optimize__(LVL)))
|
||||
#define CONST_FUNC __attribute__((__const__))
|
||||
#define PURE_FUNC __attribute__((__pure__))
|
||||
|
||||
// Variable attributes
|
||||
#define ATTR_CLEANUP(FUNC) __attribute__((__cleanup__(FUNC)))
|
||||
|
||||
// Structure attributes
|
||||
#define PACKED __attribute__((__packed__))
|
||||
|
||||
// General attributes
|
||||
#define USED __attribute__((__used__))
|
||||
#define UNUSED __attribute__((__unused__))
|
||||
#define WEAK __attribute__((__weak__))
|
||||
#define ALIAS(sym) __attribute__((__weak__, __alias__(sym)))
|
||||
#define SECTION(SEC) GCC_ONLY(__attribute__((__section__(SEC))))
|
||||
#define EXTERNALLY_VISIBLE GCC_ONLY(__attribute__((__externally_visible__)))
|
51
platform/silk/boot/src/util/cobs.c
Normal file
51
platform/silk/boot/src/util/cobs.c
Normal file
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "cobs.h"
|
||||
|
||||
size_t cobs_encode(void *dst_ptr, const void *src_ptr, size_t length) {
|
||||
const char *src = src_ptr;
|
||||
char *dst = dst_ptr;
|
||||
uint8_t code = 0x01;
|
||||
size_t code_idx = 0;
|
||||
size_t dst_idx = 1;
|
||||
|
||||
for (size_t src_idx = 0; src_idx < length; ++src_idx) {
|
||||
if (src[src_idx] == '\0') {
|
||||
dst[code_idx] = code;
|
||||
code_idx = dst_idx++;
|
||||
code = 0x01;
|
||||
} else {
|
||||
dst[dst_idx++] = src[src_idx];
|
||||
code++;
|
||||
if (code == 0xff) {
|
||||
if (src_idx == length - 1) {
|
||||
// Special case: the final encoded block is 254 bytes long with no
|
||||
// zero after it. While it's technically a valid encoding if a
|
||||
// trailing zero is appended, it causes the output to be one byte
|
||||
// longer than it needs to be. This violates consistent overhead
|
||||
// contract and could overflow a carefully sized buffer.
|
||||
break;
|
||||
}
|
||||
dst[code_idx] = code;
|
||||
code_idx = dst_idx++;
|
||||
code = 0x01;
|
||||
}
|
||||
}
|
||||
}
|
||||
dst[code_idx] = code;
|
||||
return dst_idx;
|
||||
}
|
40
platform/silk/boot/src/util/cobs.h
Normal file
40
platform/silk/boot/src/util/cobs.h
Normal file
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
//! An implementation of Consistent Overhead Byte Stuffing
|
||||
//!
|
||||
//! http://conferences.sigcomm.org/sigcomm/1997/papers/p062.pdf
|
||||
//! http://en.wikipedia.org/wiki/Consistent_Overhead_Byte_Stuffing
|
||||
|
||||
//! Evaluates to the offset required when encoding in-place
|
||||
#define COBS_OVERHEAD(n) (((n) + 253) / 254)
|
||||
//! Evaluates to the maximum buffer size required to hold n bytes of data
|
||||
//! after COBS encoding.
|
||||
#define MAX_SIZE_AFTER_COBS_ENCODING(n) ((n) + COBS_OVERHEAD(n))
|
||||
|
||||
//! COBS-encode a buffer out to another buffer.
|
||||
//!
|
||||
//! @param [out] dst destination buffer. The buffer must be at least
|
||||
//! MAX_SIZE_AFTER_COBS_ENCODING(length) bytes long.
|
||||
//! @param [in] src source buffer
|
||||
//! @param length length of src
|
||||
size_t cobs_encode(void * restrict dst, const void * restrict src,
|
||||
size_t length);
|
48
platform/silk/boot/src/util/crc32.c
Normal file
48
platform/silk/boot/src/util/crc32.c
Normal file
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "util/crc32.h"
|
||||
|
||||
// Nybble-wide table driven CRC-32 algorithm
|
||||
//
|
||||
// A compromise between speed and size, this algorithm uses a lookup table to
|
||||
// calculate the CRC four bits at a time with a size cost of only 64 bytes. By
|
||||
// contrast, a byte-wide algorithm requires a lookup table sixteen times larger!
|
||||
//
|
||||
// The lookup table is generated by the crc32_lut.py
|
||||
|
||||
static const uint32_t s_lookup_table[] = {
|
||||
0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac,
|
||||
0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c,
|
||||
0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c,
|
||||
0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c,
|
||||
};
|
||||
|
||||
uint32_t crc32(uint32_t crc, const void * restrict data, size_t length) {
|
||||
if (data == 0) {
|
||||
return 0;
|
||||
}
|
||||
const uint8_t * restrict bytes = data;
|
||||
|
||||
crc ^= 0xffffffff;
|
||||
while (length--) {
|
||||
crc = (crc >> 4) ^ s_lookup_table[(crc ^ *bytes) & 0xf];
|
||||
crc = (crc >> 4) ^ s_lookup_table[(crc ^ (*bytes >> 4)) & 0xf];
|
||||
bytes++;
|
||||
}
|
||||
crc ^= 0xffffffff;
|
||||
return crc;
|
||||
}
|
67
platform/silk/boot/src/util/crc32.h
Normal file
67
platform/silk/boot/src/util/crc32.h
Normal file
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
//! \file
|
||||
//! Calculate the CRC-32 checksum of data.
|
||||
//!
|
||||
//! The checksum is the standard CRC-32 used by zlib, PNG and others.
|
||||
//! The model parameters for the algorithm, as described in A Painless Guide to
|
||||
//! CRC Error Detection Algorithms (http://www.zlib.net/crc_v3.txt), are:
|
||||
//! Name: "CRC-32"
|
||||
//! Width: 32
|
||||
//! Poly: 04C11DB7
|
||||
//! Init: FFFFFFFF
|
||||
//! RefIn: True
|
||||
//! RefOut: True
|
||||
//! XorOut: FFFFFFFF
|
||||
//! Check: CBF43926
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
//! Update a running CRC-32 checksum with the bytes of data and return the
|
||||
//! updated CRC-32. If data is NULL, the function returns the required initial
|
||||
//! value for the CRC.
|
||||
//!
|
||||
//! This function is drop-in compatible with zlib's crc32 function.
|
||||
//!
|
||||
//! \par Usage
|
||||
//! \code
|
||||
//! uint32_t crc = crc32(0, NULL, 0);
|
||||
//! while (read_buffer(data, length)) {
|
||||
//! crc = crc32(crc, data, length);
|
||||
//! }
|
||||
//! \endcode
|
||||
uint32_t crc32(uint32_t crc, const void * restrict data, size_t length);
|
||||
|
||||
//! The initial CRC register value for a standard CRC-32 checksum.
|
||||
//!
|
||||
//! It is the same value as is returned by the `crc32` function when data is
|
||||
//! NULL.
|
||||
//!
|
||||
//! \code
|
||||
//! assert(CRC32_INIT == crc32(0, NULL, 0));
|
||||
//! \endcode
|
||||
#define CRC32_INIT (0)
|
||||
|
||||
//! The residue constant of the CRC-32 algorithm.
|
||||
//!
|
||||
//! If the CRC-32 value of a message is appended (little-endian) onto the
|
||||
//! end of the message, the CRC-32 of the concatenated message and CRC will be
|
||||
//! equal to CRC32_RESIDUE if the message has not been corrupted in transit.
|
||||
#define CRC32_RESIDUE (0x2144DF1C)
|
49
platform/silk/boot/src/util/delay.c
Normal file
49
platform/silk/boot/src/util/delay.c
Normal file
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "delay.h"
|
||||
#include "stm32f4xx.h"
|
||||
#include "util/attributes.h"
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
// Calculated using the formula from the firmware's delay_init:
|
||||
// ceil( NS_PER_US / ( clock_period * INSTRUCTIONS_PER_LOOP )
|
||||
// where NS_PER_US = 1000 and INSTRUCTIONS_PER_LOOP = 3,
|
||||
// and clock_period = 62.5
|
||||
#define LOOPS_PER_US (6)
|
||||
|
||||
void NOINLINE delay_us(uint32_t us) {
|
||||
uint32_t delay_loops = us * LOOPS_PER_US;
|
||||
|
||||
__asm volatile (
|
||||
"spinloop: \n"
|
||||
" subs %[delay_loops], #1 \n"
|
||||
" bne spinloop \n"
|
||||
: [delay_loops] "+r" (delay_loops) // read-write operand
|
||||
:
|
||||
: "cc"
|
||||
);
|
||||
}
|
||||
|
||||
void delay_ms(uint32_t millis) {
|
||||
// delay_us(millis*1000) is not used because a long delay could easily
|
||||
// overflow the veriable. Without the outer loop, a delay of even five
|
||||
// seconds would overflow.
|
||||
while (millis--) {
|
||||
delay_us(1000);
|
||||
}
|
||||
}
|
28
platform/silk/boot/src/util/delay.h
Normal file
28
platform/silk/boot/src/util/delay.h
Normal file
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
//! Carefully timed spinloop that allows one to delay at a microsecond
|
||||
//! granularity.
|
||||
void delay_us(uint32_t us);
|
||||
|
||||
//! Waits for a certain amount of milliseconds by busy-waiting.
|
||||
//!
|
||||
//! @param millis The number of milliseconds to wait for
|
||||
void delay_ms(uint32_t millis);
|
46
platform/silk/boot/src/util/misc.c
Normal file
46
platform/silk/boot/src/util/misc.c
Normal file
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#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';
|
||||
}
|
34
platform/silk/boot/src/util/misc.h
Normal file
34
platform/silk/boot/src/util/misc.h
Normal file
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#define MIN(a, b) (((a) < (b)) ? (a) : (b))
|
||||
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
|
||||
|
||||
#define MHZ_TO_HZ(hz) (((uint32_t)(hz)) * 1000000)
|
||||
|
||||
#define ARRAY_LENGTH(array) (sizeof((array))/sizeof((array)[0]))
|
||||
|
||||
//! Find the log base two of a number rounded up
|
||||
int ceil_log_two(uint32_t n);
|
||||
|
||||
//! Convert num to a hex string and put in buffer
|
||||
void itoa_hex(uint32_t num, char *buffer, int buffer_length);
|
80
platform/silk/boot/src/util/net.h
Normal file
80
platform/silk/boot/src/util/net.h
Normal file
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// When compiling test, the host OS might have conflicting defines for this:
|
||||
#undef ntohs
|
||||
#undef htons
|
||||
#undef ntohl
|
||||
#undef htonl
|
||||
#undef ltohs
|
||||
#undef ltohl
|
||||
|
||||
static inline uint16_t ntohs(uint16_t v) {
|
||||
// return ((v & 0x00ff) << 8) | ((v & 0xff00) >> 8);
|
||||
return __builtin_bswap16(v);
|
||||
}
|
||||
|
||||
static inline uint16_t htons(uint16_t v) {
|
||||
return ntohs(v);
|
||||
}
|
||||
|
||||
static inline uint32_t ntohl(uint32_t v) {
|
||||
// return ((v & 0x000000ff) << 24) |
|
||||
// ((v & 0x0000ff00) << 8) |
|
||||
// ((v & 0x00ff0000) >> 8) |
|
||||
// ((v & 0xff000000) >> 24);
|
||||
return __builtin_bswap32(v);
|
||||
}
|
||||
|
||||
static inline uint32_t htonl(uint32_t v) {
|
||||
return ntohl(v);
|
||||
}
|
||||
|
||||
#define ltohs(v) (v)
|
||||
#define ltohl(v) (v)
|
||||
|
||||
// Types for values in network byte-order. They are wrapped in structs so that
|
||||
// the compiler will disallow implicit casting of these types to or from
|
||||
// integral types. This way it is a compile error to try using variables of
|
||||
// these types without first performing a byte-order conversion.
|
||||
// There is no overhead for wrapping the values in structs.
|
||||
typedef struct net16 {
|
||||
uint16_t v;
|
||||
} net16;
|
||||
|
||||
typedef struct net32 {
|
||||
uint32_t v;
|
||||
} net32;
|
||||
|
||||
static inline uint16_t ntoh16(net16 net) {
|
||||
return ntohs(net.v);
|
||||
}
|
||||
|
||||
static inline net16 hton16(uint16_t v) {
|
||||
return (net16){ htons(v) };
|
||||
}
|
||||
|
||||
static inline uint32_t ntoh32(net32 net) {
|
||||
return ntohl(net.v);
|
||||
}
|
||||
|
||||
static inline net32 hton32(uint32_t v) {
|
||||
return (net32){ htonl(v) };
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue