qp_st7789.c (6669B)
1 // Copyright 2021 Paul Cotter (@gr1mr3aver) 2 // Copyright 2021-2023 Nick Brassel (@tzarc) 3 // SPDX-License-Identifier: GPL-2.0-or-later 4 5 #include "qp_internal.h" 6 #include "qp_comms.h" 7 #include "qp_st7789.h" 8 #include "qp_st77xx_opcodes.h" 9 #include "qp_st7789_opcodes.h" 10 #include "qp_tft_panel.h" 11 12 #ifdef QUANTUM_PAINTER_ST7789_SPI_ENABLE 13 # include "qp_comms_spi.h" 14 #endif // QUANTUM_PAINTER_ST7789_SPI_ENABLE 15 16 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 17 // Common 18 19 // Driver storage 20 tft_panel_dc_reset_painter_device_t st7789_drivers[ST7789_NUM_DEVICES] = {0}; 21 22 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 23 // Automatic viewport offsets 24 25 #ifndef ST7789_NO_AUTOMATIC_OFFSETS 26 static inline void st7789_automatic_viewport_offsets(painter_device_t device, painter_rotation_t rotation) { 27 painter_driver_t *driver = (painter_driver_t *)device; 28 29 // clang-format off 30 const struct { 31 uint16_t offset_x; 32 uint16_t offset_y; 33 } rotation_offsets_240x240[] = { 34 [QP_ROTATION_0] = { .offset_x = 0, .offset_y = 0 }, 35 [QP_ROTATION_90] = { .offset_x = 0, .offset_y = 0 }, 36 [QP_ROTATION_180] = { .offset_x = 0, .offset_y = 80 }, 37 [QP_ROTATION_270] = { .offset_x = 80, .offset_y = 0 }, 38 }; 39 // clang-format on 40 41 if (driver->panel_width == 240 && driver->panel_height == 240) { 42 driver->offset_x = rotation_offsets_240x240[rotation].offset_x; 43 driver->offset_y = rotation_offsets_240x240[rotation].offset_y; 44 } 45 } 46 #endif // ST7789_NO_AUTOMATIC_OFFSETS 47 48 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 49 // Initialization 50 51 __attribute__((weak)) bool qp_st7789_init(painter_device_t device, painter_rotation_t rotation) { 52 // clang-format off 53 const uint8_t st7789_init_sequence[] = { 54 // Command, Delay, N, Data[N] 55 ST77XX_CMD_RESET, 120, 0, 56 ST77XX_CMD_SLEEP_OFF, 5, 0, 57 ST77XX_SET_PIX_FMT, 0, 1, 0x55, 58 ST77XX_CMD_INVERT_ON, 0, 0, 59 ST77XX_CMD_NORMAL_ON, 0, 0, 60 ST77XX_CMD_DISPLAY_ON, 20, 0 61 }; 62 // clang-format on 63 if (!qp_comms_bulk_command_sequence(device, st7789_init_sequence, sizeof(st7789_init_sequence))) { 64 return false; 65 } 66 67 // Configure the rotation (i.e. the ordering and direction of memory writes in GRAM) 68 const uint8_t madctl[] = { 69 [QP_ROTATION_0] = ST77XX_MADCTL_RGB, 70 [QP_ROTATION_90] = ST77XX_MADCTL_RGB | ST77XX_MADCTL_MX | ST77XX_MADCTL_MV, 71 [QP_ROTATION_180] = ST77XX_MADCTL_RGB | ST77XX_MADCTL_MX | ST77XX_MADCTL_MY, 72 [QP_ROTATION_270] = ST77XX_MADCTL_RGB | ST77XX_MADCTL_MV | ST77XX_MADCTL_MY, 73 }; 74 if (!qp_comms_command_databyte(device, ST77XX_SET_MADCTL, madctl[rotation])) { 75 return false; 76 } 77 78 #ifndef ST7789_NO_AUTOMATIC_VIEWPORT_OFFSETS 79 st7789_automatic_viewport_offsets(device, rotation); 80 #endif // ST7789_NO_AUTOMATIC_VIEWPORT_OFFSETS 81 82 return true; 83 } 84 85 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 86 // Driver vtable 87 88 const tft_panel_dc_reset_painter_driver_vtable_t st7789_driver_vtable = { 89 .base = 90 { 91 .init = qp_st7789_init, 92 .power = qp_tft_panel_power, 93 .clear = qp_tft_panel_clear, 94 .flush = qp_tft_panel_flush, 95 .pixdata = qp_tft_panel_pixdata, 96 .viewport = qp_tft_panel_viewport, 97 .palette_convert = qp_tft_panel_palette_convert_rgb565_swapped, 98 .append_pixels = qp_tft_panel_append_pixels_rgb565, 99 .append_pixdata = qp_tft_panel_append_pixdata, 100 }, 101 .num_window_bytes = 2, 102 .swap_window_coords = false, 103 .opcodes = 104 { 105 .display_on = ST77XX_CMD_DISPLAY_ON, 106 .display_off = ST77XX_CMD_DISPLAY_OFF, 107 .set_column_address = ST77XX_SET_COL_ADDR, 108 .set_row_address = ST77XX_SET_ROW_ADDR, 109 .enable_writes = ST77XX_SET_MEM, 110 }, 111 }; 112 113 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 114 // SPI 115 116 #ifdef QUANTUM_PAINTER_ST7789_SPI_ENABLE 117 118 // Factory function for creating a handle to the ST7789 device 119 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) { 120 for (uint32_t i = 0; i < ST7789_NUM_DEVICES; ++i) { 121 tft_panel_dc_reset_painter_device_t *driver = &st7789_drivers[i]; 122 if (!driver->base.driver_vtable) { 123 driver->base.driver_vtable = (const painter_driver_vtable_t *)&st7789_driver_vtable; 124 driver->base.comms_vtable = (const painter_comms_vtable_t *)&spi_comms_with_dc_vtable; 125 driver->base.panel_width = panel_width; 126 driver->base.panel_height = panel_height; 127 driver->base.rotation = QP_ROTATION_0; 128 driver->base.offset_x = 0; 129 driver->base.offset_y = 0; 130 driver->base.native_bits_per_pixel = 16; // RGB565 131 132 // SPI and other pin configuration 133 driver->base.comms_config = &driver->spi_dc_reset_config; 134 driver->spi_dc_reset_config.spi_config.chip_select_pin = chip_select_pin; 135 driver->spi_dc_reset_config.spi_config.divisor = spi_divisor; 136 driver->spi_dc_reset_config.spi_config.lsb_first = false; 137 driver->spi_dc_reset_config.spi_config.mode = spi_mode; 138 driver->spi_dc_reset_config.dc_pin = dc_pin; 139 driver->spi_dc_reset_config.reset_pin = reset_pin; 140 driver->spi_dc_reset_config.command_params_uses_command_pin = false; 141 142 if (!qp_internal_register_device((painter_device_t)driver)) { 143 memset(driver, 0, sizeof(tft_panel_dc_reset_painter_device_t)); 144 return NULL; 145 } 146 147 return (painter_device_t)driver; 148 } 149 } 150 return NULL; 151 } 152 153 #endif // QUANTUM_PAINTER_ST7789_SPI_ENABLE 154 155 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////