<?php
/*!***************************************************************************
*! FILE NAME  : i2c.inc
*! DESCRIPTION: Provides functions to read/write over i2c buses in the camera,
*!              low-level R/W and additionally:
*!              Setting per-slave protection mask,
*!              Synchronizing system clock with a "CMOS" one
*!              Reading from/writing to EEPROM memory
*! Copyright (C) 2008-2016 Elphel, Inc
*! -----------------------------------------------------------------------------**
*!
*!  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 <http://www.gnu.org/licenses/>.
*! -----------------------------------------------------------------------------**
*!  $Log: i2c.inc,v $
*!  Revision 1.4  2012/01/12 19:09:28  elphel
*!  bug fix
*!
*!  Revision 1.3  2011/12/22 05:35:11  elphel
*!  Added "slow" option (for slow i2c devices) and reading the sensor board temperature
*!
*!  Revision 1.2  2010/08/10 21:11:40  elphel
*!  added EEPROM support of the 10359 and 10338D boards.
*!
*!  Revision 1.1.1.1  2008/11/27 20:04:03  elphel
*!
*!
*!  Revision 1.3  2008/10/04 16:09:04  elphel
*!  updating FPGA time with system time from CMOS
*!
*!  Revision 1.2  2008/06/04 20:26:23  elphel
*!  includes SMBus operations, needed to activate USB on the 10369A board
*!
*!  Revision 1.2  2008/03/16 01:24:09  elphel
*!  Added i2c speed control interface
*!
*!  Revision 1.1  2008/03/15 23:05:18  elphel
*!  split i2c.php into i2c.php and i2c.inc (to be used from other scripts)
*!
*!
*/
//$i2c_slow_safe=array(3,4,2,2,7,7); /// sensor i2c delays are 2,2,1,1,7,7
// In NC393: buses 0..3 - sensors(old bus=0), 4 - aux (old bus=1)
/// NC393-fucnctions to read/write sensor ports
/// Reusing NC53 parameters: $width->$name, $raw ->
function i2c_send_sensor($name,         ///< device class name (get all by "cat /sys/devices/soc0/elphel393-sensor-i2c@0/i2c_all")
		                 $sensor_port,  ///< sensor port :0..3
		                 $ra,           ///< register address
		                 $data,         ///< data to write
		                 $sa7_offset=0) ///< slave address offset when there are multiple devices (2*sub-channel in 10359)
		                                ///< return true on success, false on failure
{ //$a<0 - use raw read/write
	$path = '/sys/devices/soc0/elphel393-sensor-i2c@0/i2c'.strval($sensor_port);
	$rslt = file_put_contents ($path, $name.' ' . strval($sa7_offset) . ' ' . strval($ra) . ' ' . strval($data));
	return $rslt; 
}

function i2c_receive_sensor($name,         ///< device class name (get all by "cat /sys/devices/soc0/elphel393-sensor-i2c@0/i2c_all")
		                    $sensor_port,  ///< sensor port :0..3
		                    $ra,           ///< register address
		                    $sa7_offset=0) ///< slave address offset when there are multiple devices (2*sub-channel in 10359)
		                                   ///< @return read data or -1 on error
{
	// Initiate read
	$path = '/sys/devices/soc0/elphel393-sensor-i2c@0/i2c'.strval($sensor_port);
	$rslt = file_put_contents ($path, $name.' ' . strval($sa7_offset) . ' ' . strval($ra) );
	if (!$rslt)
		return -1;
	$rslt = file_get_contents ($path);
	if ($rslt === false)
		return -1;
	return intval($rslt);
}
/** Read 256 bytes from device as a string */
function i2c_read256b_sensor($name,         ///< device class name (get all by "cat /sys/devices/soc0/elphel393-sensor-i2c@0/i2c_all")
		                     $sensor_port,  ///< sensor port :0..3
		                     $sa7_offset=0) ///< slave address offset when there are multiple devices (2*sub-channel in 10359)
		                                    ///< @return read data string or partial (empty) string on error
{
	$rslt = '';
	for ($ra=0;$ra<256;$ra++){
		if (($d = i2c_receive_sensor($name,$sensor_port,$ra,$sa7_offset))<0) break;
		$rslt .= chr($d);
	}
	return $rslt;
}

