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