///////////////////////////////////////////////////////////////////////////////////////
// File          RP1.xs
// Description:  XS module for HiPi::GPIO -on Pi5 with RP1
// Copyright:    Copyright (c) 2023 Mark Dootson
// License:      This is free software; you can redistribute it and/or modify it under
//               the same terms as the Perl 5 programming language system itself.
//
//               RP1 use details from pinctrl util
//               https://github.com/raspberrypi/utils/blob/master/pinctrl/gpiochip_rp1.c
///////////////////////////////////////////////////////////////////////////////////////

#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
#include "mylib/include/ppport.h"

#include <sys/mman.h>
#include <stdio.h>
#include <string.h>

#define PI_INIT_FAILED -1

/* gpio: 0-53 */

#define PI_MIN_GPIO       0
#define PI_MAX_GPIO      53
#define PI_PIN_ERROR     -1
#define PI_EDGE_ERROR    -1

/* level: 0-1 */

#define PI_OFF   0
#define PI_ON    1

#define PI_CLEAR 0
#define PI_SET   1

#define PI_LOW   0
#define PI_HIGH  1

/* mode: 0-11 */

#define PI_MAX_MODE 11

/* pud: 0-2 */

#define PI_PUD_OFF  0
#define PI_PUD_DOWN 1
#define PI_PUD_UP   2
#define PI_PUD_UNSET 0x08

/* FSEL MAPING */

#define HIPI_MODE_INPUT  0x00
#define HIPI_MODE_OUTPUT 0x01
#define HIPI_MODE_ALT0   0x04
#define HIPI_MODE_ALT1   0x05
#define HIPI_MODE_ALT2   0x06
#define HIPI_MODE_ALT3   0x07
#define HIPI_MODE_ALT4   0x03
#define HIPI_MODE_ALT5   0x02
#define HIPI_MODE_ALT6   0x08
#define HIPI_MODE_ALT7   0x09
#define HIPI_MODE_ALT8   0x0a
#define HIPI_MODE_NONE   0x0b

/* locations and offsets */

#define GPIO_BASE     0x200000
#define GPIO_LEN      0x030000

/* Just hard code this so we can output */
/* It has no use. */
#define PERI_BASE_ADDRESS 0x1f000d0000

static int fdMem = -1;
static volatile uint32_t * gpio_register = MAP_FAILED;

// pre-declares required
static int  do_initialise(void);
static void do_uninitialise(void);
static void send_module_error( char * error );

#define RP1_NUM_GPIOS 54

#define RP1_IO_BANK0_OFFSET      0x00000000
#define RP1_IO_BANK1_OFFSET      0x00004000
#define RP1_IO_BANK2_OFFSET      0x00008000
#define RP1_SYS_RIO_BANK0_OFFSET 0x00010000
#define RP1_SYS_RIO_BANK1_OFFSET 0x00014000
#define RP1_SYS_RIO_BANK2_OFFSET 0x00018000
#define RP1_PADS_BANK0_OFFSET    0x00020000
#define RP1_PADS_BANK1_OFFSET    0x00024000
#define RP1_PADS_BANK2_OFFSET    0x00028000

#define RP1_RW_OFFSET  0x0000
#define RP1_XOR_OFFSET 0x1000
#define RP1_SET_OFFSET 0x2000
#define RP1_CLR_OFFSET 0x3000

#define RP1_GPIO_CTRL_FSEL_LSB     0
#define RP1_GPIO_CTRL_FSEL_MASK    (0x1f << RP1_GPIO_CTRL_FSEL_LSB)     // 5 bits
#define RP1_GPIO_CTRL_F_M_LSB      5
#define RP1_GPIO_CTRL_F_M_MASK     (0x7f << RP1_GPIO_CTRL_F_M_LSB)      // 7 bits
#define RP1_GPIO_CTRL_OUTOVER_LSB  12
#define RP1_GPIO_CTRL_OUTOVER_MASK (0x03 << RP1_GPIO_CTRL_OUTOVER_LSB)  // 2 bits
#define RP1_GPIO_CTRL_OEOVER_LSB   14
#define RP1_GPIO_CTRL_OEOVER_MASK  (0x03 << RP1_GPIO_CTRL_OEOVER_LSB)   // 2 bits
#define RP1_GPIO_CTRL_INOVER_LSB   16
#define RP1_GPIO_CTRL_INOVER_MASK  (0x03 << RP1_GPIO_CTRL_INOVER_LSB)   // 2 bits

#define RP1_PADS_OD_SET       (1 << 7)
#define RP1_PADS_IE_SET       (1 << 6)
#define RP1_PADS_PUE_SET      (1 << 3)
#define RP1_PADS_PDE_SET      (1 << 2)
#define RP1_PADS_SCHMITT_SET  (1 << 1)
#define RP1_PADS_SLEW_SET     (1 << 0)

#define RP1_GPIO_IO_REG_STATUS_OFFSET(offset) (((offset * 2) + 0) * sizeof(uint32_t))
#define RP1_GPIO_IO_REG_CTRL_OFFSET(offset)   (((offset * 2) + 1) * sizeof(uint32_t))
#define RP1_GPIO_PADS_REG_OFFSET(offset)      (sizeof(uint32_t) + (offset * sizeof(uint32_t)))

#define RP1_GPIO_SYS_RIO_REG_OUT_OFFSET        0x0
#define RP1_GPIO_SYS_RIO_REG_OE_OFFSET         0x4
#define RP1_GPIO_SYS_RIO_REG_SYNC_IN_OFFSET    0x8

#define rp1_gpio_write32(base, peri_offset, reg_offset, value) \
    base[(peri_offset + reg_offset)/4] = value

#define rp1_gpio_read32(base, peri_offset, reg_offset) \
    base[(peri_offset + reg_offset)/4]

typedef struct
{
   uint32_t io[3];
   uint32_t pads[3];
   uint32_t sys_rio[3];
} GPIO_STATE_T;

