/*
 * This file is part of the MicroPython project, http://micropython.org/
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2020-2021 Damien P. George
 * Copyright (c) 2026 Robert Hammelrath
 *
 * 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 OR COPYRIGHT HOLDERS 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.
 */

#if MICROPY_PY_MACHINE_QECNT

#include "py/runtime.h"
#include "py/mphal.h"
#include "py/objint.h"
#include "shared/runtime/mpirq.h"
#include "extmod/modmachine.h"
#include "modmachine.h"
#include "fsl_clock.h"
#include "fsl_enc.h"
#include "fsl_xbara.h"
#include "fsl_iomuxc.h"
#include "fsl_gpio.h"

typedef struct _machine_encoder_obj_t {
    mp_obj_base_t base;
    ENC_Type *instance;
    int8_t id;
    bool active;
    uint8_t input_a;
    uint8_t input_b;
    uint8_t mode;
    uint8_t match_pin;
    uint8_t phases_inv;
    uint32_t max_count;
    uint32_t min_count;
    uint32_t filter;
    mp_obj_t reset_pin;
    uint16_t mp_irq_flags;
    uint16_t mp_irq_trigger;
    mp_irq_obj_t *mp_irq_obj;
    enc_config_t enc_config;
} machine_encoder_obj_t;

typedef struct _encoder_xbar_signal_t {
    xbar_output_signal_t enc_input_a;
    xbar_output_signal_t enc_input_b;
    xbar_output_signal_t enc_index;
    xbar_output_signal_t enc_home;
    xbar_output_signal_t enc_trigger;
    xbar_input_signal_t enc_match;
} encoder_xbar_signal_t;

#define ENCODER_ALL_INTERRUPTS     (0x7f)

#define XBAR_IN                    (1)
#define XBAR_OUT                   (0)

#define COUNTER_UP                 (-2)
#define COUNTER_DOWN               (-3)
#define MODE_ENCODER               (0)
#define MODE_COUNTER               (1)
#define MP_ENCODER_ALLOWED_FLAGS   (kENC_HOMETransitionFlag | kENC_INDEXPulseFlag | kENC_PositionCompareFlag | kENC_PositionRollUnderFlag | kENC_PositionRollOverFlag)
static void encoder_deinit_single(machine_encoder_obj_t *self);

#if defined MIMXRT117x_SERIES

#define XBAR_ENC_DIR_OFFSET_1      (4)
#define XBAR_ENC_DIR_REGISTER_1    GPR20
#define XBAR_ENC_DIR_OFFSET_2      (32)
#define XBAR_ENC_DIR_REGISTER_2    GPR21
#define XBAR_OUT_MIN               (4)
#define XBAR_OUT_MAX               (42)
#define XBAR_STRING                "XBAR1_INOUT"
#define XBAR_STRING_LEN            strlen(XBAR_STRING)

static encoder_xbar_signal_t xbar_signal_table[FSL_FEATURE_SOC_ENC_COUNT] = {
    { kXBARA1_OutputDec1Phasea,
      kXBARA1_OutputDec1Phaseb,
      kXBARA1_OutputDec1Index,
      kXBARA1_OutputDec1Home,
      kXBARA1_OutputDec1Trigger,
      kXBARA1_InputDec1PosMatch },

    { kXBARA1_OutputDec2Phasea,
      kXBARA1_OutputDec2Phaseb,
      kXBARA1_OutputDec2Index,
      kXBARA1_OutputDec2Home,
      kXBARA1_OutputDec2Trigger,
      kXBARA1_InputDec2PosMatch },

    { kXBARA1_OutputDec3Phasea,
      kXBARA1_OutputDec3Phaseb,
      kXBARA1_OutputDec3Index,
      kXBARA1_OutputDec3Home,
      kXBARA1_OutputDec3Trigger,
      kXBARA1_InputDec3PosMatch },

    { kXBARA1_OutputDec4Phasea,
      kXBARA1_OutputDec4Phaseb,
      kXBARA1_OutputDec4Index,
      kXBARA1_OutputDec4Home,
      kXBARA1_OutputDec4Trigger,
      kXBARA1_InputDec4PosMatch },
};

#else // defined MIMXRT117x_SERIES

#if !defined(XBAR_ENC_DIR_OFFSET)
#define XBAR_ENC_DIR_OFFSET        (12)
#define XBAR_ENC_DIR_REGISTER      GPR6
#define XBAR_OUT_MIN               (4)
#define XBAR_OUT_MAX               (19)
#endif
#define XBAR_STRING                "XBAR_INOUT"
#define XBAR_STRING_LEN            strlen(XBAR_STRING)

