summaryrefslogtreecommitdiff
path: root/hw/display/tc6393xb.c
blob: 92f7120acc5bc39958ced84489eb24466379ca59 (plain)
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
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
/*
 * Toshiba TC6393XB I/O Controller.
 * Found in Sharp Zaurus SL-6000 (tosa) or some
 * Toshiba e-Series PDAs.
 *
 * Most features are currently unsupported!!!
 *
 * This code is licensed under the GNU GPL v2.
 *
 * Contributions after 2012-01-13 are licensed under the terms of the
 * GNU GPL, version 2 or (at your option) any later version.
 */
#include "qemu/osdep.h"
#include "qapi/error.h"
#include "qemu/host-utils.h"
#include "hw/hw.h"
#include "hw/devices.h"
#include "hw/block/flash.h"
#include "ui/console.h"
#include "ui/pixel_ops.h"
#include "sysemu/block-backend.h"
#include "sysemu/blockdev.h"

#define IRQ_TC6393_NAND		0
#define IRQ_TC6393_MMC		1
#define IRQ_TC6393_OHCI		2
#define IRQ_TC6393_SERIAL	3
#define IRQ_TC6393_FB		4

#define	TC6393XB_NR_IRQS	8

#define TC6393XB_GPIOS  16

#define SCR_REVID	0x08		/* b Revision ID	*/
#define SCR_ISR		0x50		/* b Interrupt Status	*/
#define SCR_IMR		0x52		/* b Interrupt Mask	*/
#define SCR_IRR		0x54		/* b Interrupt Routing	*/
#define SCR_GPER	0x60		/* w GP Enable		*/
#define SCR_GPI_SR(i)	(0x64 + (i))	/* b3 GPI Status	*/
#define SCR_GPI_IMR(i)	(0x68 + (i))	/* b3 GPI INT Mask	*/
#define SCR_GPI_EDER(i)	(0x6c + (i))	/* b3 GPI Edge Detect Enable */
#define SCR_GPI_LIR(i)	(0x70 + (i))	/* b3 GPI Level Invert	*/
#define SCR_GPO_DSR(i)	(0x78 + (i))	/* b3 GPO Data Set	*/
#define SCR_GPO_DOECR(i) (0x7c + (i))	/* b3 GPO Data OE Control */
#define SCR_GP_IARCR(i)	(0x80 + (i))	/* b3 GP Internal Active Register Control */
#define SCR_GP_IARLCR(i) (0x84 + (i))	/* b3 GP INTERNAL Active Register Level Control */
#define SCR_GPI_BCR(i)	(0x88 + (i))	/* b3 GPI Buffer Control */
#define SCR_GPA_IARCR	0x8c		/* w GPa Internal Active Register Control */
#define SCR_GPA_IARLCR	0x90		/* w GPa Internal Active Register Level Control */
#define SCR_GPA_BCR	0x94		/* w GPa Buffer Control */
#define SCR_CCR		0x98		/* w Clock Control	*/
#define SCR_PLL2CR	0x9a		/* w PLL2 Control	*/
#define SCR_PLL1CR	0x9c		/* l PLL1 Control	*/
#define SCR_DIARCR	0xa0		/* b Device Internal Active Register Control */
#define SCR_DBOCR	0xa1		/* b Device Buffer Off Control */
#define SCR_FER		0xe0		/* b Function Enable	*/
#define SCR_MCR		0xe4		/* w Mode Control	*/
#define SCR_CONFIG	0xfc		/* b Configuration Control */
#define SCR_DEBUG	0xff		/* b Debug		*/

#define NAND_CFG_COMMAND    0x04    /* w Command        */
#define NAND_CFG_BASE       0x10    /* l Control Base Address */
#define NAND_CFG_INTP       0x3d    /* b Interrupt Pin  */
#define NAND_CFG_INTE       0x48    /* b Int Enable     */
#define NAND_CFG_EC         0x4a    /* b Event Control  */
#define NAND_CFG_ICC        0x4c    /* b Internal Clock Control */
#define NAND_CFG_ECCC       0x5b    /* b ECC Control    */
#define NAND_CFG_NFTC       0x60    /* b NAND Flash Transaction Control */
#define NAND_CFG_NFM        0x61    /* b NAND Flash Monitor */
#define NAND_CFG_NFPSC      0x62    /* b NAND Flash Power Supply Control */
#define NAND_CFG_NFDC       0x63    /* b NAND Flash Detect Control */

