/*
 * Copyright (c) 2019-2021, Erich Styger
 *
 * Driver for GPIO pins
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include "McuLibconfig.h"
#include "McuGPIO.h"
#include "McuUtility.h"
#include "McuLog.h"
#include <stddef.h>
#include <string.h> /* for memset */
#include <assert.h>
#if MCUGPIO_CONFIG_USE_FREERTOS_HEAP
  #include "McuRTOS.h"
#else
  #include <stdlib.h> /* for malloc()/free() */
#endif
#if McuLib_CONFIG_NXP_SDK_USED
  #include "fsl_gpio.h"
#elif McuLib_CONFIG_CPU_IS_STM32
  /* nothing special needed */
#endif
#if McuLib_CONFIG_CPU_IS_KINETIS
  #include "fsl_port.h"
#elif McuLib_CONFIG_CPU_IS_LPC
  #include "fsl_iocon.h"
#elif McuLib_CONFIG_CPU_IS_IMXRT
  #include "fsl_iomuxc.h"
#elif McuLib_CONFIG_CPU_IS_STM32
  #include "stm32f3xx_hal.h"
#elif McuLib_CONFIG_CPU_IS_ESP32
  #include "driver/gpio.h"
#elif McuLib_CONFIG_CPU_IS_RPxxxx
  #include "hardware/gpio.h"
#elif McuLib_CONFIG_CPU_IS_MCX
  #include "fsl_port.h"
#endif

#if McuLib_CONFIG_CPU_IS_LPC && McuLib_CONFIG_CORTEX_M==0 /* LPC845 specific defines, not available in SDK */
  #define McuGPIO_IOCON_PIO_CLKDIV0 0x00u      /*!<@brief IOCONCLKDIV0 */
  #define McuGPIO_IOCON_PIO_HYS_EN 0x20u       /*!<@brief Enable hysteresis */
  #define McuGPIO_IOCON_PIO_INV_DI 0x00u       /*!<@brief Input not invert */

  #define McuGPIO_IOCON_PIO_MODE_PULL_INACTIVE  (0x0u<<3) /* Inactive (no pull-down/pull-up resistor enabled) */
  #define McuGPIO_IOCON_PIO_MODE_PULL_DOWN      (0x1u<<3) /* Pull-down enabled */
  #define McuGPIO_IOCON_PIO_MODE_PULL_UP        (0x2u<<3) /* Pull-up enabled */
  #define McuGPIO_IOCON_PIO_MODE_PULL_REPEATER  (0x3u<<3) /* Repeater mode */

  #define McuGPIO_IOCON_PIO_OD_DI 0x00u        /*!<@brief Disables Open-drain function */
  #define McuGPIO_IOCON_PIO_SMODE_BYPASS 0x00u /*!<@brief Bypass input filter */

  #define McuGPIO_IOCON_PIO_DEFAULTS  \
                /* Enable hysteresis */ \
                McuGPIO_IOCON_PIO_HYS_EN | \
                /* Input not invert */ \
                McuGPIO_IOCON_PIO_INV_DI | \
                /* Disables Open-drain function */ \
                McuGPIO_IOCON_PIO_OD_DI | \
                /* Bypass input filter */ \
                McuGPIO_IOCON_PIO_SMODE_BYPASS | \
                /* IOCONCLKDIV0 */ \
                McuGPIO_IOCON_PIO_CLKDIV0
#endif

/* default configuration, used for initializing the config */
static const McuGPIO_Config_t defaultConfig =
{
    .isInput = true,
    .isHighOnInit = false,
    .hw = {
      #if McuLib_CONFIG_NXP_SDK_USED && !McuLib_CONFIG_IS_KINETIS_KE
        .gpio = NULL,
      #elif McuLib_CONFIG_CPU_IS_STM32
        .gpio = NULL,
      #elif McuLib_CONFIG_SDK_VERSION_USED==McuLib_CONFIG_SDK_LINUX
        .name = "",
      #endif
    #if McuLib_CONFIG_CPU_IS_KINETIS
      .port = 0,
      #if McuLib_CONFIG_IS_KINETIS_KE
      .portType = 0,
      .portNum = 0,
      #endif
   #elif McuLib_CONFIG_CPU_IS_LPC && McuLib_CONFIG_CORTEX_M==0
      .port = 0,
      .iocon = -1,
    #elif McuLib_CONFIG_CPU_IS_LPC
      .port = 0,
    #elif McuLib_CONFIG_CPU_IS_MCX
      .port = NULL, /* e.g. PORT0 */
    #endif
    #if McuLib_CONFIG_CPU_IS_ESP32
      .pin = GPIO_NUM_NC,
    #else
      .pin = 0,
    #endif
      .pull = McuGPIO_PULL_DISABLE,
    }
};

typedef struct McuGPIO_t {
#if McuLib_CONFIG_CPU_IS_ESP32
  bool isHigh; /* status of output pin, because we cannot toggle pin and we cannot read pin status if it is output pin */
#endif
#if McuLib_CONFIG_SDK_VERSION_USED==McuLib_CONFIG_SDK_LINUX
  struct gpiod_line *line; /* will be requested with gpiod_chip_get_line() */
  bool isHigh; /* status of output pin, because we cannot toggle pin and we cannot read pin status if it is output pin */
#endif
  bool isInput;
  McuGPIO_HwPin_t hw;
} McuGPIO_t;

void McuGPIO_GetDefaultConfig(McuGPIO_Config_t *config) {
  assert(config!=NULL);
  memcpy(config, &defaultConfig, sizeof(*config));
}