/** Write string to device */
function i2c_write256b_sensor($data,         ///< Sring to write
							  $name,         ///< device class name (get all by "cat /sys/devices/soc0/elphel393-sensor-i2c@0/i2c_all")
							  $sensor_port,  ///< sensor port :0..3
							  $sa7_offset=0) ///< slave address offset when there are multiple devices (2*sub-channel in 10359)
											 ///< @return number of bytes written
{
	$len = 0;
	if (strlen($data)<256) $data.=chr(0);
	for ($ra=0;$ra<strlen($data);$ra++){
		$b = ord(substr($data,$ra,1));
		if (($d = i2c_send_sensor($name,$sensor_port,$ra,$b,$sa7_offset))<0) break;
		usleep(10000);
		$len ++;
	}
	return $len;
}

///sys/devices/soc0/elphel393-sensor-i2c@0# echo "mt9p006 8 9" > i2c3; cat i2c3
/*
cy22393: 0x69 1 1 100 kHz
sensor_temp: 0x18 1 2 100 kHz
sensor_eeprom: 0x50 1 1 100 kHz
pca9500_eeprom: 0x50 1 1 100 kHz
el10359_32: 0x8 1 4 500 kHz
el10359: 0x8 1 2 500 kHz
mt9p006: 0x48 1 2 500 kHz
mt9f002: 0x10 2 2 500 kHz
 */
/** Convert nc353 addres/width to sa7 and register address */
function aw_to_sa7r($adr,   ///< composite address, nc353 style (256 bytes for each byte-wide device, 512 bytes for each 16-bit one)
		$width  ///< Data with 16 or 8
		)                   ///< @return: array(sa7,ra)
{
	$sa7 = ($adr >> (($width == 16) ? 9 : 8)) & 0x7f;
	$ra =  ($adr >> (($width == 16) ? 1 : 0)) & 0xff;
	return array(sa7,ra);

}

function getSlowArray($usec=0){
  if ($usec<0) $usec=0;
  $SCL_low=3+(int) ($usec*8);
  $SCL_high=4+(int) ($usec*8);
  if ($SCL_low>254) $SCL_low=254;
  if ($SCL_high>254) $SCL_high=254;
  $i2c_slow_safe=array($SCL_low ,$SCL_high,2,2,7,7); /// sensor i2c delays are 2,2,1,1,7,7
  return $i2c_slow_safe;
}
///Map i2c bus of 393 to "old" one
function i2c_bus353($bus){
	if ($bus<4) return 0;
	else return $bus-3;
}
function i2c_ctl($bus,$data="") { //!$data - string of decimal values separated by ":", empty - don't change
   $size=6; // bytes per bus
   $data=trim($data);
   if ($data!="") {
     $darr=explode(":",$data);
     $i2cctl  = fopen('/dev/xi2cctl', 'w');
     for ($i=0; ($i<$size) && ($i<count($darr)); $i++) if ($darr[$i]!=""){
       fseek ($i2cctl, i2c_bus353($bus)*$size+$i) ;
       fwrite($i2cctl, chr($darr[$i]+0), 1);
     }
     fclose($i2cctl);
   }
   $i2cctl  = fopen('/dev/xi2cctl', 'r');
   fseek ($i2cctl, i2c_bus353($bus)*$size) ;
   $data = fread($i2cctl, $size);
   fclose($i2cctl);
   $xml = new SimpleXMLElement("<?xml version='1.0'?><i2cctl/>");
           $xml->addChild ('scl_high',ord($data[0]));
           $xml->addChild ('scl_low', ord($data[1]));
           $xml->addChild ('slave2master', ord($data[2]));
           $xml->addChild ('master2slave', ord($data[3]));
           $xml->addChild ('filter_sda',   ord($data[4]));
           $xml->addChild ('filter_scl',   ord($data[5]));
//           $data=$xml->asXML();
   return $xml;

} // end of i2c_ctl()

