mirror of
https://github.com/google/pebble.git
synced 2025-06-16 14:43: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
56
src/fw/drivers/display/display.h
Normal file
56
src/fw/drivers/display/display.h
Normal file
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* 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/display.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef struct {
|
||||
uint8_t address;
|
||||
uint8_t* data;
|
||||
} DisplayRow;
|
||||
|
||||
typedef bool(*NextRowCallback)(DisplayRow* row);
|
||||
typedef void(*UpdateCompleteCallback)(void);
|
||||
|
||||
//! Show the splash screen before the display has been fully initialized.
|
||||
void display_show_splash_screen(void);
|
||||
|
||||
void display_init(void);
|
||||
|
||||
uint32_t display_baud_rate_change(uint32_t new_frequency_hz);
|
||||
|
||||
void display_clear(void);
|
||||
|
||||
void display_update(NextRowCallback nrcb, UpdateCompleteCallback uccb);
|
||||
|
||||
bool display_update_in_progress(void);
|
||||
|
||||
void display_pulse_vcom(void);
|
||||
|
||||
//! Show the panic screen.
|
||||
//!
|
||||
//! This function is only defined if the display hardware and driver support it.
|
||||
void display_show_panic_screen(uint32_t error_code);
|
||||
|
||||
typedef struct GPoint GPoint;
|
||||
|
||||
void display_set_offset(GPoint offset);
|
||||
|
||||
GPoint display_get_offset(void);
|
602
src/fw/drivers/display/ice40lp/ice40lp.c
Normal file
602
src/fw/drivers/display/ice40lp/ice40lp.c
Normal file
|
@ -0,0 +1,602 @@
|
|||
/*
|
||||
* 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/ice40lp/ice40lp.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "FreeRTOS.h"
|
||||
#include "applib/graphics/framebuffer.h"
|
||||
#include "applib/graphics/gtypes.h"
|
||||
#include "board/board.h"
|
||||
#include "board/display.h"
|
||||
#include "debug/power_tracking.h"
|
||||
#include "drivers/clocksource.h"
|
||||
#include "drivers/display/ice40lp/fpga_bitstream.auto.h"
|
||||
#include "drivers/display/ice40lp/ice40lp_definitions.h"
|
||||
#include "drivers/display/ice40lp/ice40lp_internal.h"
|
||||
#include "drivers/display/ice40lp/snowy_boot.h"
|
||||
#include "drivers/dma.h"
|
||||
#include "drivers/exti.h"
|
||||
#include "drivers/gpio.h"
|
||||
#include "drivers/periph_config.h"
|
||||
#include "drivers/pmic.h"
|
||||
#include "drivers/spi.h"
|
||||
#include "drivers/spi_dma.h"
|
||||
#include "kernel/events.h"
|
||||
#include "kernel/event_loop.h"
|
||||
#include "kernel/util/sleep.h"
|
||||
#include "kernel/util/stop.h"
|
||||
#include "os/mutex.h"
|
||||
#include "os/tick.h"
|
||||
#include "semphr.h"
|
||||
#include "services/common/analytics/analytics.h"
|
||||
#include "services/common/compositor/compositor.h"
|
||||
#include "services/common/compositor/compositor_display.h"
|
||||
#include "services/common/system_task.h"
|
||||
#include "system/logging.h"
|
||||
#include "system/passert.h"
|
||||
#include "system/profiler.h"
|
||||
#include "task.h"
|
||||
#include "util/attributes.h"
|
||||
#include "util/net.h"
|
||||
#include "util/reverse.h"
|
||||
#include "util/size.h"
|
||||
|
||||
#define STM32F4_COMPATIBLE
|
||||
#define STM32F7_COMPATIBLE
|
||||
#include <mcu.h>
|
||||
|
||||
#if DISPLAY_ORIENTATION_ROW_MAJOR || DISPLAY_ORIENTATION_ROW_MAJOR_INVERTED
|
||||
#define DISP_LINES DISP_ROWS
|
||||
#define DISP_PIXELS DISP_COLS
|
||||
#elif DISPLAY_ORIENTATION_COLUMN_MAJOR_INVERTED
|
||||
#define DISP_LINES DISP_COLS
|
||||
#define DISP_PIXELS DISP_ROWS
|
||||
#else
|
||||
#error "Unknown or missing display orientation define"
|
||||
#endif
|
||||
|
||||
typedef void (*PopulateLineCB)(
|
||||
int column, uint8_t * restrict line_buffer, const void *cb_data);
|
||||
|
||||
//! 2 buffers to hold line data being transferred.
|
||||
static uint8_t DMA_READ_BSS s_line_buffer[2][DISP_PIXELS];
|
||||
//! buffer index keeps track of which line buffer is in use
|
||||
static uint32_t s_buffer_idx;
|
||||
//! line index is the line of the display currently being updated
|
||||
static uint32_t s_line_index;
|
||||
|
||||
//! offset for shifting the image origin from the display's origin
|
||||
//! display coordinates (0,0) are top-left,
|
||||
//! offset positive values shift right and down
|
||||
static GPoint s_disp_offset;
|
||||
|
||||
static bool s_update_in_progress;
|
||||
static bool s_terminate_pending;
|
||||
|
||||
static RtcTicks s_start_ticks;
|
||||
|
||||
static bool s_initialized = false;
|
||||
|
||||
//! lockout to prevent display updates when the panic screen is shown
|
||||
static bool s_panic_screen_lockout;
|
||||
|
||||
static PebbleMutex *s_display_update_mutex;
|
||||
static SemaphoreHandle_t s_fpga_busy;
|
||||
|
||||
static UpdateCompleteCallback s_update_complete_callback;
|
||||
|
||||
static void prv_start_dma_transfer(uint8_t *addr, uint32_t length);
|
||||
static void display_interrupt_intn(bool *should_context_switch);
|
||||
|
||||
static inline OPTIMIZE_FUNC(2) void prv_pixel_scramble(
|
||||
uint8_t * restrict line_buf, const uint8_t px_odd, const uint8_t px_even,
|
||||
const int offset) {
|
||||
uint8_t msb, lsb;
|
||||
msb = (px_odd & 0b00101010) | (px_even & 0b00101010) >> 1;
|
||||
lsb = (px_odd & 0b00010101) << 1 | (px_even & 0b00010101);
|
||||
line_buf[offset/2] = msb;
|
||||
line_buf[offset/2 + DISP_PIXELS/2] = lsb;
|
||||
}
|
||||
|
||||
static inline OPTIMIZE_FUNC(2) void prv_row_major_get_line(
|
||||
uint8_t * restrict line, const uint8_t * restrict image_buf,
|
||||
int index) {
|
||||
#if DISPLAY_ORIENTATION_ROW_MAJOR_INVERTED
|
||||
// Optimized line renderer for Robert.
|
||||
// Could easily apply to the other screens, but only Robert really needs it.
|
||||
// By loading both pixels with a single load, we can cut down code size (cache benefit)
|
||||
// and decrease number of bus accesses.
|
||||
// Theoretically loading 4 pixels at a time should be better, but GCC generated much
|
||||
// worse code that way.
|
||||
|
||||
#if DISPLAY_ORIENTATION_ROTATED_180
|
||||
// Setup srcbuf to be the end, since we need to scan backwards horizontally
|
||||
const uint16_t *srcbuf = (const uint16_t *)(image_buf + DISP_PIXELS * index);
|
||||
for (int dst_offset = 0; dst_offset < DISP_PIXELS/2; dst_offset++) {
|
||||
// Get the two pixels
|
||||
const uint16_t pix = *srcbuf++;
|
||||
|
||||
// Swizzle the pixels
|
||||
line[0] = ((pix) & 0b101010) | ((pix >> 9) & 0b010101);
|
||||
line[DISP_PIXELS/2] = ((pix << 1) & 0b101010) | ((pix >> 8) & 0b010101);
|
||||
line++;
|
||||
}
|
||||
#else
|
||||
// Setup srcbuf to be the end, since we need to scan backwards horizontally
|
||||
const uint16_t *srcbuf = (const uint16_t *)(image_buf + DISP_PIXELS * (DISP_LINES - index) - 2);
|
||||
for (int dst_offset = 0; dst_offset < DISP_PIXELS/2; dst_offset++) {
|
||||
// Get the two pixels
|
||||
const uint16_t pix = *srcbuf--;
|
||||
|
||||
// Swizzle the pixels
|
||||
line[0] = ((pix >> 8) & 0b101010) | ((pix >> 1) & 0b010101);
|
||||
line[DISP_PIXELS/2] = ((pix >> 7) & 0b101010) | ((pix) & 0b010101);
|
||||
line++;
|
||||
}
|
||||
#endif // DISPLAY_ORIENTATION_ROTATED_180
|
||||
#else
|
||||
// adjust line index according to display offset,
|
||||
// populate blank (black) line if this exceeds the source framebuffer
|
||||
index -= s_disp_offset.y;
|
||||
if (!WITHIN(index, 0, DISP_LINES - 1)) {
|
||||
memset(line, 0, DISP_PIXELS);
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t odd, even;
|
||||
#if PLATFORM_SPALDING
|
||||
const GBitmapDataRowInfoInternal *row_infos = g_gbitmap_spalding_data_row_infos;
|
||||
const uint8_t *row_start = image_buf + row_infos[index].offset;
|
||||
#endif
|
||||
|
||||
// Line starts with MSB of each color in all pixels
|
||||
// Line finishes with LSB of each color in all pixels
|
||||
// separate src_offset is adjusted according to manufacturing offset,
|
||||
// loop condition/continue makes sure we don't read past boundaries of src framebuffer
|
||||
for (int src_offset = -s_disp_offset.x, dst_offset = 0;
|
||||
src_offset < DISP_PIXELS && dst_offset < DISP_PIXELS;
|
||||
src_offset += 2, dst_offset += 2) {
|
||||
#if !PLATFORM_SPALDING
|
||||
if (src_offset < 0) {
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if DISPLAY_ORIENTATION_ROW_MAJOR
|
||||
#if PLATFORM_SPALDING
|
||||
even = WITHIN(src_offset + 1, row_infos[index].min_x, row_infos[index].max_x) ?
|
||||
row_start[src_offset + 1] : 0;
|
||||
odd = WITHIN(src_offset, row_infos[index].min_x, row_infos[index].max_x) ?
|
||||
row_start[src_offset] : 0;
|
||||
#else
|
||||
#error Unsupported display
|
||||
#endif
|
||||
#elif DISPLAY_ORIENTATION_COLUMN_MAJOR_INVERTED
|
||||
even = image_buf[DISP_COLS * (DISP_ROWS-2 - src_offset) + index];
|
||||
odd = image_buf[DISP_COLS * (DISP_ROWS-2 - src_offset + 1) + index];
|
||||
#endif
|
||||
prv_pixel_scramble(line, odd, even, dst_offset);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static void prv_framebuffer_populate_line(
|
||||
int index, uint8_t * restrict line) {
|
||||
const uint8_t *frame_buffer = compositor_get_framebuffer()->buffer;
|
||||
prv_row_major_get_line(line, frame_buffer, index);
|
||||
}
|
||||
|
||||
static void enable_display_dma_clock(void) {
|
||||
power_tracking_start(PowerSystemMcuDma2);
|
||||
}
|
||||
|
||||
static void disable_display_dma(void) {
|
||||
// Properly disable DMA interrupts and deinitialize the DMA controller to prevent pending
|
||||
// interrupts from firing when the clock is re-enabled (this could possibly cause a stray
|
||||
// terminate callback being added to kernel main)
|
||||
|
||||
spi_ll_slave_write_dma_stop(ICE40LP->spi_port);
|
||||
power_tracking_stop(PowerSystemMcuDma2);
|
||||
}
|
||||
|
||||
static void prv_terminate_transfer(void *data) {
|
||||
if (s_panic_screen_lockout) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Only need intn when communicating with the display.
|
||||
// Disable EXTI interrupt before ending the frame to prevent possible race condition resulting
|
||||
// from a almost empty FIFO on the FPGA triggering a terminate call before the interrupt is
|
||||
// disabled
|
||||
exti_disable(ICE40LP->busy_exti);
|
||||
|
||||
disable_display_dma();
|
||||
display_spi_end_transaction();
|
||||
|
||||
analytics_stopwatch_stop(ANALYTICS_APP_METRIC_DISPLAY_WRITE_TIME);
|
||||
|
||||
s_update_in_progress = false;
|
||||
s_terminate_pending = false;
|
||||
|
||||
mutex_unlock(s_display_update_mutex);
|
||||
|
||||
// Store temporary variable, then NULL, to protect against the case where the compositor calls
|
||||
// into the display driver from the callback, then we NULL out the update complete callback
|
||||
// afterwards.
|
||||
UpdateCompleteCallback update_complete_cb = s_update_complete_callback;
|
||||
s_update_complete_callback = NULL;
|
||||
if (update_complete_cb) {
|
||||
update_complete_cb();
|
||||
}
|
||||
}
|
||||
|
||||
static uint32_t prv_get_next_buffer_idx(uint32_t idx) {
|
||||
return (idx + 1) % ARRAY_LENGTH(s_line_buffer);
|
||||
}
|
||||
|
||||
//! Wait for the FPGA to finish updating the display.
|
||||
//! @returns true if the FPGA is busy on exit
|
||||
static bool prv_wait_busy(void) {
|
||||
// Make sure that semaphore token count is zero before we wait on it and before we check the state
|
||||
// of the FPGA busy line to prevent the semaphore take/give from getting out of sync (not exactly
|
||||
// sure what race condition causes the out of sync bug, but it seems to happen after a while).
|
||||
// See https://pebbletechnology.atlassian.net/browse/PBL-21904
|
||||
xSemaphoreTake(s_fpga_busy, 0);
|
||||
|
||||
if (!display_busy()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// A full frame should take no longer than 33 msec to draw. If we are waiting
|
||||
// longer than that, something is very wrong.
|
||||
TickType_t max_wait_time_ticks = milliseconds_to_ticks(40);
|
||||
bool busy_on_exit = false;
|
||||
if (xSemaphoreTake(s_fpga_busy, max_wait_time_ticks) != pdTRUE) {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Display not coming out of a busy state.");
|
||||
// Nothing needs to be done to recover the FPGA from a bad state. The
|
||||
// falling edge of SCS (to start a new frame) resets the FPGA logic,
|
||||
// clearing the error state.
|
||||
busy_on_exit = true;
|
||||
}
|
||||
return busy_on_exit;
|
||||
}
|
||||
|
||||
static void prv_reprogram_display(void) {
|
||||
// CDONE is expected to go low during reprogramming. Don't pollute the logs
|
||||
// with "CDONE has gone low" messages.
|
||||
analytics_inc(ANALYTICS_DEVICE_METRIC_FPGA_REPROGRAM_COUNT,
|
||||
AnalyticsClient_System);
|
||||
exti_disable(ICE40LP->cdone_exti);
|
||||
display_program(s_fpga_bitstream, sizeof(s_fpga_bitstream));
|
||||
exti_enable(ICE40LP->cdone_exti);
|
||||
}
|
||||
|
||||
static void prv_cdone_low_handler(void *context) {
|
||||
PBL_LOG(LOG_LEVEL_ERROR,
|
||||
"CDONE has gone low. The FPGA has lost its configuration.");
|
||||
|
||||
if (!mutex_lock_with_timeout(s_display_update_mutex, 200)) {
|
||||
PBL_LOG(LOG_LEVEL_DEBUG,
|
||||
"Couldn't lock out display driver to reprogram FPGA.");
|
||||
return;
|
||||
}
|
||||
prv_reprogram_display();
|
||||
PBL_ASSERTN(!display_busy());
|
||||
mutex_unlock(s_display_update_mutex);
|
||||
}
|
||||
|
||||
static void prv_cdone_low_isr(bool *should_context_switch) {
|
||||
system_task_add_callback_from_isr(prv_cdone_low_handler, NULL,
|
||||
should_context_switch);
|
||||
}
|
||||
|
||||
void display_init(void) {
|
||||
if (s_initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
clocksource_MCO1_enable(true);
|
||||
|
||||
s_panic_screen_lockout = false;
|
||||
s_display_update_mutex = mutex_create();
|
||||
s_fpga_busy = xSemaphoreCreateBinary();
|
||||
s_update_in_progress = false;
|
||||
s_terminate_pending = false;
|
||||
s_update_complete_callback = NULL;
|
||||
|
||||
display_start();
|
||||
display_program(s_fpga_bitstream, sizeof(s_fpga_bitstream));
|
||||
// enable the power rails
|
||||
display_power_enable();
|
||||
|
||||
// Set up our INT_N interrupt, aka the "busy line" from the FPGA
|
||||
exti_configure_pin(ICE40LP->busy_exti, ExtiTrigger_Falling, display_interrupt_intn);
|
||||
// Set up an interrupt to detect the FPGA forgetting its configuration due to
|
||||
// e.g. an ESD event.
|
||||
exti_configure_pin(ICE40LP->cdone_exti, ExtiTrigger_Falling, prv_cdone_low_isr);
|
||||
exti_enable(ICE40LP->cdone_exti);
|
||||
|
||||
s_initialized = true;
|
||||
}
|
||||
|
||||
bool display_update_in_progress(void) {
|
||||
// Set this timeout to a relatively large value so that we don't unlock the mutex too early when
|
||||
// the DMA controller that is used by the display is being heavily used by another driver (e.g.
|
||||
// the bluetooth HCI port) and delays the completion of the update or kernel_main is busy with
|
||||
// other tasks (e.g. voice encoding).
|
||||
// (see https://pebbletechnology.atlassian.net/browse/PBL-21923)
|
||||
static const RtcTicks MAX_BUSY_TICKS = 200;
|
||||
|
||||
bool in_progress = !mutex_lock_with_timeout(s_display_update_mutex, 0);
|
||||
if (!in_progress) {
|
||||
mutex_unlock(s_display_update_mutex);
|
||||
} else if (!s_panic_screen_lockout) {
|
||||
if ((rtc_get_ticks() - s_start_ticks) > MAX_BUSY_TICKS) {
|
||||
// Ensure that terminate transfer is not enqueued on kernel_main twice when it is busy to
|
||||
// prevent terminate transfer from being invoked twice
|
||||
// see https://pebbletechnology.atlassian.net/browse/PBL-22084
|
||||
// Read and set the termination flag in a critical section to prevent an interrupt pending
|
||||
// a transfer if these events occur simultaneously
|
||||
bool pend_terminate = false;
|
||||
portENTER_CRITICAL();
|
||||
if (!s_terminate_pending) {
|
||||
s_terminate_pending = true;
|
||||
pend_terminate = true;
|
||||
}
|
||||
portEXIT_CRITICAL();
|
||||
if (pend_terminate) {
|
||||
PROFILER_NODE_STOP(display_transfer);
|
||||
launcher_task_add_callback(prv_terminate_transfer, NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
return in_progress;
|
||||
}
|
||||
|
||||
static void prv_do_display_update(void) {
|
||||
|
||||
if (!mutex_lock_with_timeout(s_display_update_mutex, 0)) {
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Couldn't start update.");
|
||||
return;
|
||||
}
|
||||
if (s_panic_screen_lockout) {
|
||||
mutex_unlock(s_display_update_mutex);
|
||||
return;
|
||||
}
|
||||
|
||||
analytics_stopwatch_start(ANALYTICS_APP_METRIC_DISPLAY_WRITE_TIME, PebbleTask_App);
|
||||
analytics_inc(ANALYTICS_DEVICE_METRIC_DISPLAY_UPDATES_PER_HOUR, AnalyticsClient_System);
|
||||
|
||||
// Communicating with the display, need intn.
|
||||
exti_enable(ICE40LP->busy_exti);
|
||||
|
||||
enable_display_dma_clock();
|
||||
|
||||
// Send the first line...
|
||||
prv_framebuffer_populate_line(0, s_line_buffer[s_buffer_idx]);
|
||||
|
||||
prv_wait_busy();
|
||||
display_spi_begin_transaction();
|
||||
display_start_frame();
|
||||
if (display_busy()) {
|
||||
// If the FPGA was stuck busy before, starting the frame (SCS falling edge)
|
||||
// should get it unstuck. If BUSY is still asserted, the FPGA might be
|
||||
// unprogrammed or malfunctioning. Either way, reprogramming it should get
|
||||
// it back into working order.
|
||||
PBL_LOG(LOG_LEVEL_WARNING,
|
||||
"Reprogramming FPGA because busy is stuck asserted");
|
||||
prv_reprogram_display();
|
||||
bool is_busy = display_busy();
|
||||
#ifdef TARGET_QEMU
|
||||
// Bold light-red text on a black background
|
||||
#define M(s) PBL_LOG(LOG_LEVEL_ALWAYS, "\033[1;91;40m" s "\033[0m")
|
||||
if (is_busy) {
|
||||
M("################################################");
|
||||
M("# THIS IS A QEMU BUILD #");
|
||||
M("################################################");
|
||||
M("# #");
|
||||
M("# The QEMU display driver \033[1;4mdoes not work\033[24m on #");
|
||||
M("# physical hardware. You must build without #");
|
||||
M("# the --qemu switch when flashing a bigboard. #");
|
||||
M("################################################");
|
||||
#undef M
|
||||
psleep(3000);
|
||||
}
|
||||
#endif
|
||||
PBL_ASSERTN(!is_busy);
|
||||
// The SPI clock is disabled by prv_reprogram_display.
|
||||
display_spi_begin_transaction();
|
||||
display_start_frame();
|
||||
}
|
||||
// set line index after waiting for display to free up
|
||||
uint32_t current_idx = s_buffer_idx;
|
||||
s_buffer_idx = prv_get_next_buffer_idx(s_buffer_idx);
|
||||
|
||||
// populate the second line and set the next line to be processed as the third line
|
||||
prv_framebuffer_populate_line(1, s_line_buffer[s_buffer_idx]);
|
||||
s_line_index = 2;
|
||||
|
||||
stop_mode_disable(InhibitorDisplay);
|
||||
|
||||
PROFILER_NODE_START(display_transfer);
|
||||
|
||||
s_update_in_progress = true;
|
||||
s_start_ticks = rtc_get_ticks();
|
||||
// Start the DMA last to prevent possible race conditions caused by unfortunately timed context
|
||||
// switch
|
||||
prv_start_dma_transfer(s_line_buffer[current_idx], DISP_PIXELS);
|
||||
}
|
||||
|
||||
//!
|
||||
//! Starts a redraw of the entire framebuffer to the screen.
|
||||
//!
|
||||
//! Currently does NOT:
|
||||
//! - make use of nrcb due to rotation requirements; instead accesses the framebuffer directly
|
||||
//! - support partial screen updates
|
||||
//!
|
||||
void display_update(NextRowCallback nrcb, UpdateCompleteCallback uccb) {
|
||||
PBL_ASSERTN(uccb != NULL);
|
||||
|
||||
s_update_complete_callback = uccb;
|
||||
prv_do_display_update();
|
||||
}
|
||||
|
||||
static void prv_do_display_update_cb(void *ignored) {
|
||||
prv_do_display_update();
|
||||
}
|
||||
|
||||
void display_clear(void) {
|
||||
// Set compositor buffer to the powered off color (black) and redraw.
|
||||
// Note that compositor owns this framebuffer!
|
||||
memset(compositor_get_framebuffer()->buffer, 0x00, FRAMEBUFFER_SIZE_BYTES);
|
||||
|
||||
// The display ISRs pend events on KernelMain and thus implicitly assume
|
||||
// that the display update operation began on KernelMain. If we are already
|
||||
// running on KernelMain, then just run the display update, otherwise schedule
|
||||
// a callback to run on KernelMain that performs the update
|
||||
if (pebble_task_get_current() == PebbleTask_KernelMain) {
|
||||
prv_do_display_update();
|
||||
} else {
|
||||
launcher_task_add_callback(prv_do_display_update_cb, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
void display_pulse_vcom(void) { }
|
||||
|
||||
//! @return false if there are no more lines to transfer, true if a new line transfer was started
|
||||
static bool prv_write_next_line(bool *should_context_switch) {
|
||||
if (s_line_index == 0 && (!s_terminate_pending)) {
|
||||
s_terminate_pending = true;
|
||||
PROFILER_NODE_STOP(display_transfer);
|
||||
|
||||
PebbleEvent e = {
|
||||
.type = PEBBLE_CALLBACK_EVENT,
|
||||
.callback = {
|
||||
.callback = prv_terminate_transfer,
|
||||
},
|
||||
};
|
||||
*should_context_switch = event_put_isr(&e);
|
||||
return false;
|
||||
}
|
||||
|
||||
prv_start_dma_transfer(s_line_buffer[s_buffer_idx], DISP_PIXELS);
|
||||
|
||||
if (s_line_index < DISP_LINES) {
|
||||
s_buffer_idx = prv_get_next_buffer_idx(s_buffer_idx);
|
||||
prv_framebuffer_populate_line(s_line_index, s_line_buffer[s_buffer_idx]);
|
||||
s_line_index++;
|
||||
} else {
|
||||
// done
|
||||
s_line_index = 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//! When the FPGA leaves the busy state while frame data is being sent, this interrupt will
|
||||
//! signal that the next line can be sent to the display.
|
||||
static void display_interrupt_intn(bool *should_context_switch) {
|
||||
if (s_update_in_progress) {
|
||||
if (!spi_ll_slave_dma_in_progress(ICE40LP->spi_port)) {
|
||||
// DMA transfer is complete
|
||||
if (prv_write_next_line(should_context_switch)) {
|
||||
stop_mode_disable(InhibitorDisplay);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Only release the semaphore after the end of an update
|
||||
signed portBASE_TYPE was_higher_priority_task_woken = pdFALSE;
|
||||
xSemaphoreGiveFromISR(s_fpga_busy, &was_higher_priority_task_woken);
|
||||
*should_context_switch = (*should_context_switch) ||
|
||||
(was_higher_priority_task_woken != pdFALSE);
|
||||
}
|
||||
}
|
||||
|
||||
// DMA
|
||||
//////////////////
|
||||
|
||||
//! This interrupt fires when the transfer of a line has completed.
|
||||
static bool prv_write_dma_irq_handler(const SPISlavePort *request, void *context) {
|
||||
PROFILER_NODE_START(framebuffer_dma);
|
||||
bool should_context_switch = false;
|
||||
if (display_busy() || !prv_write_next_line(&should_context_switch)) {
|
||||
stop_mode_enable(InhibitorDisplay);
|
||||
}
|
||||
PROFILER_NODE_STOP(framebuffer_dma);
|
||||
return should_context_switch;
|
||||
}
|
||||
|
||||
static void prv_start_dma_transfer(uint8_t *addr, uint32_t length) {
|
||||
spi_ll_slave_write_dma_start(ICE40LP->spi_port, addr, length, prv_write_dma_irq_handler, NULL);
|
||||
}
|
||||
|
||||
void display_show_panic_screen(uint32_t error_code) {
|
||||
// Lock out display driver from performing further updates
|
||||
if (!mutex_lock_with_timeout(s_display_update_mutex, 200)) {
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Couldn't lock out display driver.");
|
||||
return;
|
||||
}
|
||||
s_panic_screen_lockout = true;
|
||||
|
||||
exti_disable(ICE40LP->cdone_exti);
|
||||
// Work around an issue which some boards exhibit where there is about a 50%
|
||||
// probability that FPGA malfunctions and draw-scene command doesn't work.
|
||||
// This can be detected in software as the FPGA asserts BUSY indefinitely.
|
||||
int retries;
|
||||
for (retries = 0; retries <= 20; ++retries) {
|
||||
if (!display_switch_to_bootloader_mode()) {
|
||||
// Probably an unconfigured FPGA. Nothing we can do about that.
|
||||
break;
|
||||
}
|
||||
if (boot_display_show_error_code(error_code)) {
|
||||
// Success!
|
||||
if (retries > 0) {
|
||||
PBL_LOG(LOG_LEVEL_WARNING, "Took %d retries to display panic screen.",
|
||||
retries);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
exti_enable(ICE40LP->cdone_exti);
|
||||
|
||||
mutex_unlock(s_display_update_mutex);
|
||||
}
|
||||
|
||||
void display_show_splash_screen(void) {
|
||||
// Assumes that the FPGA is already in bootloader mode but the SPI peripheral
|
||||
// and GPIOs are not yet configured; exactly the state that the system is
|
||||
// expected to be in before display_init() is called.
|
||||
display_start();
|
||||
display_spi_configure_default();
|
||||
boot_display_show_boot_splash();
|
||||
}
|
||||
|
||||
void display_set_offset(GPoint offset) {
|
||||
s_disp_offset = offset;
|
||||
}
|
||||
|
||||
GPoint display_get_offset(void) {
|
||||
return s_disp_offset;
|
||||
}
|
||||
|
||||
void analytics_external_collect_display_offset(void) {
|
||||
analytics_set(ANALYTICS_DEVICE_METRIC_DISPLAY_OFFSET_X, s_disp_offset.x, AnalyticsClient_System);
|
||||
analytics_set(ANALYTICS_DEVICE_METRIC_DISPLAY_OFFSET_Y, s_disp_offset.y, AnalyticsClient_System);
|
||||
}
|
35
src/fw/drivers/display/ice40lp/ice40lp.h
Normal file
35
src/fw/drivers/display/ice40lp/ice40lp.h
Normal file
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "../display.h"
|
||||
|
||||
typedef enum {
|
||||
DISP_COLOR_BLACK = 0,
|
||||
DISP_COLOR_WHITE,
|
||||
DISP_COLOR_RED,
|
||||
DISP_COLOR_GREEN,
|
||||
DISP_COLOR_BLUE,
|
||||
DISP_COLOR_MAX
|
||||
} DispColor;
|
||||
|
||||
static const uint8_t s_display_colors[DISP_COLOR_MAX] = { 0x00, 0xff, 0xc0, 0x30, 0x0c };
|
||||
|
||||
void display_fill_color(uint8_t color_value);
|
||||
void display_fill_stripes();
|
43
src/fw/drivers/display/ice40lp/ice40lp_definitions.h
Normal file
43
src/fw/drivers/display/ice40lp/ice40lp_definitions.h
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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "board/board.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
// TODO: Calling this ICE40LP kind of sucks, but I can't think of anything better without doing a
|
||||
// whole big display system refactor, so we're keeping it as ICE40LP.
|
||||
typedef struct ICE40LPDeviceState {
|
||||
} ICE40LPDeviceState;
|
||||
|
||||
typedef const struct ICE40LPDevice {
|
||||
ICE40LPDeviceState *state;
|
||||
|
||||
SPISlavePort *spi_port;
|
||||
uint32_t base_spi_frequency;
|
||||
uint32_t fast_spi_frequency;
|
||||
|
||||
const OutputConfig creset;
|
||||
const InputConfig cdone;
|
||||
const InputConfig busy;
|
||||
const ExtiConfig cdone_exti;
|
||||
const ExtiConfig busy_exti;
|
||||
|
||||
bool use_6v6_rail;
|
||||
} ICE40LPDevice;
|
241
src/fw/drivers/display/ice40lp/ice40lp_internal.c
Normal file
241
src/fw/drivers/display/ice40lp/ice40lp_internal.c
Normal file
|
@ -0,0 +1,241 @@
|
|||
/*
|
||||
* 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/ice40lp/ice40lp_internal.h"
|
||||
|
||||
#include "board/board.h"
|
||||
#include "debug/power_tracking.h"
|
||||
#include "drivers/display/ice40lp/ice40lp_definitions.h"
|
||||
#include "drivers/periph_config.h"
|
||||
#include "drivers/gpio.h"
|
||||
#include "drivers/spi.h"
|
||||
#include "drivers/exti.h"
|
||||
#include "drivers/pmic.h"
|
||||
#include "system/logging.h"
|
||||
#include "system/passert.h"
|
||||
#include "kernel/util/delay.h"
|
||||
#include "util/size.h"
|
||||
#include "util/sle.h"
|
||||
#include "kernel/util/sleep.h"
|
||||
#include "util/units.h"
|
||||
|
||||
#define STM32F4_COMPATIBLE
|
||||
#define STM32F7_COMPATIBLE
|
||||
#include <mcu.h>
|
||||
|
||||
#include <string.h>
|
||||
|
||||
bool display_busy(void) {
|
||||
return gpio_input_read(&ICE40LP->busy);
|
||||
}
|
||||
|
||||
void display_spi_begin_transaction(void) {
|
||||
spi_ll_slave_acquire(ICE40LP->spi_port);
|
||||
spi_ll_slave_scs_assert(ICE40LP->spi_port);
|
||||
power_tracking_start(PowerSystemMcuSpi6);
|
||||
}
|
||||
|
||||
void display_spi_end_transaction(void) {
|
||||
spi_ll_slave_scs_deassert(ICE40LP->spi_port);
|
||||
spi_ll_slave_release(ICE40LP->spi_port);
|
||||
power_tracking_stop(PowerSystemMcuSpi6);
|
||||
}
|
||||
|
||||
// Temporary code to support prv_do_display_update() logic that attempts to use the
|
||||
// bootloader error display
|
||||
void display_spi_configure_default(void) {
|
||||
spi_slave_set_frequency(ICE40LP->spi_port, ICE40LP->base_spi_frequency);
|
||||
}
|
||||
|
||||
void display_start() {
|
||||
periph_config_acquire_lock();
|
||||
|
||||
gpio_output_init(&ICE40LP->creset, GPIO_OType_OD, GPIO_Speed_25MHz);
|
||||
gpio_input_init(&ICE40LP->cdone);
|
||||
gpio_input_init(&ICE40LP->busy);
|
||||
|
||||
periph_config_release_lock();
|
||||
}
|
||||
|
||||
static bool prv_spin_until_creset_is(bool level) {
|
||||
int timeout_us = 500 * 1000;
|
||||
InputConfig creset_input = {
|
||||
.gpio = ICE40LP->creset.gpio,
|
||||
.gpio_pin = ICE40LP->creset.gpio_pin,
|
||||
};
|
||||
while (timeout_us > 0) {
|
||||
if (gpio_input_read(&creset_input) == level) return true;
|
||||
delay_us(100);
|
||||
timeout_us -= 100;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool prv_wait_programmed(void) {
|
||||
// The datasheet lists the typical NVCM configuration time as 56 ms.
|
||||
// Something is wrong if it takes more than twice that time.
|
||||
int timeout = 100 * 10; // * 100 microseconds
|
||||
while (!gpio_input_read(&ICE40LP->cdone)) {
|
||||
if (timeout-- == 0) {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "FPGA CDONE timeout expired!");
|
||||
return false;
|
||||
}
|
||||
delay_us(100);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool prv_try_program(const uint8_t *fpga_bitstream,
|
||||
uint32_t bitstream_size) {
|
||||
display_spi_configure_default();
|
||||
spi_ll_slave_acquire(ICE40LP->spi_port);
|
||||
spi_ll_slave_scs_assert(ICE40LP->spi_port);
|
||||
|
||||
gpio_output_set(&ICE40LP->creset, false); // CRESET LOW
|
||||
|
||||
#if !defined(TARGET_QEMU)
|
||||
// Wait until we succeed in pulling CRESET down against the external pull-up
|
||||
// and other external circuitry which is fighting against us.
|
||||
PBL_ASSERT(prv_spin_until_creset_is(false), "CRESET not low during reset");
|
||||
|
||||
// CRESET needs to be low for 200 ns to actually reset the FPGA.
|
||||
delay_us(10);
|
||||
#endif
|
||||
|
||||
gpio_output_set(&ICE40LP->creset, true); // CRESET -> HIGH
|
||||
|
||||
#if !defined(TARGET_QEMU)
|
||||
PBL_ASSERT(!gpio_input_read(&ICE40LP->cdone), "CDONE not low after reset");
|
||||
|
||||
// Wait until CRESET goes high again. It's open-drain (and someone with
|
||||
// tweezers might be grounding it) so it may take some time.
|
||||
PBL_ASSERT(prv_spin_until_creset_is(true), "CRESET not high after reset");
|
||||
|
||||
// iCE40 Programming and Configuration manual specifies that the iCE40 needs
|
||||
// 800 µs for "housekeeping" after reset is released before it is ready to
|
||||
// receive its configuration.
|
||||
delay_us(1000);
|
||||
#endif
|
||||
|
||||
|
||||
SLEDecodeContext sle_ctx;
|
||||
sle_decode_init(&sle_ctx, fpga_bitstream);
|
||||
uint8_t byte;
|
||||
while (sle_decode(&sle_ctx, &byte)) {
|
||||
spi_ll_slave_write(ICE40LP->spi_port, byte);
|
||||
}
|
||||
|
||||
// Set SCS high so that we don't process any of these clocks as commands.
|
||||
spi_ll_slave_scs_deassert(ICE40LP->spi_port);
|
||||
|
||||
static const uint8_t spi_zeros[9] = {0, 0, 0, 0, 0, 0, 0, 0, 0};
|
||||
|
||||
// 49+ SCLK cycles to tell FPGA we're done configuration.
|
||||
spi_ll_slave_burst_write(ICE40LP->spi_port, spi_zeros, ARRAY_LENGTH(spi_zeros));
|
||||
|
||||
spi_ll_slave_release(ICE40LP->spi_port);
|
||||
|
||||
// PBL-19516
|
||||
#if !defined(TARGET_QEMU)
|
||||
prv_wait_programmed();
|
||||
if (!gpio_input_read(&ICE40LP->cdone)) {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "CDONE not high after programming");
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
void display_program(const uint8_t *fpga_bitstream, uint32_t bitstream_size) {
|
||||
periph_config_acquire_lock();
|
||||
|
||||
int attempt = 1;
|
||||
while (1) {
|
||||
if (prv_try_program(fpga_bitstream, bitstream_size)) {
|
||||
break;
|
||||
}
|
||||
if (attempt++ >= 3) {
|
||||
PBL_CROAK("Too many failed FPGA programming attempts");
|
||||
}
|
||||
}
|
||||
|
||||
spi_slave_set_frequency(ICE40LP->spi_port, ICE40LP->fast_spi_frequency);
|
||||
|
||||
periph_config_release_lock();
|
||||
}
|
||||
|
||||
bool display_switch_to_bootloader_mode(void) {
|
||||
// Reset the FPGA and wait for it to program itself via NVCM.
|
||||
// NVCM configuration is initiated by pulling CRESET high while SCS is high.
|
||||
periph_config_acquire_lock();
|
||||
// SCS will already be high here.
|
||||
|
||||
// CRESET needs to be low for at least 200 ns
|
||||
gpio_output_set(&ICE40LP->creset, false);
|
||||
delay_us(1000);
|
||||
gpio_output_set(&ICE40LP->creset, true);
|
||||
bool success = prv_wait_programmed();
|
||||
if (success) {
|
||||
display_spi_configure_default();
|
||||
}
|
||||
periph_config_release_lock();
|
||||
return success;
|
||||
}
|
||||
|
||||
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.
|
||||
psleep(2);
|
||||
|
||||
if (ICE40LP->use_6v6_rail) {
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Enabling 6v6 (Display VDDC)");
|
||||
set_6V6_power_state(true);
|
||||
|
||||
psleep(2);
|
||||
}
|
||||
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Enabling 4v5 (Display VDDP)");
|
||||
set_4V5_power_state(true);
|
||||
}
|
||||
|
||||
void display_power_disable(void) {
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Disabling 4v5 (Display VDDP)");
|
||||
set_4V5_power_state(false);
|
||||
|
||||
psleep(2);
|
||||
|
||||
if (ICE40LP->use_6v6_rail) {
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Disabling 6v6 (Display VDDC)");
|
||||
set_6V6_power_state(false);
|
||||
|
||||
psleep(2);
|
||||
}
|
||||
}
|
||||
|
||||
//!
|
||||
//! Starts a frame.
|
||||
//!
|
||||
void display_start_frame(void) {
|
||||
// The iCE40UL framebuffer FPGA (S4) configuration requires a short delay
|
||||
// after asserting SCS before it is ready for a command.
|
||||
delay_us(5);
|
||||
|
||||
spi_ll_slave_write(ICE40LP->spi_port, CMD_FRAME_BEGIN);
|
||||
|
||||
// Make sure command has been transferred.
|
||||
spi_slave_wait_until_idle_blocking(ICE40LP->spi_port);
|
||||
}
|
45
src/fw/drivers/display/ice40lp/ice40lp_internal.h
Normal file
45
src/fw/drivers/display/ice40lp/ice40lp_internal.h
Normal file
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "drivers/gpio.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef enum {
|
||||
CMD_FRAME_BEGIN = 0x5,
|
||||
} DisplayCmd;
|
||||
|
||||
|
||||
|
||||
void display_spi_begin_transaction(void);
|
||||
void display_spi_end_transaction(void);
|
||||
void display_spi_configure_default(void);
|
||||
bool display_busy(void);
|
||||
void display_start(void);
|
||||
void display_program(const uint8_t *fpga_bitstream, uint32_t bitstream_size);
|
||||
void display_send_clocks(uint32_t count);
|
||||
void display_start_frame(void);
|
||||
void display_write_byte(uint8_t d);
|
||||
void display_send_cmd(DisplayCmd cmd);
|
||||
void display_power_enable(void);
|
||||
void display_power_disable(void);
|
||||
|
||||
//! Reset the FPGA into bootloader mode.
|
||||
//!
|
||||
//! @return true if successful, false if the NVCM is not programmed.
|
||||
bool display_switch_to_bootloader_mode(void);
|
136
src/fw/drivers/display/ice40lp/snowy_boot.c
Normal file
136
src/fw/drivers/display/ice40lp/snowy_boot.c
Normal file
|
@ -0,0 +1,136 @@
|
|||
/*
|
||||
* 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/ice40lp/snowy_boot.h"
|
||||
#include "drivers/display/ice40lp/ice40lp_definitions.h"
|
||||
#include "drivers/display/ice40lp/ice40lp_internal.h"
|
||||
|
||||
#include "board/board.h"
|
||||
#include "drivers/gpio.h"
|
||||
#include "drivers/periph_config.h"
|
||||
#include "drivers/pmic.h"
|
||||
#include "drivers/spi.h"
|
||||
#include "system/logging.h"
|
||||
#include "system/passert.h"
|
||||
#include "kernel/util/delay.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.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 SCENE_BLACK (0)
|
||||
#define SCENE_SPLASH (1)
|
||||
#define SCENE_UPDATE (2)
|
||||
#define SCENE_ERROR (3)
|
||||
|
||||
#define UPDATE_PROGRESS_MAX (93)
|
||||
|
||||
static void prv_start_command(uint8_t cmd) {
|
||||
display_spi_begin_transaction();
|
||||
spi_ll_slave_write(ICE40LP->spi_port, cmd);
|
||||
}
|
||||
|
||||
static void prv_send_command_arg(uint8_t arg) {
|
||||
spi_ll_slave_write(ICE40LP->spi_port, arg);
|
||||
}
|
||||
|
||||
static void prv_end_command(void) {
|
||||
display_spi_end_transaction();
|
||||
}
|
||||
|
||||
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) {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Display busy-wait timeout expired!");
|
||||
return false;
|
||||
}
|
||||
delay_us(100);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void prv_screen_on(void) {
|
||||
prv_start_command(CMD_DISPLAY_ON);
|
||||
prv_end_command();
|
||||
}
|
||||
|
||||
static void prv_screen_off(void) {
|
||||
prv_start_command(CMD_DISPLAY_OFF);
|
||||
prv_end_command();
|
||||
}
|
||||
|
||||
void prv_draw_scene(uint8_t scene) {
|
||||
prv_start_command(CMD_DRAW_SCENE);
|
||||
prv_send_command_arg(scene);
|
||||
prv_end_command();
|
||||
}
|
||||
void prv_set_parameter(uint32_t param) {
|
||||
prv_start_command(CMD_SET_PARAMETER);
|
||||
// Send in little-endian byte order
|
||||
prv_send_command_arg(param & 0xff);
|
||||
prv_send_command_arg((param >> 8) & 0xff);
|
||||
prv_send_command_arg((param >> 16) & 0xff);
|
||||
prv_send_command_arg((param >> 24) & 0xff);
|
||||
prv_end_command();
|
||||
}
|
||||
|
||||
void boot_display_show_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 boot_display_show_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);
|
||||
}
|
||||
}
|
||||
|
||||
bool boot_display_show_error_code(uint32_t error_code) {
|
||||
prv_set_parameter(error_code);
|
||||
prv_draw_scene(SCENE_ERROR);
|
||||
if (prv_wait_busy()) {
|
||||
prv_screen_on();
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void boot_display_screen_off(void) {
|
||||
prv_screen_off();
|
||||
prv_draw_scene(SCENE_BLACK);
|
||||
prv_wait_busy();
|
||||
}
|
38
src/fw/drivers/display/ice40lp/snowy_boot.h
Normal file
38
src/fw/drivers/display/ice40lp/snowy_boot.h
Normal file
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
//! Functions for controlling the display FPGA in bootloader mode, such as
|
||||
//! early in the boot process before it is reconfigured in framebuffer mode.
|
||||
//!
|
||||
//! These functions all assume that all necessary GPIOs and the SPI peripheral
|
||||
//! are configured correctly, and that the bootloader is already in bootloader
|
||||
//! mode.
|
||||
|
||||
//! Display the Pebble logo and turn on the screen.
|
||||
void boot_display_show_boot_splash(void);
|
||||
|
||||
//! Show the Pebble logo with a progress bar.
|
||||
void boot_display_show_firmware_update_progress(
|
||||
uint32_t numerator, uint32_t denominator);
|
||||
|
||||
//! Show a sad-watch error.
|
||||
bool boot_display_show_error_code(uint32_t error_code);
|
||||
|
||||
//! Black out the screen and prepare for power down.
|
||||
void boot_display_screen_off(void);
|
396
src/fw/drivers/display/sharp_ls013b7dh01/sharp_ls013b7dh01.c
Normal file
396
src/fw/drivers/display/sharp_ls013b7dh01/sharp_ls013b7dh01.c
Normal file
|
@ -0,0 +1,396 @@
|
|||
/*
|
||||
* 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 "sharp_ls013b7dh01.h"
|
||||
|
||||
#include "applib/graphics/gtypes.h"
|
||||
#include "board/board.h"
|
||||
#include "debug/power_tracking.h"
|
||||
#include "drivers/dma.h"
|
||||
#include "drivers/gpio.h"
|
||||
#include "drivers/periph_config.h"
|
||||
#include "drivers/spi.h"
|
||||
#include "kernel/util/sleep.h"
|
||||
#include "kernel/util/stop.h"
|
||||
#include "os/tick.h"
|
||||
#include "services/common/analytics/analytics.h"
|
||||
#include "system/logging.h"
|
||||
#include "system/passert.h"
|
||||
#include "util/bitset.h"
|
||||
#include "util/net.h"
|
||||
#include "util/reverse.h"
|
||||
#include "util/units.h"
|
||||
|
||||
|
||||
#define STM32F2_COMPATIBLE
|
||||
#define STM32F4_COMPATIBLE
|
||||
#include <mcu.h>
|
||||
|
||||
#include "misc.h"
|
||||
|
||||
#include "FreeRTOS.h"
|
||||
#include "task.h"
|
||||
#include "semphr.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
// GPIO constants
|
||||
static const unsigned int DISP_MODE_STATIC = 0x00;
|
||||
static const unsigned int DISP_MODE_WRITE = 0x80;
|
||||
static const unsigned int DISP_MODE_CLEAR = 0x20;
|
||||
|
||||
// We want the SPI clock to run at 2MHz by default
|
||||
static uint32_t s_spi_clock_hz;
|
||||
|
||||
// DMA constants
|
||||
static DMA_Stream_TypeDef* DISPLAY_DMA_STREAM = DMA1_Stream4;
|
||||
static const uint32_t DISPLAY_DMA_CLOCK = RCC_AHB1Periph_DMA1;
|
||||
|
||||
static bool s_initialized = false;
|
||||
|
||||
// DMA state
|
||||
static DisplayContext s_display_context;
|
||||
static uint32_t s_dma_line_buffer[DISP_DMA_BUFFER_SIZE_WORDS];
|
||||
|
||||
static SemaphoreHandle_t s_dma_update_in_progress_semaphore;
|
||||
|
||||
static void prv_display_write_byte(uint8_t d);
|
||||
static void prv_display_context_init(DisplayContext* context);
|
||||
static void prv_setup_dma_transfer(uint8_t* framebuffer_addr, int framebuffer_size);
|
||||
static bool prv_do_dma_update(void);
|
||||
|
||||
|
||||
static void prv_enable_display_spi_clock() {
|
||||
periph_config_enable(BOARD_CONFIG_DISPLAY.spi, BOARD_CONFIG_DISPLAY.spi_clk);
|
||||
power_tracking_start(PowerSystemMcuSpi2);
|
||||
}
|
||||
|
||||
static void prv_disable_display_spi_clock() {
|
||||
periph_config_disable(BOARD_CONFIG_DISPLAY.spi, BOARD_CONFIG_DISPLAY.spi_clk);
|
||||
power_tracking_stop(PowerSystemMcuSpi2);
|
||||
}
|
||||
|
||||
static void prv_enable_chip_select(void) {
|
||||
gpio_output_set(&BOARD_CONFIG_DISPLAY.cs, true);
|
||||
// setup time > 3us
|
||||
// this produces a setup time of ~7us
|
||||
for (volatile int i = 0; i < 32; i++);
|
||||
}
|
||||
|
||||
static void prv_disable_chip_select(void) {
|
||||
// delay while last byte is emitted by the SPI peripheral (~7us)
|
||||
for (volatile int i = 0; i < 48; i++);
|
||||
gpio_output_set(&BOARD_CONFIG_DISPLAY.cs, false);
|
||||
// hold time > 1us
|
||||
// this produces a delay of ~3.5us
|
||||
for (volatile int i = 0; i < 16; i++);
|
||||
}
|
||||
|
||||
|
||||
static void prv_display_start(void) {
|
||||
periph_config_acquire_lock();
|
||||
|
||||
gpio_af_init(&BOARD_CONFIG_DISPLAY.clk, GPIO_OType_PP, GPIO_Speed_50MHz, GPIO_PuPd_NOPULL);
|
||||
gpio_af_init(&BOARD_CONFIG_DISPLAY.mosi, GPIO_OType_PP, GPIO_Speed_50MHz, GPIO_PuPd_NOPULL);
|
||||
gpio_output_init(&BOARD_CONFIG_DISPLAY.cs, GPIO_OType_PP, GPIO_Speed_50MHz);
|
||||
gpio_output_init(&BOARD_CONFIG_DISPLAY.on_ctrl,
|
||||
BOARD_CONFIG_DISPLAY.on_ctrl_otype,
|
||||
GPIO_Speed_50MHz);
|
||||
|
||||
if (BOARD_CONFIG.power_5v0_options != OptionNotPresent) {
|
||||
GPIOOType_TypeDef otype = (BOARD_CONFIG.power_5v0_options == OptionActiveLowOpenDrain)
|
||||
? GPIO_OType_OD : GPIO_OType_PP;
|
||||
gpio_output_init(&BOARD_CONFIG.power_ctl_5v0, otype, GPIO_Speed_50MHz);
|
||||
}
|
||||
|
||||
if (BOARD_CONFIG.lcd_com.gpio) {
|
||||
gpio_output_init(&BOARD_CONFIG.lcd_com, GPIO_OType_PP, GPIO_Speed_50MHz);
|
||||
}
|
||||
|
||||
// Set up a SPI bus on SPI2
|
||||
SPI_InitTypeDef spi_cfg;
|
||||
SPI_I2S_DeInit(BOARD_CONFIG_DISPLAY.spi);
|
||||
SPI_StructInit(&spi_cfg);
|
||||
spi_cfg.SPI_Direction = SPI_Direction_1Line_Tx; // Write-only SPI
|
||||
spi_cfg.SPI_Mode = SPI_Mode_Master;
|
||||
spi_cfg.SPI_DataSize = SPI_DataSize_8b;
|
||||
spi_cfg.SPI_CPOL = SPI_CPOL_Low;
|
||||
spi_cfg.SPI_CPHA = SPI_CPHA_1Edge;
|
||||
spi_cfg.SPI_NSS = SPI_NSS_Soft;
|
||||
spi_cfg.SPI_BaudRatePrescaler =
|
||||
spi_find_prescaler(s_spi_clock_hz, BOARD_CONFIG_DISPLAY.spi_clk_periph);
|
||||
spi_cfg.SPI_FirstBit = SPI_FirstBit_MSB;
|
||||
SPI_Init(BOARD_CONFIG_DISPLAY.spi, &spi_cfg);
|
||||
|
||||
gpio_use(BOARD_CONFIG_DISPLAY.spi_gpio);
|
||||
SPI_Cmd(BOARD_CONFIG_DISPLAY.spi, ENABLE);
|
||||
gpio_release(BOARD_CONFIG_DISPLAY.spi_gpio);
|
||||
|
||||
if (BOARD_CONFIG.power_5v0_options != OptionNotPresent) {
|
||||
// +5V to 5V_EN pin
|
||||
gpio_output_set(&BOARD_CONFIG.power_ctl_5v0, true);
|
||||
}
|
||||
|
||||
// +5V to LCD_DISP pin (Set this pin low to turn off the display)
|
||||
gpio_output_set(&BOARD_CONFIG_DISPLAY.on_ctrl, true);
|
||||
|
||||
periph_config_release_lock();
|
||||
}
|
||||
|
||||
uint32_t display_baud_rate_change(uint32_t new_frequency_hz) {
|
||||
// Take the semaphore so that we can be sure that we are not interrupting a transfer
|
||||
xSemaphoreTake(s_dma_update_in_progress_semaphore, portMAX_DELAY);
|
||||
|
||||
uint32_t old_spi_clock_hz = s_spi_clock_hz;
|
||||
s_spi_clock_hz = new_frequency_hz;
|
||||
prv_enable_display_spi_clock();
|
||||
prv_display_start();
|
||||
prv_disable_display_spi_clock();
|
||||
|
||||
xSemaphoreGive(s_dma_update_in_progress_semaphore);
|
||||
return old_spi_clock_hz;
|
||||
}
|
||||
|
||||
void display_init(void) {
|
||||
if (s_initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
s_spi_clock_hz = MHZ_TO_HZ(2);
|
||||
|
||||
prv_display_context_init(&s_display_context);
|
||||
|
||||
vSemaphoreCreateBinary(s_dma_update_in_progress_semaphore);
|
||||
|
||||
dma_request_init(SHARP_SPI_TX_DMA);
|
||||
|
||||
prv_enable_display_spi_clock();
|
||||
|
||||
prv_display_start();
|
||||
|
||||
prv_disable_display_spi_clock();
|
||||
s_initialized = true;
|
||||
}
|
||||
|
||||
static void prv_display_context_init(DisplayContext* context) {
|
||||
context->state = DISPLAY_STATE_IDLE;
|
||||
context->get_next_row = NULL;
|
||||
context->complete = NULL;
|
||||
}
|
||||
|
||||
// Clear-all mode is entered by sending 0x04 to the panel
|
||||
void display_clear(void) {
|
||||
prv_enable_display_spi_clock();
|
||||
prv_enable_chip_select();
|
||||
|
||||
prv_display_write_byte(DISP_MODE_CLEAR);
|
||||
prv_display_write_byte(0x00);
|
||||
|
||||
prv_disable_chip_select();
|
||||
prv_disable_display_spi_clock();
|
||||
}
|
||||
|
||||
bool display_update_in_progress(void) {
|
||||
if (xSemaphoreTake(s_dma_update_in_progress_semaphore, 0) == pdPASS) {
|
||||
xSemaphoreGive(s_dma_update_in_progress_semaphore);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void display_update(NextRowCallback nrcb, UpdateCompleteCallback uccb) {
|
||||
PBL_ASSERTN(nrcb != NULL);
|
||||
PBL_ASSERTN(uccb != NULL);
|
||||
stop_mode_disable(InhibitorDisplay);
|
||||
xSemaphoreTake(s_dma_update_in_progress_semaphore, portMAX_DELAY);
|
||||
analytics_stopwatch_start(ANALYTICS_APP_METRIC_DISPLAY_WRITE_TIME, AnalyticsClient_App);
|
||||
analytics_inc(ANALYTICS_DEVICE_METRIC_DISPLAY_UPDATES_PER_HOUR, AnalyticsClient_System);
|
||||
|
||||
prv_enable_display_spi_clock();
|
||||
power_tracking_start(PowerSystemMcuDma1);
|
||||
SPI_I2S_DMACmd(BOARD_CONFIG_DISPLAY.spi, SPI_I2S_DMAReq_Tx, ENABLE);
|
||||
|
||||
prv_display_context_init(&s_display_context);
|
||||
s_display_context.get_next_row = nrcb;
|
||||
s_display_context.complete = uccb;
|
||||
|
||||
prv_do_dma_update();
|
||||
|
||||
// Block while we wait for the update to finish.
|
||||
TickType_t ticks = milliseconds_to_ticks(4000); // DMA should be fast
|
||||
if (xSemaphoreTake(s_dma_update_in_progress_semaphore, ticks) != pdTRUE) {
|
||||
// something went wrong, gather some debug info & reset
|
||||
int dma_status = DMA_GetITStatus(DISPLAY_DMA_STREAM, DMA_IT_TCIF4);
|
||||
uint32_t spi_clock_status = (RCC->APB1ENR & BOARD_CONFIG_DISPLAY.spi_clk);
|
||||
uint32_t dma_clock_status = (RCC->AHB1ENR & DISPLAY_DMA_CLOCK);
|
||||
uint32_t pri_mask = __get_PRIMASK();
|
||||
PBL_CROAK("display DMA failed: 0x%" PRIx32 " %d 0x%lx 0x%lx", pri_mask,
|
||||
dma_status, spi_clock_status, dma_clock_status);
|
||||
}
|
||||
|
||||
power_tracking_stop(PowerSystemMcuDma1);
|
||||
prv_disable_display_spi_clock();
|
||||
|
||||
xSemaphoreGive(s_dma_update_in_progress_semaphore);
|
||||
stop_mode_enable(InhibitorDisplay);
|
||||
analytics_stopwatch_stop(ANALYTICS_APP_METRIC_DISPLAY_WRITE_TIME);
|
||||
}
|
||||
|
||||
// Static mode is entered by sending 0x00 to the panel
|
||||
static void prv_display_enter_static(void) {
|
||||
prv_enable_chip_select();
|
||||
|
||||
prv_display_write_byte(DISP_MODE_STATIC);
|
||||
prv_display_write_byte(0x00);
|
||||
prv_display_write_byte(0x00);
|
||||
|
||||
prv_disable_chip_select();
|
||||
}
|
||||
|
||||
void display_pulse_vcom(void) {
|
||||
PBL_ASSERTN(BOARD_CONFIG.lcd_com.gpio != 0);
|
||||
gpio_output_set(&BOARD_CONFIG.lcd_com, true);
|
||||
// the spec requires at least 1us; this provides ~2 so should be safe
|
||||
for (volatile int i = 0; i < 8; i++);
|
||||
gpio_output_set(&BOARD_CONFIG.lcd_com, false);
|
||||
}
|
||||
|
||||
|
||||
static bool prv_dma_handler(DMARequest *request, void *context) {
|
||||
return prv_do_dma_update();
|
||||
}
|
||||
|
||||
#if DISPLAY_ORIENTATION_ROTATED_180
|
||||
//!
|
||||
//! memcpy the src buffer to dst and reverse the bits
|
||||
//! to match the display order
|
||||
//!
|
||||
static void prv_memcpy_reverse_bytes(uint8_t* dst, uint8_t* src, int bytes) {
|
||||
// Skip the mode selection and column address bytes
|
||||
dst+=2;
|
||||
while (bytes--) {
|
||||
*dst++ = reverse_byte(*src++);
|
||||
}
|
||||
}
|
||||
#else
|
||||
//!
|
||||
//! memcpy the src buffer to dst backwards (i.e. the highest src byte
|
||||
//! is the lowest byte in dst.
|
||||
//!
|
||||
static void prv_memcpy_backwards(uint32_t* dst, uint32_t* src, int length) {
|
||||
dst += length - 1;
|
||||
while (length--) {
|
||||
*dst-- = ntohl(*src++);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
//!
|
||||
//! Write a single byte synchronously to the display. Use this
|
||||
//! sparingly, as it will tie up the micro duing the write.
|
||||
//!
|
||||
static void prv_display_write_byte(uint8_t d) {
|
||||
// Block until the tx buffer is empty
|
||||
SPI_I2S_SendData(BOARD_CONFIG_DISPLAY.spi, d);
|
||||
while (!SPI_I2S_GetFlagStatus(BOARD_CONFIG_DISPLAY.spi, SPI_I2S_FLAG_TXE)) {}
|
||||
}
|
||||
|
||||
static bool prv_do_dma_update(void) {
|
||||
DisplayRow r;
|
||||
|
||||
PBL_ASSERTN(s_display_context.get_next_row != NULL);
|
||||
bool is_end_of_buffer = !s_display_context.get_next_row(&r);
|
||||
|
||||
switch (s_display_context.state) {
|
||||
case DISPLAY_STATE_IDLE:
|
||||
{
|
||||
if (is_end_of_buffer) {
|
||||
// If nothing has been modified, bail out early
|
||||
return false;
|
||||
}
|
||||
|
||||
// Enable display slave select
|
||||
prv_enable_chip_select();
|
||||
|
||||
s_display_context.state = DISPLAY_STATE_WRITING;
|
||||
|
||||
#if DISPLAY_ORIENTATION_ROTATED_180
|
||||
prv_memcpy_reverse_bytes((uint8_t*)s_dma_line_buffer, r.data, DISP_LINE_BYTES);
|
||||
s_dma_line_buffer[0] &= ~(0xffff);
|
||||
s_dma_line_buffer[0] |= (DISP_MODE_WRITE | reverse_byte(r.address + 1) << 8);
|
||||
#else
|
||||
prv_memcpy_backwards(s_dma_line_buffer, (uint32_t*)r.data, DISP_LINE_WORDS);
|
||||
s_dma_line_buffer[0] &= ~(0xffff);
|
||||
s_dma_line_buffer[0] |= (DISP_MODE_WRITE | reverse_byte(167 - r.address + 1) << 8);
|
||||
#endif
|
||||
prv_setup_dma_transfer(((uint8_t*) s_dma_line_buffer), DISP_DMA_BUFFER_SIZE_BYTES);
|
||||
|
||||
break;
|
||||
}
|
||||
case DISPLAY_STATE_WRITING:
|
||||
{
|
||||
if (is_end_of_buffer) {
|
||||
prv_display_write_byte(0x00);
|
||||
|
||||
// Disable display slave select
|
||||
prv_disable_chip_select();
|
||||
|
||||
prv_display_enter_static();
|
||||
|
||||
s_display_context.complete();
|
||||
|
||||
signed portBASE_TYPE was_higher_priority_task_woken = pdFALSE;
|
||||
xSemaphoreGiveFromISR(s_dma_update_in_progress_semaphore, &was_higher_priority_task_woken);
|
||||
|
||||
return was_higher_priority_task_woken != pdFALSE;
|
||||
}
|
||||
|
||||
#if DISPLAY_ORIENTATION_ROTATED_180
|
||||
prv_memcpy_reverse_bytes((uint8_t*)s_dma_line_buffer, r.data, DISP_LINE_BYTES);
|
||||
s_dma_line_buffer[0] &= ~(0xffff);
|
||||
s_dma_line_buffer[0] |= (DISP_MODE_WRITE | reverse_byte(r.address + 1) << 8);
|
||||
#else
|
||||
prv_memcpy_backwards(s_dma_line_buffer, (uint32_t*)r.data, DISP_LINE_WORDS);
|
||||
s_dma_line_buffer[0] &= ~(0xffff);
|
||||
s_dma_line_buffer[0] |= reverse_byte(167 - r.address + 1) << 8;
|
||||
#endif
|
||||
prv_setup_dma_transfer(((uint8_t*) s_dma_line_buffer) + 1, DISP_DMA_BUFFER_SIZE_BYTES - 1);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
WTF;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void prv_setup_dma_transfer(uint8_t *framebuffer_addr, int framebuffer_size) {
|
||||
void *dst = (void *)&(BOARD_CONFIG_DISPLAY.spi->DR);
|
||||
dma_request_start_direct(SHARP_SPI_TX_DMA, dst, framebuffer_addr, framebuffer_size,
|
||||
prv_dma_handler, NULL);
|
||||
}
|
||||
|
||||
void display_show_splash_screen(void) {
|
||||
// The bootloader has already drawn the splash screen for us; nothing to do!
|
||||
}
|
||||
|
||||
// Stubs for display offset
|
||||
void display_set_offset(GPoint offset) {}
|
||||
|
||||
GPoint display_get_offset(void) { return GPointZero; }
|
38
src/fw/drivers/display/sharp_ls013b7dh01/sharp_ls013b7dh01.h
Normal file
38
src/fw/drivers/display/sharp_ls013b7dh01/sharp_ls013b7dh01.h
Normal file
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../display.h"
|
||||
|
||||
#define DISP_LINE_BYTES (DISP_COLS / 8)
|
||||
#define DISP_LINE_WORDS (((DISP_COLS - 1) / 32) + 1)
|
||||
|
||||
// Bytes_per_line + 1 byte for the line address + 1 byte for a null trailer + 1 optional byte for a write command
|
||||
#define DISP_DMA_BUFFER_SIZE_BYTES (DISP_LINE_BYTES + 3)
|
||||
#define DISP_DMA_BUFFER_SIZE_WORDS (DISP_LINE_WORDS + 1)
|
||||
|
||||
typedef enum {
|
||||
DISPLAY_STATE_IDLE,
|
||||
DISPLAY_STATE_WRITING
|
||||
} DisplayState;
|
||||
|
||||
typedef struct {
|
||||
DisplayState state;
|
||||
NextRowCallback get_next_row;
|
||||
UpdateCompleteCallback complete;
|
||||
} DisplayContext;
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue