Import of the watch repository from Pebble

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,179 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "dbgserial.h"
#include "drivers/gpio.h"
#include "drivers/periph_config.h"
#include "stm32f7haxx_rcc.h"
#include "stm32f7haxx_gpio.h"
#include "util/attributes.h"
#include "util/cobs.h"
#include "util/crc32.h"
#include "util/net.h"
#include "util/misc.h"
#include <stdint.h>
#include <string.h>
#define MAX_MESSAGE (256)
#define FRAME_DELIMITER '\x55'
#define PULSE_TRANSPORT_PUSH (0x5021)
#define PULSE_PROTOCOL_LOGGING (0x0003)
static const int SERIAL_BAUD_RATE = 1000000;
static USART_TypeDef *const DBGSERIAL_UART = USART3;
typedef struct PACKED PulseFrame {
net16 protocol;
unsigned char information[];
} PulseFrame;
typedef struct PACKED PushPacket {
net16 protocol;
net16 length;
unsigned char information[];
} PushPacket;
static const unsigned char s_message_header[] = {
// Message type: text
1,
// Source filename
'B', 'O', 'O', 'T', 'L', 'O', 'A', 'D', 'E', 'R', 0, 0, 0, 0, 0, 0,
// Log level and task
'*', '*',
// Timestamp
0, 0, 0, 0, 0, 0, 0, 0,
// Line number
0, 0,
};
static size_t s_message_length = 0;
static unsigned char s_message_buffer[MAX_MESSAGE];
void dbgserial_init(void) {
// Enable GPIO and UART3 peripheral clocks
periph_config_enable(GPIOD, RCC_AHB1Periph_GPIOD);
periph_config_enable(DBGSERIAL_UART, RCC_APB1Periph_USART3);
DBGSERIAL_UART->CR1 &= ~USART_CR1_UE;
AfConfig tx_cfg = {
.gpio = GPIOD,
.gpio_pin = GPIO_Pin_8,
.gpio_pin_source = GPIO_PinSource8,
.gpio_af = GPIO_AF7_USART3
};
gpio_af_init(&tx_cfg, GPIO_OType_PP, GPIO_Speed_50MHz, GPIO_PuPd_NOPULL);
AfConfig rx_cfg = {
.gpio = GPIOD,
.gpio_pin = GPIO_Pin_9,
.gpio_pin_source = GPIO_PinSource9,
.gpio_af = GPIO_AF7_USART3
};
gpio_af_init(&rx_cfg, GPIO_OType_PP, GPIO_Speed_50MHz, GPIO_PuPd_NOPULL);
// configure the UART peripheral control registers and baud rate
// - 8-bit word length
// - no parity
// - RX / TX enabled
// - 1 stop bit
// - no flow control
const int k_div_precision = 100;
RCC_ClocksTypeDef clocks;
RCC_GetClocksFreq(&clocks);
// calculate the baud rate value
const uint64_t scaled_apbclock = k_div_precision * clocks.PCLK1_Frequency;
const uint32_t div = (scaled_apbclock / SERIAL_BAUD_RATE);
const uint32_t brr = (div & 0xFFF0) | ((div & 0xF) >> 1);
DBGSERIAL_UART->BRR = brr / k_div_precision;
DBGSERIAL_UART->CR2 = 0;
DBGSERIAL_UART->CR3 = 0;
DBGSERIAL_UART->CR1 = USART_CR1_RE | USART_CR1_TE | USART_CR1_UE;
}
static void prv_putchar(uint8_t c) {
while ((DBGSERIAL_UART->ISR & USART_ISR_TXE) == 0) continue;
DBGSERIAL_UART->TDR = c;
while ((DBGSERIAL_UART->ISR & USART_ISR_TXE) == 0) continue;
}
void dbgserial_print(const char* str) {
for (; *str && s_message_length < MAX_MESSAGE; ++str) {
if (*str == '\n') {
dbgserial_newline();
} else if (*str != '\r') {
s_message_buffer[s_message_length++] = *str;
}
}
}
void dbgserial_newline(void) {
uint32_t crc;
size_t raw_length = sizeof(PulseFrame) + sizeof(PushPacket) +
sizeof(s_message_header) + s_message_length + sizeof(crc);
unsigned char raw_packet[raw_length];
PulseFrame *frame = (PulseFrame *)raw_packet;
frame->protocol = hton16(PULSE_TRANSPORT_PUSH);
PushPacket *transport = (PushPacket *)frame->information;
transport->protocol = hton16(PULSE_PROTOCOL_LOGGING);
transport->length = hton16(sizeof(PushPacket) + sizeof(s_message_header) +
s_message_length);
unsigned char *app = transport->information;
memcpy(app, s_message_header, sizeof(s_message_header));
memcpy(&app[sizeof(s_message_header)], s_message_buffer,
s_message_length);
crc = crc32(CRC32_INIT, raw_packet, raw_length - sizeof(crc));
memcpy(&raw_packet[raw_length - sizeof(crc)], &crc, sizeof(crc));
unsigned char cooked_packet[MAX_SIZE_AFTER_COBS_ENCODING(raw_length)];
size_t cooked_length = cobs_encode(cooked_packet, raw_packet, raw_length);
prv_putchar(FRAME_DELIMITER);
for (size_t i = 0; i < cooked_length; ++i) {
if (cooked_packet[i] == FRAME_DELIMITER) {
prv_putchar('\0');
} else {
prv_putchar(cooked_packet[i]);
}
}
prv_putchar(FRAME_DELIMITER);
s_message_length = 0;
}
void dbgserial_putstr(const char* str) {
dbgserial_print(str);
dbgserial_newline();
}
void dbgserial_print_hex(uint32_t value) {
char buf[12];
itoa_hex(value, buf, sizeof(buf));
dbgserial_print(buf);
}

View file

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

View file

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

View file

