/*
 * gpio-common.c: functions common to both gpio.c and gpio-nls.c
 *
 * Copyright © 2015 Oscar Vermeulen, © 2016-2018 by Warren Young
 *           © 2021 Steve Tockey
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS LISTED ABOVE BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 * Except as contained in this notice, the names of the authors above shall
 * not be used in advertising or otherwise to promote the sale, use or other
 * dealings in this Software without prior written authorization from those
 * authors.
 *
 * www.obsolescenceguaranteed.blogspot.com
 * 
 * This is part of the GPIO thread, which communicates with the
 * simulator's main thread via *pdis_update and switchstatus[].
 * All of this module's other external interfaces are only called
 * by the other gpio-* modules, from the GPIO thread.
*/

#include "pidp8i.h"

#include <config.h>

#include <pthread.h>
#include <sys/file.h>
#include <sys/time.h>

#include <ctype.h>
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef HAVE_TIME_H
#   include <time.h>
#endif
#include <unistd.h>
#include <gpiolib.h>

#define BLOCK_SIZE (4*1024)


//// GLOBALS ///////////////////////////////////////////////////////////

// Flag set after we successfully init the GPIO mechanism.  While this
// is false, the rest of the code knows not to expect useful values for
// LED and switch states.  It is also useful as a cross-thread signal,
// since merely starting the blink() thread doesn't tell you whether it
// managed to lock the GPIO device.
uint8_t pidp8i_gpio_present;

// Flag external programs can set to make us use the old-style (and
// simpler, hence still supported) ledstatus[] interface for updating
// the LED values when swapping displays internally.  The external
// program doesn't need to know anything about struct display.
int pidp8i_simple_gpio_mode = 0;

// A constant meaning "indeterminate milliseconds", used for error
// returns from ms_time() and for the case where the switch is in the
// stable state in the switch_state array.
static const ms_time_t na_ms = (ms_time_t)-1;


// Adjust columns to scan based on whether the Oscar Vermeulen or James L-W
// serial mods were done, as that affects the free GPIOs for our use, and how
// the PCB connects them to the LED matrix.
#if defined(PCB_SERIAL_MOD_OV) || defined(PCB_SERIAL_MOD_JLW)
uint8_t cols[NCOLS] = {13, 12, 11,    10, 9, 8,    7, 6, 5,    4, 3, 2};
#else
uint8_t cols[NCOLS] = {13, 12, 11,    10, 9, 8,    7, 6, 5,    4, 15, 14};
#endif

uint8_t ledrows[NLEDROWS] = {20, 21, 22, 23, 24, 25, 26, 27};

uint8_t rows[NROWS] = {16, 17, 18};


// Current switch states, as reported by the debouncing algorithm.  Set
// from the GPIO thread to control the SIMH CPU thread.
uint16_t switchstatus[NROWS];

// Double-buffered LED display brightness values.  The update-to copy is
// modified by the SIMH CPU thread as it executes instructions, and the
// paint-from copy is read by the gpio-*.c module in its "set LEDs"
// loop.  When the GPIO thread is finished with the paint-from copy, it
// zeroes it and swaps it for the current "update-to" copy, giving the
// CPU thread a blank slate, and giving the GPIO thread a stable set of
// LED "on" time values to work with.
display display_bufs[3];
display* pdis_update = display_bufs + 0;    // exported to SIMH CPU thread
display* pdis_paint  = display_bufs + 1;    // exported to gpio-*.c
display* pdis_copy   = display_bufs + 2;    // used by swap_displays only

// GPIO thread control variables manipulated by start/stop_*() below
static pthread_t gpio_thread_info;
static int terminate_gpio_thread = 0;
static pthread_mutex_t gpio_start_mutex;


// Time-delayed reaction to switch changes to debounce the contacts.
// This is especially important with the incandescent lamp simulation
// feature enabled since that speeds up the GPIO scanning loop, making
// it more susceptible to contact bounce.
struct switch_state {
    // switch state currently reported via switchstatus[]
    int stable_state;

    // ms the switch state has been != stable_state, or na_ms
    // if it is currently in that same state
    ms_time_t last_change;      
};
static struct switch_state gss[NROWS][NCOLS];
static int gss_initted = 0;
static const ms_time_t debounce_ms = 50;    // time switch state must remain stable