static void McuGPIO_ConfigurePin(McuGPIO_t *pin, bool isInput, bool isHighOnInit) {
#if McuLib_CONFIG_NXP_SDK_USED
  gpio_pin_config_t pin_config; /* config for the SDK */

  memset(&pin_config, 0, sizeof(pin_config)); /* init all fields */
  if (isInput) {
  #if McuLib_CONFIG_CPU_IS_IMXRT
    pin_config.direction = kGPIO_DigitalInput;
  #else
    pin_config.pinDirection = kGPIO_DigitalInput;
  #endif
  } else {
  #if McuLib_CONFIG_CPU_IS_IMXRT
    pin_config.direction = kGPIO_DigitalOutput;
  #else
    pin_config.pinDirection = kGPIO_DigitalOutput;
  #endif
  }
  #if McuLib_CONFIG_CPU_IS_IMXRT
  pin_config.interruptMode = kGPIO_NoIntmode; /* no interrupts */
  #endif
  pin_config.outputLogic = isHighOnInit;
  #if McuLib_CONFIG_IS_KINETIS_KE
  GPIO_PinInit(pin->hw.portNum, pin->hw.pin, &pin_config);
  #elif McuLib_CONFIG_CPU_IS_KINETIS
  GPIO_PinInit(pin->hw.gpio, pin->hw.pin, &pin_config);
  #elif McuLib_CONFIG_CPU_IS_LPC
  GPIO_PinInit(pin->hw.gpio, pin->hw.port, pin->hw.pin, &pin_config);
  #elif McuLib_CONFIG_CPU_IS_IMXRT
  GPIO_PinInit(pin->hw.gpio, pin->hw.pin, &pin_config);
  #elif McuLib_CONFIG_CPU_IS_MCX
  GPIO_PinInit(pin->hw.gpio, pin->hw.pin, &pin_config);
  #endif
#elif McuLib_CONFIG_CPU_IS_STM32
  GPIO_InitTypeDef config;

  /* setup initialization structure */
  config.Alternate = 0; /* init */
  if (isInput) {
    config.Mode = GPIO_MODE_INPUT; /* configure as input pin */
  } else {
    config.Mode = GPIO_MODE_OUTPUT_PP; /* configure as push/pull output pin */
  }
  switch(pin->hw.pull) {
    case McuGPIO_PULL_DISABLE: config.Pull = GPIO_NOPULL; break;
    case McuGPIO_PULL_UP: config.Pull = GPIO_PULLUP; break;
    case McuGPIO_PULL_DOWN: config.Pull = GPIO_PULLDOWN; break;
  }
  config.Speed = GPIO_SPEED_FREQ_LOW;
  config.Pin = pin->hw.pin;
  HAL_GPIO_Init(pin->hw.gpio, &config);
  /* write default output level */
  if (!isInput) {
    HAL_GPIO_WritePin(pin->hw.gpio, pin->hw.pin, isHighOnInit?GPIO_PIN_SET:GPIO_PIN_RESET);
  }
#elif McuLib_CONFIG_CPU_IS_ESP32
  if (gpio_reset_pin(pin->hw.pin)!=ESP_OK) {
    McuLog_fatal("failed resetting pin");
  }
  if (isInput) {
    gpio_set_direction(pin->hw.pin, GPIO_MODE_INPUT);
  } else {
    gpio_set_direction(pin->hw.pin, GPIO_MODE_OUTPUT);
  }
  switch(pin->hw.pull) {
    case McuGPIO_PULL_DISABLE: gpio_set_pull_mode(pin->hw.pin, GPIO_FLOATING); break;
    case McuGPIO_PULL_UP:      gpio_set_pull_mode(pin->hw.pin, GPIO_PULLUP_ONLY); break;
    case McuGPIO_PULL_DOWN:    gpio_set_pull_mode(pin->hw.pin, GPIO_PULLDOWN_ONLY); break;
  }
  if (!isInput) {
    gpio_set_level(pin->hw.pin, isHighOnInit?1:0);
    pin->isHigh = isHighOnInit;
  }
#elif McuLib_CONFIG_CPU_IS_RPxxxx
  if (!isInput) { /* set output level first */
    gpio_put(pin->hw.pin, isHighOnInit); /* set initial logic level */
  }
  if (isInput) {
    gpio_set_dir(pin->hw.pin, GPIO_IN);
  } else {
    gpio_set_dir(pin->hw.pin, GPIO_OUT);
  }
#elif McuLib_CONFIG_SDK_VERSION_USED==McuLib_CONFIG_SDK_LINUX
  pin->line = gpiod_chip_get_line(pin->hw.chip, pin->hw.pin);
  if (isInput) {
    gpiod_line_request_input(pin->line, pin->hw.name);
  } else {
    gpiod_line_request_output(pin->line, pin->hw.name, isHighOnInit);
  }
  if (!isInput) {
    gpiod_line_set_value(pin->line, isHighOnInit?1:0);
    pin->isHigh = isHighOnInit;
  }
#endif
}

void McuGPIO_SetAsInput(McuGPIO_Handle_t gpio) {
  McuGPIO_t *pin = (McuGPIO_t*)gpio;

  assert(gpio!=NULL);
  McuGPIO_ConfigurePin(pin, true, false /* don't care */);
  pin->isInput = true;
}

void McuGPIO_SetAsOutput(McuGPIO_Handle_t gpio, bool setHigh) {
  McuGPIO_t *pin = (McuGPIO_t*)gpio;

  assert(gpio!=NULL);
  McuGPIO_ConfigurePin(pin, false, setHigh);
  pin->isInput = false;
}

