summaryrefslogtreecommitdiffstatshomepage
path: root/3rdparty/asio/src/examples/cpp14 (follow)
Commit message (Collapse)AuthorAgeFilesLines
* 3rdparty/asio: Updated to 1.30.2 Vas Crabb2024-04-2239-171/+1072
|
* 3rdparty: Updated ASIO to version 1.20.0. Vas Crabb2021-11-1540-269/+3977
| | | | | | | | | The doc folder isn't included as it's pretty big. This required include/asio/detail/win_iocp_socket_accept_op.hpp due to mismatched order in the member declarations and initialiser list for the win_iocp_socket_accept_op class. I reversed the declaration order so it matches win_iocp_socket_move_accept_op.
* Added ASIO networking library (nw) Miodrag Milanovic2016-10-0719-0/+1900
> 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 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987
// license:BSD-3-Clause
// copyright-holders:Joakim Larsson Edström
/***************************************************************************************************
 *
 *   Ericsson Information Systems PC "compatibles"
 *
 * The Ericsson PC was the first original Ericsson design for the office PC market replacing the
 * Step/One which was an OEM:ed clone of the Matsushita Mybrain 3000 (see myb3k.cpp driver).
 *
 **************************************************************
 * Ericsson PC
 *------------
 * Links: https://youtu.be/6uilOdMJc24
 * Form Factor: Desktop
 * CPU: 8088 @ 4.77MHz
 * RAM: 256K
 * Bus: 6x ISA
 * Video: Monchrome or Color 80x25 character mode. 320x200 and 640x400 grahics modes
 * Display: Orange Gas Plasma (GP) display
 * Mass storage: 2 x 5.25" 360K or 1 20Mb HDD
 * On board ports: Beeper,
 * Ports: serial, parallel
 * Internal Options: Up to 640K RAM through add-on RAM card
 * Misc: The hardware was not 100% PC compatible so non BIOS based software would not always run. 50.000+ units sold
 *
 * TODO
 * - Complete the Ericsson 1070 MDA ISA board and test all the graphics modes including 640x400 (aka HR)
 * - Add the Ericsson 1065 HDC and boot from a hard drive
 * - Implement the descrete baudrate generator
 * - Implement logic around enabling/resetting NMI and ISA bus I/O CHK
 *
 * CREDITS  The driver code is inspired from m24.cpp, myb3k.cpp and genpc.cpp. Information about the EPC has
 *          been contributed by many, mainly the people at Dalby Computer museum http://www.datormuseum.se/
 *          A dead pcb was donated by rfka01 and rom dumps by ZnaxQue@sweclockers.com
 *
 ************************************************************************************************************/
/*
 Links
 -----

 */

#include "emu.h"

#include "machine/eispc_kb.h"

// Devices
#include "cpu/i86/i86.h"
#include "machine/am9517a.h"
#include "machine/i8251.h"
#include "machine/i8255.h"
#include "machine/pit8253.h"
#include "machine/pic8259.h"
#include "machine/upd765.h"
#include "machine/ins8250.h"

// Expansion cards
//#include "bus/isa/isa.h"
//#include "bus/isa/isa_cards.h"
#include "bus/isa/ega.h"
#include "bus/isa/mda.h"
#include "bus/isa/eis_twib.h"
#include "machine/pc_lpt.h"

#include "machine/ram.h"
#include "machine/timer.h"
#include "sound/spkrdev.h"
#include "speaker.h"
#include "imagedev/floppy.h"
#include "formats/imd_dsk.h"
#include "formats/pc_dsk.h"
#include "bus/rs232/rs232.h"

#define LOG_PPI     (1U << 1)
#define LOG_PIT     (1U << 2)
#define LOG_PIC     (1U << 3)
#define LOG_KBD     (1U << 4)
#define LOG_DMA     (1U << 5)
#define LOG_IRQ     (1U << 6)
#define LOG_FDC     (1U << 7)
#define LOG_LPT     (1U << 8)
#define LOG_NMI     (1U << 9)
#define LOG_BITS    (1U << 10)

//#define VERBOSE (LOG_BITS|LOG_KBD|LOG_IRQ)
//#define LOG_OUTPUT_STREAM std::cout

#include "logmacro.h"

#define LOGPPI(...)  LOGMASKED(LOG_PPI,  __VA_ARGS__)
#define LOGPIT(...)  LOGMASKED(LOG_PIT,  __VA_ARGS__)
#define LOGPIC(...)  LOGMASKED(LOG_PIC,  __VA_ARGS__)
#define LOGKBD(...)  LOGMASKED(LOG_KBD,  __VA_ARGS__)
#define LOGDMA(...)  LOGMASKED(LOG_DMA,  __VA_ARGS__)
#define LOGIRQ(...)  LOGMASKED(LOG_IRQ,  __VA_ARGS__)
#define LOGFDC(...)  LOGMASKED(LOG_FDC,  __VA_ARGS__)
#define LOGLPT(...)  LOGMASKED(LOG_LPT,  __VA_ARGS__)
#define LOGNMI(...)  LOGMASKED(LOG_NMI,  __VA_ARGS__)
#define LOGBITS(...) LOGMASKED(LOG_BITS,  __VA_ARGS__)

class epc_state : public driver_device
{
public:
	epc_state(const machine_config &mconfig, device_type type, const char *tag) :
		driver_device(mconfig, type, tag)
		, m_maincpu(*this, "maincpu")
		, m_ram(*this, RAM_TAG)
		, m_isabus(*this, "isabus")
		, m_dma8237a(*this, "dma8237")
		, m_ppi8255(*this, "ppi8255")
		, m_io_dsw(*this, "DSW")
		, m_io_j10(*this, "J10")
		, m_lpt(*this, "lpt")
		, m_kbd8251(*this, "kbd8251")
		, m_keyboard(*this, "keyboard")
		, m_pic8259(*this, "pic8259")
		, m_pit8253(*this, "pit8253")
		, m_speaker(*this, "speaker")
		, m_fdc(*this, "fdc")
		, m_floppy_connectors(*this, "fdc:%u", 0)
		, m_uart(*this, "uart8250")
	{ }

