summaryrefslogtreecommitdiff
path: root/drivers/painter/oled_panel/qp_oled_panel.c
blob: eefee3f13f5cbc97cb8d0ec8cc73732b7b39f07f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
// Copyright 2023 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_oled_panel.h"

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Quantum Painter API implementations

// Power control
bool qp_oled_panel_power(painter_device_t device, bool power_on) {
    painter_driver_t *                  driver = (painter_driver_t *)device;
    oled_panel_painter_driver_vtable_t *vtable = (oled_panel_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_oled_panel_clear(painter_device_t device) {
    painter_driver_t *driver = (painter_driver_t *)device;
    driver->driver_vtable->init(device, driver->rotation); // Re-init the display
    return true;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Surface passthru
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

bool qp_oled_panel_passthru_pixdata(painter_device_t device, const void *pixel_data, uint32_t native_pixel_count) {
    oled_panel_painter_device_t *driver = (oled_panel_painter_device_t *)device;
    return driver->surface.base.validate_ok && driver->surface.base.driver_vtable->pixdata(&driver->surface.base, pixel_data, native_pixel_count);
}

bool qp_oled_panel_passthru_viewport(painter_device_t device, uint16_t left, uint16_t top, uint16_t right, uint16_t bottom) {
    oled_panel_painter_device_t *driver = (oled_panel_painter_device_t *)device;
    return driver->surface.base.validate_ok && driver->surface.base.driver_vtable->viewport(&driver->surface.base, left, top, right, bottom);
}

bool qp_oled_panel_passthru_palette_convert(painter_device_t device, int16_t palette_size, qp_pixel_t *palette) {
    oled_panel_painter_device_t *driver = (oled_panel_painter_device_t *)device;
    return driver->surface.base.validate_ok && driver->surface.base.driver_vtable->palette_convert(&driver->surface.base, palette_size, palette);
}

bool qp_oled_panel_passthru_append_pixels(painter_device_t device, uint8_t *target_buffer, qp_pixel_t *palette, uint32_t pixel_offset, uint32_t pixel_count, uint8_t *palette_indices) {
    oled_panel_painter_device_t *driver = (oled_panel_painter_device_t *)device;
    return driver->surface.base.validate_ok && driver->surface.base.driver_vtable->append_pixels(&driver->surface.base, target_buffer, palette, pixel_offset, pixel_count, palette_indices);
}

bool qp_oled_panel_passthru_append_pixdata(painter_device_t device, uint8_t *target_buffer, uint32_t pixdata_offset, uint8_t pixdata_byte) {
    oled_panel_painter_device_t *driver = (oled_panel_painter_device_t *)device;
    return driver->surface.base.validate_ok && driver->surface.base.driver_vtable->append_pixdata(&driver->surface.base, target_buffer, pixdata_offset, pixdata_byte);
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Flush helpers
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void qp_oled_panel_page_column_flush_rot0(painter_device_t device, surface_dirty_data_t *dirty, const uint8_t *framebuffer) {
    painter_driver_t *                  driver = (painter_driver_t *)device;
    oled_panel_painter_driver_vtable_t *vtable = (oled_panel_painter_driver_vtable_t *)driver->driver_vtable;

    // TODO: account for offset_x/y in base driver
    int min_page   = dirty->t / 8;
    int max_page   = dirty->b / 8;
    int min_column = dirty->l;
    int max_column = dirty->r;

    for (int page = min_page; page <= max_page; ++page) {
        int     cols_required = max_column - min_column + 1;
        uint8_t column_data[cols_required];
        memset(column_data, 0, cols_required);
        for (int x = min_column; x <= max_column; ++x) {
            uint16_t data_offset = x - min_column;
            for (int y = 0; y < 8; ++y) {
                uint32_t pixel_num   = ((page * 8) + y) * driver->panel_width + x;
                uint32_t byte_offset = pixel_num / 8;
                uint8_t  bit_offset  = pixel_num % 8;
                column_data[data_offset] |= ((framebuffer[byte_offset] & (1 << bit_offset)) >> bit_offset) << y;
            }
        }

        int actual_page  = page;
        int start_column = min_column;
        qp_comms_command(device, vtable->opcodes.set_page | actual_page);
        qp_comms_command(device, vtable->opcodes.set_column_lsb | (start_column & 0x0F));
        qp_comms_command(device, vtable->opcodes.set_column_msb | (start_column & 0xF0) >> 4);
        qp_comms_send(device, column_data, cols_required);
    }
}

void qp_oled_panel_page_column_flush_rot90(painter_device_t device, surface_dirty_data_t *dirty, const uint8_t *framebuffer) {
    painter_driver_t *                  driver = (painter_driver_t *)device;
    oled_panel_painter_driver_vtable_t *vtable = (oled_panel_painter_driver_vtable_t *)driver->driver_vtable;

    // TODO: account for offset_x/y in base driver
    int num_columns = driver->panel_width;
    int min_page    = dirty->l / 8;
    int max_page    = dirty->r / 8;
    int min_column  = dirty->t;
    int max_column  = dirty->b;

    for (int page = min_page; page <= max_page; ++page) {
        int     cols_required = max_column - min_column + 1;
        uint8_t column_data[cols_required];
        memset(column_data, 0, cols_required);
        for (int y = min_column; y <= max_column; ++y) {
            uint16_t data_offset = cols_required - 1 - (y - min_column);
            for (int x = 0; x < 8; ++x) {
                uint32_t pixel_num   = y * driver->panel_height + ((page * 8) + x);
                uint32_t byte_offset = pixel_num / 8;
                uint8_t  bit_offset  = pixel_num % 8;
                column_data[data_offset] |= ((framebuffer[byte_offset] & (1 << bit_offset)) >> bit_offset) << x;
            }
        }

        int actual_page  = page;
        int start_column = num_columns - 1 - max_column;
        qp_comms_command(device, vtable->opcodes.set_page | actual_page);
        qp_comms_command(device, vtable->opcodes.set_column_lsb | (start_column & 0x0F));
        qp_comms_command(device, vtable->opcodes.set_column_msb | (start_column & 0xF0) >> 4);
        qp_comms_send(device, column_data, cols_required);
    }
}

void qp_oled_panel_page_column_flush_rot180(painter_device_t device, surface_dirty_data_t *dirty, const uint8_t *framebuffer) {
    painter_driver_t *                  driver = (painter_driver_t *)device;
    oled_panel_painter_driver_vtable_t *vtable = (oled_panel_painter_driver_vtable_t *)driver->driver_vtable;

    // TODO: account for offset_x/y in base driver
    int num_pages   = driver->panel_height / 8;
    int num_columns = driver->panel_width;
    int min_page    = dirty->t / 8;
    int max_page    = dirty->b / 8;
    int min_column  = dirty->l;
    int max_column  = dirty->r;

    for (int page = min_page; page <= max_page; ++page) {
        int     cols_required = max_column - min_column + 1;
        uint8_t column_data[cols_required];
        memset(column_data, 0, cols_required);
        for (int x = min_column; x <= max_column; ++x) {
            uint16_t data_offset = cols_required - 1 - (x - min_column);
            for (int y = 0; y < 8; ++y) {
                uint32_t pixel_num   = ((page * 8) + y) * driver->panel_width + x;
                uint32_t byte_offset = pixel_num / 8;
                uint8_t  bit_offset  = pixel_num % 8;
                column_data[data_offset] |= ((framebuffer[byte_offset] & (1 << bit_offset)) >> bit_offset) << (7 - y);
            }
        }

        int actual_page  = num_pages - 1 - page;
        int start_column = num_columns - 1 - max_column;
        qp_comms_command(device, vtable->opcodes.set_page | actual_page);
        qp_comms_command(device, vtable->opcodes.set_column_lsb | (start_column & 0x0F));
        qp_comms_command(device, vtable->opcodes.set_column_msb | (start_column & 0xF0) >> 4);
        qp_comms_send(device, column_data, cols_required);
    }
}

void qp_oled_panel_page_column_flush_rot270(painter_device_t device, surface_dirty_data_t *dirty, const uint8_t *framebuffer) {
    painter_driver_t *                  driver = (painter_driver_t *)device;
    oled_panel_painter_driver_vtable_t *vtable = (oled_panel_painter_driver_vtable_t *)driver->driver_vtable;

    // TODO: account for offset_x/y in base driver
    int num_pages  = driver->panel_height / 8;
    int min_page   = dirty->l / 8;
    int max_page   = dirty->r / 8;
    int min_column = dirty->t;
    int max_column = dirty->b;

    for (int page = min_page; page <= max_page; ++page) {
        int     cols_required = max_column - min_column + 1;
        uint8_t column_data[cols_required];
        memset(column_data, 0, cols_required);
        for (int y = min_column; y <= max_column; ++y) {
            uint16_t data_offset = y - min_column;
            for (int x = 0; x < 8; ++x) {
                uint32_t pixel_num   = y * driver->panel_height + ((page * 8) + x);
                uint32_t byte_offset = pixel_num / 8;
                uint8_t  bit_offset  = pixel_num % 8;
                column_data[data_offset] |= ((framebuffer[byte_offset] & (1 << bit_offset)) >> bit_offset) << (7 - x);
            }
        }

        int actual_page  = num_pages - 1 - page;
        int start_column = min_column;
        qp_comms_command(device, vtable->opcodes.set_page | actual_page);
        qp_comms_command(device, vtable->opcodes.set_column_lsb | (start_column & 0x0F));
        qp_comms_command(device, vtable->opcodes.set_column_msb | (start_column & 0xF0) >> 4);
        qp_comms_send(device, column_data, cols_required);
    }
}