qmk_firmware

QMK firmware for my keyboards (Corne, Sweep Ferris) and trackball (Ploopy Adept)
Log | Files | Refs | Submodules | LICENSE

qp_ili9486.c (12950B)


      1 // Copyright 2021 Nick Brassel (@tzarc)
      2 // Copyright 2023 Pablo Martinez (@elpekenin) <elpekenin@elpekenin.dev>
      3 // SPDX-License-Identifier: GPL-2.0-or-later
      4 
      5 #include "qp_internal.h"
      6 #include "qp_comms.h"
      7 #include "qp_ili9486.h"
      8 #include "qp_ili9xxx_opcodes.h"
      9 #include "qp_tft_panel.h"
     10 
     11 #ifdef QUANTUM_PAINTER_ILI9486_SPI_ENABLE
     12 #    include "spi_master.h"
     13 #    include <qp_comms_spi.h>
     14 #endif // QUANTUM_PAINTER_ILI9486_SPI_ENABLE
     15 
     16 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
     17 // Common
     18 
     19 // Driver storage
     20 tft_panel_dc_reset_painter_device_t ili9486_drivers[ILI9486_NUM_DEVICES] = {0};
     21 
     22 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
     23 // Initialization
     24 
     25 bool qp_ili9486_init(painter_device_t device, painter_rotation_t rotation) {
     26     // clang-format off
     27     const uint8_t ili9486_init_sequence[] = {
     28         // Command,                 Delay,  N, Data[N]
     29         ILI9XXX_CMD_RESET,            120,  0,
     30         ILI9XXX_SET_PIX_FMT,            0,  1, 0x55,
     31         ILI9XXX_SET_PGAMMA,             0, 15, 0x0F, 0x1F, 0x1C, 0x0C, 0x0F, 0x08, 0x48, 0x98, 0x37, 0x0A, 0x13, 0x04, 0x11, 0x0D, 0x00,
     32         ILI9XXX_SET_NGAMMA,             0, 15, 0x0F, 0x32, 0x2E, 0x0B, 0x0D, 0x05, 0x47, 0x75, 0x37, 0x06, 0x10, 0x03, 0x24, 0x20, 0x00,
     33         ILI9XXX_SET_POWER_CTL_1,        0,  2, 0x0D, 0x0D,
     34         ILI9XXX_SET_POWER_CTL_2,        0,  2, 0x43, 0x00,
     35         ILI9XXX_SET_POWER_CTL_3,        0,  1, 0x00,
     36         ILI9XXX_SET_VCOM_CTL_1,         0,  4, 0x00, 0x48, 0x00, 0x48,
     37         ILI9XXX_SET_INVERSION_CTL,      0,  1, 0x02,
     38     };
     39     // clang-format on
     40     if (!qp_comms_bulk_command_sequence(device, ili9486_init_sequence, sizeof(ili9486_init_sequence))) {
     41         return false;
     42     }
     43 
     44     // Configure the rotation (i.e. the ordering and direction of memory writes in GRAM)
     45     const uint8_t madctl[] = {
     46         [QP_ROTATION_0]   = ILI9XXX_MADCTL_BGR,
     47         [QP_ROTATION_90]  = ILI9XXX_MADCTL_BGR | ILI9XXX_MADCTL_MV,
     48         [QP_ROTATION_180] = ILI9XXX_MADCTL_BGR,
     49         [QP_ROTATION_270] = ILI9XXX_MADCTL_BGR | ILI9XXX_MADCTL_MV,
     50     };
     51     const uint8_t functl[] = {
     52         [QP_ROTATION_0]   = 0x42,
     53         [QP_ROTATION_90]  = 0x62,
     54         [QP_ROTATION_180] = 0x22,
     55         [QP_ROTATION_270] = 0x02,
     56     };
     57 
     58     // clang-format off
     59     uint8_t rotation_sequence[] = {
     60         // Command,                 Delay,  N, Data[N]
     61         ILI9XXX_SET_MEM_ACS_CTL,        0,  1, madctl[rotation],
     62         ILI9XXX_SET_FUNCTION_CTL,       0,  2, 0x00, functl[rotation],
     63         ILI9XXX_CMD_SLEEP_OFF,          5,  0,
     64         ILI9XXX_CMD_DISPLAY_ON,         5,  0,
     65     };
     66     // clang-format on
     67     return qp_comms_bulk_command_sequence(device, rotation_sequence, sizeof(rotation_sequence));
     68 }
     69 
     70 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
     71 // Driver vtable
     72 
     73 // waveshare variant needs some tweaks due to shift registers
     74 static bool qp_comms_spi_dc_reset_send_command_odd_cs_pulse(painter_device_t device, uint8_t cmd) {
     75     painter_driver_t               *driver       = (painter_driver_t *)device;
     76     qp_comms_spi_dc_reset_config_t *comms_config = (qp_comms_spi_dc_reset_config_t *)driver->comms_config;
     77 
     78     gpio_write_pin_low(comms_config->spi_config.chip_select_pin);
     79     qp_comms_spi_dc_reset_send_command(device, cmd);
     80     gpio_write_pin_high(comms_config->spi_config.chip_select_pin);
     81 
     82     return true;
     83 }
     84 
     85 static uint32_t qp_comms_spi_send_data_odd_cs_pulse(painter_device_t device, const void *data, uint32_t byte_count) {
     86     painter_driver_t               *driver       = (painter_driver_t *)device;
     87     qp_comms_spi_dc_reset_config_t *comms_config = (qp_comms_spi_dc_reset_config_t *)driver->comms_config;
     88 
     89     uint32_t       bytes_remaining = byte_count;
     90     const uint8_t *p               = (const uint8_t *)data;
     91     uint32_t       max_msg_length  = 1024;
     92 
     93     gpio_write_pin_high(comms_config->dc_pin);
     94     while (bytes_remaining > 0) {
     95         uint32_t bytes_this_loop = MIN(bytes_remaining, max_msg_length);
     96         bool     odd_bytes       = bytes_this_loop & 1;
     97 
     98         // send data
     99         gpio_write_pin_low(comms_config->spi_config.chip_select_pin);
    100         spi_transmit(p, bytes_this_loop);
    101         p += bytes_this_loop;
    102 
    103         // extra CS toggle, for alignment
    104         if (odd_bytes) {
    105             gpio_write_pin_high(comms_config->spi_config.chip_select_pin);
    106             gpio_write_pin_low(comms_config->spi_config.chip_select_pin);
    107         }
    108 
    109         bytes_remaining -= bytes_this_loop;
    110     }
    111 
    112     return byte_count - bytes_remaining;
    113 }
    114 
    115 static uint32_t qp_ili9486_send_data_toggling(painter_device_t device, const uint8_t *data, uint32_t byte_count) {
    116     painter_driver_t               *driver       = (painter_driver_t *)device;
    117     qp_comms_spi_dc_reset_config_t *comms_config = (qp_comms_spi_dc_reset_config_t *)driver->comms_config;
    118 
    119     uint32_t ret;
    120     for (uint8_t j = 0; j < byte_count; ++j) {
    121         gpio_write_pin_low(comms_config->spi_config.chip_select_pin);
    122         ret = qp_comms_spi_dc_reset_send_data(device, &data[j], 1);
    123         gpio_write_pin_high(comms_config->spi_config.chip_select_pin);
    124     }
    125 
    126     return ret;
    127 }
    128 
    129 static bool qp_comms_spi_send_command_sequence_odd_cs_pulse(painter_device_t device, const uint8_t *sequence, size_t sequence_len) {
    130     for (size_t i = 0; i < sequence_len;) {
    131         uint8_t command   = sequence[i];
    132         uint8_t delay     = sequence[i + 1];
    133         uint8_t num_bytes = sequence[i + 2];
    134 
    135         qp_comms_spi_dc_reset_send_command_odd_cs_pulse(device, command);
    136         if (num_bytes > 0) {
    137             qp_ili9486_send_data_toggling(device, &sequence[i + 3], num_bytes);
    138         }
    139 
    140         if (delay > 0) {
    141             wait_ms(delay);
    142         }
    143         i += (3 + num_bytes);
    144     }
    145 
    146     return true;
    147 }
    148 
    149 static bool qp_ili9486_viewport(painter_device_t device, uint16_t left, uint16_t top, uint16_t right, uint16_t bottom) {
    150     painter_driver_t                           *driver = (painter_driver_t *)device;
    151     tft_panel_dc_reset_painter_driver_vtable_t *vtable = (tft_panel_dc_reset_painter_driver_vtable_t *)driver->driver_vtable;
    152 
    153     // Fix up the drawing location if required
    154     left += driver->offset_x;
    155     right += driver->offset_x;
    156     top += driver->offset_y;
    157     bottom += driver->offset_y;
    158 
    159     // Check if we need to manually swap the window coordinates based on whether or not we're in a sideways rotation
    160     if (vtable->swap_window_coords && (driver->rotation == QP_ROTATION_90 || driver->rotation == QP_ROTATION_270)) {
    161         uint16_t temp;
    162 
    163         temp = left;
    164         left = top;
    165         top  = temp;
    166 
    167         temp   = right;
    168         right  = bottom;
    169         bottom = temp;
    170     }
    171 
    172     // Set up the x-window
    173     uint8_t xbuf[4] = {left >> 8, left & 0xFF, right >> 8, right & 0xFF};
    174     qp_comms_spi_dc_reset_send_command_odd_cs_pulse(device, vtable->opcodes.set_column_address);
    175     qp_ili9486_send_data_toggling(device, xbuf, 4);
    176 
    177     // Set up the y-window
    178     uint8_t ybuf[4] = {top >> 8, top & 0xFF, bottom >> 8, bottom & 0xFF};
    179     qp_comms_spi_dc_reset_send_command_odd_cs_pulse(device, vtable->opcodes.set_row_address);
    180     qp_ili9486_send_data_toggling(device, ybuf, 4);
    181 
    182     // Lock in the window
    183     qp_comms_spi_dc_reset_send_command_odd_cs_pulse(device, vtable->opcodes.enable_writes);
    184     return true;
    185 }
    186 
    187 // Regular
    188 const tft_panel_dc_reset_painter_driver_vtable_t ili9486_driver_vtable = {
    189     .base =
    190         {
    191             .init            = qp_ili9486_init,
    192             .power           = qp_tft_panel_power,
    193             .clear           = qp_tft_panel_clear,
    194             .flush           = qp_tft_panel_flush,
    195             .pixdata         = qp_tft_panel_pixdata,
    196             .viewport        = qp_tft_panel_viewport,
    197             .palette_convert = qp_tft_panel_palette_convert_rgb565_swapped,
    198             .append_pixels   = qp_tft_panel_append_pixels_rgb565,
    199             .append_pixdata  = qp_tft_panel_append_pixdata,
    200         },
    201     .num_window_bytes   = 2,
    202     .swap_window_coords = false,
    203     .opcodes =
    204         {
    205             .display_on         = ILI9XXX_CMD_DISPLAY_ON,
    206             .display_off        = ILI9XXX_CMD_DISPLAY_OFF,
    207             .set_column_address = ILI9XXX_SET_COL_ADDR,
    208             .set_row_address    = ILI9XXX_SET_PAGE_ADDR,
    209             .enable_writes      = ILI9XXX_SET_MEM,
    210         },
    211 };
    212 
    213 // Waveshare tweaks
    214 const tft_panel_dc_reset_painter_driver_vtable_t ili9486_waveshare_driver_vtable = {
    215     .base =
    216         {
    217             .init            = qp_ili9486_init,
    218             .power           = qp_tft_panel_power,
    219             .clear           = qp_tft_panel_clear,
    220             .flush           = qp_tft_panel_flush,
    221             .pixdata         = qp_tft_panel_pixdata,
    222             .viewport        = qp_ili9486_viewport,
    223             .palette_convert = qp_tft_panel_palette_convert_rgb565_swapped,
    224             .append_pixels   = qp_tft_panel_append_pixels_rgb565,
    225             .append_pixdata  = qp_tft_panel_append_pixdata,
    226         },
    227     .num_window_bytes   = 2,
    228     .swap_window_coords = false,
    229     .opcodes =
    230         {
    231             .display_on         = ILI9XXX_CMD_DISPLAY_ON,
    232             .display_off        = ILI9XXX_CMD_DISPLAY_OFF,
    233             .set_column_address = ILI9XXX_SET_COL_ADDR,
    234             .set_row_address    = ILI9XXX_SET_PAGE_ADDR,
    235             .enable_writes      = ILI9XXX_SET_MEM,
    236         },
    237 };
    238 
    239 static const painter_comms_with_command_vtable_t spi_comms_odd_cs_pulse_vtable = {
    240     .base =
    241         {
    242             .comms_init  = qp_comms_spi_dc_reset_init,
    243             .comms_start = qp_comms_spi_start,
    244             .comms_send  = qp_comms_spi_send_data_odd_cs_pulse,
    245             .comms_stop  = qp_comms_spi_stop,
    246         },
    247     .send_command          = qp_comms_spi_dc_reset_send_command_odd_cs_pulse,
    248     .bulk_command_sequence = qp_comms_spi_send_command_sequence_odd_cs_pulse,
    249 };
    250 
    251 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    252 // SPI
    253 
    254 #ifdef QUANTUM_PAINTER_ILI9486_SPI_ENABLE
    255 
    256 // Factory function for creating a handle to the ILI9486 device
    257 painter_device_t qp_ili9486_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) {
    258     for (uint32_t i = 0; i < ILI9486_NUM_DEVICES; ++i) {
    259         tft_panel_dc_reset_painter_device_t *driver = &ili9486_drivers[i];
    260         if (!driver->base.driver_vtable) {
    261             driver->base.driver_vtable         = (const painter_driver_vtable_t *)&ili9486_driver_vtable;
    262             driver->base.comms_vtable          = (const painter_comms_vtable_t *)&spi_comms_with_dc_vtable;
    263             driver->base.native_bits_per_pixel = 16; // RGB565
    264             driver->base.panel_width           = panel_width;
    265             driver->base.panel_height          = panel_height;
    266             driver->base.rotation              = QP_ROTATION_0;
    267             driver->base.offset_x              = 0;
    268             driver->base.offset_y              = 0;
    269 
    270             // SPI and other pin configuration
    271             driver->base.comms_config                              = &driver->spi_dc_reset_config;
    272             driver->spi_dc_reset_config.spi_config.chip_select_pin = chip_select_pin;
    273             driver->spi_dc_reset_config.spi_config.divisor         = spi_divisor;
    274             driver->spi_dc_reset_config.spi_config.lsb_first       = false;
    275             driver->spi_dc_reset_config.spi_config.mode            = spi_mode;
    276             driver->spi_dc_reset_config.dc_pin                     = dc_pin;
    277             driver->spi_dc_reset_config.reset_pin                  = reset_pin;
    278 
    279             if (!qp_internal_register_device((painter_device_t)driver)) {
    280                 memset(driver, 0, sizeof(tft_panel_dc_reset_painter_device_t));
    281                 return NULL;
    282             }
    283 
    284             return (painter_device_t)driver;
    285         }
    286     }
    287     return NULL;
    288 }
    289 
    290 painter_device_t qp_ili9486_make_spi_waveshare_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) {
    291     painter_device_t device = qp_ili9486_make_spi_device(panel_width, panel_height, chip_select_pin, dc_pin, reset_pin, spi_divisor, spi_mode);
    292     if (device) {
    293         tft_panel_dc_reset_painter_device_t *driver = (tft_panel_dc_reset_painter_device_t *)device;
    294         driver->base.driver_vtable                  = (const painter_driver_vtable_t *)&ili9486_waveshare_driver_vtable;
    295         driver->base.comms_vtable                   = (const painter_comms_vtable_t *)&spi_comms_odd_cs_pulse_vtable;
    296     }
    297     return device;
    298 }
    299 
    300 #endif // QUANTUM_PAINTER_ILI9486_SPI_ENABLE
    301 
    302 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////