/*
 * Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#ifndef _HARDWARE_PIO_H_
#define _HARDWARE_PIO_H_

#include "pico.h"
#include "hardware/address_mapped.h"
#include "hardware/structs/pio.h"
#include "hardware/gpio.h"
#include "hardware/regs/dreq.h"
#include "hardware/pio_instructions.h"

// PICO_CONFIG: PARAM_ASSERTIONS_ENABLED_PIO, Enable/disable assertions in the PIO module, type=bool, default=0, group=hardware_pio
#ifndef PARAM_ASSERTIONS_ENABLED_PIO
#define PARAM_ASSERTIONS_ENABLED_PIO 0
#endif

/** \file hardware/pio.h
 *  \defgroup hardware_pio hardware_pio
 *
 * Programmable I/O (PIO) API
 *
 * A programmable input/output block (PIO) is a versatile hardware interface which
 * can support a number of different IO standards. There are two PIO blocks in the RP2040
 *
 * Each PIO is programmable in the same sense as a processor: the four state machines independently
 * execute short, sequential programs, to manipulate GPIOs and transfer data. Unlike a general
 * purpose processor, PIO state machines are highly specialised for IO, with a focus on determinism,
 * precise timing, and close integration with fixed-function hardware. Each state machine is equipped
 * with:
 *  * Two 32-bit shift registers – either direction, any shift count
 *  * Two 32-bit scratch registers
 *  * 4×32 bit bus FIFO in each direction (TX/RX), reconfigurable as 8×32 in a single direction
 *  * Fractional clock divider (16 integer, 8 fractional bits)
 *  * Flexible GPIO mapping
 *  * DMA interface, sustained throughput up to 1 word per clock from system DMA
 *  * IRQ flag set/clear/status
 *
 * Full details of the PIO can be found in the RP2040 datasheet.
 */

