#ifndef _pin_magic_
#define _pin_magic_

// This header file serves two purposes:
//
// 1) Isolate non-portable MCU port- and pin-specific identifiers and
//    operations so the library code itself remains somewhat agnostic
//    (PORTs and pin numbers are always referenced through macros).
//
// 2) GCC doesn't always respect the "inline" keyword, so this is a
//    ham-fisted manner of forcing the issue to minimize function calls.
//    This sometimes makes the library a bit bigger than before, but fast++.
//    However, because they're macros, we need to be SUPER CAREFUL about
//    parameters -- for example, write8(x) may expand to multiple PORT
//    writes that all refer to x, so it needs to be a constant or fixed
//    variable and not something like *ptr++ (which, after macro
//    expansion, may increment the pointer repeatedly and run off into
//    la-la land).  Macros also give us fine-grained control over which
//    operations are inlined on which boards (balancing speed against
//    available program space).

// When using the TFT shield, control and data pins exist in set physical
// locations, but the ports and bitmasks corresponding to each vary among
// boards.  A separate set of pin definitions is given for each supported
// board type.
// When using the TFT breakout board, control pins are configurable but
// the data pins are still fixed -- making every data pin configurable
// would be much too slow.  The data pin layouts are not the same between
// the shield and breakout configurations -- for the latter, pins were
// chosen to keep the tutorial wiring manageable more than making optimal
// use of ports and bitmasks.  So there's a second set of pin definitions
// given for each supported board.

// Shield pin usage:
// LCD Data Bit :    7    6    5    4    3    2    1    0
// Digital pin #:    7    6   13    4   11   10    9    8
// Uno port/pin :  PD7  PD6  PB5  PD4  PB3  PB2  PB1  PB0
// Mega port/pin:  PH4  PH3  PB7  PG5  PB5  PB4  PH6  PH5
// Leo port/pin :  PE6  PD7  PC7  PD4  PB7  PB6  PB5  PB4
// Due port/pin : PC23 PC24 PB27 PC26  PD7 PC29 PC21 PC22
// Breakout pin usage:
// LCD Data Bit :   7   6   5   4   3   2   1   0
// Uno dig. pin :   7   6   5   4   3   2   9   8
// Uno port/pin : PD7 PD6 PD5 PD4 PD3 PD2 PB1 PB0
// Mega dig. pin:  29  28  27  26  25  24  23  22
// Mega port/pin: PA7 PA6 PA5 PA4 PA3 PA2 PA1 PA0 (one contiguous PORT)
// Leo dig. pin :   7   6   5   4   3   2   9   8
// Leo port/pin : PE6 PD7 PC6 PD4 PD0 PD1 PB5 PB4
// Due dig. pin :  40  39  38  37  36  35  34  33
// Due port/pin : PC8 PC7 PC6 PC5 PC4 PC3 PC2 PC1 (one contiguous PORT. -ish…)

// Pixel read operations require a minimum 400 nS delay from RD_ACTIVE
// to polling the input pins.  At 16 MHz, one machine cycle is 62.5 nS.
// This code burns 7 cycles (437.5 nS) doing nothing; the RJMPs are
// equivalent to two NOPs each, final NOP burns the 7th cycle, and the
// last line is a radioactive mutant emoticon.
#define DELAY7                                                                 \
  asm volatile("rjmp .+0"                                                      \
               "\n\t"                                                          \
               "rjmp .+0"                                                      \
               "\n\t"                                                          \
               "rjmp .+0"                                                      \
               "\n\t"                                                          \
               "nop"                                                           \
               "\n" ::);

#if defined(__AVR_ATmega168__) || defined(__AVR_ATmega328P__) ||               \
    defined(__AVR_ATmega328__) || defined(__AVR_ATmega8__)

// Arduino Uno, Duemilanove, etc.

#ifdef USE_ADAFRUIT_SHIELD_PINOUT

// LCD control lines:
// RD (read), WR (write), CD (command/data), CS (chip select)
#define RD_PORT PORTC /*pin A0 */
#define WR_PORT PORTC /*pin A1 */
#define CD_PORT PORTC /*pin A2 */
#define CS_PORT PORTC /*pin A3 */
#define RD_MASK B00000001
#define WR_MASK B00000010
#define CD_MASK B00000100
#define CS_MASK B00001000

