mirror of
https://github.com/google/pebble.git
synced 2025-05-22 03:14:52 +00:00
Import of the watch repository from Pebble
This commit is contained in:
commit
3b92768480
10334 changed files with 2564465 additions and 0 deletions
49
platform/robert/boot/src/drivers/button.c
Normal file
49
platform/robert/boot/src/drivers/button.c
Normal file
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "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();
|
||||
}
|
27
platform/robert/boot/src/drivers/button.h
Normal file
27
platform/robert/boot/src/drivers/button.h
Normal file
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "button_id.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
void button_init(void);
|
||||
|
||||
bool button_is_pressed(ButtonId id);
|
||||
uint8_t button_get_state_bits(void);
|
41
platform/robert/boot/src/drivers/button_id.h
Normal file
41
platform/robert/boot/src/drivers/button_id.h
Normal file
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
//! @addtogroup UI
|
||||
//! @{
|
||||
//! @addtogroup Clicks
|
||||
//! \brief Dealing with button input
|
||||
//! @{
|
||||
|
||||
//! Button ID values
|
||||
//! @see \ref click_recognizer_get_button_id()
|
||||
typedef enum {
|
||||
//! Back button
|
||||
BUTTON_ID_BACK = 0,
|
||||
//! Up button
|
||||
BUTTON_ID_UP,
|
||||
//! Select (middle) button
|
||||
BUTTON_ID_SELECT,
|
||||
//! Down button
|
||||
BUTTON_ID_DOWN,
|
||||
//! Total number of buttons
|
||||
NUM_BUTTONS
|
||||
} ButtonId;
|
||||
|
||||
//! @} // end addtogroup Clicks
|
||||
//! @} // end addtogroup UI
|
179
platform/robert/boot/src/drivers/dbgserial.c
Normal file
179
platform/robert/boot/src/drivers/dbgserial.c
Normal 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);
|
||||
}
|
34
platform/robert/boot/src/drivers/dbgserial.h
Normal file
34
platform/robert/boot/src/drivers/dbgserial.h
Normal file
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <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)));
|
34
platform/robert/boot/src/drivers/display.h
Normal file
34
platform/robert/boot/src/drivers/display.h
Normal file
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
void display_init(void);
|
||||
|
||||
void display_boot_splash(void);
|
||||
|
||||
void display_error_code(uint32_t);
|
||||
|
||||
//! Do whatever is necessary to prevent visual artifacts when resetting
|
||||
//! the watch.
|
||||
void display_prepare_for_reset(void);
|
||||
|
||||
//! Display the progress of a firmware update.
|
||||
//!
|
||||
//! The progress is expressed as a rational number less than or equal to 1.
|
||||
//! When numerator == denominator, the progress indicator shows that the update
|
||||
//! is complete.
|
||||
void display_firmware_update_progress(uint32_t numerator, uint32_t denominator);
|
210
platform/robert/boot/src/drivers/display/boot_fpga.c
Normal file
210
platform/robert/boot/src/drivers/display/boot_fpga.c
Normal 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 *)¶m, 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();
|
||||
}
|
186
platform/robert/boot/src/drivers/display/ice40lp.c
Normal file
186
platform/robert/boot/src/drivers/display/ice40lp.c
Normal 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)
|
||||
}
|
27
platform/robert/boot/src/drivers/display/ice40lp.h
Normal file
27
platform/robert/boot/src/drivers/display/ice40lp.h
Normal file
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#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);
|
|
@ -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;
|
46
platform/robert/boot/src/drivers/exti.h
Normal file
46
platform/robert/boot/src/drivers/exti.h
Normal file
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "board/board.h"
|
||||
|
||||
typedef enum {
|
||||
ExtiTrigger_Rising,
|
||||
ExtiTrigger_Falling,
|
||||
ExtiTrigger_RisingFalling
|
||||
} ExtiTrigger;
|
||||
|
||||
//! See section 12.2.5 "External interrupt/event line mapping" in the STM32F2 reference manual
|
||||
typedef enum {
|
||||
ExtiLineOther_RTCAlarm = 17,
|
||||
ExtiLineOther_RTCWakeup = 22
|
||||
} ExtiLineOther;
|
||||
|
||||
typedef void (*ExtiHandlerCallback)(void);
|
||||
|
||||
//! Configures the given EXTI and NVIC for the given configuration.
|
||||
void exti_configure_pin(ExtiConfig cfg, ExtiTrigger trigger, ExtiHandlerCallback cb);
|
||||
//! Configures the given EXTI and NVIC for the given configuration.
|
||||
void exti_configure_other(ExtiLineOther exti_line, ExtiTrigger trigger);
|
||||
|
||||
static inline void exti_enable(ExtiConfig config);
|
||||
static inline void exti_disable(ExtiConfig config);
|
||||
|
||||
void exti_enable_other(ExtiLineOther);
|
||||
void exti_disable_other(ExtiLineOther);
|
||||
|
||||
#include "exti.inl.h"
|
27
platform/robert/boot/src/drivers/exti.inl.h
Normal file
27
platform/robert/boot/src/drivers/exti.inl.h
Normal file
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
//! @file exti.inl.h
|
||||
//!
|
||||
//! Helper functions intended to be inlined into the calling code.
|
||||
|
||||
static inline void exti_enable(ExtiConfig config) {
|
||||
exti_enable_other(config.exti_line);
|
||||
}
|
||||
|
||||
static inline void exti_disable(ExtiConfig config) {
|
||||
exti_disable_other(config.exti_line);
|
||||
}
|
40
platform/robert/boot/src/drivers/flash.h
Normal file
40
platform/robert/boot/src/drivers/flash.h
Normal file
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
//! Configure the micro's peripherals to communicate with the flash chip
|
||||
void flash_init(void);
|
||||
|
||||
//! Read 1 or more bytes starting at the specified 24bit address into
|
||||
//! the provided buffer. This function does no range checking, so it is
|
||||
//! currently possible to run off the end of the flash.
|
||||
//!
|
||||
//! @param buffer A byte-buffer that will be used to store the data
|
||||
//! read from flash.
|
||||
//! @param start_addr The address of the first byte to be read from flash.
|
||||
//! @param buffer_size The total number of bytes to be read from flash.
|
||||
void flash_read_bytes(uint8_t* buffer, uint32_t start_addr, uint32_t buffer_size);
|
||||
|
||||
//! Check if we can talk to the flash.
|
||||
//! @return true if the CFI table can be queried.
|
||||
bool flash_sanity_check(void);
|
||||
|
||||
//! Get the checksum of a region of flash
|
||||
uint32_t flash_calculate_checksum(uint32_t flash_addr, uint32_t length);
|
38
platform/robert/boot/src/drivers/flash/flash_crc.c
Normal file
38
platform/robert/boot/src/drivers/flash/flash_crc.c
Normal file
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "drivers/flash.h"
|
||||
|
||||
#include "util/crc32.h"
|
||||
|
||||
#define CRC_CHUNK_SIZE 1024
|
||||
|
||||
uint32_t flash_calculate_checksum(uint32_t flash_addr, uint32_t num_bytes) {
|
||||
uint8_t buffer[CRC_CHUNK_SIZE];
|
||||
|
||||
uint32_t crc = CRC32_INIT;
|
||||
|
||||
while (num_bytes > CRC_CHUNK_SIZE) {
|
||||
flash_read_bytes(buffer, flash_addr, CRC_CHUNK_SIZE);
|
||||
crc = crc32(crc, buffer, CRC_CHUNK_SIZE);
|
||||
|
||||
num_bytes -= CRC_CHUNK_SIZE;
|
||||
flash_addr += CRC_CHUNK_SIZE;
|
||||
}
|
||||
|
||||
flash_read_bytes(buffer, flash_addr, num_bytes);
|
||||
return crc32(crc, buffer, num_bytes);
|
||||
}
|
220
platform/robert/boot/src/drivers/flash/mt25q.c
Normal file
220
platform/robert/boot/src/drivers/flash/mt25q.c
Normal 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();
|
||||
}
|
158
platform/robert/boot/src/drivers/gpio.c
Normal file
158
platform/robert/boot/src/drivers/gpio.c
Normal file
|
@ -0,0 +1,158 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#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);
|
||||
}
|
89
platform/robert/boot/src/drivers/gpio.h
Normal file
89
platform/robert/boot/src/drivers/gpio.h
Normal 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);
|
97
platform/robert/boot/src/drivers/i2c.h
Normal file
97
platform/robert/boot/src/drivers/i2c.h
Normal file
|
@ -0,0 +1,97 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "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);
|
479
platform/robert/boot/src/drivers/i2c/i2c.c
Normal file
479
platform/robert/boot/src/drivers/i2c/i2c.c
Normal 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);
|
||||
}
|
95
platform/robert/boot/src/drivers/i2c/i2c_definitions.h
Normal file
95
platform/robert/boot/src/drivers/i2c/i2c_definitions.h
Normal 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)
|
336
platform/robert/boot/src/drivers/i2c/i2c_hal.c
Normal file
336
platform/robert/boot/src/drivers/i2c/i2c_hal.c
Normal 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);
|
||||
}
|
35
platform/robert/boot/src/drivers/i2c/i2c_hal.h
Normal file
35
platform/robert/boot/src/drivers/i2c/i2c_hal.h
Normal file
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#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);
|
37
platform/robert/boot/src/drivers/i2c/i2c_hal_definitions.h
Normal file
37
platform/robert/boot/src/drivers/i2c/i2c_hal_definitions.h
Normal file
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
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);
|
331
platform/robert/boot/src/drivers/max14690_pmic.c
Normal file
331
platform/robert/boot/src/drivers/max14690_pmic.c
Normal 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, ®ister_value);
|
||||
|
||||
if (!success) {
|
||||
// Failed to read the current register value
|
||||
return false;
|
||||
}
|
||||
|
||||
if (enable) {
|
||||
if (*ref_count) {
|
||||
(*ref_count)++;
|
||||
return true;
|
||||
} else {
|
||||
// Set the register byte to XXXXX01X to enable the rail, mask and set
|
||||
register_value = (register_value & ~0x06) | 0x02;
|
||||
|
||||
success = prv_write_register(rail_control_reg, register_value);
|
||||
|
||||
if (success) {
|
||||
// We enabled the rail!
|
||||
*ref_count = 1;
|
||||
|
||||
// We need to wait a bit for the rail to stabilize before continuing to use the device.
|
||||
// It takes 2.6ms for the LDO rails to ramp.
|
||||
delay_ms(3);
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (*ref_count <= 1) {
|
||||
// Set the register byte to XXXXX00X to disable the rail, just mask
|
||||
register_value = (register_value & ~0x06);
|
||||
|
||||
success = prv_write_register(rail_control_reg, register_value);
|
||||
|
||||
if (success) {
|
||||
// We disabled the rail!
|
||||
*ref_count = 0;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
(*ref_count)--;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool pmic_power_off(void) {
|
||||
bool ret = prv_write_register(PmicRegisters_PWR_CFG, 0xB2);
|
||||
|
||||
if (ret) {
|
||||
// Goodbye cruel world. The PMIC should be removing our power at any time now.
|
||||
|
||||
while (1) {}
|
||||
__builtin_unreachable();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool prv_set_mon_config_register(uint8_t value) {
|
||||
return prv_write_register(PmicRegisters_MON_CFG, value);
|
||||
}
|
||||
|
||||
static bool prv_set_mon_config(const PmicMonConfig *config) {
|
||||
const uint8_t ratio_config = 4 - config->ratio; // 4:1 is 0b00, 1:1 is 0b11.
|
||||
|
||||
const uint8_t register_value = (ratio_config << 4) | config->source_config;
|
||||
bool result = prv_set_mon_config_register(register_value);
|
||||
|
||||
// Need to wait a short period of time for the reading to settle due to capacitance on the line.
|
||||
delay_us(200);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool pmic_enable_battery_measure(void) {
|
||||
prv_mon_config_lock();
|
||||
|
||||
return prv_set_mon_config(&MON_CONFIG[PMIC_MON_CONFIG_VBAT_INDEX]);
|
||||
|
||||
// Don't prv_unlock, we don't want anyone else mucking with the mon config until
|
||||
// pmic_disable_battery_measure is called.
|
||||
}
|
||||
|
||||
bool pmic_disable_battery_measure(void) {
|
||||
bool result = prv_set_mon_config_register(0);
|
||||
|
||||
// Releases the lock that was previously aquired in pmic_enable_battery_measure.
|
||||
prv_mon_config_unlock();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool pmic_set_charger_state(bool enable) {
|
||||
// Defaults to ON
|
||||
// Default value is 0xF7
|
||||
const uint8_t register_value = enable ? 0xf7 : 0xf6;
|
||||
|
||||
bool result = prv_write_register(PmicRegisters_CHG_CNTL_A, register_value);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
bool pmic_is_charging(void) {
|
||||
uint8_t val;
|
||||
if (!prv_read_register(PmicRegisters_STATUSA, &val)) {
|
||||
// NOTE: When running on QEMU, i2c reads return false. For now, just assume a failed
|
||||
// i2c read means we are charging
|
||||
return true;
|
||||
}
|
||||
|
||||
uint8_t chgstat = val & 0x07; // 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);
|
||||
}
|
||||
}
|
103
platform/robert/boot/src/drivers/periph_config.c
Normal file
103
platform/robert/boot/src/drivers/periph_config.c
Normal file
|
@ -0,0 +1,103 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#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);
|
||||
}
|
25
platform/robert/boot/src/drivers/periph_config.h
Normal file
25
platform/robert/boot/src/drivers/periph_config.h
Normal file
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <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);
|
61
platform/robert/boot/src/drivers/pmic.h
Normal file
61
platform/robert/boot/src/drivers/pmic.h
Normal file
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#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);
|
41
platform/robert/boot/src/drivers/pwr.c
Normal file
41
platform/robert/boot/src/drivers/pwr.c
Normal file
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "drivers/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;
|
||||
}
|
25
platform/robert/boot/src/drivers/pwr.h
Normal file
25
platform/robert/boot/src/drivers/pwr.h
Normal file
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
void pwr_access_backup_domain(bool enable_access);
|
||||
|
||||
bool pwr_did_boot_from_standby(void);
|
||||
|
||||
void pwr_clear_boot_from_standby_flag(void);
|
109
platform/robert/boot/src/drivers/system_flash.c
Normal file
109
platform/robert/boot/src/drivers/system_flash.c
Normal 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;
|
||||
}
|
60
platform/robert/boot/src/drivers/system_flash.h
Normal file
60
platform/robert/boot/src/drivers/system_flash.h
Normal file
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#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);
|
39
platform/robert/boot/src/drivers/watchdog.c
Normal file
39
platform/robert/boot/src/drivers/watchdog.c
Normal file
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "drivers/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;
|
||||
}
|
24
platform/robert/boot/src/drivers/watchdog.h
Normal file
24
platform/robert/boot/src/drivers/watchdog.h
Normal file
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
void watchdog_init(void);
|
||||
void watchdog_start(void);
|
||||
|
||||
bool watchdog_check_reset_flag(void);
|
Loading…
Add table
Add a link
Reference in a new issue