//---------------------------------------------------------------------------
// Copyright (C) 2007 Dallas Semiconductor Corporation, All Rights Reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL DALLAS SEMICONDUCTOR BE LIABLE FOR ANY CLAIM, DAMAGES
// OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
// Except as contained in this notice, the name of Dallas Semiconductor
// shall not be used except as stated in the Dallas Semiconductor
// Branding Policy.
//---------------------------------------------------------------------------
//
//  winusbds2490.c - DS2490 utility functions.
//                   (Requires Microsoft's WinUSB to be installed)
//
//  Version: 
//

#include "ownet.h"
#include "winusbds2490.h"

// global DS2490 state
SMALLINT USBLevel[MAX_PORTNUM]; 
SMALLINT USBSpeed[MAX_PORTNUM]; 
SMALLINT USBVersion[MAX_PORTNUM]; 
SMALLINT USBVpp[MAX_PORTNUM]; 

//---------------------------------------------------------------------------
// Attempt to resync and detect a DS2490
//
// 'portnum'    - number 0 to MAX_PORTNUM-1.  This number was provided to
//                OpenCOM to indicate the port number.
//
// Returns:  TRUE  - DS2490 detected successfully
//           FALSE - Could not detect DS2490
//
SMALLINT DS2490Detect(WINUSB_INTERFACE_HANDLE hDevice)
{
   SMALLINT present,vpp;

   WINUSB_SETUP_PACKET setup;
   ULONG nOutput = 0;

   // reset the DS2490
   DS2490Reset(hDevice);

   // set the strong pullup duration to infinite
   setup.RequestType = 0x40;
   setup.Request = COMM_CMD;
   setup.Value = COMM_SET_DURATION | COMM_IM;
   setup.Index = 0x0000;
   setup.Length = 0;

   // call the driver
   WinUsb_ControlTransfer(hDevice,
                          setup,
                          NULL,
                          0,
                          &nOutput,
                          NULL);

   // set the 12V pullup duration to 512us
   setup.RequestType = 0x40;
   setup.Request = COMM_CMD;
   setup.Value = COMM_SET_DURATION | COMM_IM | COMM_TYPE;
   setup.Index = 0x0040;
   setup.Length = 0;

   // call the driver
   WinUsb_ControlTransfer(hDevice,
                          setup,
                          NULL,
                          0,
                          &nOutput,
                          NULL);

   // disable strong pullup, but leave program pulse enabled (faster)
   setup.RequestType = 0x40;
   setup.Request = MODE_CMD;
   setup.Value = MOD_PULSE_EN;
   setup.Index = ENABLEPULSE_PRGE;
   setup.Length = 0;

   // call the driver
   WinUsb_ControlTransfer(hDevice,
                          setup,
                          NULL,
                          0,
                          &nOutput,
                          NULL);

   // return result of short check
   return DS2490ShortCheck(hDevice, &present, &vpp);
}


//---------------------------------------------------------------------------
// Check to see if there is a short on the 1-Wire bus. Used to stop 
// communication with the DS2490 while the short is in effect to not
// overrun the buffers.
//
// '*present' - flag set (1) if device presence detected
// '*vpp' - flag set (1) if Vpp programming voltage detected
//
// Returns:  TRUE  - DS2490 1-Wire is NOT shorted
//           FALSE - Could not detect DS2490 or 1-Wire shorted
//
SMALLINT DS2490ShortCheck(WINUSB_INTERFACE_HANDLE hDevice, SMALLINT *present, SMALLINT *vpp)
{
   STATUS_PACKET status;
   BYTE nResultRegisters;
   BYTE i;

   // get the result registers (if any)
   if (!DS2490GetStatus(hDevice, &status, &nResultRegisters))
      return FALSE;

   // get vpp present flag
   *vpp = ((status.StatusFlags & STATUSFLAGS_12VP) != 0);

   //	Check for short
   if(status.CommBufferStatus != 0)
   {	
      return FALSE;
   }
   else
   {  
      // check for short
	   for (i = 0; i < nResultRegisters; i++)
	   {	
         // check for SH bit (0x02), ignore 0xA5
         if (status.CommResultCodes[i] & COMMCMDERRORRESULT_SH)
         {	
            // short detected
            return FALSE; 
         }
      }
   }

   // check for No 1-Wire device condition
   *present = TRUE;
   // loop through result registers
   for (i = 0; i < nResultRegisters; i++)
   {   
      // only check for error conditions when the condition is not a ONEWIREDEVICEDETECT
      if (status.CommResultCodes[i] != ONEWIREDEVICEDETECT)
      {   
         // check for NRS bit (0x01)
         if (status.CommResultCodes[i] & COMMCMDERRORRESULT_NRS)
         {   
            // empty bus detected
            *present = FALSE;
         }
      }
   }

   return TRUE;
}


