/* 

Access Dallas 1-Wire Devices with ATMEL AVRs

Author of the initial code: Peter Dannegger (danni(at)specs.de)

modified by Martin Thomas (mthomas(at)rhrk.uni-kl.de)

 9/2004 - use of delay.h, optional bus configuration at runtime

10/2009 - additional delay in ow_bit_io for recovery

 5/2010 - timing modifcations, additonal config-values and comments,

          use of atomic.h macros, internal pull-up support

 7/2010 - added method to skip recovery time after last bit transfered

          via ow_command_skip_last_recovery

*/

#include <avr/io.h>

#include <util/delay.h>

#include <util/atomic.h>


#include "onewire.h"


#ifdef OW_ONE_BUS


#define OW_GET_IN()   ( OW_IN & (1<<OW_PIN))

#define OW_OUT_LOW()  ( OW_OUT &= (~(1 << OW_PIN)) )

#define OW_OUT_HIGH() ( OW_OUT |= (1 << OW_PIN) )

#define OW_DIR_IN()   ( OW_DDR &= (~(1 << OW_PIN )) )

#define OW_DIR_OUT()  ( OW_DDR |= (1 << OW_PIN) )


#else


/* set bus-config with ow_set_bus() */
uint8_t OW_PIN_MASK; 
volatile uint8_t* OW_IN;
volatile uint8_t* OW_OUT;
volatile uint8_t* OW_DDR;

#define OW_GET_IN()   ( *OW_IN & OW_PIN_MASK )

#define OW_OUT_LOW()  ( *OW_OUT &= (uint8_t) ~OW_PIN_MASK )

#define OW_OUT_HIGH() ( *OW_OUT |= (uint8_t)  OW_PIN_MASK )

#define OW_DIR_IN()   ( *OW_DDR &= (uint8_t) ~OW_PIN_MASK )

#define OW_DIR_OUT()  ( *OW_DDR |= (uint8_t)  OW_PIN_MASK )


void ow_set_bus(volatile uint8_t* in,
	volatile uint8_t* out,
	volatile uint8_t* ddr,
	uint8_t pin)
{
	OW_DDR=ddr;
	OW_OUT=out;
	OW_IN=in;
	OW_PIN_MASK = (1 << pin);
	ow_reset();
}

#endif


uint8_t ow_input_pin_state()
{
	return OW_GET_IN();
}

void ow_parasite_enable(void)
{
	OW_OUT_HIGH();
	OW_DIR_OUT();
}

void ow_parasite_disable(void)
{
	OW_DIR_IN();
#if (!OW_USE_INTERNAL_PULLUP)

	OW_OUT_LOW();
#endif

}


uint8_t ow_reset(void)
{
	uint8_t err;
	
	OW_OUT_LOW();
	OW_DIR_OUT();            // pull OW-Pin low for 480us

	_delay_us(480);

	ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
		// set Pin as input - wait for clients to pull low

		OW_DIR_IN(); // input

#if OW_USE_INTERNAL_PULLUP

		OW_OUT_HIGH();
#endif

	
		_delay_us(64);       // was 66

		err = OW_GET_IN();   // no presence detect

		                     // if err!=0: nobody pulled to low, still high

	}
	
	// after a delay the clients should release the line

	// and input-pin gets back to high by pull-up-resistor

	_delay_us(480 - 64);       // was 480-66

	if( OW_GET_IN() == 0 ) {
		err = 1;             // short circuit, expected low but got high

	}
	
	return err;
}


/* Timing issue when using runtime-bus-selection (!OW_ONE_BUS):

   The master should sample at the end of the 15-slot after initiating

   the read-time-slot. The variable bus-settings need more

   cycles than the constant ones so the delays had to be shortened 

   to achive a 15uS overall delay 

   Setting/clearing a bit in I/O Register needs 1 cyle in OW_ONE_BUS

   but around 14 cyles in configureable bus (us-Delay is 4 cyles per uS) */
