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

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

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

# at 600 dpi
my $CANON_MAX_WIDTH = 5100 ;	# 8.5in		(Letter: 8.5 x 11.0in)
my $CANON_MAX_HEIGHT = 7020 ;	# 11.7in	(DIN A4: 8.3 x 11.7in)
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\n" ) ;
    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 = ' ' ;
  my $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 ;
  my $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\n" , $addr , $v001 , scalar(@data) , $count ) ;
  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=0x0140
my @cp2155_slope01_data = (
0x80,0x25,0x74,0x25,0x68,0x25,0x5c,0x25,0x51,0x25,0x45,0x25,0x3a,0x25,0x2e,0x25,
0x23,0x25,0x17,0x25,0x0c,0x25,0x00,0x25,0xf5,0x24,0xea,0x24,0xde,0x24,0xd3,0x24,
0xc8,0x24,0xbc,0x24,0xb1,0x24,0xa6,0x24,0x9b,0x24,0x90,0x24,0x85,0x24,0x79,0x24,
0x6e,0x24,0x63,0x24,0x58,0x24,0x4d,0x24,0x42,0x24,0x37,0x24,0x2c,0x24,0x22,0x24,
0x17,0x24,0x0c,0x24,0x01,0x24,0xf6,0x23,0xec,0x23,0xe1,0x23,0xd6,0x23,0xcb,0x23,
0xc1,0x23,0xb6,0x23,0xab,0x23,0xa1,0x23,0x96,0x23,0x8c,0x23,0x81,0x23,0x77,0x23,
0x6c,0x23,0x62,0x23,0x57,0x23,0x4d,0x23,0x43,0x23,0x38,0x23,0x2e,0x23,0x24,0x23,
0x19,0x23,0x0f,0x23,0x05,0x23,0xfb,0x22,0xf1,0x22,0xe6,0x22,0xdc,0x22,0xd2,0x22,
0xc8,0x22,0xbe,0x22,0xb4,0x22,0xaa,0x22,0xa0,0x22,0x96,0x22,0x8c,0x22,0x82,0x22,
0x78,0x22,0x6e,0x22,0x64,0x22,0x5b,0x22,0x51,0x22,0x47,0x22,0x3d,0x22,0x33,0x22,
0x2a,0x22,0x20,0x22,0x16,0x22,0x0d,0x22,0x03,0x22,0xf9,0x21,0xf0,0x21,0xe6,0x21,
0xdd,0x21,0xd3,0x21,0xc9,0x21,0xc0,0x21,0xb7,0x21,0xad,0x21,0xa4,0x21,0x9a,0x21,
0x91,0x21,0x87,0x21,0x7e,0x21,0x75,0x21,0x6b,0x21,0x62,0x21,0x59,0x21,0x50,0x21,
0x46,0x21,0x3d,0x21,0x34,0x21,0x2b,0x21,0x22,0x21,0x18,0x21,0x0f,0x21,0x06,0x21,
0xfd,0x20,0xf4,0x20,0xeb,0x20,0xe2,0x20,0xd9,0x20,0xd0,0x20,0xc7,0x20,0xbe,0x20,
0xb5,0x20,0xac,0x20,0xa3,0x20,0x9b,0x20,0x92,0x20,0x89,0x20,0x80,0x20,0x77,0x20,
0x6f,0x20,0x66,0x20,0x5d,0x20,0x54,0x20,0x4c,0x20,0x43,0x20,0x3a,0x20,0x32,0x20,
0x29,0x20,0x20,0x20,0x18,0x20,0x0f,0x20,0x07,0x20,0xfe,0x1f,0xf6,0x1f,0xed,0x1f,
0xe5,0x1f,0xdc,0x1f,0xd4,0x1f,0xcb,0x1f,0xc3,0x1f,0xbb,0x1f,0xb2,0x1f,0xaa,0x1f,
0xa1,0x1f,0x99,0x1f,0x91,0x1f,0x89,0x1f,0x80,0x1f,0x78,0x1f,0x70,0x1f,0x68,0x1f,
) ;

