diff options
Diffstat (limited to 'drivers')
24 files changed, 2201 insertions, 1 deletions
| diff --git a/drivers/gpio/sn74x154.c b/drivers/gpio/sn74x154.c new file mode 100644 index 0000000000..5f21f12b55 --- /dev/null +++ b/drivers/gpio/sn74x154.c @@ -0,0 +1,58 @@ +/* Copyright 2022 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program.  If not, see <http://www.gnu.org/licenses/>. + */ + +#include "sn74x154.h" +#include "gpio.h" + +#define ADDRESS_PIN_COUNT 4 + +#ifndef SN74X154_ADDRESS_PINS +#    error sn74x154: no address pins defined! +#endif + +static const pin_t address_pins[ADDRESS_PIN_COUNT] = SN74X154_ADDRESS_PINS; + +void sn74x154_init(void) { +    for (int i = 0; i < ADDRESS_PIN_COUNT; i++) { +        setPinOutput(address_pins[i]); +        writePinLow(address_pins[i]); +    } + +#if defined(SN74X154_E0_PIN) +    setPinOutput(SN74X154_E0_PIN); +    writePinHigh(SN74X154_E0_PIN); +#endif + +#if defined(SN74X154_E1_PIN) +    setPinOutput(SN74X154_E1_PIN); +    writePinHigh(SN74X154_E1_PIN); +#endif +} + +void sn74x154_set_enabled(bool enabled) { +#if defined(SN74X154_E0_PIN) +    writePin(SN74X154_E0_PIN, !enabled); +#endif +#if defined(SN74X154_E1_PIN) +    writePin(SN74X154_E1_PIN, !enabled); +#endif +} + +void sn74x154_set_addr(uint8_t address) { +    for (int i = 0; i < ADDRESS_PIN_COUNT; i++) { +        writePin(address_pins[i], address & (1 << i)); +    } +} diff --git a/drivers/gpio/sn74x154.h b/drivers/gpio/sn74x154.h new file mode 100644 index 0000000000..ce6a9ddb0e --- /dev/null +++ b/drivers/gpio/sn74x154.h @@ -0,0 +1,48 @@ +/* Copyright 2022 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program.  If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <stdint.h> +#include <stdbool.h> + +/** + * Driver for 74x154 4-to-16 decoder/demultiplexer with inverting outputs + * https://assets.nexperia.com/documents/data-sheet/74HC_HCT154.pdf + */ + +/** + * Initialize the address and output enable pins. + */ +void sn74x154_init(void); + +/** + * Set the enabled state. + * + * When enabled is true, pulls the E0 and E1 pins low. + * + * \param enabled The enable state to set. + */ +void sn74x154_set_enabled(bool enabled); + +/** + * Set the output pin address. + * + * The selected output pin will be pulled low, while the remaining output pins will be high. + * + * \param address The address to set, from 0 to 15. + */ +void sn74x154_set_addr(uint8_t address); diff --git a/drivers/lcd/hd44780.c b/drivers/lcd/hd44780.c new file mode 100644 index 0000000000..c988ebe56c --- /dev/null +++ b/drivers/lcd/hd44780.c @@ -0,0 +1,284 @@ +/* +Copyright 2022 + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program.  If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "hd44780.h" +#include "gpio.h" +#include "progmem.h" +#include "wait.h" + +#ifndef HD44780_DATA_PINS +#    error hd44780: no data pins defined! +#endif + +#ifndef HD44780_RS_PIN +#    error hd44780: no RS pin defined! +#endif + +#ifndef HD44780_RW_PIN +#    error hd44780: no R/W pin defined! +#endif + +#ifndef HD44780_E_PIN +#    error hd44780: no E pin defined! +#endif + +static const pin_t data_pins[4] = HD44780_DATA_PINS; + +#ifndef HD44780_DISPLAY_COLS +#    define HD44780_DISPLAY_COLS 16 +#endif + +#ifndef HD44780_DISPLAY_LINES +#    define HD44780_DISPLAY_LINES 2 +#endif + +#ifndef HD44780_DDRAM_LINE0_ADDR +#    define HD44780_DDRAM_LINE0_ADDR 0x00 +#endif +#ifndef HD44780_DDRAM_LINE1_ADDR +#    define HD44780_DDRAM_LINE1_ADDR 0x40 +#endif + +#define HD44780_INIT_DELAY_MS 16 +#define HD44780_ENABLE_DELAY_US 1 + +static void hd44780_latch(void) { +    writePinHigh(HD44780_E_PIN); +    wait_us(HD44780_ENABLE_DELAY_US); +    writePinLow(HD44780_E_PIN); +} + +void hd44780_write(uint8_t data, bool isData) { +    writePin(HD44780_RS_PIN, isData); +    writePinLow(HD44780_RW_PIN); + +    for (int i = 0; i < 4; i++) { +        setPinOutput(data_pins[i]); +    } + +    // Write high nibble +    for (int i = 0; i < 4; i++) { +        writePin(data_pins[i], (data >> 4) & (1 << i)); +    } +    hd44780_latch(); + +    // Write low nibble +    for (int i = 0; i < 4; i++) { +        writePin(data_pins[i], data & (1 << i)); +    } +    hd44780_latch(); + +    for (int i = 0; i < 4; i++) { +        writePinHigh(data_pins[i]); +    } +} + +uint8_t hd44780_read(bool isData) { +    uint8_t data = 0; + +    writePin(HD44780_RS_PIN, isData); +    writePinHigh(HD44780_RW_PIN); + +    for (int i = 0; i < 4; i++) { +        setPinInput(data_pins[i]); +    } + +    writePinHigh(HD44780_E_PIN); +    wait_us(HD44780_ENABLE_DELAY_US); + +    // Read high nibble +    for (int i = 0; i < 4; i++) { +        data |= (readPin(data_pins[i]) << i); +    } + +    data <<= 4; + +    writePinLow(HD44780_E_PIN); +    wait_us(HD44780_ENABLE_DELAY_US); +    writePinHigh(HD44780_E_PIN); +    wait_us(HD44780_ENABLE_DELAY_US); + +    // Read low nibble +    for (int i = 0; i < 4; i++) { +        data |= (readPin(data_pins[i]) << i); +    } + +    writePinLow(HD44780_E_PIN); + +    return data; +} + +bool hd44780_busy(void) { +    return hd44780_read(false) & HD44780_BUSY_FLAG; +} + +void hd44780_command(uint8_t command) { +    while (hd44780_busy()) +        ; +    hd44780_write(command, false); +} + +void hd44780_data(uint8_t data) { +    while (hd44780_busy()) +        ; +    hd44780_write(data, true); +} + +void hd44780_clear(void) { +    hd44780_command(HD44780_CMD_CLEAR_DISPLAY); +} + +void hd44780_home(void) { +    hd44780_command(HD44780_CMD_RETURN_HOME); +} + +void hd44780_on(bool cursor, bool blink) { +    if (cursor) { +        if (blink) { +            hd44780_command(HD44780_CMD_DISPLAY | HD44780_DISPLAY_ON | HD44780_DISPLAY_CURSOR | HD44780_DISPLAY_BLINK); +        } else { +            hd44780_command(HD44780_CMD_DISPLAY | HD44780_DISPLAY_ON | HD44780_DISPLAY_CURSOR); +        } +    } else { +        hd44780_command(HD44780_CMD_DISPLAY | HD44780_DISPLAY_ON); +    } +} + +void hd44780_off() { +    hd44780_command(HD44780_CMD_DISPLAY); +} + +void hd44780_set_cgram_address(uint8_t address) { +    hd44780_command(HD44780_CMD_SET_CGRAM_ADDRESS + (address & 0x3F)); +} + +void hd44780_set_ddram_address(uint8_t address) { +    hd44780_command(HD44780_CMD_SET_DDRAM_ADDRESS + (address & 0x7F)); +} + +void hd44780_init(bool cursor, bool blink) { +    setPinOutput(HD44780_RS_PIN); +    setPinOutput(HD44780_RW_PIN); +    setPinOutput(HD44780_E_PIN); + +    for (int i = 0; i < 4; i++) { +        setPinOutput(data_pins[i]); +    } + +    wait_ms(HD44780_INIT_DELAY_MS); + +    // Manually configure for 4-bit mode - can't use hd44780_command() yet +    // HD44780U datasheet, Fig. 24 (p46) +    writePinHigh(data_pins[0]); // Function set +    writePinHigh(data_pins[1]); // DL = 1 +    hd44780_latch(); +    wait_ms(5); +    // Send again +    hd44780_latch(); +    wait_us(64); +    // And again (?) +    hd44780_latch(); +    wait_us(64); + +    writePinLow(data_pins[0]); // DL = 0 +    hd44780_latch(); +    wait_us(64); + +#if HD44780_DISPLAY_LINES == 1 +    hd44780_command(HD44780_CMD_FUNCTION); // 4 bit, 1 line, 5x8 dots +#else +    hd44780_command(HD44780_CMD_FUNCTION | HD44780_FUNCTION_2_LINES); // 4 bit, 2 lines, 5x8 dots +#endif +    hd44780_on(cursor, blink); +    hd44780_clear(); +    hd44780_home(); +    hd44780_command(HD44780_CMD_ENTRY_MODE | HD44780_ENTRY_MODE_INC); +} + +void hd44780_set_cursor(uint8_t col, uint8_t line) { +    register uint8_t address = col; + +#if HD44780_DISPLAY_LINES == 1 +    address += HD44780_DDRAM_LINE0_ADDR; +#elif HD44780_DISPLAY_LINES == 2 +    if (line == 0) { +        address += HD44780_DDRAM_LINE0_ADDR; +    } else { +        address += HD44780_DDRAM_LINE1_ADDR; +    } +#endif + +    hd44780_set_ddram_address(address); +} + +void hd44780_define_char(uint8_t index, uint8_t *data) { +    hd44780_set_cgram_address((index & 0x7) << 3); +    for (uint8_t i = 0; i < 8; i++) { +        hd44780_data(data[i]); +    } +} + +void hd44780_putc(char c) { +    while (hd44780_busy()) +        ; +    uint8_t current_position = hd44780_read(false); + +    if (c == '\n') { +        hd44780_set_cursor(0, current_position < HD44780_DDRAM_LINE1_ADDR ? 1 : 0); +    } else { +#if defined(HD44780_WRAP_LINES) +#    if HD44780_DISPLAY_LINES == 1 +        if (current_position == HD44780_DDRAM_LINE0_ADDR + HD44780_DISPLAY_COLS) { +            // Go to start of line +            hd44780_set_cursor(0, 0); +        } +#    elif HD44780_DISPLAY_LINES == 2 +        if (current_position == HD44780_DDRAM_LINE0_ADDR + HD44780_DISPLAY_COLS) { +            // Go to start of second line +            hd44780_set_cursor(0, 1); +        } else if (current_position == HD44780_DDRAM_LINE1_ADDR + HD44780_DISPLAY_COLS) { +            // Go to start of first line +            hd44780_set_cursor(0, 0); +        } +#    endif +#endif +        hd44780_data(c); +    } +} + +void hd44780_puts(const char *s) { +    register char c; +    while ((c = *s++)) { +        hd44780_putc(c); +    } +} + +#if defined(__AVR__) +void hd44780_define_char_P(uint8_t index, const uint8_t *data) { +    hd44780_set_cgram_address(index << 3); +    for (uint8_t i = 0; i < 8; i++) { +        hd44780_data(pgm_read_byte(data++)); +    } +} + +void hd44780_puts_P(const char *s) { +    register char c; +    while ((c = pgm_read_byte(s++))) { +        hd44780_putc(c); +    } +} +#endif diff --git a/drivers/lcd/hd44780.h b/drivers/lcd/hd44780.h new file mode 100644 index 0000000000..9e43339344 --- /dev/null +++ b/drivers/lcd/hd44780.h @@ -0,0 +1,220 @@ +/* +Copyright 2022 + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program.  If not, see <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +#include <stdint.h> +#include <stdbool.h> + +/** + * \defgroup hd44780 + * + * HD44780 Character LCD Driver + * \{ + */ + +/* + * HD44780 instructions + * https://www.sparkfun.com/datasheets/LCD/HD44780.pdf + * Table 6 (p24) + */ +// Clear display +#define HD44780_CMD_CLEAR_DISPLAY 0x01 +// Return home +#define HD44780_CMD_RETURN_HOME 0x02 +// Entry mode set +#define HD44780_CMD_ENTRY_MODE 0x04 +#define HD44780_ENTRY_MODE_INC 0x02   // I/D +#define HD44780_ENTRY_MODE_SHIFT 0x01 // S +// Display on/off control +#define HD44780_CMD_DISPLAY 0x08 +#define HD44780_DISPLAY_ON 0x04     // D +#define HD44780_DISPLAY_CURSOR 0x02 // C +#define HD44780_DISPLAY_BLINK 0x01  // B +// Cursor or display shift +#define HD44780_CMD_MOVE 0x10 +#define HD44780_MOVE_DISPLAY 0x08 // S/C +#define HD44780_MOVE_RIGHT 0x04   // R/L +// Function set +#define HD44780_CMD_FUNCTION 0x20 +#define HD44780_FUNCTION_8_BIT 0x10     // DL +#define HD44780_FUNCTION_2_LINES 0x08   // N +#define HD44780_FUNCTION_5X10_DOTS 0x04 // F +// Set CGRAM address +#define HD44780_CMD_SET_CGRAM_ADDRESS 0x40 +// Set DDRAM address +#define HD44780_CMD_SET_DDRAM_ADDRESS 0x80 + +// Bitmask for busy flag when reading +#define HD44780_BUSY_FLAG 0x80 + +/** + * \brief Write a byte to the display. + * + * \param data The byte to send to the display. + * \param isData Whether the byte is an instruction or character data. + */ +void hd44780_write(uint8_t data, bool isData); + +/** + * \brief Read a byte from the display. + * + * \param isData Whether to read the current cursor position, or the character at the cursor. + * + * \return If `isData` is `true`, the returned byte will be the character at the current DDRAM address. Otherwise, it will be the current DDRAM address and the busy flag. + */ +uint8_t hd44780_read(bool isData); + +/** + * \brief Indicates whether the display is currently processing, and cannot accept instructions. + * + * \return `true` if the display is busy. + */ +bool hd44780_busy(void); + +/** + * \brief Send a command to the display. Refer to the datasheet for the valid commands. + * + * This function waits for the display to clear the busy flag before sending the command. + * + * \param command The command to send. + */ +void hd44780_command(uint8_t command); + +/** + * \brief Send a byte of data to the display. + * + * This function waits for the display to clear the busy flag before sending the data. + * + * \param data The byte of data to send. + */ +void hd44780_data(uint8_t data); + +/** + * \brief Clear the display. + * + * This function is called on init. + */ +void hd44780_clear(void); + +/** + * \brief Move the cursor to the home position. + * + * This function is called on init. + */ +void hd44780_home(void); + +/** + * \brief Turn the display on, and/or set the cursor position. + * + * This function is called on init. + * + * \param cursor Whether to show the cursor. + * \param blink Whether to blink the cursor, if shown. + */ +void hd44780_on(bool cursor, bool blink); + +/** + * \brief Turn the display off. + */ +void hd44780_off(void); + +/** + * \brief Set the CGRAM address. + * + * This function is used when defining custom characters. + * + * \param address The CGRAM address to move to, from `0x00` to `0x3F`. + */ +void hd44780_set_cgram_address(uint8_t address); + +/** + * \brief Set the DDRAM address. + * + * This function is used when printing characters to the display, and setting the cursor. + * + * \param address The DDRAM address to move to, from `0x00` to `0x7F`. + */ +void hd44780_set_ddram_address(uint8_t address); + +/** + * \brief Initialize the display. + * + * This function should be called only once, before any of the other functions can be called. + * + * \param cursor Whether to show the cursor. + * \param blink Whether to blink the cursor, if shown. + */ +void hd44780_init(bool cursor, bool blink); + +/** + * \brief Move the cursor to the specified position on the display. + * + * \param col The column number to move to, from 0 to 15 on 16x2 displays. + * \param line The line number to move to, either 0 or 1 on 16x2 displays. + */ +void hd44780_set_cursor(uint8_t col, uint8_t line); + +/** + * \brief Define a custom character. + * + * \param index The index of the custom character to define, from 0 to 7. + * \param data An array of 8 bytes containing the 5-bit row data of the character, where the first byte is the topmost row, and the least significant bit of each byte is the rightmost column. + */ +void hd44780_define_char(uint8_t index, uint8_t *data); + +/** + * \brief Print a character to the display. The newline character will move the cursor to the start of the next line. + * + * The exact character shown may depend on the ROM code of your particular display - refer to the datasheet for the full character set. + * + * \param c The character to print. + */ +void hd44780_putc(char c); + +/** + * \brief Print a string of characters to the display. + * + * \param s The string to print. + */ +void hd44780_puts(const char *s); + +#if defined(__AVR__) || defined(__DOXYGEN__) +/** + * \brief Define a custom character from PROGMEM. + * + * On ARM devices, this function is simply an alias of hd44780_define_char(). + * + * \param index The index of the custom character to define, from 0 to 7. + * \param data A PROGMEM array of 8 bytes containing the 5-bit row data of the character, where the first byte is the topmost row, and the least significant bit of each byte is the rightmost column. + */ +void hd44780_define_char_P(uint8_t index, const uint8_t *data); + +/** + * \brief Print a string of characters from PROGMEM to the display. + * + * On ARM devices, this function is simply an alias of hd44780_puts(). + * + * \param s The PROGMEM string to print. + */ +void hd44780_puts_P(const char *s); +#else +#    define hd44780_define_char_P(index, data) hd44780_define_char(index, data) +#    define hd44780_puts_P(s) hd44780_puts(s) +#endif + +/** \} */ diff --git a/drivers/led/issi/is31fl3737.c b/drivers/led/issi/is31fl3737.c index 9f2a13de45..bce0c34b2c 100644 --- a/drivers/led/issi/is31fl3737.c +++ b/drivers/led/issi/is31fl3737.c @@ -57,6 +57,10 @@  #    define ISSI_PERSISTENCE 0  #endif +#ifndef ISSI_PWM_FREQUENCY +#    define ISSI_PWM_FREQUENCY 0b000 // PFS - IS31FL3737B only +#endif +  #ifndef ISSI_SWPULLUP  #    define ISSI_SWPULLUP PUR_0R  #endif @@ -159,7 +163,7 @@ void IS31FL3737_init(uint8_t addr) {      // Set global current to maximum.      IS31FL3737_write_register(addr, ISSI_REG_GLOBALCURRENT, 0xFF);      // Disable software shutdown. -    IS31FL3737_write_register(addr, ISSI_REG_CONFIGURATION, 0x01); +    IS31FL3737_write_register(addr, ISSI_REG_CONFIGURATION, ((ISSI_PWM_FREQUENCY & 0b111) << 3) | 0x01);      // Wait 10ms to ensure the device has woken up.      wait_ms(10); diff --git a/drivers/painter/comms/qp_comms_spi.c b/drivers/painter/comms/qp_comms_spi.c new file mode 100644 index 0000000000..e644ba9f84 --- /dev/null +++ b/drivers/painter/comms/qp_comms_spi.c @@ -0,0 +1,137 @@ +// Copyright 2021 Nick Brassel (@tzarc) +// SPDX-License-Identifier: GPL-2.0-or-later + +#ifdef QUANTUM_PAINTER_SPI_ENABLE + +#    include "spi_master.h" +#    include "qp_comms_spi.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Base SPI support + +bool qp_comms_spi_init(painter_device_t device) { +    struct painter_driver_t *     driver       = (struct painter_driver_t *)device; +    struct qp_comms_spi_config_t *comms_config = (struct qp_comms_spi_config_t *)driver->comms_config; + +    // Initialize the SPI peripheral +    spi_init(); + +    // Set up CS as output high +    setPinOutput(comms_config->chip_select_pin); +    writePinHigh(comms_config->chip_select_pin); + +    return true; +} + +bool qp_comms_spi_start(painter_device_t device) { +    struct painter_driver_t *     driver       = (struct painter_driver_t *)device; +    struct qp_comms_spi_config_t *comms_config = (struct qp_comms_spi_config_t *)driver->comms_config; + +    return spi_start(comms_config->chip_select_pin, comms_config->lsb_first, comms_config->mode, comms_config->divisor); +} + +uint32_t qp_comms_spi_send_data(painter_device_t device, const void *data, uint32_t byte_count) { +    uint32_t       bytes_remaining = byte_count; +    const uint8_t *p               = (const uint8_t *)data; +    while (bytes_remaining > 0) { +        uint32_t bytes_this_loop = bytes_remaining < 1024 ? bytes_remaining : 1024; +        spi_transmit(p, bytes_this_loop); +        p += bytes_this_loop; +        bytes_remaining -= bytes_this_loop; +    } + +    return byte_count - bytes_remaining; +} + +void qp_comms_spi_stop(painter_device_t device) { +    struct painter_driver_t *     driver       = (struct painter_driver_t *)device; +    struct qp_comms_spi_config_t *comms_config = (struct qp_comms_spi_config_t *)driver->comms_config; +    spi_stop(); +    writePinHigh(comms_config->chip_select_pin); +} + +const struct painter_comms_vtable_t spi_comms_vtable = { +    .comms_init  = qp_comms_spi_init, +    .comms_start = qp_comms_spi_start, +    .comms_send  = qp_comms_spi_send_data, +    .comms_stop  = qp_comms_spi_stop, +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// SPI with D/C and RST pins + +#    ifdef QUANTUM_PAINTER_SPI_DC_RESET_ENABLE + +bool qp_comms_spi_dc_reset_init(painter_device_t device) { +    if (!qp_comms_spi_init(device)) { +        return false; +    } + +    struct painter_driver_t *              driver       = (struct painter_driver_t *)device; +    struct qp_comms_spi_dc_reset_config_t *comms_config = (struct qp_comms_spi_dc_reset_config_t *)driver->comms_config; + +    // Set up D/C as output low, if specified +    if (comms_config->dc_pin != NO_PIN) { +        setPinOutput(comms_config->dc_pin); +        writePinLow(comms_config->dc_pin); +    } + +    // Set up RST as output, if specified, performing a reset in the process +    if (comms_config->reset_pin != NO_PIN) { +        setPinOutput(comms_config->reset_pin); +        writePinLow(comms_config->reset_pin); +        wait_ms(20); +        writePinHigh(comms_config->reset_pin); +        wait_ms(20); +    } + +    return true; +} + +uint32_t qp_comms_spi_dc_reset_send_data(painter_device_t device, const void *data, uint32_t byte_count) { +    struct painter_driver_t *              driver       = (struct painter_driver_t *)device; +    struct qp_comms_spi_dc_reset_config_t *comms_config = (struct qp_comms_spi_dc_reset_config_t *)driver->comms_config; +    writePinHigh(comms_config->dc_pin); +    return qp_comms_spi_send_data(device, data, byte_count); +} + +void qp_comms_spi_dc_reset_send_command(painter_device_t device, uint8_t cmd) { +    struct painter_driver_t *              driver       = (struct painter_driver_t *)device; +    struct qp_comms_spi_dc_reset_config_t *comms_config = (struct qp_comms_spi_dc_reset_config_t *)driver->comms_config; +    writePinLow(comms_config->dc_pin); +    spi_write(cmd); +} + +void qp_comms_spi_dc_reset_bulk_command_sequence(painter_device_t device, const uint8_t *sequence, size_t sequence_len) { +    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 (delay > 0) { +            wait_ms(delay); +        } +        i += (3 + num_bytes); +    } +} + +const struct painter_comms_with_command_vtable_t spi_comms_with_dc_vtable = { +    .base = +        { +            .comms_init  = qp_comms_spi_dc_reset_init, +            .comms_start = qp_comms_spi_start, +            .comms_send  = qp_comms_spi_dc_reset_send_data, +            .comms_stop  = qp_comms_spi_stop, +        }, +    .send_command          = qp_comms_spi_dc_reset_send_command, +    .bulk_command_sequence = qp_comms_spi_dc_reset_bulk_command_sequence, +}; + +#    endif // QUANTUM_PAINTER_SPI_DC_RESET_ENABLE + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#endif // QUANTUM_PAINTER_SPI_ENABLE diff --git a/drivers/painter/comms/qp_comms_spi.h b/drivers/painter/comms/qp_comms_spi.h new file mode 100644 index 0000000000..9989987327 --- /dev/null +++ b/drivers/painter/comms/qp_comms_spi.h @@ -0,0 +1,51 @@ +// Copyright 2021 Nick Brassel (@tzarc) +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#ifdef QUANTUM_PAINTER_SPI_ENABLE + +#    include <stdint.h> + +#    include "gpio.h" +#    include "qp_internal.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Base SPI support + +struct qp_comms_spi_config_t { +    pin_t    chip_select_pin; +    uint16_t divisor; +    bool     lsb_first; +    int8_t   mode; +}; + +bool     qp_comms_spi_init(painter_device_t device); +bool     qp_comms_spi_start(painter_device_t device); +uint32_t qp_comms_spi_send_data(painter_device_t device, const void* data, uint32_t byte_count); +void     qp_comms_spi_stop(painter_device_t device); + +extern const struct painter_comms_vtable_t spi_comms_vtable; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// SPI with D/C and RST pins + +#    ifdef QUANTUM_PAINTER_SPI_DC_RESET_ENABLE + +struct qp_comms_spi_dc_reset_config_t { +    struct qp_comms_spi_config_t spi_config; +    pin_t                        dc_pin; +    pin_t                        reset_pin; +}; + +void     qp_comms_spi_dc_reset_send_command(painter_device_t device, uint8_t cmd); +uint32_t qp_comms_spi_dc_reset_send_data(painter_device_t device, const void* data, uint32_t byte_count); +void     qp_comms_spi_dc_reset_bulk_command_sequence(painter_device_t device, const uint8_t* sequence, size_t sequence_len); + +extern const struct painter_comms_with_command_vtable_t spi_comms_with_dc_vtable; + +#    endif // QUANTUM_PAINTER_SPI_DC_RESET_ENABLE + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#endif // QUANTUM_PAINTER_SPI_ENABLE diff --git a/drivers/painter/gc9a01/qp_gc9a01.c b/drivers/painter/gc9a01/qp_gc9a01.c new file mode 100644 index 0000000000..ad76d58b07 --- /dev/null +++ b/drivers/painter/gc9a01/qp_gc9a01.c @@ -0,0 +1,150 @@ +// Copyright 2021 Paul Cotter (@gr1mr3aver) +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <wait.h> +#include "qp_internal.h" +#include "qp_comms.h" +#include "qp_gc9a01.h" +#include "qp_gc9a01_opcodes.h" +#include "qp_tft_panel.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Driver storage +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +tft_panel_dc_reset_painter_device_t gc9a01_drivers[GC9A01_NUM_DEVICES] = {0}; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Initialization +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +bool qp_gc9a01_init(painter_device_t device, painter_rotation_t rotation) { +    // A lot of these "unknown" opcodes are sourced from other OSS projects and are seemingly required for this display to function. +    // clang-format off +    const uint8_t gc9a01_init_sequence[] = { +        // Command,                 Delay,  N, Data[N] +        GC9A01_SET_INTER_REG_ENABLE2,   0,  0, +        0xEB,                           0,  1, 0x14, +        GC9A01_SET_INTER_REG_ENABLE1,   0,  0, +        GC9A01_SET_INTER_REG_ENABLE2,   0,  0, +        0xEB,                           0,  1, 0x14, +        0x84,                           0,  1, 0x40, +        0x85,                           0,  1, 0xFF, +        0x86,                           0,  1, 0xFF, +        0x87,                           0,  1, 0xFF, +        0x88,                           0,  1, 0x0A, +        0x89,                           0,  1, 0x21, +        0x8a,                           0,  1, 0x00, +        0x8b,                           0,  1, 0x80, +        0x8c,                           0,  1, 0x01, +        0x8d,                           0,  1, 0x01, +        0x8e,                           0,  1, 0xFF, +        0x8f,                           0,  1, 0xFF, +        GC9A01_SET_FUNCTION_CTL,        0,  2, 0x00, 0x20, +        GC9A01_SET_PIX_FMT,             0,  1, 0x55, +        0x90,                           0,  4, 0x08, 0x08, 0x08, 0x08, +        0xBD,                           0,  1, 0x06, +        0xBC,                           0,  1, 0x00, +        0xFF,                           0,  3, 0x60, 0x01, 0x04, +        GC9A01_SET_POWER_CTL_2,         0,  1, 0x13, +        GC9A01_SET_POWER_CTL_3,         0,  1, 0x13, +        GC9A01_SET_POWER_CTL_4,         0,  1, 0x22, +        0xBE,                           0,  1, 0x11, +        0xE1,                           0,  2, 0x10, 0x0E, +        0xDF,                           0,  3, 0x21, 0x0C, 0x02, +        GC9A01_SET_GAMMA1,              0,  6, 0x45, 0x09, 0x08, 0x08, 0x26, 0x2A, +        GC9A01_SET_GAMMA2,              0,  6, 0x43, 0x70, 0x72, 0x36, 0x37, 0x6F, +        GC9A01_SET_GAMMA3,              0,  6, 0x45, 0x09, 0x08, 0x08, 0x26, 0x2A, +        GC9A01_SET_GAMMA4,              0,  6, 0x43, 0x70, 0x72, 0x36, 0x37, 0x6F, +        0xED,                           0,  2, 0x1B, 0x0B, +        0xAE,                           0,  1, 0x77, +        0xCD,                           0,  1, 0x63, +        0x70,                           0,  9, 0x07, 0x07, 0x04, 0x0E, 0x0F, 0x09, 0x07, 0x08, 0x03, +        GC9A01_SET_FRAME_RATE,          0,  1, 0x34, +        0x62,                           0, 12, 0x18, 0x0D, 0x71, 0xED, 0x70, 0x70, 0x18, 0x0F, 0x71, 0xEF, 0x70, 0x70, +        0x63,                           0, 12, 0x18, 0x11, 0x71, 0xF1, 0x70, 0x70, 0x18, 0x13, 0x71, 0xF3, 0x70, 0x70, +        0x64,                           0,  7, 0x28, 0x29, 0xF1, 0x01, 0xF1, 0x00, 0x07, +        0x66,                           0, 10, 0x3C, 0x00, 0xCD, 0x67, 0x45, 0x45, 0x10, 0x00, 0x00, 0x00, +        0x67,                           0, 10, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x01, 0x54, 0x10, 0x32, 0x98, +        0x74,                           0,  7, 0x10, 0x85, 0x80, 0x00, 0x00, 0x4E, 0x00, +        0x98,                           0,  2, 0x3E, 0x07, +        GC9A01_CMD_TEARING_OFF,         0,  0, +        GC9A01_CMD_INVERT_OFF,          0,  0, +        GC9A01_CMD_SLEEP_OFF,         120,  0, +        GC9A01_CMD_DISPLAY_ON,         20,  0 +    }; +    // clang-format on + +    // clang-format on +    qp_comms_bulk_command_sequence(device, gc9a01_init_sequence, sizeof(gc9a01_init_sequence)); + +    // Configure the rotation (i.e. the ordering and direction of memory writes in GRAM) +    const uint8_t madctl[] = { +        [QP_ROTATION_0]   = GC9A01_MADCTL_BGR, +        [QP_ROTATION_90]  = GC9A01_MADCTL_BGR | GC9A01_MADCTL_MX | GC9A01_MADCTL_MV, +        [QP_ROTATION_180] = GC9A01_MADCTL_BGR | GC9A01_MADCTL_MX | GC9A01_MADCTL_MY, +        [QP_ROTATION_270] = GC9A01_MADCTL_BGR | GC9A01_MADCTL_MV | GC9A01_MADCTL_MY, +    }; +    qp_comms_command_databyte(device, GC9A01_SET_MEM_ACS_CTL, madctl[rotation]); + +    return true; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Driver vtable +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +const struct tft_panel_dc_reset_painter_driver_vtable_t gc9a01_driver_vtable = { +    .base = +        { +            .init            = qp_gc9a01_init, +            .power           = qp_tft_panel_power, +            .clear           = qp_tft_panel_clear, +            .flush           = qp_tft_panel_flush, +            .pixdata         = qp_tft_panel_pixdata, +            .viewport        = qp_tft_panel_viewport, +            .palette_convert = qp_tft_panel_palette_convert, +            .append_pixels   = qp_tft_panel_append_pixels, +        }, +    .rgb888_to_native16bit = qp_rgb888_to_rgb565_swapped, +    .num_window_bytes      = 2, +    .swap_window_coords    = false, +    .opcodes = +        { +            .display_on         = GC9A01_CMD_DISPLAY_ON, +            .display_off        = GC9A01_CMD_DISPLAY_OFF, +            .set_column_address = GC9A01_SET_COL_ADDR, +            .set_row_address    = GC9A01_SET_PAGE_ADDR, +            .enable_writes      = GC9A01_SET_MEM, +        }, +}; + +#ifdef QUANTUM_PAINTER_GC9A01_SPI_ENABLE +// Factory function for creating a handle to the ILI9341 device +painter_device_t qp_gc9a01_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 < GC9A01_NUM_DEVICES; ++i) { +        tft_panel_dc_reset_painter_device_t *driver = &gc9a01_drivers[i]; +        if (!driver->base.driver_vtable) { +            driver->base.driver_vtable         = (const struct painter_driver_vtable_t *)&gc9a01_driver_vtable; +            driver->base.comms_vtable          = (const struct painter_comms_vtable_t *)&spi_comms_with_dc_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; + +            // 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; +            return (painter_device_t)driver; +        } +    } +    return NULL; +} + +#endif // QUANTUM_PAINTER_GC9A01_SPI_ENABLE diff --git a/drivers/painter/gc9a01/qp_gc9a01.h b/drivers/painter/gc9a01/qp_gc9a01.h new file mode 100644 index 0000000000..e2b1939564 --- /dev/null +++ b/drivers/painter/gc9a01/qp_gc9a01.h @@ -0,0 +1,37 @@ +// Copyright 2021 Paul Cotter (@gr1mr3aver) +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "gpio.h" +#include "qp_internal.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Quantum Painter GC9A01 configurables (add to your keyboard's config.h) + +#ifndef GC9A01_NUM_DEVICES +/** + * @def This controls the maximum number of GC9A01 devices that Quantum Painter can communicate with at any one time. + *      Increasing this number allows for multiple displays to be used. + */ +#    define GC9A01_NUM_DEVICES 1 +#endif + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Quantum Painter GC9A01 device factories + +#ifdef QUANTUM_PAINTER_GC9A01_SPI_ENABLE +/** + * Factory method for an GC9A01 SPI LCD 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 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_gc9a01_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_GC9A01_SPI_ENABLE diff --git a/drivers/painter/gc9a01/qp_gc9a01_opcodes.h b/drivers/painter/gc9a01/qp_gc9a01_opcodes.h new file mode 100644 index 0000000000..6ff4efe7a8 --- /dev/null +++ b/drivers/painter/gc9a01/qp_gc9a01_opcodes.h @@ -0,0 +1,78 @@ +// Copyright 2021 Paul Cotter (@gr1mr3aver) +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Quantum Painter GC9A01 command opcodes +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Level 1 command opcodes + +#define GC9A01_GET_ID_INFO 0x04       // Get ID information +#define GC9A01_GET_STATUS 0x09        // Get status +#define GC9A01_CMD_SLEEP_ON 0x10      // Enter sleep mode +#define GC9A01_CMD_SLEEP_OFF 0x11     // Exit sleep mode +#define GC9A01_CMD_PARTIAL_ON 0x12    // Enter partial mode +#define GC9A01_CMD_PARTIAL_OFF 0x13   // Exit partial mode +#define GC9A01_CMD_INVERT_ON 0x20     // Enter inverted mode +#define GC9A01_CMD_INVERT_OFF 0x21    // Exit inverted mode +#define GC9A01_CMD_DISPLAY_OFF 0x28   // Disable display +#define GC9A01_CMD_DISPLAY_ON 0x29    // Enable display +#define GC9A01_SET_COL_ADDR 0x2A      // Set column address +#define GC9A01_SET_PAGE_ADDR 0x2B     // Set page address +#define GC9A01_SET_MEM 0x2C           // Set memory +#define GC9A01_SET_PARTIAL_AREA 0x30  // Set partial area +#define GC9A01_SET_VSCROLL 0x33       // Set vertical scroll def +#define GC9A01_CMD_TEARING_ON 0x34    // Tearing line enabled +#define GC9A01_CMD_TEARING_OFF 0x35   // Tearing line disabled +#define GC9A01_SET_MEM_ACS_CTL 0x36   // Set mem access ctl +#define GC9A01_SET_VSCROLL_ADDR 0x37  // Set vscroll start addr +#define GC9A01_CMD_IDLE_OFF 0x38      // Exit idle mode +#define GC9A01_CMD_IDLE_ON 0x39       // Enter idle mode +#define GC9A01_SET_PIX_FMT 0x3A       // Set pixel format +#define GC9A01_SET_MEM_CONT 0x3C      // Set memory continue +#define GC9A01_SET_TEAR_SCANLINE 0x44 // Set tearing scanline +#define GC9A01_GET_TEAR_SCANLINE 0x45 // Get tearing scanline +#define GC9A01_SET_BRIGHTNESS 0x51    // Set brightness +#define GC9A01_SET_DISPLAY_CTL 0x53   // Set display ctl +#define GC9A01_GET_ID1 0xDA           // Get ID1 +#define GC9A01_GET_ID2 0xDB           // Get ID2 +#define GC9A01_GET_ID3 0xDC           // Get ID3 + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Level 2 command opcodes + +#define GC9A01_SET_RGB_IF_SIG_CTL 0xB0     // RGB IF signal ctl +#define GC9A01_SET_BLANKING_PORCH_CTL 0xB5 // Set blanking porch ctl +#define GC9A01_SET_FUNCTION_CTL 0xB6       // Set function ctl +#define GC9A01_SET_TEARING_EFFECT 0xBA     // Set backlight ctl 3 +#define GC9A01_SET_IF_CTL 0xF6             // Set interface control + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Level 3 command opcodes + +#define GC9A01_SET_FRAME_RATE 0xE8        // Set frame rate +#define GC9A01_SET_SPI_2DATA 0xE9         // Set frame rate +#define GC9A01_SET_POWER_CTL_1 0xC1       // Set power ctl 1 +#define GC9A01_SET_POWER_CTL_2 0xC3       // Set power ctl 2 +#define GC9A01_SET_POWER_CTL_3 0xC4       // Set power ctl 3 +#define GC9A01_SET_POWER_CTL_4 0xC9       // Set power ctl 4 +#define GC9A01_SET_POWER_CTL_7 0xA7       // Set power ctl 7 +#define GC9A01_SET_INTER_REG_ENABLE1 0xFE // Enable Inter Register 1 +#define GC9A01_SET_INTER_REG_ENABLE2 0xEF // Enable Inter Register 2 +#define GC9A01_SET_GAMMA1 0xF0            // +#define GC9A01_SET_GAMMA2 0xF1 +#define GC9A01_SET_GAMMA3 0xF2 +#define GC9A01_SET_GAMMA4 0xF3 + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// MADCTL Flags +#define GC9A01_MADCTL_MY 0b10000000 +#define GC9A01_MADCTL_MX 0b01000000 +#define GC9A01_MADCTL_MV 0b00100000 +#define GC9A01_MADCTL_ML 0b00010000 +#define GC9A01_MADCTL_RGB 0b00000000 +#define GC9A01_MADCTL_BGR 0b00001000 +#define GC9A01_MADCTL_MH 0b00000100 diff --git a/drivers/painter/ili9xxx/qp_ili9163.c b/drivers/painter/ili9xxx/qp_ili9163.c new file mode 100644 index 0000000000..beaac0fbb5 --- /dev/null +++ b/drivers/painter/ili9xxx/qp_ili9163.c @@ -0,0 +1,121 @@ +// Copyright 2021 Nick Brassel (@tzarc) +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "qp_internal.h" +#include "qp_comms.h" +#include "qp_ili9163.h" +#include "qp_ili9xxx_opcodes.h" +#include "qp_tft_panel.h" + +#ifdef QUANTUM_PAINTER_ILI9163_SPI_ENABLE +#    include "qp_comms_spi.h" +#endif // QUANTUM_PAINTER_ILI9163_SPI_ENABLE + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Common + +// Driver storage +tft_panel_dc_reset_painter_device_t ili9163_drivers[ILI9163_NUM_DEVICES] = {0}; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Initialization + +bool qp_ili9163_init(painter_device_t device, painter_rotation_t rotation) { +    // clang-format off +    const uint8_t ili9163_init_sequence[] = { +        // Command,                 Delay,  N, Data[N] +        ILI9XXX_CMD_RESET,            120,  0, +        ILI9XXX_CMD_SLEEP_OFF,          5,  0, +        ILI9XXX_SET_PIX_FMT,            0,  1, 0x55, +        ILI9XXX_SET_GAMMA,              0,  1, 0x04, +        ILI9XXX_ENABLE_3_GAMMA,         0,  1, 0x01, +        ILI9XXX_SET_FUNCTION_CTL,       0,  2, 0xFF, 0x06, +        ILI9XXX_SET_PGAMMA,             0, 15, 0x36, 0x29, 0x12, 0x22, 0x1C, 0x15, 0x42, 0xB7, 0x2F, 0x13, 0x12, 0x0A, 0x11, 0x0B, 0x06, +        ILI9XXX_SET_NGAMMA,             0, 15, 0x09, 0x16, 0x2D, 0x0D, 0x13, 0x15, 0x40, 0x48, 0x53, 0x0C, 0x1D, 0x25, 0x2E, 0x34, 0x39, +        ILI9XXX_SET_FRAME_CTL_NORMAL,   0,  2, 0x08, 0x02, +        ILI9XXX_SET_POWER_CTL_1,        0,  2, 0x0A, 0x02, +        ILI9XXX_SET_POWER_CTL_2,        0,  1, 0x02, +        ILI9XXX_SET_VCOM_CTL_1,         0,  2, 0x50, 0x63, +        ILI9XXX_SET_VCOM_CTL_2,         0,  1, 0x00, +        ILI9XXX_CMD_PARTIAL_OFF,        0,  0, +        ILI9XXX_CMD_DISPLAY_ON,        20,  0 +    }; +    // clang-format on +    qp_comms_bulk_command_sequence(device, ili9163_init_sequence, sizeof(ili9163_init_sequence)); + +    // Configure the rotation (i.e. the ordering and direction of memory writes in GRAM) +    const uint8_t madctl[] = { +        [QP_ROTATION_0]   = ILI9XXX_MADCTL_BGR, +        [QP_ROTATION_90]  = ILI9XXX_MADCTL_BGR | ILI9XXX_MADCTL_MX | ILI9XXX_MADCTL_MV, +        [QP_ROTATION_180] = ILI9XXX_MADCTL_BGR | ILI9XXX_MADCTL_MX | ILI9XXX_MADCTL_MY, +        [QP_ROTATION_270] = ILI9XXX_MADCTL_BGR | ILI9XXX_MADCTL_MV | ILI9XXX_MADCTL_MY, +    }; +    qp_comms_command_databyte(device, ILI9XXX_SET_MEM_ACS_CTL, madctl[rotation]); + +    return true; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Driver vtable + +const struct tft_panel_dc_reset_painter_driver_vtable_t ili9163_driver_vtable = { +    .base = +        { +            .init            = qp_ili9163_init, +            .power           = qp_tft_panel_power, +            .clear           = qp_tft_panel_clear, +            .flush           = qp_tft_panel_flush, +            .pixdata         = qp_tft_panel_pixdata, +            .viewport        = qp_tft_panel_viewport, +            .palette_convert = qp_tft_panel_palette_convert, +            .append_pixels   = qp_tft_panel_append_pixels, +        }, +    .rgb888_to_native16bit = qp_rgb888_to_rgb565_swapped, +    .num_window_bytes      = 2, +    .swap_window_coords    = false, +    .opcodes = +        { +            .display_on         = ILI9XXX_CMD_DISPLAY_ON, +            .display_off        = ILI9XXX_CMD_DISPLAY_OFF, +            .set_column_address = ILI9XXX_SET_COL_ADDR, +            .set_row_address    = ILI9XXX_SET_PAGE_ADDR, +            .enable_writes      = ILI9XXX_SET_MEM, +        }, +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// SPI + +#ifdef QUANTUM_PAINTER_ILI9163_SPI_ENABLE + +// Factory function for creating a handle to the ILI9163 device +painter_device_t qp_ili9163_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 < ILI9163_NUM_DEVICES; ++i) { +        tft_panel_dc_reset_painter_device_t *driver = &ili9163_drivers[i]; +        if (!driver->base.driver_vtable) { +            driver->base.driver_vtable         = (const struct painter_driver_vtable_t *)&ili9163_driver_vtable; +            driver->base.comms_vtable          = (const struct painter_comms_vtable_t *)&spi_comms_with_dc_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->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; +            return (painter_device_t)driver; +        } +    } +    return NULL; +} + +#endif // QUANTUM_PAINTER_ILI9163_SPI_ENABLE + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/drivers/painter/ili9xxx/qp_ili9163.h b/drivers/painter/ili9xxx/qp_ili9163.h new file mode 100644 index 0000000000..88d23629a9 --- /dev/null +++ b/drivers/painter/ili9xxx/qp_ili9163.h @@ -0,0 +1,37 @@ +// Copyright 2021 Nick Brassel (@tzarc) +// 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 ILI9163_NUM_DEVICES +/** + * @def This controls the maximum number of ILI9163 devices that Quantum Painter can communicate with at any one time. + *      Increasing this number allows for multiple displays to be used. + */ +#    define ILI9163_NUM_DEVICES 1 +#endif + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Quantum Painter ILI9163 device factories + +#ifdef QUANTUM_PAINTER_ILI9163_SPI_ENABLE +/** + * Factory method for an ILI9163 SPI LCD 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 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_ili9163_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_ILI9163_SPI_ENABLE diff --git a/drivers/painter/ili9xxx/qp_ili9341.c b/drivers/painter/ili9xxx/qp_ili9341.c new file mode 100644 index 0000000000..1f41dcfc0b --- /dev/null +++ b/drivers/painter/ili9xxx/qp_ili9341.c @@ -0,0 +1,128 @@ +// Copyright 2021 Nick Brassel (@tzarc) +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "qp_internal.h" +#include "qp_comms.h" +#include "qp_ili9341.h" +#include "qp_ili9xxx_opcodes.h" +#include "qp_tft_panel.h" + +#ifdef QUANTUM_PAINTER_ILI9341_SPI_ENABLE +#    include <qp_comms_spi.h> +#endif // QUANTUM_PAINTER_ILI9341_SPI_ENABLE + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Common + +// Driver storage +tft_panel_dc_reset_painter_device_t ili9341_drivers[ILI9341_NUM_DEVICES] = {0}; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Initialization + +bool qp_ili9341_init(painter_device_t device, painter_rotation_t rotation) { +    // clang-format off +    const uint8_t ili9341_init_sequence[] = { +        // Command,                 Delay,  N, Data[N] +        ILI9XXX_CMD_RESET,            120,  0, +        ILI9XXX_CMD_SLEEP_OFF,          5,  0, +        ILI9XXX_POWER_CTL_A,            0,  5, 0x39, 0x2C, 0x00, 0x34, 0x02, +        ILI9XXX_POWER_CTL_B,            0,  3, 0x00, 0xD9, 0x30, +        ILI9XXX_POWER_ON_SEQ_CTL,       0,  4, 0x64, 0x03, 0x12, 0x81, +        ILI9XXX_SET_PUMP_RATIO_CTL,     0,  1, 0x20, +        ILI9XXX_SET_POWER_CTL_1,        0,  1, 0x26, +        ILI9XXX_SET_POWER_CTL_2,        0,  1, 0x11, +        ILI9XXX_SET_VCOM_CTL_1,         0,  2, 0x35, 0x3E, +        ILI9XXX_SET_VCOM_CTL_2,         0,  1, 0xBE, +        ILI9XXX_DRV_TIMING_CTL_A,       0,  3, 0x85, 0x10, 0x7A, +        ILI9XXX_DRV_TIMING_CTL_B,       0,  2, 0x00, 0x00, +        ILI9XXX_SET_BRIGHTNESS,         0,  1, 0xFF, +        ILI9XXX_ENABLE_3_GAMMA,         0,  1, 0x00, +        ILI9XXX_SET_GAMMA,              0,  1, 0x01, +        ILI9XXX_SET_PGAMMA,             0, 15, 0x0F, 0x29, 0x24, 0x0C, 0x0E, 0x09, 0x4E, 0x78, 0x3C, 0x09, 0x13, 0x05, 0x17, 0x11, 0x00, +        ILI9XXX_SET_NGAMMA,             0, 15, 0x00, 0x16, 0x1B, 0x04, 0x11, 0x07, 0x31, 0x33, 0x42, 0x05, 0x0C, 0x0A, 0x28, 0x2F, 0x0F, +        ILI9XXX_SET_PIX_FMT,            0,  1, 0x05, +        ILI9XXX_SET_FRAME_CTL_NORMAL,   0,  2, 0x00, 0x1B, +        ILI9XXX_SET_FUNCTION_CTL,       0,  2, 0x0A, 0xA2, +        ILI9XXX_CMD_PARTIAL_OFF,        0,  0, +        ILI9XXX_CMD_DISPLAY_ON,        20,  0 +    }; +    // clang-format on +    qp_comms_bulk_command_sequence(device, ili9341_init_sequence, sizeof(ili9341_init_sequence)); + +    // Configure the rotation (i.e. the ordering and direction of memory writes in GRAM) +    const uint8_t madctl[] = { +        [QP_ROTATION_0]   = ILI9XXX_MADCTL_BGR, +        [QP_ROTATION_90]  = ILI9XXX_MADCTL_BGR | ILI9XXX_MADCTL_MX | ILI9XXX_MADCTL_MV, +        [QP_ROTATION_180] = ILI9XXX_MADCTL_BGR | ILI9XXX_MADCTL_MX | ILI9XXX_MADCTL_MY, +        [QP_ROTATION_270] = ILI9XXX_MADCTL_BGR | ILI9XXX_MADCTL_MV | ILI9XXX_MADCTL_MY, +    }; +    qp_comms_command_databyte(device, ILI9XXX_SET_MEM_ACS_CTL, madctl[rotation]); + +    return true; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Driver vtable + +const struct tft_panel_dc_reset_painter_driver_vtable_t ili9341_driver_vtable = { +    .base = +        { +            .init            = qp_ili9341_init, +            .power           = qp_tft_panel_power, +            .clear           = qp_tft_panel_clear, +            .flush           = qp_tft_panel_flush, +            .pixdata         = qp_tft_panel_pixdata, +            .viewport        = qp_tft_panel_viewport, +            .palette_convert = qp_tft_panel_palette_convert, +            .append_pixels   = qp_tft_panel_append_pixels, +        }, +    .rgb888_to_native16bit = qp_rgb888_to_rgb565_swapped, +    .num_window_bytes      = 2, +    .swap_window_coords    = false, +    .opcodes = +        { +            .display_on         = ILI9XXX_CMD_DISPLAY_ON, +            .display_off        = ILI9XXX_CMD_DISPLAY_OFF, +            .set_column_address = ILI9XXX_SET_COL_ADDR, +            .set_row_address    = ILI9XXX_SET_PAGE_ADDR, +            .enable_writes      = ILI9XXX_SET_MEM, +        }, +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// SPI + +#ifdef QUANTUM_PAINTER_ILI9341_SPI_ENABLE + +// Factory function for creating a handle to the ILI9341 device +painter_device_t qp_ili9341_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 < ILI9341_NUM_DEVICES; ++i) { +        tft_panel_dc_reset_painter_device_t *driver = &ili9341_drivers[i]; +        if (!driver->base.driver_vtable) { +            driver->base.driver_vtable         = (const struct painter_driver_vtable_t *)&ili9341_driver_vtable; +            driver->base.comms_vtable          = (const struct painter_comms_vtable_t *)&spi_comms_with_dc_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; + +            // 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; +            return (painter_device_t)driver; +        } +    } +    return NULL; +} + +#endif // QUANTUM_PAINTER_ILI9341_SPI_ENABLE + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/drivers/painter/ili9xxx/qp_ili9341.h b/drivers/painter/ili9xxx/qp_ili9341.h new file mode 100644 index 0000000000..28b0152a84 --- /dev/null +++ b/drivers/painter/ili9xxx/qp_ili9341.h @@ -0,0 +1,37 @@ +// Copyright 2021 Nick Brassel (@tzarc) +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "gpio.h" +#include "qp_internal.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Quantum Painter ILI9341 configurables (add to your keyboard's config.h) + +#ifndef ILI9341_NUM_DEVICES +/** + * @def This controls the maximum number of ILI9341 devices that Quantum Painter can communicate with at any one time. + *      Increasing this number allows for multiple displays to be used. + */ +#    define ILI9341_NUM_DEVICES 1 +#endif + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Quantum Painter ILI9341 device factories + +#ifdef QUANTUM_PAINTER_ILI9341_SPI_ENABLE +/** + * Factory method for an ILI9341 SPI LCD 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 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_ili9341_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_ILI9341_SPI_ENABLE diff --git a/drivers/painter/ili9xxx/qp_ili9xxx_opcodes.h b/drivers/painter/ili9xxx/qp_ili9xxx_opcodes.h new file mode 100644 index 0000000000..1fa395cb89 --- /dev/null +++ b/drivers/painter/ili9xxx/qp_ili9xxx_opcodes.h @@ -0,0 +1,100 @@ +// Copyright 2021 Nick Brassel (@tzarc) +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Quantum Painter ILI9xxx command opcodes +#define ILI9XXX_CMD_NOP 0x00                // No operation +#define ILI9XXX_CMD_RESET 0x01              // Software reset +#define ILI9XXX_GET_ID_INFO 0x04            // Get ID information +#define ILI9XXX_GET_STATUS 0x09             // Get status +#define ILI9XXX_GET_PWR_MODE 0x0A           // Get power mode +#define ILI9XXX_GET_MADCTL 0x0B             // Get MADCTL +#define ILI9XXX_GET_PIX_FMT 0x0C            // Get pixel format +#define ILI9XXX_GET_IMG_FMT 0x0D            // Get image format +#define ILI9XXX_GET_SIG_MODE 0x0E           // Get signal mode +#define ILI9XXX_GET_SELF_DIAG 0x0F          // Get self-diagnostics +#define ILI9XXX_CMD_SLEEP_ON 0x10           // Enter sleep mode +#define ILI9XXX_CMD_SLEEP_OFF 0x11          // Exist sleep mode +#define ILI9XXX_CMD_PARTIAL_ON 0x12         // Enter partial mode +#define ILI9XXX_CMD_PARTIAL_OFF 0x13        // Exit partial mode +#define ILI9XXX_CMD_INVERT_ON 0x20          // Enter inverted mode +#define ILI9XXX_CMD_INVERT_OFF 0x21         // Exit inverted mode +#define ILI9XXX_SET_GAMMA 0x26              // Set gamma params +#define ILI9XXX_CMD_DISPLAY_OFF 0x28        // Disable display +#define ILI9XXX_CMD_DISPLAY_ON 0x29         // Enable display +#define ILI9XXX_SET_COL_ADDR 0x2A           // Set column address +#define ILI9XXX_SET_PAGE_ADDR 0x2B          // Set page address +#define ILI9XXX_SET_MEM 0x2C                // Set memory +#define ILI9XXX_SET_COLOR 0x2D              // Set color +#define ILI9XXX_GET_MEM 0x2E                // Get memory +#define ILI9XXX_SET_PARTIAL_AREA 0x30       // Set partial area +#define ILI9XXX_SET_VSCROLL 0x33            // Set vertical scroll def +#define ILI9XXX_CMD_TEARING_ON 0x34         // Tearing line enabled +#define ILI9XXX_CMD_TEARING_OFF 0x35        // Tearing line disabled +#define ILI9XXX_SET_MEM_ACS_CTL 0x36        // Set mem access ctl +#define ILI9XXX_SET_VSCROLL_ADDR 0x37       // Set vscroll start addr +#define ILI9XXX_CMD_IDLE_OFF 0x38           // Exit idle mode +#define ILI9XXX_CMD_IDLE_ON 0x39            // Enter idle mode +#define ILI9XXX_SET_PIX_FMT 0x3A            // Set pixel format +#define ILI9XXX_SET_MEM_CONT 0x3C           // Set memory continue +#define ILI9XXX_GET_MEM_CONT 0x3E           // Get memory continue +#define ILI9XXX_SET_TEAR_SCANLINE 0x44      // Set tearing scanline +#define ILI9XXX_GET_TEAR_SCANLINE 0x45      // Get tearing scanline +#define ILI9XXX_SET_BRIGHTNESS 0x51         // Set brightness +#define ILI9XXX_GET_BRIGHTNESS 0x52         // Get brightness +#define ILI9XXX_SET_DISPLAY_CTL 0x53        // Set display ctl +#define ILI9XXX_GET_DISPLAY_CTL 0x54        // Get display ctl +#define ILI9XXX_SET_CABC 0x55               // Set CABC +#define ILI9XXX_GET_CABC 0x56               // Get CABC +#define ILI9XXX_SET_CABC_MIN 0x5E           // Set CABC min +#define ILI9XXX_GET_CABC_MIN 0x5F           // Set CABC max +#define ILI9XXX_GET_ID1 0xDA                // Get ID1 +#define ILI9XXX_GET_ID2 0xDB                // Get ID2 +#define ILI9XXX_GET_ID3 0xDC                // Get ID3 +#define ILI9XXX_SET_RGB_IF_SIG_CTL 0xB0     // RGB IF signal ctl +#define ILI9XXX_SET_FRAME_CTL_NORMAL 0xB1   // Set frame ctl (normal) +#define ILI9XXX_SET_FRAME_CTL_IDLE 0xB2     // Set frame ctl (idle) +#define ILI9XXX_SET_FRAME_CTL_PARTIAL 0xB3  // Set frame ctl (partial) +#define ILI9XXX_SET_INVERSION_CTL 0xB4      // Set inversion ctl +#define ILI9XXX_SET_BLANKING_PORCH_CTL 0xB5 // Set blanking porch ctl +#define ILI9XXX_SET_FUNCTION_CTL 0xB6       // Set function ctl +#define ILI9XXX_SET_ENTRY_MODE 0xB7         // Set entry mode +#define ILI9XXX_SET_LIGHT_CTL_1 0xB8        // Set backlight ctl 1 +#define ILI9XXX_SET_LIGHT_CTL_2 0xB9        // Set backlight ctl 2 +#define ILI9XXX_SET_LIGHT_CTL_3 0xBA        // Set backlight ctl 3 +#define ILI9XXX_SET_LIGHT_CTL_4 0xBB        // Set backlight ctl 4 +#define ILI9XXX_SET_LIGHT_CTL_5 0xBC        // Set backlight ctl 5 +#define ILI9XXX_SET_LIGHT_CTL_7 0xBE        // Set backlight ctl 7 +#define ILI9XXX_SET_LIGHT_CTL_8 0xBF        // Set backlight ctl 8 +#define ILI9XXX_SET_POWER_CTL_1 0xC0        // Set power ctl 1 +#define ILI9XXX_SET_POWER_CTL_2 0xC1        // Set power ctl 2 +#define ILI9XXX_SET_VCOM_CTL_1 0xC5         // Set VCOM ctl 1 +#define ILI9XXX_SET_VCOM_CTL_2 0xC7         // Set VCOM ctl 2 +#define ILI9XXX_POWER_CTL_A 0xCB            // Set power control A +#define ILI9XXX_POWER_CTL_B 0xCF            // Set power control B +#define ILI9XXX_DRV_TIMING_CTL_A 0xE8       // Set driver timing control A +#define ILI9XXX_DRV_TIMING_CTL_B 0xEA       // Set driver timing control B +#define ILI9XXX_POWER_ON_SEQ_CTL 0xED       // Set Power on sequence control +#define ILI9XXX_SET_NVMEM 0xD0              // Set NVMEM data +#define ILI9XXX_GET_NVMEM_KEY 0xD1          // Get NVMEM protect key +#define ILI9XXX_GET_NVMEM_STATUS 0xD2       // Get NVMEM status +#define ILI9XXX_GET_ID4 0xD3                // Get ID4 +#define ILI9XXX_SET_PGAMMA 0xE0             // Set positive gamma +#define ILI9XXX_SET_NGAMMA 0xE1             // Set negative gamma +#define ILI9XXX_SET_DGAMMA_CTL_1 0xE2       // Set digital gamma ctl 1 +#define ILI9XXX_SET_DGAMMA_CTL_2 0xE3       // Set digital gamma ctl 2 +#define ILI9XXX_ENABLE_3_GAMMA 0xF2         // Enable 3 gamma +#define ILI9XXX_SET_IF_CTL 0xF6             // Set interface control +#define ILI9XXX_SET_PUMP_RATIO_CTL 0xF7     // Set pump ratio control + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// MADCTL Flags +#define ILI9XXX_MADCTL_MY 0b10000000 +#define ILI9XXX_MADCTL_MX 0b01000000 +#define ILI9XXX_MADCTL_MV 0b00100000 +#define ILI9XXX_MADCTL_ML 0b00010000 +#define ILI9XXX_MADCTL_RGB 0b00000000 +#define ILI9XXX_MADCTL_BGR 0b00001000 +#define ILI9XXX_MADCTL_MH 0b00000100 diff --git a/drivers/painter/ssd1351/qp_ssd1351.c b/drivers/painter/ssd1351/qp_ssd1351.c new file mode 100644 index 0000000000..970e7e67f3 --- /dev/null +++ b/drivers/painter/ssd1351/qp_ssd1351.c @@ -0,0 +1,125 @@ +// Copyright 2021 Nick Brassel (@tzarc) +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "qp_internal.h" +#include "qp_comms.h" +#include "qp_ssd1351.h" +#include "qp_ssd1351_opcodes.h" +#include "qp_tft_panel.h" + +#ifdef QUANTUM_PAINTER_SSD1351_SPI_ENABLE +#    include "qp_comms_spi.h" +#endif // QUANTUM_PAINTER_SSD1351_SPI_ENABLE + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Common + +// Driver storage +tft_panel_dc_reset_painter_device_t ssd1351_drivers[SSD1351_NUM_DEVICES] = {0}; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Initialization + +bool qp_ssd1351_init(painter_device_t device, painter_rotation_t rotation) { +    tft_panel_dc_reset_painter_device_t *driver = (tft_panel_dc_reset_painter_device_t *)device; + +    // clang-format off +    const uint8_t ssd1351_init_sequence[] = { +        // Command,                 Delay, N, Data[N] +        SSD1351_COMMANDLOCK,           5,  1, 0x12, +        SSD1351_COMMANDLOCK,           5,  1, 0xB1, +        SSD1351_DISPLAYOFF,            5,  0, +        SSD1351_CLOCKDIV,              5,  1, 0xF1, +        SSD1351_MUXRATIO,              5,  1, 0x7F, +        SSD1351_DISPLAYOFFSET,         5,  1, 0x00, +        SSD1351_SETGPIO,               5,  1, 0x00, +        SSD1351_FUNCTIONSELECT,        5,  1, 0x01, +        SSD1351_PRECHARGE,             5,  1, 0x32, +        SSD1351_VCOMH,                 5,  1, 0x05, +        SSD1351_NORMALDISPLAY,         5,  0, +        SSD1351_CONTRASTABC,           5,  3, 0xC8, 0x80, 0xC8, +        SSD1351_CONTRASTMASTER,        5,  1, 0x0F, +        SSD1351_SETVSL,                5,  3, 0xA0, 0xB5, 0x55, +        SSD1351_PRECHARGE2,            5,  1, 0x01, +        SSD1351_DISPLAYON,             5,  0, +    }; +    // clang-format on +    qp_comms_bulk_command_sequence(device, ssd1351_init_sequence, sizeof(ssd1351_init_sequence)); + +    // Configure the rotation (i.e. the ordering and direction of memory writes in GRAM) +    const uint8_t madctl[] = { +        [QP_ROTATION_0]   = SSD1351_MADCTL_BGR | SSD1351_MADCTL_MY, +        [QP_ROTATION_90]  = SSD1351_MADCTL_BGR | SSD1351_MADCTL_MX | SSD1351_MADCTL_MY | SSD1351_MADCTL_MV, +        [QP_ROTATION_180] = SSD1351_MADCTL_BGR | SSD1351_MADCTL_MX, +        [QP_ROTATION_270] = SSD1351_MADCTL_BGR | SSD1351_MADCTL_MV, +    }; +    qp_comms_command_databyte(device, SSD1351_SETREMAP, madctl[rotation]); +    qp_comms_command_databyte(device, SSD1351_STARTLINE, (rotation == QP_ROTATION_0 || rotation == QP_ROTATION_90) ? driver->base.panel_height : 0); + +    return true; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Driver vtable + +const struct tft_panel_dc_reset_painter_driver_vtable_t ssd1351_driver_vtable = { +    .base = +        { +            .init            = qp_ssd1351_init, +            .power           = qp_tft_panel_power, +            .clear           = qp_tft_panel_clear, +            .flush           = qp_tft_panel_flush, +            .pixdata         = qp_tft_panel_pixdata, +            .viewport        = qp_tft_panel_viewport, +            .palette_convert = qp_tft_panel_palette_convert, +            .append_pixels   = qp_tft_panel_append_pixels, +        }, +    .rgb888_to_native16bit = qp_rgb888_to_rgb565_swapped, +    .num_window_bytes      = 1, +    .swap_window_coords    = true, +    .opcodes = +        { +            .display_on         = SSD1351_DISPLAYON, +            .display_off        = SSD1351_DISPLAYOFF, +            .set_column_address = SSD1351_SETCOLUMN, +            .set_row_address    = SSD1351_SETROW, +            .enable_writes      = SSD1351_WRITERAM, +        }, +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// SPI + +#ifdef QUANTUM_PAINTER_SSD1351_SPI_ENABLE + +// Factory function for creating a handle to the SSD1351 device +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) { +    for (uint32_t i = 0; i < SSD1351_NUM_DEVICES; ++i) { +        tft_panel_dc_reset_painter_device_t *driver = &ssd1351_drivers[i]; +        if (!driver->base.driver_vtable) { +            driver->base.driver_vtable         = (const struct painter_driver_vtable_t *)&ssd1351_driver_vtable; +            driver->base.comms_vtable          = (const struct painter_comms_vtable_t *)&spi_comms_with_dc_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->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; +            return (painter_device_t)driver; +        } +    } +    return NULL; +} + +#endif // QUANTUM_PAINTER_SSD1351_SPI_ENABLE + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/drivers/painter/ssd1351/qp_ssd1351.h b/drivers/painter/ssd1351/qp_ssd1351.h new file mode 100644 index 0000000000..0df34f204d --- /dev/null +++ b/drivers/painter/ssd1351/qp_ssd1351.h @@ -0,0 +1,37 @@ +// Copyright 2021 Nick Brassel (@tzarc) +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "gpio.h" +#include "qp_internal.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Quantum Painter SSD1351 configurables (add to your keyboard's config.h) + +#ifndef SSD1351_NUM_DEVICES +/** + * @def This controls the maximum number of SSD1351 devices that Quantum Painter can communicate with at any one time. + *      Increasing this number allows for multiple displays to be used. + */ +#    define SSD1351_NUM_DEVICES 1 +#endif + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Quantum Painter SSD1351 device factories + +#ifdef QUANTUM_PAINTER_SSD1351_SPI_ENABLE +/** + * Factory method for an SSD1351 SPI OLED 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 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_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); +#endif // QUANTUM_PAINTER_SSD1351_SPI_ENABLE diff --git a/drivers/painter/ssd1351/qp_ssd1351_opcodes.h b/drivers/painter/ssd1351/qp_ssd1351_opcodes.h new file mode 100644 index 0000000000..48ed2a3a7c --- /dev/null +++ b/drivers/painter/ssd1351/qp_ssd1351_opcodes.h @@ -0,0 +1,48 @@ +// Copyright 2021 Nick Brassel (@tzarc) +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Quantum Painter SSD1351 command opcodes + +// System function commands +#define SSD1351_SETCOLUMN 0x15 +#define SSD1351_SETROW 0x75 +#define SSD1351_WRITERAM 0x5C +#define SSD1351_READRAM 0x5D +#define SSD1351_SETREMAP 0xA0 +#define SSD1351_STARTLINE 0xA1 +#define SSD1351_DISPLAYOFFSET 0xA2 +#define SSD1351_DISPLAYALLOFF 0xA4 +#define SSD1351_DISPLAYALLON 0xA5 +#define SSD1351_NORMALDISPLAY 0xA6 +#define SSD1351_INVERTDISPLAY 0xA7 +#define SSD1351_FUNCTIONSELECT 0xAB +#define SSD1351_DISPLAYOFF 0xAE +#define SSD1351_DISPLAYON 0xAF +#define SSD1351_PRECHARGE 0xB1 +#define SSD1351_DISPLAYENHANCE 0xB2 +#define SSD1351_CLOCKDIV 0xB3 +#define SSD1351_SETVSL 0xB4 +#define SSD1351_SETGPIO 0xB5 +#define SSD1351_PRECHARGE2 0xB6 +#define SSD1351_SETGRAY 0xB8 +#define SSD1351_USELUT 0xB9 +#define SSD1351_PRECHARGELEVEL 0xBB +#define SSD1351_VCOMH 0xBE +#define SSD1351_CONTRASTABC 0xC1 +#define SSD1351_CONTRASTMASTER 0xC7 +#define SSD1351_MUXRATIO 0xCA +#define SSD1351_COMMANDLOCK 0xFD +#define SSD1351_HORIZSCROLL 0x96 +#define SSD1351_STOPSCROLL 0x9E +#define SSD1351_STARTSCROLL 0x9F + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// SETREMAP (MADCTL) Flags +#define SSD1351_MADCTL_MY 0b00010000 +#define SSD1351_MADCTL_MX 0b00000010 +#define SSD1351_MADCTL_MV 0b00000001 +#define SSD1351_MADCTL_RGB 0b01100000 +#define SSD1351_MADCTL_BGR 0b01100100 diff --git a/drivers/painter/st77xx/qp_st7789.c b/drivers/painter/st77xx/qp_st7789.c new file mode 100644 index 0000000000..d005ece050 --- /dev/null +++ b/drivers/painter/st77xx/qp_st7789.c @@ -0,0 +1,144 @@ +// Copyright 2021 Paul Cotter (@gr1mr3aver) +// Copyright 2021 Nick Brassel (@tzarc) +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "qp_internal.h" +#include "qp_comms.h" +#include "qp_st7789.h" +#include "qp_st77xx_opcodes.h" +#include "qp_st7789_opcodes.h" +#include "qp_tft_panel.h" + +#ifdef QUANTUM_PAINTER_ST7789_SPI_ENABLE +#    include "qp_comms_spi.h" +#endif // QUANTUM_PAINTER_ST7789_SPI_ENABLE + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Common + +// Driver storage +tft_panel_dc_reset_painter_device_t st7789_drivers[ST7789_NUM_DEVICES] = {0}; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Automatic viewport offsets + +#ifndef ST7789_NO_AUTOMATIC_OFFSETS +static inline void st7789_automatic_viewport_offsets(painter_device_t device, painter_rotation_t rotation) { +    struct painter_driver_t *driver = (struct painter_driver_t *)device; + +    // clang-format off +    const struct { +        uint16_t offset_x; +        uint16_t offset_y; +    } rotation_offsets_240x240[] = { +        [QP_ROTATION_0]   = { .offset_x =  0, .offset_y =  0 }, +        [QP_ROTATION_90]  = { .offset_x =  0, .offset_y =  0 }, +        [QP_ROTATION_180] = { .offset_x =  0, .offset_y = 80 }, +        [QP_ROTATION_270] = { .offset_x = 80, .offset_y =  0 }, +    }; +    // clang-format on + +    if (driver->panel_width == 240 && driver->panel_height == 240) { +        driver->offset_x = rotation_offsets_240x240[rotation].offset_x; +        driver->offset_y = rotation_offsets_240x240[rotation].offset_y; +    } +} +#endif // ST7789_NO_AUTOMATIC_OFFSETS + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Initialization + +bool qp_st7789_init(painter_device_t device, painter_rotation_t rotation) { +    // clang-format off +    const uint8_t st7789_init_sequence[] = { +        // Command,                 Delay, N, Data[N] +        ST77XX_CMD_RESET,            120,  0, +        ST77XX_CMD_SLEEP_OFF,          5,  0, +        ST77XX_SET_PIX_FMT,            0,  1, 0x55, +        ST77XX_CMD_INVERT_ON,          0,  0, +        ST77XX_CMD_NORMAL_ON,          0,  0, +        ST77XX_CMD_DISPLAY_ON,        20,  0 +    }; +    // clang-format on +    qp_comms_bulk_command_sequence(device, st7789_init_sequence, sizeof(st7789_init_sequence)); + +    // Configure the rotation (i.e. the ordering and direction of memory writes in GRAM) +    const uint8_t madctl[] = { +        [QP_ROTATION_0]   = ST77XX_MADCTL_RGB, +        [QP_ROTATION_90]  = ST77XX_MADCTL_RGB | ST77XX_MADCTL_MX | ST77XX_MADCTL_MV, +        [QP_ROTATION_180] = ST77XX_MADCTL_RGB | ST77XX_MADCTL_MX | ST77XX_MADCTL_MY, +        [QP_ROTATION_270] = ST77XX_MADCTL_RGB | ST77XX_MADCTL_MV | ST77XX_MADCTL_MY, +    }; +    qp_comms_command_databyte(device, ST77XX_SET_MADCTL, madctl[rotation]); + +#ifndef ST7789_NO_AUTOMATIC_VIEWPORT_OFFSETS +    st7789_automatic_viewport_offsets(device, rotation); +#endif // ST7789_NO_AUTOMATIC_VIEWPORT_OFFSETS + +    return true; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Driver vtable + +const struct tft_panel_dc_reset_painter_driver_vtable_t st7789_driver_vtable = { +    .base = +        { +            .init            = qp_st7789_init, +            .power           = qp_tft_panel_power, +            .clear           = qp_tft_panel_clear, +            .flush           = qp_tft_panel_flush, +            .pixdata         = qp_tft_panel_pixdata, +            .viewport        = qp_tft_panel_viewport, +            .palette_convert = qp_tft_panel_palette_convert, +            .append_pixels   = qp_tft_panel_append_pixels, +        }, +    .rgb888_to_native16bit = qp_rgb888_to_rgb565_swapped, +    .num_window_bytes      = 2, +    .swap_window_coords    = false, +    .opcodes = +        { +            .display_on         = ST77XX_CMD_DISPLAY_ON, +            .display_off        = ST77XX_CMD_DISPLAY_OFF, +            .set_column_address = ST77XX_SET_COL_ADDR, +            .set_row_address    = ST77XX_SET_ROW_ADDR, +            .enable_writes      = ST77XX_SET_MEM, +        }, +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// SPI + +#ifdef QUANTUM_PAINTER_ST7789_SPI_ENABLE + +// Factory function for creating a handle to the ST7789 device +painter_device_t qp_st7789_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 < ST7789_NUM_DEVICES; ++i) { +        tft_panel_dc_reset_painter_device_t *driver = &st7789_drivers[i]; +        if (!driver->base.driver_vtable) { +            driver->base.driver_vtable         = (const struct painter_driver_vtable_t *)&st7789_driver_vtable; +            driver->base.comms_vtable          = (const struct painter_comms_vtable_t *)&spi_comms_with_dc_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->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; +            return (painter_device_t)driver; +        } +    } +    return NULL; +} + +#endif // QUANTUM_PAINTER_ST7789_SPI_ENABLE + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/drivers/painter/st77xx/qp_st7789.h b/drivers/painter/st77xx/qp_st7789.h new file mode 100644 index 0000000000..ec61f5d70b --- /dev/null +++ b/drivers/painter/st77xx/qp_st7789.h @@ -0,0 +1,44 @@ +// Copyright 2021 Paul Cotter (@gr1mr3aver) +// Copyright 2021 Nick Brassel (@tzarc) +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "gpio.h" +#include "qp_internal.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Quantum Painter ST7789 configurables (add to your keyboard's config.h) + +#ifndef ST7789_NUM_DEVICES +/** + * @def This controls the maximum number of ST7789 devices that Quantum Painter can communicate with at any one time. + *      Increasing this number allows for multiple displays to be used. + */ +#    define ST7789_NUM_DEVICES 1 +#endif + +// Additional configuration options to be copied to your keyboard's config.h (don't change here): + +// If you know exactly which offsets should be used on your panel with respect to selected rotation, then this config +// option allows you to save some flash space -- you'll need to invoke qp_set_viewport_offsets() instead from your keyboard. +//       #define ST7789_NO_AUTOMATIC_VIEWPORT_OFFSETS + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Quantum Painter ST7789 device factories + +#ifdef QUANTUM_PAINTER_ST7789_SPI_ENABLE +/** + * Factory method for an ST7789 SPI LCD 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 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_st7789_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_ST7789_SPI_ENABLE diff --git a/drivers/painter/st77xx/qp_st7789_opcodes.h b/drivers/painter/st77xx/qp_st7789_opcodes.h new file mode 100644 index 0000000000..b5baba7184 --- /dev/null +++ b/drivers/painter/st77xx/qp_st7789_opcodes.h @@ -0,0 +1,64 @@ +// Copyright 2021 Paul Cotter (@gr1mr3aver) +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Quantum Painter ST7789 additional command opcodes + +// System function commands +#define ST7789_GET_SELF_DIAG 0x0F      // Get self-diagnostic result +#define ST7789_SET_VERT_SCRL 0x33      // Set vertical scroll definition +#define ST7789_SET_VERT_SCRL_ADDR 0x37 // SEt Vertical scroll start address +#define ST7789_SET_MEM_CONT 0x3C       // Memory Write continue +#define ST7789_GET_MEM_CONT 0x3E       // Memory Read continue +#define ST7789_SET_TEAR_LINE 0x44      // Set tear scanline +#define ST7789_GET_TEAR_LINE 0x45      // Get tear scanline +#define ST7789_SET_BRIGHTNESS 0x51     // Set display brightness +#define ST7789_GET_BRIGHTNESS 0x52     // Get display brightness +#define ST7789_SET_CTRL 0x53           // Set CTRL display +#define ST7789_GET_CTRL 0x54           // Get CTRL display value +#define ST7789_SET_CAB_COLOR 0x55      // Set content adaptive brightness control and color enhancement +#define ST7789_GET_CAB_COLOR 0x56      // Get content adaptive brightness control and color enhancement +#define ST7789_SET_CAB_BRIGHTNESS 0x5E // Set content adaptive minimum brightness +#define ST7789_GET_CAB_BRIGHTNESS 0x5F // Get content adaptive minimum brightness +#define ST7789_GET_ABC_SELF_DIAG 0x68  // Get Auto brightness control self diagnostics + +// Panel Function Commands +#define ST7789_SET_RAM_CTL 0xB0            // Set RAM control +#define ST7789_SET_RGB_CTL 0xB1            // Set RGB control +#define ST7789_SET_PORCH_CTL 0xB2          // Set Porch control +#define ST7789_SET_FRAME_RATE_CTL_1 0xB3   // Set frame rate control 1 +#define ST7789_SET_PARTIAL_CTL 0xB5        // Set Partial control +#define ST7789_SET_GATE_CTL 0xB7           // Set gate control +#define ST7789_SET_GATE_ON_TIMING 0xB8     // Set gate on timing adjustment +#define ST7789_SET_DIGITAL_GAMMA_ON 0xBA   // Enable digital gamma +#define ST7789_SET_VCOM 0xBB               // Set VCOM +#define ST7789_SET_POWER_SAVE 0xBC         // Set power saving mode +#define ST7789_SET_DISP_OFF_POWER 0xBD     // Set display off power saving +#define ST7789_SET_LCM_CTL 0xC0            // Set LCM control +#define ST7789_SET_IDS 0xC1                // Set IDs +#define ST7789_SET_VDV_VRH_ON 0xC2         // Set VDV and VRH command enable +#define ST7789_SET_VRH 0xC3                // Set VRH +#define ST7789_SET_VDV 0xC4                // Set VDV +#define ST7789_SET_VCOM_OFFSET 0xC5        // Set VCOM offset ctl +#define ST7789_SET_FRAME_RATE_CTL_2 0xC6   // Set frame rate control 2 +#define ST7789_SET_CABC_CTL 0xC7           // Set CABC Control +#define ST7789_GET_REG_1 0xC8              // Get register value selection1 +#define ST7789_GET_REG_2 0xCA              // Get register value selection2 +#define ST7789_SET_PWM_FREQ 0xCC           // Set PWM frequency +#define ST7789_SET_POWER_CTL_1 0xD0        // Set power ctl 1 +#define ST7789_SET_VAP_VAN_ON 0xD2         // Enable VAP/VAN signal output +#define ST7789_SET_CMD2_ENABLE 0xDF        // Enable command 2 +#define ST7789_SET_PGAMMA 0xE0             // Set positive gamma +#define ST7789_SET_NGAMMA 0xE1             // Set negative gamma +#define ST7789_SET_DIGITAL_GAMMA_RED 0xE2  // Set digital gamma lookup table for red +#define ST7789_SET_DIGITAL_GAMMA_BLUE 0xE3 // Get digital gamma lookup table for blue +#define ST7789_SET_GATE_CTL_2 0xE4         // Set gate control 2 +#define ST7789_SET_SPI2_ENABLE 0xE7        // Enable SPI2 +#define ST7789_SET_POWER_CTL_2 0xE8        // Set power ctl 2 +#define ST7789_SET_EQ_TIME_CTL 0xE9        // Set equalize time control +#define ST7789_SET_PROG_CTL 0xEC           // Set program control +#define ST7789_SET_PROG_MODE_ENABLE 0xFA   // Set program mode enable +#define ST7789_SET_NVMEM 0xFC              // Set NVMEM data +#define ST7789_SET_PROG_ACTION 0xFE        // Set program action diff --git a/drivers/painter/st77xx/qp_st77xx_opcodes.h b/drivers/painter/st77xx/qp_st77xx_opcodes.h new file mode 100644 index 0000000000..131378d832 --- /dev/null +++ b/drivers/painter/st77xx/qp_st77xx_opcodes.h @@ -0,0 +1,51 @@ +// Copyright 2021 Paul Cotter (@gr1mr3aver) +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Quantum Painter ST77XX command opcodes + +// System function commands +#define ST77XX_CMD_NOP 0x00          // No operation +#define ST77XX_CMD_RESET 0x01        // Software reset +#define ST77XX_GET_ID_INFO 0x04      // Get ID information +#define ST77XX_GET_STATUS 0x09       // Get status +#define ST77XX_GET_PWR_MODE 0x0A     // Get power mode +#define ST77XX_GET_MADCTL 0x0B       // Get mem access ctl +#define ST77XX_GET_PIX_FMT 0x0C      // Get pixel format +#define ST77XX_GET_IMG_FMT 0x0D      // Get image format +#define ST77XX_GET_SIG_MODE 0x0E     // Get signal mode +#define ST77XX_CMD_SLEEP_ON 0x10     // Enter sleep mode +#define ST77XX_CMD_SLEEP_OFF 0x11    // Exist sleep mode +#define ST77XX_CMD_PARTIAL_ON 0x12   // Enter partial mode +#define ST77XX_CMD_NORMAL_ON 0x13    // Exit partial mode +#define ST77XX_CMD_INVERT_OFF 0x20   // Exit inverted mode +#define ST77XX_CMD_INVERT_ON 0x21    // Enter inverted mode +#define ST77XX_SET_GAMMA 0x26        // Set gamma params +#define ST77XX_CMD_DISPLAY_OFF 0x28  // Disable display +#define ST77XX_CMD_DISPLAY_ON 0x29   // Enable display +#define ST77XX_SET_COL_ADDR 0x2A     // Set column address +#define ST77XX_SET_ROW_ADDR 0x2B     // Set page (row) address +#define ST77XX_SET_MEM 0x2C          // Set memory +#define ST77XX_GET_MEM 0x2E          // Get memory +#define ST77XX_SET_PARTIAL_AREA 0x30 // Set partial area +#define ST77XX_CMD_TEARING_OFF 0x34  // Tearing line disabled +#define ST77XX_CMD_TEARING_ON 0x35   // Tearing line enabled +#define ST77XX_SET_MADCTL 0x36       // Set mem access ctl +#define ST77XX_CMD_IDLE_OFF 0x38     // Exit idle mode +#define ST77XX_CMD_IDLE_ON 0x39      // Enter idle mode +#define ST77XX_SET_PIX_FMT 0x3A      // Set pixel format +#define ST77XX_GET_ID1 0xDA          // Get ID1 +#define ST77XX_GET_ID2 0xDB          // Get ID2 +#define ST77XX_GET_ID3 0xDC          // Get ID3 + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// MADCTL Flags +#define ST77XX_MADCTL_MY 0b10000000 +#define ST77XX_MADCTL_MX 0b01000000 +#define ST77XX_MADCTL_MV 0b00100000 +#define ST77XX_MADCTL_ML 0b00010000 +#define ST77XX_MADCTL_RGB 0b00000000 +#define ST77XX_MADCTL_BGR 0b00001000 +#define ST77XX_MADCTL_MH 0b00000100 diff --git a/drivers/painter/tft_panel/qp_tft_panel.c b/drivers/painter/tft_panel/qp_tft_panel.c new file mode 100644 index 0000000000..4d636c9509 --- /dev/null +++ b/drivers/painter/tft_panel/qp_tft_panel.c @@ -0,0 +1,130 @@ +// Copyright 2021 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_tft_panel.h" + +#define BYTE_SWAP(x) (((((uint16_t)(x)) >> 8) & 0x00FF) | ((((uint16_t)(x)) << 8) & 0xFF00)) + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Native pixel format conversion + +uint16_t qp_rgb888_to_rgb565(uint8_t r, uint8_t g, uint8_t b) { +    uint16_t rgb565 = (((uint16_t)r) >> 3) << 11 | (((uint16_t)g) >> 2) << 5 | (((uint16_t)b) >> 3); +    return rgb565; +} + +uint16_t qp_rgb888_to_rgb565_swapped(uint8_t r, uint8_t g, uint8_t b) { +    uint16_t rgb565 = (((uint16_t)r) >> 3) << 11 | (((uint16_t)g) >> 2) << 5 | (((uint16_t)b) >> 3); +    return BYTE_SWAP(rgb565); +} + +uint16_t qp_rgb888_to_bgr565(uint8_t r, uint8_t g, uint8_t b) { +    uint16_t bgr565 = (((uint16_t)b) >> 3) << 11 | (((uint16_t)g) >> 2) << 5 | (((uint16_t)r) >> 3); +    return bgr565; +} + +uint16_t qp_rgb888_to_bgr565_swapped(uint8_t r, uint8_t g, uint8_t b) { +    uint16_t bgr565 = (((uint16_t)b) >> 3) << 11 | (((uint16_t)g) >> 2) << 5 | (((uint16_t)r) >> 3); +    return BYTE_SWAP(bgr565); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Quantum Painter API implementations + +// Power control +bool qp_tft_panel_power(painter_device_t device, bool power_on) { +    struct painter_driver_t *                          driver = (struct painter_driver_t *)device; +    struct tft_panel_dc_reset_painter_driver_vtable_t *vtable = (struct tft_panel_dc_reset_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_tft_panel_clear(painter_device_t device) { +    struct painter_driver_t *driver = (struct painter_driver_t *)device; +    driver->driver_vtable->init(device, driver->rotation); // Re-init the LCD +    return true; +} + +// Screen flush +bool qp_tft_panel_flush(painter_device_t device) { +    // No-op, as there's no framebuffer in RAM for this device. +    return true; +} + +// Viewport to draw to +bool qp_tft_panel_viewport(painter_device_t device, uint16_t left, uint16_t top, uint16_t right, uint16_t bottom) { +    struct painter_driver_t *                          driver = (struct painter_driver_t *)device; +    struct tft_panel_dc_reset_painter_driver_vtable_t *vtable = (struct tft_panel_dc_reset_painter_driver_vtable_t *)driver->driver_vtable; + +    // Fix up the drawing location if required +    left += driver->offset_x; +    right += driver->offset_x; +    top += driver->offset_y; +    bottom += driver->offset_y; + +    // Check if we need to manually swap the window coordinates based on whether or not we're in a sideways rotation +    if (vtable->swap_window_coords && (driver->rotation == QP_ROTATION_90 || driver->rotation == QP_ROTATION_270)) { +        uint16_t temp; + +        temp = left; +        left = top; +        top  = temp; + +        temp   = right; +        right  = bottom; +        bottom = temp; +    } + +    if (vtable->num_window_bytes == 1) { +        // Set up the x-window +        uint8_t xbuf[2] = {left & 0xFF, right & 0xFF}; +        qp_comms_command_databuf(device, vtable->opcodes.set_column_address, xbuf, sizeof(xbuf)); + +        // Set up the y-window +        uint8_t ybuf[2] = {top & 0xFF, bottom & 0xFF}; +        qp_comms_command_databuf(device, vtable->opcodes.set_row_address, ybuf, sizeof(ybuf)); +    } else if (vtable->num_window_bytes == 2) { +        // Set up the x-window +        uint8_t xbuf[4] = {left >> 8, left & 0xFF, right >> 8, right & 0xFF}; +        qp_comms_command_databuf(device, vtable->opcodes.set_column_address, xbuf, sizeof(xbuf)); + +        // Set up the y-window +        uint8_t ybuf[4] = {top >> 8, top & 0xFF, bottom >> 8, bottom & 0xFF}; +        qp_comms_command_databuf(device, vtable->opcodes.set_row_address, ybuf, sizeof(ybuf)); +    } + +    // Lock in the window +    qp_comms_command(device, vtable->opcodes.enable_writes); +    return true; +} + +// Stream pixel data to the current write position in GRAM +bool qp_tft_panel_pixdata(painter_device_t device, const void *pixel_data, uint32_t native_pixel_count) { +    qp_comms_send(device, pixel_data, native_pixel_count * sizeof(uint16_t)); +    return true; +} + +// Convert supplied palette entries into their native equivalents +bool qp_tft_panel_palette_convert(painter_device_t device, int16_t palette_size, qp_pixel_t *palette) { +    struct painter_driver_t *                          driver = (struct painter_driver_t *)device; +    struct tft_panel_dc_reset_painter_driver_vtable_t *vtable = (struct tft_panel_dc_reset_painter_driver_vtable_t *)driver->driver_vtable; +    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}); +        palette[i].rgb565 = vtable->rgb888_to_native16bit(rgb.r, rgb.g, rgb.b); +    } +    return true; +} + +// Append pixels to the target location, keyed by the pixel index +bool qp_tft_panel_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) { +    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; +} diff --git a/drivers/painter/tft_panel/qp_tft_panel.h b/drivers/painter/tft_panel/qp_tft_panel.h new file mode 100644 index 0000000000..6eddfc503d --- /dev/null +++ b/drivers/painter/tft_panel/qp_tft_panel.h @@ -0,0 +1,67 @@ +// Copyright 2021 Nick Brassel (@tzarc) +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "color.h" +#include "qp_internal.h" + +#ifdef QUANTUM_PAINTER_SPI_ENABLE +#    include "qp_comms_spi.h" +#endif // QUANTUM_PAINTER_SPI_ENABLE + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Common TFT panel implementation using D/C, and RST pins. + +typedef uint16_t (*rgb888_to_native_uint16_t)(uint8_t r, uint8_t g, uint8_t b); + +// Driver vtable with extras +struct tft_panel_dc_reset_painter_driver_vtable_t { +    struct painter_driver_vtable_t base; // must be first, so it can be cast to/from the painter_driver_vtable_t* type + +    // Conversion function for palette entries +    rgb888_to_native_uint16_t rgb888_to_native16bit; + +    // Number of bytes for transmitting x/y coordinates +    uint8_t num_window_bytes; + +    // Whether or not the x/y coords should be swapped on 90/270 rotation +    bool swap_window_coords; + +    // Opcodes for normal display operation +    struct { +        uint8_t display_on; +        uint8_t display_off; +        uint8_t set_column_address; +        uint8_t set_row_address; +        uint8_t enable_writes; +    } opcodes; +}; + +// Device definition +typedef struct tft_panel_dc_reset_painter_device_t { +    struct 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 +        struct qp_comms_spi_dc_reset_config_t spi_dc_reset_config; +#endif // QUANTUM_PAINTER_SPI_ENABLE + +        // TODO: I2C/parallel etc. +    }; +} tft_panel_dc_reset_painter_device_t; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Forward declarations for injecting into concrete driver vtables + +bool qp_tft_panel_power(painter_device_t device, bool power_on); +bool qp_tft_panel_clear(painter_device_t device); +bool qp_tft_panel_flush(painter_device_t device); +bool qp_tft_panel_viewport(painter_device_t device, uint16_t left, uint16_t top, uint16_t right, uint16_t bottom); +bool qp_tft_panel_pixdata(painter_device_t device, const void *pixel_data, uint32_t native_pixel_count); +bool qp_tft_panel_palette_convert(painter_device_t device, int16_t palette_size, qp_pixel_t *palette); +bool qp_tft_panel_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); + +uint16_t qp_rgb888_to_rgb565(uint8_t r, uint8_t g, uint8_t b); +uint16_t qp_rgb888_to_rgb565_swapped(uint8_t r, uint8_t g, uint8_t b); +uint16_t qp_rgb888_to_bgr565(uint8_t r, uint8_t g, uint8_t b); +uint16_t qp_rgb888_to_bgr565_swapped(uint8_t r, uint8_t g, uint8_t b); | 
