#!/usr/bin/perl
use Device::USB ;

my $timeout = 500 ;
my $usb ; 
my $dev ;
my $cfg ;
my $cfgval ;
my $debug = 13 ;

#*****************************************************
#          Canon LiDE calibration and scan
#*****************************************************

# at 600 dpi
my $CANON_MAX_WIDTH = 5100 ;	# 8.5in
# this may not be right
my $CANON_MAX_HEIGHT = 7000 ;	# 11.66in
# Just for my scanner, or is this universal?  Calibrate?
my $LEFT_EDGE = 0x01b3 ;
my $TOP_EDGE = 0x0041 ;

my $FLG_GRAY       = 0x01 ;	# grayscale
my $FLG_FORCE_CAL  = 0x02 ;	# force calibration
my $FLG_PPM_HEADER = 0x04 ;	# include PPM header in scan file

#*****************************************************
#            CP2155 communication primitives
#   Provides I/O routines to Philips CP2155BE chip
#*****************************************************

# Write single byte to CP2155 register
sub cp2155_set
{
	my ( $reg , $val ) = @_ ;
	my @data = ( ($reg >> 8) & 0xff , ($reg & 0xff) , 0x01 , 0x00 , $val ) ;
	my $status = $dev -> bulk_write ( 0x02 , dez2bin(@data) , $timeout ) ;

	if ( $status < 0 )
	{
DBG ( 1 , "cp2155_set: sanei_usb_write_bulk error %02x\n" , $status ) ;
		return ( 'SANE_STATUS_IO_ERROR' ) ;
	}

	return ( 'SANE_STATUS_GOOD' ) ;
}

# Read single byte from CP2155 register
sub cp2155_get
{
	my $reg = $_[0] ;
	my @data = ( 0x01 , $reg , 0x01 , 0x00 ) ;
	my $status = $dev -> bulk_write ( 0x02 , dez2bin(@data) , $timeout ) ;

	if ( $status < 0 )
	{
DBG ( 1 , "cp2155_get: sanei_usb_write_bulk error\n" ) ;
		return ( 'SANE_STATUS_IO_ERROR' ) ;
	}

	usleep ( 0.001 ) ;

	my $data = ' ' ;
	$status = $dev -> bulk_read ( 0x83 , $data , 1 , $timeout ) ;
	$_[1] = ord ( $data ) ;

	if ( $status < 0 )
	{
DBG ( 1 , "cp2155_get: sanei_usb_read_bulk error\n" ) ;
		return ( 'SANE_STATUS_IO_ERROR' ) ;
	}

	return ( 'SANE_STATUS_GOOD' ) ;
}

# Write a block of data to CP2155 chip
sub cp2155_write
{
	my ( $count , @data ) = @_ ;
	my @head = ( 0x04 , 0x70 , ($count & 0xff) , ($count >> 8) & 0xff) ;

	while ( @data < $count )
	{
		push ( @data , 0x00 ) ;
	}

	my $data = dez2bin ( @head , @data ) ;
	my $status = $dev -> bulk_write ( 0x02 , $data , $timeout ) ;

	if ( $status < 0 )
	{
DBG ( 1 , "cp2155_write: sanei_usb_write_bulk error\n" ) ;
		return ( 'SANE_STATUS_IO_ERROR' ) ;
	}
  
	return ( 'SANE_STATUS_GOOD' ) ;
}

# Read a block of data from CP2155 chip
sub cp2155_read
{
#	my ( $size , $data ) = @_ ;
	my $size = $_[0] ;
	my @data = ( 0x05 , 0x70 , ($size & 0xff) , ($size >> 8) & 0xff ) ;
	my $status = $dev -> bulk_write ( 0x02 , dez2bin(@data) , $timeout ) ;

	if ( $status < 0 )
	{
DBG ( 1 , "cp2155_get: sanei_usb_write_bulk error\n" ) ;
		return ( 'SANE_STATUS_IO_ERROR' ) ;
	}

	usleep ( 0.001 ) ;

	$_[1] = "\0" x $size ;
	$status = $dev -> bulk_read ( 0x83 , $_[1] , $size , $timeout ) ;

	if ( $status < 0 )
	{
DBG ( 1 , "cp2155_read: sanei_usb_read_bulk error %d\n" , $size ) ;
		return ( 'SANE_STATUS_IO_ERROR' ) ;
	}

	return ( 'SANE_STATUS_GOOD' ) ;
}

#------------------------------------------
# High level function definitions
#------------------------------------------

sub cp2155_block1
{
	my ( $v001 , $addr , @data ) = @_ ;
	my $count = @data ;
  
	while ( $count & 0x0f )
	{
		$count ++ ;
	}

	my $pgLO = $count & 0xff ;
	my $pgHI = ($count >> 8) & 0xff ;
DBG ( 1 , "cp2155_block1 %06x %02x %04x %04x %04x %04x %04x %04x\n" , $addr , $v001 , scalar(@data) , $count,  $pgLO, $pgHI, (($addr >> 16) & 0xff), (($addr >> 8) & 0xff)) ;
	cp2155_set ( 0x71 , 0x01 ) ;
	cp2155_set ( 0x0230 , 0x11 ) ;
	cp2155_set ( 0x71 , $v001 ) ;
	cp2155_set ( 0x72 , $pgHI ) ;
	cp2155_set ( 0x73 , $pgLO ) ;
	cp2155_set ( 0x74 , ($addr >> 16) & 0xff ) ;
	cp2155_set ( 0x75 , ($addr >> 8) & 0xff ) ;
	cp2155_set ( 0x76 , $addr & 0xff ) ;
	cp2155_set ( 0x0239 , 0x40 ) ;
	cp2155_set ( 0x0238 , 0x89 ) ;
	cp2155_set ( 0x023c , 0x2f ) ;
	cp2155_set ( 0x0264 , 0x20 ) ;
	cp2155_write ( $count , @data ) ;
}

# size=0x0100
my @cp2155_block2_data = (
0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f,
0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f,
0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2a,0x2b,0x2c,0x2d,0x2e,0x2f,
0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x3b,0x3c,0x3d,0x3e,0x3f,
0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x4b,0x4c,0x4d,0x4e,0x4f,
0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5a,0x5b,0x5c,0x5d,0x5e,0x5f,
0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x6b,0x6c,0x6d,0x6e,0x6f,
0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x7b,0x7c,0x7d,0x7e,0x7f,
0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8a,0x8b,0x8c,0x8d,0x8e,0x8f,
0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0x9b,0x9c,0x9d,0x9e,0x9f,
0xa0,0xa1,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xab,0xac,0xad,0xae,0xaf,
0xb0,0xb1,0xb2,0xb3,0xb4,0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xbb,0xbc,0xbd,0xbe,0xbf,
0xc0,0xc1,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xcb,0xcc,0xcd,0xce,0xcf,
0xd0,0xd1,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xdb,0xdc,0xdd,0xde,0xdf,
0xe0,0xe1,0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xeb,0xec,0xed,0xee,0xef,
0xf0,0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa,0xfb,0xfc,0xfd,0xfe,0xff,
) ;