# size=0x0018
my @cp2155_slope02_data = (
0x80,0x25,0xd9,0x24,0x38,0x24,0x9d,0x23,0x07,0x23,0x75,0x22,0xe9,0x21,0x60,0x21,
0xdc,0x20,0x5c,0x20,0xe0,0x1f,0x68,0x1f,
) ;

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

# size=0x0018
my @cp2155_slope04_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=0x0040
my @cp2155_slope05_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,
) ;

# size=0x0018
my @cp2155_slope06_data = (
0x80,0x25,0x93,0x21,0x65,0x1e,0xc3,0x1b,0x8d,0x19,0xab,0x17,0x0a,0x16,0xa0,0x14,
0x61,0x13,0x46,0x12,0x4a,0x11,0x68,0x10,
) ;

# size=0x0100
my @cp2155_slope07_data = (
0x00,0x32,0x00,0x32,0x00,0x32,0x00,0x32,0x00,0x32,0x00,0x32,0x00,0x32,0x00,0x32,
0x00,0x32,0x00,0x32,0x00,0x32,0x00,0x32,0x00,0x32,0x00,0x32,0x00,0x32,0x00,0x32,
0x00,0x32,0x00,0x32,0x00,0x32,0x00,0x32,0x00,0x32,0x00,0x32,0x00,0x32,0x00,0x32,
0x00,0x32,0x00,0x32,0x00,0x32,0x00,0x32,0x00,0x32,0x00,0x32,0x00,0x32,0x00,0x32,
0x00,0x32,0x00,0x32,0x00,0x32,0x00,0x32,0x00,0x32,0x00,0x32,0x00,0x32,0x00,0x32,
0x00,0x32,0x00,0x32,0x00,0x32,0x00,0x32,0x00,0x32,0x00,0x32,0x00,0x32,0x00,0x32,
0x00,0x32,0xbc,0x30,0x89,0x2f,0x64,0x2e,0x4d,0x2d,0x43,0x2c,0x45,0x2b,0x52,0x2a,
0x69,0x29,0x8a,0x28,0xb5,0x27,0xe8,0x26,0x23,0x26,0x66,0x25,0xaf,0x24,0x00,0x24,
0x57,0x23,0xb5,0x22,0x17,0x22,0x80,0x21,0xee,0x20,0x60,0x20,0xd7,0x1f,0x53,0x1f,
0xd3,0x1e,0x57,0x1e,0xde,0x1d,0x6a,0x1d,0xf9,0x1c,0x8b,0x1c,0x20,0x1c,0xb9,0x1b,
0x54,0x1b,0xf3,0x1a,0x93,0x1a,0x37,0x1a,0xdd,0x19,0x85,0x19,0x30,0x19,0xdd,0x18,
0x8c,0x18,0x3d,0x18,0xf0,0x17,0xa5,0x17,0x5c,0x17,0x14,0x17,0xce,0x16,0x8a,0x16,
0x47,0x16,0x06,0x16,0xc7,0x15,0x88,0x15,0x4b,0x15,0x10,0x15,0xd6,0x14,0x9d,0x14,
0x65,0x14,0x2e,0x14,0xf9,0x13,0xc4,0x13,0x91,0x13,0x5f,0x13,0x2d,0x13,0xfd,0x12,
0xce,0x12,0x9f,0x12,0x72,0x12,0x45,0x12,0x19,0x12,0xee,0x11,0xc4,0x11,0x9a,0x11,
0x71,0x11,0x49,0x11,0x22,0x11,0xfb,0x10,0xd5,0x10,0xb0,0x10,0x8c,0x10,0x68,0x10,
) ;

# size=0x0018
my @cp2155_slope08_data = (
0x00,0x32,0x27,0x2a,0x6f,0x24,0x15,0x20,0xa8,0x1c,0xe5,0x19,0x9e,0x17,0xb5,0x15,
0x16,0x14,0xb0,0x12,0x79,0x11,0x68,0x10,
) ;