static encoder_xbar_signal_t xbar_signal_table[FSL_FEATURE_SOC_ENC_COUNT] = {
    { kXBARA1_OutputEnc1PhaseAInput,
      kXBARA1_OutputEnc1PhaseBInput,
      kXBARA1_OutputEnc1Index,
      kXBARA1_OutputEnc1Home,
      kXBARA1_OutputEnc1Trigger,
      kXBARA1_InputEnc1PosMatch },

    #if FSL_FEATURE_SOC_ENC_COUNT > 1

    { kXBARA1_OutputEnc2PhaseAInput,
      kXBARA1_OutputEnc2PhaseBInput,
      kXBARA1_OutputEnc2Index,
      kXBARA1_OutputEnc2Home,
      kXBARA1_OutputEnc2Trigger,
      kXBARA1_InputEnc2PosMatch },

    #if FSL_FEATURE_SOC_ENC_COUNT > 2

    { kXBARA1_OutputEnc3PhaseAInput,
      kXBARA1_OutputEnc3PhaseBInput,
      kXBARA1_OutputEnc3Index,
      kXBARA1_OutputEnc3Home,
      kXBARA1_OutputEnc3Trigger,
      kXBARA1_InputEnc3PosMatch },

    { kXBARA1_OutputEnc4PhaseAInput,
      kXBARA1_OutputEnc4PhaseBInput,
      kXBARA1_OutputEnc4Index,
      kXBARA1_OutputEnc4Home,
      kXBARA1_OutputEnc4Trigger,
      kXBARA1_InputEnc4PosMatch },

    #endif
    #endif
};
#endif // defined MIMXRT117x_SERIES

static machine_encoder_obj_t *encoder_table[FSL_FEATURE_SOC_ENC_COUNT];
static ENC_Type *enc_instances[] = ENC_BASE_PTRS;
static IRQn_Type enc_irqn[] = ENC_COMPARE_IRQS;

__attribute__((section(".ram_functions"))) void irq_callback(int irq_num) {
    machine_encoder_obj_t *self = encoder_table[irq_num];
    if (self != NULL) {
        self->mp_irq_flags = ENC_GetStatusFlags(self->instance);
        // In case of a position match event, disable that interrupt such that is is only handled
        // once until enabled again. This is needed since otherwise the match interrupt will
        // be triggered again as long as the match condition is true.
        if (self->mp_irq_flags & kENC_PositionCompareFlag) {
            ENC_DisableInterrupts(self->instance, kENC_PositionCompareInerruptEnable);
        }
        ENC_ClearStatusFlags(self->instance, self->mp_irq_flags);
        __DSB();
        mp_irq_handler(self->mp_irq_obj);
    }
}

__attribute__((section(".ram_functions"))) void ENC1_IRQHandler(void) {
    irq_callback(0);
}

#if FSL_FEATURE_SOC_ENC_COUNT > 1

__attribute__((section(".ram_functions"))) void ENC2_IRQHandler(void) {
    irq_callback(1);
}

#if FSL_FEATURE_SOC_ENC_COUNT > 2
__attribute__((section(".ram_functions"))) void ENC3_IRQHandler(void) {
    irq_callback(2);
}

__attribute__((section(".ram_functions"))) void ENC4_IRQHandler(void) {
    irq_callback(3);
}
#endif
#endif

static void mp_machine_encoder_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
    machine_encoder_obj_t *self = MP_OBJ_TO_PTR(self_in);
    if (self->mode == MODE_ENCODER) {
        int32_t min = (int32_t)self->min_count;
        if (min > 0) {
            min += (self->phases_inv - 1);
        }
        int32_t max = (int32_t)self->max_count;
        if (max > 0) {
            max += (self->phases_inv - 1);
        }
        int32_t match = (int32_t)self->enc_config.positionCompareValue;
        if (match > 0) {
            match += (self->phases_inv - 1);
        }
        mp_printf(print, "Encoder(%d, phases=%d, max=%ld, min=%ld, match=%ld, filter_ns=%lu)",
            self->id, 4 / self->phases_inv,
            max / self->phases_inv, min / self->phases_inv,
            match / self->phases_inv, self->filter);
    } else {
        mp_printf(print, "Counter(%d, max=%ld, min=%ld, match=%ld, filter_ns=%lu)",
            self->id, self->max_count, self->min_count,
            self->enc_config.positionCompareValue, self->filter);
    }
}

// Utility functions
//

static void encoder_set_iomux(const machine_pin_obj_t *pin, const machine_pin_af_obj_t *af) {
    IOMUXC_SetPinMux(pin->muxRegister, af->af_mode, af->input_register, af->input_daisy, pin->configRegister, 0U);
    IOMUXC_SetPinConfig(pin->muxRegister, af->af_mode, af->input_register, af->input_daisy, pin->configRegister, 0x10B0U);
}

// decode the AF objects module and Port numer. Returns NULL if it is not a XBAR object
static const machine_pin_af_obj_t *af_name_decode_xbar(const machine_pin_af_obj_t *af_obj,
    xbar_input_signal_t *io_number) {
    const char *str;
    size_t len;
    size_t xlen = XBAR_STRING_LEN;
    str = (char *)qstr_data(af_obj->name, &len);
    // test for the name starting with XBAR
    if (len < (xlen + 2) || strncmp(str, XBAR_STRING, xlen) != 0) {
        return NULL;
    }
    // Get I/O number, e.g. XBAR_INOUT03
    *io_number = (str[xlen] - '0') * 10 + (str[xlen + 1] - '0');
    return af_obj;
}