// Flow-control switch states which are owned by -- that is, primarily
// modified by -- the PDP8/pidp8i module, but we can't define these
// there because we refer to them below, and not all programs that link
// to us link to that module as well.  For such programs, it's fine if
// these flags stay 0.
int cpuRun = 0, swSingInst = 0, swSingStep = 0;

// Flag to override ILS mode, forcing fallback to NLS mode.  Set when
// the PDP-8 instruction decoding loop detects that we're using the
// ratio form of SET THROTTLE, which prevents the use of ILS due to the
// way instructions are executed in that mode.  Defined here rather than
// in gpio-ils.c because we don't want to make code that sets this
// conditional based on whether ILS is in fact actually enabled.
int suppressILS = 0;

// Flags to force either ILS or NLS mode, set via the executable name
// suppressILS will override forceILS
int forceILS = 0,  forceNLS = 0;


// Flag set when sim_instr() exits due to some SIMH event like Ctrl-E,
// which lets us resume from our imposed "pause" display state.
int resumeFromInstructionLoopExit = 0;


// SCP's signal handlers, which we extend.
#if !defined(HAVE_SIGHANDLER_T) && defined(HAVE_SIG_T)
typedef sig_t sighandler_t;
#endif
static sighandler_t scp_term_handler = 0;

//// MEMORY MAPPED GPIO FUNCTIONS //////////////////////////////////////

//// map_peripheral ////////////////////////////////////////////////////
// Exposes the physical address defined in the passed structure

static int map_peripheral(int exclusive)
{
int must_map=exclusive;	//!!!!!!!!!!!!!!!!!!!!!!!!!!!! VERY WRONG THIS
	// init GPIO stuff ----------
        int num_gpios;
	int ret;

	ret = gpiolib_init();

	if (ret < 0)
	{
		printf("Failed to initialise gpiolib - %d\n", ret);
    		if (must_map) return EFAULT;
	}

	num_gpios = ret;
	if (!num_gpios)
	{
		printf("No GPIO chips found\n");
    		if (must_map) return EFAULT;
	}

	// -----
	int i;
	uint32_t gpiomask[(MAX_GPIO_PINS + 31)/32] = { 0 };
#define ARRAY_SIZE(_a) (sizeof(_a)/sizeof(_a[0]))
	for (i = ARRAY_SIZE(gpiomask) - 1; i >= 0; i--)
	{
		if (gpiomask[i])
			break;
	}
	if (i < 0)
		memset(gpiomask, 0xff, sizeof(gpiomask));

	ret = gpiolib_mmap();
	if (ret)
	{
		if (ret == EACCES && geteuid())
			printf("\n\nWARNING: Must be root to drive PiDP-8 front panel!\n\n\n");
		else
			printf("Failed to mmap gpiolib - %s\n", strerror(ret));
    		if (must_map) return EFAULT;
	}

    // Set ledrow GPIO pins to output
    for (size_t i = 0; i < NLEDROWS; i++) {       // Define rows as input
	gpio_set_fsel(ledrows[i], GPIO_FSEL_OUTPUT);
	gpio_set_drive(ledrows[i], DRIVE_LOW);
    }
    // Set row GPIO pins to pull-up state
    for (size_t i = 0; i < NROWS; i++) {       // Define rows as input
	gpio_set_fsel(rows[i], GPIO_FSEL_INPUT);
	gpio_set_pull(rows[i], PULL_UP);
    }
    // Set col GPIO pins to pull-up state
    for (size_t i = 0; i < NCOLS; i++) {       // Define columns as input
	gpio_set_fsel(cols[i], GPIO_FSEL_INPUT);
	gpio_set_pull(cols[i], PULL_UP);
    }
    pidp8i_gpio_present = 1;
    return 0;
}

//// DOUBLE BUFFERED DISPLAY MANIPULATION FUNCTIONS ////////////////////

//// swap_displays ////////////////////////////////////////////////////
// Clear the current "paint-from" display, then exchange the double-
// buffered display pointers atomically, saving the current update-to
// display pointer as our paint-from display pointer and re-pointing
// the update-to pointer at the now-zeroed paint-from values.  This
// gives the CPU thread a blank slate to begin modifying while the GPIO
// thread consumes the values provided by the CPU thread.

#define SWAP(dir) \
    __atomic_exchange_n (&pdis_update, display_bufs + dir, __ATOMIC_SEQ_CST)