# size=0x01f4
my @cp2155_slope09_data = (
0x80,0x25,0x00,0x25,0x84,0x24,0x0b,0x24,0x96,0x23,0x23,0x23,0xb3,0x22,0x46,0x22,
0xdb,0x21,0x73,0x21,0x0e,0x21,0xab,0x20,0x4a,0x20,0xeb,0x1f,0x8f,0x1f,0x34,0x1f,
0xdc,0x1e,0x85,0x1e,0x31,0x1e,0xde,0x1d,0x8d,0x1d,0x3e,0x1d,0xf0,0x1c,0xa4,0x1c,
0x59,0x1c,0x10,0x1c,0xc9,0x1b,0x83,0x1b,0x3e,0x1b,0xfa,0x1a,0xb8,0x1a,0x77,0x1a,
0x38,0x1a,0xf9,0x19,0xbc,0x19,0x80,0x19,0x44,0x19,0x0a,0x19,0xd1,0x18,0x99,0x18,
0x62,0x18,0x2c,0x18,0xf7,0x17,0xc3,0x17,0x8f,0x17,0x5d,0x17,0x2b,0x17,0xfa,0x16,
0xca,0x16,0x9b,0x16,0x6c,0x16,0x3e,0x16,0x11,0x16,0xe5,0x15,0xb9,0x15,0x8e,0x15,
0x64,0x15,0x3a,0x15,0x11,0x15,0xe9,0x14,0xc1,0x14,0x9a,0x14,0x73,0x14,0x4d,0x14,
0x27,0x14,0x02,0x14,0xde,0x13,0xba,0x13,0x96,0x13,0x74,0x13,0x51,0x13,0x2f,0x13,
0x0d,0x13,0xec,0x12,0xcc,0x12,0xab,0x12,0x8c,0x12,0x6c,0x12,0x4d,0x12,0x2f,0x12,
0x11,0x12,0xf3,0x11,0xd5,0x11,0xb8,0x11,0x9c,0x11,0x80,0x11,0x64,0x11,0x48,0x11,
0x2d,0x11,0x12,0x11,0xf7,0x10,0xdd,0x10,0xc3,0x10,0xa9,0x10,0x90,0x10,0x77,0x10,
0x5e,0x10,0x46,0x10,0x2e,0x10,0x16,0x10,0xfe,0x0f,0xe7,0x0f,0xd0,0x0f,0xb9,0x0f,
0xa2,0x0f,0x8c,0x0f,0x76,0x0f,0x60,0x0f,0x4b,0x0f,0x35,0x0f,0x20,0x0f,0x0b,0x0f,
0xf7,0x0e,0xe2,0x0e,0xce,0x0e,0xba,0x0e,0xa6,0x0e,0x92,0x0e,0x7f,0x0e,0x6c,0x0e,
0x59,0x0e,0x46,0x0e,0x33,0x0e,0x21,0x0e,0x0f,0x0e,0xfd,0x0d,0xeb,0x0d,0xd9,0x0d,
0xc8,0x0d,0xb6,0x0d,0xa5,0x0d,0x94,0x0d,0x83,0x0d,0x73,0x0d,0x62,0x0d,0x52,0x0d,
0x41,0x0d,0x31,0x0d,0x22,0x0d,0x12,0x0d,0x02,0x0d,0xf3,0x0c,0xe3,0x0c,0xd4,0x0c,
0xc5,0x0c,0xb6,0x0c,0xa7,0x0c,0x99,0x0c,0x8a,0x0c,0x7c,0x0c,0x6e,0x0c,0x60,0x0c,
0x52,0x0c,0x44,0x0c,0x36,0x0c,0x28,0x0c,0x1b,0x0c,0x0d,0x0c,0x00,0x0c,0xf3,0x0b,
0xe6,0x0b,0xd9,0x0b,0xcc,0x0b,0xbf,0x0b,0xb3,0x0b,0xa6,0x0b,0x9a,0x0b,0x8e,0x0b,
0x81,0x0b,0x75,0x0b,0x69,0x0b,0x5d,0x0b,0x52,0x0b,0x46,0x0b,0x3a,0x0b,0x2f,0x0b,
0x23,0x0b,0x18,0x0b,0x0d,0x0b,0x02,0x0b,0xf6,0x0a,0xeb,0x0a,0xe1,0x0a,0xd6,0x0a,
0xcb,0x0a,0xc0,0x0a,0xb6,0x0a,0xab,0x0a,0xa1,0x0a,0x97,0x0a,0x8c,0x0a,0x82,0x0a,
0x78,0x0a,0x6e,0x0a,0x64,0x0a,0x5a,0x0a,0x50,0x0a,0x47,0x0a,0x3d,0x0a,0x33,0x0a,
0x2a,0x0a,0x20,0x0a,0x17,0x0a,0x0e,0x0a,0x04,0x0a,0xfb,0x09,0xf2,0x09,0xe9,0x09,
0xe0,0x09,0xd7,0x09,0xce,0x09,0xc6,0x09,0xbd,0x09,0xb4,0x09,0xab,0x09,0xa3,0x09,
0x9a,0x09,0x92,0x09,0x8a,0x09,0x81,0x09,0x79,0x09,0x71,0x09,0x69,0x09,0x61,0x09,
0x59,0x09,0x51,0x09,0x49,0x09,0x41,0x09,0x39,0x09,0x31,0x09,0x29,0x09,0x22,0x09,
0x1a,0x09,0x12,0x09,0x0b,0x09,0x03,0x09,0xfc,0x08,0xf5,0x08,0xed,0x08,0xe6,0x08,
0xdf,0x08,0xd8,0x08,0xd0,0x08,0xc9,0x08,0xc2,0x08,0xbb,0x08,0xb4,0x08,0xad,0x08,
0xa6,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,0x01,0xe3,0x02,0x22,0x1d,0x00,0x01,0x90,0x00,0x00,0x00,0x29,0xe0,0x00,
0x00,0xfa,0x07,0xff,0xff,0xff,0xff,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x02,
0x00,0x03,0x01,0x01,0x01,0x01,0x04,0x07,0x30,0x31,0x10,0x11,0x01,0x05,0x12,0x12,
0x00,0x00,0x00,0x00,0x04,0x80,0x80,0x80,0x80,0x80,0x82,0x83,0x50,0x50,0x50,0x06,
0x50,0x06,0x00,0x00,0xd2,0x00,0x00,0x06,0x00,0x00,0x02,0x80,0x79,0x02,0x1c,0x9c,
0x38,0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,
0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,
0x05,0x00,0x00,0x00,0x00,
) ;