bool McuGPIO_IsInput(McuGPIO_Handle_t gpio) {
  McuGPIO_t *pin = (McuGPIO_t*)gpio;
  return pin->isInput;
}

bool McuGPIO_IsOutput(McuGPIO_Handle_t gpio) {
  McuGPIO_t *pin = (McuGPIO_t*)gpio;
  return !pin->isInput;
}

#if McuLib_CONFIG_CPU_IS_IMXRT
void McuGPIO_SetMux(McuGPIO_HwPin_t *hw, uint32_t muxRegister, uint32_t muxMode, uint32_t inputRegister, uint32_t inputDaisy, uint32_t configRegister) {
  hw->mux.muxRegister = muxRegister;
  hw->mux.muxMode = muxMode;
  hw->mux.inputRegister = inputRegister;
  hw->mux.inputDaisy = inputDaisy;
  hw->mux.configRegister = configRegister;
}
#endif

McuGPIO_Handle_t McuGPIO_InitGPIO(McuGPIO_Config_t *config) {
  McuGPIO_t *handle;

  assert(config!=NULL);
  /* allocate memory for handle */
#if MCUGPIO_CONFIG_USE_FREERTOS_HEAP
  handle = (McuGPIO_t*)pvPortMalloc(sizeof(McuGPIO_t)); /* get a new device descriptor */
#else
  handle = (McuGPIO_t*)malloc(sizeof(McuGPIO_t)); /* get a new device descriptor */
#endif
  assert(handle!=NULL);
  if (handle!=NULL) { /* if malloc failed, will return NULL pointer */
    memset(handle, 0, sizeof(McuGPIO_t)); /* init all fields */
    handle->isInput = config->isInput;
    memcpy(&handle->hw, &config->hw, sizeof(handle->hw)); /* copy hardware information */
  }
#if McuLib_CONFIG_CPU_IS_RPxxxx
  gpio_init(config->hw.pin);
#endif
  McuGPIO_ConfigurePin(handle, config->isInput, config->isHighOnInit);
  McuGPIO_SetPullResistor((McuGPIO_Handle_t)handle, config->hw.pull); /* GPIO muxing might be done with setting the pull registers, e.g. for LPC845 */

  /* do the pin muxing */
#if McuLib_CONFIG_IS_KINETIS_KE
  /* no pin muxing needed */
#elif McuLib_CONFIG_CPU_IS_KINETIS
  PORT_SetPinMux(config->hw.port, config->hw.pin, kPORT_MuxAsGpio);
#elif McuLib_CONFIG_CPU_IS_LPC && McuLib_CONFIG_CORTEX_M==0 /* e.g. LPC845 */
  /* nothing needed here, because IOCON_PinMuxSet() is already called with the McuGPIO_SetPullResistor() above. */
#elif McuLib_CONFIG_CPU_IS_LPC && McuLib_CONFIG_CORTEX_M==33 /* LPC55S69 for LPC55S16 */
  #define _IOCON_PIO_DIGITAL_EN 0x0100u  /*!<@brief Enables digital function */
  #define _IOCON_PIO_FUNC0 0x00u         /*!<@brief Selects pin function 0 */
  #define _IOCON_PIO_INV_DI 0x00u        /*!<@brief Input function is not inverted */
  #define _IOCON_PIO_MODE_PULLUP 0x20u   /*!<@brief Selects pull-up function */
  #define _IOCON_PIO_OPENDRAIN_DI 0x00u  /*!<@brief Open drain is disabled */
  #define _IOCON_PIO_SLEW_STANDARD 0x00u /*!<@brief Standard mode, output slew rate control is enabled */

  static const uint32_t port_pin_config = (
                                      _IOCON_PIO_FUNC0 |
                                      /* Selects pull-up function */
                                      _IOCON_PIO_MODE_PULLUP |
                                      /* Standard mode, output slew rate control is enabled */
                                      _IOCON_PIO_SLEW_STANDARD |
                                      /* Input function is not inverted */
                                      _IOCON_PIO_INV_DI |
                                      /* Enables digital function */
                                      _IOCON_PIO_DIGITAL_EN |
                                      /* Open drain is disabled */
                                      _IOCON_PIO_OPENDRAIN_DI);
  IOCON_PinMuxSet(IOCON, config->hw.port, config->hw.pin, port_pin_config);
#elif McuLib_CONFIG_CPU_IS_IMXRT
  IOMUXC_SetPinMux( handle->hw.mux.muxRegister,
                    handle->hw.mux.muxMode,
                    handle->hw.mux.inputRegister,
                    handle->hw.mux.inputDaisy,
                    handle->hw.mux.configRegister,
                    0U);
#if McuLib_CONFIG_CPU_VARIANT==McuLib_CONFIG_CPU_VARIANT_NXP_IMXRT1064
  /* GPIO1 and GPIO6 share same IO MUX function, configured in GPR26 */
  if (handle->hw.gpio==GPIO1) {
    IOMUXC_GPR->GPR26 &= ~(1<<handle->hw.pin); /* 0: GPIO1, 1: GPIO6 */
  } else if (handle->hw.gpio==GPIO6) {
    IOMUXC_GPR->GPR26 |= (1<<handle->hw.pin);  /* 0: GPIO1, 1: GPIO6 */

  /* GPIO2 and GPIO7 share same IO MUX function, configured in GPR27 */
  } else if (handle->hw.gpio==GPIO2) {
    IOMUXC_GPR->GPR27 &= (1<<handle->hw.pin);  /* 0: GPIO2, 1: GPIO7 */
  } else if (handle->hw.gpio==GPIO7) {
    IOMUXC_GPR->GPR27 |= (1<<handle->hw.pin);  /* 0: GPIO2, 1: GPIO7 */

  /* GPIO3 and GPIO8 share same IO MUX function, configured in GPR28 */
  } else if (handle->hw.gpio==GPIO3) {
    IOMUXC_GPR->GPR28 &= (1<<handle->hw.pin);  /* 0: GPIO3, 1: GPIO8 */
  } else if (handle->hw.gpio==GPIO8) {
    IOMUXC_GPR->GPR28 |= (1<<handle->hw.pin);  /* 0: GPIO3, 1: GPIO8 */

  /* GPIO4 and GPIO9 share same IO MUX function, configured in GPR29 */
  } else if (handle->hw.gpio==GPIO4) {
    IOMUXC_GPR->GPR29 &= (1<<handle->hw.pin);  /* 0: GPIO4, 1: GPIO9 */
  } else if (handle->hw.gpio==GPIO9) {
    IOMUXC_GPR->GPR29 |= (1<<handle->hw.pin);  /* 0: GPIO4, 1: GPIO9 */
  }
#endif /* McuLib_CONFIG_CPU_VARIANT_NXP_IMXRT1064 */
#elif McuLib_CONFIG_CPU_IS_STM32
  /* no muxing */
#elif McuLib_CONFIG_CPU_IS_ESP32
 /* no muxing needed */
#elif McuLib_CONFIG_CPU_IS_RPxxxx
  /* no muxing needed */
#elif McuLib_CONFIG_SDK_VERSION_USED==McuLib_CONFIG_SDK_LINUX
  /* no muxing needed */
#endif
  return (McuGPIO_Handle_t)handle;
}

