/* ds1085.c - i2c interface to the frequency synthesizer chip.
 *  Copyright (C) 2022 by Jed Marti
 *   This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *  This program 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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see https://www.gnu.org/licenses/.
 */

#include <C8051F380.h>
#include <stdint.h>

#include "config.h"
#include "lfxmit.h"
#include "storage.h"
#include "i2c.h"
#include "serial.h"
#include "ds1085.h"

#include "lex.h"

// Uncomment the frequency you want, only one at a time allowed. These are created by the
// upper level ds1085.c program and the doit script.
// #include "meters2200.h"
#include "meters630.h"
//#include "meters160.h"
// #include "meters80.h"
//#include "meters40.h"

// See lex.c
extern __xdata uint8_t    ttype;           // The current token type.
extern __xdata uint32_t   N;               // Integer values.
extern __xdata char       STR[];           // String/identifier values.
extern __bit              doit;            // Whether or not to execute
extern __xdata floatOrInt IACC;            // Shared 32 bit value.
extern __xdata floatOrInt IACCB;           // Another one.
extern __xdata floatOrInt FACC;            // And a third one.
extern uint16_t           DREG;            // Typically an address.
__xdata int8_t offset;                     // DS1085 Range register offset, typically 15.

// I2C globals.
extern __xdata uint8_t I2CREGISTER;        // Which DS1085 register.
extern uint8_t *I2CBUFFER;                 // Buffer address to send/receive.
extern __xdata uint8_t I2CADDR;            // I2C Bus address
extern __bit I2CWRITE;                     // Set to 1 for writing, 0 for reading.
extern __xdata uint8_t I2CCNT;             // Number of bytes.
extern volatile __xdata uint8_t I2CSTATUS; // I2C status value.

// See errors.c
extern __bit errorThrown;

int32_t abs(int32_t v)
// 32 bit absolute value.
{
  if (v < 0) return - v;
  return v;
}


void initDS1085(void)
// Called during startup. You need to wait 10 milliseconds after this (the caller does this
// as the millisecond timer isn't on yet.
{
  uint32_t i;
  
  // We need the offset from the range register globally which is used to calculate the offset
  // register during a frequency change.
  I2CWRITE = 0;
  I2CCNT = 2;
  I2CBUFFER = STR;
  I2CREGISTER = ds1085RANGE_REG;
  readI2C();
  offset = STR[0] >> 3;
  // putstring("  DS1085 range "); puthexbyte(offset); terpri();

  for (i = 0; i < 65535; ++i);
  
  // Turn off the write EEPROM, caller waits 10 ms whilst other stuff is ongoing.
  I2CWRITE = 1;
  I2CCNT = 1;
  I2CBUFFER = STR;
  STR[0] = 0x08;             // WC = 1, turn off writing.
  I2CREGISTER = ds1085ADDR_BYTE;
  I2CADDR = DS1085;
  writeI2C();
  //  showI2C();
}



void showBand(void)
// Show the current band and the frequencies allowed.
{
  putstring("  ");
  putuint16(band);
  putstring(" meters, ");
  putuint32(lowFrequency / 1000);
  putstring(" kHz to ");
  putuint32(highFrequency / 1000);
  putstring(" kHz by ");
  putuint32(freqInc);
  putstring(" Hz\n");
}



// Use integer accumulator for bestDiff.
uint16_t searchBandTable(void)
// Search the band table for the nearest frequency to that requested. Requested frequency is in the
// integer accumulator IACC. Returns the best match.
{
  uint16_t i;
  __xdata int32_t  bestdiff;
  __xdata int32_t  diff;
  __xdata uint16_t besti = 0;
  __xdata int32_t  f;

  bestdiff = 2147483647L;

  // Search the available table. Some frequencies can have more than 256 entries hence 16 bit index.
  for (i = 0; i < (sizeof(bandTable) / sizeof(ftable)); ++i)
  {
    f = bandTable[i].frequency;
    if (f == IACC.i) return i;
    diff = abs(f - IACC.i);
    if (diff < bestdiff)
    {
      bestdiff = diff;
      besti = i;
    }
  }

  return besti;
}



