Rockbox open source high quality audio player as a Music Player Daemon
mpris rockbox mpd libadwaita audio rust zig deno
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

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

authored by

Aidan MacDonald and committed by
Solomon Peachy
4ceb9e22 e98dc793

+172 -38
+1
firmware/target/arm/stm32/clock-stm32h7.h
··· 33 33 STM_CLOCK_SPI4_KER, 34 34 STM_CLOCK_SPI5_KER, 35 35 STM_CLOCK_SPI6_KER, 36 + STM_CLOCK_LTDC_KER, 36 37 STM_NUM_CLOCKS, 37 38 }; 38 39
+38 -6
firmware/target/arm/stm32/echoplayer/clock-echoplayer.c
··· 19 19 * 20 20 ****************************************************************************/ 21 21 #include "clock-stm32h7.h" 22 + #include "lcd-echoplayer.h" 22 23 #include "panic.h" 23 24 #include "regs/stm32h743/flash.h" 24 25 #include "regs/stm32h743/fmc.h" ··· 38 39 39 40 static void init_pll(void) 40 41 { 41 - /* Select HSE/4 input for PLL1 (6 MHz) */ 42 + /* For simplicity, PLL parameters are hardcoded */ 43 + _Static_assert(STM32_HSE_FREQ == 24000000, 44 + "HSE frequency not correct"); 45 + _Static_assert(LCD_DOTCLOCK_FREQ == 6199200, 46 + "PLL3 parameters not correct for dot clock"); 47 + 48 + /* 49 + * Use HSE/4 input for PLL1 50 + * Use HSE/16 input for PLL3 51 + */ 42 52 reg_writef(RCC_PLLCKSELR, 43 53 PLLSRC_V(HSE), 44 54 DIVM1(4), 45 55 DIVM2(0), 46 - DIVM3(0)); 56 + DIVM3(16)); 47 57 48 - /* Enable PLL1P and PLL1Q */ 58 + /* Enable PLL1P, PLL1Q, PLL3R */ 49 59 reg_writef(RCC_PLLCFGR, 50 60 DIVP1EN(1), 51 61 DIVQ1EN(1), ··· 55 65 DIVR2EN(0), 56 66 DIVP3EN(0), 57 67 DIVQ3EN(0), 58 - DIVR3EN(0), 68 + DIVR3EN(1), 59 69 PLL1RGE_V(4_8MHZ), 60 - PLL1VCOSEL_V(WIDE)); 70 + PLL1VCOSEL_V(WIDE), 71 + PLL1FRACEN(0), 72 + PLL3RGE_V(1_2MHZ), 73 + PLL3VCOSEL_V(MEDIUM), 74 + PLL3FRACEN(0)); 61 75 62 76 reg_writef(RCC_PLL1DIVR, 63 77 DIVN(80 - 1), /* 6 * 80 = 480 MHz */ ··· 65 79 DIVQ(8 - 1), /* 480 / 8 = 60 MHz */ 66 80 DIVR(1 - 1)); 67 81 68 - reg_writef(RCC_CR, PLL1ON(1)); 82 + reg_writef(RCC_PLL3FRACR, FRACN(1468)); 83 + reg_writef(RCC_PLL3DIVR, 84 + DIVN(161 - 1), /* approx 241.768 MHz */ 85 + DIVP(1 - 1), 86 + DIVQ(1 - 1), 87 + DIVR(39 - 1)); /* approx 6.1992 MHz */ 88 + 89 + reg_writef(RCC_PLLCFGR, PLL3FRACEN(1)); 90 + 91 + reg_writef(RCC_CR, PLL1ON(1), PLL3ON(1)); 69 92 while (!reg_readf(RCC_CR, PLL1RDY)); 93 + while (!reg_readf(RCC_CR, PLL3RDY)); 70 94 } 71 95 72 96 static void init_vos(void) ··· 136 160 static void init_periph_clock(void) 137 161 { 138 162 reg_writef(RCC_D2CCIP1R, SPI45SEL_V(HSE)); 163 + 164 + /* Enable AXI SRAM in sleep mode to allow DMA'ing out of it */ 165 + reg_writef(RCC_AHB3LPENR, AXISRAMEN(1)); 139 166 } 140 167 141 168 void stm_target_clock_init(void) ··· 155 182 case STM_CLOCK_SPI5_KER: 156 183 reg_writef(RCC_APB2ENR, SPI5EN(enable)); 157 184 reg_writef(RCC_APB2LPENR, SPI5EN(enable)); 185 + break; 186 + 187 + case STM_CLOCK_LTDC_KER: 188 + reg_writef(RCC_APB3ENR, LTDCEN(enable)); 189 + reg_writef(RCC_APB3LPENR, LTDCEN(enable)); 158 190 break; 159 191 160 192 default:
+71 -32
firmware/target/arm/stm32/echoplayer/lcd-echoplayer.c
··· 21 21 #include "system.h" 22 22 #include "kernel.h" 23 23 #include "lcd.h" 24 + #include "lcd-echoplayer.h" 24 25 #include "nvic-arm.h" 25 26 #include "spi-stm32h7.h" 26 27 #include "gpio-stm32h7.h" 27 28 #include "clock-stm32h7.h" 28 29 #include "regs/stm32h743/rcc.h" 29 30 #include "regs/stm32h743/spi.h" 31 + #include "regs/stm32h743/ltdc.h" 30 32 31 - /* ILI9342C specifies 10 MHz max */ 32 - #define LCD_SPI_FREQ 10000000 33 + /* 34 + * ILI9342C specifies 10 MHz max 35 + * 36 + * Use 12MHz for now -- for some reason using 6 MHz doesn't work 37 + * to enable RGB mode, but works fine to send graphics in SPI mode? 38 + */ 39 + #define LCD_SPI_FREQ 12000000 33 40 34 41 struct stm_spi_config spi_cfg = { 35 42 .instance = ITA_SPI5, ··· 51 58 stm_spi_transmit(&spi, arr, sizeof(arr)); \ 52 59 } while (0) 53 60 54 - static void set_row_column_address(int x, int y, int w, int h) 61 + static void init_ltdc(void) 55 62 { 56 - ili_cmd(0x2a, x >> 8, x & 0xff, (w-1) >> 8, (w-1) & 0xff); 57 - ili_cmd(0x2b, y >> 8, y & 0xff, (h-1) >> 8, (h-1) & 0xff); 63 + /* Enable LTDC clock */ 64 + stm_clock_enable(STM_CLOCK_LTDC_KER); 65 + 66 + /* Set timing parameters */ 67 + const uint32_t hsw = LCD_HSW - 1; 68 + const uint32_t ahbp = hsw + LCD_HBP; 69 + const uint32_t aaw = ahbp + LCD_HAW; 70 + const uint32_t totw = aaw + LCD_HFP; 71 + 72 + const uint32_t vsh = LCD_VSH - 1; 73 + const uint32_t avbp = vsh + LCD_VBP; 74 + const uint32_t aah = avbp + LCD_VAH; 75 + const uint32_t toth = aah + LCD_VFP; 76 + 77 + reg_writef(LTDC_SSCR, HSW(hsw), VSH(vsh)); 78 + reg_writef(LTDC_BPCR, AHBP(ahbp), AVBP(avbp)); 79 + reg_writef(LTDC_AWCR, AAW(aaw), AAH(aah)); 80 + reg_writef(LTDC_TWCR, TOTALW(totw), TOTALH(toth)); 81 + 82 + /* Set interface polarity */ 83 + reg_writef(LTDC_GCR, HSPOL(0), VSPOL(0), DEPOL(0), PCPOL(0), DEN(0)); 84 + 85 + /* Enable layer 1 to blit framebuffer to whole screen */ 86 + const uint32_t row_bytes = LCD_WIDTH * FB_DATA_SZ; 87 + 88 + reg_assignf(LTDC_LAYER_WHPCR(0), WHSPPOS(ahbp + LCD_HAW), WHSTPOS(ahbp + 1)); 89 + reg_assignf(LTDC_LAYER_WVPCR(0), WVSPPOS(avbp + LCD_VAH), WVSTPOS(avbp + 1)); 90 + reg_assignf(LTDC_LAYER_PFCR(0), PF(BV_LTDC_LAYER_PFCR_PF_RGB565)); 91 + reg_var(LTDC_LAYER_CFBAR(0)) = (uintptr_t)FBADDR(0, 0); 92 + reg_assignf(LTDC_LAYER_CFBLR(0), CFBP(row_bytes), CFBLL(row_bytes + 7)); 93 + reg_assignf(LTDC_LAYER_CFBLNR(0), CFBLNBR(LCD_HEIGHT)); 94 + reg_assignf(LTDC_LAYER_CR(0), LEN(1)); 95 + 96 + /* Reload shadow registers to enable layer 1 */ 97 + reg_writef(LTDC_SRCR, IMR(1)); 98 + 99 + /* Enable LTDC output */ 100 + reg_writef(LTDC_GCR, LTDCEN(1)); 58 101 } 59 102 60 103 void lcd_init_device(void) ··· 62 105 /* Configure SPI bus */ 63 106 stm_spi_init(&spi, &spi_cfg); 64 107 nvic_enable_irq(NVIC_IRQN_SPI5); 108 + 109 + /* Enable LCD controller */ 110 + init_ltdc(); 65 111 66 112 /* Ensure controller is reset */ 67 113 gpio_set_level(GPIO_LCD_RESET, 0); ··· 77 123 /* memory access control (X/Y invert, BGR panel) */ 78 124 ili_cmd(0x36, 0xc8); 79 125 80 - /* pixel format set (16bpp) */ 81 - ili_cmd(0x3a, 0x55); 126 + /* pixel format set (18bpp for RGB bus, 16bpp for SPI bus) */ 127 + ili_cmd(0x3a, 0x65); 128 + 129 + /* send set EXTC command to allow configuring RGB interface */ 130 + ili_cmd(0xc8, 0xff, 0x93, 0x42); 131 + 132 + /* 133 + * Enable RGB interface transferring to internal GRAM. 134 + * 135 + * Direct to shift register mode doesn't work; for one, the 136 + * framebuffer doesn't get transferred properly which might 137 + * just be timing issues. Two, the display is horizontally 138 + * flipped and there doesn't seem to be a way to change it 139 + * in the shift register mode. 140 + */ 141 + ili_cmd(0xb0, 0xc0); 142 + ili_cmd(0xf6, 0x01, 0x00, 0x06); 82 143 83 144 /* display ON */ 84 145 ili_cmd(0x29); ··· 91 152 92 153 void lcd_update(void) 93 154 { 94 - lcd_update_rect(0, 0, LCD_WIDTH, LCD_HEIGHT); 155 + commit_dcache(); 95 156 } 96 157 97 158 void lcd_update_rect(int x, int y, int width, int height) 98 159 { 99 - /* row buffer to minimize time wasted in post-transaction delay */ 100 - static uint16_t row[LCD_WIDTH * 2]; 101 - 102 160 if (x < 0) 103 161 x = 0; 104 162 else if (x >= LCD_WIDTH) ··· 115 173 if (height > LCD_HEIGHT - y) 116 174 height = LCD_HEIGHT - y; 117 175 118 - set_row_column_address(x, y, width, height); 119 - ili_cmd(0x2c); 120 - 121 - for (int py = y; py < height; ++py) 122 - { 123 - for (int px = x; px < width; ++px) 124 - { 125 - fb_data *fb = FBADDR(px, py); 126 - uint16_t *data = &row[px * 2]; 127 - 128 - data[0] = 0x100; 129 - data[0] |= (FB_UNPACK_RED(*fb) >> 3) << 3; 130 - data[0] |= (FB_UNPACK_GREEN(*fb) >> 5); 131 - 132 - data[1] = 0x100; 133 - data[1] |= ((FB_UNPACK_GREEN(*fb) >> 2) & 0x7) << 5; 134 - data[1] |= (FB_UNPACK_BLUE(*fb) >> 3); 135 - } 136 - 137 - stm_spi_transmit(&spi, &row[x * 2], width * sizeof(*row) * 2); 138 - } 176 + for (int dy = y; dy < height; ++dy) 177 + commit_dcache_range(FBADDR(x, dy), FB_DATA_SZ * width); 139 178 } 140 179 141 180 void spi5_irq_handler(void)
+62
firmware/target/arm/stm32/echoplayer/lcd-echoplayer.h
··· 1 + /*************************************************************************** 2 + * __________ __ ___. 3 + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ 4 + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / 5 + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < 6 + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ 7 + * \/ \/ \/ \/ \/ 8 + * $Id$ 9 + * 10 + * Copyright (C) 2025 Aidan MacDonald 11 + * 12 + * This program is free software; you can redistribute it and/or 13 + * modify it under the terms of the GNU General Public License 14 + * as published by the Free Software Foundation; either version 2 15 + * of the License, or (at your option) any later version. 16 + * 17 + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 18 + * KIND, either express or implied. 19 + * 20 + ****************************************************************************/ 21 + #ifndef __LCD_ECHOPLAYER_H__ 22 + #define __LCD_ECHOPLAYER_H__ 23 + 24 + #include "config.h" 25 + 26 + /* Hsync pulse width in units of dot clocks */ 27 + #define LCD_HSW 10 28 + 29 + /* Hsync back porch in units of dot clocks */ 30 + #define LCD_HBP 20 31 + 32 + /* Horizontal active width in units of dot clocks */ 33 + #define LCD_HAW LCD_WIDTH 34 + 35 + /* Hsync front porch in units of dot clocks */ 36 + #define LCD_HFP 10 37 + 38 + /* Vsync pulse height in units of horizontal lines */ 39 + #define LCD_VSH 2 40 + 41 + /* Vsync back porch in units of horizontal lines */ 42 + #define LCD_VBP 2 43 + 44 + /* Vertical active height in units of horizontal lines */ 45 + #define LCD_VAH LCD_HEIGHT 46 + 47 + /* Vsync front porch in units of horizontal lines */ 48 + #define LCD_VFP 2 49 + 50 + /* Total horizontal width in dots */ 51 + #define LCD_HWIDTH (LCD_HSW + LCD_HBP + LCD_HAW + LCD_HFP) 52 + 53 + /* Total vertical height in lines */ 54 + #define LCD_VHEIGHT (LCD_VSH + LCD_VBP + LCD_VAH + LCD_VFP) 55 + 56 + /* Target frame rate */ 57 + #define LCD_FPS 70 58 + 59 + /* Dot clock frequency */ 60 + #define LCD_DOTCLOCK_FREQ (LCD_FPS * LCD_HWIDTH * LCD_VHEIGHT) 61 + 62 + #endif /* __LCD_ECHOPLAYER_H__ */