# size=0x0004
my @cp2155_slope01_data = (
0x80,0x25,0x01,0x00,
) ;

# size=0x0018
my @cp2155_slope02_data = (
0x80,0x25,0x0a,0x00,0x05,0x00,0x03,0x00,0x02,0x00,0x02,0x00,0x01,0x00,0x01,0x00,
0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,
) ;

# size=0x0048
my @cp2155_slope03_data = (
0x80,0x25,0x58,0x25,0x32,0x25,0x0b,0x25,0xe5,0x24,0xc0,0x24,0x9a,0x24,0x75,0x24,
0x50,0x24,0x2b,0x24,0x07,0x24,0xe3,0x23,0xbf,0x23,0x9c,0x23,0x79,0x23,0x56,0x23,
0x33,0x23,0x11,0x23,0xee,0x22,0xcd,0x22,0xab,0x22,0x8a,0x22,0x68,0x22,0x48,0x22,
0x27,0x22,0x07,0x22,0xe6,0x21,0xc7,0x21,0xa7,0x21,0x87,0x21,0x68,0x21,0x49,0x21,
0x2a,0x21,0x0c,0x21,0xee,0x20,0xd0,0x20,
) ;

# size=0x0018
my @cp2155_slope04_data = (
0x80,0x25,0x04,0x25,0x8c,0x24,0x18,0x24,0xa5,0x23,0x36,0x23,0xca,0x22,0x60,0x22,
0xf8,0x21,0x93,0x21,0x30,0x21,0xd0,0x20,
) ;

# size=0x0048
#my @cp2155_slope05_data = (
#0x80,0x25,0x58,0x25,0x32,0x25,0x0b,0x25,0xe5,0x24,0xc0,0x24,0x9a,0x24,0x75,0x24,
#0x50,0x24,0x2b,0x24,0x07,0x24,0xe3,0x23,0xbf,0x23,0x9c,0x23,0x79,0x23,0x56,0x23,
#0x33,0x23,0x11,0x23,0xee,0x22,0xcd,0x22,0xab,0x22,0x8a,0x22,0x68,0x22,0x48,0x22,
#0x27,0x22,0x07,0x22,0xe6,0x21,0xc7,0x21,0xa7,0x21,0x87,0x21,0x68,0x21,0x49,0x21,
#0x2a,0x21,0x0c,0x21,0xee,0x20,0xd0,0x20,
#) ;

# size=0x0018
#my @cp2155_slope06_data = (
#0x80,0x25,0x04,0x25,0x8c,0x24,0x18,0x24,0xa5,0x23,0x36,0x23,0xca,0x22,0x60,0x22,
#0xf8,0x21,0x93,0x21,0x30,0x21,0xd0,0x20,
#) ;

# size=0x0100
my @cp2155_slope07_data = (
0x80,0x25,0x80,0x25,0x80,0x25,0x80,0x25,0x80,0x25,0x80,0x25,0x80,0x25,0x80,0x25,
0x80,0x25,0x80,0x25,0x80,0x25,0x80,0x25,0x80,0x25,0x80,0x25,0x80,0x25,0x80,0x25,
0x80,0x25,0x80,0x25,0x80,0x25,0x80,0x25,0x80,0x25,0x80,0x25,0x80,0x25,0x80,0x25,
0x80,0x25,0x80,0x25,0x80,0x25,0x80,0x25,0x80,0x25,0x80,0x25,0x80,0x25,0x80,0x25,
0x80,0x25,0x80,0x25,0x80,0x25,0x80,0x25,0x80,0x25,0x80,0x25,0x80,0x25,0x80,0x25,
0x80,0x25,0x80,0x25,0x80,0x25,0x80,0x25,0x80,0x25,0x80,0x25,0x80,0x25,0x80,0x25,
0x80,0x25,0x80,0x25,0x80,0x25,0xf0,0x23,0x80,0x22,0x2c,0x21,0xf1,0x1f,0xcd,0x1e,
0xbd,0x1d,0xc0,0x1c,0xd2,0x1b,0xf4,0x1a,0x22,0x1a,0x5e,0x19,0xa4,0x18,0xf5,0x17,
0x4f,0x17,0xb2,0x16,0x1d,0x16,0x90,0x15,0x09,0x15,0x89,0x14,0x0e,0x14,0x9a,0x13,
0x2a,0x13,0xc0,0x12,0x59,0x12,0xf8,0x11,0x9a,0x11,0x3f,0x11,0xe9,0x10,0x96,0x10,
0x46,0x10,0xf8,0x0f,0xae,0x0f,0x66,0x0f,0x21,0x0f,0xde,0x0e,0x9e,0x0e,0x60,0x0e,
0x23,0x0e,0xe9,0x0d,0xb0,0x0d,0x7a,0x0d,0x44,0x0d,0x11,0x0d,0xdf,0x0c,0xaf,0x0c,
0x80,0x0c,0x52,0x0c,0x25,0x0c,0xfa,0x0b,0xd0,0x0b,0xa7,0x0b,0x80,0x0b,0x59,0x0b,
0x33,0x0b,0x0e,0x0b,0xea,0x0a,0xc8,0x0a,0xa5,0x0a,0x84,0x0a,0x64,0x0a,0x44,0x0a,
0x25,0x0a,0x07,0x0a,0xe9,0x09,0xcd,0x09,0xb0,0x09,0x95,0x09,0x7a,0x09,0x60,0x09,
0x46,0x09,0x2c,0x09,0x14,0x09,0xfc,0x08,0xe4,0x08,0xcd,0x08,0xb6,0x08,0xa0,0x08,
) ;

# size=0x0018
my @cp2155_slope08_data = (
0x80,0x25,0xc0,0x1c,0x4f,0x17,0x9a,0x13,0xe9,0x10,0xde,0x0e,0x44,0x0d,0xfa,0x0b,
0xea,0x0a,0x07,0x0a,0x46,0x09,0xa0,0x08,
) ;