void writeI2C16(uint16_t v)
//   The DS1085 bytes are backward from the 8051. Here we reverse the byte order of the argument.
{
  LED0 = 1;
  STR[0] = v >> 8;
  STR[1] = v;
  I2CWRITE = 1;
  I2CCNT = 2;
  I2CBUFFER = STR;
  I2CADDR = DS1085;
  STO = 0;
  STA = 1;
   while (I2CSTATUS == I2C_BUSY);         // Wait for completion.
  if (I2CSTATUS != I2C_IDLE)
  {
    putstring("Bad write I2CSTATUS ");
    putbyte(I2CSTATUS);
    terpri();
  }
  LED0 = 0;
}



void runFrequency(void)
//     This is from a program call (see setFrequency below for command line). Syntax is:
//        frequency <expression>
// The expression should result in the freuency in hertz. Look up the frequency in the current
// table if it's in range and then set the synthesizer appropriately.
{
  uint16_t bestf;
  uint8_t  off;
  

  // Fetch the frequency.
  incPC();
  parseExpression();
  if (errorThrown) return;

  // If we're skipping this then just return.
  if (!doit) return;

  // Check the result, must be an integer.
  DREG = popST();
  if (tag() != DESC_INT)
  {
    putstring("FREQUENCY must integer\n");
    errorThrown = 1;
    return;
  }

  // Get the integer value.
  getIACC();

  // See if we're in the band. Punt if it doesn't work.
  if (IACC.i < lowFrequency || IACC.i >= highFrequency)
  {
    putuint32(IACC.i);
    putstring(" is not in the band ");
    putuint32(lowFrequency);
    putstring(" - ");
    putuint32(highFrequency);
    terpri();
    errorThrown = 1;
    return;
  }

  // Find the best approximation. If not an exact match, then show how close
  // we came.
  bestf = searchBandTable();
  if (bandTable[bestf].frequency != IACC.i)
  {
    putstring("  For ");
    putuint32(IACC.i);
    putstring(" best setting is ");
    putuint32(bandTable[bestf].frequency);
    terpri();
  }

  // putstring("  Frequency = "); putuint32(bandTable[bestf].frequency); putch('\n');

  // If the device was powered down, this should fire it back up.
  CTRL0 = 0;
  
  // Fire up the bugger. Here's the DAC value which is already shifted.
  // putstring("  DAC = "); puthex16(bandTable[bestf].dac); putch('\n');
  // Note: writeI2C16 sets the DS1085 address once so we don't do it again.
  waitMS(DS1085WAIT);
  I2CREGISTER = ds1085DAC_WORD;
  writeI2C16(bandTable[bestf].dac);
  
  // The offset.
  off = (uint8_t) (bandTable[bestf].offset + offset);
  waitMS(DS1085WAIT);
  I2CBUFFER = &off;
  I2CCNT = 1;
  I2CREGISTER = ds1085OFFSET;
  writeI2C();

  // The MUX register.
  IACC.i16.i16a = 0x1800 + (((uint16_t) bandTable[bestf].prescale) << 7);
  waitMS(DS1085WAIT);
  I2CREGISTER = ds1085MUX_WORD;
  writeI2C16(IACC.i16.i16a);

  // The divisor.
  I2CREGISTER = ds1085DIV_WORD;
  waitMS(DS1085WAIT);
  writeI2C16(bandTable[bestf].divisor);

}



void setFrequency(void)
//    The command line version. Syntax is:
//         f frequency-in-hertz-integer
//
{
  uint16_t bestf;
  uint8_t  off;

  // Must be a number following.
  ntok();
  if (ttype != LEXNUM)
  {
    putstring("Missing value\n");
    return;
  }

  // Verify in range.
  if (N < lowFrequency || N > highFrequency)
  {
    putstring("Out of range\n");
    return;
  }

  // Turn on output if off. Search for the best match.
  CTRL0 = 0;
  IACC.i = N;
  bestf = searchBandTable();
  if (bandTable[bestf].frequency != IACC.i)
  {
    putstring("  For ");
    putuint32(N);
    putstring(" best setting is ");
    putuint32(bandTable[bestf].frequency);
    terpri();
  }

  //  putstring("  Frequency = "); putuint32(bandTable[bestf].frequency); putch('\n');

  // Fire up the bugger. Here's the DAC value already shifted by the .h creator.
  // putstring("  DAC = "); puthex16(bandTable[bestf].dac); putch('\n');
  //   waitMS(DS1085WAIT);
  // writeI2C16 sets I2CADDR so we don't have to do it again.
  I2CREGISTER = ds1085DAC_WORD;
  writeI2C16(bandTable[bestf].dac);
  
  // The offset.
  off = (uint8_t) (bandTable[bestf].offset + offset);
  //putstring("  OFFSET = "); puthexbyte(off); putch('\n');
  waitMS(DS1085WAIT);
  I2CBUFFER = &off;
  I2CCNT = 1;
  I2CREGISTER = ds1085OFFSET;
  writeI2C();

  // The MUX - for output 1.
  IACC.i16.i16a = 0x1800 + (((uint16_t) bandTable[bestf].prescale) << 7);
  //putstring("  MUX = "); puthex16(IACC.i16.i16a); putch('\n');
  waitMS(DS1085WAIT);
  I2CREGISTER = ds1085MUX_WORD;
  writeI2C16(IACC.i16.i16a);

  // The divisor.
  //putstring("  Divisor = "); puthex16(bandTable[bestf].divisor);
  waitMS(DS1085WAIT);
  I2CREGISTER = ds1085DIV_WORD;
  writeI2C16(bandTable[bestf].divisor);

  // Temporary for frequency testing.
  //  MORSE = 1;
  //putstring("\n\n");
}