# size=0x0075
my @cp2155_set_regs_data2 = (
0x00,0x00,0x01,0xe3,0x02,0x22,0x1d,0x00,0x01,0x90,0x00,0x00,0x00,0x29,0xe0,0x04,
0x04,0xfa,0x07,0xff,0xff,0xff,0xff,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x02,
0x00,0x03,0x01,0x01,0x01,0x01,0x04,0x07,0x31,0x31,0x11,0x11,0x01,0x05,0x12,0x12,
0x00,0x05,0x0d,0x0d,0x0d,0x83,0x83,0x83,0x83,0x83,0x81,0x81,0x50,0x50,0x50,0x06,
0x50,0x06,0x00,0x00,0xd2,0x00,0x00,0x06,0x00,0x00,0x02,0x80,0x79,0x02,0x1c,0x9c,
0x38,0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,
0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,
0x05,0x00,0x00,0x00,0x00,
) ;

# size=0x0075
my @cp2155_set_regs_data3 = (
0x00,0x00,0x01,0xb3,0x01,0xd2,0x1d,0x00,0x00,0x10,0x00,0x00,0x00,0x2e,0x00,0x04,
0x04,0xf8,0x07,0xff,0xff,0xff,0xff,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x02,
0x00,0x03,0x01,0x01,0x01,0x01,0x04,0x07,0x31,0x31,0x11,0x11,0x01,0x05,0x12,0x12,
0x00,0x1d,0x15,0x15,0x15,0x81,0x81,0x81,0x81,0x81,0x83,0x83,0x01,0x01,0x01,0x06,
0x01,0x06,0x00,0x00,0x02,0x00,0x00,0x06,0x00,0x00,0x02,0x83,0x7c,0x02,0x1c,0x9c,
0x38,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,
0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,
0x0d,0x00,0x00,0x00,0x00,
) ;

