summaryrefslogtreecommitdiffstatshomepage
path: root/src/devices/video/hd44780.cpp
blob: ade4187a027616030a246376b1f5734611cd75d0 (plain) (blame)
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
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
// license:BSD-3-Clause
// copyright-holders:Sandro Ronco
/***************************************************************************

    Hitachi HD44780 LCD controller

    TODO:
    - dump internal CGROM via decap
    - finish emulating osc pin: determine video timings from it,
       and if possible, remove m_busy_factor
      The internal oscillator is known to be based on the value of the resistor
       Rf, presumably (based on the HD44780U datasheet diagrams) with an
       equation close to this:
       For 5V power, Frequency in Hz = 1 / (2 * PI * Rf * 6.5pF)
       For 3V power, Frequency in Hz = 1 / (2 * PI * Rf * 7.86pF)
      The vast majority of devices use the internal oscillator with an Rf
       resistor of 91kOhms, for ~270kHz clock speed.
      However, a few Hitachi-made LCD modules use an Rf resistor of
       200kOhms instead, for a ~122.4kHz clock speed, and it isn't clear
       why they did this, as it is lower than the minimum intended clock
       speed on the datasheet.
    DONE
    - busy flag durations in clock cycles
    - blink timing in clock cycles

    BUSY TIMINGS:
    Manufacturer:            Hitachi           Epson         Samsung
    Device:                  HD44780         SED1278  KS0066+S6A0069
    Clear display:        410 cycles      410 cycles      410 cycles
    Return Home:          410 cycles      410 cycles      410 cycles
    Entry Mode:            10 cycles       10 cycles       10 cycles
    Display on/off:        10 cycles       10 cycles       10 cycles
    Cursor/Display Shift:  10 cycles       10 cycles       10 cycles
    Function Set:          10 cycles       10 cycles       10 cycles
    Set CGRAM:             10 cycles       10 cycles       10 cycles
    Set DDRAM:             10 cycles       10 cycles       10 cycles
    Data Write:            10 cycles[1]    10 cycles       11 cycles
    Data Read:             10 cycles[1]    10 cycles       11 cycles[2,3]
    Blink Invert Rate: 102400 cycles   102400 cycles        ? cycles[4]
    [1] HD44780 bug: BUSY is only active for 10 cycles, but the ac inc/dec on
         data read or write happens on the 11th cycle.
        This bug is documented in the later HD44780U datasheet, as well as in
         the SED1278 datasheet to note the bug is fixed there.
        We do not emulate this bug, but emulate as SED1278 where it is fixed.
    [2] KS0066 bug: a Data read from CGRAM after a CGRAM write +
         increment/decrement has completed will read the previous byte, not
         the new one the address counter now points to!
        This bug is documented in the datasheet.
    [3] S6A0069 note: the first data read from CGRAM must have a set address
         command or a display shift command sent before the read command, or
         it will instead return the potentially garbage data that was most
         recently put into the read buffer register as the serially-accessed
         ram hasn't had a chance to fully complete a serial cycle and update
         said buffer. Also, if the AC direction has not been explicitly set
         beforehand by a display shift command, the byte address
         auto-increment vs auto-decrement might be doing either one randomly
         after power-up.
        In short, this documentation change plus some minor behavior change
         "fixes" the bug of the KS0066, with the above caveat.
    [4] In all likelyhood, this is almost certainly 102400 for compatibility
         with the original HD44780A00

    Known HD44780-compatible clones:
    Epson SED1278
    Samsung KS0066[U]
    Samsung S6A0069 (equivalent to KS0066U?)
    Sitronix ST7066U
    Sunplus SPLC780C (timings equivalent to SED1278?)

    Possibly compatible clones:
    Toshiba T1719A (may be custom for Brother)

    Similar, but not quite compatible variants:
    Hitachi HD66780 LCD-IIA (more or less the same as 44780 but has a
     different LCD driver waveform, and like 44780 has
     16 Common/40 Segment drivers)
    Samsung KS0073 (uses different DPRAM memory mapping than HD44780, and
     has 34 Common/64 Segment drivers; it also has a low power mode and
     6-pixel character width modes)
    Samsung S6A0073 (equivalent to KS0073)
    Novatek NT7603 (clocked at twice the speed, has different LCD driver
     waveforms, and has 16 Common/80 Segment drivers)
    Novatek NT7605 (clocked at twice the speed, has different LCD driver
     waveforms, and has 16 Common/100 Segment drivers)
    Solomon Systech SSD1803 (2.7-3.45V only, 34 Common/100 Segment drivers,
     has a 6-pixel character width mode, and when in 4-line mode, has a
     memory map like KS0073; has two unique CGROMs)


    Character set equivalency:
    Hitachi      Samsung    Epson       Sitronix   Sunplus      [Supported]
    HD44780A00   KS0066F00  SED1278F0A  ST7066-0A  SPLC780C-01  Y
    HD44780UA00                                                 Y
    HD44780UA01
                                                   SPLC780C-17
    HD44780UA02                         ST7066-0R               Y
                            SED1278F0H  ST7066-0T  SPLC780C-02
                            SED1278F0B  ST7066-0B  SPLC780C-03  Y
                 KS0066F03                         SPLC780C-08
                 KS0066F04                         SPLC780C-13
                 KS0066F05              ST7066-0E  SPLC780C-11  Y
                 KS0066F06                         SPLC780C-12
                                        ST7066-1G  SPLC780C-14
                                                   SPLC780C-15
                                                   SPLC780C-19
                            SED1278F0C
                 KS0066F59  SED1278F0E
                            SED1278F0G             SPLC780C-18


    DPRAM 80-byte wrap behavior is based on increment/decrement:
    In 1-line mode:
    4E 4F 00 01 02 ... 4D 4E 4F 00 01 02 ...

    In 2-line mode:
    L1: 26 27 00 01 02 ... 24 25 26 27 00 01 02 ...
    L2: 66 67 40 41 42 ... 64 65 66 67 40 41 42 ...

***************************************************************************/

