/* ****************************************************************************
 * File:      acc_LIS2DW12
 * Date:      29 Jan 2024
 * Author:    Andrew Levido
 *
 * Copyright 2025 Imbrius Pty Ltd - All Rights Reserved
 *
 *****************************************************************************/
#ifndef _acc_LIS2DW12_H
#define _acc_LIS2DW12_H

#ifdef __cplusplus
extern "C" {
#endif

/* Description ---------------------------------------------------------------*/

/* Implements a subset of the features available on the LIS2DW12 accelerometer 
 * including all operating and power modes, continuous and 1-shot conversions
 * orientation detection, tap or double-tap detection, freefall detection, 
 * activity/inactivity detection including low-power sleep. Does not (currently)
 * support FIFO operation or user offset.
 * 
 * Expects:
 *   - 4-wire SPI  (specified in initialisation)
 *   - acc_ISR() called from interrupt associated with LIS2DW12 INT1 pin
 *     (rising edge trigger, no pullup or pulldown) This handles all callbacks
 */

/* Includes ------------------------------------------------------------------*/
#include <stdint.h>
#include <stdbool.h>
#include "main.h"

/* Defines -------------------------------------------------------------------*/
// Result codes used by this driver
#define ACC_OK              0   // Success
#define ACC_ERR_PARAM      -1   // Invalid parameter
#define ACC_ERR_I2C        -2   // I2C tx/rx error
#define ACC_ERR_ID         -3   // Chip ID does not match ACC_CHIP_ID
#define ACC_ERR_INIT       -4   // Function called before initialisation
#define ACC_ERR_MODE       -5   // Function not possible in this mode

// Device I2C Address
#define ACC_DEV_ADDRESS    0x19
/* Initialisation ------------------------------------------------------------*/

/// Accelerometer power modes 
typedef enum { ACC_PWR_LOW, ACC_PWR_HI, ACC_PWR_1SHOT } acc_pwr_mode_t;
/// Accelerometer low power modes
typedef enum { ACC_LP1, ACC_LP2, ACC_LP3, ACC_LP4 } acc_lp_mode_t;
/// Accelerometer output data rates
typedef enum { ACC_ODR_PWR_DN, ACC_ODR_1HZ6, ACC_ODR_12HZ5, ACC_ODR_25HZ, 
               ACC_ODR_50HZ, ACC_ODR_100HZ, ACC_ODR_200HZ, ACC_ODR_400HZ, 
               ACC_ODR_800_HZ,  ACC_ODR_1KHZ6 } acc_odr_t;
/// Bandwith for low-pass and high-pass filters
typedef enum { ACC_BW_0, ACC_BW_1, ACC_BW_2, ACC_BW_3 } acc_bw_t;
/// Accelerometer full scale setting
typedef enum { ACC_FS_2G, ACC_FS_4G, ACC_FS_8G, ACC_FS_16G } acc_fs_t;

/// Accelerometer configuration structure
typedef struct {
  bool low_noise;           // Enable low-noise settling
  acc_pwr_mode_t pwrmode;   // Power operating mode
  acc_lp_mode_t lpmode;     // Low Power operating mode
  acc_odr_t odr;            // Output data rate
  acc_bw_t bandwidth;       // Digital filter (HPF & LPF) cutoff 
  acc_fs_t fullscale;       // Accelerometer full scale value
} acc_init_cfg_t;

/** Initialise accelerometer driver
 *
 * Initialises the SPI interface. Checks communication with the device then 
 * sets operating mode, data rate, filter bandwidths, full-scale range. 
 * Must be called before any other driver function. 
 * 
 * @param cfg             Pointer to a populated configuration structure
 * @returns               0 if successful, negative number if not
 */
int32_t acc_init(acc_init_cfg_t * cfg);

/** Soft reset accelerometer.
 * 
 * Reloads default parameters. Includes 1ms delay. Does not reload NV
 * parameters.
 *
 * @returns               0 if successful, negative number if not
 */
int32_t acc_soft_reset(void);

/** Get output data rate 
 * 
 *  Retrieve actual ODR, which may differ from the requested rate if it was
 *  not valid for that configuration.
 */ 
acc_odr_t acc_get_odr(void);

/* Read Accelerometer Data ---------------------------------------------------*/

/// Acceleration reading configuration structure
typedef struct {
  bool src_hpf;                    // true - LPF path, false = LPF path
  void (*read_cb)(bool valid);     // Callback. Valid true if new data has been read
} acc_read_cfg_t;

/** Configure accelerometer read.
 * 
 * @param cfg             Pointer to a populated configuration structure
 * @returns               0 if successful, negative number if not
 */
int32_t acc_read_cfg(acc_read_cfg_t * cfg);

/** Start data conversion
 * 
 *  Initialises data conversion (contiuous or one-shot depending on the mode 
 *  selected). The callback function is called when the conversion is done 
 * 
 * @returns               0 if successful, negative number if not
 */
int32_t acc_read_start(void);

/** Stop data conversion
 *
 * @returns               0 if successful, negative number if not
 */
int32_t  acc_read_stop(void);

/** Read acceleration on x-axis 
 * 
 * @returns               Raw acceleration previously read by driver
 */
int16_t acc_read_x(void); 

/** Read acceleration on y-axis 
 * 
 * @returns               Raw acceleration previously read by driver
 */
int16_t acc_read_y(void); 

/** Read acceleration on z-axis 
 * 
 * @returns               Raw acceleration previously read by driver
 */
int16_t acc_read_z(void); 

/** Read temperature
 * 
 * @returns               Raw temperature previously read by driver
 */
int16_t acc_read_temp(void); 

/* Orientation detection function -----------------------------------------------*/

/// Orientation data source
typedef enum { ACC_ORIENT_DIRECT, ACC_ORIENT_LPF } acc_or_src_t;
/// Orientation detection threshold (degrees)
typedef enum { ACC_OR_THS_80, ACC_OR_THS_70, ACC_OR_THS_60, ACC_OR_THS_50 
             } acc_orient_ths_t;
/// Orientation result type    
typedef enum  { ACC_OR_BAD,  ACC_OR_XD = 1, ACC_OR_XU = 2, 
                ACC_OR_YD = 4, ACC_OR_YU = 8, ACC_OR_ZD = 16, ACC_OR_ZU = 32 
              } acc_orient_t;

/// Orientation detection configuration structure
typedef struct {
  acc_or_src_t or_src;            // Orientation signal source
  acc_orient_ths_t orient_ths;    // Orientation threshold angle
  bool orient_limit_4D;           // Limit orientation to X-Y axis
  void (*orient_cb)(acc_orient_t orientation); // Callback
} acc_orient_cfg_t;

/** Configure orientation detection.
 * 
 * @param cfg             Pointer to a populated configuration structure
 * @returns               0 if successful, negative number if not
 */
int32_t acc_orient_cfg(acc_orient_cfg_t * cfg);

/** Start orientation detecton function 
 * 
 *  Initialises orientation detection function. The callback function is called
 *  when the orientation changes 
 * 
 * @returns               0 if successful, negative number if not
 */
int32_t acc_orient_start(void);

/** Stop orientation function interrupts
 * 
 * @returns               0 if successful, negative number if not
 */
int32_t  acc_orient_stop(void);

/** Read orientation data
 * 
 * @param orientation     Pointer to a variable to hold the orientation
 * @returns               0 if successful, negative number if not
 */
int32_t acc_read_orient(acc_orient_t * orientation);

/* Detect Tap or Double-tap Function -----------------------------------------*/

/// Tap axis priority for multiple taps
typedef enum { ACC_TAP_PRIO_XYZ, ACC_TAP_PRIO_YXZ, ACC_TAP_PRIO_XZY,
               ACC_TAP_PRIO_ZYX, ACC_TAP_PRIO_YZX = 5, ACC_TAP_PRIO_ZXY 
             } acc_tap_prio_t;

/// Tap result type
typedef enum {  ACC_TAP_BAD,
                ACC_TAP_ZP  = 0x01, ACC_TAP_YP  = 0x02, ACC_TAP_XP  = 0x04,
                ACC_TAP_ZN  = 0x09, ACC_TAP_YN  = 0x0A, ACC_TAP_XN  = 0x0C,
} acc_tap_t;

/// Tap detection configuration structure
typedef struct {
  bool double_tap;              // True = double tap, false = single tap
  uint8_t tap_ths_x;            // Tap threshold x-axis (1LSB - fs/32)
  uint8_t tap_ths_y;            // Tap threshold y-axis (1LSB - fs/32)
  uint8_t tap_ths_z;            // Tap threshold z-axis (1LSB - fs/32)
  bool tap_enable_x;            // Enable tap detection on x-axis
  bool tap_enable_y;            // Enable tap detection on y-axis
  bool tap_enable_z;            // Enable tap detection on z-axis
  acc_tap_prio_t tap_prio;      // Tap axis priority 
  uint8_t tap_shock_time;       // 0 = 4/ODR, 1 = 8/ODR ... 3 = 16/ODR
  uint8_t tap_quiet_time;       // 0 = 2/ODR, 1 = 4/ODR ... 3 = 8/ODR
  uint8_t tap_latency_time;     // 0 = 16/ODR, 1 = 32/ODR ... 15 = 256/ODR
  void (*tap_cb) (acc_tap_t tap); // Callback function 

} acc_tap_cfg_t;

/** Configure tap detection.
 * 
 * @param cfg             Pointer to a populated configuration structure
 * @returns               0 if successful, negative number if not
 */
int32_t acc_tap_cfg(acc_tap_cfg_t * cfg);

/** Start tap function detection.
 * 
 *  Initialises tap/doubletap detection function. The callback function is 
 *  called when a valid tap occurs. Data sheet recommend ODR >= 400Hz
 * 
 * @returns               0 if successful, negative number if not
 */
int32_t acc_tap_start(void);

/** Stop tap detection
 * 
 * @returns               0 if successful, negative number if not
 */
int32_t  acc_tap_stop(void);

/* Detect Free-fall Function -------------------------------------------------*/

typedef enum { ACC_FF_THS_156MG, ACC_FF_THS_219MG, ACC_FF_THS_250MG,
               ACC_FF_THS_312MG, ACC_FF_THS_344MG, ACC_FF_THS_406MG,
               ACC_FF_THS_469MG, ACC_FF_THS_500MG
               } acc_ff_ths_t;

typedef struct {
   acc_ff_ths_t threshold;      // Freefall detection threshold 
   uint8_t duration;            // 0..31 in units of 1/ODR
   void (*freefall_cb)(void);
} acc_ff_cfg_t;

/** Configure free-fall detection function.
 * 
 * @param cfg             Pointer to a populated configuration structure
 * @returns               0 if successful, negative number if not
 */
int32_t acc_freefall_cfg(acc_ff_cfg_t * cfg);

/** Start freefall detection function.
 * 
 *  Initialises free-fall detection function. The callback function is 
 *  called when a free fall event occurs. 
 * 
 * @returns               0 if successful, negative number if not
 */
int32_t acc_freefall_start(void);

/** Stop free-fall detection function 
 * 
 * @returns               0 if successful, negative number if not
 */
int32_t  acc_freefall_stop(void);

/* Sleep / Activity Detection -------------------------------------------------*/

typedef enum { ACC_ACT_HPF, ACC_ACT_OFFSET } acc_act_src_t;

typedef enum { ACC_ACT_INT_CHANGE, ACC_ACT_INT_WAKEUP } acc_act_int_t;

typedef enum { ACC_ACT_INACTIVE, ACC_ACT_SLEEP, ACC_ACT_WAKE} acc_act_t;

typedef struct {
  acc_act_src_t act_src;        // Wakeup/Activity signal source
  uint8_t threshold;            // 0 .. 63 in units of 1/64 full scale
  uint8_t inact_dur;            // time < ths for inactivity 0..15 in 152/ODR steps
  uint8_t wake_dur;             // time > ths for wake 0..3 in 1/ODR steps
  bool no_lp;                   // dont sleep on inactivity
  acc_act_int_t int_mode;       // interrupt on activity change or wakeup
  void (*activity_cb)(bool awake);
} acc_act_cfg_t;

/** Configure activity detection function
 * 
 * @param cfg             Pointer to a populated configuration structure
 * @returns               0 if successful, negative number if not
 */
int32_t acc_activity_cfg(acc_act_cfg_t * cfg);

/** Start actvity/inactivity detection function. 
 * 
 *  Initialises factivity detection function. The callback function is 
 *  called when an activity event occurs
 *
 * @returns               0 if successful, negative number if not
 */
int32_t acc_activity_start(void);

/** Stop activity/inactivity function interrupts
 * 
 * @returns               0 if successful, negative number if not
 */
int32_t  acc_activity_stop(void);

/** Read activity data
 * 
 * @param activity        Pointer to a variable to hold the orientation
 * @returns               0 if successful, negative number if not
 */
int32_t acc_read_activity(acc_act_t * activity);

/* Interrupt Service Routine Declarations ------------------------------------*/

void acc_ISR(void);

#ifdef __cplusplus
}
#endif

#endif

/* End acc_LIS2DW12 */