with HAL;     use HAL;
with HAL.I2C; use HAL.I2C;

private with Ada.Unchecked_Conversion;

package LSM303AGR is
  -- TODO: support other power modes HiRes/LoPower,
  -- Specifically allowing them in configure procedure
  -- and adjusting conversions after reading the
  -- accelerometer sensor.
  -- TODO: Add magnetometer support.

  type Register_Address is new UInt8;
  type Device_Identifier is new UInt8;
  type Data_Rate is
   (PowerDown, Freq_1, Freq_10, Freq_25, Freq_50, Freq_100, Freq_200, Freq_400,
    Low_Power, Hi_Res_1k6_Low_power_5k3);

  -- TODO: Support HiRes / LowPower / Normal operation modes
  type Axis_Data is range -2**9 .. 2**9 - 1 with
    Size => 10;

  type All_Axes_Data is record
      -- Represents accelerometer data for 3-Axes
    X, Y, Z : Axis_Data;
  end record;

  type LSM303AGR_Accelerometer (Port : not null Any_I2C_Port) is
   tagged limited private;

  function Check_Accelerometer_Device_Id
   (This : LSM303AGR_Accelerometer) return Boolean;

  function Check_Magnetometer_Device_Id
   (This : LSM303AGR_Accelerometer) return Boolean;

  procedure Configure (This : LSM303AGR_Accelerometer; Date_Rate : Data_Rate);

  function Read_Accelerometer
   (This : LSM303AGR_Accelerometer) return All_Axes_Data;

private
  type LSM303AGR_Accelerometer (Port : not null Any_I2C_Port)
  is tagged limited null record;

  for Data_Rate use
   (PowerDown => 0, Freq_1 => 1, Freq_10 => 2, Freq_25 => 3, Freq_50 => 4,
    Freq_100 => 5, Freq_200 => 6, Freq_400 => 7, Low_Power => 8,
    Hi_Res_1k6_Low_power_5k3 => 9);

  Accelerometer_Address   : constant I2C_Address       := 16#32#;
  Accelerometer_Device_Id : constant Device_Identifier := 2#0011_0011#;

  Magnetometer_Address   : constant I2C_Address       := 16#3C#;
  Magnetometer_Device_Id : constant Device_Identifier := 2#0100_0000#;

  WHO_AM_I_A : constant Register_Address := 16#0F#;
  WHO_AM_I_M : constant Register_Address := 16#4F#;

  CTRL_REG1_A : constant Register_Address := 16#20#;
  CTRL_REG2_A : constant Register_Address := 16#21#;
  CTRL_REG3_A : constant Register_Address := 16#22#;
  CTRL_REG4_A : constant Register_Address := 16#23#;
  CTRL_REG5_A : constant Register_Address := 16#24#;
  CTRL_REG6_A : constant Register_Address := 16#25#;

  DATACAPTURE_A : constant Register_Address := 16#26#;
  STATUS_REG_A  : constant Register_Address := 16#27#;
  OUT_X_L_A     : constant Register_Address := 16#28#;
  OUT_X_H_A     : constant Register_Address := 16#29#;
  OUT_Y_L_A     : constant Register_Address := 16#2A#;
  OUT_Y_H_A     : constant Register_Address := 16#2B#;
  OUT_Z_L_A     : constant Register_Address := 16#2C#;
  OUT_Z_H_A     : constant Register_Address := 16#2D#;

  MULTI_BYTE_READ : constant := 2#1000_0000#;

  NORMAL_MODE_DIVISOR : constant := 6;

  type CTRL_REG1_A_Register is record
    Xen  : Bit   := 0;
    Yen  : Bit   := 0;
    Zen  : Bit   := 0;
    LPen : Bit   := 0;
    ODR  : UInt4 := 0;
  end record;

  for CTRL_REG1_A_Register use record
    Xen  at 0 range 0 .. 0;
    Yen  at 0 range 1 .. 1;
    Zen  at 0 range 2 .. 2;
    LPen at 0 range 3 .. 3;
    ODR  at 0 range 4 .. 7;
  end record;

  function To_UInt8 is new Ada.Unchecked_Conversion
   (CTRL_REG1_A_Register, UInt8);
  function To_Reg is new Ada.Unchecked_Conversion
   (UInt8, CTRL_REG1_A_Register);

  type STATUS_REG_A_Register is record
    ZYXOVR : Bit := 0;
    ZOVR   : Bit := 0;
    YOVR   : Bit := 0;
    XOVR   : Bit := 0;
    ZYXDA  : Bit := 0;
    ZDA    : Bit := 0;
    YDA    : Bit := 0;
    XDA    : Bit := 0;
  end record;

  for STATUS_REG_A_Register use record
    XDA at 0 range 0 .. 0;
    YDA at 0 range 1 .. 1;
    ZDA at 0 range 2 .. 2;

    ZYXDA at 0 range 3 .. 3;

    XOVR at 0 range 4 .. 4;
    YOVR at 0 range 5 .. 5;
    ZOVR at 0 range 6 .. 6;

    ZYXOVR at 0 range 7 .. 7;
  end record;

  function To_UInt8 is new Ada.Unchecked_Conversion
   (STATUS_REG_A_Register, UInt8);
  function To_Reg is new Ada.Unchecked_Conversion
   (UInt8, STATUS_REG_A_Register);

end LSM303AGR;
