// This controls the shift register that generates the address lines for A0..A15 for most
// chip families.  This is not used by the PromDevice8755A code.
//
// Note that this uses direct port control instead of digitalWrite calls so that the code
// can run fast enough to meet the tBLC requirements for SDP and block writes.  This
// sacrifices portability and readability for speed. //
//
// This code will only work on Arduino Uno and Nano hardware.  The ports for other
// Arduinos map to different IO pins.

#include "PromAddressDriver.h"

#define ADDR_CLK_HI     A3
#define ADDR_CLK_LO     A4
#define ADDR_DATA       A5

// Define masks for the address clk and data lines on PC3..PC5 for direct port control.
#define ADDR_CLK_HI_MASK    0x08
#define ADDR_CLK_LO_MASK    0x10
#define ADDR_DATA_MASK      0x20

// For larger ROMs, address lines A16..A18 are controlled by D10..D12 (PB2..PB4).
#define UPPER_ADDR_MASK     0x1c

// When using the 74LS595 shift registers, the RCLK lines of both shift registers can be
// connected to D13 (PB5).  Uncomment the #define SHIFT_REGISTER_IS_595 in Configure.h to
// enable the code for this.
#define RCLK_595_MASK       0x20


void PromAddressDriver::begin()
{
    // The address control pins are always outputs.
    pinMode(ADDR_DATA, OUTPUT);
    pinMode(ADDR_CLK_LO, OUTPUT);
    pinMode(ADDR_CLK_HI, OUTPUT);
    digitalWrite(ADDR_DATA, LOW);
    digitalWrite(ADDR_CLK_LO, LOW);
    digitalWrite(ADDR_CLK_HI, LOW);
    DDRB |= UPPER_ADDR_MASK | RCLK_595_MASK; // Set D10..D13 as outputs

    // To save time, the setAddress only writes the hi byte if it has changed.
    // The value used to detect the change is initialized to a non-zero value,
    // so set an initial address to avoid the the case where the first address
    // written is the 'magic' initial address.
    setAddress(0x0000);
}


// Set a 16 bit address in the two address shift registers and
// the upper bits on the extened address pins.
void PromAddressDriver::setAddress(uint32_t address)
{
    static byte lastHi = 0xca;
    static byte lastUpper = 0xca;
    byte upper = (address >> 16) & 0xff;
    byte hi = (address >> 8) & 0xff;
    byte lo = address & 0xff;

    if (upper != lastUpper)
    {
        setUpperAddress(upper);
        lastUpper = upper;
    }
    if (hi != lastHi)
    {
        setAddressRegister(ADDR_CLK_HI, hi);
        lastHi = hi;
    }
    setAddressRegister(ADDR_CLK_LO, lo);
}


void PromAddressDriver::setUpperAddress(byte addr)
{
    // Set the upper address on pins D10..D12.
    PORTB = (PORTB & ~UPPER_ADDR_MASK) | ((addr << 2) & UPPER_ADDR_MASK);
}


// Shift an 8-bit value into one of the address shift registers.  Note that
// the data pins are tied together, selecting the high or low address register
// is a matter of using the correct clock pin to shift the data in.
void PromAddressDriver::setAddressRegister(uint8_t clkPin, byte addr)
{
    byte mask = 0;
    if (clkPin == ADDR_CLK_HI)
        mask = ADDR_CLK_HI_MASK;
    else if (clkPin == ADDR_CLK_LO)
        mask = ADDR_CLK_LO_MASK;

    // Make sure the clock is low to start.
    PORTC &= ~mask;

    // Shift 8 bits in, starting with the MSB.
    for (int ix = 0; (ix < 8); ix++)
    {
        // Set the data bit
        if (addr & 0x80)
        {
            PORTC |= ADDR_DATA_MASK;
        }
        else
        {
            PORTC &= ~ADDR_DATA_MASK;
        }

        // Toggle the clock high then low
        PORTC |= mask;
        delayMicroseconds(3);
        PORTC &= ~mask;
        addr <<= 1;
    }

    // Toggle the RCLK pin to output the data for 74LS595 shift registers.  This pin is
    // not connected when using 74LS164 shift registers.
    PORTB &= ~RCLK_595_MASK;
    delayMicroseconds(1);
    PORTB |= RCLK_595_MASK;
    delayMicroseconds(1);
    PORTB &= ~RCLK_595_MASK;
}