function i2c_ctl_arr($bus,$darr=array()) { //!$data - 6-element array
   $size=6; // bytes per bus
   if (count($darr)>00) {
     $i2cctl  = fopen('/dev/xi2cctl', 'w');
     for ($i=0; ($i<$size) && ($i<count($darr)); $i++) if ($darr[$i]!=""){
       fseek ($i2cctl, i2c_bus353($bus)*$size+$i) ;
       fwrite($i2cctl, chr($darr[$i]+0), 1);
     }
     fclose($i2cctl);
   }
   $i2cctl  = fopen('/dev/xi2cctl', 'r');
   fseek ($i2cctl, i2c_bus353($bus)*$size) ;
   $data = fread($i2cctl, $size);
   fclose($i2cctl);
   for ($i=0;$i<$size; $i++) $darr[$i]=ord($data[$i]);
   return $darr;
} // end of i2c_ctl()


function i2c_send($width, $bus, $a, $d, $raw = 0) { // $a<0 - use raw read/write
	if ($bus < 4)
		return i2c_send_sensor ( $width, $bus, $a, $d, $raw );
	else
		$bus = i2c_bus353 ( $bus );
	if ($bus == 2) { // System i2c in nc393 (was 5)
		$return = -1;
		$w = ($width == 16) ?'w' : 'b';
		$sa7r = aw_to_sa7r($a,$width); // works for raw also, $width is 8 for raw
		if ($raw){
			exec ( 'i2cset -y 0 '.$sa7r[0].' '.$d,                     $i2c_data, $return );
		} else {
			exec ( 'i2cget -y 0 '.$sa7r[0].' '.$sa7r[1].' '.$d.' '.$w, $i2c_data, $return );
		}
		if ($return != 0) return -1;
		return ($width == 16)?2:1;
	}
	
	$w = ($width == 16) ? 2 : 1;
	$i2c_fn = '/dev/xi2c' . ($raw ? 'raw' : (($w == 2) ? '16' : '8')) . (($bus == 0) ? '' : '_aux');
	$i2c = fopen ( $i2c_fn, 'w' );
	fseek ( $i2c, $w * $a );
	if ($w == 1)
		$res = fwrite ( $i2c, chr ( $d ) );
	else
		$res = fwrite ( $i2c, chr ( floor ( $d / 256 ) ) . chr ( $d - 256 * floor ( $d / 256 ) ) );
	fclose ( $i2c );
	return $res;
} // end of i2c_send()

// Seems no difference from i2c_send for nc393
function i2c_send_slow($width, $bus, $a, $d, $raw = 0, $extrausec = -1) { // $a<0 - use raw read/write
	if ($bus < 4)
		return i2c_send_sensor ( $width, $bus, $a, $d, $raw );
	else
		$bus = i2c_bus353 ( $bus );
	if (($bus == 0) && ($extrausec >= 0)) {
		$i2c_old_ctrl = i2c_ctl_arr ( 0 );
		i2c_ctl_arr ( 0, getSlowArray ( $extrausec ) );
	}
	if ($bus == 2) { // System i2c in nc393 (was 5)
		$return = -1;
		$w = ($width == 16) ?'w' : 'b';
		$sa7r = aw_to_sa7r($a,$width); // works for raw also, $width is 8 for raw
		if ($raw){
			exec ( 'i2cset -y 0 '.$sa7r[0].' '.$d,                     $i2c_data, $return );
		} else {
			exec ( 'i2cget -y 0 '.$sa7r[0].' '.$sa7r[1].' '.$d.' '.$w, $i2c_data, $return );
		}
		if ($return != 0) return -1;
		return ($width == 16)?2:1;
	}

	$w = ($width == 16) ? 2 : 1;
	$i2c_fn = '/dev/xi2c' . ($raw ? 'raw' : (($w == 2) ? '16' : '8')) . (($bus == 0) ? '' : '_aux');
	$i2c = fopen ( $i2c_fn, 'w' );
	fseek ( $i2c, $w * $a );
	if ($w == 1)
		$res = fwrite ( $i2c, chr ( $d ) );
	else
		$res = fwrite ( $i2c, chr ( floor ( $d / 256 ) ) . chr ( $d - 256 * floor ( $d / 256 ) ) );
	fclose ( $i2c );
	if (($bus == 0) && ($extrausec >= 0)) {
		i2c_ctl_arr ( 0, $i2c_old_ctrl ); // / restore old speed (not thread-safe)
	}
	return $res;
} // end of i2c_send()

