mirror of
https://github.com/Rockbox/rockbox.git
synced 2026-07-10 13:29:52 -04:00
echoplayer: drive LCD parallel bus using STM32 LTDC
This is much, much faster than using SPI and is able to offload the CPU completely. Change-Id: Ia4c0289775296fe41d594ba849bd057e8482306e
This commit is contained in:
parent
e98dc7936c
commit
4ceb9e22d6
4 changed files with 172 additions and 38 deletions
|
|
@ -33,6 +33,7 @@ enum stm_clock
|
|||
STM_CLOCK_SPI4_KER,
|
||||
STM_CLOCK_SPI5_KER,
|
||||
STM_CLOCK_SPI6_KER,
|
||||
STM_CLOCK_LTDC_KER,
|
||||
STM_NUM_CLOCKS,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
*
|
||||
****************************************************************************/
|
||||
#include "clock-stm32h7.h"
|
||||
#include "lcd-echoplayer.h"
|
||||
#include "panic.h"
|
||||
#include "regs/stm32h743/flash.h"
|
||||
#include "regs/stm32h743/fmc.h"
|
||||
|
|
@ -38,14 +39,23 @@ static void init_hse(void)
|
|||
|
||||
static void init_pll(void)
|
||||
{
|
||||
/* Select HSE/4 input for PLL1 (6 MHz) */
|
||||
/* For simplicity, PLL parameters are hardcoded */
|
||||
_Static_assert(STM32_HSE_FREQ == 24000000,
|
||||
"HSE frequency not correct");
|
||||
_Static_assert(LCD_DOTCLOCK_FREQ == 6199200,
|
||||
"PLL3 parameters not correct for dot clock");
|
||||
|
||||
/*
|
||||
* Use HSE/4 input for PLL1
|
||||
* Use HSE/16 input for PLL3
|
||||
*/
|
||||
reg_writef(RCC_PLLCKSELR,
|
||||
PLLSRC_V(HSE),
|
||||
DIVM1(4),
|
||||
DIVM2(0),
|
||||
DIVM3(0));
|
||||
DIVM3(16));
|
||||
|
||||
/* Enable PLL1P and PLL1Q */
|
||||
/* Enable PLL1P, PLL1Q, PLL3R */
|
||||
reg_writef(RCC_PLLCFGR,
|
||||
DIVP1EN(1),
|
||||
DIVQ1EN(1),
|
||||
|
|
@ -55,9 +65,13 @@ static void init_pll(void)
|
|||
DIVR2EN(0),
|
||||
DIVP3EN(0),
|
||||
DIVQ3EN(0),
|
||||
DIVR3EN(0),
|
||||
DIVR3EN(1),
|
||||
PLL1RGE_V(4_8MHZ),
|
||||
PLL1VCOSEL_V(WIDE));
|
||||
PLL1VCOSEL_V(WIDE),
|
||||
PLL1FRACEN(0),
|
||||
PLL3RGE_V(1_2MHZ),
|
||||
PLL3VCOSEL_V(MEDIUM),
|
||||
PLL3FRACEN(0));
|
||||
|
||||
reg_writef(RCC_PLL1DIVR,
|
||||
DIVN(80 - 1), /* 6 * 80 = 480 MHz */
|
||||
|
|
@ -65,8 +79,18 @@ static void init_pll(void)
|
|||
DIVQ(8 - 1), /* 480 / 8 = 60 MHz */
|
||||
DIVR(1 - 1));
|
||||
|
||||
reg_writef(RCC_CR, PLL1ON(1));
|
||||
reg_writef(RCC_PLL3FRACR, FRACN(1468));
|
||||
reg_writef(RCC_PLL3DIVR,
|
||||
DIVN(161 - 1), /* approx 241.768 MHz */
|
||||
DIVP(1 - 1),
|
||||
DIVQ(1 - 1),
|
||||
DIVR(39 - 1)); /* approx 6.1992 MHz */
|
||||
|
||||
reg_writef(RCC_PLLCFGR, PLL3FRACEN(1));
|
||||
|
||||
reg_writef(RCC_CR, PLL1ON(1), PLL3ON(1));
|
||||
while (!reg_readf(RCC_CR, PLL1RDY));
|
||||
while (!reg_readf(RCC_CR, PLL3RDY));
|
||||
}
|
||||
|
||||
static void init_vos(void)
|
||||
|
|
@ -136,6 +160,9 @@ static void init_lse(void)
|
|||
static void init_periph_clock(void)
|
||||
{
|
||||
reg_writef(RCC_D2CCIP1R, SPI45SEL_V(HSE));
|
||||
|
||||
/* Enable AXI SRAM in sleep mode to allow DMA'ing out of it */
|
||||
reg_writef(RCC_AHB3LPENR, AXISRAMEN(1));
|
||||
}
|
||||
|
||||
void stm_target_clock_init(void)
|
||||
|
|
@ -157,6 +184,11 @@ void stm_target_clock_enable(enum stm_clock clock, bool enable)
|
|||
reg_writef(RCC_APB2LPENR, SPI5EN(enable));
|
||||
break;
|
||||
|
||||
case STM_CLOCK_LTDC_KER:
|
||||
reg_writef(RCC_APB3ENR, LTDCEN(enable));
|
||||
reg_writef(RCC_APB3LPENR, LTDCEN(enable));
|
||||
break;
|
||||
|
||||
default:
|
||||
panicf("%s: unsupported clock %d", __func__, (int)clock);
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -21,15 +21,22 @@
|
|||
#include "system.h"
|
||||
#include "kernel.h"
|
||||
#include "lcd.h"
|
||||
#include "lcd-echoplayer.h"
|
||||
#include "nvic-arm.h"
|
||||
#include "spi-stm32h7.h"
|
||||
#include "gpio-stm32h7.h"
|
||||
#include "clock-stm32h7.h"
|
||||
#include "regs/stm32h743/rcc.h"
|
||||
#include "regs/stm32h743/spi.h"
|
||||
#include "regs/stm32h743/ltdc.h"
|
||||
|
||||
/* ILI9342C specifies 10 MHz max */
|
||||
#define LCD_SPI_FREQ 10000000
|
||||
/*
|
||||
* ILI9342C specifies 10 MHz max
|
||||
*
|
||||
* Use 12MHz for now -- for some reason using 6 MHz doesn't work
|
||||
* to enable RGB mode, but works fine to send graphics in SPI mode?
|
||||
*/
|
||||
#define LCD_SPI_FREQ 12000000
|
||||
|
||||
struct stm_spi_config spi_cfg = {
|
||||
.instance = ITA_SPI5,
|
||||
|
|
@ -51,10 +58,46 @@ struct stm_spi spi;
|
|||
stm_spi_transmit(&spi, arr, sizeof(arr)); \
|
||||
} while (0)
|
||||
|
||||
static void set_row_column_address(int x, int y, int w, int h)
|
||||
static void init_ltdc(void)
|
||||
{
|
||||
ili_cmd(0x2a, x >> 8, x & 0xff, (w-1) >> 8, (w-1) & 0xff);
|
||||
ili_cmd(0x2b, y >> 8, y & 0xff, (h-1) >> 8, (h-1) & 0xff);
|
||||
/* Enable LTDC clock */
|
||||
stm_clock_enable(STM_CLOCK_LTDC_KER);
|
||||
|
||||
/* Set timing parameters */
|
||||
const uint32_t hsw = LCD_HSW - 1;
|
||||
const uint32_t ahbp = hsw + LCD_HBP;
|
||||
const uint32_t aaw = ahbp + LCD_HAW;
|
||||
const uint32_t totw = aaw + LCD_HFP;
|
||||
|
||||
const uint32_t vsh = LCD_VSH - 1;
|
||||
const uint32_t avbp = vsh + LCD_VBP;
|
||||
const uint32_t aah = avbp + LCD_VAH;
|
||||
const uint32_t toth = aah + LCD_VFP;
|
||||
|
||||
reg_writef(LTDC_SSCR, HSW(hsw), VSH(vsh));
|
||||
reg_writef(LTDC_BPCR, AHBP(ahbp), AVBP(avbp));
|
||||
reg_writef(LTDC_AWCR, AAW(aaw), AAH(aah));
|
||||
reg_writef(LTDC_TWCR, TOTALW(totw), TOTALH(toth));
|
||||
|
||||
/* Set interface polarity */
|
||||
reg_writef(LTDC_GCR, HSPOL(0), VSPOL(0), DEPOL(0), PCPOL(0), DEN(0));
|
||||
|
||||
/* Enable layer 1 to blit framebuffer to whole screen */
|
||||
const uint32_t row_bytes = LCD_WIDTH * FB_DATA_SZ;
|
||||
|
||||
reg_assignf(LTDC_LAYER_WHPCR(0), WHSPPOS(ahbp + LCD_HAW), WHSTPOS(ahbp + 1));
|
||||
reg_assignf(LTDC_LAYER_WVPCR(0), WVSPPOS(avbp + LCD_VAH), WVSTPOS(avbp + 1));
|
||||
reg_assignf(LTDC_LAYER_PFCR(0), PF(BV_LTDC_LAYER_PFCR_PF_RGB565));
|
||||
reg_var(LTDC_LAYER_CFBAR(0)) = (uintptr_t)FBADDR(0, 0);
|
||||
reg_assignf(LTDC_LAYER_CFBLR(0), CFBP(row_bytes), CFBLL(row_bytes + 7));
|
||||
reg_assignf(LTDC_LAYER_CFBLNR(0), CFBLNBR(LCD_HEIGHT));
|
||||
reg_assignf(LTDC_LAYER_CR(0), LEN(1));
|
||||
|
||||
/* Reload shadow registers to enable layer 1 */
|
||||
reg_writef(LTDC_SRCR, IMR(1));
|
||||
|
||||
/* Enable LTDC output */
|
||||
reg_writef(LTDC_GCR, LTDCEN(1));
|
||||
}
|
||||
|
||||
void lcd_init_device(void)
|
||||
|
|
@ -63,6 +106,9 @@ void lcd_init_device(void)
|
|||
stm_spi_init(&spi, &spi_cfg);
|
||||
nvic_enable_irq(NVIC_IRQN_SPI5);
|
||||
|
||||
/* Enable LCD controller */
|
||||
init_ltdc();
|
||||
|
||||
/* Ensure controller is reset */
|
||||
gpio_set_level(GPIO_LCD_RESET, 0);
|
||||
sleep(12);
|
||||
|
|
@ -77,8 +123,23 @@ void lcd_init_device(void)
|
|||
/* memory access control (X/Y invert, BGR panel) */
|
||||
ili_cmd(0x36, 0xc8);
|
||||
|
||||
/* pixel format set (16bpp) */
|
||||
ili_cmd(0x3a, 0x55);
|
||||
/* pixel format set (18bpp for RGB bus, 16bpp for SPI bus) */
|
||||
ili_cmd(0x3a, 0x65);
|
||||
|
||||
/* send set EXTC command to allow configuring RGB interface */
|
||||
ili_cmd(0xc8, 0xff, 0x93, 0x42);
|
||||
|
||||
/*
|
||||
* Enable RGB interface transferring to internal GRAM.
|
||||
*
|
||||
* Direct to shift register mode doesn't work; for one, the
|
||||
* framebuffer doesn't get transferred properly which might
|
||||
* just be timing issues. Two, the display is horizontally
|
||||
* flipped and there doesn't seem to be a way to change it
|
||||
* in the shift register mode.
|
||||
*/
|
||||
ili_cmd(0xb0, 0xc0);
|
||||
ili_cmd(0xf6, 0x01, 0x00, 0x06);
|
||||
|
||||
/* display ON */
|
||||
ili_cmd(0x29);
|
||||
|
|
@ -91,14 +152,11 @@ bool lcd_active(void)
|
|||
|
||||
void lcd_update(void)
|
||||
{
|
||||
lcd_update_rect(0, 0, LCD_WIDTH, LCD_HEIGHT);
|
||||
commit_dcache();
|
||||
}
|
||||
|
||||
void lcd_update_rect(int x, int y, int width, int height)
|
||||
{
|
||||
/* row buffer to minimize time wasted in post-transaction delay */
|
||||
static uint16_t row[LCD_WIDTH * 2];
|
||||
|
||||
if (x < 0)
|
||||
x = 0;
|
||||
else if (x >= LCD_WIDTH)
|
||||
|
|
@ -115,27 +173,8 @@ void lcd_update_rect(int x, int y, int width, int height)
|
|||
if (height > LCD_HEIGHT - y)
|
||||
height = LCD_HEIGHT - y;
|
||||
|
||||
set_row_column_address(x, y, width, height);
|
||||
ili_cmd(0x2c);
|
||||
|
||||
for (int py = y; py < height; ++py)
|
||||
{
|
||||
for (int px = x; px < width; ++px)
|
||||
{
|
||||
fb_data *fb = FBADDR(px, py);
|
||||
uint16_t *data = &row[px * 2];
|
||||
|
||||
data[0] = 0x100;
|
||||
data[0] |= (FB_UNPACK_RED(*fb) >> 3) << 3;
|
||||
data[0] |= (FB_UNPACK_GREEN(*fb) >> 5);
|
||||
|
||||
data[1] = 0x100;
|
||||
data[1] |= ((FB_UNPACK_GREEN(*fb) >> 2) & 0x7) << 5;
|
||||
data[1] |= (FB_UNPACK_BLUE(*fb) >> 3);
|
||||
}
|
||||
|
||||
stm_spi_transmit(&spi, &row[x * 2], width * sizeof(*row) * 2);
|
||||
}
|
||||
for (int dy = y; dy < height; ++dy)
|
||||
commit_dcache_range(FBADDR(x, dy), FB_DATA_SZ * width);
|
||||
}
|
||||
|
||||
void spi5_irq_handler(void)
|
||||
|
|
|
|||
62
firmware/target/arm/stm32/echoplayer/lcd-echoplayer.h
Normal file
62
firmware/target/arm/stm32/echoplayer/lcd-echoplayer.h
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
||||
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
||||
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
||||
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
||||
* \/ \/ \/ \/ \/
|
||||
* $Id$
|
||||
*
|
||||
* Copyright (C) 2025 Aidan MacDonald
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
||||
* KIND, either express or implied.
|
||||
*
|
||||
****************************************************************************/
|
||||
#ifndef __LCD_ECHOPLAYER_H__
|
||||
#define __LCD_ECHOPLAYER_H__
|
||||
|
||||
#include "config.h"
|
||||
|
||||
/* Hsync pulse width in units of dot clocks */
|
||||
#define LCD_HSW 10
|
||||
|
||||
/* Hsync back porch in units of dot clocks */
|
||||
#define LCD_HBP 20
|
||||
|
||||
/* Horizontal active width in units of dot clocks */
|
||||
#define LCD_HAW LCD_WIDTH
|
||||
|
||||
/* Hsync front porch in units of dot clocks */
|
||||
#define LCD_HFP 10
|
||||
|
||||
/* Vsync pulse height in units of horizontal lines */
|
||||
#define LCD_VSH 2
|
||||
|
||||
/* Vsync back porch in units of horizontal lines */
|
||||
#define LCD_VBP 2
|
||||
|
||||
/* Vertical active height in units of horizontal lines */
|
||||
#define LCD_VAH LCD_HEIGHT
|
||||
|
||||
/* Vsync front porch in units of horizontal lines */
|
||||
#define LCD_VFP 2
|
||||
|
||||
/* Total horizontal width in dots */
|
||||
#define LCD_HWIDTH (LCD_HSW + LCD_HBP + LCD_HAW + LCD_HFP)
|
||||
|
||||
/* Total vertical height in lines */
|
||||
#define LCD_VHEIGHT (LCD_VSH + LCD_VBP + LCD_VAH + LCD_VFP)
|
||||
|
||||
/* Target frame rate */
|
||||
#define LCD_FPS 70
|
||||
|
||||
/* Dot clock frequency */
|
||||
#define LCD_DOTCLOCK_FREQ (LCD_FPS * LCD_HWIDTH * LCD_VHEIGHT)
|
||||
|
||||
#endif /* __LCD_ECHOPLAYER_H__ */
|
||||
Loading…
Add table
Add a link
Reference in a new issue