#define NAND_DATA   0x00        /* l Data       */
#define NAND_MODE   0x04        /* b Mode       */
#define NAND_STATUS 0x05        /* b Status     */
#define NAND_ISR    0x06        /* b Interrupt Status */
#define NAND_IMR    0x07        /* b Interrupt Mask */

#define NAND_MODE_WP        0x80
#define NAND_MODE_CE        0x10
#define NAND_MODE_ALE       0x02
#define NAND_MODE_CLE       0x01
#define NAND_MODE_ECC_MASK  0x60
#define NAND_MODE_ECC_EN    0x20
#define NAND_MODE_ECC_READ  0x40
#define NAND_MODE_ECC_RST   0x60

struct TC6393xbState {
    MemoryRegion iomem;
    qemu_irq irq;
    qemu_irq *sub_irqs;
    struct {
        uint8_t ISR;
        uint8_t IMR;
        uint8_t IRR;
        uint16_t GPER;
        uint8_t GPI_SR[3];
        uint8_t GPI_IMR[3];
        uint8_t GPI_EDER[3];
        uint8_t GPI_LIR[3];
        uint8_t GP_IARCR[3];
        uint8_t GP_IARLCR[3];
        uint8_t GPI_BCR[3];
        uint16_t GPA_IARCR;
        uint16_t GPA_IARLCR;
        uint16_t CCR;
        uint16_t PLL2CR;
        uint32_t PLL1CR;
        uint8_t DIARCR;
        uint8_t DBOCR;
        uint8_t FER;
        uint16_t MCR;
        uint8_t CONFIG;
        uint8_t DEBUG;
    } scr;
    uint32_t gpio_dir;
    uint32_t gpio_level;
    uint32_t prev_level;
    qemu_irq handler[TC6393XB_GPIOS];
    qemu_irq *gpio_in;

    struct {
        uint8_t mode;
        uint8_t isr;
        uint8_t imr;
    } nand;
    int nand_enable;
    uint32_t nand_phys;
    DeviceState *flash;
    ECCState ecc;

    QemuConsole *con;
    MemoryRegion vram;
    uint16_t *vram_ptr;
    uint32_t scr_width, scr_height; /* in pixels */
    qemu_irq l3v;
    unsigned blank : 1,
             blanked : 1;
};

qemu_irq *tc6393xb_gpio_in_get(TC6393xbState *s)
{
    return s->gpio_in;
}

static void tc6393xb_gpio_set(void *opaque, int line, int level)
{
//    TC6393xbState *s = opaque;

    if (line > TC6393XB_GPIOS) {
        printf("%s: No GPIO pin %i\n", __FUNCTION__, line);
        return;
    }

    // FIXME: how does the chip reflect the GPIO input level change?
}

void tc6393xb_gpio_out_set(TC6393xbState *s, int line,
                    qemu_irq handler)
{
    if (line >= TC6393XB_GPIOS) {
        fprintf(stderr, "TC6393xb: no GPIO pin %d\n", line);
        return;
    }

    s->handler[line] = handler;
}

static void tc6393xb_gpio_handler_update(TC6393xbState *s)
{
    uint32_t level, diff;
    int bit;

    level = s->gpio_level & s->gpio_dir;

    for (diff = s->prev_level ^ level; diff; diff ^= 1 << bit) {
        bit = ctz32(diff);
        qemu_set_irq(s->handler[bit], (level >> bit) & 1);
    }

    s->prev_level = level;
}

qemu_irq tc6393xb_l3v_get(TC6393xbState *s)
{
    return s->l3v;
}

static void tc6393xb_l3v(void *opaque, int line, int level)
{
    TC6393xbState *s = opaque;
    s->blank = !level;
    fprintf(stderr, "L3V: %d\n", level);
}