typedef enum
{
    RP1_FSEL_ALT0       = 0x0,
    RP1_FSEL_ALT1       = 0x1,
    RP1_FSEL_ALT2       = 0x2,
    RP1_FSEL_ALT3       = 0x3,
    RP1_FSEL_ALT4       = 0x4,
    RP1_FSEL_ALT5       = 0x5,
    RP1_FSEL_ALT6       = 0x6,
    RP1_FSEL_ALT7       = 0x7,
    RP1_FSEL_ALT8       = 0x8,
    RP1_FSEL_COUNT,
    RP1_FSEL_SYS_RIO    = RP1_FSEL_ALT5,
    RP1_FSEL_NULL       = 0x1f
} RP1_FSEL_T;

typedef enum
{
    GPIO_FSEL_FUNC0,
    GPIO_FSEL_FUNC1,
    GPIO_FSEL_FUNC2,
    GPIO_FSEL_FUNC3,
    GPIO_FSEL_FUNC4,
    GPIO_FSEL_FUNC5,
    GPIO_FSEL_FUNC6,
    GPIO_FSEL_FUNC7,
    GPIO_FSEL_FUNC8,
    /* ... */
    GPIO_FSEL_INPUT = 0x10,
    GPIO_FSEL_OUTPUT,
    GPIO_FSEL_GPIO, /* Preserves direction if possible, else input */
    GPIO_FSEL_NONE, /* If possible, else input */
    GPIO_FSEL_MAX
} GPIO_FSEL_T;

typedef enum
{
    PULL_NONE,
    PULL_DOWN,
    PULL_UP,
    PULL_MAX
} GPIO_PULL_T;

typedef enum
{
    DIR_INPUT,
    DIR_OUTPUT,
    DIR_MAX,
} GPIO_DIR_T;

typedef enum
{
    DRIVE_LOW,
    DRIVE_HIGH,
    DRIVE_MAX
} GPIO_DRIVE_T;

typedef enum
{
    SCHMITT_OFF,
    SCHMITT_ON,
    SCHMITT_MAX,
} GPIO_SCHMITT_T;

typedef enum
{
    SLEW_SLOW,
    SLEW_FAST,
    SLEW_MAX,
} GPIO_SLEW_T;

static const GPIO_STATE_T gpio_state = {
    .io = {RP1_IO_BANK0_OFFSET, RP1_IO_BANK1_OFFSET, RP1_IO_BANK2_OFFSET},
    .pads = {RP1_PADS_BANK0_OFFSET, RP1_PADS_BANK1_OFFSET, RP1_PADS_BANK2_OFFSET},
    .sys_rio = {RP1_SYS_RIO_BANK0_OFFSET, RP1_SYS_RIO_BANK1_OFFSET, RP1_SYS_RIO_BANK2_OFFSET},
};

static const int rp1_bank_base[] = {0, 28, 34};