# size=0x0075
my @cp2155_set_regs_data4 = (
0x00,0x00,0x01,0xb3,0x01,0xd2,0x1d,0x00,0x00,0x70,0x00,0x00,0x00,0x15,0xe0,0x04,
0x04,0xf8,0x07,0xff,0xff,0xff,0xff,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x02,
0x00,0x03,0x01,0x01,0x01,0x01,0x04,0x07,0x31,0x31,0x11,0x11,0x01,0x05,0x12,0x12,
0x01,0x05,0x05,0x05,0x05,0x83,0x83,0x83,0x83,0x83,0x83,0x83,0x10,0x10,0x10,0x06,
0x10,0x06,0x00,0x03,0xe8,0x00,0x00,0x06,0x00,0x00,0x02,0x83,0x7c,0x02,0x1c,0x9c,
0x38,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,
0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,
0x0d,0x00,0x00,0x00,0x00,
) ;

# size=0x0075
my @cp2155_set_regs_data5 = (
0x00,0x00,0x0b,0x3b,0x0c,0x3a,0x1d,0x00,0x03,0x10,0x00,0x00,0x00,0x15,0xe0,0x04,
0x04,0xf8,0x07,0xff,0xff,0xff,0xff,0x0a,0xf0,0x0a,0xf0,0x0a,0xf0,0x00,0x01,0x02,
0x00,0x03,0x01,0x01,0x01,0x01,0x04,0x07,0x31,0x31,0x11,0x11,0x01,0x05,0x12,0x12,
0x01,0x05,0x05,0x05,0x05,0x83,0x83,0xc3,0xc3,0xc3,0xc1,0xc1,0x40,0x00,0x40,0x06,
0x40,0x06,0x00,0x00,0x04,0x00,0x01,0x80,0x00,0x00,0x02,0x01,0x01,0x01,0x1c,0x9c,
0x38,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,
0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,
0x0a,0x00,0x00,0x00,0x00,
) ;

# size=0x0075
my @cp2155_set_regs_data6 = (
0x00,0x00,0x01,0xb3,0x02,0x32,0x1d,0x00,0x00,0x70,0x00,0x00,0x00,0x2e,0x00,0x04,
0x04,0xf8,0x07,0xff,0xff,0xff,0xff,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x02,
0x00,0x03,0x01,0x01,0x01,0x01,0x04,0x07,0x31,0x31,0x11,0x11,0x01,0x05,0x12,0x12,
0x03,0x05,0x05,0x05,0x05,0x41,0x61,0x21,0x21,0x25,0x25,0x25,0x7d,0x7d,0x7d,0x06,
0x7d,0x06,0x00,0x36,0xd0,0x00,0x00,0x06,0x00,0x00,0x02,0x83,0x7c,0x02,0x1c,0x9c,
0x38,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,
0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,
0x0d,0x00,0x00,0x00,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,0x90,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,0x10,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 , 0xea ) ;
  cp2155_set ( 0x9b , 0x07 ) ;
  cp2155_set ( 0x9b , 0x05 ) ;
  cp2155_set ( 0x9b , 0x07 ) ;
  cp2155_set ( 0x9b , 0x05 ) ;
  cp2155_set ( 0x90 , 0xfa ) ;
  cp2155_set ( 0xb0 , $v001 ) ;
}

sub cp2155_block5
{
  my ( $v001 ) = @_ ;
  DBG ( 1 , "cp2155_block5 %02x\n" , $v001 ) ;
  cp2155_set ( 0x90 , 0xe8 ) ;
  cp2155_set ( 0x9b , 0x06 ) ;
  cp2155_set ( 0x9b , 0x04 ) ;
  cp2155_set ( 0x90 , 0xf8 ) ;
  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 ) ;

  dispreg ( 0x4a ) ;    # FIXME do something with this data
  dispreg ( 0x4b ) ;
  dispreg ( 0x4c ) ;

  cp2155_set ( 0x11 , $v002 ) ;
  cp2155_set ( 0x60 , 0x01 ) ;
  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 read_buttons
{
  dispreg ( 0x91 ) ;   # check for pressed buttons
}

