#ifndef PROX_TMD2635_H_
#define PROX_TMD2635_H_

// Prerequisites - I2C driver
#include "i2c.h"
#include <stdbool.h>

#define TMD2635_I2C_ADDR		(0x72u) // For use with UNSHIFTED address type, 0b0111.001W, where W is the read/write bit

#define PROX_SENSOR_THRESHOLD	(1500u)
#define PROX_SENSOR_HYSTERESIS	(100u)

// Register addresses
#define PROX_REG_ENABLE         (0x80u) // Enable chip oscillator, proximity detection, and proximity wait function
#define PROX_REG_PRATE          (0x82u) // duration of sample, 88us*(PRATE+1)
#define PROX_REG_PILTL			(0x88u) // proximity low threshold, low byte
#define PROX_REG_PILTH			(0x89u) // proximity low threshold, high byte
#define PROX_REG_PIHTL			(0x8Au) // proximity high threshold, low byte
#define PROX_REG_PIHTH			(0x8Bu) // proximity high threshold, high byte
#define PROX_REG_PERS			(0x8Cu) // persistence counter, number of cycles outside PILT/PIHT that triggers an interrupt
#define PROX_REG_CFG0			(0x8Du) // contains only PWLONG bit
#define PROX_REG_PCFG0			(0x8Eu) // PD amplifier gain and max number of pulses
#define PROX_REG_PCFG1          (0x8Fu) // pulse length and laser current
#define PROX_REG_REVID			(0x91u) // Revision ID (normally reads as 0x10)
#define PROX_REG_DEVID			(0x92u) // Device ID (normally reads as 0x44)
#define PROX_REG_STATUS			(0x9Bu) // status flags
#define PROX_REG_PDATAL         (0x9Cu) // Proximity data low byte
#define PROX_REG_PDATAH         (0x9Du) // Proximity data high byte
#define PROX_REG_REVID2			(0xA6u) // Version ID, normally reads 0x01 or 0x0E
#define PROX_REG_SOFTRST        (0xA8u) // Software reset
#define PROX_REG_PWTIME			(0xA9u) // wait time between pulses in 2.78ms increments (programmable between 2.78 to 711.68ms)
										// note that PWLONG increases wait time by 12x (so it would be 33.36ms to 8.54s)
#define PROX_REG_CFG8				(0xAAu) // Photodiode select (near/far/both)
#define PROX_REG_CFG3				(0xABu) // Status bits clear when read, and sleep after interrupt
#define PROX_REG_CFG6				(0xAEu) // Auto pulse control (APC) disable
#define PROX_REG_PFILTER				(0xB3u) // Moving average filter
#define PROX_REG_POFFSETL       (0xC0u) // Offset low byte (subtracted from proximity data)
#define PROX_REG_POFFSETH       (0xC1u) // Offset high byte (just the sign bit at b0)
#define PROX_REG_CALIB          (0xD7u) // Calibration control (automatically calculate offset)
#define PROX_REG_CALIBCFG       (0xD9u) // Bin search target for calib, auto offset adjust, and hardware averaging
#define PROX_REG_CALIBSTAT      (0xDCu) // If offset was adjusted and calib complete flags
#define PROX_REG_INTENAB		(0xDDu) // Interrupt enable flags
#define PROX_REG_FAC_L			(0xE5u) // Factory data, lot code, low byte
#define PROX_REG_FAC_H			(0xE6u) // Factory data, lot code, high byte
#define PROX_REG_TEST9          (0xF9u) // No idea what this does, but the datasheet says this must be set to 0x07

// Bit and field definitions
#define PROX_ENABLE_PON				(0x01u) // enable oscillator and ADC
#define PROX_ENABLE_PEN				(0x04u) // enables proximity detection
#define PROX_ENABLE_PWEN			(0x10u) // enables proximity wait function

#define	PROX_CFG0_RSVD				(0x40u) // must be OR'd with any CFG0 setting
#define	PROX_CFG0_PWLONG			(0x08u)

#define	PROX_PCFG0_PGAIN_Pos		(6)
#define	PROX_PCFG0_PGAIN_Msk		(0x03u << PROX_PCFG0_PGAIN_Pos)
#define	PROX_PCFG0_PGAIN(value)		((value & 0x03u) << PROX_PCFG0_PGAIN_Pos)
#define	PROX_PCFG0_PGAIN_1x			PROX_PCFG0_PGAIN(0)
#define	PROX_PCFG0_PGAIN_2x			PROX_PCFG0_PGAIN(1)
#define	PROX_PCFG0_PGAIN_4x			PROX_PCFG0_PGAIN(2)
#define	PROX_PCFG0_PGAIN_8x			PROX_PCFG0_PGAIN(3)
#define	PROX_PCFG0_PPULSE_Pos		(0)
#define	PROX_PCFG0_PPULSE_Msk		(0x3Fu << PROX_PCFG0_PPULSE_Pos)
#define	PROX_PCFG0_PPULSE(value)	((value & 0x3Fu) << PROX_PCFG0_PPULSE_Pos)