function smbus_send($a, $d) { // d - array
	$i2c_fn = '/dev/xi2c8_aux';
	$i2c = fopen ( $i2c_fn, 'w' );
	fseek ( $i2c, $a );
	$cmd = chr ( count ( $d ) );
	foreach ( $d as $b )
		$cmd .= chr ( $b );
		// var_dump($cmd);
	$res = fwrite ( $i2c, $cmd );
	fclose ( $i2c );
	return $res;
} // end of i2c_send()


function i2c_receive($width, $bus, $a, $raw = 0) {
	if ($bus < 4)
		return i2c_receive_sensor ( $width, $bus, $a, $raw );
	else
		$bus = i2c_bus353 ( $bus );
	if ($bus == 2) { // System i2c in nc393 (was 5)
		$w = ($width == 16) ?'w' : 'b';
		$sa7r = aw_to_sa7r($a,$width); // works for raw also, $width is 8 for raw
		if ($raw){
			exec ( 'i2cget -y 0 '.$sa7r[0],                     $i2c_data, $return );
		} else {
			exec ( 'i2cget -y 0 '.$sa7r[0].' '.$sa7r[1].' '.$w, $i2c_data, $return );
		}
		if ($return != 0) return -1;			
		return $i2c_data[0];
	}
	$w = ($width == 16) ? 2 : 1;
	$i2c_fn = '/dev/xi2c' . ($raw ? 'raw' : (($w == 2) ? '16' : '8')) . (($bus == 0) ? '' : '_aux');
	$i2c = fopen ( $i2c_fn, 'r' );
	fseek ( $i2c, $w * $a );
	$data = fread ( $i2c, $w );
	fclose ( $i2c );
	if (strlen ( $data ) < $w)
		return - 1;
	$v = unpack ( ($w == 1) ? 'C' : 'n1', $data );
	return $v [1];
} // end of i2c_receive()

// i2c_receive_slow is the same as i2c_receive for nc393
function i2c_receive_slow($width, $bus, $a, $raw = 0, $extrausec = -1) {
	if ($bus < 4)
		return i2c_receive_sensor ( $width, $bus, $a, $raw );
	else
		$bus = i2c_bus353 ( $bus );
	if (($bus == 0) && ($extrausec >= 0)) {
		$i2c_old_ctrl = i2c_ctl_arr ( 0 );
		i2c_ctl_arr ( 0, getSlowArray ( $extrausec ) );
	}
	if ($bus == 2) { // System i2c in nc393 (was 5)
		$w = ($width == 16) ?'w' : 'b';
		$sa7r = aw_to_sa7r($a,$width); // works for raw also, $width is 8 for raw
		if ($raw){
			exec ( 'i2cget -y 0 '.$sa7r[0],                     $i2c_data, $return );
		} else {
			exec ( 'i2cget -y 0 '.$sa7r[0].' '.$sa7r[1].' '.$w, $i2c_data, $return );
		}
		if ($return != 0) return -1;
		return $i2c_data[0];
	}
	$w = ($width == 16) ? 2 : 1;
	$i2c_fn = '/dev/xi2c' . ($raw ? 'raw' : (($w == 2) ? '16' : '8')) . (($bus == 0) ? '' : '_aux');
	$i2c = fopen ( $i2c_fn, 'r' );
	fseek ( $i2c, $w * $a );
	$data = fread ( $i2c, $w );
	fclose ( $i2c );
	if (strlen ( $data ) < $w)
		return - 1;
	$v = unpack ( ($w == 1) ? 'C' : 'n1', $data );
	if ($bus == 0) {
		i2c_ctl_arr ( 0, $i2c_old_ctrl ); // / restore old speed (not thread-safe)
	}
	return $v [1];
} // end of i2c_receive()