# size=0x0100
my @cp2155_slope09_data = (
0x80,0x25,0x80,0x25,0x80,0x25,0x80,0x25,0x80,0x25,0x80,0x25,0x80,0x25,0x80,0x25,
0x80,0x25,0x80,0x25,0x80,0x25,0x80,0x25,0x80,0x25,0x80,0x25,0x80,0x25,0x80,0x25,
0x80,0x25,0x80,0x25,0x80,0x25,0x80,0x25,0x80,0x25,0x80,0x25,0x80,0x25,0x80,0x25,
0x80,0x25,0x80,0x25,0x80,0x25,0x80,0x25,0x80,0x25,0x80,0x25,0x80,0x25,0x80,0x25,
0x80,0x25,0x80,0x25,0x80,0x25,0x80,0x25,0x80,0x25,0x80,0x25,0x80,0x25,0x80,0x25,
0x80,0x25,0x80,0x25,0x80,0x25,0x80,0x25,0x80,0x25,0x80,0x25,0x80,0x25,0x80,0x25,
0x80,0x25,0x80,0x25,0x80,0x25,0xf0,0x23,0x80,0x22,0x2c,0x21,0xf1,0x1f,0xcd,0x1e,
0xbd,0x1d,0xc0,0x1c,0xd2,0x1b,0xf4,0x1a,0x22,0x1a,0x5e,0x19,0xa4,0x18,0xf5,0x17,
0x4f,0x17,0xb2,0x16,0x1d,0x16,0x90,0x15,0x09,0x15,0x89,0x14,0x0e,0x14,0x9a,0x13,
0x2a,0x13,0xc0,0x12,0x59,0x12,0xf8,0x11,0x9a,0x11,0x3f,0x11,0xe9,0x10,0x96,0x10,
0x46,0x10,0xf8,0x0f,0xae,0x0f,0x66,0x0f,0x21,0x0f,0xde,0x0e,0x9e,0x0e,0x60,0x0e,
0x23,0x0e,0xe9,0x0d,0xb0,0x0d,0x7a,0x0d,0x44,0x0d,0x11,0x0d,0xdf,0x0c,0xaf,0x0c,
0x80,0x0c,0x52,0x0c,0x25,0x0c,0xfa,0x0b,0xd0,0x0b,0xa7,0x0b,0x80,0x0b,0x59,0x0b,
0x33,0x0b,0x0e,0x0b,0xea,0x0a,0xc8,0x0a,0xa5,0x0a,0x84,0x0a,0x64,0x0a,0x44,0x0a,
0x25,0x0a,0x07,0x0a,0xe9,0x09,0xcd,0x09,0xb0,0x09,0x95,0x09,0x7a,0x09,0x60,0x09,
0x46,0x09,0x2c,0x09,0x14,0x09,0xfc,0x08,0xe4,0x08,0xcd,0x08,0xb6,0x08,0xa0,0x08,
) ;

# size=0x0018
my @cp2155_slope10_data = (
0x80,0x25,0xc0,0x1c,0x4f,0x17,0x9a,0x13,0xe9,0x10,0xde,0x0e,0x44,0x0d,0xfa,0x0b,
0xea,0x0a,0x07,0x0a,0x46,0x09,0xa0,0x08,
) ;

sub cp2155_block2
{
	my ( $addr ) = @_ ;
DBG ( 1 , "cp2155_block2 %06x\n" , $addr ) ;
	cp2155_block1 ( 0x16 , $addr , @cp2155_block2_data ) ;
}

sub cp2155_set_slope
{
	my ( $addr , @data ) = @_ ;
DBG ( 1 , "cp2155_set_slope %06x %04x\n" , $addr , scalar(@data) ) ;
	cp2155_block1 ( 0x14 , $addr , @data ) ;
}

# size=0x0075
my @cp2155_set_regs_data1 = (
0x00,0x00,0x00,0x69,0x00,0xa8,0x1d,0x00,0x00,0x10,0x00,0x00,0x00,0x2e,0x00,0x00,
0x00,     0x07,0x32,0x32,0x32,0x32,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x02,
0x00,0x03,0x11,0x15,0x15,0x15,0x04,0x07,0x28,0x29,0x08,0x09,0x02,0x06,0x12,0x12,
0x00,0x14,0x14,0x03,0x14,0x80,0x80,0x80,0x80,0x80,0x82,0x83,0x01,0x01,0x01,0x06,
0x01,0x06,0x00,0x00,0x02,0x00,0x00,0x06,0x00,0x00,0x02,0x83,0x7c,0x02,0x1c,0x9c,
0x38,0x28,0x28,0x27,0x27,0x25,0x25,0x21,0x21,0x1c,0x1c,0x16,0x16,0x0f,0x0f,0x08,
0x08,0x00,0x00,0x08,0x08,0x0f,0x0f,0x16,0x16,0x1c,0x1c,0x21,0x21,0x25,0x25,0x27,
0x27,0x01,0x01,0x11,0x00,
) ;

# size=0x0075
my @cp2155_set_regs_data2 = (
0x00,0x00,0x00,0x69,0x00,0xa8,0x1d,0x00,0x01,0x90,0x00,0x00,0x00,0x15,0xe0,0x04,
0x04,     0x07,0x32,0x32,0x32,0x32,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x02,
0x00,0x03,0x15,0x15,0x15,0x15,0x04,0x07,0x29,0x29,0x09,0x09,0x02,0x06,0x12,0x12,
0x00,0x05,0x05,0x01,0x05,0x83,0x83,0x83,0x83,0x83,0x83,0x83,0x12,0x12,0x12,0x06,
0x12,0x06,0x00,0x03,0xe8,0x00,0x00,0x06,0x00,0x00,0x02,0x80,0x7c,0x01,0x1c,0x9c,
0x38,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,
0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,
0x14,0x10,0x10,0x00,0x00,
) ;

# size=0x0075
my @cp2155_set_regs_data3 = (
0x00,0x00,0x00,0x69,0x14,0x58,0x1d,0x00,0x77,0xb0,0x00,0x00,0x00,0x15,0xe0,0x04,
0x04,     0x07,0x32,0x32,0x32,0x32,0x07,0xd0,0x07,0xd0,0x07,0xd0,0x00,0x01,0x02,
0x00,0x03,0x15,0x15,0x15,0x15,0x04,0x07,0x29,0x29,0x09,0x09,0x02,0x06,0x12,0x12,
0x00,0x05,0x05,0x01,0x05,0x83,0x83,0xc3,0xc3,0xc3,0xc1,0xc1,0x12,0x00,0x12,0x06,
0x12,0x06,0x00,0x00,0x04,0x00,0x01,0x80,0x00,0x00,0x02,0x01,0x01,0x01,0x1c,0x9c,
0x38,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,
0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,
0x14,0x00,0x00,0x00,0x00,
) ;

# size=0x0075
my @cp2155_set_regs_data4 = (
0x00,0x00,0x00,0x69,0x00,0xe8,0x1d,0x00,0x00,0x70,0x00,0x00,0x00,0x2e,0x00,0x04,
0x04,     0x07,0x32,0x32,0x32,0x32,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x02,
0x00,0x03,0x15,0x15,0x15,0x15,0x04,0x07,0x29,0x29,0x09,0x09,0x02,0x06,0x12,0x12,
0x03,0x05,0x05,0x03,0x05,0x41,0x61,0x21,0x21,0x25,0x25,0x25,0x40,0x40,0x40,0x06,
0x40,0x06,0x00,0x36,0xd0,0x00,0x00,0x06,0x00,0x00,0x02,0x83,0x7c,0x02,0x1c,0x9c,
0x38,0x28,0x28,0x27,0x27,0x25,0x25,0x21,0x21,0x1c,0x1c,0x16,0x16,0x0f,0x0f,0x08,
0x08,0x00,0x00,0x08,0x08,0x0f,0x0f,0x16,0x16,0x1c,0x1c,0x21,0x21,0x25,0x25,0x27,
0x27,0x02,0x02,0x22,0x00,
) ;

