/* i2c.c - interrupt driven communication.
 *  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 "ds1085.h"
#include "serial.h"

#ifdef I2CTRACE
// Trace code. If you need this you have to reduce the size of something else.
// SMB0CN
// SMB0CF
// SMB0DAT
// I2CSTATUS
  #define TSIZE 99
  __xdata uint8_t TI2C[TSIZE];
  uint8_t TIN;
#endif

// Usually an address in D-space.
extern uint16_t           DREG;

// Set when there's an error.
extern __bit              errorThrown;

// D-space handled by dmalloc.
extern __xdata uint8_t    D[];

// A useful 4 bytes.
extern __xdata floatOrInt IACC;

// Globals for I2C operation. Send or receive 2 bytes.
volatile __xdata uint8_t I2CSTATUS;
volatile __xdata uint8_t I2CSTATE;

// The 7 bit address, already shifted.
__xdata uint8_t I2CADDR;
__xdata uint8_t I2CREGISTER;

// Number of bytes to read or write.
__xdata uint8_t I2CCNT;

// Pointer to buffer to write or read.
uint8_t *I2CBUFFER;

// Set to 1 when we're writing, 0 for reading.
__bit I2CWRITE;

#ifdef I2CTRACE
  static void putdec3(uint8_t v)
  {
    putch('0' + v / 100);
    v = v % 100;
    putch('0' + v / 10);
    v = v % 10;
    putch('0' + v);
    putch(' ');
  }
  
  
  void showI2C(void)
  // Dump the I2C trace buffer.
  {
    uint8_t i;
    putstring("  0CN  0CF  DAT STAT\n");
  
    for (i = 0; i < TIN; )
    {
      putstring("  0x");
      puthexbyte(TI2C[i++]);          // SMB0CN
      putstring(" 0x");
      puthexbyte(TI2C[i++]);          // SMB0CF
      putstring(" 0x");
      puthexbyte(TI2C[i++]);          // SMB0DAT
      putch(' ');
      putdec3(TI2C[i++]);             // I2CSTATUS
      terpri();
    };
    TIN = 0;
  }
#endif



void initI2C(void)
// Note that init51.c has already initialized the cross bar for I2C. We run it
// at 100k since we're not doing much and don't have much control over the bus capacitance.
{
  // Turn off, mode0 16 bit with reload. Clock is SYSCLOCK / 12.
  TMR2CN = 0;

  // Should give us 100khtz.
  TMR2 = TMR2RL = 65536 - 40;
  TR2 = 1;

  // Timer 2 16 bit overflow at 100 khtz, and turn it on. Timer not interrupting though.
  // Bus free timeout enabled.
  SMB0CF = 0xC6;

  // Indicate idle.
  I2CSTATUS = I2C_IDLE;
  I2CSTATE = I2CSTATE_IDLE;

  // Enable I2C interrupts (ESMB0).
  EIE1 |= 0x01;

  #ifdef I2CTRACE
    // Save initial register values, timer.
    TIN = 0;
    TI2C[TIN++] = SMB0CN;
    TI2C[TIN++] = SMB0CF;
    TI2C[TIN++] = TMR2CN;
    TI2C[TIN++] = EIE1;
    showI2C();
  #endif

  // Busy check. If this is on, then we have a hardware error.
  if (SMB0CF & 0x20)
  {
    LED0 = 1;
    putstring("I2C busy\n");
    for(;;);
  }

  // SDA should not be held low, if so, we have a hardware error.
  if (!P0_6)
  {
    LED0 = 1;
    putstring("SDA being held low\n");
    for(;;);
  }
}




/*
 * ISS_SMBUS(...) -
 *     Interrupt service for I2C mass storage.
 * Parameters: none
 * External References:
 *   I2CSTATUS: Command of what to do and what's the result.
 *   I2CSLAVEADDR: The slave address we're sending to.
 *   I2CBUF: 128 byte sector buffer.
 *   I2CPTR: The pointer into this buffer.
 *   I2CSTATE: The system state.
 * Returned values: none, better not be.
 * Errors detected:
 *   Bad status, bad slave address.
 */