static void tc6393xb_sub_irq(void *opaque, int line, int level) {
    TC6393xbState *s = opaque;
    uint8_t isr = s->scr.ISR;
    if (level)
        isr |= 1 << line;
    else
        isr &= ~(1 << line);
    s->scr.ISR = isr;
    qemu_set_irq(s->irq, isr & s->scr.IMR);
}

#define SCR_REG_B(N)                            \
    case SCR_ ##N: return s->scr.N
#define SCR_REG_W(N)                            \
    case SCR_ ##N: return s->scr.N;             \
    case SCR_ ##N + 1: return s->scr.N >> 8;
#define SCR_REG_L(N)                            \
    case SCR_ ##N: return s->scr.N;             \
    case SCR_ ##N + 1: return s->scr.N >> 8;    \
    case SCR_ ##N + 2: return s->scr.N >> 16;   \
    case SCR_ ##N + 3: return s->scr.N >> 24;
#define SCR_REG_A(N)                            \
    case SCR_ ##N(0): return s->scr.N[0];       \
    case SCR_ ##N(1): return s->scr.N[1];       \
    case SCR_ ##N(2): return s->scr.N[2]

static uint32_t tc6393xb_scr_readb(TC6393xbState *s, hwaddr addr)
{
    switch (addr) {
        case SCR_REVID:
            return 3;
        case SCR_REVID+1:
            return 0;
        SCR_REG_B(ISR);
        SCR_REG_B(IMR);
        SCR_REG_B(IRR);
        SCR_REG_W(GPER);
        SCR_REG_A(GPI_SR);
        SCR_REG_A(GPI_IMR);
        SCR_REG_A(GPI_EDER);
        SCR_REG_A(GPI_LIR);
        case SCR_GPO_DSR(0):
        case SCR_GPO_DSR(1):
        case SCR_GPO_DSR(2):
            return (s->gpio_level >> ((addr - SCR_GPO_DSR(0)) * 8)) & 0xff;
        case SCR_GPO_DOECR(0):
        case SCR_GPO_DOECR(1):
        case SCR_GPO_DOECR(2):
            return (s->gpio_dir >> ((addr - SCR_GPO_DOECR(0)) * 8)) & 0xff;
        SCR_REG_A(GP_IARCR);
        SCR_REG_A(GP_IARLCR);
        SCR_REG_A(GPI_BCR);
        SCR_REG_W(GPA_IARCR);
        SCR_REG_W(GPA_IARLCR);
        SCR_REG_W(CCR);
        SCR_REG_W(PLL2CR);
        SCR_REG_L(PLL1CR);
        SCR_REG_B(DIARCR);
        SCR_REG_B(DBOCR);
        SCR_REG_B(FER);
        SCR_REG_W(MCR);
        SCR_REG_B(CONFIG);
        SCR_REG_B(DEBUG);
    }
    fprintf(stderr, "tc6393xb_scr: unhandled read at %08x\n", (uint32_t) addr);
    return 0;
}
#undef SCR_REG_B
#undef SCR_REG_W
#undef SCR_REG_L
#undef SCR_REG_A

#define SCR_REG_B(N)                                \
    case SCR_ ##N: s->scr.N = value; return;
#define SCR_REG_W(N)                                \
    case SCR_ ##N: s->scr.N = (s->scr.N & ~0xff) | (value & 0xff); return; \
    case SCR_ ##N + 1: s->scr.N = (s->scr.N & 0xff) | (value << 8); return
#define SCR_REG_L(N)                                \
    case SCR_ ##N: s->scr.N = (s->scr.N & ~0xff) | (value & 0xff); return;   \
    case SCR_ ##N + 1: s->scr.N = (s->scr.N & ~(0xff << 8)) | (value & (0xff << 8)); return;     \
    case SCR_ ##N + 2: s->scr.N = (s->scr.N & ~(0xff << 16)) | (value & (0xff << 16)); return;   \
    case SCR_ ##N + 3: s->scr.N = (s->scr.N & ~(0xff << 24)) | (value & (0xff << 24)); return;