function i2c_setprot($bus, $slave, $bit, $value) { // !slave is MSB aligned, LSB==0)
	if ($bus != 4)
		return -1; // applicable only to grand-daughter i2c (like imu, gps) 
	$bus = i2c_bus353 ( $bus );
	$i2cprot = fopen ("/dev/xi2cenable", 'r+' );
	
	fseek ( $i2cprot, ($bus * 128) + ($slave >> 1) );
	$data = ord ( fread ( $i2cprot, 1 ) );
	if ($value)
		$data |= (1 << $bit);
	else
		$data &= ~ (1 << $bit);
	fseek ( $i2cprot, ($bus * 128) + ($slave >> 1) );
	fwrite ( $i2cprot, chr ( $data ) );
	fclose ( $i2cprot );
} // end of i2c_setprot ()

function i2c_getCMOSClock() {
	echo "**** FUNCTION NOT SUPPORTED in NC393 *****";
	return - 1;
	$i2c = fopen ( '/dev/xi2c8_aux', 'r' );
	fseek ( $i2c, 0x5102 ); // seconds in clock on the 10369 board
	$data = fread ( $i2c, 7 ); // sec/min/hours/days/weekdays/century-months/years, BCD
	fclose ( $i2c );
	if (strlen ( $data ) < 7)
		return "error";
	$v = unpack ( 'C*', $data );
	$d = array (
			"sec" => ($v [1] & 0xf) + 10 * (($v [1] >> 4) & 0x7),
			"min" => ($v [2] & 0xf) + 10 * (($v [2] >> 4) & 0x7),
			"hrs" => ($v [3] & 0xf) + 10 * (($v [3] >> 4) & 0x3),
			"day" => ($v [4] & 0xf) + 10 * (($v [4] >> 4) & 0x3),
			"wday" => $v [5] & 0x7,
			"month" => ($v [6] & 0xf) + 10 * (($v [6] >> 4) & 0x1),
			"year" => ($v [7] & 0xf) + 10 * (($v [7] >> 4) & 0xf) + (($v [6] & 0x80) ? 2000 : 1900) 
	);
	exec ( "date -s " . sprintf ( "%02d%02d%02d%02d%04d.%02d", $d ["month"], $d ["day"], $d ["hrs"], $d ["min"], $d ["year"], $d ["sec"] ), $out, $ret );
	if ($ret == 0) {
		elphel_set_fpga_time ( time () + 0.0 );
	}
	return $ret;
} // end of i2c_getCMOSClock()

function i2c_bcd($v) {
  $d=intval($v,10);
  return $d + 6*floor($d/10);
} // end of i2c_bcd($v)

function i2c_setCMOSClock(){
 $ts=time();
 $data=chr(i2c_bcd(gmdate("s",$ts))).
       chr(i2c_bcd(gmdate("i",$ts))).
       chr(i2c_bcd(gmdate("H",$ts))).
       chr(i2c_bcd(gmdate("d",$ts))).
       chr(i2c_bcd(gmdate("w",$ts))).
       chr(i2c_bcd(gmdate("m",$ts))+ ((intval(gmdate("Y",$ts),10)>=2000)?0x80:0)).
       chr(i2c_bcd(gmdate("y",$ts)));
   $i2c  = fopen('/dev/xi2c8_aux', 'w');
   fseek ($i2c, 0x5102) ; //seconds in clock on the 10369 board
   $written= fwrite($i2c, $data);
   fclose($i2c);
   if ($written < strlen($data)) return "error";
   return "OK";
} // end of i2c_setCMOSClock()