#include "emu.h"
#include "video/hd44780.h"

//#define VERBOSE 1
#include "logmacro.h"


//**************************************************************************
//  DEVICE DEFINITIONS
//**************************************************************************

DEFINE_DEVICE_TYPE(HD44780,  hd44780_device,  "hd44780",  "Hitachi HD44780 LCD Controller")
DEFINE_DEVICE_TYPE(HD44780U, hd44780u_device, "hd44780u", "Hitachi HD44780U LCD Controller")
DEFINE_DEVICE_TYPE(SED1278,  sed1278_device,  "sed1278",  "Epson SED1278 LCD Controller") // packaged as either SED1278F0B or SED1278D0B
DEFINE_DEVICE_TYPE(KS0066,   ks0066_device,   "ks0066",   "Samsung KS0066 LCD Controller")


//-------------------------------------------------
//  ROM( hd44780 )
//-------------------------------------------------

ROM_START( hd44780 )
	ROM_DEFAULT_BIOS("a00")
	ROM_SYSTEM_BIOS(0, "a00", "A00")

	ROM_REGION( 0x1000, "cgrom", 0 )
	ROMX_LOAD( "hd44780_a00.bin",    0x0000, 0x1000,  BAD_DUMP CRC(e459877c) SHA1(65cf075a988cdcbb316b9afdd0529b374a1a65ec), ROM_BIOS(0)) // from page 97 of the 1985 HD44780 datasheet from crystalfontz
ROM_END

