File:  [Isaki's NoNo m68k/m88k emulator] / nono / vm / pio.cpp
Revision 1.1.1.19 (vendor branch): download - view: text, annotated - select for diffs
Wed Apr 29 17:06:00 2026 UTC (2 months, 3 weeks ago) by root
Branches: MAIN, Isaki
CVS tags: v027, HEAD
nono 1.7.0

//
// nono
// Copyright (C) 2020 nono project
// Licensed under nono-license.txt
//

//
// PIO/PPI (i8255)
//

// IODevice
//    |
//    |  +-------------+
//    +--| i8255Device | (8255 としての共通部分)
//       +-------------+
//          |
//          |  +-----------+
//          +--| PPIDevice |  (X68030)
//          |  +-----------+
//          |
//          |  +------------+
//          +--| PIO0Device | (LUNA)
//          |  +------------+
//          |
//          |  +------------+
//          +--| PIO1Device | (LUNA)
//             +------------+

#include "pio.h"
#include "adpcm.h"
#include "bitops.h"
#include "dipsw.h"
#include "event.h"
#include "interrupt.h"
#include "lcd.h"
#include "mainapp.h"
#include "monitor.h"
#include "mpu64180.h"
#include "power.h"
#include "scheduler.h"

//
// i8255
//

// コンストラクタ
i8255Device::i8255Device(uint objid_)
	: inherited(objid_)
{
}

// デストラクタ
i8255Device::~i8255Device()
{
}

// リセット
void
i8255Device::ResetHard(bool poweron)
{
	// clears the control register
	// and place ports A, B, and C in input mode.
	// The input latches in ports A, B, and C are not cleared.

	ctrl = 0b10011011;
}

// コントロールポートへの書き込み。
void
i8255Device::WriteCtrl(uint32 data)
{
	if ((data & 0x80)) {
		// 最上位が立っていればモード設定
		ctrl = data;
		if (__predict_false(loglevel >= 1)) {
			if (loglevel >= 2) {
				putlogn("Ctrl <- $%02x: GrA/Bmode=%u/%u PA=%s PB=%s PC=%s:%s",
					data,
					GetGroupAMode(), GetGroupBMode(),
					PortADir() == DIR_IN ? "IN" : "OUT",
					PortBDir() == DIR_IN ? "IN" : "OUT",
					PortCHDir() == DIR_IN ? "IN" : "OUT",
					PortCLDir() == DIR_IN ? "IN" : "OUT");
			} else {
				putlogn("Ctrl <- $%02x: SetMode", data);
			}
		}

		// モード設定すると PC はゼロクリアされる
		for (int pc = 0; pc < 8; pc++) {
			SetPC(pc, 0);
		}
	} else {
		// portC 出力
		//
		// 0  x  x  x  P  P  P  V
		//             |  |  |  +-- 書き込むビットデータ
		//             +--+--+----- 書き込むビット位置

		uint pc = (data >> 1) & 7;
		uint val = data & 1;
		putlog(2, "Ctrl <- $%02x: SetPC%u=%u", data, pc, val);
		SetPC(pc, val);
	}
}

bool
i8255Device::PokePort(uint32 offset, uint32 data)
{
	return false;
}

// 共通部分を表示。
// 戻り値は表示を終えた次の行。
int
i8255Device::MonitorScreenCommon(TextScreen& screen, int y)
{
	// Control = $xx
	//  GroupA (PortA,PortCH) Mode: 0(Basic I/O)
	//  GroupB (PortB,PortCL) Mode: 1(Strobed I/O)
	//  Port Direction: PA=OUT PB=IN  PC(H)=OUT PC(L)=OUT

	screen.Print(1, y, "Control = $%02x:", ctrl);
	static const char * const modestr[] = {
		"Basic I/O",
		"Strobed I/O",
		"BiDirection",
		"BiDirection",
	};
	const int x = 16;
	screen.Print(x, y++, "GroupA (PortA,PortCH) Mode: %u(%s)",
		GetGroupAMode(),
		modestr[GetGroupAMode()]);
	screen.Print(x, y++, "GroupB (PortB,PortCL) Mode: %u(%s)",
		GetGroupBMode(),
		modestr[GetGroupBMode()]);
	static const char *inoutstr[] = {
		"OUT",
		"IN ",
	};
	screen.Print(x, y++, "Port Direction: PA=%s PB=%s PC(H)=%s PC(L)=%s",
		inoutstr[PortADir()],
		inoutstr[PortBDir()],
		inoutstr[PortCHDir()],
		inoutstr[PortCLDir()]);

	return y;
}

//
// X68k PPI
//

// InsideOut p.135
static const busdata ppi_wait = busdata::Wait(19 * 40_nsec);

// コンストラクタ
PPIDevice::PPIDevice()
	: inherited(OBJ_PPI)
{
}

// デストラクタ
PPIDevice::~PPIDevice()
{
}

// 初期化
bool
PPIDevice::Init()
{
	adpcm = GetADPCMDevice();

	return true;
}

busdata
PPIDevice::ReadPort(uint32 offset)
{
	busdata data;

	switch (offset) {
	 case 0:
	 case 1:
		data = 0xff;
		break;
	 case 2:
		data = GetPC();
		putlog(1, "PortC -> $%02x", data.Data());
		break;
	 case 3:
		putlog(0, "Read $e9a007 Ctrl (NOT IMPLEMENTED)");
		data = 0xff;
		break;
	 default:
		VMPANIC("corrupted offset=%u", offset);
	}

	data |= ppi_wait;
	data |= BusData::Size1;
	return data;
}

busdata
PPIDevice::WritePort(uint32 offset, uint32 data)
{
	switch (offset) {
	 case 0:
	 case 1:
		break;
	 case 2:	// PPI PortC
		for (int i = 0; i < 8; i++) {
			SetPC(i, data & 1);
			data >>= 1;
		}
		break;
	 case 3:
		WriteCtrl(data);
		break;
	 default:
		VMPANIC("corrupted offset=%u", offset);
	}
	busdata r = ppi_wait;
	r |= BusData::Size1;
	return r;
}

busdata
PPIDevice::PeekPort(uint32 offset)
{
	// XXX
	return 0xff;
}

// PortC 読み込み
uint32
PPIDevice::GetPC() const
{
	uint32 data = 0;

	// パンの状態はこちらで持っていない。
	if (adpcm->GetPanOutR() == false) {
		data |= 0x01;
	}
	if (adpcm->GetPanOutL() == false) {
		data |= 0x02;
	}

	// サンプリングレートは書き込み値をこちらでも持っている(ADPCMにもある)。
	data |= adpcm_rate << 2;

	// bit4: PC4 (通常 0)
	// bit5: PC5 (通常 0)
	// bit6: IOA5 (1=ジョイスティック#1のAが押された状態にする, 0=通常)
	// bit6: IOA6 (1=ジョイスティック#1のBが押された状態にする, 0=通常)

	return data;
}

// PortC 書き込み
void
PPIDevice::SetPC(uint pc, uint val)
{
	switch (pc) {
	 case 0:	// ADPCM PAN Left Off
		adpcm->SetPanOutL(!val);
		return;
	 case 1:	// ADPCM PAN Right Off
		adpcm->SetPanOutR(!val);
		return;
	 case 2:	// ADPCM サンプリングレート
		adpcm_rate &= ~1U;
		adpcm_rate |= val ? 1U : 0;
		adpcm->SetRate(adpcm_rate);
		return;
	 case 3:	// ADPCM サンプリングレート
		adpcm_rate &= ~2U;
		adpcm_rate |= val ? 2U : 0;
		adpcm->SetRate(adpcm_rate);
		return;

	 case 4:	// ジョイスティック #1 コントロール
	 case 5:	// ジョイスティック #2 コントロール
	 case 6:	// ジョイスティック #1 ボタン A
	 case 7:	// ジョイスティック #1 ボタン B
		putlog(1, "SetPC(%u) <- %u (NOT IMPLEMENTED)", pc, val);
		return;
	 default:
		VMPANIC("corrupted pc=%u", pc);
	}
}

//
// LUNA PIO0
//
// 仕様は 0x49000000 から 4バイトだが、
// LUNA-I   では 0x48000000 から 0x4bffffff まで 4バイト単位のミラーが見える。
// LUNA-88K では 0x48000000 から 0x49ffffff までが
// (こちらはロングワード配置なので) 16バイト単位のミラーに見える。
//
// PIO から読み出せる値のビットの並び順が機種によって異なる。
// LUNA-I   では DIPSW 1番が LSB、8番が MSB の並び順、
// LUNA-88K では DIPSW 1番が MSB、8番が LSB の並び順。
// 値は、並び順に関わらずどちらも
// %0 が false = down = "0"、%1 が true = up = "1" である。
//
//    #1 #2 #3 #4 #5 #6 #7 #8
//  +-------------------------+
//  |    [] [] [] [] [] [] [] | 本体外観
//  | []                      |
//  +-------------------------+
//
// luna-dipsw1 = "01111111"    ← 設定ファイル
//                ||||||||     ↓ このクラスのメンバ変数
//                |||||||+- dipsw1[7] = true;
//                ||||||+-- dipsw1[6] = true;
//                |||||+--- dipsw1[5] = true;
//                ||||+---- dipsw1[4] = true;
//                |||+----- dipsw1[3] = true;
//                ||+------ dipsw1[2] = true;
//                |+------- dipsw1[1] = true;
//                +-------- dipsw1[0] = false;
//
//              b7   b6   b5   b4   b3   b2   b1   b0
//            +----+----+----+----+----+----+----+----+
// LUNA-I     | #8 | #7 | #6 | #5 | #4 | #3 | #2 | #1 |
// PortA      |  1 |  1 |  1 |  1 |  1 |  1 |  1 |  0 | %1=UP, %0=DOWN
//            +----+----+----+----+----+----+----+----+
//
//              b7   b6   b5   b4   b3   b2   b1   b0
//            +----+----+----+----+----+----+----+----+
// LUNA-88K   | #1 | #2 | #3 | #4 | #5 | #6 | #7 | #8 |
// PortA      |  0 |  1 |  1 |  1 |  1 |  1 |  1 |  1 | %1=UP, %0=DOWN
//            +----+----+----+----+----+----+----+----+
//
// LUNA-I:
// #1-1 up なら UNIX をロードして移行。down なら ROM モニタで起動。
// #1-2 up ならディスプレイコンソール。down ならシリアルコンソール。
// #1-3 up - color display, down - force to have monochrome display
// #1-4 up - no write verification,
//      down - verification on every harddisk write operation
// #1-5 up - OS is UniOS-U (SystemV/COFF),
//      down - OS is UniOS (4.3BSD/a.out OMAGIC)
// #1-6 down - force monochrome display?
// #1-7 up - boot from local device, down - boot from network
// #1-8 up - normal boot, down - start diagnostics と書いてあるが
//      down にすると画面真っ暗で詳細不明。
//
// #2-x すべて up にすると /boot が自動的にカーネルを起動。一つでも down が
//      あれば /boot がプロンプトで停止。これは ROM ではなく NetBSD のブート
//      ローダの話。
//      どこか由来のソースコードらしいがたぶんビット演算を間違えている。
//
// LUNA-88K:
// #1-1 up なら ROM モニタで停止、down ならマルチユーザブート。
//      OpenBSD カーネルも up なら UserKernelConfig で止まる模様?。
// #1-2 up なら外付けシリアルコンソール、down なら内蔵ビットマップ。
// #1-3..#1-7 は不明?
// #1-8 down なら自己診断後電源オフするようだ。
// #2-x はユーザ(?)

// コンストラクタ
PIO0Device::PIO0Device()
	: inherited(OBJ_PIO0)
{
	// LUNA-I と LUNA-88K とでポートのビット並びが逆。どうして…
	if (gMainApp.IsLUNA88K()) {
		msb_first = true;
	} else {
		msb_first = false;
	}

	// PIO0/PIO1
	monitor = gMonitorManager->Regist(ID_MONITOR_PIO, this);
	monitor->SetCallback(&PIO0Device::MonitorScreen);
	monitor->SetSize(76, 15);
}

// デストラクタ
PIO0Device::~PIO0Device()
{
}

// 初期化
bool
PIO0Device::Init()
{
	interrupt = GetInterruptDevice();
	pio1 = GetPIO1Device();

	return true;
}

// リセット
void
PIO0Device::ResetHard(bool poweron)
{
	auto dipswdev = GetDipswDevice();

	// XXX 本当は XP から割り込みが上がったタイミングとからしいけど、
	//     XP が出来るまでは当面リセットでラッチにしておく。
	for (int i = 0; i < 8; i++) {
		dipsw1[i] = dipswdev->Get(i);
		dipsw2[i] = dipswdev->Get(i + 8);
	}

	// XXX どうなる?
	for (int i = 0; i < intreq.size(); i++) {
		intreq[i] = false;
		inten[i] = false;
	}
}

busdata
PIO0Device::ReadPort(uint32 offset)
{
	busdata data;

	switch (offset) {
	 case 0:
		// PortA
		data = GetPA();

		// 割り込み要求(High)をクリア
		intreq[INT_H] = false;
		ChangeInterrupt();

		putlog(1, "DIP-SW1 -> $%02x", data.Data());
		break;

	 case 1:
		// PortB
		data = GetPB();

		// 割り込み要求(Low)をクリア
		intreq[INT_L] = false;
		ChangeInterrupt();

		putlog(1, "DIP-SW2 -> $%02x", data.Data());
		break;

	 case 2:
		// PortC
		data = GetPC();
		putlog(1, "PortC -> $%02x", data.Data());
		break;

	 case 3:
		data = ReadCtrl();
		putlog(1, "Ctrl -> $%02x", data.Data());
		break;

	 default:
		VMPANIC("corrupted offset=%u", offset);
	}

	// XXX wait?
	data |= BusData::Size1;
	return data;
}

busdata
PIO0Device::WritePort(uint32 offset, uint32 data)
{
	switch (offset) {
	 case 0:	// PIO0 PortA: DIPSW1 (入力のみ)
	 case 1:	// PIO0 PortB: DIPSW2 (入力のみ)
		break;

	 case 2:	// PIO0 PortC: ホスト割り込み制御
		for (int i = 0; i < 8; i++) {
			SetPC(i, data & 1);
			data >>= 1;
		}
		break;

	 case 3:	// コントロール
		WriteCtrl(data);
		break;

	 default:
		VMPANIC("corrupted offset=%u", offset);
	}

	// XXX wait?
	busdata r = BusData::Size1;
	return r;
}

busdata
PIO0Device::PeekPort(uint32 offset)
{
	switch (offset) {
	 case 0:
		return GetPA();

	 case 1:
		return GetPB();

	 case 2:
		return GetPC();

	 case 3:
		return ReadCtrl();

	 default:
		return 0xff;
	}
}

// PIO0 が PIO0/PIO1 のモニタ両方を担当する。
void
PIO0Device::MonitorScreen(Monitor *, TextScreen& screen)
{
	int y;

	screen.Clear();

	// PIO0
	screen.Puts(0, 0, "PIO0 (BaseAddr: $49000000)");
	y = MonitorScreenCommon(screen, 1);

	// PIO0 固有部
	uint32 pa = GetPA();
	uint32 pb = GetPB();
	screen.Print(1, y,     "PortA(DIP-SW1)=$%02x:", pa);
	screen.Print(1, y + 1, "PortB(DIP-SW2)=$%02x:", pb);
	for (int i = 0; i < 8; i++) {
		// この図は bit7..0 をこの順で表示するので bitpos の向きは固定。
		int bitpos = 7 - i;
		// DIPSW の物理位置 #1 をバイトの下にするか上にするかが機種によって
		// 違う。どうして…。
		int label = (msb_first ? i : bitpos) + 1;

		bool d1 = (pa & (1U << bitpos));
		bool d2 = (pb & (1U << bitpos));
		screen.Print(21 + i * 7, y,     TA::OnOff(d1), "DIP1-%u", label);
		screen.Print(21 + i * 7, y + 1, TA::OnOff(d2), "DIP2-%u", label);
	}
	y += 2;

	uint32 pc = GetPC();
	screen.Print(1, y, "PortC(XP etc.)=$%02x:", pc);
	static const char * const pcstr[] = {
		"XP RST", "PARITY", "------", "INT5EN",
		"INT5RQ", "INT1EN", "------", "INT1RQ",
	};
	for (int i = 0; i < 8; i++) {
		screen.Puts(21 + i * 7, y, TA::OnOff(pc & (1U << (7 - i))), pcstr[i]);
	}
	y++;

	// PIO1 移譲
	y++;
	pio1->MonitorScreenPIO1(screen, y);
}

// PortA (DIP-SW 1) 読み込み
uint32
PIO0Device::GetPA() const
{
	uint32 data = 0;
	for (int i = 0; i < 8; i++) {
		data |= ((int)dipsw1[i]) << i;
	}
	if (msb_first) {
		data = bitrev8(data);
	}
	return data & 0xff;
}

// PortB (DIP-SW 2) 読み込み
uint32
PIO0Device::GetPB() const
{
	uint32 data = 0;
	for (int i = 0; i < 8; i++) {
		data |= ((int)dipsw2[i]) << i;
	}
	if (msb_first) {
		data = bitrev8(data);
	}
	return data & 0xff;
}

// PortC 読み込み
uint32
PIO0Device::GetPC() const
{
	uint32 data;

	data = 0x00	// XXX 空きビットは何が読めるか?
		 | (intreq[INT_L]	? 0x01 : 0)
		 | (inten[INT_L]	? 0x04 : 0)
		 | (intreq[INT_H]	? 0x08 : 0)
		 | (inten[INT_H]	? 0x10 : 0)
		 | (parity			? 0x40 : 0)
		 | (xp_reset		? 0x80 : 0);
	return data;
}

// PortC 書き込み
void
PIO0Device::SetPC(uint pc, uint val)
{
	switch (pc) {
	 case 0:
		// INT1 割り込み要求 (Read Only)
		return;
	 case 1:
		// Not Connected.
		return;
	 case 2:
		inten[INT_L] = val;
		ChangeInterrupt();
		return;
	 case 3:
		// INT5 割り込み要求 (Read Only)
		return;
	 case 4:
		inten[INT_H] = val;
		ChangeInterrupt();
		return;
	 case 5:
		// Not Connected.
		return;
	 case 6:
		// パリティ有効/無効
		// 使わないので無視するが読み返しのために覚えておく
		parity = val;
		return;
	 case 7:
		// XP リセット
		if ((val ^ xp_reset)) {
			xp_reset = val;
			putlog(1, "XP Reset %u", xp_reset);
			auto mpu64180 = GetMPU64180Device();
			mpu64180->ChangeRESET(xp_reset);
		}
		return;
	 default:
		VMPANIC("corrupted pc=%u", pc);
	}
}

// とりあえず、XP からここまではエッジトリガーで、
// ここから割り込みコントローラへはレベルトリガーとしておく。

// XP からの割り込み (High)
void
PIO0Device::InterruptXPHigh()
{
	intreq[INT_H] = true;
	ChangeInterrupt();
}


// XP からの割り込み (Low)
void
PIO0Device::InterruptXPLow()
{
	intreq[INT_L] = true;
	ChangeInterrupt();
}

// 割り込み状態を変更。
// intreq[], inten[] の変更で呼ぶこと。
void
PIO0Device::ChangeInterrupt()
{
	bool val;

	val = (inten[INT_H] && intreq[INT_H]);
	interrupt->ChangeINT(DEVICE_XPINT_HIGH, val);

	val = (inten[INT_L] && intreq[INT_L]);
	interrupt->ChangeINT(DEVICE_XPINT_LOW, val);
}


//
// LUNA PIO1
//
// 仕様は 0x4d000000 から 4バイトだが、
// LUNA-I   では 0x4c000000 から 0x4fffffff まで 4バイト単位でミラーが見える。
// LUNA-88K では 0x4c000000 から 0x4dffffff までが
// (こちらはロングワード配置なので) 16バイト単位のミラーに見える。

// コンストラクタ
PIO1Device::PIO1Device()
	: inherited(OBJ_PIO1)
{
}

// デストラクタ
PIO1Device::~PIO1Device()
{
}

// 初期化
bool
PIO1Device::Init()
{
	lcd = GetLCDDevice();
	mpu64180 = GetMPU64180Device();
	powerdev = GetPowerDevice();

	return true;
}

// リセット
void
PIO1Device::ResetHard(bool poweron)
{
	inherited::ResetHard(poweron);

	// 電源ユニットは POWF* 信号が 'L' になると 1msec 後に電源オフになる。
	// 正確には「1msec 以内に 'H' にすれば回避できる」なので、実際に電源が
	// オフになるのはそれよりもっと十分長いとは思われる。
	// 8255 リセット時、ポート C の各ピンは入力モードになっているので、
	// ハイインピーダンス。たぶん電源的にも 'H' になってると想像する。
	// なので電源オンで POWF* がハイインピーダンスなだけでは電源オフ
	// タイマーは動作しないんじゃないかな。
	power = true;
	powerdev->SetSystemPowerOn(power);
}

busdata
PIO1Device::ReadPort(uint32 offset)
{
	busdata data;

	switch (offset) {
	 case 0:
		// PIO1 PortA: LCD データ
		data = lcd->Read();
		break;

	 case 1:
		// PIO1 PortB: 無効
		data = 0xff;
		break;

	 case 2:
		// PortC
		data = lcd->Get();
		data |= xp_intreq ? 0 : 0x02;
		// 残りのビットは %1 だろうか?
		data |= 0x0d;
		putlog(1, "PortC -> $%02x", data.Data());
		break;

	 case 3:
		data = ReadCtrl();
		putlog(1, "Ctrl -> $%02x", data.Data());
		break;

	 default:
		VMPANIC("corrupted offset=%u", offset);
	}

	// XXX wait?
	data |= BusData::Size1;
	return data;
}

busdata
PIO1Device::WritePort(uint32 offset, uint32 data)
{
	switch (offset) {
	 case 0:	// PIO1 PortA: LCD データ
		lcd->Write(data);
		break;

	 case 1:	// PIO1 PortB: XP の INT0 をアサート
		// データ不問でアサートする。
		putlog(1, "Assert XP INT0");
		mpu64180->AssertINT0();
		break;

	 case 2:	// PIO1 PortC: LCD/パワーオフ制御
		for (int i = 0; i < 8; i++) {
			SetPC(i, data & 1);
			data >>= 1;
		}
		break;

	 case 3:	// コントロール
		WriteCtrl(data);
		break;

	 default:
		VMPANIC("corrupted offset=%u", offset);
	}

	// XXX wait?
	busdata r = BusData::Size1;
	return r;
}

busdata
PIO1Device::PeekPort(uint32 offset)
{
	switch (offset) {
	 case 0:	// PIO1 PortA LCD データ
		return lcd->Peek();

	 case 1:	// PIO1 PortB 無効
		return 0xff;

	 case 2:	// PIO1 PortC
		return GetPC();

	 case 3:
		return ReadCtrl();

	 default:
		return 0xff;
	}
}

// PIO0 の下請け。
void
PIO1Device::MonitorScreenPIO1(TextScreen& screen, int y)
{
	screen.Puts(0, y++, "PIO1 (BaseAddr: $4d000000)");

	// 共通部分
	y = MonitorScreenCommon(screen, y++);

	// PIO1 固有部
	screen.Print(1, y++, "PortA(LCDDATA)=$%02x", lcd->Peek());
	screen.Print(1, y++, "PortB(NotConn)=$%02x", 0xff);
	uint32 pc = GetPC();
	screen.Print(1, y, "PortC(LCD etc)=$%02x:", pc);
	static const char * const pcstr[] = {
		"LCD E",  "LCD RS", "LCD RD", "!PWOFF",
		"------", "------", "!XPINT", "------",
	};
	for (int i = 0; i < 8; i++) {
		screen.Puts(21 + i * 7, y, TA::OnOff(pc & (1U << (7 - i))), pcstr[i]);
	}
}

uint32
PIO1Device::GetPC() const
{
	uint32 data;

	data = lcd->Get();
	data |= xp_intreq ? 0 : 0x02;
	data |= power ? 0x10 : 0;
	// 残りのビットは %1 だろうか?
	data |= 0x0d;

	return data;
}

void
PIO1Device::SetPC(uint pc, uint val)
{
	switch (pc) {
	 case 0:
	 case 1:
	 case 2:
	 case 3:
		// PortC の下半分は入力ポートとして使われることを想定して
		// いるので、ここへの書き込みは起きないだろうし、無視する。
		return;

	 case 4:
		// 8255 はモード変更 (WriteCtrl) をするとポート C を 'L' にするが、
		// PC4 (POWF*) が 'L' だと電源オフ指示なので、1msec 以内に
		// ソフトウェアでこれをキャンセルしないといけない。
		// どうしてこうなった…。
		power = val;
		if (power) {
			// 電源オフ取り消し
			putlog(1, "Power-off canceled");
			powerdev->SetSystemPowerOn(true);
		} else {
			// 1msec 後に電源オフ
			putlog(1, "Power-off scheduled");
			powerdev->SetSystemPowerOn(false);
		}
		return;
	 case 5:
		lcd->SetRW(val);
		return;
	 case 6:
		lcd->SetRS(val);
		return;
	 case 7:
		lcd->SetE(val);
		return;

	 default:
		VMPANIC("corrupted pc=%u", pc);
	}
}

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.