Skip to content

Commit

Permalink
Initial
Browse files Browse the repository at this point in the history
  • Loading branch information
elpekenin committed Aug 26, 2023
1 parent a9e2f6a commit a753cca
Show file tree
Hide file tree
Showing 9 changed files with 350 additions and 1 deletion.
38 changes: 38 additions & 0 deletions docs/quantum_painter.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ Supported devices:
| 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` |
| LS0xx (SPI) | MIP LCD | User-defined | SPI | `QUANTUM_PAINTER_DRIVERS += ls0xx_spi` |
| RGB565 Surface | Virtual | User-defined | None | `QUANTUM_PAINTER_DRIVERS += rgb565_surface` |

## Quantum Painter Configuration :id=quantum-painter-config
Expand Down Expand Up @@ -386,6 +387,43 @@ Native color format rgb565 is compatible with ST7789

<!-- tabs:end -->

### ** Common: MIP (SPI) **

Most MIP display panels use a 3-pin interface -- SPI SCK, SPI MOSI and SPI CS pins.

For these displays, QMK's `spi_master` must already be correctly configured for the platform you're building for.

The pin assignment for SPI CS is specified during device construction.

<!-- tabs:start -->

#### ** LS0xx Series **

Enabling support for the LS0xx family of displays in Quantum Painter is done by adding the following to `rules.mk`:

```make
QUANTUM_PAINTER_ENABLE = yes
QUANTUM_PAINTER_DRIVERS += ls0xx_spi
```

Creating a device in firmware can then be done with the following API:

```c
uint8_t buf[SURFACE_REQUIRED_BUFFER_BYTE_SIZE(panel_width, panel_height, 1)] = {0}; // framebuffer for pixels' data
painter_device_t qp_ls0xx_make_spi_device(uint16_t panel_width, uint16_t panel_height, pin_t chip_select_pin, uint16_t spi_divisor, int spi_mode, void *buf);
```
The device handle returned from the `qp_ls0xx_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 LS0XX_NUM_DEVICES 3
```

<!-- tabs:end -->

### ** Common: Surfaces **

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.
Expand Down
57 changes: 57 additions & 0 deletions drivers/painter/mip_panel/qp_ls0xx.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Copyright 2023 Pablo Martinez (@elpekenin) <elpekenin@elpekenin.dev>
// SPDX-License-Identifier: GPL-2.0-or-later

#include "qp_ls0xx.h"
#include "qp_ls0xx_panel.h"

#ifdef QUANTUM_PAINTER_LS0XX_SPI_ENABLE
# include "qp_comms_spi.h"
#endif // QUANTUM_PAINTER_LS0XX_SPI_ENABLE

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Common

// Driver storage
mip_panel_painter_device_t ls0xx_device_t_drivers[LS0XX_NUM_DEVICES] = {0};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// SPI

#ifdef QUANTUM_PAINTER_LS0XX_SPI_ENABLE

// Factory function for creating a handle to the LS0XX device
painter_device_t qp_ls0xx_device_t_make_spi_device(uint16_t panel_width, uint16_t panel_height, pin_t chip_select_pin, uint16_t spi_divisor, int spi_mode, void *buf) {
for (uint32_t i = 0; i < LS0XX_NUM_DEVICES; ++i) {
mip_panel_painter_device_t *driver = &ls0xx_device_t_drivers[i];
if (!driver->base.driver_vtable) {
painter_device_t surface = qp_make_mono1bpp_surface_advanced(&driver->surface, 1, panel_width, panel_height, buf);
if (!surface) {
return NULL;
}
driver->base.driver_vtable = (const painter_driver_vtable_t *)&ls0xx_driver_vtable;
driver->base.comms_vtable = (const painter_comms_vtable_t *)&spi_comms_vtable;
driver->base.native_bits_per_pixel = 1;
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->base.comms_config = &driver->spi_config;
driver->spi_config.chip_select_pin = chip_select_pin;
driver->spi_config.lsb_first = false;
driver->spi_config.divisor = spi_divisor;
driver->spi_config.mode = spi_mode;
driver->surface.invert_order = true;
if (!qp_internal_register_device((painter_device_t)driver)) {
memset(driver, 0, sizeof(mip_panel_painter_device_t));
return NULL;
}
return (painter_device_t)driver;
}
}
return NULL;
}

#endif // QUANTUM_PAINTER_LS0XX_SPI_ENABLE

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
36 changes: 36 additions & 0 deletions drivers/painter/mip_panel/qp_ls0xx.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright 2023 Pablo Martinez (@elpekenin) <elpekenin@elpekenin.dev>
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include "gpio.h"
#include "qp_internal.h"

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Quantum Painter ILI9163 configurables (add to your keyboard's config.h)

#ifndef LS0XX_NUM_DEVICES
/**
* @def This controls the maximum number of LS0XX devices that Quantum Painter can communicate with at any one time.
* Increasing this number allows for multiple displays to be used.
*/
# define LS0XX_NUM_DEVICES 1
#endif

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Quantum Painter ILI9163 device factories

#ifdef QUANTUM_PAINTER_LS0XX_SPI_ENABLE
/**
* Factory method for an LS0XX MIP SPI device.
*
* @param panel_width[in] the width of the display panel
* @param panel_height[in] the height of the display panel
* @param chip_select_pin[in] the GPIO pin used for SPI chip select
* @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
* @param buf[in] the address of the buffer where data will be stored
* @return the device handle used with all drawing routines in Quantum Painter
*/
painter_device_t qp_ls0xx_device_t_make_spi_device(uint16_t panel_width, uint16_t panel_height, pin_t chip_select_pin, uint16_t spi_divisor, int spi_mode, void *buf);
#endif // QUANTUM_PAINTER_LS0XX_SPI_ENABLE
10 changes: 10 additions & 0 deletions drivers/painter/mip_panel/qp_ls0xx_opcodes.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Copyright 2023 Pablo Martinez (@elpekenin) <elpekenin@elpekenin.dev>
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Quantum Painter LS0XX command opcodes (lsb_first, as the display expects them)
#define LS0XX_WRITE 0x80 // Start sending framebuffer
#define LS0XX_VCOM 0x40 // Set VCOM high
#define LS0XX_CLEAR 0x20 // Clear
158 changes: 158 additions & 0 deletions drivers/painter/mip_panel/qp_ls0xx_panel.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
// Copyright 2023 Pablo Martinez (@elpekenin) <elpekenin@elpekenin.dev>
// SPDX-License-Identifier: GPL-2.0-or-later

/* Code adapted from Zephyr's and Adafruit's drivers
*
* Should support several LS0xx because they are pretty much
* the same, with different sizes.
*
* Driver was developed on a LS013B7DH03, cant grant anything else will work as is.
*
* Note: xx does not mean 2 chars, names are pretty long
*/

#include "qp_internal.h"
#include "qp_surface.h"
#include "qp_surface_internal.h"
#include "qp_ls0xx_panel.h"
#include "qp_ls0xx_opcodes.h"

#ifdef QUANTUM_PAINTER_SPI_ENABLE
# include "spi_master.h"
#endif // QUANTUM_PAINTER_SPI_ENABLE

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Quantum Painter API implementations

__attribute__((weak)) bool qp_ls0xx_init(painter_device_t device, painter_rotation_t rotation) {
mip_panel_painter_device_t *mip_dev = (mip_panel_painter_device_t *)device;
painter_driver_t * surface = (painter_driver_t *)&mip_dev->surface;

// Init the internal surface
if (!surface->driver_vtable->init(surface, rotation)) {
qp_dprintf("Failed to init internal surface in qp_ls0xx_init\n");
return false;
}

mip_dev->base.rotation = rotation;
surface->rotation = rotation;

writePinHigh(mip_dev->spi_config.chip_select_pin);
const uint8_t ls0xx_init_sequence[] = {LS0XX_CLEAR, 0};
spi_transmit(ls0xx_init_sequence, ARRAY_SIZE(ls0xx_init_sequence));
writePinLow(mip_dev->spi_config.chip_select_pin);

return true;
}

bool qp_ls0xx_passthru_power(painter_device_t device, bool power_on) {
// No-op
return true;
}

bool qp_ls0xx_passthru_clear(painter_device_t device) {
// Just re-init the display
painter_driver_t *driver = (painter_driver_t *)device;
return qp_ls0xx_init(device, driver->rotation);
}

// FIXME: This code may not work on the biggest displays of the family
// Since it uses uint8_t, we can "only" have 255 rows
bool qp_ls0xx_flush(painter_device_t device) {
mip_panel_painter_device_t *mip_dev = (mip_panel_painter_device_t *)device;
surface_painter_device_t * surface = &(mip_dev->surface);
surface_dirty_data_t dirty = surface->dirty;

if (!dirty.is_dirty) {
// nothing to be sent
return true;
}

// find out dirty area
uint8_t top = dirty.t;
uint8_t bottom = dirty.b;

// bytes sent for each row's data
uint8_t bytes_per_line = (mip_dev->base.panel_width + 7) / 8 * mip_dev->base.native_bits_per_pixel;
// offset used to access such data
uint16_t buffer_offset = top * bytes_per_line;

// start sending
writePinHigh(mip_dev->spi_config.chip_select_pin);
uint8_t cmd = LS0XX_WRITE | LS0XX_VCOM;
spi_transmit(&cmd, 1);

// dummy data for alignment, value doesnt matter
uint8_t dummy = 0;

// iterate over the lines
for (uint8_t i = 0; i < bottom + 1 - top; ++i) {
// set y-pos (counts from 1, needs the `+1`)
uint8_t n_line = bitrev(i + top + 1);
spi_transmit(&n_line, 1);

// send data
spi_transmit(&surface->u8buffer[buffer_offset], bytes_per_line);
buffer_offset += bytes_per_line;

spi_transmit(&dummy, 1);
}

spi_transmit(&dummy, 1);
writePinLow(mip_dev->spi_config.chip_select_pin);

// clear surface's dirty area, no API to prevent extra prints
surface->base.driver_vtable->flush(surface);

return true;
}

bool qp_ls0xx_passthru_viewport(painter_device_t device, uint16_t left, uint16_t top, uint16_t right, uint16_t bottom) {
mip_panel_painter_device_t *mip_dev = (mip_panel_painter_device_t *)device;
painter_driver_t * surface = (painter_driver_t *)&mip_dev->surface;

return surface->driver_vtable->viewport(surface, left, top, right, bottom);
}

bool qp_ls0xx_passthru_pixdata(painter_device_t device, const void *pixel_data, uint32_t native_pixel_count) {
mip_panel_painter_device_t *mip_dev = (mip_panel_painter_device_t *)device;
painter_driver_t * surface = (painter_driver_t *)&mip_dev->surface;

return surface->driver_vtable->pixdata(surface, pixel_data, native_pixel_count);
}

bool qp_ls0xx_passthru_palette_convert(painter_device_t device, int16_t palette_size, qp_pixel_t *palette) {
mip_panel_painter_device_t *mip_dev = (mip_panel_painter_device_t *)device;
painter_driver_t * surface = (painter_driver_t *)&mip_dev->surface;

return surface->driver_vtable->palette_convert(surface, palette_size, palette);
}

bool qp_ls0xx_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) {
mip_panel_painter_device_t *mip_dev = (mip_panel_painter_device_t *)device;
painter_driver_t * surface = (painter_driver_t *)&mip_dev->surface;

return surface->driver_vtable->append_pixels(surface, target_buffer, palette, pixel_offset, pixel_count, palette_indices);
}

bool qp_ls0xx_passthru_append_pixdata(painter_device_t device, uint8_t *target_buffer, uint32_t pixdata_offset, uint8_t pixdata_byte) {
mip_panel_painter_device_t *mip_dev = (mip_panel_painter_device_t *)device;
painter_driver_t * surface = (painter_driver_t *)&mip_dev->surface;

return surface->driver_vtable->append_pixdata(surface, target_buffer, pixdata_offset, pixdata_byte);
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Driver vtable

const painter_driver_vtable_t ls0xx_driver_vtable = {
.init = qp_ls0xx_init,
.power = qp_ls0xx_passthru_power,
.clear = qp_ls0xx_passthru_clear,
.flush = qp_ls0xx_flush,
.pixdata = qp_ls0xx_passthru_pixdata,
.viewport = qp_ls0xx_passthru_viewport,
.palette_convert = qp_ls0xx_passthru_palette_convert,
.append_pixels = qp_ls0xx_passthru_append_pixels,
.append_pixdata = qp_ls0xx_passthru_append_pixdata,
};
32 changes: 32 additions & 0 deletions drivers/painter/mip_panel/qp_ls0xx_panel.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Copyright 2023 Pablo Martinez (@elpekenin) <elpekenin@elpekenin.dev>
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include "qp_internal.h"
#include "qp_surface_internal.h"

#include "qp_comms_spi.h"

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Common MIP panel implementation for Sharp displays, using 3-wire SPI.

// Device definition
typedef struct mip_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_config_t spi_config;
#endif // QUANTUM_PAINTER_SPI_ENABLE

// TODO: I2C/parallel etc.
};

surface_painter_device_t surface;
} mip_panel_painter_device_t;

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Forward declarations for injecting into concrete driver vtables
extern const painter_driver_vtable_t ls0xx_driver_vtable;
6 changes: 6 additions & 0 deletions quantum/painter/qp.h
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,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_LS0XX_ENABLE
# include "qp_ls0xx.h"
#else // QUANTUM_PAINTER_LS0XX_ENABLE
# define LS0XX_NUM_DEVICES 0
#endif // QUANTUM_PAINTER_LS0XX_ENABLE

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Quantum Painter Extras

Expand Down
1 change: 1 addition & 0 deletions quantum/painter/qp_internal.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ enum {
+ (ST7735_NUM_DEVICES) // ST7735
+ (GC9A01_NUM_DEVICES) // GC9A01
+ (SSD1351_NUM_DEVICES) // SSD1351
+ (LS0XX_NUM_DEVICES) // LS0XX
};

static painter_device_t qp_devices[QP_NUM_DEVICES] = {NULL};
Expand Down
13 changes: 12 additions & 1 deletion quantum/painter/rules.mk
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ VALID_QUANTUM_PAINTER_DRIVERS := \
st7735_spi \
st7789_spi \
gc9a01_spi \
ssd1351_spi
ssd1351_spi \
ls0xx_spi

#-------------------------------------------------------------------------------

Expand Down Expand Up @@ -135,6 +136,16 @@ 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)),ls0xx_spi)
QUANTUM_PAINTER_NEEDS_SURFACE := yes
QUANTUM_PAINTER_NEEDS_COMMS_SPI := yes
OPT_DEFS += -DQUANTUM_PAINTER_LS0XX_ENABLE -DQUANTUM_PAINTER_LS0XX_SPI_ENABLE
COMMON_VPATH += \
$(DRIVER_PATH)/painter/mip_panel
SRC += \
$(DRIVER_PATH)/painter/mip_panel/qp_ls0xx_panel.c \
$(DRIVER_PATH)/painter/mip_panel/qp_ls0xx.c

endif
endef

Expand Down

0 comments on commit a753cca

Please sign in to comment.