mirror of
https://github.com/google/pebble.git
synced 2025-05-31 15:33:11 +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
85
platform/silk/boot/src/drivers/button.c
Normal file
85
platform/silk/boot/src/drivers/button.c
Normal file
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "drivers/button.h"
|
||||
|
||||
#include "board/board.h"
|
||||
#include "drivers/periph_config.h"
|
||||
#include "drivers/gpio.h"
|
||||
|
||||
static void initialize_button_common(void) {
|
||||
if (!BOARD_CONFIG_BUTTON.button_com.gpio) {
|
||||
// This board doesn't use a button common pin.
|
||||
return;
|
||||
}
|
||||
|
||||
// Configure BUTTON_COM to drive low. When the button
|
||||
// is pressed this pin will be connected to the pin for the
|
||||
// button.
|
||||
GPIO_InitTypeDef GPIO_InitStructure;
|
||||
GPIO_StructInit(&GPIO_InitStructure);
|
||||
|
||||
GPIO_InitStructure.GPIO_Pin = BOARD_CONFIG_BUTTON.button_com.gpio_pin;
|
||||
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
|
||||
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
|
||||
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
|
||||
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
|
||||
GPIO_Init(BOARD_CONFIG_BUTTON.button_com.gpio, &GPIO_InitStructure);
|
||||
GPIO_WriteBit(BOARD_CONFIG_BUTTON.button_com.gpio, BOARD_CONFIG_BUTTON.button_com.gpio_pin, 0);
|
||||
}
|
||||
|
||||
static void initialize_button(const ButtonConfig* config) {
|
||||
// Configure the pin itself
|
||||
GPIO_InitTypeDef GPIO_InitStructure;
|
||||
GPIO_StructInit(&GPIO_InitStructure);
|
||||
|
||||
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
|
||||
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
|
||||
GPIO_InitStructure.GPIO_PuPd = config->pull;
|
||||
GPIO_InitStructure.GPIO_Pin = config->gpio_pin;
|
||||
GPIO_Init(config->gpio, &GPIO_InitStructure);
|
||||
}
|
||||
|
||||
bool button_is_pressed(ButtonId id) {
|
||||
const ButtonConfig* button_config = &BOARD_CONFIG_BUTTON.buttons[id];
|
||||
uint8_t bit = GPIO_ReadInputDataBit(button_config->gpio, button_config->gpio_pin);
|
||||
return bit;
|
||||
}
|
||||
|
||||
uint8_t button_get_state_bits(void) {
|
||||
uint8_t button_state = 0x00;
|
||||
for (int i = 0; i < NUM_BUTTONS; ++i) {
|
||||
button_state |= (button_is_pressed(i) ? 0x01 : 0x00) << i;
|
||||
}
|
||||
return button_state;
|
||||
}
|
||||
|
||||
void button_init(void) {
|
||||
// Need to disable button wakeup functionality
|
||||
// or the buttons don't register input
|
||||
PWR_WakeUpPinCmd(PWR_WakeUp_Pin1, DISABLE);
|
||||
PWR_WakeUpPinCmd(PWR_WakeUp_Pin2, DISABLE);
|
||||
PWR_WakeUpPinCmd(PWR_WakeUp_Pin3, DISABLE);
|
||||
|
||||
periph_config_enable(RCC_APB2PeriphClockCmd, RCC_APB2Periph_SYSCFG);
|
||||
|
||||
initialize_button_common();
|
||||
for (int i = 0; i < NUM_BUTTONS; ++i) {
|
||||
initialize_button(&BOARD_CONFIG_BUTTON.buttons[i]);
|
||||
}
|
||||
|
||||
periph_config_disable(RCC_APB2PeriphClockCmd, RCC_APB2Periph_SYSCFG);
|
||||
}
|
27
platform/silk/boot/src/drivers/button.h
Normal file
27
platform/silk/boot/src/drivers/button.h
Normal file
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "button_id.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
void button_init(void);
|
||||
|
||||
bool button_is_pressed(ButtonId id);
|
||||
uint8_t button_get_state_bits(void);
|
41
platform/silk/boot/src/drivers/button_id.h
Normal file
41
platform/silk/boot/src/drivers/button_id.h
Normal file
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
//! @addtogroup UI
|
||||
//! @{
|
||||
//! @addtogroup Clicks
|
||||
//! \brief Dealing with button input
|
||||
//! @{
|
||||
|
||||
//! Button ID values
|
||||
//! @see \ref click_recognizer_get_button_id()
|
||||
typedef enum {
|
||||
//! Back button
|
||||
BUTTON_ID_BACK = 0,
|
||||
//! Up button
|
||||
BUTTON_ID_UP,
|
||||
//! Select (middle) button
|
||||
BUTTON_ID_SELECT,
|
||||
//! Down button
|
||||
BUTTON_ID_DOWN,
|
||||
//! Total number of buttons
|
||||
NUM_BUTTONS
|
||||
} ButtonId;
|
||||
|
||||
//! @} // end addtogroup Clicks
|
||||
//! @} // end addtogroup UI
|
176
platform/silk/boot/src/drivers/dbgserial.c
Normal file
176
platform/silk/boot/src/drivers/dbgserial.c
Normal file
|
@ -0,0 +1,176 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "drivers/dbgserial.h"
|
||||
|
||||
#include "drivers/periph_config.h"
|
||||
|
||||
#include "drivers/gpio.h"
|
||||
|
||||
#include "stm32f4xx_rcc.h"
|
||||
#include "stm32f4xx_gpio.h"
|
||||
#include "stm32f4xx_usart.h"
|
||||
#include "util/attributes.h"
|
||||
#include "util/cobs.h"
|
||||
#include "util/crc32.h"
|
||||
#include "util/misc.h"
|
||||
#include "util/misc.h"
|
||||
#include "util/net.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#define MAX_MESSAGE (256)
|
||||
#define FRAME_DELIMITER '\x55'
|
||||
#define PULSE_TRANSPORT_PUSH (0x5021)
|
||||
#define PULSE_PROTOCOL_LOGGING (0x0003)
|
||||
|
||||
static const int SERIAL_BAUD_RATE = 1000000;
|
||||
|
||||
typedef struct PACKED PulseFrame {
|
||||
net16 protocol;
|
||||
unsigned char information[];
|
||||
} PulseFrame;
|
||||
|
||||
typedef struct PACKED PushPacket {
|
||||
net16 protocol;
|
||||
net16 length;
|
||||
unsigned char information[];
|
||||
} PushPacket;
|
||||
|
||||
static const unsigned char s_message_header[] = {
|
||||
// Message type: text
|
||||
1,
|
||||
// Source filename
|
||||
'B', 'O', 'O', 'T', 'L', 'O', 'A', 'D', 'E', 'R', 0, 0, 0, 0, 0, 0,
|
||||
// Log level and task
|
||||
'*', '*',
|
||||
// Timestamp
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
// Line number
|
||||
0, 0,
|
||||
};
|
||||
|
||||
static size_t s_message_length = 0;
|
||||
static unsigned char s_message_buffer[MAX_MESSAGE];
|
||||
|
||||
|
||||
void dbgserial_init(void) {
|
||||
GPIO_InitTypeDef GPIO_InitStructure;
|
||||
USART_InitTypeDef USART_InitStructure;
|
||||
|
||||
// Enable GPIO and UART3 peripheral clocks
|
||||
periph_config_enable(RCC_APB2PeriphClockCmd, RCC_APB2Periph_USART1);
|
||||
|
||||
// USART_OverSampling8Cmd(USART3, ENABLE);
|
||||
|
||||
/* Connect PXx to USARTx_Tx*/
|
||||
GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_USART1);
|
||||
|
||||
/* Connect PXx to USARTx_Rx*/
|
||||
GPIO_PinAFConfig(GPIOB, GPIO_PinSource7, GPIO_AF_USART1);
|
||||
|
||||
/* Configure USART Tx as alternate function */
|
||||
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
|
||||
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
|
||||
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
|
||||
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
|
||||
|
||||
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
|
||||
GPIO_Init(GPIOA, &GPIO_InitStructure);
|
||||
|
||||
/* Configure USART Rx as alternate function */
|
||||
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
|
||||
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
|
||||
GPIO_Init(GPIOB, &GPIO_InitStructure);
|
||||
|
||||
/* USART configuration */
|
||||
USART_InitStructure.USART_BaudRate = SERIAL_BAUD_RATE;
|
||||
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
|
||||
USART_InitStructure.USART_StopBits = USART_StopBits_1;
|
||||
USART_InitStructure.USART_Parity = USART_Parity_No;
|
||||
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
|
||||
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
|
||||
USART_Init(USART1, &USART_InitStructure);
|
||||
|
||||
/* Enable USART */
|
||||
USART_Cmd(USART1, ENABLE);
|
||||
}
|
||||
|
||||
static void prv_putchar(uint8_t c) {
|
||||
while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET) continue;
|
||||
USART_SendData(USART1, c);
|
||||
while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET) continue;
|
||||
}
|
||||
|
||||
void dbgserial_print(const char* str) {
|
||||
for (; *str && s_message_length < MAX_MESSAGE; ++str) {
|
||||
if (*str == '\n') {
|
||||
dbgserial_newline();
|
||||
} else if (*str != '\r') {
|
||||
s_message_buffer[s_message_length++] = *str;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void dbgserial_newline(void) {
|
||||
uint32_t crc;
|
||||
size_t raw_length = sizeof(PulseFrame) + sizeof(PushPacket) +
|
||||
sizeof(s_message_header) + s_message_length + sizeof(crc);
|
||||
unsigned char raw_packet[raw_length];
|
||||
|
||||
PulseFrame *frame = (PulseFrame *)raw_packet;
|
||||
frame->protocol = hton16(PULSE_TRANSPORT_PUSH);
|
||||
|
||||
PushPacket *transport = (PushPacket *)frame->information;
|
||||
transport->protocol = hton16(PULSE_PROTOCOL_LOGGING);
|
||||
transport->length = hton16(sizeof(PushPacket) + sizeof(s_message_header) +
|
||||
s_message_length);
|
||||
|
||||
unsigned char *app = transport->information;
|
||||
memcpy(app, s_message_header, sizeof(s_message_header));
|
||||
memcpy(&app[sizeof(s_message_header)], s_message_buffer,
|
||||
s_message_length);
|
||||
|
||||
crc = crc32(CRC32_INIT, raw_packet, raw_length - sizeof(crc));
|
||||
memcpy(&raw_packet[raw_length - sizeof(crc)], &crc, sizeof(crc));
|
||||
|
||||
unsigned char cooked_packet[MAX_SIZE_AFTER_COBS_ENCODING(raw_length)];
|
||||
size_t cooked_length = cobs_encode(cooked_packet, raw_packet, raw_length);
|
||||
|
||||
prv_putchar(FRAME_DELIMITER);
|
||||
for (size_t i = 0; i < cooked_length; ++i) {
|
||||
if (cooked_packet[i] == FRAME_DELIMITER) {
|
||||
prv_putchar('\0');
|
||||
} else {
|
||||
prv_putchar(cooked_packet[i]);
|
||||
}
|
||||
}
|
||||
prv_putchar(FRAME_DELIMITER);
|
||||
|
||||
s_message_length = 0;
|
||||
}
|
||||
|
||||
void dbgserial_putstr(const char* str) {
|
||||
dbgserial_print(str);
|
||||
|
||||
dbgserial_newline();
|
||||
}
|
||||
|
||||
void dbgserial_print_hex(uint32_t value) {
|
||||
char buf[12];
|
||||
itoa_hex(value, buf, sizeof(buf));
|
||||
dbgserial_print(buf);
|
||||
}
|
34
platform/silk/boot/src/drivers/dbgserial.h
Normal file
34
platform/silk/boot/src/drivers/dbgserial.h
Normal file
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
void dbgserial_init(void);
|
||||
|
||||
void dbgserial_putstr(const char* str);
|
||||
|
||||
void dbgserial_newline(void);
|
||||
|
||||
//! Like dbgserial_putstr, but without a terminating newline
|
||||
void dbgserial_print(const char* str);
|
||||
|
||||
void dbgserial_print_hex(uint32_t value);
|
||||
|
||||
void dbgserial_putstr_fmt(char* buffer, unsigned int buffer_size, const char* fmt, ...)
|
||||
__attribute__((format(printf, 3, 4)));
|
34
platform/silk/boot/src/drivers/display.h
Normal file
34
platform/silk/boot/src/drivers/display.h
Normal file
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
void display_init(void);
|
||||
|
||||
void display_boot_splash(void);
|
||||
|
||||
void display_error_code(uint32_t);
|
||||
|
||||
//! Do whatever is necessary to prevent visual artifacts when resetting
|
||||
//! the watch.
|
||||
void display_prepare_for_reset(void);
|
||||
|
||||
//! Display the progress of a firmware update.
|
||||
//!
|
||||
//! The progress is expressed as a rational number less than or equal to 1.
|
||||
//! When numerator == denominator, the progress indicator shows that the update
|
||||
//! is complete.
|
||||
void display_firmware_update_progress(uint32_t numerator, uint32_t denominator);
|
|
@ -0,0 +1,47 @@
|
|||
#define dead_face_width 92
|
||||
#define dead_face_height 44
|
||||
static const unsigned char dead_face_bits[] = {
|
||||
0x00, 0x0c, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x30, 0x00,
|
||||
0x00, 0x18, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x18, 0x00,
|
||||
0x00, 0x30, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x0c, 0x00,
|
||||
0x00, 0x60, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x06, 0x00,
|
||||
0x00, 0xc0, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x03, 0x00,
|
||||
0x00, 0x80, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8c, 0x01, 0x00,
|
||||
0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd8, 0x00, 0x00,
|
||||
0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00,
|
||||
0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00,
|
||||
0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd8, 0x00, 0x00,
|
||||
0x00, 0x80, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8c, 0x01, 0x00,
|
||||
0x00, 0xc0, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x03, 0x00,
|
||||
0x00, 0x60, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x06, 0x00,
|
||||
0x00, 0x30, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x0c, 0x00,
|
||||
0x00, 0x18, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x18, 0x00,
|
||||
0x00, 0x0c, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x30, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x80, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0xfe, 0xff, 0xff, 0x7f, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0xe0, 0x7f, 0x00, 0x00, 0xfe, 0x07, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0xfe, 0x01, 0x00, 0x00, 0x80, 0x7f, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x80, 0x1f, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x01, 0x00, 0x00,
|
||||
0x00, 0x00, 0xf0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x80, 0x0f, 0x00, 0x00,
|
||||
0x00, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00,
|
||||
0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00,
|
||||
0x00, 0x80, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x01, 0x00,
|
||||
0x00, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00,
|
||||
0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00,
|
||||
0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00,
|
||||
0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00,
|
||||
0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00,
|
||||
0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00,
|
||||
0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01,
|
||||
0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03,
|
||||
0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06,
|
||||
0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c,
|
||||
0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08 };
|
|
@ -0,0 +1,11 @@
|
|||
#define empty_bar_width 96
|
||||
#define empty_bar_height 8
|
||||
static const unsigned char empty_bar_bits[] = {
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
|
||||
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
|
||||
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
|
||||
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
|
||||
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
|
||||
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
|
|
@ -0,0 +1,20 @@
|
|||
#define error_url_width 109
|
||||
#define error_url_height 14
|
||||
static const unsigned char error_url_bits[] = {
|
||||
0x00, 0x00, 0x06, 0x83, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x06, 0x83, 0x01, 0x00, 0x00, 0x00, 0x00, 0x0c,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x83, 0x01, 0x00, 0x00, 0x00,
|
||||
0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0xd8, 0x78, 0x36, 0x9b, 0x79, 0xc0,
|
||||
0xe3, 0xd9, 0x0c, 0x8c, 0x67, 0xdb, 0x3c, 0x1b, 0xf8, 0xfd, 0x7e, 0xbf,
|
||||
0xfd, 0xe0, 0xf7, 0xfb, 0x1f, 0xc6, 0xef, 0xfb, 0x7e, 0x1f, 0x98, 0xcd,
|
||||
0x66, 0xb3, 0xcd, 0x60, 0x36, 0x9b, 0x19, 0xc6, 0x6c, 0x18, 0x66, 0x03,
|
||||
0x98, 0xfd, 0x66, 0xb3, 0xfd, 0x60, 0x30, 0x9b, 0x19, 0xc6, 0x6f, 0x18,
|
||||
0x66, 0x03, 0x98, 0xfd, 0x66, 0xb3, 0xfd, 0x60, 0x30, 0x9b, 0x19, 0xc3,
|
||||
0x6f, 0x18, 0x66, 0x03, 0x98, 0x0d, 0x66, 0xb3, 0x0d, 0x60, 0x36, 0x9b,
|
||||
0x19, 0xc3, 0x60, 0x18, 0x66, 0x03, 0xf8, 0xfd, 0x7e, 0xbf, 0xfd, 0xec,
|
||||
0xf7, 0x9b, 0x19, 0xc3, 0x6f, 0x18, 0x7e, 0x03, 0xd8, 0x78, 0x36, 0x9b,
|
||||
0x79, 0xcc, 0xe3, 0x99, 0x99, 0x81, 0x67, 0x18, 0x3c, 0x03, 0x18, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00,
|
||||
0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00 };
|
103
platform/silk/boot/src/drivers/display/resources/hex_digits.h
Normal file
103
platform/silk/boot/src/drivers/display/resources/hex_digits.h
Normal file
|
@ -0,0 +1,103 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
// hex_digits_bits is indexed on the digit:
|
||||
// ie: hex_digits_bits[0] is the bits to display 0 on
|
||||
// the screen stored in xbm format
|
||||
static const uint8_t hex_digits_bits[][36] = {
|
||||
{
|
||||
0x7C, 0x00, 0xFE, 0x00, 0xFF, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xC7, 0x01,
|
||||
0xC7, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xC7, 0x01,
|
||||
0xC7, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xFF, 0x01, 0xFE, 0x00, 0x7C, 0x00,
|
||||
},
|
||||
{
|
||||
0x38, 0x00, 0x3C, 0x00, 0x3E, 0x00, 0x3E, 0x00, 0x38, 0x00, 0x38, 0x00,
|
||||
0x38, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00,
|
||||
0x38, 0x00, 0x38, 0x00, 0x38, 0x00, 0xFE, 0x00, 0xFE, 0x00, 0xFE, 0x00,
|
||||
},
|
||||
{
|
||||
0x7C, 0x00, 0xFE, 0x00, 0xFF, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xC0, 0x01,
|
||||
0xC0, 0x01, 0xE0, 0x00, 0xF0, 0x00, 0x78, 0x00, 0x3C, 0x00, 0x1E, 0x00,
|
||||
0x0E, 0x00, 0x0F, 0x00, 0x07, 0x00, 0xFF, 0x01, 0xFF, 0x01, 0xFF, 0x01,
|
||||
},
|
||||
{
|
||||
0x7C, 0x00, 0xFE, 0x00, 0xFF, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xC0, 0x01,
|
||||
0xC0, 0x01, 0xF8, 0x00, 0x78, 0x00, 0xF8, 0x00, 0xC0, 0x01, 0xC0, 0x01,
|
||||
0xC0, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xFF, 0x01, 0xFE, 0x00, 0x7C, 0x00,
|
||||
},
|
||||
{
|
||||
0xE0, 0x00, 0xE0, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0xF8, 0x00, 0xF8, 0x00,
|
||||
0xF8, 0x00, 0xFC, 0x00, 0xEC, 0x00, 0xEE, 0x00, 0xE6, 0x00, 0xFF, 0x01,
|
||||
0xFF, 0x01, 0xFF, 0x01, 0xE0, 0x00, 0xE0, 0x00, 0xE0, 0x00, 0xE0, 0x00,
|
||||
},
|
||||
{
|
||||
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x07, 0x00, 0x07, 0x00, 0x07, 0x00,
|
||||
0x7F, 0x00, 0xFF, 0x00, 0xFF, 0x01, 0xC7, 0x01, 0xC0, 0x01, 0xC0, 0x01,
|
||||
0xC7, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xFF, 0x01, 0xFE, 0x00, 0x7C, 0x00,
|
||||
},
|
||||
{
|
||||
0x7C, 0x00, 0xFE, 0x00, 0xFF, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0x07, 0x00,
|
||||
0x07, 0x00, 0x77, 0x00, 0xFF, 0x00, 0xFF, 0x01, 0xC7, 0x01, 0xC7, 0x01,
|
||||
0xC7, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xFF, 0x01, 0xFE, 0x00, 0x7C, 0x00,
|
||||
},
|
||||
{
|
||||
0xFF, 0x01, 0xFF, 0x01, 0xFF, 0x01, 0xE0, 0x00, 0xE0, 0x00, 0x70, 0x00,
|
||||
0x70, 0x00, 0x70, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00,
|
||||
0x1C, 0x00, 0x1C, 0x00, 0x1C, 0x00, 0x1C, 0x00, 0x1C, 0x00, 0x1C, 0x00,
|
||||
},
|
||||
{
|
||||
0x7C, 0x00, 0xFE, 0x00, 0xFF, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xC7, 0x01,
|
||||
0xC7, 0x01, 0xFE, 0x00, 0x7C, 0x00, 0xFE, 0x00, 0xC7, 0x01, 0xC7, 0x01,
|
||||
0xC7, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xFF, 0x01, 0xFE, 0x00, 0x7C, 0x00,
|
||||
},
|
||||
{
|
||||
0x7C, 0x00, 0xFE, 0x00, 0xFF, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xC7, 0x01,
|
||||
0xC7, 0x01, 0xC7, 0x01, 0xFF, 0x01, 0xFE, 0x01, 0xDC, 0x01, 0xC0, 0x01,
|
||||
0xC0, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xFF, 0x01, 0xFE, 0x00, 0x7C, 0x00,
|
||||
},
|
||||
{
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0x00, 0xFE, 0x00,
|
||||
0xFF, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xF0, 0x01, 0xFC, 0x01, 0xCE, 0x01,
|
||||
0xC7, 0x01, 0xC7, 0x01, 0xE7, 0x01, 0xFF, 0x01, 0xDF, 0x01, 0xCE, 0x01,
|
||||
},
|
||||
{
|
||||
0x07, 0x00, 0x07, 0x00, 0x07, 0x00, 0x07, 0x00, 0xE7, 0x00, 0xF7, 0x01,
|
||||
0xFF, 0x01, 0xCF, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xC7, 0x01,
|
||||
0xC7, 0x01, 0xC7, 0x01, 0xCF, 0x01, 0xFF, 0x01, 0xF7, 0x01, 0xE7, 0x00,
|
||||
},
|
||||
{
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0x00, 0xFE, 0x00,
|
||||
0xFF, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0x07, 0x00, 0x07, 0x00, 0x07, 0x00,
|
||||
0x07, 0x00, 0xC7, 0x01, 0xC7, 0x01, 0xFF, 0x01, 0xFE, 0x00, 0x7C, 0x00,
|
||||
},
|
||||
{
|
||||
0xC0, 0x01, 0xC0, 0x01, 0xC0, 0x01, 0xC0, 0x01, 0xCE, 0x01, 0xDF, 0x01,
|
||||
0xFF, 0x01, 0xE7, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xC7, 0x01,
|
||||
0xC7, 0x01, 0xC7, 0x01, 0xE7, 0x01, 0xFF, 0x01, 0xDF, 0x01, 0xCE, 0x01,
|
||||
},
|
||||
{
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0x00, 0xFE, 0x00,
|
||||
0xFF, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xC7, 0x01, 0xFF, 0x01, 0xFF, 0x01,
|
||||
0x07, 0x00, 0xC7, 0x01, 0xC7, 0x01, 0xFF, 0x01, 0xFE, 0x00, 0x7C, 0x00,
|
||||
},
|
||||
{
|
||||
0xE0, 0x00, 0xF0, 0x00, 0xF8, 0x00, 0x38, 0x00, 0xFE, 0x00, 0xFE, 0x00,
|
||||
0xFE, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00,
|
||||
0x38, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00,
|
||||
}
|
||||
};
|
|
@ -0,0 +1,35 @@
|
|||
#define pebble_logo_width 105
|
||||
#define pebble_logo_height 27
|
||||
static const unsigned char pebble_logo_bits[] = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x03, 0x00, 0x18, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x03, 0x00,
|
||||
0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00,
|
||||
0x03, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60,
|
||||
0x00, 0x00, 0x03, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x60, 0x00, 0x00, 0x03, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0xE0,
|
||||
0x03, 0x00, 0x1F, 0x60, 0xF8, 0x00, 0xC3, 0x07, 0x18, 0xC0, 0x07, 0x00,
|
||||
0x00, 0xF8, 0x0F, 0xC0, 0x7F, 0x60, 0xFE, 0x03, 0xF3, 0x1F, 0x18, 0xF0,
|
||||
0x1F, 0x00, 0x00, 0x0C, 0x18, 0x60, 0xC0, 0x60, 0x03, 0x06, 0x1B, 0x30,
|
||||
0x18, 0x18, 0x30, 0x00, 0x00, 0x06, 0x30, 0x30, 0x80, 0xE1, 0x01, 0x0C,
|
||||
0x0F, 0x60, 0x18, 0x0C, 0x60, 0x00, 0x00, 0x03, 0x60, 0x18, 0x00, 0xE3,
|
||||
0x00, 0x18, 0x07, 0xC0, 0x18, 0x06, 0xC0, 0x00, 0x80, 0x01, 0x40, 0x0C,
|
||||
0x00, 0x62, 0x00, 0x10, 0x03, 0x80, 0x18, 0x03, 0x80, 0x00, 0x80, 0x01,
|
||||
0xC0, 0x0C, 0x00, 0x66, 0x00, 0x30, 0x03, 0x80, 0x19, 0x03, 0x80, 0x01,
|
||||
0x80, 0x01, 0xC0, 0xFC, 0xFF, 0x67, 0x00, 0x30, 0x03, 0x80, 0x19, 0xFF,
|
||||
0xFF, 0x01, 0x80, 0x01, 0xC0, 0xFC, 0xFF, 0x63, 0x00, 0x30, 0x03, 0x80,
|
||||
0x19, 0xFF, 0xFF, 0x00, 0x80, 0x01, 0xC0, 0x0C, 0x00, 0x60, 0x00, 0x30,
|
||||
0x03, 0x80, 0x19, 0x03, 0x00, 0x00, 0x80, 0x01, 0xC0, 0x0C, 0x00, 0x60,
|
||||
0x00, 0x30, 0x03, 0x80, 0x19, 0x03, 0x00, 0x00, 0x80, 0x01, 0x40, 0x0C,
|
||||
0x00, 0x62, 0x00, 0x10, 0x03, 0x80, 0x18, 0x03, 0x80, 0x00, 0x80, 0x03,
|
||||
0x60, 0x18, 0x00, 0xC3, 0x00, 0x18, 0x06, 0xC0, 0x18, 0x06, 0xC0, 0x00,
|
||||
0x80, 0x07, 0x30, 0x30, 0x80, 0x81, 0x01, 0x0C, 0x0C, 0x60, 0x18, 0x0C,
|
||||
0x60, 0x00, 0x80, 0x0D, 0x18, 0x60, 0xC0, 0x00, 0x03, 0x06, 0x18, 0x30,
|
||||
0x38, 0x18, 0x30, 0x00, 0x80, 0xF9, 0x0F, 0xC0, 0x7F, 0x00, 0xFE, 0x03,
|
||||
0xF0, 0x1F, 0x70, 0xF0, 0x1F, 0x00, 0x80, 0xE1, 0x03, 0x00, 0x1F, 0x00,
|
||||
0xF8, 0x00, 0xC0, 0x07, 0x60, 0xC0, 0x07, 0x00, 0x80, 0x01, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, };
|
365
platform/silk/boot/src/drivers/display/sharp_ls013b7dh01.c
Normal file
365
platform/silk/boot/src/drivers/display/sharp_ls013b7dh01.c
Normal file
|
@ -0,0 +1,365 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "board/display.h"
|
||||
#include "drivers/periph_config.h"
|
||||
#include "drivers/gpio.h"
|
||||
#include "drivers/dbgserial.h"
|
||||
#include "util/attributes.h"
|
||||
#include "util/delay.h"
|
||||
|
||||
#include "stm32f4xx_dma.h"
|
||||
#include "stm32f4xx_gpio.h"
|
||||
#include "stm32f4xx_rcc.h"
|
||||
#include "stm32f4xx_spi.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
// Bootloader images
|
||||
#include "drivers/display/resources/hex_digits.h"
|
||||
#include "drivers/display/resources/dead_face.xbm"
|
||||
#include "drivers/display/resources/empty_bar.xbm"
|
||||
#include "drivers/display/resources/error_url.xbm"
|
||||
#include "drivers/display/resources/pebble_logo.xbm"
|
||||
|
||||
#define DISP_LINE_BYTES (DISP_COLS / 8)
|
||||
#define DISP_LINE_WORDS (((DISP_COLS - 1) / 32) + 1)
|
||||
|
||||
// GPIO constants
|
||||
#define DISP_SPI (SPI2)
|
||||
#define DISP_GPIO (GPIOB)
|
||||
#define DISPLAY_SPI_CLOCK (RCC_APB1Periph_SPI2)
|
||||
#define DISP_PIN_SCS (GPIO_Pin_9)
|
||||
#define DISP_PINSOURCE_SCS (GPIO_PinSource9)
|
||||
#define DISP_PIN_SCLK (GPIO_Pin_10)
|
||||
#define DISP_PINSOURCE_SCKL (GPIO_PinSource10)
|
||||
#define DISP_PIN_SI (GPIO_Pin_15)
|
||||
#define DISP_PINSOURCE_SI (GPIO_PinSource15)
|
||||
#define DISP_LCD_GPIO (GPIOA)
|
||||
#define DISP_PIN_LCD (GPIO_Pin_0)
|
||||
#define DISP_PINSOURCE_LCD (GPIO_PinSource0)
|
||||
#define DISP_MODE_STATIC (0x00)
|
||||
#define DISP_MODE_WRITE (0x80)
|
||||
#define DISP_MODE_CLEAR (0x20)
|
||||
|
||||
// The bootloader leaves SYSCLK at defaults (connected to HSI at 16 Mhz),
|
||||
// and there are no prescalers on any of the buses. Since the display
|
||||
// can handle a max of 2 Mhz, we want to divide by 8
|
||||
#define DISPLAY_PERIPH_PRESCALER (SPI_BaudRatePrescaler_8)
|
||||
|
||||
static void prv_enable_display_spi_clock() {
|
||||
periph_config_enable(RCC_APB1PeriphClockCmd, DISPLAY_SPI_CLOCK);
|
||||
}
|
||||
|
||||
static void prv_disable_display_spi_clock() {
|
||||
periph_config_disable(RCC_APB1PeriphClockCmd, DISPLAY_SPI_CLOCK);
|
||||
}
|
||||
|
||||
static void prv_enable_chip_select(void) {
|
||||
GPIO_WriteBit(DISP_GPIO, DISP_PIN_SCS, Bit_SET);
|
||||
// required setup time > 3us
|
||||
delay_us(7);
|
||||
}
|
||||
|
||||
static void prv_disable_chip_select(void) {
|
||||
// delay while last byte is emitted by the SPI peripheral
|
||||
delay_us(7);
|
||||
|
||||
GPIO_WriteBit(DISP_GPIO, DISP_PIN_SCS, Bit_RESET);
|
||||
// hold time > 1us
|
||||
// produce a delay 4ms
|
||||
delay_us(4);
|
||||
}
|
||||
|
||||
//! These functions needed to be called around any commands that
|
||||
//! are sent to the display. NOINLINE only for code size savings.
|
||||
static NOINLINE void prv_enable_display_access(void) {
|
||||
prv_enable_display_spi_clock();
|
||||
prv_enable_chip_select();
|
||||
}
|
||||
|
||||
static NOINLINE void prv_disable_display_access(void) {
|
||||
prv_disable_chip_select();
|
||||
prv_disable_display_spi_clock();
|
||||
}
|
||||
|
||||
//! Write a single byte synchronously to the display. This is the only practical
|
||||
//! way to write to the display in the bootloader since we don't have interrupts.
|
||||
static void prv_display_write_byte(uint8_t d) {
|
||||
// Block until the tx buffer is empty
|
||||
SPI_I2S_SendData(DISP_SPI, d);
|
||||
while (!SPI_I2S_GetFlagStatus(DISP_SPI, SPI_I2S_FLAG_TXE)) {}
|
||||
}
|
||||
|
||||
// Since all these values are constant we can save code space
|
||||
// by storing the initialized struct in memory rather than
|
||||
// needing to copy in each value
|
||||
static GPIO_InitTypeDef s_disp_gpio_init = {
|
||||
.GPIO_OType = GPIO_OType_PP,
|
||||
.GPIO_PuPd = GPIO_PuPd_NOPULL,
|
||||
.GPIO_Mode = GPIO_Mode_AF,
|
||||
.GPIO_Speed = GPIO_Speed_50MHz,
|
||||
.GPIO_Pin = DISP_PIN_SCLK | DISP_PIN_SI
|
||||
};
|
||||
|
||||
static SPI_InitTypeDef s_disp_spi_init = {
|
||||
.SPI_Direction = SPI_Direction_1Line_Tx, // Write-only SPI
|
||||
.SPI_Mode = SPI_Mode_Master,
|
||||
.SPI_DataSize = SPI_DataSize_8b,
|
||||
.SPI_CPOL = SPI_CPOL_Low,
|
||||
.SPI_CPHA = SPI_CPHA_1Edge,
|
||||
.SPI_NSS = SPI_NSS_Soft,
|
||||
// We want the SPI clock to run at 2MHz
|
||||
.SPI_BaudRatePrescaler = DISPLAY_PERIPH_PRESCALER,
|
||||
// MSB order allows us to write pixels out without reversing bytes, but command bytes
|
||||
// have to be reversed
|
||||
.SPI_FirstBit = SPI_FirstBit_MSB,
|
||||
.SPI_CRCPolynomial = 7 // default
|
||||
};
|
||||
|
||||
static void prv_display_start(void) {
|
||||
// Setup alternate functions
|
||||
GPIO_PinAFConfig(DISP_GPIO, DISP_PINSOURCE_SCKL, GPIO_AF_SPI2);
|
||||
GPIO_PinAFConfig(DISP_GPIO, DISP_PINSOURCE_SI, GPIO_AF_SPI2);
|
||||
|
||||
// Init display GPIOs
|
||||
GPIO_Init(DISP_GPIO, &s_disp_gpio_init);
|
||||
|
||||
// Init CS
|
||||
s_disp_gpio_init.GPIO_Mode = GPIO_Mode_OUT;
|
||||
s_disp_gpio_init.GPIO_OType = GPIO_OType_PP;
|
||||
s_disp_gpio_init.GPIO_Pin = DISP_PIN_SCS;
|
||||
GPIO_Init(DISP_GPIO, &s_disp_gpio_init);
|
||||
|
||||
// Init LCD Control
|
||||
s_disp_gpio_init.GPIO_Mode = GPIO_Mode_OUT;
|
||||
s_disp_gpio_init.GPIO_OType = GPIO_OType_PP;
|
||||
s_disp_gpio_init.GPIO_Pin = DISP_PIN_LCD;
|
||||
GPIO_Init(DISP_LCD_GPIO, &s_disp_gpio_init);
|
||||
|
||||
// Set up a SPI bus on SPI2
|
||||
SPI_I2S_DeInit(DISP_SPI);
|
||||
SPI_Init(DISP_SPI, &s_disp_spi_init);
|
||||
|
||||
SPI_Cmd(DISP_SPI, ENABLE);
|
||||
|
||||
|
||||
// Hold LCD on
|
||||
GPIO_WriteBit(DISP_LCD_GPIO, DISP_PIN_LCD, Bit_SET);
|
||||
}
|
||||
|
||||
// Clear-all mode is entered by sending 0x04 to the panel
|
||||
void display_clear(void) {
|
||||
prv_enable_display_access();
|
||||
|
||||
prv_display_write_byte(DISP_MODE_CLEAR);
|
||||
prv_display_write_byte(0x00);
|
||||
|
||||
prv_disable_display_access();
|
||||
}
|
||||
|
||||
//! Static mode is entered by sending 0x00 to the panel
|
||||
//! This stops any further updates being registered by
|
||||
//! the display, preventing corruption on shutdown / boot
|
||||
static void prv_display_enter_static(void) {
|
||||
prv_enable_display_access();
|
||||
|
||||
prv_display_write_byte(DISP_MODE_STATIC);
|
||||
prv_display_write_byte(0x00);
|
||||
prv_display_write_byte(0x00);
|
||||
|
||||
prv_disable_display_access();
|
||||
}
|
||||
|
||||
// Helper to reverse command bytes
|
||||
static uint8_t prv_reverse_bits(uint8_t input) {
|
||||
uint8_t result;
|
||||
__asm__ ("rev %[result], %[input]\n\t"
|
||||
"rbit %[result], %[result]"
|
||||
: [result] "=r" (result)
|
||||
: [input] "r" (input));
|
||||
return result;
|
||||
}
|
||||
|
||||
static void prv_display_start_write(void) {
|
||||
prv_enable_display_access();
|
||||
|
||||
prv_display_write_byte(DISP_MODE_WRITE);
|
||||
}
|
||||
|
||||
static void prv_display_write_line(uint8_t line_addr, const uint8_t *line) {
|
||||
// 1-indexed (ugh) 8bit line address (1-168)
|
||||
prv_display_write_byte(prv_reverse_bits(line_addr + 1));
|
||||
|
||||
for (int i = 0; i < DISP_LINE_BYTES; ++i) {
|
||||
prv_display_write_byte(prv_reverse_bits(line[i]));
|
||||
}
|
||||
|
||||
prv_display_write_byte(0x00);
|
||||
}
|
||||
|
||||
static void prv_display_end_write(void) {
|
||||
prv_display_write_byte(0x00);
|
||||
|
||||
|
||||
prv_disable_display_access();
|
||||
}
|
||||
|
||||
// Round a bit offset to a byte offset
|
||||
static unsigned prv_round_to_byte(unsigned x) {
|
||||
return (x + 7) >> 3;
|
||||
}
|
||||
|
||||
// Draw bitmap onto buffer.
|
||||
static void prv_draw_bitmap(const uint8_t *bitmap, unsigned x_offset, unsigned y_offset,
|
||||
unsigned width, unsigned height,
|
||||
uint8_t buffer[DISP_ROWS][DISP_LINE_BYTES]) {
|
||||
// Need to convert offsets to bytes for the horizontal dimensions
|
||||
x_offset = prv_round_to_byte(x_offset);
|
||||
width = prv_round_to_byte(width);
|
||||
|
||||
for (unsigned i = 0; i < height; i++) {
|
||||
memcpy(buffer[i + y_offset] + x_offset, bitmap + i * width, width);
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_display_buffer(uint8_t buffer[DISP_ROWS][DISP_LINE_BYTES]) {
|
||||
prv_display_start_write();
|
||||
for (int i = 0; i < DISP_ROWS; i++) {
|
||||
prv_display_write_line(i, buffer[i]);
|
||||
}
|
||||
prv_display_end_write();
|
||||
}
|
||||
|
||||
void display_boot_splash(void) {
|
||||
uint8_t buffer[DISP_ROWS][DISP_LINE_BYTES];
|
||||
// Draw black
|
||||
memset(buffer, 0x00, sizeof(buffer));
|
||||
|
||||
prv_draw_bitmap(pebble_logo_bits, 16, 64, pebble_logo_width, pebble_logo_height, buffer);
|
||||
|
||||
prv_display_buffer(buffer);
|
||||
}
|
||||
|
||||
static void prv_set_bit(uint8_t x, uint8_t y, uint8_t buffer[DISP_ROWS][DISP_LINE_BYTES]) {
|
||||
buffer[y][x / 8] |= (1 << (x % 8));
|
||||
}
|
||||
|
||||
static void prv_render_char(unsigned digit, uint8_t x_offset_bits, uint8_t y_offset,
|
||||
uint8_t buffer[DISP_ROWS][DISP_LINE_BYTES]) {
|
||||
const unsigned char_rows = 18, char_cols = 9;
|
||||
const uint8_t * char_data = hex_digits_bits[digit];
|
||||
|
||||
// Each character requires 2 bytes of storage
|
||||
for (unsigned y = 0; y < char_rows; y++) {
|
||||
unsigned cur_y = y_offset + y;
|
||||
uint8_t first_byte = char_data[2 * y];
|
||||
|
||||
for (unsigned x = 0; x < char_cols; x++) {
|
||||
bool pixel;
|
||||
if (x < 8) { // Pixel is in first byte
|
||||
pixel = first_byte & (1 << x);
|
||||
}
|
||||
else { // Last pixel is in second byte
|
||||
pixel = char_data[2 * y + 1] & 1;
|
||||
}
|
||||
|
||||
// The buffer is already all black, so just set the white pixels
|
||||
if (pixel) {
|
||||
prv_set_bit(x_offset_bits + x, cur_y, buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_draw_code(uint32_t code, uint8_t buffer[DISP_ROWS][DISP_LINE_BYTES]) {
|
||||
const unsigned y_offset = 116; // beneath sad face, above url
|
||||
unsigned x_offset = 28; // Aligned with sad face
|
||||
|
||||
// Extract and print digits
|
||||
for (int i = 7; i >= 0; i--) {
|
||||
// Mask off 4 bits at a time
|
||||
uint32_t mask = (0xf << (i * 4));
|
||||
unsigned digit = ((code & mask) >> (i * 4));
|
||||
prv_render_char(digit, x_offset, y_offset, buffer);
|
||||
|
||||
// Each character is 9px wide plus 2px of padding
|
||||
x_offset += 11;
|
||||
}
|
||||
}
|
||||
|
||||
void display_error_code(uint32_t code) {
|
||||
uint8_t buffer[DISP_ROWS][DISP_LINE_BYTES];
|
||||
memset(buffer, 0x00, sizeof(buffer));
|
||||
|
||||
prv_draw_bitmap(dead_face_bits, 24, 32, dead_face_width, dead_face_height, buffer);
|
||||
|
||||
prv_draw_code(code, buffer);
|
||||
|
||||
prv_draw_bitmap(error_url_bits, 16, 144, error_url_width, error_url_height, buffer);
|
||||
|
||||
prv_display_buffer(buffer);
|
||||
}
|
||||
|
||||
//! Do whatever is necessary to prevent visual artifacts when resetting
|
||||
//! the watch.
|
||||
void display_prepare_for_reset(void) {
|
||||
prv_display_enter_static();
|
||||
}
|
||||
|
||||
//! Display the progress of a firmware update.
|
||||
//!
|
||||
//! The progress is expressed as a rational number less than or equal to 1.
|
||||
//! When numerator == denominator, the progress indicator shows that the update
|
||||
//! is complete.
|
||||
void display_firmware_update_progress(uint32_t numerator, uint32_t denominator) {
|
||||
// Dimensions for progress bar
|
||||
const unsigned x_offset = 24, y_offset = 106,
|
||||
inner_bar_width = 94, inner_bar_height = 6;
|
||||
|
||||
static unsigned s_prev_num_pixels = -1;
|
||||
// Calculate number of pixels to fill in
|
||||
unsigned num_pixels = inner_bar_width * numerator / denominator;
|
||||
|
||||
if (num_pixels == s_prev_num_pixels) {
|
||||
return;
|
||||
}
|
||||
s_prev_num_pixels = num_pixels;
|
||||
|
||||
uint8_t buffer[DISP_ROWS][DISP_LINE_BYTES];
|
||||
memset(buffer, 0x00, sizeof(buffer));
|
||||
|
||||
prv_draw_bitmap(pebble_logo_bits, 16, 64, pebble_logo_width, pebble_logo_height, buffer);
|
||||
|
||||
|
||||
prv_draw_bitmap(empty_bar_bits, x_offset, y_offset, empty_bar_width, empty_bar_height, buffer);
|
||||
|
||||
for (unsigned y = 0; y < inner_bar_height; y++) {
|
||||
for (unsigned x = 0; x < num_pixels; x++) {
|
||||
// Add 1 to offsets so we don't write into outer box
|
||||
prv_set_bit(x + x_offset + 1, y_offset + y + 1, buffer);
|
||||
}
|
||||
}
|
||||
|
||||
prv_display_buffer(buffer);
|
||||
}
|
||||
|
||||
void display_init(void) {
|
||||
prv_enable_display_spi_clock();
|
||||
prv_display_start();
|
||||
prv_disable_display_spi_clock();
|
||||
}
|
46
platform/silk/boot/src/drivers/exti.h
Normal file
46
platform/silk/boot/src/drivers/exti.h
Normal file
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "board/board.h"
|
||||
|
||||
typedef enum {
|
||||
ExtiTrigger_Rising,
|
||||
ExtiTrigger_Falling,
|
||||
ExtiTrigger_RisingFalling
|
||||
} ExtiTrigger;
|
||||
|
||||
//! See section 12.2.5 "External interrupt/event line mapping" in the STM32F2 reference manual
|
||||
typedef enum {
|
||||
ExtiLineOther_RTCAlarm = 17,
|
||||
ExtiLineOther_RTCWakeup = 22
|
||||
} ExtiLineOther;
|
||||
|
||||
typedef void (*ExtiHandlerCallback)(void);
|
||||
|
||||
//! Configures the given EXTI and NVIC for the given configuration.
|
||||
void exti_configure_pin(ExtiConfig cfg, ExtiTrigger trigger, ExtiHandlerCallback cb);
|
||||
//! Configures the given EXTI and NVIC for the given configuration.
|
||||
void exti_configure_other(ExtiLineOther exti_line, ExtiTrigger trigger);
|
||||
|
||||
static inline void exti_enable(ExtiConfig config);
|
||||
static inline void exti_disable(ExtiConfig config);
|
||||
|
||||
void exti_enable_other(ExtiLineOther);
|
||||
void exti_disable_other(ExtiLineOther);
|
||||
|
||||
#include "exti.inl.h"
|
27
platform/silk/boot/src/drivers/exti.inl.h
Normal file
27
platform/silk/boot/src/drivers/exti.inl.h
Normal file
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
//! @file exti.inl.h
|
||||
//!
|
||||
//! Helper functions intended to be inlined into the calling code.
|
||||
|
||||
static inline void exti_enable(ExtiConfig config) {
|
||||
exti_enable_other(config.exti_line);
|
||||
}
|
||||
|
||||
static inline void exti_disable(ExtiConfig config) {
|
||||
exti_disable_other(config.exti_line);
|
||||
}
|
40
platform/silk/boot/src/drivers/flash.h
Normal file
40
platform/silk/boot/src/drivers/flash.h
Normal file
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
//! Configure the micro's peripherals to communicate with the flash chip
|
||||
void flash_init(void);
|
||||
|
||||
//! Read 1 or more bytes starting at the specified 24bit address into
|
||||
//! the provided buffer. This function does no range checking, so it is
|
||||
//! currently possible to run off the end of the flash.
|
||||
//!
|
||||
//! @param buffer A byte-buffer that will be used to store the data
|
||||
//! read from flash.
|
||||
//! @param start_addr The address of the first byte to be read from flash.
|
||||
//! @param buffer_size The total number of bytes to be read from flash.
|
||||
void flash_read_bytes(uint8_t* buffer, uint32_t start_addr, uint32_t buffer_size);
|
||||
|
||||
//! Check if we can talk to the flash.
|
||||
//! @return true if the CFI table can be queried.
|
||||
bool flash_sanity_check(void);
|
||||
|
||||
//! Get the checksum of a region of flash
|
||||
uint32_t flash_calculate_checksum(uint32_t flash_addr, uint32_t length);
|
38
platform/silk/boot/src/drivers/flash/flash_crc.c
Normal file
38
platform/silk/boot/src/drivers/flash/flash_crc.c
Normal file
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "drivers/flash.h"
|
||||
|
||||
#include "util/crc32.h"
|
||||
|
||||
#define CRC_CHUNK_SIZE 1024
|
||||
|
||||
uint32_t flash_calculate_checksum(uint32_t flash_addr, uint32_t num_bytes) {
|
||||
uint8_t buffer[CRC_CHUNK_SIZE];
|
||||
|
||||
uint32_t crc = CRC32_INIT;
|
||||
|
||||
while (num_bytes > CRC_CHUNK_SIZE) {
|
||||
flash_read_bytes(buffer, flash_addr, CRC_CHUNK_SIZE);
|
||||
crc = crc32(crc, buffer, CRC_CHUNK_SIZE);
|
||||
|
||||
num_bytes -= CRC_CHUNK_SIZE;
|
||||
flash_addr += CRC_CHUNK_SIZE;
|
||||
}
|
||||
|
||||
flash_read_bytes(buffer, flash_addr, num_bytes);
|
||||
return crc32(crc, buffer, num_bytes);
|
||||
}
|
209
platform/silk/boot/src/drivers/flash/mx25u.c
Normal file
209
platform/silk/boot/src/drivers/flash/mx25u.c
Normal file
|
@ -0,0 +1,209 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "board/board.h"
|
||||
#include "drivers/flash.h"
|
||||
#include "drivers/gpio.h"
|
||||
#include "drivers/periph_config.h"
|
||||
#include "util/delay.h"
|
||||
|
||||
#include "stm32f4xx_gpio.h"
|
||||
#include "stm32f4xx_qspi.h"
|
||||
|
||||
#define MX25U_FASTREAD_DUMMYCYCLES 4
|
||||
|
||||
typedef enum MX25UCommand {
|
||||
// SPI/QSPI Commands
|
||||
MX25UCommand_FastRead = 0x0B, // FAST_READ
|
||||
MX25UCommand_QSPIEnable = 0x35, // QPI
|
||||
MX25UCommand_ResetEnable = 0x66, // RSTEN
|
||||
MX25UCommand_Reset = 0x99, // RST
|
||||
|
||||
// QSPI only commands
|
||||
MX25UCommand_QSPI_ID = 0xAF, // QPIID
|
||||
} MX25UCommand;
|
||||
|
||||
// Helpful Enums
|
||||
typedef enum {
|
||||
QSPIFlag_Retain = 0,
|
||||
QSPIFlag_ClearTC = 1,
|
||||
} QSPIFlag;
|
||||
|
||||
static void prv_enable_qspi_clock(void) {
|
||||
periph_config_enable(RCC_AHB3PeriphClockCmd, RCC_AHB3Periph_QSPI);
|
||||
}
|
||||
|
||||
static void prv_disable_qspi_clock(void) {
|
||||
periph_config_disable(RCC_AHB3PeriphClockCmd, RCC_AHB3Periph_QSPI);
|
||||
}
|
||||
|
||||
static void prv_set_num_data_bytes(uint32_t length) {
|
||||
// From the docs: QSPI_DataLength: Number of data to be retrieved, value+1.
|
||||
// so 0 is 1 byte, so we substract 1 from the length. -1 is read the entire flash length.
|
||||
QSPI_SetDataLength(length - 1);
|
||||
}
|
||||
|
||||
static void prv_wait_for_qspi_transfer_complete(QSPIFlag actions) {
|
||||
while (QSPI_GetFlagStatus(QSPI_FLAG_TC) == RESET) { }
|
||||
|
||||
if (actions == QSPIFlag_ClearTC) {
|
||||
QSPI_ClearFlag(QSPI_FLAG_TC);
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_wait_for_qspi_not_busy(void) {
|
||||
while (QSPI_GetFlagStatus(QSPI_FLAG_BUSY) != RESET) { }
|
||||
}
|
||||
|
||||
static void prv_quad_enable() {
|
||||
QSPI_ComConfig_InitTypeDef qspi_com_config;
|
||||
QSPI_ComConfig_StructInit(&qspi_com_config);
|
||||
qspi_com_config.QSPI_ComConfig_FMode = QSPI_ComConfig_FMode_Indirect_Write;
|
||||
qspi_com_config.QSPI_ComConfig_IMode = QSPI_ComConfig_IMode_1Line;
|
||||
qspi_com_config.QSPI_ComConfig_Ins = MX25UCommand_QSPIEnable;
|
||||
QSPI_ComConfig_Init(&qspi_com_config);
|
||||
|
||||
prv_wait_for_qspi_transfer_complete(QSPIFlag_ClearTC);
|
||||
|
||||
prv_wait_for_qspi_not_busy();
|
||||
}
|
||||
|
||||
static void prv_flash_reset(void) {
|
||||
QSPI_ComConfig_InitTypeDef qspi_com_config;
|
||||
QSPI_ComConfig_StructInit(&qspi_com_config);
|
||||
qspi_com_config.QSPI_ComConfig_FMode = QSPI_ComConfig_FMode_Indirect_Write;
|
||||
qspi_com_config.QSPI_ComConfig_IMode = QSPI_ComConfig_IMode_4Line;
|
||||
qspi_com_config.QSPI_ComConfig_Ins = MX25UCommand_ResetEnable;
|
||||
QSPI_ComConfig_Init(&qspi_com_config);
|
||||
|
||||
prv_wait_for_qspi_transfer_complete(QSPIFlag_ClearTC);
|
||||
|
||||
QSPI_ComConfig_StructInit(&qspi_com_config);
|
||||
qspi_com_config.QSPI_ComConfig_FMode = QSPI_ComConfig_FMode_Indirect_Write;
|
||||
qspi_com_config.QSPI_ComConfig_IMode = QSPI_ComConfig_IMode_4Line;
|
||||
qspi_com_config.QSPI_ComConfig_Ins = MX25UCommand_Reset;
|
||||
QSPI_ComConfig_Init(&qspi_com_config);
|
||||
|
||||
prv_wait_for_qspi_transfer_complete(QSPIFlag_ClearTC);
|
||||
|
||||
delay_us(12000); // 12ms reset if busy with an erase!
|
||||
|
||||
// Return the flash to Quad SPI mode, all our commands are quad-spi and it'll just cause
|
||||
// problems/bugs for someone if it comes back in single spi mode
|
||||
prv_quad_enable();
|
||||
}
|
||||
|
||||
static bool prv_flash_check_whoami(void) {
|
||||
const unsigned int num_whoami_bytes = 3;
|
||||
|
||||
prv_set_num_data_bytes(num_whoami_bytes);
|
||||
|
||||
QSPI_ComConfig_InitTypeDef qspi_com_config;
|
||||
QSPI_ComConfig_StructInit(&qspi_com_config);
|
||||
qspi_com_config.QSPI_ComConfig_FMode = QSPI_ComConfig_FMode_Indirect_Read;
|
||||
qspi_com_config.QSPI_ComConfig_DMode = QSPI_ComConfig_DMode_4Line;
|
||||
qspi_com_config.QSPI_ComConfig_IMode = QSPI_ComConfig_IMode_4Line;
|
||||
qspi_com_config.QSPI_ComConfig_Ins = MX25UCommand_QSPI_ID;
|
||||
QSPI_ComConfig_Init(&qspi_com_config);
|
||||
|
||||
prv_wait_for_qspi_transfer_complete(QSPIFlag_ClearTC);
|
||||
|
||||
uint32_t read_whoami = 0;
|
||||
for (unsigned int i = 0; i < num_whoami_bytes; ++i) {
|
||||
read_whoami |= QSPI_ReceiveData8() << (8 * i);
|
||||
}
|
||||
|
||||
prv_wait_for_qspi_not_busy();
|
||||
|
||||
const uint32_t expected_whoami = 0x3725c2;
|
||||
if (read_whoami == expected_whoami) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void flash_init(void) {
|
||||
prv_enable_qspi_clock();
|
||||
// init GPIOs
|
||||
for (unsigned i = 0; i < QSpiPinCount; ++i) {
|
||||
gpio_af_init(&BOARD_CONFIG_FLASH_PINS[i], GPIO_OType_PP, GPIO_Speed_100MHz, GPIO_PuPd_NOPULL);
|
||||
}
|
||||
|
||||
// Init QSPI peripheral
|
||||
QSPI_InitTypeDef qspi_config;
|
||||
QSPI_StructInit(&qspi_config);
|
||||
qspi_config.QSPI_SShift = QSPI_SShift_HalfCycleShift;
|
||||
qspi_config.QSPI_Prescaler = 0x0;
|
||||
qspi_config.QSPI_CKMode = QSPI_CKMode_Mode0;
|
||||
qspi_config.QSPI_CSHTime = QSPI_CSHTime_1Cycle;
|
||||
qspi_config.QSPI_FSize = 22; // 2^23 = 8MB. -> 23 - 1 = 22
|
||||
qspi_config.QSPI_FSelect = QSPI_FSelect_1;
|
||||
qspi_config.QSPI_DFlash = QSPI_DFlash_Disable;
|
||||
QSPI_Init(&qspi_config);
|
||||
|
||||
QSPI_Cmd(ENABLE);
|
||||
|
||||
// Must call quad_enable first, all commands are QSPI
|
||||
prv_quad_enable();
|
||||
|
||||
// Reset the flash to stop any program's or erase in progress from before reboot
|
||||
prv_flash_reset();
|
||||
|
||||
prv_disable_qspi_clock();
|
||||
}
|
||||
|
||||
bool flash_sanity_check(void) {
|
||||
prv_enable_qspi_clock();
|
||||
|
||||
bool result = prv_flash_check_whoami();
|
||||
|
||||
prv_disable_qspi_clock();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void flash_read_bytes(uint8_t *buffer_ptr, uint32_t start_addr, uint32_t buffer_size) {
|
||||
prv_enable_qspi_clock();
|
||||
|
||||
prv_set_num_data_bytes(buffer_size);
|
||||
|
||||
QSPI_ComConfig_InitTypeDef qspi_com_config;
|
||||
QSPI_ComConfig_StructInit(&qspi_com_config);
|
||||
qspi_com_config.QSPI_ComConfig_FMode = QSPI_ComConfig_FMode_Indirect_Read;
|
||||
qspi_com_config.QSPI_ComConfig_DMode = QSPI_ComConfig_DMode_4Line;
|
||||
qspi_com_config.QSPI_ComConfig_DummyCycles = MX25U_FASTREAD_DUMMYCYCLES;
|
||||
qspi_com_config.QSPI_ComConfig_ADMode = QSPI_ComConfig_ADMode_4Line;
|
||||
qspi_com_config.QSPI_ComConfig_IMode = QSPI_ComConfig_IMode_4Line;
|
||||
qspi_com_config.QSPI_ComConfig_ADSize = QSPI_ComConfig_ADSize_24bit;
|
||||
qspi_com_config.QSPI_ComConfig_Ins = MX25UCommand_FastRead;
|
||||
QSPI_ComConfig_Init(&qspi_com_config);
|
||||
|
||||
QSPI_SetAddress(start_addr);
|
||||
|
||||
uint8_t *write_ptr = buffer_ptr;
|
||||
for (unsigned i = 0; i < buffer_size; ++i) {
|
||||
write_ptr[i] = QSPI_ReceiveData8();
|
||||
}
|
||||
|
||||
QSPI_ClearFlag(QSPI_FLAG_TC);
|
||||
prv_wait_for_qspi_not_busy();
|
||||
|
||||
prv_disable_qspi_clock();
|
||||
}
|
43
platform/silk/boot/src/drivers/gpio.c
Normal file
43
platform/silk/boot/src/drivers/gpio.c
Normal file
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "drivers/gpio.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define GPIO_EN_MASK ((RCC_AHB1ENR_GPIOHEN << 1) - 1)
|
||||
|
||||
void gpio_enable_all(void) {
|
||||
RCC->AHB1ENR |= GPIO_EN_MASK;
|
||||
}
|
||||
|
||||
void gpio_disable_all(void) {
|
||||
RCC->AHB1ENR &= ~GPIO_EN_MASK;
|
||||
}
|
||||
|
||||
void gpio_af_init(const AfConfig *af_config, GPIOOType_TypeDef otype, GPIOSpeed_TypeDef speed,
|
||||
GPIOPuPd_TypeDef pupd) {
|
||||
GPIO_InitTypeDef init = {
|
||||
.GPIO_Pin = af_config->gpio_pin,
|
||||
.GPIO_Mode = GPIO_Mode_AF,
|
||||
.GPIO_Speed = speed,
|
||||
.GPIO_OType = otype,
|
||||
.GPIO_PuPd = pupd
|
||||
};
|
||||
|
||||
GPIO_PinAFConfig(af_config->gpio, af_config->gpio_pin_source, af_config->gpio_af);
|
||||
GPIO_Init(af_config->gpio, &init);
|
||||
}
|
28
platform/silk/boot/src/drivers/gpio.h
Normal file
28
platform/silk/boot/src/drivers/gpio.h
Normal file
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "stm32f4xx_gpio.h"
|
||||
|
||||
#include "board/board.h"
|
||||
|
||||
void gpio_enable_all(void);
|
||||
void gpio_disable_all(void);
|
||||
void gpio_af_init(const AfConfig *af_config, GPIOOType_TypeDef otype, GPIOSpeed_TypeDef speed,
|
||||
GPIOPuPd_TypeDef pupd);
|
743
platform/silk/boot/src/drivers/i2c.c
Normal file
743
platform/silk/boot/src/drivers/i2c.c
Normal file
|
@ -0,0 +1,743 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "util/misc.h"
|
||||
#include "drivers/i2c.h"
|
||||
#include "drivers/periph_config.h"
|
||||
#include "drivers/gpio.h"
|
||||
#include "system/passert.h"
|
||||
#include "system/logging.h"
|
||||
#include "util/delay.h"
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "stm32f4xx_gpio.h"
|
||||
#include "stm32f4xx_rcc.h"
|
||||
#include "stm32f4xx_i2c.h"
|
||||
|
||||
#define portBASE_TYPE int
|
||||
#define pdFALSE 0
|
||||
#define portEND_SWITCHING_ISR(expr) (void)(expr)
|
||||
|
||||
#define I2C_ERROR_TIMEOUT_MS (1000)
|
||||
#define I2C_TIMEOUT_ATTEMPTS_MAX (2 * 1000 * 1000)
|
||||
#define I2C_NORMAL_MODE_CLOCK_SPEED_MAX (100000)
|
||||
#define I2C_NACK_COUNT_MAX (1000) // MFI NACKs while busy. We delay ~1ms between retries so this is approximately a 1s timeout
|
||||
|
||||
#define I2C_READ_WRITE_BIT (0x01)
|
||||
|
||||
typedef struct I2cTransfer {
|
||||
uint8_t device_address;
|
||||
bool read_not_write; //True for read, false for write
|
||||
uint8_t register_address;
|
||||
uint8_t size;
|
||||
uint8_t idx;
|
||||
uint8_t *data;
|
||||
enum TransferState {
|
||||
TRANSFER_STATE_WRITE_ADDRESS_TX,
|
||||
TRANSFER_STATE_WRITE_REG_ADDRESS,
|
||||
TRANSFER_STATE_REPEAT_START,
|
||||
TRANSFER_STATE_WRITE_ADDRESS_RX,
|
||||
TRANSFER_STATE_WAIT_FOR_DATA,
|
||||
TRANSFER_STATE_READ_DATA,
|
||||
TRANSFER_STATE_WRITE_DATA,
|
||||
TRANSFER_STATE_END_WRITE,
|
||||
|
||||
TRANSFER_STATE_INVALID,
|
||||
} state;
|
||||
bool result;
|
||||
uint16_t nack_count;
|
||||
}I2cTransfer;
|
||||
|
||||
typedef struct I2cBus{
|
||||
I2C_TypeDef *i2c;
|
||||
uint8_t user_count;
|
||||
I2cTransfer transfer;
|
||||
volatile bool busy;
|
||||
}I2cBus;
|
||||
|
||||
static I2cBus i2c_buses[BOARD_I2C_BUS_COUNT];
|
||||
|
||||
static uint32_t s_guard_events[] = {
|
||||
I2C_EVENT_MASTER_MODE_SELECT,
|
||||
I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED,
|
||||
I2C_EVENT_MASTER_BYTE_TRANSMITTED,
|
||||
I2C_EVENT_MASTER_MODE_SELECT,
|
||||
I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED,
|
||||
I2C_EVENT_MASTER_BYTE_RECEIVED,
|
||||
I2C_EVENT_MASTER_BYTE_TRANSMITTING,
|
||||
I2C_EVENT_MASTER_BYTE_TRANSMITTED,
|
||||
};
|
||||
|
||||
static bool s_initialized = false;
|
||||
|
||||
/*----------------SEMAPHORE/LOCKING FUNCTIONS--------------------------*/
|
||||
|
||||
static void bus_lock(I2cBus *bus) {
|
||||
}
|
||||
|
||||
static void bus_unlock(I2cBus *bus) {
|
||||
}
|
||||
|
||||
static bool semaphore_take(I2cBus *bus) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool semaphore_wait(I2cBus *bus) {
|
||||
bus->busy = true;
|
||||
volatile uint32_t timeout_attempts = I2C_TIMEOUT_ATTEMPTS_MAX;
|
||||
while ((timeout_attempts-- > 0) && (bus->busy));
|
||||
bus->busy = false;
|
||||
return (timeout_attempts != 0);
|
||||
}
|
||||
|
||||
static void semaphore_give(I2cBus *bus) {
|
||||
}
|
||||
|
||||
/*-------------------BUS/PIN CONFIG FUNCTIONS--------------------------*/
|
||||
|
||||
//! Configure bus power supply control pin as output
|
||||
//! Lock bus and peripheral config access before configuring pins
|
||||
void i2c_bus_rail_ctl_config(OutputConfig pin_config) {
|
||||
GPIO_InitTypeDef GPIO_InitStructure;
|
||||
GPIO_StructInit(&GPIO_InitStructure);
|
||||
|
||||
GPIO_InitStructure.GPIO_Pin = pin_config.gpio_pin;
|
||||
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
|
||||
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
|
||||
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
|
||||
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
|
||||
GPIO_Init(pin_config.gpio, &GPIO_InitStructure);
|
||||
}
|
||||
|
||||
//! Configure bus pins for use by I2C peripheral
|
||||
//! Lock bus and peripheral config access before configuring pins
|
||||
static void bus_pin_cfg_i2c(AfConfig pin_config) {
|
||||
GPIO_InitTypeDef gpio_init_struct;
|
||||
gpio_init_struct.GPIO_Pin = pin_config.gpio_pin;
|
||||
gpio_init_struct.GPIO_Mode = GPIO_Mode_AF;
|
||||
gpio_init_struct.GPIO_Speed = GPIO_Speed_50MHz;
|
||||
gpio_init_struct.GPIO_OType = GPIO_OType_OD;
|
||||
gpio_init_struct.GPIO_PuPd = GPIO_PuPd_NOPULL;
|
||||
GPIO_Init(pin_config.gpio, &gpio_init_struct);
|
||||
|
||||
GPIO_PinAFConfig(pin_config.gpio, pin_config.gpio_pin_source, pin_config.gpio_af);
|
||||
}
|
||||
|
||||
//! Configure bus pin as input
|
||||
//! Lock bus and peripheral config access before use
|
||||
static void bus_pin_cfg_input(AfConfig pin_config) {
|
||||
// Configure pin as high impedance input
|
||||
GPIO_InitTypeDef gpio_init_struct;
|
||||
gpio_init_struct.GPIO_Pin = pin_config.gpio_pin;
|
||||
gpio_init_struct.GPIO_Mode = GPIO_Mode_IN;
|
||||
gpio_init_struct.GPIO_Speed = GPIO_Speed_2MHz;
|
||||
gpio_init_struct.GPIO_PuPd = GPIO_PuPd_NOPULL;
|
||||
GPIO_Init(pin_config.gpio, &gpio_init_struct);
|
||||
}
|
||||
|
||||
//! Configure bus pin as output
|
||||
//! Lock bus and peripheral config access before use
|
||||
static void bus_pin_cfg_output(AfConfig pin_config, bool pin_state) {
|
||||
// Configure pin as output
|
||||
GPIO_InitTypeDef gpio_init_struct;
|
||||
gpio_init_struct.GPIO_Pin = pin_config.gpio_pin;
|
||||
gpio_init_struct.GPIO_Mode = GPIO_Mode_OUT;
|
||||
gpio_init_struct.GPIO_Speed = GPIO_Speed_2MHz;
|
||||
gpio_init_struct.GPIO_PuPd = GPIO_PuPd_NOPULL;
|
||||
GPIO_Init(pin_config.gpio, &gpio_init_struct);
|
||||
|
||||
// Set bit high or low
|
||||
GPIO_WriteBit(pin_config.gpio, pin_config.gpio_pin, (pin_state) ? Bit_SET : Bit_RESET);
|
||||
}
|
||||
|
||||
//! Power down I2C bus power supply
|
||||
//! Always lock bus and peripheral config access before use
|
||||
static void bus_rail_power_down(uint8_t bus_idx) {
|
||||
if (BOARD_CONFIG.i2c_bus_configs[bus_idx].rail_ctl_fn == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
BOARD_CONFIG.i2c_bus_configs[bus_idx].rail_ctl_fn(false);
|
||||
|
||||
// Drain through pull-ups
|
||||
bus_pin_cfg_output(BOARD_CONFIG.i2c_bus_configs[bus_idx].i2c_scl, false);
|
||||
bus_pin_cfg_output(BOARD_CONFIG.i2c_bus_configs[bus_idx].i2c_sda, false);
|
||||
}
|
||||
|
||||
//! Power up I2C bus power supply
|
||||
//! Always lock bus and peripheral config access before use
|
||||
static void bus_rail_power_up(uint8_t bus_idx) {
|
||||
if (BOARD_CONFIG.i2c_bus_configs[bus_idx].rail_ctl_fn == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
// check that at least enough time has elapsed since the last turn-off
|
||||
// TODO: is this necessary in bootloader?
|
||||
static const uint32_t MIN_STOP_TIME_MS = 10;
|
||||
delay_ms(MIN_STOP_TIME_MS);
|
||||
|
||||
bus_pin_cfg_input(BOARD_CONFIG.i2c_bus_configs[bus_idx].i2c_scl);
|
||||
bus_pin_cfg_input(BOARD_CONFIG.i2c_bus_configs[bus_idx].i2c_sda);
|
||||
|
||||
BOARD_CONFIG.i2c_bus_configs[bus_idx].rail_ctl_fn(true);
|
||||
}
|
||||
|
||||
//! Initialize the I2C peripheral
|
||||
//! Lock the bus and peripheral config access before initialization
|
||||
static void bus_init(uint8_t bus_idx) {
|
||||
// Initialize peripheral
|
||||
I2C_InitTypeDef i2c_init_struct;
|
||||
I2C_StructInit(&i2c_init_struct);
|
||||
|
||||
if (BOARD_CONFIG.i2c_bus_configs[bus_idx].clock_speed > I2C_NORMAL_MODE_CLOCK_SPEED_MAX) { //Fast mode
|
||||
i2c_init_struct.I2C_DutyCycle = BOARD_CONFIG.i2c_bus_configs[bus_idx].duty_cycle;
|
||||
}
|
||||
i2c_init_struct.I2C_ClockSpeed = BOARD_CONFIG.i2c_bus_configs[bus_idx].clock_speed;
|
||||
i2c_init_struct.I2C_Ack = I2C_Ack_Enable;
|
||||
|
||||
I2C_Init(i2c_buses[bus_idx].i2c, &i2c_init_struct);
|
||||
I2C_Cmd(i2c_buses[bus_idx].i2c, ENABLE);
|
||||
}
|
||||
|
||||
//! Configure the bus pins, enable the peripheral clock and initialize the I2C peripheral.
|
||||
//! Always lock the bus and peripheral config access before enabling it
|
||||
static void bus_enable(uint8_t bus_idx) {
|
||||
// Don't power up rail if the bus is already in use (enable can be called to reset bus)
|
||||
if (i2c_buses[bus_idx].user_count == 0) {
|
||||
bus_rail_power_up(bus_idx);
|
||||
}
|
||||
|
||||
bus_pin_cfg_i2c(BOARD_CONFIG.i2c_bus_configs[bus_idx].i2c_scl);
|
||||
bus_pin_cfg_i2c(BOARD_CONFIG.i2c_bus_configs[bus_idx].i2c_sda);
|
||||
|
||||
// Enable peripheral clock
|
||||
periph_config_enable(RCC_APB1PeriphClockCmd, BOARD_CONFIG.i2c_bus_configs[bus_idx].clock_ctrl);
|
||||
|
||||
bus_init(bus_idx);
|
||||
}
|
||||
|
||||
//! De-initialize and gate the clock to the peripheral
|
||||
//! Power down rail if the bus supports that and no devices are using it
|
||||
//! Always lock the bus and peripheral config access before disabling it
|
||||
static void bus_disable(uint8_t bus_idx) {
|
||||
I2C_DeInit(i2c_buses[bus_idx].i2c);
|
||||
|
||||
periph_config_disable(RCC_APB1PeriphClockCmd, BOARD_CONFIG.i2c_bus_configs[bus_idx].clock_ctrl);
|
||||
|
||||
// Do not de-power rail if there are still devices using bus (just reset peripheral and pin configuration during a bus reset)
|
||||
if (i2c_buses[bus_idx].user_count == 0) {
|
||||
bus_rail_power_down(bus_idx);
|
||||
}
|
||||
else {
|
||||
bus_pin_cfg_input(BOARD_CONFIG.i2c_bus_configs[bus_idx].i2c_scl);
|
||||
bus_pin_cfg_input(BOARD_CONFIG.i2c_bus_configs[bus_idx].i2c_sda);
|
||||
}
|
||||
}
|
||||
|
||||
//! Perform a soft reset of the bus
|
||||
//! Always lock the bus before reset
|
||||
static void bus_reset(uint8_t bus_idx) {
|
||||
bus_disable(bus_idx);
|
||||
bus_enable(bus_idx);
|
||||
}
|
||||
|
||||
/*---------------INIT/USE/RELEASE/RESET FUNCTIONS----------------------*/
|
||||
|
||||
void i2c_init(void) {
|
||||
for (uint32_t i = 0; i < ARRAY_LENGTH(i2c_buses); i++) {
|
||||
i2c_buses[i].i2c = BOARD_CONFIG.i2c_bus_configs[i].i2c;
|
||||
i2c_buses[i].user_count = 0;
|
||||
i2c_buses[i].busy = false;
|
||||
i2c_buses[i].transfer.idx = 0;
|
||||
i2c_buses[i].transfer.size = 0;
|
||||
i2c_buses[i].transfer.data = NULL;
|
||||
i2c_buses[i].transfer.state = TRANSFER_STATE_INVALID;
|
||||
|
||||
NVIC_InitTypeDef NVIC_InitStructure;
|
||||
NVIC_InitStructure.NVIC_IRQChannel = BOARD_CONFIG.i2c_bus_configs[i].ev_irq_channel;
|
||||
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x0c;
|
||||
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x00;
|
||||
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
|
||||
NVIC_Init(&NVIC_InitStructure);
|
||||
|
||||
NVIC_InitStructure.NVIC_IRQChannel = BOARD_CONFIG.i2c_bus_configs[i].er_irq_channel;
|
||||
NVIC_Init(&NVIC_InitStructure);
|
||||
|
||||
I2C_DeInit(i2c_buses[i].i2c);
|
||||
}
|
||||
|
||||
s_initialized = true;
|
||||
|
||||
for (uint32_t i = 0; i < ARRAY_LENGTH(i2c_buses); i++) {
|
||||
if (BOARD_CONFIG.i2c_bus_configs[i].rail_cfg_fn) {
|
||||
BOARD_CONFIG.i2c_bus_configs[i].rail_cfg_fn();
|
||||
}
|
||||
if (BOARD_CONFIG.i2c_bus_configs[i].rail_ctl_fn) {
|
||||
bus_rail_power_down(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void i2c_use(I2cDevice device_id) {
|
||||
PBL_ASSERTN(s_initialized);
|
||||
PBL_ASSERT(device_id < BOARD_CONFIG.i2c_device_count, "I2C device ID out of bounds %d (max: %d)",
|
||||
device_id, BOARD_CONFIG.i2c_device_count);
|
||||
|
||||
uint8_t bus_idx = BOARD_CONFIG.i2c_device_map[device_id];
|
||||
I2cBus *bus = &i2c_buses[bus_idx];
|
||||
|
||||
bus_lock(bus);
|
||||
|
||||
if (bus->user_count == 0) {
|
||||
bus_enable(bus_idx);
|
||||
}
|
||||
bus->user_count++;
|
||||
|
||||
bus_unlock(bus);
|
||||
}
|
||||
|
||||
void i2c_release(I2cDevice device_id) {
|
||||
PBL_ASSERTN(s_initialized);
|
||||
PBL_ASSERTN(device_id < BOARD_CONFIG.i2c_device_count);
|
||||
|
||||
uint8_t bus_idx = BOARD_CONFIG.i2c_device_map[device_id];
|
||||
I2cBus *bus = &i2c_buses[bus_idx];
|
||||
|
||||
bus_lock(bus);
|
||||
|
||||
if (bus->user_count == 0) {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Attempted release of disabled bus %d by device %d", bus_idx, device_id);
|
||||
bus_unlock(bus);
|
||||
return;
|
||||
}
|
||||
|
||||
bus->user_count--;
|
||||
if (bus->user_count == 0) {
|
||||
bus_disable(bus_idx);
|
||||
}
|
||||
|
||||
bus_unlock(bus);
|
||||
}
|
||||
|
||||
void i2c_reset(I2cDevice device_id) {
|
||||
PBL_ASSERTN(s_initialized);
|
||||
PBL_ASSERTN(device_id < BOARD_CONFIG.i2c_device_count);
|
||||
|
||||
uint8_t bus_idx = BOARD_CONFIG.i2c_device_map[device_id];
|
||||
I2cBus *bus = &i2c_buses[bus_idx];
|
||||
|
||||
// Take control of bus; only one task may use bus at a time
|
||||
bus_lock(bus);
|
||||
|
||||
if (bus->user_count == 0) {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Attempted reset of disabled bus %d by device %d", bus_idx, device_id);
|
||||
bus_unlock(bus);
|
||||
return;
|
||||
}
|
||||
|
||||
PBL_LOG(LOG_LEVEL_WARNING, "Resetting I2C bus %" PRId8, bus_idx);
|
||||
|
||||
// decrement user count for reset so that if this user is the only user, the
|
||||
// bus will be powered down during the reset
|
||||
bus->user_count--;
|
||||
|
||||
// Reset and reconfigure bus and pins
|
||||
bus_reset(bus_idx);
|
||||
|
||||
//Restore user count
|
||||
bus->user_count++;
|
||||
|
||||
bus_unlock(bus);
|
||||
}
|
||||
|
||||
/*--------------------DATA TRANSFER FUNCTIONS--------------------------*/
|
||||
|
||||
//! Wait a short amount of time for busy bit to clear
|
||||
static bool wait_for_busy_clear(uint8_t bus_idx) {
|
||||
unsigned int attempts = I2C_TIMEOUT_ATTEMPTS_MAX;
|
||||
while((i2c_buses[bus_idx].i2c->SR2 & I2C_SR2_BUSY) != 0) {
|
||||
--attempts;
|
||||
if (!attempts) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//! Abort the transfer
|
||||
//! Should only be called when the bus is locked
|
||||
static void abort_transfer(I2cBus *bus) {
|
||||
// Disable all interrupts on the bus
|
||||
bus->i2c->CR2 &= ~(I2C_CR2_ITEVTEN | I2C_CR2_ITERREN | I2C_CR2_ITBUFEN);
|
||||
// Generate a stop condition
|
||||
bus->i2c->CR1 |= I2C_CR1_STOP;
|
||||
bus->transfer.state = TRANSFER_STATE_INVALID;
|
||||
}
|
||||
|
||||
//! Set up and start a transfer to a device, wait for it to finish and clean up after the transfer has completed
|
||||
static bool do_transfer(I2cDevice device_id, bool read_not_write, uint8_t device_address, uint8_t register_address, uint8_t size, uint8_t *data) {
|
||||
PBL_ASSERTN(s_initialized);
|
||||
PBL_ASSERTN(device_id < BOARD_CONFIG.i2c_device_count);
|
||||
|
||||
uint8_t bus_idx = BOARD_CONFIG.i2c_device_map[device_id];
|
||||
I2cBus *bus = &i2c_buses[bus_idx];
|
||||
|
||||
// Take control of bus; only one task may use bus at a time
|
||||
bus_lock(bus);
|
||||
|
||||
if (bus->user_count == 0) {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Attempted access to disabled bus %d by device %d", bus_idx, device_id);
|
||||
bus_unlock(bus);
|
||||
return false;
|
||||
}
|
||||
|
||||
// If bus is busy (it shouldn't be as this function waits for the bus to report a non-idle state
|
||||
// before exiting) reset the bus and wait for it to become not-busy
|
||||
// Exit if bus remains busy. User module should reset the I2C module at this point
|
||||
if((bus->i2c->SR2 & I2C_SR2_BUSY) != 0) {
|
||||
bus_reset(bus_idx);
|
||||
|
||||
if (!wait_for_busy_clear(bus_idx)) {
|
||||
// Bus did not recover after reset
|
||||
bus_unlock(bus);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Take binary semaphore so that next take will block
|
||||
PBL_ASSERT(semaphore_take(bus), "Could not acquire semaphore token");
|
||||
|
||||
// Set up transfer
|
||||
bus->transfer.device_address = device_address;
|
||||
bus->transfer.register_address = register_address;
|
||||
bus->transfer.read_not_write = read_not_write;
|
||||
bus->transfer.size = size;
|
||||
bus->transfer.idx = 0;
|
||||
bus->transfer.state = TRANSFER_STATE_WRITE_ADDRESS_TX;
|
||||
bus->transfer.data = data;
|
||||
bus->transfer.nack_count = 0;
|
||||
|
||||
// Ack received bytes
|
||||
I2C_AcknowledgeConfig(bus->i2c, ENABLE);
|
||||
|
||||
bool result = false;
|
||||
|
||||
do {
|
||||
// Generate start event
|
||||
bus->i2c->CR1 |= I2C_CR1_START;
|
||||
//Enable event and error interrupts
|
||||
bus->i2c->CR2 |= I2C_CR2_ITEVTEN | I2C_CR2_ITERREN;
|
||||
|
||||
// Wait on semaphore until it is released by interrupt or a timeout occurs
|
||||
if (semaphore_wait(bus)) {
|
||||
|
||||
if (bus->transfer.state == TRANSFER_STATE_INVALID) {
|
||||
// Transfer is complete
|
||||
result = bus->transfer.result;
|
||||
if (!result) {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "I2C Error on bus %" PRId8, bus_idx);
|
||||
}
|
||||
|
||||
} else if (bus->transfer.nack_count < I2C_NACK_COUNT_MAX) {
|
||||
// NACK received after start condition sent: the MFI chip NACKs start conditions whilst it is busy
|
||||
// Retry start condition after a short delay.
|
||||
// A NACK count is incremented for each NACK received, so that legitimate NACK
|
||||
// errors cause the transfer to be aborted (after the NACK count max has been reached).
|
||||
|
||||
bus->transfer.nack_count++;
|
||||
|
||||
delay_ms(1);
|
||||
|
||||
} else {
|
||||
// Too many NACKs received, abort transfer
|
||||
abort_transfer(bus);
|
||||
break;
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "I2C Error: too many NACKs received on bus %" PRId8, bus_idx);
|
||||
}
|
||||
|
||||
} else {
|
||||
// Timeout, abort transfer
|
||||
abort_transfer(bus);
|
||||
break;
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Transfer timed out on bus %" PRId8, bus_idx);
|
||||
}
|
||||
} while (bus->transfer.state != TRANSFER_STATE_INVALID);
|
||||
|
||||
// Return semaphore token so another transfer can be started
|
||||
semaphore_give(bus);
|
||||
|
||||
// Wait for bus to to clear the busy flag before a new transfer starts
|
||||
// Theoretically a transfer could complete successfully, but the busy flag never clears,
|
||||
// which would cause the next transfer to fail
|
||||
if (!wait_for_busy_clear(bus_idx)) {
|
||||
// Reset I2C bus if busy flag does not clear
|
||||
bus_reset(bus_idx);
|
||||
}
|
||||
|
||||
bus_unlock(bus);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool i2c_read_register(I2cDevice device_id, uint8_t i2c_device_address, uint8_t register_address, uint8_t *result) {
|
||||
return i2c_read_register_block(device_id, i2c_device_address, register_address, 1, result);
|
||||
}
|
||||
|
||||
bool i2c_read_register_block(I2cDevice device_id, uint8_t i2c_device_address, uint8_t
|
||||
register_address_start, uint8_t read_size, uint8_t* result_buffer) {
|
||||
// Do transfer locks the bus
|
||||
bool result = do_transfer(device_id, true, i2c_device_address, register_address_start, read_size, result_buffer);
|
||||
|
||||
if (!result) {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Read failed on bus %" PRId8, BOARD_CONFIG.i2c_device_map[device_id]);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool i2c_write_register(I2cDevice device_id, uint8_t i2c_device_address, uint8_t register_address,
|
||||
uint8_t value) {
|
||||
return i2c_write_register_block(device_id, i2c_device_address, register_address, 1, &value);
|
||||
}
|
||||
|
||||
bool i2c_write_register_block(I2cDevice device_id, uint8_t i2c_device_address, uint8_t
|
||||
register_address_start, uint8_t write_size, const uint8_t* buffer) {
|
||||
// Do transfer locks the bus
|
||||
bool result = do_transfer(device_id, false, i2c_device_address, register_address_start, write_size, (uint8_t*)buffer);
|
||||
|
||||
if (!result) {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Write failed on bus %" PRId8, BOARD_CONFIG.i2c_device_map[device_id]);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*------------------------INTERRUPT FUNCTIONS--------------------------*/
|
||||
|
||||
//! End a transfer and disable further interrupts
|
||||
//! Only call from interrupt functions
|
||||
static portBASE_TYPE end_transfer_irq(I2cBus *bus, bool result) {
|
||||
bus->i2c->CR2 &= ~(I2C_CR2_ITEVTEN | I2C_CR2_ITERREN | I2C_CR2_ITBUFEN);
|
||||
bus->i2c->CR1 |= I2C_CR1_STOP;
|
||||
bus->transfer.result = result;
|
||||
bus->transfer.state = TRANSFER_STATE_INVALID;
|
||||
|
||||
bus->busy = false;
|
||||
return pdFALSE;
|
||||
}
|
||||
|
||||
//! Pause a transfer, disabling interrupts during the pause
|
||||
//! Only call from interrupt functions
|
||||
static portBASE_TYPE pause_transfer_irq(I2cBus *bus) {
|
||||
bus->i2c->CR2 &= ~(I2C_CR2_ITEVTEN | I2C_CR2_ITERREN | I2C_CR2_ITBUFEN);
|
||||
bus->busy = false;
|
||||
return pdFALSE;
|
||||
}
|
||||
|
||||
//! Handle an IRQ event on the specified \a bus
|
||||
static portBASE_TYPE irq_event_handler(I2cBus *bus) {
|
||||
if (bus->transfer.state == TRANSFER_STATE_INVALID) {
|
||||
|
||||
// Disable interrupts if spurious interrupt received
|
||||
bus->i2c->CR2 &= ~(I2C_CR2_ITEVTEN | I2C_CR2_ITBUFEN);
|
||||
return pdFALSE;
|
||||
}
|
||||
|
||||
// Check that the expected event occurred
|
||||
if (I2C_CheckEvent(bus->i2c, s_guard_events[bus->transfer.state]) == ERROR) {
|
||||
// Ignore interrupt - A spurious byte transmitted event as well as an interrupt with no
|
||||
// discernible event associated with it occur after repeat start events are generated
|
||||
return pdFALSE;
|
||||
}
|
||||
portBASE_TYPE should_context_switch = pdFALSE;
|
||||
|
||||
switch (bus->transfer.state) {
|
||||
case TRANSFER_STATE_WRITE_ADDRESS_TX:
|
||||
// Write the i2c device address to the bus to select it in write mode.
|
||||
bus->i2c->DR = bus->transfer.device_address & ~I2C_READ_WRITE_BIT;
|
||||
bus->transfer.state = TRANSFER_STATE_WRITE_REG_ADDRESS;
|
||||
break;
|
||||
|
||||
case TRANSFER_STATE_WRITE_REG_ADDRESS:
|
||||
// Write the register address
|
||||
bus->i2c->DR = bus->transfer.register_address;
|
||||
|
||||
if (bus->transfer.read_not_write) {
|
||||
bus->transfer.state = TRANSFER_STATE_REPEAT_START;
|
||||
} else {
|
||||
// Enable TXE interrupt for writing
|
||||
bus->i2c->CR2 |= I2C_CR2_ITBUFEN;
|
||||
bus->transfer.state = TRANSFER_STATE_WRITE_DATA;
|
||||
}
|
||||
break;
|
||||
|
||||
case TRANSFER_STATE_REPEAT_START:
|
||||
// Generate a repeat start
|
||||
bus->i2c->CR1 |= I2C_CR1_START;
|
||||
bus->transfer.state = TRANSFER_STATE_WRITE_ADDRESS_RX;
|
||||
break;
|
||||
|
||||
case TRANSFER_STATE_WRITE_ADDRESS_RX:
|
||||
// Write the I2C device address again, but this time in read mode.
|
||||
bus->i2c->DR = bus->transfer.device_address | I2C_READ_WRITE_BIT;
|
||||
if (bus->transfer.size == 1) {
|
||||
// Last byte, we want to NACK this one to tell the slave to stop sending us bytes.
|
||||
bus->i2c->CR1 &= ~I2C_CR1_ACK;
|
||||
}
|
||||
bus->transfer.state = TRANSFER_STATE_WAIT_FOR_DATA;
|
||||
break;
|
||||
|
||||
case TRANSFER_STATE_WAIT_FOR_DATA:
|
||||
//This state just ensures that the transition to receive mode event happened
|
||||
|
||||
// Enable RXNE interrupt for writing
|
||||
bus->i2c->CR2 |= I2C_CR2_ITBUFEN;
|
||||
bus->transfer.state = TRANSFER_STATE_READ_DATA;
|
||||
break;
|
||||
|
||||
case TRANSFER_STATE_READ_DATA:
|
||||
bus->transfer.data[bus->transfer.idx] = bus->i2c->DR;
|
||||
bus->transfer.idx++;
|
||||
|
||||
if (bus->transfer.idx + 1 == bus->transfer.size) {
|
||||
// Last byte, we want to NACK this one to tell the slave to stop sending us bytes.
|
||||
bus->i2c->CR1 &= ~I2C_CR1_ACK;
|
||||
}
|
||||
else if (bus->transfer.idx == bus->transfer.size) {
|
||||
// End transfer after all bytes have been received
|
||||
bus->i2c->CR2 &= ~I2C_CR2_ITBUFEN;
|
||||
should_context_switch = end_transfer_irq(bus, true);
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case TRANSFER_STATE_WRITE_DATA:
|
||||
bus->i2c->DR = bus->transfer.data[bus->transfer.idx];
|
||||
bus->transfer.idx++;
|
||||
if (bus->transfer.idx == bus->transfer.size) {
|
||||
bus->i2c->CR2 &= ~I2C_CR2_ITBUFEN;
|
||||
bus->transfer.state = TRANSFER_STATE_END_WRITE;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case TRANSFER_STATE_END_WRITE:
|
||||
// End transfer after all bytes have been sent
|
||||
should_context_switch = end_transfer_irq(bus, true);
|
||||
break;
|
||||
|
||||
default:
|
||||
// Abort transfer from invalid state - should never reach here (state machine logic broken)
|
||||
should_context_switch = end_transfer_irq(bus, false);
|
||||
break;
|
||||
}
|
||||
|
||||
return should_context_switch;
|
||||
}
|
||||
|
||||
//! Handle error interrupt on the specified \a bus
|
||||
static portBASE_TYPE irq_error_handler(I2cBus *bus) {
|
||||
if (bus->transfer.state == TRANSFER_STATE_INVALID) {
|
||||
|
||||
// Disable interrupts if spurious interrupt received
|
||||
bus->i2c->CR2 &= ~I2C_CR2_ITERREN;
|
||||
return pdFALSE;
|
||||
}
|
||||
|
||||
// Data overrun and bus errors can only really be handled by terminating the transfer and
|
||||
// trying to recover bus to an idle state. Each error will be logged. In each case a stop
|
||||
// condition will be sent and then we will wait on the busy flag to clear (if it doesn't,
|
||||
// a soft reset of the bus will be performed (handled in wait i2c_do_transfer).
|
||||
|
||||
if ((bus->i2c->SR1 & I2C_SR1_OVR) != 0) {
|
||||
bus->i2c->SR1 &= ~I2C_SR1_OVR;
|
||||
|
||||
// Data overrun
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Data overrun during I2C transaction; Bus: 0x%p", bus->i2c);
|
||||
}
|
||||
if ((bus->i2c->SR1 & I2C_SR1_BERR) != 0) {
|
||||
bus->i2c->SR1 &= ~I2C_SR1_BERR;
|
||||
|
||||
// Bus error: invalid start or stop condition detected
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Bus error detected during I2C transaction; Bus: 0x%p", bus->i2c);
|
||||
}
|
||||
if ((bus->i2c->SR1 & I2C_SR1_AF) != 0) {
|
||||
bus->i2c->SR1 &= ~I2C_SR1_AF;
|
||||
|
||||
// NACK received.
|
||||
//
|
||||
// The MFI chip will cause NACK errors during read operations after writing a start bit (first start
|
||||
// or repeat start indicating that it is busy. The transfer must be paused and the start condition sent
|
||||
// again after a delay and the state machine set back a step.
|
||||
//
|
||||
// If the NACK is received after any other action log an error and abort the transfer
|
||||
|
||||
|
||||
if (bus->transfer.state == TRANSFER_STATE_WAIT_FOR_DATA) {
|
||||
bus->transfer.state = TRANSFER_STATE_WRITE_ADDRESS_RX;
|
||||
return pause_transfer_irq(bus);
|
||||
}
|
||||
else if (bus->transfer.state == TRANSFER_STATE_WRITE_REG_ADDRESS){
|
||||
bus->transfer.state = TRANSFER_STATE_WRITE_ADDRESS_TX;
|
||||
return pause_transfer_irq(bus);
|
||||
}
|
||||
else {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "NACK received during I2C transfer; Bus: 0x%p", bus->i2c);
|
||||
}
|
||||
}
|
||||
|
||||
return end_transfer_irq(bus, false);
|
||||
|
||||
}
|
||||
|
||||
void I2C1_EV_IRQHandler(void) {
|
||||
portEND_SWITCHING_ISR(irq_event_handler(&i2c_buses[0]));
|
||||
}
|
||||
|
||||
void I2C1_ER_IRQHandler(void) {
|
||||
portEND_SWITCHING_ISR(irq_error_handler(&i2c_buses[0]));
|
||||
}
|
||||
|
||||
void I2C2_EV_IRQHandler(void) {
|
||||
portEND_SWITCHING_ISR(irq_event_handler(&i2c_buses[1]));
|
||||
}
|
||||
|
||||
void I2C2_ER_IRQHandler(void) {
|
||||
portEND_SWITCHING_ISR(irq_error_handler(&i2c_buses[1]));
|
||||
}
|
||||
|
||||
void I2C3_EV_IRQHandler(void) {
|
||||
portEND_SWITCHING_ISR(irq_event_handler(&i2c_buses[0]));
|
||||
}
|
||||
|
||||
void I2C3_ER_IRQHandler(void) {
|
||||
portEND_SWITCHING_ISR(irq_error_handler(&i2c_buses[0]));
|
||||
}
|
||||
|
||||
/*------------------------COMMAND FUNCTIONS--------------------------*/
|
||||
|
||||
void command_power_2v5(char *arg) {
|
||||
// Intentionally ignore the s_running_count and make it so!
|
||||
// This is intended for low level electrical test only
|
||||
if(!strcmp("on", arg)) {
|
||||
bus_rail_power_up(1);
|
||||
} else {
|
||||
bus_rail_power_down(1);
|
||||
}
|
||||
}
|
86
platform/silk/boot/src/drivers/i2c.h
Normal file
86
platform/silk/boot/src/drivers/i2c.h
Normal file
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "board/board.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
//! Initialize the I2C driver. Must be called before first use
|
||||
void i2c_init(void);
|
||||
|
||||
//! Start using the I2C bus connected to the device specified by \a device_id
|
||||
//! Must be called before any other use of the bus is performed
|
||||
//! @param device_id ID of device
|
||||
void i2c_use(I2cDevice device_id);
|
||||
|
||||
//! Stop using the I2C bus connected to the device specified by \a device_id
|
||||
//! Call when done with the bus
|
||||
//! @param device_id ID of device
|
||||
void i2c_release(I2cDevice device_id);
|
||||
|
||||
//! Reset the bus
|
||||
//! Will re-initialize the bus and cycle the power to the bus if this is
|
||||
//! supported for the bus the device specified by \a device_id is connected to)
|
||||
//! @param device_id ID of device, this will identify the bus to be reset
|
||||
void i2c_reset(I2cDevice device_id);
|
||||
|
||||
//! Manually bang out the clock on the bus specified by \a device_id for a period
|
||||
//! of time or until the data line recovers
|
||||
//! Must not be called before \ref i2c_use has been called for the device
|
||||
//! @param device_id ID of device, this will identify the bus to be recovered
|
||||
//! @return true if the data line recovered, false otherwise
|
||||
bool i2c_bitbang_recovery(I2cDevice device_id);
|
||||
|
||||
//! Read the value of a register
|
||||
//! Must not be called before \ref i2c_use has been called for the device
|
||||
//! @param device_id ID of device to communicate with
|
||||
//! @param i2c_device_address Device bus address
|
||||
//! @param register_address Address of register to read
|
||||
//! @param result Pointer to destination buffer
|
||||
//! @return true if transfer succeeded, false if error occurred
|
||||
bool i2c_read_register(I2cDevice device_id, uint8_t i2c_device_address, uint8_t register_address, uint8_t *result);
|
||||
|
||||
//! Read a sequence of registers starting from \a register_address_start
|
||||
//! Must not be called before \ref i2c_use has been called for the device
|
||||
//! @param device_id ID of device to communicate with
|
||||
//! @param i2c_device_address Device bus address
|
||||
//! @param register_address_start Address of first register to read
|
||||
//! @param read_size Number of bytes to read
|
||||
//! @param result_buffer Pointer to destination buffer
|
||||
//! @return true if transfer succeeded, false if error occurred
|
||||
bool i2c_read_register_block(I2cDevice device_id, uint8_t i2c_device_address, uint8_t register_address_start, uint8_t read_size, uint8_t* result_buffer);
|
||||
|
||||
//! Write to a register
|
||||
//! Must not be called before \ref i2c_use has been called for the device
|
||||
//! @param device_id ID of device to communicate with
|
||||
//! @param i2c_device_address Device bus address
|
||||
//! @param register_address Address of register to write to
|
||||
//! @param value Data value to write
|
||||
//! @return true if transfer succeeded, false if error occurred
|
||||
bool i2c_write_register(I2cDevice device_id, uint8_t i2c_device_address, uint8_t register_address, uint8_t value);
|
||||
|
||||
//! Write to a sequence of registers starting from \a register_address_start
|
||||
//! Must not be called before \ref i2c_use has been called for the device
|
||||
//! @param device_id ID of device to communicate with
|
||||
//! @param i2c_device_address Device bus address
|
||||
//! @param register_address_start Address of first register to read
|
||||
//! @param write_size Number of bytes to write
|
||||
//! @param buffer Pointer to source buffer
|
||||
//! @return true if transfer succeeded, false if error occurred
|
||||
bool i2c_write_register_block(I2cDevice device_id, uint8_t i2c_device_address, uint8_t register_address_start, uint8_t write_size, const uint8_t* buffer);
|
35
platform/silk/boot/src/drivers/i2c_private.c
Normal file
35
platform/silk/boot/src/drivers/i2c_private.c
Normal file
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
#include "drivers/pmic.h"
|
||||
#include "drivers/gpio.h"
|
||||
#include "util/delay.h"
|
||||
#include "board/board.h"
|
||||
|
||||
#include "stm32f4xx_gpio.h"
|
||||
#include "stm32f4xx_rcc.h"
|
||||
#include "stm32f4xx_i2c.h"
|
||||
|
||||
void silk_rail_ctl_fn(bool enable) {
|
||||
// NYI
|
||||
return;
|
||||
}
|
||||
|
||||
void silk_rail_cfg_fn(void) {
|
||||
// NYI
|
||||
return;
|
||||
}
|
29
platform/silk/boot/src/drivers/periph_config.h
Normal file
29
platform/silk/boot/src/drivers/periph_config.h
Normal file
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "stm32f4xx.h"
|
||||
|
||||
typedef void (*ClockCmd)(uint32_t periph, FunctionalState state);
|
||||
|
||||
static inline void periph_config_enable(ClockCmd clock_cmd, uint32_t periph) {
|
||||
clock_cmd(periph, ENABLE);
|
||||
}
|
||||
|
||||
static inline void periph_config_disable(ClockCmd clock_cmd, uint32_t periph) {
|
||||
clock_cmd(periph, DISABLE);
|
||||
}
|
37
platform/silk/boot/src/drivers/pmic.h
Normal file
37
platform/silk/boot/src/drivers/pmic.h
Normal file
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
//! Initialize the PMIC driver. Call this once at startup.
|
||||
bool pmic_init(void);
|
||||
|
||||
//! Whether or not the usb charger is detected by the pmic.
|
||||
bool pmic_is_usb_connected(void);
|
||||
|
||||
//! Returns true if this boot was caused by a charger event from standby, but
|
||||
//! there is no charger connected.
|
||||
bool pmic_boot_due_to_charger_disconnect(void);
|
||||
|
||||
//! Enter what we consider to be a "powered off" state, which is the as3701
|
||||
//! standby state, where we keep the RTC powered.
|
||||
bool pmic_power_off(void);
|
||||
|
||||
//! Fully shut down the as3701, which does not power the RTC.
|
||||
bool pmic_full_power_off(void);
|
311
platform/silk/boot/src/drivers/pmic/as3701b.c
Normal file
311
platform/silk/boot/src/drivers/pmic/as3701b.c
Normal file
|
@ -0,0 +1,311 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "drivers/pmic.h"
|
||||
|
||||
#include "board/board.h"
|
||||
#include "drivers/gpio.h"
|
||||
#include "drivers/i2c.h"
|
||||
#include "system/passert.h"
|
||||
|
||||
#include "stm32f4xx_rcc.h"
|
||||
#include "stm32f4xx_gpio.h"
|
||||
#include "stm32f4xx_adc.h"
|
||||
|
||||
typedef enum {
|
||||
PmicRegisters_SD1_VOLTAGE = 0x01,
|
||||
PmicRegisters_LDO1_VOLTAGE = 0x02,
|
||||
PmicRegisters_LDO2_VOLTAGE = 0x03,
|
||||
|
||||
PmicRegisters_GPIO1_CNTL = 0x09,
|
||||
PmicRegisters_GPIO2_CNTL = 0x0a,
|
||||
PmicRegisters_GPIO3_CNTL = 0x0b,
|
||||
PmicRegisters_GPIO4_CNTL = 0x0c,
|
||||
PmicRegisters_GPIO5_CNTL = 0x0d,
|
||||
PmicRegisters_GPIO_SIG_OUT = 0x20,
|
||||
PmicRegisters_GPIO_SIG_IN = 0x21,
|
||||
|
||||
PmicRegisters_REG1_VOLTAGE = 0x22,
|
||||
PmicRegisters_REG2_VOLTAGE = 0x23,
|
||||
PmicRegisters_REG_CNTL = 0x24,
|
||||
|
||||
PmicRegisters_GPIO_CNTL1 = 0x25,
|
||||
PmicRegisters_GPIO_CNTL2 = 0x26,
|
||||
PmicRegisters_SD_CNTL1 = 0x30,
|
||||
|
||||
PmicRegisters_BATT_VOLTAGE_MON = 0x32,
|
||||
PmicRegisters_STARTUP_CNTL = 0x33,
|
||||
PmicRegisters_REFERENCE_CNTL = 0x35,
|
||||
PmicRegisters_RESET_CNTL = 0x36,
|
||||
PmicRegisters_OVERTEMP_CNTL = 0x37,
|
||||
PmicRegisters_REG_STANDBY_MOD1 = 0x39,
|
||||
|
||||
PmicRegisters_PWM_CNTL_L = 0x41,
|
||||
PmicRegisters_PWM_CNTL_H = 0x42,
|
||||
|
||||
PmicRegisters_CURR1_VAL = 0x43,
|
||||
PmicRegisters_CURR2_VAL = 0x44,
|
||||
|
||||
PmicRegisters_REG_STATUS = 0x73,
|
||||
PmicRegisters_INT_MASK_1 = 0x74,
|
||||
PmicRegisters_INT_MASK_2 = 0x75,
|
||||
PmicRegisters_INT_STATUS_1 = 0x77,
|
||||
PmicRegisters_INT_STATUS_2 = 0x78,
|
||||
PmicRegisters_CHARGE_CNTL = 0x80,
|
||||
PmicRegisters_CHARGE_VOLTAGE_CNTL = 0x81,
|
||||
PmicRegisters_CHARGE_CURRENT_CNTL = 0x82,
|
||||
PmicRegisters_CHARGE_CONFIG_1 = 0x83,
|
||||
PmicRegisters_CHARGE_CONFIG_2 = 0x84,
|
||||
PmicRegisters_CHARGE_SUPERVISION = 0x85,
|
||||
PmicRegisters_CHARGE_STATUS_1 = 0x86,
|
||||
PmicRegisters_CHARGE_STATUS_2 = 0x87,
|
||||
|
||||
PmicRegisters_LOCK_REG = 0x8e,
|
||||
|
||||
PmicRegisters_CHIP_ID = 0x90,
|
||||
PmicRegisters_CHIP_REV = 0x91,
|
||||
|
||||
PmicRegisters_FUSE_5 = 0xa5,
|
||||
PmicRegisters_FUSE_6 = 0xa6,
|
||||
PmicRegisters_FUSE_7 = 0xa7,
|
||||
PmicRegisters_FUSE_8 = 0xa8,
|
||||
PmicRegisters_FUSE_9 = 0xa9,
|
||||
PmicRegisters_FUSE_10 = 0xaa,
|
||||
PmicRegisters_FUSE_11 = 0xab,
|
||||
PmicRegisters_FUSE_12 = 0xac,
|
||||
PmicRegisters_FUSE_13 = 0xad,
|
||||
PmicRegisters_FUSE_14 = 0xae,
|
||||
PmicRegisters_FUSE_15 = 0xaf,
|
||||
} PmicRegisters;
|
||||
|
||||
// These are values for the reset_reason field of the ResetControl register.
|
||||
// None of these values should ever be changed, as conversions are done on
|
||||
// readings dont directly out of the ResetControl register.
|
||||
// See Figure 79 of the AS3701B datasheet for more information.
|
||||
typedef enum {
|
||||
PmicResetReason_PowerUpFromScratch = 0x00, //!< Battery or charger insertion from scratch
|
||||
PmicResetReason_ResVoltFall = 0x01, //!< Battery voltage drop below 2.75V
|
||||
|
||||
PmicResetReason_ForcedReset = 0x02, //!< sw force_reset
|
||||
|
||||
PmicResetReason_OnPulledHigh = 0x03, //!< Force sw power_off, ON pulled high
|
||||
PmicResetReason_Charger = 0x04, //!< Forced sw power_off, Charger detected.
|
||||
|
||||
PmicResetReason_XRES = 0x05, //!< External trigger through XRES
|
||||
PmicResetReason_OverTemperature = 0x06, //!< Reset caused by overtemperature
|
||||
|
||||
PmicResetReason_OnKeyHold = 0x08, //!< Reset for holding down on key
|
||||
|
||||
PmicResetReason_StandbyInterrupt = 0x0b, //!< Reset for interrupt in standby
|
||||
PmicResetReason_StandbyOnPulledHigh = 0x0c, //!< Reset for ON pulled high in standby
|
||||
|
||||
PmicResetReason_Unknown,
|
||||
} PmicResetReason;
|
||||
|
||||
static void prv_start_120hz_clock(void);
|
||||
|
||||
static bool prv_init_gpio(void) {
|
||||
return true;
|
||||
}
|
||||
|
||||
//! Interrupt masks for InterruptStatus1 and InterruptMask1 registers
|
||||
enum PmicInt1 {
|
||||
PmicInt1_Trickle = (1 << 0), //!< Trickle charge
|
||||
PmicInt1_NoBat = (1 << 1), //!< Battery detached
|
||||
PmicInt1_Resume = (1 << 2), //!< Resuming charge on drop after full
|
||||
PmicInt1_EOC = (1 << 3), //!< End of charge
|
||||
PmicInt1_ChDet = (1 << 4), //!< Charger detected
|
||||
PmicInt1_OnKey = (1 << 5), //!< On Key held
|
||||
PmicInt1_OvTemp = (1 << 6), //!< Set when 110deg is exceeded
|
||||
PmicInt1_LowBat = (1 << 7), //!< Low Battery detected. Set when BSUP drops below ResVoltFall
|
||||
};
|
||||
|
||||
enum PmicRail {
|
||||
PmicRail_SD1, //!< 1.8V
|
||||
PmicRail_LDO1, //!< 3.0V
|
||||
PmicRail_LDO2, //!< 2.0V
|
||||
};
|
||||
|
||||
#define AS3701B_CHIP_ID 0x11
|
||||
#define AS3701B_WRITE_ADDR 0x80
|
||||
#define AS3701B_READ_ADDR 0x81
|
||||
|
||||
static bool prv_read_register(uint8_t register_address, uint8_t *result) {
|
||||
i2c_use(I2C_DEVICE_AS3701B);
|
||||
bool rv = i2c_read_register(I2C_DEVICE_AS3701B, AS3701B_READ_ADDR, register_address, result);
|
||||
i2c_release(I2C_DEVICE_AS3701B);
|
||||
return rv;
|
||||
}
|
||||
|
||||
static bool prv_write_register(uint8_t register_address, uint8_t value) {
|
||||
i2c_use(I2C_DEVICE_AS3701B);
|
||||
bool rv = i2c_write_register(I2C_DEVICE_AS3701B, AS3701B_WRITE_ADDR, register_address, value);
|
||||
i2c_release(I2C_DEVICE_AS3701B);
|
||||
return rv;
|
||||
}
|
||||
|
||||
static bool prv_register_set_bit(uint8_t register_address, uint8_t bit) {
|
||||
uint8_t val;
|
||||
if (!prv_read_register(register_address, &val)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
val |= (1 << bit);
|
||||
return prv_write_register(register_address, val);
|
||||
}
|
||||
|
||||
static bool prv_register_clear_bit(uint8_t register_address, uint8_t bit) {
|
||||
uint8_t val;
|
||||
if (!prv_read_register(register_address, &val)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
val &= ~(1 << bit);
|
||||
return prv_write_register(register_address, val);
|
||||
}
|
||||
|
||||
// Read the interrupt status registers to clear pending bits.
|
||||
static void prv_clear_pending_interrupts(void) {
|
||||
uint8_t throwaway_read_result;
|
||||
prv_read_register(PmicRegisters_INT_STATUS_1, &throwaway_read_result);
|
||||
prv_read_register(PmicRegisters_INT_STATUS_2, &throwaway_read_result);
|
||||
}
|
||||
|
||||
// Set up 120Hz clock which is used for VCOM.
|
||||
// Slowest possible setting, with divisor of 16 and high/low duration of 256us.
|
||||
void prv_start_120hz_clock(void) {
|
||||
const uint8_t pwm_high_low_time_us = (256 - 1);
|
||||
prv_write_register(PmicRegisters_PWM_CNTL_H, pwm_high_low_time_us);
|
||||
prv_write_register(PmicRegisters_PWM_CNTL_L, pwm_high_low_time_us);
|
||||
|
||||
bool success = false;
|
||||
uint8_t ref_cntl;
|
||||
if (prv_read_register(PmicRegisters_REFERENCE_CNTL, &ref_cntl)) {
|
||||
ref_cntl |= 0x3; // Divisor of 16
|
||||
prv_write_register(PmicRegisters_REFERENCE_CNTL, ref_cntl);
|
||||
|
||||
// Enable PWM Output on GPIO2 (Fig. 64)
|
||||
// Bits 6-4: Mode, 0x1 = Output
|
||||
// Bits 0-3: iosf, 0xe = PWM
|
||||
uint8_t val = (1 << 4) | 0x0e;
|
||||
success = prv_write_register(PmicRegisters_GPIO2_CNTL, val);
|
||||
}
|
||||
PBL_ASSERT(success, "Failed to start PMIC 120Hz PWM");
|
||||
}
|
||||
|
||||
static bool prv_is_alive(void) {
|
||||
uint8_t chip_id;
|
||||
if (!prv_read_register(PmicRegisters_CHIP_ID, &chip_id)) {
|
||||
return false;
|
||||
}
|
||||
const bool found = (chip_id == AS3701B_CHIP_ID);
|
||||
if (found) {
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Found the as3701b");
|
||||
} else {
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Error: read as3701b whoami byte 0x%x, expecting 0x11", chip_id);
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
bool pmic_init(void) {
|
||||
prv_init_gpio();
|
||||
if (!prv_is_alive()) {
|
||||
return false;
|
||||
}
|
||||
prv_start_120hz_clock();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool pmic_is_usb_connected(void) {
|
||||
uint8_t status;
|
||||
if (!prv_read_register(PmicRegisters_CHARGE_STATUS_2, &status)) {
|
||||
return false;
|
||||
}
|
||||
// ChargerStatus2 (Fig. 98)
|
||||
// Bit 2: Charger detected
|
||||
return !!(status & (1 << 2));
|
||||
}
|
||||
|
||||
static PmicResetReason prv_reset_reason(void) {
|
||||
uint8_t val;
|
||||
if (!prv_read_register(PmicRegisters_RESET_CNTL, &val)) {
|
||||
return PmicResetReason_Unknown;
|
||||
}
|
||||
return (val & 0xf0) >> 4;
|
||||
}
|
||||
|
||||
// If the pmic indicates that we were reset due to a charger interrupt, but the
|
||||
// charger is currently disconnected, then we know we were woken by a disconnect event.
|
||||
bool pmic_boot_due_to_charger_disconnect(void) {
|
||||
if (prv_reset_reason() != PmicResetReason_StandbyInterrupt) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t int_status;
|
||||
if (!prv_read_register(PmicRegisters_INT_STATUS_1, &int_status)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ((int_status & PmicInt1_ChDet) && !pmic_is_usb_connected());
|
||||
}
|
||||
|
||||
// This is a hard power off, resulting in all rails being disabled.
|
||||
bool pmic_full_power_off(void) {
|
||||
// ResetControl (Fig. 79)
|
||||
// Bit 1: power_off - Start a reset cycle, and wait for ON or charger to complete the reset.
|
||||
if (prv_register_set_bit(PmicRegisters_RESET_CNTL, 1)) {
|
||||
while (1) {}
|
||||
__builtin_unreachable();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// On the as3701b, a power_off will cut power to all rails. We want to keep the
|
||||
// RTC alive, so rather than performing a sw_power_off, enter the pmic's standby
|
||||
// mode, powering down all but LDO2.
|
||||
bool pmic_power_off(void) {
|
||||
// Only enable interrupts that should be able to wake us out of standby
|
||||
// - Wake on charger detect
|
||||
const uint8_t int_mask = ~((uint8_t)PmicInt1_ChDet);
|
||||
prv_write_register(PmicRegisters_INT_MASK_1, int_mask);
|
||||
prv_write_register(PmicRegisters_INT_MASK_2, 0xff);
|
||||
|
||||
// Clear interrupt status so we're not woken immediately (read the regs)
|
||||
prv_clear_pending_interrupts();
|
||||
|
||||
// Set Reg_Standby_mod1 to specify which rails to turn off / keep on
|
||||
// - SD1, LDO1 off
|
||||
// - LDO2 on
|
||||
// - Disable regulator pulldowns
|
||||
prv_write_register(PmicRegisters_REG_STANDBY_MOD1, 0xa);
|
||||
|
||||
// Set standby_mode_on (bit 4) in ReferenceControl to 1 (See Fig. 78)
|
||||
if (prv_register_set_bit(PmicRegisters_REFERENCE_CNTL, 4)) {
|
||||
while (1) {}
|
||||
__builtin_unreachable();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void set_ldo3_power_state(bool enabled) {
|
||||
}
|
||||
|
||||
void set_4V5_power_state(bool enabled) {
|
||||
}
|
||||
|
||||
void set_6V6_power_state(bool enabled) {
|
||||
}
|
111
platform/silk/boot/src/drivers/system_flash.c
Normal file
111
platform/silk/boot/src/drivers/system_flash.c
Normal file
|
@ -0,0 +1,111 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "drivers/system_flash.h"
|
||||
|
||||
#include "drivers/dbgserial.h"
|
||||
#include "util/misc.h"
|
||||
|
||||
#include "stm32f4xx_flash.h"
|
||||
|
||||
static uint16_t s_sectors[] = {
|
||||
FLASH_Sector_0, FLASH_Sector_1, FLASH_Sector_2, FLASH_Sector_3,
|
||||
FLASH_Sector_4, FLASH_Sector_5, FLASH_Sector_6, FLASH_Sector_7,
|
||||
FLASH_Sector_8, FLASH_Sector_9, FLASH_Sector_10, FLASH_Sector_11
|
||||
};
|
||||
static uint32_t s_sector_addresses[] = {
|
||||
ADDR_FLASH_SECTOR_0, ADDR_FLASH_SECTOR_1, ADDR_FLASH_SECTOR_2, ADDR_FLASH_SECTOR_3,
|
||||
ADDR_FLASH_SECTOR_4, ADDR_FLASH_SECTOR_5, ADDR_FLASH_SECTOR_6, ADDR_FLASH_SECTOR_7,
|
||||
ADDR_FLASH_SECTOR_8, ADDR_FLASH_SECTOR_9, ADDR_FLASH_SECTOR_10, ADDR_FLASH_SECTOR_11
|
||||
};
|
||||
|
||||
int prv_get_sector_num_for_address(uint32_t address) {
|
||||
if (address < s_sector_addresses[0]) {
|
||||
dbgserial_print("address ");
|
||||
dbgserial_print_hex(address);
|
||||
dbgserial_putstr(" is outside system flash");
|
||||
return -1;
|
||||
}
|
||||
for (size_t i=0; i < ARRAY_LENGTH(s_sector_addresses)-1; ++i) {
|
||||
if (s_sector_addresses[i] <= address
|
||||
&& address < s_sector_addresses[i+1]) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return ARRAY_LENGTH(s_sector_addresses)-1;
|
||||
}
|
||||
|
||||
bool system_flash_erase(
|
||||
uint32_t address, size_t length,
|
||||
SystemFlashProgressCb progress_callback, void *progress_context) {
|
||||
if (length == 0) {
|
||||
// Nothing to do
|
||||
return true;
|
||||
}
|
||||
|
||||
int first_sector = prv_get_sector_num_for_address(address);
|
||||
int last_sector = prv_get_sector_num_for_address(address + length - 1);
|
||||
if (first_sector < 0 || last_sector < 0) {
|
||||
return false;
|
||||
}
|
||||
int count = last_sector - first_sector + 1;
|
||||
if (progress_callback) {
|
||||
progress_callback(0, count, progress_context);
|
||||
}
|
||||
|
||||
FLASH_Unlock();
|
||||
for (int sector = first_sector; sector <= last_sector; ++sector) {
|
||||
FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_OPERR | FLASH_FLAG_WRPERR |
|
||||
FLASH_FLAG_PGAERR | FLASH_FLAG_PGPERR | FLASH_FLAG_PGSERR);
|
||||
if (FLASH_EraseSector(
|
||||
s_sectors[sector], VoltageRange_1) != FLASH_COMPLETE) {
|
||||
dbgserial_print("failed to erase sector ");
|
||||
dbgserial_print_hex(sector);
|
||||
dbgserial_newline();
|
||||
FLASH_Lock();
|
||||
return false;
|
||||
}
|
||||
if (progress_callback) {
|
||||
progress_callback(sector - first_sector + 1, count, progress_context);
|
||||
}
|
||||
}
|
||||
FLASH_Lock();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool system_flash_write(
|
||||
uint32_t address, const void *data, size_t length,
|
||||
SystemFlashProgressCb progress_callback, void *progress_context) {
|
||||
FLASH_Unlock();
|
||||
FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_OPERR | FLASH_FLAG_WRPERR |
|
||||
FLASH_FLAG_PGAERR | FLASH_FLAG_PGPERR|FLASH_FLAG_PGSERR);
|
||||
|
||||
const uint8_t *data_array = data;
|
||||
for (uint32_t i = 0; i < length; ++i) {
|
||||
if (FLASH_ProgramByte(address + i, data_array[i]) != FLASH_COMPLETE) {
|
||||
dbgserial_print("failed to write address ");
|
||||
dbgserial_print_hex(address);
|
||||
dbgserial_newline();
|
||||
FLASH_Lock();
|
||||
return false;
|
||||
}
|
||||
if (progress_callback && i % 128 == 0) {
|
||||
progress_callback(i/128, length/128, progress_context);
|
||||
}
|
||||
}
|
||||
FLASH_Lock();
|
||||
return true;
|
||||
}
|
64
platform/silk/boot/src/drivers/system_flash.h
Normal file
64
platform/silk/boot/src/drivers/system_flash.h
Normal file
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "stm32f4xx_flash.h"
|
||||
|
||||
#define ADDR_FLASH_SECTOR_0 ((uint32_t)0x08000000) /* Base @ of Sector 0, 16 Kbytes */
|
||||
#define ADDR_FLASH_SECTOR_1 ((uint32_t)0x08004000) /* Base @ of Sector 1, 16 Kbytes */
|
||||
#define ADDR_FLASH_SECTOR_2 ((uint32_t)0x08008000) /* Base @ of Sector 2, 16 Kbytes */
|
||||
#define ADDR_FLASH_SECTOR_3 ((uint32_t)0x0800C000) /* Base @ of Sector 3, 16 Kbytes */
|
||||
#define ADDR_FLASH_SECTOR_4 ((uint32_t)0x08010000) /* Base @ of Sector 4, 64 Kbytes */
|
||||
#define ADDR_FLASH_SECTOR_5 ((uint32_t)0x08020000) /* Base @ of Sector 5, 128 Kbytes */
|
||||
#define ADDR_FLASH_SECTOR_6 ((uint32_t)0x08040000) /* Base @ of Sector 6, 128 Kbytes */
|
||||
#define ADDR_FLASH_SECTOR_7 ((uint32_t)0x08060000) /* Base @ of Sector 7, 128 Kbytes */
|
||||
#define ADDR_FLASH_SECTOR_8 ((uint32_t)0x08080000) /* Base @ of Sector 8, 128 Kbytes */
|
||||
#define ADDR_FLASH_SECTOR_9 ((uint32_t)0x080A0000) /* Base @ of Sector 9, 128 Kbytes */
|
||||
#define ADDR_FLASH_SECTOR_10 ((uint32_t)0x080C0000) /* Base @ of Sector 10, 128 Kbytes */
|
||||
#define ADDR_FLASH_SECTOR_11 ((uint32_t)0x080E0000) /* Base @ of Sector 11, 128 Kbytes */
|
||||
|
||||
typedef void (*SystemFlashProgressCb)(
|
||||
uint32_t progress, uint32_t total, void *context);
|
||||
|
||||
// Erase the sectors of flash which lie within the given address range.
|
||||
//
|
||||
// If the address range overlaps even one single byte of a sector, the entire
|
||||
// sector is erased.
|
||||
//
|
||||
// If progress_callback is not NULL, it is called at the beginning of the erase
|
||||
// process and after each sector is erased. The rational number (progress/total)
|
||||
// increases monotonically as the sector erasue procedure progresses.
|
||||
//
|
||||
// Returns true if successful, false if an error occurred.
|
||||
bool system_flash_erase(
|
||||
uint32_t address, size_t length,
|
||||
SystemFlashProgressCb progress_callback, void *progress_context);
|
||||
|
||||
// Write data into flash. The flash must already be erased.
|
||||
//
|
||||
// If progress_callback is not NULL, it is called at the beginning of the
|
||||
// writing process and periodically thereafter. The rational number
|
||||
// (progress/total) increases monotonically as the data is written.
|
||||
//
|
||||
// Returns true if successful, false if an error occurred.
|
||||
bool system_flash_write(
|
||||
uint32_t address, const void *data, size_t length,
|
||||
SystemFlashProgressCb progress_callback, void *progress_context);
|
41
platform/silk/boot/src/drivers/watchdog.c
Normal file
41
platform/silk/boot/src/drivers/watchdog.c
Normal file
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "drivers/watchdog.h"
|
||||
|
||||
#include "stm32f4xx_dbgmcu.h"
|
||||
#include "stm32f4xx_iwdg.h"
|
||||
#include "stm32f4xx_rcc.h"
|
||||
|
||||
void watchdog_init(void) {
|
||||
IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable);
|
||||
|
||||
IWDG_SetPrescaler(IWDG_Prescaler_64); // ~8 seconds
|
||||
IWDG_SetReload(0xfff);
|
||||
|
||||
IWDG_WriteAccessCmd(IWDG_WriteAccess_Disable);
|
||||
|
||||
DBGMCU_APB1PeriphConfig(DBGMCU_IWDG_STOP, ENABLE);
|
||||
}
|
||||
|
||||
void watchdog_start(void) {
|
||||
IWDG_Enable();
|
||||
IWDG_ReloadCounter();
|
||||
}
|
||||
|
||||
bool watchdog_check_reset_flag(void) {
|
||||
return RCC_GetFlagStatus(RCC_FLAG_IWDGRST) != RESET;
|
||||
}
|
24
platform/silk/boot/src/drivers/watchdog.h
Normal file
24
platform/silk/boot/src/drivers/watchdog.h
Normal file
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
void watchdog_init(void);
|
||||
void watchdog_start(void);
|
||||
|
||||
bool watchdog_check_reset_flag(void);
|
Loading…
Add table
Add a link
Reference in a new issue