ROM_START( hd44780u )
	ROM_DEFAULT_BIOS("a00")
	ROM_SYSTEM_BIOS(0, "a00", "A00")
	ROM_SYSTEM_BIOS(1, "a02", "A02")
	// Note the HD44780UA01 font does exist in the 1994 M24T026 Hitachi LCD Controller Driver LSI Data Book on bitsavers as well

	ROM_REGION( 0x1000, "cgrom", 0 )
	ROMX_LOAD( "hd44780u_a00.bin",    0x0000, 0x1000,  BAD_DUMP CRC(8494cb6b) SHA1(2d4f9cf5ff81f20d2f4e4640f5b8a697a3781eef), ROM_BIOS(0)) // from page 17 of the 1999 HD44780U datasheet
	// this has 6 characters different from the HD44780A00
	// this differs slightly (see the '7') from the HD44780UA00 font in the 1994 M24T026 Hitachi LCD Controller Driver LSI Data Book on bitsavers
	// and needs confirmation from a real device as to which is correct; confirmation on the horizontal offset of the '[' would also be helpful
	ROMX_LOAD( "hd44780u_a02.bin",    0x0000, 0x1000,  BAD_DUMP CRC(6d522b42) SHA1(db8f59573c81933cfc9d3232d419406f0896e60b), ROM_BIOS(1)) // from page 18 of the 1999 HD44780U datasheet
	// this differs slightly (see the '7') from the HD44780UA02 font in the 1994 M24T026 Hitachi LCD Controller Driver LSI Data Book on bitsavers
	// and needs confirmation from a real device as to which is correct
ROM_END


ROM_START( sed1278 )
	ROM_DEFAULT_BIOS("0a")
	ROM_SYSTEM_BIOS(0, "0a", "0A")
	ROM_SYSTEM_BIOS(1, "0b", "0B")

	ROM_REGION( 0x1000, "cgrom", 0 )
	ROMX_LOAD( "sed1278_0a.bin",    0x0000, 0x1000,  BAD_DUMP CRC(e459877c) SHA1(65cf075a988cdcbb316b9afdd0529b374a1a65ec), ROM_BIOS(0)) // from page 9-32 of the SED1278 datasheet (100% identical to hd44780a00)
	ROMX_LOAD( "sed1278_0b.bin",    0x0000, 0x1000,  BAD_DUMP CRC(962498b7) SHA1(41866836ab4ed7bd4c3539bc8df492ba7d7ff42a), ROM_BIOS(1)) // from page 9-33 of the SED1278 datasheet
ROM_END

// TODO: many other SED1278 variants, documented in the datasheet

// ks0066_f00 is 100% identical to hd44780a00, see page 61 of the KS0066 non-U datasheet
// later ks0066 masks may be different, see page 48 of the other KS0066 datasheet
ROM_START( ks0066 )
	ROM_DEFAULT_BIOS("f00")
	ROM_SYSTEM_BIOS(0, "f00", "F00")
	//ROM_SYSTEM_BIOS(?, "f03", "F03")
	//ROM_SYSTEM_BIOS(?, "f04", "F04")
	ROM_SYSTEM_BIOS(1, "f05", "F05")
	//ROM_SYSTEM_BIOS(?, "f06", "F06")
	//ROM_SYSTEM_BIOS(?, "f59", "F59")

	ROM_REGION( 0x1000, "cgrom", 0 )
	ROMX_LOAD( "ks0066_f00.bin",    0x0000, 0x1000,  BAD_DUMP CRC(e459877c) SHA1(65cf075a988cdcbb316b9afdd0529b374a1a65ec), ROM_BIOS(0)) // from page 61 of the KS0066 non-U datasheet
	ROMX_LOAD( "ks0066_f05.bin",    0x0000, 0x1000,  BAD_DUMP CRC(af9e7bd6) SHA1(0196e871584ee5d370856e7307c0f9d1466e3e51), ROM_BIOS(1)) // from page 51 of the KS0066 datasheet
ROM_END


// TODO: Sitronix and Sunplus devices/variants

//**************************************************************************
//  live device
//**************************************************************************

//-------------------------------------------------
//  hd44780_device - constructor
//-------------------------------------------------

hd44780_device::hd44780_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
	: hd44780_device(mconfig, HD44780, tag, owner, clock)
{
}

hd44780_device::hd44780_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock)
	: device_t(mconfig, type, tag, owner, clock)
	, m_pixel_update_cb(*this)
	, m_busy_factor(1.0)
	, m_cgrom(nullptr)
	, m_cgrom_region(*this, DEVICE_SELF)
	, m_rs_input(0)
	, m_rw_input(0)
	, m_db_input(0)
	, m_enabled(false)
	, m_function_set_at_any_time(false)
{
}