# size=0x0075
my @cp2155_set_regs_data5 = (
0x00,0x00,0x00,0x69,0x14,0x58,0x1d,0x00,0x77,0xb0,0x00,0x00,0x00,0x15,0xe0,0x04,
0x04,     0x07,0x32,0x32,0x32,0x32,0x07,0xd0,0x07,0xd0,0x07,0xd0,0x00,0x01,0x02,
0x00,0x03,0x15,0x15,0x15,0x15,0x04,0x07,0x29,0x29,0x09,0x09,0x02,0x06,0x12,0x12,
0x00,0x05,0x05,0x01,0x05,0x83,0x83,0xc3,0xc3,0xc3,0xc1,0xc1,0x12,0x00,0x12,0x06,
0x12,0x06,0x00,0x00,0x04,0x00,0x01,0x80,0x00,0x00,0x02,0x01,0x01,0x01,0x1c,0x9c,
0x38,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,
0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,
0x14,0x00,0x00,0x00,0x00,
) ;

# size=0x0075
my @cp2155_set_regs_data6 = (
0x00,0x00,0x00,0x69,0x00,0xe8,0x1d,0x00,0x00,0x70,0x00,0x00,0x00,0x2e,0x00,0x04,
0x04,     0x07,0x32,0x32,0x32,0x32,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x02,
0x00,0x03,0x15,0x15,0x15,0x15,0x04,0x07,0x29,0x29,0x09,0x09,0x02,0x06,0x12,0x12,
0x03,0x05,0x05,0x03,0x05,0x41,0x61,0x21,0x21,0x25,0x25,0x25,0x40,0x40,0x40,0x06,
0x40,0x06,0x00,0x36,0xd0,0x00,0x00,0x06,0x00,0x00,0x02,0x83,0x7c,0x02,0x1c,0x9c,
0x38,0x28,0x28,0x27,0x27,0x25,0x25,0x21,0x21,0x1c,0x1c,0x16,0x16,0x0f,0x0f,0x08,
0x08,0x00,0x00,0x08,0x08,0x0f,0x0f,0x16,0x16,0x1c,0x1c,0x21,0x21,0x25,0x25,0x27,
0x27,0x02,0x02,0x22,0x00,
) ;

# size=0x0075
my @cp2155_set_regs_nr = (
0x07,0x07,0x08,0x09,0x0a,0x0b,0xa0,0xa1,0xa2,0xa3,0x64,0x65,0x61,0x62,0x63,0x50,
0x50,     0x51,0x5a,0x5b,0x5c,0x5d,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5e,
0x5f,0x5f,0x60,0x60,0x60,0x60,0x50,0x51,0x81,0x81,0x82,0x82,0x83,0x84,0x80,0x80,
0xb0,0x10,0x10,0x9b,0x10,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x12,0x13,0x16,0x21,
0x22,0x20,0x1d,0x1e,0x1f,0x66,0x67,0x68,0x1a,0x1b,0x1c,0x15,0x14,0x17,0x43,0x44,
0x45,0x23,0x33,0x24,0x34,0x25,0x35,0x26,0x36,0x27,0x37,0x28,0x38,0x29,0x39,0x2a,
0x3a,0x2b,0x3b,0x2c,0x3c,0x2d,0x3d,0x2e,0x3e,0x2f,0x3f,0x30,0x40,0x31,0x41,0x32,
0x42,0xca,0xca,0xca,0x18,
) ;

sub cp2155_set_regs
{
	my ( @data ) = @_ ;
DBG ( 1 , "cp2155_set_regs\n" ) ;

	for ( my $i = 0 ; $i < 0x0075 ; $i ++ )
	{
		cp2155_set ( $cp2155_set_regs_nr[$i] , $data[$i] ) ;
	}
}

sub cp2155_block4
{
	my ( $v001 ) = @_ ;
DBG ( 1 , "cp2155_block4 %02x\n" , $v001 ) ;
	cp2155_set ( 0x90 , 0xd8 ) ;
	cp2155_set ( 0x90 , 0xd8 ) ;
	cp2155_set ( 0xb0 , $v001 ) ;
}

sub cp2155_block6
{
	my ( $v001 , $v002 ) = @_ ;
DBG ( 1 , "cp2155_block6 %02x %02x\n" , $v001 , $v002 ) ;
	cp2155_set ( 0x80 , $v001 ) ;
	cp2155_set ( 0x11 , $v002 ) ;
}

sub cp2155_block7
{
	my ( $v001 , $v002 , $v003 ) = @_ ;
DBG ( 1 , "cp2155_block7 %02x %02x %02x\n" , $v001 , $v002 , $v003 ) ;
	cp2155_block6 ( $v001 , $v002 ) ;
 	cp2155_set ( 0x9b , $v003 ) ;
}

sub cp2155_block8
{
DBG ( 1 , "cp2155_block8\n" ) ;
	cp2155_set ( 0x04 , 0x0c ) ;
	cp2155_set ( 0x05 , 0x00 ) ;
	cp2155_set ( 0x06 , 0x00 ) ;
}

sub cp2155_block9
{
DBG ( 1 , "cp2155_block9\n" ) ;
	cp2155_block2 ( 0x000000 ) ;
	cp2155_block2 ( 0x000100 ) ;
	cp2155_block2 ( 0x000200 ) ;
}

sub cp2155_motor
{
	my ( $v001 , $v002 ) = @_ ;
DBG ( 1 , "cp2155_motor %02x %02x\n" , $v001 , $v002 ) ;
  
	cp2155_set ( 0x10 , $v001 ) ;

	cp2155_get ( 0x4a , $value ) ;
DBG ( 1 , "0x4a: %02x\n" , $value ) ;
	cp2155_get ( 0x4b , $value ) ;
DBG ( 1 , "0x4b: %02x\n" , $value ) ;
	cp2155_get ( 0x4c , $value ) ;
DBG ( 1 , "0x4c: %02x\n" , $value ) ;

	cp2155_set ( 0x11 , $v002 ) ;
	cp2155_set ( 0x60 , 0x15 ) ;
	cp2155_set ( 0x80 , 0x12 ) ;
	cp2155_set ( 0x03 , 0x01 ) ;   # start motor
}

#------------------------------------------

#sub disp
#{
#	my ( $reg , $data ) = @_ ;
#	print dez2hex($reg) , '= ' , dez2hex($data) , ' ' , sprintf('%08b',$data) , "\n" ;
#}

#sub get
#{
#	my ( $reg ) = @_ ;
#	dispreg ( $reg ) ;
#}

#sub dispreg
#{
#	my ( $reg ) = @_ ;
#	my $data = 0 ;
#	cp2155_get ( $reg , $data ) ;
#	disp ( $reg , $data ) ;

#	if ( ( $reg & 0xff ) == 0x46 )
#	{
#		usleep ( 0.1 ) ;
#	}
#}

