aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/ugfx/gdisp/is31fl3731c/gdisp_is31fl3731c.c
blob: 71882440222122e404a03f781e8801936f81c396 (plain) (blame)
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
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
/*
Copyright 2016 Fred Sundvik <fsundvik@gmail.com>

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 "gfx.h"

#if GFX_USE_GDISP

#    define GDISP_DRIVER_VMT GDISPVMT_IS31FL3731C_QMK
#    define GDISP_SCREEN_HEIGHT LED_HEIGHT
#    define GDISP_SCREEN_WIDTH LED_WIDTH

#    include "gdisp_lld_config.h"
#    include "src/gdisp/gdisp_driver.h"

#    include "board_is31fl3731c.h"

// Can't include led_tables from here
extern const uint8_t CIE1931_CURVE[];

/*===========================================================================*/
/* Driver local definitions.                                                 */
/*===========================================================================*/

#    ifndef GDISP_INITIAL_CONTRAST
#        define GDISP_INITIAL_CONTRAST 0
#    endif
#    ifndef GDISP_INITIAL_BACKLIGHT
#        define GDISP_INITIAL_BACKLIGHT 0
#    endif

#    define GDISP_FLG_NEEDFLUSH (GDISP_FLG_DRIVER << 0)

#    define IS31_ADDR_DEFAULT 0x74

#    define IS31_REG_CONFIG 0x00
// bits in reg
#    define IS31_REG_CONFIG_PICTUREMODE 0x00
#    define IS31_REG_CONFIG_AUTOPLAYMODE 0x08
#    define IS31_REG_CONFIG_AUDIOPLAYMODE 0x18
// D2:D0 bits are starting frame for autoplay mode

#    define IS31_REG_PICTDISP 0x01  // D2:D0 frame select for picture mode

#    define IS31_REG_AUTOPLAYCTRL1 0x02
// D6:D4 number of loops (000=infty)
// D2:D0 number of frames to be used

#    define IS31_REG_AUTOPLAYCTRL2 0x03  // D5:D0 delay time (*11ms)

#    define IS31_REG_DISPLAYOPT 0x05
#    define IS31_REG_DISPLAYOPT_INTENSITY_SAME 0x20  // same intensity for all frames
#    define IS31_REG_DISPLAYOPT_BLINK_ENABLE 0x8
// D2:D0 bits blink period time (*0.27s)

#    define IS31_REG_AUDIOSYNC 0x06
#    define IS31_REG_AUDIOSYNC_ENABLE 0x1

#    define IS31_REG_FRAMESTATE 0x07

#    define IS31_REG_BREATHCTRL1 0x08
// D6:D4 fade out time (26ms*2^i)
// D2:D0 fade in time (26ms*2^i)

#    define IS31_REG_BREATHCTRL2 0x09
#    define IS31_REG_BREATHCTRL2_ENABLE 0x10
// D2:D0 extinguish time (3.5ms*2^i)

#    define IS31_REG_SHUTDOWN 0x0A
#    define IS31_REG_SHUTDOWN_OFF 0x0
#    define IS31_REG_SHUTDOWN_ON 0x1

#    define IS31_REG_AGCCTRL 0x0B
#    define IS31_REG_ADCRATE 0x0C

#    define IS31_COMMANDREGISTER 0xFD
#    define IS31_FUNCTIONREG 0x0B  // helpfully called 'page nine'
#    define IS31_FUNCTIONREG_SIZE 0xD

#    define IS31_FRAME_SIZE 0xB4

#    define IS31_PWM_REG 0x24
#    define IS31_PWM_SIZE 0x90

#    define IS31_LED_MASK_SIZE 0x12

#    define IS31

/*===========================================================================*/
/* Driver local functions.                                                   */
/*===========================================================================*/

typedef struct {
    uint8_t write_buffer_offset;
    uint8_t write_buffer[IS31_FRAME_SIZE];
    uint8_t frame_buffer[GDISP_SCREEN_HEIGHT * GDISP_SCREEN_WIDTH];
    uint8_t page;
} __attribute__((__packed__)) PrivData;