static const machine_pin_af_obj_t *find_xbar_af(const machine_pin_obj_t *pin, xbar_input_signal_t *io_number) {
    const machine_pin_af_obj_t *af = NULL;
    for (int i = 0; i < pin->af_list_len; ++i) {
        af = af_name_decode_xbar(&(pin->af_list[i]), io_number);
        if (af != NULL) {
            return af;
        }
    }
    mp_raise_ValueError(MP_ERROR_TEXT("invalid input Pin"));
}

static uint8_t connect_pin_to_encoder(mp_obj_t desc, xbar_output_signal_t encoder_signal, uint8_t direction) {
    xbar_input_signal_t xbar_pin;
    const machine_pin_obj_t *pin = pin_find(desc);
    const machine_pin_af_obj_t *af = find_xbar_af(pin,  &xbar_pin);
    encoder_set_iomux(pin, af);
    if (direction == XBAR_IN) {
        XBARA_SetSignalsConnection(XBARA1, xbar_pin, encoder_signal);
    } else {
        // No API here, so do basic Register access.
        #if defined MIMXRT117x_SERIES
        if (xbar_pin >= XBAR_OUT_MIN && xbar_pin <= XBAR_OUT_MAX) {
            if (xbar_pin < XBAR_ENC_DIR_OFFSET_2) {
                IOMUXC_GPR->XBAR_ENC_DIR_REGISTER_1 |= 1 << (xbar_pin - XBAR_ENC_DIR_OFFSET_1);
                XBARA_SetSignalsConnection(XBARA1, encoder_signal, xbar_pin);
            } else {
                IOMUXC_GPR->XBAR_ENC_DIR_REGISTER_2 |= 1 << (xbar_pin - XBAR_ENC_DIR_OFFSET_2);
                XBARA_SetSignalsConnection(XBARA1, encoder_signal, xbar_pin);
            }
        } else {
            mp_raise_ValueError(MP_ERROR_TEXT("invalid match Pin"));
        }
        #else
        if (xbar_pin >= XBAR_OUT_MIN && xbar_pin <= XBAR_OUT_MAX) {
            IOMUXC_GPR->XBAR_ENC_DIR_REGISTER |= 1 << (xbar_pin + XBAR_ENC_DIR_OFFSET); // Compare the offset 12 with other MCU
            XBARA_SetSignalsConnection(XBARA1, encoder_signal, xbar_pin);
        } else {
            mp_raise_ValueError(MP_ERROR_TEXT("invalid match Pin"));
        }
        #endif  // defined MIMXRT117x_SERIES
    }
    return xbar_pin;
}

static void clear_encoder_registers(machine_encoder_obj_t *self) {
    // Create a High pulse on the Trigger input, clearing Position, Revolution and Hold registers.
    XBARA_SetSignalsConnection(XBARA1, kXBARA1_InputLogicHigh, xbar_signal_table[self->id].enc_home);
    XBARA_SetSignalsConnection(XBARA1, kXBARA1_InputLogicLow, xbar_signal_table[self->id].enc_home);
    if (self->reset_pin) {
        connect_pin_to_encoder(self->reset_pin, xbar_signal_table[self->id].enc_home, XBAR_IN);
    }
}

//
// Functions for configuring the ENC Device
//
// Calculate the filter parameters based on a filter_ns value, telling the shortest
// pulse that will be detected.
//
static uint32_t calc_filter(uint32_t filter_ns, uint16_t *count, uint16_t *period) {

    #if defined MIMXRT117x_SERIES
    uint32_t freq_khz = CLOCK_GetRootClockFreq(kCLOCK_Root_Bus) / 1000;
    #else
    uint32_t freq_khz = CLOCK_GetIpgFreq() / 1000;
    #endif

    uint32_t cycles = (filter_ns * (freq_khz / 1000)) / 1000;
    if (cycles == 0) {
        // Set filter off
        *count = 0;
        *period = 0;
    } else {
        uint16_t pmax = cycles / 10;
        if (pmax > 255) {
            pmax = 255;
        }
        if (pmax == 0) {
            pmax = 1;
        }
        uint16_t cnt;
        cnt = cycles / pmax;
        if (cnt > 10) {
            cnt = 10;
        }
        *count = cnt >= 3 ? cnt - 3 : 0;
        *period = pmax;
    }
    return ((1000000000 / freq_khz) + 1) * (*count + 3) * *period / 1000;
}