static const char *rp1_gpio_fsel_names[RP1_NUM_GPIOS][RP1_FSEL_COUNT] =
{
    { "SPI0_SIO3" , "DPI_PCLK"     , "TXD1"         , "SDA0"         , 0              , "SYS_RIO00" , "PROC_RIO00" , "PIO0"       , "SPI2_CE0" , }, // 0
    { "SPI0_SIO2" , "DPI_DE"       , "RXD1"         , "SCL0"         , 0              , "SYS_RIO01" , "PROC_RIO01" , "PIO1"       , "SPI2_SIO1", }, // 1
    { "SPI0_CE3"  , "DPI_VSYNC"    , "CTS1"         , "SDA1"         , "IR_RX0"       , "SYS_RIO02" , "PROC_RIO02" , "PIO2"       , "SPI2_SIO0", }, // 2
    { "SPI0_CE2"  , "DPI_HSYNC"    , "RTS1"         , "SCL1"         , "IR_TX0"       , "SYS_RIO03" , "PROC_RIO03" , "PIO3"       , "SPI2_SCLK", }, // 3
    { "GPCLK0"    , "DPI_D0"       , "TXD2"         , "SDA2"         , "RI0"          , "SYS_RIO04" , "PROC_RIO04" , "PIO4"       , "SPI3_CE0" , }, // 4
    { "GPCLK1"    , "DPI_D1"       , "RXD2"         , "SCL2"         , "DTR0"         , "SYS_RIO05" , "PROC_RIO05" , "PIO5"       , "SPI3_SIO1", }, // 5
    { "GPCLK2"    , "DPI_D2"       , "CTS2"         , "SDA3"         , "DCD0"         , "SYS_RIO06" , "PROC_RIO06" , "PIO6"       , "SPI3_SIO0", }, // 6
    { "SPI0_CE1"  , "DPI_D3"       , "RTS2"         , "SCL3"         , "DSR0"         , "SYS_RIO07" , "PROC_RIO07" , "PIO7"       , "SPI3_SCLK", }, // 7
    { "SPI0_CE0"  , "DPI_D4"       , "TXD3"         , "SDA0"         , 0              , "SYS_RIO08" , "PROC_RIO08" , "PIO8"       , "SPI4_CE0" , }, // 8
    { "SPI0_MISO" , "DPI_D5"       , "RXD3"         , "SCL0"         , 0              , "SYS_RIO09" , "PROC_RIO09" , "PIO9"       , "SPI4_SIO0", }, // 9
    { "SPI0_MOSI" , "DPI_D6"       , "CTS3"         , "SDA1"         , 0              , "SYS_RIO010", "PROC_RIO010", "PIO10"      , "SPI4_SIO1", }, // 10    
    { "SPI0_SCLK" , "DPI_D7"       , "RTS3"         , "SCL1"         , 0              , "SYS_RIO011", "PROC_RIO011", "PIO11"      , "SPI4_SCLK", }, // 11
    { "PWM0_CHAN0", "DPI_D8"       , "TXD4"         , "SDA2"         , "AAUD_LEFT"    , "SYS_RIO012", "PROC_RIO012", "PIO12"      , "SPI5_CE0" , }, // 12
    { "PWM0_CHAN1", "DPI_D9"       , "RXD4"         , "SCL2"         , "AAUD_RIGHT"   , "SYS_RIO013", "PROC_RIO013", "PIO13"      , "SPI5_SIO1", }, // 13
    { "PWM0_CHAN2", "DPI_D10"      , "CTS4"         , "SDA3"         , "TXD0"         , "SYS_RIO014", "PROC_RIO014", "PIO14"      , "SPI5_SIO0", }, // 14
    { "PWM0_CHAN3", "DPI_D11"      , "RTS4"         , "SCL3"         , "RXD0"         , "SYS_RIO015", "PROC_RIO015", "PIO15"      , "SPI5_SCLK", }, // 15
    { "SPI1_CE2"  , "DPI_D12"      , "DSI0_TE_EXT"  , 0              , "CTS0"         , "SYS_RIO016", "PROC_RIO016", "PIO16"      , },              // 16
    { "SPI1_CE1"  , "DPI_D13"      , "DSI1_TE_EXT"  , 0              , "RTS0"         , "SYS_RIO017", "PROC_RIO017", "PIO17"      , },              // 17
    { "SPI1_CE0"  , "DPI_D14"      , "I2S0_SCLK"    , "PWM0_CHAN2"   , "I2S1_SCLK"    , "SYS_RIO018", "PROC_RIO018", "PIO18"      , "GPCLK1",   },  // 18
    { "SPI1_MISO" , "DPI_D15"      , "I2S0_WS"      , "PWM0_CHAN3"   , "I2S1_WS"      , "SYS_RIO019", "PROC_RIO019", "PIO19"      , },              // 19
    { "SPI1_MOSI" , "DPI_D16"      , "I2S0_SDI0"    , "GPCLK0"       , "I2S1_SDI0"    , "SYS_RIO020", "PROC_RIO020", "PIO20"      , },              // 20
    { "SPI1_SCLK" , "DPI_D17"      , "I2S0_SDO0"    , "GPCLK1"       , "I2S1_SDO0"    , "SYS_RIO021", "PROC_RIO021", "PIO21"      , },              // 21
    { "SD0CLK"    , "DPI_D18"      , "I2S0_SDI1"    , "SDA3"         , "I2S1_SDI1"    , "SYS_RIO022", "PROC_RIO022", "PIO22"      , },              // 22
    { "SD0_CMD"   , "DPI_D19"      , "I2S0_SDO1"    , "SCL3"         , "I2S1_SDO1"    , "SYS_RIO023", "PROC_RIO023", "PIO23"      , },              // 23
    { "SD0_DAT0"  , "DPI_D20"      , "I2S0_SDI2"    , 0              , "I2S1_SDI2"    , "SYS_RIO024", "PROC_RIO024", "PIO24"      , "SPI2_CE1" , }, // 24
    { "SD0_DAT1"  , "DPI_D21"      , "I2S0_SDO2"    , "MIC_CLK"      , "I2S1_SDO2"    , "SYS_RIO025", "PROC_RIO025", "PIO25"      , "SPI3_CE1" , }, // 25
    { "SD0_DAT2"  , "DPI_D22"      , "I2S0_SDI3"    , "MIC_DAT0"     , "I2S1_SDI3"    , "SYS_RIO026", "PROC_RIO026", "PIO26"      , "SPI5_CE1" , }, // 26
    { "SD0_DAT3"  , "DPI_D23"      , "I2S0_SDO3"    , "MIC_DAT1"     , "I2S1_SDO3"    , "SYS_RIO027", "PROC_RIO027", "PIO27"      , "SPI1_CE1" , }, // 27
    { "SD1CLK"    , "SDA4"         , "I2S2_SCLK"    , "SPI6_MISO"    , "VBUS_EN0"     , "SYS_RIO10" , "PROC_RIO10" , },                             // 28
    { "SD1_CMD"   , "SCL4"         , "I2S2_WS"      , "SPI6_MOSI"    , "VBUS_OC0"     , "SYS_RIO11" , "PROC_RIO11" , },                             // 29
    { "SD1_DAT0"  , "SDA5"         , "I2S2_SDI0"    , "SPI6_SCLK"    , "TXD5"         , "SYS_RIO12" , "PROC_RIO12" , },                             // 30
    { "SD1_DAT1"  , "SCL5"         , "I2S2_SDO0"    , "SPI6_CE0"     , "RXD5"         , "SYS_RIO13" , "PROC_RIO13" , },                             // 31
    { "SD1_DAT2"  , "GPCLK3"       , "I2S2_SDI1"    , "SPI6_CE1"     , "CTS5"         , "SYS_RIO14" , "PROC_RIO14" , },                             // 32
    { "SD1_DAT3"  , "GPCLK4"       , "I2S2_SDO1"    , "SPI6_CE2"     , "RTS5"         , "SYS_RIO15" , "PROC_RIO15" , },                             // 33
    { "PWM1_CHAN2", "GPCLK3"       , "VBUS_EN0"     , "SDA4"         , "MIC_CLK"      , "SYS_RIO20" , "PROC_RIO20" , },                             // 34
    { "SPI8_CE1"  , "PWM1_CHAN0"   , "VBUS_OC0"     , "SCL4"         , "MIC_DAT0"     , "SYS_RIO21" , "PROC_RIO21" , },                             // 35
    { "SPI8_CE0"  , "TXD5"         , "PCIE_CLKREQ_N", "SDA5"         , "MIC_DAT1"     , "SYS_RIO22" , "PROC_RIO22" , },                             // 36
    { "SPI8_MISO" , "RXD5"         , "MIC_CLK"      , "SCL5"         , "PCIE_CLKREQ_N", "SYS_RIO23" , "PROC_RIO23" , },                             // 37
    { "SPI8_MOSI" , "RTS5"         , "MIC_DAT0"     , "SDA6"         , "AAUD_LEFT"    , "SYS_RIO24" , "PROC_RIO24" , "DSI0_TE_EXT", },              // 38
    { "SPI8_SCLK" , "CTS5"         , "MIC_DAT1"     , "SCL6"         , "AAUD_RIGHT"   , "SYS_RIO25" , "PROC_RIO25" , "DSI1_TE_EXT", },              // 39
    { "PWM1_CHAN1", "TXD5"         , "SDA4"         , "SPI6_MISO"    , "AAUD_LEFT"    , "SYS_RIO26" , "PROC_RIO26" , },                             // 40
    { "PWM1_CHAN2", "RXD5"         , "SCL4"         , "SPI6_MOSI"    , "AAUD_RIGHT"   , "SYS_RIO27" , "PROC_RIO27" , },                             // 41
    { "GPCLK5"    , "RTS5"         , "VBUS_EN1"     , "SPI6_SCLK"    , "I2S2_SCLK"    , "SYS_RIO28" , "PROC_RIO28" , },                             // 42
    { "GPCLK4"    , "CTS5"         , "VBUS_OC1"     , "SPI6_CE0"     , "I2S2_WS"      , "SYS_RIO29" , "PROC_RIO29" , },                             // 43
    { "GPCLK5"    , "SDA5"         , "PWM1_CHAN0"   , "SPI6_CE1"     , "I2S2_SDI0"    , "SYS_RIO210", "PROC_RIO210", },                             // 44
    { "PWM1_CHAN3", "SCL5"         , "SPI7_CE0"     , "SPI6_CE2"     , "I2S2_SDO0"    , "SYS_RIO211", "PROC_RIO211", },                             // 45
    { "GPCLK3"    , "SDA4"         , "SPI7_MOSI"    , "MIC_CLK"      , "I2S2_SDI1"    , "SYS_RIO212", "PROC_RIO212", "DSI0_TE_EXT", },              // 46
    { "GPCLK5"    , "SCL4"         , "SPI7_MISO"    , "MIC_DAT0"     , "I2S2_SDO1"    , "SYS_RIO213", "PROC_RIO213", "DSI1_TE_EXT", },              // 47
    { "PWM1_CHAN0", "PCIE_CLKREQ_N", "SPI7_SCLK"    , "MIC_DAT1"     , "TXD5"         , "SYS_RIO214", "PROC_RIO214", },                             // 48
    { "SPI8_SCLK" , "SPI7_SCLK"    , "SDA5"         , "AAUD_LEFT"    , "RXD5"         , "SYS_RIO215", "PROC_RIO215", },                             // 49
    { "SPI8_MISO" , "SPI7_MOSI"    , "SCL5"         , "AAUD_RIGHT"   , "VBUS_EN2"     , "SYS_RIO216", "PROC_RIO216", },                             // 50
    { "SPI8_MOSI" , "SPI7_MISO"    , "SDA6"         , "AAUD_LEFT"    , "VBUS_OC2"     , "SYS_RIO217", "PROC_RIO217", },                             // 51
    { "SPI8_CE0"  , 0              , "SCL6"         , "AAUD_RIGHT"   , "VBUS_EN3"     , "SYS_RIO218", "PROC_RIO218", },                             // 52
    { "SPI8_CE1"  , "SPI7_CE0"     , 0              , "PCIE_CLKREQ_N", "VBUS_OC3"     , "SYS_RIO219", "PROC_RIO219", },                             // 53 
};