// These are macros for I/O operations...

// Write 8-bit value to LCD data lines
#define write8inline(d)                                                        \
  {                                                                            \
    PORTD = (PORTD & B00101111) | ((d)&B11010000);                             \
    PORTB = (PORTB & B11010000) | ((d)&B00101111);                             \
    WR_STROBE;                                                                 \
  } // STROBEs are defined later

// Read 8-bit value from LCD data lines.  The signle argument
// is a destination variable; this isn't a function and doesn't
// return a value in the conventional sense.
#define read8inline(result)                                                    \
  {                                                                            \
    RD_ACTIVE;                                                                 \
    DELAY7;                                                                    \
    result = (PIND & B11010000) | (PINB & B00101111);                          \
    RD_IDLE;                                                                   \
  }

// These set the PORT directions as required before the write and read
// operations.  Because write operations are much more common than reads,
// the data-reading functions in the library code set the PORT(s) to
// input before a read, and restore them back to the write state before
// returning.  This avoids having to set it for output inside every
// drawing method.  The default state has them initialized for writes.
#define setWriteDirInline()                                                    \
  {                                                                            \
    DDRD |= B11010000;                                                         \
    DDRB |= B00101111;                                                         \
  }
#define setReadDirInline()                                                     \
  {                                                                            \
    DDRD &= ~B11010000;                                                        \
    DDRB &= ~B00101111;                                                        \
  }

#else // Uno w/Breakout board

#define write8inline(d)                                                        \
  {                                                                            \
    PORTD = (PORTD & B00000011) | ((d)&B11111100);                             \
    PORTB = (PORTB & B11111100) | ((d)&B00000011);                             \
    WR_STROBE;                                                                 \
  }
#define read8inline(result)                                                    \
  {                                                                            \
    RD_ACTIVE;                                                                 \
    DELAY7;                                                                    \
    result = (PIND & B11111100) | (PINB & B00000011);                          \
    RD_IDLE;                                                                   \
  }
#define setWriteDirInline()                                                    \
  {                                                                            \
    DDRD |= B11111100;                                                         \
    DDRB |= B00000011;                                                         \
  }
#define setReadDirInline()                                                     \
  {                                                                            \
    DDRD &= ~B11111100;                                                        \
    DDRB &= ~B00000011;                                                        \
  }

#endif

// As part of the inline control, macros reference other macros...if any
// of these are left undefined, an equivalent function version (non-inline)
// is declared later.  The Uno has a moderate amount of program space, so
// only write8() is inlined -- that one provides the most performance
// benefit, but unfortunately also generates the most bloat.  This is
// why only certain cases are inlined for each board.
#define write8 write8inline

#elif defined(__AVR_ATmega1281__) || defined(__AVR_ATmega2561__) ||            \
    defined(__AVR_ATmega2560__) || defined(__AVR_ATmega1280__)

// Arduino Mega, ADK, etc.

#ifdef USE_ADAFRUIT_SHIELD_PINOUT

#define RD_PORT PORTF
#define WR_PORT PORTF
#define CD_PORT PORTF
#define CS_PORT PORTF
#define RD_MASK B00000001
#define WR_MASK B00000010
#define CD_MASK B00000100
#define CS_MASK B00001000

#define write8inline(d)                                                        \
  {                                                                            \
    PORTH =                                                                    \
        (PORTH & B10000111) | (((d)&B11000000) >> 3) | (((d)&B00000011) << 5); \
    PORTB = (PORTB & B01001111) | (((d)&B00101100) << 2);                      \
    PORTG = (PORTG & B11011111) | (((d)&B00010000) << 1);                      \
    WR_STROBE;                                                                 \
  }
#define read8inline(result)                                                    \
  {                                                                            \
    RD_ACTIVE;                                                                 \
    DELAY7;                                                                    \
    result = ((PINH & B00011000) << 3) | ((PINB & B10110000) >> 2) |           \
             ((PING & B00100000) >> 1) | ((PINH & B01100000) >> 5);            \
    RD_IDLE;                                                                   \
  }