#define PROX_PCFG1_PPULSELEN_Pos    (5)
#define PROX_PCFG1_PPULSELEN_Msk    (0x07u << PROX_PCFG1_PPULSELEN_Pos)
#define PROX_PCFG1_PPULSELEN(value) ((value & 0x07u) << PROX_PCFG1_PPULSELEN_Pos)
#define PROX_PCFG1_PPULSELEN_1us    PROX_PCFG1_PPULSELEN(0)
#define PROX_PCFG1_PPULSELEN_2us    PROX_PCFG1_PPULSELEN(1)
#define PROX_PCFG1_PPULSELEN_4us    PROX_PCFG1_PPULSELEN(2)
#define PROX_PCFG1_PPULSELEN_8us    PROX_PCFG1_PPULSELEN(3)
#define PROX_PCFG1_PPULSELEN_12us   PROX_PCFG1_PPULSELEN(4)
#define PROX_PCFG1_PPULSELEN_16us   PROX_PCFG1_PPULSELEN(5)
#define PROX_PCFG1_PPULSELEN_24us   PROX_PCFG1_PPULSELEN(6)
#define PROX_PCFG1_PPULSELEN_32us   PROX_PCFG1_PPULSELEN(7)
#define PROX_PCFG1_PLDRIVE_Pos      (0)
#define PROX_PCFG1_PLDRIVE_Msk      (0x0Fu << PROX_PCFG1_PLDRIVE_Pos)
#define PROX_PCFG1_PLDRIVE(value)   ((value & 0x0Fu) << PROX_PCFG1_PLDRIVE_Pos)
#define PROX_PCFG1_PLDRIVE_7mA      PROX_PCFG1_PLDRIVE(5)
#define PROX_PCFG1_PLDRIVE_8mA      PROX_PCFG1_PLDRIVE(6)
#define PROX_PCFG1_PLDRIVE_9mA      PROX_PCFG1_PLDRIVE(7)
#define PROX_PCFG1_PLDRIVE_10mA     PROX_PCFG1_PLDRIVE(8)

// All status flags can be cleared by writing with '1'
// Or if INT_READ_CLEAR in CFG3 is set, all are cleared when the register is read
#define	PROX_STATUS_PHIGH			(0x80u) // PDATA is above high threshold, only triggers when PINT set
#define	PROX_STATUS_PLOW			(0x40u) // PDATA is below low threshold, only triggers when PINT set
#define	PROX_STATUS_PSAT			(0x20u) // saturation event occurred
#define	PROX_STATUS_PINT			(0x10u)	// prox results exceeded threshold and persistence settings
#define	PROX_STATUS_CINT			(0x08u) // calibration complete
#define	PROX_STATUS_ZINT			(0x04u) // zero detection, a zero reading of PDATA caused offset to be adjusted (only if AUTO_OFFSET_ADJ set)
#define	PROX_STATUS_PSAT_REFLECTIVE	(0x02u) // saturation during IR VCSEL on time (from IR reflection)
#define	PROX_STATUS_PSAT_AMBIENT	(0x01u) // saturation during IR VCSEL off (from ambient light)

#define PROX_SOFTRST_RST        (0x01u) // Set to trigger a software reset

#define PROX_CFG8_PDSELECT_Pos		(0)
#define PROX_CFG8_PDSELECT_Msk		(0x03u << PROX_CFG8_PDSELECT_Pos)
#define PROX_CFG8_PDSELECT(value)	((value & 0x03u) << PROX_CFG8_PDSELECT_Pos)
#define PROX_CFG8_PDSELECT_FAR		PROX_CFG8_PDSELECT(1)
#define PROX_CFG8_PDSELECT_NEAR		PROX_CFG8_PDSELECT(2)
#define PROX_CFG8_PDSELECT_BOTH		PROX_CFG8_PDSELECT(3)

#define PROX_CFG3_RSVD				(0x04u) // Must be OR'd with any CFG3 setting
#define PROX_CFG3_INT_READ_CLEAR	(0x80u)
#define PROX_CFG3_SAI				(0x08u)

#define PROX_CFG6_RSVD				(0x3Fu) // Must be OR'd with any CFG6 setting

#define PROX_PFILTER_PMAVG_Pos		(0)
#define PROX_PFILTER_PMAVG_Msk		(0x03u << PROX_PFILTER_PMAVG_Pos)
#define PROX_PFILTER_PMAVG(value)	((value & 0x03u) << PROX_PFILTER_PMAVG_Pos)
#define PROX_PFILTER_PMAVG_None		PROX_PFILTER_PMAVG(0)
#define PROX_PFILTER_PMAVG_2		PROX_PFILTER_PMAVG(1)
#define PROX_PFILTER_PMAVG_4		PROX_PFILTER_PMAVG(2)
#define PROX_PFILTER_PMAVG_8		PROX_PFILTER_PMAVG(3)

#define PROX_CALIB_CALAVG			(0x80u) // 1: Enables averaging during calibration, 0: ignores averaging
#define PROX_CALIB_ELECTRICAL		(0x20u) // 1: Electrical calibration only, 0: optical and electrical
#define PROX_CALIB_PRATE			(0x10u) // 1: Enables PRATE during calibration, 0: ignores PRATE
#define PROX_CALIB_START			(0x01u) // Begin calibration