void swap_displays ()
{
        if (pidp8i_simple_gpio_mode) {
            // We're linked to a program that wants to use the old
            // ledstatus[] interface for updating the display, so copy
            // its values into the paint-from display structure and
            // return.  We don't need to touch the update-to display or
            // swap anything, because set_pidp8i_leds won't be called.
            static const int levels = 32;
            memcpy (pdis_paint->curr, ledstatus, 
                    sizeof (pdis_paint->curr));
            pdis_paint->cycle_count = levels;

            for (size_t row = 0; row < NLEDROWS; ++row) {
                size_t *prow = pdis_paint->on[row];
                for (size_t col = 0, mask = 1; col < NCOLS;
                        ++col, mask <<= 1) {
                    prow[col] = !!(ledstatus[row] & mask) * levels;
                }
            }
        }
        else {
            // Clear old paint-from display, but make a copy 
            // first (see below)
            memcpy (pdis_copy,pdis_paint, sizeof(display));
            memset (pdis_paint, 0, sizeof(display));

            // Send old paint-from display to CPU as new update-to
            // display, and overwrite paint-from pointer with prior
            // update-to pointer.
            pdis_paint = pdis_update == display_bufs + 0 ?
                    SWAP(1) :
                    SWAP(0);

            if (pdis_paint->cycle_count == 0) { 
                // special case: there has been no update since last swapping
                // which means the the old update and new paint buffer 
                // is all zeros. In this case we bring in current LED values from the 
                // copy we saved before switching. The ILS code will 
                // treat this accordingly
                memcpy(pdis_paint->curr,pdis_copy->curr,sizeof(pdis_copy->curr));
           }
        }

}


//// FINE GRAINED SLEEP FUNCTIONS //////////////////////////////////////

//// sleep_ns //////////////////////////////////////////////////////////
// Like sleep(2) except that it takes nanoseconds instead of seconds

void sleep_ns(ns_time_t ns)
{
    struct timespec ts = { 0, ns };

#if defined(HAVE_CLOCK_NANOSLEEP)
    clock_nanosleep(CLOCK_REALTIME, 0, &ts, NULL);
#elif defined(HAVE_NANOSLEEP)
    nanosleep(&ts, NULL);
#elif defined(HAVE_USLEEP)
    usleep(ns / 1000);
#else
#   error Cannot build GPIO controller without high-res "sleep" function!
#endif

}


//// ms_time ///////////////////////////////////////////////////////////
// Like time(2) except that it returns milliseconds since the Unix epoch

ms_time_t ms_time(ms_time_t* pt)
{
    struct timeval tv;
    if (gettimeofday(&tv, 0) == 0) {
        ms_time_t t =
                tv.tv_sec  * 1000.0 +
                tv.tv_usec / 1000.0;
        if (pt) *pt = t;
        return t;
    }
    else {
        return na_ms;
    }
}


//// SWITCH DEBOUNCING and READING FUNCTIONS ///////////////////////////

//// report_ss /////////////////////////////////////////////////////////
// Save given switch state ss into the exported switchstatus bitfield
// so the simulator core will see it.  (Constrast the gss matrix,
// which holds our internal view of the unstable truth.)

static void report_ss(int row, int col, int ss,
        struct switch_state* pss)
{
    pss->stable_state = ss;
    pss->last_change = na_ms;

    int mask = 1 << col;
    if (ss) switchstatus[row] |=  mask;
    else    switchstatus[row] &= ~mask;

    #ifdef DEBUG
        printf("%cSS[%d][%02d] = %d  ", gss_initted ? 'N' : 'I', row, col, ss);
    #endif
}


//// debounce_switch ///////////////////////////////////////////////////
// Given the state of the switch at (row,col), work out if this requires
// a change in our exported switch state.

static void debounce_switch(int row, int col, int ss, ms_time_t now_ms)
{
    struct switch_state* pss = &gss[row][col];

    if (!gss_initted) {
        // First time thru, so set this switch's module-global and
        // exported state to its defaults now that we know the switch's
        // initial state.
        report_ss(row, col, ss, pss);
    }
    else if (ss == pss->stable_state) {
        // This switch is still/again in the state we consider "stable",
        // which we are reporting in our switchstatus bitfield.  Reset
        // the debounce timer in case it is returning to its stable
        // state from a brief blip into the other state.
        pss->last_change = na_ms;
    }
    else if (pss->last_change == na_ms) {
        // This switch just left what we consider the "stable" state, so
        // start the debounce timer.
        pss->last_change = now_ms;
    }
    else if ((now_ms - pss->last_change) > debounce_ms) {
        // Switch has been in the new state long enough for the contacts
        // to have stopped bouncing: report its state change to outsiders.
        report_ss(row, col, ss, pss);
    }
    // else, switch was in the new state both this time and the one prior,
    // but it hasn't been there long enough to report it
}