sub bin2dez
{
	my ( $data ) = @_ ;
	my @data = unpack ( 'C*' , $data ) ;
	return ( @data )
}

sub dez2bin
{
	my ( @data ) = @_ ;
	my $data = pack ( 'C*' , @data ) ;
	return ( $data )
}

sub hex2dez
{
	my ( $txt ) = @_ ;
	$txt =~ s![\da-f]+:!!ig ;
	$txt =~ s!\A\s+!! ;
	$txt =~ s!\s+\Z!! ;
	my @data = split ( m!\s+! , $txt ) ;

	foreach ( @data )
	{
		$_ = hex ( $_ ) ;
	}

	return ( @data ) ;
}

sub dez2hex
{
	my ( @line ) = @_ ;
	my $anz = @line ;
	my $line = sprintf ( '%02x ' x $anz , @line ) ;
	return ( $line ) ;
}

sub bin2hex
{
	my ( $data ) = @_ ;
	my @data = bin2dez ( $data ) ;
	my $txt = dez2hex ( @data ) ;
	return ( $txt ) ;
}

sub hex2bin
{
	my ( $txt ) = @_ ;
	my @data = hex2dez ( $txt ) ;
	my $data = dez2bin ( @data ) ;
	return ( $data ) ;
}

sub control_msg
{
	my ( $dev , $txt ) = @_ ;
	my ( $typ , $req , $Lv , $Hv , $Li , $Hi , $Ls , $Hs ) = hex2dez ( $txt ) ;
	my $value = $Hv*256 + $Lv ;
	my $index = $Hi*256 + $Li ;
	my $size = $Hs*256 + $Ls ;
	my $buffer = "\0" x $size ;
	my $retval = $dev -> control_msg ( $typ , $req , $value , $index , $buffer , $size , $timeout ) ;
	my @buffer = bin2dez ( $buffer ) ;
	return ( @buffer ) ;
}

sub printhex
{
	my ( @data ) = @_ ;

	while ( @data )
	{
		my @line = splice ( @data , 0 , 16 ) ;
		my $line = dez2hex ( @line ) ;
		print $line , "\n" ;
	}
}

#sub display
#{
#	my ( $dev , $key , $val , $txt ) = @_ ;
#	my $pad = ' ' x 22 ;
#	$key = substr ( $key . $pad , 0 , 22 ) ;

#	if ( $txt == 1 )
#	{
#		$txt = $dev -> get_string_simple ( $val ) ;
#	}

#	if ( $txt )
#	{
#		$txt = ' (' . $txt . ')' ;
#	}

#	print ( $key , $val , $txt , "\n" ) ;
#}

sub DBG
{
	my ( $level , $format , @msg ) = @_ ;
  
	if ( $level <= $debug )
	{
		printf ( $format , @msg ) ;
	}
}

sub usleep
{
	my ( $time ) = @_ ;
	select ( undef , undef , undef , $time ) ;
}

sub newopen
{
	my ( $fname ) = @_ ;
	local *FILEHANDLE ;
  
	unless ( open ( FILEHANDLE , $fname ) )
	{
		return ( undef ) ;
	}

	return ( *FILEHANDLE ) ;
}

#------------------------------------------

sub Wait
{
	while ( 1 )
	{
		usleep ( 0.2 ) ;
		cp2155_get ( 0x46 , $value ) ;
		DBG ( 1 , "home sensor: %02x\n" , $value ) ;

		if ( ( $value == 0x08 ) || ( $value == 0x00 ) )
		{
			last ( ) ;
		}
	}
}

sub read_buttons
{
	#check for pressed buttons
	cp2155_get ( 0x91 , $value ) ;
	DBG ( 1 , "0x91: %02x\n" , $value ) ;
}

# Wait until data ready
sub wait_for_data
{
	my $start_time = time() ;
	my $value = '' ;

DBG ( 12 , "waiting...\n" ) ;

	while ( 1 )
	{
		my $size = 0 ;

		if ( cp2155_get ( 0xa5 , $value ) ne 'SANE_STATUS_GOOD' )
		{
			return ( -1 ) ;
		}

		$size += $value ;

		if ( cp2155_get ( 0xa6 , $value ) ne 'SANE_STATUS_GOOD' )
		{
			return ( -1 ) ;
		}

		$size <<= 8 ;
		$size += $value ;

		if ( cp2155_get ( 0xa7 , $value ) ne 'SANE_STATUS_GOOD' )
		{
			return ( -1 ) ;
		}

		$size <<= 8 ;
		$size += $value ;

		if ( $size != 0 )
		{
			return ( 2 * $size ) ;
		}

		# Give it 5 seconds
		if ( ( time() - $start_time ) > 5 )
		{
DBG ( 1 , "wait_for_data: timed out (%ld)\n" , $size ) ;
			return ( -1 ) ;
		}

		usleep ( 0.004 ) ;
	}
}

sub go_home
{
	my $value = 0 ;
	cp2155_set ( 0x9b , 0x01 ) ;
	cp2155_get ( 0x46 , $value ) ;
DBG ( 1 , "0x46: %02x\n" , $value ) ;
	cp2155_get ( 0x46 , $value ) ;
DBG ( 1 , "0x46: %02x\n" , $value ) ;

	usleep ( 0.1 ) ;

	cp2155_block6 ( 0x12 , 0xc1 ) ;
	cp2155_get ( 0x46 , $value ) ;
DBG ( 1 , "0x46: %02x\n" , $value ) ;

	usleep ( 0.1 ) ;

	cp2155_block6 ( 0x12 , 0xc1 ) ;

	cp2155_set ( 0x9b , 0x01 ) ;
	cp2155_get ( 0x46 , $value ) ;
DBG ( 1 , "0x46: %02x\n" , $value ) ;
	cp2155_get ( 0x46 , $value ) ;
DBG ( 1 , "0x46: %02x\n" , $value ) ;
	cp2155_get ( 0x46 , $value ) ;
DBG ( 1 , "0x46: %02x\n" , $value ) ;
	
	usleep ( 0.1 ) ;

	cp2155_block6 ( 0x12 , 0xc1 ) ;
	cp2155_set ( 0x01 , 0x29 ) ;
	cp2155_block8 ( ) ;
	cp2155_set ( 0x01 , 0x29 ) ;

	cp2155_block9 ( ) ;
	cp2155_set ( 0x90 , 0xc8 ) ;
	cp2155_set ( 0x90 , 0xc8 ) ;
	cp2155_set ( 0xb0 , 0x03 ) ;
	cp2155_set_regs ( @cp2155_set_regs_data6 ) ;

	cp2155_set_slope ( 0x030000 , @cp2155_slope09_data ) ;
	cp2155_set_slope ( 0x030200 , @cp2155_slope09_data ) ;
	cp2155_set_slope ( 0x030400 , @cp2155_slope10_data ) ;
	cp2155_set_slope ( 0x030600 , @cp2155_slope09_data ) ;
	cp2155_set_slope ( 0x030800 , @cp2155_slope10_data ) ;

	cp2155_motor ( 0x05 , 0x35 ) ;
	
	Wait ( ) ;

	cp2155_block6 ( 0x12 , 0x25 ) ;
	cp2155_set ( 0x80 , 0x12 ) ;
	cp2155_set ( 0x9b , 0x01 ) ;

	cp2155_get ( 0x46 , $value ) ;
DBG ( 1 , "0x46: %02x\n" , $value ) ;

	usleep ( 0.1 ) ;

	cp2155_block6 ( 0x12 , 0x25 ) ;
	return ( 0 ) ;
}