McuGPIO_Handle_t McuGPIO_DeinitGPIO(McuGPIO_Handle_t gpio) {
  assert(gpio!=NULL);
#if McuLib_CONFIG_SDK_VERSION_USED==McuLib_CONFIG_SDK_LINUX
  McuGPIO_t *pin = (McuGPIO_t*)gpio;
  gpiod_line_release(pin->line);
#elif McuLib_CONFIG_CPU_IS_RPxxxx
  McuGPIO_t *pin = (McuGPIO_t*)gpio;
  gpio_deinit(pin->hw.pin);
#endif
#if MCUGPIO_CONFIG_USE_FREERTOS_HEAP
  vPortFree(gpio);
#else
  free(gpio);
#endif
  return NULL;
}

void McuGPIO_SetLow(McuGPIO_Handle_t gpio) {
  assert(gpio!=NULL);
  McuGPIO_t *pin = (McuGPIO_t*)gpio;

#if McuLib_CONFIG_CPU_IS_KINETIS
  #if McuLib_CONFIG_SDK_VERSION < 250
  GPIO_ClearPinsOutput(pin->hw.gpio, (1<<pin->hw.pin));
  #elif McuLib_CONFIG_IS_KINETIS_KE
  GPIO_PortClear(pin->hw.portNum, (1<<pin->hw.pin));
  #else
  GPIO_PortClear(pin->hw.gpio, (1<<pin->hw.pin));
  #endif
#elif McuLib_CONFIG_CPU_IS_LPC
  GPIO_PortClear(pin->hw.gpio, pin->hw.port, (1<<pin->hw.pin));
#elif McuLib_CONFIG_CPU_IS_IMXRT
  GPIO_PinWrite(pin->hw.gpio, pin->hw.pin, 0U);
#elif McuLib_CONFIG_CPU_IS_STM32
  HAL_GPIO_WritePin(pin->hw.gpio, pin->hw.pin, GPIO_PIN_RESET);
#elif McuLib_CONFIG_CPU_IS_ESP32
  gpio_set_level(pin->hw.pin, 0); /* low */
  pin->isHigh = false;
#elif McuLib_CONFIG_CPU_IS_RPxxxx
  gpio_put(pin->hw.pin, 0);
#elif McuLib_CONFIG_SDK_VERSION_USED==McuLib_CONFIG_SDK_LINUX
  gpiod_line_set_value(pin->line, 0);
  pin->isHigh = false;
#elif McuLib_CONFIG_CPU_IS_MCX
  GPIO_PinWrite(pin->hw.gpio, pin->hw.pin, 0);
#endif
}

void McuGPIO_SetHigh(McuGPIO_Handle_t gpio) {
  assert(gpio!=NULL);
  McuGPIO_t *pin = (McuGPIO_t*)gpio;

#if McuLib_CONFIG_CPU_IS_KINETIS
  #if McuLib_CONFIG_SDK_VERSION < 250
  GPIO_SetPinsOutput(pin->hw.gpio, (1<<pin->hw.pin));
  #elif McuLib_CONFIG_IS_KINETIS_KE
  GPIO_PortSet(pin->hw.portNum, (1<<pin->hw.pin));
  #else
  GPIO_PortSet(pin->hw.gpio, (1<<pin->hw.pin));
  #endif
#elif McuLib_CONFIG_CPU_IS_LPC
  GPIO_PortSet(pin->hw.gpio, pin->hw.port, (1<<pin->hw.pin));
#elif McuLib_CONFIG_CPU_IS_IMXRT
  GPIO_PinWrite(pin->hw.gpio, pin->hw.pin, 1U);
#elif McuLib_CONFIG_CPU_IS_STM32
  HAL_GPIO_WritePin(pin->hw.gpio, pin->hw.pin, GPIO_PIN_SET);
#elif McuLib_CONFIG_CPU_IS_ESP32
  gpio_set_level(pin->hw.pin, 1); /* high */
  pin->isHigh = true;
#elif McuLib_CONFIG_CPU_IS_RPxxxx
  gpio_put(pin->hw.pin, 1);
#elif McuLib_CONFIG_SDK_VERSION_USED==McuLib_CONFIG_SDK_LINUX
  gpiod_line_set_value(pin->line, 1);
  pin->isHigh = true;
#elif McuLib_CONFIG_CPU_IS_MCX
  GPIO_PinWrite(pin->hw.gpio, pin->hw.pin, 1);
#endif
}