// MicroPython API functions
//
static void mp_machine_encoder_init_helper_common(machine_encoder_obj_t *self,
    mp_arg_val_t args[], enc_config_t *enc_config) {

    enum { ARG_reset, ARG_match, ARG_match_pin, ARG_filter_ns, ARG_max, ARG_min, ARG_index };

    // Check for a Home pin, resetting the counters
    if (args[ARG_reset].u_obj != MP_ROM_INT(-1)) {
        if (args[ARG_reset].u_obj != mp_const_none) {
            self->reset_pin = args[ARG_reset].u_obj;
            connect_pin_to_encoder(self->reset_pin, xbar_signal_table[self->id].enc_home, XBAR_IN);
            self->enc_config.HOMETriggerMode = kENC_HOMETriggerOnRisingEdge;
        } else {
            XBARA_SetSignalsConnection(XBARA1, kXBARA1_InputLogicLow, xbar_signal_table[self->id].enc_home);
            self->reset_pin = NULL;
        }
    }

    // Check for a Match pin for the compare match signal
    if (args[ARG_match_pin].u_obj != MP_ROM_INT(-1)) {
        if (args[ARG_match_pin].u_obj != mp_const_none) {
            self->match_pin = connect_pin_to_encoder(args[ARG_match_pin].u_obj, xbar_signal_table[self->id].enc_match, XBAR_OUT);
        } else {
            // Disconnect the XBAR from the output by switching it to an input.
            #if defined MIMXRT117x_SERIES
            if (self->match_pin < XBAR_ENC_DIR_OFFSET_2) {
                IOMUXC_GPR->XBAR_ENC_DIR_REGISTER_1 &= ~(1 << (self->match_pin - XBAR_ENC_DIR_OFFSET_1));
            } else {
                IOMUXC_GPR->XBAR_ENC_DIR_REGISTER_2 &= ~(1 << (self->match_pin - XBAR_ENC_DIR_OFFSET_2));
            }
            #else
            IOMUXC_GPR->XBAR_ENC_DIR_REGISTER &= ~(1 << (self->match_pin + XBAR_ENC_DIR_OFFSET));
            #endif
        }
    }

    if (args[ARG_match].u_obj != mp_const_none) {
        uint32_t compare = mp_obj_int_get_truncated(args[ARG_match].u_obj) * self->phases_inv;
        self->enc_config.positionCompareValue = compare;
        self->instance->LCOMP = (uint16_t)(compare) & 0xffff;        // Lower 16 pos bits.
        self->instance->UCOMP = (uint16_t)(compare >> 16U) & 0xffff; // Upper 16 pos bits.
    } else {
        // Set to the reset value
        self->enc_config.positionCompareValue = 0xFFFFFFFFU;
        self->instance->LCOMP = 0xffff;
        self->instance->UCOMP = 0xffff;
        // disable the interrupt to avoid false trigger
        ENC_DisableInterrupts(self->instance, kENC_PositionCompareInerruptEnable);
    }

    if (args[ARG_filter_ns].u_int >= 0) {
        self->filter = calc_filter(args[ARG_filter_ns].u_int,
            &(enc_config->filterCount), &(enc_config->filterSamplePeriod));
    }

    if (args[ARG_max].u_obj != mp_const_none) {
        self->max_count = mp_obj_int_get_truncated(args[ARG_max].u_obj) * self->phases_inv;
    }

    if (args[ARG_min].u_obj != mp_const_none) {
        self->min_count = mp_obj_int_get_truncated(args[ARG_min].u_obj) * self->phases_inv;
    }

    enc_config->positionModulusValue =
        self->max_count == 0 ? 0xffffffff : self->max_count + self->phases_inv - 1;
    enc_config->positionInitialValue = self->min_count;
    enc_config->enableModuloCountMode = true;

    // Count cycles on RollOverModulus or index pulse
    if (args[ARG_index].u_obj != MP_ROM_INT(-1)) {
        if (args[ARG_index].u_obj != mp_const_none) {
            connect_pin_to_encoder(args[ARG_index].u_obj, xbar_signal_table[self->id].enc_index, XBAR_IN);
            enc_config->revolutionCountCondition = kENC_RevolutionCountOnINDEXPulse;
            enc_config->INDEXTriggerMode = kENC_INDEXTriggerOnRisingEdge;
        } else {
            enc_config->revolutionCountCondition = kENC_RevolutionCountOnRollOverModulus;
            XBARA_SetSignalsConnection(XBARA1, kXBARA1_InputLogicLow, xbar_signal_table[self->id].enc_index);
        }
    }

    // Initialize the ENC module and start
    ENC_Init(self->instance, enc_config);
    clear_encoder_registers(self);
    // Set the position and clear the rev register
    ENC_SetInitialPositionValue(self->instance, self->min_count);
    ENC_DoSoftwareLoadInitialPositionValue(self->instance);
    self->instance->REV = 0;
    ENC_ClearStatusFlags(self->instance, 0xff); // Clear all status flags
    self->active = true;
}