#define PROX_CALIBCFG_TARGET_Pos    (5)
#define PROX_CALIBCFG_TARGET_Msk    (0x7u << PROX_CALIBCFG_TARGET_Pos)
#define PROX_CALIBCFG_TARGET(value) ((value & 0x07u) << PROX_CALIBCFG_TARGET_Pos)
// Note - target value during calibration is equal to 2^(PROX_CALIBCFG_TARGET+2) - 1
// For example, PROX_CALIBCFG_TARGET = 3, value = 2^(2+3) - 1 = 2^5 - 1 = 31
#define PROX_CALIBCFG_RSVD          (0x10u) // Must be OR'd with any CALIBCFG settings
#define PROX_CALIBCFG_AUTO_OFFSET_ADJ (0x08u) // 1: automatically decrement offset if proximity data is ever zero, 0: don't change offset
#define PROX_CALIBCFG_AVG_Pos       (0)
#define PROX_CALIBCFG_AVG_Msk       (0x07u << PROX_CALIBCFG_AVG_Pos)
#define PROX_CALIBCFG_AVG(value)    ((value & 0x07u) << PROX_CALIBCFG_AVG_Pos)
// Note - hardware averaging set here in the calibration config register DOES APPLY to normal
// proximity reads. The number of averaging cycles is 2^PROX_CALIBCFG_AVG
// So a value of zero in PROX_CALIBCFG_AVG disables the averaging (1 cycle, no averaging)

#define PROX_CALIBSTAT_OFFSET_ADJUSTED  (0x04u) // Flag when offset was automatically decremented. Cleared by writing 1 to it
#define PROX_CALIBSTAT_CALIB_FINISHED   (0x01u) // Flag indicating calibration is finished. Reset when matching bit in status register is cleared

#define PROX_INTENAB_PIM			(0x20u)	// Interrupt Mode, 0: level, 1: state
#define PROX_INTENAB_PIEN			(0x10u) // Proximity interrupt enable
#define PROX_INTENAB_PSIEN			(0x08u) // Saturation interrupt enable
#define PROX_INTENAB_CIEN			(0x04u) // calibration interrupt enable
#define PROX_INTENAB_ZIEN			(0x02u) // zero interrupt enable

#define PROX_TEST9_VAL				(0x07u) // Datasheet simply says this must be set to TEST9

typedef enum _cal_target {
	Cal_Target_3 = 0,
	Cal_Target_7 = 1,
	Cal_Target_15 = 2,
	Cal_Target_31 = 3,
	Cal_Target_63 = 4, 
	Cal_Target_127 = 5, 
	Cal_Target_255 = 6, 
	Cal_Target_511 = 7 
} Cal_Target_T;

typedef enum _prox_pulse_len {
	Pulse_Length_1us = 0,
	Pulse_Length_2us = 1, 
	Pulse_Length_4us = 2, 
	Pulse_Length_8us = 3, 
	Pulse_Length_12us = 4, 
	Pulse_Length_16us = 5, 
	Pulse_Length_24us = 6, 
	Pulse_Length_32us = 7, 
} Prox_Pulse_Length;

typedef enum _prox_pd_select {
	Prox_PD_Near = 2,
	Prox_PD_Far = 1,
	Prox_PD_Both = 3	
} Prox_PD_Select;

typedef struct _prox_settings {
	bool pwen; // wait time enable
	uint8_t prate; // duration of a proximity event (actual wait time between set of pulses)
	bool pwlong; // long wait time, wait is multiplied by 12
	uint8_t pgain; // can be 1, 2, 4, or 8
	uint8_t ppulse; // Number of pulses in a prox cycle. if APC is on (default), this is the max number. Otherwise, it's just the number of pulses
	Prox_Pulse_Length plen; // Duration of a single pulse, from 1us to 32us (0: 1us, 7: 32us. any number larger than 7 here is not allowed)
	uint8_t pldrive; // laser drive strength in mA, range is 2 to 10
	uint8_t pwtime; // wait time in 2.78ms increments (33.36ms when pwlong is true), setting = 0 is actually 1 unit of wait time
	Prox_PD_Select pdselect; // near, far, or both photodiodes
	uint8_t pmavg; // Moving average filter over multiple cycles
	uint8_t proxavg; // Hardware repetition and averaging per cycle
} Prox_Settings;

uint8_t tmd2635_init(I2C_Handle* hi2c, bool softreset);
uint16_t tmd2635_get_distance(I2C_Handle* hi2c);
uint16_t tmd2635_get_offset(I2C_Handle* hi2c);
uint8_t tmd2635_set_offset(I2C_Handle* hi2c, uint16_t new_offset);
bool tmd2635_calibrate(I2C_Handle* hi2c, Cal_Target_T tgt);
bool tmd2635_is_connected(void);
Prox_Settings* tmd2635_get_settings(void);
bool tmd2635_apply_settings(I2C_Handle* hi2c);

#endif /* PROX_TMD2635_H_ */
