xref: /openbmc/qemu/hw/display/dm163.c (revision 19ed42e8)
1 /*
2  * QEMU DM163 8x3-channel constant current led driver
3  * driving columns of associated 8x8 RGB matrix.
4  *
5  * Copyright (C) 2024 Samuel Tardieu <sam@rfc1149.net>
6  * Copyright (C) 2024 Arnaud Minier <arnaud.minier@telecom-paris.fr>
7  * Copyright (C) 2024 Inès Varhol <ines.varhol@telecom-paris.fr>
8  *
9  * SPDX-License-Identifier: GPL-2.0-or-later
10  */
11 
12 /*
13  * The reference used for the DM163 is the following :
14  * http://www.siti.com.tw/product/spec/LED/DM163.pdf
15  */
16 
17 #include "qemu/osdep.h"
18 #include "qapi/error.h"
19 #include "migration/vmstate.h"
20 #include "hw/irq.h"
21 #include "hw/qdev-properties.h"
22 #include "hw/display/dm163.h"
23 #include "ui/console.h"
24 #include "trace.h"
25 
26 #define LED_SQUARE_SIZE 100
27 /* Number of frames a row stays visible after being turned off. */
28 #define ROW_PERSISTENCE 3
29 #define TURNED_OFF_ROW (COLOR_BUFFER_SIZE - 1)
30 
31 static const VMStateDescription vmstate_dm163 = {
32     .name = TYPE_DM163,
33     .version_id = 1,
34     .minimum_version_id = 1,
35     .fields = (const VMStateField[]) {
36         VMSTATE_UINT64_ARRAY(bank0_shift_register, DM163State, 3),
37         VMSTATE_UINT64_ARRAY(bank1_shift_register, DM163State, 3),
38         VMSTATE_UINT16_ARRAY(latched_outputs, DM163State, DM163_NUM_LEDS),
39         VMSTATE_UINT16_ARRAY(outputs, DM163State, DM163_NUM_LEDS),
40         VMSTATE_UINT8(dck, DM163State),
41         VMSTATE_UINT8(en_b, DM163State),
42         VMSTATE_UINT8(lat_b, DM163State),
43         VMSTATE_UINT8(rst_b, DM163State),
44         VMSTATE_UINT8(selbk, DM163State),
45         VMSTATE_UINT8(sin, DM163State),
46         VMSTATE_UINT8(activated_rows, DM163State),
47         VMSTATE_UINT32_2DARRAY(buffer, DM163State, COLOR_BUFFER_SIZE,
48                                RGB_MATRIX_NUM_COLS),
49         VMSTATE_UINT8(last_buffer_idx, DM163State),
50         VMSTATE_UINT8_ARRAY(buffer_idx_of_row, DM163State, RGB_MATRIX_NUM_ROWS),
51         VMSTATE_UINT8_ARRAY(row_persistence_delay, DM163State,
52                             RGB_MATRIX_NUM_ROWS),
53         VMSTATE_END_OF_LIST()
54     }
55 };
56 
57 static void dm163_reset_hold(Object *obj, ResetType type)
58 {
59     DM163State *s = DM163(obj);
60 
61     s->sin = 0;
62     s->dck = 0;
63     s->rst_b = 0;
64     /* Ensuring the first falling edge of lat_b isn't missed */
65     s->lat_b = 1;
66     s->selbk = 0;
67     s->en_b = 0;
68     /* Reset stops the PWM, not the shift and latched registers. */
69     memset(s->outputs, 0, sizeof(s->outputs));
70 
71     s->activated_rows = 0;
72     s->redraw = 0;
73     trace_dm163_redraw(s->redraw);
74     for (unsigned i = 0; i < COLOR_BUFFER_SIZE; i++) {
75         memset(s->buffer[i], 0, sizeof(s->buffer[0]));
76     }
77     s->last_buffer_idx = 0;
78     memset(s->buffer_idx_of_row, TURNED_OFF_ROW, sizeof(s->buffer_idx_of_row));
79     memset(s->row_persistence_delay, 0, sizeof(s->row_persistence_delay));
80 }
81 
82 static void dm163_dck_gpio_handler(void *opaque, int line, int new_state)
83 {
84     DM163State *s = opaque;
85 
86     if (new_state && !s->dck) {
87         /*
88          * On raising dck, sample selbk to get the bank to use, and
89          * sample sin for the bit to enter into the bank shift buffer.
90          */
91         uint64_t *sb =
92             s->selbk ? s->bank1_shift_register : s->bank0_shift_register;
93         /* Output the outgoing bit on sout */
94         const bool sout = (s->selbk ? sb[2] & MAKE_64BIT_MASK(63, 1) :
95                            sb[2] & MAKE_64BIT_MASK(15, 1)) != 0;
96         qemu_set_irq(s->sout, sout);
97         /* Enter sin into the shift buffer */
98         sb[2] = (sb[2] << 1) | ((sb[1] >> 63) & 1);
99         sb[1] = (sb[1] << 1) | ((sb[0] >> 63) & 1);
100         sb[0] = (sb[0] << 1) | s->sin;
101     }
102 
103     s->dck = new_state;
104     trace_dm163_dck(new_state);
105 }
106 
107 static void dm163_propagate_outputs(DM163State *s)
108 {
109     s->last_buffer_idx = (s->last_buffer_idx + 1) % RGB_MATRIX_NUM_ROWS;
110     /* Values are output when reset is high and enable is low. */
111     if (s->rst_b && !s->en_b) {
112         memcpy(s->outputs, s->latched_outputs, sizeof(s->outputs));
113     } else {
114         memset(s->outputs, 0, sizeof(s->outputs));
115     }
116     for (unsigned x = 0; x < RGB_MATRIX_NUM_COLS; x++) {
117         /* Grouping the 3 RGB channels in a pixel value */
118         const uint16_t b = extract16(s->outputs[3 * x + 0], 6, 8);
119         const uint16_t g = extract16(s->outputs[3 * x + 1], 6, 8);
120         const uint16_t r = extract16(s->outputs[3 * x + 2], 6, 8);
121         uint32_t rgba = 0;
122 
123         trace_dm163_channels(3 * x + 2, r);
124         trace_dm163_channels(3 * x + 1, g);
125         trace_dm163_channels(3 * x + 0, b);
126 
127         rgba = deposit32(rgba,  0, 8, r);
128         rgba = deposit32(rgba,  8, 8, g);
129         rgba = deposit32(rgba, 16, 8, b);
130 
131         /* Led values are sent from the last one to the first one */
132         s->buffer[s->last_buffer_idx][RGB_MATRIX_NUM_COLS - x - 1] = rgba;
133     }
134     for (unsigned row = 0; row < RGB_MATRIX_NUM_ROWS; row++) {
135         if (s->activated_rows & (1 << row)) {
136             s->buffer_idx_of_row[row] = s->last_buffer_idx;
137             s->redraw |= (1 << row);
138             trace_dm163_redraw(s->redraw);
139         }
140     }
141 }
142 
143 static void dm163_en_b_gpio_handler(void *opaque, int line, int new_state)
144 {
145     DM163State *s = opaque;
146 
147     s->en_b = new_state;
148     dm163_propagate_outputs(s);
149     trace_dm163_en_b(new_state);
150 }
151 
152 static uint8_t dm163_bank0(const DM163State *s, uint8_t led)
153 {
154     /*
155      * Bank 0 uses 6 bits per led, so a value may be stored accross
156      * two uint64_t entries.
157      */
158     const uint8_t low_bit = 6 * led;
159     const uint8_t low_word = low_bit / 64;
160     const uint8_t high_word = (low_bit + 5) / 64;
161     const uint8_t low_shift = low_bit % 64;
162 
163     if (low_word == high_word) {
164         /* Simple case: the value belongs to one entry. */
165         return extract64(s->bank0_shift_register[low_word], low_shift, 6);
166     }
167 
168     const uint8_t nb_bits_in_low_word = 64 - low_shift;
169     const uint8_t nb_bits_in_high_word = 6 - nb_bits_in_low_word;
170 
171     const uint64_t bits_in_low_word = \
172         extract64(s->bank0_shift_register[low_word], low_shift,
173                   nb_bits_in_low_word);
174     const uint64_t bits_in_high_word = \
175         extract64(s->bank0_shift_register[high_word], 0,
176                   nb_bits_in_high_word);
177     uint8_t val = 0;
178 
179     val = deposit32(val, 0, nb_bits_in_low_word, bits_in_low_word);
180     val = deposit32(val, nb_bits_in_low_word, nb_bits_in_high_word,
181                     bits_in_high_word);
182 
183     return val;
184 }
185 
186 static uint8_t dm163_bank1(const DM163State *s, uint8_t led)
187 {
188     const uint64_t entry = s->bank1_shift_register[led / RGB_MATRIX_NUM_COLS];
189     return extract64(entry, 8 * (led % RGB_MATRIX_NUM_COLS), 8);
190 }
191 
192 static void dm163_lat_b_gpio_handler(void *opaque, int line, int new_state)
193 {
194     DM163State *s = opaque;
195 
196     if (s->lat_b && !new_state) {
197         for (int led = 0; led < DM163_NUM_LEDS; led++) {
198             s->latched_outputs[led] = dm163_bank0(s, led) * dm163_bank1(s, led);
199         }
200         dm163_propagate_outputs(s);
201     }
202 
203     s->lat_b = new_state;
204     trace_dm163_lat_b(new_state);
205 }
206 
207 static void dm163_rst_b_gpio_handler(void *opaque, int line, int new_state)
208 {
209     DM163State *s = opaque;
210 
211     s->rst_b = new_state;
212     dm163_propagate_outputs(s);
213     trace_dm163_rst_b(new_state);
214 }
215 
216 static void dm163_selbk_gpio_handler(void *opaque, int line, int new_state)
217 {
218     DM163State *s = opaque;
219 
220     s->selbk = new_state;
221     trace_dm163_selbk(new_state);
222 }
223 
224 static void dm163_sin_gpio_handler(void *opaque, int line, int new_state)
225 {
226     DM163State *s = opaque;
227 
228     s->sin = new_state;
229     trace_dm163_sin(new_state);
230 }
231 
232 static void dm163_rows_gpio_handler(void *opaque, int line, int new_state)
233 {
234     DM163State *s = opaque;
235 
236     if (new_state) {
237         s->activated_rows |= (1 << line);
238         s->buffer_idx_of_row[line] = s->last_buffer_idx;
239         s->redraw |= (1 << line);
240         trace_dm163_redraw(s->redraw);
241     } else {
242         s->activated_rows &= ~(1 << line);
243         s->row_persistence_delay[line] = ROW_PERSISTENCE;
244     }
245     trace_dm163_activated_rows(s->activated_rows);
246 }
247 
248 static void dm163_invalidate_display(void *opaque)
249 {
250     DM163State *s = (DM163State *)opaque;
251     s->redraw = 0xFF;
252     trace_dm163_redraw(s->redraw);
253 }
254 
255 static void update_row_persistence_delay(DM163State *s, unsigned row)
256 {
257     if (s->row_persistence_delay[row]) {
258         s->row_persistence_delay[row]--;
259     } else {
260         /*
261          * If the ROW_PERSISTENCE delay is up,
262          * the row is turned off.
263          */
264         s->buffer_idx_of_row[row] = TURNED_OFF_ROW;
265         s->redraw |= (1 << row);
266         trace_dm163_redraw(s->redraw);
267     }
268 }
269 
270 static uint32_t *update_display_of_row(DM163State *s, uint32_t *dest,
271                                        unsigned row)
272 {
273     for (unsigned _ = 0; _ < LED_SQUARE_SIZE; _++) {
274         for (int x = 0; x < RGB_MATRIX_NUM_COLS * LED_SQUARE_SIZE; x++) {
275             /* UI layer guarantees that there's 32 bits per pixel (Mar 2024) */
276             *dest++ = s->buffer[s->buffer_idx_of_row[row]][x / LED_SQUARE_SIZE];
277         }
278     }
279 
280     dpy_gfx_update(s->console, 0, LED_SQUARE_SIZE * row,
281                     RGB_MATRIX_NUM_COLS * LED_SQUARE_SIZE, LED_SQUARE_SIZE);
282     s->redraw &= ~(1 << row);
283     trace_dm163_redraw(s->redraw);
284 
285     return dest;
286 }
287 
288 static void dm163_update_display(void *opaque)
289 {
290     DM163State *s = (DM163State *)opaque;
291     DisplaySurface *surface = qemu_console_surface(s->console);
292     uint32_t *dest;
293 
294     dest = surface_data(surface);
295     for (unsigned row = 0; row < RGB_MATRIX_NUM_ROWS; row++) {
296         update_row_persistence_delay(s, row);
297         if (!extract8(s->redraw, row, 1)) {
298             dest += LED_SQUARE_SIZE * LED_SQUARE_SIZE * RGB_MATRIX_NUM_COLS;
299             continue;
300         }
301         dest = update_display_of_row(s, dest, row);
302     }
303 }
304 
305 static const GraphicHwOps dm163_ops = {
306     .invalidate  = dm163_invalidate_display,
307     .gfx_update  = dm163_update_display,
308 };
309 
310 static void dm163_realize(DeviceState *dev, Error **errp)
311 {
312     DM163State *s = DM163(dev);
313 
314     qdev_init_gpio_in(dev, dm163_rows_gpio_handler, RGB_MATRIX_NUM_ROWS);
315     qdev_init_gpio_in(dev, dm163_sin_gpio_handler, 1);
316     qdev_init_gpio_in(dev, dm163_dck_gpio_handler, 1);
317     qdev_init_gpio_in(dev, dm163_rst_b_gpio_handler, 1);
318     qdev_init_gpio_in(dev, dm163_lat_b_gpio_handler, 1);
319     qdev_init_gpio_in(dev, dm163_selbk_gpio_handler, 1);
320     qdev_init_gpio_in(dev, dm163_en_b_gpio_handler, 1);
321     qdev_init_gpio_out_named(dev, &s->sout, "sout", 1);
322 
323     s->console = graphic_console_init(dev, 0, &dm163_ops, s);
324     qemu_console_resize(s->console, RGB_MATRIX_NUM_COLS * LED_SQUARE_SIZE,
325                         RGB_MATRIX_NUM_ROWS * LED_SQUARE_SIZE);
326 }
327 
328 static void dm163_class_init(ObjectClass *klass, void *data)
329 {
330     DeviceClass *dc = DEVICE_CLASS(klass);
331     ResettableClass *rc = RESETTABLE_CLASS(klass);
332 
333     dc->desc = "DM163";
334     dc->vmsd = &vmstate_dm163;
335     dc->realize = dm163_realize;
336     rc->phases.hold = dm163_reset_hold;
337     set_bit(DEVICE_CATEGORY_DISPLAY, dc->categories);
338 }
339 
340 static const TypeInfo dm163_types[] = {
341     {
342         .name = TYPE_DM163,
343         .parent = TYPE_DEVICE,
344         .instance_size = sizeof(DM163State),
345         .class_init = dm163_class_init
346     }
347 };
348 
349 DEFINE_TYPES(dm163_types)
350