	void epc(machine_config &config);
	void init_epc();


protected:
	virtual void machine_start() override;
	virtual void machine_reset() override;

private:
	required_device<i8086_cpu_device> m_maincpu;
	required_device<ram_device> m_ram;
	required_device<isa8_device> m_isabus;

	// DMA
	DECLARE_WRITE_LINE_MEMBER(dma_tc_w);
	DECLARE_WRITE_LINE_MEMBER(dreq0_ck_w);
	DECLARE_WRITE_LINE_MEMBER( epc_dma_hrq_changed );
	DECLARE_WRITE_LINE_MEMBER( epc_dma8237_out_eop );
	DECLARE_READ8_MEMBER( epc_dma_read_byte );
	DECLARE_WRITE8_MEMBER( epc_dma_write_byte );
	template <int Channel> uint8_t epc_dma8237_io_r(offs_t offset);
	template <int Channel> void epc_dma8237_io_w(offs_t offset, uint8_t data);
	template <int Channel> DECLARE_WRITE_LINE_MEMBER(epc_dack_w);
	required_device<am9517a_device> m_dma8237a;
	uint8_t m_dma_segment[4];
	uint8_t m_dma_active;
	bool m_tc;
	bool m_txd;
	bool m_rxrdy;
	bool m_int;
	bool m_dreq0_ck;

	// PPI
	required_device<i8255_device> m_ppi8255;
	DECLARE_WRITE8_MEMBER(ppi_portb_w);
	DECLARE_READ8_MEMBER(ppi_portc_r);
	uint8_t m_ppi_portb;
	required_ioport m_io_dsw;
	required_ioport m_io_j10;

	// Printer port
	optional_device<pc_lpt_device> m_lpt;

	// Keyboard Controller/USART
	required_device<i8251_device> m_kbd8251;
	required_device<eispc_keyboard_device> m_keyboard;
	emu_timer *m_kbdclk_timer;
	TIMER_CALLBACK_MEMBER(rxtxclk_w);
	int m_rxtx_clk_state;

	// Interrupt Controller
	required_device<pic8259_device> m_pic8259;
	DECLARE_WRITE_LINE_MEMBER(int_w);
	uint8_t m_nmi_enabled;
	uint8_t m_8087_int = 0;
	uint8_t m_parer_int = 0;
	uint8_t m_iochck_int = 0;
	void update_nmi();

	// Timer
	required_device<pit8253_device> m_pit8253;

	// Speaker
	DECLARE_WRITE_LINE_MEMBER(speaker_ck_w);
	required_device<speaker_sound_device> m_speaker;
	bool m_pc4;
	bool m_pc5;

	void epc_map(address_map &map);
	void epc_io(address_map &map);

	// FDC
	void check_fdc_irq();
	void check_fdc_drq();
	required_device<i8272a_device> m_fdc;
	uint8_t m_ocr;
	bool m_irq;     // System signal after glue logic
	bool m_drq;     // System signal after glue logic
	bool m_fdc_irq; // FDC output pin
	bool m_fdc_drq; // FDC output pin

	optional_device_array<floppy_connector, 4> m_floppy_connectors;
	DECLARE_FLOPPY_FORMATS( epc_floppy_formats );

	// UART
	required_device<ins8250_device> m_uart;
};

void epc_state::check_fdc_irq()
{
	bool pirq = m_irq;
	m_irq = m_fdc_irq && (m_ocr & 4) && (m_ocr & 8);  // IRQ enabled and not in reset?
	if(m_irq != pirq) // has the state changed?
	{
		LOGIRQ("FDC: IRQ6 request: %d\n", m_irq);
		m_pic8259->ir6_w(m_irq);
	}
}

void epc_state::check_fdc_drq()
{
	bool pdrq = m_drq;
	m_drq = m_fdc_drq && (m_ocr & 4) && (m_ocr & 8); // DREQ enabled and not in reset?
	if(m_drq != pdrq) // has the state changed?
	{
		LOGDMA("FDC: DMA channel 2 request: %d\n", m_drq);
		m_dma8237a->dreq2_w(m_drq);
	}
}

void epc_state::epc_map(address_map &map)
{
	map.unmap_value_high();
	map(0x20000, 0x9ffff).noprw(); // Base RAM - mapped to avoid unmaped errors when BIOS is probing RAM size
	// 0xa0000-0xaffff is reserved
	map(0xb0000, 0xb7fff).noprw(); // Monochrome RAM - mapped to avoid unaped errors when BIOS is probing RAM size
	map(0xb0000, 0xb7fff).noprw(); // Monochrome RAM - mapped to avoid unaped errors when BIOS is probing RAM size
	map(0xb8000, 0xbffff).noprw(); // Color/Graphics RAM - mapped to avoid unaped errors when BIOS is probing RAM size
	map(0xc0000, 0xeffff).noprw(); // Expansion ROM area - Hard Disk BIOS etc
	map(0xf0000, 0xfffff).rom().region("bios", 0);
}

