diff --git a/builddefs/build_keyboard.mk b/builddefs/build_keyboard.mk index a1c1d5b9769..12a8c5b67be 100644 --- a/builddefs/build_keyboard.mk +++ b/builddefs/build_keyboard.mk @@ -455,6 +455,7 @@ $(eval $(call add_qmk_prefix_defs,MCU_PORT_NAME,MCU_PORT_NAME)) $(eval $(call add_qmk_prefix_defs,MCU_FAMILY,MCU_FAMILY)) $(eval $(call add_qmk_prefix_defs,MCU_SERIES,MCU_SERIES)) $(eval $(call add_qmk_prefix_defs,BOARD,BOARD)) +$(eval $(call add_qmk_prefix_defs,OPT,OPT)) # Control whether intermediate file listings are generated # e.g.: diff --git a/docs/quantum_painter.md b/docs/quantum_painter.md index 8acf6aeb361..e8fa1bdeceb 100644 --- a/docs/quantum_painter.md +++ b/docs/quantum_painter.md @@ -13,22 +13,24 @@ QUANTUM_PAINTER_DRIVERS += ...... You will also likely need to select an appropriate driver in `rules.mk`, which is listed below. -!> Quantum Painter is not currently integrated with system-level operations such as disabling displays after a configurable timeout, or when the keyboard goes into suspend. Users will need to handle this manually at the current time. +!> Quantum Painter is not currently integrated with system-level operations such as when the keyboard goes into suspend. Users will need to handle this manually at the current time. The QMK CLI can be used to convert from normal images such as PNG files or animated GIFs, as well as fonts from TTF files. Supported devices: -| Display Panel | Panel Type | Size | Comms Transport | Driver | -|----------------|--------------------|------------------|-----------------|---------------------------------------------| -| GC9A01 | RGB LCD (circular) | 240x240 | SPI + D/C + RST | `QUANTUM_PAINTER_DRIVERS += gc9a01_spi` | -| ILI9163 | RGB LCD | 128x128 | SPI + D/C + RST | `QUANTUM_PAINTER_DRIVERS += ili9163_spi` | -| ILI9341 | RGB LCD | 240x320 | SPI + D/C + RST | `QUANTUM_PAINTER_DRIVERS += ili9341_spi` | -| ILI9488 | RGB LCD | 320x480 | SPI + D/C + RST | `QUANTUM_PAINTER_DRIVERS += ili9488_spi` | -| SSD1351 | RGB OLED | 128x128 | SPI + D/C + RST | `QUANTUM_PAINTER_DRIVERS += ssd1351_spi` | -| ST7735 | RGB LCD | 132x162, 80x160 | SPI + D/C + RST | `QUANTUM_PAINTER_DRIVERS += st7735_spi` | -| ST7789 | RGB LCD | 240x320, 240x240 | SPI + D/C + RST | `QUANTUM_PAINTER_DRIVERS += st7789_spi` | -| RGB565 Surface | Virtual | User-defined | None | `QUANTUM_PAINTER_DRIVERS += rgb565_surface` | +| Display Panel | Panel Type | Size | Comms Transport | Driver | +|---------------|--------------------|------------------|-----------------|------------------------------------------| +| GC9A01 | RGB LCD (circular) | 240x240 | SPI + D/C + RST | `QUANTUM_PAINTER_DRIVERS += gc9a01_spi` | +| ILI9163 | RGB LCD | 128x128 | SPI + D/C + RST | `QUANTUM_PAINTER_DRIVERS += ili9163_spi` | +| ILI9341 | RGB LCD | 240x320 | SPI + D/C + RST | `QUANTUM_PAINTER_DRIVERS += ili9341_spi` | +| ILI9488 | RGB LCD | 320x480 | SPI + D/C + RST | `QUANTUM_PAINTER_DRIVERS += ili9488_spi` | +| SSD1351 | RGB OLED | 128x128 | SPI + D/C + RST | `QUANTUM_PAINTER_DRIVERS += ssd1351_spi` | +| ST7735 | RGB LCD | 132x162, 80x160 | SPI + D/C + RST | `QUANTUM_PAINTER_DRIVERS += st7735_spi` | +| ST7789 | RGB LCD | 240x320, 240x240 | SPI + D/C + RST | `QUANTUM_PAINTER_DRIVERS += st7789_spi` | +| SH1106 (SPI) | Monochrome OLED | 128x64 | SPI + D/C + RST | `QUANTUM_PAINTER_DRIVERS += sh1106_spi` | +| SH1106 (I2C) | Monochrome OLED | 128x64 | I2C | `QUANTUM_PAINTER_DRIVERS += sh1106_i2c` | +| Surface | Virtual | User-defined | None | `QUANTUM_PAINTER_DRIVERS += surface` | ## Quantum Painter Configuration :id=quantum-painter-config @@ -188,7 +190,8 @@ Writing /home/qmk/qmk_firmware/keyboards/my_keeb/generated/noto11.qff.c... -### ** Common: Standard TFT (SPI + D/C + RST) ** + +### ** LCD ** Most TFT display panels use a 5-pin interface -- SPI SCK, SPI MOSI, SPI CS, D/C, and RST pins. @@ -302,32 +305,6 @@ The maximum number of displays can be configured by changing the following in yo Native color format rgb888 is compatible with ILI9488 -#### ** SSD1351 ** - -Enabling support for the SSD1351 in Quantum Painter is done by adding the following to `rules.mk`: - -```make -QUANTUM_PAINTER_ENABLE = yes -QUANTUM_PAINTER_DRIVERS += ssd1351_spi -``` - -Creating a SSD1351 device in firmware can then be done with the following API: - -```c -painter_device_t qp_ssd1351_make_spi_device(uint16_t panel_width, uint16_t panel_height, pin_t chip_select_pin, pin_t dc_pin, pin_t reset_pin, uint16_t spi_divisor, int spi_mode); -``` - -The device handle returned from the `qp_ssd1351_make_spi_device` function can be used to perform all other drawing operations. - -The maximum number of displays can be configured by changing the following in your `config.h` (default is 1): - -```c -// 3 displays: -#define SSD1351_NUM_DEVICES 3 -``` - -Native color format rgb565 is compatible with SSD1351 - #### ** ST7735 ** Enabling support for the ST7735 in Quantum Painter is done by adding the following to `rules.mk`: @@ -386,62 +363,139 @@ Native color format rgb565 is compatible with ST7789 -### ** Common: Surfaces ** +### ** OLED ** -Quantum Painter has surface drivers which are able to target a buffer in RAM. In general, surfaces keep track of the "dirty" region -- the area that has been drawn to since the last flush -- so that when transferring to the display they can transfer the minimal amount of data to achieve the end result. +OLED displays tend to use 5-pin SPI when at larger resolutions, or when using color -- SPI SCK, SPI MOSI, SPI CS, D/C, and RST pins. Smaller OLEDs may use I2C instead. -!> These generally require significant amounts of RAM, so at large sizes and/or higher bit depths, they may not be usable on all MCUs. +When using these displays, either `spi_master` or `i2c_master` must already be correctly configured for both the platform and panel you're building for. + +For SPI, the pin assignments for SPI CS, D/C, and RST are specified during device construction -- for I2C the panel's address is specified instead. -#### ** RGB565 Surface ** +#### ** SSD1351 ** -Enabling support for RGB565 surfaces in Quantum Painter is done by adding the following to `rules.mk`: +Enabling support for the SSD1351 in Quantum Painter is done by adding the following to `rules.mk`: ```make QUANTUM_PAINTER_ENABLE = yes -QUANTUM_PAINTER_DRIVERS += rgb565_surface +QUANTUM_PAINTER_DRIVERS += ssd1351_spi ``` -Creating a RGB565 surface in firmware can then be done with the following API: +Creating a SSD1351 device in firmware can then be done with the following API: ```c -painter_device_t qp_rgb565_make_surface(uint16_t panel_width, uint16_t panel_height, void *buffer); +painter_device_t qp_ssd1351_make_spi_device(uint16_t panel_width, uint16_t panel_height, pin_t chip_select_pin, pin_t dc_pin, pin_t reset_pin, uint16_t spi_divisor, int spi_mode); ``` -The `buffer` is a user-supplied area of memory, and is assumed to be of the size `sizeof(uint16_t) * panel_width * panel_height`. +The device handle returned from the `qp_ssd1351_make_spi_device` function can be used to perform all other drawing operations. -The device handle returned from the `qp_rgb565_make_surface` function can be used to perform all other drawing operations. +The maximum number of displays can be configured by changing the following in your `config.h` (default is 1): + +```c +// 3 displays: +#define SSD1351_NUM_DEVICES 3 +``` + +Native color format rgb565 is compatible with SSD1351 + +#### ** SH1106 ** + +Enabling support for the SH1106 in Quantum Painter is done by adding the following to `rules.mk`: + +```make +QUANTUM_PAINTER_ENABLE = yes +# For SPI: +QUANTUM_PAINTER_DRIVERS += sh1106_spi +# For I2C: +QUANTUM_PAINTER_DRIVERS += sh1106_i2c +``` + +Creating a SH1106 device in firmware can then be done with the following APIs: + +```c +// SPI-based SH1106: +painter_device_t qp_sh1106_make_spi_device(uint16_t panel_width, uint16_t panel_height, pin_t chip_select_pin, pin_t dc_pin, pin_t reset_pin, uint16_t spi_divisor, int spi_mode); +// I2C-based SH1106: +painter_device_t qp_sh1106_make_i2c_device(uint16_t panel_width, uint16_t panel_height, uint8_t i2c_address); +``` + +The device handle returned from the `qp_sh1106_make_???_device` function can be used to perform all other drawing operations. + +The maximum number of displays of each type can be configured by changing the following in your `config.h` (default is 1): + +```c +// 3 SPI displays: +#define SH1106_NUM_SPI_DEVICES 3 +// 3 I2C displays: +#define SH1106_NUM_I2C_DEVICES 3 +``` + +Native color format mono2 is compatible with SH1106 + + + +### ** Surface ** + +Quantum Painter has a surface driver which is able to target a buffer in RAM. In general, surfaces keep track of the "dirty" region -- the area that has been drawn to since the last flush -- so that when transferring to the display they can transfer the minimal amount of data to achieve the end result. + +!> These generally require significant amounts of RAM, so at large sizes and/or higher bit depths, they may not be usable on all MCUs. + +Enabling support for surfaces in Quantum Painter is done by adding the following to `rules.mk`: + +```make +QUANTUM_PAINTER_ENABLE = yes +QUANTUM_PAINTER_DRIVERS += surface +``` + +Creating a surface in firmware can then be done with the following APIs: + +```c +// 16bpp RGB565 surface: +painter_device_t qp_make_rgb565_surface(uint16_t panel_width, uint16_t panel_height, void *buffer); +// 1bpp monochrome surface: +painter_device_t qp_make_mono1bpp_surface(uint16_t panel_width, uint16_t panel_height, void *buffer); +``` + +The `buffer` is a user-supplied area of memory, which can be statically allocated using `SURFACE_REQUIRED_BUFFER_BYTE_SIZE`: + +```c +// Buffer required for a 240x80 16bpp surface: +uint8_t framebuffer[SURFACE_REQUIRED_BUFFER_BYTE_SIZE(240, 80, 16)]; +``` + +The device handle returned from the `qp_make_?????_surface` function can be used to perform all other drawing operations. Example: ```c static painter_device_t my_surface; -static uint16_t my_framebuffer[320 * 240]; // Allocate a buffer for a 320x240 RGB565 display +static uint8_t my_framebuffer[SURFACE_REQUIRED_BUFFER_BYTE_SIZE(240, 80, 16)]; // Allocate a buffer for a 16bpp 240x80 RGB565 display void keyboard_post_init_kb(void) { - my_surface = qp_rgb565_make_surface(320, 240, my_framebuffer); + my_surface = qp_rgb565_make_surface(240, 80, my_framebuffer); qp_init(my_surface, QP_ROTATION_0); + keyboard_post_init_user(); } ``` -The maximum number of RGB565 surfaces can be configured by changing the following in your `config.h` (default is 1): +The maximum number of surfaces can be configured by changing the following in your `config.h` (default is 1): ```c // 3 surfaces: -#define RGB565_SURFACE_NUM_DEVICES 3 +#define SURFACE_NUM_DEVICES 3 ``` -To transfer the contents of the RGB565 surface to another display, the following API can be invoked: +To transfer the contents of the surface to another display of the same pixel format, the following API can be invoked: ```c -bool qp_rgb565_surface_draw(painter_device_t surface, painter_device_t display, uint16_t x, uint16_t y); +bool qp_surface_draw(painter_device_t surface, painter_device_t display, uint16_t x, uint16_t y); ``` The `surface` is the surface to copy out from. The `display` is the target display to draw into. `x` and `y` are the target location to draw the surface pixel data. Under normal circumstances, the location should be consistent, as the dirty region is calculated with respect to the `x` and `y` coordinates -- changing those will result in partial, overlapping draws. -?> Calling `qp_flush()` on the surface resets its dirty region. Copying the surface contents to the display also automatically resets the dirty region. +!> The surface and display panel must have the same native pixel format. - +?> Calling `qp_flush()` on the surface resets its dirty region. Copying the surface contents to the display also automatically resets the dirty region. diff --git a/drivers/painter/comms/qp_comms_dummy.c b/drivers/painter/comms/qp_comms_dummy.c new file mode 100644 index 00000000000..2ed49d22327 --- /dev/null +++ b/drivers/painter/comms/qp_comms_dummy.c @@ -0,0 +1,34 @@ +// Copyright 2023 Nick Brassel (@tzarc) +// SPDX-License-Identifier: GPL-2.0-or-later + +#ifdef QUANTUM_PAINTER_DUMMY_COMMS_ENABLE + +# include "qp_comms_dummy.h" + +static bool dummy_comms_init(painter_device_t device) { + // No-op. + return true; +} + +static bool dummy_comms_start(painter_device_t device) { + // No-op. + return true; +} + +static void dummy_comms_stop(painter_device_t device) { + // No-op. +} + +uint32_t dummy_comms_send(painter_device_t device, const void *data, uint32_t byte_count) { + // No-op. + return byte_count; +} + +painter_comms_vtable_t dummy_comms_vtable = { + // These are all effective no-op's because they're not actually needed. + .comms_init = dummy_comms_init, + .comms_start = dummy_comms_start, + .comms_stop = dummy_comms_stop, + .comms_send = dummy_comms_send}; + +#endif // QUANTUM_PAINTER_DUMMY_COMMS_ENABLE diff --git a/drivers/painter/comms/qp_comms_dummy.h b/drivers/painter/comms/qp_comms_dummy.h new file mode 100644 index 00000000000..b2d5d6eea5c --- /dev/null +++ b/drivers/painter/comms/qp_comms_dummy.h @@ -0,0 +1,11 @@ +// Copyright 2023 Nick Brassel (@tzarc) +// SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + +#ifdef QUANTUM_PAINTER_DUMMY_COMMS_ENABLE + +# include "qp_internal.h" + +extern painter_comms_vtable_t dummy_comms_vtable; + +#endif // QUANTUM_PAINTER_DUMMY_COMMS_ENABLE diff --git a/drivers/painter/comms/qp_comms_i2c.c b/drivers/painter/comms/qp_comms_i2c.c new file mode 100644 index 00000000000..ec45ddfb3b6 --- /dev/null +++ b/drivers/painter/comms/qp_comms_i2c.c @@ -0,0 +1,94 @@ +// Copyright 2022 Nick Brassel (@tzarc) +// SPDX-License-Identifier: GPL-2.0-or-later + +#ifdef QUANTUM_PAINTER_I2C_ENABLE + +# include "i2c_master.h" +# include "qp_comms_i2c.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Helpers + +static uint32_t qp_comms_i2c_send_raw(painter_device_t device, const void *data, uint32_t byte_count) { + painter_driver_t * driver = (painter_driver_t *)device; + qp_comms_i2c_config_t *comms_config = (qp_comms_i2c_config_t *)driver->comms_config; + i2c_status_t res = i2c_transmit(comms_config->chip_address << 1, data, byte_count, I2C_TIMEOUT); + if (res < 0) { + return 0; + } + return byte_count; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Base I2C support + +bool qp_comms_i2c_init(painter_device_t device) { + i2c_init(); + return true; +} + +bool qp_comms_i2c_start(painter_device_t device) { + painter_driver_t * driver = (painter_driver_t *)device; + qp_comms_i2c_config_t *comms_config = (qp_comms_i2c_config_t *)driver->comms_config; + return i2c_start(comms_config->chip_address << 1) == I2C_STATUS_SUCCESS; +} + +uint32_t qp_comms_i2c_send_data(painter_device_t device, const void *data, uint32_t byte_count) { + return qp_comms_i2c_send_raw(device, data, byte_count); +} + +void qp_comms_i2c_stop(painter_device_t device) { + i2c_stop(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Command+Data I2C support + +static const uint8_t cmd_byte = 0x00; +static const uint8_t data_byte = 0x40; + +void qp_comms_i2c_cmddata_send_command(painter_device_t device, uint8_t cmd) { + uint8_t buf[2] = {cmd_byte, cmd}; + qp_comms_i2c_send_raw(device, &buf, 2); +} + +uint32_t qp_comms_i2c_cmddata_send_data(painter_device_t device, const void *data, uint32_t byte_count) { + uint8_t buf[1 + byte_count]; + buf[0] = data_byte; + memcpy(&buf[1], data, byte_count); + if (qp_comms_i2c_send_raw(device, buf, sizeof(buf)) != sizeof(buf)) { + return 0; + } + return byte_count; +} + +void qp_comms_i2c_bulk_command_sequence(painter_device_t device, const uint8_t *sequence, size_t sequence_len) { + uint8_t buf[32]; + for (size_t i = 0; i < sequence_len;) { + uint8_t command = sequence[i]; + uint8_t delay = sequence[i + 1]; + uint8_t num_bytes = sequence[i + 2]; + buf[0] = cmd_byte; + buf[1] = command; + memcpy(&buf[2], &sequence[i + 3], num_bytes); + qp_comms_i2c_send_raw(device, buf, num_bytes + 2); + if (delay > 0) { + wait_ms(delay); + } + i += (3 + num_bytes); + } +} + +const painter_comms_with_command_vtable_t i2c_comms_cmddata_vtable = { + .base = + { + .comms_init = qp_comms_i2c_init, + .comms_start = qp_comms_i2c_start, + .comms_send = qp_comms_i2c_cmddata_send_data, + .comms_stop = qp_comms_i2c_stop, + }, + .send_command = qp_comms_i2c_cmddata_send_command, + .bulk_command_sequence = qp_comms_i2c_bulk_command_sequence, +}; + +#endif // QUANTUM_PAINTER_I2C_ENABLE diff --git a/drivers/painter/comms/qp_comms_i2c.h b/drivers/painter/comms/qp_comms_i2c.h new file mode 100644 index 00000000000..70083d6526f --- /dev/null +++ b/drivers/painter/comms/qp_comms_i2c.h @@ -0,0 +1,28 @@ +// Copyright 2022 Nick Brassel (@tzarc) +// SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + +#ifdef QUANTUM_PAINTER_I2C_ENABLE + +# include + +# include "gpio.h" +# include "qp_internal.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Base I2C support + +typedef struct qp_comms_i2c_config_t { + uint8_t chip_address; +} qp_comms_i2c_config_t; + +bool qp_comms_i2c_init(painter_device_t device); +bool qp_comms_i2c_start(painter_device_t device); +uint32_t qp_comms_i2c_send_data(painter_device_t device, const void* data, uint32_t byte_count); +void qp_comms_i2c_stop(painter_device_t device); + +extern const painter_comms_with_command_vtable_t i2c_comms_cmddata_vtable; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#endif // QUANTUM_PAINTER_I2C_ENABLE diff --git a/drivers/painter/comms/qp_comms_spi.c b/drivers/painter/comms/qp_comms_spi.c index 7534e844d83..9f52bc7d1f3 100644 --- a/drivers/painter/comms/qp_comms_spi.c +++ b/drivers/painter/comms/qp_comms_spi.c @@ -105,13 +105,21 @@ void qp_comms_spi_dc_reset_send_command(painter_device_t device, uint8_t cmd) { } void qp_comms_spi_dc_reset_bulk_command_sequence(painter_device_t device, const uint8_t *sequence, size_t sequence_len) { + painter_driver_t * driver = (painter_driver_t *)device; + qp_comms_spi_dc_reset_config_t *comms_config = (qp_comms_spi_dc_reset_config_t *)driver->comms_config; for (size_t i = 0; i < sequence_len;) { uint8_t command = sequence[i]; uint8_t delay = sequence[i + 1]; uint8_t num_bytes = sequence[i + 2]; qp_comms_spi_dc_reset_send_command(device, command); if (num_bytes > 0) { - qp_comms_spi_dc_reset_send_data(device, &sequence[i + 3], num_bytes); + if (comms_config->command_params_uses_command_pin) { + for (uint8_t j = 0; j < num_bytes; j++) { + qp_comms_spi_dc_reset_send_command(device, sequence[i + 3 + j]); + } + } else { + qp_comms_spi_dc_reset_send_data(device, &sequence[i + 3], num_bytes); + } } if (delay > 0) { wait_ms(delay); diff --git a/drivers/painter/comms/qp_comms_spi.h b/drivers/painter/comms/qp_comms_spi.h index b3da86d5733..ff323c3c10e 100644 --- a/drivers/painter/comms/qp_comms_spi.h +++ b/drivers/painter/comms/qp_comms_spi.h @@ -1,6 +1,5 @@ // Copyright 2021 Nick Brassel (@tzarc) // SPDX-License-Identifier: GPL-2.0-or-later - #pragma once #ifdef QUANTUM_PAINTER_SPI_ENABLE @@ -36,6 +35,7 @@ typedef struct qp_comms_spi_dc_reset_config_t { qp_comms_spi_config_t spi_config; pin_t dc_pin; pin_t reset_pin; + bool command_params_uses_command_pin; // keep D/C held low when sending command sequences for data bytes } qp_comms_spi_dc_reset_config_t; void qp_comms_spi_dc_reset_send_command(painter_device_t device, uint8_t cmd); diff --git a/drivers/painter/gc9a01/qp_gc9a01.c b/drivers/painter/gc9a01/qp_gc9a01.c index a2eb2cf57c0..fe6fa7a9d02 100644 --- a/drivers/painter/gc9a01/qp_gc9a01.c +++ b/drivers/painter/gc9a01/qp_gc9a01.c @@ -2,7 +2,6 @@ // Copyright 2023 Nick Brassel (@tzarc) // SPDX-License-Identifier: GPL-2.0-or-later -#include #include "qp_internal.h" #include "qp_comms.h" #include "qp_gc9a01.h" @@ -135,13 +134,14 @@ painter_device_t qp_gc9a01_make_spi_device(uint16_t panel_width, uint16_t panel_ driver->base.offset_y = 0; // SPI and other pin configuration - driver->base.comms_config = &driver->spi_dc_reset_config; - driver->spi_dc_reset_config.spi_config.chip_select_pin = chip_select_pin; - driver->spi_dc_reset_config.spi_config.divisor = spi_divisor; - driver->spi_dc_reset_config.spi_config.lsb_first = false; - driver->spi_dc_reset_config.spi_config.mode = spi_mode; - driver->spi_dc_reset_config.dc_pin = dc_pin; - driver->spi_dc_reset_config.reset_pin = reset_pin; + driver->base.comms_config = &driver->spi_dc_reset_config; + driver->spi_dc_reset_config.spi_config.chip_select_pin = chip_select_pin; + driver->spi_dc_reset_config.spi_config.divisor = spi_divisor; + driver->spi_dc_reset_config.spi_config.lsb_first = false; + driver->spi_dc_reset_config.spi_config.mode = spi_mode; + driver->spi_dc_reset_config.dc_pin = dc_pin; + driver->spi_dc_reset_config.reset_pin = reset_pin; + driver->spi_dc_reset_config.command_params_uses_command_pin = false; if (!qp_internal_register_device((painter_device_t)driver)) { memset(driver, 0, sizeof(tft_panel_dc_reset_painter_device_t)); diff --git a/drivers/painter/gc9a01/qp_gc9a01.h b/drivers/painter/gc9a01/qp_gc9a01.h index e2b19395640..31a3804b504 100644 --- a/drivers/painter/gc9a01/qp_gc9a01.h +++ b/drivers/painter/gc9a01/qp_gc9a01.h @@ -1,6 +1,5 @@ // Copyright 2021 Paul Cotter (@gr1mr3aver) // SPDX-License-Identifier: GPL-2.0-or-later - #pragma once #include "gpio.h" diff --git a/drivers/painter/gc9a01/qp_gc9a01_opcodes.h b/drivers/painter/gc9a01/qp_gc9a01_opcodes.h index 6ff4efe7a8a..828e42752b0 100644 --- a/drivers/painter/gc9a01/qp_gc9a01_opcodes.h +++ b/drivers/painter/gc9a01/qp_gc9a01_opcodes.h @@ -1,6 +1,5 @@ // Copyright 2021 Paul Cotter (@gr1mr3aver) // SPDX-License-Identifier: GPL-2.0-or-later - #pragma once //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/drivers/painter/generic/qp_rgb565_surface.c b/drivers/painter/generic/qp_rgb565_surface.c deleted file mode 100644 index 9c283e06872..00000000000 --- a/drivers/painter/generic/qp_rgb565_surface.c +++ /dev/null @@ -1,284 +0,0 @@ -// Copyright 2022 Nick Brassel (@tzarc) -// SPDX-License-Identifier: GPL-2.0-or-later -#include "color.h" -#include "qp_rgb565_surface.h" -#include "qp_draw.h" - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// Common - -// Device definition -typedef struct rgb565_surface_painter_device_t { - painter_driver_t base; // must be first, so it can be cast to/from the painter_device_t* type - - // The target buffer - uint16_t *buffer; - - // Manually manage the viewport for streaming pixel data to the display - uint16_t viewport_l; - uint16_t viewport_t; - uint16_t viewport_r; - uint16_t viewport_b; - - // Current write location to the display when streaming pixel data - uint16_t pixdata_x; - uint16_t pixdata_y; - - // Maintain a dirty region so we can stream only what we need - bool is_dirty; - uint16_t dirty_l; - uint16_t dirty_t; - uint16_t dirty_r; - uint16_t dirty_b; - -} rgb565_surface_painter_device_t; - -// Driver storage -rgb565_surface_painter_device_t surface_drivers[RGB565_SURFACE_NUM_DEVICES] = {0}; - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// Helpers - -static inline void increment_pixdata_location(rgb565_surface_painter_device_t *surface) { - // Increment the X-position - surface->pixdata_x++; - - // If the x-coord has gone past the right-side edge, loop it back around and increment the y-coord - if (surface->pixdata_x > surface->viewport_r) { - surface->pixdata_x = surface->viewport_l; - surface->pixdata_y++; - } - - // If the y-coord has gone past the bottom, loop it back to the top - if (surface->pixdata_y > surface->viewport_b) { - surface->pixdata_y = surface->viewport_t; - } -} - -static inline void setpixel(rgb565_surface_painter_device_t *surface, uint16_t x, uint16_t y, uint16_t rgb565) { - // Skip messing with the dirty info if the original value already matches - if (surface->buffer[y * surface->base.panel_width + x] != rgb565) { - // Maintain dirty region - if (surface->dirty_l > x) { - surface->dirty_l = x; - } - if (surface->dirty_r < x) { - surface->dirty_r = x; - } - if (surface->dirty_t > y) { - surface->dirty_t = y; - } - if (surface->dirty_b < y) { - surface->dirty_b = y; - } - - // Always dirty after a setpixel - surface->is_dirty = true; - - // Update the pixel data in the buffer - surface->buffer[y * surface->base.panel_width + x] = rgb565; - } -} - -static inline void append_pixel(rgb565_surface_painter_device_t *surface, uint16_t rgb565) { - setpixel(surface, surface->pixdata_x, surface->pixdata_y, rgb565); - increment_pixdata_location(surface); -} - -static inline void stream_pixdata(rgb565_surface_painter_device_t *surface, const uint16_t *data, uint32_t native_pixel_count) { - for (uint32_t pixel_counter = 0; pixel_counter < native_pixel_count; ++pixel_counter) { - append_pixel(surface, data[pixel_counter]); - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// Driver vtable - -static bool qp_rgb565_surface_init(painter_device_t device, painter_rotation_t rotation) { - painter_driver_t * driver = (painter_driver_t *)device; - rgb565_surface_painter_device_t *surface = (rgb565_surface_painter_device_t *)driver; - memset(surface->buffer, 0, driver->panel_width * driver->panel_height * driver->native_bits_per_pixel / 8); - return true; -} - -static bool qp_rgb565_surface_power(painter_device_t device, bool power_on) { - // No-op. - return true; -} - -static bool qp_rgb565_surface_clear(painter_device_t device) { - painter_driver_t *driver = (painter_driver_t *)device; - driver->driver_vtable->init(device, driver->rotation); // Re-init the surface - return true; -} - -static bool qp_rgb565_surface_flush(painter_device_t device) { - painter_driver_t * driver = (painter_driver_t *)device; - rgb565_surface_painter_device_t *surface = (rgb565_surface_painter_device_t *)driver; - surface->dirty_l = surface->dirty_t = UINT16_MAX; - surface->dirty_r = surface->dirty_b = 0; - surface->is_dirty = false; - return true; -} - -static bool qp_rgb565_surface_viewport(painter_device_t device, uint16_t left, uint16_t top, uint16_t right, uint16_t bottom) { - painter_driver_t * driver = (painter_driver_t *)device; - rgb565_surface_painter_device_t *surface = (rgb565_surface_painter_device_t *)driver; - - // Set the viewport locations - surface->viewport_l = left; - surface->viewport_t = top; - surface->viewport_r = right; - surface->viewport_b = bottom; - - // Reset the write location to the top left - surface->pixdata_x = left; - surface->pixdata_y = top; - return true; -} - -// Stream pixel data to the current write position in GRAM -static bool qp_rgb565_surface_pixdata(painter_device_t device, const void *pixel_data, uint32_t native_pixel_count) { - painter_driver_t * driver = (painter_driver_t *)device; - rgb565_surface_painter_device_t *surface = (rgb565_surface_painter_device_t *)driver; - stream_pixdata(surface, (const uint16_t *)pixel_data, native_pixel_count); - return true; -} - -// Pixel colour conversion -static bool qp_rgb565_surface_palette_convert_rgb565_swapped(painter_device_t device, int16_t palette_size, qp_pixel_t *palette) { - for (int16_t i = 0; i < palette_size; ++i) { - RGB rgb = hsv_to_rgb_nocie((HSV){palette[i].hsv888.h, palette[i].hsv888.s, palette[i].hsv888.v}); - uint16_t rgb565 = (((uint16_t)rgb.r) >> 3) << 11 | (((uint16_t)rgb.g) >> 2) << 5 | (((uint16_t)rgb.b) >> 3); - palette[i].rgb565 = __builtin_bswap16(rgb565); - } - return true; -} - -// Append pixels to the target location, keyed by the pixel index -static bool qp_rgb565_surface_append_pixels_rgb565(painter_device_t device, uint8_t *target_buffer, qp_pixel_t *palette, uint32_t pixel_offset, uint32_t pixel_count, uint8_t *palette_indices) { - uint16_t *buf = (uint16_t *)target_buffer; - for (uint32_t i = 0; i < pixel_count; ++i) { - buf[pixel_offset + i] = palette[palette_indices[i]].rgb565; - } - return true; -} - -// Append data to the target location -static bool qp_rgb565_surface_append_pixdata(painter_device_t device, uint8_t *target_buffer, uint32_t pixdata_offset, uint8_t pixdata_byte) { - target_buffer[pixdata_offset] = pixdata_byte; - return true; -} - -const painter_driver_vtable_t rgb565_surface_driver_vtable = { - .init = qp_rgb565_surface_init, - .power = qp_rgb565_surface_power, - .clear = qp_rgb565_surface_clear, - .flush = qp_rgb565_surface_flush, - .pixdata = qp_rgb565_surface_pixdata, - .viewport = qp_rgb565_surface_viewport, - .palette_convert = qp_rgb565_surface_palette_convert_rgb565_swapped, - .append_pixels = qp_rgb565_surface_append_pixels_rgb565, - .append_pixdata = qp_rgb565_surface_append_pixdata, -}; - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// Comms vtable - -static bool qp_rgb565_surface_comms_init(painter_device_t device) { - // No-op. - return true; -} -static bool qp_rgb565_surface_comms_start(painter_device_t device) { - // No-op. - return true; -} -static void qp_rgb565_surface_comms_stop(painter_device_t device) { - // No-op. -} -uint32_t qp_rgb565_surface_comms_send(painter_device_t device, const void *data, uint32_t byte_count) { - // No-op. - return byte_count; -} - -painter_comms_vtable_t rgb565_surface_driver_comms_vtable = { - // These are all effective no-op's because they're not actually needed. - .comms_init = qp_rgb565_surface_comms_init, - .comms_start = qp_rgb565_surface_comms_start, - .comms_stop = qp_rgb565_surface_comms_stop, - .comms_send = qp_rgb565_surface_comms_send}; - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// Factory function for creating a handle to an rgb565 surface - -painter_device_t qp_rgb565_make_surface(uint16_t panel_width, uint16_t panel_height, void *buffer) { - for (uint32_t i = 0; i < RGB565_SURFACE_NUM_DEVICES; ++i) { - rgb565_surface_painter_device_t *driver = &surface_drivers[i]; - if (!driver->base.driver_vtable) { - driver->base.driver_vtable = &rgb565_surface_driver_vtable; - driver->base.comms_vtable = &rgb565_surface_driver_comms_vtable; - driver->base.native_bits_per_pixel = 16; // RGB565 - driver->base.panel_width = panel_width; - driver->base.panel_height = panel_height; - driver->base.rotation = QP_ROTATION_0; - driver->base.offset_x = 0; - driver->base.offset_y = 0; - driver->buffer = (uint16_t *)buffer; - return (painter_device_t)driver; - } - } - return NULL; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// Drawing routine to copy out the dirty region and send it to another device - -bool qp_rgb565_surface_draw(painter_device_t surface, painter_device_t display, uint16_t x, uint16_t y) { - painter_driver_t * surface_driver = (painter_driver_t *)surface; - rgb565_surface_painter_device_t *surface_handle = (rgb565_surface_painter_device_t *)surface_driver; - - // If we're not dirty... we're done. - if (!surface_handle->is_dirty) { - return true; - } - - // Set the target drawing area - bool ok = qp_viewport(display, x + surface_handle->dirty_l, y + surface_handle->dirty_t, x + surface_handle->dirty_r, y + surface_handle->dirty_b); - if (!ok) { - return false; - } - - // Housekeeping of the amount of pixels to transfer - uint32_t total_pixel_count = QUANTUM_PAINTER_PIXDATA_BUFFER_SIZE / sizeof(uint16_t); - uint32_t pixel_counter = 0; - uint16_t *target_buffer = (uint16_t *)qp_internal_global_pixdata_buffer; - - // Fill the global pixdata area so that we can start transferring to the panel - for (uint16_t y = surface_handle->dirty_t; y <= surface_handle->dirty_b; ++y) { - for (uint16_t x = surface_handle->dirty_l; x <= surface_handle->dirty_r; ++x) { - // Update the target buffer - target_buffer[pixel_counter++] = surface_handle->buffer[y * surface_handle->base.panel_width + x]; - - // If we've accumulated enough data, send it - if (pixel_counter == total_pixel_count) { - ok = qp_pixdata(display, qp_internal_global_pixdata_buffer, pixel_counter); - if (!ok) { - return false; - } - // Reset the counter - pixel_counter = 0; - } - } - } - - // If there's any leftover data, send it - if (pixel_counter > 0) { - ok = qp_pixdata(display, qp_internal_global_pixdata_buffer, pixel_counter); - if (!ok) { - return false; - } - } - - // Clear the dirty info for the surface - return qp_flush(surface); -} diff --git a/drivers/painter/generic/qp_rgb565_surface.h b/drivers/painter/generic/qp_rgb565_surface.h deleted file mode 100644 index 19e919bb913..00000000000 --- a/drivers/painter/generic/qp_rgb565_surface.h +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2022 Nick Brassel (@tzarc) -// SPDX-License-Identifier: GPL-2.0-or-later -#include "qp_internal.h" - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// Quantum Painter RGB565 surface configurables (add to your keyboard's config.h) - -#ifndef RGB565_SURFACE_NUM_DEVICES -/** - * @def This controls the maximum number of surface devices that Quantum Painter can use at any one time. - * Increasing this number allows for multiple framebuffers to be used. Each requires its own RAM allocation. - */ -# define RGB565_SURFACE_NUM_DEVICES 1 -#endif - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// Forward declarations - -#ifdef QUANTUM_PAINTER_RGB565_SURFACE_ENABLE -/** - * Factory method for an RGB565 surface (aka framebuffer). - * - * @param panel_width[in] the width of the display panel - * @param panel_height[in] the height of the display panel - * @param buffer[in] pointer to a preallocated buffer of size `(sizeof(uint16_t) * panel_width * panel_height)` - * @return the device handle used with all drawing routines in Quantum Painter - */ -painter_device_t qp_rgb565_make_surface(uint16_t panel_width, uint16_t panel_height, void *buffer); - -/** - * Helper method to draw the dirty contents of the framebuffer to the target device. - * - * After successful completion, the dirty area is reset. - * - * @param surface[in] the surface to copy from - * @param display[in] the display to copy into - * @param x[in] the x-location of the original position of the framebuffer - * @param y[in] the y-location of the original position of the framebuffer - * @return whether the draw operation completed successfully - */ -bool qp_rgb565_surface_draw(painter_device_t surface, painter_device_t display, uint16_t x, uint16_t y); -#endif // QUANTUM_PAINTER_RGB565_SURFACE_ENABLE diff --git a/drivers/painter/generic/qp_surface.h b/drivers/painter/generic/qp_surface.h new file mode 100644 index 00000000000..a2917936492 --- /dev/null +++ b/drivers/painter/generic/qp_surface.h @@ -0,0 +1,67 @@ +// Copyright 2022 Nick Brassel (@tzarc) +// SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + +#include "qp_internal.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Quantum Painter surface helpers + +// Helper for determining buffer size required for a surface +#define SURFACE_REQUIRED_BUFFER_BYTE_SIZE(w, h, bpp) ((((w) * (h) * (bpp)) + 7) / 8) + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Quantum Painter surface configurables (add to your keyboard's config.h) + +#ifndef SURFACE_NUM_DEVICES +/** + * @def This controls the maximum number of surface devices that Quantum Painter can use at any one time. + * Increasing this number allows for multiple framebuffers to be used. Each requires its own RAM allocation. + */ +# define SURFACE_NUM_DEVICES 1 +#endif + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Forward declarations + +#ifdef QUANTUM_PAINTER_SURFACE_ENABLE + +// Surface struct +struct surface_painter_device_t; +typedef struct surface_painter_device_t surface_painter_device_t; + +/** + * Factory method for an RGB565 surface (aka framebuffer). + * + * @param panel_width[in] the width of the display panel + * @param panel_height[in] the height of the display panel + * @param buffer[in] pointer to a preallocated uint8_t buffer of size `SURFACE_REQUIRED_BUFFER_BYTE_SIZE(panel_width, panel_height, 16)` + * @return the device handle used with all drawing routines in Quantum Painter + */ +painter_device_t qp_make_rgb565_surface(uint16_t panel_width, uint16_t panel_height, void *buffer); + +/** + * Factory method for a 1bpp monochrome surface (aka framebuffer). + * + * @param panel_width[in] the width of the display panel + * @param panel_height[in] the height of the display panel + * @param buffer[in] pointer to a preallocated uint8_t buffer of size `SURFACE_REQUIRED_BUFFER_BYTE_SIZE(panel_width, panel_height, 1)` + * @return the device handle used with all drawing routines in Quantum Painter + */ +painter_device_t qp_make_mono1bpp_surface(uint16_t panel_width, uint16_t panel_height, void *buffer); + +/** + * Helper method to draw the contents of the framebuffer to the target device. + * + * After successful completion, the dirty area is reset. + * + * @param surface[in] the surface to copy from + * @param target[in] the target device to copy into + * @param x[in] the x-location of the original position of the framebuffer + * @param y[in] the y-location of the original position of the framebuffer + * @param entire_surface[in] whether the entire surface should be drawn, instead of just the dirty region + * @return whether the draw operation completed successfully + */ +bool qp_surface_draw(painter_device_t surface, painter_device_t target, uint16_t x, uint16_t y, bool entire_surface); + +#endif // QUANTUM_PAINTER_SURFACE_ENABLE diff --git a/drivers/painter/generic/qp_surface_common.c b/drivers/painter/generic/qp_surface_common.c new file mode 100644 index 00000000000..2da96c73ac7 --- /dev/null +++ b/drivers/painter/generic/qp_surface_common.c @@ -0,0 +1,141 @@ +// Copyright 2022 Nick Brassel (@tzarc) +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "color.h" +#include "qp_draw.h" +#include "qp_surface_internal.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Driver storage + +surface_painter_device_t surface_drivers[SURFACE_NUM_DEVICES] = {0}; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Helpers + +void qp_surface_increment_pixdata_location(surface_viewport_data_t *viewport) { + // Increment the X-position + viewport->pixdata_x++; + + // If the x-coord has gone past the right-side edge, loop it back around and increment the y-coord + if (viewport->pixdata_x > viewport->viewport_r) { + viewport->pixdata_x = viewport->viewport_l; + viewport->pixdata_y++; + } + + // If the y-coord has gone past the bottom, loop it back to the top + if (viewport->pixdata_y > viewport->viewport_b) { + viewport->pixdata_y = viewport->viewport_t; + } +} + +void qp_surface_update_dirty(surface_dirty_data_t *dirty, uint16_t x, uint16_t y) { + // Maintain dirty region + if (dirty->l > x) { + dirty->l = x; + dirty->is_dirty = true; + } + if (dirty->r < x) { + dirty->r = x; + dirty->is_dirty = true; + } + if (dirty->t > y) { + dirty->t = y; + dirty->is_dirty = true; + } + if (dirty->b < y) { + dirty->b = y; + dirty->is_dirty = true; + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Driver vtable + +bool qp_surface_init(painter_device_t device, painter_rotation_t rotation) { + painter_driver_t * driver = (painter_driver_t *)device; + surface_painter_device_t *surface = (surface_painter_device_t *)driver; + memset(surface->buffer, 0, SURFACE_REQUIRED_BUFFER_BYTE_SIZE(driver->panel_width, driver->panel_height, driver->native_bits_per_pixel)); + + surface->dirty.l = 0; + surface->dirty.t = 0; + surface->dirty.r = surface->base.panel_width - 1; + surface->dirty.b = surface->base.panel_height - 1; + surface->dirty.is_dirty = true; + + return true; +} + +bool qp_surface_power(painter_device_t device, bool power_on) { + // No-op. + return true; +} + +bool qp_surface_clear(painter_device_t device) { + painter_driver_t *driver = (painter_driver_t *)device; + driver->driver_vtable->init(device, driver->rotation); // Re-init the surface + return true; +} + +bool qp_surface_flush(painter_device_t device) { + painter_driver_t * driver = (painter_driver_t *)device; + surface_painter_device_t *surface = (surface_painter_device_t *)driver; + surface->dirty.l = surface->dirty.t = UINT16_MAX; + surface->dirty.r = surface->dirty.b = 0; + surface->dirty.is_dirty = false; + return true; +} + +bool qp_surface_viewport(painter_device_t device, uint16_t left, uint16_t top, uint16_t right, uint16_t bottom) { + painter_driver_t * driver = (painter_driver_t *)device; + surface_painter_device_t *surface = (surface_painter_device_t *)driver; + + // Set the viewport locations + surface->viewport.viewport_l = left; + surface->viewport.viewport_t = top; + surface->viewport.viewport_r = right; + surface->viewport.viewport_b = bottom; + + // Reset the write location to the top left + surface->viewport.pixdata_x = left; + surface->viewport.pixdata_y = top; + return true; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Drawing routine to copy out the dirty region and send it to another device + +bool qp_surface_draw(painter_device_t surface, painter_device_t target, uint16_t x, uint16_t y, bool entire_surface) { + painter_driver_t * surface_driver = (painter_driver_t *)surface; + surface_painter_device_t *surface_handle = (surface_painter_device_t *)surface_driver; + painter_driver_t * target_driver = (painter_driver_t *)target; + + // If we're not dirty... we're done. + if (!surface_handle->dirty.is_dirty) { + qp_dprintf("qp_surface_draw: ok (not dirty, skipping)\n"); + return true; + } + + // If we have incompatible bit depths, drop out + if (surface_driver->native_bits_per_pixel != target_driver->native_bits_per_pixel) { + qp_dprintf("qp_surface_draw: fail (incompatible bpp: surface=%d, target=%d)\n", (int)surface_driver->native_bits_per_pixel, (int)target_driver->native_bits_per_pixel); + return false; + } + + // Offload to the pixdata transfer function + surface_painter_driver_vtable_t *vtable = (surface_painter_driver_vtable_t *)surface_driver->driver_vtable; + bool ok = vtable->target_pixdata_transfer(surface_driver, target_driver, x, y, entire_surface); + if (!ok) { + qp_dprintf("qp_surface_draw: fail (could not transfer pixel data)\n"); + return false; + } + + // Clear the dirty info for the surface + ok = qp_flush(surface); + if (!ok) { + qp_dprintf("qp_surface_draw: fail (could not flush)\n"); + return false; + } + qp_dprintf("qp_surface_draw: ok\n"); + return true; +} diff --git a/drivers/painter/generic/qp_surface_internal.h b/drivers/painter/generic/qp_surface_internal.h new file mode 100644 index 00000000000..71f82e924d1 --- /dev/null +++ b/drivers/painter/generic/qp_surface_internal.h @@ -0,0 +1,119 @@ +// Copyright 2022 Nick Brassel (@tzarc) +// SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + +#ifdef QUANTUM_PAINTER_SURFACE_ENABLE + +# include "qp_surface.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Internal declarations + +// Surface vtable +typedef struct surface_painter_driver_vtable_t { + painter_driver_vtable_t base; // must be first, so it can be cast to/from the painter_driver_vtable_t* type + + bool (*target_pixdata_transfer)(painter_driver_t *surface_driver, painter_driver_t *target_driver, uint16_t x, uint16_t y, bool entire_surface); +} surface_painter_driver_vtable_t; + +typedef struct surface_dirty_data_t { + bool is_dirty; + uint16_t l; + uint16_t t; + uint16_t r; + uint16_t b; +} surface_dirty_data_t; + +typedef struct surface_viewport_data_t { + // Manually manage the viewport for streaming pixel data to the display + uint16_t viewport_l; + uint16_t viewport_t; + uint16_t viewport_r; + uint16_t viewport_b; + + // Current write location to the display when streaming pixel data + uint16_t pixdata_x; + uint16_t pixdata_y; +} surface_viewport_data_t; + +// Surface struct +typedef struct surface_painter_device_t { + painter_driver_t base; // must be first, so it can be cast to/from the painter_device_t* type + + // The target buffer + union { + void * buffer; + uint8_t * u8buffer; + uint16_t *u16buffer; + }; + + // Manually manage the viewport for streaming pixel data to the display + surface_viewport_data_t viewport; + + // Maintain a dirty region so we can stream only what we need + surface_dirty_data_t dirty; +} surface_painter_device_t; + +/** + * Factory method for an RGB565 surface (aka framebuffer). Accepts an external device table. + * + * @param device_table[in] the table of devices to use for instantiation + * @param device_table_len[in] the length of the table of devices + * @param panel_width[in] the width of the display panel + * @param panel_height[in] the height of the display panel + * @param buffer[in] pointer to a preallocated uint8_t buffer of size `SURFACE_REQUIRED_BUFFER_BYTE_SIZE(panel_width, panel_height, 16)` + * @return the device handle used with all drawing routines in Quantum Painter + */ +painter_device_t qp_make_rgb565_surface_advanced(surface_painter_device_t *device_table, size_t device_table_len, uint16_t panel_width, uint16_t panel_height, void *buffer); + +/** + * Factory method for a 1bpp monochrome surface (aka framebuffer). + * + * @param device_table[in] the table of devices to use for instantiation + * @param device_table_len[in] the length of the table of devices + * @param panel_width[in] the width of the display panel + * @param panel_height[in] the height of the display panel + * @param buffer[in] pointer to a preallocated uint8_t buffer of size `SURFACE_REQUIRED_BUFFER_BYTE_SIZE(panel_width, panel_height, 16)` + * @return the device handle used with all drawing routines in Quantum Painter + */ +painter_device_t qp_make_mono1bpp_surface_advanced(surface_painter_device_t *device_table, size_t device_table_len, uint16_t panel_width, uint16_t panel_height, void *buffer); + +// Driver storage +extern surface_painter_device_t surface_drivers[SURFACE_NUM_DEVICES]; + +// Surface common APIs +bool qp_surface_init(painter_device_t device, painter_rotation_t rotation); +bool qp_surface_power(painter_device_t device, bool power_on); +bool qp_surface_clear(painter_device_t device); +bool qp_surface_flush(painter_device_t device); +bool qp_surface_viewport(painter_device_t device, uint16_t left, uint16_t top, uint16_t right, uint16_t bottom); +void qp_surface_increment_pixdata_location(surface_viewport_data_t *viewport); +void qp_surface_update_dirty(surface_dirty_data_t *dirty, uint16_t x, uint16_t y); + +#endif // QUANTUM_PAINTER_SURFACE_ENABLE + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Factory functions for creating a handle to a surface + +#define SURFACE_FACTORY_FUNCTION_IMPL(function_name, vtable, bpp) \ + painter_device_t(function_name##_advanced)(surface_painter_device_t * device_table, size_t device_table_len, uint16_t panel_width, uint16_t panel_height, void *buffer) { \ + for (uint32_t i = 0; i < device_table_len; ++i) { \ + surface_painter_device_t *driver = &device_table[i]; \ + if (!driver->base.driver_vtable) { \ + driver->base.driver_vtable = (painter_driver_vtable_t *)&(vtable); \ + driver->base.native_bits_per_pixel = (bpp); \ + driver->base.comms_vtable = &dummy_comms_vtable; \ + driver->base.panel_width = panel_width; \ + driver->base.panel_height = panel_height; \ + driver->base.rotation = QP_ROTATION_0; \ + driver->base.offset_x = 0; \ + driver->base.offset_y = 0; \ + driver->buffer = buffer; \ + return (painter_device_t)driver; \ + } \ + } \ + return NULL; \ + } \ + painter_device_t(function_name)(uint16_t panel_width, uint16_t panel_height, void *buffer) { \ + return (function_name##_advanced)(surface_drivers, SURFACE_NUM_DEVICES, panel_width, panel_height, buffer); \ + } diff --git a/drivers/painter/generic/qp_surface_mono1bpp.c b/drivers/painter/generic/qp_surface_mono1bpp.c new file mode 100644 index 00000000000..c66b56519da --- /dev/null +++ b/drivers/painter/generic/qp_surface_mono1bpp.c @@ -0,0 +1,113 @@ +// Copyright 2022 Nick Brassel (@tzarc) +// SPDX-License-Identifier: GPL-2.0-or-later + +#ifdef QUANTUM_PAINTER_SURFACE_ENABLE + +# include "color.h" +# include "qp_draw.h" +# include "qp_surface_internal.h" +# include "qp_comms_dummy.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Surface driver impl: mono1bpp + +static inline void setpixel_mono1bpp(surface_painter_device_t *surface, uint16_t x, uint16_t y, bool mono_pixel) { + uint16_t w = surface->base.panel_width; + uint16_t h = surface->base.panel_height; + + // Drop out if it's off-screen + if (x >= w || y >= h) { + return; + } + + // Figure out which location needs to be updated + uint32_t pixel_num = y * w + x; + uint32_t byte_offset = pixel_num / 8; + uint8_t bit_offset = pixel_num % 8; + bool curr_val = (surface->u8buffer[byte_offset] & (1 << bit_offset)) ? true : false; + + // Skip messing with the dirty info if the original value already matches + if (curr_val != mono_pixel) { + // Update the dirty region + qp_surface_update_dirty(&surface->dirty, x, y); + + // Update the pixel data in the buffer + if (mono_pixel) { + surface->u8buffer[byte_offset] |= (1 << bit_offset); + } else { + surface->u8buffer[byte_offset] &= ~(1 << bit_offset); + } + } +} + +static inline void append_pixel_mono1bpp(surface_painter_device_t *surface, bool mono_pixel) { + setpixel_mono1bpp(surface, surface->viewport.pixdata_x, surface->viewport.pixdata_y, mono_pixel); + qp_surface_increment_pixdata_location(&surface->viewport); +} + +static inline void stream_pixdata_mono1bpp(surface_painter_device_t *surface, const uint8_t *data, uint32_t native_pixel_count) { + for (uint32_t pixel_counter = 0; pixel_counter < native_pixel_count; ++pixel_counter) { + uint32_t byte_offset = pixel_counter / 8; + uint8_t bit_offset = pixel_counter % 8; + append_pixel_mono1bpp(surface, (data[byte_offset] & (1 << bit_offset)) ? true : false); + } +} + +// Stream pixel data to the current write position in GRAM +static bool qp_surface_pixdata_mono1bpp(painter_device_t device, const void *pixel_data, uint32_t native_pixel_count) { + painter_driver_t * driver = (painter_driver_t *)device; + surface_painter_device_t *surface = (surface_painter_device_t *)driver; + stream_pixdata_mono1bpp(surface, (const uint8_t *)pixel_data, native_pixel_count); + return true; +} + +// Pixel colour conversion +static bool qp_surface_palette_convert_mono1bpp(painter_device_t device, int16_t palette_size, qp_pixel_t *palette) { + for (int16_t i = 0; i < palette_size; ++i) { + palette[i].mono = (palette[i].hsv888.v > 127) ? 1 : 0; + } + return true; +} + +// Append pixels to the target location, keyed by the pixel index +static bool qp_surface_append_pixels_mono1bpp(painter_device_t device, uint8_t *target_buffer, qp_pixel_t *palette, uint32_t pixel_offset, uint32_t pixel_count, uint8_t *palette_indices) { + for (uint32_t i = 0; i < pixel_count; ++i) { + uint32_t pixel_num = pixel_offset + i; + uint32_t byte_offset = pixel_num / 8; + uint8_t bit_offset = pixel_num % 8; + if (palette[palette_indices[i]].mono) { + target_buffer[byte_offset] |= (1 << bit_offset); + } else { + target_buffer[byte_offset] &= ~(1 << bit_offset); + } + } + return true; +} + +static bool mono1bpp_target_pixdata_transfer(painter_driver_t *surface_driver, painter_driver_t *target_driver, uint16_t x, uint16_t y, bool entire_surface) { + return false; // Not yet supported. +} + +static bool qp_surface_append_pixdata_mono1bpp(painter_device_t device, uint8_t *target_buffer, uint32_t pixdata_offset, uint8_t pixdata_byte) { + return false; // Just use 1bpp images. +} + +const surface_painter_driver_vtable_t mono1bpp_surface_driver_vtable = { + .base = + { + .init = qp_surface_init, + .power = qp_surface_power, + .clear = qp_surface_clear, + .flush = qp_surface_flush, + .pixdata = qp_surface_pixdata_mono1bpp, + .viewport = qp_surface_viewport, + .palette_convert = qp_surface_palette_convert_mono1bpp, + .append_pixels = qp_surface_append_pixels_mono1bpp, + .append_pixdata = qp_surface_append_pixdata_mono1bpp, + }, + .target_pixdata_transfer = mono1bpp_target_pixdata_transfer, +}; + +SURFACE_FACTORY_FUNCTION_IMPL(qp_make_mono1bpp_surface, mono1bpp_surface_driver_vtable, 1); + +#endif // QUANTUM_PAINTER_SURFACE_ENABLE diff --git a/drivers/painter/generic/qp_surface_rgb565.c b/drivers/painter/generic/qp_surface_rgb565.c new file mode 100644 index 00000000000..8883ed541d8 --- /dev/null +++ b/drivers/painter/generic/qp_surface_rgb565.c @@ -0,0 +1,145 @@ +// Copyright 2022 Nick Brassel (@tzarc) +// SPDX-License-Identifier: GPL-2.0-or-later + +#ifdef QUANTUM_PAINTER_SURFACE_ENABLE + +# include "color.h" +# include "qp_draw.h" +# include "qp_surface_internal.h" +# include "qp_comms_dummy.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Surface driver impl: rgb565 + +static inline void setpixel_rgb565(surface_painter_device_t *surface, uint16_t x, uint16_t y, uint16_t rgb565) { + uint16_t w = surface->base.panel_width; + uint16_t h = surface->base.panel_height; + + // Drop out if it's off-screen + if (x >= w || y >= h) { + return; + } + + // Skip messing with the dirty info if the original value already matches + if (surface->u16buffer[y * w + x] != rgb565) { + // Update the dirty region + qp_surface_update_dirty(&surface->dirty, x, y); + + // Update the pixel data in the buffer + surface->u16buffer[y * w + x] = rgb565; + } +} + +static inline void append_pixel_rgb565(surface_painter_device_t *surface, uint16_t rgb565) { + setpixel_rgb565(surface, surface->viewport.pixdata_x, surface->viewport.pixdata_y, rgb565); + qp_surface_increment_pixdata_location(&surface->viewport); +} + +static inline void stream_pixdata_rgb565(surface_painter_device_t *surface, const uint16_t *data, uint32_t native_pixel_count) { + for (uint32_t pixel_counter = 0; pixel_counter < native_pixel_count; ++pixel_counter) { + append_pixel_rgb565(surface, data[pixel_counter]); + } +} + +// Stream pixel data to the current write position in GRAM +static bool qp_surface_pixdata_rgb565(painter_device_t device, const void *pixel_data, uint32_t native_pixel_count) { + painter_driver_t * driver = (painter_driver_t *)device; + surface_painter_device_t *surface = (surface_painter_device_t *)driver; + stream_pixdata_rgb565(surface, (const uint16_t *)pixel_data, native_pixel_count); + return true; +} + +// Pixel colour conversion +static bool qp_surface_palette_convert_rgb565_swapped(painter_device_t device, int16_t palette_size, qp_pixel_t *palette) { + for (int16_t i = 0; i < palette_size; ++i) { + RGB rgb = hsv_to_rgb_nocie((HSV){palette[i].hsv888.h, palette[i].hsv888.s, palette[i].hsv888.v}); + uint16_t rgb565 = (((uint16_t)rgb.r) >> 3) << 11 | (((uint16_t)rgb.g) >> 2) << 5 | (((uint16_t)rgb.b) >> 3); + palette[i].rgb565 = __builtin_bswap16(rgb565); + } + return true; +} + +// Append pixels to the target location, keyed by the pixel index +static bool qp_surface_append_pixels_rgb565(painter_device_t device, uint8_t *target_buffer, qp_pixel_t *palette, uint32_t pixel_offset, uint32_t pixel_count, uint8_t *palette_indices) { + uint16_t *buf = (uint16_t *)target_buffer; + for (uint32_t i = 0; i < pixel_count; ++i) { + buf[pixel_offset + i] = palette[palette_indices[i]].rgb565; + } + return true; +} + +static bool rgb565_target_pixdata_transfer(painter_driver_t *surface_driver, painter_driver_t *target_driver, uint16_t x, uint16_t y, bool entire_surface) { + surface_painter_device_t *surface_handle = (surface_painter_device_t *)surface_driver; + + uint16_t l = entire_surface ? 0 : surface_handle->dirty.l; + uint16_t t = entire_surface ? 0 : surface_handle->dirty.t; + uint16_t r = entire_surface ? (surface_handle->base.panel_width - 1) : surface_handle->dirty.r; + uint16_t b = entire_surface ? (surface_handle->base.panel_height - 1) : surface_handle->dirty.b; + + // Set the target drawing area + bool ok = qp_viewport((painter_device_t)target_driver, x + l, y + t, x + r, y + b); + if (!ok) { + qp_dprintf("rgb565_target_pixdata_transfer: fail (could not set target viewport)\n"); + return false; + } + + // Housekeeping of the amount of pixels to transfer + uint32_t total_pixel_count = (8 * QUANTUM_PAINTER_PIXDATA_BUFFER_SIZE) / surface_driver->native_bits_per_pixel; + uint32_t pixel_counter = 0; + uint16_t *target_buffer = (uint16_t *)qp_internal_global_pixdata_buffer; + + // Fill the global pixdata area so that we can start transferring to the panel + for (uint16_t y = t; y <= b; ++y) { + for (uint16_t x = l; x <= r; ++x) { + // Update the target buffer + target_buffer[pixel_counter++] = surface_handle->u16buffer[y * surface_handle->base.panel_width + x]; + + // If we've accumulated enough data, send it + if (pixel_counter == total_pixel_count) { + ok = qp_pixdata((painter_device_t)target_driver, qp_internal_global_pixdata_buffer, pixel_counter); + if (!ok) { + qp_dprintf("rgb565_target_pixdata_transfer: fail (could not stream pixdata to target)\n"); + return false; + } + // Reset the counter + pixel_counter = 0; + } + } + } + + // If there's any leftover data, send it + if (pixel_counter > 0) { + ok = qp_pixdata((painter_device_t)target_driver, qp_internal_global_pixdata_buffer, pixel_counter); + if (!ok) { + qp_dprintf("rgb565_target_pixdata_transfer: fail (could not stream pixdata to target)\n"); + return false; + } + } + + return true; +} + +static bool qp_surface_append_pixdata_rgb565(painter_device_t device, uint8_t *target_buffer, uint32_t pixdata_offset, uint8_t pixdata_byte) { + target_buffer[pixdata_offset] = pixdata_byte; + return true; +} + +const surface_painter_driver_vtable_t rgb565_surface_driver_vtable = { + .base = + { + .init = qp_surface_init, + .power = qp_surface_power, + .clear = qp_surface_clear, + .flush = qp_surface_flush, + .pixdata = qp_surface_pixdata_rgb565, + .viewport = qp_surface_viewport, + .palette_convert = qp_surface_palette_convert_rgb565_swapped, + .append_pixels = qp_surface_append_pixels_rgb565, + .append_pixdata = qp_surface_append_pixdata_rgb565, + }, + .target_pixdata_transfer = rgb565_target_pixdata_transfer, +}; + +SURFACE_FACTORY_FUNCTION_IMPL(qp_make_rgb565_surface, rgb565_surface_driver_vtable, 16); + +#endif // QUANTUM_PAINTER_SURFACE_ENABLE diff --git a/drivers/painter/ili9xxx/qp_ili9163.c b/drivers/painter/ili9xxx/qp_ili9163.c index a75be579048..7f439dc317a 100644 --- a/drivers/painter/ili9xxx/qp_ili9163.c +++ b/drivers/painter/ili9xxx/qp_ili9163.c @@ -103,13 +103,14 @@ painter_device_t qp_ili9163_make_spi_device(uint16_t panel_width, uint16_t panel driver->base.native_bits_per_pixel = 16; // RGB565 // SPI and other pin configuration - driver->base.comms_config = &driver->spi_dc_reset_config; - driver->spi_dc_reset_config.spi_config.chip_select_pin = chip_select_pin; - driver->spi_dc_reset_config.spi_config.divisor = spi_divisor; - driver->spi_dc_reset_config.spi_config.lsb_first = false; - driver->spi_dc_reset_config.spi_config.mode = spi_mode; - driver->spi_dc_reset_config.dc_pin = dc_pin; - driver->spi_dc_reset_config.reset_pin = reset_pin; + driver->base.comms_config = &driver->spi_dc_reset_config; + driver->spi_dc_reset_config.spi_config.chip_select_pin = chip_select_pin; + driver->spi_dc_reset_config.spi_config.divisor = spi_divisor; + driver->spi_dc_reset_config.spi_config.lsb_first = false; + driver->spi_dc_reset_config.spi_config.mode = spi_mode; + driver->spi_dc_reset_config.dc_pin = dc_pin; + driver->spi_dc_reset_config.reset_pin = reset_pin; + driver->spi_dc_reset_config.command_params_uses_command_pin = false; if (!qp_internal_register_device((painter_device_t)driver)) { memset(driver, 0, sizeof(tft_panel_dc_reset_painter_device_t)); diff --git a/drivers/painter/ili9xxx/qp_ili9163.h b/drivers/painter/ili9xxx/qp_ili9163.h index 88d23629a92..a9b3befd48a 100644 --- a/drivers/painter/ili9xxx/qp_ili9163.h +++ b/drivers/painter/ili9xxx/qp_ili9163.h @@ -1,6 +1,5 @@ // Copyright 2021 Nick Brassel (@tzarc) // SPDX-License-Identifier: GPL-2.0-or-later - #pragma once #include "gpio.h" diff --git a/drivers/painter/ili9xxx/qp_ili9341.c b/drivers/painter/ili9xxx/qp_ili9341.c index 4130271f71b..a101b292aa0 100644 --- a/drivers/painter/ili9xxx/qp_ili9341.c +++ b/drivers/painter/ili9xxx/qp_ili9341.c @@ -110,13 +110,14 @@ painter_device_t qp_ili9341_make_spi_device(uint16_t panel_width, uint16_t panel driver->base.offset_y = 0; // SPI and other pin configuration - driver->base.comms_config = &driver->spi_dc_reset_config; - driver->spi_dc_reset_config.spi_config.chip_select_pin = chip_select_pin; - driver->spi_dc_reset_config.spi_config.divisor = spi_divisor; - driver->spi_dc_reset_config.spi_config.lsb_first = false; - driver->spi_dc_reset_config.spi_config.mode = spi_mode; - driver->spi_dc_reset_config.dc_pin = dc_pin; - driver->spi_dc_reset_config.reset_pin = reset_pin; + driver->base.comms_config = &driver->spi_dc_reset_config; + driver->spi_dc_reset_config.spi_config.chip_select_pin = chip_select_pin; + driver->spi_dc_reset_config.spi_config.divisor = spi_divisor; + driver->spi_dc_reset_config.spi_config.lsb_first = false; + driver->spi_dc_reset_config.spi_config.mode = spi_mode; + driver->spi_dc_reset_config.dc_pin = dc_pin; + driver->spi_dc_reset_config.reset_pin = reset_pin; + driver->spi_dc_reset_config.command_params_uses_command_pin = false; if (!qp_internal_register_device((painter_device_t)driver)) { memset(driver, 0, sizeof(tft_panel_dc_reset_painter_device_t)); diff --git a/drivers/painter/ili9xxx/qp_ili9341.h b/drivers/painter/ili9xxx/qp_ili9341.h index 28b0152a84c..d850aba1146 100644 --- a/drivers/painter/ili9xxx/qp_ili9341.h +++ b/drivers/painter/ili9xxx/qp_ili9341.h @@ -1,6 +1,5 @@ // Copyright 2021 Nick Brassel (@tzarc) // SPDX-License-Identifier: GPL-2.0-or-later - #pragma once #include "gpio.h" diff --git a/drivers/painter/ili9xxx/qp_ili9488.c b/drivers/painter/ili9xxx/qp_ili9488.c index a8da52132e0..63deaf5f2e9 100644 --- a/drivers/painter/ili9xxx/qp_ili9488.c +++ b/drivers/painter/ili9xxx/qp_ili9488.c @@ -103,13 +103,14 @@ painter_device_t qp_ili9488_make_spi_device(uint16_t panel_width, uint16_t panel driver->base.offset_y = 0; // SPI and other pin configuration - driver->base.comms_config = &driver->spi_dc_reset_config; - driver->spi_dc_reset_config.spi_config.chip_select_pin = chip_select_pin; - driver->spi_dc_reset_config.spi_config.divisor = spi_divisor; - driver->spi_dc_reset_config.spi_config.lsb_first = false; - driver->spi_dc_reset_config.spi_config.mode = spi_mode; - driver->spi_dc_reset_config.dc_pin = dc_pin; - driver->spi_dc_reset_config.reset_pin = reset_pin; + driver->base.comms_config = &driver->spi_dc_reset_config; + driver->spi_dc_reset_config.spi_config.chip_select_pin = chip_select_pin; + driver->spi_dc_reset_config.spi_config.divisor = spi_divisor; + driver->spi_dc_reset_config.spi_config.lsb_first = false; + driver->spi_dc_reset_config.spi_config.mode = spi_mode; + driver->spi_dc_reset_config.dc_pin = dc_pin; + driver->spi_dc_reset_config.reset_pin = reset_pin; + driver->spi_dc_reset_config.command_params_uses_command_pin = false; if (!qp_internal_register_device((painter_device_t)driver)) { memset(driver, 0, sizeof(tft_panel_dc_reset_painter_device_t)); diff --git a/drivers/painter/ili9xxx/qp_ili9488.h b/drivers/painter/ili9xxx/qp_ili9488.h index 21b8f033228..da56f1090fd 100644 --- a/drivers/painter/ili9xxx/qp_ili9488.h +++ b/drivers/painter/ili9xxx/qp_ili9488.h @@ -1,6 +1,5 @@ // Copyright 2021 Nick Brassel (@tzarc) // SPDX-License-Identifier: GPL-2.0-or-later - #pragma once #include "gpio.h" diff --git a/drivers/painter/oled_panel/qp_oled_panel.c b/drivers/painter/oled_panel/qp_oled_panel.c new file mode 100644 index 00000000000..eefee3f13f5 --- /dev/null +++ b/drivers/painter/oled_panel/qp_oled_panel.c @@ -0,0 +1,195 @@ +// Copyright 2023 Nick Brassel (@tzarc) +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "color.h" +#include "qp_internal.h" +#include "qp_comms.h" +#include "qp_draw.h" +#include "qp_oled_panel.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Quantum Painter API implementations + +// Power control +bool qp_oled_panel_power(painter_device_t device, bool power_on) { + painter_driver_t * driver = (painter_driver_t *)device; + oled_panel_painter_driver_vtable_t *vtable = (oled_panel_painter_driver_vtable_t *)driver->driver_vtable; + qp_comms_command(device, power_on ? vtable->opcodes.display_on : vtable->opcodes.display_off); + return true; +} + +// Screen clear +bool qp_oled_panel_clear(painter_device_t device) { + painter_driver_t *driver = (painter_driver_t *)device; + driver->driver_vtable->init(device, driver->rotation); // Re-init the display + return true; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Surface passthru +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +bool qp_oled_panel_passthru_pixdata(painter_device_t device, const void *pixel_data, uint32_t native_pixel_count) { + oled_panel_painter_device_t *driver = (oled_panel_painter_device_t *)device; + return driver->surface.base.validate_ok && driver->surface.base.driver_vtable->pixdata(&driver->surface.base, pixel_data, native_pixel_count); +} + +bool qp_oled_panel_passthru_viewport(painter_device_t device, uint16_t left, uint16_t top, uint16_t right, uint16_t bottom) { + oled_panel_painter_device_t *driver = (oled_panel_painter_device_t *)device; + return driver->surface.base.validate_ok && driver->surface.base.driver_vtable->viewport(&driver->surface.base, left, top, right, bottom); +} + +bool qp_oled_panel_passthru_palette_convert(painter_device_t device, int16_t palette_size, qp_pixel_t *palette) { + oled_panel_painter_device_t *driver = (oled_panel_painter_device_t *)device; + return driver->surface.base.validate_ok && driver->surface.base.driver_vtable->palette_convert(&driver->surface.base, palette_size, palette); +} + +bool qp_oled_panel_passthru_append_pixels(painter_device_t device, uint8_t *target_buffer, qp_pixel_t *palette, uint32_t pixel_offset, uint32_t pixel_count, uint8_t *palette_indices) { + oled_panel_painter_device_t *driver = (oled_panel_painter_device_t *)device; + return driver->surface.base.validate_ok && driver->surface.base.driver_vtable->append_pixels(&driver->surface.base, target_buffer, palette, pixel_offset, pixel_count, palette_indices); +} + +bool qp_oled_panel_passthru_append_pixdata(painter_device_t device, uint8_t *target_buffer, uint32_t pixdata_offset, uint8_t pixdata_byte) { + oled_panel_painter_device_t *driver = (oled_panel_painter_device_t *)device; + return driver->surface.base.validate_ok && driver->surface.base.driver_vtable->append_pixdata(&driver->surface.base, target_buffer, pixdata_offset, pixdata_byte); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Flush helpers +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +void qp_oled_panel_page_column_flush_rot0(painter_device_t device, surface_dirty_data_t *dirty, const uint8_t *framebuffer) { + painter_driver_t * driver = (painter_driver_t *)device; + oled_panel_painter_driver_vtable_t *vtable = (oled_panel_painter_driver_vtable_t *)driver->driver_vtable; + + // TODO: account for offset_x/y in base driver + int min_page = dirty->t / 8; + int max_page = dirty->b / 8; + int min_column = dirty->l; + int max_column = dirty->r; + + for (int page = min_page; page <= max_page; ++page) { + int cols_required = max_column - min_column + 1; + uint8_t column_data[cols_required]; + memset(column_data, 0, cols_required); + for (int x = min_column; x <= max_column; ++x) { + uint16_t data_offset = x - min_column; + for (int y = 0; y < 8; ++y) { + uint32_t pixel_num = ((page * 8) + y) * driver->panel_width + x; + uint32_t byte_offset = pixel_num / 8; + uint8_t bit_offset = pixel_num % 8; + column_data[data_offset] |= ((framebuffer[byte_offset] & (1 << bit_offset)) >> bit_offset) << y; + } + } + + int actual_page = page; + int start_column = min_column; + qp_comms_command(device, vtable->opcodes.set_page | actual_page); + qp_comms_command(device, vtable->opcodes.set_column_lsb | (start_column & 0x0F)); + qp_comms_command(device, vtable->opcodes.set_column_msb | (start_column & 0xF0) >> 4); + qp_comms_send(device, column_data, cols_required); + } +} + +void qp_oled_panel_page_column_flush_rot90(painter_device_t device, surface_dirty_data_t *dirty, const uint8_t *framebuffer) { + painter_driver_t * driver = (painter_driver_t *)device; + oled_panel_painter_driver_vtable_t *vtable = (oled_panel_painter_driver_vtable_t *)driver->driver_vtable; + + // TODO: account for offset_x/y in base driver + int num_columns = driver->panel_width; + int min_page = dirty->l / 8; + int max_page = dirty->r / 8; + int min_column = dirty->t; + int max_column = dirty->b; + + for (int page = min_page; page <= max_page; ++page) { + int cols_required = max_column - min_column + 1; + uint8_t column_data[cols_required]; + memset(column_data, 0, cols_required); + for (int y = min_column; y <= max_column; ++y) { + uint16_t data_offset = cols_required - 1 - (y - min_column); + for (int x = 0; x < 8; ++x) { + uint32_t pixel_num = y * driver->panel_height + ((page * 8) + x); + uint32_t byte_offset = pixel_num / 8; + uint8_t bit_offset = pixel_num % 8; + column_data[data_offset] |= ((framebuffer[byte_offset] & (1 << bit_offset)) >> bit_offset) << x; + } + } + + int actual_page = page; + int start_column = num_columns - 1 - max_column; + qp_comms_command(device, vtable->opcodes.set_page | actual_page); + qp_comms_command(device, vtable->opcodes.set_column_lsb | (start_column & 0x0F)); + qp_comms_command(device, vtable->opcodes.set_column_msb | (start_column & 0xF0) >> 4); + qp_comms_send(device, column_data, cols_required); + } +} + +void qp_oled_panel_page_column_flush_rot180(painter_device_t device, surface_dirty_data_t *dirty, const uint8_t *framebuffer) { + painter_driver_t * driver = (painter_driver_t *)device; + oled_panel_painter_driver_vtable_t *vtable = (oled_panel_painter_driver_vtable_t *)driver->driver_vtable; + + // TODO: account for offset_x/y in base driver + int num_pages = driver->panel_height / 8; + int num_columns = driver->panel_width; + int min_page = dirty->t / 8; + int max_page = dirty->b / 8; + int min_column = dirty->l; + int max_column = dirty->r; + + for (int page = min_page; page <= max_page; ++page) { + int cols_required = max_column - min_column + 1; + uint8_t column_data[cols_required]; + memset(column_data, 0, cols_required); + for (int x = min_column; x <= max_column; ++x) { + uint16_t data_offset = cols_required - 1 - (x - min_column); + for (int y = 0; y < 8; ++y) { + uint32_t pixel_num = ((page * 8) + y) * driver->panel_width + x; + uint32_t byte_offset = pixel_num / 8; + uint8_t bit_offset = pixel_num % 8; + column_data[data_offset] |= ((framebuffer[byte_offset] & (1 << bit_offset)) >> bit_offset) << (7 - y); + } + } + + int actual_page = num_pages - 1 - page; + int start_column = num_columns - 1 - max_column; + qp_comms_command(device, vtable->opcodes.set_page | actual_page); + qp_comms_command(device, vtable->opcodes.set_column_lsb | (start_column & 0x0F)); + qp_comms_command(device, vtable->opcodes.set_column_msb | (start_column & 0xF0) >> 4); + qp_comms_send(device, column_data, cols_required); + } +} + +void qp_oled_panel_page_column_flush_rot270(painter_device_t device, surface_dirty_data_t *dirty, const uint8_t *framebuffer) { + painter_driver_t * driver = (painter_driver_t *)device; + oled_panel_painter_driver_vtable_t *vtable = (oled_panel_painter_driver_vtable_t *)driver->driver_vtable; + + // TODO: account for offset_x/y in base driver + int num_pages = driver->panel_height / 8; + int min_page = dirty->l / 8; + int max_page = dirty->r / 8; + int min_column = dirty->t; + int max_column = dirty->b; + + for (int page = min_page; page <= max_page; ++page) { + int cols_required = max_column - min_column + 1; + uint8_t column_data[cols_required]; + memset(column_data, 0, cols_required); + for (int y = min_column; y <= max_column; ++y) { + uint16_t data_offset = y - min_column; + for (int x = 0; x < 8; ++x) { + uint32_t pixel_num = y * driver->panel_height + ((page * 8) + x); + uint32_t byte_offset = pixel_num / 8; + uint8_t bit_offset = pixel_num % 8; + column_data[data_offset] |= ((framebuffer[byte_offset] & (1 << bit_offset)) >> bit_offset) << (7 - x); + } + } + + int actual_page = num_pages - 1 - page; + int start_column = min_column; + qp_comms_command(device, vtable->opcodes.set_page | actual_page); + qp_comms_command(device, vtable->opcodes.set_column_lsb | (start_column & 0x0F)); + qp_comms_command(device, vtable->opcodes.set_column_msb | (start_column & 0xF0) >> 4); + qp_comms_send(device, column_data, cols_required); + } +} diff --git a/drivers/painter/oled_panel/qp_oled_panel.h b/drivers/painter/oled_panel/qp_oled_panel.h new file mode 100644 index 00000000000..ccc7ab92048 --- /dev/null +++ b/drivers/painter/oled_panel/qp_oled_panel.h @@ -0,0 +1,68 @@ +// Copyright 2023 Nick Brassel (@tzarc) +// SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + +#include "color.h" +#include "qp_internal.h" +#include "qp_surface_internal.h" + +#ifdef QUANTUM_PAINTER_SPI_ENABLE +# include "qp_comms_spi.h" +#endif // QUANTUM_PAINTER_SPI_ENABLE + +#ifdef QUANTUM_PAINTER_I2C_ENABLE +# include "qp_comms_i2c.h" +#endif // QUANTUM_PAINTER_I2C_ENABLE + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Common OLED panel implementation + +// Driver vtable with extras +typedef struct oled_panel_painter_driver_vtable_t { + painter_driver_vtable_t base; // must be first, so it can be cast to/from the painter_driver_vtable_t* type + + // Opcodes for normal display operation + struct { + uint8_t display_on; + uint8_t display_off; + uint8_t set_page; + uint8_t set_column_lsb; + uint8_t set_column_msb; + } opcodes; +} oled_panel_painter_driver_vtable_t; + +// Device definition +typedef struct oled_panel_painter_device_t { + painter_driver_t base; // must be first, so it can be cast to/from the painter_device_t* type + + union { +#ifdef QUANTUM_PAINTER_SPI_ENABLE + // SPI-based configurables + qp_comms_spi_dc_reset_config_t spi_dc_reset_config; +#endif // QUANTUM_PAINTER_SPI_ENABLE +#ifdef QUANTUM_PAINTER_I2C_ENABLE + // I2C-based configurables + qp_comms_i2c_config_t i2c_config; +#endif // QUANTUM_PAINTER_I2C_ENABLE + }; + + surface_painter_device_t surface; +} oled_panel_painter_device_t; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Forward declarations for injecting into concrete driver vtables + +bool qp_oled_panel_power(painter_device_t device, bool power_on); +bool qp_oled_panel_clear(painter_device_t device); + +bool qp_oled_panel_passthru_pixdata(painter_device_t device, const void *pixel_data, uint32_t native_pixel_count); +bool qp_oled_panel_passthru_viewport(painter_device_t device, uint16_t left, uint16_t top, uint16_t right, uint16_t bottom); +bool qp_oled_panel_passthru_palette_convert(painter_device_t device, int16_t palette_size, qp_pixel_t *palette); +bool qp_oled_panel_passthru_append_pixels(painter_device_t device, uint8_t *target_buffer, qp_pixel_t *palette, uint32_t pixel_offset, uint32_t pixel_count, uint8_t *palette_indices); +bool qp_oled_panel_passthru_append_pixdata(painter_device_t device, uint8_t *target_buffer, uint32_t pixdata_offset, uint8_t pixdata_byte); + +// Helpers for flushing data from the dirty region to the correct location on the OLED +void qp_oled_panel_page_column_flush_rot0(painter_device_t device, surface_dirty_data_t *dirty, const uint8_t *framebuffer); +void qp_oled_panel_page_column_flush_rot90(painter_device_t device, surface_dirty_data_t *dirty, const uint8_t *framebuffer); +void qp_oled_panel_page_column_flush_rot180(painter_device_t device, surface_dirty_data_t *dirty, const uint8_t *framebuffer); +void qp_oled_panel_page_column_flush_rot270(painter_device_t device, surface_dirty_data_t *dirty, const uint8_t *framebuffer); diff --git a/drivers/painter/sh1106/qp_sh1106.c b/drivers/painter/sh1106/qp_sh1106.c new file mode 100644 index 00000000000..7cb6e398fac --- /dev/null +++ b/drivers/painter/sh1106/qp_sh1106.c @@ -0,0 +1,206 @@ +// Copyright 2023 Nick Brassel (@tzarc) +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "qp_internal.h" +#include "qp_comms.h" +#include "qp_oled_panel.h" +#include "qp_sh1106.h" +#include "qp_sh1106_opcodes.h" +#include "qp_surface.h" +#include "qp_surface_internal.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Driver storage +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +typedef struct sh1106_device_t { + oled_panel_painter_device_t oled; + + uint8_t framebuffer[SURFACE_REQUIRED_BUFFER_BYTE_SIZE(128, 64, 1)]; +} sh1106_device_t; + +static sh1106_device_t sh1106_drivers[SH1106_NUM_DEVICES] = {0}; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Quantum Painter API implementations + +// Initialisation +__attribute__((weak)) bool qp_sh1106_init(painter_device_t device, painter_rotation_t rotation) { + sh1106_device_t *driver = (sh1106_device_t *)device; + + // Change the surface geometry based on the panel rotation + if (rotation == QP_ROTATION_90 || rotation == QP_ROTATION_270) { + driver->oled.surface.base.panel_width = driver->oled.base.panel_height; + driver->oled.surface.base.panel_height = driver->oled.base.panel_width; + } else { + driver->oled.surface.base.panel_width = driver->oled.base.panel_width; + driver->oled.surface.base.panel_height = driver->oled.base.panel_height; + } + + // Init the internal surface + if (!qp_init(&driver->oled.surface.base, QP_ROTATION_0)) { + qp_dprintf("Failed to init internal surface in qp_sh1106_init\n"); + return false; + } + + // clang-format off + const uint8_t sh1106_init_sequence[] = { + // Command, Delay, N, Data[N] + SH1106_SET_MUX_RATIO, 0, 1, 0x3F, + SH1106_DISPLAY_OFFSET, 0, 1, 0x00, + SH1106_DISPLAY_START_LINE, 0, 0, + SH1106_SET_SEGMENT_REMAP_INV, 0, 0, + SH1106_COM_SCAN_DIR_DEC, 0, 0, + SH1106_COM_PADS_HW_CFG, 0, 1, 0x12, + SH1106_SET_CONTRAST, 0, 1, 0x7F, + SH1106_ALL_ON_RESUME, 0, 0, + SH1106_NON_INVERTING_DISPLAY, 0, 0, + SH1106_SET_OSC_DIVFREQ, 0, 1, 0x80, + SH1106_SET_CHARGE_PUMP, 0, 1, 0x14, + SH1106_DISPLAY_ON, 0, 0, + }; + // clang-format on + + qp_comms_bulk_command_sequence(device, sh1106_init_sequence, sizeof(sh1106_init_sequence)); + return true; +} + +// Screen flush +bool qp_sh1106_flush(painter_device_t device) { + sh1106_device_t *driver = (sh1106_device_t *)device; + + if (!driver->oled.surface.dirty.is_dirty) { + return true; + } + + switch (driver->oled.base.rotation) { + default: + case QP_ROTATION_0: + qp_oled_panel_page_column_flush_rot0(device, &driver->oled.surface.dirty, driver->framebuffer); + break; + case QP_ROTATION_90: + qp_oled_panel_page_column_flush_rot90(device, &driver->oled.surface.dirty, driver->framebuffer); + break; + case QP_ROTATION_180: + qp_oled_panel_page_column_flush_rot180(device, &driver->oled.surface.dirty, driver->framebuffer); + break; + case QP_ROTATION_270: + qp_oled_panel_page_column_flush_rot270(device, &driver->oled.surface.dirty, driver->framebuffer); + break; + } + + // Clear the dirty area + qp_flush(&driver->oled.surface); + + return true; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Driver vtable +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +const oled_panel_painter_driver_vtable_t sh1106_driver_vtable = { + .base = + { + .init = qp_sh1106_init, + .power = qp_oled_panel_power, + .clear = qp_oled_panel_clear, + .flush = qp_sh1106_flush, + .pixdata = qp_oled_panel_passthru_pixdata, + .viewport = qp_oled_panel_passthru_viewport, + .palette_convert = qp_oled_panel_passthru_palette_convert, + .append_pixels = qp_oled_panel_passthru_append_pixels, + .append_pixdata = qp_oled_panel_passthru_append_pixdata, + }, + .opcodes = + { + .display_on = SH1106_DISPLAY_ON, + .display_off = SH1106_DISPLAY_OFF, + .set_page = SH1106_PAGE_ADDR, + .set_column_lsb = SH1106_SETCOLUMN_LSB, + .set_column_msb = SH1106_SETCOLUMN_MSB, + }, +}; + +#ifdef QUANTUM_PAINTER_SH1106_SPI_ENABLE +// Factory function for creating a handle to the SH1106 device +painter_device_t qp_sh1106_make_spi_device(uint16_t panel_width, uint16_t panel_height, pin_t chip_select_pin, pin_t dc_pin, pin_t reset_pin, uint16_t spi_divisor, int spi_mode) { + for (uint32_t i = 0; i < SH1106_NUM_DEVICES; ++i) { + sh1106_device_t *driver = &sh1106_drivers[i]; + if (!driver->oled.base.driver_vtable) { + painter_device_t surface = qp_make_mono1bpp_surface_advanced(&driver->oled.surface, 1, panel_width, panel_height, driver->framebuffer); + if (!surface) { + return NULL; + } + + // Setup the OLED device + driver->oled.base.driver_vtable = (const painter_driver_vtable_t *)&sh1106_driver_vtable; + driver->oled.base.comms_vtable = (const painter_comms_vtable_t *)&spi_comms_with_dc_vtable; + driver->oled.base.native_bits_per_pixel = 1; // 1bpp mono + driver->oled.base.panel_width = panel_width; + driver->oled.base.panel_height = panel_height; + driver->oled.base.rotation = QP_ROTATION_0; + driver->oled.base.offset_x = 0; + driver->oled.base.offset_y = 0; + + // SPI and other pin configuration + driver->oled.base.comms_config = &driver->oled.spi_dc_reset_config; + driver->oled.spi_dc_reset_config.spi_config.chip_select_pin = chip_select_pin; + driver->oled.spi_dc_reset_config.spi_config.divisor = spi_divisor; + driver->oled.spi_dc_reset_config.spi_config.lsb_first = false; + driver->oled.spi_dc_reset_config.spi_config.mode = spi_mode; + driver->oled.spi_dc_reset_config.dc_pin = dc_pin; + driver->oled.spi_dc_reset_config.reset_pin = reset_pin; + driver->oled.spi_dc_reset_config.command_params_uses_command_pin = true; + + if (!qp_internal_register_device((painter_device_t)driver)) { + memset(driver, 0, sizeof(sh1106_device_t)); + return NULL; + } + + return (painter_device_t)driver; + } + } + return NULL; +} + +#endif // QUANTUM_PAINTER_SH1106_SPI_ENABLE + +#ifdef QUANTUM_PAINTER_SH1106_I2C_ENABLE +// Factory function for creating a handle to the SH1106 device +painter_device_t qp_sh1106_make_i2c_device(uint16_t panel_width, uint16_t panel_height, uint8_t i2c_address) { + for (uint32_t i = 0; i < SH1106_NUM_DEVICES; ++i) { + sh1106_device_t *driver = &sh1106_drivers[i]; + if (!driver->oled.base.driver_vtable) { + // Instantiate the surface, intentional swap of width/high due to transpose + painter_device_t surface = qp_make_mono1bpp_surface_advanced(&driver->oled.surface, 1, panel_width, panel_height, driver->framebuffer); + if (!surface) { + return NULL; + } + + // Setup the OLED device + driver->oled.base.driver_vtable = (const painter_driver_vtable_t *)&sh1106_driver_vtable; + driver->oled.base.comms_vtable = (const painter_comms_vtable_t *)&i2c_comms_cmddata_vtable; + driver->oled.base.native_bits_per_pixel = 1; // 1bpp mono + driver->oled.base.panel_width = panel_width; + driver->oled.base.panel_height = panel_height; + driver->oled.base.rotation = QP_ROTATION_0; + driver->oled.base.offset_x = 0; + driver->oled.base.offset_y = 0; + + // I2C configuration + driver->oled.base.comms_config = &driver->oled.i2c_config; + driver->oled.i2c_config.chip_address = i2c_address; + + if (!qp_internal_register_device((painter_device_t)driver)) { + memset(driver, 0, sizeof(sh1106_device_t)); + return NULL; + } + + return (painter_device_t)driver; + } + } + return NULL; +} + +#endif // QUANTUM_PAINTER_SH1106_SPI_ENABLE diff --git a/drivers/painter/sh1106/qp_sh1106.h b/drivers/painter/sh1106/qp_sh1106.h new file mode 100644 index 00000000000..6c325dba4ba --- /dev/null +++ b/drivers/painter/sh1106/qp_sh1106.h @@ -0,0 +1,66 @@ +// Copyright 2023 Nick Brassel (@tzarc) +// SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + +#include "gpio.h" +#include "qp_internal.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Quantum Painter SH1106 configurables (add to your keyboard's config.h) + +#if defined(QUANTUM_PAINTER_SH1106_SPI_ENABLE) && !defined(SH1106_NUM_SPI_DEVICES) +/** + * @def This controls the maximum number of SPI SH1106 devices that Quantum Painter can communicate with at any one time. + * Increasing this number allows for multiple displays to be used. + */ +# define SH1106_NUM_SPI_DEVICES 1 +#else +# define SH1106_NUM_SPI_DEVICES 0 +#endif + +#if defined(QUANTUM_PAINTER_SH1106_I2C_ENABLE) && !defined(SH1106_NUM_I2C_DEVICES) +/** + * @def This controls the maximum number of I2C SH1106 devices that Quantum Painter can communicate with at any one time. + * Increasing this number allows for multiple displays to be used. + */ +# define SH1106_NUM_I2C_DEVICES 1 +#else +# define SH1106_NUM_I2C_DEVICES 0 +#endif + +#define SH1106_NUM_DEVICES ((SH1106_NUM_SPI_DEVICES) + (SH1106_NUM_I2C_DEVICES)) + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Quantum Painter SH1106 device factories + +#ifdef QUANTUM_PAINTER_SH1106_SPI_ENABLE + +/** + * Factory method for an SH1106 SPI LCD device. + * + * @param panel_width[in] the width of the display in pixels (usually 128) + * @param panel_height[in] the height of the display in pixels (usually 64) + * @param chip_select_pin[in] the GPIO pin used for SPI chip select + * @param dc_pin[in] the GPIO pin used for D/C control + * @param reset_pin[in] the GPIO pin used for RST + * @param spi_divisor[in] the SPI divisor to use when communicating with the display + * @param spi_mode[in] the SPI mode to use when communicating with the display + * @return the device handle used with all drawing routines in Quantum Painter + */ +painter_device_t qp_sh1106_make_spi_device(uint16_t panel_width, uint16_t panel_height, pin_t chip_select_pin, pin_t dc_pin, pin_t reset_pin, uint16_t spi_divisor, int spi_mode); + +#endif // QUANTUM_PAINTER_SH1106_SPI_ENABLE + +#ifdef QUANTUM_PAINTER_SH1106_I2C_ENABLE + +/** + * Factory method for an SH1106 I2C LCD device. + * + * @param panel_width[in] the width of the display in pixels (usually 128) + * @param panel_height[in] the height of the display in pixels (usually 64) + * @param i2c_address[in] the I2C address to use + * @return the device handle used with all drawing routines in Quantum Painter + */ +painter_device_t qp_sh1106_make_i2c_device(uint16_t panel_width, uint16_t panel_height, uint8_t i2c_address); + +#endif // QUANTUM_PAINTER_SH1106_I2C_ENABLE diff --git a/drivers/painter/sh1106/qp_sh1106_opcodes.h b/drivers/painter/sh1106/qp_sh1106_opcodes.h new file mode 100644 index 00000000000..a2e100d7702 --- /dev/null +++ b/drivers/painter/sh1106/qp_sh1106_opcodes.h @@ -0,0 +1,26 @@ +// Copyright 2023 Nick Brassel (@tzarc) +// SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + +#define SH1106_DISPLAY_ON 0xAF +#define SH1106_DISPLAY_OFF 0xAE +#define SH1106_SET_OSC_DIVFREQ 0xD5 +#define SH1106_SET_MUX_RATIO 0xA8 +#define SH1106_DISPLAY_OFFSET 0xD3 +#define SH1106_DISPLAY_START_LINE 0x40 +#define SH1106_SET_CHARGE_PUMP 0x8D +#define SH1106_SET_SEGMENT_REMAP_NORMAL 0xA0 +#define SH1106_SET_SEGMENT_REMAP_INV 0xA1 +#define SH1106_COM_SCAN_DIR_INC 0xC0 +#define SH1106_COM_SCAN_DIR_DEC 0xC8 +#define SH1106_COM_PADS_HW_CFG 0xDA +#define SH1106_SET_CONTRAST 0x81 +#define SH1106_SET_PRECHARGE_PERIOD 0xD9 +#define SH1106_VCOM_DETECT 0xDB +#define SH1106_ALL_ON_RESUME 0xA4 +#define SH1106_NON_INVERTING_DISPLAY 0xA6 +#define SH1106_DEACTIVATE_SCROLL 0x2E + +#define SH1106_SETCOLUMN_LSB 0x00 +#define SH1106_SETCOLUMN_MSB 0x10 +#define SH1106_PAGE_ADDR 0xB0 diff --git a/drivers/painter/ssd1351/qp_ssd1351.c b/drivers/painter/ssd1351/qp_ssd1351.c index 434b7f03278..3270a362c2f 100644 --- a/drivers/painter/ssd1351/qp_ssd1351.c +++ b/drivers/painter/ssd1351/qp_ssd1351.c @@ -107,13 +107,14 @@ painter_device_t qp_ssd1351_make_spi_device(uint16_t panel_width, uint16_t panel driver->base.native_bits_per_pixel = 16; // RGB565 // SPI and other pin configuration - driver->base.comms_config = &driver->spi_dc_reset_config; - driver->spi_dc_reset_config.spi_config.chip_select_pin = chip_select_pin; - driver->spi_dc_reset_config.spi_config.divisor = spi_divisor; - driver->spi_dc_reset_config.spi_config.lsb_first = false; - driver->spi_dc_reset_config.spi_config.mode = spi_mode; - driver->spi_dc_reset_config.dc_pin = dc_pin; - driver->spi_dc_reset_config.reset_pin = reset_pin; + driver->base.comms_config = &driver->spi_dc_reset_config; + driver->spi_dc_reset_config.spi_config.chip_select_pin = chip_select_pin; + driver->spi_dc_reset_config.spi_config.divisor = spi_divisor; + driver->spi_dc_reset_config.spi_config.lsb_first = false; + driver->spi_dc_reset_config.spi_config.mode = spi_mode; + driver->spi_dc_reset_config.dc_pin = dc_pin; + driver->spi_dc_reset_config.reset_pin = reset_pin; + driver->spi_dc_reset_config.command_params_uses_command_pin = false; if (!qp_internal_register_device((painter_device_t)driver)) { memset(driver, 0, sizeof(tft_panel_dc_reset_painter_device_t)); diff --git a/drivers/painter/ssd1351/qp_ssd1351.h b/drivers/painter/ssd1351/qp_ssd1351.h index 0df34f204d5..0045c4926b8 100644 --- a/drivers/painter/ssd1351/qp_ssd1351.h +++ b/drivers/painter/ssd1351/qp_ssd1351.h @@ -1,6 +1,5 @@ // Copyright 2021 Nick Brassel (@tzarc) // SPDX-License-Identifier: GPL-2.0-or-later - #pragma once #include "gpio.h" diff --git a/drivers/painter/ssd1351/qp_ssd1351_opcodes.h b/drivers/painter/ssd1351/qp_ssd1351_opcodes.h index 48ed2a3a7c2..ca8e2bf77eb 100644 --- a/drivers/painter/ssd1351/qp_ssd1351_opcodes.h +++ b/drivers/painter/ssd1351/qp_ssd1351_opcodes.h @@ -1,6 +1,5 @@ // Copyright 2021 Nick Brassel (@tzarc) // SPDX-License-Identifier: GPL-2.0-or-later - #pragma once //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/drivers/painter/st77xx/qp_st7735.c b/drivers/painter/st77xx/qp_st7735.c index 98baf400abf..6d00c95bb1f 100644 --- a/drivers/painter/st77xx/qp_st7735.c +++ b/drivers/painter/st77xx/qp_st7735.c @@ -127,13 +127,14 @@ painter_device_t qp_st7735_make_spi_device(uint16_t panel_width, uint16_t panel_ driver->base.native_bits_per_pixel = 16; // RGB565 // SPI and other pin configuration - driver->base.comms_config = &driver->spi_dc_reset_config; - driver->spi_dc_reset_config.spi_config.chip_select_pin = chip_select_pin; - driver->spi_dc_reset_config.spi_config.divisor = spi_divisor; - driver->spi_dc_reset_config.spi_config.lsb_first = false; - driver->spi_dc_reset_config.spi_config.mode = spi_mode; - driver->spi_dc_reset_config.dc_pin = dc_pin; - driver->spi_dc_reset_config.reset_pin = reset_pin; + driver->base.comms_config = &driver->spi_dc_reset_config; + driver->spi_dc_reset_config.spi_config.chip_select_pin = chip_select_pin; + driver->spi_dc_reset_config.spi_config.divisor = spi_divisor; + driver->spi_dc_reset_config.spi_config.lsb_first = false; + driver->spi_dc_reset_config.spi_config.mode = spi_mode; + driver->spi_dc_reset_config.dc_pin = dc_pin; + driver->spi_dc_reset_config.reset_pin = reset_pin; + driver->spi_dc_reset_config.command_params_uses_command_pin = false; if (!qp_internal_register_device((painter_device_t)driver)) { memset(driver, 0, sizeof(tft_panel_dc_reset_painter_device_t)); diff --git a/drivers/painter/st77xx/qp_st7735.h b/drivers/painter/st77xx/qp_st7735.h index a9ce16bef19..e65b7ca7060 100644 --- a/drivers/painter/st77xx/qp_st7735.h +++ b/drivers/painter/st77xx/qp_st7735.h @@ -2,7 +2,6 @@ // Copyright 2021 Nick Brassel (@tzarc) // Copyright 2022 David Hoelscher (@customMK) // SPDX-License-Identifier: GPL-2.0-or-later - #pragma once #include "gpio.h" @@ -42,4 +41,4 @@ * @return the device handle used with all drawing routines in Quantum Painter */ painter_device_t qp_st7735_make_spi_device(uint16_t panel_width, uint16_t panel_height, pin_t chip_select_pin, pin_t dc_pin, pin_t reset_pin, uint16_t spi_divisor, int spi_mode); -#endif // QUANTUM_PAINTER_ST7735_SPI_ENABLE \ No newline at end of file +#endif // QUANTUM_PAINTER_ST7735_SPI_ENABLE diff --git a/drivers/painter/st77xx/qp_st7735_opcodes.h b/drivers/painter/st77xx/qp_st7735_opcodes.h index 816e32f3d76..f390d113c5d 100644 --- a/drivers/painter/st77xx/qp_st7735_opcodes.h +++ b/drivers/painter/st77xx/qp_st7735_opcodes.h @@ -1,6 +1,5 @@ // Copyright 2022 David Hoelscher (@customMK) // SPDX-License-Identifier: GPL-2.0-or-later - #pragma once //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/drivers/painter/st77xx/qp_st7789.c b/drivers/painter/st77xx/qp_st7789.c index f9065f5178b..855a9cc0c86 100644 --- a/drivers/painter/st77xx/qp_st7789.c +++ b/drivers/painter/st77xx/qp_st7789.c @@ -126,13 +126,14 @@ painter_device_t qp_st7789_make_spi_device(uint16_t panel_width, uint16_t panel_ driver->base.native_bits_per_pixel = 16; // RGB565 // SPI and other pin configuration - driver->base.comms_config = &driver->spi_dc_reset_config; - driver->spi_dc_reset_config.spi_config.chip_select_pin = chip_select_pin; - driver->spi_dc_reset_config.spi_config.divisor = spi_divisor; - driver->spi_dc_reset_config.spi_config.lsb_first = false; - driver->spi_dc_reset_config.spi_config.mode = spi_mode; - driver->spi_dc_reset_config.dc_pin = dc_pin; - driver->spi_dc_reset_config.reset_pin = reset_pin; + driver->base.comms_config = &driver->spi_dc_reset_config; + driver->spi_dc_reset_config.spi_config.chip_select_pin = chip_select_pin; + driver->spi_dc_reset_config.spi_config.divisor = spi_divisor; + driver->spi_dc_reset_config.spi_config.lsb_first = false; + driver->spi_dc_reset_config.spi_config.mode = spi_mode; + driver->spi_dc_reset_config.dc_pin = dc_pin; + driver->spi_dc_reset_config.reset_pin = reset_pin; + driver->spi_dc_reset_config.command_params_uses_command_pin = false; if (!qp_internal_register_device((painter_device_t)driver)) { memset(driver, 0, sizeof(tft_panel_dc_reset_painter_device_t)); diff --git a/drivers/painter/st77xx/qp_st7789.h b/drivers/painter/st77xx/qp_st7789.h index ec61f5d70b7..03d618cae41 100644 --- a/drivers/painter/st77xx/qp_st7789.h +++ b/drivers/painter/st77xx/qp_st7789.h @@ -1,7 +1,6 @@ // Copyright 2021 Paul Cotter (@gr1mr3aver) // Copyright 2021 Nick Brassel (@tzarc) // SPDX-License-Identifier: GPL-2.0-or-later - #pragma once #include "gpio.h" diff --git a/drivers/painter/st77xx/qp_st7789_opcodes.h b/drivers/painter/st77xx/qp_st7789_opcodes.h index b5baba71846..4b46f994b48 100644 --- a/drivers/painter/st77xx/qp_st7789_opcodes.h +++ b/drivers/painter/st77xx/qp_st7789_opcodes.h @@ -1,6 +1,5 @@ // Copyright 2021 Paul Cotter (@gr1mr3aver) // SPDX-License-Identifier: GPL-2.0-or-later - #pragma once //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/drivers/painter/st77xx/qp_st77xx_opcodes.h b/drivers/painter/st77xx/qp_st77xx_opcodes.h index 131378d8328..c01e2b21e62 100644 --- a/drivers/painter/st77xx/qp_st77xx_opcodes.h +++ b/drivers/painter/st77xx/qp_st77xx_opcodes.h @@ -1,6 +1,5 @@ // Copyright 2021 Paul Cotter (@gr1mr3aver) // SPDX-License-Identifier: GPL-2.0-or-later - #pragma once //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/drivers/painter/tft_panel/qp_tft_panel.h b/drivers/painter/tft_panel/qp_tft_panel.h index 67168645b7e..3b184f2eba9 100644 --- a/drivers/painter/tft_panel/qp_tft_panel.h +++ b/drivers/painter/tft_panel/qp_tft_panel.h @@ -1,5 +1,6 @@ // Copyright 2021 Nick Brassel (@tzarc) // SPDX-License-Identifier: GPL-2.0-or-later +#pragma once #include "color.h" #include "qp_internal.h" diff --git a/quantum/painter/qff.c b/quantum/painter/qff.c index cd6af788f9e..8590f5b4009 100644 --- a/quantum/painter/qff.c +++ b/quantum/painter/qff.c @@ -10,7 +10,7 @@ //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // QFF API -bool qff_read_font_descriptor(qp_stream_t *stream, uint8_t *line_height, bool *has_ascii_table, uint16_t *num_unicode_glyphs, uint8_t *bpp, bool *has_palette, painter_compression_t *compression_scheme, uint32_t *total_bytes) { +bool qff_read_font_descriptor(qp_stream_t *stream, uint8_t *line_height, bool *has_ascii_table, uint16_t *num_unicode_glyphs, uint8_t *bpp, bool *has_palette, bool *is_panel_native, painter_compression_t *compression_scheme, uint32_t *total_bytes) { // Seek to the start qp_stream_setpos(stream, 0); @@ -49,7 +49,7 @@ bool qff_read_font_descriptor(qp_stream_t *stream, uint8_t *line_height, bool *h *num_unicode_glyphs = font_descriptor.num_unicode_glyphs; } if (bpp || has_palette) { - if (!qgf_parse_format(font_descriptor.format, bpp, has_palette)) { + if (!qgf_parse_format(font_descriptor.format, bpp, has_palette, is_panel_native)) { return false; } } @@ -102,7 +102,7 @@ bool qff_validate_stream(qp_stream_t *stream) { bool has_ascii_table; uint16_t num_unicode_glyphs; - if (!qff_read_font_descriptor(stream, NULL, &has_ascii_table, &num_unicode_glyphs, NULL, NULL, NULL, NULL)) { + if (!qff_read_font_descriptor(stream, NULL, &has_ascii_table, &num_unicode_glyphs, NULL, NULL, NULL, NULL, NULL)) { return false; } @@ -127,7 +127,7 @@ uint32_t qff_get_total_size(qp_stream_t *stream) { // Read the font descriptor, grabbing the size uint32_t total_size; - if (!qff_read_font_descriptor(stream, NULL, NULL, NULL, NULL, NULL, NULL, &total_size)) { + if (!qff_read_font_descriptor(stream, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &total_size)) { return false; } diff --git a/quantum/painter/qff.h b/quantum/painter/qff.h index d1d629582f5..c3b831da178 100644 --- a/quantum/painter/qff.h +++ b/quantum/painter/qff.h @@ -85,4 +85,4 @@ typedef struct QP_PACKED qff_unicode_glyph_table_v1_t { bool qff_validate_stream(qp_stream_t *stream); uint32_t qff_get_total_size(qp_stream_t *stream); -bool qff_read_font_descriptor(qp_stream_t *stream, uint8_t *line_height, bool *has_ascii_table, uint16_t *num_unicode_glyphs, uint8_t *bpp, bool *has_palette, painter_compression_t *compression_scheme, uint32_t *total_bytes); +bool qff_read_font_descriptor(qp_stream_t *stream, uint8_t *line_height, bool *has_ascii_table, uint16_t *num_unicode_glyphs, uint8_t *bpp, bool *has_palette, bool *is_panel_native, painter_compression_t *compression_scheme, uint32_t *total_bytes); diff --git a/quantum/painter/qgf.c b/quantum/painter/qgf.c index 6a4af07001e..bc2df949334 100644 --- a/quantum/painter/qgf.c +++ b/quantum/painter/qgf.c @@ -24,22 +24,23 @@ bool qgf_validate_block_header(qgf_block_header_v1_t *desc, uint8_t expected_typ return true; } -bool qgf_parse_format(qp_image_format_t format, uint8_t *bpp, bool *has_palette) { +bool qgf_parse_format(qp_image_format_t format, uint8_t *bpp, bool *has_palette, bool *is_panel_native) { // clang-format off - static const struct QP_PACKED { + static const struct QP_PACKED { uint8_t bpp; bool has_palette; + bool is_panel_native; } formats[] = { - [GRAYSCALE_1BPP] = {.bpp = 1, .has_palette = false}, - [GRAYSCALE_2BPP] = {.bpp = 2, .has_palette = false}, - [GRAYSCALE_4BPP] = {.bpp = 4, .has_palette = false}, - [GRAYSCALE_8BPP] = {.bpp = 8, .has_palette = false}, - [PALETTE_1BPP] = {.bpp = 1, .has_palette = true}, - [PALETTE_2BPP] = {.bpp = 2, .has_palette = true}, - [PALETTE_4BPP] = {.bpp = 4, .has_palette = true}, - [PALETTE_8BPP] = {.bpp = 8, .has_palette = true}, - [RGB565_16BPP] = {.bpp = 16, .has_palette = false}, - [RGB888_24BPP] = {.bpp = 24, .has_palette = false}, + [GRAYSCALE_1BPP] = {.bpp = 1, .has_palette = false, .is_panel_native = false}, + [GRAYSCALE_2BPP] = {.bpp = 2, .has_palette = false, .is_panel_native = false}, + [GRAYSCALE_4BPP] = {.bpp = 4, .has_palette = false, .is_panel_native = false}, + [GRAYSCALE_8BPP] = {.bpp = 8, .has_palette = false, .is_panel_native = false}, + [PALETTE_1BPP] = {.bpp = 1, .has_palette = true, .is_panel_native = false}, + [PALETTE_2BPP] = {.bpp = 2, .has_palette = true, .is_panel_native = false}, + [PALETTE_4BPP] = {.bpp = 4, .has_palette = true, .is_panel_native = false}, + [PALETTE_8BPP] = {.bpp = 8, .has_palette = true, .is_panel_native = false}, + [RGB565_16BPP] = {.bpp = 16, .has_palette = false, .is_panel_native = true}, + [RGB888_24BPP] = {.bpp = 24, .has_palette = false, .is_panel_native = true}, }; // clang-format on @@ -56,13 +57,16 @@ bool qgf_parse_format(qp_image_format_t format, uint8_t *bpp, bool *has_palette) if (has_palette) { *has_palette = formats[format].has_palette; } + if (is_panel_native) { + *is_panel_native = formats[format].is_panel_native; + } return true; } -bool qgf_parse_frame_descriptor(qgf_frame_v1_t *frame_descriptor, uint8_t *bpp, bool *has_palette, bool *is_delta, painter_compression_t *compression_scheme, uint16_t *delay) { +bool qgf_parse_frame_descriptor(qgf_frame_v1_t *frame_descriptor, uint8_t *bpp, bool *has_palette, bool *is_panel_native, bool *is_delta, painter_compression_t *compression_scheme, uint16_t *delay) { // Decode the format - qgf_parse_format(frame_descriptor->format, bpp, has_palette); + qgf_parse_format(frame_descriptor->format, bpp, has_palette, is_panel_native); // Copy out the required info if (is_delta) { @@ -173,7 +177,7 @@ void qgf_seek_to_frame_descriptor(qp_stream_t *stream, uint16_t frame_number) { qp_stream_setpos(stream, offset); } -bool qgf_validate_frame_descriptor(qp_stream_t *stream, uint16_t frame_number, uint8_t *bpp, bool *has_palette, bool *is_delta) { +bool qgf_validate_frame_descriptor(qp_stream_t *stream, uint16_t frame_number, uint8_t *bpp, bool *has_palette, bool *is_panel_native, bool *is_delta) { // Seek to the correct location qgf_seek_to_frame_descriptor(stream, frame_number); @@ -189,7 +193,7 @@ bool qgf_validate_frame_descriptor(qp_stream_t *stream, uint16_t frame_number, u return false; } - return qgf_parse_frame_descriptor(&frame_descriptor, bpp, has_palette, is_delta, NULL, NULL); + return qgf_parse_frame_descriptor(&frame_descriptor, bpp, has_palette, is_panel_native, is_delta, NULL, NULL); } bool qgf_validate_palette_descriptor(qp_stream_t *stream, uint16_t frame_number, uint8_t bpp) { @@ -253,8 +257,9 @@ bool qgf_validate_stream(qp_stream_t *stream) { // Validate the frame descriptor block uint8_t bpp; bool has_palette; + bool is_panel_native; bool has_delta; - if (!qgf_validate_frame_descriptor(stream, i, &bpp, &has_palette, &has_delta)) { + if (!qgf_validate_frame_descriptor(stream, i, &bpp, &has_palette, &is_panel_native, &has_delta)) { return false; } diff --git a/quantum/painter/qgf.h b/quantum/painter/qgf.h index 54585edd041..33a37709e6f 100644 --- a/quantum/painter/qgf.h +++ b/quantum/painter/qgf.h @@ -65,7 +65,7 @@ _Static_assert(sizeof(qgf_frame_offsets_v1_t) == sizeof(qgf_block_header_v1_t), typedef struct QP_PACKED qgf_frame_v1_t { qgf_block_header_v1_t header; // = { .type_id = 0x02, .neg_type_id = (~0x02), .length = 6 } - qp_image_format_t format : 8; // Frame format, see qp.h. + qp_image_format_t format : 8; // Frame format, see qp_internal_formats.h. uint8_t flags; // Frame flags, see below. painter_compression_t compression_scheme : 8; // Compression scheme, see qp.h. uint8_t transparency_index; // palette index used for transparent pixels (not yet implemented) @@ -131,6 +131,6 @@ uint32_t qgf_get_total_size(qp_stream_t *stream); bool qgf_validate_stream(qp_stream_t *stream); bool qgf_validate_block_header(qgf_block_header_v1_t *desc, uint8_t expected_typeid, int32_t expected_length); bool qgf_read_graphics_descriptor(qp_stream_t *stream, uint16_t *image_width, uint16_t *image_height, uint16_t *frame_count, uint32_t *total_bytes); -bool qgf_parse_format(qp_image_format_t format, uint8_t *bpp, bool *has_palette); +bool qgf_parse_format(qp_image_format_t format, uint8_t *bpp, bool *has_palette, bool *is_panel_native); void qgf_seek_to_frame_descriptor(qp_stream_t *stream, uint16_t frame_number); -bool qgf_parse_frame_descriptor(qgf_frame_v1_t *frame_descriptor, uint8_t *bpp, bool *has_palette, bool *is_delta, painter_compression_t *compression_scheme, uint16_t *delay); +bool qgf_parse_frame_descriptor(qgf_frame_v1_t *frame_descriptor, uint8_t *bpp, bool *has_palette, bool *is_panel_native, bool *is_delta, painter_compression_t *compression_scheme, uint16_t *delay); diff --git a/quantum/painter/qp.c b/quantum/painter/qp.c index 609163afe2a..dc69789484f 100644 --- a/quantum/painter/qp.c +++ b/quantum/painter/qp.c @@ -12,11 +12,11 @@ // Internal driver validation static bool validate_driver_vtable(painter_driver_t *driver) { - return (driver->driver_vtable && driver->driver_vtable->init && driver->driver_vtable->power && driver->driver_vtable->clear && driver->driver_vtable->viewport && driver->driver_vtable->pixdata && driver->driver_vtable->palette_convert && driver->driver_vtable->append_pixels && driver->driver_vtable->append_pixdata) ? true : false; + return (driver && driver->driver_vtable && driver->driver_vtable->init && driver->driver_vtable->power && driver->driver_vtable->clear && driver->driver_vtable->viewport && driver->driver_vtable->pixdata && driver->driver_vtable->palette_convert && driver->driver_vtable->append_pixels && driver->driver_vtable->append_pixdata) ? true : false; } static bool validate_comms_vtable(painter_driver_t *driver) { - return (driver->comms_vtable && driver->comms_vtable->comms_init && driver->comms_vtable->comms_start && driver->comms_vtable->comms_stop && driver->comms_vtable->comms_send) ? true : false; + return (driver && driver->comms_vtable && driver->comms_vtable->comms_init && driver->comms_vtable->comms_start && driver->comms_vtable->comms_stop && driver->comms_vtable->comms_send) ? true : false; } static bool validate_driver_integrity(painter_driver_t *driver) { diff --git a/quantum/painter/qp.h b/quantum/painter/qp.h index 68cb40aa59d..873a9d9f329 100644 --- a/quantum/painter/qp.h +++ b/quantum/painter/qp.h @@ -539,6 +539,12 @@ int16_t qp_drawtext_recolor(painter_device_t device, uint16_t x, uint16_t y, pai # define SSD1351_NUM_DEVICES 0 #endif // QUANTUM_PAINTER_SSD1351_ENABLE +#ifdef QUANTUM_PAINTER_SH1106_ENABLE +# include "qp_sh1106.h" +#else // QUANTUM_PAINTER_SH1106_ENABLE +# define SH1106_NUM_DEVICES 0 +#endif // QUANTUM_PAINTER_SH1106_ENABLE + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Quantum Painter Extras diff --git a/quantum/painter/qp_draw_image.c b/quantum/painter/qp_draw_image.c index fb17a05a1b6..87c59148c21 100644 --- a/quantum/painter/qp_draw_image.c +++ b/quantum/painter/qp_draw_image.c @@ -115,6 +115,7 @@ typedef struct qgf_frame_info_t { painter_compression_t compression_scheme; uint8_t bpp; bool has_palette; + bool is_panel_native; bool is_delta; uint16_t left; uint16_t top; @@ -143,7 +144,7 @@ static bool qp_drawimage_prepare_frame_for_stream_read(painter_device_t device, } // Parse out the frame info - if (!qgf_parse_frame_descriptor(&frame_descriptor, &info->bpp, &info->has_palette, &info->is_delta, &info->compression_scheme, &info->delay)) { + if (!qgf_parse_frame_descriptor(&frame_descriptor, &info->bpp, &info->has_palette, &info->is_panel_native, &info->is_delta, &info->compression_scheme, &info->delay)) { return false; } @@ -236,8 +237,8 @@ static bool qp_drawimage_recolor_impl(painter_device_t device, uint16_t x, uint1 if (frame_info->is_delta) { l = x + frame_info->left; t = y + frame_info->top; - r = x + frame_info->right - 1; - b = y + frame_info->bottom - 1; + r = x + frame_info->right; + b = y + frame_info->bottom; } else { l = x; t = y; @@ -263,7 +264,7 @@ static bool qp_drawimage_recolor_impl(painter_device_t device, uint16_t x, uint1 } bool ret = false; - if (frame_info->bpp <= 8) { + if (!frame_info->is_panel_native) { // Set up the output state qp_internal_pixel_output_state_t output_state = {.device = device, .pixel_write_pos = 0, .max_pixels = qp_internal_num_pixels_in_buffer(device)}; diff --git a/quantum/painter/qp_draw_text.c b/quantum/painter/qp_draw_text.c index ff6fc01d11e..1ac5cab6463 100644 --- a/quantum/painter/qp_draw_text.c +++ b/quantum/painter/qp_draw_text.c @@ -19,6 +19,7 @@ typedef struct qff_font_handle_t { uint16_t num_unicode_glyphs; uint8_t bpp; bool has_palette; + bool is_panel_native; painter_compression_t compression_scheme; union { qp_stream_t stream; @@ -97,7 +98,7 @@ static painter_font_handle_t qp_load_font_internal(bool (*stream_factory)(qff_fo #endif // QUANTUM_PAINTER_LOAD_FONTS_TO_RAM // Read the info (parsing already successful above, no need to check return value) - qff_read_font_descriptor(&font->stream, &font->base.line_height, &font->has_ascii_table, &font->num_unicode_glyphs, &font->bpp, &font->has_palette, &font->compression_scheme, NULL); + qff_read_font_descriptor(&font->stream, &font->base.line_height, &font->has_ascii_table, &font->num_unicode_glyphs, &font->bpp, &font->has_palette, &font->is_panel_native, &font->compression_scheme, NULL); if (!qp_internal_bpp_capable(font->bpp)) { qp_dprintf("qp_load_font: fail (image bpp too high (%d), check QUANTUM_PAINTER_SUPPORTS_256_PALETTE or QUANTUM_PAINTER_SUPPORTS_NATIVE_COLORS)\n", (int)font->bpp); diff --git a/quantum/painter/qp_internal.c b/quantum/painter/qp_internal.c index 87a30c3f9b7..0e81467e26f 100644 --- a/quantum/painter/qp_internal.c +++ b/quantum/painter/qp_internal.c @@ -16,6 +16,7 @@ enum { + (ST7735_NUM_DEVICES) // ST7735 + (GC9A01_NUM_DEVICES) // GC9A01 + (SSD1351_NUM_DEVICES) // SSD1351 + + (SH1106_NUM_DEVICES) // SH1106 }; static painter_device_t qp_devices[QP_NUM_DEVICES] = {NULL}; diff --git a/quantum/painter/qp_internal_formats.h b/quantum/painter/qp_internal_formats.h index 194f82b31a8..1beb604b9e7 100644 --- a/quantum/painter/qp_internal_formats.h +++ b/quantum/painter/qp_internal_formats.h @@ -44,8 +44,8 @@ typedef enum qp_image_format_t { PALETTE_2BPP = 0x05, PALETTE_4BPP = 0x06, PALETTE_8BPP = 0x07, - RGB565_16BPP = 0x08, - RGB888_24BPP = 0x09, + RGB565_16BPP = 0x08, // Natively streamed to the panel, no interpolation or palette handling + RGB888_24BPP = 0x09, // Natively streamed to the panel, no interpolation or palette handling } qp_image_format_t; typedef enum painter_compression_t { IMAGE_UNCOMPRESSED, IMAGE_COMPRESSED_RLE } painter_compression_t; diff --git a/quantum/painter/rules.mk b/quantum/painter/rules.mk index 7752936cbdc..ca81cffb032 100644 --- a/quantum/painter/rules.mk +++ b/quantum/painter/rules.mk @@ -6,14 +6,16 @@ QUANTUM_PAINTER_LVGL_INTEGRATION ?= no # The list of permissible drivers that can be listed in QUANTUM_PAINTER_DRIVERS VALID_QUANTUM_PAINTER_DRIVERS := \ - rgb565_surface \ - ili9163_spi \ - ili9341_spi \ - ili9488_spi \ - st7735_spi \ - st7789_spi \ - gc9a01_spi \ - ssd1351_spi + surface \ + ili9163_spi \ + ili9341_spi \ + ili9488_spi \ + st7735_spi \ + st7789_spi \ + gc9a01_spi \ + ssd1351_spi \ + sh1106_i2c \ + sh1106_spi #------------------------------------------------------------------------------- @@ -42,7 +44,9 @@ ifeq ($(strip $(QUANTUM_PAINTER_ANIMATIONS_ENABLE)), yes) endif # Comms flags +QUANTUM_PAINTER_NEEDS_COMMS_DUMMY ?= no QUANTUM_PAINTER_NEEDS_COMMS_SPI ?= no +QUANTUM_PAINTER_NEEDS_COMMS_I2C ?= no # Handler for each driver define handle_quantum_painter_driver @@ -51,12 +55,8 @@ define handle_quantum_painter_driver ifeq ($$(filter $$(strip $$(CURRENT_PAINTER_DRIVER)),$$(VALID_QUANTUM_PAINTER_DRIVERS)),) $$(error "$$(CURRENT_PAINTER_DRIVER)" is not a valid Quantum Painter driver) - else ifeq ($$(strip $$(CURRENT_PAINTER_DRIVER)),rgb565_surface) - OPT_DEFS += -DQUANTUM_PAINTER_RGB565_SURFACE_ENABLE - COMMON_VPATH += \ - $(DRIVER_PATH)/painter/generic - SRC += \ - $(DRIVER_PATH)/painter/generic/qp_rgb565_surface.c \ + else ifeq ($$(strip $$(CURRENT_PAINTER_DRIVER)),surface) + QUANTUM_PAINTER_NEEDS_SURFACE := yes else ifeq ($$(strip $$(CURRENT_PAINTER_DRIVER)),ili9163_spi) QUANTUM_PAINTER_NEEDS_COMMS_SPI := yes @@ -135,16 +135,60 @@ define handle_quantum_painter_driver $(DRIVER_PATH)/painter/tft_panel/qp_tft_panel.c \ $(DRIVER_PATH)/painter/ssd1351/qp_ssd1351.c + else ifeq ($$(strip $$(CURRENT_PAINTER_DRIVER)),sh1106_spi) + QUANTUM_PAINTER_NEEDS_SURFACE := yes + QUANTUM_PAINTER_NEEDS_COMMS_SPI := yes + QUANTUM_PAINTER_NEEDS_COMMS_SPI_DC_RESET := yes + OPT_DEFS += -DQUANTUM_PAINTER_SH1106_ENABLE -DQUANTUM_PAINTER_SH1106_SPI_ENABLE + COMMON_VPATH += \ + $(DRIVER_PATH)/painter/oled_panel \ + $(DRIVER_PATH)/painter/sh1106 + SRC += \ + $(DRIVER_PATH)/painter/oled_panel/qp_oled_panel.c \ + $(DRIVER_PATH)/painter/sh1106/qp_sh1106.c + + else ifeq ($$(strip $$(CURRENT_PAINTER_DRIVER)),sh1106_i2c) + QUANTUM_PAINTER_NEEDS_SURFACE := yes + QUANTUM_PAINTER_NEEDS_COMMS_I2C := yes + OPT_DEFS += -DQUANTUM_PAINTER_SH1106_ENABLE -DQUANTUM_PAINTER_SH1106_I2C_ENABLE + COMMON_VPATH += \ + $(DRIVER_PATH)/painter/oled_panel \ + $(DRIVER_PATH)/painter/sh1106 + SRC += \ + $(DRIVER_PATH)/painter/oled_panel/qp_oled_panel.c \ + $(DRIVER_PATH)/painter/sh1106/qp_sh1106.c + endif endef # Iterate through the listed drivers for the build, including what's necessary $(foreach qp_driver,$(QUANTUM_PAINTER_DRIVERS),$(eval $(call handle_quantum_painter_driver,$(qp_driver)))) +# If a surface is needed, set up the required files +ifeq ($(strip $(QUANTUM_PAINTER_NEEDS_SURFACE)), yes) + QUANTUM_PAINTER_NEEDS_COMMS_DUMMY := yes + OPT_DEFS += -DQUANTUM_PAINTER_SURFACE_ENABLE + COMMON_VPATH += \ + $(DRIVER_PATH)/painter/generic + SRC += \ + $(DRIVER_PATH)/painter/generic/qp_surface_common.c \ + $(DRIVER_PATH)/painter/generic/qp_surface_mono1bpp.c \ + $(DRIVER_PATH)/painter/generic/qp_surface_rgb565.c +endif + +# If dummy comms is needed, set up the required files +ifeq ($(strip $(QUANTUM_PAINTER_NEEDS_COMMS_DUMMY)), yes) + OPT_DEFS += -DQUANTUM_PAINTER_DUMMY_COMMS_ENABLE + VPATH += $(DRIVER_PATH)/painter/comms + SRC += \ + $(QUANTUM_DIR)/painter/qp_comms.c \ + $(DRIVER_PATH)/painter/comms/qp_comms_dummy.c +endif + # If SPI comms is needed, set up the required files ifeq ($(strip $(QUANTUM_PAINTER_NEEDS_COMMS_SPI)), yes) OPT_DEFS += -DQUANTUM_PAINTER_SPI_ENABLE - QUANTUM_LIB_SRC += spi_master.c + SPI_DRIVER_REQUIRED = yes VPATH += $(DRIVER_PATH)/painter/comms SRC += \ $(QUANTUM_DIR)/painter/qp_comms.c \ @@ -155,7 +199,17 @@ ifeq ($(strip $(QUANTUM_PAINTER_NEEDS_COMMS_SPI)), yes) endif endif +# If I2C comms is needed, set up the required files +ifeq ($(strip $(QUANTUM_PAINTER_NEEDS_COMMS_I2C)), yes) + OPT_DEFS += -DQUANTUM_PAINTER_I2C_ENABLE + I2C_DRIVER_REQUIRED = yes + VPATH += $(DRIVER_PATH)/painter/comms + SRC += \ + $(QUANTUM_DIR)/painter/qp_comms.c \ + $(DRIVER_PATH)/painter/comms/qp_comms_i2c.c +endif + # Check if LVGL needs to be enabled ifeq ($(strip $(QUANTUM_PAINTER_LVGL_INTEGRATION)), yes) - include $(QUANTUM_DIR)/painter/lvgl/rules.mk + include $(QUANTUM_DIR)/painter/lvgl/rules.mk endif