# Scanner init, called at calibration and scan time.
#   Returns:
#    1 if this was the first time the scanner was plugged in,
#    0 afterward, and
#   -1 on error. 
sub init
{
	my ( $scanner ) = @_ ;
	my $value = 0 ;
	my $result = 0 ;

	cp2155_get ( 0xd0 , $value ) ;
DBG ( 1 , "INIT: 0x0d: %02x\n" , $value ) ;
	# Detect if scanner is plugged in
	if ( $value != 0x81 )
	{
DBG ( 1 , "INIT: invalid value %02x\n" , $value ) ;
		return ( -1 ) ;
	}

#	usleep ( 1 ) ;

	cp2155_get ( 0x46 , $value ) ;
DBG ( 1 , "0x46: %02x\n" , $value ) ;
	# Scanner already initialized?
	if ( $value != 0x00 )
	{
		return ( 1 ) ;
	}
	
	#Scanner must be intialize
	cp2155_set ( 0x02 , 0x01 ) ;

#	usleep ( 0.013 ) ;

	cp2155_set ( 0x02 , 0x00 ) ;
	cp2155_set ( 0x01 , 0x00 ) ;
	cp2155_set ( 0x01 , 0x28 ) ;
	cp2155_set ( 0x90 , 0x4f ) ;
	cp2155_set ( 0x92 , 0xff ) ;

#	usleep ( 0.061 ) ;
	read_buttons ( ) ;

	cp2155_set ( 0x93 , 0x00 ) ;
	cp2155_set ( 0x91 , 0x1f ) ;
	cp2155_set ( 0x95 , 0x1f ) ;
	cp2155_set ( 0x97 , 0x1f ) ;
	cp2155_set ( 0x9b , 0x00 ) ;
	cp2155_set ( 0x9c , 0x07 ) ;
	cp2155_set ( 0x90 , 0x4d ) ;

#	usleep ( 0.060 ) ;

	cp2155_set ( 0x90 , 0xcd ) ;

#	usleep ( 0.062 ) ;

	cp2155_set ( 0x90 , 0xcc ) ;

#	usleep ( 0.063 ) ;

	cp2155_set ( 0x9b , 0x01 ) ;
	cp2155_set ( 0xa0 , 0x04 ) ;

#	usleep ( 0.203 ) ;

	cp2155_set ( 0xa0 , 0x05 ) ;
	cp2155_set ( 0x01 , 0x28 ) ;
	cp2155_set ( 0x04 , 0x0c ) ;
	cp2155_set ( 0x05 , 0x00 ) ;
	cp2155_set ( 0x06 , 0x00 ) ;
	cp2155_set ( 0x98 , 0x00 ) ;

	cp2155_set ( 0x98 , 0x00 ) ;
	cp2155_set ( 0x98 , 0x02 ) ;
	cp2155_set ( 0x99 , 0x28 ) ;
	cp2155_set ( 0x9a , 0x03 ) ;
	cp2155_set ( 0x80 , 0x10 ) ;
	cp2155_set ( 0x8d , 0x00 ) ;
	cp2155_set ( 0x8d , 0x04 ) ;

	cp2155_get ( 0x8b , $value ) ;
	cp2155_get ( 0x8b , $value ) ;
	cp2155_set ( 0x85 , 0x00 ) ;
	cp2155_set ( 0x87 , 0x00 ) ;
	cp2155_set ( 0x88 , 0x70 ) ;

	cp2155_get ( 0x8b , $value ) ;
	cp2155_get ( 0x8b , $value ) ;
	cp2155_set ( 0x85 , 0x03 ) ;
	cp2155_set ( 0x87 , 0x00 ) ;
	cp2155_set ( 0x88 , 0x28 ) ;

	cp2155_get ( 0x8b, $value ) ;
	cp2155_get ( 0x8b, $value ) ;
	cp2155_set ( 0x85, 0x06 ) ;
	cp2155_set ( 0x87, 0x00 ) ;
	cp2155_set ( 0x88, 0x28 ) ;

	for ( my $reg = 0 ; $reg < 0xff ; $reg ++ )
	{
		cp2155_get ( $reg , $value ) ;
	}

#	usleep ( 0.003 ) ;
	
	cp2155_set ( 0x60 , 0x01 ) ;
	cp2155_set ( 0x10 , 0x04 ) ;

	usleep ( 0.998 ) ;

	cp2155_set ( 0x9b , 0x01 ) ;
	cp2155_get ( 0x46 , $value ) ;
DBG ( 1 , "0x46: %02x\n" , $value ) ;
	
	cp2155_block7 ( 0x12 , 0x00 , 0x01 ) ;

#	usleep ( 0.300 ) ;
  
	cp2155_get ( 0x46 , $value ) ;
DBG ( 1 , "0x46: %02x\n" , $value ) ;
	cp2155_get ( 0x46 , $value ) ;
DBG ( 1 , "0x46: %02x\n" , $value ) ;
	cp2155_get ( 0x46 , $value ) ;
DBG ( 1 , "0x46: %02x\n" , $value ) ;
	
	#Query head position
	cp2155_block6 ( 0x12 , 0x00 ) ;
	cp2155_set ( 0x01 , 0x28 ) ;
	cp2155_block8 ( ) ;
	cp2155_set ( 0x01 , 0x29 ) ;
	cp2155_block9 ( ) ;
	
	cp2155_set_regs ( @cp2155_set_regs_data1 ) ;
	cp2155_set_slope ( 0x030000 , @cp2155_slope01_data ) ;
	cp2155_set_slope ( 0x030200 , @cp2155_slope01_data ) ;
	cp2155_set_slope ( 0x030400 , @cp2155_slope02_data ) ;
	cp2155_set_slope ( 0x030600 , @cp2155_slope01_data ) ;
	cp2155_set_slope ( 0x030800 , @cp2155_slope02_data ) ;

	cp2155_motor ( 0x15 , 0x93 ) ;
	cp2155_set ( 0x9b , 0x03 ) ;
    
	Wait ( ) ;

	cp2155_get ( 0x46 , $value ) ;
DBG ( 1 , "0x46: %02x\n" , $value ) ; 
	if ( $value == 0x00 )
	{
		go_home ( ) ;
	}
	
	usleep( 0.240 ) ;
	  
	cp2155_block6 ( 0x12 , 0x83 ) ;
	cp2155_set ( 0x01 , 0x29 ) ;	
	cp2155_block8 ( ) ;
	cp2155_set ( 0x01 , 0x29 ) ;	
	cp2155_block9 ( ) ;
	cp2155_block4 ( 0x00 ) ;
	
	cp2155_set_regs ( @cp2155_set_regs_data2 ) ;
	cp2155_set_slope ( 0x030000 , @cp2155_slope03_data ) ;
	cp2155_set_slope ( 0x030200 , @cp2155_slope03_data ) ;
	cp2155_set_slope ( 0x030400 , @cp2155_slope04_data ) ;
	cp2155_set_slope ( 0x030600 , @cp2155_slope03_data ) ;
	cp2155_set_slope ( 0x030800 , @cp2155_slope04_data ) ;

	cp2155_motor ( 0x05 , 0x93 ) ;
	
	Wait ( ) ;
	
DBG ( 1 , "INIT state: %0d\n" , $result ) ;
	return ( $result ) ;
}