#define setWriteDirInline()                                                    \
  {                                                                            \
    DDRH |= B01111000;                                                         \
    DDRB |= B10110000;                                                         \
    DDRG |= B00100000;                                                         \
  }
#define setReadDirInline()                                                     \
  {                                                                            \
    DDRH &= ~B01111000;                                                        \
    DDRB &= ~B10110000;                                                        \
    DDRG &= ~B00100000;                                                        \
  }

#else // Mega w/Breakout board

#define write8inline(d)                                                        \
  {                                                                            \
    PORTA = (d);                                                               \
    WR_STROBE;                                                                 \
  }
#define read8inline(result)                                                    \
  {                                                                            \
    RD_ACTIVE;                                                                 \
    DELAY7;                                                                    \
    result = PINA;                                                             \
    RD_IDLE;                                                                   \
  }
#define setWriteDirInline() DDRA = 0xff
#define setReadDirInline() DDRA = 0

#endif

// All of the functions are inlined on the Arduino Mega.  When using the
// breakout board, the macro versions aren't appreciably larger than the
// function equivalents, and they're super simple and fast.  When using
// the shield, the macros become pretty complicated...but this board has
// so much code space, the macros are used anyway.  If you need to free
// up program space, some macros can be removed, at a minor cost in speed.
#define write8 write8inline
#define read8 read8inline
#define setWriteDir setWriteDirInline
#define setReadDir setReadDirInline
#define writeRegister8 writeRegister8inline
#define writeRegister16 writeRegister16inline
#define writeRegisterPair writeRegisterPairInline

#elif defined(__AVR_ATmega32U4__)

// Arduino Leonardo

#ifdef USE_ADAFRUIT_SHIELD_PINOUT

#define RD_PORT PORTF
#define WR_PORT PORTF
#define CD_PORT PORTF
#define CS_PORT PORTF
#define RD_MASK B10000000
#define WR_MASK B01000000
#define CD_MASK B00100000
#define CS_MASK B00010000

#define write8inline(d)                                                        \
  {                                                                            \
    PORTE = (PORTE & B10111111) | (((d)&B10000000) >> 1);                      \
    PORTD = (PORTD & B01101111) | (((d)&B01000000) << 1) | ((d)&B00010000);    \
    PORTC = (PORTC & B01111111) | (((d)&B00100000) << 2);                      \
    PORTB = (PORTB & B00001111) | (((d)&B00001111) << 4);                      \
    WR_STROBE;                                                                 \
  }
#define read8inline(result)                                                    \
  {                                                                            \
    RD_ACTIVE;                                                                 \
    DELAY7;                                                                    \
    result = ((PINE & B01000000) << 1) | ((PIND & B10000000) >> 1) |           \
             ((PINC & B10000000) >> 2) | ((PINB & B11110000) >> 4) |           \
             (PIND & B00010000);                                               \
    RD_IDLE;                                                                   \
  }
#define setWriteDirInline()                                                    \
  {                                                                            \
    DDRE |= B01000000;                                                         \
    DDRD |= B10010000;                                                         \
    DDRC |= B10000000;                                                         \
    DDRB |= B11110000;                                                         \
  }
#define setReadDirInline()                                                     \
  {                                                                            \
    DDRE &= ~B01000000;                                                        \
    DDRD &= ~B10010000;                                                        \
    DDRC &= ~B10000000;                                                        \
    DDRB &= ~B11110000;                                                        \
  }

#else // Leonardo w/Breakout board

#define write8inline(d)                                                        \
  {                                                                            \
    uint8_t dr1 = (d) >> 1, dl1 = (d) << 1;                                    \
    PORTE = (PORTE & B10111111) | (dr1 & B01000000);                           \
    PORTD = (PORTD & B01101100) | (dl1 & B10000000) | (((d)&B00001000) >> 3) | \
            (dr1 & B00000010) | ((d)&B00010000);                               \
    PORTC = (PORTC & B10111111) | (dl1 & B01000000);                           \
    PORTB = (PORTB & B11001111) | (((d)&B00000011) << 4);                      \
    WR_STROBE;                                                                 \
  }