static uint8_t ow_bit_io_intern( uint8_t b, uint8_t with_parasite_enable )
{
	ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
#if OW_USE_INTERNAL_PULLUP

		OW_OUT_LOW();
#endif

		OW_DIR_OUT();    // drive bus low

		_delay_us(2);    // T_INT > 1usec accoding to timing-diagramm

		if ( b ) {
			OW_DIR_IN(); // to write "1" release bus, resistor pulls high

#if OW_USE_INTERNAL_PULLUP

			OW_OUT_HIGH();
#endif

		}

		// "Output data from the DS18B20 is valid for 15usec after the falling

		// edge that initiated the read time slot. Therefore, the master must 

		// release the bus and then sample the bus state within 15ussec from 

		// the start of the slot."

		_delay_us(15-2-OW_CONF_DELAYOFFSET);
		
		if( OW_GET_IN() == 0 ) {
			b = 0;  // sample at end of read-timeslot

		}
	
		_delay_us(60-15-2+OW_CONF_DELAYOFFSET);
#if OW_USE_INTERNAL_PULLUP

		OW_OUT_HIGH();
#endif

		OW_DIR_IN();
	
		if ( with_parasite_enable ) {
			ow_parasite_enable();
		}
	
	} /* ATOMIC_BLOCK */

	_delay_us(OW_RECOVERY_TIME); // may be increased for longer wires


	return b;
}

uint8_t ow_bit_io( uint8_t b )
{
	return ow_bit_io_intern( b & 1, 0 );
}

uint8_t ow_byte_wr( uint8_t b )
{
	uint8_t i = 8, j;
	
	do {
		j = ow_bit_io( b & 1 );
		b >>= 1;
		if( j ) {
			b |= 0x80;
		}
	} while( --i );
	
	return b;
}

uint8_t ow_byte_wr_with_parasite_enable( uint8_t b )
{
	uint8_t i = 8, j;
	
	do {
		if ( i != 1 ) {
			j = ow_bit_io_intern( b & 1, 0 );
		} else {
			j = ow_bit_io_intern( b & 1, 1 );
		}
		b >>= 1;
		if( j ) {
			b |= 0x80;
		}
	} while( --i );
	
	return b;
}


uint8_t ow_byte_rd( void )
{
	// read by sending only "1"s, so bus gets released

	// after the init low-pulse in every slot

	return ow_byte_wr( 0xFF ); 
}


uint8_t ow_rom_search( uint8_t diff, uint8_t *id )
{
	uint8_t i, j, next_diff;
	uint8_t b;
	
	if( ow_reset() ) {
		return OW_PRESENCE_ERR;         // error, no device found <--- early exit!

	}
	
	ow_byte_wr( OW_SEARCH_ROM );        // ROM search command

	next_diff = OW_LAST_DEVICE;         // unchanged on last device

	
	i = OW_ROMCODE_SIZE * 8;            // 8 bytes

	
	do {
		j = 8;                          // 8 bits

		do {
			b = ow_bit_io( 1 );         // read bit

			if( ow_bit_io( 1 ) ) {      // read complement bit

				if( b ) {               // 0b11

					return OW_DATA_ERR; // data error <--- early exit!

				}
			}
			else {
				if( !b ) {              // 0b00 = 2 devices

					if( diff > i || ((*id & 1) && diff != i) ) {
						b = 1;          // now 1

						next_diff = i;  // next pass 0

					}
				}
			}
			ow_bit_io( b );             // write bit

			*id >>= 1;
			if( b ) {
				*id |= 0x80;            // store bit

			}
			
			i--;
			
		} while( --j );
		
		id++;                           // next byte

	
	} while( i );
	
	return next_diff;                   // to continue search

}


static void ow_command_intern( uint8_t command, uint8_t *id, uint8_t with_parasite_enable )
{
	uint8_t i;

	ow_reset();

	if( id ) {
		ow_byte_wr( OW_MATCH_ROM );     // to a single device

		i = OW_ROMCODE_SIZE;
		do {
			ow_byte_wr( *id );
			id++;
		} while( --i );
	} 
	else {
		ow_byte_wr( OW_SKIP_ROM );      // to all devices

	}
	
	if ( with_parasite_enable  ) {
		ow_byte_wr_with_parasite_enable( command );
	} else {
		ow_byte_wr( command );
	}
}

void ow_command( uint8_t command, uint8_t *id )
{
	ow_command_intern( command, id, 0);
}

void ow_command_with_parasite_enable( uint8_t command, uint8_t *id )
{
	ow_command_intern( command, id, 1 );
}