Skip to content

Commit

Permalink
Merge pull request #12 from adafruit/teensy4
Browse files Browse the repository at this point in the history
Initial Teensy 4.0/4.1 support
  • Loading branch information
PaintYourDragon authored May 23, 2020
2 parents f27da0d + af5c045 commit 0651691
Show file tree
Hide file tree
Showing 5 changed files with 301 additions and 16 deletions.
173 changes: 169 additions & 4 deletions arch.h
Original file line number Diff line number Diff line change
Expand Up @@ -999,6 +999,162 @@ IRAM_ATTR uint32_t _PM_timerStop(void *tptr) {

#endif // ESP32

// i.MX 1062-SPECIFIC CODE (Teensy 4.0, 4.1) -------------------------------

#if defined(__IMXRT1062__) // (Teensy 4)

// i.MX only allows full 32-bit aligned writes to GPIO.
#define _PM_STRICT_32BIT_IO ///< Change core.c behavior for long accesses only

#if defined(ARDUINO)

static const struct {
volatile uint32_t *base; ///< GPIO base address for pin
uint8_t bit; ///< GPIO bit number for pin (0-31)
} _PM_teensyPins[] = {
{&CORE_PIN0_PORTREG, CORE_PIN0_BIT},
{&CORE_PIN1_PORTREG, CORE_PIN1_BIT},
{&CORE_PIN2_PORTREG, CORE_PIN2_BIT},
{&CORE_PIN3_PORTREG, CORE_PIN3_BIT},
{&CORE_PIN4_PORTREG, CORE_PIN4_BIT},
{&CORE_PIN5_PORTREG, CORE_PIN5_BIT},
{&CORE_PIN6_PORTREG, CORE_PIN6_BIT},
{&CORE_PIN7_PORTREG, CORE_PIN7_BIT},
{&CORE_PIN8_PORTREG, CORE_PIN8_BIT},
{&CORE_PIN9_PORTREG, CORE_PIN9_BIT},
{&CORE_PIN10_PORTREG, CORE_PIN10_BIT},
{&CORE_PIN11_PORTREG, CORE_PIN11_BIT},
{&CORE_PIN12_PORTREG, CORE_PIN12_BIT},
{&CORE_PIN13_PORTREG, CORE_PIN13_BIT},
{&CORE_PIN14_PORTREG, CORE_PIN14_BIT},
{&CORE_PIN15_PORTREG, CORE_PIN15_BIT},
{&CORE_PIN16_PORTREG, CORE_PIN16_BIT},
{&CORE_PIN17_PORTREG, CORE_PIN17_BIT},
{&CORE_PIN18_PORTREG, CORE_PIN18_BIT},
{&CORE_PIN19_PORTREG, CORE_PIN19_BIT},
{&CORE_PIN20_PORTREG, CORE_PIN20_BIT},
{&CORE_PIN21_PORTREG, CORE_PIN21_BIT},
{&CORE_PIN22_PORTREG, CORE_PIN22_BIT},
{&CORE_PIN23_PORTREG, CORE_PIN23_BIT},
{&CORE_PIN24_PORTREG, CORE_PIN24_BIT},
{&CORE_PIN25_PORTREG, CORE_PIN25_BIT},
{&CORE_PIN26_PORTREG, CORE_PIN26_BIT},
{&CORE_PIN27_PORTREG, CORE_PIN27_BIT},
{&CORE_PIN28_PORTREG, CORE_PIN28_BIT},
{&CORE_PIN29_PORTREG, CORE_PIN29_BIT},
{&CORE_PIN30_PORTREG, CORE_PIN30_BIT},
{&CORE_PIN31_PORTREG, CORE_PIN31_BIT},
{&CORE_PIN32_PORTREG, CORE_PIN32_BIT},
{&CORE_PIN33_PORTREG, CORE_PIN33_BIT},
{&CORE_PIN34_PORTREG, CORE_PIN34_BIT},
{&CORE_PIN35_PORTREG, CORE_PIN35_BIT},
{&CORE_PIN36_PORTREG, CORE_PIN36_BIT},
{&CORE_PIN37_PORTREG, CORE_PIN37_BIT},
{&CORE_PIN38_PORTREG, CORE_PIN38_BIT},
{&CORE_PIN39_PORTREG, CORE_PIN39_BIT},
};

#define _PM_SET_OFFSET 33 ///< 0x84 byte offset = 33 longs
#define _PM_CLEAR_OFFSET 34 ///< 0x88 byte offset = 34 longs
#define _PM_TOGGLE_OFFSET 35 ///< 0x8C byte offset = 35 longs

#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
#define _PM_byteOffset(pin) (_PM_teensyPins[pin].bit / 8)
#define _PM_wordOffset(pin) (_PM_teensyPins[pin].bit / 16)
#else
#define _PM_byteOffset(pin) (3 - (_PM_teensyPins[pin].bit / 8))
#define _PM_wordOffset(pin) (1 - (_PM_teensyPins[pin].bit / 16))
#endif

#define _PM_portOutRegister(pin) (void *)_PM_teensyPins[pin].base

#define _PM_portSetRegister(pin) \
((volatile uint32_t *)_PM_teensyPins[pin].base + _PM_SET_OFFSET)

#define _PM_portClearRegister(pin) \
((volatile uint32_t *)_PM_teensyPins[pin].base + _PM_CLEAR_OFFSET)

#define _PM_portToggleRegister(pin) \
((volatile uint32_t *)_PM_teensyPins[pin].base + _PM_TOGGLE_OFFSET)

// As written, because it's tied to a specific timer right now, the
// Arduino lib only permits one instance of the Protomatter_core struct,
// which it sets up when calling begin().
void *_PM_protoPtr = NULL;

// Code as written works with the Periodic Interrupt Timer directly,
// rather than using the Teensy IntervalTimer library, reason being we
// need to be able to poll the current timer value in _PM_timerGetCount(),
// but that's not available from IntervalTimer, and the timer base address
// it keeps is a private member (possible alternative is to do dirty pool
// and access the pointer directly, knowing it's the first element in the
// IntervalTimer object, but this is fraught with peril).

#define _PM_timerFreq 24000000 // 24 MHz
#define _PM_timerNum 0 // PIT timer #0 (can be 0-3)
#define _PM_TIMER_DEFAULT (IMXRT_PIT_CHANNELS + _PM_timerNum) // PIT channel *

// Interrupt service routine for Periodic Interrupt Timer
static void _PM_timerISR(void) {
IMXRT_PIT_CHANNEL_t *timer = _PM_TIMER_DEFAULT;
_PM_row_handler(_PM_protoPtr); // In core.c
timer->TFLG = 1; // Clear timer interrupt
}

// Initialize, but do not start, timer.
void _PM_timerInit(void *tptr) {
IMXRT_PIT_CHANNEL_t *timer = (IMXRT_PIT_CHANNEL_t *)tptr;
CCM_CCGR1 |= CCM_CCGR1_PIT(CCM_CCGR_ON); // Enable clock signal to PIT
PIT_MCR = 1; // Enable PIT
timer->TCTRL = 0; // Disable timer and interrupt
timer->LDVAL = 100000; // Timer initial load value
// Interrupt is attached but not enabled yet
attachInterruptVector(IRQ_PIT, &_PM_timerISR);
NVIC_ENABLE_IRQ(IRQ_PIT);
}

// Set timer period, initialize count value to zero, enable timer.
inline void _PM_timerStart(void *tptr, uint32_t period) {
IMXRT_PIT_CHANNEL_t *timer = (IMXRT_PIT_CHANNEL_t *)tptr;
timer->TCTRL = 0; // Disable timer and interrupt
timer->LDVAL = period; // Set load value
// timer->CVAL = period; // And current value (just in case?)
timer->TFLG = 1; // Clear timer interrupt
timer->TCTRL = 3; // Enable timer and interrupt
}

// Return current count value (timer enabled or not).
// Timer must be previously initialized.
inline uint32_t _PM_timerGetCount(void *tptr) {
IMXRT_PIT_CHANNEL_t *timer = (IMXRT_PIT_CHANNEL_t *)tptr;
return (timer->LDVAL - timer->CVAL);
}

// Disable timer and return current count value.
// Timer must be previously initialized.
uint32_t _PM_timerStop(void *tptr) {
IMXRT_PIT_CHANNEL_t *timer = (IMXRT_PIT_CHANNEL_t *)tptr;
timer->TCTRL = 0; // Disable timer and interrupt
return _PM_timerGetCount(tptr);
}

#define _PM_clockHoldHigh \
asm("nop; nop; nop; nop; nop; nop; nop;"); \
asm("nop; nop; nop; nop; nop; nop; nop;");
#define _PM_clockHoldLow \
asm("nop; nop; nop; nop; nop; nop; nop; nop; nop; nop;"); \
asm("nop; nop; nop; nop; nop; nop; nop; nop; nop; nop;");

#define _PM_chunkSize 1 ///< DON'T unroll loop, Teensy 4 is SO FAST

#elif defined(CIRCUITPY)

// Teensy 4 CircuitPython magic goes here.

#endif

#endif // __IMXRT1062__ (Teensy 4)

// DEFAULTS IF NOT DEFINED ABOVE -------------------------------------------

#if !defined(_PM_chunkSize)
Expand Down Expand Up @@ -1066,6 +1222,14 @@ __attribute__((noinline)) void _PM_convert_565_byte(Protomatter_core *core,
dest += core->bufferSize * (1 - core->activeBuffer);
}

#if defined(_PM_portToggleRegister) && !defined(_PM_STRICT_32BIT_IO)
// core->clockMask mask is already an 8-bit value
uint8_t clockMask = core->clockMask;
#else
// core->clockMask mask is 32-bit, shift down to 8-bit for this func.
uint8_t clockMask = core->clockMask >> (core->portOffset * 8);
#endif

// No need to clear matrix buffer, loops below do a full overwrite
// (except for any scanline pad, which was already initialized in the
// begin() function and won't be touched here).
Expand Down Expand Up @@ -1116,7 +1280,7 @@ __attribute__((noinline)) void _PM_convert_565_byte(Protomatter_core *core,
uint32_t blueBit = initialBlueBit;
for (uint8_t plane = 0; plane < core->numPlanes; plane++) {
#if defined(_PM_portToggleRegister)
uint8_t prior = core->clockMask; // Set clock bit on 1st out
uint8_t prior = clockMask; // Set clock bit on 1st out
#endif
for (uint16_t x = 0; x < width; x++) {
uint16_t upperRGB = upperSrc[x]; // Pixel in upper half
Expand All @@ -1136,7 +1300,7 @@ __attribute__((noinline)) void _PM_convert_565_byte(Protomatter_core *core,
result |= pinMask[5];
#if defined(_PM_portToggleRegister)
dest[x] = result ^ prior;
prior = result | core->clockMask; // Set clock bit on next out
prior = result | clockMask; // Set clock bit on next out
#else
dest[x] = result;
#endif
Expand All @@ -1161,7 +1325,7 @@ __attribute__((noinline)) void _PM_convert_565_byte(Protomatter_core *core,
// so idle clock appears LOW -- but really the matrix samples on
// a rising edge and we could leave it high, but at this stage
// in development just want the scope "readable."
dest[-pad] &= ~core->clockMask; // Negative index is legal & intentional
dest[-pad] &= ~clockMask; // Negative index is legal & intentional
#endif
dest += bitplaneSize; // Advance one scanline in dest buffer
} // end plane
Expand Down Expand Up @@ -1209,11 +1373,12 @@ void _PM_convert_565_word(Protomatter_core *core, uint16_t *source,
#if defined(_PM_portToggleRegister)
// No per-chain loop is required; one clock bit handles all chains
uint32_t offset = 0; // Current position in the 'dest' buffer
uint16_t mask = core->clockMask >> (core->portOffset * 16);
for (uint8_t row = 0; row < core->numRowPairs; row++) {
for (uint8_t plane = 0; plane < core->numPlanes; plane++) {
dest[offset++] = 0; // First element of each plane
for (uint16_t x = 1; x < bitplaneSize; x++) { // All subsequent items
dest[offset++] = core->clockMask;
dest[offset++] = mask;
}
}
}
Expand Down
Loading

0 comments on commit 0651691

Please sign in to comment.