//// read_switches /////////////////////////////////////////////////////
// Iterate through the switch GPIO pins, passing them to the debouncing
// mechanism above for eventual reporting to the PDP-8 CPU thread.

void read_switches (ns_time_t delay)
{
    // Save current ms-since-epoch for debouncer.  No point making it
    // retrieve this value for each switch.
    ms_time_t now_ms;
    ms_time(&now_ms);

    // Flip columns to input.  Since the internal pull-ups are enabled,
    // this pulls all switch GPIO pins high that aren't shorted to the
    // row line by the switch.
    for (size_t i = 0; i < NCOLS; ++i) {
	gpio_set_dir(cols[i], DIR_INPUT);
    }

    // Read the switch rows
    for (size_t i = 0; i < NROWS; ++i) {
        // Put 0V out on the switch row so that closed switches will
        // drag its column line down; give it time to settle.
	gpio_set_dir(rows[i], DIR_OUTPUT);
	gpio_set_drive(rows[i], DRIVE_LOW);
        sleep_ns (delay);

        // Read all the switches in this row
        for (size_t j = 0; j < NCOLS; ++j) {
	    int ss = gpio_get_level(cols[j]);

	    debounce_switch(i, j, !!ss, now_ms);
        }

        // Stop sinking current from this row of switches
	gpio_set_dir(rows[i], DIR_INPUT);
    }

    fflush(stdout);
    gss_initted = 1;
}


//// UNGROUPED FUNCTIONS ///////////////////////////////////////////////

//// pi_type ///////////////////////////////////////////////////////////
// Return a short string succinctly describing the type of Raspberry Pi
// we're running on, or "cake" if it's not a pi.

static const char* pi_type()
{
	return "pinctrl I/O";
}


//// update_led_states /////////////////////////////////////////////////
// Generic front panel LED updater used by NLS full time and by ILS
// while the CPU is in STOP mode.  Just uses the paint-from display's
// bitfields to turn the LEDs on full-brightness.

void update_led_states (const us_time_t delay)
{
    uint16_t *pcurr = pdis_paint->curr;

#if 0   // debugging
    static time_t last = 0, now;
    if (time(&now) != last) {
        printf("\r\nLED: [PC:%04o] [MA:%04o] [MB:%04o] [AC:%04o] [MQ:%04o]",
                pcurr[0], pcurr[1], pcurr[2], pcurr[3], pcurr[4]);
        last = now;
    }
#endif
    
#if 0
// This appears to be no longer necessary in the cycle-realistic version
    // Override Execute and Run LEDs if the CPU is currently stopped,
    // since we only get set_pidp8i_leds calls while the CPU's running.
    if (cpuRun == 0) {
        pdis_paint->curr[5] &= ~(1 << 2);
        pdis_paint->curr[6] &= ~(1 << 7);
    }
#endif

    for (size_t row = 0; row < NLEDROWS; ++row) {
        for (size_t col = 0; col < NCOLS; ++col) {
            gpio_set_fsel(cols[col], GPIO_FSEL_INPUT);
            if ((pcurr[row] & (1 << col)) == 0) {
                //GPIO_SET = 1 << cols[col];
		gpio_set_drive(cols[col], DRIVE_HIGH);
            }
            else {
                //GPIO_CLR = 1 << cols[col];
		gpio_set_drive(cols[col], DRIVE_LOW);
            }
            gpio_set_fsel(cols[col], GPIO_FSEL_OUTPUT);
        }

        // Toggle this LED row on
        gpio_set_fsel(ledrows[row], GPIO_FSEL_INPUT);
        gpio_set_drive(ledrows[row], DRIVE_HIGH);
        gpio_set_fsel(ledrows[row], GPIO_FSEL_OUTPUT);

        sleep_us (delay);

        // Toggle this LED row off
        gpio_set_fsel(ledrows[row], GPIO_FSEL_INPUT);
        gpio_set_drive(ledrows[row], DRIVE_LOW);
        gpio_set_fsel(ledrows[row], GPIO_FSEL_OUTPUT);

        // Small delay to reduce UDN2981 ghosting
        sleep_us (10);
    }
}


