[sane-devel] New bug introduced for Canon LiDE 30

Monty xiphmont@xiph.org
Thu, 11 Sep 2003 12:39:42 -0400


--M9NhX3UHpAaciwkO
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline




On Thu, Sep 11, 2003 at 01:08:37PM +0200, Till Kamppeter wrote:
> Monty wrote:
> >LiDE20 and liDE30 never worked well; the autocal code was broken.  I
> >know, I have them.  I spent several days debugging the plustek backend
> >for exactly these scanners last month; see CVS (gerhard integrated my
> >patches I believe), or contact me for a Canoscan LiDE patch against
> >vanilla 1.0.12.
> 
> Can you send me the patch?

Gerhard has, I believe, integrated it into his latest Plustek backend
download.  He may have added additional improvements since I made the
patch; you should probably ask him... specifically, I only enabled the
patch for CanoScan LiDE (as that's all I had to test) but it likely
fixes autocal for a wider array of scanners if you turn it on in the
conf file.  Gerhard may have correctly identified a larger default
set.

Regardless, the patch as I originally constructed it is attached. It
applies to vanilla 1.0.12.

The patch has nothing whatsoever to do with USB communication issues;
like I said, I use 1.0.12 happily with USB, but I need a hub to make
most USB devices work properly with my box and Linux.  It's not
specific to SANE; most of my USB devices when not on the hub will work
perfectly one week, not at all the next...

Monty

--M9NhX3UHpAaciwkO
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="canoscan-patch-20030815.txt"

diff -ruNw sane-backends-1.0.12/backend/canoscan-calibrate.c sane-backends-1.0.12-canoscan/backend/canoscan-calibrate.c
--- sane-backends-1.0.12/backend/canoscan-calibrate.c	1969-12-31 19:00:00.000000000 -0500
+++ sane-backends-1.0.12-canoscan/backend/canoscan-calibrate.c	2003-08-15 01:54:37.000000000 -0400
@@ -0,0 +1,982 @@
+/*.............................................................................
+ * Project : SANE library for Plustek flatbed scanners; canoscan calibration
+ *.............................................................................
+ */
+
+/** @file canoscan-calibrate.c
+ *  @brief Calibration routines.
+ *
+ * Based on sources acquired from Plustek Inc. and Gerhard Jaeger
+ * <gerhard@gjaeger.de>
+ *
+ * Current rehash by Monty <monty@xiph.org>
+ *
+ * The basic premise: The stock Plustek-usbshading.c in the plustek
+ * driver is effectively nonfunctional for Canon CanoScan scanners.
+ * These scanners rely heavily on all calibration steps, especially
+ * fine white, to produce acceptible scan results.  However, to make
+ * autocalibration work and make it work well involves some
+ * substantial mucking aobut in code that supports thirty other
+ * scanners with widely varying characteristics... none of which I own
+ * or can test.
+ *
+ * Therefore, I'm splitting out a few calibration functions I need
+ * to modify for the CanoScan which allows me to simplify things 
+ * greatly for the CanoScan without worrying about breaking other
+ * scanners, as well as reuse the vast majority of the Plustek
+ * driver infrastructure without forking.
+ *
+ * History:
+ * - 0.45m - birth of the file; tested extensively with the LiDE 20
+ * 
+ * This file is part of the SANE package.
+ *
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ *
+ * As a special exception, the authors of SANE give permission for
+ * additional uses of the libraries contained in this release of SANE.
+ *
+ * The exception is that, if you link a SANE library with other files
+ * to produce an executable, this does not by itself cause the
+ * resulting executable to be covered by the GNU General Public
+ * License.  Your use of that executable is in no way restricted on
+ * account of linking the SANE library code into it.
+ *
+ * This exception does not, however, invalidate any other reasons why
+ * the executable file might be covered by the GNU General Public
+ * License.
+ *
+ * If you submit changes to SANE to the maintainers to be included in
+ * a subsequent release, you agree by submitting the changes that
+ * those changes may be distributed with this exception intact.
+ *
+ * If you write modifications of your own for SANE, it is your choice
+ * whether to permit this exception to apply to your modifications.
+ * If you do not wish that, delete this exception notice.
+ * <hr> */
+ 
+static SANE_Bool cano_HostSwap_p(void){
+  /* the NatSemi 983x is a big endian chip, and the line protocol data
+     all arrives big-endian.  This determines if we need to swap to
+     host-order */
+  u_short pattern = 0xfeed; /* deadbeef */
+  unsigned char *bytewise = (unsigned char *)&pattern;
+  DBG( _DBG_INFO2, "cano_HostSwap_p()\n" );
+  if (bytewise[0] == 0xfe){
+    DBG( _DBG_INFO2, "We're big-endian!  No need to swap!\n" );
+    DBG( _DBG_INFO2, "cano_HostSwap_p() done\n" );
+    return 0;
+  }
+  DBG( _DBG_INFO2, "We're little-endian!  NatSemi LM9833 is big!  Must swap calibration data!\n" );
+  DBG( _DBG_INFO2, "cano_HostSwap_p() done\n" );
+  return 1;
+}
+
+static int strip_state=0; /* 0 for not ready,  1 pos white lamp on,  2 lamp off */
+
+static int cano_PrepareToReadWhiteCal(pPlustek_Device dev){
+  pHWDef    hw       = &dev->usbDev.HwSetting;
+  switch (strip_state){
+  case 0: {
+    if(!usb_ModuleToHome( dev, SANE_TRUE )){
+      DBG( _DBG_ERROR, "cano_PrepareToReadWhiteCal() failed\n" );
+      return _E_LAMP_NOT_IN_POS;
+    }
+    if( !usb_ModuleMove(dev, MOVE_Forward,
+			(u_long)dev->usbDev.pSource->ShadingOriginY)) {
+      DBG( _DBG_ERROR, "cano_PrepareToReadWhiteCal() failed\n" );
+      return _E_LAMP_NOT_IN_POS;
+    }
+    break;
+  }    
+  case 2:
+    
+    a_bRegs[0x29] = hw->bReg_0x29;
+    usb_switchLamp( dev, SANE_TRUE );
+    if( !usbio_WriteReg( dev->fd, 0x29, a_bRegs[0x29])) {
+      DBG( _DBG_ERROR, "cano_PrepareToReadWhiteCal() failed\n" );
+      return _E_LAMP_NOT_IN_POS;
+    }
+    break;
+  }
+  
+  strip_state=1;
+  return 0;
+}
+
+static int cano_PrepareToReadBlackCal(pPlustek_Device dev){
+  if(strip_state==0)
+    if(cano_PrepareToReadWhiteCal(dev))return SANE_FALSE;
+  if(strip_state!=2){    
+    /* switch lamp off to read dark data... */
+    a_bRegs[0x29] = 0;
+    usb_switchLamp( dev, SANE_FALSE );
+    strip_state=2;
+  }
+  return 0;
+}
+
+static int cano_LampOnAfterCalibration(pPlustek_Device dev){
+  pHWDef    hw       = &dev->usbDev.HwSetting;
+  switch (strip_state){
+  case 2:
+    
+    a_bRegs[0x29] = hw->bReg_0x29;
+    usb_switchLamp( dev, SANE_TRUE );
+    if( !usbio_WriteReg( dev->fd, 0x29, a_bRegs[0x29])) {
+      DBG( _DBG_ERROR, "cano_LampOnAfterCalibration() failed\n" );
+      return _E_LAMP_NOT_IN_POS;
+    }
+    strip_state=1;
+    break;
+  }
+  return 0;
+}
+
+static SANE_Bool cano_adjLampSetting( u_short *min, u_short *max, 
+				      u_short *off,u_short val){
+  
+  u_long newoff = *off;
+  if(val<IDEAL_GainNormal && val>IDEAL_GainNormal-8000) return SANE_FALSE;
+
+  if(val > IDEAL_GainNormal-4000){
+    *max   = newoff;
+    *off = ((newoff + *min)>>1);
+  }else{
+    u_short bisect = (newoff + *max)>>1;
+    u_short twice  =  newoff*2;
+    *min   = newoff;
+    *off= twice<bisect?twice:bisect;
+  }
+
+  if(*min+1 >= *max) return SANE_FALSE;
+  return SANE_TRUE;
+}
+
+/** cano_AdjustLightsource
+ * coarse calibration step 0
+ * [Monty changes]: On the CanoScan at least, the default lamp
+ * settings are several *hundred* percent too high and vary from
+ * scanner-to-scanner by 20-50%. This is only for CIS devices 
+ * where the lamp_off parameter is adjustable; I'd make it more general, 
+ * but I only have the CIS hardware to test.
+ */
+
+static int cano_AdjustLightsource( pPlustek_Device dev)
+{
+  char      tmp[40];
+  int       i=0;
+  pDCapsDef scaps      = &dev->usbDev.Caps;
+  pHWDef    hw         = &dev->usbDev.HwSetting;
+  u_long    dw, bytes2get;
+
+  RGBUShortDef max_rgb, min_rgb;
+
+  if( ! (hw->bReg_0x26 & _ONE_CH_COLOR)) return SANE_FALSE;
+  if(  _IS_PLUSTEKMOTOR(hw->motorModel)) return SANE_FALSE;
+  if( usb_IsEscPressed())  return SANE_FALSE;
+
+
+  DBG( _DBG_INFO2, "cano_AdjustLightsource()\n" );
+
+  /* define the strip to scan for coarse calibration;  done at 300dpi */
+  m_ScanParam.Size.dwLines  = 1;
+  m_ScanParam.Size.dwPixels = scaps->Normal.Size.x *
+    scaps->OpticDpi.x / 300UL;
+  
+  m_ScanParam.Size.dwBytes  = m_ScanParam.Size.dwPixels * 2;
+
+  if( m_ScanParam.bDataType == SCANDATATYPE_Color )
+    m_ScanParam.Size.dwBytes *=3;
+  
+  m_ScanParam.Origin.x = (u_short)((u_long) hw->wActivePixelsStart *
+				   300UL / scaps->OpticDpi.x);
+  m_ScanParam.bCalibration = PARAM_Gain;
+  
+  DBG( _DBG_INFO2, "Coarse Calibration Strip:\n" );
+  DBG( _DBG_INFO2, "Lines    = %lu\n", m_ScanParam.Size.dwLines  );
+  DBG( _DBG_INFO2, "Pixels   = %lu\n", m_ScanParam.Size.dwPixels );
+  DBG( _DBG_INFO2, "Bytes    = %lu\n", m_ScanParam.Size.dwBytes  );
+  DBG( _DBG_INFO2, "Origin.X = %u\n",  m_ScanParam.Origin.x );
+
+  max_rgb.Red   = max_rgb.Green = max_rgb.Blue = 0xffff;
+  min_rgb.Red   = hw->red_lamp_on;
+  min_rgb.Green = hw->green_lamp_on;
+  min_rgb.Blue  = hw->blue_lamp_on;
+
+  while(1){
+  
+    m_ScanParam.dMCLK = dMCLK;
+    if( !usb_SetScanParameters( dev, &m_ScanParam )){
+      return SANE_FALSE;
+    }
+    
+    bytes2get = m_ScanParam.Size.dwPhyBytes;
+    if( m_ScanParam.bDataType == SCANDATATYPE_Color )
+      bytes2get *=3;
+    
+    if(	!usb_ScanBegin( dev, SANE_FALSE) ||
+	!usb_ScanReadImage( dev, pScanBuffer, bytes2get ) ||
+	!usb_ScanEnd( dev )) {
+      DBG( _DBG_ERROR, "cano_AdjustLightsource() failed\n" );
+      return SANE_FALSE;
+    }
+    
+    DBG( _DBG_INFO2, "PhyBytes  = %lu\n",  m_ScanParam.Size.dwPhyBytes  );
+    DBG( _DBG_INFO2, "PhyPixels = %lu\n",  m_ScanParam.Size.dwPhyPixels );
+    
+    sprintf( tmp, "/tmp/coarse-lamp-%u.raw", i );
+    
+    dumpPic( tmp, NULL, 0 );
+    dumpPic( tmp, pScanBuffer, bytes2get );
+    
+    if(cano_HostSwap_p())
+      usb_Swap((u_short *)pScanBuffer, bytes2get );
+
+    sprintf( tmp, "/tmp/coarse-lamp-swap%u.raw", i++ );
+    
+    dumpPic( tmp, NULL, 0 );
+    dumpPic( tmp, pScanBuffer, bytes2get );
+    
+    
+    {  
+      RGBUShortDef tmp;
+      u_long       dwR, dwG, dwB;
+      u_long       dwDiv = 10;
+      u_long       dwLoop1 = m_ScanParam.Size.dwPhyPixels / dwDiv, dwLoop2;
+      SANE_Bool    adj = SANE_FALSE;
+      
+      tmp.Red = tmp.Green = tmp.Blue = 0;
+      
+      /* find out the max pixel value for R, G, B */
+      for( dw = 0; dwLoop1; dwLoop1-- ) {
+	
+	/* do some averaging... */
+	for (dwLoop2 = dwDiv, dwR = dwG = dwB = 0; dwLoop2; dwLoop2--, dw++)
+	  if( m_ScanParam.bDataType == SCANDATATYPE_Color )
+	    {
+	      dwR += ((u_short*)pScanBuffer)[dw];
+	      dwG += ((u_short*)pScanBuffer)[dw+m_ScanParam.Size.dwPhyPixels+1];
+	      dwB += ((u_short*)pScanBuffer)[dw+(m_ScanParam.Size.dwPhyPixels+1)*2];
+	    }else{
+	      dwG += ((u_short*)pScanBuffer)[dw];
+	    }
+
+	dwR = dwR / dwDiv;
+	dwG = dwG / dwDiv;
+	dwB = dwB / dwDiv;
+	
+	if(tmp.Red < dwR)
+	  tmp.Red = dwR;
+	if(tmp.Green < dwG)
+	  tmp.Green = dwG;
+	if(tmp.Blue < dwB)
+	  tmp.Blue = dwB;
+	
+      }
+      
+      if( m_ScanParam.bDataType == SCANDATATYPE_Color )
+	DBG( _DBG_INFO2, "red_lamp_off  = %u/%u/%u\n", min_rgb.Red  ,hw->red_lamp_off  ,max_rgb.Red  );
+      DBG( _DBG_INFO2, "green_lamp_on = %u/%u/%u\n", min_rgb.Green,hw->green_lamp_off,max_rgb.Green);
+      if( m_ScanParam.bDataType == SCANDATATYPE_Color )
+	DBG( _DBG_INFO2, "blue_lamp_off = %u/%u/%u\n", min_rgb.Blue ,hw->blue_lamp_off ,max_rgb.Blue );
+      
+      DBG(_DBG_INFO2, "CUR(R,G,B)= 0x%04x(%u), 0x%04x(%u), 0x%04x(%u)\n",
+	  tmp.Red, tmp.Red, tmp.Green, tmp.Green, tmp.Blue, tmp.Blue);
+      
+      /* bisect */
+      adj=0;
+      if( m_ScanParam.bDataType == SCANDATATYPE_Color )
+	adj += cano_adjLampSetting(&min_rgb.Red,&max_rgb.Red,&hw->red_lamp_off,tmp.Red);
+      adj   += cano_adjLampSetting(&min_rgb.Green,&max_rgb.Green,&hw->green_lamp_off,tmp.Green);
+      if( m_ScanParam.bDataType == SCANDATATYPE_Color )
+	adj += cano_adjLampSetting(&min_rgb.Blue,&max_rgb.Blue,&hw->blue_lamp_off,tmp.Blue);
+      
+      if( !adj ) break;
+      usb_AdjustLamps(dev);
+    }
+  }
+
+  DBG( _DBG_INFO2, "red_lamp_on    = %u\n", hw->red_lamp_on  );
+  DBG( _DBG_INFO2, "red_lamp_off   = %u\n", hw->red_lamp_off );
+  DBG( _DBG_INFO2, "green_lamp_on  = %u\n", hw->green_lamp_on  );
+  DBG( _DBG_INFO2, "green_lamp_off = %u\n", hw->green_lamp_off );
+  DBG( _DBG_INFO2, "blue_lamp_on   = %u\n", hw->blue_lamp_on   );
+  DBG( _DBG_INFO2, "blue_lamp_off  = %u\n", hw->blue_lamp_off  );
+    
+  DBG( _DBG_INFO2, "cano_AdjustLightsource() done.\n" );
+
+  return SANE_TRUE;
+}
+
+
+static SANE_Bool cano_adjGainSetting( u_char *min, u_char *max, 
+				 u_char *gain,u_long val){
+
+  u_long newgain = *gain;
+
+  if(val<IDEAL_GainNormal && val>IDEAL_GainNormal-8000) return SANE_FALSE;
+
+  if(val > IDEAL_GainNormal-4000){
+    *max   = newgain;
+    *gain  = (newgain + *min)>>1;
+  }else{
+    *min   = newgain;
+    *gain  = (newgain + *max)>>1;
+  }
+
+  if(*min+1 >= *max) return SANE_FALSE;
+  return SANE_TRUE;
+}
+
+/** cano_AdjustGain
+ * function to perform the "coarse calibration step" part 1.
+ * We scan reference image pixels to determine the optimum coarse gain settings
+ * for R, G, B. (Analog gain and offset prior to ADC). These coefficients are
+ * applied at the line rate during normal scanning.
+ * The scanned line should contain a white strip with some black at the
+ * beginning. The function searches for the maximum value which corresponds to
+ * the maximum white value.
+ * Affects register 0x3b, 0x3c and 0x3d
+ *
+ * adjLightsource, above, steals most of this function's thunder.
+ */
+
+static SANE_Bool cano_AdjustGain( pPlustek_Device dev ) {
+  char      tmp[40];
+  int       i=0,adj=1;
+  pDCapsDef scaps    = &dev->usbDev.Caps;
+  pHWDef    hw       = &dev->usbDev.HwSetting;
+  u_long    dw, bytes2get;
+
+  unsigned char max[3], min[3];
+  
+  if( usb_IsEscPressed()) return SANE_FALSE;
+  
+  bMaxITA = 0xff;
+
+  max[0]  = max[1] = max[2] = 0x3f;
+  min[0]  = min[1] = min[2] = 1;
+
+  DBG( _DBG_INFO2, "cano_AdjustGain()\n" );
+  
+  /*
+   * define the strip to scan for coarse calibration
+   * done at 300dpi
+   */
+
+  m_ScanParam.Size.dwLines  = 1;				/* for gain */
+  m_ScanParam.Size.dwPixels = scaps->Normal.Size.x *
+    scaps->OpticDpi.x / 300UL;
+  
+  m_ScanParam.Size.dwBytes  = m_ScanParam.Size.dwPixels * 2;
+  if(m_ScanParam.bDataType == SCANDATATYPE_Color ) 
+    m_ScanParam.Size.dwBytes *=3;
+  
+  m_ScanParam.Origin.x = (u_short)((u_long) hw->wActivePixelsStart *
+				   300UL / scaps->OpticDpi.x);
+  m_ScanParam.bCalibration = PARAM_Gain;
+  
+  DBG( _DBG_INFO2, "Coarse Calibration Strip:\n" );
+  DBG( _DBG_INFO2, "Lines    = %lu\n", m_ScanParam.Size.dwLines  );
+  DBG( _DBG_INFO2, "Pixels   = %lu\n", m_ScanParam.Size.dwPixels );
+  DBG( _DBG_INFO2, "Bytes    = %lu\n", m_ScanParam.Size.dwBytes  );
+  DBG( _DBG_INFO2, "Origin.X = %u\n",  m_ScanParam.Origin.x );
+  
+  while(adj){
+    m_ScanParam.dMCLK = dMCLK;
+    
+    if( !usb_SetScanParameters( dev, &m_ScanParam ))
+      return SANE_FALSE;
+    
+    bytes2get = m_ScanParam.Size.dwPhyBytes;
+    if( m_ScanParam.bDataType == SCANDATATYPE_Color ) 
+      bytes2get *=3;
+    
+    if(	!usb_ScanBegin( dev, SANE_FALSE) ||
+	!usb_ScanReadImage( dev, pScanBuffer, bytes2get ) ||
+	!usb_ScanEnd( dev )) {
+      DBG( _DBG_ERROR, "cano_AdjustGain() failed\n" );
+      return SANE_FALSE;
+    }
+    
+    DBG( _DBG_INFO2, "PhyBytes  = %lu\n",  m_ScanParam.Size.dwPhyBytes  );
+    DBG( _DBG_INFO2, "PhyPixels = %lu\n",  m_ScanParam.Size.dwPhyPixels );
+    
+    sprintf( tmp, "/tmp/coarse-gain-%u.raw", i++ );
+    
+    dumpPic( tmp, NULL, 0 );
+    dumpPic( tmp, pScanBuffer, bytes2get );
+    
+    if(cano_HostSwap_p())
+      usb_Swap((u_short *)pScanBuffer, bytes2get );
+    
+    if( m_ScanParam.bDataType == SCANDATATYPE_Color ) {
+      
+      RGBUShortDef max_rgb;
+      u_long       dwR, dwG, dwB;
+      u_long       dwDiv = 10;
+      u_long       dwLoop1 = m_ScanParam.Size.dwPhyPixels / dwDiv, dwLoop2;
+      
+      max_rgb.Red = max_rgb.Green = max_rgb.Blue = 0;
+      
+      /* find out the max pixel value for R, G, B */
+      for( dw = 0; dwLoop1; dwLoop1-- ) {
+	
+	/* do some averaging... */
+	for (dwLoop2 = dwDiv, dwR = dwG = dwB = 0; dwLoop2; dwLoop2--, dw++) {
+	  dwR += ((u_short*)pScanBuffer)[dw];
+	  dwG += ((u_short*)pScanBuffer)[dw+m_ScanParam.Size.dwPhyPixels+1];
+	  dwB += ((u_short*)pScanBuffer)[dw+(m_ScanParam.Size.dwPhyPixels+1)*2];
+	}
+	dwR = dwR / dwDiv;
+	dwG = dwG / dwDiv;
+	dwB = dwB / dwDiv;
+	
+	if(max_rgb.Red < dwR)
+	  max_rgb.Red = dwR;
+	if(max_rgb.Green < dwG)
+	  max_rgb.Green = dwG;
+	if(max_rgb.Blue < dwB)
+	  max_rgb.Blue = dwB;
+      }
+      
+      DBG(_DBG_INFO2, "MAX(R,G,B)= 0x%04x(%u), 0x%04x(%u), 0x%04x(%u)\n",
+	  max_rgb.Red, max_rgb.Red, max_rgb.Green,
+	  max_rgb.Green, max_rgb.Blue, max_rgb.Blue );
+      
+      adj  = cano_adjGainSetting(min  , max  , a_bRegs+0x3b,max_rgb.Red  );
+      adj |= cano_adjGainSetting(min+1, max+1, a_bRegs+0x3c,max_rgb.Green);
+      adj |= cano_adjGainSetting(min+2, max+2, a_bRegs+0x3d,max_rgb.Blue );
+      
+    } else {
+      
+      u_short	w_max = 0;
+      
+      for( dw = 0; dw < m_ScanParam.Size.dwPhyPixels; dw++ ) {
+	if( w_max < ((u_short*)pScanBuffer)[dw])
+	  w_max = ((u_short*)pScanBuffer)[dw];
+      }
+      
+      adj = cano_adjGainSetting(min,max,a_bRegs+0x3c,w_max);
+      a_bRegs[0x3a] = (a_bRegs[0x3d] = a_bRegs[0x3c]);
+      
+      DBG(_DBG_INFO2, "MAX(G)= 0x%04x(%u)\n", w_max, w_max );
+      
+    }
+    DBG( _DBG_INFO2, "REG[0x3b] = %u\n", a_bRegs[0x3b] );
+    DBG( _DBG_INFO2, "REG[0x3c] = %u\n", a_bRegs[0x3c] );
+    DBG( _DBG_INFO2, "REG[0x3d] = %u\n", a_bRegs[0x3d] );
+  
+  }
+  
+  DBG( _DBG_INFO2, "cano_AdjustGain() done.\n" );
+
+  return SANE_TRUE;
+}
+
+
+static int cano_GetNewOffset(u_long *val, int channel, signed char *low, 
+			    signed char *now, signed char *high ){
+
+  /* if we're too black, we're likely off the low end */
+  if(val[channel]<=16){
+    low[channel]=now[channel];
+    now[channel]=(now[channel]+high[channel])/2;
+
+    a_bRegs[0x38+channel]= (now[channel]&0x3f);
+
+    if(low[channel]+1>=high[channel])return 0;
+    return 1;
+  }else if (val[channel]>=2048){
+    high[channel]=now[channel];
+    now[channel]=(now[channel]+low[channel])/2;
+
+    a_bRegs[0x38+channel]= (now[channel]&0x3f);
+
+    if(low[channel]+1>=high[channel])return 0;
+    return 1;
+  }
+  return 0;
+}
+
+/** cano_AdjustOffset
+ * function to perform the "coarse calibration step" part 2.
+ * We scan reference image pixels to determine the optimum coarse offset settings
+ * for R, G, B. (Analog gain and offset prior to ADC). These coefficients are
+ * applied at the line rate during normal scanning.
+ * On CIS based devices, we switch the light off, on CCD devices, we use the optical
+ * black pixels.
+ * Affects register 0x38, 0x39 and 0x3a
+ */
+
+/* Move this to a bisection-based algo and correct some fenceposts;
+   Plustek's example code disagrees with NatSemi's docs; going by the
+   docs works better, I will assume the docs are correct. --Monty */
+
+static int cano_AdjustOffset( pPlustek_Device dev )
+{
+  pDCapsDef scaps      = &dev->usbDev.Caps;
+  char          tmp[40];
+  int           adj=1;
+  int           i;
+  u_long        dw, dwPixels, bytes2get;
+  u_long        dwSum[3];
+  
+  signed char low[3]={-32,-32,-32};
+  signed char now[3]={0,0,0};
+  signed char high[3]={31,31,31};
+
+  pHWDef hw = &dev->usbDev.HwSetting;
+  if( usb_IsEscPressed()) return SANE_FALSE;
+  
+  DBG( _DBG_INFO2, "cano_AdjustOffset()\n" );
+  
+  m_ScanParam.Size.dwLines  = 1;
+  m_ScanParam.Size.dwPixels = scaps->Normal.Size.x *
+    scaps->OpticDpi.x / 300UL;
+  
+  dwPixels = m_ScanParam.Size.dwPixels;
+  
+  m_ScanParam.Size.dwBytes  = m_ScanParam.Size.dwPixels * 2;
+  if( m_ScanParam.bDataType == SCANDATATYPE_Color ) 
+    m_ScanParam.Size.dwBytes *= 3;
+  
+  m_ScanParam.Origin.x = (u_short)((u_long)hw->bOpticBlackStart * 300UL /
+				   dev->usbDev.Caps.OpticDpi.x);
+  m_ScanParam.bCalibration = PARAM_Offset;
+  m_ScanParam.dMCLK        = dMCLK;
+  
+  
+  if( !usb_SetScanParameters( dev, &m_ScanParam )) {
+    DBG( _DBG_ERROR, "cano_AdjustOffset() failed\n" );
+    return SANE_FALSE;
+  }
+  
+  i = 0;
+  
+  bytes2get = m_ScanParam.Size.dwPhyBytes;
+  if( m_ScanParam.bDataType == SCANDATATYPE_Color )
+    bytes2get *= 3;
+  
+  DBG( _DBG_INFO2, "S.dwPixels  = %lu\n", m_ScanParam.Size.dwPixels );		
+  DBG( _DBG_INFO2, "dwPixels    = %lu\n", dwPixels );
+  DBG( _DBG_INFO2, "dwPhyBytes  = %lu\n", m_ScanParam.Size.dwPhyBytes );
+  DBG( _DBG_INFO2, "dwPhyPixels = %lu\n", m_ScanParam.Size.dwPhyPixels );
+  DBG( _DBG_INFO2, "bytes2get   = %lu\n", bytes2get );
+  
+  while( adj ) {
+    
+    if((!usb_ScanBegin(dev, SANE_FALSE)) ||
+       (!usb_ScanReadImage(dev,pScanBuffer,bytes2get)) ||
+       !usb_ScanEnd( dev )) {
+      DBG( _DBG_ERROR, "cano_AdjustOffset() failed\n" );
+      return SANE_FALSE;
+    }
+    
+    sprintf( tmp, "/tmp/coarse-off-%u.raw", i++ );
+    
+    dumpPic( tmp, NULL, 0 );
+    dumpPic( tmp, pScanBuffer, bytes2get );
+    
+    if(cano_HostSwap_p())
+      usb_Swap((u_short *)pScanBuffer, bytes2get );
+    
+    if( m_ScanParam.bDataType == SCANDATATYPE_Color ) {
+      
+      dwSum[0] = dwSum[1] = dwSum[2] = 0;
+      
+      for (dw = 0; dw < dwPixels; dw++) {
+	
+	dwSum[0] += ((u_short*)pScanBuffer)[dw];
+	dwSum[1] += ((u_short*)pScanBuffer)[dw+m_ScanParam.Size.dwPhyPixels+1];
+	dwSum[2] += ((u_short*)pScanBuffer)[dw+(m_ScanParam.Size.dwPhyPixels+1)*2];
+
+      }
+      
+      DBG( _DBG_INFO2, "RedSum   = %lu, ave = %lu\n",
+	   dwSum[0], dwSum[0] /dwPixels );
+      DBG( _DBG_INFO2, "GreenSum = %lu, ave = %lu\n",
+	   dwSum[1], dwSum[1] /dwPixels );
+      DBG( _DBG_INFO2, "BlueSum  = %lu, ave = %lu\n",
+	   dwSum[2], dwSum[2] /dwPixels );
+      
+      /* do averaging for each channel */
+      dwSum[0] /= dwPixels;
+      dwSum[1] /= dwPixels;
+      dwSum[2] /= dwPixels;
+      
+      adj  = cano_GetNewOffset( dwSum, 0, low, now, high );
+      adj |= cano_GetNewOffset( dwSum, 1, low, now, high );
+      adj |= cano_GetNewOffset( dwSum, 2, low, now, high );
+      
+      DBG( _DBG_INFO2, "RedOff   = %d/%d/%d\n", (int)low[0],(int)now[0],(int)high[0]);
+      DBG( _DBG_INFO2, "GreenOff = %d/%d/%d\n", (int)low[1],(int)now[1],(int)high[1]);
+      DBG( _DBG_INFO2, "BlueOff  = %d/%d/%d\n", (int)low[2],(int)now[2],(int)high[2]);
+      
+    } else {
+      dwSum[0] = 0;
+      
+      for( dw = 0; dw < dwPixels; dw++ )
+	dwSum[0] += ((u_short*)pScanBuffer)[dw];
+      dwSum [0] /= dwPixels;
+      
+      DBG( _DBG_INFO2, "Sum   = %lu, ave = %lu\n",
+	   dwSum[0], dwSum[0] /dwPixels );
+      
+      adj = cano_GetNewOffset( dwSum, 0, low, now, high );
+
+      a_bRegs[0x3a] = a_bRegs[0x39] = a_bRegs[0x38];
+      
+      DBG( _DBG_INFO2, "GrayOff = %d/%d/%d\n", (int)low[0],(int)now[0],(int)high[0]);
+
+    }
+    
+    DBG( _DBG_INFO2, "REG[0x38] = %u\n", a_bRegs[0x38] );
+    DBG( _DBG_INFO2, "REG[0x39] = %u\n", a_bRegs[0x39] );
+    DBG( _DBG_INFO2, "REG[0x3a] = %u\n", a_bRegs[0x3a] );
+
+    _UIO(sanei_lm983x_write(dev->fd, 0x38, &a_bRegs[0x38], 3, SANE_TRUE));
+
+  }
+  
+  if( m_ScanParam.bDataType == SCANDATATYPE_Color ) {
+    a_bRegs[0x38] = now[0];	
+    a_bRegs[0x39] = now[1];	
+    a_bRegs[0x3a] = now[2];
+  } else {
+    
+    a_bRegs[0x38] = a_bRegs[0x39] = a_bRegs[0x3a] = now[0];	
+  }
+  
+  DBG( _DBG_INFO2, "cano_AdjustOffset() done.\n" );
+  
+  return SANE_TRUE;
+}
+
+/** usb_AdjustDarkShading
+ * fine calibration part 1
+ *
+ */
+static SANE_Bool cano_AdjustDarkShading( pPlustek_Device dev )
+{
+  char         tmp[40];
+  pScanDef     scanning = &dev->scanning;
+  pDCapsDef    scaps    = &dev->usbDev.Caps;
+  unsigned int i,j;
+  
+  DBG( _DBG_INFO2, "cano_AdjustDarkShading()\n" );
+  if( usb_IsEscPressed())
+    return SANE_FALSE;
+   
+  m_ScanParam = scanning->sParam;
+
+  if( m_ScanParam.PhyDpi.x > 75)
+    m_ScanParam.Size.dwLines = 64;
+  else
+    m_ScanParam.Size.dwLines = 32;
+
+  m_ScanParam.Origin.y  = 0;
+  m_ScanParam.bBitDepth = 16;
+  
+  m_ScanParam.UserDpi.y = scaps->OpticDpi.y;
+  m_ScanParam.Size.dwBytes = m_ScanParam.Size.dwPixels * 2;
+  
+  if( m_ScanParam.bDataType == SCANDATATYPE_Color ) 
+    m_ScanParam.Size.dwBytes *= 3;
+  
+  m_ScanParam.bCalibration = PARAM_DarkShading;
+  m_ScanParam.dMCLK        = dMCLK;
+  
+  sprintf( tmp, "/tmp/fine-dark.raw" );
+  dumpPic( tmp, NULL, 0 );
+  
+  usb_SetScanParameters( dev, &m_ScanParam );
+  if( usb_ScanBegin( dev, SANE_FALSE ) &&
+      usb_ScanReadImage( dev, pScanBuffer, m_ScanParam.Size.dwTotalBytes)) {
+    
+    dumpPic( tmp, pScanBuffer, m_ScanParam.Size.dwTotalBytes );
+    
+    if(cano_HostSwap_p())
+      usb_Swap((u_short *)pScanBuffer, m_ScanParam.Size.dwTotalBytes);
+  }
+  if (!usb_ScanEnd( dev )){
+    DBG( _DBG_ERROR, "cano_AdjustDarkShading() failed\n" );
+    return SANE_FALSE;
+  }
+  
+  /* average the n lines, compute reg values */
+  if( scanning->sParam.bDataType == SCANDATATYPE_Color ) {
+    int      step   = m_ScanParam.Size.dwPhyPixels + 1;
+    int      stepW  = m_ScanParam.Size.dwPhyPixels;
+    for(i=0;i<m_ScanParam.Size.dwPhyPixels;i++){
+      u_long red=0,green=0,blue=0;
+      u_short *bufp   = ((u_short *)pScanBuffer)+i;
+
+      for(j=0;j<m_ScanParam.Size.dwPhyLines;j++){
+	red   += *bufp;   bufp+=step;
+	green += *bufp;   bufp+=step;
+	blue  += *bufp;   bufp+=step;
+      }
+
+      a_wDarkShading[i]         = red/j   +1000;
+      a_wDarkShading[i+stepW]   = green/j +1000;
+      a_wDarkShading[i+stepW*2] = blue/j  +1000;
+
+    }
+    
+    if(cano_HostSwap_p())
+      usb_Swap(a_wDarkShading, m_ScanParam.Size.dwPhyPixels * 2 * 3 );
+
+  }else{
+    int      step   = m_ScanParam.Size.dwPhyPixels + 1;
+    for(i=0;i<m_ScanParam.Size.dwPhyPixels;i++){
+      u_long gray=0;
+      u_short *bufp   = ((u_short *)pScanBuffer)+i;
+
+      for(j=0;j<m_ScanParam.Size.dwPhyLines;j++){
+	gray   += *bufp;   bufp+=step;
+      }
+      
+      a_wDarkShading[i]= gray/j;
+      
+    }
+    if(cano_HostSwap_p())
+      usb_Swap(a_wDarkShading, m_ScanParam.Size.dwPhyPixels * 2 );
+    memcpy(a_wDarkShading+ m_ScanParam.Size.dwPhyPixels * 2,
+	   a_wDarkShading, m_ScanParam.Size.dwPhyPixels * 2);
+    memcpy(a_wDarkShading+ m_ScanParam.Size.dwPhyPixels * 4,
+	   a_wDarkShading, m_ScanParam.Size.dwPhyPixels * 2);
+    
+  }
+    
+  DBG( _DBG_INFO2, "cano_AdjustDarkShading() done\n" );
+  return SANE_TRUE;
+}
+
+/** usb_AdjustWhiteShading
+ * fine calibration part 2 - read the white calibration area and calculate
+ * the gain coefficient for each pixel
+ *
+ */
+static SANE_Bool cano_AdjustWhiteShading( pPlustek_Device dev )
+{
+  char         tmp[40];
+  pScanDef     scanning = &dev->scanning;
+  pDCapsDef    scaps    = &dev->usbDev.Caps;
+  pHWDef       hw       = &dev->usbDev.HwSetting;
+  unsigned int i,j;
+  
+  DBG( _DBG_INFO2, "cano_AdjustWhiteShading()\n" );
+  if( usb_IsEscPressed())
+    return SANE_FALSE;
+  
+  m_ScanParam = scanning->sParam;
+  if( m_ScanParam.PhyDpi.x > 75)
+    m_ScanParam.Size.dwLines = 64;
+  else
+    m_ScanParam.Size.dwLines = 32;
+  
+  m_ScanParam.Origin.y  = 0;
+  m_ScanParam.bBitDepth = 16;
+  
+  m_ScanParam.UserDpi.y = scaps->OpticDpi.y;
+  m_ScanParam.Size.dwBytes = m_ScanParam.Size.dwPixels * 2;
+  
+  if( m_ScanParam.bDataType == SCANDATATYPE_Color ) 
+    m_ScanParam.Size.dwBytes *= 3;
+  
+  m_ScanParam.bCalibration = PARAM_WhiteShading;
+  m_ScanParam.dMCLK        = dMCLK;
+  
+  m_ScanParam.Size.dwBytes = m_ScanParam.Size.dwPixels * 2;
+  if( hw->bReg_0x26 & _ONE_CH_COLOR &&
+      m_ScanParam.bDataType == SCANDATATYPE_Color ) {
+    m_ScanParam.Size.dwBytes *= 3;
+  }
+  
+  sprintf( tmp, "/tmp/fine-white.raw" );
+  dumpPic( tmp, NULL, 0 );
+  
+  if( usb_SetScanParameters( dev, &m_ScanParam ) &&
+      usb_ScanBegin( dev, SANE_FALSE ) &&
+      usb_ScanReadImage( dev, pScanBuffer, m_ScanParam.Size.dwTotalBytes)) {
+    
+    dumpPic( tmp, pScanBuffer, m_ScanParam.Size.dwTotalBytes );
+    
+    if(cano_HostSwap_p())
+      usb_Swap((u_short *)pScanBuffer, m_ScanParam.Size.dwTotalBytes);
+    if (!usb_ScanEnd( dev )){
+      DBG( _DBG_ERROR, "cano_AdjustWhiteShading() failed\n" );
+      return SANE_FALSE;
+    }
+  }else{
+    DBG( _DBG_ERROR, "cano_AdjustWhiteShading() failed\n" );
+    return SANE_FALSE;
+  }
+  
+  /* average the n lines, compute reg values */
+  if( scanning->sParam.bDataType == SCANDATATYPE_Color ) {
+    int      step   = m_ScanParam.Size.dwPhyPixels + 1;
+    int      stepW  = m_ScanParam.Size.dwPhyPixels;
+    for(i=0;i<m_ScanParam.Size.dwPhyPixels;i++){
+      u_long red=0,green=0,blue=0;
+      u_short *bufp   = ((u_short *)pScanBuffer)+i;
+
+      for(j=0;j<m_ScanParam.Size.dwPhyLines;j++){
+	red   += *bufp;   bufp+=step;
+	green += *bufp;   bufp+=step;
+	blue  += *bufp;   bufp+=step;
+      }
+
+      /* an extra 5% on the top */
+      red   = 68811.*16384.*j/red;
+      green = 68811.*16384.*j/green;
+      blue  = 68811.*16384.*j/blue;
+
+      a_wWhiteShading[i]         = (red>65535?65535:red);
+      a_wWhiteShading[i+stepW]   = (green>65535?65535:green);
+      a_wWhiteShading[i+stepW*2] = (blue>65535?65535:blue);
+
+
+    }
+
+    if(cano_HostSwap_p())
+      usb_Swap(a_wWhiteShading, m_ScanParam.Size.dwPhyPixels * 2 * 3 );
+    
+  }else{
+    int      step   = m_ScanParam.Size.dwPhyPixels + 1;
+    for(i=0;i<m_ScanParam.Size.dwPhyPixels;i++){
+      u_long gray=0;
+      u_short *bufp   = ((u_short *)pScanBuffer)+i;
+
+      for(j=0;j<m_ScanParam.Size.dwPhyLines;j++){
+	gray   += *bufp;   bufp+=step;
+      }
+      
+      gray = 65535.*16384.*j/gray;
+      a_wWhiteShading[i]= (gray>65535?65535:gray);
+      
+    }
+    if(cano_HostSwap_p())
+      usb_Swap(a_wWhiteShading, m_ScanParam.Size.dwPhyPixels * 2 );
+    memcpy(a_wWhiteShading+ m_ScanParam.Size.dwPhyPixels * 2,
+	   a_wWhiteShading, m_ScanParam.Size.dwPhyPixels * 2);
+    memcpy(a_wWhiteShading+ m_ScanParam.Size.dwPhyPixels * 4,
+	   a_wWhiteShading, m_ScanParam.Size.dwPhyPixels * 2);
+
+  }
+    
+  DBG( _DBG_INFO2, "cano_AdjustWhiteShading() done\n" );
+  return SANE_TRUE;
+}
+
+static int cano_DoCalibration( pPlustek_Device dev )
+{
+    pScanDef  scanning = &dev->scanning;
+    pHWDef    hw       = &dev->usbDev.HwSetting;
+    pDCapsDef scaps    = &dev->usbDev.Caps;
+    
+
+    if( SANE_TRUE == scanning->fCalibrated )
+      return SANE_TRUE;
+
+    DBG( _DBG_INFO2, "cano_DoCalibration()\n" );
+
+    if(	!( hw->bReg_0x26 & _ONE_CH_COLOR)){
+      DBG( _DBG_ERROR, "altCalibration can't work with non-line-rate CIS devices\n" );
+      return SANE_FALSE; // can't cal this  
+    }
+    if( _IS_PLUSTEKMOTOR(hw->motorModel)){
+      DBG( _DBG_ERROR, "altCalibration can't work with this Plustek motor control setup\n" );
+      return SANE_FALSE; // can't cal this
+    }
+    if( scanning->sParam.bChannels != 1){
+      DBG( _DBG_ERROR, "altCalibration can't work with non-line-rate CIS devices\n" );
+      return SANE_FALSE; // can't cal this
+    }
+
+
+    /* Don't allow calibration settings from the other driver to confuse our use of
+       a few of its functions */
+    scaps->workaroundFlag &= ~_WAF_SKIP_WHITEFINE; 
+    scaps->workaroundFlag &= ~_WAF_SKIP_FINE; 
+    scaps->workaroundFlag &= ~_WAF_BYPASS_CALIBRATION; 
+
+    /* Set the shading position to undefined */
+    strip_state=0;
+    usb_PrepareCalibration( dev );
+    
+    /* You *really* don't want to skip calibration for CanoScan devices. I promise. */
+    usb_SetMCLK( dev, &scanning->sParam );
+	
+    DBG( _DBG_INFO2, "###### ADJUST LAMP (COARSE)#######\n" );
+    if(cano_PrepareToReadWhiteCal(dev))return SANE_FALSE;
+    a_bRegs[0x45] &= ~0x10;
+    if( !cano_AdjustLightsource(dev)) {
+      DBG( _DBG_ERROR, "Coarse Calibration failed!!!\n" );
+      return _E_INTERNAL;
+    }
+
+    DBG( _DBG_INFO2, "###### ADJUST OFFSET (COARSE) ####\n" );
+    if(cano_PrepareToReadBlackCal(dev))return SANE_FALSE;
+    if( !cano_AdjustOffset(dev)) {
+      DBG( _DBG_ERROR, "Coarse Calibration failed!!!\n" );
+      return _E_INTERNAL;
+    }
+
+    DBG( _DBG_INFO2, "###### ADJUST GAIN (COARSE)#######\n" );
+    if(cano_PrepareToReadWhiteCal(dev))return SANE_FALSE;
+    if( !cano_AdjustGain(dev)) {
+      DBG( _DBG_ERROR, "Coarse Calibration failed!!!\n" );
+      return _E_INTERNAL;
+    }
+
+    DBG( _DBG_INFO2, "###### ADJUST DARK (FINE) ########\n" );
+    if(cano_PrepareToReadBlackCal(dev))return SANE_FALSE;
+    a_bRegs[0x45] |= 0x10;
+    if( !cano_AdjustDarkShading(dev)) {
+      DBG( _DBG_ERROR, "Fine Calibration failed!!!\n" );
+      return _E_INTERNAL;
+    }
+
+    DBG( _DBG_INFO2, "###### ADJUST WHITE (FINE) #######\n" );
+    if(cano_PrepareToReadWhiteCal(dev))return SANE_FALSE;
+    
+    if(!usb_ModuleToHome( dev, SANE_TRUE )) return SANE_FALSE;
+    if( !usb_ModuleMove(dev, MOVE_Forward,
+    		(u_long)dev->usbDev.pSource->ShadingOriginY)) {
+      return _E_INTERNAL;
+    }
+    if( !cano_AdjustWhiteShading(dev)) {
+      DBG( _DBG_ERROR, "Fine Calibration failed!!!\n" );
+      return _E_INTERNAL;
+    }
+
+    /* Lamp on if it's not */
+    cano_LampOnAfterCalibration(dev);
+    strip_state=0;
+    
+    /*
+     * home the sensor after calibration
+     */
+    usb_ModuleToHome( dev, SANE_TRUE );
+    scanning->fCalibrated = SANE_TRUE;
+
+    DBG( _DBG_INFO2, "cano_DoCalibration() done\n" );
+    return SANE_TRUE;
+}
+
diff -ruNw sane-backends-1.0.12/backend/plustek-share.h sane-backends-1.0.12-canoscan/backend/plustek-share.h
--- sane-backends-1.0.12/backend/plustek-share.h	2003-05-16 16:59:43.000000000 -0400
+++ sane-backends-1.0.12-canoscan/backend/plustek-share.h	2003-08-15 00:40:34.000000000 -0400
@@ -258,6 +258,10 @@
  * for adjusting the usb stuff
  */
 typedef struct {
+        int     altCalibrate;  /* force use of the alternate canoscan
+                                  autocal; perhaps other Canon
+                                  scanners require the alternate
+                                  autocalibration as well */
 	int     lampOff;
 	int     lampOffOnEnd;
 	int     warmup;
diff -ruNw sane-backends-1.0.12/backend/plustek-usb.c sane-backends-1.0.12-canoscan/backend/plustek-usb.c
--- sane-backends-1.0.12/backend/plustek-usb.c	2003-05-16 16:59:43.000000000 -0400
+++ sane-backends-1.0.12-canoscan/backend/plustek-usb.c	2003-08-15 00:40:34.000000000 -0400
@@ -963,7 +963,19 @@
 	 */
 	usb_ModuleStatus( dev );
 
+        if(dev->usbDev.vendor==0x04A9 &&    /* canoscan ----------*/
+           (dev->usbDev.product==0x220D  || /* LiDE20 and related */
+            dev->usbDev.product==0x220E)    /* LiDE30 and related */
+           ){
+          result = cano_DoCalibration( dev );
+        }else{
+          if(dev->adj.altCalibrate)
+            result = cano_DoCalibration( dev );
+          else
     result = usb_DoCalibration( dev );
+        }
+
+
     if( SANE_TRUE != result ) {
 		DBG( _DBG_INFO, "calibration failed!!!\n" );
         return result;
diff -ruNw sane-backends-1.0.12/backend/plustek.c sane-backends-1.0.12-canoscan/backend/plustek.c
--- sane-backends-1.0.12/backend/plustek.c	2003-05-16 16:59:44.000000000 -0400
+++ sane-backends-1.0.12-canoscan/backend/plustek.c	2003-08-15 02:22:09.000000000 -0400
@@ -175,6 +175,7 @@
 # include "plustek-usbscan.c"
 # include "plustek-usbimg.c"
 # include "plustek-usbshading.c"
+# include "canoscan-calibrate.c"
 # include "plustek-usb.c"
 #endif
 
@@ -299,6 +300,7 @@
 	DBG( _DBG_SANE_INIT, "warmup       : %ds\n",  cnf->adj.warmup          );
 	DBG( _DBG_SANE_INIT, "lampOff      : %d\n",   cnf->adj.lampOff         );
 	DBG( _DBG_SANE_INIT, "lampOffOnEnd : %d\n",   cnf->adj.lampOffOnEnd    );
+        DBG( _DBG_SANE_INIT, "altCalibrate : >%s<\n", cnf->adj.altCalibrate?"yes":"no"   );
 	DBG( _DBG_SANE_INIT, "skipCalibr.  : %d\n",   cnf->adj.skipCalibration );
 	DBG( _DBG_SANE_INIT, "skipFine     : %d\n",   cnf->adj.skipFine        );
 	DBG( _DBG_SANE_INIT, "skipFineWhite: %d\n",   cnf->adj.skipFineWhite   );
@@ -1434,6 +1436,8 @@
 			
 			ival = 0;
    			decodeVal( str, "enableTPA", _INT, &config.adj.enableTpa, &ival);
+			decodeVal( str, "altCalibrate",
+									  _INT, &config.adj.altCalibrate,&ival);
 			decodeVal( str, "skipCalibration",
 									  _INT, &config.adj.skipCalibration,&ival);
 			decodeVal( str, "skipFine",

--M9NhX3UHpAaciwkO--