<?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
 */

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 );
	$bus353 = i2c_bus353 ( $bus );
	$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()

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 ) );
	}
	$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 );
	$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()
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 ) );
	}
	$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)
	$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=1,$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));
   }
   $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=1,$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
   if (!is_string($data)) return -1;
   if (strlen($data)>256) return -2;
   if (strlen($data)<256) $data.=chr(0);
   $i2c_fn='/dev/xi2c8'.(($bus==0)?'':'_aux');
   $i2c  = fopen($i2c_fn, 'w');
   $len=0;
   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 ()
?>