static void rp1_gpio_get_bank(int num, int *bank, int *offset)
{
    *bank = *offset = 0;
    if (num >= RP1_NUM_GPIOS)
    {
        assert(0);
        return;
    }

    if (num < rp1_bank_base[1])
        *bank = 0;
    else if (num < rp1_bank_base[2])
        *bank = 1;
    else
        *bank = 2;

   *offset = num - rp1_bank_base[*bank];
}

static uint32_t rp1_gpio_ctrl_read( int bank, int offset)
{
    return rp1_gpio_read32(gpio_register, gpio_state.io[bank], RP1_GPIO_IO_REG_CTRL_OFFSET(offset));
}

static void rp1_gpio_ctrl_write( int bank, int offset, uint32_t value)
{
    rp1_gpio_write32(gpio_register, gpio_state.io[bank], RP1_GPIO_IO_REG_CTRL_OFFSET(offset), value);
}

static uint32_t rp1_gpio_pads_read( int bank, int offset)
{
    return rp1_gpio_read32(gpio_register, gpio_state.pads[bank], RP1_GPIO_PADS_REG_OFFSET(offset));
}

static void rp1_gpio_pads_write( int bank, int offset, uint32_t value)
{
    rp1_gpio_write32(gpio_register, gpio_state.pads[bank], RP1_GPIO_PADS_REG_OFFSET(offset), value);
}

static uint32_t rp1_gpio_sys_rio_out_read( int bank )
{
    return rp1_gpio_read32(gpio_register, gpio_state.sys_rio[bank], RP1_GPIO_SYS_RIO_REG_OUT_OFFSET);
}

static uint32_t rp1_gpio_sys_rio_sync_in_read( int bank )
{
    return rp1_gpio_read32(gpio_register, gpio_state.sys_rio[bank], RP1_GPIO_SYS_RIO_REG_SYNC_IN_OFFSET);
}

static void rp1_gpio_sys_rio_out_write( int bank, uint32_t value)
{
    rp1_gpio_write32(gpio_register, gpio_state.sys_rio[bank], RP1_GPIO_SYS_RIO_REG_OUT_OFFSET, value);
}

static void rp1_gpio_sys_rio_out_clr( int bank, int offset )
{
    rp1_gpio_write32(gpio_register, gpio_state.sys_rio[bank],
                     RP1_GPIO_SYS_RIO_REG_OUT_OFFSET + RP1_CLR_OFFSET,
                     1U << offset);
}

static void rp1_gpio_sys_rio_out_set( int bank, int offset )
{
    rp1_gpio_write32(gpio_register, gpio_state.sys_rio[bank],
                     RP1_GPIO_SYS_RIO_REG_OUT_OFFSET + RP1_SET_OFFSET,
                     1U << offset);
}

