1/* sane - Scanner Access Now Easy.
2   Copyright (C) Marian Eichholz 2001
3   This file is part of the SANE package.
4
5   This program is free software; you can redistribute it and/or
6   modify it under the terms of the GNU General Public License as
7   published by the Free Software Foundation; either version 2 of the
8   License, or (at your option) any later version.
9
10   This program is distributed in the hope that it will be useful, but
11   WITHOUT ANY WARRANTY; without even the implied warranty of
12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13   General Public License for more details.
14
15   You should have received a copy of the GNU General Public License
16   along with this program.  If not, see <https://www.gnu.org/licenses/>.
17
18   As a special exception, the authors of SANE give permission for
19   additional uses of the libraries contained in this release of SANE.
20
21   The exception is that, if you link a SANE library with other files
22   to produce an executable, this does not by itself cause the
23   resulting executable to be covered by the GNU General Public
24   License.  Your use of that executable is in no way restricted on
25   account of linking the SANE library code into it.
26
27   This exception does not, however, invalidate any other reasons why
28   the executable file might be covered by the GNU General Public
29   License.
30
31   If you submit changes to SANE to the maintainers to be included in
32   a subsequent release, you agree by submitting the changes that
33   those changes may be distributed with this exception intact.
34
35   If you write modifications of your own for SANE, it is your choice
36   whether to permit this exception to apply to your modifications.
37   If you do not wish that, delete this exception notice.
38*/
39
40/* ======================================================================
41
42Userspace scan tool for the Microtek 3600 scanner
43
44slider movement
45
46(C) Marian Eichholz 2001
47
48====================================================================== */
49
50#include "sm3600-scantool.h"
51
52#include <math.h>
53
54/* tuning constants for DoOriginate */
55#define CCH_BONSAI              60
56#define BLACK_HOLE_GRAY         30
57#define BLACK_BED_LEVEL         10
58
59/* changed by user request from 100, there are probably darker stripes */
60#define CHASSIS_GRAY_LEVEL      75
61
62typedef enum { ltHome, ltUnknown, ltBed, ltError } TLineType;
63
64#define INST_ASSERT2() { if (this->nErrorState) return ltError; }
65
66static unsigned char auchRegsSingleLine[]={
67  0x00 /*0x01*/, 0x00 /*0x02*/, 0x3F /*0x03*/,
68  0xB4 /*!!0x04!!*/, 0x14 /*!!0x05!!*/, 0,0,
69  0x00 /*0x08*/, 0x3F /*!!0x09!!*/,
70  1,0,
71  0x6D /*0x0C*/,
72  0x70 /*0x0D*/, 0x69 /*0x0E*/, 0xD0 /*0x0F*/,
73  0x00 /*0x10*/, 0x00 /*0x11*/, 0x40 /*0x12*/,
74  0x15 /*0x13*/, 0x80 /*0x14*/, 0x2A /*0x15*/,
75  0xC0 /*0x16*/, 0x40 /*0x17*/, 0xC0 /*0x18*/,
76  0x40 /*0x19*/, 0xFF /*0x1A*/, 0x01 /*0x1B*/,
77  0x88 /*0x1C*/, 0x40 /*0x1D*/, 0x4C /*0x1E*/,
78  0x50 /*0x1F*/, 0x00 /*0x20*/, 0x0C /*0x21*/,
79  0x21 /*0x22*/, 0xF0 /*0x23*/, 0x40 /*0x24*/,
80  0x00 /*0x25*/, 0x0A /*0x26*/, 0xF0 /*0x27*/,
81  0x00 /*0x28*/, 0x00 /*0x29*/, 0x4E /*0x2A*/,
82  0xF0 /*0x2B*/, 0x00 /*0x2C*/, 0x00 /*0x2D*/,
83  0x4E /*0x2E*/, 0x88 /*R_CCAL*/, 0x88 /*R_CCAL2*/,
84  0x84 /*R_CCAL3*/, 0xEA /*R_LEN*/, 0x24 /*R_LENH*/,
85  0x63 /*0x34*/, 0x29 /*0x35*/, 0x00 /*0x36*/,
86  0x00 /*0x37*/, 0x00 /*0x38*/, 0x00 /*0x39*/,
87  0x00 /*0x3A*/, 0x00 /*0x3B*/, 0xFF /*0x3C*/,
88  0x0F /*0x3D*/, 0x00 /*0x3E*/, 0x00 /*0x3F*/,
89  0x01 /*0x40*/, 0x00 /*0x41*/, 0x00 /*R_CSTAT*/,
90  0x03 /*R_SPD*/, 0x01 /*0x44*/, 0x00 /*0x45*/,
91  0x59 /*!!R_CTL!!*/, 0xC0 /*0x47*/, 0x40 /*0x48*/,
92  0x96 /*!!0x49!!*/, 0xD8 /*0x4A*/ };
93
94/* ======================================================================
95
96GetLineType()
97
98Reads a scan line at the actual position and classifies it as
99"on the flatbed area" or "at home position" or "elsewhere".
100This can be used to calculate the proper stepping width
101
102====================================================================== */
103
104static TLineType GetLineType(TInstance *this)
105{
106  unsigned char  achLine[CCH_BONSAI+1];
107  unsigned char *puchBuffer;
108  int            cchBulk,i,iHole;
109  int            axHoles[3];
110  long           lSum;
111  TBool          bHolesOk;
112  int            lMedian;
113  bHolesOk=false;
114  RegWriteArray(this,R_ALL, 74, auchRegsSingleLine);
115  INST_ASSERT2();
116  /*     dprintf(DEBUG_SCAN,"originate-%d...",iStripe); */
117  RegWrite(this,R_CTL, 1, 0x59);    /* #2496[062.5] */
118  RegWrite(this,R_CTL, 1, 0xD9);    /* #2497[062.5] */
119  i=WaitWhileScanning(this,5); if (i) return i;
120
121  cchBulk=MAX_PIXEL_PER_SCANLINE;
122  /*
123  cchBulk=RegRead(this,R_STAT, 2);
124  if (cchBulk!=MAX_PIXEL_PER_SCANLINE)
125    return SetError(this,SANE_STATUS_INVAL,
126		    "illegal scan line width reported (%d)",cchBulk);
127  */
128  puchBuffer=(unsigned char*)calloc(1,cchBulk);
129  CHECK_POINTER(puchBuffer);
130  if (BulkReadBuffer(this,puchBuffer, cchBulk)!=cchBulk)
131    {
132      free(puchBuffer);
133      return SetError(this,SANE_STATUS_IO_ERROR,"truncated bulk");
134    }
135  lSum=0;
136  for (i=0; i<cchBulk; i++)
137    lSum+=puchBuffer[i]; /* gives total white level */
138  for (i=0; i<CCH_BONSAI; i++)
139    {
140      int iBulk=i*(cchBulk)/CCH_BONSAI;
141      achLine[i]=puchBuffer[iBulk+40]; /* simple, basta */
142    }
143  /* the bonsai line is supported only for curiosity */
144  for (i=0; i<CCH_BONSAI; i++)
145    achLine[i]=achLine[i]/26+'0'; /* '0'...'9' */
146  achLine[CCH_BONSAI]='\0';
147
148  i=200;
149  iHole=0;
150  dprintf(DEBUG_ORIG,"");
151  while (i<MAX_PIXEL_PER_SCANLINE && iHole<3)
152    {
153      int c;
154      while (i<MAX_PIXEL_PER_SCANLINE && puchBuffer[i]>BLACK_HOLE_GRAY) i++; /* not very black */
155      c=0;
156      dprintf(DEBUG_ORIG,"~ i=%d",i);
157      while (i<MAX_PIXEL_PER_SCANLINE && puchBuffer[i]<=BLACK_HOLE_GRAY) { i++; c++; }
158      dprintf(DEBUG_ORIG,"~ c=%d",c);
159      if (c>90) /* 90% of min hole diameter */
160	{
161	  axHoles[iHole]=i-c/2; /* store the middle of the hole */
162	  dprintf(DEBUG_ORIG,"~ #%d=%d",iHole,axHoles[iHole]);
163	  iHole++;
164	  i+=10; /* some hysteresis */
165	}
166    }
167  if (iHole==3)
168    {
169      bHolesOk=true;
170      for (i=0; i<2; i++)
171	{
172	  int xDistance=axHoles[i+1]-axHoles[i];
173	  if (xDistance<1050 || xDistance>1400)
174	    bHolesOk=false;
175	}
176      if (axHoles[0]<350 || axHoles[0]>900) /* >2 cm tolerance */
177	bHolesOk=false;
178    }
179  else
180    bHolesOk=false;
181  lMedian=lSum/cchBulk;
182  /* this is *definitely* dirty style. We should pass the information
183     by other means... */
184  if (bHolesOk)
185    {
186      /* black reference */
187      this->calibration.nHoleGray=puchBuffer[axHoles[0]];
188      switch (this->model)
189	{
190	case sm3600:
191	  /* bed corner */
192	  this->calibration.xMargin=axHoles[0]-480;
193	  this->calibration.yMargin=413;
194	  break;
195	case sm3700:
196	case sm3750: /* basically unknown sub-brand */
197	default:
198	  this->calibration.xMargin=axHoles[0]-462;
199	  this->calibration.yMargin=330;
200	  break;
201	} /* switch */
202    }
203  dprintf(DEBUG_ORIG,"~ %s - %d\n",
204	  achLine,
205	  lMedian);
206  free(puchBuffer);
207  i=WaitWhileBusy(this,2); if (i) return i;
208  if (bHolesOk && lMedian>CHASSIS_GRAY_LEVEL)
209    return ltHome;
210  if (lMedian<=BLACK_BED_LEVEL)
211    return ltBed;
212  return ltUnknown;
213}
214
215#ifdef INSANE_VERSION
216
217/* **********************************************************************
218
219FakeCalibration()
220
221If DoOriginate() and this Calibration code is skipped,
222we should at least provide for some fake measurements.
223Thus a test scan of the scanner's inside is possible.
224
225********************************************************************** */
226
227__SM3600EXPORT__
228TState FakeCalibration(TInstance *this)
229{
230  if (this->calibration.bCalibrated)
231    return SANE_STATUS_GOOD;
232  this->calibration.bCalibrated=true;
233  if (!this->calibration.achStripeY)
234    {
235      this->calibration.achStripeY=calloc(1,MAX_PIXEL_PER_SCANLINE);
236      if (!this->calibration.achStripeY)
237	return SetError(this,SANE_STATUS_NO_MEM,"no memory for calib Y");
238    }
239  memset(this->calibration.achStripeY,0xC0,MAX_PIXEL_PER_SCANLINE);
240  /* scan *every* nonsense */
241  this->calibration.xMargin=this->calibration.yMargin=0;
242  return SANE_STATUS_GOOD;
243}
244
245#endif
246
247/* **********************************************************************
248
249DoCalibration() and friends
250
251********************************************************************** */
252
253#define SM3600_CALIB_USE_MEDIAN
254#define SM3600_CALIB_APPLY_HANNING_WINDOW
255
256#ifdef SM3600_CALIB_USE_MEDIAN
257typedef int (*TQSortProc)(const void *, const void *);
258
259static
260int CompareProc(const unsigned char *p1, const unsigned char *p2)
261{
262  return *p1 - *p2;
263}
264#endif
265
266#define MAX_CALIB_STRIPES 8
267
268__SM3600EXPORT__
269TState DoCalibration(TInstance *this)
270{
271#ifdef SM3600_CALIB_USE_RMS
272  long   aulSum[MAX_PIXEL_PER_SCANLINE];
273#endif
274#ifdef SM3600_CALIB_USE_MEDIAN
275  unsigned char aauchY[MAX_CALIB_STRIPES][MAX_PIXEL_PER_SCANLINE];
276  unsigned char auchRow[MAX_CALIB_STRIPES];
277#endif
278#ifdef SM3600_CALIB_APPLY_HANNING_WINDOW
279  unsigned char auchHanning[MAX_PIXEL_PER_SCANLINE];
280#endif
281
282  int    iLine,i;
283  int    yStart,cStripes,cyGap;
284  TState rc;
285  if (this->calibration.bCalibrated)
286    return SANE_STATUS_GOOD;
287
288  switch (this->model)
289    {
290    case sm3600:
291      yStart=200;
292      cStripes=MAX_CALIB_STRIPES;
293      cyGap=10;
294      break;
295    case sm3700: /* in fact, the 3600 calibration should do!!! */
296    case sm3750:
297    default:
298      yStart=100;  /* 54 is perimeter */
299      cStripes=MAX_CALIB_STRIPES;
300      cyGap=10;
301      break;
302    } /* switch */
303
304  DoJog(this,yStart);
305  /* scan a gray line at 600 DPI */
306  if (!this->calibration.achStripeY)
307    {
308      this->calibration.achStripeY=calloc(1,MAX_PIXEL_PER_SCANLINE);
309      if (!this->calibration.achStripeY)
310	return SetError(this,SANE_STATUS_NO_MEM,"no memory for calib Y");
311    }
312#ifdef SM3600_CALIB_USE_RMS
313  memset(aulSum,0,sizeof(aulSum));
314#endif
315  for (iLine=0; iLine<cStripes; iLine++)
316    {
317      dprintf(DEBUG_CALIB,"calibrating %i...\n",iLine);
318      RegWriteArray(this,R_ALL, 74, auchRegsSingleLine);
319      INST_ASSERT();
320      RegWrite(this,R_CTL, 1, 0x59);    /* #2496[062.5] */
321      RegWrite(this,R_CTL, 1, 0xD9);    /* #2497[062.5] */
322      rc=WaitWhileScanning(this,5); if (rc) { return rc; }
323      if (BulkReadBuffer(this,
324#ifdef SM3600_CALIB_USE_RMS
325			 this->calibration.achStripeY,
326#endif
327#ifdef SM3600_CALIB_USE_MEDIAN
328			 aauchY[iLine],
329#endif
330			 MAX_PIXEL_PER_SCANLINE)
331	  !=MAX_PIXEL_PER_SCANLINE)
332	return SetError(this,SANE_STATUS_IO_ERROR,"truncated bulk");
333#ifdef SM3600_CALIB_USE_RMS
334      for (i=0; i<MAX_PIXEL_PER_SCANLINE; i++)
335	aulSum[i]+=(long)this->calibration.achStripeY[i]*
336	  (long)this->calibration.achStripeY[i];
337#endif
338      DoJog(this,cyGap);
339    }
340#ifdef SM3600_CALIB_USE_RMS
341  for (i=0; i<MAX_PIXEL_PER_SCANLINE; i++)
342    this->calibration.achStripeY[i]=(unsigned char)(int)sqrt(aulSum[i]/cStripes);
343#endif
344#ifdef SM3600_CALIB_USE_MEDIAN
345  /* process the collected lines rowwise. Use intermediate buffer for qsort */
346  for (i=0; i<MAX_PIXEL_PER_SCANLINE; i++)
347    {
348      for (iLine=0; iLine<cStripes; iLine++)
349	auchRow[iLine]=aauchY[iLine][i];
350      qsort(auchRow,cStripes, sizeof(unsigned char), (TQSortProc)CompareProc);
351      this->calibration.achStripeY[i]=auchRow[(cStripes-1)/2];
352    }
353#endif
354#ifdef SM3600_CALIB_APPLY_HANNING_WINDOW
355  memcpy(auchHanning,this->calibration.achStripeY,sizeof(auchHanning));
356  for (i=1; i<MAX_PIXEL_PER_SCANLINE-1; i++)
357    this->calibration.achStripeY[i]=(unsigned char)
358      ((2*(int)auchHanning[i]+auchHanning[i-1]+auchHanning[i+1])/4);
359#endif
360
361  DoJog(this,-yStart-cStripes*cyGap);
362  INST_ASSERT();
363  this->calibration.bCalibrated=true;
364  return SANE_STATUS_GOOD;
365}
366
367/* **********************************************************************
368
369DoOriginate()
370
371*shall* one time move the slider safely back to its origin.
372No idea, hoiw to achieve this, for now...
373
374********************************************************************** */
375
376__SM3600EXPORT__
377TState DoOriginate(TInstance *this, TBool bStepOut)
378{
379  TLineType lt;
380  if (this->bVerbose)
381    fprintf(stderr,"carriage return...\n");
382  DBG(DEBUG_INFO,"DoOriginate()\n");
383  INST_ASSERT();
384  lt=GetLineType(this);
385  /* if we are already at home, fine. If not, first jump a bit forward */
386  DBG(DEBUG_JUNK,"lt1=%d\n",(int)lt);
387  if (lt!=ltHome && bStepOut) DoJog(this,150);
388  while (lt!=ltHome && !this->state.bCanceled)
389    {
390      lt=GetLineType(this);
391      DBG(DEBUG_JUNK,"lt2=%d\n",(int)lt);
392      INST_ASSERT();
393      switch (lt)
394	{
395	case ltHome: continue;
396	case ltBed:  DoJog(this,-240); break; /* worst case: 1 cm */
397	default:     DoJog(this,-15); break; /* 0.X mm */
398	}
399    }
400  DoJog(this,1); INST_ASSERT(); /* Correction for 1 check line */
401  DBG(DEBUG_JUNK,"lt3=%d\n",(int)lt);
402  if (this->state.bCanceled)
403    return SANE_STATUS_CANCELLED;
404  return DoCalibration(this);
405}
406
407/* **********************************************************************
408
409DoJog(nDistance)
410
411The distance is given in 600 DPI.
412
413********************************************************************** */
414
415__SM3600EXPORT__
416TState DoJog(TInstance *this, int nDistance)
417{
418  int cSteps;
419  int nSpeed,nRest;
420  dprintf(DEBUG_SCAN,"jogging %d units...\n",nDistance);
421  if (!nDistance) return 0;
422  RegWrite(this,0x34, 1, 0x63);
423  RegWrite(this,0x49, 1, 0x96);
424  WaitWhileBusy(this,2);
425  RegWrite(this,0x34, 1, 0x63);
426  RegWrite(this,0x49, 1, 0x9E); /* that is a difference! */
427  INST_ASSERT();
428  cSteps=(nDistance>0) ? nDistance : -nDistance;
429  {
430    unsigned char uchRegs2587[]={
431      0x00 /*0x01*/, 0x00 /*0x02*/, 0x3F /*0x03*/,
432      0x40 /*!!0x04!!*/, 0x00 /*!!0x05!!*/,
433      0,0, /* steps */
434      0x00 /*0x08*/, 0x00 /*!!0x09!!*/,
435      0,0, /* y count */
436      0x6D /*0x0C*/,
437      0x70 /*0x0D*/, 0x69 /*0x0E*/, 0xD0 /*0x0F*/,
438      0x00 /*0x10*/, 0x00 /*0x11*/, 0x40 /*0x12*/,
439      0x15 /*0x13*/, 0x80 /*0x14*/, 0x2A /*0x15*/,
440      0xC0 /*0x16*/, 0x40 /*0x17*/, 0xC0 /*0x18*/,
441      0x40 /*0x19*/, 0xFF /*0x1A*/, 0x01 /*0x1B*/,
442      0x88 /*0x1C*/, 0x40 /*0x1D*/, 0x4C /*0x1E*/,
443      0x50 /*0x1F*/, 0x00 /*0x20*/, 0x0C /*0x21*/,
444      0x21 /*0x22*/, 0xF0 /*0x23*/, 0x40 /*0x24*/,
445      0x00 /*0x25*/, 0x0A /*0x26*/, 0xF0 /*0x27*/,
446      0x00 /*0x28*/, 0x00 /*0x29*/, 0x4E /*0x2A*/,
447      0xF0 /*0x2B*/, 0x00 /*0x2C*/, 0x00 /*0x2D*/,
448      0x4E /*0x2E*/, 0x88 /*R_CCAL*/, 0x88 /*R_CCAL2*/,
449      0x84 /*R_CCAL3*/, 0xEA /*R_LEN*/, 0x24 /*R_LENH*/,
450      0x63 /*0x34*/, 0x29 /*0x35*/, 0x00 /*0x36*/,
451      0x00 /*0x37*/, 0x00 /*0x38*/, 0x00 /*0x39*/,
452      0x00 /*0x3A*/, 0x00 /*0x3B*/, 0xFF /*0x3C*/,
453      0x0F /*0x3D*/, 0x00 /*0x3E*/, 0x00 /*0x3F*/,
454      0x01 /*0x40*/, 0x00 /*0x41*/, 0x80 /*R_CSTAT*/,
455      0x03 /*R_SPD*/, 0x01 /*0x44*/, 0x00 /*0x45*/,
456      0x79 /*!!R_CTL!!*/, 0xC0 /*0x47*/, 0x40 /*0x48*/,
457      0x9E /*!!0x49!!*/, 0xD8 /*0x4A*/ };
458    RegWriteArray(this,R_ALL, 74, uchRegs2587);
459  }    /* #2587[065.4] */
460  INST_ASSERT();
461  RegWrite(this,R_STPS,2,cSteps);
462  /* do some magic for slider acceleration */
463  if (cSteps>600) /* only large movements are accelerated */
464    {
465      RegWrite(this,0x34, 1, 0xC3);
466      RegWrite(this,0x47, 2, 0xA000);    /* initial speed */
467    }
468  /* start back or forth movement */
469  if (nDistance>0)
470    {
471      RegWrite(this,R_CTL, 1, 0x39);    /* #2588[065.4] */
472      RegWrite(this,R_CTL, 1, 0x79);    /* #2589[065.4] */
473      RegWrite(this,R_CTL, 1, 0xF9);    /* #2590[065.4] */
474    }
475  else
476    {
477      RegWrite(this,R_CTL, 1, 0x59);
478      RegWrite(this,R_CTL, 1, 0xD9);
479    }
480  INST_ASSERT();
481  /* accelerate the slider each 100 us */
482  if (cSteps>600)
483    {
484      nRest=cSteps;
485      for (nSpeed=0x9800; nRest>600 && nSpeed>=0x4000; nSpeed-=0x800)
486	{
487	  nRest=RegRead(this,R_POS, 2);
488	  usleep(100);
489	  /* perhaps 40C0 is the fastest possible value */
490	  RegWrite(this,0x47, 2, nSpeed>0x4000 ? nSpeed : 0x40C0);
491	}
492    }
493  INST_ASSERT();
494  usleep(100);
495  return WaitWhileBusy(this,1000); /* thanks Mattias Ellert */
496}
497