static void mp_machine_encoder_init_helper(machine_encoder_obj_t *self,
    size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
    enum { ARG_phase_a, ARG_phase_b, ARG_phases, ARG_reset,
           ARG_match, ARG_match_pin, ARG_filter_ns, ARG_max, ARG_min, ARG_index};

    static const mp_arg_t allowed_args[] = {
        { MP_QSTR_phase_a, MP_ARG_OBJ, {.u_rom_obj = mp_const_none} },
        { MP_QSTR_phase_b, MP_ARG_OBJ, {.u_rom_obj = mp_const_none} },
        { MP_QSTR_phases, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} },
        { MP_QSTR_reset, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_INT(-1)} },
        { MP_QSTR_match, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = mp_const_none} },
        { MP_QSTR_match_pin, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_INT(-1)} },
        { MP_QSTR_filter_ns, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} },
        { MP_QSTR_max, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = mp_const_none} },
        { MP_QSTR_min, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = mp_const_none} },
        { MP_QSTR_index, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_INT(-1)} },
    };
    mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
    mp_arg_parse_all(n_args, pos_args, kw_args,
        MP_ARRAY_SIZE(allowed_args), allowed_args, args);

    // Process the Encoder specific keyword arguments
    // Get referred Pin object(s) and connect them to the encoder
    if (args[ARG_phase_a].u_obj != mp_const_none) {
        self->input_a = connect_pin_to_encoder(args[ARG_phase_a].u_obj, xbar_signal_table[self->id].enc_input_a, XBAR_IN);
    }
    if (args[ARG_phase_b].u_obj != mp_const_none) {
        self->input_b = connect_pin_to_encoder(args[ARG_phase_b].u_obj, xbar_signal_table[self->id].enc_input_b, XBAR_IN);
    }
    // Check for valid input pins
    if (self->input_a == 0 || self->input_b == 0 || self->input_a == self->input_b) {
        mp_raise_ValueError(MP_ERROR_TEXT("invalid or missing input pins"));
    }

    // Get the Phases argument
    if (args[ARG_phases].u_int != -1) {
        if ((args[ARG_phases].u_int != 1) && (args[ARG_phases].u_int != 2) && (args[ARG_phases].u_int != 4)) {
            mp_raise_ValueError(MP_ERROR_TEXT("invalid value for phases"));
        }
        self->phases_inv = 4 / args[ARG_phases].u_int;
    }

    // Set the common options
    mp_machine_encoder_init_helper_common(self, args + ARG_reset, &self->enc_config);

    ENC_DoSoftwareLoadInitialPositionValue(self->instance); /* Update the position counter with initial value. */
}

// Qencoder(id, input_a, input_b, [args])
static mp_obj_t mp_machine_encoder_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) {
    // Check number of arguments
    mp_arg_check_num(n_args, n_kw, 1, MP_OBJ_FUN_ARGS_MAX, true);

    CLOCK_EnableClock(kCLOCK_Iomuxc); // just in case it was not set yet
    XBARA_Init(XBARA1);

    uint8_t id = mp_obj_get_int(args[0]);
    if (id < 0 || id >= FSL_FEATURE_SOC_ENC_COUNT) {
        mp_raise_ValueError(MP_ERROR_TEXT("invalid encoder/counter id"));
    }
    // check, if the encoder is already in use, and if yes, deinit it
    if (encoder_table[id] != NULL) {
        encoder_deinit_single(encoder_table[id]);
    }

    // Connect the trigger input to low level
    XBARA_SetSignalsConnection(XBARA1, kXBARA1_InputLogicLow, xbar_signal_table[id].enc_trigger);

    // Create and populate the Encoder object.
    machine_encoder_obj_t *self = encoder_table[id];
    if (self == NULL) {
        self = mp_obj_malloc(machine_encoder_obj_t, &machine_encoder_type);
        encoder_table[id] = self;
    }
    // Set defaults for ENC Config.
    self->id = id;
    self->input_a = 0;
    self->input_b = 0;
    self->instance = enc_instances[id + 1];
    self->max_count = 0;
    self->min_count = 0;
    self->mp_irq_flags = 0;
    self->mp_irq_trigger = 0;
    self->mp_irq_obj = NULL;
    self->match_pin = 0;
    self->phases_inv = 4;  // default: phases = 1
    self->reset_pin = NULL;
    self->mode = MODE_ENCODER;

    // Set defaults for ENC Config
    ENC_GetDefaultConfig(&self->enc_config);
    self->enc_config.revolutionCountCondition = kENC_RevolutionCountOnRollOverModulus;

    // Process the remaining parameters
    mp_map_t kw_args;
    mp_map_init_fixed_table(&kw_args, n_kw, args + n_args);
    mp_machine_encoder_init_helper(self, n_args - 1, args + 1, &kw_args);

    return MP_OBJ_FROM_PTR(self);
}

static void encoder_deinit_single(machine_encoder_obj_t *self) {
    if (self->active) {
        if (self->mp_irq_obj && self->mp_irq_obj->handler) {
            DisableIRQ(enc_irqn[self->id + 1]);
            ENC_DisableInterrupts(self->instance, ENCODER_ALL_INTERRUPTS);
        }
        if (self->match_pin != 0) {
            // Disconnect the XBAR from the output by switching it to an input.
            #if defined MIMXRT117x_SERIES
            if (self->match_pin < XBAR_ENC_DIR_OFFSET_2) {
                IOMUXC_GPR->XBAR_ENC_DIR_REGISTER_1 &= ~(1 << (self->match_pin - XBAR_ENC_DIR_OFFSET_1));
            } else {
                IOMUXC_GPR->XBAR_ENC_DIR_REGISTER_2 &= ~(1 << (self->match_pin - XBAR_ENC_DIR_OFFSET_2));
            }
            #else
            IOMUXC_GPR->XBAR_ENC_DIR_REGISTER &= ~(1 << (self->match_pin + XBAR_ENC_DIR_OFFSET));
            #endif
        }
        ENC_Deinit(self->instance);
    }
    self->active = false;
}