void epc_state::epc_io(address_map &map)
{
	map(0x0000, 0x000f).mirror(0x10).lrw8("dma8237_rw",
		[this](offs_t offset) -> uint8_t
		{
			uint8_t data = m_dma8237a->read(offset);
			LOGDMA("dma8237_r %04x\n", offset);
			return data;
		},
		[this](offs_t offset, uint8_t data)
		{
			LOGDMA("dma8237_w %04x: %02x\n", offset, data);
			m_dma8237a->write(offset, data);
		}
	);

	map(0x0020, 0x0021).mirror(0x1e).lrw8("pic8259_rw",
		[this](offs_t offset) -> uint8_t
		{
			uint8_t data = m_pic8259->read(offset);
			LOGPIC("pic8259_r %04x: %02x\n", offset, data);
			return data;
		},
		[this](offs_t offset, uint8_t data)
		{
			LOGPIC("pic8259_w %04x: %02x\n", offset, data);
			m_pic8259->write(offset, data);
		}
	);

	map(0x0040, 0x0043).mirror(0x1c).lrw8("pit8253_rw",
		[this](offs_t offset) -> uint8_t
		{
			uint8_t data = m_pit8253->read(offset);
			LOGPIT("pit8253_r %04x\n", offset);
			return data;
		},
		[this](offs_t offset, uint8_t data)
		{
			LOGPIT("pit8253_w %04x: %02x\n", offset, data);
			m_pit8253->write(offset, data);
		}
	);

	map(0x0060, 0x0060).mirror(0x1c).lrw8("kbd_8251_data_rw",
		[this]() -> uint8_t
		{
			uint8_t data = m_kbd8251->data_r();
			LOGKBD("kbd8251_r %02x\n", data);
			return data;
		},
		[this](offs_t offset, uint8_t data)
		{
			LOGKBD("kbd8251_w 0x60 %02x\n", data);
			m_kbd8251->data_w(data);
		}
	);
									// NOTE: PPI Port A is not mapped
	map(0x0061, 0x0061).mirror(0x1c).lrw8("ppi8255_rw", // PPI Port B
		[this](offs_t offset) -> uint8_t
		{
			uint8_t data = m_ppi8255->read(1);
			LOGPPI("ppi8255_r Port B: %02x\n", data);
			return data;
		},
		[this](offs_t offset, uint8_t data)
		{
			LOGPPI("ppi8255_w Port B: %02x\n", data);
			m_ppi8255->write(1, data);
		}
	);

	map(0x0062, 0x0062).mirror(0x1c).lrw8("ppi8255_rw", // PPI Port C
		[this](offs_t offset) -> uint8_t
		{
			uint8_t data = m_ppi8255->read(2);
			LOGPPI("ppi8255_r Port C: %02x\n", data);
			return data;
		},
		[this](offs_t offset, uint8_t data)
		{
			LOGPPI("ppi8255_w Port C: %02x\n", data);
			m_ppi8255->write(2, data);
		}
	);

	map(0x0063, 0x0063).lrw8("ppi8255_rw",  // PPI Control register
		[this](offs_t offset) -> uint8_t
		{
			uint8_t data = m_ppi8255->read(3);
			LOGPPI("ppi8255_r Control: %02x\n", data);
			return data;
		},
		[this](offs_t offset, uint8_t data)
		{
			LOGPPI("ppi8255_w Control: %02x\n", data);
			m_ppi8255->write(3, data);
		}
	);

	map(0x0070, 0x0070).mirror(0x0e).lw8("i8251_data_w",
		[this](offs_t offset, uint8_t data)
		{
			LOGKBD("kbd8251_w 0x70: %02x\n", data);
			m_kbd8251->data_w(data);
		}
	);

	map(0x0071, 0x0071).mirror(0x0e).lrw8("kbd_8251_stat_ctrl_rw",
		[this](offs_t offset) -> uint8_t
		{
			uint8_t stat = m_kbd8251->status_r();
			//LOGKBD("kbd8251_status_r %02x\n", stat);
			return stat;
		},
		[this](offs_t offset, uint8_t data)
		{
			LOGKBD("kbd8251_control_w 0x71: %02x\n", data);
			m_kbd8251->control_w(data);
		}
	);

	map(0x0080, 0x0083).mirror(0xc).lw8("dma_segement_w",
		[this](offs_t offset, uint8_t data)
		{
			LOGDMA("dma_segment_w %04x: %02x\n", offset, data);
			m_dma_segment[offset] = data & 0x0f;
		}
	);

	map(0x00a0, 0x00a1).mirror(0xe).lw8("nmi_enable_w",
		[this](offs_t offset, uint8_t data)
		{
			LOGNMI("nmi_enable_w %04x: %02x\n", offset, data);
			m_nmi_enabled = BIT(data,7);
			update_nmi();
		}
	);

	// FDC Output Control Register (same as PC XT DOR)
	map(0x03f2, 0x03f3).lw8("ocr_w",        // B0-B1 Drive select 0-3
		[this](offs_t offset, uint8_t data) // B2 FDC Reset line
		{                   // B3 Enable FDC DMA/IRQ
			LOGFDC("FDC OCR: %02x\n", data);// B4-B7 Motor on for selected drive
			uint8_t pocr = m_ocr;
			uint8_t fid = m_ocr & 3;
			m_ocr = data;
			if ((m_ocr & 4) && m_floppy_connectors[fid]) // Not in reset and there is a floppy drive attached
			{
				floppy_image_device *floppy = m_floppy_connectors[fid]->get_device(); // try to retrieve the floppy
				if (floppy)
				{
					LOGFDC(" - Motor %s for drive %d\n", (m_ocr & (0x10 << fid)) ? "ON" : "OFF", fid);
					floppy->mon_w(!(m_ocr & (0x10 << fid)));
					LOGFDC(" - Setting a floppy for drive %d\n", fid);
					m_fdc->set_floppy((m_ocr & (0x10 << fid)) ? floppy : nullptr);
				}
			}
			if (((pocr ^ m_ocr) & 4) && (m_ocr & 4) == 0) // If FDC reset state bit has changed to low then reset the FDC
				m_fdc->reset();
			check_fdc_irq();
			check_fdc_drq();
		}
	);

	map(0x03f4, 0x03f5).m(m_fdc, FUNC(i8272a_device::map));

	map(0x03bc, 0x03be).lrw8("lpt_rw",
		[this](address_space &space, offs_t offset, uint8_t mem_mask) -> uint8_t
		{
			uint8_t data = m_lpt->read(space, offset);
			LOGLPT("LPT read offset %02x: %02x\n", offset, data);
			return data;
		},
		[this](address_space &space, offs_t offset, uint8_t data)
		{
			LOGLPT("LPT write offset %02x: %02x\n", offset, data);
			m_lpt->write(space, offset, data);
		}
	);

	map(0x03f8, 0x03ff).rw(m_uart, FUNC(ins8250_device::ins8250_r), FUNC(ins8250_device::ins8250_w));
}

