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
dm163_reset_hold(Object * obj,ResetType type)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
dm163_dck_gpio_handler(void * opaque,int line,int new_state)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
dm163_propagate_outputs(DM163State * s)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
dm163_en_b_gpio_handler(void * opaque,int line,int new_state)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
dm163_bank0(const DM163State * s,uint8_t led)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
dm163_bank1(const DM163State * s,uint8_t led)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
dm163_lat_b_gpio_handler(void * opaque,int line,int new_state)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
dm163_rst_b_gpio_handler(void * opaque,int line,int new_state)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
dm163_selbk_gpio_handler(void * opaque,int line,int new_state)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
dm163_sin_gpio_handler(void * opaque,int line,int new_state)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
dm163_rows_gpio_handler(void * opaque,int line,int new_state)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
dm163_invalidate_display(void * opaque)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
update_row_persistence_delay(DM163State * s,unsigned row)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
update_display_of_row(DM163State * s,uint32_t * dest,unsigned row)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
dm163_update_display(void * opaque)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
dm163_realize(DeviceState * dev,Error ** errp)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
dm163_class_init(ObjectClass * klass,void * data)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