// Some common routines and macros
#    define PRIV(g) ((PrivData *)g->priv)

/*===========================================================================*/
/* Driver exported functions.                                                */
/*===========================================================================*/

static GFXINLINE void write_page(GDisplay *g, uint8_t page) {
    uint8_t tx[2] __attribute__((aligned(2)));
    tx[0] = IS31_COMMANDREGISTER;
    tx[1] = page;
    write_data(g, tx, 2);
}

static GFXINLINE void write_register(GDisplay *g, uint8_t page, uint8_t reg, uint8_t data) {
    uint8_t tx[2] __attribute__((aligned(2)));
    tx[0] = reg;
    tx[1] = data;
    write_page(g, page);
    write_data(g, tx, 2);
}

static GFXINLINE void write_ram(GDisplay *g, uint8_t page, uint16_t offset, uint16_t length) {
    PRIV(g)->write_buffer_offset = offset;
    write_page(g, page);
    write_data(g, (uint8_t *)PRIV(g), length + 1);
}

LLDSPEC bool_t gdisp_lld_init(GDisplay *g) {
    // The private area is the display surface.
    g->priv = gfxAlloc(sizeof(PrivData));
    __builtin_memset(PRIV(g), 0, sizeof(PrivData));
    PRIV(g)->page = 0;

    // Initialise the board interface
    init_board(g);
    gfxSleepMilliseconds(10);

    // zero function page, all registers (assuming full_page is all zeroes)
    write_ram(g, IS31_FUNCTIONREG, 0, IS31_FUNCTIONREG_SIZE);
    set_hardware_shutdown(g, false);
    gfxSleepMilliseconds(10);
    // software shutdown
    write_register(g, IS31_FUNCTIONREG, IS31_REG_SHUTDOWN, IS31_REG_SHUTDOWN_OFF);
    gfxSleepMilliseconds(10);
    // zero function page, all registers
    write_ram(g, IS31_FUNCTIONREG, 0, IS31_FUNCTIONREG_SIZE);
    gfxSleepMilliseconds(10);

    // zero all LED registers on all 8 pages, and enable the mask
    __builtin_memcpy(PRIV(g)->write_buffer, get_led_mask(g), IS31_LED_MASK_SIZE);
    for (uint8_t i = 0; i < 8; i++) {
        write_ram(g, i, 0, IS31_FRAME_SIZE);
        gfxSleepMilliseconds(1);
    }

    // software shutdown disable (i.e. turn stuff on)
    write_register(g, IS31_FUNCTIONREG, IS31_REG_SHUTDOWN, IS31_REG_SHUTDOWN_OFF);
    gfxSleepMilliseconds(10);

    // Finish Init
    post_init_board(g);

    /* Initialise the GDISP structure */
    g->g.Width       = GDISP_SCREEN_WIDTH;
    g->g.Height      = GDISP_SCREEN_HEIGHT;
    g->g.Orientation = GDISP_ROTATE_0;
    g->g.Powermode   = powerOff;
    g->g.Backlight   = GDISP_INITIAL_BACKLIGHT;
    g->g.Contrast    = GDISP_INITIAL_CONTRAST;
    return TRUE;
}

#    if GDISP_HARDWARE_FLUSH
LLDSPEC void gdisp_lld_flush(GDisplay *g) {
    // Don't flush if we don't need it.
    if (!(g->flags & GDISP_FLG_NEEDFLUSH)) return;

    PRIV(g)->page++;
    PRIV(g)->page %= 2;
    // TODO: some smarter algorithm for this
    // We should run only one physical page at a time
    // This way we don't need to send so much data, and
    // we could use slightly less memory
    uint8_t *src = PRIV(g)->frame_buffer;
    for (int y = 0; y < GDISP_SCREEN_HEIGHT; y++) {
        for (int x = 0; x < GDISP_SCREEN_WIDTH; x++) {
            uint8_t val                                     = (uint16_t)*src * g->g.Backlight / 100;
            PRIV(g)->write_buffer[get_led_address(g, x, y)] = CIE1931_CURVE[val];
            ++src;
        }
    }
    write_ram(g, PRIV(g)->page, IS31_PWM_REG, IS31_PWM_SIZE);
    gfxSleepMilliseconds(1);
    write_register(g, IS31_FUNCTIONREG, IS31_REG_PICTDISP, PRIV(g)->page);

    g->flags &= ~GDISP_FLG_NEEDFLUSH;
}
#    endif