# 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
{
  cp2155_set ( 0x90 , 0xf8 ) ;
  dispreg ( 0x46 ) ;   # 03
  dispreg ( 0x46 ) ;   # 00
  usleep ( 0.1 ) ;

  cp2155_block6 ( 0x12 , 0xc1 ) ;
  dispreg ( 0x46 ) ;   # 00
  usleep ( 0.1 ) ;

  cp2155_block6 ( 0x12 , 0xc1 ) ;

  cp2155_set ( 0x90 , 0xf8 ) ;
  dispreg ( 0x46 ) ;   # 00
  usleep ( 0.1 ) ;

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

  cp2155_block9 ( ) ;
  cp2155_block5 ( 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 ) ;
  my $value = 0 ;

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

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

  cp2155_block6 ( 0x12 , 0x25 ) ;
  cp2155_set ( 0x80 , 0x12 ) ;
  dispreg ( 0x46 ) ;   # 08
  usleep ( 0.1 ) ;

  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 ;

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

  my $result = 0 ;
  cp2155_get ( 0x46 , $value ) ;
  # Scanner already initialized?
  if ( $value == 0x00 )
  {
    cp2155_set ( 0x02 , 0x01 ) ;
    usleep ( 0.013 ) ;
    cp2155_set ( 0x02 , 0x00 ) ;

    cp2155_set ( 0x01 , 0x00 ) ;
    cp2155_set ( 0x01 , 0x28 ) ;
    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 ) ;
    $result = 1 ;
  }
  # Scan without init starts here
  cp2155_set ( 0x90 , 0x27 ) ;
  cp2155_set ( 0x92 , 0xf7 ) ;
  cp2155_set ( 0x94 , 0xf7 ) ;
  usleep ( 0.060 ) ;
  cp2155_set ( 0x93 , 0x00 ) ;
  cp2155_set ( 0x91 , 0x1f ) ;
  cp2155_set ( 0x95 , 0x0f ) ;
  cp2155_set ( 0x97 , 0x0f ) ;
  cp2155_set ( 0x9b , 0x00 ) ;
  cp2155_set ( 0x9c , 0x07 ) ;
  cp2155_set ( 0x90 , 0xf0 ) ;
  usleep ( 0.055 ) ;
  cp2155_set ( 0x9b , 0x04 ) ;
  cp2155_set ( 0x98 , 0x00 ) ;
  cp2155_set ( 0x98 , 0x00 ) ;
  cp2155_set ( 0x98 , 0x02 ) ;
  cp2155_set ( 0x99 , 0x3b ) ;
  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 , 0x00 ) ;

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

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

  usleep ( 0.003 ) ;
  cp2155_set ( 0x60 , 0x01 ) ;
  usleep ( 0.998 ) ;

  cp2155_set ( 0x90 , 0xf8 ) ;
  cp2155_set ( 0x60 , 0x01 ) ;
  cp2155_set ( 0x9b , 0x04 ) ;

  cp2155_get ( 0x46 , $value ) ;

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

# Scan a number of lines and append to PPM file
sub do_scan_lines
{
  my ( $scanner , $filehandle , $width , $x_start , $x_end , $y_len , $y_min , $y_max ) = @_ ;
  my $status = 'SANE_STATUS_GOOD' ;
  my $value = 0 ;

  cp2155_block6 ( 0x12 , 0x83 ) ;
  cp2155_set ( 0x90 , 0xf8 ) ;
  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_block5 ( 0x01 ) ;

  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_slope07_data ) ;
  cp2155_set_slope ( 0x030200 , @cp2155_slope07_data ) ;
  cp2155_set_slope ( 0x030400 , @cp2155_slope08_data ) ;
  cp2155_set_slope ( 0x030600 , @cp2155_slope07_data ) ;
  cp2155_set_slope ( 0x030800 , @cp2155_slope08_data ) ;

  cp2155_set ( 0x90 , 0xfa ) ;

  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 ) ;

  my $readbuf = '' ;
  my $linebuf = '' ;
  my $line = 0 ;

  # 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 > 65000 )
    {
      $datasize = 65000 ;
    }

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

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

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

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

    $linebuf .= $readbuf ;

    while ( length ( $linebuf ) >= 3*$width )
    {
      $line ++ ;
      # save line-by-line RGB to file,
      if ( $line >= $y_min )
      {
        # use scanner->width instead of width to remove pad bytes
        print $filehandle ( substr ( $linebuf , $width*0 , $scanner->{'width'} ) ) ;
        print $filehandle ( substr ( $linebuf , $width*1 , $scanner->{'width'} ) ) ;
        print $filehandle ( substr ( $linebuf , $width*2 , $scanner->{'width'} ) ) ;
        DBG ( 6 , "%ld: line %ld written...\n" , $line , $line-$TOP_EDGE ) ;
      }

      $linebuf = substr ( $linebuf , $width*3 ) ;
    }
  }

  return ( $status ) ;
}