#ifdef __cplusplus
extern "C" {
#endif

static_assert(PIO_SM0_SHIFTCTRL_FJOIN_RX_LSB == PIO_SM0_SHIFTCTRL_FJOIN_TX_LSB + 1, "");

/** \brief FIFO join states
 *  \ingroup hardware_pio
 */
enum pio_fifo_join {
    PIO_FIFO_JOIN_NONE = 0,
    PIO_FIFO_JOIN_TX = 1,
    PIO_FIFO_JOIN_RX = 2,
};

enum pio_mov_status_type {
    STATUS_TX_LESSTHAN = 0,
    STATUS_RX_LESSTHAN = 1
};

typedef pio_hw_t *PIO;

/** Identifier for the first (PIO 0) hardware PIO instance (for use in PIO functions).
 *
 * e.g. pio_gpio_init(pio0, 5)
 *
 *  \ingroup hardware_pio
 * @{
 */
#define pio0 pio0_hw
/** @} */

/** Identifier for the second (PIO 1) hardware PIO instance (for use in PIO functions).
 *
 * e.g. pio_gpio_init(pio1, 5)
 *
 *  \ingroup hardware_pio
 * @{
 */
#define pio1 pio1_hw
/** @} */

/** \brief PIO state machine configuration
 *  \defgroup sm_config sm_config
 *  \ingroup hardware_pio
 *
 * A PIO block needs to be configured, these functions provide helpers to set up configuration
 * structures. See \ref pio_sm_set_config
 *
 */

/** \brief PIO Configuration structure
 *  \ingroup sm_config
 */
typedef struct {
    uint32_t clkdiv;
    uint32_t execctrl;
    uint32_t shiftctrl;
    uint32_t pinctrl;
} pio_sm_config;

static inline void check_sm_param(uint sm) {
    valid_params_if(PIO, sm < NUM_PIO_STATE_MACHINES);
}

/*! \brief Set the 'out' pins in a state machine configuration
 *  \ingroup sm_config
 *
 * Can overlap with the 'in', 'set' and 'sideset' pins
 *
 * \param c Pointer to the configuration structure to modify
 * \param out_base 0-31 First pin to set as output
 * \param out_count 0-32 Number of pins to set.
 */
static inline void sm_config_set_out_pins(pio_sm_config *c, uint out_base, uint out_count) {
    assert(out_base < 32);
    assert(out_count <= 32);
    c->pinctrl = (c->pinctrl & ~(PIO_SM0_PINCTRL_OUT_BASE_BITS | PIO_SM0_PINCTRL_OUT_COUNT_BITS)) |
                 (out_base << PIO_SM0_PINCTRL_OUT_BASE_LSB) |
                 (out_count << PIO_SM0_PINCTRL_OUT_COUNT_LSB);
}

/*! \brief Set the 'set' pins in a state machine configuration
 *  \ingroup sm_config
 *
 * Can overlap with the 'in', 'out' and 'sideset' pins
 *
 * \param c Pointer to the configuration structure to modify
 * \param set_base 0-31 First pin to set as
 * \param set_count 0-5 Number of pins to set.
 */
static inline void sm_config_set_set_pins(pio_sm_config *c, uint set_base, uint set_count) {
    assert(set_base < 32);
    assert(set_count <= 5);
    c->pinctrl = (c->pinctrl & ~(PIO_SM0_PINCTRL_SET_BASE_BITS | PIO_SM0_PINCTRL_SET_COUNT_BITS)) |
                 (set_base << PIO_SM0_PINCTRL_SET_BASE_LSB) |
                 (set_count << PIO_SM0_PINCTRL_SET_COUNT_LSB);
}

/*! \brief Set the 'in' pins in a state machine configuration
 *  \ingroup sm_config
 *
 * Can overlap with the 'out', ''set' and 'sideset' pins
 *
 * \param c Pointer to the configuration structure to modify
 * \param in_base 0-31 First pin to set as input
 */
static inline void sm_config_set_in_pins(pio_sm_config *c, uint in_base) {
    assert(in_base < 32);
    c->pinctrl = (c->pinctrl & ~PIO_SM0_PINCTRL_IN_BASE_BITS) |
                 (in_base << PIO_SM0_PINCTRL_IN_BASE_LSB);
}

/*! \brief Set the 'sideset' pins in a state machine configuration
 *  \ingroup sm_config
 *
 * Can overlap with the 'in', 'out' and 'set' pins
 *
 * \param c Pointer to the configuration structure to modify
 * \param sideset_base base pin for 'side set'
 */
static inline void sm_config_set_sideset_pins(pio_sm_config *c, uint sideset_base) {
    assert(sideset_base < 32);
    c->pinctrl = (c->pinctrl & ~PIO_SM0_PINCTRL_SIDESET_BASE_BITS) |
                 (sideset_base << PIO_SM0_PINCTRL_SIDESET_BASE_LSB);
}

/*! \brief Set the 'sideset' options in a state machine configuration
 *  \ingroup sm_config
 *
 * \param c Pointer to the configuration structure to modify
 * \param bit_count Number of bits to steal from delay field in the instruction for use of side set
 * \param optional True if the topmost side set bit is used as a flag for whether to apply side set on that instruction
 * \param pindirs True if the side set affects pin directions rather than values
 */
static inline void sm_config_set_sideset(pio_sm_config *c, uint bit_count, bool optional, bool pindirs) {
    assert(bit_count <= 32);
    c->pinctrl = (c->pinctrl & ~PIO_SM0_PINCTRL_SIDESET_COUNT_BITS) |
                 (bit_count << PIO_SM0_PINCTRL_SIDESET_COUNT_LSB);

    c->execctrl = (c->execctrl & ~(PIO_SM0_EXECCTRL_SIDE_EN_BITS | PIO_SM0_EXECCTRL_SIDE_PINDIR_BITS)) |
                  (!!optional << PIO_SM0_EXECCTRL_SIDE_EN_LSB) |
                  (!!pindirs << PIO_SM0_EXECCTRL_SIDE_PINDIR_LSB);
}

/*! \brief Set the state machine clock divider (from a floating point value) in a state machine configuration
 *  \ingroup sm_config
 *
 * The clock divider acts on the system clock to provide a clock for the state machine.
 * See the datasheet for more details.
 *
 * \param c Pointer to the configuration structure to modify
 * \param div The fractional divisor to be set. 1 for full speed. An integer clock divisor of n
 *  will cause the state machine to run 1 cycle in every n.
 *  Note that for small n, the jitter introduced by a fractional divider (e.g. 2.5) may be unacceptable
 *  although it will depend on the use case.
 */
static inline void sm_config_set_clkdiv(pio_sm_config *c, float div) {
    uint16_t div_int = (uint16_t) div;
    uint8_t div_frac = (uint8_t) ((div - div_int) * (1u << 8u));
    c->clkdiv =
            (div_frac << PIO_SM0_CLKDIV_FRAC_LSB) |
            (div_int << PIO_SM0_CLKDIV_INT_LSB);
}

/*! \brief Set the state machine clock divider (from integer and fractional parts - 16:8) in a state machine configuration
 *  \ingroup sm_config
 *
 * The clock divider acts on the system clock to provide a clock for the state machine.
 * See the datasheet for more details.
 *
 * \param c Pointer to the configuration structure to modify
 * \param div_int Integer part of the divisor
 * \param div_frac Fractional part in 1/256ths
 * \sa sm_config_set_clkdiv
 */
static inline void sm_config_set_clkdiv_int_frac(pio_sm_config *c, uint16_t div_int, uint8_t div_frac) {
    c->clkdiv =
            (div_frac << PIO_SM0_CLKDIV_FRAC_LSB) |
            (div_int << PIO_SM0_CLKDIV_INT_LSB);
}

/*! \brief Set the wrap addresses in a state machine configuration
 *  \ingroup sm_config
 *
 * \param c Pointer to the configuration structure to modify
 * \param wrap_target the instruction memory address to wrap to
 * \param wrap        the instruction memory address after which to set the program counter to wrap_target
 *                    if the instruction does not itself update the program_counter
 */
static inline void sm_config_set_wrap(pio_sm_config *c, uint wrap_target, uint wrap) {
    assert(wrap < PIO_INSTRUCTION_COUNT);
    assert(wrap_target < PIO_INSTRUCTION_COUNT);
    c->execctrl = (c->execctrl & ~(PIO_SM0_EXECCTRL_WRAP_TOP_BITS | PIO_SM0_EXECCTRL_WRAP_BOTTOM_BITS)) |
                  (wrap_target << PIO_SM0_EXECCTRL_WRAP_BOTTOM_LSB) |
                  (wrap << PIO_SM0_EXECCTRL_WRAP_TOP_LSB);
}

/*! \brief Set the 'jmp' pin in a state machine configuration
 *  \ingroup sm_config
 *
 * \param c Pointer to the configuration structure to modify
 * \param pin The raw GPIO pin number to use as the source for a `jmp pin` instruction 
 */
static inline void sm_config_set_jmp_pin(pio_sm_config *c, uint pin) {
    assert(pin < 32);
    c->execctrl = (c->execctrl & ~PIO_SM0_EXECCTRL_JMP_PIN_BITS) |
                  (pin << PIO_SM0_EXECCTRL_JMP_PIN_LSB);
}

/*! \brief Setup 'in' shifting parameters in a state machine configuration
 *  \ingroup sm_config
 *
 * \param c Pointer to the configuration structure to modify
 * \param shift_right true to shift ISR to right, false to shift ISR to left
 * \param autopush whether autopush is enabled
 * \param push_threshold threshold in bits to shift in before auto/conditional re-pushing of the ISR  
 */
static inline void sm_config_set_in_shift(pio_sm_config *c, bool shift_right, bool autopush, uint push_threshold) {
    valid_params_if(PIO, push_threshold <= 32);
    c->shiftctrl = (c->shiftctrl &
                    ~(PIO_SM0_SHIFTCTRL_IN_SHIFTDIR_BITS |
                      PIO_SM0_SHIFTCTRL_AUTOPUSH_BITS |
                      PIO_SM0_SHIFTCTRL_PUSH_THRESH_BITS)) |
                   (!!shift_right << PIO_SM0_SHIFTCTRL_IN_SHIFTDIR_LSB) |
                   (!!autopush << PIO_SM0_SHIFTCTRL_AUTOPUSH_LSB) |
                   ((push_threshold & 0x1fu) << PIO_SM0_SHIFTCTRL_PUSH_THRESH_LSB);
}

/*! \brief Setup 'out' shifting parameters in a state machine configuration
 *  \ingroup sm_config
 *
 * \param c Pointer to the configuration structure to modify
 * \param shift_right true to shift OSR to right, false to shift OSR to left
 * \param autopull whether autopull is enabled
 * \param pull_threshold threshold in bits to shift out before auto/conditional re-pulling of the OSR  
 */
static inline void sm_config_set_out_shift(pio_sm_config *c, bool shift_right, bool autopull, uint pull_threshold) {
    valid_params_if(PIO, pull_threshold <= 32);
    c->shiftctrl = (c->shiftctrl &
                    ~(PIO_SM0_SHIFTCTRL_OUT_SHIFTDIR_BITS |
                      PIO_SM0_SHIFTCTRL_AUTOPULL_BITS |
                      PIO_SM0_SHIFTCTRL_PULL_THRESH_BITS)) |
                   (!!shift_right << PIO_SM0_SHIFTCTRL_OUT_SHIFTDIR_LSB) |
                   (!!autopull << PIO_SM0_SHIFTCTRL_AUTOPULL_LSB) |
                   ((pull_threshold & 0x1fu) << PIO_SM0_SHIFTCTRL_PULL_THRESH_LSB);
}

/*! \brief Setup the FIFO joining in a state machine configuration
 *  \ingroup sm_config
 *
 * \param c Pointer to the configuration structure to modify
 * \param join Specifies the join type. \see enum pio_fifo_join
 */
static inline void sm_config_set_fifo_join(pio_sm_config *c, enum pio_fifo_join join) {
    assert(join >= 0 && join <= 2);
    c->shiftctrl = (c->shiftctrl & ~(PIO_SM0_SHIFTCTRL_FJOIN_TX_BITS | PIO_SM0_SHIFTCTRL_FJOIN_RX_BITS)) |
                   (join << PIO_SM0_SHIFTCTRL_FJOIN_TX_LSB);
}

/*! \brief Set special 'out' operations in a state machine configuration
 *  \ingroup sm_config
 *
 * \param c Pointer to the configuration structure to modify
 * \param sticky to enable 'sticky' output (i.e. re-asserting most recent OUT/SET pin values on subsequent cycles)
 * \param has_enable_pin true to enable auxiliary OUT enable pin 
 * \param enable_pin_index pin index for auxiliary OUT enable
 */
static inline void sm_config_set_out_special(pio_sm_config *c, bool sticky, bool has_enable_pin, int enable_pin_index) {
    c->execctrl = (c->execctrl &
                   ~(PIO_SM0_EXECCTRL_OUT_STICKY_BITS | PIO_SM0_EXECCTRL_INLINE_OUT_EN_BITS |
                     PIO_SM0_EXECCTRL_OUT_EN_SEL_BITS)) |
                  (!!sticky << PIO_SM0_EXECCTRL_OUT_STICKY_LSB) |
                  (!!has_enable_pin << PIO_SM0_EXECCTRL_INLINE_OUT_EN_LSB) |
                  ((enable_pin_index << PIO_SM0_EXECCTRL_OUT_EN_SEL_LSB) & PIO_SM0_EXECCTRL_OUT_EN_SEL_BITS);
}

/*! \brief Set source for 'mov status' in a state machine configuration
 *  \ingroup sm_config
 *
 * \param c Pointer to the configuration structure to modify
 * \param status_sel the status operation selector 
 * \param status_n parameter for the mov status operation (currently a bit count)
 */
static inline void sm_config_set_mov_status(pio_sm_config *c, enum pio_mov_status_type status_sel, uint status_n) {
    c->execctrl = (c->execctrl
                   & ~(PIO_SM0_EXECCTRL_STATUS_SEL_BITS | PIO_SM0_EXECCTRL_STATUS_N_BITS))
                  | ((status_sel << PIO_SM0_EXECCTRL_STATUS_SEL_LSB) & PIO_SM0_EXECCTRL_STATUS_SEL_BITS)
                  | ((status_n << PIO_SM0_EXECCTRL_STATUS_N_LSB) & PIO_SM0_EXECCTRL_STATUS_N_BITS);
}


/*! \brief  Get the default state machine configuration
 *  \ingroup sm_config
 *
 * Setting | Default
 * --------|--------
 * Out Pins | 32 starting at 0
 * Set Pins | 0 starting at 0
 * In Pins (base) | 0
 * Side Set Pins (base) | 0
 * Side Set | disabled
 * Wrap | wrap=31, wrap_to=0
 * In Shift | shift_direction=right, autopush=false, push_thrshold=32
 * Out Shift | shift_direction=right, autopull=false, pull_thrshold=32
 * Jmp Pin | 0
 * Out Special | sticky=false, has_enable_pin=false, enable_pin_index=0 
 * Mov Status | status_sel=STATUS_TX_LESSTHAN, n=0
 *
 * \return the default state machine configuration which can then be modified.
 */
static inline pio_sm_config pio_get_default_sm_config() {
    pio_sm_config c = {0, 0, 0};
    sm_config_set_clkdiv_int_frac(&c, 1, 0);
    sm_config_set_wrap(&c, 0, 31);
    sm_config_set_in_shift(&c, true, false, 32);
    sm_config_set_out_shift(&c, true, false, 32);
    return c;
}

/*! \brief Apply a state machine configuration to a state machine
 *  \ingroup hardware_pio
 *
 * \param pio Handle to PIO instance; either \ref pio0 or \ref pio1
 * \param sm State machine index (0..3)
 * \param config the configuration to apply
*/
static inline void pio_sm_set_config(PIO pio, uint sm, const pio_sm_config *config) {
    check_sm_param(sm);
    pio->sm[sm].clkdiv = config->clkdiv;
    pio->sm[sm].execctrl = config->execctrl;
    pio->sm[sm].shiftctrl = config->shiftctrl;
    pio->sm[sm].pinctrl = config->pinctrl;
}

/*! \brief Return the instance number of a PIO instance
 *  \ingroup hardware_pio
 *
 * \param pio The PIO instance; either \ref pio0 or \ref pio1
 * \return the PIO instance number (either 0 or 1)
 */
static inline uint pio_get_index(PIO pio) {
    assert(pio == pio0 || pio == pio1);
    return pio == pio1 ? 1 : 0;
}

/*! \brief Setup the function select for a GPIO to use output from the given PIO instance 
 *  \ingroup hardware_pio
 *
 * \param pio The PIO instance; either \ref pio0 or \ref pio1
 * \param pin the GPIO pin whose function select to set
 */
static inline void pio_gpio_init(PIO pio, uint pin) {
    assert(pio == pio0 || pio == pio1);
    gpio_set_function(pin, pio == pio0 ? GPIO_FUNC_PIO0 : GPIO_FUNC_PIO1);
}

/*! \brief Return the DREQ to use for pacing transfers to a particular state machine
 *  \ingroup hardware_pio
 *
 * \param pio The PIO instance; either \ref pio0 or \ref pio1
 * \param sm State machine index (0..3)
 * \param is_tx true for sending data to the state machine, false for received data from the state machine
 */
static inline uint pio_get_dreq(PIO pio, uint sm, bool is_tx) {
    assert(pio == pio0 || pio == pio1);
    check_sm_param(sm);
    return sm + (is_tx ? 0 : NUM_PIO_STATE_MACHINES) + (pio == pio0 ? DREQ_PIO0_TX0 : DREQ_PIO1_TX0);
}

typedef struct pio_program {
    const uint16_t *instructions;
    uint8_t length;
    int8_t origin; // required instruction memory origin or -1
} __packed pio_program_t;

/*! \brief Determine whether the given program can (at the time of the call) be loaded onto the PIO instance
 *  \ingroup hardware_pio
 *
 * \param pio The PIO instance; either \ref pio0 or \ref pio1
 * \param program the program definition
 * \return true if the program can be loaded; false if there is not suitable space in the instruction memory
 */
bool pio_can_add_program(PIO pio, const pio_program_t *program);

/*! \brief Determine whether the given program can (at the time of the call) be loaded onto the PIO instance starting at a particular location
 *  \ingroup hardware_pio
 *
 * \param pio The PIO instance; either \ref pio0 or \ref pio1
 * \param program the program definition
 * \param offset the instruction memory offset wanted for the start of the program
 * \return true if the program can be loaded at that location; false if there is not space in the instruction memory
 */
bool pio_can_add_program_at_offset(PIO pio, const pio_program_t *program, uint offset);

/*! \brief Attempt to load the program, panicking if not possible
 *  \ingroup hardware_pio
 *
 * \see pico_can_add_program if you need to check whether the program can be loaded
 *
 * \param pio The PIO instance; either \ref pio0 or \ref pio1
 * \param program the program definition
 * \return the instruction memory offset the program is loaded at
 */
uint pio_add_program(PIO pio, const pio_program_t *program);

/*! \brief Attempt to load the program at the specified instruction memory offset, panicking if not possible
 *  \ingroup hardware_pio
 *
 * \see pico_can_add_program_at_offset if you need to check whether the program can be loaded
 *
 * \param pio The PIO instance; either \ref pio0 or \ref pio1
 * \param program the program definition
 * \param offset the instruction memory offset wanted for the start of the program
 */
void pio_add_program_at_offset(PIO pio, const pio_program_t *program, uint offset);

/*! \brief Remove a program from a PIO instance's instruction memory
 *  \ingroup hardware_pio
 *
 * \param pio The PIO instance; either \ref pio0 or \ref pio1
 * \param program the program definition
 * \param loaded_offset the loaded offset returned when the program was added
 */
void pio_remove_program(PIO pio, const pio_program_t *program, uint loaded_offset);

/*! \brief Clears all of a PIO instance's instruction memory
 *  \ingroup hardware_pio
 *
 * \param pio The PIO instance; either \ref pio0 or \ref pio1
 */
void pio_clear_instruction_memory(PIO pio);

/*! \brief Resets the state machine to a consistent state, and configures it
 *  \ingroup hardware_pio
 *
 * This method:
 * - disables the state machine (if running)
 * - clears the FIFOs
 * - applies the configuration
 * - resets any internal state
 * - jumps to the initial program location
 *
 * The state machine is disabled on return from this call
 *
 * \param pio The PIO instance; either \ref pio0 or \ref pio1
 * \param sm State machine index (0..3)
 * \param initial_pc the initial program memory offset to run from
 * \param config the configuration to apply (or NULL to apply defaults)
 */
void pio_sm_init(PIO pio, uint sm, uint initial_pc, const pio_sm_config *config);

/*! \brief Enable or disable a PIO state machine
 *  \ingroup hardware_pio
 *
 * \param pio The PIO instance; either \ref pio0 or \ref pio1
 * \param sm State machine index (0..3)
 * \param enabled true to enable the state machine; false to disable
 */
static inline void pio_sm_set_enabled(PIO pio, uint sm, bool enabled) {
    pio->ctrl = (pio->ctrl & ~(1u << sm)) | (!!enabled << sm);
}

/*! \brief Enable or disable multiple PIO state machines
 *  \ingroup hardware_pio
 *
 * Note that this method just sets the enabled state of the state machine;
 * if now enabled they continue exactly from where they left off.
 *
 * \see pio_enable_sm_mask_in_sync if you wish to enable multiple state machines
 * and ensure their clock dividers are in sync.
 *
 * \param pio The PIO instance; either \ref pio0 or \ref pio1
 * \param mask bit mask of state machine indexes to modify the enabled state of
 * \param enabled true to enable the state machines; false to disable
 */
static inline void pio_set_sm_mask_enabled(PIO pio, uint32_t mask, bool enabled) {
    pio->ctrl = (pio->ctrl & ~mask) | (enabled ? mask : 0u);
}

/*! \brief Restart a state machine with a known state
 *  \ingroup hardware_pio
 *
 * This method clears the ISR, shift counters, clock divider counter
 * pin write flags, delay counter, latched EXEC instruction, and IRQ wait condition.
 *
 * \param pio The PIO instance; either \ref pio0 or \ref pio1
 * \param sm State machine index (0..3)
 */
static inline void pio_sm_restart(PIO pio, uint sm) {
    pio->ctrl |= 1u << (PIO_CTRL_SM_RESTART_LSB + sm);
}

/*! \brief Restart multiple state machine with a known state
 *  \ingroup hardware_pio
 *
 * This method clears the ISR, shift counters, clock divider counter
 * pin write flags, delay counter, latched EXEC instruction, and IRQ wait condition.
 *
 * \param pio The PIO instance; either \ref pio0 or \ref pio1
 * \param mask bit mask of state machine indexes to modify the enabled state of
 */
static inline void pio_restart_sm_mask(PIO pio, uint32_t mask) {
    pio->ctrl |= (mask << PIO_CTRL_SM_RESTART_LSB) & PIO_CTRL_SM_RESTART_BITS;
}

/*! \brief Restart a state machine's clock divider (resetting the fractional count)
 *  \ingroup hardware_pio
 *
 * \param pio The PIO instance; either \ref pio0 or \ref pio1
 * \param sm State machine index (0..3)
 */
static inline void pio_sm_clkdiv_restart(PIO pio, uint sm) {
    pio->ctrl |= 1u << (PIO_CTRL_CLKDIV_RESTART_LSB + sm);
}

/*! \brief Restart multiple state machines' clock dividers (resetting the fractional count)
 *  \ingroup hardware_pio
 *
 * This method can be used to guarantee that multiple state machines with fractional clock dividers
 * are exactly in sync
 *
 * \param pio The PIO instance; either \ref pio0 or \ref pio1
 * \param mask bit mask of state machine indexes to modify the enabled state of
 */
static inline void pio_clkdiv_restart_sm_mask(PIO pio, uint32_t mask) {
    pio->ctrl |= (mask << PIO_CTRL_CLKDIV_RESTART_LSB) & PIO_CTRL_CLKDIV_RESTART_BITS;
}

/*! \brief Enable multiple PIO state machines synchronizing their clock dividers
 *  \ingroup hardware_pio
 *
 * \param pio The PIO instance; either \ref pio0 or \ref pio1
 * \param mask bit mask of state machine indexes to modify the enabled state of
 */
static inline void pio_enable_sm_mask_in_sync(PIO pio, uint32_t mask) {
    pio->ctrl |= ((mask << PIO_CTRL_CLKDIV_RESTART_LSB) & PIO_CTRL_CLKDIV_RESTART_BITS) |
                 ((mask << PIO_CTRL_SM_ENABLE_LSB) & PIO_CTRL_SM_ENABLE_BITS);
}

/*! \brief Return the current program counter for a state machine
 *  \ingroup hardware_pio
 *
 * \param pio The PIO instance; either \ref pio0 or \ref pio1
 * \param sm State machine index (0..3)
 * \return the program counter
 */
static inline uint8_t pio_sm_get_pc(PIO pio, uint sm) {
    check_sm_param(sm);
    return (uint8_t) pio->sm[sm].addr;
}

/*! \brief Immediately execute an instruction on a state machine
 *  \ingroup hardware_pio
 *
 * This instruction is executed instead of the next instruction in the normal control flow on the state machine.
 * Subsequent calls to this method replace the previous executed
 * instruction if it is still running. \see pio_sm_is_exec_stalled to see if an executed instruction
 * is still running (i.e. it is stalled on some condition)
 *
 * \param pio The PIO instance; either \ref pio0 or \ref pio1
 * \param sm State machine index (0..3)
 * \param instr the encoded PIO instruction
 */
inline static void pio_sm_exec(PIO pio, uint sm, uint instr) {
    check_sm_param(sm);
    pio->sm[sm].instr = instr;
}

/*! \brief Determine if an instruction set by pio_sm_exec() is stalled executing
 *  \ingroup hardware_pio
 *
 * \param pio The PIO instance; either \ref pio0 or \ref pio1
 * \param sm State machine index (0..3)
 * \return true if the executed instruction is still running (stalled)
 */
static inline bool pio_sm_is_exec_stalled(PIO pio, uint sm) {
    check_sm_param(sm);
    return !!(pio->sm[sm].execctrl & PIO_SM0_EXECCTRL_EXEC_STALLED_BITS);
}

/*! \brief Immediately execute an instruction on a state machine and wait for it to complete
 *  \ingroup hardware_pio
 *
 * This instruction is executed instead of the next instruction in the normal control flow on the state machine.
 * Subsequent calls to this method replace the previous executed
 * instruction if it is still running. \see pio_sm_is_exec_stalled to see if an executed instruction
 * is still running (i.e. it is stalled on some condition)
 *
 * \param pio The PIO instance; either \ref pio0 or \ref pio1
 * \param sm State machine index (0..3)
 * \param instr the encoded PIO instruction
 */
static inline void pio_sm_exec_wait_blocking(PIO pio, uint sm, uint instr) {
    pio_sm_exec(pio, sm, instr);
    while (pio_sm_is_exec_stalled(pio, sm)) tight_loop_contents();
}

/*! \brief Set the current wrap configuration for a state machine
 *  \ingroup hardware_pio
 *
 * \param pio The PIO instance; either \ref pio0 or \ref pio1
 * \param sm State machine index (0..3)
 * \param wrap_target the instruction memory address to wrap to
 * \param wrap        the instruction memory address after which to set the program counter to wrap_target
 *                    if the instruction does not itself update the program_counter
 */
static inline void pio_sm_set_wrap(PIO pio, uint sm, uint wrap_target, uint wrap) {
    check_sm_param(sm);
    pio->sm[sm].execctrl =
            (pio->sm[sm].execctrl & ~(PIO_SM0_EXECCTRL_WRAP_TOP_BITS | PIO_SM0_EXECCTRL_WRAP_BOTTOM_BITS)) |
            (wrap_target << PIO_SM0_EXECCTRL_WRAP_BOTTOM_LSB) |
            (wrap << PIO_SM0_EXECCTRL_WRAP_TOP_LSB);
}

/*! \brief Set the current 'out' pins for a state machine
 *  \ingroup sm_config
 *
 * Can overlap with the 'in', 'set' and 'sideset' pins
 *
 * \param pio The PIO instance; either \ref pio0 or \ref pio1
 * \param sm State machine index (0..3)
 * \param out_base 0-31 First pin to set as output
 * \param out_count 0-32 Number of pins to set.
 */
static inline void pio_sm_set_out_pins(PIO pio, uint sm, uint out_base, uint out_count) {
    check_sm_param(sm);
    assert(out_base < 32);
    assert(out_count <= 32);
    pio->sm[sm].pinctrl = (pio->sm[sm].pinctrl & ~(PIO_SM0_PINCTRL_OUT_BASE_BITS | PIO_SM0_PINCTRL_OUT_COUNT_BITS)) |
                 (out_base << PIO_SM0_PINCTRL_OUT_BASE_LSB) |
                 (out_count << PIO_SM0_PINCTRL_OUT_COUNT_LSB);
}


/*! \brief Set the current 'set' pins for a state machine
 *  \ingroup sm_config
 *
 * Can overlap with the 'in', 'out' and 'sideset' pins
 *
 * \param pio The PIO instance; either \ref pio0 or \ref pio1
 * \param sm State machine index (0..3)
 * \param set_base 0-31 First pin to set as
 * \param set_count 0-5 Number of pins to set.
 */
static inline void pio_sm_set_set_pins(PIO pio, uint sm, uint set_base, uint set_count) {
    check_sm_param(sm);
    assert(set_base < 32);
    assert(set_count <= 5);
    pio->sm[sm].pinctrl = (pio->sm[sm].pinctrl & ~(PIO_SM0_PINCTRL_SET_BASE_BITS | PIO_SM0_PINCTRL_SET_COUNT_BITS)) |
                 (set_base << PIO_SM0_PINCTRL_SET_BASE_LSB) |
                 (set_count << PIO_SM0_PINCTRL_SET_COUNT_LSB);
}

/*! \brief Set the current 'in' pins for a state machine
 *  \ingroup sm_config
 *
 * Can overlap with the 'out', ''set' and 'sideset' pins
 *
 * \param pio The PIO instance; either \ref pio0 or \ref pio1
 * \param sm State machine index (0..3)
 * \param in_base 0-31 First pin to set as input
 */
static inline void pio_sm_set_in_pins(PIO pio, uint sm, uint in_base) {
    check_sm_param(sm);
    assert(in_base < 32);
    pio->sm[sm].pinctrl = (pio->sm[sm].pinctrl & ~PIO_SM0_PINCTRL_IN_BASE_BITS) |
                 (in_base << PIO_SM0_PINCTRL_IN_BASE_LSB);
}

/*! \brief Set the current 'sideset' pins for a state machine
 *  \ingroup sm_config
 *
 * Can overlap with the 'in', 'out' and 'set' pins
 *
 * \param pio The PIO instance; either \ref pio0 or \ref pio1
 * \param sm State machine index (0..3)
 * \param sideset_base base pin for 'side set'
 */
static inline void pio_sm_set_sideset_pins(PIO pio, uint sm, uint sideset_base) {
    check_sm_param(sm);
    assert(sideset_base < 32);
    pio->sm[sm].pinctrl = (pio->sm[sm].pinctrl & ~PIO_SM0_PINCTRL_SIDESET_BASE_BITS) |
                 (sideset_base << PIO_SM0_PINCTRL_SIDESET_BASE_LSB);
}

/*! \brief Write a word of data to a state machine's TX FIFO
 *  \ingroup hardware_pio
 *
 * If the FIFO is full, the most recent value will be overwritten
 *
 * \param pio The PIO instance; either \ref pio0 or \ref pio1
 * \param sm State machine index (0..3)
 * \param data the 32 bit data value
 */
static inline void pio_sm_put(PIO pio, uint sm, uint32_t data) {
    check_sm_param(sm);
    pio->txf[sm] = data;
}

/*! \brief Read a word of data from a state machine's RX FIFO
 *  \ingroup hardware_pio
 *
 * If the FIFO is empty, the return value is zero.
 *
 * \param pio The PIO instance; either \ref pio0 or \ref pio1
 * \param sm State machine index (0..3)
 */
static inline uint32_t pio_sm_get(PIO pio, uint sm) {
    check_sm_param(sm);
    return pio->rxf[sm];
}

/*! \brief Determine if a state machine's RX FIFO is full
 *  \ingroup hardware_pio
 *
 * \param pio The PIO instance; either \ref pio0 or \ref pio1
 * \param sm State machine index (0..3)
 * \return true if the RX FIFO is full
 */
static inline bool pio_sm_is_rx_fifo_full(PIO pio, uint sm) {
    check_sm_param(sm);
    return (pio->fstat & (1u << (PIO_FSTAT_RXFULL_LSB + sm))) != 0;
}

/*! \brief Determine if a state machine's RX FIFO is empty
 *  \ingroup hardware_pio
 *
 * \param pio The PIO instance; either \ref pio0 or \ref pio1
 * \param sm State machine index (0..3)
 * \return true if the RX FIFO is empty
 */
static inline bool pio_sm_is_rx_fifo_empty(PIO pio, uint sm) {
    check_sm_param(sm);
    return (pio->fstat & (1u << (PIO_FSTAT_RXEMPTY_LSB + sm))) != 0;
}

/*! \brief Return the number of elements currently in a state machine's RX FIFO
 *  \ingroup hardware_pio
 *
 * \param pio The PIO instance; either \ref pio0 or \ref pio1
 * \param sm State machine index (0..3)
 * \return the number of elements in the RX FIFO
 */
static inline uint pio_sm_get_rx_fifo_level(PIO pio, uint sm) {
    check_sm_param(sm);
    int bitoffs = PIO_FLEVEL_RX0_LSB + sm * (PIO_FLEVEL_RX1_LSB - PIO_FLEVEL_RX0_LSB);
    const uint32_t mask = PIO_FLEVEL_RX0_BITS >> PIO_FLEVEL_RX0_LSB;
    return (pio->flevel >> bitoffs) & mask;
}

/*! \brief Determine if a state machine's TX FIFO is full
 *  \ingroup hardware_pio
 *
 * \param pio The PIO instance; either \ref pio0 or \ref pio1
 * \param sm State machine index (0..3)
 * \return true if the TX FIFO is full
 */
static inline bool pio_sm_is_tx_fifo_full(PIO pio, uint sm) {
    check_sm_param(sm);
    return (pio->fstat & (1u << (PIO_FSTAT_TXFULL_LSB + sm))) != 0;
}

/*! \brief Determine if a state machine's TX FIFO is empty
 *  \ingroup hardware_pio
 *
 * \param pio The PIO instance; either \ref pio0 or \ref pio1
 * \param sm State machine index (0..3)
 * \return true if the TX FIFO is empty
 */
static inline bool pio_sm_is_tx_fifo_empty(PIO pio, uint sm) {
    check_sm_param(sm);
    return (pio->fstat & (1u << (PIO_FSTAT_TXEMPTY_LSB + sm))) != 0;
}

/*! \brief Return the number of elements currently in a state machine's TX FIFO
 *  \ingroup hardware_pio
 *
 * \param pio The PIO instance; either \ref pio0 or \ref pio1
 * \param sm State machine index (0..3)
 * \return the number of elements in the TX FIFO
 */
static inline uint pio_sm_get_tx_fifo_level(PIO pio, uint sm) {
    check_sm_param(sm);
    unsigned int bitoffs = PIO_FLEVEL_TX0_LSB + sm * (PIO_FLEVEL_TX1_LSB - PIO_FLEVEL_TX0_LSB);
    const uint32_t mask = PIO_FLEVEL_TX0_BITS >> PIO_FLEVEL_TX0_LSB;
    return (pio->flevel >> bitoffs) & mask;
}

/*! \brief Write a word of data to a state machine's TX FIFO, blocking if the FIFO is full
 *  \ingroup hardware_pio
 *
 * \param pio The PIO instance; either \ref pio0 or \ref pio1
 * \param sm State machine index (0..3)
 * \param data the 32 bit data value
 */
static inline void pio_sm_put_blocking(PIO pio, uint sm, uint32_t data) {
    check_sm_param(sm);
    while (pio_sm_is_tx_fifo_full(pio, sm)) tight_loop_contents();
    pio_sm_put(pio, sm, data);
}

/*! \brief Read a word of data from a state machine's RX FIFO, blocking if the FIFO is empty
 *  \ingroup hardware_pio
 *
 * \param pio The PIO instance; either \ref pio0 or \ref pio1
 * \param sm State machine index (0..3)
 */
static inline uint32_t pio_sm_get_blocking(PIO pio, uint sm) {
    check_sm_param(sm);
    while (pio_sm_is_rx_fifo_empty(pio, sm)) tight_loop_contents();
    return pio_sm_get(pio, sm);
}

/*! \brief Empty out a state machine's TX FIFO
 *  \ingroup hardware_pio
 *
 * This method executes `pull` instructions on the state machine until the TX FIFO is empty
 *
 * \param pio The PIO instance; either \ref pio0 or \ref pio1
 * \param sm State machine index (0..3)
 */
void pio_sm_drain_tx_fifo(PIO pio, uint sm);

/*! \brief set the current clock divider for a state machine
 *  \ingroup hardware_pio
 *
 * \param pio The PIO instance; either \ref pio0 or \ref pio1
 * \param sm State machine index (0..3)
 * \param div the floating point clock divider
 */
static inline void pio_sm_set_clkdiv(PIO pio, uint sm, float div) {
    check_sm_param(sm);
    uint16_t div_int = (uint16_t) div;
    uint8_t div_frac = (uint8_t) ((div - div_int) * (1u << 8u));
    pio->sm[sm].clkdiv =
            (div_frac << PIO_SM0_CLKDIV_FRAC_LSB) |
            (div_int << PIO_SM0_CLKDIV_INT_LSB);
}

/*! \brief set the current clock divider for a state machine using a 16:8 fraction
 *  \ingroup hardware_pio
 *
 * \param pio The PIO instance; either \ref pio0 or \ref pio1
 * \param sm State machine index (0..3)
 * \param div_int the integer part of the clock divider
 * \param div_frac the fractional part of the clock divider in 1/256s
 */
static inline void pio_sm_set_clkdiv_int_frac(PIO pio, uint sm, uint16_t div_int, uint8_t div_frac) {
    check_sm_param(sm);
    pio->sm[sm].clkdiv =
            (div_frac << PIO_SM0_CLKDIV_FRAC_LSB) |
            (div_int << PIO_SM0_CLKDIV_INT_LSB);
}

/*! \brief Clear a state machine's TX and RX FIFOFs
 *  \ingroup hardware_pio
 *
 * \param pio The PIO instance; either \ref pio0 or \ref pio1
 * \param sm State machine index (0..3)
 */
static inline void pio_sm_clear_fifos(PIO pio, uint sm) {
    // changing the FIFO join state clears the fifo
    check_sm_param(sm);
    hw_xor_bits(&pio->sm[sm].shiftctrl, PIO_SM0_SHIFTCTRL_FJOIN_RX_BITS);
    hw_xor_bits(&pio->sm[sm].shiftctrl, PIO_SM0_SHIFTCTRL_FJOIN_RX_BITS);
}

/*! \brief Use a state machine to set a value on all pins for the PIO instance
 *  \ingroup hardware_pio
 *
 * This method repeatedly reconfigures the target state machine's pin configuration and executes 'set' instructions to set values on all 32 pins,
 * before restoring the state machine's pin configuration to what it was.
 *
 * This method is provided as a convenience to set initial pin states, and should not be used against a state machine that is enabled.
 *
 * \param pio The PIO instance; either \ref pio0 or \ref pio1
 * \param sm State machine index (0..3) to use
 * \param pin_values the pin values to set
 */
void pio_sm_set_pins(PIO pio, uint sm, uint32_t pin_values);

/*! \brief Use a state machine to set a value on multiple pins for the PIO instance
 *  \ingroup hardware_pio
 *
 * This method repeatedly reconfigures the target state machine's pin configuration and executes 'set' instructions to set values on up to 32 pins,
 * before restoring the state machine's pin configuration to what it was.
 *
 * This method is provided as a convenience to set initial pin states, and should not be used against a state machine that is enabled.
 *
 * \param pio The PIO instance; either \ref pio0 or \ref pio1
 * \param sm State machine index (0..3) to use
 * \param pin_values the pin values to set (if the corresponding bit in pin_mask is set)
 * \param pin_mask a bit for each pin to indicate whether the corresponding pin_value for that pin should be applied.
 */
void pio_sm_set_pins_with_mask(PIO pio, uint sm, uint32_t pin_values, uint32_t pin_mask);

/*! \brief Use a state machine to set the pin directions for multiple pins for the PIO instance
 *  \ingroup hardware_pio
 *
 * This method repeatedly reconfigures the target state machine's pin configuration and executes 'set' instructions to set pin directions on up to 32 pins,
 * before restoring the state machine's pin configuration to what it was.
 *
 * This method is provided as a convenience to set initial pin directions, and should not be used against a state machine that is enabled.
 *
 * \param pio The PIO instance; either \ref pio0 or \ref pio1
 * \param sm State machine index (0..3) to use
 * \param pin_dirs the pin directions to set - 1 = out, 0 = in (if the corresponding bit in pin_mask is set)
 * \param pin_mask a bit for each pin to indicate whether the corresponding pin_value for that pin should be applied.
 */
void pio_sm_set_pindirs_with_mask(PIO pio, uint sm, uint32_t pin_dirs, uint32_t pin_mask);

/*! \brief Use a state machine to set the same pin direction for multiple consecutive pins for the PIO instance
 *  \ingroup hardware_pio
 *
 * This method repeatedly reconfigures the target state machine's pin configuration and executes 'set' instructions to set the pin direction on consecutive pins,
 * before restoring the state machine's pin configuration to what it was.
 *
 * This method is provided as a convenience to set initial pin directions, and should not be used against a state machine that is enabled.
 *
 * \param pio The PIO instance; either \ref pio0 or \ref pio1
 * \param sm State machine index (0..3) to use
 * \param pin_base the first pin to set a direction for
 * \param pin_count the count of consecutive pins to set the direction for
 * \param is_out the direction to set; true = out, false = in
 */
void pio_sm_set_consecutive_pindirs(PIO pio, uint sm, uint pin_base, uint pin_count, bool is_out);

/*! \brief Mark a state machine as used
 *  \ingroup hardware_pio
 *
 * Method for cooperative claiming of hardware. Will cause a panic if the state machine
 * is already claimed. Use of this method by libraries detects accidental
 * configurations that would fail in unpredictable ways.
 *
 * \param pio The PIO instance; either \ref pio0 or \ref pio1
 * \param sm State machine index (0..3)
 */
void pio_sm_claim(PIO pio, uint sm);

/*! \brief Mark multiple state machines as used
 *  \ingroup hardware_pio
 *
 * Method for cooperative claiming of hardware. Will cause a panic if any of the state machines
 * are already claimed. Use of this method by libraries detects accidental
 * configurations that would fail in unpredictable ways.
 *
 * \param pio The PIO instance; either \ref pio0 or \ref pio1
 * \param sm_mask Mask of state machine indexes
 */
void pio_claim_sm_mask(PIO pio, uint sm_mask);

/*! \brief Mark a state machine as no longer used
 *  \ingroup hardware_pio
 *
 * Method for cooperative claiming of hardware.
 *
 * \param pio The PIO instance; either \ref pio0 or \ref pio1
 * \param sm State machine index (0..3)
 */
void pio_sm_unclaim(PIO pio, uint sm);

/*! \brief Claim a free state machine on a PIO instance
 *  \ingroup hardware_pio
 *
 * \param pio The PIO instance; either \ref pio0 or \ref pio1
 * \param required if true the function will panic if none are available
 * \return the state machine index or -1 if required was false, and none were free
 */
int pio_claim_unused_sm(PIO pio, bool required);

#ifdef __cplusplus
}
#endif

#endif // _PIO_H_
