diff options
Diffstat (limited to 'drivers')
26 files changed, 2290 insertions, 83 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); diff --git a/drivers/sensors/pmw3360.c b/drivers/sensors/pmw3360.c index 8c977be1c8..5f4d17a3f0 100644 --- a/drivers/sensors/pmw3360.c +++ b/drivers/sensors/pmw3360.c @@ -1,6 +1,7 @@ /* Copyright 2020 Christopher Courtney, aka Drashna Jael're (@drashna) <drashna@live.com> * Copyright 2019 Sunjun Kim * Copyright 2020 Ploopy Corporation + * Copyright 2022 Ulrich Spörlein * * 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 @@ -83,7 +84,11 @@ # define MAX_CPI 0x77 #endif -bool _inBurst = false; +static const pin_t pins[] = PMW3360_CS_PINS; +#define NUMBER_OF_SENSORS (sizeof(pins) / sizeof(pin_t)) + +// per-sensor driver state +static bool _inBurst[NUMBER_OF_SENSORS] = {0}; #ifdef CONSOLE_ENABLE void print_byte(uint8_t byte) { @@ -92,18 +97,18 @@ void print_byte(uint8_t byte) { #endif #define constrain(amt, low, high) ((amt) < (low) ? (low) : ((amt) > (high) ? (high) : (amt))) -bool pmw3360_spi_start(void) { - bool status = spi_start(PMW3360_CS_PIN, PMW3360_SPI_LSBFIRST, PMW3360_SPI_MODE, PMW3360_SPI_DIVISOR); +bool pmw3360_spi_start(int8_t index) { + bool status = spi_start(pins[index], PMW3360_SPI_LSBFIRST, PMW3360_SPI_MODE, PMW3360_SPI_DIVISOR); // tNCS-SCLK, 120ns wait_us(1); return status; } -spi_status_t pmw3360_write(uint8_t reg_addr, uint8_t data) { - pmw3360_spi_start(); +spi_status_t pmw3360_write(int8_t index, uint8_t reg_addr, uint8_t data) { + pmw3360_spi_start(index); if (reg_addr != REG_Motion_Burst) { - _inBurst = false; + _inBurst[index] = false; } // send address of the register, with MSBit = 1 to indicate it's a write @@ -114,13 +119,13 @@ spi_status_t pmw3360_write(uint8_t reg_addr, uint8_t data) { wait_us(35); spi_stop(); - // tSWW/tSWR (=180us) minus tSCLK-NCS. Could be shortened, but is looks like a safe lower bound + // tSWW/tSWR (=180us) minus tSCLK-NCS. Could be shortened, but it looks like a safe lower bound wait_us(145); return status; } -uint8_t pmw3360_read(uint8_t reg_addr) { - pmw3360_spi_start(); +uint8_t pmw3360_read(int8_t index, uint8_t reg_addr) { + pmw3360_spi_start(index); // send adress of the register, with MSBit = 0 to indicate it's a read spi_write(reg_addr & 0x7f); // tSRAD (=160us) @@ -136,36 +141,62 @@ uint8_t pmw3360_read(uint8_t reg_addr) { return data; } -bool pmw3360_init(void) { - setPinOutput(PMW3360_CS_PIN); +bool pmw3360_check_signature(int8_t index) { + uint8_t pid = pmw3360_read(index, REG_Product_ID); + uint8_t iv_pid = pmw3360_read(index, REG_Inverse_Product_ID); + uint8_t SROM_ver = pmw3360_read(index, REG_SROM_ID); + return (pid == firmware_signature[0] && iv_pid == firmware_signature[1] && SROM_ver == firmware_signature[2]); // signature for SROM 0x04 +} - spi_init(); - _inBurst = false; +void pmw3360_upload_firmware(int8_t index) { + // Datasheet claims we need to disable REST mode first, but during startup + // it's already disabled and we're not turning it on ... + // pmw3360_write(index, REG_Config2, 0x00); // disable REST mode + pmw3360_write(index, REG_SROM_Enable, 0x1d); - spi_stop(); - pmw3360_spi_start(); - spi_stop(); + wait_ms(10); + + pmw3360_write(index, REG_SROM_Enable, 0x18); + + pmw3360_spi_start(index); + spi_write(REG_SROM_Load_Burst | 0x80); + wait_us(15); - pmw3360_write(REG_Shutdown, 0xb6); // Shutdown first - wait_ms(300); + for (uint16_t i = 0; i < FIRMWARE_LENGTH; i++) { + spi_write(pgm_read_byte(firmware_data + i)); +#ifndef PMW3360_FIRMWARE_UPLOAD_FAST + wait_us(15); +#endif + } + wait_us(200); - pmw3360_spi_start(); + pmw3360_read(index, REG_SROM_ID); + pmw3360_write(index, REG_Config2, 0x00); +} + +bool pmw3360_init(int8_t index) { + if (index >= NUMBER_OF_SENSORS) { + return false; + } + spi_init(); + + // power up, need to first drive NCS high then low. + // the datasheet does not say for how long, 40us works well in practice. + pmw3360_spi_start(index); wait_us(40); spi_stop(); wait_us(40); - - // power up, need to first drive NCS high then low, see above. - pmw3360_write(REG_Power_Up_Reset, 0x5a); + pmw3360_write(index, REG_Power_Up_Reset, 0x5a); wait_ms(50); // read registers and discard - pmw3360_read(REG_Motion); - pmw3360_read(REG_Delta_X_L); - pmw3360_read(REG_Delta_X_H); - pmw3360_read(REG_Delta_Y_L); - pmw3360_read(REG_Delta_Y_H); + pmw3360_read(index, REG_Motion); + pmw3360_read(index, REG_Delta_X_L); + pmw3360_read(index, REG_Delta_X_H); + pmw3360_read(index, REG_Delta_Y_L); + pmw3360_read(index, REG_Delta_Y_H); - pmw3360_upload_firmware(); + pmw3360_upload_firmware(index); spi_stop(); @@ -174,13 +205,13 @@ bool pmw3360_init(void) { wait_ms(1); - pmw3360_write(REG_Config2, 0x00); + pmw3360_write(index, REG_Config2, 0x00); - pmw3360_write(REG_Angle_Tune, constrain(ROTATIONAL_TRANSFORM_ANGLE, -127, 127)); + pmw3360_write(index, REG_Angle_Tune, constrain(ROTATIONAL_TRANSFORM_ANGLE, -127, 127)); - pmw3360_write(REG_Lift_Config, PMW3360_LIFTOFF_DISTANCE); + pmw3360_write(index, REG_Lift_Config, PMW3360_LIFTOFF_DISTANCE); - bool init_success = pmw3360_check_signature(); + bool init_success = pmw3360_check_signature(index); #ifdef CONSOLE_ENABLE if (init_success) { dprintf("pmw3360 signature verified"); @@ -189,66 +220,38 @@ bool pmw3360_init(void) { } #endif - writePinLow(PMW3360_CS_PIN); - return init_success; } -void pmw3360_upload_firmware(void) { - // Datasheet claims we need to disable REST mode first, but during startup - // it's already disabled and we're not turning it on ... - // pmw3360_write(REG_Config2, 0x00); // disable REST mode - pmw3360_write(REG_SROM_Enable, 0x1d); - - wait_ms(10); - - pmw3360_write(REG_SROM_Enable, 0x18); - - pmw3360_spi_start(); - spi_write(REG_SROM_Load_Burst | 0x80); - wait_us(15); - - for (uint16_t i = 0; i < FIRMWARE_LENGTH; i++) { - spi_write(pgm_read_byte(firmware_data + i)); -#ifndef PMW3360_FIRMWARE_UPLOAD_FAST - wait_us(15); -#endif - } - wait_us(200); - - pmw3360_read(REG_SROM_ID); - pmw3360_write(REG_Config2, 0x00); -} - -bool pmw3360_check_signature(void) { - uint8_t pid = pmw3360_read(REG_Product_ID); - uint8_t iv_pid = pmw3360_read(REG_Inverse_Product_ID); - uint8_t SROM_ver = pmw3360_read(REG_SROM_ID); - return (pid == firmware_signature[0] && iv_pid == firmware_signature[1] && SROM_ver == firmware_signature[2]); // signature for SROM 0x04 -} - +// Only support reading the value from sensor #0, no one is using this anyway. uint16_t pmw3360_get_cpi(void) { - uint8_t cpival = pmw3360_read(REG_Config1); + uint8_t cpival = pmw3360_read(0, REG_Config1); return (uint16_t)((cpival + 1) & 0xFF) * CPI_STEP; } +// Write same CPI to all sensors. void pmw3360_set_cpi(uint16_t cpi) { uint8_t cpival = constrain((cpi / CPI_STEP) - 1, 0, MAX_CPI); - pmw3360_write(REG_Config1, cpival); + for (size_t i = 0; i < NUMBER_OF_SENSORS; i++) { + pmw3360_write(i, REG_Config1, cpival); + } } -report_pmw3360_t pmw3360_read_burst(void) { +report_pmw3360_t pmw3360_read_burst(int8_t index) { report_pmw3360_t report = {0}; + if (index >= NUMBER_OF_SENSORS) { + return report; + } - if (!_inBurst) { + if (!_inBurst[index]) { #ifdef CONSOLE_ENABLE - dprintf("burst on"); + dprintf("burst on for index %d", index); #endif - pmw3360_write(REG_Motion_Burst, 0x00); - _inBurst = true; + pmw3360_write(index, REG_Motion_Burst, 0x00); + _inBurst[index] = true; } - pmw3360_spi_start(); + pmw3360_spi_start(index); spi_write(REG_Motion_Burst); wait_us(35); // waits for tSRAD_MOTBR @@ -261,7 +264,7 @@ report_pmw3360_t pmw3360_read_burst(void) { report.mdy = spi_read(); if (report.motion & 0b111) { // panic recovery, sometimes burst mode works weird. - _inBurst = false; + _inBurst[index] = false; } spi_stop(); diff --git a/drivers/sensors/pmw3360.h b/drivers/sensors/pmw3360.h index eec7295871..3aa8ed0ed8 100644 --- a/drivers/sensors/pmw3360.h +++ b/drivers/sensors/pmw3360.h @@ -52,8 +52,14 @@ # define ROTATIONAL_TRANSFORM_ANGLE 0x00 #endif -#ifndef PMW3360_CS_PIN -# error "No chip select pin defined -- missing PMW3360_CS_PIN" +// Support single and plural spellings +#ifndef PMW3360_CS_PINS +# ifndef PMW3360_CS_PIN +# error "No chip select pin defined -- missing PMW3360_CS_PIN or PMW3360_CS_PINS" +# else +# define PMW3360_CS_PINS \ + { PMW3360_CS_PIN } +# endif #endif typedef struct { @@ -66,10 +72,8 @@ typedef struct { int8_t mdy; } report_pmw3360_t; -bool pmw3360_init(void); -void pmw3360_upload_firmware(void); -bool pmw3360_check_signature(void); +bool pmw3360_init(int8_t index); uint16_t pmw3360_get_cpi(void); void pmw3360_set_cpi(uint16_t cpi); /* Reads and clears the current delta values on the sensor */ -report_pmw3360_t pmw3360_read_burst(void); +report_pmw3360_t pmw3360_read_burst(int8_t index); |