//// turn_on/off_pidp8i_leds ///////////////////////////////////////////
// Set GPIO pins into a state that [dis]connects power to/from the LEDs.
// Doesn't pay any attention to the panel values.

void turn_on_pidp8i_leds ()
{
    for (size_t row = 0; row < NLEDROWS; ++row) {
	gpio_set_drive(ledrows[row], DRIVE_LOW);
    }
    for (size_t col = 0; col < NCOLS; ++col) {
	gpio_set_fsel(cols[col], GPIO_FSEL_OUTPUT);
    }
}

void turn_off_pidp8i_leds ()
{
    for (size_t row = 0; row < NLEDROWS; ++row) {
	gpio_set_drive(ledrows[row], DRIVE_LOW);
    }
    for (size_t col = 0; col < NCOLS; ++col) {
	gpio_set_dir(cols[col], DIR_INPUT);
    }
}


//// init_pidp8i_gpio //////////////////////////////////////////////////
// Initialize the GPIO pins to the initial states required by
// gpio_thread().   It's a separate exported function so that scanswitch
// can also use it.

void init_pidp8i_gpio (void)
{

    // Set GPIO pins to their starting state
    turn_on_pidp8i_leds ();
    for (size_t i = 0; i < NROWS; i++) {       // Define rows as input
	gpio_set_dir(rows[i], DIR_INPUT);
    }
}


//// gpio_thread ///////////////////////////////////////////////////////
// The GPIO thread entry point: initializes GPIO and then calls
// the gpio_core () implementation linked to this program.

static void *gpio_thread (void *terminate)
{
    // Set thread to real time priority
    struct sched_param sp;
    sp.sched_priority = 4;  // not high, just above the minimum of 1
    int rt = pthread_setschedparam(pthread_self(), SCHED_FIFO, &sp) == 0;

    // Tell the user about our configuration, succinctly
    const char* pt = pi_type();
    printf(
           "PiDP-8/I @VERSION@ [%s] [%cls] [%spcb] [%sgpio]"

#ifdef DEBUG
        " [debug]"
#endif
        "%s",
        pt,
        forceNLS ? 'n' : 'i',
        pt[0] == 'p' ? 
#ifdef PCB_SERIAL_MOD_OV
            "sermod" : 
#elif PCB_SERIAL_MOD_JLW
            "altser" : 
#else
            "std" : 
#endif
            "no",
        (pidp8i_gpio_present ? "" : "no"),
        (rt ? " [rt]" : "")
    );

    // It's okay for our caller to resume executing, if it's locked
    // waiting for us to finish the initialization bits that can only
    // happen in this thread.
    pthread_mutex_unlock (&gpio_start_mutex);

    // If we didn't map the GPIO peripheral and get here, it was
    // optional, and we've done all we can without it.
    if (!pidp8i_gpio_present) return (void*)-1;

    // Set GPIO pins to initial states
    init_pidp8i_gpio ();

    // Hand off control to the gpio_core () variant linked to this
    // program: either the new incandescent lamp simulator or the old
    // stock version.
    //extern void gpio_core (struct bcm2835_peripheral*, int* terminate);
    //gpio_core (&gpio, (int*)terminate);
    extern void gpio_core (int* terminate);
    gpio_core ((int*)terminate);

    // gpio_core () leaves all cols, rows, ledrows are set to input, and
    // it's safe to leave them in that state.  No need to de-init GPIO.
    gss_initted = 0;
    return 0;
}

//// map_gpio_for_pidp8i ///////////////////////////////////////////////
// Wrapper around map_peripheral() taking care of some higher level
// details.  This is the interface we expose to outsiders.

int map_gpio_for_pidp8i (int must_map)
{
    int retval = map_peripheral (1);
    if (retval !=0) return retval;

#ifdef PCB_SERIAL_MOD_JLW
    // this does Jams L-W's mod through pinctrl:
		gpio_set_fsel(2, GPIO_FSEL_GPIO);
		gpio_set_fsel(3, GPIO_FSEL_GPIO);
#endif
	
    return 0;
}