void epc_state::machine_start()
{
	m_maincpu->space(AS_PROGRAM).install_ram(0, m_ram->size() - 1, m_ram->pointer());

	std::fill_n(&m_dma_segment[0], 4, 0);

	save_item(NAME(m_dma_segment));
	save_item(NAME(m_dma_active));
	save_item(NAME(m_tc));
	save_item(NAME(m_txd));
	save_item(NAME(m_rxrdy));
	save_item(NAME(m_int));
	save_item(NAME(m_dreq0_ck));
	save_item(NAME(m_ppi_portb));
	save_item(NAME(m_rxtx_clk_state));
	save_item(NAME(m_nmi_enabled));
	save_item(NAME(m_8087_int));
	save_item(NAME(m_parer_int));
	save_item(NAME(m_iochck_int));
	save_item(NAME(m_pc4));
	save_item(NAME(m_pc5));
	save_item(NAME(m_ocr));
	save_item(NAME(m_irq));
	save_item(NAME(m_drq));
	save_item(NAME(m_fdc_irq));
	save_item(NAME(m_fdc_drq));
}

void epc_state::machine_reset()
{
	m_dma_active = 0;
	m_tc = false;
	m_txd = false;
	m_rxrdy = false;
	m_int = 1;
	m_dreq0_ck = true;
	m_ppi_portb = 0;
	m_rxtx_clk_state = 0;
	m_nmi_enabled = 0;
	m_8087_int = 0;
	m_parer_int = 0;
	m_iochck_int = 0;
	m_pc4 = 0;
	m_pc5 = 0;
	m_ocr = 0;
	m_irq = 0;
	m_drq = 0;
	m_fdc_irq = 0;
	m_fdc_drq = 0;

	m_keyboard->rst_line_w(ASSERT_LINE);
	m_kbd8251->write_cts(0); // Tied to GND
}

void epc_state::init_epc()
{
	/* Keyboard UART Rxc/Txc is 19.2 kHz from x96 divider */
	m_kbdclk_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(epc_state::rxtxclk_w), this));
	m_kbdclk_timer->adjust(attotime::from_hz((XTAL(18'432'000) / 96) / 5));
}

TIMER_CALLBACK_MEMBER(epc_state::rxtxclk_w)
{
	m_kbd8251->write_rxc(m_rxtx_clk_state);
	m_kbd8251->write_txc(m_rxtx_clk_state);
	m_rxtx_clk_state ^= 0x01;
	m_kbdclk_timer->adjust(attotime::from_hz((XTAL(18'432'000) / 96) / 5));
}


template <int Channel>
uint8_t epc_state::epc_dma8237_io_r(offs_t offset)
{
	LOGDMA("epc_dma8237_io_r: %d\n", Channel);
	if (Channel == 2)
		return m_fdc->dma_r();
	else
		return m_isabus->dack_r(Channel);
}

template <int Channel>
void epc_state::epc_dma8237_io_w(offs_t offset, uint8_t data)
{
	LOGDMA("epc_dma8237_io_w: %d - %02x\n", Channel, data);
	if (Channel == 2)
		m_fdc->dma_w(data);
	else
		m_isabus->dack_w(Channel, data);
}

template <int Channel>
WRITE_LINE_MEMBER(epc_state::epc_dack_w)
{
	LOGDMA("epc_dack_w: %d - %d\n", Channel, state);

	m_isabus->dack_line_w(Channel, state);

	if (!state)
	{
		m_dma_active |= 1 << Channel;
		if (Channel == 0)
			m_dma8237a->dreq0_w(0);
		if (m_tc)
			m_isabus->eop_w(Channel, ASSERT_LINE);
	}
	else
	{
		m_dma_active &= ~(1 << Channel);
		if (m_tc)
			m_isabus->eop_w(Channel, CLEAR_LINE);
	}
}

WRITE_LINE_MEMBER(epc_state::dma_tc_w)
{
	m_tc = (state == ASSERT_LINE);
	for (int channel = 0; channel < 4; channel++)
	{
		if (BIT(m_dma_active, channel))
		{
			LOGDMA("dma_tc_w ch %d: %d\n", channel, state);
			m_isabus->eop_w(channel, state);
		}
	}

	// Special treatment for on board FDC
	if (BIT(m_dma_active, 2))
	{
		m_fdc->tc_w(0);
	}
	else
	{
		m_fdc->tc_w(1);
	}
}

WRITE_LINE_MEMBER(epc_state::dreq0_ck_w)
{
	if (state && !m_dreq0_ck && !BIT(m_dma_active, 0))
		m_dma8237a->dreq0_w(1);

	m_dreq0_ck = state;
}

WRITE_LINE_MEMBER(epc_state::speaker_ck_w)
{
	m_pc5 = state;
	m_pc4 = (m_ppi_portb & 0x02) && state ? 1 : 0;
	m_speaker->level_w(m_pc4);
}