// encoder_deinit_all()
void machine_encoder_deinit_all(void) {
    for (int i = 0; i < ARRAY_SIZE(encoder_table); i++) {
        if (encoder_table[i] != NULL) {
            encoder_deinit_single(encoder_table[i]);
            encoder_table[i] = NULL;
        }
    }
}

// encoder.deinit()
static mp_obj_t machine_encoder_deinit(mp_obj_t self_in) {
    encoder_deinit_single(MP_OBJ_TO_PTR(self_in));
    return mp_const_none;
}
static MP_DEFINE_CONST_FUN_OBJ_1(machine_encoder_deinit_obj, machine_encoder_deinit);

// encoder.value([value])
static mp_obj_t machine_encoder_value(size_t n_args, const mp_obj_t *args) {
    machine_encoder_obj_t *self = MP_OBJ_TO_PTR(args[0]);
    if (!self->active) {
        mp_raise_ValueError(MP_ERROR_TEXT("device stopped"));
    }
    uint32_t actual_value = ENC_GetPositionValue(self->instance);
    if (n_args > 1) {
        uint32_t value = mp_obj_int_get_truncated(args[1]) * self->phases_inv;
        // Set the position register
        ENC_SetInitialPositionValue(self->instance, value);
        ENC_DoSoftwareLoadInitialPositionValue(self->instance);
        // Reset the INIT Value, unlocking the counter.
        ENC_SetInitialPositionValue(self->instance, 0);
    }
    // Get the position as signed 32 bit value.
    int32_t value = (int32_t)actual_value;
    if (value > 0) {
        value += (self->phases_inv - 1);
    }
    return mp_obj_new_int(value / self->phases_inv);
}
static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_encoder_value_obj, 1, 2, machine_encoder_value);

// encoder.cycles([value])
static mp_obj_t machine_encoder_cycles(size_t n_args, const mp_obj_t *args) {
    machine_encoder_obj_t *self = MP_OBJ_TO_PTR(args[0]);
    if (!self->active) {
        mp_raise_ValueError(MP_ERROR_TEXT("device stopped"));
    }

    int16_t cycles = (int16_t)ENC_GetRevolutionValue(self->instance);
    if (n_args > 1) {
        // Set the revolution value
        self->instance->REV = mp_obj_get_int(args[1]);
    }
    return MP_OBJ_NEW_SMALL_INT(cycles);
}
static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_encoder_cycles_obj, 1, 2, machine_encoder_cycles);

// -------------------------------------- IRQ set-up --------------------------

static mp_uint_t encoder_irq_trigger(mp_obj_t self_in, mp_uint_t new_trigger) {
    machine_encoder_obj_t *self = MP_OBJ_TO_PTR(self_in);
    self->mp_irq_trigger = new_trigger;
    // Clear previous interrupts and enable the requested interrupts.
    ENC_ClearStatusFlags(self->instance, ENCODER_ALL_INTERRUPTS);
    ENC_DisableInterrupts(self->instance, ENCODER_ALL_INTERRUPTS);
    ENC_EnableInterrupts(self->instance, new_trigger);
    return 0;
}

static mp_uint_t encoder_irq_info(mp_obj_t self_in, mp_uint_t info_type) {
    machine_encoder_obj_t *self = MP_OBJ_TO_PTR(self_in);
    if (info_type == MP_IRQ_INFO_FLAGS) {
        return self->mp_irq_flags;
    } else if (info_type == MP_IRQ_INFO_TRIGGERS) {
        return self->mp_irq_trigger;
    }
    return 0;
}

static const mp_irq_methods_t encoder_irq_methods = {
    .trigger = encoder_irq_trigger,
    .info = encoder_irq_info,
};

// encoder.irq(trigger=ENCODER.IRQ_MATCH, handler=None, hard=False)
static mp_obj_t machine_encoder_irq(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
    mp_arg_val_t args[MP_IRQ_ARG_INIT_NUM_ARGS];
    mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_IRQ_ARG_INIT_NUM_ARGS, mp_irq_init_args, args);
    machine_encoder_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]);
    bool any_args = n_args > 1 || kw_args->used != 0;

    if (!self->active) {
        mp_raise_ValueError(MP_ERROR_TEXT("device stopped"));
    }

    if (self->mp_irq_obj == NULL) {
        self->mp_irq_obj = mp_irq_new(&encoder_irq_methods, MP_OBJ_FROM_PTR(self));
        self->mp_irq_obj->ishard = false;
    }

    if (any_args) {
        // Check the handler
        mp_obj_t handler = args[MP_IRQ_ARG_INIT_handler].u_obj;
        if (handler != mp_const_none && !mp_obj_is_callable(handler)) {
            mp_raise_ValueError(MP_ERROR_TEXT("handler must be None or callable"));
        }

        // Check the trigger
        mp_uint_t trigger = args[MP_IRQ_ARG_INIT_trigger].u_int;
        mp_uint_t not_supported = trigger & ~MP_ENCODER_ALLOWED_FLAGS;
        if (trigger != 0 && not_supported) {
            mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("trigger 0x%04x unsupported"), not_supported);
        }

        self->mp_irq_obj->handler = handler;
        self->mp_irq_obj->ishard = args[MP_IRQ_ARG_INIT_hard].u_bool;
        self->mp_irq_trigger = trigger;

        // Clear pending interrupt flags
        ENC_ClearStatusFlags(self->instance, ENCODER_ALL_INTERRUPTS);
        ENC_DisableInterrupts(self->instance, ENCODER_ALL_INTERRUPTS);
        if (self->mp_irq_obj->handler != mp_const_none) {
            ENC_EnableInterrupts(self->instance, trigger);
            EnableIRQ(enc_irqn[self->id + 1]);
        } else {
            ENC_DisableInterrupts(self->instance, trigger);
            DisableIRQ(enc_irqn[self->id + 1]);
        }
    }
    return MP_OBJ_FROM_PTR(self->mp_irq_obj);
}
MP_DEFINE_CONST_FUN_OBJ_KW(machine_encoder_irq_obj, 1, machine_encoder_irq);