# Scan and save the resulting image as r,g,b non-interleaved PPM file
sub do_scan
{
	my ( $scanner ) = @_ ;
	my $status = 'SANE_STATUS_GOOD' ;
	my $value = 0 ;

	my $fp = newopen ( '>' . $scanner->{'fname'} ) ;

	unless ( $fp )
	{
		my $errno = $! ;
DBG ( 1 , "err:%s when opening %s\n" , $errno , $scanner->{'fname'} ) ;
		return ( 'SANE_STATUS_IO_ERROR' ) ;
	}

#	if ( $scanner->{'flags'} & $FLG_PPM_HEADER )
#	{
		printf $fp ( "P6\n%ld %ld\n255\n" , $scanner->{'width'} , $scanner->{'height'} ) ;
#	}

	my $width = $scanner->{'width'} ;
	my $height = $scanner->{'height'} ;
	# set width to next multiple of 0x10
	while ( $width & 0x0f )
	{
		$width ++ ;
	}

	my $x_start = $LEFT_EDGE ;
	my $x_end = $LEFT_EDGE + ($width * 600 / $scanner->{'resolution'}) - 1 ;
	$x_start = $LEFT_EDGE + $scanner->{'x1'} ;
	$x_end = $x_start + ($width * 600 / $scanner->{'resolution'}) - 1 ;
#	my $y_len = 3 * $height ;
	my $y_len = 3 * ( $TOP_EDGE + $scanner->{'y2'} ) ;
DBG ( 1 , "DEBUG 1: %ld %ld - %ld %ld %ld\n" , $width , $height , $x_start , $x_end , $y_len ) ;

	cp2155_block7 ( 0x12 , 0x83 , 0x01 ) ;
	
	#usleep ( 0.03 ) ;
	
	cp2155_get ( 0x46 , $value ) ;
	cp2155_get ( 0x46 , $value ) ;
	cp2155_get ( 0x46 , $value ) ;

	cp2155_block6 ( 0x12 , 0x83 ) ;
	cp2155_get ( 0x46 , $value ) ;

	cp2155_set ( 0x01 , 0x29 ) ;
	cp2155_block8 ( ) ;
	cp2155_set ( 0x01 , 0x29 ) ;

	cp2155_block9 ( ) ;
	cp2155_block4 ( 0x00 ) ;

	my @readbuf = @cp2155_set_regs_data5 ;
	#$readbuf[2] = ($x_start >> 8) & 0xff ;
	#$readbuf[3] = ($x_start) & 0xff ;
	#$readbuf[4] = ($x_end >> 8) & 0xff ;
	#$readbuf[5] = ($x_end) & 0xff ;
	#$readbuf[69] = ($y_len >> 16) & 0xff ;
	#$readbuf[70] = ($y_len >> 8) & 0xff ;
	#$readbuf[71] = ($y_len) & 0xff ;
#DBG ( 1 , "DEBUG 3: %02x %02x - %02x %02x - %02x %02x %02x\n" ,
	#$readbuf[2], $readbuf[3], $readbuf[4], $readbuf[5], $readbuf[69], $readbuf[70], $readbuf[71] ) ;
	cp2155_set_regs ( @readbuf ) ;

	cp2155_set_slope ( 0x030000 , @cp2155_slope03_data ) ;
	cp2155_set_slope ( 0x030200 , @cp2155_slope03_data ) ;
	cp2155_set_slope ( 0x030400 , @cp2155_slope04_data ) ;
	cp2155_set_slope ( 0x030600 , @cp2155_slope03_data ) ;
	cp2155_set_slope ( 0x030800 , @cp2155_slope04_data ) ;

	cp2155_set ( 0x9b , 0x00 ) ;

	cp2155_motor ( 0x05 , 0xd1 ) ;

	cp2155_set ( 0x71 , 0x01 ) ;
	cp2155_set ( 0x0230 , 0x11 ) ;
	cp2155_set ( 0x71 , 0x18 ) ;
	cp2155_set ( 0x72 , 0x00 ) ;
	cp2155_set ( 0x73 , 0x10 ) ;
	cp2155_set ( 0x0239 , 0x40 ) ;
	cp2155_set ( 0x0238 , 0x89 ) ;
	cp2155_set ( 0x023c , 0x2f ) ;
	cp2155_set ( 0x0264 , 0x20 ) ;
	
	$value = 0x00;
	while ( 1 )
	{
		cp2155_get ( 0xa5 , $value ) ;
		DBG ( 1 , "0xa5: %02x\n" , $value ) ;
		cp2155_get ( 0xa6 , $value ) ;
		DBG ( 1 , "0xa6: %02x\n" , $value ) ;
		cp2155_get ( 0xa7 , $value ) ;			
		DBG ( 1 , "0xa7: %02x\n" , $value ) ;
		if ( $value == 0xe8 )
		{
			last ( ) ;
		}
	}

	my $readbuf = '' ;
	my $linebuf = "\0" x (3*$width) ;
	my $line = 0 ;
	my $slot = 0 ;
	my $ptr = 0 ;

	my $y_min = $scanner->{'y1'} + $TOP_EDGE ;
	my $y_max = $scanner->{'y2'} + $TOP_EDGE ;

	# Data coming back is "width" bytes Red data, width bytes Green,
	# width bytes Blue, repeat for "height" lines.
	while ( $line < $y_max )
	{
		my $datasize = wait_for_data ( ) ;
		if ( $datasize < 0 )
		{
#DBG ( 1 , "no data\n" ) ;
			last ( ) ;
    	}
    	
		if ( $datasize > 60000 )
		{
			$datasize = 60000 ;
		}

#DBG ( 12 , "scanning %ld %ld %ld\n" , $line , $height , $datasize ) ;

		cp2155_set ( 0x72 , 0x35 ) ; #($datasize >> 8) & 0xff ) ;
		cp2155_set ( 0x73 , 0xd0 ) ; #($datasize) & 0xff ) ;

		my $status = cp2155_read ( $datasize , $readbuf ) ;

		if ( $status ne 'SANE_STATUS_GOOD' )
		{
			$status = 'SANE_STATUS_INVAL' ;
			last ( ) ;
		}

		# Contorsions to convert data from line-by-line RGB to byte-by-byte RGB,
		# without reading in the whole buffer first.  One image line is
		# constructed in buffer linebuf and written to temp file if complete.
		my $idx = 0 ;

		while ( $idx < $datasize )
		{
			substr ( $linebuf , $ptr , 1 ) = substr ( $readbuf , $idx , 1 ) ;
			$idx ++ ;
			$ptr += 3 ;
			
			# check if line is complete
			if ( $ptr >= 3*$width )
			{
				$slot ++ ;
				$ptr = $slot ;

				if ( $slot == 3 )
				{
					$slot = 0 ;
					$ptr = 0 ;

					if ( $line >= $y_min )
					{
						# use scanner->width instead of width to remove pad bytes
						print $fp ( substr ( $linebuf , 0 , 3 * $scanner->{'width'} ) ) ;
#DBG ( 6 , "%ld: line %ld written...\n" , $line , $line-$TOP_EDGE ) ;
					}
					else
					{
#DBG ( 6 , "%ld: skipping line...\n" , $line ) ;
					}
					$line ++ ;
				}
			}
		}
	}

	close ( $fp ) ;
#DBG ( 6 , "created scan file %s\n" , $scanner->{'fname'} ) ;

	return ( $status ) ;
}