/**********************************************************
 *
 * PPI8255 interface
 *
 *
 * PORT A (not used)
 *
 * Reads of port A is shadowed by UART8251A's read register
 * gaining some compatibility with PC software. The UART8251
 * communicates with the serial keyboard and extends it with
 * write capability enabling keyboard led control as with a
 * PC AT keyboard.
 *
 * PORT B (output)
 * 0 - PB0 -             - Control signal for the sound generator (short beeps)
 * 1 - PB1 -             - Control signal for the sound generator
 * 2 - PB2 -             - Unused
 * 3 - PB3 -             - Data select for the configuration switches 0=SW1-4 1=SW5-8
 * 4 - PB4 - *           - Enable ram parity check
 * 5 - PB5 - *           - Enable expansion I/O check
 * 6 - PB6 - *           - Keyboard reset
 * 7 - PB7 -             - Reset keyboard interrupt
 *
 * PORT C
 * 0 - PC0 -         - Dipswitch SW 1/5 PB3=0/PB3=1
 * 1 - PC1 -         - Dipswitch SW 2/6 PB3=0/PB3=1
 * 2 - PC2 -         - Dipswitch SW 3/7 PB3=0/PB3=1
 * 3 - PC3 -         - Dipswitch SW 4/8 PB3=0/PB3=1
 * 4 - PC4 - SPK     - Speaker/cassette data (spare in PC XT spec)
 * 5 - PC5 - OUT2    - OUT2 from 8253 (ibmpcjr compatible)
 * 6 - PC6 -
 * 7 - PC7 -
 *
 * Ericsson PC SW:
 * 1   - Not used. Must be set to OFF
 * 2   - OFF - 8087 present
 *       ON  - No 8087 present *)
 * 3   - Not Used. Don't care but OFF *)
 * 4   - Not Used. Must be set to ON
 * 5+6 - Used to select display
 *       OFF OFF - Monochrome HR graphics monitor 3111 installed + 1020 color secondary monitor
 *       ON  OFF - Monochrome HR graphics monitor 3111 installed + optional 1020 color main monitor *)
 *       OFF ON  - Not used
 *       ON  ON  - Not used
 * 7+8 - Used to select number of disk drives
 *       OFF OFF - Not used
 *       ON  OFF - Not used
 *       OFF ON  - two disk drives, system units 1030-1 and 1030-2
 *       ON  ON  - one disk drive, system units 1030-3, 1030-4, 1031-1 and 1031-2
 *
 *           *)  - Factory settings
 *
 **********************************************************/

READ8_MEMBER( epc_state::ppi_portc_r )
{
	uint8_t data;

	// Read 4 configurations dip switches depending on PB3
	data = (m_io_dsw->read() >> ((m_ppi_portb & 0x08) ? 4 : 0) & 0x0f);

	data |= (m_pc4 ? 1U << 4 : 0); // Feedback from gated speaker beep
	data |= (m_pc5 ? 1U << 5 : 0); // Feedback from timer source for speaker beep

	LOGPPI("PPI Port C read: %02x\n", data);

	return data;
}

WRITE8_MEMBER( epc_state::ppi_portb_w )
{
	LOGPPI("PPI Port B write: %02x\n", data);
	LOGPPI(" PB0 - Enable beeper             : %d\n", (data & 0x01)  ? 1 : 0);
	LOGPPI(" PB1 - Beeper data               : %d\n", (data & 0x02)  ? 1 : 0);
	LOGPPI(" PB2 - Unused                    : %d\n", (data & 0x04)  ? 1 : 0);
	LOGPPI(" PB3 - Port C dip switch select  : %d\n", (data & 0x08)  ? 1 : 0);
	LOGPPI(" PB4 - RAM parity enable         : %d\n", (data & 0x10)  ? 1 : 0);
	LOGPPI(" PB5 - ISA error checking enable : %d\n", (data & 0x20)  ? 1 : 0);
	LOGPPI(" PB6 - Reset keyboard            : %d\n", (data & 0x40)  ? 1 : 0);
	LOGPPI(" PB7 - Reset keyboard interrupt  : %d\n", (data & 0x80)  ? 1 : 0);

	uint8_t changed = m_ppi_portb ^ data;

	m_ppi_portb = data;

	if (changed & 0x40)
	{
		if (m_ppi_portb & 0x40)
		{
			LOGKBD("PB6 set, clearing Keyboard RESET\n");
			m_keyboard->rst_line_w(CLEAR_LINE);
		}
		else
		{
			LOGKBD("PB6 cleared, asserting Keyboard RESET\n");
			m_keyboard->rst_line_w(ASSERT_LINE);
		}
	}

	if (changed & m_ppi_portb & 0x80)
	{
		LOGIRQ("PB7 set, clearing IRQ1 and releasing HOLD\n");
		m_pic8259->ir1_w(CLEAR_LINE);
		m_keyboard->hold_w(ASSERT_LINE);
	}
}

WRITE_LINE_MEMBER(epc_state::int_w)
{
	if (m_int != state)
	{
		LOGIRQ("int_w: %d\n", state);
		m_int = state;
		m_maincpu->set_input_line(0, m_int);
	}
}

static void epc_isa8_cards(device_slot_interface &device)
{
	device.option_add("epc_mda", ISA8_EPC_MDA);
	device.option_add("ega", ISA8_EGA);
	device.option_add("epc_twib", ISA8_EIS_TWIB);
	// device.option_add("epc_hdc1065", ISA8_EPC_HDC1065);
	// device.option_add("epc_mb1080", ISA8_EPC_MB1080);
}

FLOPPY_FORMATS_MEMBER( epc_state::epc_floppy_formats )
	FLOPPY_PC_FORMAT,
	FLOPPY_IMD_FORMAT
FLOPPY_FORMATS_END

static void epc_sd_floppies(device_slot_interface &device)
{
	device.option_add("525sd", FLOPPY_525_SD);
}