static uint32_t rp1_gpio_sys_rio_oe_read(int bank)
{
    return rp1_gpio_read32(gpio_register, gpio_state.sys_rio[bank], RP1_GPIO_SYS_RIO_REG_OE_OFFSET);
}

// set direction INPUT
static void rp1_gpio_sys_rio_oe_clr( int bank, int offset)
{
    rp1_gpio_write32(gpio_register, gpio_state.sys_rio[bank],
                     RP1_GPIO_SYS_RIO_REG_OE_OFFSET + RP1_CLR_OFFSET,
                     1U << offset);
}

// set direction OUTPUT
static void rp1_gpio_sys_rio_oe_set( int bank, int offset)
{
    rp1_gpio_write32(gpio_register, gpio_state.sys_rio[bank],
                     RP1_GPIO_SYS_RIO_REG_OE_OFFSET + RP1_SET_OFFSET,
                     1U << offset);
}

static void rp1_gpio_set_dir(uint32_t gpio, GPIO_DIR_T dir)
{
    int bank, offset;

    rp1_gpio_get_bank(gpio, &bank, &offset);

    if (dir == DIR_INPUT)
        rp1_gpio_sys_rio_oe_clr(bank, offset);
    else if (dir == DIR_OUTPUT)
        rp1_gpio_sys_rio_oe_set(bank, offset);
    else
        assert(0);
}

static GPIO_DIR_T rp1_gpio_get_dir(unsigned gpio)
{
    int bank, offset;
    GPIO_DIR_T dir;
    uint32_t reg;

    rp1_gpio_get_bank(gpio, &bank, &offset);
    reg = rp1_gpio_sys_rio_oe_read(bank);

    dir = (reg & (1U << offset)) ? DIR_OUTPUT : DIR_INPUT;

    return dir;
}

static GPIO_FSEL_T rp1_gpio_get_fsel(unsigned gpio)
{
    int bank, offset;
    uint32_t reg;
    GPIO_FSEL_T fsel;
    RP1_FSEL_T rsel;

    rp1_gpio_get_bank(gpio, &bank, &offset);
    reg = rp1_gpio_ctrl_read( bank, offset);
    rsel = ((reg & RP1_GPIO_CTRL_FSEL_MASK) >> RP1_GPIO_CTRL_FSEL_LSB);
    if (rsel == RP1_FSEL_SYS_RIO)
        fsel = GPIO_FSEL_GPIO;
    else if (rsel == RP1_FSEL_NULL)
        fsel = GPIO_FSEL_NONE;
    else if (rsel < RP1_FSEL_COUNT)
        fsel = (GPIO_FSEL_T)rsel;
    else
        fsel = GPIO_FSEL_MAX;

    return fsel;
}

static void rp1_gpio_set_fsel(unsigned gpio, const GPIO_FSEL_T func)
{
    
    int bank, offset;
    uint32_t ctrl_reg;
    uint32_t pad_reg;
    uint32_t old_pad_reg;
    RP1_FSEL_T rsel;

    if (func < (GPIO_FSEL_T)RP1_FSEL_COUNT)
        rsel = (RP1_FSEL_T)func;
    else if (func == GPIO_FSEL_INPUT ||
             func == GPIO_FSEL_OUTPUT ||
             func == GPIO_FSEL_GPIO)
        rsel = RP1_FSEL_SYS_RIO;
    else if (func == GPIO_FSEL_NONE)
        rsel = RP1_FSEL_NULL;
    else
        return;

    rp1_gpio_get_bank(gpio, &bank, &offset);
    if (func == GPIO_FSEL_INPUT)
        rp1_gpio_set_dir(gpio, DIR_INPUT);
    else if (func == GPIO_FSEL_OUTPUT)
        rp1_gpio_set_dir(gpio, DIR_OUTPUT);

    ctrl_reg = rp1_gpio_ctrl_read(bank, offset) & ~RP1_GPIO_CTRL_FSEL_MASK;
    ctrl_reg |= rsel << RP1_GPIO_CTRL_FSEL_LSB;
    rp1_gpio_ctrl_write(bank, offset, ctrl_reg);

    pad_reg = rp1_gpio_pads_read(bank, offset);
    old_pad_reg = pad_reg;
    if (rsel == RP1_FSEL_NULL)
    {
        // Disable input
        pad_reg &= ~RP1_PADS_IE_SET;
    }
    else
    {
        // Enable input
        pad_reg |= RP1_PADS_IE_SET;
    }

    if (rsel != RP1_FSEL_NULL)
    {
        // Enable peripheral func output
        pad_reg &= ~RP1_PADS_OD_SET;
    }
    else
    {
        // Disable peripheral func output
        pad_reg |= RP1_PADS_OD_SET;
    }

    if (pad_reg != old_pad_reg)
        rp1_gpio_pads_write(bank, offset, pad_reg);
}

static int rp1_gpio_get_level(unsigned gpio)
{
    int bank, offset;
    uint32_t pad_reg;
    uint32_t reg;
    int level;

    rp1_gpio_get_bank(gpio, &bank, &offset);
    pad_reg = rp1_gpio_pads_read(bank, offset);
    if (!(pad_reg & RP1_PADS_IE_SET))
	return -1;
    reg = rp1_gpio_sys_rio_sync_in_read(bank);
    level = (reg & (1U << offset)) ? 1 : 0;

    return level;
}

static void rp1_gpio_set_drive(unsigned gpio, GPIO_DRIVE_T drv)
{
    uint32_t reg;
    int bank, offset;

    rp1_gpio_get_bank(gpio, &bank, &offset);
    
    //reg = rp1_gpio_sys_rio_out_read(bank);
    //if (drv == DRIVE_HIGH)
    //    reg |= (1U << offset);
    //else if (drv == DRIVE_LOW)
    //    reg &= ~(1U << offset);
    //rp1_gpio_sys_rio_out_write(bank, reg);
    
    if (drv == DRIVE_HIGH)
        rp1_gpio_sys_rio_out_set(bank, offset);
    else if (drv == DRIVE_LOW)
        rp1_gpio_sys_rio_out_clr(bank, offset);
    else
        assert(0);
    
}