//// pidp8i_term_handler ///////////////////////////////////////////////
// Handle SIGTERM by shutting down our GPIO thread, then pass it on to
// SCP's handler, if one is registered.
//
// We don't do this with SIGINT instead because that's basically an
// alias for SCP's Ctrl-E handler.  We don't want to do this every time
// someone pulls up the SCP command console.

static void
pidp8i_term_handler (int sig)
{
    if (scp_term_handler) scp_term_handler (sig);

    if (pidp8i_gpio_present) {
        turn_off_pidp8i_leds ();
        stop_pidp8i_gpio_thread ();
    }
}


//// start/stop_pidp8i_gpio_thread /////////////////////////////////////
// Start and stop gpio_thread().  We export these functions rather than
// export gpio_thread() directly so this module's users don't have to
// know anything about pthreads and such.

int start_pidp8i_gpio_thread (const int must_map)
{
    char errs[100];
    
    if (map_gpio_for_pidp8i (must_map) != 0) {
        return EFAULT;
    }

    // Until gpio_core () reads the switches for the first time, we need
    // to mark them as all-open, lest we have a race condition with the
    // simulator where it interprets the all-0 initial switchstatus[]
    // value as "all switches closed," which includes the shutdown seq!
    memset (switchstatus, 0xFF, sizeof (switchstatus));

    // Create the startup sequencing mutex and lock it once to block the
    // GPIO thread after it's sufficiently initialized.
    int pcerr;
    if (    ((pcerr = pthread_mutex_init (&gpio_start_mutex, NULL)) != 0) ||
            ((pcerr = pthread_mutex_lock (&gpio_start_mutex)) != 0)) {
        perror ("GPIO startup sequence mutex creation failed");
        return errno;
    }

    // Create the actual GPIO handler thread
    pcerr = pthread_create (&gpio_thread_info, NULL, gpio_thread,
                            &terminate_gpio_thread);
    if (pcerr != 0) {
        int errlen = snprintf (errs, sizeof(errs), "Error creating "
                "PiDP-8/I GPIO thread: %s\n", strerror (pcerr));
        if (errlen > 0 && errlen < sizeof(errs)) write (2, errs, errlen);
    }
    else if (must_map && !pidp8i_gpio_present) {
        int errlen = snprintf (errs, sizeof(errs), "Cannot run the %s "
                "while another PiDP-8/I program runs.\r\n", must_map);
        if (errlen > 0 && errlen < sizeof(errs)) write (2, errs, errlen);
        pcerr = EACCES;
    }
    else {
        // Shut the GPIO thread down gracefully on fatal signals.
        scp_term_handler = signal (SIGTERM, pidp8i_term_handler);

        // Don't return until GPIO thread is sufficiently initted.
        //
        // There are two possible sequences:
        //
        // 1. We get here before the GPIO thread gets to its "unlock"
        //    call, so our back-to-back locks in this thread stall this
        //    thread until the GPIO thread gets to its unlock condition,
        //    which removes the first lock taken above, and our second
        //    unlock call below removes the second lock.
        //
        // 2. The GPIO thread hits its "unlock" call before we get here,
        //    so it unlocks the lock we made above, so that these two
        //    calls happen back to back, with no real effect.  We got
        //    here too late to cause any problem that this interlock
        //    solves, so it just locks and immediately unlocks.
        pthread_mutex_lock   (&gpio_start_mutex);
        pthread_mutex_unlock (&gpio_start_mutex);
    }

    return pcerr;
}

void stop_pidp8i_gpio_thread ()
{
    terminate_gpio_thread = 1;
    if (pthread_join (gpio_thread_info, NULL) && pidp8i_gpio_present) {
        // Warn only if we think it should succeed.  If we die due to a
        // signal, we'll be called twice and this'll fail the second
        // time, but the flag will be cleared, so we shouldn't complain.
        printf("\r\nError joining multiplex thread\r\n");
    }
    //unmap_peripheral (&gpio);	// not necessary for pinctrl
#ifdef PCB_SERIAL_MOD_JLW
    gpio_set_fsel(2, GPIO_FSEL_FUNC0);	// revert back to I2C function
    gpio_set_fsel(3, GPIO_FSEL_FUNC0);	// revert back to I2C function
#endif
    pthread_mutex_destroy (&gpio_start_mutex);
}
