/*
  SFBPower.h Power control and monitoring routines for the SFB.
  Copyright (C) 2009 The Regents of the University of New Mexico.  All rights reserved.

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
  USA

  $Id$
*/

/**
  \file SFBPower.h Power control and monitoring routines for the SFB.
  \author David H. Ackley.  
  \date (C) 2009 All rights reserved.
  \lgpl
 */

#ifndef SFBPOWER_H
#define SFBPOWER_H

#include "SFBTypes.h"     /* For u8 */
#include "SFBConstants.h" /* For FACE_COUNT */
#include "SFBAssert.h"    /* For API_ASSERT_MAX */

extern int powerDraw();

extern int powerDraw(u32 face);

extern int rawPower(u32 face);

/** What is the current voltage measurement on \a face?  Returns a
    backwards-averaged value if \a smoothed is \c true or absent;
    returns the latest available sample if \a smoothed is present and
    \c false.

    Note that the calculated voltages are based on the datasheet
    properties of the ADC and the nominal values of the (1%) scaling
    resistors on the board, so the resulting values can differ
    substantially from the true voltage and from one board to another,
    but should be relatively consistent internally.

    \returns Millivolts at face

    \blinks #E_API_BAD_FACE if \a face is not one of #NORTH..#WEST

    \since 0.9.7
 */
extern s32 faceVoltage(u32 face, bool smoothed = true) ; 

/** Access current main (VEE) rail voltage.  Returns a
    backwards-averaged value if \a smoothed is \c true or absent;
    returns the latest available sample if \a smoothed is present and
    \c false.

    Be aware that, unless the hardware has been calibrated, the
    calculated voltages are based on the datasheet properties of the
    ADC and the nominal values of the (1%) scaling resistors on the
    board, so the resulting values can differ significantly from the
    true voltage and from face to face and from board to board, but
    should be relatively consistent on a given face.

    \returns Smoothed or latest millivolts measured on the requested
    rail

    \since 0.9.7
 */
extern s32 railVoltage(bool smoothed = true) ;

extern int rawVoltage();

/** Turn on (\a on != 0) or off (\a on == 0) the outbound power on \a
    face.

    \blinks #E_API_BAD_FACE if \a face is not one of #NORTH..#WEST
 */
extern void powerOut(u32 face, int on);  

/** True if output power is active to \a face, false otherwise.

    \blinks #E_API_BAD_FACE if \a face is not one of #NORTH..#WEST

    \since 0.9.11
 */
extern bool isPowerOut(u32 face);  

extern void powerIn(u32 face, int on);

/* Set all outbound power ON */
extern void initPowerOuts();

/** \cond All this stuff is basically private, but included in SFBPower.h
    just so that voltage calibration sketches can get at it.
 */

extern void invalidateCalibrationData();

enum ADCSampleIndices {
  NORTH_VSENSE_ADC_IDX,
  SOUTH_VSENSE_ADC_IDX,
  EAST_VSENSE_ADC_IDX,
  WEST_VSENSE_ADC_IDX,
  VEE_VSENSE_ADC_IDX,
  VSENSE_IDX_COUNT,

  VCC_VSENSE_ADC_IDX = VSENSE_IDX_COUNT,
  
  NORTH_ISENSE_ADC_IDX,
  SOUTH_ISENSE_ADC_IDX,
  EAST_ISENSE_ADC_IDX,
  WEST_ISENSE_ADC_IDX,
  ADC_IDX_COUNT
};

#define ADC_NOMINAL_MV_PER_COUNT 3.22581
#define ADC_FACE_RATIO 10.0
#define ADC_VCC_RATIO 2.0

#define NOMINAL_COUNTS_FOR_MV_FACE(mv) ((u32) (0.5+((mv)/ADC_NOMINAL_MV_PER_COUNT/ADC_FACE_RATIO)))
#define NOMINAL_COUNTS_FOR_MV_VCC(mv) ((u32) (0.5+((mv)/ADC_NOMINAL_MV_PER_COUNT/ADC_VCC_RATIO)))
#define NOMINAL_MV_FOR_COUNT_FACE(count) ((u32) (0.5+((count)*ADC_NOMINAL_MV_PER_COUNT*ADC_FACE_RATIO)))

#define EXTRA_SLOPE_BITS 6
#define UNCALIBRATED_SLOPE_FACE ((u32) ((ADC_NOMINAL_MV_PER_COUNT*ADC_FACE_RATIO)*(1<<EXTRA_SLOPE_BITS)))
#define UNCALIBRATED_CUTOFF 300
#define UNCALIBRATED_INTERCEPT_FACE (UNCALIBRATED_CUTOFF*UNCALIBRATED_SLOPE_FACE)

#define CUTOFF_INDEX 0
#define CUTOFF_BITS 5
#define CUTOFF_CENTER 16
#define CUTOFF_CT_PER_VALUE 10
#define CUTOFF_OFFSET 300

#define INTERCEPT_INDEX (CUTOFF_INDEX+CUTOFF_BITS)
#define INTERCEPT_BITS 5
#define INTERCEPT_CENTER 16
#define INTERCEPT_MV_PER_VALUE 25
#define INTERCEPT_OFFSET -300

#define SLOPE0_INDEX (INTERCEPT_INDEX+INTERCEPT_BITS)
#define SLOPE0_BITS 7
#define SLOPE0_CENTER 64
#define SLOPE0_SCALED_MV_PER_CT_PER_VALUE 10
#define SLOPE0_OFFSET (UNCALIBRATED_SLOPE_FACE+SLOPE0_CENTER*SLOPE0_SCALED_MV_PER_CT_PER_VALUE)

