diff options
-rw-r--r-- | jack/jack.c | 3 | ||||
-rw-r--r-- | jack/jack.h | 1 | ||||
-rw-r--r-- | keymaps/beiwagon/keymap.c | 14 | ||||
-rw-r--r-- | keymaps/onekey/f411_ls0x/3270_11.qff.c | 11 | ||||
-rw-r--r-- | keymaps/onekey/f411_ls0x/keymap.c | 15 | ||||
-rw-r--r-- | keymaps/onekey/f411_ls0x/keymap.json | 3 | ||||
-rw-r--r-- | keymaps/onekey/f411_ls0x/rules.mk | 3 | ||||
-rw-r--r-- | modules/ls0xx/ls0xx.c | 277 | ||||
-rw-r--r-- | modules/ls0xx/ls0xx.h | 27 | ||||
-rw-r--r-- | modules/ls0xx/ls0xx_opcodes.h | 9 | ||||
-rw-r--r-- | modules/ls0xx/qmk_module.json | 4 | ||||
-rw-r--r-- | modules/ls0xx/rules.mk | 7 |
12 files changed, 359 insertions, 15 deletions
diff --git a/jack/jack.c b/jack/jack.c index 9a59cb0..f2e9944 100644 --- a/jack/jack.c +++ b/jack/jack.c @@ -80,6 +80,7 @@ void keyboard_post_init_user(void) { if (!autocorrect_is_enabled()) autocorrect_enable(); #endif + keyboard_post_init_keymap(); } void matrix_scan_user(void) { @@ -103,6 +104,8 @@ uint16_t keycode_config(uint16_t keycode) { return keycode; } uint8_t mod_config(uint8_t mod) { return mod; } #endif +__attribute__((weak)) void keyboard_post_init_keymap(void) {} + #ifdef RP2040_MATH_IN_ROM typedef void (*init_fn)(void); diff --git a/jack/jack.h b/jack/jack.h index 0bce15b..eb9182c 100644 --- a/jack/jack.h +++ b/jack/jack.h @@ -29,6 +29,7 @@ void tap_code_register(uint8_t code, uint8_t mods, uint16_t delay, bool register_tap); void process_tap_code_buffer(void); void tap_string(const char *str); +void keyboard_post_init_keymap(void); #ifdef TAP_DANCE_ENABLE void td_reset(tap_dance_state_t *state, void *user_data); void em_dash_mins(tap_dance_state_t *state, void *user_data); diff --git a/keymaps/beiwagon/keymap.c b/keymaps/beiwagon/keymap.c index 00b8dd9..eaa9c44 100644 --- a/keymaps/beiwagon/keymap.c +++ b/keymaps/beiwagon/keymap.c @@ -5,16 +5,22 @@ // clang-format off const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { [_BASE] = LAYOUT_jack( - G(KC_F), G(KC_T), G(KC_R), + G(KC_F), G(KC_Q), G(KC_R), KC_VOLD, PNP, KC_VOLU, REMOVE, XRGB_HUI, XRGB_NXT, XRGB_TOG, XRGB_HUD, TG(_LOWER) ), [_LOWER] = LAYOUT_jack( - QK_MAKE, _______, _______, - QK_BOOT, _______, _______, - EE_CLR, _______, _______, + QK_BOOT, EE_CLR, QK_MAKE, + _______, _______, _______, + _______, _______, _______, _______, _______, _______ ) }; // clang-format on + +void keyboard_post_init_keymap(void) { + for (int i = 0; i < RGB_MATRIX_LED_COUNT; ++i) + if (g_led_config.flags[i] & LED_FLAG_UNDERGLOW) + g_led_config.flags[i] &= ~LED_FLAG_UNDERGLOW; +} diff --git a/keymaps/onekey/f411_ls0x/3270_11.qff.c b/keymaps/onekey/f411_ls0x/3270_11.qff.c index 43e5cbc..c241e51 100644 --- a/keymaps/onekey/f411_ls0x/3270_11.qff.c +++ b/keymaps/onekey/f411_ls0x/3270_11.qff.c @@ -1,15 +1,18 @@ -// Copyright 2025 QMK -- generated source code only, font retains original copyright -// SPDX-License-Identifier: GPL-2.0-or-later +// Copyright 2025 QMK -- generated source code only, font retains original +// copyright SPDX-License-Identifier: GPL-2.0-or-later // This file was auto-generated by `painter-convert-font-image` with arguments: // format | mono2 -// unicode_glyphs | +// unicode_glyphs | // output | qmk_firmware // input | 3270_11.png // Font's metadata // --------------- -// Glyphs: , !, ", #, $, %, &, ', (, ), *, +, ,, -, ., /, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, :, ;, <, =, >, ?, @, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, [, \, ], ^, _, `, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, {, |, }, ~ +// Glyphs: , !, ", #, $, %, &, ', (, ), *, +, ,, -, ., /, 0, 1, 2, 3, 4, 5, 6, +// 7, 8, 9, :, ;, <, =, >, ?, @, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, +// Q, R, S, T, U, V, W, X, Y, Z, [, \, ], ^, _, `, a, b, c, d, e, f, g, h, i, j, +// k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, {, |, }, ~ #include <qp.h> diff --git a/keymaps/onekey/f411_ls0x/keymap.c b/keymaps/onekey/f411_ls0x/keymap.c index 75d65f3..584bce6 100644 --- a/keymaps/onekey/f411_ls0x/keymap.c +++ b/keymaps/onekey/f411_ls0x/keymap.c @@ -3,12 +3,11 @@ #include QMK_KEYBOARD_H #include "qp.h" #include "qp_surface.h" -#include "3270_11.qff.h" +#include "ls0xx.h" const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { LAYOUT_ortho_1x1(KC_A)}; -static painter_font_handle_t font; static painter_device_t display; static uint8_t framebuffer[SURFACE_REQUIRED_BUFFER_BYTE_SIZE( LS0XX_WIDTH, LS0XX_HEIGHT, 1)] = {0}; @@ -17,7 +16,15 @@ void keyboard_post_init_user(void) { display = qp_ls0xx_device_t_make_spi_device(LS0XX_WIDTH, LS0XX_HEIGHT, LS0XX_CS_PIN, LS0XX_SPI_DIVISOR, 0, framebuffer); - font = qp_load_font_mem(font_3270_11); qp_init(display, QP_ROTATION_0); - qp_drawtext(display, 0, 0, font, "foo bar baz"); +} + +void housekeeping_task_user(void) { + static uint32_t last_draw = 0; + if (timer_elapsed32(last_draw) > 33) { + last_draw = timer_read32(); + for (int i = 0; i < LS0XX_HEIGHT - 1; i += 8) + qp_rect(display, 0, i, 7, i + 7, 255, 255, 255, true); + qp_flush(display); + } } diff --git a/keymaps/onekey/f411_ls0x/keymap.json b/keymaps/onekey/f411_ls0x/keymap.json new file mode 100644 index 0000000..9eba3d8 --- /dev/null +++ b/keymaps/onekey/f411_ls0x/keymap.json @@ -0,0 +1,3 @@ +{ + "modules": ["ls0xx"] +} diff --git a/keymaps/onekey/f411_ls0x/rules.mk b/keymaps/onekey/f411_ls0x/rules.mk index e1e5f55..7166553 100644 --- a/keymaps/onekey/f411_ls0x/rules.mk +++ b/keymaps/onekey/f411_ls0x/rules.mk @@ -1,6 +1,3 @@ -CONSOLE_ENABLE = yes BOOTMAGIC_ENABLE = yes QUANTUM_PAINTER_ENABLE = yes -QUANTUM_PAINTER_DRIVERS += ls0xx_spi SRC += 3270_11.qff.c -EXTRAFLAGS += -Wno-maybe-uninitialized diff --git a/modules/ls0xx/ls0xx.c b/modules/ls0xx/ls0xx.c new file mode 100644 index 0000000..01a02c6 --- /dev/null +++ b/modules/ls0xx/ls0xx.c @@ -0,0 +1,277 @@ +// Copyright Pablo Martinez (@elpekenin) <[email protected]> +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "ls0xx.h" + +#include "ls0xx_opcodes.h" +#include "qp_comms_spi.h" +#include "qp_surface_internal.h" +#include "spi_master.h" + +typedef struct { + painter_driver_t base; // must be first, so it can be cast to/from the + // painter_device_t* type + qp_comms_spi_config_t spi_config; + surface_painter_device_t surface; +} ls0xx_painter_device_t; + +// need custom comms vtable for inverted CS logic +bool inverted_cs_init(painter_device_t device) { + painter_driver_t *driver = (painter_driver_t *)device; + qp_comms_spi_config_t *comms_config = + (qp_comms_spi_config_t *)driver->comms_config; + + // Initialize the SPI peripheral + spi_init(); + + // Set up CS as output high + gpio_set_pin_output(comms_config->chip_select_pin); + gpio_write_pin_low(comms_config->chip_select_pin); + + return true; +} + +bool inverted_cs_start(painter_device_t device) { + painter_driver_t *driver = (painter_driver_t *)device; + qp_comms_spi_config_t *comms_config = + (qp_comms_spi_config_t *)driver->comms_config; + + spi_start_config_t config = (spi_start_config_t){ + .slave_pin = comms_config->chip_select_pin, + .lsb_first = comms_config->lsb_first, + .mode = comms_config->mode, + .divisor = comms_config->divisor, + .cs_active_low = false, + }; + + return spi_start_extended(&config); +} + +const painter_comms_vtable_t spi_comms_inverted_cs_vtable = { + .comms_init = inverted_cs_init, + .comms_start = inverted_cs_start, + .comms_send = qp_comms_spi_send_data, + .comms_stop = qp_comms_spi_stop, +}; + +ls0xx_painter_device_t ls0xx_device_t_drivers[LS0XX_NUM_DEVICES] = {0}; + +__attribute__((weak)) bool qp_ls0xx_init(painter_device_t device, + painter_rotation_t rotation) { + ls0xx_painter_device_t *ls0xx = (ls0xx_painter_device_t *)device; + painter_driver_t *surface = (painter_driver_t *)&ls0xx->surface; + + // Change the surface geometry based on the panel rotation + if (rotation == QP_ROTATION_90 || rotation == QP_ROTATION_270) { + surface->panel_width = ls0xx->base.panel_height; + surface->panel_height = ls0xx->base.panel_width; + } else { + surface->panel_width = ls0xx->base.panel_width; + surface->panel_height = ls0xx->base.panel_height; + } + + // Init the internal surface + if (!surface->driver_vtable->init(surface, QP_ROTATION_0)) { + qp_dprintf("Failed to init internal surface in qp_ls0xx_init\n"); + return false; + } + + ls0xx->base.rotation = rotation; + + writePinHigh(ls0xx->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(ls0xx->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); +} + +bool qp_ls0xx_flush(painter_device_t device) { + ls0xx_painter_device_t *ls0xx = (ls0xx_painter_device_t *)device; + surface_painter_device_t *surface = &(ls0xx->surface); + surface_dirty_data_t dirty = surface->dirty; + + if (!dirty.is_dirty) { + // nothing to be sent + return true; + } + + // find out dirty area. update is done on **complete** horizontal lines, + // which means that if device is sideways, we need to check l/r instead + uint16_t top = 0; + uint16_t bottom = 0; + switch (ls0xx->base.rotation) { + case QP_ROTATION_0: + top = dirty.t; + bottom = dirty.b; + break; + + // TODO: Check whether this are backwards + case QP_ROTATION_90: + top = dirty.l; + bottom = dirty.r; + break; + + case QP_ROTATION_180: + top = dirty.b; + bottom = dirty.t; + break; + + case QP_ROTATION_270: + top = dirty.r; + bottom = dirty.l; + break; + } + + // bytes sent for each row's data + uint8_t bytes_per_line = + (ls0xx->base.panel_width + 7) / 8 * ls0xx->base.native_bits_per_pixel; + // offset used to access such data + uint16_t buffer_offset = top * bytes_per_line; + + // start sending + writePinHigh(ls0xx->spi_config.chip_select_pin); + uint8_t cmd = LS0XX_WRITE | LS0XX_VCOM; + spi_transmit(&cmd, 1); + + // dummy data for alignment, value doesn't 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(ls0xx->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) { + ls0xx_painter_device_t *ls0xx = (ls0xx_painter_device_t *)device; + painter_driver_t *surface = (painter_driver_t *)&ls0xx->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) { + ls0xx_painter_device_t *ls0xx = (ls0xx_painter_device_t *)device; + painter_driver_t *surface = (painter_driver_t *)&ls0xx->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) { + ls0xx_painter_device_t *ls0xx = (ls0xx_painter_device_t *)device; + painter_driver_t *surface = (painter_driver_t *)&ls0xx->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) { + ls0xx_painter_device_t *ls0xx = (ls0xx_painter_device_t *)device; + painter_driver_t *surface = (painter_driver_t *)&ls0xx->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) { + ls0xx_painter_device_t *ls0xx = (ls0xx_painter_device_t *)device; + painter_driver_t *surface = (painter_driver_t *)&ls0xx->surface; + + return surface->driver_vtable->append_pixdata(surface, target_buffer, + pixdata_offset, pixdata_byte); +} + +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, +}; + +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) { + ls0xx_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_inverted_cs_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; + + // SPI and other pin configuration + 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; + + if (!qp_internal_register_device((painter_device_t)driver)) { + memset(driver, 0, sizeof(ls0xx_painter_device_t)); + return NULL; + } + + return (painter_device_t)driver; + } + } + + return NULL; +} diff --git a/modules/ls0xx/ls0xx.h b/modules/ls0xx/ls0xx.h new file mode 100644 index 0000000..49339e9 --- /dev/null +++ b/modules/ls0xx/ls0xx.h @@ -0,0 +1,27 @@ +// Copyright Pablo Martinez (@elpekenin) <[email protected]> +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "qp.h" + +#ifndef LS0XX_NUM_DEVICES +#define LS0XX_NUM_DEVICES 1 +#endif + +/** + * Factory method for an LS0XX SPI device. + * + * Args: + * panel_width: Width of the display + * panel_height: Height of the display + * chip_select_pin: GPIO pin used for SPI chip select (CS) + * spi_divisor: SPI divisor to use when communicating with the display + * spi_mode: SPI mode to use when communicating with the display + * buf: Address of the buffer where data will be stored + */ +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); diff --git a/modules/ls0xx/ls0xx_opcodes.h b/modules/ls0xx/ls0xx_opcodes.h new file mode 100644 index 0000000..2c3928a --- /dev/null +++ b/modules/ls0xx/ls0xx_opcodes.h @@ -0,0 +1,9 @@ +// Copyright Pablo Martinez (@elpekenin) <[email protected]> +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +// NOTE: written 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 diff --git a/modules/ls0xx/qmk_module.json b/modules/ls0xx/qmk_module.json new file mode 100644 index 0000000..4d5af68 --- /dev/null +++ b/modules/ls0xx/qmk_module.json @@ -0,0 +1,4 @@ +{ + "maintainer": "elpekenin", + "module_name": "ls0xx" +} diff --git a/modules/ls0xx/rules.mk b/modules/ls0xx/rules.mk new file mode 100644 index 0000000..351f33b --- /dev/null +++ b/modules/ls0xx/rules.mk @@ -0,0 +1,7 @@ +QUANTUM_PAINTER_NEEDS_SURFACE := yes + +# FIXME: not sure if this here is good enough, may be processed *after* +# qp's internal makefile and not impact it +QUANTUM_PAINTER_NEEDS_COMMS_SPI := yes + +SRC += $(MODULE_PATH_LS0XX)/ls0xx.c |