static void rp1_gpio_set_pull( unsigned gpio, GPIO_PULL_T pull)
{
    uint32_t reg;
    int bank, offset;

    rp1_gpio_get_bank(gpio, &bank, &offset);
    reg = rp1_gpio_pads_read(bank, offset);
    reg &= ~(RP1_PADS_PDE_SET | RP1_PADS_PUE_SET);
    if (pull == PULL_UP)
        reg |= RP1_PADS_PUE_SET;
    else if (pull == PULL_DOWN)
        reg |= RP1_PADS_PDE_SET;
    rp1_gpio_pads_write(bank, offset, reg);
}

static GPIO_PULL_T rp1_gpio_get_pull(unsigned gpio)
{
    uint32_t reg;
    GPIO_PULL_T pull = PULL_NONE;
    int bank, offset;

    rp1_gpio_get_bank(gpio, &bank, &offset);
    reg = rp1_gpio_pads_read(bank, offset);
    if (reg & RP1_PADS_PUE_SET)
        pull = PULL_UP;
    else if (reg & RP1_PADS_PDE_SET)
        pull = PULL_DOWN;

    return pull;
}

static void rp1_gpio_set_schmitt( unsigned gpio, GPIO_SCHMITT_T schmitt )
{
    uint32_t reg;
    int bank, offset;

    rp1_gpio_get_bank(gpio, &bank, &offset);
    reg = rp1_gpio_pads_read(bank, offset);
    reg &= ~RP1_PADS_SCHMITT_SET;
    
    if( schmitt == SCHMITT_ON) {
        reg |= RP1_PADS_SCHMITT_SET;
    }
        
    rp1_gpio_pads_write(bank, offset, reg);
}

static GPIO_SCHMITT_T rp1_gpio_get_schmitt(unsigned gpio)
{
    uint32_t reg;
    int bank, offset;

    rp1_gpio_get_bank(gpio, &bank, &offset);
    reg = rp1_gpio_pads_read(bank, offset);
    
    return ( reg & RP1_PADS_SCHMITT_SET ) ? SCHMITT_ON : SCHMITT_OFF;
}

static void rp1_gpio_set_slew( unsigned gpio, GPIO_SLEW_T slew )
{
    uint32_t reg;
    int bank, offset;

    rp1_gpio_get_bank(gpio, &bank, &offset);
    reg = rp1_gpio_pads_read(bank, offset);
    reg &= ~RP1_PADS_SLEW_SET;
    
    if( slew == SLEW_FAST) {
        reg |= RP1_PADS_SLEW_SET;
    }
        
    rp1_gpio_pads_write(bank, offset, reg);
}

static GPIO_SLEW_T rp1_gpio_get_slew(unsigned gpio)
{
    uint32_t reg;
    int bank, offset;

    rp1_gpio_get_bank(gpio, &bank, &offset);
    reg = rp1_gpio_pads_read(bank, offset);
    
    return ( reg & RP1_PADS_SLEW_SET ) ? SLEW_FAST : SLEW_SLOW;
}

static GPIO_DRIVE_T rp1_gpio_get_drive(unsigned gpio)
{
    uint32_t reg;
    int bank, offset;

    rp1_gpio_get_bank(gpio, &bank, &offset);
    reg = rp1_gpio_sys_rio_out_read(bank);
    return (reg & (1U << offset)) ? DRIVE_HIGH : DRIVE_LOW;
}

static const char *rp1_gpio_get_name(unsigned gpio)
{
    static char name_buf[16];
    
    if (gpio >= RP1_NUM_GPIOS)
        return NULL;

    sprintf(name_buf, "GPIO%d", gpio);
    return name_buf;
}

static const char *rp1_gpio_get_fsel_name(unsigned gpio, GPIO_FSEL_T fsel)
{
    const char *name = NULL;
    switch (fsel)
    {
    case GPIO_FSEL_GPIO:
        name = "GPIO";
        break;
    case GPIO_FSEL_INPUT:
        name = "INPUT";
        break;
    case GPIO_FSEL_OUTPUT:
        name = "OUTPUT";
        break;
    case GPIO_FSEL_NONE:
        name = "NONE";
        break;
    case GPIO_FSEL_FUNC0:
    case GPIO_FSEL_FUNC1:
    case GPIO_FSEL_FUNC2:
    case GPIO_FSEL_FUNC3:
    case GPIO_FSEL_FUNC4:
    case GPIO_FSEL_FUNC5:
    case GPIO_FSEL_FUNC6:
    case GPIO_FSEL_FUNC7:
    case GPIO_FSEL_FUNC8:
        if (gpio < RP1_NUM_GPIOS)
        {
            name = rp1_gpio_fsel_names[gpio][fsel - GPIO_FSEL_FUNC0];
            if (!name)
                name = "-";
        }
        break;
    default:
        return NULL;
    }
    return name;
}

static int rp1_gpio_count()
{
    return RP1_NUM_GPIOS;
}

// map a memory block
static uint32_t *  map_gpiomem(int fd, uint32_t addr, uint32_t len)
{
    return (uint32_t *) mmap(0, len, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_SHARED|MAP_LOCKED, fd, addr);
}

// open /dev/gpiomem
static int open_gpiomen(void)
{
    if ((fdMem = open("/dev/gpiomem0", O_RDWR | O_SYNC) ) < 0)
    {
        return PI_INIT_FAILED;
    }
    
    return 0;
}

// initialise lib
static int do_initialise(void)
{    
    if(open_gpiomen() == PI_INIT_FAILED)
    {
        send_module_error("HiPi::GPIO failed to open memory device /dev/gpiomem0");
        do_uninitialise();
        return 0;
    }
    
    gpio_register = map_gpiomem(fdMem, GPIO_BASE, GPIO_LEN);

    if (gpio_register == MAP_FAILED)
    {
        send_module_error("HiPi::GPIO failed to map gpio memory block");
        do_uninitialise();
        return 0;
    }
    
    return 1;
}