#define SCR_REG_A(N)                                \
    case SCR_ ##N(0): s->scr.N[0] = value; return;   \
    case SCR_ ##N(1): s->scr.N[1] = value; return;   \
    case SCR_ ##N(2): s->scr.N[2] = value; return

static void tc6393xb_scr_writeb(TC6393xbState *s, hwaddr addr, uint32_t value)
{
    switch (addr) {
        SCR_REG_B(ISR);
        SCR_REG_B(IMR);
        SCR_REG_B(IRR);
        SCR_REG_W(GPER);
        SCR_REG_A(GPI_SR);
        SCR_REG_A(GPI_IMR);
        SCR_REG_A(GPI_EDER);
        SCR_REG_A(GPI_LIR);
        case SCR_GPO_DSR(0):
        case SCR_GPO_DSR(1):
        case SCR_GPO_DSR(2):
            s->gpio_level = (s->gpio_level & ~(0xff << ((addr - SCR_GPO_DSR(0))*8))) | ((value & 0xff) << ((addr - SCR_GPO_DSR(0))*8));
            tc6393xb_gpio_handler_update(s);
            return;
        case SCR_GPO_DOECR(0):
        case SCR_GPO_DOECR(1):
        case SCR_GPO_DOECR(2):
            s->gpio_dir = (s->gpio_dir & ~(0xff << ((addr - SCR_GPO_DOECR(0))*8))) | ((value & 0xff) << ((addr - SCR_GPO_DOECR(0))*8));
            tc6393xb_gpio_handler_update(s);
            return;
        SCR_REG_A(GP_IARCR);
        SCR_REG_A(GP_IARLCR);
        SCR_REG_A(GPI_BCR);
        SCR_REG_W(GPA_IARCR);
        SCR_REG_W(GPA_IARLCR);
        SCR_REG_W(CCR);
        SCR_REG_W(PLL2CR);
        SCR_REG_L(PLL1CR);
        SCR_REG_B(DIARCR);
        SCR_REG_B(DBOCR);
        SCR_REG_B(FER);
        SCR_REG_W(MCR);
        SCR_REG_B(CONFIG);
        SCR_REG_B(DEBUG);
    }
    fprintf(stderr, "tc6393xb_scr: unhandled write at %08x: %02x\n",
					(uint32_t) addr, value & 0xff);
}
#undef SCR_REG_B
#undef SCR_REG_W
#undef SCR_REG_L
#undef SCR_REG_A

static void tc6393xb_nand_irq(TC6393xbState *s) {
    qemu_set_irq(s->sub_irqs[IRQ_TC6393_NAND],
            (s->nand.imr & 0x80) && (s->nand.imr & s->nand.isr));
}

static uint32_t tc6393xb_nand_cfg_readb(TC6393xbState *s, hwaddr addr) {
    switch (addr) {
        case NAND_CFG_COMMAND:
            return s->nand_enable ? 2 : 0;
        case NAND_CFG_BASE:
        case NAND_CFG_BASE + 1:
        case NAND_CFG_BASE + 2:
        case NAND_CFG_BASE + 3:
            return s->nand_phys >> (addr - NAND_CFG_BASE);
    }
    fprintf(stderr, "tc6393xb_nand_cfg: unhandled read at %08x\n", (uint32_t) addr);
    return 0;
}
static void tc6393xb_nand_cfg_writeb(TC6393xbState *s, hwaddr addr, uint32_t value) {
    switch (addr) {
        case NAND_CFG_COMMAND:
            s->nand_enable = (value & 0x2);
            return;
        case NAND_CFG_BASE:
        case NAND_CFG_BASE + 1:
        case NAND_CFG_BASE + 2:
        case NAND_CFG_BASE + 3:
            s->nand_phys &= ~(0xff << ((addr - NAND_CFG_BASE) * 8));
            s->nand_phys |= (value & 0xff) << ((addr - NAND_CFG_BASE) * 8);
            return;
    }
    fprintf(stderr, "tc6393xb_nand_cfg: unhandled write at %08x: %02x\n",
					(uint32_t) addr, value & 0xff);
}

