diff --git a/meson.build b/meson.build
index 9df1ed87..11298e9e 100644
--- a/meson.build
+++ b/meson.build
@@ -461,7 +461,8 @@ cs7000_src = ['platform/drivers/stubs/nvmem_stub.c',
'platform/drivers/stubs/cps_io_stub.c',
'platform/drivers/stubs/radio_stub.c',
'platform/drivers/stubs/keyboard_stub.c',
- 'platform/drivers/stubs/display_stub.c',
+ 'platform/drivers/display/ST7735R_CS7000.c',
+ 'platform/drivers/backlight/backlight_CS7000.c',
'platform/drivers/stubs/audio_stub.c',
'platform/drivers/GPIO/gpio_shiftReg.c',
'platform/drivers/SPI/spi_custom.c',
diff --git a/platform/drivers/backlight/backlight_CS7000.c b/platform/drivers/backlight/backlight_CS7000.c
new file mode 100644
index 00000000..0a8f5bd3
--- /dev/null
+++ b/platform/drivers/backlight/backlight_CS7000.c
@@ -0,0 +1,72 @@
+/***************************************************************************
+ * Copyright (C) 2024 by Silvano Seva IU2KWO *
+ * *
+ * 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 3 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, see *
+ ***************************************************************************/
+
+#include
+#include
+#include
+#include "backlight.h"
+
+void backlight_init()
+{
+ gpio_setMode(LCD_BKLIGHT, ALTERNATE | ALTERNATE_FUNC(3));
+
+ /*
+ * Configure TIM8 for backlight PWM: Fpwm = 1kHz with 8 bit of resolution.
+ * APB2 freq. is 84MHz, but timer runs at twice this frequency.
+ * Then: PSC = 655 to have Ftick = 256.097kHz
+ * With ARR = 256, Fpwm is 1kHz;
+ * Backlight pin is connected to TIM8 CR1.
+ */
+ RCC->APB2ENR |= RCC_APB2ENR_TIM8EN;
+ __DSB();
+
+ TIM8->ARR = 255;
+ TIM8->PSC = 654;
+ TIM8->CNT = 0;
+ TIM8->CR1 |= TIM_CR1_ARPE; /* LCD backlight is on PC6, TIM8-CH1 */
+ TIM8->CCMR2 |= TIM_CCMR1_OC2M_2
+ | TIM_CCMR1_OC2M_1
+ | TIM_CCMR1_OC2PE;
+ TIM8->CCER |= TIM_CCER_CC4E;
+ TIM8->BDTR |= TIM_BDTR_MOE;
+ TIM8->CCR1 = 0;
+ TIM8->EGR = TIM_EGR_UG; /* Update registers */
+ TIM8->CR1 |= TIM_CR1_CEN; /* Start timer */
+}
+
+void backlight_terminate()
+{
+ /* Shut down backlight */
+ gpio_setMode(LCD_BKLIGHT, OUTPUT);
+ gpio_clearPin(LCD_BKLIGHT);
+
+ /* Shut down timer */
+ RCC->APB2ENR &= ~RCC_APB2ENR_TIM8EN;
+ __DSB();
+}
+
+/*
+ * This function is defined in display.h
+ */
+void display_setBacklightLevel(uint8_t level)
+{
+ if(level > 100)
+ level = 100;
+
+ uint8_t pwmLevel = (2 * level) + (level * 55)/100; // Convert value to 0 - 255
+ TIM8->CCR4 = pwmLevel;
+}
diff --git a/platform/drivers/display/ST7735R_CS7000.c b/platform/drivers/display/ST7735R_CS7000.c
new file mode 100644
index 00000000..19e8bc0b
--- /dev/null
+++ b/platform/drivers/display/ST7735R_CS7000.c
@@ -0,0 +1,292 @@
+/***************************************************************************
+ * Copyright (C) 2024 by Silvano Seva IU2KWO *
+ * *
+ * 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 3 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, see *
+ ***************************************************************************/
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+enum ST775RCmd
+{
+ ST775R_CMD_NOP = 0x00,
+ ST775R_CMD_SWRESET = 0x01,
+ ST775R_CMD_RDID = 0x04,
+ ST775R_CMD_RDDST = 0x09,
+ ST775R_CMD_RDDPM = 0x0A,
+ ST775R_CMD_RDDMADCTL = 0x0B,
+ ST775R_CMD_RDDCOLMOD = 0x0C,
+ ST775R_CMD_RDDIM = 0x0D,
+ ST775R_CMD_RDDSM = 0x0E,
+ ST775R_CMD_SLPIN = 0x10,
+ ST775R_CMD_SLPOUT = 0x11,
+ ST775R_CMD_PTLON = 0x12,
+ ST775R_CMD_NORON = 0x13,
+ ST775R_CMD_INVOFF = 0x20,
+ ST775R_CMD_INVON = 0x21,
+ ST775R_CMD_GAMSET = 0x26,
+ ST775R_CMD_DISPOFF = 0x28,
+ ST775R_CMD_DISPON = 0x29,
+ ST775R_CMD_CASET = 0x2A,
+ ST775R_CMD_RASET = 0x2B,
+ ST775R_CMD_RAMWR = 0x2C,
+ ST775R_CMD_RGBSET = 0x2D,
+ ST775R_CMD_RAMRD = 0x2E,
+ ST775R_CMD_PTLAR = 0x30,
+ ST775R_CMD_TEOFF = 0x34,
+ ST775R_CMD_TEON = 0x35,
+ ST775R_CMD_MADCTL = 0x36,
+ ST775R_CMD_IDMOFF = 0x38,
+ ST775R_CMD_IDMON = 0x39,
+ ST775R_CMD_COLMOD = 0x3A,
+ ST775R_CMD_RDID1 = 0xDA,
+ ST775R_CMD_RDID2 = 0xDB,
+ ST775R_CMD_RDID3 = 0xDC
+};
+
+static inline void sendCmd(uint8_t cmd)
+{
+ // Set D/C low (command mode), clear WR and data lines
+ GPIOD->BSRR = 0x30FF0000;
+ asm volatile(" mov r1, #21 \n"
+ "___loop_d: cmp r1, #0 \n"
+ " itt ne \n"
+ " subne r1, r1, #1 \n"
+ " bne ___loop_d \n":::"r1");
+ GPIOD->BSRR = cmd | (1 << 13);
+}
+
+static inline void sendData(uint8_t val)
+{
+ // Set D/C high (data mode), clear WR and data lines
+ GPIOD->BSRR = 0x20FF1000;
+ asm volatile(" mov r1, #21 \n"
+ "___loop_e: cmp r1, #0 \n"
+ " itt ne \n"
+ " subne r1, r1, #1 \n"
+ " bne ___loop_e \n":::"r1");
+ GPIOD->BSRR = val | (1 << 13);
+}
+
+void display_init()
+{
+ backlight_init();
+
+ /* Set up gpios */
+ gpio_setMode(LCD_D0, OUTPUT);
+ gpio_setMode(LCD_D1, OUTPUT);
+ gpio_setMode(LCD_D2, OUTPUT);
+ gpio_setMode(LCD_D3, OUTPUT);
+ gpio_setMode(LCD_D4, OUTPUT);
+ gpio_setMode(LCD_D5, OUTPUT);
+ gpio_setMode(LCD_D6, OUTPUT);
+ gpio_setMode(LCD_D7, OUTPUT);
+ gpio_setMode(LCD_WR, OUTPUT);
+ gpio_setMode(LCD_RD, OUTPUT);
+ gpio_setMode(LCD_DC, OUTPUT);
+ gpio_setMode(LCD_RST, OUTPUT);
+ gpio_setMode(LCD_CS, OUTPUT);
+
+ gpio_setOutputSpeed(LCD_D0, HIGH);
+ gpio_setOutputSpeed(LCD_D1, HIGH);
+ gpio_setOutputSpeed(LCD_D2, HIGH);
+ gpio_setOutputSpeed(LCD_D3, HIGH);
+ gpio_setOutputSpeed(LCD_D4, HIGH);
+ gpio_setOutputSpeed(LCD_D5, HIGH);
+ gpio_setOutputSpeed(LCD_D6, HIGH);
+ gpio_setOutputSpeed(LCD_D7, HIGH);
+ gpio_setOutputSpeed(LCD_WR, HIGH);
+ gpio_setOutputSpeed(LCD_RD, HIGH);
+ gpio_setOutputSpeed(LCD_DC, HIGH);
+ gpio_setOutputSpeed(LCD_RST, HIGH);
+ gpio_setOutputSpeed(LCD_CS, HIGH);
+
+ gpio_clearPin(LCD_RST); /* Put LCD in reset mode */
+ gpio_setPin(LCD_CS); /* CS idle state is high level */
+ gpio_setPin(LCD_DC); /* Idle state for DC line */
+ gpio_setPin(LCD_WR); /* Idle state for WR line */
+ gpio_setPin(LCD_RD); /* Idle state for RD line */
+ gpio_setPin(LCD_RST); /* Exit from reset */
+
+ delayMs(10);
+
+ gpio_clearPin(LCD_CS);
+
+ sendCmd(ST775R_CMD_SWRESET);
+ sendCmd(0xb1); /* Undocumented command */
+ sendData(5);
+ sendData(8);
+ sendData(5);
+ sendCmd(0xb2); /* Undocumented command */
+ sendData(5);
+ sendData(8);
+ sendData(5);
+ sendCmd(0xb3); /* Undocumented command */
+ sendData(5);
+ sendData(8);
+ sendData(5);
+ sendData(5);
+ sendData(8);
+ sendData(5);
+ sendCmd(0xb4); /* Undocumented command */
+ sendData(0);
+ sendCmd(0xb6); /* Undocumented command */
+ sendData(0xb4);
+ sendData(0xf0);
+ sendCmd(0xc0); /* Undocumented command */
+ sendData(0xa2);
+ sendData(2);
+ /* sendData(0x85); TODO: see original fw */
+ sendData(0x84);
+ sendCmd(0xc1); /* Undocumented command */
+ sendData(5);
+ sendCmd(0xc2); /* Undocumented command */
+ sendData(10);
+ sendData(0);
+ sendCmd(0xc3); /* Undocumented command */
+ sendData(0x8a);
+ sendData(0x2a);
+ sendCmd(0xc4); /* Undocumented command */
+ sendData(0x8a);
+ sendData(0xee);
+ sendCmd(0xc5); /* Undocumented command */
+ sendData(0xe);
+ sendCmd(ST775R_CMD_MADCTL);
+ sendData(0xB0);
+ sendCmd(0xe0); /* Undocumented command */
+ sendData(5);
+ /* sendData(0x28); TODO: see original fw */
+ /* sendData(0x28); TODO: see original fw */
+ sendData(0x16);
+ sendData(0xf);
+ sendData(0x18);
+ sendData(0x2f);
+ sendData(0x28);
+ sendData(0x20);
+ sendData(0x22);
+ sendData(0x1f);
+ sendData(0x1b);
+ sendData(0x23);
+ sendData(0x37);
+ sendData(0);
+ sendData(7);
+ sendData(2);
+ sendData(0x10);
+ sendCmd(0xe1); /* Undocumented command */
+ sendData(7);
+ /* sendData(0x28); TODO: see original fw */
+ /* sendData(0x28); TODO: see original fw */
+ sendData(0x1b);
+ sendData(0xf);
+ sendData(0x17);
+ sendData(0x33);
+ sendData(0x2c);
+ sendData(0x29);
+ sendData(0x2e);
+ sendData(0x30);
+ sendData(0x30);
+ sendData(0x39);
+ sendData(0x3f);
+ sendData(0);
+ sendData(7);
+ sendData(3);
+ sendData(0x10);
+ sendCmd(0xf0); /* Undocumented command */
+ sendData(1);
+ sendCmd(0xf6); /* Undocumented command */
+ sendData(0);
+ sendCmd(ST775R_CMD_COLMOD);
+ sendData(0x05); /* 16bpp - RGB 565 */
+ sendCmd(ST775R_CMD_RASET);
+ sendData(0);
+ sendData(0);
+ sendData(0);
+ sendData(0x7f); /* 128 rows */
+ sendCmd(ST775R_CMD_CASET);
+ sendData(0);
+ sendData(0);
+ sendData(0);
+ sendData(0x9f); /* 160 columns */
+
+ /* Exit from sleep */
+ sendCmd(ST775R_CMD_SLPOUT);
+ delayMs(120);
+
+ /* Enable display */
+ sendCmd(ST775R_CMD_DISPON);
+
+ gpio_setPin(LCD_CS);
+}
+
+void display_terminate()
+{
+}
+
+void display_renderRows(uint8_t startRow, uint8_t endRow, void *fb)
+{
+ /*
+ * Put screen data lines back to output mode, since they are in common with
+ * keyboard buttons and the keyboard driver sets them as inputs.
+ */
+ gpio_setMode(LCD_D0, OUTPUT);
+ gpio_setMode(LCD_D1, OUTPUT);
+ gpio_setMode(LCD_D2, OUTPUT);
+ gpio_setMode(LCD_D3, OUTPUT);
+ gpio_setMode(LCD_D4, OUTPUT);
+ gpio_setMode(LCD_D5, OUTPUT);
+ gpio_setMode(LCD_D6, OUTPUT);
+ gpio_setMode(LCD_D7, OUTPUT);
+
+ /*
+ * Select the display, configure start and end rows in display driver and
+ * write to display memory.
+ */
+ gpio_clearPin(LCD_CS);
+
+ sendCmd(ST775R_CMD_RASET);
+ sendData(0x00);
+ sendData(startRow);
+ sendData(0x00);
+ sendData(endRow);
+ sendCmd(ST775R_CMD_RAMWR);
+
+ for(uint8_t y = startRow; y < endRow; y++)
+ {
+ for(uint8_t x = 0; x < CONFIG_SCREEN_WIDTH; x++)
+ {
+ size_t pos = x + y * CONFIG_SCREEN_WIDTH;
+ uint16_t pixel = ((uint16_t *) fb)[pos];
+ sendData((pixel >> 8) & 0xff);
+ sendData(pixel & 0xff);
+ }
+ }
+
+ gpio_setPin(LCD_CS);
+}
+
+void display_render(void *fb)
+{
+ display_renderRows(0, CONFIG_SCREEN_HEIGHT, fb);
+}
+
+void display_setContrast(uint8_t contrast)
+{
+ /* This controller does not support contrast regulation */
+ (void) contrast;
+}