1 /*
2   Copyright (C) 2001 Bertrik Sikken (bertrik@zonnet.nl)
3 
4   This program is free software; you can redistribute it and/or
5   modify it under the terms of the GNU General Public License
6   as published by the Free Software Foundation; either version 2
7   of the License, or (at your option) any later version.
8 
9   This program is distributed in the hope that it will be useful,
10   but WITHOUT ANY WARRANTY; without even the implied warranty of
11   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12   GNU General Public License for more details.
13 
14   You should have received a copy of the GNU General Public License
15   along with this program.  If not, see <https://www.gnu.org/licenses/>.
16 */
17 
18 /*
19     Core NIASH chip functions.
20 */
21 
22 #include <stdio.h>		/* fopen, fread, fwrite, fclose etc */
23 #include <stdarg.h>		/* va_list for vfprintf */
24 #include <string.h>		/* memcpy, memset */
25 #include <unistd.h>		/* unlink */
26 #include <stdlib.h>		/* malloc, free */
27 #include <math.h>		/* exp, pow */
28 
29 #include "niash_xfer.h"
30 #include "niash_core.h"
31 
32 
33 #ifndef MIN
34 #define MIN(a,b) (((a) < (b)) ? (a) : (b))
35 #endif
36 
37 #ifndef MAX
38 #define MAX(a,b) (((a) > (b)) ? (a) : (b))
39 #endif
40 
41 
42 #define XFER_BUF_SIZE  0xF000
43 
44 
45 /* HP3400 firmware data */
46 static unsigned char abData0000[] = {
47   0xfe, 0x9f, 0x58, 0x1b, 0x00, 0x03, 0xa4, 0x02, 0x63, 0x02, 0x33, 0x02,
48   0x0d, 0x02, 0xf0, 0x01,
49   0xd8, 0x01, 0xc5, 0x01, 0xb5, 0x01, 0xa8, 0x01, 0x9d, 0x01, 0x93, 0x01,
50   0x8b, 0x01, 0x84, 0x01,
51   0x7e, 0x01, 0x79, 0x01, 0x74, 0x01, 0x70, 0x01, 0x6d, 0x01, 0x69, 0x01,
52   0x67, 0x01, 0x64, 0x01,
53   0x62, 0x01, 0x60, 0x01, 0x5f, 0x01, 0x5d, 0x01, 0x5c, 0x01, 0x5b, 0x01,
54   0x5a, 0x01, 0x59, 0x01,
55   0x58, 0x01, 0x57, 0x01, 0x57, 0x01, 0x56, 0x01, 0x56, 0x01, 0x55, 0x01,
56   0x55, 0x01, 0x54, 0x01,
57   0x54, 0x01, 0x54, 0x01, 0x54, 0x01, 0x53, 0x01, 0x53, 0x01, 0x53, 0x01,
58   0x53, 0x01, 0x52, 0x81
59 };
60 
61 /*  1st word : 0x9ffe = 40958, strip 15th bit: 0x1ffe = 8190
62     2nd word : 0x1b58 = 7000 -> coincidence ?
63     other words: formula: y = 676 / (2 - exp(0.113 * (1-x)) ), where x = 0 for first entry
64 */
65 
66 /* more HP3400 firmware data */
67 static unsigned char abData0400[] = {
68   0xa4, 0x82, 0x00, 0x80, 0xa4, 0x82, 0xaa, 0x02, 0xc0, 0x02, 0xe8, 0x02,
69   0x3e, 0x03, 0xc8, 0x03,
70   0x58, 0x1b, 0xfe, 0x9f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
71   0x00, 0x00, 0x00, 0x00,
72   0x00, 0x00, 0x00, 0x00
73 };
74 
75 
76 
77 static void
_ConvertMotorTable(unsigned char *pabOld, unsigned char *pabNew, int iSize, int iLpi)78 _ConvertMotorTable (unsigned char *pabOld, unsigned char *pabNew, int iSize,
79 		    int iLpi)
80 {
81   int iData, i, iBit15;
82 
83   for (i = 0; i < (iSize / 2); i++)
84     {
85       iData = pabOld[2 * i + 0] + (pabOld[2 * i + 1] << 8);
86       iBit15 = (iData & 0x8000);
87       iData = (iData & 0x7FFF);
88       if (iData <= 0x400)
89 	{
90 	  iData = iData * iLpi / 300;
91 	}
92       if (iBit15 != 0)
93 	{
94 	  iData |= 0x8000;
95 	}
96       pabNew[2 * i + 0] = iData & 255;
97       pabNew[2 * i + 1] = (iData >> 8) & 255;
98     }
99 }
100 
101 
102 /*************************************************************************
103   _ProbeRegisters
104   ===============
105     Tries to determine certain hardware properties.
106 
107   This is done by checking the writeability of some scanner registers.
108   We cannot rely simply on the scanner model to contain a specific
109   chip. The HP3300c for example uses one of at least three slightly
110   different scanner ASICs (NIASH00012, NIASH00013 and NIASH00014).
111 
112   OUT pHWParams     Hardware parameters, updated fields:
113         fGamma16    TRUE if 16 bit gamma tables can be used
114         fReg07      TRUE if reg07 is writeable
115         iBufferSize Size of scanner's internal buffer
116 
117   Returns TRUE if a NIASH chipset was found.
118 *************************************************************************/
119 static SANE_Bool
_ProbeRegisters(THWParams * pHWParams)120 _ProbeRegisters (THWParams * pHWParams)
121 {
122   unsigned char bData1, bData2;
123   int iHandle;
124 
125   iHandle = pHWParams->iXferHandle;
126 
127   DBG (DBG_MSG, "Probing scanner...\n");
128 
129   /* check register 0x04 */
130   NiashWriteReg (iHandle, 0x04, 0x55);
131   NiashReadReg (iHandle, 0x04, &bData1);
132   NiashWriteReg (iHandle, 0x04, 0xAA);
133   NiashReadReg (iHandle, 0x04, &bData2);
134   NiashWriteReg (iHandle, 0x04, 0x07);
135   if ((bData1 != 0x55) || (bData2 != 0xAA))
136     {
137       DBG (DBG_ERR, "  No NIASH chipset found!\n");
138       return SANE_FALSE;
139     }
140 
141   /* check writeability of register 3 bit 1 */
142   NiashReadReg (iHandle, 0x03, &bData1);
143   NiashWriteReg (iHandle, 0x03, bData1 | 0x02);
144   NiashReadReg (iHandle, 0x03, &bData2);
145   NiashWriteReg (iHandle, 0x03, bData1);
146   pHWParams->fGamma16 = ((bData2 & 0x02) != 0);
147   DBG (DBG_MSG, "  Gamma table entries are %d bit\n",
148        pHWParams->fGamma16 ? 16 : 8);
149 
150   /* check register 0x07 */
151   NiashReadReg (iHandle, 0x07, &bData1);
152   NiashWriteReg (iHandle, 0x07, 0x1C);
153   NiashReadReg (iHandle, 0x07, &bData2);
154   NiashWriteReg (iHandle, 0x07, bData1);
155   pHWParams->fReg07 = (bData2 == 0x1C);
156 
157   if (!pHWParams->fGamma16)
158     {
159       /* internal scan buffer size is an educated guess, but seems to correlate
160          well with the size calculated from several windows driver log files
161          size = 128kB - 44088 unsigned chars (space required for gamma/calibration table)
162        */
163       pHWParams->iBufferSize = 86984L;
164       DBG (DBG_MSG, "  NIASH version < 00014\n");
165     }
166   else
167     {
168       pHWParams->iBufferSize = 0x60000L;
169       if (!pHWParams->fReg07)
170 	{
171 	  DBG (DBG_MSG, "  NIASH version = 00014\n");
172 	}
173       else
174 	{
175 	  DBG (DBG_MSG, "  NIASH version > 00014\n");
176 	}
177     }
178 
179   return SANE_TRUE;
180 }
181 
182 
183 /* returns 0 on success, < 0 otherwise */
184 STATIC int
NiashOpen(THWParams * pHWParams, const char *pszName)185 NiashOpen (THWParams * pHWParams, const char *pszName)
186 {
187   int iXferHandle;
188 
189   iXferHandle = NiashXferOpen (pszName, &pHWParams->eModel);
190   if (iXferHandle < 0)
191     {
192       DBG (DBG_ERR, "NiashXferOpen failed for '%s'\n", pszName);
193       return -1;
194     }
195 
196   pHWParams->iXferHandle = iXferHandle;
197 
198   NiashWakeup (pHWParams->iXferHandle);
199 
200   /* default HW params */
201   pHWParams->iSensorSkew = 8;
202   pHWParams->iTopLeftX = 0;
203   pHWParams->iTopLeftY = 3;
204   pHWParams->fReg07 = SANE_FALSE;
205   pHWParams->iSkipLines = 0;
206   pHWParams->iExpTime = 5408;
207   pHWParams->iReversedHead = SANE_TRUE;
208 
209   switch (pHWParams->eModel)
210     {
211 
212     case eHp3300c:
213       DBG (DBG_MSG, "Setting params for Hp3300\n");
214       pHWParams->iTopLeftX = 4;
215       pHWParams->iTopLeftY = 11;
216       pHWParams->iSkipLines = 14;
217       break;
218 
219     case eHp3400c:
220     case eHp4300c:
221       DBG (DBG_MSG, "Setting params for Hp3400c/Hp4300c\n");
222       pHWParams->iTopLeftX = 3;
223       pHWParams->iTopLeftY = 14;
224       pHWParams->fReg07 = SANE_TRUE;
225       break;
226 
227     case eAgfaTouch:
228       DBG (DBG_MSG, "Setting params for AgfaTouch\n");
229       pHWParams->iReversedHead = SANE_FALSE;	/* head not reversed on Agfa Touch */
230       pHWParams->iTopLeftX = 3;
231       pHWParams->iTopLeftY = 10;
232       pHWParams->iSkipLines = 7;
233       break;
234 
235     case eUnknownModel:
236       DBG (DBG_MSG, "Setting params for UnknownModel\n");
237       break;
238 
239     default:
240       DBG (DBG_ERR, "ERROR: internal error! (%d)\n", (int) pHWParams->eModel);
241       return -1;
242     }
243 
244   /* autodetect some hardware properties */
245   if (!_ProbeRegisters (pHWParams))
246     {
247       DBG (DBG_ERR, "_ProbeRegisters failed!\n");
248       return -1;
249     }
250 
251   return 0;
252 }
253 
254 
255 STATIC void
NiashClose(THWParams * pHWPar)256 NiashClose (THWParams * pHWPar)
257 {
258   NiashXferClose (pHWPar->iXferHandle);
259   pHWPar->iXferHandle = 0;
260 }
261 
262 
263 static void
WriteRegWord(int iHandle, unsigned char bReg, SANE_Word wData)264 WriteRegWord (int iHandle, unsigned char bReg, SANE_Word wData)
265 {
266   NiashWriteReg (iHandle, bReg, wData & 0xFF);
267   NiashWriteReg (iHandle, bReg + 1, (wData >> 8) & 0xFF);
268 }
269 
270 
271 /* calculate a 4096 unsigned char gamma table */
272 STATIC void
CalcGamma(unsigned char *pabTable, double Gamma)273 CalcGamma (unsigned char *pabTable, double Gamma)
274 {
275   int i, iData;
276 
277   /* fill gamma table */
278   for (i = 0; i < 4096; i++)
279     {
280       iData = floor (256.0 * pow (((double) i / 4096.0), 1.0 / Gamma));
281       pabTable[i] = iData;
282     }
283 }
284 
285 
286 /*
287   Hp3400WriteFw
288   =============
289     Writes data to scanners with a NIASH00019 chipset, e.g.
290     gamma, calibration and motor control data.
291 
292   IN  pabData   pointer to firmware data
293       iLen      Size of firmware date (unsigned chars)
294       iAddr     Scanner address to write to
295 */
296 static void
Hp3400cWriteFW(int iXferHandle, unsigned char *pabData, int iLen, int iAddr)297 Hp3400cWriteFW (int iXferHandle, unsigned char *pabData, int iLen, int iAddr)
298 {
299   iAddr--;
300   NiashWriteReg (iXferHandle, 0x21, iAddr & 0xFF);
301   NiashWriteReg (iXferHandle, 0x22, (iAddr >> 8) & 0xFF);
302   NiashWriteReg (iXferHandle, 0x23, (iAddr >> 16) & 0xFF);
303   NiashWriteBulk (iXferHandle, pabData, iLen);
304 }
305 
306 
307 /* Writes the gamma and offset/gain tables to the scanner.
308    In case a calibration file exist, it will be used for offset/gain */
309 STATIC void
WriteGammaCalibTable(unsigned char *pabGammaR, unsigned char *pabGammaG, unsigned char *pabGammaB, unsigned char *pabCalibTable, int iGain, int iOffset, THWParams * pHWPar)310 WriteGammaCalibTable (unsigned char *pabGammaR, unsigned char *pabGammaG,
311 		      unsigned char *pabGammaB, unsigned char *pabCalibTable,
312 		      int iGain, int iOffset, THWParams * pHWPar)
313 {
314   int i, j, k;
315   static unsigned char abGamma[60000];
316   int iData;
317   int iHandle;
318 
319   iHandle = pHWPar->iXferHandle;
320 
321   j = 0;
322   /* fill gamma table for red component */
323   /* pad entries with 0 for 16-bit gamma table */
324   for (i = 0; i < 4096; i++)
325     {
326       if (pHWPar->fGamma16)
327 	{
328 	  abGamma[j++] = 0;
329 	}
330       abGamma[j++] = pabGammaR[i];
331     }
332   /* fill gamma table for green component */
333   for (i = 0; i < 4096; i++)
334     {
335       if (pHWPar->fGamma16)
336 	{
337 	  abGamma[j++] = 0;
338 	}
339       abGamma[j++] = pabGammaG[i];
340     }
341   /* fill gamma table for blue component */
342   for (i = 0; i < 4096; i++)
343     {
344       if (pHWPar->fGamma16)
345 	{
346 	  abGamma[j++] = 0;
347 	}
348       abGamma[j++] = pabGammaB[i];
349     }
350 
351   if (pabCalibTable == NULL)
352     {
353       iData = (iGain << 6) + iOffset;
354       for (i = 0; i < HW_PIXELS; i++)
355 	{
356 	  for (k = 0; k < 3; k++)
357 	    {
358 	      abGamma[j++] = (iData) & 255;
359 	      abGamma[j++] = (iData >> 8) & 255;
360 	    }
361 	}
362     }
363   else
364     {
365       memcpy (&abGamma[j], pabCalibTable, HW_PIXELS * 6);
366       j += HW_PIXELS * 6;
367     }
368 
369   NiashWriteReg (iHandle, 0x02, 0x80);
370   NiashWriteReg (iHandle, 0x03, 0x01);
371   NiashWriteReg (iHandle, 0x03, 0x11);
372   NiashWriteReg (iHandle, 0x02, 0x84);
373 
374   if (pHWPar->fReg07)
375     {
376       Hp3400cWriteFW (iHandle, abGamma, j, 0x2000);
377     }
378   else
379     {
380       NiashWriteBulk (iHandle, abGamma, j);
381     }
382 
383   NiashWriteReg (iHandle, 0x02, 0x80);
384 }
385 
386 
387 static void
WriteAFEReg(int iHandle, int iReg, int iData)388 WriteAFEReg (int iHandle, int iReg, int iData)
389 {
390   NiashWriteReg (iHandle, 0x25, iReg);
391   NiashWriteReg (iHandle, 0x26, iData);
392 }
393 
394 
395 /* setup the analog front-end -> coarse calibration */
396 static void
WriteAFE(int iHandle)397 WriteAFE (int iHandle)
398 {
399   /* see WM8143 datasheet */
400 
401   WriteAFEReg (iHandle, 0x04, 0x00);
402   WriteAFEReg (iHandle, 0x03, 0x12);
403   WriteAFEReg (iHandle, 0x02, 0x04);
404   WriteAFEReg (iHandle, 0x05, 0x10);
405   WriteAFEReg (iHandle, 0x01, 0x03);
406 
407   WriteAFEReg (iHandle, 0x20, 0xc0);	/*c8 *//* red offset */
408   WriteAFEReg (iHandle, 0x21, 0xc0);	/*c8 *//* green offset */
409   WriteAFEReg (iHandle, 0x22, 0xc0);	/*d0 *//* blue offset */
410 
411   WriteAFEReg (iHandle, 0x28, 0x05);	/*5 *//* red gain */
412   WriteAFEReg (iHandle, 0x29, 0x03);	/*3 *//* green gain */
413   WriteAFEReg (iHandle, 0x2A, 0x04);	/*4 *//* blue gain */
414 }
415 
416 
417 /* wait for the carriage to return */
418 static void
WaitReadyBit(int iHandle)419 WaitReadyBit (int iHandle)
420 {
421   unsigned char bData;
422 
423   do
424     {
425       NiashReadReg (iHandle, 0x03, &bData);
426     }
427   while ((bData & 8) == 0);
428 }
429 
430 
431 /*
432   Initialisation specific for NIASH00014 and lower chips
433 */
434 static void
InitNiash00014(TScanParams * pParams, THWParams * pHWParams)435 InitNiash00014 (TScanParams * pParams, THWParams * pHWParams)
436 {
437   int iHandle, iLpiCode;
438 
439   iHandle = pHWParams->iXferHandle;
440 
441   /* exposure time (in units 24/Fcrystal)? */
442   WriteRegWord (iHandle, 0x08, pHWParams->iExpTime - 1);
443 
444   /* width in pixels */
445   WriteRegWord (iHandle, 0x12, pParams->iWidth - 1);
446 
447   /* top */
448   WriteRegWord (iHandle, 0x17, pParams->iTop);
449   WriteRegWord (iHandle, 0x19, pParams->iTop);
450 
451   /* time between stepper motor steps (in units of 24/Fcrystal)? */
452   iLpiCode = pParams->iLpi * pHWParams->iExpTime / 1200L;
453 
454   if (!pHWParams->fGamma16)
455     {
456       /* NIASH 00012 / 00013 init */
457 
458       /* LPI specific settings */
459       if (pParams->iLpi < 600)
460 	{
461 	  /* set halfres bit */
462 	  NiashWriteReg (iHandle, 0x06, 0x01);
463 	  /* double lpi code because of halfres bit */
464 	  iLpiCode *= 2;
465 	}
466       else
467 	{
468 	  /* clear halfres bit */
469 	  NiashWriteReg (iHandle, 0x06, 0x00);
470 	  /* add exptime to make it scan slower */
471 	  iLpiCode += pHWParams->iExpTime;
472 	}
473 
474       /* unknown setting */
475       WriteRegWord (iHandle, 0x27, 0x7FD2);
476       WriteRegWord (iHandle, 0x29, 0x6421);
477 
478     }
479   else
480     {
481       /* NIASH 00014 init */
482 
483       /* halfres bit always cleared */
484       NiashWriteReg (iHandle, 0x06, 0x00);
485 
486       /* LPI specific settings */
487       if (pParams->iLpi >= 600)
488 	{
489 	  /* add exptime to make it scan slower */
490 	  iLpiCode += pHWParams->iExpTime;
491 	}
492 
493       /* unknown setting */
494       WriteRegWord (iHandle, 0x27, 0xc862);	/*c862 */
495       WriteRegWord (iHandle, 0x29, 0xb853);	/*b853 */
496     }
497 
498   /* LPI code */
499   WriteRegWord (iHandle, 0x0A, iLpiCode - 1);
500 
501   /* backtrack reversing speed */
502   NiashWriteReg (iHandle, 0x1E, (iLpiCode - 1) / 32);
503 }
504 
505 
506 /*
507   Initialisation specific for NIASH00019 chips
508 */
509 static void
InitNiash00019(TScanParams * pParams, THWParams * pHWParams)510 InitNiash00019 (TScanParams * pParams, THWParams * pHWParams)
511 {
512   int iHandle, iLpiCode;
513   static unsigned char abMotor[512];
514 
515 
516   iHandle = pHWParams->iXferHandle;
517 
518   /* exposure time (in units 24/Fcrystal)? */
519   WriteRegWord (iHandle, 0x08, pHWParams->iExpTime);
520 
521   /* width in pixels */
522   WriteRegWord (iHandle, 0x12, pParams->iWidth);
523 
524   /* ? */
525   WriteRegWord (iHandle, 0x27, 0xc862);	/*c862 */
526   WriteRegWord (iHandle, 0x29, 0xb853);	/*b853 */
527 
528   /* specific handling of 150 dpi resolution */
529   if (pParams->iLpi == 150)
530     {
531       /* use 300 LPI but skip every other line */
532       pParams->iLpi = 300;
533       NiashWriteReg (iHandle, 0x06, 0x01);
534     }
535   else
536     {
537       NiashWriteReg (iHandle, 0x06, 0x00);
538     }
539 
540   /* DPI and position table */
541   NiashWriteReg (iHandle, 0x07, 0x02);
542   _ConvertMotorTable (abData0000, abMotor, sizeof (abData0000),
543 		      pParams->iLpi);
544   Hp3400cWriteFW (iHandle, abMotor, sizeof (abData0000), 0x000);
545   _ConvertMotorTable (abData0400, abMotor, sizeof (abData0400),
546 		      pParams->iLpi);
547   Hp3400cWriteFW (iHandle, abMotor, sizeof (abData0400), 0x400);
548 
549   /* backtrack reversing speed */
550   iLpiCode = pParams->iLpi * pHWParams->iExpTime / 1200L;
551   NiashWriteReg (iHandle, 0x1E, (iLpiCode - 1) / 32);
552 }
553 
554 
555 /*
556   Scanner initialisation common to all NIASH chips
557 */
558 static void
InitNiashCommon(TScanParams * pParams, THWParams * pHWParams)559 InitNiashCommon (TScanParams * pParams, THWParams * pHWParams)
560 {
561   int iWidthHW, iHandle, iMaxLevel;
562 
563 
564   iHandle = pHWParams->iXferHandle;
565 
566   NiashWriteReg (iHandle, 0x02, 0x80);
567   NiashWriteReg (iHandle, 0x03, 0x11);
568   NiashWriteReg (iHandle, 0x01, 0x8B);
569   NiashWriteReg (iHandle, 0x05, 0x01);
570 
571   /* dpi */
572   WriteRegWord (iHandle, 0x0C, pParams->iDpi);
573 
574   /* calculate width in units of HW resolution */
575   iWidthHW = pParams->iWidth * (HW_DPI / pParams->iDpi);
576 
577   /* set left and right limits */
578   if (pHWParams->iReversedHead)
579     {
580       /* head is reversed */
581       /* right */
582       WriteRegWord (iHandle, 0x0E,
583 		    3 * (HW_PIXELS - (pParams->iLeft + iWidthHW)));
584 
585       /* left */
586       WriteRegWord (iHandle, 0x10, 3 * (HW_PIXELS - pParams->iLeft) - 1);
587     }
588   else
589     {
590       /* head is not reversed */
591       /*left  */
592       WriteRegWord (iHandle, 0x0E, 3 * pParams->iLeft);
593 
594       /* right */
595       WriteRegWord (iHandle, 0x10, 3 * (pParams->iLeft + iWidthHW) - 1);
596     }
597 
598   /* bottom */
599   WriteRegWord (iHandle, 0x1B, pParams->iBottom);	/* 0x393C); */
600 
601   /* forward jogging speed */
602   NiashWriteReg (iHandle, 0x1D, 0x60);
603 
604   /* backtrack reversing speed? */
605   NiashWriteReg (iHandle, 0x2B, 0x15);
606 
607   /* backtrack distance */
608   if (pParams->iLpi < 600)
609     {
610       NiashWriteReg (iHandle, 0x1F, 0x30);
611     }
612   else
613     {
614       NiashWriteReg (iHandle, 0x1F, 0x18);
615     }
616 
617   /* max buffer level before backtrace */
618   iMaxLevel = MIN (pHWParams->iBufferSize / pParams->iWidth, 250);
619   NiashWriteReg (iHandle, 0x14, iMaxLevel - 1);
620 
621   /* lamp PWM, max = 0x1ff? */
622   WriteRegWord (iHandle, 0x2C, 0x01FF);
623 
624   /* not needed? */
625   NiashWriteReg (iHandle, 0x15, 0x90);	/* 90 */
626   NiashWriteReg (iHandle, 0x16, 0x70);	/* 70 */
627 
628   WriteAFE (iHandle);
629 
630   WaitReadyBit (iHandle);
631 
632   NiashWriteReg (iHandle, 0x03, 0x05);
633 
634   NiashWriteReg (iHandle, 0x02, pParams->fCalib ? 0x88 : 0xA8);
635 }
636 
637 
638 /* write registers */
639 STATIC SANE_Bool
InitScan(TScanParams * pParams, THWParams * pHWParams)640 InitScan (TScanParams * pParams, THWParams * pHWParams)
641 {
642   int iHeight;
643   int iExpTime;
644   TScanParams Params;
645 
646   /* check validity of scanparameters */
647   switch (pParams->iDpi)
648     {
649     case 150:
650     case 300:
651     case 600:
652       break;
653     default:
654       DBG (DBG_ERR, "Invalid dpi (%d)\n", pParams->iDpi);
655       return SANE_FALSE;
656     }
657 
658   iHeight = (pParams->iBottom - pParams->iTop + 1);
659   if (iHeight <= 0)
660     {
661       DBG (DBG_ERR, "Invalid height (%d)\n", iHeight);
662       return SANE_FALSE;
663     }
664 
665   if (pParams->iWidth <= 0)
666     {
667       DBG (DBG_ERR, "Invalid width (%d)\n", pParams->iWidth);
668       return SANE_FALSE;
669     }
670 
671   switch (pParams->iLpi)
672     {
673     case 150:
674     case 300:
675     case 600:
676       break;
677     default:
678       DBG (DBG_ERR, "Invalid lpi (%d)\n", pParams->iLpi);
679       return SANE_FALSE;
680     }
681 
682   /* exposure time (in units of 24/Fcrystal?), must be divisible by 8 !!! */
683   iExpTime = 5408;
684   if ((iExpTime % 8) != 0)
685     {
686       DBG (DBG_ERR, "Invalid exposure time (%d)\n", iExpTime);
687       return SANE_FALSE;
688     }
689 
690   /*
691    *** Done checking scan parameters validity ***
692    */
693 
694   /*
695      copy the parameters locally and make pParams point to the local copy
696    */
697   memcpy (&Params, pParams, sizeof (Params));
698   pParams = &Params;
699 
700   if (!pHWParams->fReg07)
701     {
702       /* init NIASH00014 and lower */
703       InitNiash00014 (pParams, pHWParams);
704     }
705   else
706     {
707       /* init NIASH00019 */
708       InitNiash00019 (pParams, pHWParams);
709     }
710 
711   /* common NIASH init */
712   InitNiashCommon (pParams, pHWParams);
713 
714   return SANE_TRUE;
715 }
716 
717 
718 /************************************************************************/
719 
720 static SANE_Bool
XferBufferGetLine(int iHandle, TDataPipe * p, unsigned char *pabLine, SANE_Bool fReturn)721 XferBufferGetLine (int iHandle, TDataPipe * p, unsigned char *pabLine,
722 		   SANE_Bool fReturn)
723 {
724   unsigned char bData, bData2;
725   SANE_Bool fJustDone = SANE_FALSE;
726   /* all calculated transfers done ? */
727   if (p->iLinesLeft == 0)
728     return SANE_FALSE;
729 
730   /* time for a fresh read? */
731   if (p->iCurLine == 0)
732     {
733       int iLines;
734       iLines = p->iLinesPerXferBuf;
735       /* read only as many lines as needed */
736       if (p->iLinesLeft > 0 && p->iLinesLeft <= iLines)
737 	{
738 	  iLines = p->iLinesLeft;
739 	  DBG (DBG_MSG, "\n");
740 	  DBG (DBG_MSG, "last bulk read\n");
741 	  if (iLines < p->iLinesPerXferBuf)
742 	    {
743 	      DBG (DBG_MSG,
744 		   "reading reduced number of lines: %d instead of %d\n",
745 		   iLines, p->iLinesPerXferBuf);
746 	    }
747 	  fJustDone = SANE_TRUE;
748 	}
749       /* reading old buffer level */
750       NiashReadReg (iHandle, 0x20, &bData);
751       NiashReadBulk (iHandle, p->pabXferBuf, iLines * p->iBytesPerLine);
752       /* reding new buffer level */
753       NiashReadReg (iHandle, 0x20, &bData2);
754       if (fJustDone && fReturn)
755 	{
756 	  NiashWriteReg (iHandle, 0x02, 0x80);
757 	  DBG (DBG_MSG, "returning scanner head\n");
758 	}
759       DBG (DBG_MSG,
760 	   "buffer level = %3d, <reading %5d unsigned chars>, buffer level = %3d\r",
761 	   (int) bData, iLines * p->iBytesPerLine, (int) bData2);
762       fflush (stdout);
763     }
764   /* copy one line */
765   if (pabLine != NULL)
766     {
767       memcpy (pabLine, &p->pabXferBuf[p->iCurLine * p->iBytesPerLine],
768 	      p->iBytesPerLine);
769     }
770   /* advance pointer */
771   p->iCurLine = (p->iCurLine + 1) % p->iLinesPerXferBuf;
772 
773   /* one transfer line less to the XFerBuffer */
774   if (p->iLinesLeft > 0)
775     --(p->iLinesLeft);
776   return SANE_TRUE;
777 }
778 
779 
780 static void
XferBufferInit(int iHandle, TDataPipe * p)781 XferBufferInit (int iHandle, TDataPipe * p)
782 {
783   int i;
784 
785   p->pabXferBuf = (unsigned char *) malloc (XFER_BUF_SIZE);
786   p->iCurLine = 0;
787 
788   /* skip garbage lines */
789   for (i = 0; i < p->iSkipLines; i++)
790     {
791       XferBufferGetLine (iHandle, p, NULL, SANE_FALSE);
792     }
793 }
794 
795 /* static procedure that fills the circular buffer in advance to any
796    circular buffer data retrieval */
797 static void
CircBufferFill(int iHandle, TDataPipe * p, SANE_Bool iReversedHead)798 CircBufferFill (int iHandle, TDataPipe * p, SANE_Bool iReversedHead)
799 {
800   int i;
801   for (i = 0; i < p->iLinesPerCircBuf; i++)
802     {
803       if (iReversedHead)
804 	{
805 	  XferBufferGetLine (iHandle, p,
806 			     &p->pabCircBuf[p->iRedLine * p->iBytesPerLine],
807 			     SANE_FALSE);
808 	}
809       else
810 	{
811 	  XferBufferGetLine (iHandle, p,
812 			     &p->pabCircBuf[p->iBluLine * p->iBytesPerLine],
813 			     SANE_FALSE);
814 	}
815       /* advance pointers */
816       p->iRedLine = (p->iRedLine + 1) % p->iLinesPerCircBuf;
817       p->iGrnLine = (p->iGrnLine + 1) % p->iLinesPerCircBuf;
818       p->iBluLine = (p->iBluLine + 1) % p->iLinesPerCircBuf;
819     }
820 }
821 
822 static void
XferBufferExit(TDataPipe * p)823 XferBufferExit (TDataPipe * p)
824 {
825   if (p->pabXferBuf != NULL)
826     {
827       free (p->pabXferBuf);
828       p->pabXferBuf = NULL;
829     }
830   else
831     {
832       DBG (DBG_ERR, "XferBufExit: Xfer buffer not initialised!\n");
833     }
834 }
835 
836 
837 /* unscrambles a line:
838    - combining the proper R, G and B lines and converting them to interpixel RGB
839    - mirroring left to right
840 */
841 static void
_UnscrambleLine(unsigned char *pabLine, unsigned char *pabRed, unsigned char *pabGrn, unsigned char *pabBlu, int iWidth, SANE_Bool iReversedHead, int iScaleDownDpi, int iBufWeight)842 _UnscrambleLine (unsigned char *pabLine,
843 		 unsigned char *pabRed, unsigned char *pabGrn,
844 		 unsigned char *pabBlu, int iWidth, SANE_Bool iReversedHead,
845 		 int iScaleDownDpi, int iBufWeight)
846 {
847   /* never change an approved algorithm ...
848      so take Bertriks original source for this special case */
849   if (iScaleDownDpi == 1 && iBufWeight == 0)
850     {
851       int i, j;
852       if (iReversedHead)
853 	{
854 	  /* reversed */
855 	  for (i = 0; i < iWidth; i++)
856 	    {
857 	      j = (iWidth - i) * 3;
858 	      pabLine[j - 3] = pabRed[i];
859 	      pabLine[j - 2] = pabGrn[i + iWidth];
860 	      pabLine[j - 1] = pabBlu[i + iWidth * 2];
861 	    }
862 	}
863       else
864 	{
865 	  /* not reversed */
866 	  for (i = 0; i < iWidth; i++)
867 	    {
868 	      pabLine[3 * i] = pabRed[i];
869 	      pabLine[3 * i + 1] = pabGrn[i + iWidth];
870 	      pabLine[3 * i + 2] = pabBlu[i + iWidth * 2];
871 	    }
872 	}
873     }
874   else
875     {
876       int i, j;			/* loop variables */
877       int c;			/* color buffer accumulator for horizontal average */
878 
879       /* initialize for incremental color buffer access */
880       int iInc = 1;
881       int iStart = 0;
882 
883       /* set for "from the end to the front" of the circular color buffers */
884       if (iReversedHead)
885 	{
886 	  iStart = iWidth - iScaleDownDpi;
887 	  iInc = -1;
888 	}
889 
890       /* each pixel is the mean of iScaleDownDpi
891          so set the skip width accordingly */
892       iInc *= iScaleDownDpi;
893 
894       for (i = iStart; i >= 0 && i < iWidth; i += iInc)
895 	{
896 	  /* collect the red pixels */
897 	  for (c = j = 0; j < iScaleDownDpi; ++j)
898 	    c += pabRed[i + j];
899 	  *pabLine =
900 	    (*pabLine * iBufWeight + c / iScaleDownDpi) / (iBufWeight + 1);
901 	  pabLine++;
902 
903 	  /* collect the green pixels */
904 	  for (c = j = 0; j < iScaleDownDpi; ++j)
905 	    c += pabGrn[i + iWidth + j];
906 	  *pabLine =
907 	    (*pabLine * iBufWeight + c / iScaleDownDpi) / (iBufWeight + 1);
908 	  pabLine++;
909 
910 	  /* collect the blue pixels */
911 	  for (c = j = 0; j < iScaleDownDpi; ++j)
912 	    c += pabBlu[i + 2 * iWidth + j];
913 	  *pabLine =
914 	    (*pabLine * iBufWeight + c / iScaleDownDpi) / (iBufWeight + 1);
915 	  pabLine++;
916 	}
917     }
918 
919 }
920 
921 
922 /* gets an unscrambled line from the circular buffer. the first couple of lines contain garbage,
923    if fReturn==SANE_TRUE, the head will return automatically on an end of scan */
924 STATIC SANE_Bool
CircBufferGetLineEx(int iHandle, TDataPipe * p, unsigned char *pabLine, SANE_Bool iReversedHead, SANE_Bool fReturn)925 CircBufferGetLineEx (int iHandle, TDataPipe * p, unsigned char *pabLine,
926 		     SANE_Bool iReversedHead, SANE_Bool fReturn)
927 {
928   int iLineCount;
929   for (iLineCount = 0; iLineCount < p->iScaleDownLpi; ++iLineCount)
930     {
931       if (iReversedHead)
932 	{
933 	  if (!XferBufferGetLine (iHandle, p,
934 				  &p->pabCircBuf[p->iRedLine *
935 						 p->iBytesPerLine], fReturn))
936 	    return SANE_FALSE;
937 	}
938       else
939 	{
940 	  if (!XferBufferGetLine (iHandle, p,
941 				  &p->pabCircBuf[p->iBluLine *
942 						 p->iBytesPerLine], fReturn))
943 	    return SANE_FALSE;
944 	}
945       if (pabLine != NULL)
946 	{
947 	  _UnscrambleLine (pabLine,
948 			   &p->pabCircBuf[p->iRedLine * p->iBytesPerLine],
949 			   &p->pabCircBuf[p->iGrnLine * p->iBytesPerLine],
950 			   &p->pabCircBuf[p->iBluLine * p->iBytesPerLine],
951 			   p->iWidth * p->iScaleDownDpi, iReversedHead,
952 			   p->iScaleDownDpi, iLineCount);
953 	}
954 
955       /* advance pointers */
956       p->iRedLine = (p->iRedLine + 1) % p->iLinesPerCircBuf;
957       p->iGrnLine = (p->iGrnLine + 1) % p->iLinesPerCircBuf;
958       p->iBluLine = (p->iBluLine + 1) % p->iLinesPerCircBuf;
959     }
960   return SANE_TRUE;
961 }
962 
963 
964 /* gets an unscrambled line from the circular buffer. the first couple of lines contain garbage */
965 STATIC SANE_Bool
CircBufferGetLine(int iHandle, TDataPipe * p, unsigned char *pabLine, SANE_Bool iReversedHead)966 CircBufferGetLine (int iHandle, TDataPipe * p, unsigned char *pabLine,
967 		   SANE_Bool iReversedHead)
968 {
969   return CircBufferGetLineEx (iHandle, p, pabLine, iReversedHead, SANE_FALSE);
970 }
971 
972 
973 /* try to keep the number of transfers the same, but make them all
974    as good as possible the same size to avoid cranking in critical
975    situations
976 */
977 static int
_OptimizeXferSize(int iLines, int iLinesPerXfer)978 _OptimizeXferSize (int iLines, int iLinesPerXfer)
979 {
980   int iXfers;
981   iXfers = (iLines + iLinesPerXfer - 1) / iLinesPerXfer;
982   while (--iLinesPerXfer > 0
983 	 && (iLines + iLinesPerXfer - 1) / iLinesPerXfer == iXfers);
984   return iLinesPerXfer + 1;
985 }
986 
987 STATIC void
CircBufferInit(int iHandle, TDataPipe * p, int iWidth, int iHeight, int iMisAlignment, SANE_Bool iReversedHead, int iScaleDownDpi, int iScaleDownLpi)988 CircBufferInit (int iHandle, TDataPipe * p,
989 		int iWidth, int iHeight,
990 		int iMisAlignment, SANE_Bool iReversedHead,
991 		int iScaleDownDpi, int iScaleDownLpi)
992 {
993 
994   /* relevant for internal read and write functions */
995   p->iScaleDownLpi = iScaleDownLpi;
996   p->iScaleDownDpi = iScaleDownDpi;
997   p->iWidth = iWidth;
998   p->iBytesPerLine = iWidth * iScaleDownDpi * BYTES_PER_PIXEL;
999   p->iSaneBytesPerLine = iWidth * BYTES_PER_PIXEL;
1000   if (iMisAlignment == 0)
1001     {
1002       p->iLinesPerCircBuf = 1;
1003     }
1004   else
1005     {
1006       p->iLinesPerCircBuf = 3 * iMisAlignment;
1007     }
1008 
1009   DBG (DBG_MSG, "_iScaleDown (Dpi,Lpi) = (%d,%d)\n", p->iScaleDownDpi,
1010        p->iScaleDownLpi);
1011   DBG (DBG_MSG, "_iBytesPerLine = %d\n", p->iBytesPerLine);
1012   DBG (DBG_MSG, "_iLinesPerCircBuf = %d\n", p->iLinesPerCircBuf);
1013   p->pabCircBuf =
1014     (unsigned char *) malloc (p->iBytesPerLine * p->iLinesPerCircBuf);
1015   if (p->pabCircBuf == NULL)
1016     {
1017       DBG (DBG_ERR,
1018 	   "Unable to allocate %d unsigned chars for circular buffer\n",
1019 	   (int) (p->iBytesPerLine * p->iLinesPerCircBuf));
1020       return;
1021     }
1022   DBG (DBG_MSG, "Allocated %d unsigned chars for circular buffer\n",
1023        p->iBytesPerLine * p->iLinesPerCircBuf);
1024 
1025   if (iReversedHead)
1026     {
1027       p->iBluLine = 0;
1028       p->iGrnLine = iMisAlignment;
1029       p->iRedLine = iMisAlignment * 2;
1030     }
1031   else
1032     {
1033       p->iRedLine = 0;
1034       p->iGrnLine = iMisAlignment;
1035       p->iBluLine = iMisAlignment * 2;
1036     }
1037 
1038   /* negative height is an indication for "no Check" */
1039   if (iHeight < 0)
1040     {
1041       p->iLinesLeft = -1;
1042       p->iLinesPerXferBuf = XFER_BUF_SIZE / p->iBytesPerLine;
1043       DBG (DBG_MSG, "using unchecked XFER_BUF_SIZE\n");
1044       DBG (DBG_MSG, "_iXFerSize = %d\n",
1045 	   p->iBytesPerLine * p->iLinesPerXferBuf);
1046     }
1047   else
1048     {
1049 #define SAFETY_LINES 0
1050 #define MAX_LINES_PER_XFERBUF 800
1051       /* estimate of number of unsigned chars to transfer at all via the USB */
1052       /* add some lines for security */
1053 
1054       p->iLinesLeft =
1055 	iHeight + p->iSkipLines + p->iLinesPerCircBuf + SAFETY_LINES;
1056       p->iLinesPerXferBuf = XFER_BUF_SIZE / p->iBytesPerLine;
1057       /* with more than 800 lines the timing is spoiled */
1058       if (p->iLinesPerXferBuf > MAX_LINES_PER_XFERBUF)
1059 	{
1060 	  p->iLinesPerXferBuf = MAX_LINES_PER_XFERBUF;
1061 	}
1062       /* final optimization to keep critical scans smooth */
1063       p->iLinesPerXferBuf =
1064 	_OptimizeXferSize (p->iLinesLeft, p->iLinesPerXferBuf);
1065 
1066       DBG (DBG_MSG, "_iXFerSize = %d for %d transfer(s)\n",
1067 	   (int) p->iLinesPerXferBuf * p->iBytesPerLine,
1068 	   (p->iLinesLeft + p->iLinesPerXferBuf - 1) / p->iLinesPerXferBuf);
1069     }
1070   DBG (DBG_MSG, "_iLinesPerXferBuf = %d\n", p->iLinesPerXferBuf);
1071 
1072   /* init transfer buffer */
1073   XferBufferInit (iHandle, p);
1074 
1075   /* fill circular buffer */
1076   CircBufferFill (iHandle, p, iReversedHead);
1077 }
1078 
1079 
1080 STATIC void
CircBufferExit(TDataPipe * p)1081 CircBufferExit (TDataPipe * p)
1082 {
1083   XferBufferExit (p);
1084   if (p->pabCircBuf != NULL)
1085     {
1086       DBG (DBG_MSG, "\n");
1087       free (p->pabCircBuf);
1088       p->pabCircBuf = NULL;
1089     }
1090   else
1091     {
1092       DBG (DBG_ERR, "CircBufferExit: Circular buffer not initialised!\n");
1093     }
1094 }
1095 
1096 
1097 /************************************************************************/
1098 
1099 
1100 
1101 static int
_CalcAvg(unsigned char *pabBuf, int n, int iStep)1102 _CalcAvg (unsigned char *pabBuf, int n, int iStep)
1103 {
1104   int i, j, x;
1105 
1106   for (i = j = x = 0; i < n; i++)
1107     {
1108       x += pabBuf[j];
1109       j += iStep;
1110     }
1111   return (x / n);
1112 }
1113 
1114 
1115 /* converts white line data and black point data into a calibration table */
1116 static void
CreateCalibTable(unsigned char *abWhite, unsigned char bBlackR, unsigned char bBlackG, unsigned char bBlackB, int iReversedHead, unsigned char *pabCalibTable)1117 CreateCalibTable (unsigned char *abWhite, unsigned char bBlackR,
1118 		  unsigned char bBlackG, unsigned char bBlackB,
1119 		  int iReversedHead, unsigned char *pabCalibTable)
1120 {
1121   int i, j, iGain, iOffset, iData;
1122   unsigned char *pabPixel;
1123 
1124   j = 0;
1125   for (i = 0; i < HW_PIXELS; i++)
1126     {
1127       if (iReversedHead)
1128 	{
1129 	  pabPixel = &abWhite[(HW_PIXELS - i - 1) * 3];
1130 	}
1131       else
1132 	{
1133 	  pabPixel = &abWhite[i * 3];
1134 	}
1135       /* red */
1136       if (bBlackR > 16)
1137 	bBlackR = 16;
1138       iGain = 65536 / MAX (1, pabPixel[0] - bBlackR);
1139       iOffset = bBlackR * 4;
1140       if (iOffset > 63)
1141 	iOffset = 63;
1142       iData = (iGain << 6) + iOffset;
1143       pabCalibTable[j++] = (iData) & 255;
1144       pabCalibTable[j++] = (iData >> 8) & 255;
1145       /* green */
1146       if (bBlackG > 16)
1147 	bBlackG = 16;
1148       iGain = 65536 / MAX (1, pabPixel[1] - bBlackG);
1149       iOffset = bBlackG * 4;
1150       if (iOffset > 63)
1151 	iOffset = 63;
1152       iData = (iGain << 6) + iOffset;
1153       pabCalibTable[j++] = (iData) & 255;
1154       pabCalibTable[j++] = (iData >> 8) & 255;
1155       /* blue */
1156       if (bBlackB > 16)
1157 	bBlackB = 16;
1158       iGain = 65536 / MAX (1, pabPixel[2] - bBlackB);
1159       iOffset = bBlackB * 4;
1160       if (iOffset > 63)
1161 	iOffset = 63;
1162       iData = (iGain << 6) + iOffset;
1163       pabCalibTable[j++] = (iData) & 255;
1164       pabCalibTable[j++] = (iData >> 8) & 255;
1165     }
1166 }
1167 
1168 
1169 /*************************************************************************
1170   Lamp control functions
1171 *************************************************************************/
1172 STATIC SANE_Bool
GetLamp(THWParams * pHWParams, SANE_Bool * pfLampIsOn)1173 GetLamp (THWParams * pHWParams, SANE_Bool * pfLampIsOn)
1174 {
1175   unsigned char bData;
1176 
1177   NiashReadReg (pHWParams->iXferHandle, 0x03, &bData);
1178   *pfLampIsOn = ((bData & 0x01) != 0);
1179   return SANE_TRUE;
1180 }
1181 
1182 
1183 STATIC SANE_Bool
SetLamp(THWParams * pHWParams, SANE_Bool fLampOn)1184 SetLamp (THWParams * pHWParams, SANE_Bool fLampOn)
1185 {
1186   unsigned char bData;
1187   int iHandle;
1188 
1189   iHandle = pHWParams->iXferHandle;
1190 
1191   NiashReadReg (iHandle, 0x03, &bData);
1192   if (fLampOn)
1193     {
1194       NiashWriteReg (iHandle, 0x03, bData | 0x01);
1195     }
1196   else
1197     {
1198       NiashWriteReg (iHandle, 0x03, bData & ~0x01);
1199     }
1200   return SANE_TRUE;
1201 }
1202 
1203 
1204 /*************************************************************************
1205   Experimental simple calibration, but also returning the white levels
1206 *************************************************************************/
1207 STATIC SANE_Bool
SimpleCalibExt(THWParams * pHWPar, unsigned char *pabCalibTable, unsigned char *pabCalWhite)1208 SimpleCalibExt (THWParams * pHWPar, unsigned char *pabCalibTable,
1209 		unsigned char *pabCalWhite)
1210 {
1211   unsigned char bMinR, bMinG, bMinB;
1212   TDataPipe DataPipe;
1213   TScanParams Params;
1214   unsigned char abGamma[4096];
1215   int i, j;
1216   static unsigned char abBuf[HW_PIXELS * 3 * 71];	/* Careful : see startWhite and endWhite below */
1217   static unsigned char abLine[HW_PIXELS * 3];
1218   static unsigned char abWhite[HW_PIXELS * 3];
1219   unsigned char *pabWhite;
1220   int iWhiteR, iWhiteG, iWhiteB;
1221   int iHandle;
1222   SANE_Bool iReversedHead;
1223   int startWhiteY, endWhiteY;
1224   int startBlackY, endBlackY;
1225   int endBlackX;
1226 
1227   iHandle = pHWPar->iXferHandle;
1228   iReversedHead = pHWPar->iReversedHead;
1229 
1230   DataPipe.iSkipLines = pHWPar->iSkipLines;
1231 
1232   Params.iDpi = HW_DPI;
1233   Params.iLpi = HW_DPI;
1234   if (iReversedHead)		/* hp scanners */
1235     Params.iTop = 60;
1236   else				/* agfa scanners */
1237     Params.iTop = 30;
1238   Params.iBottom = HP3300C_BOTTOM;
1239   Params.iLeft = 0;
1240   Params.iWidth = HW_PIXELS;
1241   Params.iHeight = 54;
1242   Params.fCalib = SANE_TRUE;
1243 
1244   /* write gamma table with neutral gain / offset */
1245   CalcGamma (abGamma, 1.0);
1246   WriteGammaCalibTable (abGamma, abGamma, abGamma, NULL, 256, 0, pHWPar);
1247 
1248   if (!InitScan (&Params, pHWPar))
1249     {
1250       if (pabCalWhite)
1251 	pabCalWhite[0] = pabCalWhite[1] = pabCalWhite[2] = 0;
1252       return SANE_FALSE;
1253     }
1254 
1255   /* Definition of white and black areas */
1256   if (iReversedHead)
1257     {				/* hp scanners */
1258       startWhiteY = 0;
1259       endWhiteY = 15;
1260       startBlackY = 16;
1261       endBlackY = 135;
1262       endBlackX = HW_PIXELS;
1263     }
1264   else
1265     {				/* agfa scanners */
1266       startWhiteY = 0;
1267       endWhiteY = 70;
1268       startBlackY = 86;
1269       endBlackY = 135;
1270       endBlackX = 3374;
1271     }
1272 
1273   CircBufferInit (iHandle, &DataPipe, HW_PIXELS, -1, Params.iLpi / 150,
1274 		  iReversedHead, 1, 1);
1275   /* white level */
1276   /* skip some lines */
1277   for (i = 0; i < startWhiteY; i++)
1278     {
1279       CircBufferGetLine (iHandle, &DataPipe, abLine, iReversedHead);
1280     }
1281   /* Get white lines */
1282   for (i = 0; i < endWhiteY - startWhiteY + 1; i++)
1283     {
1284       CircBufferGetLine (iHandle, &DataPipe, &abBuf[i * HW_PIXELS * 3],
1285 			 iReversedHead);
1286     }
1287   /* black level */
1288   bMinR = 255;
1289   bMinG = 255;
1290   bMinB = 255;
1291   /* Skip some lines */
1292   for (i = 0; i < startBlackY; i++)
1293     {
1294       CircBufferGetLine (iHandle, &DataPipe, abLine, iReversedHead);
1295     }
1296   for (i = 0; i < endBlackY - startBlackY + 1; i++)
1297     {
1298       CircBufferGetLine (iHandle, &DataPipe, abLine, iReversedHead);
1299       for (j = 0; j < endBlackX; j++)
1300 	{
1301 	  bMinR = MIN (abLine[j * 3 + 0], bMinR);
1302 	  bMinG = MIN (abLine[j * 3 + 1], bMinG);
1303 	  bMinB = MIN (abLine[j * 3 + 2], bMinB);
1304 	}
1305     }
1306   CircBufferExit (&DataPipe);
1307   FinishScan (pHWPar);
1308 
1309   /* calc average white level */
1310   pabWhite = abBuf;
1311   for (i = 0; i < HW_PIXELS; i++)
1312     {
1313       abWhite[i * 3 + 0] =
1314 	_CalcAvg (&pabWhite[i * 3 + 0], endWhiteY - startWhiteY + 1,
1315 		  HW_PIXELS * 3);
1316       abWhite[i * 3 + 1] =
1317 	_CalcAvg (&pabWhite[i * 3 + 1], endWhiteY - startWhiteY + 1,
1318 		  HW_PIXELS * 3);
1319       abWhite[i * 3 + 2] =
1320 	_CalcAvg (&pabWhite[i * 3 + 2], endWhiteY - startWhiteY + 1,
1321 		  HW_PIXELS * 3);
1322     }
1323   iWhiteR = _CalcAvg (&abWhite[0], HW_PIXELS, 3);
1324   iWhiteG = _CalcAvg (&abWhite[1], HW_PIXELS, 3);
1325   iWhiteB = _CalcAvg (&abWhite[2], HW_PIXELS, 3);
1326 
1327   DBG (DBG_MSG, "Black level (%d,%d,%d), White level (%d,%d,%d)\n",
1328        (int) bMinR, (int) bMinG, (int) bMinB, iWhiteR, iWhiteG, iWhiteB);
1329 
1330   /* convert the white line and black point into a calibration table */
1331   CreateCalibTable (abWhite, bMinR, bMinG, bMinB, iReversedHead,
1332 		    pabCalibTable);
1333   /* assign the White Levels */
1334   if (pabCalWhite)
1335     {
1336       pabCalWhite[0] = iWhiteR;
1337       pabCalWhite[1] = iWhiteG;
1338       pabCalWhite[2] = iWhiteB;
1339     }
1340   return SANE_TRUE;
1341 }
1342 
1343 
1344 /*************************************************************************
1345   FinishScan
1346   ==========
1347     Finishes the scan. Makes the scanner head move back to the home position.
1348 
1349 *************************************************************************/
1350 STATIC void
FinishScan(THWParams * pHWParams)1351 FinishScan (THWParams * pHWParams)
1352 {
1353   NiashWriteReg (pHWParams->iXferHandle, 0x02, 0x80);
1354 }
1355