static uint32_t tc6393xb_nand_readb(TC6393xbState *s, hwaddr addr) {
    switch (addr) {
        case NAND_DATA + 0:
        case NAND_DATA + 1:
        case NAND_DATA + 2:
        case NAND_DATA + 3:
            return nand_getio(s->flash);
        case NAND_MODE:
            return s->nand.mode;
        case NAND_STATUS:
            return 0x14;
        case NAND_ISR:
            return s->nand.isr;
        case NAND_IMR:
            return s->nand.imr;
    }
    fprintf(stderr, "tc6393xb_nand: unhandled read at %08x\n", (uint32_t) addr);
    return 0;
}
static void tc6393xb_nand_writeb(TC6393xbState *s, hwaddr addr, uint32_t value) {
//    fprintf(stderr, "tc6393xb_nand: write at %08x: %02x\n",
//					(uint32_t) addr, value & 0xff);
    switch (addr) {
        case NAND_DATA + 0:
        case NAND_DATA + 1:
        case NAND_DATA + 2:
        case NAND_DATA + 3:
            nand_setio(s->flash, value);
            s->nand.isr |= 1;
            tc6393xb_nand_irq(s);
            return;
        case NAND_MODE:
            s->nand.mode = value;
            nand_setpins(s->flash,
                    value & NAND_MODE_CLE,
                    value & NAND_MODE_ALE,
                    !(value & NAND_MODE_CE),
                    value & NAND_MODE_WP,
                    0); // FIXME: gnd
            switch (value & NAND_MODE_ECC_MASK) {
                case NAND_MODE_ECC_RST:
                    ecc_reset(&s->ecc);
                    break;
                case NAND_MODE_ECC_READ:
                    // FIXME
                    break;
                case NAND_MODE_ECC_EN:
                    ecc_reset(&s->ecc);
            }
            return;
        case NAND_ISR:
            s->nand.isr = value;
            tc6393xb_nand_irq(s);
            return;
        case NAND_IMR:
            s->nand.imr = value;
            tc6393xb_nand_irq(s);
            return;
    }
    fprintf(stderr, "tc6393xb_nand: unhandled write at %08x: %02x\n",
					(uint32_t) addr, value & 0xff);
}

#define BITS 8
#include "tc6393xb_template.h"
#define BITS 15
#include "tc6393xb_template.h"
#define BITS 16
#include "tc6393xb_template.h"
#define BITS 24
#include "tc6393xb_template.h"
#define BITS 32
#include "tc6393xb_template.h"

static void tc6393xb_draw_graphic(TC6393xbState *s, int full_update)
{
    DisplaySurface *surface = qemu_console_surface(s->con);

    switch (surface_bits_per_pixel(surface)) {
        case 8:
            tc6393xb_draw_graphic8(s);
            break;
        case 15:
            tc6393xb_draw_graphic15(s);
            break;
        case 16:
            tc6393xb_draw_graphic16(s);
            break;
        case 24:
            tc6393xb_draw_graphic24(s);
            break;
        case 32:
            tc6393xb_draw_graphic32(s);
            break;
        default:
            printf("tc6393xb: unknown depth %d\n",
                   surface_bits_per_pixel(surface));
            return;
    }

    dpy_gfx_update(s->con, 0, 0, s->scr_width, s->scr_height);
}

static void tc6393xb_draw_blank(TC6393xbState *s, int full_update)
{
    DisplaySurface *surface = qemu_console_surface(s->con);
    int i, w;
    uint8_t *d;

    if (!full_update)
        return;

    w = s->scr_width * surface_bytes_per_pixel(surface);
    d = surface_data(surface);
    for(i = 0; i < s->scr_height; i++) {
        memset(d, 0, w);
        d += surface_stride(surface);
    }

    dpy_gfx_update(s->con, 0, 0, s->scr_width, s->scr_height);
}