void setDAC(void)
// Test case. A debug routine not currently in use.
{
  uint16_t v;
  ntok();
  if (ttype != LEXNUM)
  {
    putstring("Missing value\n");
    return;
  }
  v = ((uint16_t) N) << 6;
  putstring("  writing "); puthex16(v); putch('\n');
  I2CREGISTER = ds1085DAC_WORD;
  writeI2C16(v);
  //  showI2C();

  //  for (v = 0; v < 65530; ++v);
  
  I2CWRITE = 0;
  I2CCNT = 2;
  v = 0xDEAD;
  I2CBUFFER = (uint8_t*) &IACC;
  I2CREGISTER = ds1085DAC_WORD;
  readI2C();
  v = (((uint16_t) IACC.i8.b8a) << 8) + ((uint16_t) IACC.i8.b8b);
  putstring("  DAC "); puthex16(v); putch('\n');
}



void powerDownDS1085(void)
// Power the thing down. This is the result of executing a SLEEP statement.
{

  I2CWRITE = 1;
  I2CCNT = 2;
  I2CREGISTER = ds1085MUX_WORD;
  IACC.i16.i16a = 0x3800;
  I2CBUFFER = (uint8_t*) &IACC.i16.i16a;
  I2CADDR = DS1085;
  writeI2C();
  CTRL0 = 1;
}


void incoff(void)
// A debug routine to increment the current offset. Not used currently.
{
  ++offset;
  putstring("  offset="); putbyte(offset); terpri();
}



void decoff(void)
// A debug routine to decrement the current offset. Not used currently.
{
  --offset;
  putstring("  offset="); putbyte(offset); terpri();
}



void readDS1085(void)
// A debug routine to dump the DS1085 registers.
{
  uint16_t v;

  I2CWRITE = 0;
  I2CCNT = 2;
  I2CBUFFER = (uint8_t*) &v;
  I2CREGISTER = ds1085MUX_WORD;
  readI2C();
  v = (v >> 8) | (v << 8);
  putstring("  MUX "); puthex16(v); terpri();
 
  I2CWRITE = 0;
  I2CCNT = 2;
  I2CBUFFER = (uint8_t*) &v;
  I2CREGISTER = ds1085DAC_WORD;
  readI2C();
  v = (v >> 8) | (v << 8);
  putstring("  DAC "); puthex16(v); terpri();

  I2CWRITE = 0;
  I2CCNT = 2;
  I2CBUFFER = (uint8_t*) &v;
  I2CREGISTER = ds1085RANGE_REG;
  readI2C();
  v = (v >> 8) | (v << 8);
  putstring("  Range "); puthex16(v);  terpri();

  I2CWRITE = 0;
  I2CCNT = 2;
  I2CBUFFER = (uint8_t*) &v;
  I2CREGISTER = ds1085DIV_WORD;
  readI2C();
  v = (v >> 8) | (v << 8);
  putstring("  Div "); puthex16(v); terpri();

  // Addr.
  I2CREGISTER = ds1085ADDR_BYTE;
  I2CWRITE = 0;
  I2CCNT = 1;
  I2CBUFFER = (uint8_t*) &IACC.i8.b8a;
  readI2C();
  putstring("  ADDR 0x"); puthexbyte(IACC.i8.b8a); terpri();
  

}


// EOF