function i2c_read256b($slave = 0xa0, $bus = 4, $extrausec = 0) { // will read 256 bytes from slave (address is 8-bit, includes r/~w)
	if ($bus < 4)
		return i2c_read256b_sensor ( $slave, $bus, $extrausec ); // ($name, $sensor_port, $sa7_offset)
	else
		$bus = i2c_bus353 ( $bus );
	if (($bus == 0) && ($extrausec >= 0)) {
		$i2c_old_ctrl = i2c_ctl_arr ( 0 );
		i2c_ctl_arr ( 0, getSlowArray ( $extrausec ) );
	}
	if ($bus == 2) { // System i2c in nc393 (was 5)
		$sa7 = $slave >> 1;
		/* // very slow
		for($i = 0; $i < 256; $i ++) {
			exec ( 'i2cget -y 0 ' . $sa7 . ' ' . $i . ' b', $i2c_data, $return );
			if ($return != 0)
				return - 1;
			}
		$data == "";
		foreach ($i2c_data as $c) $data.=chr($c);
		*/
		exec('i2cdump -y 0 ' . $sa7 . ' b', $i2c_data, $return );
		if ($return != 0) return - 1;
		// Extract data from dump table
		$line="";
		for ($j=0; $j<16; $j++) $line .= substr($i2c_data[$j+1],4,48);
		$data == "";
		foreach (explode(' ',$line) as $e) $data.=chr(intval($e,16));
		return $data;
	}
	$i2c_fn = '/dev/xi2c8' . (($bus == 0) ? '' : '_aux');
	$i2c = fopen ( $i2c_fn, 'r' );
	fseek ( $i2c, $slave * 128 ); // 256 per slave, but slave are only even
	$data = fread ( $i2c, 256 ); // full 256 bytes
	fclose ( $i2c );
	if (($bus == 0) && ($extrausec >= 0)) {
		i2c_ctl_arr ( 0, $i2c_old_ctrl ); // / restore old speed (not thread-safe)
	}
	return $data;
} // end of i2c_read256b ()
//this EEPROM writes only 4 bytes sequentionally (only 2 LSBs are incremented)

function i2c_write256b($data, $slave = 0xa0, $bus = 4, $extrausec = 0) { // will write up to 256 bytes $data to slave (address is 8-bit, includes r/~w). EEPROM should be un-protected
	if ($bus < 4)
		return i2c_write256b_sensor ( $data, $slave, $bus, $extrausec ); // ($data, $name, $sensor_port, $sa7_offset)
	else
		$bus = i2c_bus353 ( $bus );
	if (($bus == 0) && ($extrausec >= 0)) {
		$i2c_old_ctrl = i2c_ctl_arr ( 0 );
		i2c_ctl_arr ( 0, getSlowArray ( $extrausec ) );
	}
	$maxretries = 200; // measured - 19
	$len = 0;
	if (! is_string ( $data ))
		return - 1;
	if (strlen ( $data ) > 256)
		return - 2;
	if (strlen ( $data ) < 256)
		$data .= chr ( 0 );

	if ($bus == 2) { // System i2c in nc393 (was 5)
		$sa7 = $slave >> 1;
		foreach (str_split($data) as $d) {
			exec ( 'i2cset -y 0 ' . $sa7 . ' ' .$len . ' ' . ord($d) . ' b', $i2c_data, $return );
			if ($return != 0)
				return - 1;
			usleep ( 10000 );
			$len ++;
		}
		return $len;
	}
//foreach (str_split($data) as $d) echo ord($d);		
	$i2c_fn = '/dev/xi2c8' . (($bus == 0) ? '' : '_aux');
	$i2c = fopen ( $i2c_fn, 'w' );
	for($i = 0; $i < strlen ( $data ); $i += 4) {
		for($retry = 0; $retry < $maxretries; $retry ++) {
			fseek ( $i2c, $slave * 128 + $i ); // 256 per slave, but slave are only even
			$rslt = fwrite ( $i2c, substr ( $data, $i, 4 ) );
			if ($rslt > 0)
				break;
		}
		if ($rslt <= 0) {
			$len = $rslt;
			break;
		}
		$len += $rslt;
	}
	fclose ( $i2c );
	if (($bus == 0) && ($extrausec >= 0)) {
		i2c_ctl_arr ( 0, $i2c_old_ctrl ); // / restore old speed (not thread-safe)
	}
	return $len;
} // end of i2c_write256b ()
?>