#    if GDISP_HARDWARE_DRAWPIXEL
LLDSPEC void gdisp_lld_draw_pixel(GDisplay *g) {
    coord_t x, y;

    switch (g->g.Orientation) {
        default:
        case GDISP_ROTATE_0:
            x = g->p.x;
            y = g->p.y;
            break;
        case GDISP_ROTATE_180:
            x = GDISP_SCREEN_WIDTH - 1 - g->p.x;
            y = g->p.y;
            break;
    }
    PRIV(g)->frame_buffer[y * GDISP_SCREEN_WIDTH + x] = gdispColor2Native(g->p.color);
    g->flags |= GDISP_FLG_NEEDFLUSH;
}
#    endif

#    if GDISP_HARDWARE_PIXELREAD
LLDSPEC color_t gdisp_lld_get_pixel_color(GDisplay *g) {
    coord_t x, y;

    switch (g->g.Orientation) {
        default:
        case GDISP_ROTATE_0:
            x = g->p.x;
            y = g->p.y;
            break;
        case GDISP_ROTATE_180:
            x = GDISP_SCREEN_WIDTH - 1 - g->p.x;
            y = g->p.y;
            break;
    }
    return gdispNative2Color(PRIV(g)->frame_buffer[y * GDISP_SCREEN_WIDTH + x]);
}
#    endif

#    if GDISP_NEED_CONTROL && GDISP_HARDWARE_CONTROL
LLDSPEC void gdisp_lld_control(GDisplay *g) {
    switch (g->p.x) {
        case GDISP_CONTROL_POWER:
            if (g->g.Powermode == (powermode_t)g->p.ptr) return;
            switch ((powermode_t)g->p.ptr) {
                case powerOff:
                case powerSleep:
                case powerDeepSleep:
                    write_register(g, IS31_FUNCTIONREG, IS31_REG_SHUTDOWN, IS31_REG_SHUTDOWN_OFF);
                    break;
                case powerOn:
                    write_register(g, IS31_FUNCTIONREG, IS31_REG_SHUTDOWN, IS31_REG_SHUTDOWN_ON);
                    break;
                default:
                    return;
            }
            g->g.Powermode = (powermode_t)g->p.ptr;
            return;

        case GDISP_CONTROL_ORIENTATION:
            if (g->g.Orientation == (orientation_t)g->p.ptr) return;
            switch ((orientation_t)g->p.ptr) {
                /* Rotation is handled by the drawing routines */
                case GDISP_ROTATE_0:
                case GDISP_ROTATE_180:
                    g->g.Height = GDISP_SCREEN_HEIGHT;
                    g->g.Width  = GDISP_SCREEN_WIDTH;
                    break;
                case GDISP_ROTATE_90:
                case GDISP_ROTATE_270:
                    g->g.Height = GDISP_SCREEN_WIDTH;
                    g->g.Width  = GDISP_SCREEN_HEIGHT;
                    break;
                default:
                    return;
            }
            g->g.Orientation = (orientation_t)g->p.ptr;
            return;

        case GDISP_CONTROL_BACKLIGHT:
            if (g->g.Backlight == (unsigned)g->p.ptr) return;
            unsigned val   = (unsigned)g->p.ptr;
            g->g.Backlight = val > 100 ? 100 : val;
            g->flags |= GDISP_FLG_NEEDFLUSH;
            return;
    }
}
#    endif  // GDISP_NEED_CONTROL

#endif  // GFX_USE_GDISP