static void tc6393xb_update_display(void *opaque)
{
    TC6393xbState *s = opaque;
    DisplaySurface *surface = qemu_console_surface(s->con);
    int full_update;

    if (s->scr_width == 0 || s->scr_height == 0)
        return;

    full_update = 0;
    if (s->blanked != s->blank) {
        s->blanked = s->blank;
        full_update = 1;
    }
    if (s->scr_width != surface_width(surface) ||
        s->scr_height != surface_height(surface)) {
        qemu_console_resize(s->con, s->scr_width, s->scr_height);
        full_update = 1;
    }
    if (s->blanked)
        tc6393xb_draw_blank(s, full_update);
    else
        tc6393xb_draw_graphic(s, full_update);
}


static uint64_t tc6393xb_readb(void *opaque, hwaddr addr,
                               unsigned size)
{
    TC6393xbState *s = opaque;

    switch (addr >> 8) {
        case 0:
            return tc6393xb_scr_readb(s, addr & 0xff);
        case 1:
            return tc6393xb_nand_cfg_readb(s, addr & 0xff);
    };

    if ((addr &~0xff) == s->nand_phys && s->nand_enable) {
//        return tc6393xb_nand_readb(s, addr & 0xff);
        uint8_t d = tc6393xb_nand_readb(s, addr & 0xff);
//        fprintf(stderr, "tc6393xb_nand: read at %08x: %02hhx\n", (uint32_t) addr, d);
        return d;
    }

//    fprintf(stderr, "tc6393xb: unhandled read at %08x\n", (uint32_t) addr);
    return 0;
}

static void tc6393xb_writeb(void *opaque, hwaddr addr,
                            uint64_t value, unsigned size) {
    TC6393xbState *s = opaque;

    switch (addr >> 8) {
        case 0:
            tc6393xb_scr_writeb(s, addr & 0xff, value);
            return;
        case 1:
            tc6393xb_nand_cfg_writeb(s, addr & 0xff, value);
            return;
    };

    if ((addr &~0xff) == s->nand_phys && s->nand_enable)
        tc6393xb_nand_writeb(s, addr & 0xff, value);
    else
        fprintf(stderr, "tc6393xb: unhandled write at %08x: %02x\n",
                (uint32_t) addr, (int)value & 0xff);
}

static const GraphicHwOps tc6393xb_gfx_ops = {
    .gfx_update  = tc6393xb_update_display,
};

TC6393xbState *tc6393xb_init(MemoryRegion *sysmem, uint32_t base, qemu_irq irq)
{
    TC6393xbState *s;
    DriveInfo *nand;
    static const MemoryRegionOps tc6393xb_ops = {
        .read = tc6393xb_readb,
        .write = tc6393xb_writeb,
        .endianness = DEVICE_NATIVE_ENDIAN,
        .impl = {
            .min_access_size = 1,
            .max_access_size = 1,
        },
    };

    s = (TC6393xbState *) g_malloc0(sizeof(TC6393xbState));
    s->irq = irq;
    s->gpio_in = qemu_allocate_irqs(tc6393xb_gpio_set, s, TC6393XB_GPIOS);

    s->l3v = qemu_allocate_irq(tc6393xb_l3v, s, 0);
    s->blanked = 1;

    s->sub_irqs = qemu_allocate_irqs(tc6393xb_sub_irq, s, TC6393XB_NR_IRQS);

    nand = drive_get(IF_MTD, 0, 0);
    s->flash = nand_init(nand ? blk_by_legacy_dinfo(nand) : NULL,
                         NAND_MFR_TOSHIBA, 0x76);

    memory_region_init_io(&s->iomem, NULL, &tc6393xb_ops, s, "tc6393xb", 0x10000);
    memory_region_add_subregion(sysmem, base, &s->iomem);

    memory_region_init_ram(&s->vram, NULL, "tc6393xb.vram", 0x100000,
                           &error_fatal);
    vmstate_register_ram_global(&s->vram);
    s->vram_ptr = memory_region_get_ram_ptr(&s->vram);
    memory_region_add_subregion(sysmem, base + 0x100000, &s->vram);
    s->scr_width = 480;
    s->scr_height = 640;
    s->con = graphic_console_init(NULL, 0, &tc6393xb_gfx_ops, s);

    return s;
}