void epc_state::epc(machine_config &config)
{
	I8088(config, m_maincpu, XTAL(14'318'181) / 3.0); // TWE crystal marked X1 verified divided through a 82874
	m_maincpu->set_addrmap(AS_PROGRAM, &epc_state::epc_map);
	m_maincpu->set_addrmap(AS_IO, &epc_state::epc_io);
	m_maincpu->set_irq_acknowledge_callback("pic8259", FUNC(pic8259_device::inta_cb));

	// DMA
	AM9517A(config, m_dma8237a, XTAL(14'318'181) / 3.0); // TWE crystal marked X1 verified
	m_dma8237a->out_hreq_callback().set(FUNC(epc_state::epc_dma_hrq_changed));
	m_dma8237a->out_eop_callback().set(FUNC(epc_state::dma_tc_w));
	m_dma8237a->in_memr_callback().set(FUNC(epc_state::epc_dma_read_byte));
	m_dma8237a->out_memw_callback().set(FUNC(epc_state::epc_dma_write_byte));
	m_dma8237a->in_ior_callback<1>().set(FUNC(epc_state::epc_dma8237_io_r<1>));
	m_dma8237a->in_ior_callback<2>().set(FUNC(epc_state::epc_dma8237_io_r<2>));
	m_dma8237a->in_ior_callback<3>().set(FUNC(epc_state::epc_dma8237_io_r<3>));
	m_dma8237a->out_iow_callback<0>().set(FUNC(epc_state::epc_dma8237_io_w<0>));
	m_dma8237a->out_iow_callback<1>().set(FUNC(epc_state::epc_dma8237_io_w<1>));
	m_dma8237a->out_iow_callback<2>().set(FUNC(epc_state::epc_dma8237_io_w<2>));
	m_dma8237a->out_iow_callback<3>().set(FUNC(epc_state::epc_dma8237_io_w<3>));
	m_dma8237a->out_dack_callback<0>().set(FUNC(epc_state::epc_dack_w<0>));
	m_dma8237a->out_dack_callback<1>().set(FUNC(epc_state::epc_dack_w<1>));
	m_dma8237a->out_dack_callback<2>().set(FUNC(epc_state::epc_dack_w<2>));
	m_dma8237a->out_dack_callback<3>().set(FUNC(epc_state::epc_dack_w<3>));

	// TTL-level serial keyboard callback
	EISPC_KB(config, "keyboard").txd_cb().set([this](bool state)
	{
		LOGBITS("KBD->EPC: %d\n", state);
		m_kbd8251->write_rxd(state);
	});

	// Keyboard USART
	I8251( config, m_kbd8251, XTAL(14'318'181) / 6.0 ); // TWE crystal marked X1 verified divided through a 82874

	m_kbd8251->txd_handler().set([this](bool state)
	{
		if (m_txd != state)
		{
			LOGBITS("EPC->KBD: %d\n", state);
			m_txd = state;
			m_keyboard->rxd_w(m_txd);
		}
	});

	m_kbd8251->rxrdy_handler().set([this](bool state)
	{
		m_rxrdy = state;
		LOGKBD("KBD RxRdy: %d HOLD: %d\n", m_rxrdy ? 1 : 0, m_rxrdy ? 0 : 1);
		m_keyboard->hold_w(!m_rxrdy);
		if (m_rxrdy)
		{
			LOGIRQ("RxRdy set, asserting IRQ1\n");
			m_pic8259->ir1_w(ASSERT_LINE); // Cleared by setting PB7
		}
	});
	m_kbd8251->dtr_handler().set([this](bool state) // Controls RCLK for INS8250, either 19.2KHz or INS8250 BAUDOUT
	{
		LOGKBD("KBD DTR: %d\n", state ? 1 : 0); // TODO: Implement clock selection mux, need to check what state does what
	});

	// Interrupt Controller
	PIC8259(config, m_pic8259);
	m_pic8259->out_int_callback().set(FUNC(epc_state::int_w));

	// Parallel port
	I8255A(config, m_ppi8255);
	m_ppi8255->out_pa_callback().set([this] (uint8_t data) { LOGPPI("PPI: write %02x to unused Port A\n", data); } ); // Port A is not used
	m_ppi8255->out_pb_callback().set(FUNC(epc_state::ppi_portb_w));
	m_ppi8255->in_pc_callback().set(FUNC(epc_state::ppi_portc_r));

	// system board Parallel port
	PC_LPT(config, m_lpt);
	m_lpt->irq_handler().set([this](int state)
	{   // Jumper field J10 decides what IRQ to pull
		if ((m_io_j10->read() & 0x03) == 0x01) { LOGIRQ("LPT IRQ2: %d\n", state); m_pic8259->ir2_w(state); }
		if ((m_io_j10->read() & 0x0c) == 0x04) { LOGIRQ("LPT IRQ3: %d\n", state); m_pic8259->ir3_w(state); }
		if ((m_io_j10->read() & 0x30) == 0x10) { LOGIRQ("LPT IRQ4: %d\n", state); m_pic8259->ir4_w(state); }
		if ((m_io_j10->read() & 0xc0) == 0x40) { LOGIRQ("LPT IRQ7: %d\n", state); m_pic8259->ir7_w(state); } // Factory setting
	});

	// Timer
	PIT8253(config, m_pit8253);
	m_pit8253->set_clk<0>((XTAL(14'318'181) / 3.0) / 2.0 );
	m_pit8253->set_clk<1>((XTAL(14'318'181) / 3.0) / 2.0 );
	m_pit8253->set_clk<2>((XTAL(14'318'181) / 3.0) / 2.0 );
	m_pit8253->out_handler<0>().set(m_pic8259, FUNC(pic8259_device::ir0_w));
	m_pit8253->out_handler<1>().set(FUNC(epc_state::dreq0_ck_w));
	m_pit8253->out_handler<2>().set(FUNC(epc_state::speaker_ck_w));

	// Speaker
	SPEAKER(config, "mono").front_center();
	SPEAKER_SOUND(config, m_speaker).add_route(ALL_OUTPUTS, "mono", 1.00);

	// ISA bus
	ISA8(config, m_isabus,  XTAL(14'318'181) / 3.0); // TEW crystal marked X1 verified
	m_isabus->set_memspace(m_maincpu, AS_PROGRAM);
	m_isabus->set_iospace(m_maincpu, AS_IO);
	m_isabus->irq2_callback().set(m_pic8259, FUNC(pic8259_device::ir2_w)); // Reserved in service manual
	m_isabus->irq3_callback().set(m_pic8259, FUNC(pic8259_device::ir3_w));
	m_isabus->irq4_callback().set(m_pic8259, FUNC(pic8259_device::ir4_w));
	m_isabus->irq5_callback().set(m_pic8259, FUNC(pic8259_device::ir5_w));
	m_isabus->irq6_callback().set(m_pic8259, FUNC(pic8259_device::ir6_w));
	m_isabus->irq7_callback().set(m_pic8259, FUNC(pic8259_device::ir7_w));
	m_isabus->drq1_callback().set(m_dma8237a, FUNC(am9517a_device::dreq1_w));
	m_isabus->drq2_callback().set(m_dma8237a, FUNC(am9517a_device::dreq2_w));
	m_isabus->drq3_callback().set(m_dma8237a, FUNC(am9517a_device::dreq3_w));
	m_isabus->iochck_callback().set([this] (int state)
	{
		if (m_nmi_enabled && !state && 0)
		{
			LOGNMI("IOCHCK: NMI Requested\n");
			update_nmi();
		}
	});

	ISA8_SLOT(config, "isa1", 0, m_isabus, epc_isa8_cards, "epc_mda", false);
	ISA8_SLOT(config, "isa2", 0, m_isabus, epc_isa8_cards, nullptr, false);
	ISA8_SLOT(config, "isa3", 0, m_isabus, epc_isa8_cards, nullptr, false);
	ISA8_SLOT(config, "isa4", 0, m_isabus, epc_isa8_cards, nullptr, false);
	ISA8_SLOT(config, "isa5", 0, m_isabus, epc_isa8_cards, nullptr, false);
	ISA8_SLOT(config, "isa6", 0, m_isabus, epc_isa8_cards, nullptr, false);

	// System board has 128kB memory with parity, expansion can be achieved through the
	// 128kB Memory Expansion Board 1090 and/or the 128kB Multifunction Board MB1080-001
	// and/or the 384kB MB1080-002. The MB1080 DRAM might need to be dynamically added as
	// base address and also a video memory hole is configurable.
	// Some RAM sizes are disabled because they trigger issue #5776, just until that is sorted out
	//RAM(config, m_ram).set_default_size("128K").set_extra_options("256K, 384K, 512K, 640K");
	RAM(config, m_ram).set_default_size("128K").set_extra_options("384K");

	// FDC
	I8272A(config, m_fdc, XTAL(16'000'000) / 2, false); // TEW crystal marked X3 verified
	m_fdc->intrq_wr_callback().set([this] (int state){ m_fdc_irq = state; check_fdc_irq(); });
	m_fdc->drq_wr_callback().set([this] (int state){ m_fdc_drq = state; check_fdc_drq(); });
	FLOPPY_CONNECTOR(config, m_floppy_connectors[0], epc_sd_floppies, "525sd", epc_floppy_formats);
	FLOPPY_CONNECTOR(config, m_floppy_connectors[1], epc_sd_floppies, "525sd", epc_floppy_formats);
	//SOFTWARE_LIST(config, "epc_flop_list").set_original("epc_flop");

	// system board UART TODO: Implement the descrete "Baud Rate Clock" from schematics that generates clocks for the 8250
	INS8250(config, m_uart, XTAL(18'432'000) / 10); // TEW crystal marked X2 verified. TODO: Let 8051 DTR control RCLK (see above)
	m_uart->out_tx_callback().set("uart", FUNC(rs232_port_device::write_txd));
	m_uart->out_dtr_callback().set("uart", FUNC(rs232_port_device::write_dtr));
	m_uart->out_rts_callback().set("uart", FUNC(rs232_port_device::write_rts));
	m_uart->out_int_callback().set([this](int state)
	{   // Jumper field J10 decides what IRQ to pull
		if ((m_io_j10->read() & 0x03) == 0x02) { LOGIRQ("UART IRQ2: %d\n", state); m_pic8259->ir2_w(state); }
		if ((m_io_j10->read() & 0x0c) == 0x08) { LOGIRQ("UART IRQ3: %d\n", state); m_pic8259->ir3_w(state); }
		if ((m_io_j10->read() & 0x30) == 0x20) { LOGIRQ("UART IRQ4: %d\n", state); m_pic8259->ir4_w(state); } // Factory setting
		if ((m_io_j10->read() & 0xc0) == 0x80) { LOGIRQ("UART IRQ7: %d\n", state); m_pic8259->ir7_w(state); }
	});

	rs232_port_device &rs232(RS232_PORT(config, "uart", default_rs232_devices, nullptr));
	rs232.rxd_handler().set(m_uart, FUNC(ins8250_uart_device::rx_w));
	rs232.dcd_handler().set(m_uart, FUNC(ins8250_uart_device::dcd_w));
	rs232.dsr_handler().set(m_uart, FUNC(ins8250_uart_device::dsr_w));
	rs232.ri_handler().set(m_uart, FUNC(ins8250_uart_device::ri_w));
	rs232.cts_handler().set(m_uart, FUNC(ins8250_uart_device::cts_w));
}

