pi1541/iec_bus.h
2018-05-20 14:53:34 +10:00

602 lines
No EOL
19 KiB
C++

// Pi1541 - A Commodore 1541 disk drive emulator
// Copyright(C) 2018 Stephen White
//
// This file is part of Pi1541.
//
// Pi1541 is free software : you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Pi1541 is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Pi1541. If not, see <http://www.gnu.org/licenses/>.
#ifndef IEC_BUS_H
#define IEC_BUS_H
#include "debug.h"
#include "m6522.h"
#include "rpi-gpio.h"
#include "rpiHardware.h"
#define INPUT_BUTTON_DEBOUNCE_THRESHOLD 20000
#define INPUT_BUTTON_REPEAT_THRESHOLD 460000
// DIN ATN is inverted and then connected to pb7 and ca1
// - also input to xor with ATNAout pb4
// - output of xor is inverted and connected to DIN pin 5 DATAout (OC so can only pull low)
// VIA ATNin pb7 input to xor
// VIA ATNin ca1 output from inverted DIN 3 ATN
// DIN DATA is inverted and then connected to pb1
// VIA DATAin pb0 output from inverted DIN 5 DATA
// VIA DATAout pb1 inverted and then connected to DIN DATA
// DIN CLK is inverted and then connected to pb2
// VIA CLKin pb2 output from inverted DIN 4 CLK
// VIA CLKout pb3 inverted and then connected to DIN CLK
// $1800
// PB 0 data in
// PB 1 data out
// PB 2 clock in
// PB 3 clock out
// PB 4 ATNA
// PB 5,6 device address
// PB 7,CA1 ATN IN
// If ATN and ATNA are out of sync
// - VIA's DATA IN will automatically set high and hence the DATA line will be pulled low (activated)
// If ATN and ATNA are in sync
// - the output from the XOR gate will be low and the output of its inverter will go high
// - when this occurs the DATA line must be still able to be pulled low via the PC or VIA's inverted PB1 (DATA OUT)
//
// Therefore in the same vein if PB7 is set to output it could cause the input of the XOR to be pulled low
//
enum PIGPIO
{
// Original Non-split lines
PIGPIO_ATN = 2, // 3
PIGPIO_DATA = 18, // 12
PIGPIO_CLOCK = 17, // 11
PIGPIO_SRQ = 19, // 35
PIGPIO_RESET = 3, // 5
// Pinout for those that want to split the lines (and the common ones like buttons, sound and LED)
// 0 IDSC //28
// 1 IDSD //27
// 2 I2C_SDA //3
// 3 I2C_CLK //5
PIGPIO_IN_BUTTON4 = 4, // 07 Common
PIGPIO_IN_BUTTON5 = 5, // 29 Common
PIGPIO_OUT_RESET = 6, // 31
// 7 SPI0_CS1 //26
// 8 SPI0_CS0 //24
// 9 SPI0_MISO //21
// 10 SPI0_MOSI //19
// 11 SPI0_CLK //23
PIGPIO_OUT_ATN = 12, // 32
PIGPIO_OUT_SOUND = 13, // 33 Common
// 14 TX //8
// 15 RX //10
PIGPIO_OUT_LED = 16, // 36 Common
PIGPIO_OUT_CLOCK = 17, // 11
PIGPIO_OUT_DATA = 18, // 12
PIGPIO_OUT_SRQ = 19, // 35
PIGPIO_IN_RESET = 20, // 38
PIGPIO_IN_SRQ = 21, // 40
PIGPIO_IN_BUTTON2 = 22, // 15 Common
PIGPIO_IN_BUTTON3 = 23, // 16 Common
PIGPIO_IN_ATN = 24, // 18
PIGPIO_IN_DATA = 25, // 22
PIGPIO_IN_CLOCK = 26, // 37
PIGPIO_IN_BUTTON1 = 27 // 13 Common
};
enum PIGPIOMasks
{
// Non-split lines
//PIGPIO_MASK_IN_ATN = 1 << PIGPIO_ATN,
//PIGPIO_MASK_IN_DATA = 1 << PIGPIO_DATA,
//PIGPIO_MASK_IN_CLOCK = 1 << PIGPIO_CLOCK,
//PIGPIO_MASK_IN_SRQ = 1 << PIGPIO_SRQ,
//PIGPIO_MASK_IN_RESET = 1 << PIGPIO_RESET,
// Split lines
//PIGPIO_MASK_IN_ATN = 1 << PIGPIO_IN_ATN,
//PIGPIO_MASK_IN_DATA = 1 << PIGPIO_IN_DATA,
//PIGPIO_MASK_IN_CLOCK = 1 << PIGPIO_IN_CLOCK,
//PIGPIO_MASK_IN_SRQ = 1 << PIGPIO_IN_SRQ,
//PIGPIO_MASK_IN_RESET = 1 << PIGPIO_IN_RESET,
PIGPIO_MASK_IN_BUTTON1 = 1 << PIGPIO_IN_BUTTON1,
PIGPIO_MASK_IN_BUTTON2 = 1 << PIGPIO_IN_BUTTON2,
PIGPIO_MASK_IN_BUTTON3 = 1 << PIGPIO_IN_BUTTON3,
PIGPIO_MASK_IN_BUTTON4 = 1 << PIGPIO_IN_BUTTON4,
PIGPIO_MASK_IN_BUTTON5 = 1 << PIGPIO_IN_BUTTON5,
};
static const unsigned ButtonPinFlags[5] = { PIGPIO_MASK_IN_BUTTON1, PIGPIO_MASK_IN_BUTTON2, PIGPIO_MASK_IN_BUTTON3, PIGPIO_MASK_IN_BUTTON4, PIGPIO_MASK_IN_BUTTON5 };
///////////////////////////////////////////////////////////////////////////////////////////////
// Original Non-split lines
///////////////////////////////////////////////////////////////////////////////////////////////
// I kind of stuffed up selecting what pins should be what.
// Originally I wanted the hardware interface to be as simple as possible and was focused on all the connections down one end of the Pi header.
// Also, originally I was only worried about ATN, DATA, CLOCK and RESET. Buttons were optional. With DATA and CLOCK only needing to go output.
// With hindsight (now wanting to support NIBTools ATN should have been put in the same pin group as DATA and CLOCK);
// This now requires 2 GPIO writes as PIN2 is in SEL0 and pins 17 and 18 are in SEL1.
//GPFSEL0
// 9 8 7 6 5 4 3 2 1 0
// 000 000 000 000 000 000 000 001 000 000
// To support NIBTools ATN can be made an output as well
static const u32 PI_OUTPUT_MASK_GPFSEL0 = ~((1 << (PIGPIO_ATN * 3)));
//GPFSEL1 RX TX
// 19 18 17 16 15 14 13 12 11 10
// 000 001 001 000 000 000 000 000 000 000 (bits 21 and 24 for GPIO 17 and 18 as outputs)
//static const u32 PI_OUTPUT_MASK_GPFSEL1 = ~((1 << 21) | (1 << 24));
// 000 001 001 001 000 000 001 000 000 000 (bits 21 and 24 for GPIO 17 and 18 as outputs)
//static const u32 PI_OUTPUT_MASK_GPFSEL1 = ~((1 << ((PIGPIO_OUT_SOUND - 10) * 3)) | (1 << ((PIGPIO_OUT_LED - 10) * 3)) | (1 << ((PIGPIO_CLOCK - 10) * 3)) | (1 << ((PIGPIO_DATA - 10) * 3)));
static const u32 PI_OUTPUT_MASK_GPFSEL1 = ~((1 << ((PIGPIO_CLOCK - 10) * 3)) | (1 << ((PIGPIO_DATA - 10) * 3)));
///////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////
// Pinout for those that want to split the lines (and the common ones like buttons, sound and LED)
///////////////////////////////////////////////////////////////////////////////////////////////
// OUT (need 7)
// CLK
// DATA
// SRQ
// ATN
// RST
// LED
// SND
// IN (need 10)
// CLK
// DATA
// ATN
// RST
// SRQ
// BTN1
// BTN2
// BTN3
// BTN4
// BTN5
//GPFSEL0
// S S S
// P P P I I
// I I I 2 2
// 0 0 0 C C
// M C C S S I I
// I E E C D D D
// S 0 1 W R R L A S S
// O RST B5 B4 1 1 D C
// 9 8 7 6 5 4 3 2 1 0
//GPFSEL1
// RX TX S S
// RX TX P P
// RX TX I I
// RX TX 0 0
// RX TX M
// RX TX C O
// W W W W RX TX W W L S
// SRQ DTA CLK LED RX TX SND ATN K I
// 19 18 17 16 15 14 13 12 11 10
//GPFSEL2
// X X
// X X
// X X
// X X
// X X
// X X
// X X R R R R R R R R
// X X B1 CLK DTA ATN B3 B2 SRQ RST
// 29 28 27 26 25 24 23 22 21 20
// 000 000 000 000 000 000 000 000 000 000
///////////////////////////////////////////////////////////////////////////////////////////////
enum VIAPortPins
{
VIAPORTPINS_DATAIN = 0x01, //pb0
VIAPORTPINS_DATAOUT = 0x02, //pb1
VIAPORTPINS_CLOCKIN = 0x04, //pb2
VIAPORTPINS_CLOCKOUT = 0x08,//pb3
VIAPORTPINS_ATNAOUT = 0x10, //pb4
VIAPORTPINS_DEVSEL0 = 0x20, //pb5
VIAPORTPINS_DEVSEL1 = 0x40, //pb5
VIAPORTPINS_ATNIN = 0x80 //bp7
};
typedef bool(*CheckStatus)();
class IEC_Bus
{
public:
static inline void Initialise(void)
{
volatile int index; // Force a real delay in the loop below.
// Clear all outputs to 0
write32(ARM_GPIO_GPCLR0, 0xFFFFFFFF);
if (!splitIECLines)
{
// This means that when any pin is turn to output it will output a 0 and pull lines low (ie an activation state on the IEC bus)
// Note: on the IEC bus you never output a 1 you simply tri state and it will be pulled up to a 1 (ie inactive state on the IEC bus) if no one else is pulling it low.
myOutsGPFSEL0 = read32(ARM_GPIO_GPFSEL0);
myOutsGPFSEL1 = read32(ARM_GPIO_GPFSEL1);
myOutsGPFSEL1 |= (1 << ((PIGPIO_OUT_LED - 10) * 3));
myOutsGPFSEL1 |= (1 << ((PIGPIO_OUT_SOUND - 10) * 3));
}
else
{
RPI_SetGpioPinFunction((rpi_gpio_pin_t)PIGPIO_IN_BUTTON4, FS_INPUT);
RPI_SetGpioPinFunction((rpi_gpio_pin_t)PIGPIO_IN_BUTTON5, FS_INPUT);
RPI_SetGpioPinFunction((rpi_gpio_pin_t)PIGPIO_IN_RESET, FS_INPUT);
RPI_SetGpioPinFunction((rpi_gpio_pin_t)PIGPIO_IN_SRQ, FS_INPUT);
RPI_SetGpioPinFunction((rpi_gpio_pin_t)PIGPIO_IN_BUTTON2, FS_INPUT);
RPI_SetGpioPinFunction((rpi_gpio_pin_t)PIGPIO_IN_BUTTON3, FS_INPUT);
RPI_SetGpioPinFunction((rpi_gpio_pin_t)PIGPIO_IN_ATN, FS_INPUT);
RPI_SetGpioPinFunction((rpi_gpio_pin_t)PIGPIO_IN_DATA, FS_INPUT);
RPI_SetGpioPinFunction((rpi_gpio_pin_t)PIGPIO_IN_CLOCK, FS_INPUT);
RPI_SetGpioPinFunction((rpi_gpio_pin_t)PIGPIO_IN_BUTTON1, FS_INPUT);
RPI_SetGpioPinFunction((rpi_gpio_pin_t)PIGPIO_OUT_RESET, FS_OUTPUT);
RPI_SetGpioPinFunction((rpi_gpio_pin_t)PIGPIO_OUT_ATN, FS_OUTPUT);
RPI_SetGpioPinFunction((rpi_gpio_pin_t)PIGPIO_OUT_SOUND, FS_OUTPUT);
RPI_SetGpioPinFunction((rpi_gpio_pin_t)PIGPIO_OUT_LED, FS_OUTPUT);
RPI_SetGpioPinFunction((rpi_gpio_pin_t)PIGPIO_OUT_CLOCK, FS_OUTPUT);
RPI_SetGpioPinFunction((rpi_gpio_pin_t)PIGPIO_OUT_DATA, FS_OUTPUT);
RPI_SetGpioPinFunction((rpi_gpio_pin_t)PIGPIO_OUT_SRQ, FS_OUTPUT);
}
// Set up audio.
write32(CM_PWMDIV, CM_PASSWORD + 0x2000);
write32(CM_PWMCTL, CM_PASSWORD + CM_ENAB + CM_SRC_OSCILLATOR); // Use Default 100MHz Clock
// Set PWM
write32(PWM_RNG1, 0x1B4); // 8bit 44100Hz Mono
write32(PWM_RNG2, 0x1B4);
write32(PWM_CTL, PWM_USEF2 + PWM_PWEN2 + PWM_USEF1 + PWM_PWEN1 + PWM_CLRF1);
int buttonCount = sizeof(ButtonPinFlags) / sizeof(unsigned);
for (index = 0; index < buttonCount; ++index)
{
InputButton[index] = false;
InputButtonPrev[index] = false;
validInputCount[index] = 0;
inputRepeatThreshold[index] = INPUT_BUTTON_REPEAT_THRESHOLD;
inputRepeat[index] = 0;
inputRepeatPrev[index] = 0;
}
// Enable the internal pullups for the input button pins using the method described in BCM2835-ARM-Peripherals manual.
RPI_GpioBase->GPPUD = 2;
for (index = 0; index < 150; ++index)
{
}
RPI_GpioBase->GPPUDCLK0 = PIGPIO_MASK_IN_BUTTON1 | PIGPIO_MASK_IN_BUTTON2 | PIGPIO_MASK_IN_BUTTON3 | PIGPIO_MASK_IN_BUTTON4 | PIGPIO_MASK_IN_BUTTON5;
for (index = 0; index < 150; ++index)
{
}
RPI_GpioBase->GPPUD = 0;
RPI_GpioBase->GPPUDCLK0 = 0;
}
static inline void UpdateButton(int index, unsigned gplev0)
{
bool inputcurrent = (gplev0 & ButtonPinFlags[index]) == 0;
InputButtonPrev[index] = InputButton[index];
inputRepeatPrev[index] = inputRepeat[index];
if (inputcurrent)
{
validInputCount[index]++;
if (validInputCount[index] == INPUT_BUTTON_DEBOUNCE_THRESHOLD)
{
InputButton[index] = true;
inputRepeatThreshold[index] = INPUT_BUTTON_DEBOUNCE_THRESHOLD + INPUT_BUTTON_REPEAT_THRESHOLD;
inputRepeat[index]++;
}
if (validInputCount[index] == inputRepeatThreshold[index])
{
inputRepeat[index]++;
inputRepeatThreshold[index] += INPUT_BUTTON_REPEAT_THRESHOLD / inputRepeat[index];
}
}
else
{
InputButton[index] = false;
validInputCount[index] = 0;
inputRepeatThreshold[index] = INPUT_BUTTON_REPEAT_THRESHOLD;
inputRepeat[index] = 0;
inputRepeatPrev[index] = 0;
}
}
static void Read(void);
static void WaitUntilReset(void)
{
unsigned gplev0;
do
{
gplev0 = read32(ARM_GPIO_GPLEV0);
Resetting = (gplev0 & PIGPIO_MASK_IN_RESET) == (invertIECInputs ? PIGPIO_MASK_IN_RESET : 0);
if (Resetting)
IEC_Bus::WaitMicroSeconds(100);
}
while (Resetting);
}
// Out going
static void PortB_OnPortOut(void* pUserData, unsigned char status)
{
bool oldDataSetToOut = DataSetToOut;
bool oldClockSetToOut = ClockSetToOut;
// These are the values the VIA is trying to set the outputs to
VIA_Atna = (status & (unsigned char)VIAPORTPINS_ATNAOUT) != 0;
VIA_Data = (status & (unsigned char)VIAPORTPINS_DATAOUT) != 0; // VIA DATAout PB1 inverted and then connected to DIN DATA
VIA_Clock = (status & (unsigned char)VIAPORTPINS_CLOCKOUT) != 0; // VIA CLKout PB3 inverted and then connected to DIN CLK
// Emulate the XOR gate UD3
AtnaDataSetToOut = (VIA_Atna != PI_Atn);
if (AtnaDataSetToOut)
{
// if the output of the XOR gate is high (ie VIA_Atna != PI_Atn) then this is inverted and pulls DATA low (activating it)
PI_Data = true;
if (VIA) VIA->GetPortB()->SetInput(VIAPORTPINS_DATAIN, true); // simulate the read in software
}
if (VIA)
{
// If the VIA's data and clock outputs ever get set to inputs the real hardware reads these lines as asserted.
bool PB1SetToInput = (VIA->GetPortB()->GetDirection() & 2) == 0;
bool PB3SetToInput = (VIA->GetPortB()->GetDirection() & 8) == 0;
if (PB1SetToInput) VIA_Data = true;
if (PB3SetToInput) VIA_Clock = true;
}
ClockSetToOut = VIA_Clock;
DataSetToOut = VIA_Data;
if (!oldDataSetToOut && DataSetToOut)
{
PI_Data = true;
if (VIA) VIA->GetPortB()->SetInput(VIAPORTPINS_DATAOUT, true); // simulate the read in software
}
if (!oldClockSetToOut && ClockSetToOut)
{
PI_Clock = true;
if (VIA) VIA->GetPortB()->SetInput(VIAPORTPINS_CLOCKIN, true); // simulate the read in software
}
}
static inline void RefreshOuts(void)
{
unsigned set = 0;
unsigned clear = 0;
if (!splitIECLines)
{
unsigned outputs = 0;
if (AtnaDataSetToOut || DataSetToOut) outputs |= (FS_OUTPUT << ((PIGPIO_DATA - 10) * 3));
if (ClockSetToOut) outputs |= (FS_OUTPUT << ((PIGPIO_CLOCK - 10) * 3));
unsigned nValue = (myOutsGPFSEL1 & PI_OUTPUT_MASK_GPFSEL1) | outputs;
write32(ARM_GPIO_GPFSEL1, nValue);
}
else
{
if (AtnaDataSetToOut || DataSetToOut) set |= 1 << PIGPIO_OUT_DATA;
else clear |= 1 << PIGPIO_OUT_DATA;
if (ClockSetToOut) set |= 1 << PIGPIO_OUT_CLOCK;
else clear |= 1 << PIGPIO_OUT_CLOCK;
}
if (OutputLED) set |= 1 << PIGPIO_OUT_LED;
else clear |= 1 << PIGPIO_OUT_LED;
if (OutputSound) set |= 1 << PIGPIO_OUT_SOUND;
else clear |= 1 << PIGPIO_OUT_SOUND;
write32(ARM_GPIO_GPSET0, set);
write32(ARM_GPIO_GPCLR0, clear);
}
static void WaitMicroSeconds(u32 amount)
{
u32 count;
for (count = 0; count < amount; ++count)
{
unsigned before;
unsigned after;
// We try to update every micro second and use as a rough timer to count micro seconds
before = read32(ARM_SYSTIMER_CLO);
do
{
after = read32(ARM_SYSTIMER_CLO);
} while (after == before);
}
}
///////////////////////////////////////////////////////////////////////////////////////////////
// Manual methods used by IEC_Commands
static inline void AssertData()
{
if (!DataSetToOut)
{
DataSetToOut = true;
RefreshOuts();
}
}
static inline void ReleaseData()
{
if (DataSetToOut)
{
DataSetToOut = false;
RefreshOuts();
}
}
static inline void AssertClock()
{
if (!ClockSetToOut)
{
ClockSetToOut = true;
RefreshOuts();
}
}
static inline void ReleaseClock()
{
if (ClockSetToOut)
{
ClockSetToOut = false;
RefreshOuts();
}
}
static inline bool GetPI_Atn() { return PI_Atn; }
static inline bool IsAtnAsserted() { return PI_Atn; }
static inline bool IsAtnReleased() { return !PI_Atn; }
static inline bool GetPI_Data() { return PI_Data; }
static inline bool IsDataAsserted() { return PI_Data; }
static inline bool IsDataReleased() { return !PI_Data; }
static inline bool GetPI_Clock() { return PI_Clock; }
static inline bool IsClockAsserted() { return PI_Clock; }
static inline bool IsClockReleased() { return !PI_Clock; }
static inline bool GetPI_Reset() { return PI_Reset; }
static inline bool IsDataSetToOut() { return DataSetToOut; }
static inline bool IsAtnaDataSetToOut() { return AtnaDataSetToOut; }
static inline bool IsClockSetToOut() { return ClockSetToOut; }
static inline bool IsReset() { return Resetting; }
static inline void WaitWhileAtnAsserted()
{
while (IsAtnAsserted())
{
Read();
}
}
///////////////////////////////////////////////////////////////////////////////////////////////
static inline void SetSplitIECLines(bool value)
{
splitIECLines = value;
if (splitIECLines)
{
PIGPIO_MASK_IN_ATN = 1 << PIGPIO_IN_ATN;
PIGPIO_MASK_IN_DATA = 1 << PIGPIO_IN_DATA;
PIGPIO_MASK_IN_CLOCK = 1 << PIGPIO_IN_CLOCK;
PIGPIO_MASK_IN_SRQ = 1 << PIGPIO_IN_SRQ;
PIGPIO_MASK_IN_RESET = 1 << PIGPIO_IN_RESET;
}
}
static inline void SetInvertIECInputs(bool value)
{
invertIECInputs = value;
if (value)
{
PI_Atn = ~PI_Atn;
PI_Data = ~PI_Data;
PI_Clock = ~PI_Clock;
PI_Reset = ~PI_Reset;
}
}
// CA1 input ATN
// If CA1 is ever set to output
// - CA1 will start to drive pb7
// CA2, CB1 and CB2 are not connected
// - check if pulled high or low
static m6522* VIA;
static inline void Reset(void)
{
WaitUntilReset();
// VIA $1800
// CA2, CB1 and CB2 are not connected (reads as high)
// VIA $1C00
// CB1 not connected (reads as high)
VIA_Atna = false;
VIA_Data = false;
VIA_Clock = false;
DataSetToOut = false;
ClockSetToOut = false;
PI_Atn = false;
PI_Data = false;
PI_Clock = false;
AtnaDataSetToOut = (VIA_Atna != PI_Atn);
if (AtnaDataSetToOut) PI_Data = true;
RefreshOuts();
}
static bool GetInputButtonPressed(int buttonIndex) { return InputButton[buttonIndex] && !InputButtonPrev[buttonIndex]; }
static bool GetInputButtonReleased(int buttonIndex) { return !InputButton[buttonIndex] && InputButtonPrev[buttonIndex]; }
static bool GetInputButton(int buttonIndex) { return InputButton[buttonIndex]; }
static bool GetInputButtonRepeating(int buttonIndex) { return inputRepeat[buttonIndex] != inputRepeatPrev[buttonIndex]; }
static bool OutputLED;
static bool OutputSound;
private:
static bool splitIECLines;
static bool invertIECInputs;
static u32 PIGPIO_MASK_IN_ATN;
static u32 PIGPIO_MASK_IN_DATA;
static u32 PIGPIO_MASK_IN_CLOCK;
static u32 PIGPIO_MASK_IN_SRQ;
static u32 PIGPIO_MASK_IN_RESET;
static bool PI_Atn;
static bool PI_Data;
static bool PI_Clock;
static bool PI_Reset;
static bool VIA_Atna;
static bool VIA_Data;
static bool VIA_Clock;
static bool DataSetToOut;
static bool AtnaDataSetToOut;
static bool ClockSetToOut;
static bool Resetting;
static u32 myOutsGPFSEL0;
static u32 myOutsGPFSEL1;
static bool InputButton[5];
static bool InputButtonPrev[5];
static u32 validInputCount[5];
static u32 inputRepeatThreshold[5];
static u32 inputRepeat[5];
static u32 inputRepeatPrev[5];
};
#endif