//---------------------------------------------------------------------------
// Stop any on-going pulses
//
// Returns:  TRUE  - pulse stopped
//           FALSE - Could not stop pulse
//
SMALLINT DS2490HaltPulse(WINUSB_INTERFACE_HANDLE hDevice)
{
   STATUS_PACKET status;
   BYTE nResultRegisters;
   WINUSB_SETUP_PACKET setup;
   ULONG nOutput = 0;
   long limit;

   // set a time limit
   limit = msGettick() + 300;
   // loop until confirm pulse has ended or timeout
   do
   {
      // HalExecWhenIdle, ResumeExecution to stop an infinite pulse
      
      // HalExecWhenIdle
      setup.RequestType = 0x40;
      setup.Request = CONTROL_CMD;
      setup.Value = CTL_HALT_EXE_IDLE;
      setup.Index = 0x00;
      setup.Length = 0x00;

      // call the driver
      if (!WinUsb_ControlTransfer(hDevice,
                          setup,
                          NULL,
                          0,
                          &nOutput,
                          NULL))
      {
         // failure
         break;
      }

      // ResumeExecution   
      setup.RequestType = 0x40;
      setup.Request = CONTROL_CMD;
      setup.Value = CTL_RESUME_EXE;
      setup.Index = 0x00;
      setup.Length = 0x00;

      // call the driver
      if (!WinUsb_ControlTransfer(hDevice,
                          setup,
                          NULL,
                          0,
                          &nOutput,
                          NULL))
      {
         // failure
         break;
      }

      // read the status to see if the pulse has been stopped
      if (!DS2490GetStatus(hDevice, &status, &nResultRegisters))
      {
         // failure
         break;
      }
      else
      {
         // check the SPU flag
         if ((status.StatusFlags & STATUSFLAGS_SPUA) == 0)
         {
            // success
            // disable both pulse types
            setup.RequestType = 0x40;
            setup.Request = MODE_CMD;
            setup.Value = MOD_PULSE_EN;
            setup.Index = 0;
            setup.Length = 0x00;

            // call the driver
            WinUsb_ControlTransfer(hDevice,
                                   setup,
                                   NULL,
                                   0,
                                   &nOutput,
                                   NULL);

            return TRUE;
         }
      }
   }
   while (limit > msGettick());

   return FALSE;
}

//---------------------------------------------------------------------------
// Description: Gets the status of the DS2490 device
// Input:       hDevice - the handle to the DS2490 device
//              pStatus - the Status Packet to be filled with data
//              pResultSize - the number of result register codes returned
//                can be NULL
// Returns:     FALSE on failure, TRUE on success
// Error Codes: DS2490COMM_ERR_USBDEVICE
SMALLINT DS2490GetStatus(WINUSB_INTERFACE_HANDLE hDevice, STATUS_PACKET *status, BYTE *pResultSize)
{
   // buffer to retrieve status
   SMALLINT i = 0;
   WINUSB_PIPE_INFORMATION pipe;
   ULONG nOutput = sizeof(STATUS_PACKET);
   BYTE *bufOutput = (BYTE *)status;
   USB_INTERFACE_DESCRIPTOR iface;

   if (WinUsb_QueryInterfaceSettings(hDevice, 0, &iface) == FALSE) 
   {
      // Error
      OWERROR(OWERROR_ADAPTER_ERROR);
      return FALSE;
   }

   // get a winusb pipe 0 (which should be equivalent to DS2490_EP1), so we can read through it.
   if (!WinUsb_QueryPipe (hDevice, 0, 0, &pipe))
   {
      // Error
      OWERROR(OWERROR_ADAPTER_ERROR);
      return FALSE;
   }
   // get a winusb pipe and read the status from it.
   if (!WinUsb_ReadPipe(hDevice, pipe.PipeId, bufOutput, sizeof(STATUS_PACKET), &nOutput, NULL))
      return FALSE;

   // any bytes returned after the 16th byte are place in the CommResultCodes field
   if (pResultSize != NULL)
   {
      *pResultSize = (BYTE) nOutput - 16;
   }

   return TRUE;
}


