mirror of
https://github.com/google/pebble.git
synced 2025-05-31 07:23:12 +00:00
Import of the watch repository from Pebble
This commit is contained in:
commit
3b92768480
10334 changed files with 2564465 additions and 0 deletions
210
platform/robert/boot/src/drivers/display/boot_fpga.c
Normal file
210
platform/robert/boot/src/drivers/display/boot_fpga.c
Normal file
|
@ -0,0 +1,210 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "drivers/display.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "board/board.h"
|
||||
#include "drivers/dbgserial.h"
|
||||
#include "drivers/display/bootloader_fpga_bitstream.auto.h"
|
||||
#include "drivers/display/ice40lp.h"
|
||||
#include "drivers/display/ice40lp_definitions.h"
|
||||
#include "system/passert.h"
|
||||
#include "util/delay.h"
|
||||
#include "util/misc.h"
|
||||
#include "util/sle.h"
|
||||
|
||||
#define CMD_NULL (0)
|
||||
#define CMD_SET_PARAMETER (1)
|
||||
#define CMD_DISPLAY_OFF (2)
|
||||
#define CMD_DISPLAY_ON (3)
|
||||
#define CMD_DRAW_SCENE (4)
|
||||
#define CMD_RESET_RELEASE (8)
|
||||
#define CMD_RESET_ASSERT (9)
|
||||
|
||||
#define SCENE_BLACK (0)
|
||||
#define SCENE_SPLASH (1)
|
||||
#define SCENE_UPDATE (2)
|
||||
#define SCENE_ERROR (3)
|
||||
|
||||
#define UPDATE_PROGRESS_MAX (47)
|
||||
|
||||
static uint8_t s_decoded_fpga_image[35000]; // the FPGA image is currently ~30k
|
||||
|
||||
static bool prv_reset_fpga(void) {
|
||||
const uint32_t length = sle_decode(s_fpga_bitstream, sizeof(s_fpga_bitstream),
|
||||
s_decoded_fpga_image, sizeof(s_decoded_fpga_image));
|
||||
return display_program(s_decoded_fpga_image, length);
|
||||
}
|
||||
|
||||
static bool prv_wait_busy(void) {
|
||||
// The display should come out of busy within 35 milliseconds;
|
||||
// it is a waste of time to wait more than twice that.
|
||||
int timeout = 50 * 10;
|
||||
while (display_busy()) {
|
||||
if (timeout-- == 0) {
|
||||
dbgserial_putstr("Display busy-wait timeout expired!");
|
||||
return false;
|
||||
}
|
||||
delay_us(100);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void prv_screen_on(void) {
|
||||
display_write_cmd(CMD_DISPLAY_ON, NULL, 0);
|
||||
}
|
||||
|
||||
static void prv_screen_off(void) {
|
||||
display_write_cmd(CMD_DISPLAY_OFF, NULL, 0);
|
||||
}
|
||||
|
||||
static void prv_draw_scene(uint8_t scene) {
|
||||
display_write_cmd(CMD_DRAW_SCENE, &scene, sizeof(scene));
|
||||
}
|
||||
|
||||
static void prv_set_parameter(uint32_t param) {
|
||||
display_write_cmd(CMD_SET_PARAMETER, (uint8_t *)¶m, sizeof(param));
|
||||
}
|
||||
|
||||
#ifdef DISPLAY_DEMO_LOOP
|
||||
static void prv_play_demo_loop(void) {
|
||||
while (1) {
|
||||
for (int i = 0; i <= UPDATE_PROGRESS_MAX; ++i) {
|
||||
display_firmware_update_progress(i, UPDATE_PROGRESS_MAX);
|
||||
delay_ms(80);
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i <= 0xf; ++i) {
|
||||
display_error_code(i * 0x11111111);
|
||||
delay_ms(200);
|
||||
}
|
||||
for (uint32_t i = 0; i < 8; ++i) {
|
||||
for (uint32_t j = 1; j <= 0xf; ++j) {
|
||||
display_error_code(j << (i * 4));
|
||||
delay_ms(200);
|
||||
}
|
||||
}
|
||||
display_error_code(0x01234567);
|
||||
delay_ms(200);
|
||||
display_error_code(0x89abcdef);
|
||||
delay_ms(200);
|
||||
display_error_code(0xcafebabe);
|
||||
delay_ms(200);
|
||||
display_error_code(0xfeedface);
|
||||
delay_ms(200);
|
||||
display_error_code(0x8badf00d);
|
||||
delay_ms(200);
|
||||
display_error_code(0xbad1ce40);
|
||||
delay_ms(200);
|
||||
display_error_code(0xbeefcace);
|
||||
delay_ms(200);
|
||||
display_error_code(0x0defaced);
|
||||
delay_ms(200);
|
||||
display_error_code(0xd15ea5e5);
|
||||
delay_ms(200);
|
||||
display_error_code(0xdeadbeef);
|
||||
delay_ms(200);
|
||||
display_boot_splash();
|
||||
delay_ms(1000);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void display_init(void) {
|
||||
display_start();
|
||||
if (!prv_reset_fpga()) {
|
||||
dbgserial_putstr("FPGA configuration failed.");
|
||||
return;
|
||||
}
|
||||
|
||||
// enable the power rails
|
||||
display_power_enable();
|
||||
|
||||
// start with the screen off
|
||||
prv_screen_off();
|
||||
|
||||
// Work around an issue which some boards exhibit where the FPGA ring
|
||||
// oscillator can start up with higher harmonics, massively overclocking the
|
||||
// design and causing malfunction. When this occurrs, the draw-scene command
|
||||
// will not work, asserting BUSY indefinitely but never updating the display.
|
||||
// Other commands such as display-on and display-off are less affected by the
|
||||
// overclocking, so the display can be turned on while the FPGA is in this
|
||||
// state, showing only garbage.
|
||||
// FPGA malfunction can be detected in software. In an attempt to restore
|
||||
// proper functioning, the FPGA can be reset and reconfigured in the hopes
|
||||
// that the ring oscillator will start up and oscillate without any higher
|
||||
// harmonics. Bootloader release 03 attempts to mitigate this problem by
|
||||
// delaying oscillator startup until after configuration completes. Time will
|
||||
// tell whether this actually fixes things.
|
||||
for (int retries = 0; retries <= 10; ++retries) {
|
||||
prv_draw_scene(SCENE_SPLASH);
|
||||
if (prv_wait_busy()) {
|
||||
prv_screen_on();
|
||||
dbgserial_print("Display initialized after ");
|
||||
dbgserial_print_hex(retries);
|
||||
dbgserial_putstr(" retries.");
|
||||
#ifdef DISPLAY_DEMO_LOOP
|
||||
prv_play_demo_loop();
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
if (!prv_reset_fpga()) {
|
||||
dbgserial_putstr("FPGA configuration failed.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// It's taken too many attempts and the FPGA still isn't behaving. Give up on
|
||||
// showing the splash screen and keep the screen off so that the user doesn't
|
||||
// see a broken-looking staticky screen on boot.
|
||||
dbgserial_putstr("Display initialization failed.");
|
||||
prv_screen_off();
|
||||
}
|
||||
|
||||
void display_boot_splash(void) {
|
||||
prv_wait_busy();
|
||||
prv_draw_scene(SCENE_SPLASH);
|
||||
// Don't turn the screen on until the boot-splash is fully drawn.
|
||||
prv_wait_busy();
|
||||
prv_screen_on();
|
||||
}
|
||||
|
||||
void display_firmware_update_progress(
|
||||
uint32_t numerator, uint32_t denominator) {
|
||||
static uint8_t last_bar_fill = UINT8_MAX;
|
||||
// Scale progress to the number of pixels in the progress bar,
|
||||
// rounding half upwards.
|
||||
uint8_t bar_fill =
|
||||
((numerator * UPDATE_PROGRESS_MAX) + ((denominator+1)/2)) / denominator;
|
||||
// Don't waste time and power redrawing the same screen repeatedly.
|
||||
if (bar_fill != last_bar_fill) {
|
||||
last_bar_fill = bar_fill;
|
||||
prv_set_parameter(bar_fill);
|
||||
prv_draw_scene(SCENE_UPDATE);
|
||||
}
|
||||
}
|
||||
|
||||
void display_error_code(uint32_t error_code) {
|
||||
prv_set_parameter(error_code);
|
||||
prv_draw_scene(SCENE_ERROR);
|
||||
}
|
||||
|
||||
void display_prepare_for_reset(void) {
|
||||
prv_screen_off();
|
||||
}
|
186
platform/robert/boot/src/drivers/display/ice40lp.c
Normal file
186
platform/robert/boot/src/drivers/display/ice40lp.c
Normal file
|
@ -0,0 +1,186 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "ice40lp.h"
|
||||
|
||||
#include "board/board.h"
|
||||
#include "drivers/dbgserial.h"
|
||||
#include "drivers/display/ice40lp_definitions.h"
|
||||
#include "drivers/gpio.h"
|
||||
#include "drivers/periph_config.h"
|
||||
#include "drivers/pmic.h"
|
||||
#include "system/logging.h"
|
||||
#include "system/passert.h"
|
||||
#include "util/delay.h"
|
||||
|
||||
#include "stm32f7xx.h"
|
||||
#include "misc.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
|
||||
|
||||
static void prv_spi_init(void) {
|
||||
// Configure the GPIO (SCLK, MOSI - no MISO since the SPI is TX-only)
|
||||
gpio_af_init(&ICE40LP->spi.clk, GPIO_OType_PP, GPIO_Speed_25MHz, GPIO_PuPd_NOPULL);
|
||||
gpio_af_init(&ICE40LP->spi.mosi, GPIO_OType_PP, GPIO_Speed_25MHz, GPIO_PuPd_NOPULL);
|
||||
|
||||
// Reset the SPI peripheral and enable the clock
|
||||
RCC_APB2PeriphResetCmd(ICE40LP->spi.rcc_bit, ENABLE);
|
||||
RCC_APB2PeriphResetCmd(ICE40LP->spi.rcc_bit, DISABLE);
|
||||
periph_config_enable(ICE40LP->spi.periph, ICE40LP->spi.rcc_bit);
|
||||
|
||||
// Configure CR1 first:
|
||||
// * TX-only mode (BIDIMODE | BIDIOE)
|
||||
// * software control NSS pin (SSM | SSI)
|
||||
// * master mode (MSTR)
|
||||
// * clock polarity high / 2nd edge (CPOL | CPHA)
|
||||
ICE40LP->spi.periph->CR1 = SPI_CR1_BIDIMODE | SPI_CR1_BIDIOE | SPI_CR1_SSM | SPI_CR1_SSI |
|
||||
SPI_CR1_MSTR | SPI_CR1_CPOL | SPI_CR1_CPHA;
|
||||
|
||||
// Configure CR2:
|
||||
// * 8-bit data size (DS[4:0] == 0b0111)
|
||||
// * 1/4 RX threshold (for 8-bit transfers)
|
||||
ICE40LP->spi.periph->CR2 = SPI_CR2_DS_0 | SPI_CR2_DS_1 | SPI_CR2_DS_2 | SPI_CR2_FRXTH;
|
||||
|
||||
// enable the SPI
|
||||
ICE40LP->spi.periph->CR1 |= SPI_CR1_SPE;
|
||||
}
|
||||
|
||||
static void prv_spi_write(const uint8_t *data, uint32_t len) {
|
||||
for (uint32_t i = 0; i < len; ++i) {
|
||||
// Wait until we can transmit.
|
||||
while (!(ICE40LP->spi.periph->SR & SPI_SR_TXE)) continue;
|
||||
|
||||
// Write a byte. STM32F7 needs to access as 8 bits in order to actually do 8 bits.
|
||||
*(volatile uint8_t*)&ICE40LP->spi.periph->DR = data[i];
|
||||
}
|
||||
|
||||
// Wait until the TX FIFO is empty plus an extra little bit for the shift-register.
|
||||
while (((ICE40LP->spi.periph->SR & SPI_SR_FTLVL) >> __builtin_ctz(SPI_SR_FTLVL)) > 0) continue;
|
||||
delay_us(10);
|
||||
}
|
||||
|
||||
bool display_busy(void) {
|
||||
return gpio_input_read(&ICE40LP->busy);
|
||||
}
|
||||
|
||||
void display_start(void) {
|
||||
// Configure SCS before CRESET and before configuring the SPI so that we don't end up with the
|
||||
// FPGA in the "SPI Master Configuration Interface" on bigboards which don't have NVCM. If we end
|
||||
// up in this mode, the FPGA will drive the clock and put the SPI peripheral in a bad state.
|
||||
gpio_output_init(&ICE40LP->spi.scs, GPIO_OType_PP, GPIO_Speed_25MHz);
|
||||
gpio_output_set(&ICE40LP->spi.scs, false);
|
||||
gpio_input_init(&ICE40LP->cdone);
|
||||
gpio_input_init(&ICE40LP->busy);
|
||||
gpio_output_init(&ICE40LP->creset, GPIO_OType_OD, GPIO_Speed_25MHz);
|
||||
|
||||
prv_spi_init();
|
||||
}
|
||||
|
||||
bool display_program(const uint8_t *fpga_bitstream, uint32_t bitstream_size) {
|
||||
InputConfig creset_input = {
|
||||
.gpio = ICE40LP->creset.gpio,
|
||||
.gpio_pin = ICE40LP->creset.gpio_pin,
|
||||
};
|
||||
|
||||
delay_ms(1);
|
||||
|
||||
gpio_output_set(&ICE40LP->spi.scs, true); // SCS asserted (low)
|
||||
gpio_output_set(&ICE40LP->creset, false); // CRESET LOW
|
||||
|
||||
delay_ms(1);
|
||||
|
||||
if (gpio_input_read(&creset_input)) {
|
||||
dbgserial_putstr("CRESET not low during reset");
|
||||
return false;
|
||||
}
|
||||
|
||||
gpio_output_set(&ICE40LP->creset, true); // CRESET -> HIGH
|
||||
|
||||
delay_ms(1);
|
||||
|
||||
if (gpio_input_read(&ICE40LP->cdone)) {
|
||||
dbgserial_putstr("CDONE not low after reset");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!gpio_input_read(&creset_input)) {
|
||||
dbgserial_putstr("CRESET not high after reset");
|
||||
return false;
|
||||
}
|
||||
|
||||
delay_ms(1);
|
||||
|
||||
// Program the FPGA
|
||||
prv_spi_write(fpga_bitstream, bitstream_size);
|
||||
|
||||
// Set SCS high so that we don't process any of these clocks as commands.
|
||||
gpio_output_set(&ICE40LP->spi.scs, false); // SCS not asserted (high)
|
||||
|
||||
// 49+ SCLK cycles to tell FPGA we're done configuration.
|
||||
static const uint8_t spi_zeros[9] = {0, 0, 0, 0, 0, 0, 0, 0, 0};
|
||||
prv_spi_write(spi_zeros, sizeof(spi_zeros));
|
||||
|
||||
if (!gpio_input_read(&ICE40LP->cdone)) {
|
||||
dbgserial_putstr("CDONE not high after programming");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void display_power_enable(void) {
|
||||
// The display requires us to wait 1ms between each power rail coming up. The PMIC
|
||||
// initialization brings up the 3.2V rail (VLCD on the display, LD02 on the PMIC) for us, but
|
||||
// we still need to wait before turning on the subsequent rails.
|
||||
delay_ms(2);
|
||||
|
||||
if (ICE40LP->use_6v6_rail) {
|
||||
dbgserial_putstr("Enabling 6v6 (Display VDDC)");
|
||||
set_6V6_power_state(true);
|
||||
|
||||
delay_ms(2);
|
||||
}
|
||||
|
||||
dbgserial_putstr("Enabling 4v5 (Display VDDP)");
|
||||
set_4V5_power_state(true);
|
||||
}
|
||||
|
||||
void display_power_disable(void) {
|
||||
dbgserial_putstr("Disabling 4v5 (Display VDDP)");
|
||||
set_4V5_power_state(false);
|
||||
|
||||
delay_ms(2);
|
||||
|
||||
if (ICE40LP->use_6v6_rail) {
|
||||
dbgserial_putstr("Disabling 6v6 (Display VDDC)");
|
||||
set_6V6_power_state(false);
|
||||
|
||||
delay_ms(2);
|
||||
}
|
||||
}
|
||||
|
||||
void display_write_cmd(uint8_t cmd, uint8_t *arg, uint32_t arg_len) {
|
||||
gpio_output_set(&ICE40LP->spi.scs, true); // SCS asserted (low)
|
||||
delay_us(100);
|
||||
|
||||
prv_spi_write(&cmd, sizeof(cmd));
|
||||
if (arg_len) {
|
||||
prv_spi_write(arg, arg_len);
|
||||
}
|
||||
|
||||
gpio_output_set(&ICE40LP->spi.scs, false); // SCS not asserted (high)
|
||||
}
|
27
platform/robert/boot/src/drivers/display/ice40lp.h
Normal file
27
platform/robert/boot/src/drivers/display/ice40lp.h
Normal file
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
bool display_busy(void);
|
||||
void display_start(void);
|
||||
bool display_program(const uint8_t *fpga_bitstream, uint32_t bitstream_size);
|
||||
void display_write_cmd(uint8_t cmd, uint8_t *arg, uint32_t arg_len);
|
||||
void display_power_enable(void);
|
||||
void display_power_disable(void);
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "board/board.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
typedef const struct ICE40LPDevice {
|
||||
struct {
|
||||
SPI_TypeDef *periph;
|
||||
uint32_t rcc_bit;
|
||||
AfConfig clk;
|
||||
AfConfig mosi;
|
||||
OutputConfig scs;
|
||||
} spi;
|
||||
|
||||
OutputConfig creset;
|
||||
InputConfig cdone;
|
||||
InputConfig busy;
|
||||
|
||||
bool use_6v6_rail;
|
||||
} ICE40LPDevice;
|
Loading…
Add table
Add a link
Reference in a new issue