/*
 * Copyright 2021-2023 Morse Micro
 *
 * This file is licensed under terms that can be found in the LICENSE.md file in the root
 * directory of the Morse Micro IoT SDK software package.
 */


/**
 * @defgroup MMHAL Morse Micro Hardware Abstraction Layer (mmhal) API
 *
 * This API provides abstraction from the underlying hardware/BSP.
 *
 * @{
 */

#pragma once
#include "halow_config.h"
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <time.h>
#include "mmhal_flash.h"
#include "mmhal_wlan.h"


#ifdef __cplusplus
extern "C" {
#endif


/** Initialization before RTOS scheduler starts. */
void mmhal_early_init(void);


/** Initialization after RTOS scheduler started. */
void mmhal_init(void);

/** Enumeration of ISR states (i.e., whether in ISR or not). */
enum mmhal_isr_state
{
    MMHAL_NOT_IN_ISR,               /**< The function was not executed from ISR context. */
    MMHAL_IN_ISR,                   /**< The function was executed from ISR context. */
    MMHAL_ISR_STATE_UNKNOWN,        /**< The HAL does not support checking ISR state. */
};

/**
 * Enumeration for different LED's on the board.
 *
 * @note Some of these LED's may not be available on all boards and some of these values
 *       may refer to the same LED.
 */
enum mmhal_led_id
{
  LED_RED,
  LED_GREEN,
  LED_BLUE,
  LED_WHITE
};

/** Enumeration of MCU sleep state. */
enum mmhal_sleep_state
{
    MMHAL_SLEEP_DISABLED,   /**< Disable MCU sleep. */
    MMHAL_SLEEP_SHALLOW,    /**< MCU to enter shallow sleep. */
    MMHAL_SLEEP_DEEP,       /**< MCU can enter deep sleep. */
};

/** A value of 0 turns OFF an LED */
#define LED_OFF   0

/**
 * A value of 255 turns an LED ON fully.
 *
 * Some boards support varying an LED's brightness. For these boards a value between 1 and 255
 * will result in proportionately varying levels of brightness. LED's that do not have a
 * brightness control feature will just turn ON fully for any non zero value.
 */
#define LED_ON    255

/**
 * Get the current ISR state  (i.e., whether in ISR or not).
 *
 * @returns the current ISR state, or @c MMHAL_ISR_STATE_UNKNOWN if the HAL does not support
 *          checking ISR state.
 */
enum mmhal_isr_state mmhal_get_isr_state(void);

/**
 * Write to the debug log.
 *
 * It is assumed the caller will have mechanisms in place to prevent concurrent access.
 *
 * @param data  Buffer containing data to write.
 * @param len   Length of data in buffer.
 */
void mmhal_log_write(const uint8_t *data, size_t len);

/**
 * Flush the debug log before returning.
 *
 * @warning Implementations of this function must support being invoked with interrupts disabled.
 */
void mmhal_log_flush(void);

/**
 * Generate a random 32 bit integer within the given range.
 *
 * @param min   Minimum value (inclusive).
 * @param max   Maximum value (inclusive).
 *
 * @returns a randomly generated integer (min <= i <= max).
 */
uint32_t mmhal_random_u32(uint32_t min, uint32_t max);

/** Reset the microcontroller. */
void mmhal_reset(void);

/**
 * Set the specified LED to the requested level. Do nothing if the requested LED does not exist.
 *
 * @param led   The LED to set, if the platform supports it. See @ref mmhal_led_id
 * @param level The level to set it to. 0 means OFF and non-zero means ON. If the platform supports
 *              brightness levels then 255 is full. Defines @ref LED_ON and @ref LED_OFF are
 *              provided for ease of use.
 */
void mmhal_set_led(uint8_t led, uint8_t level);

/**
 * Set the error LED to the requested state.
 *
 * @note This function is called by the bootloader and so needs to do all the initialization
 *       required to set the LED's as the bootloader does not use the regular BSP initialization
 *       located in main.c for configuring @c GPIO's and setting clock gates as required.
 *
 * @param state Set to true if the LED needs to be turned on,
 *              or false if the led needs to be turned off.
 */
void mmhal_set_error_led(bool state);

/**
 * Enumeration for buttons on the board.
 *
 * @note The support for each button is platform dependent.
 */
enum mmhal_button_id
{
  BUTTON_ID_USER0
};

/**
 * Enumeration for button states
 */
enum mmhal_button_state
{
  BUTTON_RELEASED,
  BUTTON_PRESSED
};

/** Button state callback function prototype. */
typedef void (*mmhal_button_state_cb_t)(enum mmhal_button_id button_id, enum mmhal_button_state button_state);

/**
 * Registers a callback handler for button state changes.
 *
 * @note The callback will be executed in an Interrupt Service Routine context
 *
 * @param button_id The button whose state should be reported to the callback
 * @param button_state_cb The function to call on button state change, or NULL to disable.
 * @returns True if the callback is registered successfully, False if not supported
 */
bool mmhal_set_button_callback(enum mmhal_button_id button_id, mmhal_button_state_cb_t button_state_cb);

/**
 * Reads the state of the specified button.
 *
 * @param button_id The button state to read
 * @returns The current button state, or BUTTON_RELEASED if not supported
 */
enum mmhal_button_state mmhal_get_button(enum mmhal_button_id button_id);


/**
 * Reads information that can be used to identify the hardware platform, such as
 * hardware ID and version number, in the form of a user readable string.
 *
 * This function attempts to detect the correct hardware and version.
 * The actual means of detecting the correct hardware and version will vary from
 * implementation to implementation and may use means such as identification
 * information stored in EEPROM or devices detected on GPIO, SPI or I2C interfaces.
 * Returns false if the hardware could not be identified correctly.
 *
 * @param version_buffer The pre-allocated buffer to return the hardware ID and version in.
 * @param version_buffer_length The length of the pre-allocated buffer.
 * @returns              True if the hardware was correctly identified and returned.
 */
bool mmhal_get_hardware_version(char * version_buffer, size_t version_buffer_length);

/**
 * Macro to simplify debug pin mask/value definition.
 *
 * @param _pin_num  The pin number to set in the mask. Must be 0-31 (inclusive).
 *
 * Example:
 *
 *     mmhal_set_debug_pins(MMHAL_DEBUG_PIN(0), MMHAL_DEBUG_PIN(0));
 */
#define MMHAL_DEBUG_PIN(_pin_num)   (1ul << (_pin_num))

/** Bit mask with all debug pins selected. */
#define MMHAL_ALL_DEBUG_PINS        (UINT32_MAX)

/**
 * Set the value one or more debug pins.
 *
 * Each platform can define up to 32 GPIOs for application use. If a GPIO is not supported
 * by a platform then attempts to set it will be silently ignored. These GPIOs are intended
 * for debug/test purposes.
 *
 * @param mask      Mask of GPIOs to modify. Each bit in this mask that is set will result in
 *                  the corresponding GPIO being being set to the corresponding value given in
 *                  @p values.
 * @param values    Bit field, where each bit corresponds to a GPIO, specifying the direction
 *                  to drive each GPIO with 1 meaning drive high and 0 meaning drive low.
 *                  Only GPIOs with the corresponding bit set in @p mask will be modified.
 *
 * @sa MM_DEBUG_PIN_MASK
 */
void mmhal_set_debug_pins(uint32_t mask, uint32_t values);

/**
 * Returns the time of day as set in the RTC.
 * Time is in UTC.
 *
 * @return    Epoch time (seconds since 1 Jan 1970) or 0 on failure.
 */
time_t mmhal_get_time();

/**
 * Sets the RTC to the specified time in UTC.
 *
 * @note While Unix epoch time supports years from 1970, most Real Time Clock
 *       chips support years from 2000 only as they store the year as years
 *       from 2000. So do not attempt to set any years below 2000 as this could cause
 *       the year to wrap around to an unreasonably high value. Definitely do not do:
 * @code
 * mmhal_set_time(0);
 * @endcode
 *
 * @param epoch Time in Unix epoch time (seconds since 1 Jan 1970).
 */
void mmhal_set_time(time_t epoch);

/**
 * Function to prepare MCU to enter sleep.
 * This will halt the timer that generates the RTOS tick.
 *
 * @param expected_idle_time_ms Expected time to sleep in milliseconds.
 *
 * @returns the type of sleep permitted by the current system state.
 */
enum mmhal_sleep_state mmhal_sleep_prepare(uint32_t expected_idle_time_ms);

/**
 * Function to enter MCU sleep.
 *
 * @param sleep_state             Sleep state to enter into.
 * @param expected_idle_time_ms   Expected time to sleep in milliseconds.
 *
 * @returns Elapsed sleep time in milliseconds.
 */
uint32_t mmhal_sleep(enum mmhal_sleep_state sleep_state, uint32_t expected_idle_time_ms);

/**
 * Function to abort the MCU sleep state.
 *
 * @note This must only be invoked after @ref mmhal_sleep_prepare()
 *       and before @ref mmhal_sleep().
 *
 * @param sleep_state         Sleep state to abort.
 */
void mmhal_sleep_abort(enum mmhal_sleep_state sleep_state);

/**
 * Function to cleanup on exit from the MCU sleep state.
 */
void mmhal_sleep_cleanup(void);

/** Enumeration of veto_id ranges for use with @ref mmhal_set_deep_sleep_veto() and
 *  @ref mmhal_clear_deep_sleep_veto(). */
enum mmhal_veto_id
{
    /** Start of deep sleep veto ID range that is available for application use. */
    MMHAL_VETO_ID_APP_MIN = 0,
    /** End of deep sleep veto ID range that is available for application use. */
    MMHAL_VETO_ID_APP_MAX = 7,
    /** Start of deep sleep veto ID range that is available for HAL use. */
    MMHAL_VETO_ID_HAL_MIN = 8,
    /** End of deep sleep veto ID range that is available for HAL use. */
    MMHAL_VETO_ID_HAL_MAX = 15,
    /** Start of deep sleep veto ID range that is allocated for morselib use. Note that this
     *  must not be changed as it is built into morselib. */
    MMHAL_VETO_ID_MORSELIB_MIN = 16,
    /** End of deep sleep veto ID range that is allocated for morselib use. Note that this must not
     *  be changed as it is built into morselib. */
    MMHAL_VETO_ID_MORSELIB_MAX = 19,
    /** Deep sleep veto ID for data-link subsystem. */
    MMHAL_VETO_ID_DATALINK     = 20,
    /** Deep sleep veto ID allocated to @ref MMCONFIG. */
    MMHAL_VETO_ID_MMCONFIG     = 21,
    /** Start of deep sleep veto ID range reserved for future use. */
    MMHAL_VETO_ID_RESERVED_MIN = 22,
    /** End of deep sleep veto ID range reserved for future use. */
    MMHAL_VETO_ID_RESERVED_MAX = 31,
};

/** Enumeration of deep sleep modes for the data-link subsystem. */
enum mmhal_datalink_deepsleep_mode
{
    /** Deep sleep mode is disabled. */
    MMHAL_DATALINK_DEEPSLEEP_DISABLED,
    /** Enable deep sleep until activity occurs on data-link transport. */
    MMHAL_DATALINK_DEEPSLEEP_ONE_SHOT,
};

/**
 * Sets a deep sleep veto that will prevent the device from entering deep sleep. The device
 * will not enter deep sleep until there are no vetoes remaining. This veto can be cleared
 * by a call to @ref mmhal_clear_deep_sleep_veto() with the same veto_id.
 *
 * Up to 32 vetoes are supported (ID numbers 0-31). Each veto should be used exclusively by
 * a given aspect of the system (e.g., to prevent deep sleep when a DMA transfer is in progress,
 * or to prevent deep sleep when there is log data buffered for transmit, etc.).
 *
 * @param veto_id   The veto identifier. Valid values are 0-31, and these are split up into ranges
 *                  for use by different subsystems -- see @ref mmhal_veto_id.
 */
void mmhal_set_deep_sleep_veto(uint8_t veto_id);

/**
 * Clears a deep sleep veto that was preventing the device from entering deep sleep (see
 * @ref mmhal_set_deep_sleep_veto()). If the given veto was not already set then this has
 * no effect.
 *
 * @param veto_id   The veto identifier. Valid values are 0-31, and these are split up into ranges
 *                  for use by different subsystems -- see @ref mmhal_veto_id.
 */
void mmhal_clear_deep_sleep_veto(uint8_t veto_id);

/**
 * Set the data-link deep sleep mode. See @ref mmhal_datalink_deepsleep_mode for possible deep
 * sleep modes. Note that a given platform may not support all modes.
 *
 * @param mode    The deep sleep mode to set.
 *
 * @returns true if the mode was set successfully; false on failure (e.g.  unsupported mode).
 */
bool mmhal_datalink_set_deepsleep_mode(enum mmhal_datalink_deepsleep_mode mode);

#ifdef __cplusplus
}
#endif

/** @} */