#define read8inline(result)                                                    \
  {                                                                            \
    RD_ACTIVE;                                                                 \
    DELAY7;                                                                    \
    result = (((PINE & B01000000) | (PIND & B00000010)) << 1) |                \
             (((PINC & B01000000) | (PIND & B10000000)) >> 1) |                \
             ((PIND & B00000001) << 3) | ((PINB & B00110000) >> 4) |           \
             (PIND & B00010000);                                               \
    RD_IDLE;                                                                   \
  }
#define setWriteDirInline()                                                    \
  {                                                                            \
    DDRE |= B01000000;                                                         \
    DDRD |= B10010011;                                                         \
    DDRC |= B01000000;                                                         \
    DDRB |= B00110000;                                                         \
  }
#define setReadDirInline()                                                     \
  {                                                                            \
    DDRE &= ~B01000000;                                                        \
    DDRD &= ~B10010011;                                                        \
    DDRC &= ~B01000000;                                                        \
    DDRB &= ~B00110000;                                                        \
  }

#endif

// On the Leonardo, only the write8() macro is used -- though even that
// might be excessive given the code size and available program space
// on this board.  You may need to disable this to get any sizable
// program to compile.
#define write8 write8inline

#elif defined(__SAM3X8E__)

// Arduino Due

#ifdef USE_ADAFRUIT_SHIELD_PINOUT

#define RD_PORT PIOA /*pin A0 */
#define WR_PORT PIOA /*pin A1 */
#define CD_PORT PIOA /*pin A2 */
#define CS_PORT PIOA /*pin A3 */
#define RD_MASK 0x00010000
#define WR_MASK 0x01000000
#define CD_MASK 0x00800000
#define CS_MASK 0x00400000

#define write8inline(d)                                                        \
  {                                                                            \
    PIO_Set(PIOD, (((d)&0x08) << (7 - 3)));                                    \
    PIO_Clear(PIOD, (((~d) & 0x08) << (7 - 3)));                               \
    PIO_Set(PIOC, (((d)&0x01) << (22 - 0)) | (((d)&0x02) << (21 - 1)) |        \
                      (((d)&0x04) << (29 - 2)) | (((d)&0x10) << (26 - 4)) |    \
                      (((d)&0x40) << (24 - 6)) | (((d)&0x80) << (23 - 7)));    \
    PIO_Clear(PIOC,                                                            \
              (((~d) & 0x01) << (22 - 0)) | (((~d) & 0x02) << (21 - 1)) |      \
                  (((~d) & 0x04) << (29 - 2)) | (((~d) & 0x10) << (26 - 4)) |  \
                  (((~d) & 0x40) << (24 - 6)) | (((~d) & 0x80) << (23 - 7)));  \
    PIO_Set(PIOB, (((d)&0x20) << (27 - 5)));                                   \
    PIO_Clear(PIOB, (((~d) & 0x20) << (27 - 5)));                              \
    WR_STROBE;                                                                 \
  }

#define read8inline(result)                                                    \
  {                                                                            \
                                                                               \
    RD_ACTIVE;                                                                 \
    delayMicroseconds(1);                                                      \
    result = (((PIOC->PIO_PDSR & (1 << 23)) >> (23 - 7)) |                     \
              ((PIOC->PIO_PDSR & (1 << 24)) >> (24 - 6)) |                     \
              ((PIOB->PIO_PDSR & (1 << 27)) >> (27 - 5)) |                     \
              ((PIOC->PIO_PDSR & (1 << 26)) >> (26 - 4)) |                     \
              ((PIOD->PIO_PDSR & (1 << 7)) >> (7 - 3)) |                       \
              ((PIOC->PIO_PDSR & (1 << 29)) >> (29 - 2)) |                     \
              ((PIOC->PIO_PDSR & (1 << 21)) >> (21 - 1)) |                     \
              ((PIOC->PIO_PDSR & (1 << 22)) >> (22 - 0)));                     \
    RD_IDLE;                                                                   \
  }