void McuGPIO_Toggle(McuGPIO_Handle_t gpio) {
  assert(gpio!=NULL);
  McuGPIO_t *pin = (McuGPIO_t*)gpio;

#if McuLib_CONFIG_CPU_IS_KINETIS
  #if McuLib_CONFIG_SDK_VERSION < 250
  GPIO_TogglePinsOutput(pin->hw.gpio, (1<<pin->hw.pin));
  #elif McuLib_CONFIG_IS_KINETIS_KE
  GPIO_PortToggle(pin->hw.portNum, (1<<pin->hw.pin));
  #else
  GPIO_PortToggle(pin->hw.gpio, (1<<pin->hw.pin));
  #endif
#elif McuLib_CONFIG_CPU_IS_LPC
  GPIO_PortToggle(pin->hw.gpio, pin->hw.port, (1<<pin->hw.pin));
#elif McuLib_CONFIG_CPU_IS_IMXRT
  GPIO_PortToggle(pin->hw.gpio, (1<<pin->hw.pin));
#elif McuLib_CONFIG_CPU_IS_STM32
  HAL_GPIO_TogglePin(pin->hw.gpio, pin->hw.pin);
#elif McuLib_CONFIG_CPU_IS_ESP32
  if (pin->isHigh) {
    gpio_set_level(pin->hw.pin, 0);
    pin->isHigh = false;
  } else {
    gpio_set_level(pin->hw.pin, 1);
    pin->isHigh = true;
  }
#elif McuLib_CONFIG_CPU_IS_RPxxxx
  gpio_xor_mask(1<<pin->hw.pin);
#elif McuLib_CONFIG_SDK_VERSION_USED==McuLib_CONFIG_SDK_LINUX
  if (pin->isHigh) {
    gpiod_line_set_value(pin->line, 0);
    pin->isHigh = false;
  } else {
    gpiod_line_set_value(pin->line, 1);
    pin->isHigh = true;
  }
#elif McuLib_CONFIG_CPU_IS_MCX
  GPIO_PortToggle(pin->hw.gpio, 1<<pin->hw.pin);
#endif
}

void McuGPIO_SetValue(McuGPIO_Handle_t gpio, bool val) {
  assert(gpio!=NULL);
  McuGPIO_t *pin = (McuGPIO_t*)gpio;

  if (val) { /* set to HIGH */
#if McuLib_CONFIG_CPU_IS_KINETIS
  #if McuLib_CONFIG_SDK_VERSION < 250
    GPIO_SetPinsOutput(pin->hw.gpio, (1<<pin->hw.pin));
  #elif McuLib_CONFIG_IS_KINETIS_KE
    GPIO_PortSet(pin->hw.portNum, (1<<pin->hw.pin));
  #else
    GPIO_PortSet(pin->hw.gpio, (1<<pin->hw.pin));
  #endif
#elif McuLib_CONFIG_CPU_IS_LPC
    GPIO_PortSet(pin->hw.gpio, pin->hw.port, (1<<pin->hw.pin));
#elif McuLib_CONFIG_CPU_IS_IMXRT
    GPIO_PinWrite(pin->hw.gpio, pin->hw.pin, 1U);
#elif McuLib_CONFIG_CPU_IS_STM32
    HAL_GPIO_WritePin(pin->hw.gpio, pin->hw.pin, GPIO_PIN_RESET);
#elif McuLib_CONFIG_CPU_IS_ESP32
    gpio_set_level(pin->hw.pin, 1); /* high */
    pin->isHigh = true;
#elif McuLib_CONFIG_CPU_IS_RPxxxx
    gpio_put(pin->hw.pin, 1);
#elif McuLib_CONFIG_SDK_VERSION_USED==McuLib_CONFIG_SDK_LINUX
    gpiod_line_set_value(pin->line, 0);
    pin->isHigh = false;
#elif McuLib_CONFIG_CPU_IS_MCX
    GPIO_PinWrite(pin->hw.gpio, pin->hw.pin, 1);
#endif
  } else { /* set to LOW */
#if McuLib_CONFIG_CPU_IS_KINETIS
  #if McuLib_CONFIG_SDK_VERSION < 250
    GPIO_ClearPinsOutput(pin->hw.gpio, (1<<pin->hw.pin));
  #elif McuLib_CONFIG_IS_KINETIS_KE
    GPIO_PortClear(pin->hw.portNum, (1<<pin->hw.pin));
  #else
    GPIO_PortClear(pin->hw.gpio, (1<<pin->hw.pin));
  #endif
#elif McuLib_CONFIG_CPU_IS_LPC
    GPIO_PortClear(pin->hw.gpio, pin->hw.port, (1<<pin->hw.pin));
#elif McuLib_CONFIG_CPU_IS_IMXRT
    GPIO_PinWrite(pin->hw.gpio, pin->hw.pin, 1U);
#elif McuLib_CONFIG_CPU_IS_STM32
    HAL_GPIO_WritePin(pin->hw.gpio, pin->hw.pin, GPIO_PIN_SET);
#elif McuLib_CONFIG_CPU_IS_ESP32
    gpio_set_level(pin->hw.pin, 0); /* low */
    pin->isHigh = false;
#elif McuLib_CONFIG_CPU_IS_RPxxxx
    gpio_put(pin->hw.pin, 0);
#elif McuLib_CONFIG_SDK_VERSION_USED==McuLib_CONFIG_SDK_LINUX
    gpiod_line_set_value(pin->line, 1);
    pin->isHigh = true;
#elif McuLib_CONFIG_CPU_IS_MCX
    GPIO_PinWrite(pin->hw.gpio, pin->hw.pin, 0);
#endif
  }
}