// uninitialise lib
static void do_uninitialise(void)
{
    if (gpio_register != MAP_FAILED) munmap((void *)gpio_register, GPIO_LEN);
    gpio_register == MAP_FAILED;
    
    if (fdMem != -1)
    {
       close(fdMem);
       fdMem = -1;
    }
}

// delay microseconds
static void delay_microseconds(uint32_t inputmicros)
{
    struct timespec ts, rem;
    int seconds = inputmicros / 1000000;
    int micros  = inputmicros % 1000000;
    
    ts.tv_sec  = seconds;
    ts.tv_nsec = micros * 1000;
 
    while ( clock_nanosleep(CLOCK_REALTIME, 0, &ts, &rem) )
    {
       ts = rem;
    } 
}

// set pud mode

static void do_gpio_set_pud(unsigned gpio, unsigned pud)
{
    
    if( pud == PI_PUD_UNSET )
        pud = PI_PUD_OFF;
    
    rp1_gpio_set_pull( gpio, pud );

}

// get pud mode

int do_gpio_get_pud(unsigned gpio)
{
    return rp1_gpio_get_pull( gpio );
}

// set pin mode

static void do_gpio_set_mode(unsigned gpio, unsigned mode)
{
   rp1_gpio_set_fsel( gpio, mode );
}


// get pin mode
int do_gpio_get_mode(unsigned gpio)
{
   GPIO_DIR_T dir;
   int mode = rp1_gpio_get_fsel( gpio );
   if( mode == GPIO_FSEL_GPIO ) {
      dir = rp1_gpio_get_dir( gpio );
      if( dir == DIR_OUTPUT ) {
        return GPIO_FSEL_OUTPUT;
      } else {
        return GPIO_FSEL_INPUT;
      }
   } else {
      return mode;
   }
}

// read pin value
static int do_gpio_read(unsigned gpio)
{    
    return rp1_gpio_get_level( gpio );   
}

// write pin value
static void do_gpio_write(unsigned gpio, unsigned level)
{    
    rp1_gpio_set_drive( gpio, level );
}

// get schmitt value
static int do_gpio_get_schmitt(unsigned gpio)
{    
    return rp1_gpio_get_schmitt( gpio );
}

// set schmitt value
static void do_gpio_set_schmitt(unsigned gpio, unsigned schmitt)
{    
    rp1_gpio_set_schmitt( gpio, schmitt );
}

// get slew value
static int do_gpio_get_slew(unsigned gpio)
{    
    return rp1_gpio_get_slew( gpio );
}

// set slew value
static void do_gpio_set_slew(unsigned gpio, unsigned slew)
{    
    rp1_gpio_set_slew( gpio, slew );
}

// send error to module

static void send_module_error( char * error )
{
    dSP;
	ENTER;
        SAVETMPS;
        PUSHMARK(SP);
        EXTEND(SP, 1);
        PUSHs(sv_2mortal(newSVpv(error, 0)));
        PUTBACK;
        call_pv("HiPi::GPIO::error_report", G_DISCARD);
        FREETMPS;
        LEAVE;
}

static uint32_t map_fsel_rp1_to_hipi( GPIO_FSEL_T fsel )
{
    int hipi_mode = HIPI_MODE_NONE;
    switch (fsel)
    {
    case GPIO_FSEL_INPUT:
        hipi_mode = HIPI_MODE_INPUT;
        break;
    case GPIO_FSEL_OUTPUT:
        hipi_mode = HIPI_MODE_OUTPUT;
        break;
    case GPIO_FSEL_GPIO:
        hipi_mode = HIPI_MODE_INPUT;
        break;
    case GPIO_FSEL_FUNC0:
        hipi_mode = HIPI_MODE_ALT0;
        break;
    case GPIO_FSEL_FUNC1:
        hipi_mode = HIPI_MODE_ALT1;
        break;
    case GPIO_FSEL_FUNC2:
        hipi_mode = HIPI_MODE_ALT2;
        break;
    case GPIO_FSEL_FUNC3:
        hipi_mode = HIPI_MODE_ALT3;
        break;
    case GPIO_FSEL_FUNC4:
        hipi_mode = HIPI_MODE_ALT4;
        break;
    case GPIO_FSEL_FUNC5:
        hipi_mode = HIPI_MODE_ALT5;
        break;
    case GPIO_FSEL_FUNC6:
        hipi_mode = HIPI_MODE_ALT6;
        break;
    case GPIO_FSEL_FUNC7:
        hipi_mode = HIPI_MODE_ALT7;
        break;
    case GPIO_FSEL_FUNC8:
        hipi_mode = HIPI_MODE_ALT8;
        break;
    default:
        hipi_mode = HIPI_MODE_NONE;
    }
    return hipi_mode;
}

static GPIO_FSEL_T map_fsel_hipi_to_rp1( int fsel )
{
    GPIO_FSEL_T rp1_mode = GPIO_FSEL_NONE;
    switch (fsel)
    {
    case HIPI_MODE_INPUT:
        rp1_mode = GPIO_FSEL_INPUT;
        break;
    case HIPI_MODE_OUTPUT:
        rp1_mode = GPIO_FSEL_OUTPUT;
        break;
    case HIPI_MODE_ALT0:
        rp1_mode = GPIO_FSEL_FUNC0;
        break;
    case HIPI_MODE_ALT1:
        rp1_mode = GPIO_FSEL_FUNC1;
        break;
    case HIPI_MODE_ALT2:
        rp1_mode = GPIO_FSEL_FUNC2;
        break;
    case HIPI_MODE_ALT3:
        rp1_mode = GPIO_FSEL_FUNC3;
        break;
    case HIPI_MODE_ALT4:
        rp1_mode = GPIO_FSEL_FUNC4;
        break;
    case HIPI_MODE_ALT5:
        rp1_mode = GPIO_FSEL_FUNC5;
        break;
    case HIPI_MODE_ALT6:
        rp1_mode = GPIO_FSEL_FUNC6;
        break;
    case HIPI_MODE_ALT7:
        rp1_mode = GPIO_FSEL_FUNC7;
        break;
    case HIPI_MODE_ALT8:
        rp1_mode = GPIO_FSEL_FUNC8;
        break;
    default:
        rp1_mode = GPIO_FSEL_NONE;
    }
    return rp1_mode;
}