#define setWriteDirInline()                                                    \
  {                                                                            \
    PIOD->PIO_MDDR |= 0x00000080; /*PIOD->PIO_SODR =  0x00000080;*/            \
    PIOD->PIO_OER |= 0x00000080;                                               \
    PIOD->PIO_PER |= 0x00000080;                                               \
    PIOC->PIO_MDDR |= 0x25E00000; /*PIOC->PIO_SODR =  0x25E00000;*/            \
    PIOC->PIO_OER |= 0x25E00000;                                               \
    PIOC->PIO_PER |= 0x25E00000;                                               \
    PIOB->PIO_MDDR |= 0x08000000; /*PIOB->PIO_SODR =  0x08000000;*/            \
    PIOB->PIO_OER |= 0x08000000;                                               \
    PIOB->PIO_PER |= 0x08000000;                                               \
  }

#define setReadDirInline()                                                     \
  {                                                                            \
    pmc_enable_periph_clk(ID_PIOD);                                            \
    pmc_enable_periph_clk(ID_PIOC);                                            \
    pmc_enable_periph_clk(ID_PIOB);                                            \
    PIOD->PIO_PUDR |= 0x00000080;                                              \
    PIOD->PIO_IFDR |= 0x00000080;                                              \
    PIOD->PIO_ODR |= 0x00000080;                                               \
    PIOD->PIO_PER |= 0x00000080;                                               \
    PIOC->PIO_PUDR |= 0x25E00000;                                              \
    PIOC->PIO_IFDR |= 0x25E00000;                                              \
    PIOC->PIO_ODR |= 0x25E00000;                                               \
    PIOC->PIO_PER |= 0x25E00000;                                               \
    PIOB->PIO_PUDR |= 0x08000000;                                              \
    PIOB->PIO_IFDR |= 0x08000000;                                              \
    PIOB->PIO_ODR |= 0x08000000;                                               \
    PIOB->PIO_PER |= 0x08000000;                                               \
  }

// Control signals are ACTIVE LOW (idle is HIGH)
// Command/Data: LOW = command, HIGH = data
// These are single-instruction operations and always inline
#define RD_ACTIVE RD_PORT->PIO_CODR |= RD_MASK
#define RD_IDLE RD_PORT->PIO_SODR |= RD_MASK
#define WR_ACTIVE WR_PORT->PIO_CODR |= WR_MASK
#define WR_IDLE WR_PORT->PIO_SODR |= WR_MASK
#define CD_COMMAND CD_PORT->PIO_CODR |= CD_MASK
#define CD_DATA CD_PORT->PIO_SODR |= CD_MASK
#define CS_ACTIVE CS_PORT->PIO_CODR |= CS_MASK
#define CS_IDLE CS_PORT->PIO_SODR |= CS_MASK

#else // Due w/Breakout board

#define write8inline(d)                                                        \
  {                                                                            \
    PIO_Set(PIOC, (((d)&0xFF) << 1));                                          \
    PIO_Clear(PIOC, (((~d) & 0xFF) << 1));                                     \
    WR_STROBE;                                                                 \
  }

#define read8inline(result)                                                    \
  {                                                                            \
    RD_ACTIVE;                                                                 \
    delayMicroseconds(1);                                                      \
    result = ((PIOC->PIO_PDSR & 0x1FE) >> 1);                                  \
    RD_IDLE;                                                                   \
  }

#define setWriteDirInline()                                                    \
  {                                                                            \
    PIOC->PIO_MDDR |= 0x000001FE; /*PIOC->PIO_SODR |=  0x000001FE;*/           \
    PIOC->PIO_OER |= 0x000001FE;                                               \
    PIOC->PIO_PER |= 0x000001FE;                                               \
  }

#define setReadDirInline()                                                     \
  {                                                                            \
    pmc_enable_periph_clk(ID_PIOC);                                            \
    PIOC->PIO_PUDR |= 0x000001FE;                                              \
    PIOC->PIO_IFDR |= 0x000001FE;                                              \
    PIOC->PIO_ODR |= 0x000001FE;                                               \
    PIOC->PIO_PER |= 0x000001FE;                                               \
  }

