/***************************************************************************
 *   lm75 Version 0.1                                                      *
 *   Copyright (C) 2011 by Tobias Müller                                   *
 *   Tobias_Mueller@twam.info                                              *
 *                                                                         *
 *   based on Examples from FTDI (http://www.ftdichip.com/Support/         *
 *   Documents/AppNotes/AN_113_FTDI_Hi_Speed_USB_To_I2C_Example.pdf)       *
 *                                                                         *
 *   Command descriptions can be found on http://www.ftdichip.com/Support/ *
 *   Documents/AppNotes/AN_108_Command_Processor_for_MPSSE_and_MCU_Host_   *
 *   Bus_Emulation_Modes.pdf                                               *
 *                                                                         *
 *   To build use the following gcc statement                              *
 *   gcc -o lm75 lm75.c -lftd2xx                                           *
 *                                                                         *
 *   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 2 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, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <ftd2xx.h>

const char MSB_FALLING_EDGE_CLOCK_BYTE_IN = 0x20;
const char MSB_FALLING_EDGE_CLOCK_BYTE_OUT = 0x11;
const char MSB_FALLING_EDGE_CLOCK_BIT_OUT = 0x13;
const char MSB_RISING_EDGE_CLOCK_BIT_IN = 0x22;

#define MAX_DEVICES	5

const char LM75 = 0x9E;

FT_STATUS ftStatus;
FT_HANDLE ftHandle;

char inputBuffer[1024];
char outputBuffer[1024];
unsigned int outputSize;
unsigned int outputSent;
unsigned int inputSize;
unsigned int inputRead;

int scan_devices() {
	char* pcBufLD[MAX_DEVICES + 1];
	char cBufLD[MAX_DEVICES][64];
	int iNumDevs = 0;
	int i;

	for(i = 0; i < MAX_DEVICES; i++) {
		pcBufLD[i] = cBufLD[i];
	}
	pcBufLD[MAX_DEVICES] = NULL;

	FT_STATUS ftStatus = FT_ListDevices(pcBufLD, &iNumDevs, FT_LIST_ALL | FT_OPEN_BY_SERIAL_NUMBER);

	if (ftStatus != FT_OK) {
		fprintf(stderr, "Error: FT_ListDevices(%d)\n", ftStatus);
		return 1;
	}

	for (i = 0; ( (i <MAX_DEVICES) && (i < iNumDevs) ); i++) {
		printf("Device %d Serial Number - %s\n", i, cBufLD[i]);
	}

	return 0;
}

void i2c_start() {
	int i;

	for (i = 0; i < 4; ++i) {
		// SDA high, SCL high
		outputBuffer[outputSize++] = 0x80;
		outputBuffer[outputSize++] = 0x03;
		outputBuffer[outputSize++] = 0x03;
	}

	for (i = 0; i < 4; ++i) {
		// SDA low, SCL high
		outputBuffer[outputSize++] = 0x80;
		outputBuffer[outputSize++] = 0x01;
		outputBuffer[outputSize++] = 0x03;
	}

	// SDA low, SCL low
	outputBuffer[outputSize++] = 0x80;
	outputBuffer[outputSize++] = 0x00;
	outputBuffer[outputSize++] = 0x03;
}

void i2c_stop() {
	int i;

	for (i = 0; i < 4; ++i) {
		// SDA low, SCL high
		outputBuffer[outputSize++] = 0x80;
		outputBuffer[outputSize++] = 0x01;
		outputBuffer[outputSize++] = 0x03;
	}

	for (i = 0; i < 4; ++i) {
		// SDA high, SCL high
		outputBuffer[outputSize++] = 0x80;
		outputBuffer[outputSize++] = 0x03;
		outputBuffer[outputSize++] = 0x03;
	}

	// SDA tristate, SCL tristate
	outputBuffer[outputSize++] = 0x80;
	outputBuffer[outputSize++] = 0x00;
	outputBuffer[outputSize++] = 0x00;
}

int i2c_send_byte_and_check_ack(char data) {
	// clock data byte on clock edge MSB first
	outputBuffer[outputSize++] = MSB_FALLING_EDGE_CLOCK_BYTE_OUT;
	// data length of 0x0000 means 1 byte
	outputBuffer[outputSize++] = 0x00;
	outputBuffer[outputSize++] = 0x00;
	outputBuffer[outputSize++] = data;

	// SDA tristate, SCL low
	outputBuffer[outputSize++] = 0x80;
	outputBuffer[outputSize++] = 0x00;
	outputBuffer[outputSize++] = 0x01;

	outputBuffer[outputSize++] = MSB_RISING_EDGE_CLOCK_BIT_IN;
	// length of 0x00 means scan 1 bit
	outputBuffer[outputSize++] = 0x00;
	outputBuffer[outputSize++] = 0x87;

	ftStatus = FT_Write(ftHandle, outputBuffer, outputSize, &outputSent);
	outputSize = 0;

	ftStatus = FT_Read(ftHandle, inputBuffer, 1, &inputRead);

	if ((ftStatus != FT_OK) || (inputRead == 0)) {
		return -1;
	} else if (((inputBuffer[0] & 0x01) != 0x00)) {
		return -1;
	}

	// SDA high, SCL low
	outputBuffer[outputSize++] = 0x80;
	outputBuffer[outputSize++] = 0x02;
	outputBuffer[outputSize++] = 0x03;

	return 0;
}

int main(int argc, char** argv) {
	int i;

	// print scan if no parameter is given
	if (argc <= 1) {
		scan_devices();
		return EXIT_SUCCESS;
	}

	// 100 kHz clock
	unsigned short dwClockDivisor = 0x012B;

	// setup
	if((ftStatus = FT_OpenEx(argv[1], FT_OPEN_BY_SERIAL_NUMBER, &ftHandle)) != FT_OK){
		/*
			This can fail if the VCP driver is loaded!

			Linux: use lsmod to check this and rmmod ftdi_sio to remove also rmmod usbserial
			OS X: sudo kextunload /System/Library/Extensions/FTDIUSBSerialDriver.kext
	 	*/
		printf("Error FT_OpenEx(%d)\n", ftStatus);
		return 1;
	}

	// reset device
	ftStatus = FT_ResetDevice(ftHandle);

	// Purge USB receive buffer first by reading out all old data from FT2232H receive buffer
	ftStatus |= FT_GetQueueStatus(ftHandle, &inputSize);
	if ((ftStatus == FT_OK) && (inputSize > 0))
		FT_Read(ftHandle, &inputBuffer, inputSize, &inputRead);

	//Set USB request transfer size
	ftStatus |= FT_SetUSBParameters(ftHandle, 65536, 65535);
	//Disable event and error characters
	ftStatus |= FT_SetChars(ftHandle, 0, 0, 0, 0);
	//Sets the read and write timeouts in milliseconds for the FT2232H
	ftStatus |= FT_SetTimeouts(ftHandle, 0, 5000);
	//Set the latency timer
	ftStatus |= FT_SetLatencyTimer(ftHandle, 16);
	//Reset controller
	ftStatus |= FT_SetBitMode(ftHandle, 0x0, 0x00);

	// enable MPSEE mode
	ftStatus |= FT_SetBitMode(ftHandle, 0x0, 0x02);

	if (ftStatus != FT_OK) {
		fprintf(stderr, "Error occured: %u\n", ftStatus);
	}

	// Disables the clk divide by 5 to allow for a 60MHz master clock.
	outputBuffer[0] = '\x8A';
	// Disable adaptive clocking
	outputBuffer[1] = '\x97';
	// Enables 3 phase data clocking. Used by I2C interfaces to allow data on both clock edges.
	outputBuffer[2] = '\x8C';
	// sent of commands
	ftStatus = FT_Write(ftHandle, outputBuffer, 3, &outputSent);

	/*
		ADBUS0 TCK/SK ---> SCL
		ADBUS1 TDI/DO -+-> SDA
		ADBUS2 TDO/DI -+
		ADBUS3 TMS/CS
		ADBUSS GPIOL0
		ADBUS5 GPIOL1
		ADBUS6 GPIOl2
		ADBUS7 GPIOL3
	*/

	// Set values and directions of lower 8 pins (ADBUS7-0)
	outputBuffer[0] = 0x80;
	// Set SK,DO high
	outputBuffer[1] = 0x03;
	// Set SK,DO as output, other as input
	outputBuffer[2] = 0x03;

	// Set clock divisor
	outputBuffer[3] = 0x86;
	// low byte
	outputBuffer[4] = dwClockDivisor & 0xFF;
	// high byte
	outputBuffer[5] = (dwClockDivisor >> 8) & 0xFF;

	// sent of commands
	ftStatus = FT_Write(ftHandle, outputBuffer, 6, &outputSent);

	// Turn of Loopback
	outputBuffer[0] = 0x85;
	ftStatus = FT_Write(ftHandle, outputBuffer, 1, &outputSent);

	outputSize = 0;

	i2c_start();
	i2c_send_byte_and_check_ack(LM75);
	i2c_send_byte_and_check_ack(0x00);

	i2c_start();
	i2c_send_byte_and_check_ack(LM75 | 0x01);

	// SCL low, SDA tristate
	outputBuffer[outputSize++] = 0x80;
	outputBuffer[outputSize++] = 0x00;
	outputBuffer[outputSize++] = 0x01;

	for (i = 0; i < 2; ++i) {
		outputBuffer[outputSize++] = MSB_FALLING_EDGE_CLOCK_BYTE_IN;
		// data length of 0x0000 means 1 byte
		outputBuffer[outputSize++] = 0x00;
		outputBuffer[outputSize++] = 0x00;

		outputBuffer[outputSize++] = MSB_FALLING_EDGE_CLOCK_BIT_OUT;
		// 0x00 means scan 1 bit
		outputBuffer[outputSize++] = 0x00;
		if (i < 2-1) {
			outputBuffer[outputSize++] = 0x00;
		} else {
			outputBuffer[outputSize++] = 0x01;
		}
	}

	// send answer back immediate
	outputBuffer[outputSize++] = 0x87;

	ftStatus = FT_Write(ftHandle, outputBuffer, outputSize, &outputSent);
	outputSize = 0;

	ftStatus = FT_Read(ftHandle, inputBuffer, 2, &inputRead);

	// SDA high, SCL low
	outputBuffer[outputSize++] = 0x80;
	outputBuffer[outputSize++] = 0x02;
	outputBuffer[outputSize++] = 0x03;

	i2c_stop();
	ftStatus = FT_Write(ftHandle, outputBuffer, outputSize, &outputSent);
	outputSize = 0;

	FT_Close(ftHandle);

	double temperature = inputBuffer[0];

	if (inputBuffer[1] & 0x80) {
		temperature += 0.5;
	}

	printf("Temperature: %.1lf C\n", temperature);

	return 0;
}