hd44780u_device::hd44780u_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) :
	hd44780_device(mconfig, HD44780U, tag, owner, clock)
{
}

sed1278_device::sed1278_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) :
	hd44780_device(mconfig, SED1278, tag, owner, clock)
{
}

ks0066_device::ks0066_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) :
	hd44780_device(mconfig, KS0066, tag, owner, clock)
{
}


//-------------------------------------------------
//  rom_region - device-specific ROM region
//-------------------------------------------------

const tiny_rom_entry *hd44780_device::device_rom_region() const
{
	return ROM_NAME(hd44780);
}

const tiny_rom_entry *hd44780u_device::device_rom_region() const
{
	return ROM_NAME(hd44780u);
}

const tiny_rom_entry *sed1278_device::device_rom_region() const
{
	return ROM_NAME(sed1278);
}

const tiny_rom_entry *ks0066_device::device_rom_region() const
{
	return ROM_NAME(ks0066);
}

//-------------------------------------------------
//  device_start - device-specific startup
//-------------------------------------------------

void hd44780_device::device_start()
{
	m_cgrom = m_cgrom_region.found() ? m_cgrom_region : memregion("cgrom")->base();

	m_pixel_update_cb.resolve();

	m_busy_timer = timer_alloc(FUNC(hd44780_device::clear_busy_flag), this);
	m_blink_timer = timer_alloc(FUNC(hd44780_device::blink_tick), this);

	// state saving
	save_item(NAME(m_busy_factor));
	save_item(NAME(m_busy_flag));
	save_item(NAME(m_ac));
	save_item(NAME(m_dr));
	save_item(NAME(m_ir));
	save_item(NAME(m_active_ram));
	save_item(NAME(m_display_on));
	save_item(NAME(m_cursor_on));
	save_item(NAME(m_shift_on));
	save_item(NAME(m_blink_on));
	save_item(NAME(m_direction));
	save_item(NAME(m_data_len));
	save_item(NAME(m_num_line));
	save_item(NAME(m_char_size));
	save_item(NAME(m_disp_shift));
	save_item(NAME(m_blink));
	save_item(NAME(m_ddram));
	save_item(NAME(m_cgram));
	save_item(NAME(m_nibble));
	save_item(NAME(m_rs_input));
	save_item(NAME(m_rw_input));
	save_item(NAME(m_db_input));
	save_item(NAME(m_enabled));
	save_item(NAME(m_rs_state));
	save_item(NAME(m_rw_state));
}

//-------------------------------------------------
//  device_reset - device-specific reset
//-------------------------------------------------

void hd44780_device::device_reset()
{
	memset(m_ddram, 0x20, sizeof(m_ddram)); // filled with SPACE char
	memset(m_cgram, 0, sizeof(m_cgram));

	m_ac         = 0;
	m_dr         = 0;
	m_ir         = 0;
	m_active_ram = DDRAM;
	m_display_on = false;
	m_cursor_on  = false;
	m_blink_on   = false;
	m_shift_on   = false;
	m_direction  = 1;
	m_data_len   = 8;
	m_num_line   = 1;
	m_char_size  = 8;
	m_disp_shift = 0;
	m_blink      = false;
	m_nibble     = false;
	m_first_cmd  = true;
	m_rs_state   = 0;
	m_rw_state   = 0;

	set_busy_flag(410);
}

//-------------------------------------------------
//  device_clock_changed
//-------------------------------------------------

void hd44780_device::device_clock_changed()
{
	// (re)adjust blink timer
	attotime period = attotime::from_ticks(102400, clock()); // blink happens every 102400 cycles
	attotime remain = m_blink_timer->remaining();

	m_blink_timer->adjust((remain > period) ? period : remain, 0, period);
}

//-------------------------------------------------
//  device validity check
//-------------------------------------------------