// When using the TFT breakout board, control pins are configurable.
#define RD_ACTIVE rdPort->PIO_CODR |= rdPinSet  // PIO_Clear(rdPort, rdPinSet)
#define RD_IDLE rdPort->PIO_SODR |= rdPinSet    // PIO_Set(rdPort, rdPinSet)
#define WR_ACTIVE wrPort->PIO_CODR |= wrPinSet  // PIO_Clear(wrPort, wrPinSet)
#define WR_IDLE wrPort->PIO_SODR |= wrPinSet    // PIO_Set(wrPort, wrPinSet)
#define CD_COMMAND cdPort->PIO_CODR |= cdPinSet // PIO_Clear(cdPort, cdPinSet)
#define CD_DATA cdPort->PIO_SODR |= cdPinSet    // PIO_Set(cdPort, cdPinSet)
#define CS_ACTIVE csPort->PIO_CODR |= csPinSet  // PIO_Clear(csPort, csPinSet)
#define CS_IDLE csPort->PIO_SODR |= csPinSet    // PIO_Set(csPort, csPinSet)

#endif

#else

#error "Board type unsupported / not recognized"

#endif

#if !defined(__SAM3X8E__)
// Stuff common to all Arduino AVR board types:

#ifdef USE_ADAFRUIT_SHIELD_PINOUT

// Control signals are ACTIVE LOW (idle is HIGH)
// Command/Data: LOW = command, HIGH = data
// These are single-instruction operations and always inline
#define RD_ACTIVE RD_PORT &= ~RD_MASK
#define RD_IDLE RD_PORT |= RD_MASK
#define WR_ACTIVE WR_PORT &= ~WR_MASK
#define WR_IDLE WR_PORT |= WR_MASK
#define CD_COMMAND CD_PORT &= ~CD_MASK
#define CD_DATA CD_PORT |= CD_MASK
#define CS_ACTIVE CS_PORT &= ~CS_MASK
#define CS_IDLE CS_PORT |= CS_MASK

#else // Breakout board

// When using the TFT breakout board, control pins are configurable.
#define RD_ACTIVE *rdPort &= rdPinUnset
#define RD_IDLE *rdPort |= rdPinSet
#define WR_ACTIVE *wrPort &= wrPinUnset
#define WR_IDLE *wrPort |= wrPinSet
#define CD_COMMAND *cdPort &= cdPinUnset
#define CD_DATA *cdPort |= cdPinSet
#define CS_ACTIVE *csPort &= csPinUnset
#define CS_IDLE *csPort |= csPinSet

#endif
#endif

// Data write strobe, ~2 instructions and always inline
#define WR_STROBE                                                              \
  {                                                                            \
    WR_ACTIVE;                                                                 \
    WR_IDLE;                                                                   \
  }

// These higher-level operations are usually functionalized,
// except on Mega where's there's gobs and gobs of program space.

// Set value of TFT register: 8-bit address, 8-bit value
#define writeRegister8inline(a, d)                                             \
  {                                                                            \
    CD_COMMAND;                                                                \
    write8(a);                                                                 \
    CD_DATA;                                                                   \
    write8(d);                                                                 \
  }

// Set value of TFT register: 16-bit address, 16-bit value
// See notes at top about macro expansion, hence hi & lo temp vars
#define writeRegister16inline(a, d)                                            \
  {                                                                            \
    uint8_t hi, lo;                                                            \
    hi = (a) >> 8;                                                             \
    lo = (a);                                                                  \
    CD_COMMAND;                                                                \
    write8(hi);                                                                \
    write8(lo);                                                                \
    hi = (d) >> 8;                                                             \
    lo = (d);                                                                  \
    CD_DATA;                                                                   \
    write8(hi);                                                                \
    write8(lo);                                                                \
  }

// Set value of 2 TFT registers: Two 8-bit addresses (hi & lo), 16-bit value
#define writeRegisterPairInline(aH, aL, d)                                     \
  {                                                                            \
    uint8_t hi = (d) >> 8, lo = (d);                                           \
    CD_COMMAND;                                                                \
    write8(aH);                                                                \
    CD_DATA;                                                                   \
    write8(hi);                                                                \
    CD_COMMAND;                                                                \
    write8(aL);                                                                \
    CD_DATA;                                                                   \
    write8(lo);                                                                \
  }

#endif // _pin_magic_