@ -0,0 +1,210 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "drivers/display.h"
#include <stdbool.h>
#include "board/board.h"
#include "drivers/dbgserial.h"
#include "drivers/display/bootloader_fpga_bitstream.auto.h"
#include "drivers/display/ice40lp.h"
#include "drivers/display/ice40lp_definitions.h"
#include "system/passert.h"
#include "util/delay.h"
#include "util/misc.h"
#include "util/sle.h"
#define CMD_NULL (0)
#define CMD_SET_PARAMETER (1)
#define CMD_DISPLAY_OFF (2)
#define CMD_DISPLAY_ON (3)
#define CMD_DRAW_SCENE (4)
#define CMD_RESET_RELEASE (8)
#define CMD_RESET_ASSERT (9)
#define SCENE_BLACK (0)
#define SCENE_SPLASH (1)
#define SCENE_UPDATE (2)
#define SCENE_ERROR (3)
#define UPDATE_PROGRESS_MAX (47)
static uint8_t s_decoded_fpga_image[35000]; // the FPGA image is currently ~30k
static bool prv_reset_fpga(void) {
const uint32_t length = sle_decode(s_fpga_bitstream, sizeof(s_fpga_bitstream),
s_decoded_fpga_image, sizeof(s_decoded_fpga_image));
return display_program(s_decoded_fpga_image, length);
}
static bool prv_wait_busy(void) {
// The display should come out of busy within 35 milliseconds;
// it is a waste of time to wait more than twice that.
int timeout = 50 * 10;
while (display_busy()) {
if (timeout-- == 0) {
dbgserial_putstr("Display busy-wait timeout expired!");
return false;
}
delay_us(100);
}
return true;
}
static void prv_screen_on(void) {
display_write_cmd(CMD_DISPLAY_ON, NULL, 0);
}
static void prv_screen_off(void) {
display_write_cmd(CMD_DISPLAY_OFF, NULL, 0);
}
static void prv_draw_scene(uint8_t scene) {
display_write_cmd(CMD_DRAW_SCENE, &scene, sizeof(scene));
}
static void prv_set_parameter(uint32_t param) {
display_write_cmd(CMD_SET_PARAMETER, (uint8_t *)&param, sizeof(param));
}
#ifdef DISPLAY_DEMO_LOOP
static void prv_play_demo_loop(void) {
while (1) {
for (int i = 0; i <= UPDATE_PROGRESS_MAX; ++i) {
display_firmware_update_progress(i, UPDATE_PROGRESS_MAX);
delay_ms(80);
}
for (uint32_t i = 0; i <= 0xf; ++i) {
display_error_code(i * 0x11111111);
delay_ms(200);
}
for (uint32_t i = 0; i < 8; ++i) {
for (uint32_t j = 1; j <= 0xf; ++j) {
display_error_code(j << (i * 4));
delay_ms(200);
}
}
display_error_code(0x01234567);
delay_ms(200);
display_error_code(0x89abcdef);
delay_ms(200);
display_error_code(0xcafebabe);
delay_ms(200);
display_error_code(0xfeedface);
delay_ms(200);
display_error_code(0x8badf00d);
delay_ms(200);
display_error_code(0xbad1ce40);
delay_ms(200);
display_error_code(0xbeefcace);
delay_ms(200);
display_error_code(0x0defaced);
delay_ms(200);
display_error_code(0xd15ea5e5);
delay_ms(200);
display_error_code(0xdeadbeef);
delay_ms(200);
display_boot_splash();
delay_ms(1000);
}
}
#endif
void display_init(void) {
display_start();
if (!prv_reset_fpga()) {
dbgserial_putstr("FPGA configuration failed.");
return;
}
// enable the power rails
display_power_enable();
// start with the screen off
prv_screen_off();
// Work around an issue which some boards exhibit where the FPGA ring
// oscillator can start up with higher harmonics, massively overclocking the
// design and causing malfunction. When this occurrs, the draw-scene command
// will not work, asserting BUSY indefinitely but never updating the display.
// Other commands such as display-on and display-off are less affected by the
// overclocking, so the display can be turned on while the FPGA is in this
// state, showing only garbage.
// FPGA malfunction can be detected in software. In an attempt to restore
// proper functioning, the FPGA can be reset and reconfigured in the hopes
// that the ring oscillator will start up and oscillate without any higher
// harmonics. Bootloader release 03 attempts to mitigate this problem by
// delaying oscillator startup until after configuration completes. Time will
// tell whether this actually fixes things.
for (int retries = 0; retries <= 10; ++retries) {
prv_draw_scene(SCENE_SPLASH);
if (prv_wait_busy()) {
prv_screen_on();
dbgserial_print("Display initialized after ");
dbgserial_print_hex(retries);
dbgserial_putstr(" retries.");
#ifdef DISPLAY_DEMO_LOOP
prv_play_demo_loop();
#endif
return;
}
if (!prv_reset_fpga()) {
dbgserial_putstr("FPGA configuration failed.");
return;
}
}
// It's taken too many attempts and the FPGA still isn't behaving. Give up on
// showing the splash screen and keep the screen off so that the user doesn't
// see a broken-looking staticky screen on boot.
dbgserial_putstr("Display initialization failed.");
prv_screen_off();
}
void display_boot_splash(void) {
prv_wait_busy();
prv_draw_scene(SCENE_SPLASH);
// Don't turn the screen on until the boot-splash is fully drawn.
prv_wait_busy();
prv_screen_on();
}
void display_firmware_update_progress(
uint32_t numerator, uint32_t denominator) {
static uint8_t last_bar_fill = UINT8_MAX;
// Scale progress to the number of pixels in the progress bar,
// rounding half upwards.
uint8_t bar_fill =
((numerator * UPDATE_PROGRESS_MAX) + ((denominator+1)/2)) / denominator;
// Don't waste time and power redrawing the same screen repeatedly.
if (bar_fill != last_bar_fill) {
last_bar_fill = bar_fill;
prv_set_parameter(bar_fill);
prv_draw_scene(SCENE_UPDATE);
}
}
void display_error_code(uint32_t error_code) {
prv_set_parameter(error_code);
prv_draw_scene(SCENE_ERROR);
}
void display_prepare_for_reset(void) {
prv_screen_off();
}

View file