void epc_state::update_nmi()
{
	if (m_nmi_enabled &&
		((m_8087_int && (m_io_dsw->read() & 0x02)) || // FPU int only if FPU is enabled by DSW2
		 (m_parer_int != 0) || // Parity error is always false as it is an emulator, at least for now
		 (m_iochck_int != 0))) // Same goes for ISA board errors
	{
		LOGNMI(" NMI Asserted\n");
		m_maincpu->set_input_line(INPUT_LINE_NMI, ASSERT_LINE);
	}
	else
	{
		LOGNMI(" NMI Cleared\n");
		m_maincpu->set_input_line(INPUT_LINE_NMI, CLEAR_LINE);
	}
}

WRITE_LINE_MEMBER( epc_state::epc_dma_hrq_changed )
{
	LOGDMA("epc_dma_hrq_changed %d\n", state);

	m_maincpu->set_input_line(INPUT_LINE_HALT, state ? ASSERT_LINE : CLEAR_LINE);

	/* Assert HLDA */
	m_dma8237a->hack_w(state);
}


READ8_MEMBER( epc_state::epc_dma_read_byte )
{
	if ((m_dma_active & 0x0f) == 0)
	{
		LOGDMA("epc_dma_read_byte failed\n");
		return 0xff;
	}

	const int seg = (BIT(m_dma_active, 2) ? 0 : 2) | (BIT(m_dma_active, 3) ? 0 : 1);
	return m_maincpu->space(AS_PROGRAM).read_byte(offset | u32(m_dma_segment[seg]) << 16);
}

