mirror of
https://github.com/google/pebble.git
synced 2025-06-03 00:33: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
31
platform/snowy/boot/src/drivers/button.h
Normal file
31
platform/snowy/boot/src/drivers/button.h
Normal file
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "button_id.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
void button_init(void);
|
||||
|
||||
bool button_is_pressed(ButtonId id);
|
||||
uint8_t button_get_state_bits(void);
|
||||
|
||||
void button_interrupt_handler(void* button_id);
|
||||
|
||||
bool button_selftest(void);
|
41
platform/snowy/boot/src/drivers/button_id.h
Normal file
41
platform/snowy/boot/src/drivers/button_id.h
Normal file
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
//! @addtogroup UI
|
||||
//! @{
|
||||
//! @addtogroup Clicks
|
||||
//! \brief Dealing with button input
|
||||
//! @{
|
||||
|
||||
//! Button ID values
|
||||
//! @see \ref click_recognizer_get_button_id()
|
||||
typedef enum {
|
||||
//! Back button
|
||||
BUTTON_ID_BACK = 0,
|
||||
//! Up button
|
||||
BUTTON_ID_UP,
|
||||
//! Select (middle) button
|
||||
BUTTON_ID_SELECT,
|
||||
//! Down button
|
||||
BUTTON_ID_DOWN,
|
||||
//! Total number of buttons
|
||||
NUM_BUTTONS
|
||||
} ButtonId;
|
||||
|
||||
//! @} // end addtogroup Clicks
|
||||
//! @} // end addtogroup UI
|
44
platform/snowy/boot/src/drivers/crc.h
Normal file
44
platform/snowy/boot/src/drivers/crc.h
Normal file
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
void crc_init(void);
|
||||
|
||||
/*
|
||||
* calculate the CRC32 for a stream of bytes.
|
||||
* NOTE: not safe to call from ISR
|
||||
*/
|
||||
uint32_t crc_calculate_bytes(const uint8_t* data, unsigned int data_length);
|
||||
|
||||
/*
|
||||
* calculate the CRC32 for a stream of bytes from flash
|
||||
* NOTE: not safe to call from ISR
|
||||
*/
|
||||
uint32_t crc_calculate_flash(uint32_t address, unsigned int num_bytes);
|
||||
|
||||
/*
|
||||
* calculate a 8-bit CRC of a given byte sequence. Note that this is not using
|
||||
* the standard CRC-8 polynomial, because the standard polynomial isn't very
|
||||
* good.
|
||||
*/
|
||||
uint8_t crc8_calculate_bytes(const uint8_t *data, unsigned int data_length);
|
||||
|
||||
void crc_calculate_incremental_start(void);
|
||||
|
||||
void crc_calculate_incremental_stop(void);
|
38
platform/snowy/boot/src/drivers/dbgserial.h
Normal file
38
platform/snowy/boot/src/drivers/dbgserial.h
Normal file
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
void dbgserial_init(void);
|
||||
|
||||
void dbgserial_putchar(uint8_t c);
|
||||
|
||||
//! Version of dbgserial_putchar that may return before the character is finished writing.
|
||||
//! Use if you don't need a guarantee that your character will be written.
|
||||
void dbgserial_putchar_lazy(uint8_t c);
|
||||
|
||||
void dbgserial_putstr(const char* str);
|
||||
|
||||
//! Like dbgserial_putstr, but without a terminating newline
|
||||
void dbgserial_print(const char* str);
|
||||
|
||||
void dbgserial_print_hex(uint32_t value);
|
||||
|
||||
void dbgserial_putstr_fmt(char* buffer, unsigned int buffer_size, const char* fmt, ...)
|
||||
__attribute__((format(printf, 3, 4)));
|
34
platform/snowy/boot/src/drivers/display.h
Normal file
34
platform/snowy/boot/src/drivers/display.h
Normal file
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
void display_init(void);
|
||||
|
||||
void display_boot_splash(void);
|
||||
|
||||
void display_error_code(uint32_t);
|
||||
|
||||
//! Do whatever is necessary to prevent visual artifacts when resetting
|
||||
//! the watch.
|
||||
void display_prepare_for_reset(void);
|
||||
|
||||
//! Display the progress of a firmware update.
|
||||
//!
|
||||
//! The progress is expressed as a rational number less than or equal to 1.
|
||||
//! When numerator == denominator, the progress indicator shows that the update
|
||||
//! is complete.
|
||||
void display_firmware_update_progress(uint32_t numerator, uint32_t denominator);
|
188
platform/snowy/boot/src/drivers/display/ice40lp.c
Normal file
188
platform/snowy/boot/src/drivers/display/ice40lp.c
Normal file
|
@ -0,0 +1,188 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "ice40lp.h"
|
||||
|
||||
#include "board/board.h"
|
||||
#include "drivers/gpio.h"
|
||||
#include "drivers/spi.h"
|
||||
#include "drivers/pmic.h"
|
||||
#include "system/logging.h"
|
||||
#include "system/passert.h"
|
||||
#include "util/delay.h"
|
||||
|
||||
#include "stm32f4xx.h"
|
||||
#include "misc.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
// We want the SPI clock to run at 16 by default
|
||||
const uint32_t SPI_DEFAULT_MHZ = 16;
|
||||
static uint32_t s_spi_clock_hz;
|
||||
|
||||
bool display_busy(void) {
|
||||
bool busy = GPIO_ReadInputDataBit(DISP_GPIO, DISP_PIN_BUSY);
|
||||
return busy;
|
||||
}
|
||||
|
||||
static void prv_configure_spi(uint32_t spi_clock_hz) {
|
||||
// Set up a SPI bus on SPI6
|
||||
SPI_InitTypeDef spi_cfg;
|
||||
SPI_I2S_DeInit(DISP_SPI);
|
||||
SPI_StructInit(&spi_cfg);
|
||||
spi_cfg.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
|
||||
spi_cfg.SPI_Mode = SPI_Mode_Master;
|
||||
spi_cfg.SPI_DataSize = SPI_DataSize_8b;
|
||||
spi_cfg.SPI_CPOL = SPI_CPOL_High;
|
||||
spi_cfg.SPI_CPHA = SPI_CPHA_2Edge;
|
||||
spi_cfg.SPI_NSS = SPI_NSS_Soft;
|
||||
spi_cfg.SPI_BaudRatePrescaler = spi_find_prescaler(spi_clock_hz, DISPLAY_SPI_CLOCK_PERIPH);
|
||||
spi_cfg.SPI_FirstBit = SPI_FirstBit_MSB;
|
||||
SPI_Init(DISP_SPI, &spi_cfg);
|
||||
|
||||
SPI_Cmd(DISP_SPI, ENABLE);
|
||||
}
|
||||
|
||||
void display_start(void) {
|
||||
// Enable the GPIOG clock; this is required before configuring the pins
|
||||
gpio_use(DISP_GPIO);
|
||||
|
||||
GPIO_PinAFConfig(DISP_GPIO, GPIO_PINSOURCE_SCK, GPIO_AF_SPI6); // SCK
|
||||
GPIO_PinAFConfig(DISP_GPIO, GPIO_PINSOURCE_MOSI, GPIO_AF_SPI6); // MOSI
|
||||
GPIO_PinAFConfig(DISP_GPIO, GPIO_PINSOURCE_MISO, GPIO_AF_SPI6); // MOSI
|
||||
|
||||
GPIO_InitTypeDef gpio_cfg;
|
||||
gpio_cfg.GPIO_OType = GPIO_OType_PP;
|
||||
gpio_cfg.GPIO_PuPd = GPIO_PuPd_NOPULL;
|
||||
gpio_cfg.GPIO_Mode = GPIO_Mode_AF;
|
||||
gpio_cfg.GPIO_Speed = GPIO_Speed_25MHz;
|
||||
gpio_cfg.GPIO_Pin = DISP_PIN_SCLK;
|
||||
GPIO_Init(DISP_GPIO, &gpio_cfg);
|
||||
|
||||
gpio_cfg.GPIO_Pin = DISP_PIN_SI;
|
||||
GPIO_Init(DISP_GPIO, &gpio_cfg);
|
||||
|
||||
gpio_cfg.GPIO_Pin = DISP_PIN_SO;
|
||||
GPIO_Init(DISP_GPIO, &gpio_cfg);
|
||||
|
||||
gpio_cfg.GPIO_Mode = GPIO_Mode_IN;
|
||||
gpio_cfg.GPIO_PuPd = GPIO_PuPd_UP;
|
||||
gpio_cfg.GPIO_Pin = DISP_PIN_CDONE;
|
||||
GPIO_Init(DISP_GPIO, &gpio_cfg);
|
||||
|
||||
gpio_cfg.GPIO_Mode = GPIO_Mode_IN;
|
||||
gpio_cfg.GPIO_PuPd = GPIO_PuPd_NOPULL;
|
||||
gpio_cfg.GPIO_Pin = DISP_PIN_BUSY;
|
||||
GPIO_Init(DISP_GPIO, &gpio_cfg);
|
||||
|
||||
gpio_cfg.GPIO_Mode = GPIO_Mode_OUT;
|
||||
gpio_cfg.GPIO_PuPd = GPIO_PuPd_NOPULL;
|
||||
gpio_cfg.GPIO_Pin = DISP_PIN_SCS;
|
||||
GPIO_Init(DISP_GPIO, &gpio_cfg);
|
||||
|
||||
gpio_cfg.GPIO_OType = GPIO_OType_OD;
|
||||
gpio_cfg.GPIO_PuPd = GPIO_PuPd_NOPULL;
|
||||
gpio_cfg.GPIO_Pin = DISP_PIN_CRESET;
|
||||
GPIO_Init(DISP_GPIO, &gpio_cfg);
|
||||
|
||||
RCC_APB2PeriphClockCmd(DISPLAY_SPI_CLOCK, ENABLE);
|
||||
|
||||
s_spi_clock_hz = MHZ_TO_HZ(SPI_DEFAULT_MHZ);
|
||||
prv_configure_spi(s_spi_clock_hz);
|
||||
}
|
||||
|
||||
bool display_program(const uint8_t *fpga_bitstream, uint32_t bitstream_size) {
|
||||
GPIO_WriteBit(DISP_GPIO, DISP_PIN_SCS, Bit_SET);
|
||||
|
||||
// wait a bit.
|
||||
delay_ms(1);
|
||||
|
||||
GPIO_WriteBit(DISP_GPIO, DISP_PIN_CRESET, Bit_RESET); // CRESET LOW
|
||||
GPIO_WriteBit(DISP_GPIO, DISP_PIN_SCS, Bit_RESET); // SCS LOW
|
||||
|
||||
delay_ms(1);
|
||||
|
||||
GPIO_WriteBit(DISP_GPIO, DISP_PIN_CRESET, Bit_SET); // CRESET -> HIGH
|
||||
|
||||
delay_ms(1);
|
||||
|
||||
PBL_ASSERT(!GPIO_ReadInputDataBit(DISP_GPIO, DISP_PIN_CDONE), "CDONE not low during reset");
|
||||
PBL_ASSERT(GPIO_ReadInputDataBit(DISP_GPIO, DISP_PIN_CRESET), "CRESET not high during reset");
|
||||
|
||||
// Program the FPGA
|
||||
for (unsigned int i = 0; i < bitstream_size; ++i) {
|
||||
display_write_byte(fpga_bitstream[i]);
|
||||
}
|
||||
|
||||
// Set SCS high so that we don't process any of these clocks as commands.
|
||||
GPIO_WriteBit(DISP_GPIO, DISP_PIN_SCS, Bit_SET); // SCS -> HIGH
|
||||
|
||||
// Send dummy clocks
|
||||
for (unsigned int i = 0; i < 8; ++i) {
|
||||
display_write_byte(0x00);
|
||||
}
|
||||
|
||||
if (!GPIO_ReadInputDataBit(DISP_GPIO, DISP_PIN_CDONE)) {
|
||||
PBL_LOG(LOG_LEVEL_WARNING, "CDONE not high after programming!");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void display_power_enable(void) {
|
||||
// The display requires us to wait 1ms between each power rail coming up. The PMIC
|
||||
// initialization brings up the 3.2V rail (VLCD on the display, LD02 on the PMIC) for us, but
|
||||
// we still need to wait before turning on the subsequent rails.
|
||||
delay_ms(2);
|
||||
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Enabling 6v6 (Display VDDC)");
|
||||
set_6V6_power_state(true);
|
||||
|
||||
delay_ms(2);
|
||||
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Enabling 4v5 (Display VDDP)");
|
||||
set_4V5_power_state(true);
|
||||
}
|
||||
|
||||
void display_power_disable(void) {
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Disabling 4v5 (Display VDDP)");
|
||||
set_4V5_power_state(false);
|
||||
|
||||
delay_ms(2);
|
||||
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Disabling 6v6 (Display VDDC)");
|
||||
set_6V6_power_state(false);
|
||||
|
||||
delay_ms(2);
|
||||
}
|
||||
|
||||
//!
|
||||
//! Write a single byte synchronously to the display. Use this
|
||||
//! sparingly, as it will tie up the micro duing the write.
|
||||
//!
|
||||
void display_write_byte(uint8_t d) {
|
||||
// Block until the tx buffer is empty
|
||||
while (!SPI_I2S_GetFlagStatus(DISP_SPI, SPI_I2S_FLAG_TXE)) continue;
|
||||
SPI_I2S_SendData(DISP_SPI, d);
|
||||
}
|
||||
|
||||
uint8_t display_write_and_read_byte(uint8_t d) {
|
||||
SPI_I2S_ReceiveData(DISP_SPI);
|
||||
while (!SPI_I2S_GetFlagStatus(DISP_SPI, SPI_I2S_FLAG_TXE)) continue;
|
||||
SPI_I2S_SendData(DISP_SPI, d);
|
||||
while (!SPI_I2S_GetFlagStatus(DISP_SPI, SPI_I2S_FLAG_RXNE)) continue;
|
||||
return SPI_I2S_ReceiveData(DISP_SPI);
|
||||
}
|
48
platform/snowy/boot/src/drivers/display/ice40lp.h
Normal file
48
platform/snowy/boot/src/drivers/display/ice40lp.h
Normal file
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "drivers/gpio.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
// GPIO constants
|
||||
#define DISP_SPI SPI6
|
||||
#define DISP_GPIO GPIOG
|
||||
|
||||
#define DISPLAY_SPI_CLOCK_PERIPH SpiPeriphClockAPB2
|
||||
#define DISPLAY_SPI_CLOCK RCC_APB2Periph_SPI6
|
||||
#define DISP_PIN_SCS GPIO_Pin_8
|
||||
#define DISP_PIN_CDONE GPIO_Pin_9
|
||||
#define DISP_PIN_BUSY GPIO_Pin_10
|
||||
#define DISP_PIN_SO GPIO_Pin_12
|
||||
#define DISP_PIN_SCLK GPIO_Pin_13
|
||||
#define DISP_PIN_SI GPIO_Pin_14
|
||||
#define DISP_PIN_CRESET GPIO_Pin_15
|
||||
|
||||
#define GPIO_PINSOURCE_SCK GPIO_PinSource13
|
||||
#define GPIO_PINSOURCE_MOSI GPIO_PinSource14
|
||||
#define GPIO_PINSOURCE_MISO GPIO_PinSource12
|
||||
|
||||
|
||||
bool display_busy(void);
|
||||
void display_start(void);
|
||||
bool display_program(const uint8_t *fpga_bitstream, uint32_t bitstream_size);
|
||||
void display_write_byte(uint8_t d);
|
||||
uint8_t display_write_and_read_byte(uint8_t d);
|
||||
void display_power_enable(void);
|
||||
void display_power_disable(void);
|
288
platform/snowy/boot/src/drivers/display/snowy_boot_fpga.c
Normal file
288
platform/snowy/boot/src/drivers/display/snowy_boot_fpga.c
Normal file
|
@ -0,0 +1,288 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "drivers/display.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "board/board.h"
|
||||
#include "drivers/dbgserial.h"
|
||||
#include "drivers/display/bootloader_fpga_bitstream.auto.h"
|
||||
#include "drivers/display/ice40lp.h"
|
||||
#include "drivers/flash/s29vs.h"
|
||||
#include "drivers/gpio.h"
|
||||
#include "drivers/periph_config.h"
|
||||
#include "drivers/pmic.h"
|
||||
#include "drivers/spi.h"
|
||||
#include "flash_region.h"
|
||||
#include "stm32f4xx_gpio.h"
|
||||
#include "stm32f4xx_rcc.h"
|
||||
#include "stm32f4xx_spi.h"
|
||||
#include "system/passert.h"
|
||||
#include "util/delay.h"
|
||||
#include "util/misc.h"
|
||||
|
||||
|
||||
#define CMD_NULL (0)
|
||||
#define CMD_SET_PARAMETER (1)
|
||||
#define CMD_DISPLAY_OFF (2)
|
||||
#define CMD_DISPLAY_ON (3)
|
||||
#define CMD_DRAW_SCENE (4)
|
||||
#define CMD_RESET_RELEASE (8)
|
||||
#define CMD_RESET_ASSERT (9)
|
||||
|
||||
#define SCENE_BLACK (0)
|
||||
#define SCENE_SPLASH (1)
|
||||
#define SCENE_UPDATE (2)
|
||||
#define SCENE_ERROR (3)
|
||||
|
||||
#define UPDATE_PROGRESS_MAX (93)
|
||||
|
||||
// The FPGA bitstream stored in NVCM may be missing or defective; a replacement
|
||||
// bitstream may be stored in the MFG info flash region, prefixed with a
|
||||
// four-byte header. The header is composed of the bitstream length followed by
|
||||
// its complement (all bits inverted).
|
||||
#define FPGA_BITSTREAM_FLASH_ADDR (FMC_BANK_1_BASE_ADDRESS + \
|
||||
FLASH_REGION_MFG_INFO_BEGIN + 0x10000)
|
||||
|
||||
struct __attribute__((packed)) FlashBitstream {
|
||||
uint16_t len;
|
||||
uint16_t len_complement;
|
||||
uint8_t bitstream[0];
|
||||
};
|
||||
|
||||
static bool prv_wait_programmed(void) {
|
||||
// The datasheet lists the typical NVCM configuration time as 56 ms.
|
||||
// Something is wrong if it takes more than twice that time.
|
||||
int timeout = 100 * 10;
|
||||
while (GPIO_ReadInputDataBit(DISP_GPIO, DISP_PIN_CDONE) == 0) {
|
||||
if (timeout-- == 0) {
|
||||
dbgserial_putstr("FPGA CDONE timeout expired!");
|
||||
return false;
|
||||
}
|
||||
delay_us(100);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool prv_reset_into_nvcm(void) {
|
||||
// Reset the FPGA and wait for it to program itself via NVCM.
|
||||
// NVCM configuration is initiated by pulling CRESET high while SCS is high.
|
||||
GPIO_WriteBit(DISP_GPIO, DISP_PIN_SCS, Bit_SET);
|
||||
// CRESET needs to be low for at least 200 ns
|
||||
GPIO_WriteBit(DISP_GPIO, DISP_PIN_CRESET, Bit_RESET);
|
||||
delay_ms(1);
|
||||
GPIO_WriteBit(DISP_GPIO, DISP_PIN_CRESET, Bit_SET);
|
||||
return prv_wait_programmed();
|
||||
}
|
||||
|
||||
static bool prv_reset_fpga(void) {
|
||||
#ifdef BLANK_FPGA
|
||||
return display_program(s_fpga_bitstream, sizeof(s_fpga_bitstream));
|
||||
#endif
|
||||
|
||||
const struct FlashBitstream *bitstream = (void *)FPGA_BITSTREAM_FLASH_ADDR;
|
||||
// Work around GCC bug https://gcc.gnu.org/bugzilla/show_bug.cgi?id=38341
|
||||
uint16_t len_complement_complement = ~bitstream->len_complement;
|
||||
if (bitstream->len != 0xffff && bitstream->len == len_complement_complement) {
|
||||
dbgserial_putstr("Configuring FPGA from bitstream in flash...");
|
||||
if (display_program(bitstream->bitstream, bitstream->len)) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
// Fall back to NVCM.
|
||||
dbgserial_putstr("No FPGA bitstream in flash.");
|
||||
}
|
||||
dbgserial_putstr("Falling back to NVCM.");
|
||||
return prv_reset_into_nvcm();
|
||||
}
|
||||
|
||||
static void prv_start_command(uint8_t cmd) {
|
||||
GPIO_WriteBit(DISP_GPIO, DISP_PIN_SCS, Bit_RESET);
|
||||
delay_us(100);
|
||||
display_write_byte(cmd);
|
||||
}
|
||||
|
||||
static void prv_send_command_arg(uint8_t arg) {
|
||||
display_write_byte(arg);
|
||||
}
|
||||
|
||||
static void prv_end_command(void) {
|
||||
while (SPI_I2S_GetFlagStatus(DISP_SPI, SPI_I2S_FLAG_BSY)) continue;
|
||||
GPIO_WriteBit(DISP_GPIO, DISP_PIN_SCS, Bit_SET);
|
||||
}
|
||||
|
||||
static bool prv_wait_busy(void) {
|
||||
// The display should come out of busy within 35 milliseconds;
|
||||
// it is a waste of time to wait more than twice that.
|
||||
int timeout = 50 * 10;
|
||||
while (display_busy()) {
|
||||
if (timeout-- == 0) {
|
||||
dbgserial_putstr("Display busy-wait timeout expired!");
|
||||
return false;
|
||||
}
|
||||
delay_us(100);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void prv_screen_on(void) {
|
||||
prv_start_command(CMD_DISPLAY_ON);
|
||||
prv_end_command();
|
||||
}
|
||||
|
||||
static void prv_screen_off(void) {
|
||||
prv_start_command(CMD_DISPLAY_OFF);
|
||||
prv_end_command();
|
||||
}
|
||||
|
||||
void prv_draw_scene(uint8_t scene) {
|
||||
prv_start_command(CMD_DRAW_SCENE);
|
||||
prv_send_command_arg(scene);
|
||||
prv_end_command();
|
||||
}
|
||||
void prv_set_parameter(uint32_t param) {
|
||||
prv_start_command(CMD_SET_PARAMETER);
|
||||
// Send in little-endian byte order
|
||||
prv_send_command_arg(param & 0xff);
|
||||
prv_send_command_arg((param >> 8) & 0xff);
|
||||
prv_send_command_arg((param >> 16) & 0xff);
|
||||
prv_send_command_arg((param >> 24) & 0xff);
|
||||
prv_end_command();
|
||||
}
|
||||
|
||||
static uint8_t prv_read_version(void) {
|
||||
GPIO_WriteBit(DISP_GPIO, DISP_PIN_SCS, Bit_RESET);
|
||||
delay_us(100);
|
||||
|
||||
uint8_t version_num = display_write_and_read_byte(0);
|
||||
GPIO_WriteBit(DISP_GPIO, DISP_PIN_SCS, Bit_SET);
|
||||
return version_num;
|
||||
}
|
||||
|
||||
void display_init(void) {
|
||||
display_start();
|
||||
bool program_success = prv_reset_fpga();
|
||||
if (!program_success) {
|
||||
dbgserial_putstr("FPGA configuration failed. Is this a bigboard?");
|
||||
// Don't waste time trying to get the FPGA unstuck if it's not configured.
|
||||
// It's just going to waste time and frustrate bigboard users.
|
||||
return;
|
||||
}
|
||||
|
||||
dbgserial_print("FPGA version: ");
|
||||
dbgserial_print_hex(prv_read_version());
|
||||
dbgserial_putstr("");
|
||||
|
||||
// enable the power rails
|
||||
display_power_enable();
|
||||
|
||||
#ifdef TEST_FPGA_RESET_COMMAND
|
||||
#define ASSERT_BUSY_IS(state) \
|
||||
dbgserial_putstr(GPIO_ReadInputDataBit(DISP_GPIO, DISP_PIN_BUSY) == state? \
|
||||
"Yes" : "No")
|
||||
|
||||
// Test out the FPGA soft-reset capability present in release-03 of the FPGA.
|
||||
dbgserial_putstr("FPGA soft-reset test");
|
||||
|
||||
dbgserial_print("Precondition: BUSY asserted during scene draw? ");
|
||||
prv_draw_scene(SCENE_BLACK);
|
||||
ASSERT_BUSY_IS(Bit_SET);
|
||||
|
||||
dbgserial_print("Is BUSY cleared after the reset command? ");
|
||||
prv_start_command(CMD_RESET_ASSERT);
|
||||
prv_end_command();
|
||||
ASSERT_BUSY_IS(Bit_RESET);
|
||||
|
||||
dbgserial_print("Are draw-scene commands ineffectual while in reset? ");
|
||||
prv_draw_scene(SCENE_BLACK);
|
||||
ASSERT_BUSY_IS(Bit_RESET);
|
||||
|
||||
dbgserial_print("Does releasing reset allow draw-scene commands "
|
||||
"to function again? ");
|
||||
prv_start_command(CMD_RESET_RELEASE);
|
||||
prv_end_command();
|
||||
prv_draw_scene(SCENE_BLACK);
|
||||
ASSERT_BUSY_IS(Bit_SET);
|
||||
|
||||
dbgserial_print("Does the draw-scene command complete? ");
|
||||
dbgserial_putstr(prv_wait_busy()? "Yes" : "No");
|
||||
#endif
|
||||
|
||||
// Work around an issue which some boards exhibit where the FPGA ring
|
||||
// oscillator can start up with higher harmonics, massively overclocking the
|
||||
// design and causing malfunction. When this occurrs, the draw-scene command
|
||||
// will not work, asserting BUSY indefinitely but never updating the display.
|
||||
// Other commands such as display-on and display-off are less affected by the
|
||||
// overclocking, so the display can be turned on while the FPGA is in this
|
||||
// state, showing only garbage.
|
||||
// FPGA malfunction can be detected in software. In an attempt to restore
|
||||
// proper functioning, the FPGA can be reset and reconfigured in the hopes
|
||||
// that the ring oscillator will start up and oscillate without any higher
|
||||
// harmonics. Bootloader release 03 attempts to mitigate this problem by
|
||||
// delaying oscillator startup until after configuration completes. Time will
|
||||
// tell whether this actually fixes things.
|
||||
for (int retries = 0; retries <= 20; ++retries) {
|
||||
prv_draw_scene(SCENE_SPLASH);
|
||||
if (prv_wait_busy()) {
|
||||
prv_screen_on();
|
||||
dbgserial_print("Display initialized after ");
|
||||
dbgserial_print_hex(retries);
|
||||
dbgserial_putstr(" retries.");
|
||||
return;
|
||||
}
|
||||
|
||||
prv_reset_fpga();
|
||||
}
|
||||
|
||||
// It's taken too many attempts and the FPGA still isn't behaving. Give up on
|
||||
// showing the splash screen and keep the screen off so that the user doesn't
|
||||
// see a broken-looking staticky screen on boot.
|
||||
dbgserial_putstr("Display initialization failed.");
|
||||
prv_screen_off();
|
||||
}
|
||||
|
||||
void display_boot_splash(void) {
|
||||
prv_wait_busy();
|
||||
prv_draw_scene(SCENE_SPLASH);
|
||||
// Don't turn the screen on until the boot-splash is fully drawn.
|
||||
prv_wait_busy();
|
||||
prv_screen_on();
|
||||
}
|
||||
|
||||
void display_firmware_update_progress(
|
||||
uint32_t numerator, uint32_t denominator) {
|
||||
static uint8_t last_bar_fill = UINT8_MAX;
|
||||
// Scale progress to the number of pixels in the progress bar,
|
||||
// rounding half upwards.
|
||||
uint8_t bar_fill =
|
||||
((numerator * UPDATE_PROGRESS_MAX) + ((denominator+1)/2)) / denominator;
|
||||
// Don't waste time and power redrawing the same screen repeatedly.
|
||||
if (bar_fill != last_bar_fill) {
|
||||
last_bar_fill = bar_fill;
|
||||
prv_set_parameter(bar_fill);
|
||||
prv_draw_scene(SCENE_UPDATE);
|
||||
}
|
||||
}
|
||||
|
||||
void display_error_code(uint32_t error_code) {
|
||||
prv_set_parameter(error_code);
|
||||
prv_draw_scene(SCENE_ERROR);
|
||||
}
|
||||
|
||||
void display_prepare_for_reset(void) {
|
||||
prv_screen_off();
|
||||
}
|
46
platform/snowy/boot/src/drivers/exti.h
Normal file
46
platform/snowy/boot/src/drivers/exti.h
Normal file
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "board/board.h"
|
||||
|
||||
typedef enum {
|
||||
ExtiTrigger_Rising,
|
||||
ExtiTrigger_Falling,
|
||||
ExtiTrigger_RisingFalling
|
||||
} ExtiTrigger;
|
||||
|
||||
//! See section 12.2.5 "External interrupt/event line mapping" in the STM32F2 reference manual
|
||||
typedef enum {
|
||||
ExtiLineOther_RTCAlarm = 17,
|
||||
ExtiLineOther_RTCWakeup = 22
|
||||
} ExtiLineOther;
|
||||
|
||||
typedef void (*ExtiHandlerCallback)(void);
|
||||
|
||||
//! Configures the given EXTI and NVIC for the given configuration.
|
||||
void exti_configure_pin(ExtiConfig cfg, ExtiTrigger trigger, ExtiHandlerCallback cb);
|
||||
//! Configures the given EXTI and NVIC for the given configuration.
|
||||
void exti_configure_other(ExtiLineOther exti_line, ExtiTrigger trigger);
|
||||
|
||||
static inline void exti_enable(ExtiConfig config);
|
||||
static inline void exti_disable(ExtiConfig config);
|
||||
|
||||
void exti_enable_other(ExtiLineOther);
|
||||
void exti_disable_other(ExtiLineOther);
|
||||
|
||||
#include "exti.inl.h"
|
27
platform/snowy/boot/src/drivers/exti.inl.h
Normal file
27
platform/snowy/boot/src/drivers/exti.inl.h
Normal file
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
//! @file exti.inl.h
|
||||
//!
|
||||
//! Helper functions intended to be inlined into the calling code.
|
||||
|
||||
static inline void exti_enable(ExtiConfig config) {
|
||||
exti_enable_other(config.exti_line);
|
||||
}
|
||||
|
||||
static inline void exti_disable(ExtiConfig config) {
|
||||
exti_disable_other(config.exti_line);
|
||||
}
|
42
platform/snowy/boot/src/drivers/flash.h
Normal file
42
platform/snowy/boot/src/drivers/flash.h
Normal file
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* Configure the micro's peripherals to communicate with the flash
|
||||
* chip.
|
||||
*/
|
||||
void flash_init(void);
|
||||
|
||||
/**
|
||||
* Read 1 or more bytes starting at the specified 24bit address into
|
||||
* the provided buffer. This function does no range checking, so it is
|
||||
* currently possible to run off the end of the flash.
|
||||
*
|
||||
* @param buffer A byte-buffer that will be used to store the data
|
||||
* read from flash.
|
||||
* @param start_addr The address of the first byte to be read from flash.
|
||||
* @param buffer_size The total number of bytes to be read from flash.
|
||||
*/
|
||||
void flash_read_bytes(uint8_t* buffer, uint32_t start_addr, uint32_t buffer_size);
|
||||
|
||||
//! Check if we can talk to the flash.
|
||||
//! @return trie if the CFI table can be queried.
|
||||
bool flash_sanity_check(void);
|
176
platform/snowy/boot/src/drivers/flash/s29vs.c
Normal file
176
platform/snowy/boot/src/drivers/flash/s29vs.c
Normal file
|
@ -0,0 +1,176 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "drivers/flash.h"
|
||||
#include "drivers/flash/s29vs.h"
|
||||
#include "drivers/gpio.h"
|
||||
#include "drivers/periph_config.h"
|
||||
#include "stm32f4xx_gpio.h"
|
||||
#include "util/delay.h"
|
||||
|
||||
|
||||
//! @param sector_address The address of the start of the sector to write the command to.
|
||||
//! @param cmd The command to write.
|
||||
static void flash_s29vs_issue_command(FlashAddress sector_address, S29VSCommand cmd) {
|
||||
// The offset in the sector we write the first part of commands to. Note that this is a 16-bit
|
||||
// word aligned address as opposed to a byte address.
|
||||
static const uint32_t COMMAND_ADDRESS = 0x555;
|
||||
|
||||
((__IO uint16_t*) (FMC_BANK_1_BASE_ADDRESS + sector_address))[COMMAND_ADDRESS] = cmd;
|
||||
}
|
||||
|
||||
static uint16_t flash_s29vs_read_short(FlashAddress addr) {
|
||||
return *((__IO uint16_t*)(FMC_BANK_1_BASE_ADDRESS + addr));
|
||||
}
|
||||
|
||||
void flash_read_bytes(uint8_t* buffer, uint32_t start_addr, uint32_t buffer_size) {
|
||||
memcpy(buffer, (void*)(FMC_BANK_1_BASE_ADDRESS + start_addr), buffer_size);
|
||||
}
|
||||
|
||||
|
||||
static void flash_s29vs_software_reset(void) {
|
||||
flash_s29vs_issue_command(0, S29VSCommand_SoftwareReset);
|
||||
}
|
||||
|
||||
void flash_init(void) {
|
||||
gpio_use(GPIOB);
|
||||
gpio_use(GPIOD);
|
||||
gpio_use(GPIOE);
|
||||
|
||||
// Configure the reset pin (D2)
|
||||
GPIO_InitTypeDef gpio_init = {
|
||||
.GPIO_Pin = GPIO_Pin_2,
|
||||
.GPIO_Mode = GPIO_Mode_OUT,
|
||||
.GPIO_Speed = GPIO_Speed_100MHz,
|
||||
.GPIO_OType = GPIO_OType_PP,
|
||||
.GPIO_PuPd = GPIO_PuPd_NOPULL
|
||||
};
|
||||
GPIO_Init(GPIOD, &gpio_init);
|
||||
|
||||
GPIO_WriteBit(GPIOD, GPIO_Pin_2, Bit_SET);
|
||||
|
||||
// Configure pins relating to the FMC peripheral (30 pins!)
|
||||
|
||||
// B7 - FMC AVD - FMC Address Valid aka Latch
|
||||
// D0-D1, D8-D15, E2-15 - FMC A, AD - FMC Address and Address/Data lines
|
||||
// D2 - Reset - GPIO Reset line
|
||||
// D3 - FMC CLK
|
||||
// D4 - FMC OE - FMC Output Enable
|
||||
// D5 - FMC WE - FMC Write Enable
|
||||
// D6 - FMC RDY - FMC Ready line
|
||||
// D7 - FMC CE - FMC Chip Enable
|
||||
|
||||
gpio_init = (GPIO_InitTypeDef) {
|
||||
.GPIO_Mode = GPIO_Mode_AF,
|
||||
.GPIO_Speed = GPIO_Speed_100MHz,
|
||||
.GPIO_OType = GPIO_OType_PP,
|
||||
.GPIO_PuPd = GPIO_PuPd_NOPULL
|
||||
};
|
||||
|
||||
GPIO_PinAFConfig(GPIOB, GPIO_PinSource7, GPIO_AF_FMC);
|
||||
gpio_init.GPIO_Pin = GPIO_Pin_7;
|
||||
GPIO_Init(GPIOB, &gpio_init);
|
||||
|
||||
for (uint8_t pin_source = 0; pin_source < 16; ++pin_source) {
|
||||
if (pin_source == 2) {
|
||||
continue;
|
||||
}
|
||||
GPIO_PinAFConfig(GPIOD, pin_source, GPIO_AF_FMC);
|
||||
}
|
||||
gpio_init.GPIO_Pin = GPIO_Pin_All & (~GPIO_Pin_2);
|
||||
GPIO_Init(GPIOD, &gpio_init);
|
||||
|
||||
for (uint8_t pin_source = 2; pin_source < 16; ++pin_source) {
|
||||
GPIO_PinAFConfig(GPIOE, pin_source, GPIO_AF_FMC);
|
||||
}
|
||||
gpio_init.GPIO_Pin = GPIO_Pin_All & (~GPIO_Pin_0) & (~GPIO_Pin_1);
|
||||
GPIO_Init(GPIOE, &gpio_init);
|
||||
|
||||
// We have configured the pins, lets perform a full HW reset to put the chip
|
||||
// in a good state
|
||||
GPIO_WriteBit(GPIOD, GPIO_Pin_2, Bit_RESET);
|
||||
delay_us(10); // only needs to be 50ns according to data sheet
|
||||
GPIO_WriteBit(GPIOD, GPIO_Pin_2, Bit_SET);
|
||||
delay_us(30); // need 200ns + 10us before CE can be pulled low
|
||||
|
||||
RCC_AHB3PeriphClockCmd(RCC_AHB3Periph_FMC, ENABLE);
|
||||
|
||||
// Setup default config for async
|
||||
// Configure the FMC peripheral itself
|
||||
FMC_NORSRAMTimingInitTypeDef nor_timing_init = {
|
||||
// time between address write and address latch (AVD high)
|
||||
// tAAVDS on datasheet, min 4 ns
|
||||
//
|
||||
// AVD low time
|
||||
// tAVDP on datasheet, min 6 ns
|
||||
.FMC_AddressSetupTime = 1,
|
||||
|
||||
// time between AVD high (address is available) and OE low (memory can write)
|
||||
// tAVDO on the datasheet, min 4 ns
|
||||
.FMC_AddressHoldTime = 1,
|
||||
|
||||
// time between OE low (memory can write) and valid data being available
|
||||
// tOE on datasheet, max 15 ns
|
||||
// 13 cycles is the default configuration in the component's configuration register
|
||||
// Setup to 3 for async
|
||||
.FMC_DataSetupTime = 3,
|
||||
|
||||
// Time between chip selects
|
||||
// not on the datasheet, picked a random safe number
|
||||
.FMC_BusTurnAroundDuration = 1,
|
||||
|
||||
.FMC_CLKDivision = 15, // Not used for async NOR
|
||||
.FMC_DataLatency = 15, // Not used for async NOR
|
||||
.FMC_AccessMode = FMC_AccessMode_A // Only used for ExtendedMode == FMC_ExtendedMode_Enable, which we don't use
|
||||
};
|
||||
|
||||
FMC_NORSRAMInitTypeDef nor_init = {
|
||||
.FMC_Bank = FMC_Bank1_NORSRAM1,
|
||||
.FMC_DataAddressMux = FMC_DataAddressMux_Enable,
|
||||
.FMC_MemoryType = FMC_MemoryType_NOR,
|
||||
.FMC_MemoryDataWidth = FMC_NORSRAM_MemoryDataWidth_16b,
|
||||
.FMC_BurstAccessMode = FMC_BurstAccessMode_Disable,
|
||||
.FMC_AsynchronousWait = FMC_AsynchronousWait_Disable,
|
||||
.FMC_WaitSignalPolarity = FMC_WaitSignalPolarity_Low,
|
||||
.FMC_WrapMode = FMC_WrapMode_Disable,
|
||||
.FMC_WaitSignalActive = FMC_WaitSignalActive_BeforeWaitState,
|
||||
.FMC_WriteOperation = FMC_WriteOperation_Enable,
|
||||
.FMC_WaitSignal = FMC_WaitSignal_Enable,
|
||||
.FMC_ExtendedMode = FMC_ExtendedMode_Disable,
|
||||
.FMC_WriteBurst = FMC_WriteBurst_Disable,
|
||||
.FMC_ContinousClock = FMC_CClock_SyncOnly,
|
||||
.FMC_ReadWriteTimingStruct = &nor_timing_init
|
||||
};
|
||||
|
||||
FMC_NORSRAMInit(&nor_init);
|
||||
|
||||
// Re-enable NOR
|
||||
FMC_NORSRAMCmd(FMC_Bank1_NORSRAM1, ENABLE);
|
||||
}
|
||||
|
||||
bool flash_sanity_check(void) {
|
||||
// Check that the first words of the CFI table are 'Q' 'R' 'Y'.
|
||||
// This will work on any flash memory, regardless of the manufacturer.
|
||||
flash_s29vs_issue_command(0, S29VSCommand_CFIEntry);
|
||||
bool ok = (flash_s29vs_read_short(0x20) & 0xff) == 'Q';
|
||||
ok = ok && (flash_s29vs_read_short(0x22) & 0xff) == 'R';
|
||||
ok = ok && (flash_s29vs_read_short(0x24) & 0xff) == 'Y';
|
||||
flash_s29vs_software_reset();
|
||||
return ok;
|
||||
}
|
63
platform/snowy/boot/src/drivers/flash/s29vs.h
Normal file
63
platform/snowy/boot/src/drivers/flash/s29vs.h
Normal file
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "drivers/flash.h"
|
||||
|
||||
//! An address in the flash address spac
|
||||
typedef uint32_t FlashAddress;
|
||||
|
||||
//! This is the memory mapped region that's mapped to the parallel flash.
|
||||
static const uintptr_t FMC_BANK_1_BASE_ADDRESS = 0x60000000;
|
||||
|
||||
//! This is the unit that we use for erasing
|
||||
static const uint32_t SECTOR_SIZE_BYTES = 0x20000; // 128kb
|
||||
//! This is the unit that we use for writing
|
||||
static const uint32_t PAGE_SIZE_BYTES = 64;
|
||||
|
||||
//! Different commands we can send to the flash
|
||||
typedef enum S29VSCommand {
|
||||
S29VSCommand_WriteBufferLoad = 0x25,
|
||||
S29VSCommand_BufferToFlash = 0x29,
|
||||
S29VSCommand_ReadStatusRegister = 0x70,
|
||||
S29VSCommand_ClearStatusRegister = 0x71,
|
||||
S29VSCommand_EraseSetup = 0x80,
|
||||
S29VSCommand_DeviceIDEntry = 0x90,
|
||||
S29VSCommand_CFIEntry = 0x98,
|
||||
S29VSCommand_ConfigureRegisterEntry = 0xD0,
|
||||
S29VSCommand_SoftwareReset = 0xF0
|
||||
} S29VSCommand;
|
||||
|
||||
//! Arguments to the S29VSCommand_EraseSetup command
|
||||
typedef enum S29VSCommandEraseAguments {
|
||||
S29VSCommandEraseAguments_ChipErase = 0x10,
|
||||
S29VSCommandEraseAguments_SectorErase = 0x30
|
||||
} S29VSCommandEraseAguments;
|
||||
|
||||
//! The bitset stored in the status register, see flash_s29vs_read_status_register
|
||||
typedef enum S29VSStatusBit {
|
||||
S29VSStatusBit_BankStatus = 0x00,
|
||||
S29VSStatusBit_SectorLockStatus = 0x01,
|
||||
S29VSStatusBit_ProgramSuspended = 0x02,
|
||||
// 0x04 is unused
|
||||
S29VSStatusBit_ProgramStatus = 0x10,
|
||||
S29VSStatusBit_EraseStatus = 0x20,
|
||||
S29VSStatusBit_EraseSuspended = 0x40,
|
||||
S29VSStatusBit_DeviceReady = 0x80,
|
||||
} S29VSStatusBit;
|
57
platform/snowy/boot/src/drivers/gpio.h
Normal file
57
platform/snowy/boot/src/drivers/gpio.h
Normal file
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#if defined(MICRO_FAMILY_STM32F2)
|
||||
#include "stm32f2xx_gpio.h"
|
||||
#elif defined(MICRO_FAMILY_STM32F4)
|
||||
#include "stm32f4xx_gpio.h"
|
||||
#endif
|
||||
|
||||
#include "board/board.h"
|
||||
|
||||
void gpio_use(GPIO_TypeDef* GPIOx);
|
||||
void gpio_release(GPIO_TypeDef* GPIOx);
|
||||
|
||||
//! Initialize a GPIO as an output.
|
||||
//!
|
||||
//! @param pin_config the BOARD_CONFIG pin configuration struct
|
||||
//! @param otype the output type of the pin (GPIO_OType_PP or GPIO_OType_OD)
|
||||
//! @param speed the output slew rate
|
||||
//! @note The slew rate should be set as low as possible for the
|
||||
//! pin function to minimize ringing and RF interference.
|
||||
void gpio_output_init(OutputConfig pin_config, GPIOOType_TypeDef otype,
|
||||
GPIOSpeed_TypeDef speed);
|
||||
|
||||
//! Assert or deassert the output pin.
|
||||
//!
|
||||
//! Asserting the output drives the pin high if pin_config.active_high
|
||||
//! is true, and drives it low if pin_config.active_high is false.
|
||||
void gpio_output_set(OutputConfig pin_config, bool asserted);
|
||||
|
||||
//! Configure a GPIO alternate function.
|
||||
//!
|
||||
//! @param pin_config the BOARD_CONFIG pin configuration struct
|
||||
//! @param otype the output type of the pin (GPIO_OType_PP or GPIO_OType_OD)
|
||||
//! @param speed the output slew rate
|
||||
//! @param pupd pull-up or pull-down configuration
|
||||
//! @note The slew rate should be set as low as possible for the
|
||||
//! pin function to minimize ringing and RF interference.
|
||||
void gpio_af_init(AfConfig af_config, GPIOOType_TypeDef otype,
|
||||
GPIOSpeed_TypeDef speed, GPIOPuPd_TypeDef pupd);
|
86
platform/snowy/boot/src/drivers/i2c.h
Normal file
86
platform/snowy/boot/src/drivers/i2c.h
Normal file
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "board/board.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
//! Initialize the I2C driver. Must be called before first use
|
||||
void i2c_init(void);
|
||||
|
||||
//! Start using the I2C bus connected to the device specified by \a device_id
|
||||
//! Must be called before any other use of the bus is performed
|
||||
//! @param device_id ID of device
|
||||
void i2c_use(I2cDevice device_id);
|
||||
|
||||
//! Stop using the I2C bus connected to the device specified by \a device_id
|
||||
//! Call when done with the bus
|
||||
//! @param device_id ID of device
|
||||
void i2c_release(I2cDevice device_id);
|
||||
|
||||
//! Reset the bus
|
||||
//! Will re-initialize the bus and cycle the power to the bus if this is
|
||||
//! supported for the bus the device specified by \a device_id is connected to)
|
||||
//! @param device_id ID of device, this will identify the bus to be reset
|
||||
void i2c_reset(I2cDevice device_id);
|
||||
|
||||
//! Manually bang out the clock on the bus specified by \a device_id for a period
|
||||
//! of time or until the data line recovers
|
||||
//! Must not be called before \ref i2c_use has been called for the device
|
||||
//! @param device_id ID of device, this will identify the bus to be recovered
|
||||
//! @return true if the data line recovered, false otherwise
|
||||
bool i2c_bitbang_recovery(I2cDevice device_id);
|
||||
|
||||
//! Read the value of a register
|
||||
//! Must not be called before \ref i2c_use has been called for the device
|
||||
//! @param device_id ID of device to communicate with
|
||||
//! @param i2c_device_address Device bus address
|
||||
//! @param register_address Address of register to read
|
||||
//! @param result Pointer to destination buffer
|
||||
//! @return true if transfer succeeded, false if error occurred
|
||||
bool i2c_read_register(I2cDevice device_id, uint8_t i2c_device_address, uint8_t register_address, uint8_t *result);
|
||||
|
||||
//! Read a sequence of registers starting from \a register_address_start
|
||||
//! Must not be called before \ref i2c_use has been called for the device
|
||||
//! @param device_id ID of device to communicate with
|
||||
//! @param i2c_device_address Device bus address
|
||||
//! @param register_address_start Address of first register to read
|
||||
//! @param read_size Number of bytes to read
|
||||
//! @param result_buffer Pointer to destination buffer
|
||||
//! @return true if transfer succeeded, false if error occurred
|
||||
bool i2c_read_register_block(I2cDevice device_id, uint8_t i2c_device_address, uint8_t register_address_start, uint8_t read_size, uint8_t* result_buffer);
|
||||
|
||||
//! Write to a register
|
||||
//! Must not be called before \ref i2c_use has been called for the device
|
||||
//! @param device_id ID of device to communicate with
|
||||
//! @param i2c_device_address Device bus address
|
||||
//! @param register_address Address of register to write to
|
||||
//! @param value Data value to write
|
||||
//! @return true if transfer succeeded, false if error occurred
|
||||
bool i2c_write_register(I2cDevice device_id, uint8_t i2c_device_address, uint8_t register_address, uint8_t value);
|
||||
|
||||
//! Write to a sequence of registers starting from \a register_address_start
|
||||
//! Must not be called before \ref i2c_use has been called for the device
|
||||
//! @param device_id ID of device to communicate with
|
||||
//! @param i2c_device_address Device bus address
|
||||
//! @param register_address_start Address of first register to read
|
||||
//! @param write_size Number of bytes to write
|
||||
//! @param buffer Pointer to source buffer
|
||||
//! @return true if transfer succeeded, false if error occurred
|
||||
bool i2c_write_register_block(I2cDevice device_id, uint8_t i2c_device_address, uint8_t register_address_start, uint8_t write_size, const uint8_t* buffer);
|
37
platform/snowy/boot/src/drivers/periph_config.h
Normal file
37
platform/snowy/boot/src/drivers/periph_config.h
Normal file
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#if defined(MICRO_FAMILY_STM32F2)
|
||||
#include "stm32f2xx.h"
|
||||
#elif defined(MICRO_FAMILY_STM32F4)
|
||||
#include "stm32f4xx.h"
|
||||
#endif
|
||||
|
||||
typedef void (*ClockCmd)(uint32_t periph, FunctionalState state);
|
||||
|
||||
static inline void periph_config_init(void) {}
|
||||
static inline void periph_config_acquire_lock(void) {}
|
||||
static inline void periph_config_release_lock(void) {}
|
||||
|
||||
static inline void periph_config_enable(ClockCmd clock_cmd, uint32_t periph) {
|
||||
clock_cmd(periph, ENABLE);
|
||||
}
|
||||
|
||||
static inline void periph_config_disable(ClockCmd clock_cmd, uint32_t periph) {
|
||||
clock_cmd(periph, DISABLE);
|
||||
}
|
64
platform/snowy/boot/src/drivers/pmic.h
Normal file
64
platform/snowy/boot/src/drivers/pmic.h
Normal file
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
//! Initialize the PMIC driver. Call this once at startup.
|
||||
bool pmic_init(void);
|
||||
|
||||
//! Tell the PMIC to power off the board and enter a standby-like state. All components will
|
||||
//! have their power removed (except for the RTC so we'll still keep time) and the PMIC itself
|
||||
//! will monitor the buttons for when to wake up.
|
||||
bool pmic_power_off(void);
|
||||
|
||||
//! Enable the battery monitor portion of the PMIC. Remember to turn this off with
|
||||
//! pmic_disable_battery_measure when immediate readings aren't required.
|
||||
bool pmic_enable_battery_measure(void);
|
||||
|
||||
//! Disable the battery monitor portion of the PMIC.
|
||||
bool pmic_disable_battery_measure(void);
|
||||
|
||||
//! Enable and disable the charging portion of the PMIC.
|
||||
bool pmic_set_charger_state(bool enable);
|
||||
|
||||
//! @return true if the PMIC thinks we're charging (adding additional charge to the battery).
|
||||
//! Note that once we hit full charge we'll no longer be charging, which is a different state
|
||||
//! that pmic_is_usb_connected.
|
||||
bool pmic_is_charging(void);
|
||||
|
||||
//! @return true if a usb-ish charger cable is currently connected.
|
||||
bool pmic_is_usb_connected(void);
|
||||
|
||||
//! Read information about the chip for tracking purposes.
|
||||
void pmic_read_chip_info(uint8_t *chip_id, uint8_t *chip_revision);
|
||||
|
||||
// FIXME: The following functions are unrelated to the PMIC and should be moved to the
|
||||
// display/accessory connector drivers once we have them.
|
||||
|
||||
//! Enables the LDO3 power rail. Used for the MFi/Magnetometer on snowy_bb, MFi on snowy_evt.
|
||||
void set_ldo3_power_state(bool enabled);
|
||||
|
||||
//! Enables the 4.5V power rail. Used for the display on snowy.
|
||||
void set_4V5_power_state(bool enabled);
|
||||
|
||||
//! Enables the 6.6V power rail. Used for the display on snowy.
|
||||
void set_6V6_power_state(bool enabled);
|
||||
|
||||
//! Enables power to the accessory connector.
|
||||
void set_accessory_power_state(bool enabled);
|
394
platform/snowy/boot/src/drivers/pmic/max14690_pmic.c
Normal file
394
platform/snowy/boot/src/drivers/pmic/max14690_pmic.c
Normal file
|
@ -0,0 +1,394 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* This file should probably go in the stm32f4 folder */
|
||||
|
||||
#include "drivers/pmic.h"
|
||||
|
||||
#include "board/board.h"
|
||||
#include "drivers/gpio.h"
|
||||
#include "drivers/i2c.h"
|
||||
#include "drivers/exti.h"
|
||||
#include "drivers/periph_config.h"
|
||||
#include "drivers/display/ice40lp.h"
|
||||
#include "system/logging.h"
|
||||
#include "system/passert.h"
|
||||
#include "util/delay.h"
|
||||
|
||||
#if defined(MICRO_FAMILY_STM32F2)
|
||||
#include "stm32f2xx_rcc.h"
|
||||
#include "stm32f2xx_gpio.h"
|
||||
#include "stm32f2xx_adc.h"
|
||||
#elif defined(MICRO_FAMILY_STM32F4)
|
||||
#include "stm32f4xx_rcc.h"
|
||||
#include "stm32f4xx_gpio.h"
|
||||
#include "stm32f4xx_adc.h"
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/* PMIC Bus Information */
|
||||
#define MAX14690_ADDR 0x50
|
||||
|
||||
//! The addresses of the registers that we can read using i2c
|
||||
typedef enum PmicRegisters {
|
||||
PmicRegisters_CHIP_ID = 0x00,
|
||||
PmicRegisters_CHIP_REV = 0x01,
|
||||
PmicRegisters_STATUSA = 0x02,
|
||||
PmicRegisters_STATUSB = 0x03,
|
||||
PmicRegisters_INTA = 0x05,
|
||||
PmicRegisters_INTB = 0x06,
|
||||
PmicRegisters_INT_MASK_A = 0x07,
|
||||
PmicRegisters_INT_MASK_B = 0x08,
|
||||
PmicRegisters_CHG_CNTL_A = 0x0A,
|
||||
PmicRegisters_BUCK1_CONFIG = 0x0D,
|
||||
PmicRegisters_BUCK2_CONFIG = 0x0F,
|
||||
PmicRegisters_LDO1_CONFIG = 0x12,
|
||||
PmicRegisters_LDO2_CONFIG = 0x14,
|
||||
PmicRegisters_LDO3_CONFIG = 0x16,
|
||||
PmicRegisters_MON_CFG = 0x19,
|
||||
PmicRegisters_HAND_SHK = 0x1D,
|
||||
PmicRegisters_PWR_CFG = 0x1F
|
||||
} PmicRegisters;
|
||||
|
||||
//! The different power rails that our PMIC controls
|
||||
typedef enum PmicRail {
|
||||
PmicRail_BUCK1, //!< 1.2V
|
||||
PmicRail_BUCK2, //!< 1.8V
|
||||
PmicRail_LDO1, //!< 2.0V - Auto - RTC
|
||||
PmicRail_LDO2, //!< 3.2V - Manual - FPGA
|
||||
|
||||
//! snowy_bb: 2.5V - Manual - MFi, Magnetometer
|
||||
//! snowy_evt: 1.8V - Manual - MFi
|
||||
PmicRail_LDO3
|
||||
} PmicRail;
|
||||
|
||||
//! Gives configuration information for reading a given rail through the monitor pin.
|
||||
typedef struct {
|
||||
const char* name; //!< Name for the rail.
|
||||
|
||||
//! What ratio we need to divide by in order to bring it into the range we can sense. We can
|
||||
//! only read between 0 and 1.8Vs, so we need to use the PMIC hardware to divide it down before
|
||||
//! sending it to us. Valid values are 1-4.
|
||||
uint8_t ratio;
|
||||
|
||||
//! The binary value we need to put in the register to select the rail.
|
||||
uint8_t source_config;
|
||||
} PmicMonConfig;
|
||||
|
||||
// Using the Binary constants GCC extension here, supported in GCC and Clang
|
||||
// https://gcc.gnu.org/onlinedocs/gcc/Binary-constants.html
|
||||
static const PmicMonConfig MON_CONFIG[] = {
|
||||
{ "+VBAT", 3, 0b001 }, // 3:1
|
||||
|
||||
// We only care about non-battery rails in MFG where we have the command_pmic_rails function.
|
||||
#ifdef RECOVERY_FW
|
||||
{ "+VSYS", 4, 0b010 }, // 4:1
|
||||
{ "+1V2", 1, 0b011 }, // 1:1, BUCK1
|
||||
{ "+1V8", 2, 0b100 }, // 2:1, BUCK2
|
||||
{ "+2V0_RTC", 2, 0b101 }, // 2:1, LDO1
|
||||
{ "+3V2", 2, 0b110 }, // 2:1, LDO2
|
||||
#ifdef BOARD_SNOWY_BB
|
||||
{ "+2V5", 2, 0b111 }, // 2:1, LDO3
|
||||
#else
|
||||
{ "+1V8_MFI_MIC", 2, 0b111 }, // 2:1, LDO3
|
||||
#endif // BOARD_SNOWY_BB
|
||||
#endif // RECOVERY_FW
|
||||
};
|
||||
|
||||
static const int PMIC_MON_CONFIG_VBAT_INDEX = 0;
|
||||
|
||||
/* Private Function Definitions */
|
||||
static bool prv_is_alive(void);
|
||||
static bool prv_set_pin_config(void);
|
||||
|
||||
//! Request that the rail be used or released. Internally refcounted per rail so you don't have
|
||||
//! to worry about turning this off on another client.
|
||||
static bool prv_update_rail_state(PmicRail rail, bool enable);
|
||||
|
||||
static void prv_mon_config_lock(void) {
|
||||
}
|
||||
|
||||
static void prv_mon_config_unlock(void) {
|
||||
}
|
||||
|
||||
static bool prv_read_register(uint8_t register_address, uint8_t *result) {
|
||||
return i2c_read_register(I2C_DEVICE_MAX14690, MAX14690_ADDR, register_address, result);
|
||||
}
|
||||
|
||||
|
||||
static bool prv_write_register(uint8_t register_address, uint8_t value) {
|
||||
return i2c_write_register(I2C_DEVICE_MAX14690, MAX14690_ADDR, register_address, value);
|
||||
}
|
||||
|
||||
/* Public Functions */
|
||||
bool pmic_init(void) {
|
||||
if (!prv_set_pin_config()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!prv_is_alive()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If not written to whithin 5 seconds of power-on the PMIC will shut down.
|
||||
//i2c_write_register(I2C_DEVICE_MAX14690, MAX14690_ADDR, PmicRegisters_HAND_SHK, 0x01);
|
||||
|
||||
// Power up 3.2V rail
|
||||
prv_update_rail_state(PmicRail_LDO2, true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool prv_update_rail_state(PmicRail rail, bool enable) {
|
||||
static int8_t s_ldo2_ref_count = 0;
|
||||
static int8_t s_ldo3_ref_count = 0;
|
||||
|
||||
int8_t *ref_count;
|
||||
uint8_t rail_control_reg;
|
||||
|
||||
if (rail == PmicRail_LDO2) {
|
||||
rail_control_reg = PmicRegisters_LDO2_CONFIG;
|
||||
ref_count = &s_ldo2_ref_count;
|
||||
} else if (rail == PmicRail_LDO3) {
|
||||
rail_control_reg = PmicRegisters_LDO3_CONFIG;
|
||||
ref_count = &s_ldo3_ref_count;
|
||||
} else {
|
||||
WTF;
|
||||
}
|
||||
|
||||
uint8_t register_value;
|
||||
bool success = prv_read_register(rail_control_reg, ®ister_value);
|
||||
|
||||
if (!success) {
|
||||
// Failed to read the current register value
|
||||
return false;
|
||||
}
|
||||
|
||||
if (enable) {
|
||||
if (*ref_count) {
|
||||
(*ref_count)++;
|
||||
return true;
|
||||
} else {
|
||||
// Set the register byte to XXXXX01X to enable the rail, mask and set
|
||||
register_value = (register_value & ~0x06) | 0x02;
|
||||
|
||||
success = prv_write_register(rail_control_reg, register_value);
|
||||
|
||||
if (success) {
|
||||
// We enabled the rail!
|
||||
*ref_count = 1;
|
||||
|
||||
// We need to wait a bit for the rail to stabilize before continuing to use the device.
|
||||
// It takes 2.6ms for the LDO rails to ramp.
|
||||
delay_ms(3);
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (*ref_count <= 1) {
|
||||
// Set the register byte to XXXXX00X to disable the rail, just mask
|
||||
register_value = (register_value & ~0x06);
|
||||
|
||||
success = prv_write_register(rail_control_reg, register_value);
|
||||
|
||||
if (success) {
|
||||
// We disabled the rail!
|
||||
*ref_count = 0;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
(*ref_count)--;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool pmic_power_off(void) {
|
||||
bool ret = prv_write_register(PmicRegisters_PWR_CFG, 0xB2);
|
||||
|
||||
if (ret) {
|
||||
// Goodbye cruel world. The PMIC should be removing our power at any time now.
|
||||
|
||||
while(1);
|
||||
__builtin_unreachable();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool prv_set_mon_config_register(uint8_t value) {
|
||||
return prv_write_register(PmicRegisters_MON_CFG, value);
|
||||
}
|
||||
|
||||
static bool prv_set_mon_config(const PmicMonConfig *config) {
|
||||
const uint8_t ratio_config = 4 - config->ratio; // 4:1 is 0b00, 1:1 is 0b11.
|
||||
|
||||
const uint8_t register_value = (ratio_config << 4) | config->source_config;
|
||||
bool result = prv_set_mon_config_register(register_value);
|
||||
|
||||
// Need to wait a short period of time for the reading to settle due to capacitance on the line.
|
||||
delay_us(200);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool pmic_enable_battery_measure(void) {
|
||||
prv_mon_config_lock();
|
||||
|
||||
return prv_set_mon_config(&MON_CONFIG[PMIC_MON_CONFIG_VBAT_INDEX]);
|
||||
|
||||
// Don't prv_unlock, we don't want anyone else mucking with the mon config until
|
||||
// pmic_disable_battery_measure is called.
|
||||
}
|
||||
|
||||
bool pmic_disable_battery_measure(void) {
|
||||
bool result = prv_set_mon_config_register(0);
|
||||
|
||||
// Releases the lock that was previously aquired in pmic_enable_battery_measure.
|
||||
prv_mon_config_unlock();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool pmic_set_charger_state(bool enable) {
|
||||
// Defaults to ON
|
||||
// Default value is 0xF7
|
||||
const uint8_t register_value = enable ? 0xf7 : 0xf6;
|
||||
|
||||
bool result = prv_write_register(PmicRegisters_CHG_CNTL_A, register_value);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
bool pmic_is_charging(void) {
|
||||
uint8_t val;
|
||||
if (!prv_read_register(PmicRegisters_STATUSA, &val)) {
|
||||
// NOTE: When running on QEMU, i2c reads return false. For now, just assume a failed
|
||||
// i2c read means we are charging
|
||||
return true;
|
||||
}
|
||||
|
||||
uint8_t chgstat = val & 0x07;
|
||||
|
||||
// TODO: Confirm that all of these values == our definition of charging
|
||||
if (chgstat == 0x02 || chgstat == 0x03 || chgstat == 0x04 ||
|
||||
chgstat == 0x05 || chgstat == 0x06) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool pmic_is_usb_connected(void) {
|
||||
// TODO: Uncomment when there is i2c support in the bootloader
|
||||
uint8_t val;
|
||||
if (!prv_read_register(PmicRegisters_STATUSB, &val)) {
|
||||
// NOTE: When running on QEMU, i2c reads return false. For now, just assume a failed
|
||||
// i2c read means we are connected to a USB cable
|
||||
return true;
|
||||
}
|
||||
|
||||
bool usb_connected = (val >> 3) & 1;
|
||||
|
||||
return usb_connected;
|
||||
}
|
||||
|
||||
void pmic_read_chip_info(uint8_t *chip_id, uint8_t *chip_revision) {
|
||||
prv_read_register(PmicRegisters_CHIP_ID, chip_id);
|
||||
prv_read_register(PmicRegisters_CHIP_REV, chip_revision);
|
||||
}
|
||||
|
||||
|
||||
/* Private Function Implementations */
|
||||
static bool prv_is_alive(void) {
|
||||
uint8_t val;
|
||||
prv_read_register(0x00, &val);
|
||||
|
||||
if (val == 0x01) {
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Found the max14690");
|
||||
return true;
|
||||
} else {
|
||||
PBL_LOG(LOG_LEVEL_DEBUG,
|
||||
"Error: read max14690 whomai byte 0x%x, expecting 0x%x", val, 0x01);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static bool prv_set_pin_config(void) {
|
||||
periph_config_acquire_lock();
|
||||
gpio_use(GPIOB);
|
||||
|
||||
GPIO_InitTypeDef gpio_init_struct;
|
||||
gpio_init_struct.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_9;
|
||||
gpio_init_struct.GPIO_Mode = GPIO_Mode_AF;
|
||||
gpio_init_struct.GPIO_Speed = GPIO_Speed_50MHz;
|
||||
gpio_init_struct.GPIO_OType = GPIO_OType_OD;
|
||||
gpio_init_struct.GPIO_PuPd = GPIO_PuPd_NOPULL;
|
||||
GPIO_Init(GPIOB, &gpio_init_struct);
|
||||
|
||||
// I2C config
|
||||
GPIO_PinAFConfig(GPIOB, GPIO_PinSource6, GPIO_AF_I2C1);
|
||||
GPIO_PinAFConfig(GPIOB, GPIO_PinSource9, GPIO_AF_I2C1);
|
||||
|
||||
gpio_release(GPIOB);
|
||||
|
||||
// Initialize the GPIOs for the 4V5, 6V6, and accessory rails
|
||||
gpio_use(GPIOF);
|
||||
gpio_init_struct.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_13;
|
||||
gpio_init_struct.GPIO_Mode = GPIO_Mode_OUT;
|
||||
gpio_init_struct.GPIO_Speed = GPIO_Speed_50MHz;
|
||||
gpio_init_struct.GPIO_OType = GPIO_OType_PP;
|
||||
gpio_init_struct.GPIO_PuPd = GPIO_PuPd_NOPULL;
|
||||
|
||||
GPIO_Init(GPIOF, &gpio_init_struct);
|
||||
gpio_release(GPIOF);
|
||||
|
||||
periph_config_release_lock();
|
||||
|
||||
// FIXME: We should probably turn this on on-demand instead of leaving it on all the time.
|
||||
i2c_use(I2C_DEVICE_MAX14690);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void set_ldo3_power_state(bool enabled) {
|
||||
i2c_use(I2C_DEVICE_MAX14690);
|
||||
prv_update_rail_state(PmicRail_LDO3, enabled);
|
||||
i2c_release(I2C_DEVICE_MAX14690);
|
||||
}
|
||||
|
||||
void set_4V5_power_state(bool enabled) {
|
||||
gpio_use(GPIOF);
|
||||
GPIO_WriteBit(GPIOF, GPIO_Pin_2, enabled?Bit_SET:Bit_RESET);
|
||||
gpio_release(GPIOF);
|
||||
}
|
||||
|
||||
void set_6V6_power_state(bool enabled) {
|
||||
gpio_use(GPIOF);
|
||||
GPIO_WriteBit(GPIOF, GPIO_Pin_3, enabled?Bit_SET:Bit_RESET);
|
||||
gpio_release(GPIOF);
|
||||
}
|
||||
|
||||
void set_accessory_power_state(bool enabled) {
|
||||
gpio_use(GPIOF);
|
||||
GPIO_WriteBit(GPIOF, GPIO_Pin_13, enabled?Bit_SET:Bit_RESET);
|
||||
gpio_release(GPIOF);
|
||||
}
|
30
platform/snowy/boot/src/drivers/spi.h
Normal file
30
platform/snowy/boot/src/drivers/spi.h
Normal file
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
typedef enum {
|
||||
SpiPeriphClockAPB1,
|
||||
SpiPeriphClockAPB2
|
||||
} SpiPeriphClock;
|
||||
|
||||
//! @internal
|
||||
//! Get the nearest SPI prescaler. Updates bus_frequency with the actual frequency
|
||||
//! @param bus_frequency the desired bus frequency
|
||||
//! @param periph_clock The peripheral clock that is used.
|
||||
uint16_t spi_find_prescaler(uint32_t bus_frequency, SpiPeriphClock periph_clock);
|
97
platform/snowy/boot/src/drivers/stm32_common/button.c
Normal file
97
platform/snowy/boot/src/drivers/stm32_common/button.c
Normal file
|
@ -0,0 +1,97 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "drivers/button.h"
|
||||
|
||||
#include "board/board.h"
|
||||
#include "drivers/periph_config.h"
|
||||
#include "drivers/gpio.h"
|
||||
|
||||
static void initialize_button_common(void) {
|
||||
if (!BOARD_CONFIG_BUTTON.button_com.gpio) {
|
||||
// This board doesn't use a button common pin.
|
||||
return;
|
||||
}
|
||||
|
||||
// Configure BUTTON_COM to drive low. When the button
|
||||
// is pressed this pin will be connected to the pin for the
|
||||
// button.
|
||||
gpio_use(BOARD_CONFIG_BUTTON.button_com.gpio);
|
||||
|
||||
GPIO_InitTypeDef GPIO_InitStructure;
|
||||
GPIO_StructInit(&GPIO_InitStructure);
|
||||
|
||||
GPIO_InitStructure.GPIO_Pin = BOARD_CONFIG_BUTTON.button_com.gpio_pin;
|
||||
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
|
||||
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
|
||||
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
|
||||
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
|
||||
GPIO_Init(BOARD_CONFIG_BUTTON.button_com.gpio, &GPIO_InitStructure);
|
||||
GPIO_WriteBit(BOARD_CONFIG_BUTTON.button_com.gpio, BOARD_CONFIG_BUTTON.button_com.gpio_pin, 0);
|
||||
|
||||
gpio_release(BOARD_CONFIG_BUTTON.button_com.gpio);
|
||||
}
|
||||
|
||||
static void initialize_button(const ButtonConfig* config) {
|
||||
// Configure the pin itself
|
||||
gpio_use(config->gpio);
|
||||
|
||||
GPIO_InitTypeDef GPIO_InitStructure;
|
||||
GPIO_StructInit(&GPIO_InitStructure);
|
||||
|
||||
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
|
||||
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
|
||||
GPIO_InitStructure.GPIO_PuPd = config->pull;
|
||||
GPIO_InitStructure.GPIO_Pin = config->gpio_pin;
|
||||
GPIO_Init(config->gpio, &GPIO_InitStructure);
|
||||
|
||||
gpio_release(config->gpio);
|
||||
}
|
||||
|
||||
bool button_is_pressed(ButtonId id) {
|
||||
const ButtonConfig* button_config = &BOARD_CONFIG_BUTTON.buttons[id];
|
||||
gpio_use(button_config->gpio);
|
||||
uint8_t bit = GPIO_ReadInputDataBit(button_config->gpio, button_config->gpio_pin);
|
||||
gpio_release(button_config->gpio);
|
||||
return !bit;
|
||||
}
|
||||
|
||||
uint8_t button_get_state_bits(void) {
|
||||
uint8_t button_state = 0x00;
|
||||
for (int i = 0; i < NUM_BUTTONS; ++i) {
|
||||
button_state |= (button_is_pressed(i) ? 0x01 : 0x00) << i;
|
||||
}
|
||||
return button_state;
|
||||
}
|
||||
|
||||
void button_init(void) {
|
||||
periph_config_acquire_lock();
|
||||
|
||||
periph_config_enable(RCC_APB2PeriphClockCmd, RCC_APB2Periph_SYSCFG);
|
||||
|
||||
initialize_button_common();
|
||||
for (int i = 0; i < NUM_BUTTONS; ++i) {
|
||||
initialize_button(&BOARD_CONFIG_BUTTON.buttons[i]);
|
||||
}
|
||||
|
||||
periph_config_disable(RCC_APB2PeriphClockCmd, RCC_APB2Periph_SYSCFG);
|
||||
|
||||
periph_config_release_lock();
|
||||
}
|
||||
|
||||
bool button_selftest(void) {
|
||||
return button_get_state_bits() == 0;
|
||||
}
|
160
platform/snowy/boot/src/drivers/stm32_common/crc.c
Normal file
160
platform/snowy/boot/src/drivers/stm32_common/crc.c
Normal file
|
@ -0,0 +1,160 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "drivers/crc.h"
|
||||
|
||||
#include "drivers/flash.h"
|
||||
#include "drivers/periph_config.h"
|
||||
#include "system/passert.h"
|
||||
|
||||
#if defined(MICRO_FAMILY_STM32F2)
|
||||
#include "stm32f2xx_crc.h"
|
||||
#include "stm32f2xx_rcc.h"
|
||||
#elif defined(MICRO_FAMILY_STM32F4)
|
||||
#include "stm32f4xx_crc.h"
|
||||
#include "stm32f4xx_rcc.h"
|
||||
#endif
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
static bool s_initialized = false;
|
||||
static bool s_clock_running = false;
|
||||
|
||||
static void enable_crc_clock(void) {
|
||||
// save the state so that if stop mode interrupts things, we resume cleanly
|
||||
s_clock_running = true;
|
||||
|
||||
periph_config_enable(RCC_AHB1PeriphClockCmd, RCC_AHB1Periph_CRC);
|
||||
}
|
||||
|
||||
static void disable_crc_clock(void) {
|
||||
// save the state so that if stop mode interrupts things, we resume cleanly
|
||||
s_clock_running = false;
|
||||
|
||||
periph_config_disable(RCC_AHB1PeriphClockCmd, RCC_AHB1Periph_CRC);
|
||||
}
|
||||
|
||||
void crc_init(void) {
|
||||
if (s_initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
s_initialized = true;
|
||||
}
|
||||
|
||||
void crc_calculate_incremental_start(void) {
|
||||
PBL_ASSERTN(s_initialized);
|
||||
|
||||
enable_crc_clock();
|
||||
CRC_ResetDR();
|
||||
}
|
||||
|
||||
static void crc_calculate_incremental_words(const uint32_t* data, unsigned int data_length) {
|
||||
PBL_ASSERTN(s_initialized);
|
||||
|
||||
CRC_CalcBlockCRC((uint32_t*) data, data_length);
|
||||
}
|
||||
|
||||
static uint32_t crc_calculate_incremental_remaining_bytes(const uint8_t* data, unsigned int data_length) {
|
||||
PBL_ASSERTN(s_initialized);
|
||||
uint32_t crc_value;
|
||||
|
||||
if (data_length >= 4) {
|
||||
const unsigned int num_words = data_length / 4;
|
||||
crc_calculate_incremental_words((uint32_t*) data, num_words);
|
||||
|
||||
data += num_words * 4;
|
||||
data_length -= num_words * 4;
|
||||
}
|
||||
|
||||
if (data_length) {
|
||||
uint32_t last_word = 0;
|
||||
for (unsigned int i = 0; i < data_length; ++i) {
|
||||
last_word = (last_word << 8) | data[i];
|
||||
}
|
||||
crc_value = CRC_CalcCRC(last_word);
|
||||
} else {
|
||||
crc_value = CRC_GetCRC();
|
||||
}
|
||||
|
||||
return crc_value;
|
||||
}
|
||||
|
||||
void crc_calculate_incremental_stop(void) {
|
||||
PBL_ASSERTN(s_initialized);
|
||||
|
||||
disable_crc_clock();
|
||||
}
|
||||
|
||||
uint32_t crc_calculate_bytes(const uint8_t* data, unsigned int data_length) {
|
||||
crc_calculate_incremental_start();
|
||||
|
||||
// First calculate the CRC of the whole words, since the hardware works 4
|
||||
// bytes at a time.
|
||||
uint32_t* data_words = (uint32_t*) data;
|
||||
const unsigned int num_words = data_length / 4;
|
||||
crc_calculate_incremental_words(data_words, num_words);
|
||||
|
||||
const unsigned int num_remaining_bytes = data_length % 4;
|
||||
const uint32_t res = crc_calculate_incremental_remaining_bytes(data + (num_words * 4), num_remaining_bytes);
|
||||
crc_calculate_incremental_stop();
|
||||
|
||||
return (res);
|
||||
}
|
||||
|
||||
uint32_t crc_calculate_flash(uint32_t address, unsigned int num_bytes) {
|
||||
crc_calculate_incremental_start();
|
||||
const unsigned int chunk_size = 128;
|
||||
|
||||
uint8_t buffer[chunk_size];
|
||||
while (num_bytes > chunk_size) {
|
||||
|
||||
flash_read_bytes(buffer, address, chunk_size);
|
||||
crc_calculate_incremental_words((const uint32_t*) buffer, chunk_size / 4);
|
||||
|
||||
num_bytes -= chunk_size;
|
||||
address += chunk_size;
|
||||
}
|
||||
|
||||
flash_read_bytes(buffer, address, num_bytes);
|
||||
const uint32_t res = crc_calculate_incremental_remaining_bytes(buffer, num_bytes);
|
||||
crc_calculate_incremental_stop();
|
||||
|
||||
return (res);
|
||||
}
|
||||
|
||||
uint8_t crc8_calculate_bytes(const uint8_t *data, unsigned int data_len) {
|
||||
// Optimal polynomial chosen based on
|
||||
// http://users.ece.cmu.edu/~koopman/roses/dsn04/koopman04_crc_poly_embedded.pdf
|
||||
// Note that this is different than the standard CRC-8 polynomial, because the
|
||||
// standard CRC-8 polynomial is not particularly good.
|
||||
|
||||
// nibble lookup table for (x^8 + x^5 + x^3 + x^2 + x + 1)
|
||||
static const uint8_t lookup_table[] =
|
||||
{ 0, 47, 94, 113, 188, 147, 226, 205, 87, 120, 9, 38, 235, 196,
|
||||
181, 154 };
|
||||
|
||||
uint16_t crc = 0;
|
||||
for (int i = data_len * 2; i > 0; i--) {
|
||||
uint8_t nibble = data[(i - 1)/ 2];
|
||||
if (i % 2 == 0) {
|
||||
nibble >>= 4;
|
||||
}
|
||||
int index = nibble ^ (crc >> 4);
|
||||
crc = lookup_table[index & 0xf] ^ (crc << 4);
|
||||
}
|
||||
return crc;
|
||||
}
|
206
platform/snowy/boot/src/drivers/stm32_common/dbgserial.c
Normal file
206
platform/snowy/boot/src/drivers/stm32_common/dbgserial.c
Normal file
|
@ -0,0 +1,206 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "drivers/dbgserial.h"
|
||||
|
||||
#include "drivers/periph_config.h"
|
||||
#include "system/passert.h"
|
||||
|
||||
#include "drivers/gpio.h"
|
||||
|
||||
#if defined(MICRO_FAMILY_STM32F2)
|
||||
#include "stm32f2xx_rcc.h"
|
||||
#include "stm32f2xx_gpio.h"
|
||||
#include "stm32f2xx_usart.h"
|
||||
#elif defined(MICRO_FAMILY_STM32F4)
|
||||
#include "stm32f4xx_rcc.h"
|
||||
#include "stm32f4xx_gpio.h"
|
||||
#include "stm32f4xx_usart.h"
|
||||
#endif
|
||||
#include "util/attributes.h"
|
||||
#include "util/cobs.h"
|
||||
#include "util/crc32.h"
|
||||
#include "util/net.h"
|
||||
#include "util/misc.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#define MAX_MESSAGE (256)
|
||||
#define FRAME_DELIMITER '\x55'
|
||||
#define PULSE_TRANSPORT_PUSH (0x5021)
|
||||
#define PULSE_PROTOCOL_LOGGING (0x0003)
|
||||
|
||||
static bool s_initialized;
|
||||
|
||||
static const int SERIAL_BAUD_RATE = 1000000;
|
||||
|
||||
typedef struct PACKED PulseFrame {
|
||||
net16 protocol;
|
||||
unsigned char information[];
|
||||
} PulseFrame;
|
||||
|
||||
typedef struct PACKED PushPacket {
|
||||
net16 protocol;
|
||||
net16 length;
|
||||
unsigned char information[];
|
||||
} PushPacket;
|
||||
|
||||
static const unsigned char s_message_header[] = {
|
||||
// Message type: text
|
||||
1,
|
||||
// Source filename
|
||||
'B', 'O', 'O', 'T', 'L', 'O', 'A', 'D', 'E', 'R', 0, 0, 0, 0, 0, 0,
|
||||
// Log level and task
|
||||
'*', '*',
|
||||
// Timestamp
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
// Line number
|
||||
0, 0,
|
||||
};
|
||||
|
||||
static size_t s_message_length = 0;
|
||||
static unsigned char s_message_buffer[MAX_MESSAGE];
|
||||
|
||||
|
||||
void dbgserial_init(void) {
|
||||
GPIO_InitTypeDef GPIO_InitStructure;
|
||||
USART_InitTypeDef USART_InitStructure;
|
||||
|
||||
periph_config_acquire_lock();
|
||||
|
||||
/* Enable GPIO and UART3 peripheral clocks */
|
||||
gpio_use(GPIOC);
|
||||
periph_config_enable(RCC_APB1PeriphClockCmd, RCC_APB1Periph_USART3);
|
||||
|
||||
//USART_OverSampling8Cmd(USART3, ENABLE);
|
||||
|
||||
/* Connect PXx to USARTx_Tx*/
|
||||
GPIO_PinAFConfig(GPIOC, GPIO_PinSource10, GPIO_AF_USART3);
|
||||
|
||||
/* Connect PXx to USARTx_Rx*/
|
||||
GPIO_PinAFConfig(GPIOC, GPIO_PinSource11, GPIO_AF_USART3);
|
||||
|
||||
/* Configure USART Tx as alternate function */
|
||||
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
|
||||
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
|
||||
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
|
||||
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
|
||||
|
||||
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
|
||||
GPIO_Init(GPIOC, &GPIO_InitStructure);
|
||||
|
||||
/* Configure USART Rx as alternate function */
|
||||
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
|
||||
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
|
||||
GPIO_Init(GPIOC, &GPIO_InitStructure);
|
||||
|
||||
/* USART configuration */
|
||||
USART_InitStructure.USART_BaudRate = SERIAL_BAUD_RATE;
|
||||
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
|
||||
USART_InitStructure.USART_StopBits = USART_StopBits_1;
|
||||
USART_InitStructure.USART_Parity = USART_Parity_No;
|
||||
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
|
||||
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
|
||||
USART_Init(USART3, &USART_InitStructure);
|
||||
|
||||
/* Enable USART */
|
||||
USART_Cmd(USART3, ENABLE);
|
||||
|
||||
periph_config_release_lock();
|
||||
gpio_release(GPIOC);
|
||||
|
||||
s_initialized = true;
|
||||
}
|
||||
|
||||
static void prv_putchar(uint8_t c) {
|
||||
if (!s_initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
while (USART_GetFlagStatus(USART3, USART_FLAG_TC) == RESET) continue;
|
||||
USART_SendData(USART3, c);
|
||||
while (USART_GetFlagStatus(USART3, USART_FLAG_TC) == RESET) continue;
|
||||
}
|
||||
|
||||
static void prv_flush(void) {
|
||||
uint32_t crc;
|
||||
size_t raw_length = sizeof(PulseFrame) + sizeof(PushPacket) +
|
||||
sizeof(s_message_header) + s_message_length + sizeof(crc);
|
||||
unsigned char raw_packet[raw_length];
|
||||
|
||||
PulseFrame *frame = (PulseFrame *)raw_packet;
|
||||
frame->protocol = hton16(PULSE_TRANSPORT_PUSH);
|
||||
|
||||
PushPacket *transport = (PushPacket *)frame->information;
|
||||
transport->protocol = hton16(PULSE_PROTOCOL_LOGGING);
|
||||
transport->length = hton16(sizeof(PushPacket) + sizeof(s_message_header) +
|
||||
s_message_length);
|
||||
|
||||
unsigned char *app = transport->information;
|
||||
memcpy(app, s_message_header, sizeof(s_message_header));
|
||||
memcpy(&app[sizeof(s_message_header)], s_message_buffer,
|
||||
s_message_length);
|
||||
|
||||
crc = crc32(CRC32_INIT, raw_packet, raw_length - sizeof(crc));
|
||||
memcpy(&raw_packet[raw_length - sizeof(crc)], &crc, sizeof(crc));
|
||||
|
||||
unsigned char cooked_packet[MAX_SIZE_AFTER_COBS_ENCODING(raw_length)];
|
||||
size_t cooked_length = cobs_encode(cooked_packet, raw_packet, raw_length);
|
||||
|
||||
prv_putchar(FRAME_DELIMITER);
|
||||
for (size_t i = 0; i < cooked_length; ++i) {
|
||||
if (cooked_packet[i] == FRAME_DELIMITER) {
|
||||
prv_putchar('\0');
|
||||
} else {
|
||||
prv_putchar(cooked_packet[i]);
|
||||
}
|
||||
}
|
||||
prv_putchar(FRAME_DELIMITER);
|
||||
|
||||
s_message_length = 0;
|
||||
}
|
||||
|
||||
void dbgserial_putstr(const char* str) {
|
||||
if (!s_initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
dbgserial_print(str);
|
||||
prv_flush();
|
||||
}
|
||||
|
||||
void dbgserial_print(const char* str) {
|
||||
if (!s_initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (; *str && s_message_length < MAX_MESSAGE; ++str) {
|
||||
if (*str == '\n') {
|
||||
prv_flush();
|
||||
} else if (*str != '\r') {
|
||||
s_message_buffer[s_message_length++] = *str;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void dbgserial_print_hex(uint32_t value) {
|
||||
char buf[12];
|
||||
itoa(value, buf, sizeof(buf));
|
||||
dbgserial_print(buf);
|
||||
}
|
||||
|
80
platform/snowy/boot/src/drivers/stm32_common/gpio.c
Normal file
80
platform/snowy/boot/src/drivers/stm32_common/gpio.c
Normal file
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "drivers/gpio.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define MAX_GPIO (9)
|
||||
|
||||
|
||||
static uint8_t s_gpio_clock_count[MAX_GPIO];
|
||||
|
||||
void gpio_use(GPIO_TypeDef* GPIOx) {
|
||||
uint8_t idx = ((((uint32_t)GPIOx) - AHB1PERIPH_BASE) / 0x0400);
|
||||
if((idx < MAX_GPIO) && !(s_gpio_clock_count[idx]++)) {
|
||||
SET_BIT(RCC->AHB1ENR, (0x1 << idx));
|
||||
}
|
||||
}
|
||||
|
||||
void gpio_release(GPIO_TypeDef* GPIOx) {
|
||||
uint8_t idx = ((((uint32_t)GPIOx) - AHB1PERIPH_BASE) / 0x0400);
|
||||
if((idx < MAX_GPIO) && s_gpio_clock_count[idx] && !(--s_gpio_clock_count[idx])) {
|
||||
CLEAR_BIT(RCC->AHB1ENR, (0x1 << idx));
|
||||
}
|
||||
}
|
||||
|
||||
void gpio_output_init(OutputConfig pin_config, GPIOOType_TypeDef otype,
|
||||
GPIOSpeed_TypeDef speed) {
|
||||
GPIO_InitTypeDef init = {
|
||||
.GPIO_Pin = pin_config.gpio_pin,
|
||||
.GPIO_Mode = GPIO_Mode_OUT,
|
||||
.GPIO_Speed = speed,
|
||||
.GPIO_OType = otype,
|
||||
.GPIO_PuPd = GPIO_PuPd_NOPULL
|
||||
};
|
||||
|
||||
gpio_use(pin_config.gpio);
|
||||
GPIO_Init(pin_config.gpio, &init);
|
||||
gpio_release(pin_config.gpio);
|
||||
}
|
||||
|
||||
void gpio_output_set(OutputConfig pin_config, bool asserted) {
|
||||
if (!pin_config.active_high) {
|
||||
asserted = !asserted;
|
||||
}
|
||||
gpio_use(pin_config.gpio);
|
||||
GPIO_WriteBit(pin_config.gpio, pin_config.gpio_pin,
|
||||
asserted? Bit_SET : Bit_RESET);
|
||||
gpio_release(pin_config.gpio);
|
||||
}
|
||||
|
||||
void gpio_af_init(AfConfig af_config, GPIOOType_TypeDef otype,
|
||||
GPIOSpeed_TypeDef speed, GPIOPuPd_TypeDef pupd) {
|
||||
GPIO_InitTypeDef init = {
|
||||
.GPIO_Pin = af_config.gpio_pin,
|
||||
.GPIO_Mode = GPIO_Mode_AF,
|
||||
.GPIO_Speed = speed,
|
||||
.GPIO_OType = otype,
|
||||
.GPIO_PuPd = pupd
|
||||
};
|
||||
|
||||
gpio_use(af_config.gpio);
|
||||
GPIO_Init(af_config.gpio, &init);
|
||||
GPIO_PinAFConfig(af_config.gpio, af_config.gpio_pin_source,
|
||||
af_config.gpio_af);
|
||||
gpio_release(af_config.gpio);
|
||||
}
|
772
platform/snowy/boot/src/drivers/stm32_common/i2c.c
Normal file
772
platform/snowy/boot/src/drivers/stm32_common/i2c.c
Normal file
|
@ -0,0 +1,772 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "util/misc.h"
|
||||
#include "drivers/i2c.h"
|
||||
#include "drivers/periph_config.h"
|
||||
#include "drivers/gpio.h"
|
||||
#include "system/passert.h"
|
||||
#include "system/logging.h"
|
||||
#include "util/delay.h"
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
#if defined(MICRO_FAMILY_STM32F2)
|
||||
#include "stm32f2xx_gpio.h"
|
||||
#include "stm32f2xx_rcc.h"
|
||||
#include "stm32f2xx_i2c.h"
|
||||
#elif defined(MICRO_FAMILY_STM32F4)
|
||||
#include "stm32f4xx_gpio.h"
|
||||
#include "stm32f4xx_rcc.h"
|
||||
#include "stm32f4xx_i2c.h"
|
||||
#include "drivers/pmic.h"
|
||||
#endif
|
||||
|
||||
#define portBASE_TYPE int
|
||||
#define pdFALSE 0
|
||||
#define portEND_SWITCHING_ISR(expr) (void)(expr)
|
||||
|
||||
#define I2C_ERROR_TIMEOUT_MS (1000)
|
||||
#define I2C_TIMEOUT_ATTEMPTS_MAX (2 * 1000 * 1000)
|
||||
#define I2C_NORMAL_MODE_CLOCK_SPEED_MAX (100000)
|
||||
#define I2C_NACK_COUNT_MAX (1000) // MFI NACKs while busy. We delay ~1ms between retries so this is approximately a 1s timeout
|
||||
|
||||
#define I2C_READ_WRITE_BIT (0x01)
|
||||
|
||||
typedef struct I2cTransfer {
|
||||
uint8_t device_address;
|
||||
bool read_not_write; //True for read, false for write
|
||||
uint8_t register_address;
|
||||
uint8_t size;
|
||||
uint8_t idx;
|
||||
uint8_t *data;
|
||||
enum TransferState {
|
||||
TRANSFER_STATE_WRITE_ADDRESS_TX,
|
||||
TRANSFER_STATE_WRITE_REG_ADDRESS,
|
||||
TRANSFER_STATE_REPEAT_START,
|
||||
TRANSFER_STATE_WRITE_ADDRESS_RX,
|
||||
TRANSFER_STATE_WAIT_FOR_DATA,
|
||||
TRANSFER_STATE_READ_DATA,
|
||||
TRANSFER_STATE_WRITE_DATA,
|
||||
TRANSFER_STATE_END_WRITE,
|
||||
|
||||
TRANSFER_STATE_INVALID,
|
||||
} state;
|
||||
bool result;
|
||||
uint16_t nack_count;
|
||||
}I2cTransfer;
|
||||
|
||||
typedef struct I2cBus{
|
||||
I2C_TypeDef *i2c;
|
||||
uint8_t user_count;
|
||||
I2cTransfer transfer;
|
||||
volatile bool busy;
|
||||
}I2cBus;
|
||||
|
||||
static I2cBus i2c_buses[BOARD_I2C_BUS_COUNT];
|
||||
|
||||
static uint32_t s_guard_events[] = {
|
||||
I2C_EVENT_MASTER_MODE_SELECT,
|
||||
I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED,
|
||||
I2C_EVENT_MASTER_BYTE_TRANSMITTED,
|
||||
I2C_EVENT_MASTER_MODE_SELECT,
|
||||
I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED,
|
||||
I2C_EVENT_MASTER_BYTE_RECEIVED,
|
||||
I2C_EVENT_MASTER_BYTE_TRANSMITTING,
|
||||
I2C_EVENT_MASTER_BYTE_TRANSMITTED,
|
||||
};
|
||||
|
||||
static bool s_initialized = false;
|
||||
|
||||
/*----------------SEMAPHORE/LOCKING FUNCTIONS--------------------------*/
|
||||
|
||||
static void bus_lock(I2cBus *bus) {
|
||||
}
|
||||
|
||||
static void bus_unlock(I2cBus *bus) {
|
||||
}
|
||||
|
||||
static bool semaphore_take(I2cBus *bus) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool semaphore_wait(I2cBus *bus) {
|
||||
bus->busy = true;
|
||||
volatile uint32_t timeout_attempts = I2C_TIMEOUT_ATTEMPTS_MAX;
|
||||
while ((timeout_attempts-- > 0) && (bus->busy));
|
||||
bus->busy = false;
|
||||
return (timeout_attempts != 0);
|
||||
}
|
||||
|
||||
static void semaphore_give(I2cBus *bus) {
|
||||
}
|
||||
|
||||
/*-------------------BUS/PIN CONFIG FUNCTIONS--------------------------*/
|
||||
|
||||
//! Configure bus power supply control pin as output
|
||||
//! Lock bus and peripheral config access before configuring pins
|
||||
void i2c_bus_rail_ctl_config(OutputConfig pin_config) {
|
||||
gpio_use(pin_config.gpio);
|
||||
|
||||
GPIO_InitTypeDef GPIO_InitStructure;
|
||||
GPIO_StructInit(&GPIO_InitStructure);
|
||||
|
||||
GPIO_InitStructure.GPIO_Pin = pin_config.gpio_pin;
|
||||
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
|
||||
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
|
||||
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
|
||||
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
|
||||
GPIO_Init(pin_config.gpio, &GPIO_InitStructure);
|
||||
|
||||
gpio_release(pin_config.gpio);
|
||||
}
|
||||
|
||||
//! Configure bus pins for use by I2C peripheral
|
||||
//! Lock bus and peripheral config access before configuring pins
|
||||
static void bus_pin_cfg_i2c(AfConfig pin_config) {
|
||||
gpio_use(pin_config.gpio);
|
||||
|
||||
GPIO_InitTypeDef gpio_init_struct;
|
||||
gpio_init_struct.GPIO_Pin = pin_config.gpio_pin;
|
||||
gpio_init_struct.GPIO_Mode = GPIO_Mode_AF;
|
||||
gpio_init_struct.GPIO_Speed = GPIO_Speed_50MHz;
|
||||
gpio_init_struct.GPIO_OType = GPIO_OType_OD;
|
||||
gpio_init_struct.GPIO_PuPd = GPIO_PuPd_NOPULL;
|
||||
GPIO_Init(pin_config.gpio, &gpio_init_struct);
|
||||
|
||||
GPIO_PinAFConfig(pin_config.gpio, pin_config.gpio_pin_source, pin_config.gpio_af);
|
||||
|
||||
gpio_release(pin_config.gpio);
|
||||
}
|
||||
|
||||
//! Configure bus pin as input
|
||||
//! Lock bus and peripheral config access before use
|
||||
static void bus_pin_cfg_input(AfConfig pin_config) {
|
||||
gpio_use(pin_config.gpio);
|
||||
|
||||
// Configure pin as high impedance input
|
||||
GPIO_InitTypeDef gpio_init_struct;
|
||||
gpio_init_struct.GPIO_Pin = pin_config.gpio_pin;
|
||||
gpio_init_struct.GPIO_Mode = GPIO_Mode_IN;
|
||||
gpio_init_struct.GPIO_Speed = GPIO_Speed_2MHz;
|
||||
gpio_init_struct.GPIO_PuPd = GPIO_PuPd_NOPULL;
|
||||
GPIO_Init(pin_config.gpio, &gpio_init_struct);
|
||||
|
||||
gpio_release(pin_config.gpio);
|
||||
}
|
||||
|
||||
//! Configure bus pin as output
|
||||
//! Lock bus and peripheral config access before use
|
||||
static void bus_pin_cfg_output(AfConfig pin_config, bool pin_state) {
|
||||
gpio_use(pin_config.gpio);
|
||||
|
||||
// Configure pin as output
|
||||
GPIO_InitTypeDef gpio_init_struct;
|
||||
gpio_init_struct.GPIO_Pin = pin_config.gpio_pin;
|
||||
gpio_init_struct.GPIO_Mode = GPIO_Mode_OUT;
|
||||
gpio_init_struct.GPIO_Speed = GPIO_Speed_2MHz;
|
||||
gpio_init_struct.GPIO_PuPd = GPIO_PuPd_NOPULL;
|
||||
GPIO_Init(pin_config.gpio, &gpio_init_struct);
|
||||
|
||||
// Set bit high or low
|
||||
GPIO_WriteBit(pin_config.gpio, pin_config.gpio_pin, (pin_state) ? Bit_SET : Bit_RESET);
|
||||
|
||||
gpio_release(pin_config.gpio);
|
||||
}
|
||||
|
||||
//! Power down I2C bus power supply
|
||||
//! Always lock bus and peripheral config access before use
|
||||
static void bus_rail_power_down(uint8_t bus_idx) {
|
||||
if (BOARD_CONFIG.i2c_bus_configs[bus_idx].rail_ctl_fn == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
BOARD_CONFIG.i2c_bus_configs[bus_idx].rail_ctl_fn(false);
|
||||
|
||||
// Drain through pull-ups
|
||||
bus_pin_cfg_output(BOARD_CONFIG.i2c_bus_configs[bus_idx].i2c_scl, false);
|
||||
bus_pin_cfg_output(BOARD_CONFIG.i2c_bus_configs[bus_idx].i2c_sda, false);
|
||||
}
|
||||
|
||||
//! Power up I2C bus power supply
|
||||
//! Always lock bus and peripheral config access before use
|
||||
static void bus_rail_power_up(uint8_t bus_idx) {
|
||||
if (BOARD_CONFIG.i2c_bus_configs[bus_idx].rail_ctl_fn == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
// check that at least enough time has elapsed since the last turn-off
|
||||
// TODO: is this necessary in bootloader?
|
||||
static const uint32_t MIN_STOP_TIME_MS = 10;
|
||||
delay_ms(MIN_STOP_TIME_MS);
|
||||
|
||||
bus_pin_cfg_input(BOARD_CONFIG.i2c_bus_configs[bus_idx].i2c_scl);
|
||||
bus_pin_cfg_input(BOARD_CONFIG.i2c_bus_configs[bus_idx].i2c_sda);
|
||||
|
||||
BOARD_CONFIG.i2c_bus_configs[bus_idx].rail_ctl_fn(true);
|
||||
}
|
||||
|
||||
//! Initialize the I2C peripheral
|
||||
//! Lock the bus and peripheral config access before initialization
|
||||
static void bus_init(uint8_t bus_idx) {
|
||||
// Initialize peripheral
|
||||
I2C_InitTypeDef i2c_init_struct;
|
||||
I2C_StructInit(&i2c_init_struct);
|
||||
|
||||
if (BOARD_CONFIG.i2c_bus_configs[bus_idx].clock_speed > I2C_NORMAL_MODE_CLOCK_SPEED_MAX) { //Fast mode
|
||||
i2c_init_struct.I2C_DutyCycle = BOARD_CONFIG.i2c_bus_configs[bus_idx].duty_cycle;
|
||||
}
|
||||
i2c_init_struct.I2C_ClockSpeed = BOARD_CONFIG.i2c_bus_configs[bus_idx].clock_speed;
|
||||
i2c_init_struct.I2C_Ack = I2C_Ack_Enable;
|
||||
|
||||
I2C_Init(i2c_buses[bus_idx].i2c, &i2c_init_struct);
|
||||
I2C_Cmd(i2c_buses[bus_idx].i2c, ENABLE);
|
||||
}
|
||||
|
||||
//! Configure the bus pins, enable the peripheral clock and initialize the I2C peripheral.
|
||||
//! Always lock the bus and peripheral config access before enabling it
|
||||
static void bus_enable(uint8_t bus_idx) {
|
||||
// Don't power up rail if the bus is already in use (enable can be called to reset bus)
|
||||
if (i2c_buses[bus_idx].user_count == 0) {
|
||||
bus_rail_power_up(bus_idx);
|
||||
}
|
||||
|
||||
bus_pin_cfg_i2c(BOARD_CONFIG.i2c_bus_configs[bus_idx].i2c_scl);
|
||||
bus_pin_cfg_i2c(BOARD_CONFIG.i2c_bus_configs[bus_idx].i2c_sda);
|
||||
|
||||
// Enable peripheral clock
|
||||
periph_config_acquire_lock();
|
||||
periph_config_enable(RCC_APB1PeriphClockCmd, BOARD_CONFIG.i2c_bus_configs[bus_idx].clock_ctrl);
|
||||
periph_config_release_lock();
|
||||
|
||||
bus_init(bus_idx);
|
||||
}
|
||||
|
||||
//! De-initialize and gate the clock to the peripheral
|
||||
//! Power down rail if the bus supports that and no devices are using it
|
||||
//! Always lock the bus and peripheral config access before disabling it
|
||||
static void bus_disable(uint8_t bus_idx) {
|
||||
I2C_DeInit(i2c_buses[bus_idx].i2c);
|
||||
|
||||
periph_config_acquire_lock();
|
||||
periph_config_disable(RCC_APB1PeriphClockCmd, BOARD_CONFIG.i2c_bus_configs[bus_idx].clock_ctrl);
|
||||
periph_config_release_lock();
|
||||
|
||||
// Do not de-power rail if there are still devices using bus (just reset peripheral and pin configuration during a bus reset)
|
||||
if (i2c_buses[bus_idx].user_count == 0) {
|
||||
bus_rail_power_down(bus_idx);
|
||||
}
|
||||
else {
|
||||
bus_pin_cfg_input(BOARD_CONFIG.i2c_bus_configs[bus_idx].i2c_scl);
|
||||
bus_pin_cfg_input(BOARD_CONFIG.i2c_bus_configs[bus_idx].i2c_sda);
|
||||
}
|
||||
}
|
||||
|
||||
//! Perform a soft reset of the bus
|
||||
//! Always lock the bus before reset
|
||||
static void bus_reset(uint8_t bus_idx) {
|
||||
bus_disable(bus_idx);
|
||||
bus_enable(bus_idx);
|
||||
}
|
||||
|
||||
/*---------------INIT/USE/RELEASE/RESET FUNCTIONS----------------------*/
|
||||
|
||||
void i2c_init(void) {
|
||||
for (uint32_t i = 0; i < ARRAY_LENGTH(i2c_buses); i++) {
|
||||
i2c_buses[i].i2c = BOARD_CONFIG.i2c_bus_configs[i].i2c;
|
||||
i2c_buses[i].user_count = 0;
|
||||
i2c_buses[i].busy = false;
|
||||
i2c_buses[i].transfer.idx = 0;
|
||||
i2c_buses[i].transfer.size = 0;
|
||||
i2c_buses[i].transfer.data = NULL;
|
||||
i2c_buses[i].transfer.state = TRANSFER_STATE_INVALID;
|
||||
|
||||
NVIC_InitTypeDef NVIC_InitStructure;
|
||||
NVIC_InitStructure.NVIC_IRQChannel = BOARD_CONFIG.i2c_bus_configs[i].ev_irq_channel;
|
||||
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x0c;
|
||||
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x00;
|
||||
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
|
||||
NVIC_Init(&NVIC_InitStructure);
|
||||
|
||||
NVIC_InitStructure.NVIC_IRQChannel = BOARD_CONFIG.i2c_bus_configs[i].er_irq_channel;
|
||||
NVIC_Init(&NVIC_InitStructure);
|
||||
|
||||
I2C_DeInit(i2c_buses[i].i2c);
|
||||
}
|
||||
|
||||
s_initialized = true;
|
||||
|
||||
for (uint32_t i = 0; i < ARRAY_LENGTH(i2c_buses); i++) {
|
||||
if (BOARD_CONFIG.i2c_bus_configs[i].rail_cfg_fn) {
|
||||
BOARD_CONFIG.i2c_bus_configs[i].rail_cfg_fn();
|
||||
}
|
||||
if (BOARD_CONFIG.i2c_bus_configs[i].rail_ctl_fn) {
|
||||
bus_rail_power_down(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void i2c_use(I2cDevice device_id) {
|
||||
PBL_ASSERTN(s_initialized);
|
||||
PBL_ASSERT(device_id < BOARD_CONFIG.i2c_device_count, "I2C device ID out of bounds %d (max: %d)",
|
||||
device_id, BOARD_CONFIG.i2c_device_count);
|
||||
|
||||
uint8_t bus_idx = BOARD_CONFIG.i2c_device_map[device_id];
|
||||
I2cBus *bus = &i2c_buses[bus_idx];
|
||||
|
||||
bus_lock(bus);
|
||||
|
||||
if (bus->user_count == 0) {
|
||||
bus_enable(bus_idx);
|
||||
}
|
||||
bus->user_count++;
|
||||
|
||||
bus_unlock(bus);
|
||||
}
|
||||
|
||||
void i2c_release(I2cDevice device_id) {
|
||||
PBL_ASSERTN(s_initialized);
|
||||
PBL_ASSERTN(device_id < BOARD_CONFIG.i2c_device_count);
|
||||
|
||||
uint8_t bus_idx = BOARD_CONFIG.i2c_device_map[device_id];
|
||||
I2cBus *bus = &i2c_buses[bus_idx];
|
||||
|
||||
bus_lock(bus);
|
||||
|
||||
if (bus->user_count == 0) {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Attempted release of disabled bus %d by device %d", bus_idx, device_id);
|
||||
bus_unlock(bus);
|
||||
return;
|
||||
}
|
||||
|
||||
bus->user_count--;
|
||||
if (bus->user_count == 0) {
|
||||
bus_disable(bus_idx);
|
||||
}
|
||||
|
||||
bus_unlock(bus);
|
||||
}
|
||||
|
||||
void i2c_reset(I2cDevice device_id) {
|
||||
PBL_ASSERTN(s_initialized);
|
||||
PBL_ASSERTN(device_id < BOARD_CONFIG.i2c_device_count);
|
||||
|
||||
uint8_t bus_idx = BOARD_CONFIG.i2c_device_map[device_id];
|
||||
I2cBus *bus = &i2c_buses[bus_idx];
|
||||
|
||||
// Take control of bus; only one task may use bus at a time
|
||||
bus_lock(bus);
|
||||
|
||||
if (bus->user_count == 0) {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Attempted reset of disabled bus %d by device %d", bus_idx, device_id);
|
||||
bus_unlock(bus);
|
||||
return;
|
||||
}
|
||||
|
||||
PBL_LOG(LOG_LEVEL_WARNING, "Resetting I2C bus %" PRId8, bus_idx);
|
||||
|
||||
// decrement user count for reset so that if this user is the only user, the
|
||||
// bus will be powered down during the reset
|
||||
bus->user_count--;
|
||||
|
||||
// Reset and reconfigure bus and pins
|
||||
bus_reset(bus_idx);
|
||||
|
||||
//Restore user count
|
||||
bus->user_count++;
|
||||
|
||||
bus_unlock(bus);
|
||||
}
|
||||
|
||||
/*--------------------DATA TRANSFER FUNCTIONS--------------------------*/
|
||||
|
||||
//! Wait a short amount of time for busy bit to clear
|
||||
static bool wait_for_busy_clear(uint8_t bus_idx) {
|
||||
unsigned int attempts = I2C_TIMEOUT_ATTEMPTS_MAX;
|
||||
while((i2c_buses[bus_idx].i2c->SR2 & I2C_SR2_BUSY) != 0) {
|
||||
--attempts;
|
||||
if (!attempts) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//! Abort the transfer
|
||||
//! Should only be called when the bus is locked
|
||||
static void abort_transfer(I2cBus *bus) {
|
||||
// Disable all interrupts on the bus
|
||||
bus->i2c->CR2 &= ~(I2C_CR2_ITEVTEN | I2C_CR2_ITERREN | I2C_CR2_ITBUFEN);
|
||||
// Generate a stop condition
|
||||
bus->i2c->CR1 |= I2C_CR1_STOP;
|
||||
bus->transfer.state = TRANSFER_STATE_INVALID;
|
||||
}
|
||||
|
||||
//! Set up and start a transfer to a device, wait for it to finish and clean up after the transfer has completed
|
||||
static bool do_transfer(I2cDevice device_id, bool read_not_write, uint8_t device_address, uint8_t register_address, uint8_t size, uint8_t *data) {
|
||||
PBL_ASSERTN(s_initialized);
|
||||
PBL_ASSERTN(device_id < BOARD_CONFIG.i2c_device_count);
|
||||
|
||||
uint8_t bus_idx = BOARD_CONFIG.i2c_device_map[device_id];
|
||||
I2cBus *bus = &i2c_buses[bus_idx];
|
||||
|
||||
// Take control of bus; only one task may use bus at a time
|
||||
bus_lock(bus);
|
||||
|
||||
if (bus->user_count == 0) {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Attempted access to disabled bus %d by device %d", bus_idx, device_id);
|
||||
bus_unlock(bus);
|
||||
return false;
|
||||
}
|
||||
|
||||
// If bus is busy (it shouldn't be as this function waits for the bus to report a non-idle state
|
||||
// before exiting) reset the bus and wait for it to become not-busy
|
||||
// Exit if bus remains busy. User module should reset the I2C module at this point
|
||||
if((bus->i2c->SR2 & I2C_SR2_BUSY) != 0) {
|
||||
bus_reset(bus_idx);
|
||||
|
||||
if (!wait_for_busy_clear(bus_idx)) {
|
||||
// Bus did not recover after reset
|
||||
bus_unlock(bus);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Take binary semaphore so that next take will block
|
||||
PBL_ASSERT(semaphore_take(bus), "Could not acquire semaphore token");
|
||||
|
||||
// Set up transfer
|
||||
bus->transfer.device_address = device_address;
|
||||
bus->transfer.register_address = register_address;
|
||||
bus->transfer.read_not_write = read_not_write;
|
||||
bus->transfer.size = size;
|
||||
bus->transfer.idx = 0;
|
||||
bus->transfer.state = TRANSFER_STATE_WRITE_ADDRESS_TX;
|
||||
bus->transfer.data = data;
|
||||
bus->transfer.nack_count = 0;
|
||||
|
||||
// Ack received bytes
|
||||
I2C_AcknowledgeConfig(bus->i2c, ENABLE);
|
||||
|
||||
bool result = false;
|
||||
|
||||
do {
|
||||
// Generate start event
|
||||
bus->i2c->CR1 |= I2C_CR1_START;
|
||||
//Enable event and error interrupts
|
||||
bus->i2c->CR2 |= I2C_CR2_ITEVTEN | I2C_CR2_ITERREN;
|
||||
|
||||
// Wait on semaphore until it is released by interrupt or a timeout occurs
|
||||
if (semaphore_wait(bus)) {
|
||||
|
||||
if (bus->transfer.state == TRANSFER_STATE_INVALID) {
|
||||
// Transfer is complete
|
||||
result = bus->transfer.result;
|
||||
if (!result) {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "I2C Error on bus %" PRId8, bus_idx);
|
||||
}
|
||||
|
||||
} else if (bus->transfer.nack_count < I2C_NACK_COUNT_MAX) {
|
||||
// NACK received after start condition sent: the MFI chip NACKs start conditions whilst it is busy
|
||||
// Retry start condition after a short delay.
|
||||
// A NACK count is incremented for each NACK received, so that legitimate NACK
|
||||
// errors cause the transfer to be aborted (after the NACK count max has been reached).
|
||||
|
||||
bus->transfer.nack_count++;
|
||||
|
||||
delay_ms(1);
|
||||
|
||||
} else {
|
||||
// Too many NACKs received, abort transfer
|
||||
abort_transfer(bus);
|
||||
break;
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "I2C Error: too many NACKs received on bus %" PRId8, bus_idx);
|
||||
}
|
||||
|
||||
} else {
|
||||
// Timeout, abort transfer
|
||||
abort_transfer(bus);
|
||||
break;
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Transfer timed out on bus %" PRId8, bus_idx);
|
||||
}
|
||||
} while (bus->transfer.state != TRANSFER_STATE_INVALID);
|
||||
|
||||
// Return semaphore token so another transfer can be started
|
||||
semaphore_give(bus);
|
||||
|
||||
// Wait for bus to to clear the busy flag before a new transfer starts
|
||||
// Theoretically a transfer could complete successfully, but the busy flag never clears,
|
||||
// which would cause the next transfer to fail
|
||||
if (!wait_for_busy_clear(bus_idx)) {
|
||||
// Reset I2C bus if busy flag does not clear
|
||||
bus_reset(bus_idx);
|
||||
}
|
||||
|
||||
bus_unlock(bus);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool i2c_read_register(I2cDevice device_id, uint8_t i2c_device_address, uint8_t register_address, uint8_t *result) {
|
||||
return i2c_read_register_block(device_id, i2c_device_address, register_address, 1, result);
|
||||
}
|
||||
|
||||
bool i2c_read_register_block(I2cDevice device_id, uint8_t i2c_device_address, uint8_t
|
||||
register_address_start, uint8_t read_size, uint8_t* result_buffer) {
|
||||
#if defined(TARGET_QEMU)
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "i2c reads on QEMU not supported");
|
||||
return false;
|
||||
#endif
|
||||
|
||||
// Do transfer locks the bus
|
||||
bool result = do_transfer(device_id, true, i2c_device_address, register_address_start, read_size, result_buffer);
|
||||
|
||||
if (!result) {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Read failed on bus %" PRId8, BOARD_CONFIG.i2c_device_map[device_id]);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool i2c_write_register(I2cDevice device_id, uint8_t i2c_device_address, uint8_t register_address,
|
||||
uint8_t value) {
|
||||
return i2c_write_register_block(device_id, i2c_device_address, register_address, 1, &value);
|
||||
}
|
||||
|
||||
bool i2c_write_register_block(I2cDevice device_id, uint8_t i2c_device_address, uint8_t
|
||||
register_address_start, uint8_t write_size, const uint8_t* buffer) {
|
||||
#if defined(TARGET_QEMU)
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "i2c writes on QEMU not supported");
|
||||
return false;
|
||||
#endif
|
||||
|
||||
// Do transfer locks the bus
|
||||
bool result = do_transfer(device_id, false, i2c_device_address, register_address_start, write_size, (uint8_t*)buffer);
|
||||
|
||||
if (!result) {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Write failed on bus %" PRId8, BOARD_CONFIG.i2c_device_map[device_id]);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*------------------------INTERRUPT FUNCTIONS--------------------------*/
|
||||
|
||||
//! End a transfer and disable further interrupts
|
||||
//! Only call from interrupt functions
|
||||
static portBASE_TYPE end_transfer_irq(I2cBus *bus, bool result) {
|
||||
bus->i2c->CR2 &= ~(I2C_CR2_ITEVTEN | I2C_CR2_ITERREN | I2C_CR2_ITBUFEN);
|
||||
bus->i2c->CR1 |= I2C_CR1_STOP;
|
||||
bus->transfer.result = result;
|
||||
bus->transfer.state = TRANSFER_STATE_INVALID;
|
||||
|
||||
bus->busy = false;
|
||||
return pdFALSE;
|
||||
}
|
||||
|
||||
//! Pause a transfer, disabling interrupts during the pause
|
||||
//! Only call from interrupt functions
|
||||
static portBASE_TYPE pause_transfer_irq(I2cBus *bus) {
|
||||
bus->i2c->CR2 &= ~(I2C_CR2_ITEVTEN | I2C_CR2_ITERREN | I2C_CR2_ITBUFEN);
|
||||
bus->busy = false;
|
||||
return pdFALSE;
|
||||
}
|
||||
|
||||
//! Handle an IRQ event on the specified \a bus
|
||||
static portBASE_TYPE irq_event_handler(I2cBus *bus) {
|
||||
if (bus->transfer.state == TRANSFER_STATE_INVALID) {
|
||||
|
||||
// Disable interrupts if spurious interrupt received
|
||||
bus->i2c->CR2 &= ~(I2C_CR2_ITEVTEN | I2C_CR2_ITBUFEN);
|
||||
return pdFALSE;
|
||||
}
|
||||
|
||||
// Check that the expected event occurred
|
||||
if (I2C_CheckEvent(bus->i2c, s_guard_events[bus->transfer.state]) == ERROR) {
|
||||
// Ignore interrupt - A spurious byte transmitted event as well as an interrupt with no
|
||||
// discernible event associated with it occur after repeat start events are generated
|
||||
return pdFALSE;
|
||||
}
|
||||
portBASE_TYPE should_context_switch = pdFALSE;
|
||||
|
||||
switch (bus->transfer.state) {
|
||||
case TRANSFER_STATE_WRITE_ADDRESS_TX:
|
||||
// Write the i2c device address to the bus to select it in write mode.
|
||||
bus->i2c->DR = bus->transfer.device_address & ~I2C_READ_WRITE_BIT;
|
||||
bus->transfer.state = TRANSFER_STATE_WRITE_REG_ADDRESS;
|
||||
break;
|
||||
|
||||
case TRANSFER_STATE_WRITE_REG_ADDRESS:
|
||||
// Write the register address
|
||||
bus->i2c->DR = bus->transfer.register_address;
|
||||
|
||||
if (bus->transfer.read_not_write) {
|
||||
bus->transfer.state = TRANSFER_STATE_REPEAT_START;
|
||||
} else {
|
||||
// Enable TXE interrupt for writing
|
||||
bus->i2c->CR2 |= I2C_CR2_ITBUFEN;
|
||||
bus->transfer.state = TRANSFER_STATE_WRITE_DATA;
|
||||
}
|
||||
break;
|
||||
|
||||
case TRANSFER_STATE_REPEAT_START:
|
||||
// Generate a repeat start
|
||||
bus->i2c->CR1 |= I2C_CR1_START;
|
||||
bus->transfer.state = TRANSFER_STATE_WRITE_ADDRESS_RX;
|
||||
break;
|
||||
|
||||
case TRANSFER_STATE_WRITE_ADDRESS_RX:
|
||||
// Write the I2C device address again, but this time in read mode.
|
||||
bus->i2c->DR = bus->transfer.device_address | I2C_READ_WRITE_BIT;
|
||||
if (bus->transfer.size == 1) {
|
||||
// Last byte, we want to NACK this one to tell the slave to stop sending us bytes.
|
||||
bus->i2c->CR1 &= ~I2C_CR1_ACK;
|
||||
}
|
||||
bus->transfer.state = TRANSFER_STATE_WAIT_FOR_DATA;
|
||||
break;
|
||||
|
||||
case TRANSFER_STATE_WAIT_FOR_DATA:
|
||||
//This state just ensures that the transition to receive mode event happened
|
||||
|
||||
// Enable RXNE interrupt for writing
|
||||
bus->i2c->CR2 |= I2C_CR2_ITBUFEN;
|
||||
bus->transfer.state = TRANSFER_STATE_READ_DATA;
|
||||
break;
|
||||
|
||||
case TRANSFER_STATE_READ_DATA:
|
||||
bus->transfer.data[bus->transfer.idx] = bus->i2c->DR;
|
||||
bus->transfer.idx++;
|
||||
|
||||
if (bus->transfer.idx + 1 == bus->transfer.size) {
|
||||
// Last byte, we want to NACK this one to tell the slave to stop sending us bytes.
|
||||
bus->i2c->CR1 &= ~I2C_CR1_ACK;
|
||||
}
|
||||
else if (bus->transfer.idx == bus->transfer.size) {
|
||||
// End transfer after all bytes have been received
|
||||
bus->i2c->CR2 &= ~I2C_CR2_ITBUFEN;
|
||||
should_context_switch = end_transfer_irq(bus, true);
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case TRANSFER_STATE_WRITE_DATA:
|
||||
bus->i2c->DR = bus->transfer.data[bus->transfer.idx];
|
||||
bus->transfer.idx++;
|
||||
if (bus->transfer.idx == bus->transfer.size) {
|
||||
bus->i2c->CR2 &= ~I2C_CR2_ITBUFEN;
|
||||
bus->transfer.state = TRANSFER_STATE_END_WRITE;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case TRANSFER_STATE_END_WRITE:
|
||||
// End transfer after all bytes have been sent
|
||||
should_context_switch = end_transfer_irq(bus, true);
|
||||
break;
|
||||
|
||||
default:
|
||||
// Abort transfer from invalid state - should never reach here (state machine logic broken)
|
||||
should_context_switch = end_transfer_irq(bus, false);
|
||||
break;
|
||||
}
|
||||
|
||||
return should_context_switch;
|
||||
}
|
||||
|
||||
//! Handle error interrupt on the specified \a bus
|
||||
static portBASE_TYPE irq_error_handler(I2cBus *bus) {
|
||||
if (bus->transfer.state == TRANSFER_STATE_INVALID) {
|
||||
|
||||
// Disable interrupts if spurious interrupt received
|
||||
bus->i2c->CR2 &= ~I2C_CR2_ITERREN;
|
||||
return pdFALSE;
|
||||
}
|
||||
|
||||
// Data overrun and bus errors can only really be handled by terminating the transfer and
|
||||
// trying to recover bus to an idle state. Each error will be logged. In each case a stop
|
||||
// condition will be sent and then we will wait on the busy flag to clear (if it doesn't,
|
||||
// a soft reset of the bus will be performed (handled in wait i2c_do_transfer).
|
||||
|
||||
if ((bus->i2c->SR1 & I2C_SR1_OVR) != 0) {
|
||||
bus->i2c->SR1 &= ~I2C_SR1_OVR;
|
||||
|
||||
// Data overrun
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Data overrun during I2C transaction; Bus: 0x%p", bus->i2c);
|
||||
}
|
||||
if ((bus->i2c->SR1 & I2C_SR1_BERR) != 0) {
|
||||
bus->i2c->SR1 &= ~I2C_SR1_BERR;
|
||||
|
||||
// Bus error: invalid start or stop condition detected
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Bus error detected during I2C transaction; Bus: 0x%p", bus->i2c);
|
||||
}
|
||||
if ((bus->i2c->SR1 & I2C_SR1_AF) != 0) {
|
||||
bus->i2c->SR1 &= ~I2C_SR1_AF;
|
||||
|
||||
// NACK received.
|
||||
//
|
||||
// The MFI chip will cause NACK errors during read operations after writing a start bit (first start
|
||||
// or repeat start indicating that it is busy. The transfer must be paused and the start condition sent
|
||||
// again after a delay and the state machine set back a step.
|
||||
//
|
||||
// If the NACK is received after any other action log an error and abort the transfer
|
||||
|
||||
|
||||
if (bus->transfer.state == TRANSFER_STATE_WAIT_FOR_DATA) {
|
||||
bus->transfer.state = TRANSFER_STATE_WRITE_ADDRESS_RX;
|
||||
return pause_transfer_irq(bus);
|
||||
}
|
||||
else if (bus->transfer.state == TRANSFER_STATE_WRITE_REG_ADDRESS){
|
||||
bus->transfer.state = TRANSFER_STATE_WRITE_ADDRESS_TX;
|
||||
return pause_transfer_irq(bus);
|
||||
}
|
||||
else {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "NACK received during I2C transfer; Bus: 0x%p", bus->i2c);
|
||||
}
|
||||
}
|
||||
|
||||
return end_transfer_irq(bus, false);
|
||||
|
||||
}
|
||||
|
||||
void I2C1_EV_IRQHandler(void) {
|
||||
portEND_SWITCHING_ISR(irq_event_handler(&i2c_buses[0]));
|
||||
}
|
||||
|
||||
void I2C1_ER_IRQHandler(void) {
|
||||
portEND_SWITCHING_ISR(irq_error_handler(&i2c_buses[0]));
|
||||
}
|
||||
|
||||
void I2C2_EV_IRQHandler(void) {
|
||||
portEND_SWITCHING_ISR(irq_event_handler(&i2c_buses[1]));
|
||||
}
|
||||
|
||||
void I2C2_ER_IRQHandler(void) {
|
||||
portEND_SWITCHING_ISR(irq_error_handler(&i2c_buses[1]));
|
||||
}
|
||||
|
||||
/*------------------------COMMAND FUNCTIONS--------------------------*/
|
||||
|
||||
void command_power_2v5(char *arg) {
|
||||
// Intentionally ignore the s_running_count and make it so!
|
||||
// This is intended for low level electrical test only
|
||||
if(!strcmp("on", arg)) {
|
||||
bus_rail_power_up(1);
|
||||
} else {
|
||||
bus_rail_power_down(1);
|
||||
}
|
||||
}
|
108
platform/snowy/boot/src/drivers/stm32_common/i2c_private.c
Normal file
108
platform/snowy/boot/src/drivers/stm32_common/i2c_private.c
Normal file
|
@ -0,0 +1,108 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
#include "drivers/pmic.h"
|
||||
#include "drivers/gpio.h"
|
||||
#include "util/delay.h"
|
||||
#include "board/board.h"
|
||||
|
||||
#if defined(MICRO_FAMILY_STM32F2)
|
||||
#include "stm32f2xx_gpio.h"
|
||||
#include "stm32f2xx_rcc.h"
|
||||
#include "stm32f2xx_i2c.h"
|
||||
#elif defined(MICRO_FAMILY_STM32F4)
|
||||
#include "stm32f4xx_gpio.h"
|
||||
#include "stm32f4xx_rcc.h"
|
||||
#include "stm32f4xx_i2c.h"
|
||||
#include "drivers/pmic.h"
|
||||
#endif
|
||||
|
||||
extern void i2c_bus_rail_ctl_config(OutputConfig pin_config);
|
||||
|
||||
static void do_rail_power(bool up, GPIO_TypeDef* const gpio, const uint32_t gpio_pin, const bool active_high) {
|
||||
if (up) {
|
||||
gpio_use(gpio);
|
||||
// enable the bus supply
|
||||
GPIO_WriteBit(gpio, gpio_pin, active_high ? Bit_SET : Bit_RESET);
|
||||
|
||||
// wait for the bus supply to stabilize and the peripherals to start up.
|
||||
// the MFI chip requires its reset pin to be stable for at least 10ms from startup.
|
||||
delay_ms(20);
|
||||
gpio_release(gpio);
|
||||
} else {
|
||||
gpio_use(gpio);
|
||||
// disable the bus supply
|
||||
GPIO_WriteBit(gpio, gpio_pin, active_high ? Bit_RESET : Bit_SET);
|
||||
gpio_release(gpio);
|
||||
}
|
||||
}
|
||||
|
||||
// SNOWY
|
||||
/////////
|
||||
void snowy_i2c_rail_1_ctl_fn(bool enable) {
|
||||
set_ldo3_power_state(enable);
|
||||
}
|
||||
|
||||
// bb2
|
||||
/////////
|
||||
void bb2_rail_ctl_fn(bool enable) {
|
||||
do_rail_power(enable, GPIOH, GPIO_Pin_0, true);
|
||||
}
|
||||
|
||||
void bb2_rail_cfg_fn(void) {
|
||||
i2c_bus_rail_ctl_config((OutputConfig){ GPIOH, GPIO_Pin_0, true});
|
||||
}
|
||||
|
||||
// v1_5
|
||||
/////////
|
||||
void v1_5_rail_ctl_fn(bool enable) {
|
||||
do_rail_power(enable, GPIOH, GPIO_Pin_0, true);
|
||||
}
|
||||
|
||||
void v1_5_rail_cfg_fn(void) {
|
||||
i2c_bus_rail_ctl_config((OutputConfig){ GPIOH, GPIO_Pin_0, true});
|
||||
}
|
||||
|
||||
// v2_0
|
||||
/////////
|
||||
void v2_0_rail_ctl_fn(bool enable) {
|
||||
do_rail_power(enable, GPIOH, GPIO_Pin_0, true);
|
||||
}
|
||||
|
||||
void v2_0_rail_cfg_fn(void) {
|
||||
i2c_bus_rail_ctl_config((OutputConfig){ GPIOH, GPIO_Pin_0, true});
|
||||
}
|
||||
|
||||
// ev2_4
|
||||
/////////
|
||||
void ev2_4_rail_ctl_fn(bool enable) {
|
||||
do_rail_power(enable, GPIOH, GPIO_Pin_0, true);
|
||||
}
|
||||
|
||||
void ev2_4_rail_cfg_fn(void) {
|
||||
i2c_bus_rail_ctl_config((OutputConfig){ GPIOH, GPIO_Pin_0, true});
|
||||
}
|
||||
|
||||
// bigboard
|
||||
////////////
|
||||
void bigboard_rail_ctl_fn(bool enable) {
|
||||
do_rail_power(enable, GPIOC, GPIO_Pin_5, true);
|
||||
}
|
||||
|
||||
void bigboard_rail_cfg_fn(void) {
|
||||
i2c_bus_rail_ctl_config((OutputConfig){ GPIOC, GPIO_Pin_5, true});
|
||||
}
|
60
platform/snowy/boot/src/drivers/stm32_common/spi.c
Normal file
60
platform/snowy/boot/src/drivers/stm32_common/spi.c
Normal file
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "drivers/spi.h"
|
||||
|
||||
#include "util/misc.h"
|
||||
#include "system/passert.h"
|
||||
|
||||
#if defined(MICRO_FAMILY_STM32F2)
|
||||
#include "stm32f2xx_rcc.h"
|
||||
#elif defined(MICRO_FAMILY_STM32F4)
|
||||
#include "stm32f4xx_rcc.h"
|
||||
#endif
|
||||
|
||||
// Deduced by looking at the prescalers in stm32f2xx_spi.h
|
||||
#define SPI_FREQ_LOG_TO_PRESCALER(LG) (((LG) - 1) * 0x8)
|
||||
|
||||
uint16_t spi_find_prescaler(uint32_t bus_frequency, SpiPeriphClock periph_clock) {
|
||||
// Get the clocks
|
||||
RCC_ClocksTypeDef clocks;
|
||||
RCC_GetClocksFreq(&clocks);
|
||||
|
||||
uint32_t clock = 0;
|
||||
// Find which peripheral clock we belong to
|
||||
if (periph_clock == SpiPeriphClockAPB1) {
|
||||
clock = clocks.PCLK1_Frequency;
|
||||
} else if (periph_clock == SpiPeriphClockAPB2) {
|
||||
clock = clocks.PCLK2_Frequency;
|
||||
} else {
|
||||
WTF;
|
||||
}
|
||||
|
||||
int lg;
|
||||
if (bus_frequency > (clock / 2)) {
|
||||
lg = 1; // Underclock to the highest possible frequency
|
||||
} else {
|
||||
uint32_t divisor = clock / bus_frequency;
|
||||
lg = ceil_log_two(divisor);
|
||||
}
|
||||
|
||||
// Prescalers only exists for values in [2 - 256] range
|
||||
PBL_ASSERTN(lg > 0);
|
||||
PBL_ASSERTN(lg < 9);
|
||||
|
||||
// return prescaler
|
||||
return (SPI_FREQ_LOG_TO_PRESCALER(lg));
|
||||
}
|
131
platform/snowy/boot/src/drivers/stm32_common/system_flash.c
Normal file
131
platform/snowy/boot/src/drivers/stm32_common/system_flash.c
Normal file
|
@ -0,0 +1,131 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "drivers/system_flash.h"
|
||||
|
||||
#include "drivers/dbgserial.h"
|
||||
#include "util/misc.h"
|
||||
|
||||
#if defined(MICRO_FAMILY_STM32F2)
|
||||
#include "stm32f2xx_flash.h"
|
||||
#elif defined(MICRO_FAMILY_STM32F4)
|
||||
#include "stm32f4xx_flash.h"
|
||||
#endif
|
||||
|
||||
static uint16_t s_sectors[] = {
|
||||
FLASH_Sector_0, FLASH_Sector_1, FLASH_Sector_2, FLASH_Sector_3,
|
||||
FLASH_Sector_4, FLASH_Sector_5, FLASH_Sector_6, FLASH_Sector_7,
|
||||
FLASH_Sector_8, FLASH_Sector_9, FLASH_Sector_10, FLASH_Sector_11 };
|
||||
static uint32_t s_sector_addresses[] = {
|
||||
ADDR_FLASH_SECTOR_0, ADDR_FLASH_SECTOR_1, ADDR_FLASH_SECTOR_2,
|
||||
ADDR_FLASH_SECTOR_3, ADDR_FLASH_SECTOR_4, ADDR_FLASH_SECTOR_5,
|
||||
ADDR_FLASH_SECTOR_6, ADDR_FLASH_SECTOR_7, ADDR_FLASH_SECTOR_8,
|
||||
ADDR_FLASH_SECTOR_9, ADDR_FLASH_SECTOR_10, ADDR_FLASH_SECTOR_11 };
|
||||
|
||||
int prv_get_sector_num_for_address(uint32_t address) {
|
||||
if (address < s_sector_addresses[0]) {
|
||||
dbgserial_print("address ");
|
||||
dbgserial_print_hex(address);
|
||||
dbgserial_print(" is outside system flash\r\n");
|
||||
return -1;
|
||||
}
|
||||
for (size_t i=0; i < ARRAY_LENGTH(s_sector_addresses)-1; ++i) {
|
||||
if (s_sector_addresses[i] <= address
|
||||
&& address < s_sector_addresses[i+1]) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return ARRAY_LENGTH(s_sector_addresses)-1;
|
||||
}
|
||||
|
||||
bool system_flash_erase(
|
||||
uint32_t address, size_t length,
|
||||
SystemFlashProgressCb progress_callback, void *progress_context) {
|
||||
dbgserial_print("system_flash_erase(");
|
||||
dbgserial_print_hex(address);
|
||||
dbgserial_print(", ");
|
||||
dbgserial_print_hex(length);
|
||||
dbgserial_print(")\r\n");
|
||||
|
||||
if (length == 0) {
|
||||
// Nothing to do
|
||||
return true;
|
||||
}
|
||||
|
||||
int first_sector = prv_get_sector_num_for_address(address);
|
||||
int last_sector = prv_get_sector_num_for_address(address + length - 1);
|
||||
if (first_sector < 0 || last_sector < 0) {
|
||||
return false;
|
||||
}
|
||||
int count = last_sector - first_sector + 1;
|
||||
if (progress_callback) {
|
||||
progress_callback(0, count, progress_context);
|
||||
}
|
||||
|
||||
FLASH_Unlock();
|
||||
for (int sector = first_sector; sector <= last_sector; ++sector) {
|
||||
FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_OPERR | FLASH_FLAG_WRPERR |
|
||||
FLASH_FLAG_PGAERR | FLASH_FLAG_PGPERR | FLASH_FLAG_PGSERR);
|
||||
if (FLASH_EraseSector(
|
||||
s_sectors[sector], VoltageRange_1) != FLASH_COMPLETE) {
|
||||
dbgserial_print("failed to erase sector ");
|
||||
dbgserial_print_hex(sector);
|
||||
dbgserial_putstr("");
|
||||
FLASH_Lock();
|
||||
return false;
|
||||
}
|
||||
if (progress_callback) {
|
||||
progress_callback(sector - first_sector + 1, count, progress_context);
|
||||
}
|
||||
}
|
||||
FLASH_Lock();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool system_flash_write(
|
||||
uint32_t address, const void *data, size_t length,
|
||||
SystemFlashProgressCb progress_callback, void *progress_context) {
|
||||
dbgserial_print("system_flash_write(");
|
||||
dbgserial_print_hex(address);
|
||||
dbgserial_print(", ");
|
||||
dbgserial_print_hex(length);
|
||||
dbgserial_print(")\r\n");
|
||||
|
||||
FLASH_Unlock();
|
||||
FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_OPERR | FLASH_FLAG_WRPERR |
|
||||
FLASH_FLAG_PGAERR | FLASH_FLAG_PGPERR|FLASH_FLAG_PGSERR);
|
||||
|
||||
const uint8_t *data_array = data;
|
||||
for (uint32_t i = 0; i < length; ++i) {
|
||||
if (FLASH_ProgramByte(address + i, data_array[i]) != FLASH_COMPLETE) {
|
||||
dbgserial_print("failed to write address ");
|
||||
dbgserial_print_hex(address);
|
||||
dbgserial_putstr("");
|
||||
FLASH_Lock();
|
||||
return false;
|
||||
}
|
||||
if (progress_callback && i % 128 == 0) {
|
||||
progress_callback(i/128, length/128, progress_context);
|
||||
}
|
||||
}
|
||||
FLASH_Lock();
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t system_flash_read(uint32_t address) {
|
||||
uint32_t data = *(volatile uint32_t*) address;
|
||||
return data;
|
||||
}
|
61
platform/snowy/boot/src/drivers/stm32_common/watchdog.c
Normal file
61
platform/snowy/boot/src/drivers/stm32_common/watchdog.c
Normal file
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "drivers/watchdog.h"
|
||||
|
||||
#include "util/bitset.h"
|
||||
#include "system/logging.h"
|
||||
|
||||
#if defined(MICRO_FAMILY_STM32F2)
|
||||
#include "stm32f2xx_dbgmcu.h"
|
||||
#include "stm32f2xx_iwdg.h"
|
||||
#include "stm32f2xx_rcc.h"
|
||||
#elif defined(MICRO_FAMILY_STM32F4)
|
||||
#include "stm32f4xx_dbgmcu.h"
|
||||
#include "stm32f4xx_iwdg.h"
|
||||
#include "stm32f4xx_rcc.h"
|
||||
#endif
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
void watchdog_init(void) {
|
||||
IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable);
|
||||
|
||||
IWDG_SetPrescaler(IWDG_Prescaler_64); // ~8 seconds
|
||||
IWDG_SetReload(0xfff);
|
||||
|
||||
IWDG_WriteAccessCmd(IWDG_WriteAccess_Disable);
|
||||
|
||||
DBGMCU_APB1PeriphConfig(DBGMCU_IWDG_STOP, ENABLE);
|
||||
}
|
||||
|
||||
void watchdog_start(void) {
|
||||
IWDG_Enable();
|
||||
watchdog_feed();
|
||||
}
|
||||
|
||||
// This behaves differently from the bootloader and the firmware.
|
||||
void watchdog_feed(void) {
|
||||
IWDG_ReloadCounter();
|
||||
}
|
||||
|
||||
bool watchdog_check_reset_flag(void) {
|
||||
return RCC_GetFlagStatus(RCC_FLAG_IWDGRST) != RESET;
|
||||
}
|
||||
|
||||
void watchdog_clear_reset_flag(void) {
|
||||
RCC_ClearFlag();
|
||||
}
|
74
platform/snowy/boot/src/drivers/system_flash.h
Normal file
74
platform/snowy/boot/src/drivers/system_flash.h
Normal file
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#if defined(MICRO_FAMILY_STM32F2)
|
||||
#include "stm32f2xx_flash.h"
|
||||
#elif defined(MICRO_FAMILY_STM32F4)
|
||||
#include "stm32f4xx_flash.h"
|
||||
#endif
|
||||
|
||||
#define ADDR_FLASH_SECTOR_0 ((uint32_t)0x08000000) /* Base @ of Sector 0, 16 Kbytes */
|
||||
#define ADDR_FLASH_SECTOR_1 ((uint32_t)0x08004000) /* Base @ of Sector 1, 16 Kbytes */
|
||||
#define ADDR_FLASH_SECTOR_2 ((uint32_t)0x08008000) /* Base @ of Sector 2, 16 Kbytes */
|
||||
#define ADDR_FLASH_SECTOR_3 ((uint32_t)0x0800C000) /* Base @ of Sector 3, 16 Kbytes */
|
||||
#define ADDR_FLASH_SECTOR_4 ((uint32_t)0x08010000) /* Base @ of Sector 4, 64 Kbytes */
|
||||
#define ADDR_FLASH_SECTOR_5 ((uint32_t)0x08020000) /* Base @ of Sector 5, 128 Kbytes */
|
||||
#define ADDR_FLASH_SECTOR_6 ((uint32_t)0x08040000) /* Base @ of Sector 6, 128 Kbytes */
|
||||
#define ADDR_FLASH_SECTOR_7 ((uint32_t)0x08060000) /* Base @ of Sector 7, 128 Kbytes */
|
||||
|
||||
// stm32f2xx only has 512k of system flash, these sectors don't exist
|
||||
#if defined(MICRO_FAMILY_STM32F4)
|
||||
#define ADDR_FLASH_SECTOR_8 ((uint32_t)0x08080000) /* Base @ of Sector 8, 128 Kbytes */
|
||||
#define ADDR_FLASH_SECTOR_9 ((uint32_t)0x080A0000) /* Base @ of Sector 9, 128 Kbytes */
|
||||
#define ADDR_FLASH_SECTOR_10 ((uint32_t)0x080C0000) /* Base @ of Sector 10, 128 Kbytes */
|
||||
#define ADDR_FLASH_SECTOR_11 ((uint32_t)0x080E0000) /* Base @ of Sector 11, 128 Kbytes */
|
||||
#endif
|
||||
|
||||
typedef void (*SystemFlashProgressCb)(
|
||||
uint32_t progress, uint32_t total, void *context);
|
||||
|
||||
// Erase the sectors of flash which lie within the given address range.
|
||||
//
|
||||
// If the address range overlaps even one single byte of a sector, the entire
|
||||
// sector is erased.
|
||||
//
|
||||
// If progress_callback is not NULL, it is called at the beginning of the erase
|
||||
// process and after each sector is erased. The rational number (progress/total)
|
||||
// increases monotonically as the sector erasue procedure progresses.
|
||||
//
|
||||
// Returns true if successful, false if an error occurred.
|
||||
bool system_flash_erase(
|
||||
uint32_t address, size_t length,
|
||||
SystemFlashProgressCb progress_callback, void *progress_context);
|
||||
|
||||
// Write data into flash. The flash must already be erased.
|
||||
//
|
||||
// If progress_callback is not NULL, it is called at the beginning of the
|
||||
// writing process and periodically thereafter. The rational number
|
||||
// (progress/total) increases monotonically as the data is written.
|
||||
//
|
||||
// Returns true if successful, false if an error occurred.
|
||||
bool system_flash_write(
|
||||
uint32_t address, const void *data, size_t length,
|
||||
SystemFlashProgressCb progress_callback, void *progress_context);
|
||||
|
||||
uint32_t system_flash_read(uint32_t address);
|
27
platform/snowy/boot/src/drivers/watchdog.h
Normal file
27
platform/snowy/boot/src/drivers/watchdog.h
Normal file
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
void watchdog_init(void);
|
||||
void watchdog_start(void);
|
||||
|
||||
void watchdog_feed(void);
|
||||
|
||||
bool watchdog_check_reset_flag(void);
|
||||
void watchdog_clear_reset_flag(void);
|
Loading…
Add table
Add a link
Reference in a new issue