void hd44780_device::device_validity_check(validity_checker &valid) const
{
	if (clock() == 0)
		osd_printf_error("LCDC clock cannot be zero!\n");
}

//-------------------------------------------------
//  timer events
//-------------------------------------------------

TIMER_CALLBACK_MEMBER(hd44780_device::clear_busy_flag)
{
	m_busy_flag = false;
}

TIMER_CALLBACK_MEMBER(hd44780_device::blink_tick)
{
	m_blink = !m_blink;
}


//**************************************************************************
//  HELPERS
//**************************************************************************

void hd44780_device::set_busy_flag(uint16_t cycles)
{
	m_busy_flag = true;
	m_busy_timer->adjust(attotime::from_ticks(cycles, clock() / m_busy_factor));
}

void hd44780_device::correct_ac()
{
	if (m_active_ram == DDRAM)
	{
		int max_ac = (m_num_line == 1) ? 0x4f : 0x67;

		if (m_ac > max_ac)
			m_ac -= max_ac + 1;
		else if (m_ac < 0)
			m_ac = max_ac;
		else if (m_num_line == 2 && m_ac > 0x27 && m_ac < 0x40)
			m_ac = 0x40 + (m_ac - 0x28);
	}
	else
		m_ac &= 0x3f;
}

void hd44780_device::update_ac(int direction)
{
	if (m_active_ram == DDRAM && m_num_line == 2 && direction == -1 && m_ac == 0x40)
		m_ac = 0x27;
	else
		m_ac += direction;

	correct_ac();
}

void hd44780_device::shift_display(int direction)
{
	m_disp_shift += direction;

	if (m_disp_shift == 0x50)
		m_disp_shift = 0;
	else if (m_disp_shift == -1)
		m_disp_shift = 0x4f;
}

void hd44780_device::update_nibble(int rs, int rw)
{
	if (m_rs_state != rs || m_rw_state != rw)
	{
		m_rs_state = rs;
		m_rw_state = rw;
		m_nibble = false;
	}

	m_nibble = !m_nibble;
}

inline void hd44780_device::pixel_update(bitmap_ind16 &bitmap, u8 line, u8 pos, u8 y, u8 x, int state)
{
	if (!m_pixel_update_cb.isnull())
	{
		m_pixel_update_cb(bitmap, line, pos, y, x, state);
	}
	else
	{
		u8 line_height = (m_char_size == 8) ? m_char_size : m_char_size + 1;

		if (m_lines <= 2)
		{
			if (pos < m_chars)
				bitmap.pix(line * (line_height + 1) + y, pos * 6 + x) = state;
		}
		else if (m_lines <= 4)
		{
			if (pos < m_chars*2)
			{
				if (pos >= m_chars)
				{
					line += 2;
					pos -= m_chars;
				}

				if (line < m_lines)
					bitmap.pix(line * (line_height + 1) + y, pos * 6 + x) = state;
			}
		}
		else
		{
			fatalerror("%s: use a custom callback for this LCD configuration (%d x %d)\n", tag(), m_lines, m_chars);
		}
	}
}


//**************************************************************************
//  device interface
//**************************************************************************

const u8 *hd44780_device::render()
{
	memset(m_render_buf, 0, sizeof(m_render_buf));

	if (m_display_on)
	{
		u8 line_size = 80 / m_num_line;

		for (int line = 0; line < m_num_line; line++)
		{
			for (int pos = 0; pos < line_size; pos++)
			{
				uint16_t char_pos = line * 0x40 + ((pos + m_disp_shift) % line_size);

				int char_base;
				if (m_ddram[char_pos] < 0x10)
				{
					// draw CGRAM characters
					if (m_char_size == 8)
						char_base = (m_ddram[char_pos] & 0x07) * 8;
					else
						char_base = ((m_ddram[char_pos] >> 1) & 0x03) * 16;
				}
				else
				{
					// draw CGROM characters
					char_base = m_ddram[char_pos] * 0x10;
				}

				const u8 *charset = (m_ddram[char_pos] < 0x10) ? m_cgram : m_cgrom;
				u8 *dest = m_render_buf + 16 * (line * line_size + pos);
				memcpy (dest, charset + char_base, m_char_size);

				if (char_pos == m_ac)
				{
					// draw the cursor
					if (m_cursor_on)
						dest[m_char_size - 1] = 0x1f;

					if (!m_blink && m_blink_on)
						memset(dest, 0x1f, m_char_size);
				}
			}
		}
	}

	return m_render_buf;
}