@ -0,0 +1,186 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "ice40lp.h"
#include "board/board.h"
#include "drivers/dbgserial.h"
#include "drivers/display/ice40lp_definitions.h"
#include "drivers/gpio.h"
#include "drivers/periph_config.h"
#include "drivers/pmic.h"
#include "system/logging.h"
#include "system/passert.h"
#include "util/delay.h"
#include "stm32f7xx.h"
#include "misc.h"
#include <string.h>
static void prv_spi_init(void) {
// Configure the GPIO (SCLK, MOSI - no MISO since the SPI is TX-only)
gpio_af_init(&ICE40LP->spi.clk, GPIO_OType_PP, GPIO_Speed_25MHz, GPIO_PuPd_NOPULL);
gpio_af_init(&ICE40LP->spi.mosi, GPIO_OType_PP, GPIO_Speed_25MHz, GPIO_PuPd_NOPULL);
// Reset the SPI peripheral and enable the clock
RCC_APB2PeriphResetCmd(ICE40LP->spi.rcc_bit, ENABLE);
RCC_APB2PeriphResetCmd(ICE40LP->spi.rcc_bit, DISABLE);
periph_config_enable(ICE40LP->spi.periph, ICE40LP->spi.rcc_bit);
// Configure CR1 first:
// * TX-only mode (BIDIMODE | BIDIOE)
// * software control NSS pin (SSM | SSI)
// * master mode (MSTR)
// * clock polarity high / 2nd edge (CPOL | CPHA)
ICE40LP->spi.periph->CR1 = SPI_CR1_BIDIMODE | SPI_CR1_BIDIOE | SPI_CR1_SSM | SPI_CR1_SSI |
SPI_CR1_MSTR | SPI_CR1_CPOL | SPI_CR1_CPHA;
// Configure CR2:
// * 8-bit data size (DS[4:0] == 0b0111)
// * 1/4 RX threshold (for 8-bit transfers)
ICE40LP->spi.periph->CR2 = SPI_CR2_DS_0 | SPI_CR2_DS_1 | SPI_CR2_DS_2 | SPI_CR2_FRXTH;
// enable the SPI
ICE40LP->spi.periph->CR1 |= SPI_CR1_SPE;
}
static void prv_spi_write(const uint8_t *data, uint32_t len) {
for (uint32_t i = 0; i < len; ++i) {
// Wait until we can transmit.
while (!(ICE40LP->spi.periph->SR & SPI_SR_TXE)) continue;
// Write a byte. STM32F7 needs to access as 8 bits in order to actually do 8 bits.
*(volatile uint8_t*)&ICE40LP->spi.periph->DR = data[i];
}
// Wait until the TX FIFO is empty plus an extra little bit for the shift-register.
while (((ICE40LP->spi.periph->SR & SPI_SR_FTLVL) >> __builtin_ctz(SPI_SR_FTLVL)) > 0) continue;
delay_us(10);
}
bool display_busy(void) {
return gpio_input_read(&ICE40LP->busy);
}
void display_start(void) {
// Configure SCS before CRESET and before configuring the SPI so that we don't end up with the
// FPGA in the "SPI Master Configuration Interface" on bigboards which don't have NVCM. If we end
// up in this mode, the FPGA will drive the clock and put the SPI peripheral in a bad state.
gpio_output_init(&ICE40LP->spi.scs, GPIO_OType_PP, GPIO_Speed_25MHz);
gpio_output_set(&ICE40LP->spi.scs, false);
gpio_input_init(&ICE40LP->cdone);
gpio_input_init(&ICE40LP->busy);
gpio_output_init(&ICE40LP->creset, GPIO_OType_OD, GPIO_Speed_25MHz);
prv_spi_init();
}
bool display_program(const uint8_t *fpga_bitstream, uint32_t bitstream_size) {
InputConfig creset_input = {
.gpio = ICE40LP->creset.gpio,
.gpio_pin = ICE40LP->creset.gpio_pin,
};
delay_ms(1);
gpio_output_set(&ICE40LP->spi.scs, true); // SCS asserted (low)
gpio_output_set(&ICE40LP->creset, false); // CRESET LOW
delay_ms(1);
if (gpio_input_read(&creset_input)) {
dbgserial_putstr("CRESET not low during reset");
return false;
}
gpio_output_set(&ICE40LP->creset, true); // CRESET -> HIGH
delay_ms(1);
if (gpio_input_read(&ICE40LP->cdone)) {
dbgserial_putstr("CDONE not low after reset");
return false;
}
if (!gpio_input_read(&creset_input)) {
dbgserial_putstr("CRESET not high after reset");
return false;
}
delay_ms(1);
// Program the FPGA
prv_spi_write(fpga_bitstream, bitstream_size);
// Set SCS high so that we don't process any of these clocks as commands.
gpio_output_set(&ICE40LP->spi.scs, false); // SCS not asserted (high)
// 49+ SCLK cycles to tell FPGA we're done configuration.
static const uint8_t spi_zeros[9] = {0, 0, 0, 0, 0, 0, 0, 0, 0};
prv_spi_write(spi_zeros, sizeof(spi_zeros));
if (!gpio_input_read(&ICE40LP->cdone)) {
dbgserial_putstr("CDONE not high after programming");
return false;
}
return true;
}
void display_power_enable(void) {
// The display requires us to wait 1ms between each power rail coming up. The PMIC
// initialization brings up the 3.2V rail (VLCD on the display, LD02 on the PMIC) for us, but
// we still need to wait before turning on the subsequent rails.
delay_ms(2);
if (ICE40LP->use_6v6_rail) {
dbgserial_putstr("Enabling 6v6 (Display VDDC)");
set_6V6_power_state(true);
delay_ms(2);
}
dbgserial_putstr("Enabling 4v5 (Display VDDP)");
set_4V5_power_state(true);
}
void display_power_disable(void) {
dbgserial_putstr("Disabling 4v5 (Display VDDP)");
set_4V5_power_state(false);
delay_ms(2);
if (ICE40LP->use_6v6_rail) {
dbgserial_putstr("Disabling 6v6 (Display VDDC)");
set_6V6_power_state(false);
delay_ms(2);
}
}
void display_write_cmd(uint8_t cmd, uint8_t *arg, uint32_t arg_len) {
gpio_output_set(&ICE40LP->spi.scs, true); // SCS asserted (low)
delay_us(100);
prv_spi_write(&cmd, sizeof(cmd));
if (arg_len) {
prv_spi_write(arg, arg_len);
}
gpio_output_set(&ICE40LP->spi.scs, false); // SCS not asserted (high)
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,158 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "drivers/gpio.h"
#include <stdint.h>
#include <stddef.h>
#define MAX_GPIO (11)
#define GPIO_EN_MASK ((RCC_AHB1ENR_GPIOKEN << 1) - 1)
void gpio_disable_all(void) {
RCC->AHB1ENR &= ~GPIO_EN_MASK;
}
static void prv_init_common(const InputConfig *input_config, GPIO_InitTypeDef *gpio_init) {
gpio_use(input_config->gpio);
GPIO_Init(input_config->gpio, gpio_init);
gpio_release(input_config->gpio);
}
static uint8_t s_gpio_clock_count[MAX_GPIO];
void gpio_use(GPIO_TypeDef* GPIOx) {
uint8_t idx = ((((uint32_t)GPIOx) - AHB1PERIPH_BASE) / 0x0400);
if ((idx < MAX_GPIO) && !(s_gpio_clock_count[idx]++)) {
SET_BIT(RCC->AHB1ENR, (0x1 << idx));
}
}
void gpio_release(GPIO_TypeDef* GPIOx) {
uint8_t idx = ((((uint32_t)GPIOx) - AHB1PERIPH_BASE) / 0x0400);
if ((idx < MAX_GPIO) && s_gpio_clock_count[idx] && !(--s_gpio_clock_count[idx])) {
CLEAR_BIT(RCC->AHB1ENR, (0x1 << idx));
}
}
void gpio_output_init(const OutputConfig *pin_config, GPIOOType_TypeDef otype,
GPIOSpeed_TypeDef speed) {
GPIO_InitTypeDef init = {
.GPIO_Pin = pin_config->gpio_pin,
.GPIO_Mode = GPIO_Mode_OUT,
.GPIO_Speed = speed,
.GPIO_OType = otype,
.GPIO_PuPd = GPIO_PuPd_NOPULL
};
gpio_use(pin_config->gpio);
GPIO_Init(pin_config->gpio, &init);
gpio_release(pin_config->gpio);
}
void gpio_output_set(const OutputConfig *pin_config, bool asserted) {
if (!pin_config->active_high) {
asserted = !asserted;
}
gpio_use(pin_config->gpio);
GPIO_WriteBit(pin_config->gpio, pin_config->gpio_pin,
asserted? Bit_SET : Bit_RESET);
gpio_release(pin_config->gpio);
}
void gpio_af_init(const AfConfig *af_config, GPIOOType_TypeDef otype,
GPIOSpeed_TypeDef speed, GPIOPuPd_TypeDef pupd) {
GPIO_InitTypeDef init = {
.GPIO_Pin = af_config->gpio_pin,
.GPIO_Mode = GPIO_Mode_AF,
.GPIO_Speed = speed,
.GPIO_OType = otype,
.GPIO_PuPd = pupd
};
gpio_use(af_config->gpio);
GPIO_PinAFConfig(af_config->gpio, af_config->gpio_pin_source,
af_config->gpio_af);
GPIO_Init(af_config->gpio, &init);
gpio_release(af_config->gpio);
}
void gpio_af_configure_low_power(const AfConfig *af_config) {
GPIO_InitTypeDef init = {
.GPIO_Pin = af_config->gpio_pin,
.GPIO_Mode = GPIO_Mode_AN,
.GPIO_Speed = GPIO_Speed_2MHz,
.GPIO_PuPd = GPIO_PuPd_NOPULL
};
gpio_use(af_config->gpio);
GPIO_Init(af_config->gpio, &init);
gpio_release(af_config->gpio);
}
void gpio_af_configure_fixed_output(const AfConfig *af_config, bool asserted) {
GPIO_InitTypeDef init = {
.GPIO_Pin = af_config->gpio_pin,
.GPIO_Mode = GPIO_Mode_OUT,
.GPIO_Speed = GPIO_Speed_2MHz,
.GPIO_OType = GPIO_OType_PP,
.GPIO_PuPd = GPIO_PuPd_NOPULL
};
gpio_use(af_config->gpio);
GPIO_Init(af_config->gpio, &init);
GPIO_WriteBit(af_config->gpio, af_config->gpio_pin,
asserted? Bit_SET : Bit_RESET);
gpio_release(af_config->gpio);
}
void gpio_input_init(const InputConfig *input_config) {
if (input_config->gpio == NULL) {
return;
}
gpio_input_init_pull_up_down(input_config, GPIO_PuPd_NOPULL);
}
void gpio_input_init_pull_up_down(const InputConfig *input_config, GPIOPuPd_TypeDef pupd) {
GPIO_InitTypeDef gpio_init = {
.GPIO_Mode = GPIO_Mode_IN,
.GPIO_PuPd = pupd,
.GPIO_Pin = input_config->gpio_pin
};
prv_init_common(input_config, &gpio_init);
}
bool gpio_input_read(const InputConfig *input_config) {
gpio_use(input_config->gpio);
uint8_t bit = GPIO_ReadInputDataBit(input_config->gpio, input_config->gpio_pin);
gpio_release(input_config->gpio);
return bit != 0;
}
void gpio_analog_init(const InputConfig *input_config) {
GPIO_InitTypeDef gpio_init = {
.GPIO_Pin = input_config->gpio_pin,
.GPIO_Mode = GPIO_Mode_AN,
.GPIO_Speed = GPIO_Speed_2MHz,
.GPIO_PuPd = GPIO_PuPd_NOPULL
};
prv_init_common(input_config, &gpio_init);
}

View file

@ -0,0 +1,89 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <stdbool.h>
#include "stm32f7xx.h"
#include "board/board.h"
void gpio_disable_all(void);
void gpio_use(GPIO_TypeDef* GPIOx);
void gpio_release(GPIO_TypeDef* GPIOx);
//! Initialize a GPIO as an output.
//!
//! @param pin_config the BOARD_CONFIG pin configuration struct
//! @param otype the output type of the pin (GPIO_OType_PP or GPIO_OType_OD)
//! @param speed the output slew rate
//! @note The slew rate should be set as low as possible for the
//! pin function to minimize ringing and RF interference.
void gpio_output_init(const OutputConfig *pin_config, GPIOOType_TypeDef otype,
GPIOSpeed_TypeDef speed);
//! Assert or deassert the output pin.
//!
//! Asserting the output drives the pin high if pin_config.active_high
//! is true, and drives it low if pin_config.active_high is false.
void gpio_output_set(const OutputConfig *pin_config, bool asserted);
//! Configure a GPIO alternate function.
//!
//! @param pin_config the BOARD_CONFIG pin configuration struct
//! @param otype the output type of the pin (GPIO_OType_PP or GPIO_OType_OD)
//! @param speed the output slew rate
//! @param pupd pull-up or pull-down configuration
//! @note The slew rate should be set as low as possible for the
//! pin function to minimize ringing and RF interference.
void gpio_af_init(const AfConfig *af_config, GPIOOType_TypeDef otype,
GPIOSpeed_TypeDef speed, GPIOPuPd_TypeDef pupd);
//! Configure a GPIO alternate function pin to minimize power consumption.
//!
//! Once a pin has been configured for low power, it is no longer
//! connected to its alternate function. \ref gpio_af_init will need to
//! be called again on the pin in order to configure it in alternate
//! function mode again.
void gpio_af_configure_low_power(const AfConfig *af_config);
//! Configure a GPIO alternate function pin to drive a constant output.
//!
//! Once a pin has been configured as a fixed output, it is no longer
//! connected to its alternate function. \ref gpio_af_init will need to
//! be called again on the pin in order to configure it in alternate
//! function mode again.
void gpio_af_configure_fixed_output(const AfConfig *af_config, bool asserted);
//! Configure all GPIOs in the system to optimize for power consumption.
//! At poweron most GPIOs can be configured as analog inputs instead of the
//! default digital input. This allows digital filtering logic to be shut down,
//! saving quite a bit of power.
void gpio_init_all(void);
//! Configure gpios as inputs (suitable for things like exti lines)
void gpio_input_init(const InputConfig *input_cfg);
//! Configure gpio as an input with internal pull up/pull down configured.
void gpio_input_init_pull_up_down(const InputConfig *input_cfg, GPIOPuPd_TypeDef pupd);
//! @return bool the current state of the GPIO pin
bool gpio_input_read(const InputConfig *input_cfg);
//! Configure gpios as analog inputs. Useful for unused GPIOs as this is their lowest power state.
void gpio_analog_init(const InputConfig *input_cfg);

View file

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

View file

@ -0,0 +1,479 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "drivers/i2c.h"
#include "i2c_definitions.h"
#include "i2c_hal.h"
#include "board/board.h"
#include "drivers/gpio.h"
#include "drivers/periph_config.h"
#include "system/passert.h"
#include "system/logging.h"
#include "util/delay.h"
#include "util/size.h"
#include <inttypes.h>
#include "stm32f7xx.h"
#define I2C_ERROR_TIMEOUT_MS (1000)
#define I2C_TIMEOUT_ATTEMPTS_MAX (2 * 1000 * 1000)
// MFI NACKs while busy. We delay ~1ms between retries so this is approximately a 1000ms timeout.
// The longest operation of the MFi chip is "start signature generation", which seems to take
// 223-224 NACKs, but sometimes for unknown reasons it can take much longer.
#define I2C_NACK_COUNT_MAX (1000)
typedef enum {
Read,
Write
} TransferDirection;
typedef enum {
SendRegisterAddress, // Send a register address, followed by a repeat start for reads
NoRegisterAddress // Do not send a register address
} TransferType;
/*----------------SEMAPHORE/LOCKING FUNCTIONS--------------------------*/
static bool prv_semaphore_take(I2CBusState *bus) {
return true;
}
static bool prv_semaphore_wait(I2CBusState *bus) {
bus->busy = true;
volatile uint32_t timeout_attempts = I2C_TIMEOUT_ATTEMPTS_MAX;
while ((timeout_attempts-- > 0) && (bus->busy)) {};
bus->busy = false;
return (timeout_attempts != 0);
}
static void prv_semaphore_give(I2CBusState *bus) {
bus->busy = false;
}
static void prv_semaphore_give_from_isr(I2CBusState *bus) {
bus->busy = false;
return;
}
/*-------------------BUS/PIN CONFIG FUNCTIONS--------------------------*/
static void prv_rail_ctl(I2CBus *bus, bool enable) {
bus->rail_ctl_fn(bus, enable);
if (enable) {
// wait for the bus supply to stabilize and the peripherals to start up.
// the MFI chip requires its reset pin to be stable for at least 10ms from startup.
delay_ms(20);
}
}
//! Power down I2C bus power supply
//! Always lock bus and peripheral config access before use
static void prv_bus_rail_power_down(I2CBus *bus) {
if (!bus->rail_ctl_fn) {
return;
}
prv_rail_ctl(bus, false);
// Drain through pull-ups
OutputConfig out_scl = {
.gpio = bus->scl_gpio.gpio,
.gpio_pin = bus->scl_gpio.gpio_pin,
.active_high = true
};
gpio_output_init(&out_scl, GPIO_PuPd_NOPULL, GPIO_Speed_2MHz);
gpio_output_set(&out_scl, false);
OutputConfig out_sda = {
.gpio = bus->sda_gpio.gpio,
.gpio_pin = bus->sda_gpio.gpio_pin,
.active_high = true
};
gpio_output_init(&out_sda, GPIO_PuPd_NOPULL, GPIO_Speed_2MHz);
gpio_output_set(&out_sda, false);
}
//! Configure bus pins for use by I2C peripheral
//! Lock bus and peripheral config access before configuring pins
static void prv_bus_pins_cfg_i2c(I2CBus *bus) {
gpio_af_init(&bus->scl_gpio, GPIO_OType_OD, GPIO_Speed_50MHz, GPIO_PuPd_NOPULL);
gpio_af_init(&bus->sda_gpio, GPIO_OType_OD, GPIO_Speed_50MHz, GPIO_PuPd_NOPULL);
}
static void prv_bus_pins_cfg_input(I2CBus *bus) {
InputConfig in_scl = {
.gpio = bus->scl_gpio.gpio,
.gpio_pin = bus->scl_gpio.gpio_pin,
};
gpio_input_init(&in_scl);
InputConfig in_sda = {
.gpio = bus->sda_gpio.gpio,
.gpio_pin = bus->sda_gpio.gpio_pin,
};
gpio_input_init(&in_sda);
}
//! Power up I2C bus power supply
//! Always lock bus and peripheral config access before use
static void prv_bus_rail_power_up(I2CBus *bus) {
if (!bus->rail_ctl_fn) {
return;
}
static const uint32_t MIN_STOP_TIME_MS = 10;
delay_ms(MIN_STOP_TIME_MS);
prv_bus_pins_cfg_input(bus);
prv_rail_ctl(bus, true);
}
//! Configure the bus pins, enable the peripheral clock and initialize the I2C peripheral.
//! Always lock the bus and peripheral config access before enabling it
static void prv_bus_enable(I2CBus *bus) {
// Don't power up rail if the bus is already in use (enable can be called to reset bus)
if (bus->state->user_count == 0) {
prv_bus_rail_power_up(bus);
}
prv_bus_pins_cfg_i2c(bus);
i2c_hal_enable(bus);
}
//! De-initialize and gate the clock to the peripheral
//! Power down rail if the bus supports that and no devices are using it
//! Always lock the bus and peripheral config access before disabling it
static void prv_bus_disable(I2CBus *bus) {
i2c_hal_disable(bus);
// Do not de-power rail if there are still devices using bus (just reset peripheral and pin
// configuration during a bus reset)
if (bus->state->user_count == 0) {
prv_bus_rail_power_down(bus);
} else {
prv_bus_pins_cfg_input(bus);
}
}
//! Perform a soft reset of the bus
//! Always lock the bus before reset
static void prv_bus_reset(I2CBus *bus) {
prv_bus_disable(bus);
prv_bus_enable(bus);
}
/*---------------INIT/USE/RELEASE/RESET FUNCTIONS----------------------*/
void i2c_init(I2CBus *bus) {
PBL_ASSERTN(bus);
*bus->state = (I2CBusState) {};
i2c_hal_init(bus);
if (bus->rail_gpio.gpio) {
gpio_output_init(&bus->rail_gpio, GPIO_OType_PP, GPIO_Speed_2MHz);
}
prv_bus_rail_power_down(bus);
}
void i2c_use(I2CSlavePort *slave) {
PBL_ASSERTN(slave);
if (slave->bus->state->user_count == 0) {
prv_bus_enable(slave->bus);
}
slave->bus->state->user_count++;
}
void i2c_release(I2CSlavePort *slave) {
PBL_ASSERTN(slave);
if (slave->bus->state->user_count == 0) {
PBL_LOG(LOG_LEVEL_ERROR, "Attempted release of disabled bus %s", slave->bus->name);
return;
}
slave->bus->state->user_count--;
if (slave->bus->state->user_count == 0) {
prv_bus_disable(slave->bus);
}
}
void i2c_reset(I2CSlavePort *slave) {
PBL_ASSERTN(slave);
if (slave->bus->state->user_count == 0) {
PBL_LOG(LOG_LEVEL_ERROR, "Attempted reset of disabled bus %s when still in use by "
"another bus", slave->bus->name);
return;
}
PBL_LOG(LOG_LEVEL_WARNING, "Resetting I2C bus %s", slave->bus->name);
// decrement user count for reset so that if this user is the only user, the
// bus will be powered down during the reset
slave->bus->state->user_count--;
// Reset and reconfigure bus and pins
prv_bus_reset(slave->bus);
// Restore user count
slave->bus->state->user_count++;
}
bool i2c_bitbang_recovery(I2CSlavePort *slave) {
PBL_ASSERTN(slave);
static const int MAX_TOGGLE_COUNT = 10;
static const int TOGGLE_DELAY = 10;
if (slave->bus->state->user_count == 0) {
PBL_LOG(LOG_LEVEL_ERROR, "Attempted bitbang recovery on disabled bus %s", slave->bus->name);
return false;
}
InputConfig in_sda = {
.gpio = slave->bus->sda_gpio.gpio,
.gpio_pin = slave->bus->sda_gpio.gpio_pin,
};
gpio_input_init(&in_sda);
OutputConfig out_scl = {
.gpio = slave->bus->scl_gpio.gpio,
.gpio_pin = slave->bus->scl_gpio.gpio_pin,
.active_high = true
};
gpio_output_init(&out_scl, GPIO_PuPd_NOPULL, GPIO_Speed_2MHz);
gpio_output_set(&out_scl, true);
bool recovered = false;
for (int i = 0; i < MAX_TOGGLE_COUNT; ++i) {
gpio_output_set(&out_scl, false);
delay_ms(TOGGLE_DELAY);
gpio_output_set(&out_scl, true);
delay_ms(TOGGLE_DELAY);
if (gpio_input_read(&in_sda)) {
recovered = true;
break;
}
}
if (recovered) {
PBL_LOG(LOG_LEVEL_DEBUG, "I2C Bus %s recovered", slave->bus->name);
} else {
PBL_LOG(LOG_LEVEL_ERROR, "I2C Bus %s still hung after bitbang reset", slave->bus->name);
}
prv_bus_pins_cfg_i2c(slave->bus);
prv_bus_reset(slave->bus);
return recovered;
}
/*--------------------DATA TRANSFER FUNCTIONS--------------------------*/
//! Wait a short amount of time for busy bit to clear
static bool prv_wait_for_not_busy(I2CBus *bus) {
static const int WAIT_DELAY = 10; // milliseconds
if (i2c_hal_is_busy(bus)) {
delay_ms(WAIT_DELAY);
if (i2c_hal_is_busy(bus)) {
PBL_LOG(LOG_LEVEL_ERROR, "Timed out waiting for bus %s to become non-busy", bus->name);
return false;
}
}
return true;
}
//! Set up and start a transfer to a bus, wait for it to finish and clean up after the transfer
//! has completed
static bool prv_do_transfer(I2CBus *bus, TransferDirection direction, uint16_t device_address,
uint8_t register_address, uint32_t size, uint8_t *data,
TransferType type) {
if (bus->state->user_count == 0) {
PBL_LOG(LOG_LEVEL_ERROR, "Attempted access to disabled bus %s", bus->name);
return false;
}
// If bus is busy (it shouldn't be as this function waits for the bus to report a non-idle state
// before exiting) reset the bus and wait for it to become not-busy
// Exit if bus remains busy. User module should reset the I2C module at this point
if (i2c_hal_is_busy(bus)) {
prv_bus_reset(bus);
if (!prv_wait_for_not_busy(bus)) {
// Bus did not recover after reset
PBL_LOG(LOG_LEVEL_ERROR, "I2C bus did not recover after reset (%s)", bus->name);
return false;
}
}
// Take binary semaphore so that next take will block
PBL_ASSERT(prv_semaphore_take(bus->state), "Could not acquire semaphore token");
// Set up transfer
bus->state->transfer = (I2CTransfer) {
.device_address = device_address,
.register_address = register_address,
.direction = direction,
.type = type,
.size = size,
.idx = 0,
.data = data,
};
i2c_hal_init_transfer(bus);
bus->state->transfer_nack_count = 0;
bool result = false;
bool complete = false;
do {
i2c_hal_start_transfer(bus);
// Wait on semaphore until it is released by interrupt or a timeout occurs
if (prv_semaphore_wait(bus->state)) {
if ((bus->state->transfer_event == I2CTransferEvent_TransferComplete) ||
(bus->state->transfer_event == I2CTransferEvent_Error)) {
// Track the max transfer duration so we can keep tabs on the MFi chip's nacking behavior
if (bus->state->transfer_event == I2CTransferEvent_Error) {
PBL_LOG(LOG_LEVEL_ERROR, "I2C Error on bus %s", bus->name);
}
complete = true;
result = (bus->state->transfer_event == I2CTransferEvent_TransferComplete);
} else if (bus->state->transfer_nack_count < I2C_NACK_COUNT_MAX) {
// NACK received after start condition sent: the MFI chip NACKs start conditions whilst it
// is busy
// Retry start condition after a short delay.
// A NACK count is incremented for each NACK received, so that legitimate NACK
// errors cause the transfer to be aborted (after the NACK count max has been reached).
bus->state->transfer_nack_count++;
// Wait 1-2ms:
delay_ms(2);
} else {
// Too many NACKs received, abort transfer
i2c_hal_abort_transfer(bus);
complete = true;
PBL_LOG(LOG_LEVEL_ERROR, "I2C Error: too many NACKs received on bus %s", bus->name);
break;
}
} else {
// Timeout, abort transfer
i2c_hal_abort_transfer(bus);
complete = true;
PBL_LOG(LOG_LEVEL_ERROR, "Transfer timed out on bus %s", bus->name);
break;
}
} while (!complete);
// Return semaphore token so another transfer can be started
prv_semaphore_give(bus->state);
// Wait for bus to to clear the busy flag before a new transfer starts
// Theoretically a transfer could complete successfully, but the busy flag never clears,
// which would cause the next transfer to fail
if (!prv_wait_for_not_busy(bus)) {
// Reset I2C bus if busy flag does not clear
prv_bus_reset(bus);
}
return result;
}
bool i2c_read_register(I2CSlavePort *slave, uint8_t register_address, uint8_t *result) {
return i2c_read_register_block(slave, register_address, 1, result);
}
bool i2c_read_register_block(I2CSlavePort *slave, uint8_t register_address_start,
uint32_t read_size, uint8_t* result_buffer) {
PBL_ASSERTN(slave);
PBL_ASSERTN(result_buffer);
// Do transfer locks the bus
bool result = prv_do_transfer(slave->bus, Read, slave->address, register_address_start, read_size,
result_buffer, SendRegisterAddress);
if (!result) {
PBL_LOG(LOG_LEVEL_ERROR, "Read failed on bus %s", slave->bus->name);
}
return result;
}
bool i2c_read_block(I2CSlavePort *slave, uint32_t read_size, uint8_t* result_buffer) {
PBL_ASSERTN(slave);
PBL_ASSERTN(result_buffer);
bool result = prv_do_transfer(slave->bus, Read, slave->address, 0, read_size, result_buffer,
NoRegisterAddress);
if (!result) {
PBL_LOG(LOG_LEVEL_ERROR, "Block read failed on bus %s", slave->bus->name);
}
return result;
}
bool i2c_write_register(I2CSlavePort *slave, uint8_t register_address, uint8_t value) {
return i2c_write_register_block(slave, register_address, 1, &value);
}
bool i2c_write_register_block(I2CSlavePort *slave, uint8_t register_address_start,
uint32_t write_size, const uint8_t* buffer) {
PBL_ASSERTN(slave);
PBL_ASSERTN(buffer);
// Do transfer locks the bus
bool result = prv_do_transfer(slave->bus, Write, slave->address, register_address_start,
write_size, (uint8_t*)buffer, SendRegisterAddress);
if (!result) {
PBL_LOG(LOG_LEVEL_ERROR, "Write failed on bus %s", slave->bus->name);
}
return result;
}
bool i2c_write_block(I2CSlavePort *slave, uint32_t write_size, const uint8_t* buffer) {
PBL_ASSERTN(slave);
PBL_ASSERTN(buffer);
// Do transfer locks the bus
bool result = prv_do_transfer(slave->bus, Write, slave->address, 0, write_size, (uint8_t*)buffer,
NoRegisterAddress);
if (!result) {
PBL_LOG(LOG_LEVEL_ERROR, "Block write failed on bus %s", slave->bus->name);
}
return result;
}
/*----------------------HAL INTERFACE--------------------------------*/
void i2c_handle_transfer_event(I2CBus *bus, I2CTransferEvent event) {
bus->state->transfer_event = event;
prv_semaphore_give_from_isr(bus->state);
}

View file

@ -0,0 +1,95 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <stdbool.h>
#include <stdint.h>
typedef enum I2CTransferEvent {
I2CTransferEvent_Timeout,
I2CTransferEvent_TransferComplete,
I2CTransferEvent_NackReceived,
I2CTransferEvent_Error,
} I2CTransferEvent;
typedef enum {
I2CTransferDirection_Read,
I2CTransferDirection_Write
} I2CTransferDirection;
typedef enum {
// Send a register address, followed by a repeat start for reads
I2CTransferType_SendRegisterAddress,
// Do not send a register address; used for block writes/reads
I2CTransferType_NoRegisterAddress
} I2CTransferType;
typedef enum I2CTransferState {
I2CTransferState_WriteAddressTx,
I2CTransferState_WriteRegAddress,
I2CTransferState_RepeatStart,
I2CTransferState_WriteAddressRx,
I2CTransferState_WaitForData,
I2CTransferState_ReadData,
I2CTransferState_WriteData,
I2CTransferState_EndWrite,
I2CTransferState_Complete,
} I2CTransferState;
typedef struct I2CTransfer {
I2CTransferState state;
uint16_t device_address;
I2CTransferDirection direction;
I2CTransferType type;
uint8_t register_address;
uint32_t size;
uint32_t idx;
uint8_t *data;
} I2CTransfer;
typedef struct I2CBusState {
I2CTransfer transfer;
I2CTransferEvent transfer_event;
int transfer_nack_count;
int user_count;
volatile bool busy;
} I2CBusState;
struct I2CBus {
I2CBusState *const state;
const struct I2CBusHal *const hal;
AfConfig scl_gpio; ///< Alternate Function configuration for SCL pin
AfConfig sda_gpio; ///< Alternate Function configuration for SDA pin
OutputConfig rail_gpio; ///< Control pin for rail
void (* const rail_ctl_fn)(I2CBus *device, bool enabled); ///< Control function for this rail.
const char *name; //! Device ID for logging purposes
};
struct I2CSlavePort {
const I2CBus *bus;
uint16_t address;
};
//! Initialize the I2C driver.
void i2c_init(I2CBus *bus);
//! Transfer event handler implemented in i2c.c and called by HAL implementation
void i2c_handle_transfer_event(I2CBus *device, I2CTransferEvent event);
#define I2C_DEBUG(fmt, args...) \
PBL_LOG_COLOR_D(LOG_DOMAIN_I2C, LOG_LEVEL_DEBUG, LOG_COLOR_LIGHT_MAGENTA, fmt, ## args)

View file

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

View file

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

View file

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

View file

@ -0,0 +1,331 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* This file should probably go in the stm32f4 folder */
#include "drivers/pmic.h"
#include "board/board.h"
#include "drivers/dbgserial.h"
#include "drivers/gpio.h"
#include "drivers/i2c.h"
#include "drivers/periph_config.h"
#include "system/logging.h"
#include "system/passert.h"
#include "util/delay.h"
#include "stm32f7xx.h"
#include <stdint.h>
#define MAX14690_ADDR (0x50)
#define MAX14690_WHOAMI (0x01)
//! The addresses of the registers that we can read using i2c
typedef enum PmicRegisters {
PmicRegisters_CHIP_ID = 0x00,
PmicRegisters_CHIP_REV = 0x01,
PmicRegisters_STATUSA = 0x02,
PmicRegisters_STATUSB = 0x03,
PmicRegisters_INTA = 0x05,
PmicRegisters_INTB = 0x06,
PmicRegisters_INT_MASK_A = 0x07,
PmicRegisters_INT_MASK_B = 0x08,
PmicRegisters_CHG_CNTL_A = 0x0A,
PmicRegisters_BUCK1_CONFIG = 0x0D,
PmicRegisters_BUCK2_CONFIG = 0x0F,
PmicRegisters_LDO1_CONFIG = 0x12,
PmicRegisters_LDO2_CONFIG = 0x14,
PmicRegisters_LDO3_CONFIG = 0x16,
PmicRegisters_MON_CFG = 0x19,
PmicRegisters_PWR_CFG = 0x1F
} PmicRegisters;
//! The different power rails that our PMIC controls
typedef enum PmicRail {
PmicRail_BUCK1, //!< 1.2V
PmicRail_BUCK2, //!< 1.8V
PmicRail_LDO1, //!< 2.0V - Auto - RTC
PmicRail_LDO2, //!< 3.2V - Manual - FPGA
//! snowy_bb: 2.5V - Manual - MFi, Magnetometer
//! snowy_evt: 1.8V - Manual - MFi
PmicRail_LDO3
} PmicRail;
//! Gives configuration information for reading a given rail through the monitor pin.
typedef struct {
const char* name; //!< Name for the rail.
//! What ratio we need to divide by in order to bring it into the range we can sense. We can
//! only read between 0 and 1.8Vs, so we need to use the PMIC hardware to divide it down before
//! sending it to us. Valid values are 1-4.
uint8_t ratio;
//! The binary value we need to put in the register to select the rail.
uint8_t source_config;
} PmicMonConfig;
// Using the Binary constants GCC extension here, supported in GCC and Clang
// https://gcc.gnu.org/onlinedocs/gcc/Binary-constants.html
static const PmicMonConfig MON_CONFIG[] = {
{ "+VBAT", 3, 0b001 }, // 3:1
};
static const int PMIC_MON_CONFIG_VBAT_INDEX = 0;
/* Private Function Definitions */
static bool prv_is_alive(void);
static void prv_set_pin_config(void);
//! Request that the rail be used or released. Internally refcounted per rail so you don't have
//! to worry about turning this off on another client.
static bool prv_update_rail_state(PmicRail rail, bool enable);
static void prv_mon_config_lock(void) {
}
static void prv_mon_config_unlock(void) {
}
static bool prv_read_register(uint8_t register_address, uint8_t *result) {
i2c_use(I2C_MAX14690);
bool rv = i2c_read_register(I2C_MAX14690, register_address, result);
i2c_release(I2C_MAX14690);
return (rv);
}
static bool prv_write_register(uint8_t register_address, uint8_t value) {
i2c_use(I2C_MAX14690);
bool rv = i2c_write_register(I2C_MAX14690, register_address, value);
i2c_release(I2C_MAX14690);
return (rv);
}
/* Public Functions */
bool pmic_init(void) {
prv_set_pin_config();
if (!prv_is_alive()) {
return false;
}
// Power up 3.2V rail
prv_update_rail_state(PmicRail_LDO2, true);
return true;
}
static bool prv_update_rail_state(PmicRail rail, bool enable) {
static int8_t s_ldo2_ref_count = 0;
static int8_t s_ldo3_ref_count = 0;
int8_t *ref_count;
uint8_t rail_control_reg = 0;
if (rail == PmicRail_LDO2) {
rail_control_reg = PmicRegisters_LDO2_CONFIG;
ref_count = &s_ldo2_ref_count;
} else if (rail == PmicRail_LDO3) {
rail_control_reg = PmicRegisters_LDO3_CONFIG;
ref_count = &s_ldo3_ref_count;
} else {
WTF;
}
uint8_t register_value;
bool success = prv_read_register(rail_control_reg, &register_value);
if (!success) {
// Failed to read the current register value
return false;
}
if (enable) {
if (*ref_count) {
(*ref_count)++;
return true;
} else {
// Set the register byte to XXXXX01X to enable the rail, mask and set
register_value = (register_value & ~0x06) | 0x02;
success = prv_write_register(rail_control_reg, register_value);
if (success) {
// We enabled the rail!
*ref_count = 1;
// We need to wait a bit for the rail to stabilize before continuing to use the device.
// It takes 2.6ms for the LDO rails to ramp.
delay_ms(3);
return true;
}
return false;
}
} else {
if (*ref_count <= 1) {
// Set the register byte to XXXXX00X to disable the rail, just mask
register_value = (register_value & ~0x06);
success = prv_write_register(rail_control_reg, register_value);
if (success) {
// We disabled the rail!
*ref_count = 0;
return true;
}
return false;
} else {
(*ref_count)--;
return true;
}
}
}
bool pmic_power_off(void) {
bool ret = prv_write_register(PmicRegisters_PWR_CFG, 0xB2);
if (ret) {
// Goodbye cruel world. The PMIC should be removing our power at any time now.
while (1) {}
__builtin_unreachable();
}
return false;
}
static bool prv_set_mon_config_register(uint8_t value) {
return prv_write_register(PmicRegisters_MON_CFG, value);
}
static bool prv_set_mon_config(const PmicMonConfig *config) {
const uint8_t ratio_config = 4 - config->ratio; // 4:1 is 0b00, 1:1 is 0b11.
const uint8_t register_value = (ratio_config << 4) | config->source_config;
bool result = prv_set_mon_config_register(register_value);
// Need to wait a short period of time for the reading to settle due to capacitance on the line.
delay_us(200);
return result;
}
bool pmic_enable_battery_measure(void) {
prv_mon_config_lock();
return prv_set_mon_config(&MON_CONFIG[PMIC_MON_CONFIG_VBAT_INDEX]);
// Don't prv_unlock, we don't want anyone else mucking with the mon config until
// pmic_disable_battery_measure is called.
}
bool pmic_disable_battery_measure(void) {
bool result = prv_set_mon_config_register(0);
// Releases the lock that was previously aquired in pmic_enable_battery_measure.
prv_mon_config_unlock();
return result;
}
bool pmic_set_charger_state(bool enable) {
// Defaults to ON
// Default value is 0xF7
const uint8_t register_value = enable ? 0xf7 : 0xf6;
bool result = prv_write_register(PmicRegisters_CHG_CNTL_A, register_value);
return result;
}
bool pmic_is_charging(void) {
uint8_t val;
if (!prv_read_register(PmicRegisters_STATUSA, &val)) {
// NOTE: When running on QEMU, i2c reads return false. For now, just assume a failed
// i2c read means we are charging
return true;
}
uint8_t chgstat = val & 0x07; // Mask off only charging status
if (chgstat == 0x02 || // Pre-charge in progress
chgstat == 0x03 || // Fast charge, CC
chgstat == 0x04 || // Fast charge, CV
chgstat == 0x05) { // Maintain charge
return true;
} else {
return false;
}
}
bool pmic_is_usb_connected(void) {
uint8_t val;
if (!prv_read_register(PmicRegisters_STATUSB, &val)) {
return false;
}
bool usb_connected = (val >> 3) & 1;
return usb_connected;
}
void pmic_read_chip_info(uint8_t *chip_id, uint8_t *chip_revision) {
prv_read_register(PmicRegisters_CHIP_ID, chip_id);
prv_read_register(PmicRegisters_CHIP_REV, chip_revision);
}
/* Private Function Implementations */
static bool prv_is_alive(void) {
uint8_t val;
prv_read_register(0x00, &val);
if (val == MAX14690_WHOAMI) {
PBL_LOG(LOG_LEVEL_DEBUG, "Found the max14690");
return true;
} else {
PBL_LOG(LOG_LEVEL_DEBUG, "Error reading max14690 WHOAMI byte");
return false;
}
}
static void prv_set_pin_config(void) {
periph_config_acquire_lock();
// Initialize the GPIOs for the 4V5 & 6V6 rails
gpio_output_init(&BOARD_CONFIG_POWER.rail_4V5_ctrl, GPIO_OType_OD, GPIO_Speed_50MHz);
if (BOARD_CONFIG_POWER.rail_6V6_ctrl.gpio) {
gpio_output_init(&BOARD_CONFIG_POWER.rail_6V6_ctrl, GPIO_OType_OD, GPIO_Speed_50MHz);
}
gpio_output_init(&BOARD_CONFIG_ACCESSORY.power_en, GPIO_OType_OD, GPIO_Speed_50MHz);
periph_config_release_lock();
}
void set_4V5_power_state(bool enabled) {
gpio_output_set(&BOARD_CONFIG_POWER.rail_4V5_ctrl, enabled);
}
void set_6V6_power_state(bool enabled) {
if (BOARD_CONFIG_POWER.rail_6V6_ctrl.gpio) {
gpio_output_set(&BOARD_CONFIG_POWER.rail_6V6_ctrl, enabled);
}
}

View file

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

View file

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

View file

@ -0,0 +1,61 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <stdbool.h>
#include <stdint.h>
//! Initialize the PMIC driver. Call this once at startup.
bool pmic_init(void);
//! Tell the PMIC to power off the board and enter a standby-like state. All components will
//! have their power removed (except for the RTC so we'll still keep time) and the PMIC itself
//! will monitor the buttons for when to wake up.
bool pmic_power_off(void);
//! Enable the battery monitor portion of the PMIC. Remember to turn this off with
//! pmic_disable_battery_measure when immediate readings aren't required.
bool pmic_enable_battery_measure(void);
//! Disable the battery monitor portion of the PMIC.
bool pmic_disable_battery_measure(void);
//! Enable and disable the charging portion of the PMIC.
bool pmic_set_charger_state(bool enable);
//! @return true if the PMIC thinks we're charging (adding additional charge to the battery).
//! Note that once we hit full charge we'll no longer be charging, which is a different state
//! that pmic_is_usb_connected.
bool pmic_is_charging(void);
//! @return true if a usb-ish charger cable is currently connected.
bool pmic_is_usb_connected(void);
//! Read information about the chip for tracking purposes.
void pmic_read_chip_info(uint8_t *chip_id, uint8_t *chip_revision);
//! Enables the LDO3 power rail. Used for the MFi/Magnetometer on snowy_bb, MFi on snowy_evt.
void set_ldo3_power_state(bool enabled);
//! Enables the 4.5V power rail. Used for the display on snowy.
void set_4V5_power_state(bool enabled);
//! Enables the 6.6V power rail. Used for the display on snowy.
void set_6V6_power_state(bool enabled);
//! Enables power to the accessory connector.
void set_accessory_power_state(bool enabled);

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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