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