# 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 + $scanner->{'x1'} ;
  my $x_end = $x_start + ( $width * 600 / $scanner->{'resolution'} ) - 1 ;
#  my $y_len = 3 * $height ;
  my $y_len = 3 * ( ( $TOP_EDGE + $scanner->{'y2'} ) * $scanner->{'resolution'} / 600 ) ;
DBG ( 1 , "DEBUG 1: %ld %ld - %ld %ld %ld\n" , $width , $height , $x_start , $x_end , $y_len ) ;
  my $y_min = ( $TOP_EDGE + $scanner->{'y1'} ) * $scanner->{'resolution'} / 600 ;
  my $y_max = ( $TOP_EDGE + $scanner->{'y2'} ) * $scanner->{'resolution'} / 600 ;

  $status = do_scan_lines ( $scanner , $fp , $width , $x_start , $x_end , $y_len , $y_min , $y_max ) ;

  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'} ) ;

  my $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 ) ;
}

# 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'} ) ;

  my $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
  my $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);
#  }

  go_home ( ) ;
  
  # 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
{
  my ( $id ) = @_ ;
  $usb = Device::USB -> new() ;
  $dev = $usb -> find_device ( 0x04a9 , $id ) ;
  $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' , 5100 , 'y2' , 6600 , 'resolution' , 300 ) ;   #Letter
my %scanner = ( 'x1' , 0 , 'y1' , 0 , 'x2' , 4980 , 'y2' , 7020 , 'resolution' , 300 ) ;   #DIN A4
CANON_OpenDevice ( 0x2224 ) ;
CANON_start_scan ( \%scanner ) ;

# Convert data file from line-by-line RGB to byte-by-byte RGB,
open ( OUTDAT , '>scan.ppm' ) ;
my $buf = '' ;
my $anzlf = 0 ;

for ( my $idx = 0 ; $idx < 32 ; $idx ++ )
{
  my $char = '' ; read ( $scanner{'fp'} , $char , 1 ) ;
  $buf .= $char ;

  if ( $char eq "\n" )
  {
    $anzlf ++ ;

    if ( $anzlf >= 3 )
    {
      last ( ) ;
    }
  }
}

my ( $format , $size , $colors ) = split ( m!\n! , $buf ) ;
my ( $xmax , $ymax ) = split ( m!\s+! , $size ) ;

print OUTDAT $format , "\n" ;
print OUTDAT $size   , "\n" ;
print OUTDAT $colors , "\n" ;

for ( my $y = 0 ; $y < $ymax ; $y ++ )
{
  my $rbuf = '' ; read ( $scanner{'fp'} , $rbuf , $xmax ) ;
  my $gbuf = '' ; read ( $scanner{'fp'} , $gbuf , $xmax ) ;
  my $bbuf = '' ; read ( $scanner{'fp'} , $bbuf , $xmax ) ;

  for ( my $x = 0 ; $x < $xmax ; $x ++ )
  {
    print OUTDAT ( substr ( $rbuf , $x , 1 ) , substr ( $gbuf , $x , 1 ) , substr ( $bbuf , $x , 1 ) ) ;
  }
}

close ( OUTDAT ) ;

CANON_finish_scan ( \%scanner ) ;
CANON_CloseDevice ( ) ;
1 ;