# Scan sequence
# resolution is 75,150,300,600,1200
# scan coordinates in 600-dpi pixels
sub scan
{
	my ( $scanner ) = @_ ;
	my $status = 'SANE_STATUS_GOOD' ;

	my $buf = '' ;

	# create gamma table
#	buf = malloc (0x400);

#	for ( my $i = 0 ; $i < 0x0400 ; $i ++ )
#	{
#		# gamma calculation by M.Reinelt <reinelt@eunet.at>
#		$buf[$i] = 255.0 * exp(log(($i + 0.5) / 1023.0) / $scanner->{'gamma'}) + 0.5 ;
#	}
	# Gamma R, write and verify
#	free (buf);

	# Resolution: dpi 75(ie) 100,150(1c) 200,300(1a) 600,1200(18)
	if ( $scanner->{'resolution'} == 75   ) { }
	elsif ( $scanner->{'resolution'} == 150  ) { }
	elsif ( $scanner->{'resolution'} == 300  ) { }
	elsif ( $scanner->{'resolution'} == 600  ) { }
	elsif ( $scanner->{'resolution'} == 1200 ) { }
	else
	{
		$scanner->{'resolution'} = 75 ;
	}

	$scanner->{'width'} = ($scanner->{'x2'} - $scanner->{'x1'}) * $scanner->{'resolution'} / 600 ;
	$scanner->{'height'} = ($scanner->{'y2'} - $scanner->{'y1'}) * $scanner->{'resolution'} / 600 ;
	$scanner->{'flags'} = 0 ;

DBG ( 1 , "dpi=%d\n" , $scanner->{'resolution'} ) ;
DBG ( 1 , "x1=%d y1=%d\n" , $scanner->{'x1'} , $scanner->{'y1'} ) ;
DBG ( 1 , "x2=%d y2=%d\n" , $scanner->{'x2'} , $scanner->{'y2'} ) ;
DBG ( 1 , "width=%ld height=%ld\n" , $scanner->{'width'} , $scanner->{'height'} ) ;

	$status = do_scan ( $scanner ) ;

	if ( $status ne 'SANE_STATUS_GOOD' )
	{
DBG ( 1 , "Failure on line of %s: %d\n" , $__FILE__, $__LINE__ ) ;
	}
	else
	{
		go_home ( ) ;
	}

	return ( $status ) ;
}

#------------------------------------------

sub CANON_finish_scan
{
	my ( $scanner ) = @_ ;
DBG ( 3 , "CANON_finish_scan:\n" ) ;

	if ( $scanner->{'fp'} )
	{
		close ( $scanner->{'fp'} ) ;
	}

	$scanner->{'fp'} = undef ;

	# remove temp file
	if ( $scanner->{'fname'} )
	{
DBG ( 4 , "removing temp file %s\n" , $scanner->{'fname'} ) ;
		unlink ( $scanner->{'fname'} ) ;
	}

	$scanner->{'fname'} = undef ;
	return ( 'SANE_STATUS_GOOD' ) ;
}

sub CANON_start_scan
{
	my ( $scanner ) = @_ ;
	
DBG ( 3 , "CANON_start_scan called\n" ) ;

	# choose a temp file name for scan data
	$scanner->{'fname'} = '/tmp/scan.XXXXXX' ;

#	if (!mktemp (scanner->fname))
#	{
#		return SANE_STATUS_IO_ERROR;
#	}

	# check if calibration needed
	$result = init ( $scanner ) ;
	if ( $result < 0 )
	{
DBG ( 1 , "Can't talk on USB.\n" ) ;
		return ( 'SANE_STATUS_IO_ERROR' ) ;
	}
	
#	if ( ( $result == 1 ) || (scanner->flags & FLG_FORCE_CAL))
#	{
#		calibrate ($scanner);
#	}
  
	# do the scan
	my $status = scan ( $scanner ) ;

	if ( $status ne 'SANE_STATUS_GOOD' )
	{
		CANON_finish_scan ( $scanner ) ;
		return ( $status ) ;
	}

	# read the temp file back out
	$scanner->{'fp'} = newopen ( '<' . $scanner->{'fname'} ) ;
DBG ( 4 , "reading %s\n" , $scanner->{'fname'} ) ;

	unless ( $scanner->{'fp'} )
	{
DBG ( 1 , "cannot open %s\n" , $scanner->{'fname'} ) ;
		return ( 'SANE_STATUS_IO_ERROR' ) ;
	}
  
	return ( 'SANE_STATUS_GOOD' ) ;
}

sub CANON_OpenDevice
{
	$usb = Device::USB -> new() ;
	$dev = $usb -> find_device ( 0x04a9 , 0x2225 ) ;
	$cfg = $dev -> get_configuration ( 0 ) ;

	$cfgval = $cfg -> bConfigurationValue() ;
	$err = $dev -> set_configuration ( $cfgval ) ;
	print $err,"\n";

	$dev -> open() ;
	print 'libusb:xxx:' , $dev -> filename() , "\n" ;	
}

sub CANON_CloseDevice
{
	$usb = undef ;
}

#------------------------------------------

my %scanner = ( 'x1' , 0 , 'y1' , 0 , 'x2' , 900 , 'y2' , 1200 , 'resolution' , 300 ) ;
CANON_OpenDevice ;
CANON_start_scan ( \%scanner ) ;
open ( OUTDAT , '>/tmp/scan-output.ppm' ) ;
my $buf = '' ;
while ( read ( $scanner{'fp'} , $buf , 16384 ) )
{
  print OUTDAT $buf ;
}
CANON_finish_scan ( \%scanner ) ;
close ( OUTDAT ) ;
CANON_CloseDevice ;
1 ;
#cp2155_set_slope ( 0x030000 , @cp2155_slope03_data )

