mirror of
https://github.com/TomHarte/CLK.git
synced 2025-04-23 01:37:21 +00:00
Extract keyboard controller.
This commit is contained in:
parent
53135ec2c0
commit
9135402d9e
153
Machines/PCCompatible/KeyboardController.hpp
Normal file
153
Machines/PCCompatible/KeyboardController.hpp
Normal file
@ -0,0 +1,153 @@
|
||||
//
|
||||
// KeyboardController.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 07/03/2025.
|
||||
// Copyright © 2025 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "PIC.hpp"
|
||||
#include "Speaker.hpp"
|
||||
|
||||
namespace PCCompatible {
|
||||
|
||||
template <Analyser::Static::PCCompatible::Model, typename Enable = void>
|
||||
class KeyboardController;
|
||||
|
||||
template <Analyser::Static::PCCompatible::Model model>
|
||||
class KeyboardController<model, typename std::enable_if_t<is_xt(model)>> {
|
||||
public:
|
||||
KeyboardController(PICs<model> &pics, PCSpeaker &) : pics_(pics) {}
|
||||
|
||||
// KB Status Port 61h high bits:
|
||||
//; 01 - normal operation. wait for keypress, when one comes in,
|
||||
//; force data line low (forcing keyboard to buffer additional
|
||||
//; keypresses) and raise IRQ1 high
|
||||
//; 11 - stop forcing data line low. lower IRQ1 and don't raise it again.
|
||||
//; drop all incoming keypresses on the floor.
|
||||
//; 10 - lower IRQ1 and force clock line low, resetting keyboard
|
||||
//; 00 - force clock line low, resetting keyboard, but on a 01->00 transition,
|
||||
//; IRQ1 would remain high
|
||||
void set_mode(const uint8_t mode) {
|
||||
const auto last_mode = mode_;
|
||||
mode_ = Mode(mode);
|
||||
switch(mode_) {
|
||||
case Mode::NormalOperation: break;
|
||||
case Mode::NoIRQsIgnoreInput:
|
||||
pics_.pic[0].template apply_edge<1>(false);
|
||||
break;
|
||||
case Mode::Reset:
|
||||
input_.clear();
|
||||
[[fallthrough]];
|
||||
case Mode::ClearIRQReset:
|
||||
pics_.pic[0].template apply_edge<1>(false);
|
||||
break;
|
||||
}
|
||||
|
||||
// If the reset condition ends, start a counter through until reset is complete.
|
||||
if(last_mode == Mode::Reset && mode_ != Mode::Reset) {
|
||||
reset_delay_ = 15; // Arbitrarily.
|
||||
}
|
||||
}
|
||||
|
||||
void run_for(const Cycles cycles) {
|
||||
if(reset_delay_ <= 0) {
|
||||
return;
|
||||
}
|
||||
reset_delay_ -= cycles.as<int>();
|
||||
if(reset_delay_ <= 0) {
|
||||
input_.clear();
|
||||
post(0xaa);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t read() {
|
||||
pics_.pic[0].template apply_edge<1>(false);
|
||||
if(input_.empty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const uint8_t key = input_.front();
|
||||
input_.erase(input_.begin());
|
||||
if(!input_.empty()) {
|
||||
pics_.pic[0].template apply_edge<1>(true);
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
void post(const uint8_t value) {
|
||||
if(mode_ != Mode::NormalOperation || reset_delay_) {
|
||||
return;
|
||||
}
|
||||
input_.push_back(value);
|
||||
pics_.pic[0].template apply_edge<1>(true);
|
||||
}
|
||||
|
||||
private:
|
||||
enum class Mode {
|
||||
NormalOperation = 0b01,
|
||||
NoIRQsIgnoreInput = 0b11,
|
||||
ClearIRQReset = 0b10,
|
||||
Reset = 0b00,
|
||||
} mode_;
|
||||
|
||||
std::vector<uint8_t> input_;
|
||||
PICs<model> &pics_;
|
||||
|
||||
int reset_delay_ = 0;
|
||||
};
|
||||
|
||||
template <Analyser::Static::PCCompatible::Model model>
|
||||
class KeyboardController<model, typename std::enable_if_t<is_at(model)>> {
|
||||
public:
|
||||
KeyboardController(PICs<model> &pics, PCSpeaker &speaker) : pics_(pics), speaker_(speaker) {}
|
||||
|
||||
void run_for([[maybe_unused]] const Cycles cycles) {
|
||||
}
|
||||
|
||||
void post([[maybe_unused]] const uint8_t value) {
|
||||
}
|
||||
|
||||
template <typename IntT>
|
||||
void write([[maybe_unused]] const uint16_t port, [[maybe_unused]] const IntT value) {
|
||||
switch(port) {
|
||||
default:
|
||||
log_.error().append("Unimplemented AT keyboard write: %04x to %04x", value, port);
|
||||
break;
|
||||
|
||||
case 0x0061:
|
||||
// TODO:
|
||||
// b7: 1 = reset IRQ 0
|
||||
// b3: enable channel check
|
||||
// b2: enable parity check
|
||||
speaker_.set_control(value & 0x01, value & 0x02);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename IntT>
|
||||
IntT read([[maybe_unused]] const uint16_t port) {
|
||||
switch(port) {
|
||||
default:
|
||||
log_.error().append("Unimplemented AT keyboard read from %04x", port);
|
||||
break;
|
||||
|
||||
case 0x0061:
|
||||
refresh_toggle_ ^= 0x10;
|
||||
log_.info().append("AT keyboard: %02x from %04x", refresh_toggle_, port);
|
||||
return refresh_toggle_;
|
||||
}
|
||||
return IntT(~0);
|
||||
}
|
||||
|
||||
private:
|
||||
Log::Logger<Log::Source::PCCompatible> log_;
|
||||
|
||||
PICs<model> &pics_;
|
||||
PCSpeaker &speaker_;
|
||||
uint8_t refresh_toggle_ = 0;
|
||||
};
|
||||
|
||||
}
|
@ -11,6 +11,7 @@
|
||||
#include "CGA.hpp"
|
||||
#include "DMA.hpp"
|
||||
#include "FloppyController.hpp"
|
||||
#include "KeyboardController.hpp"
|
||||
#include "KeyboardMapper.hpp"
|
||||
#include "MDA.hpp"
|
||||
#include "Memory.hpp"
|
||||
@ -58,141 +59,6 @@ template <Target::VideoAdaptor adaptor> struct Adaptor;
|
||||
template <> struct Adaptor<Target::VideoAdaptor::MDA> { using type = MDA; };
|
||||
template <> struct Adaptor<Target::VideoAdaptor::CGA> { using type = CGA; };
|
||||
|
||||
template <Analyser::Static::PCCompatible::Model, typename Enable = void>
|
||||
class KeyboardController;
|
||||
|
||||
template <Analyser::Static::PCCompatible::Model model>
|
||||
class KeyboardController<model, typename std::enable_if_t<is_xt(model)>> {
|
||||
public:
|
||||
KeyboardController(PICs<model> &pics, PCSpeaker &) : pics_(pics) {}
|
||||
|
||||
// KB Status Port 61h high bits:
|
||||
//; 01 - normal operation. wait for keypress, when one comes in,
|
||||
//; force data line low (forcing keyboard to buffer additional
|
||||
//; keypresses) and raise IRQ1 high
|
||||
//; 11 - stop forcing data line low. lower IRQ1 and don't raise it again.
|
||||
//; drop all incoming keypresses on the floor.
|
||||
//; 10 - lower IRQ1 and force clock line low, resetting keyboard
|
||||
//; 00 - force clock line low, resetting keyboard, but on a 01->00 transition,
|
||||
//; IRQ1 would remain high
|
||||
void set_mode(const uint8_t mode) {
|
||||
const auto last_mode = mode_;
|
||||
mode_ = Mode(mode);
|
||||
switch(mode_) {
|
||||
case Mode::NormalOperation: break;
|
||||
case Mode::NoIRQsIgnoreInput:
|
||||
pics_.pic[0].template apply_edge<1>(false);
|
||||
break;
|
||||
case Mode::Reset:
|
||||
input_.clear();
|
||||
[[fallthrough]];
|
||||
case Mode::ClearIRQReset:
|
||||
pics_.pic[0].template apply_edge<1>(false);
|
||||
break;
|
||||
}
|
||||
|
||||
// If the reset condition ends, start a counter through until reset is complete.
|
||||
if(last_mode == Mode::Reset && mode_ != Mode::Reset) {
|
||||
reset_delay_ = 15; // Arbitrarily.
|
||||
}
|
||||
}
|
||||
|
||||
void run_for(const Cycles cycles) {
|
||||
if(reset_delay_ <= 0) {
|
||||
return;
|
||||
}
|
||||
reset_delay_ -= cycles.as<int>();
|
||||
if(reset_delay_ <= 0) {
|
||||
input_.clear();
|
||||
post(0xaa);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t read() {
|
||||
pics_.pic[0].template apply_edge<1>(false);
|
||||
if(input_.empty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const uint8_t key = input_.front();
|
||||
input_.erase(input_.begin());
|
||||
if(!input_.empty()) {
|
||||
pics_.pic[0].template apply_edge<1>(true);
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
void post(const uint8_t value) {
|
||||
if(mode_ != Mode::NormalOperation || reset_delay_) {
|
||||
return;
|
||||
}
|
||||
input_.push_back(value);
|
||||
pics_.pic[0].template apply_edge<1>(true);
|
||||
}
|
||||
|
||||
private:
|
||||
enum class Mode {
|
||||
NormalOperation = 0b01,
|
||||
NoIRQsIgnoreInput = 0b11,
|
||||
ClearIRQReset = 0b10,
|
||||
Reset = 0b00,
|
||||
} mode_;
|
||||
|
||||
std::vector<uint8_t> input_;
|
||||
PICs<model> &pics_;
|
||||
|
||||
int reset_delay_ = 0;
|
||||
};
|
||||
|
||||
template <Analyser::Static::PCCompatible::Model model>
|
||||
class KeyboardController<model, typename std::enable_if_t<is_at(model)>> {
|
||||
public:
|
||||
KeyboardController(PICs<model> &pics, PCSpeaker &speaker) : pics_(pics), speaker_(speaker) {}
|
||||
|
||||
void run_for([[maybe_unused]] const Cycles cycles) {
|
||||
}
|
||||
|
||||
void post([[maybe_unused]] const uint8_t value) {
|
||||
}
|
||||
|
||||
template <typename IntT>
|
||||
void write([[maybe_unused]] const uint16_t port, [[maybe_unused]] const IntT value) {
|
||||
switch(port) {
|
||||
default:
|
||||
log.error().append("Unimplemented AT keyboard write: %04x to %04x", value, port);
|
||||
break;
|
||||
|
||||
case 0x0061:
|
||||
// TODO:
|
||||
// b7: 1 = reset IRQ 0
|
||||
// b3: enable channel check
|
||||
// b2: enable parity check
|
||||
speaker_.set_control(value & 0x01, value & 0x02);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename IntT>
|
||||
IntT read([[maybe_unused]] const uint16_t port) {
|
||||
switch(port) {
|
||||
default:
|
||||
log.error().append("Unimplemented AT keyboard read from %04x", port);
|
||||
break;
|
||||
|
||||
case 0x0061:
|
||||
refresh_toggle_ ^= 0x10;
|
||||
log.info().append("AT keyboard: %02x from %04x", refresh_toggle_, port);
|
||||
return refresh_toggle_;
|
||||
}
|
||||
return ~0;
|
||||
}
|
||||
|
||||
private:
|
||||
PICs<model> &pics_;
|
||||
PCSpeaker &speaker_;
|
||||
uint8_t refresh_toggle_ = 0;
|
||||
};
|
||||
|
||||
template <Analyser::Static::PCCompatible::Model model>
|
||||
class PITObserver {
|
||||
public:
|
||||
@ -1046,7 +912,6 @@ private:
|
||||
int cpu_divisor_ = 0;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
using namespace PCCompatible;
|
||||
|
@ -1456,6 +1456,7 @@
|
||||
4B1FBE1F2D77AAC500BAC888 /* ProcessorByModel.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ProcessorByModel.hpp; sourceTree = "<group>"; };
|
||||
4B1FBE222D7B739700BAC888 /* FloppyController.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = FloppyController.hpp; sourceTree = "<group>"; };
|
||||
4B1FBE232D7B73C800BAC888 /* Speaker.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Speaker.hpp; sourceTree = "<group>"; };
|
||||
4B1FBE242D7B753000BAC888 /* KeyboardController.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = KeyboardController.hpp; sourceTree = "<group>"; };
|
||||
4B2005402B804AA300420C5C /* OperationMapper.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = OperationMapper.hpp; sourceTree = "<group>"; };
|
||||
4B2005422B804D6400420C5C /* ARMDecoderTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ARMDecoderTests.mm; sourceTree = "<group>"; };
|
||||
4B2005462B8BD7A500420C5C /* Registers.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Registers.hpp; sourceTree = "<group>"; };
|
||||
@ -2561,6 +2562,7 @@
|
||||
429B13632B20234B006BB4CB /* CGA.hpp */,
|
||||
4267A9C92B0D4F17008A59BB /* DMA.hpp */,
|
||||
4B1FBE222D7B739700BAC888 /* FloppyController.hpp */,
|
||||
4B1FBE242D7B753000BAC888 /* KeyboardController.hpp */,
|
||||
4267A9CA2B111ED2008A59BB /* KeyboardMapper.hpp */,
|
||||
429B13622B1FCA96006BB4CB /* MDA.hpp */,
|
||||
423820132B1A235200964EFE /* Memory.hpp */,
|
||||
|
Loading…
x
Reference in New Issue
Block a user