bool McuGPIO_GetValue(McuGPIO_Handle_t gpio) {
  return McuGPIO_IsHigh(gpio);
}

bool McuGPIO_IsHigh(McuGPIO_Handle_t gpio) {
  assert(gpio!=NULL);
  McuGPIO_t *pin = (McuGPIO_t*)gpio;

#if McuLib_CONFIG_CPU_IS_KINETIS
  #if McuLib_CONFIG_SDK_VERSION < 250
  return GPIO_ReadPinInput(pin->hw.gpio, pin->hw.pin)!=0;
  #elif McuLib_CONFIG_IS_KINETIS_KE
  return GPIO_PinRead(pin->hw.portNum, pin->hw.pin)!=0;
  #else
  return GPIO_PinRead(pin->hw.gpio, pin->hw.pin)!=0;
  #endif
#elif McuLib_CONFIG_CPU_IS_LPC
  return GPIO_PinRead(pin->hw.gpio, pin->hw.port, pin->hw.pin)!=0;
#elif McuLib_CONFIG_CPU_IS_IMXRT
  return GPIO_PinRead(pin->hw.gpio, pin->hw.pin)!=0;
#elif McuLib_CONFIG_CPU_IS_STM32
  return HAL_GPIO_ReadPin(pin->hw.gpio, pin->hw.pin);
#elif McuLib_CONFIG_CPU_IS_ESP32
  if (pin->isInput) {
    return gpio_get_level(pin->hw.pin);
  } else { /* on ESP32, if pin is configured as output, gpio_get_level() always returns 0 */
    return pin->isHigh;
  }
#elif McuLib_CONFIG_CPU_IS_RPxxxx
  return gpio_get(pin->hw.pin)!=0;
#elif McuLib_CONFIG_SDK_VERSION_USED==McuLib_CONFIG_SDK_LINUX
  return gpiod_line_get_value(pin->line)!=0;
#elif McuLib_CONFIG_CPU_IS_MCX
  return GPIO_PinRead(pin->hw.gpio, pin->hw.pin);
#endif
  return false;
}

bool McuGPIO_IsLow(McuGPIO_Handle_t gpio) {
  return !McuGPIO_IsHigh(gpio);
}

void McuGPIO_GetPinStatusString(McuGPIO_Handle_t gpio, unsigned char *buf, size_t bufSize) {
  McuGPIO_t *pin = (McuGPIO_t*)gpio;

  *buf = '\0';
  if (McuGPIO_IsOutput(gpio)) {
    McuUtility_strcat(buf, bufSize, (unsigned char*)"Out:");
  } else {
    McuUtility_strcat(buf, bufSize, (unsigned char*)"Inp:");
  }
  if (McuGPIO_IsHigh(gpio)) {
    McuUtility_strcat(buf, bufSize, (unsigned char*)"H");
  } else {
    McuUtility_strcat(buf, bufSize, (unsigned char*)"L");
  }
#if (McuLib_CONFIG_NXP_SDK_USED || McuLib_CONFIG_CPU_IS_STM32) && !McuLib_CONFIG_IS_KINETIS_KE
  McuUtility_strcat(buf, bufSize, (unsigned char*)" gpio:0x");
  McuUtility_strcatNum32Hex(buf, bufSize, (uint32_t)pin->hw.gpio); /* write address */
#endif
#if McuLib_CONFIG_CPU_IS_KINETIS
  McuUtility_strcat(buf, bufSize, (unsigned char*)" port:0x");
  McuUtility_strcatNum32Hex(buf, bufSize, (uint32_t)pin->hw.port); /* write address */
#elif McuLib_CONFIG_CPU_IS_LPC /* all LPC, including M33 and M0+ */
  McuUtility_strcat(buf, bufSize, (unsigned char*)" port:");
  McuUtility_strcatNum32u(buf, bufSize, (uint32_t)pin->hw.port); /* write port number */
#endif
  McuUtility_strcat(buf, bufSize, (unsigned char*)" pin:");
  McuUtility_strcatNum32u(buf, bufSize, (uint32_t)pin->hw.pin); /* write pin number */
#if McuLib_CONFIG_CPU_IS_LPC && McuLib_CONFIG_CORTEX_M==0
  McuUtility_strcat(buf, bufSize, (unsigned char*)" iocon:");
  McuUtility_strcatNum32u(buf, bufSize, (uint32_t)pin->hw.iocon); /* write IOCON number */
#endif
  McuUtility_strcat(buf, bufSize, (unsigned char*)" pull:");
  switch(pin->hw.pull) {
    case McuGPIO_PULL_DISABLE:
      McuUtility_strcat(buf, bufSize, (unsigned char*)"dis");
      break;
    case McuGPIO_PULL_UP:
      McuUtility_strcat(buf, bufSize, (unsigned char*)"up ");
      break;
    case McuGPIO_PULL_DOWN:
      McuUtility_strcat(buf, bufSize, (unsigned char*)"dwn");
      break;
    default:
      McuUtility_strcat(buf, bufSize, (unsigned char*)"???");
      break;
  }
}