//---------------------------------------------------------------------------
// Description: Performs a hardware reset of the DS2490 equivalent to a 
//              power-on reset
// Input:       hDevice - the handle to the DS2490 device
// Returns:     FALSE on failure, TRUE on success
// Error Codes: DS2490COMM_ERR_USBDEVICE
//
SMALLINT DS2490Reset(WINUSB_INTERFACE_HANDLE hDevice)
{
   WINUSB_SETUP_PACKET setup;
   ULONG nOutput = 0;
   BOOL nStatus = FALSE;

   // setup for reset
   setup.RequestType = 0x40;
   setup.Request = CONTROL_CMD;
   setup.Value = CTL_RESET_DEVICE;
   setup.Index = 0x00;
   setup.Length = 0x00;

   // call the driver
   nStatus = WinUsb_ControlTransfer(hDevice,
                                    setup,
                                    NULL,
                                    0,
                                    &nOutput,
                                    NULL);
   return nStatus;
}


//---------------------------------------------------------------------------
// Description: Reads data from EP3
// Input:       hDevice - the handle to the DS2490 device
//              buffer - the size must be >= nBytes
//              pnBytes - the number of bytes to read from the device
// Returns:     FALSE on failure, TRUE on success
//
SMALLINT DS2490Read(WINUSB_INTERFACE_HANDLE hDevice, BYTE *buffer, WORD *pnBytes)
{
   // Synchronous read:
	ULONG	nBytes = *pnBytes;
   WINUSB_PIPE_INFORMATION pipe;
   UCHAR timeout = 15;
   BOOL nStatus = FALSE;
   int i = 0;

   // get a winusb pipe 2 (which should be equivalent to DS2490_EP3), so we can read through it.
   if (!WinUsb_QueryPipe (hDevice, 0, 2, &pipe))
   {
      // Error
      OWERROR(OWERROR_ADAPTER_ERROR);
      return FALSE;
   }

   // perform bulk read on pipe (EP3)
   if (!WinUsb_ReadPipe(hDevice, pipe.PipeId, &buffer[0], *pnBytes, &nBytes, NULL))
   {
      OWERROR(OWERROR_ADAPTER_ERROR);
		return FALSE;
   }

	*pnBytes = (WORD)nBytes;
   return TRUE;
}

//---------------------------------------------------------------------------
// Description: Writes data to EP2
// Input:       hDevice - the handle to the DS2490 device
//              buffer - the size must be >= nBytes
//              pnBytes - the number of bytes to write to the device
// Returns:     FALSE on failure, TRUE on success
//
SMALLINT DS2490Write(WINUSB_INTERFACE_HANDLE hDevice, BYTE *buffer, WORD *pnBytes)
{
   // Synchronous write:
   // assume enough room for write
	ULONG	nBytes = *pnBytes;
   WINUSB_PIPE_INFORMATION pipe;
   ULONG timeout = 300;
   BOOL nStatus = FALSE;
   int i = 0;
   DWORD lastError = 0;

   // Pipe info:  Pipe ID = 0x02  (write pipe)
   // get pipe 1 (which should be equivalent to DS2490_EP2), so we can write to it.
   if (!WinUsb_QueryPipe (hDevice, 0, 1, &pipe))
   {
      // Error
      OWERROR(OWERROR_ADAPTER_ERROR);
      return FALSE;
   }

   // perform bulk write on pipe (EP2)
   if (!WinUsb_WritePipe(hDevice, pipe.PipeId, buffer, *pnBytes, &nBytes, NULL))
   {
      OWERROR(OWERROR_ADAPTER_ERROR);
		return FALSE;
   }

   *pnBytes = (WORD)nBytes;
   return TRUE;
}