// encoder.init([kwargs])
static mp_obj_t machine_encoder_init(size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) {
    mp_machine_encoder_init_helper(args[0], n_args - 1, args + 1, kw_args);
    return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_KW(machine_encoder_init_obj, 1, machine_encoder_init);

static const mp_rom_map_elem_t machine_encoder_locals_dict_table[] = {
    { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&machine_encoder_init_obj) },
    { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&machine_encoder_deinit_obj) },
    { MP_ROM_QSTR(MP_QSTR_irq), MP_ROM_PTR(&machine_encoder_irq_obj) },
    { MP_ROM_QSTR(MP_QSTR_value), MP_ROM_PTR(&machine_encoder_value_obj) },
    { MP_ROM_QSTR(MP_QSTR_cycles), MP_ROM_PTR(&machine_encoder_cycles_obj) },

    { MP_ROM_QSTR(MP_QSTR_IRQ_RESET), MP_ROM_INT(kENC_HOMETransitionFlag) },
    { MP_ROM_QSTR(MP_QSTR_IRQ_INDEX), MP_ROM_INT(kENC_INDEXPulseFlag) },
    { MP_ROM_QSTR(MP_QSTR_IRQ_MATCH), MP_ROM_INT(kENC_PositionCompareFlag) },
    { MP_ROM_QSTR(MP_QSTR_IRQ_ROLL_UNDER), MP_ROM_INT(kENC_PositionRollUnderFlag) },
    { MP_ROM_QSTR(MP_QSTR_IRQ_ROLL_OVER), MP_ROM_INT(kENC_PositionRollOverFlag) },
};
static MP_DEFINE_CONST_DICT(machine_encoder_locals_dict, machine_encoder_locals_dict_table);

MP_DEFINE_CONST_OBJ_TYPE(
    machine_encoder_type,
    MP_QSTR_Encoder,
    MP_TYPE_FLAG_NONE,
    make_new, mp_machine_encoder_make_new,
    print, mp_machine_encoder_print,
    locals_dict, &machine_encoder_locals_dict
    );


// --- Counter class code ----------

static void mp_machine_counter_init_helper(machine_encoder_obj_t *self,
    size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
    enum { ARG_src, ARG_direction, ARG_reset, ARG_match, ARG_match_pin, ARG_filter_ns, ARG_max, ARG_min, ARG_index };
    static const mp_arg_t allowed_args[] = {
        { MP_QSTR_src, MP_ARG_OBJ, {.u_rom_obj = mp_const_none} },
        { MP_QSTR_direction, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_INT(-1)} },
        { MP_QSTR_reset, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_INT(-1)} },
        { MP_QSTR_match, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = mp_const_none} },
        { MP_QSTR_match_pin, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_INT(-1)} },
        { MP_QSTR_filter_ns, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} },
        { MP_QSTR_max, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = mp_const_none} },
        { MP_QSTR_min, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = mp_const_none} },
        { MP_QSTR_index, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_INT(-1)} },
    };

    mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
    mp_arg_parse_all(n_args, pos_args, kw_args,
        MP_ARRAY_SIZE(allowed_args), allowed_args, args);

    if (args[ARG_src].u_obj != mp_const_none) {
        self->input_a = connect_pin_to_encoder(args[ARG_src].u_obj, xbar_signal_table[self->id].enc_input_a, XBAR_IN);
    }
    if (self->input_a == 0) {
        mp_raise_ValueError(MP_ERROR_TEXT("missing input pin"));
    }

    mp_obj_t direction = args[ARG_direction].u_obj;
    if (direction != MP_ROM_INT(-1)) {
        if (direction == MP_ROM_INT(COUNTER_UP)) {
            XBARA_SetSignalsConnection(XBARA1, kXBARA1_InputLogicLow, xbar_signal_table[self->id].enc_input_b);
        } else if (direction == MP_ROM_INT(COUNTER_DOWN)) {
            XBARA_SetSignalsConnection(XBARA1, kXBARA1_InputLogicHigh, xbar_signal_table[self->id].enc_input_b);
        } else {
            connect_pin_to_encoder(direction, xbar_signal_table[self->id].enc_input_b, XBAR_IN);
        }
    }

    // Set the common options and start
    mp_machine_encoder_init_helper_common(self, args + ARG_reset, &self->enc_config);
}