WRITE8_MEMBER( epc_state::epc_dma_write_byte )
{
	if ((m_dma_active & 0x0f) == 0)
	{
		LOGDMA("epc_dma_write_byte failed\n");
		return;
	}

	const int seg = (BIT(m_dma_active, 2) ? 0 : 2) | (BIT(m_dma_active, 3) ? 0 : 1);
	m_maincpu->space(AS_PROGRAM).write_byte(offset | u32(m_dma_segment[seg]) << 16, data);
}


static INPUT_PORTS_START( epc_ports )
	PORT_START("DSW")
	PORT_DIPNAME( 0x01, 0x01, "Not used")
	PORT_DIPSETTING(    0x00, "ON - Don't use")
	PORT_DIPSETTING(    0x01, "OFF - Factory Setting")
	PORT_DIPNAME( 0x02, 0x00, "8087 installed")
	PORT_DIPSETTING(    0x00, DEF_STR(No) )
	PORT_DIPSETTING(    0x02, DEF_STR(Yes) )
	PORT_DIPNAME( 0x04, 0x04, "Not used")
	PORT_DIPSETTING(    0x00, "ON - Don't care")
	PORT_DIPSETTING(    0x04, "OFF - Factory Setting")
	PORT_DIPNAME( 0x08, 0x00, "Not used")
	PORT_DIPSETTING(    0x00, "ON - Factory Setting")
	PORT_DIPSETTING(    0x08, "OFF - Don't use")
	PORT_DIPNAME( 0x30, 0x30, "Main monitor")
	PORT_DIPSETTING(    0x00, "Not used" )
	PORT_DIPSETTING(    0x10, "Optional 1020 color" )
	PORT_DIPSETTING(    0x20, "Not used" )
	PORT_DIPSETTING(    0x30, "3111 HR Monochrome" )
	PORT_DIPNAME( 0xc0, 0x40, "Number of floppy drives")
	PORT_DIPSETTING(    0x00, "1" )
	PORT_DIPSETTING(    0x40, "2" )
	PORT_DIPSETTING(    0x80, "Not used" )
	PORT_DIPSETTING(    0xc0, "Not used" )

	PORT_START("J10") // Jumper area, field 0=no jumper 1=LPT 2=COM 3=n/a
	PORT_DIPNAME(0x03, 0x00, "IRQ2")
	PORT_DIPSETTING(0x00, "no jumper")
	PORT_DIPSETTING(0x01, "LPT")
	PORT_DIPSETTING(0x02, "COM")
	PORT_DIPNAME(0x0c, 0x00, "IRQ3")
	PORT_DIPSETTING(0x00, "no jumper")
	PORT_DIPSETTING(0x04, "LPT")
	PORT_DIPSETTING(0x08, "COM")
	PORT_DIPNAME(0x30, 0x20, "IRQ4")
	PORT_DIPSETTING(0x00, "no jumper")
	PORT_DIPSETTING(0x10, "LPT")
	PORT_DIPSETTING(0x20, "COM")
	PORT_DIPNAME(0xc0, 0x40, "IRQ7")
	PORT_DIPSETTING(0x00, "no jumper")
	PORT_DIPSETTING(0x40, "LPT")
	PORT_DIPSETTING(0x80, "COM")
INPUT_PORTS_END

ROM_START( epc )
	ROM_REGION(0x10000,"bios", 0)
	ROM_DEFAULT_BIOS("p860110")
	ROM_SYSTEM_BIOS(0, "p840705", "P840705") // TODO: Fix "Keyboard error" for this bios
	ROMX_LOAD("ericsson_8088.bin", 0xe000, 0x2000, CRC(3953c38d) SHA1(2bfc1f1d11d0da5664c3114994fc7aa3d6dd010d), ROM_BIOS(0))
	ROM_SYSTEM_BIOS(1, "p860110", "P860110")
	ROMX_LOAD("epcbios1.bin",  0xe000, 0x02000, CRC(79a83706) SHA1(33528c46a24d7f65ef5a860fbed05afcf797fc55), ROM_BIOS(1))
	ROMX_LOAD("epcbios2.bin",  0xa000, 0x02000, CRC(3ca764ca) SHA1(02232fedef22d31a641f4b65933b9e269afce19e), ROM_BIOS(1))
	ROMX_LOAD("epcbios3.bin",  0xc000, 0x02000, CRC(70483280) SHA1(b44b09da94d77b0269fc48f07d130b2d74c4bb8f), ROM_BIOS(1))
ROM_END


COMP( 1985, epc,     0,      0,      epc,     epc_ports, epc_state, init_epc,    "Ericsson Information System",     "Ericsson PC" ,          MACHINE_NOT_WORKING )
//COMP( 1985, eppc,   ibm5150, 0,  pccga,         pccga,  pc_state, empty_init,    "Ericsson Information System",     "Ericsson Portable PC",  MACHINE_NOT_WORKING )