uint32_t hd44780_device::screen_update(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect)
{
	bitmap.fill(0, cliprect);
	const u8 *img = render();

	u8 line_size = 80 / m_num_line;

	for (int line = 0; line < m_num_line; line++)
	{
		for (int pos = 0; pos < line_size; pos++)
		{
			const u8 *src = img + 16 * (line * line_size + pos);
			for (int y = 0; y < m_char_size; y++)
				for (int x = 0; x < 5; x++)
					pixel_update(bitmap, line, pos, y, x, BIT(src[y], 4 - x));
		}
	}

	return 0;
}

u8 hd44780_device::read(offs_t offset)
{
	if (m_data_len == 4 && !machine().side_effects_disabled())
		update_nibble(offset & 0x01, 1);

	switch (offset & 0x01)
	{
		case 0: return control_read();
		case 1: return data_read();
	}

	return 0;
}

void hd44780_device::write(offs_t offset, u8 data)
{
	if (m_data_len == 4 && !machine().side_effects_disabled())
		update_nibble(offset & 0x01, 0);

	switch (offset & 0x01)
	{
		case 0: control_write(data);  break;
		case 1: data_write(data);     break;
	}
}

u8 hd44780_device::db_r()
{
	if (m_enabled && m_rw_input == 1)
	{
		switch (m_rs_input)
		{
			case 0: return control_read();
			case 1: return data_read();
		}
	}

	return 0xff;
}

void hd44780_device::db_w(u8 data)
{
	m_db_input = data;
}

void hd44780_device::rs_w(int state)
{
	m_rs_input = state;
}

void hd44780_device::rw_w(int state)
{
	m_rw_input = state;
}

void hd44780_device::e_w(int state)
{
	if (m_data_len == 4 && state && !m_enabled && !machine().side_effects_disabled())
		update_nibble(m_rs_input, m_rw_input);

	if (!state && m_enabled && m_rw_input == 0)
	{
		switch (m_rs_input)
		{
			case 0: control_write(m_db_input);  break;
			case 1: data_write(m_db_input);     break;
		}
	}

	m_enabled = state;
}