void ISS_SMBUS0(void) __interrupt 7
{
  #ifdef I2CTRACE
    // Trace on entrance to interrupt.
    TI2C[TIN++] = SMB0CN;
    TI2C[TIN++] = SMB0CF;
    TI2C[TIN++] = SMB0DAT;
    TI2C[TIN++] = I2CSTATE;
  #endif

  switch (SMB0CN >> 4) {
    // Slave statii are in error.
  case 0b0100:
  case 0b0101:
  case 0b0010:
  case 0b0001:
  case 0b0000:
    I2CSTATUS = I2C_STATUSERROR;
    break;

  // A master START was generated. Load address and clear STO/STA.
  case 0b1110:
    if (I2CSTATE == I2CSTATE_SWITCHREAD)
    {
      SMB0DAT = I2CADDR | 1;
      I2CSTATE = I2CSTATE_READBYTE;
      STA = 0;
    }
    else
    {
      SMB0DAT = I2CADDR & 0xFE;
      STA = STO = 0;
      I2CSTATE = I2CSTATE_REGISTER;
      I2CSTATUS = I2C_BUSY;
    }
    break;

  case 0b1010:
    SMB0DAT = I2CADDR | 1;
    I2CSTATE = I2CSTATE_READBYTE;
    STA = 0;
    break;

  // Data or address byte sent.
  case 0b1100:
    if (!ACK) I2CSTATUS = I2C_NOACK;

    else switch (I2CSTATE) {

    // First byte. This is always the register we're interested in.
    // byte.
    case I2CSTATE_REGISTER:
      SMB0DAT = I2CREGISTER;
      I2CSTATE = I2CWRITE?I2CSTATE_WRITEBYTE:I2CSTATE_RESTART;
      break;

    // Write a byte..
    case I2CSTATE_WRITEBYTE:
      SMB0DAT = *I2CBUFFER++;
      if (--I2CCNT == 0) I2CSTATE = I2CSTATE_STOPWRITE;
      break;

    case I2CSTATE_STOPWRITE:
      STO = 1;
      I2CSTATE = I2CSTATE_IDLE;
      I2CSTATUS = I2C_IDLE;
      break;
        
    case I2CSTATE_RESTART:
      STA = 1;                        // A repeated start for read.
      I2CSTATE = I2CSTATE_SWITCHREAD;
      break;

    }
    break;

  case 0b1000:
    *I2CBUFFER++ = SMB0DAT;
    if (--I2CCNT != 0) ACK = 1;
    else
    {
      ACK = 0;
      STO = 1;
      I2CSTATUS = I2C_IDLE;
      I2CSTATE = I2CSTATE_IDLE;
    }
    break;
    
  default:
    I2CSTATE = I2CSTATE_IDLE;
    I2CSTATUS = I2C_BADBITS;
    break;
  }

  SI = 0;
}





void writeI2C(void)
/*     Caller should set:
 *   I2CWRITE: Set to 1 to indicate write.
 *   I2CCNT: Number of bytes to write.
 *   I2CBUFFER: Address of  write buffer.
 *   I2CREGISTER: client register to write to.
 */
{
  LED0 = 1;
  I2CSTATUS = I2C_BUSY;
  STO = 0;
  STA = 1;                               // Start the transfer.
  while (I2CSTATUS == I2C_BUSY);         // Wait for completion.
  if (I2CSTATUS != I2C_IDLE)
  {
    putstring("Bad write I2CSTATUS ");
    putbyte(I2CSTATUS);
    terpri();
  }
  LED0 = 0;
  #ifdef I2CTRACE
    putstring("I2C write\n");
    showI2C();
  #endif
}



uint8_t readI2C(void)
/*     Caller should set:
 *   I2CWRITE: Set to 0 to indicate read.
 *   I2CCNT: Number of bytes to read
 *   I2CBUFFER: Address of read buffer.
 *   I2CREGISTER: client register to start reading from.
 */
{
  uint32_t timeout;
  LED0 = 1;
  STO = 1;

  //  putstring("Read IE="); puthexbyte(IE); putstring(" EIE1="); puthexbyte(EIE1);
  //  putstring(" TMR2CN="); puthexbyte(TMR2CN);
  //  putstring(" SMB0CF="); puthexbyte(SMB0CF);
  //  putstring(" P0="); puthexbyte(P0);
  //  terpri();
  
  I2CSTATUS = I2C_BUSY;
  STO = 0;
  STA = 1;
  for (timeout = 0; timeout < 1000000L && I2CSTATUS == I2C_BUSY; ++timeout);
  if (timeout == 1000000L) putstring("timeout\n");
  #ifdef I2CTRACE
    putstring("I2C Read bytes "); putbyte(TIN); terpri();
    showI2C();
  #endif
    
  LED0 = 0;
  if (I2CSTATUS !=  I2C_IDLE)
  {
    putstring("Bad read I2CSTATUS ");
    putbyte(I2CSTATUS);
    putstring(" 0x");
    puthexbyte(SMB0CN);
    terpri();
    return 1;
  }
  return 0;
}

  

void runWriteI2C(void)
//     We only come here when doit is true. This is called from runWrite (files.c).
//        WRITE <fh>, <port>, <bytevector>;
{
  LED0 = 1;
  if (getPByte() == COMMA) incPC();

  // This should be the port number.
  parseExpression();
  DREG = popST();
  if (tag() != DESC_INT)
  {
    putstring("I2C port # bad\n");
    errorThrown = 1;
    return;
  }
  getIACC();
  I2CREGISTER = IACC.i8.b8a;

  if (getPByte() == COMMA) incPC();
  parseExpression();
  DREG = popST();
  if (tag() != DESC_BYTEVECTOR)
  {
    putstring("WRITE I2C not ByteVector\n");
    errorThrown = 1;
    return;
  }

  /*  LED0 = 1;
  SMB0CN = 0xE0;
  while (!SI);
  STA = 0;

  sendI2CByte(i2caddr);
  i2caddr = D[DREG + 3];
  for (IACC.i8.b8a = 0; IACC.i8.b8a < i2caddr; ++IACC.i8.b8a)
    sendI2CByte(D[DREG + 4 + IACC.i8.b8a]);

  STO = 1;
  while (!SI);
  SI = 0;
  LED0 = 0;
  */
}
    
  
  
// EOF