#define SLOPE1_INDEX (SLOPE0_INDEX+SLOPE0_BITS)
#define SLOPE1_BITS 7
#define SLOPE1_CENTER 64
#define SLOPE1_SCALED_MV_PER_CT_PER_VALUE 10
#define SLOPE1_OFFSET (UNCALIBRATED_SLOPE_FACE+SLOPE1_CENTER*SLOPE1_SCALED_MV_PER_CT_PER_VALUE)

struct xcalib {
  u32 cutoff;
  s32 slope[2];
  s32 intercept[2];

  /* Here for 'default datasheet' behavior */
  void init() {
    init(0, UNCALIBRATED_SLOPE_FACE, UNCALIBRATED_SLOPE_FACE, 0);
  }

  void init(u32 c, s32 m0, s32 m1, s32 b) {
    cutoff = c;
    slope[0] = m0;
    intercept[0] = ((NOMINAL_MV_FOR_COUNT_FACE(c)+b)<<EXTRA_SLOPE_BITS)-m0*c;
    slope[1] = m1;
    intercept[1] = ((NOMINAL_MV_FOR_COUNT_FACE(c)+b)<<EXTRA_SLOPE_BITS)-m1*c;
  }
};
extern xcalib adcCalibs[VSENSE_IDX_COUNT];

struct calibData {

  u32 data;
  inline u32 getBits(u32 bitIdx, u32 bits) {
    return (data>>bitIdx)&((1<<bits)-1);
  }
  void putBits(u32 bitIdx, u32 bits,u32 value) {
    u32 nbits = ((1<<bits)-1); 
    data &= ~(nbits<<bitIdx);
    data |= (value&nbits)<<bitIdx;
  }
  void init(u32 cutoff, u32 intercept, u32 slope0, u32 slope1) {
    putBits(CUTOFF_INDEX,CUTOFF_BITS,cutoff);
    putBits(INTERCEPT_INDEX,INTERCEPT_BITS,intercept);
    putBits(SLOPE0_INDEX,SLOPE0_BITS,slope0);
    putBits(SLOPE1_INDEX,SLOPE1_BITS,slope1);
  }
  void getFields(u32 & cutoff, u32 & intercept, u32 & slope0, u32 & slope1) {
    cutoff = getBits(CUTOFF_INDEX,CUTOFF_BITS);
    intercept = getBits(INTERCEPT_INDEX,INTERCEPT_BITS);
    slope0 = getBits(SLOPE0_INDEX,SLOPE0_BITS);
    slope1 = getBits(SLOPE1_INDEX,SLOPE1_BITS);
  }
  inline s32 getValue(u32 bitIdx, u32 bits, s32 center, s32 amtPerValue, s32 offset) {
    s32 value = getBits(bitIdx,bits);
    return (value-center)*amtPerValue+offset;
  }
  s32 getCutoffCt() { 
    return getValue(CUTOFF_INDEX,CUTOFF_BITS,CUTOFF_CENTER,CUTOFF_CT_PER_VALUE,CUTOFF_OFFSET);
  }
  s32 getInterceptMv() { 
    return
      getValue(INTERCEPT_INDEX,INTERCEPT_BITS,INTERCEPT_CENTER,INTERCEPT_MV_PER_VALUE,INTERCEPT_OFFSET);
  }
  s32 getSlopeScaledMvPerCt(u32 which) ;

  void initXcalib(xcalib & x) ;
};

extern void initxcalibsFromGene(u32 gene[VSENSE_IDX_COUNT]) ;

/** Number of bytes of voltage calibration data (stored the boot block).
 */
#define VOLTAGE_CALIBRATION_SIZE_BYTES (VSENSE_IDX_COUNT*3)

#define ADC_SCALE_BITS 6   /* This is fixed by the AD0DRx hardware; can't be changed efficiently */
#define WEIGHT_BITS 5  /* Could be 5 for a ton of smoothing, or 3..1 for less (0 would be redundant) */
#define UPDATE_SMOOTHED_BUFFER(u16buf,adcScaledData) \
  ((u16buf) = (u16) (((((u32)(u16buf))<<WEIGHT_BITS)-(u16buf)+(adcScaledData))>>WEIGHT_BITS))

static inline s32 mapADCCounts(u16 scaledCount, s32 slope, s32 intercept) {
  return (((scaledCount*slope)>>ADC_SCALE_BITS)+intercept)>>EXTRA_SLOPE_BITS;
}

extern void unpackCalibrationData(u32 gene[VSENSE_IDX_COUNT], u8 bytes[VOLTAGE_CALIBRATION_SIZE_BYTES]) ;

extern void loadCalibrationData() ;

extern u16 adcLatestSample[ADC_IDX_COUNT];

extern u16 adcSmooth[ADC_IDX_COUNT];

static inline u16 getADCSample(u32 sampleIdx, bool smoothed) {
  API_ASSERT_MAX(sampleIdx,ADC_IDX_COUNT);
  return smoothed?adcSmooth[sampleIdx]:adcLatestSample[sampleIdx];
}

#define POWER_SENSING_FLAG_CODE 0xffff  /* Can't occur in nature: ADC always gives 6 LSBs==0 */
static inline bool powerSensingEnabled() {
  return adcLatestSample[NORTH_ISENSE_ADC_IDX] != POWER_SENSING_FLAG_CODE;
}
extern void setPowerSensingEnabled(bool enable) ;

/** \endcond - 'Private' calibration methods and data */

/** \cond
    Initialization code 
*/
extern void initADC();
/** \endcond */

#endif /* SFBPOWER_H */