void hd44780_device::control_write(u8 data)
{
	if (m_data_len == 4)
	{
		if (m_nibble)
		{
			m_ir = data & 0xf0;
			return;
		}
		else
		{
			m_ir |= ((data >> 4) & 0x0f);
		}
	}
	else
	{
		m_ir = data;
	}

	if (BIT(m_ir, 7))
	{
		// set DDRAM address
		m_active_ram = DDRAM;
		m_ac = m_ir & 0x7f;
		correct_ac();
		set_busy_flag(10);

		LOG("HD44780: set DDRAM address %x\n", m_ac);
		return;
	}
	else if (BIT(m_ir, 6))
	{
		// set CGRAM address
		m_active_ram = CGRAM;
		m_ac = m_ir & 0x3f;
		set_busy_flag(10);

		LOG("HD44780: set CGRAM address %x\n", m_ac);
		return;
	}
	else if (BIT(m_ir, 5))
	{
		// function set
		if (!m_function_set_at_any_time && !m_first_cmd && m_data_len == (BIT(m_ir, 4) ? 8 : 4) && (m_char_size != (BIT(m_ir, 2) ? 10 : 8) || m_num_line != (BIT(m_ir, 3) + 1)))
		{
			logerror("HD44780: function set cannot be executed after other instructions unless the interface data length is changed\n");
			return;
		}
		m_first_cmd = true;

		m_char_size = BIT(m_ir, 2) ? 10 : 8;
		m_data_len  = BIT(m_ir, 4) ? 8 : 4;
		m_num_line  = BIT(m_ir, 3) + 1;
		correct_ac();
		set_busy_flag(10);

		LOG("HD44780: char size 5x%d, data len %d, lines %d\n", m_char_size, m_data_len, m_num_line);
		return;
	}
	else if (BIT(m_ir, 4))
	{
		// cursor or display shift
		int direction = (BIT(m_ir, 2)) ? +1 : -1;

		LOG("HD44780: %s shift %d\n", BIT(m_ir, 3) ? "display" : "cursor", direction);

		if (BIT(m_ir, 3))
			shift_display(direction);
		else
			update_ac(direction);

		set_busy_flag(10);
	}
	else if (BIT(m_ir, 3))
	{
		// display on/off control
		m_display_on = BIT(m_ir, 2);
		m_cursor_on  = BIT(m_ir, 1);
		m_blink_on   = BIT(m_ir, 0);
		set_busy_flag(10);

		LOG("HD44780: display %d, cursor %d, blink %d\n", m_display_on, m_cursor_on, m_blink_on);
	}
	else if (BIT(m_ir, 2))
	{
		// entry mode set
		m_direction = (BIT(m_ir, 1)) ? +1 : -1;
		m_shift_on  = BIT(m_ir, 0);
		set_busy_flag(10);

		LOG("HD44780: entry mode set: direction %d, shift %d\n", m_direction, m_shift_on);
	}
	else if (BIT(m_ir, 1))
	{
		// return home
		LOG("HD44780: return home\n");

		m_ac         = 0;
		m_active_ram = DDRAM;
		m_direction  = 1;
		m_disp_shift = 0;
		set_busy_flag(410);
	}
	else if (BIT(m_ir, 0))
	{
		// clear display
		LOG("HD44780: clear display\n");

		m_ac         = 0;
		m_active_ram = DDRAM;
		m_direction  = 1;
		m_disp_shift = 0;
		memset(m_ddram, 0x20, sizeof(m_ddram));
		set_busy_flag(410);

		// Some machines do a "clear display" first, even though the datasheet insists "function set" must come before all else
		return;
	}

	m_first_cmd = false;
}

u8 hd44780_device::control_read()
{
	if (m_data_len == 4)
	{
		if (m_nibble)
			return (m_busy_flag ? 0x80 : 0) | (m_ac & 0x70);
		else
			return (m_ac << 4) & 0xf0;
	}
	else
	{
		return (m_busy_flag ? 0x80 : 0) | (m_ac & 0x7f);
	}
}

void hd44780_device::data_write(u8 data)
{
	if (m_busy_flag)
	{
		logerror("HD44780: Ignoring data write %02x due of busy flag\n", data);
		return;
	}

	if (m_data_len == 4)
	{
		if (m_nibble)
		{
			m_dr = data & 0xf0;
			return;
		}
		else
		{
			m_dr |= ((data >> 4) & 0x0f);
		}
	}
	else
	{
		m_dr = data;
	}

	LOG("HD44780: %sRAM write %x %x '%c'\n", m_active_ram == DDRAM ? "DD" : "CG", m_ac, m_dr, isprint(m_dr) ? m_dr : '.');

	if (m_active_ram == DDRAM)
		m_ddram[m_ac] = m_dr;
	else
		m_cgram[m_ac] = m_dr;

	set_busy_flag(10);
	update_ac(m_direction);
	if (m_shift_on)
		shift_display(m_direction);
}

u8 hd44780_device::data_read()
{
	u8 data = (m_active_ram == DDRAM) ? m_ddram[m_ac] : m_cgram[m_ac];

	LOG("HD44780: %sRAM read %x %c\n", m_active_ram == DDRAM ? "DD" : "CG", m_ac, data);

	if (m_data_len == 4)
	{
		if (m_nibble)
			return data & 0xf0;
		else
			data = (data << 4) & 0xf0;
	}

	if (!machine().side_effects_disabled())
	{
		set_busy_flag(10);
		update_ac(m_direction);
	}

	return data;
}