// Counter(id, input, [args])
static mp_obj_t mp_machine_counter_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) {
    // Check number of arguments
    mp_arg_check_num(n_args, n_kw, 1, MP_OBJ_FUN_ARGS_MAX, true);

    CLOCK_EnableClock(kCLOCK_Iomuxc); // just in case it was not set yet
    XBARA_Init(XBARA1);

    uint8_t id = mp_obj_get_int(args[0]);
    if (id < 0 || id >= FSL_FEATURE_SOC_ENC_COUNT) {
        mp_raise_ValueError(MP_ERROR_TEXT("invalid encoder/counter id"));
    }
    // check, if the encoder is already in use, and if yes, deinit it
    if (encoder_table[id] != NULL) {
        encoder_deinit_single(encoder_table[id]);
    }

    // Connect input_b and the trigger input to a fixed level.
    XBARA_SetSignalsConnection(XBARA1, kXBARA1_InputLogicLow, xbar_signal_table[id].enc_input_b);
    XBARA_SetSignalsConnection(XBARA1, kXBARA1_InputLogicLow, xbar_signal_table[id].enc_trigger);

    // Create and populate the Qencoder object.
    machine_encoder_obj_t *self = encoder_table[id];
    if (self == NULL) {
        self = mp_obj_malloc(machine_encoder_obj_t, &machine_counter_type);
        encoder_table[id] = self;
    }
    self->id = id;
    self->input_a = 0;
    self->input_b = 0;
    self->instance = enc_instances[id + 1];
    self->max_count = 0;
    self->min_count = 0;
    self->mp_irq_flags = 0;
    self->mp_irq_trigger = 0;
    self->mp_irq_obj = NULL;
    self->match_pin = 0;
    self->phases_inv = 1;
    self->reset_pin = NULL;
    self->mode = MODE_COUNTER;

    // Set defaults for ENC Config
    ENC_GetDefaultConfig(&self->enc_config);

    // Set the mode to a 32 bit counter
    self->enc_config.decoderWorkMode = kENC_DecoderWorkAsSignalPhaseCountMode;
    self->enc_config.revolutionCountCondition = kENC_RevolutionCountOnRollOverModulus;

    // Process the remaining parameters
    mp_map_t kw_args;
    mp_map_init_fixed_table(&kw_args, n_kw, args + n_args);
    mp_machine_counter_init_helper(self, n_args - 1, args + 1, &kw_args);

    return MP_OBJ_FROM_PTR(self);
}

// counter.init([kwargs])
static mp_obj_t machine_counter_init(size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) {
    mp_machine_counter_init_helper(args[0], n_args - 1, args + 1, kw_args);
    return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_KW(machine_counter_init_obj, 1, machine_counter_init);

static const mp_rom_map_elem_t machine_counter_locals_dict_table[] = {
    { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&machine_counter_init_obj) },
    { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&machine_encoder_deinit_obj) },
    { MP_ROM_QSTR(MP_QSTR_irq), MP_ROM_PTR(&machine_encoder_irq_obj) },
    { MP_ROM_QSTR(MP_QSTR_value), MP_ROM_PTR(&machine_encoder_value_obj) },
    { MP_ROM_QSTR(MP_QSTR_cycles), MP_ROM_PTR(&machine_encoder_cycles_obj) },

    { MP_ROM_QSTR(MP_QSTR_IRQ_RESET), MP_ROM_INT(kENC_HOMETransitionFlag) },
    { MP_ROM_QSTR(MP_QSTR_IRQ_INDEX), MP_ROM_INT(kENC_INDEXPulseFlag) },
    { MP_ROM_QSTR(MP_QSTR_IRQ_MATCH), MP_ROM_INT(kENC_PositionCompareFlag) },
    { MP_ROM_QSTR(MP_QSTR_IRQ_ROLL_UNDER), MP_ROM_INT(kENC_PositionRollUnderFlag) },
    { MP_ROM_QSTR(MP_QSTR_IRQ_ROLL_OVER), MP_ROM_INT(kENC_PositionRollOverFlag) },
    { MP_ROM_QSTR(MP_QSTR_UP), MP_ROM_INT(COUNTER_UP) },
    { MP_ROM_QSTR(MP_QSTR_DOWN), MP_ROM_INT(COUNTER_DOWN) },
};
static MP_DEFINE_CONST_DICT(machine_counter_locals_dict, machine_counter_locals_dict_table);

MP_DEFINE_CONST_OBJ_TYPE(
    machine_counter_type,
    MP_QSTR_Counter,
    MP_TYPE_FLAG_NONE,
    make_new, mp_machine_counter_make_new,
    print, mp_machine_encoder_print,
    locals_dict, &machine_counter_locals_dict
    );

MP_REGISTER_ROOT_POINTER(struct _machine_encoder_obj_t *encoder_table[FSL_FEATURE_SOC_ENC_COUNT]);

#endif // MICROPY_PY_MACHINE_QECNT