void McuGPIO_SetPullResistor(McuGPIO_Handle_t gpio, McuGPIO_PullType pull) {
  McuGPIO_t *pin = (McuGPIO_t*)gpio;

  pin->hw.pull = pull;
#if McuLib_CONFIG_IS_KINETIS_KE
  /* only pull-up */
  if (pull == McuGPIO_PULL_DISABLE) {
    PORT_SetPinPullUpEnable(pin->hw.port, pin->hw.portType, pin->hw.pin, false);
  } else if (pull == McuGPIO_PULL_UP) {
    PORT_SetPinPullUpEnable(pin->hw.port, pin->hw.portType, pin->hw.pin, true);
  }
#elif McuLib_CONFIG_CPU_IS_KINETIS
   /* mask out bits for Pull-Select, Pull-Enable and Interrupt status flags, then OR-ing the new flags */
  if (pull == McuGPIO_PULL_DISABLE) {
    pin->hw.port->PCR[pin->hw.pin] = ((pin->hw.port->PCR[pin->hw.pin] &
                      /* Mask bits to zero which are setting */
                      (~(PORT_PCR_PS_MASK | PORT_PCR_PE_MASK | PORT_PCR_ISF_MASK)))
                     | PORT_PCR_PE(0)); /* disable pull resistor */
  } else if (pull == McuGPIO_PULL_UP) {
    pin->hw.port->PCR[pin->hw.pin] = ((pin->hw.port->PCR[pin->hw.pin] &
                      /* Mask bits to zero which are setting */
                      (~(PORT_PCR_PS_MASK | PORT_PCR_PE_MASK | PORT_PCR_ISF_MASK)))
                     /* | PORT_PCR_PFE(1) */ /* enable passive filter */
                     | PORT_PCR_PS(1) /* pull select 1: pull-up */
                     | PORT_PCR_PE(1)); /* enable pull resistor */
  } else if (pull == McuGPIO_PULL_DOWN) {
    pin->hw.port->PCR[pin->hw.pin] = ((pin->hw.port->PCR[pin->hw.pin] &
                      /* Mask bits to zero which are setting */
                      (~(PORT_PCR_PS_MASK | PORT_PCR_PE_MASK | PORT_PCR_ISF_MASK)))
                     | PORT_PCR_PS(0) /* pull select 0: pull-down */
                     | PORT_PCR_PE(1)); /* enable pull resistor */
  }
#elif McuLib_CONFIG_CPU_IS_LPC && McuLib_CONFIG_CORTEX_M==0 /* e.g. LPC845 */
  uint32_t IOCON_config;

  if (pull == McuGPIO_PULL_DISABLE) {
    IOCON_config = ( McuGPIO_IOCON_PIO_MODE_PULL_INACTIVE |  McuGPIO_IOCON_PIO_DEFAULTS);
  } else if (pull == McuGPIO_PULL_UP) {
    IOCON_config = ( McuGPIO_IOCON_PIO_MODE_PULL_UP |  McuGPIO_IOCON_PIO_DEFAULTS);
  } else if (pull == McuGPIO_PULL_DOWN) {
    IOCON_config = ( McuGPIO_IOCON_PIO_MODE_PULL_DOWN |  McuGPIO_IOCON_PIO_DEFAULTS);
  } else {
    IOCON_config = ( McuGPIO_IOCON_PIO_MODE_PULL_INACTIVE |  McuGPIO_IOCON_PIO_DEFAULTS);
  }
  IOCON_PinMuxSet(IOCON, pin->hw.iocon, IOCON_config);
#elif McuLib_CONFIG_CPU_IS_LPC
  uint32_t config;

  /*! IOCON_PIO_MODE:
   *  MODE - Selects function mode (on-chip pull-up/pull-down resistor control).
   *  0b00..Inactive. Inactive (no pull-down/pull-up resistor enabled).
   *  0b01..Pull-down. Pull-down resistor enabled.
   *  0b10..Pull-up. Pull-up resistor enabled.
   *  0b11..Repeater. Repeater mode.
   */
  if (pull == McuGPIO_PULL_DISABLE) {
    config = IOCON_PIO_MODE(0b00); /* inactive */
  } else if (pull == McuGPIO_PULL_UP) {
    config = IOCON_PIO_MODE(0b10); /* pull-up */
  } else if (pull == McuGPIO_PULL_DOWN) {
    config = IOCON_PIO_MODE(0b01); /* pull-down */
  } else { /* default */
    config = IOCON_PIO_MODE(0b00); /* inactive */
  }
  IOCON->PIO[pin->hw.port][pin->hw.pin] = ((IOCON->PIO[pin->hw.port][pin->hw.pin] &
                       (~(IOCON_PIO_MODE_MASK))) /* Mask bits to zero which are setting */
                      | config); /* Select function mode (on-chip pull-up/pull-down resistor control) */
#elif McuLib_CONFIG_CPU_IS_IMXRT
  if (pin->hw.mux.configRegister!=0) {
    uint32_t configValue; /* configuration of IOMUXC SW_PAD_CTL (hw.mux.configRegister) */

    configValue = *((volatile uint32_t *)pin->hw.mux.configRegister); /* read current config */
    #if 0
    configValue = (0<<16) /* Hysteresis enable field: 0: hysteresis disabled */
        | (0b00<<14) /* pull-up/down: 00: 100k Ohm pull-down, 01: 47k pull-up, 10: 100k pull-up, 11: 22k pull-up */
        | (0<<13) /* pull/keep select: 0: keeper, 1: pull */
        | (0<<12) /* pull/keep enable: 0: disabled, 1: enabled */
        | (0<<11) /* open drain: 0: disabled, 1: enabled */
        | (0b00<<6) /* speed: 00: 50 MHz, 01: 100 MHz, 10: 100 MHz, 11: 200 MHz */
        | (0b000<<3) /* drive strength: 000: disabled */
        | (0<<0) /* slew rage: 0: slow slew rate, 1: fast slew rate */
         ;
    #endif
    /* masking out the bits we are going to set: */
    configValue &= ~((0b11<<14) /* pull-up/down */
                  | (1<<13)     /* pull/keep select: */
                  | (1<<12));   /* pull/keep enable */
    if (pull == McuGPIO_PULL_DISABLE) {
      configValue |= (0b00<<14) /* pull-up/down: 00: 100k Ohm pull-down, 01: 47k pull-up, 10: 100k pull-up, 11: 22k pull-up */
                   | (0<<13) /* pull/keep select: 0: keeper, 1: pull */
                   | (0<<12); /* pull/keep enable: 0: disabled, 1: enabled */
    } else if (pull == McuGPIO_PULL_UP) { /* 100k pull-up */
      configValue |= (0b10<<14) /* pull-up/down: 00: 100k Ohm pull-down, 01: 47k pull-up, 10: 100k pull-up, 11: 22k pull-up */
                   | (1<<13) /* pull/keep select: 0: keeper, 1: pull */
                   | (1<<12); /* pull/keep enable: 0: disabled, 1: enabled */
    } else if (pull == McuGPIO_PULL_DOWN) { /* 100k pull-down */
      configValue |= (0b00<<14) /* pull-up/down: 00: 100k Ohm pull-down, 01: 47k pull-up, 10: 100k pull-up, 11: 22k pull-up */
                   | (1<<13) /* pull/keep select: 0: keeper, 1: pull */
                   | (1<<12); /* pull/keep enable: 0: disabled, 1: enabled */
    }
    IOMUXC_SetPinConfig(pin->hw.mux.muxRegister,
                        pin->hw.mux.muxMode,
                        pin->hw.mux.inputRegister,
                        pin->hw.mux.inputDaisy,
                        pin->hw.mux.configRegister,
                        configValue);
  }
#elif McuLib_CONFIG_CPU_IS_STM32 
  McuGPIO_ConfigurePin(pin, false /* don't care as only for output */, &pin->hw);
#elif McuLib_CONFIG_CPU_IS_ESP32
  if (pull == McuGPIO_PULL_DISABLE) {
    gpio_set_pull_mode(pin->hw.pin, GPIO_FLOATING);
  } else if (pull == McuGPIO_PULL_UP) {
    gpio_set_pull_mode(pin->hw.pin, GPIO_PULLUP_ONLY);
  } else if (pull == McuGPIO_PULL_DOWN) {
    gpio_set_pull_mode(pin->hw.pin, GPIO_PULLDOWN_ONLY);
  }
#elif McuLib_CONFIG_CPU_IS_RPxxxx
  if (pull == McuGPIO_PULL_DISABLE) {
    gpio_disable_pulls(pin->hw.pin);
  } else if (pull == McuGPIO_PULL_UP) {
    gpio_pull_up(pin->hw.pin);
  } else if (pull == McuGPIO_PULL_DOWN) {
    gpio_pull_down(pin->hw.pin);
  }
#elif McuLib_CONFIG_CPU_IS_MCX
  port_pin_config_t config = {
      .pullSelect=kPORT_PullDisable,
      /* Low internal pull resistor value is selected. */
      .pullValueSelect = kPORT_LowPullResistor,
      /* Fast slew rate is configured */
      .slewRate=kPORT_FastSlewRate,
      /* Passive input filter is disabled */
      .passiveFilterEnable=kPORT_PassiveFilterDisable,
      /* Open drain output is disabled */
      .openDrainEnable=kPORT_OpenDrainDisable,
      /* Low drive strength is configured */
      .driveStrength=kPORT_LowDriveStrength,
      /* Pin is configured as PIO0_10 */
      .mux=kPORT_MuxAlt0,
      /* Digital input enabled */
      .inputBuffer=kPORT_InputBufferEnable,
      /* Digital input is not inverted */
      .invertInput=kPORT_InputNormal,
      /* Pin Control Register fields [15:0] are not locked */
      .lockRegister=kPORT_UnlockRegister
  };
  if (pull == McuGPIO_PULL_DISABLE) {
    config.pullSelect = kPORT_PullDisable; /* inactive */
  } else if (pull == McuGPIO_PULL_UP) {
    config.pullSelect = kPORT_PullUp; /* pull-up */
  } else if (pull == McuGPIO_PULL_DOWN) {
    config.pullSelect = kPORT_PullDown; /* pull-down */
  } else { /* default */
    config.pullSelect = kPORT_PullDisable; /* inactive */
  }
  PORT_SetPinConfig(pin->hw.port, pin->hw.pin, (const port_pin_config_t*)&config);
#endif
}

void McuGPIO_Init(void) {
  /* nothing to do */
}

void McuGPIO_Deinit(void) {
  /* nothing to do */
}