MODULE = HiPi::GPIO::RP1  PACKAGE = HiPi::GPIO

int
xs_initialise_gpio_block()
  CODE:
    RETVAL = do_initialise();
  OUTPUT: RETVAL


void
xs_release_gpio_block()
  CODE:
    do_uninitialise();


int
xs_gpio_write( gpio, level )
    unsigned gpio
    unsigned level
  CODE:
    
    if (gpio > PI_MAX_GPIO) {
        send_module_error("bad gpio number specified");
        RETVAL = PI_PIN_ERROR;
    } else if (level > PI_ON) {
        send_module_error("bad level specified");
        RETVAL = PI_PIN_ERROR;
    } else {
        do_gpio_write( gpio, level );
        RETVAL = (int)level;
    }
    
  OUTPUT: RETVAL


int
xs_gpio_read( gpio )
    unsigned gpio
  CODE:
  
    if (gpio > PI_MAX_GPIO) {
        send_module_error("bad gpio number specified");
        RETVAL = PI_PIN_ERROR;
    } else {
        RETVAL = do_gpio_read(gpio);
    }
    
  OUTPUT: RETVAL


int
xs_gpio_set_mode( gpio, mode )
    unsigned gpio
    unsigned mode
  CODE:
  
    if (gpio > PI_MAX_GPIO) {
        send_module_error("bad gpio number specified");
        RETVAL = PI_PIN_ERROR;
    } else if( mode > PI_MAX_MODE ){
        send_module_error("bad mode specified");
        RETVAL = PI_PIN_ERROR;
    } else {
        do_gpio_set_mode( gpio, map_fsel_hipi_to_rp1( mode ));
        RETVAL = (int)mode;
    }
    
  OUTPUT: RETVAL


int
xs_gpio_get_mode( gpio )
    unsigned gpio
  CODE:
  
    if (gpio > PI_MAX_GPIO) {
        send_module_error("bad gpio number specified");
        RETVAL = PI_PIN_ERROR;
    } else {        
        RETVAL = map_fsel_rp1_to_hipi(do_gpio_get_mode( gpio ));
    }
    
  OUTPUT: RETVAL

void
xs_gpio_get_mode_name( gpio, mode )
    unsigned gpio
    int mode
  PPCODE:
    XPUSHs(sv_2mortal(newSVpv(rp1_gpio_get_fsel_name( gpio, map_fsel_hipi_to_rp1( mode ) ), 0)));

void
xs_gpio_get_current_mode_name( gpio )
    unsigned gpio
  PPCODE:
    XPUSHs(sv_2mortal(newSVpv(rp1_gpio_get_fsel_name( gpio, do_gpio_get_mode( gpio ) ), 0)));

int
xs_gpio_set_pud( gpio, pud )
    unsigned gpio
    unsigned pud
  CODE:
  
    if (gpio > PI_MAX_GPIO) {
        send_module_error("bad gpio number specified");
        RETVAL = PI_PIN_ERROR;
    } else if( pud > PI_PUD_UP ){
        send_module_error("bad pud action specified");
        RETVAL = PI_PIN_ERROR;
    } else {
        // set the pud
        do_gpio_set_pud( gpio, pud);
        RETVAL = (int)pud;
    }
    
  OUTPUT: RETVAL


int
xs_gpio_get_pud( gpio )
    unsigned gpio
  CODE:
  
    if (gpio > PI_MAX_GPIO) {
        send_module_error("bad gpio number specified");
        RETVAL = PI_PIN_ERROR;
    } else {
        // get the pud
        
        RETVAL = do_gpio_get_pud( gpio );
    }
    
  OUTPUT: RETVAL
  
int
xs_gpio_set_schmitt( gpio, schmitt )
    unsigned gpio
    unsigned schmitt
  CODE:
  
    if (gpio > PI_MAX_GPIO) {
        send_module_error("bad gpio number specified");
        RETVAL = PI_PIN_ERROR;
    } else if( schmitt >= SCHMITT_MAX ){
        send_module_error("bad schmitt value specified");
        RETVAL = PI_PIN_ERROR;
    } else {
        do_gpio_set_schmitt( gpio, schmitt);
        RETVAL = (int)schmitt;
    }
    
  OUTPUT: RETVAL


int
xs_gpio_get_schmitt( gpio )
    unsigned gpio
  CODE:
  
    if (gpio > PI_MAX_GPIO) {
        send_module_error("bad gpio number specified");
        RETVAL = PI_PIN_ERROR;
    } else {
        RETVAL = do_gpio_get_schmitt( gpio );
    }
    
  OUTPUT: RETVAL
  
int
xs_gpio_set_slew( gpio, slew )
    unsigned gpio
    unsigned slew
  CODE:
  
    if (gpio > PI_MAX_GPIO) {
        send_module_error("bad gpio number specified");
        RETVAL = PI_PIN_ERROR;
    } else if( slew >= SLEW_MAX ){
        send_module_error("bad slew value specified");
        RETVAL = PI_PIN_ERROR;
    } else {
        do_gpio_set_slew( gpio, slew);
        RETVAL = (int)slew;
    }
    
  OUTPUT: RETVAL


int
xs_gpio_get_slew( gpio )
    unsigned gpio
  CODE:
  
    if (gpio > PI_MAX_GPIO) {
        send_module_error("bad gpio number specified");
        RETVAL = PI_PIN_ERROR;
    } else {        
        RETVAL = do_gpio_get_slew( gpio );
    }
    
  OUTPUT: RETVAL


uint64_t xs_gpio_get_peripheral_base_address()
  CODE:
    RETVAL = PERI_BASE_ADDRESS;
  
  OUTPUT: RETVAL