diff options
Diffstat (limited to 'backend/niash_core.c')
-rw-r--r-- | backend/niash_core.c | 1362 |
1 files changed, 1362 insertions, 0 deletions
diff --git a/backend/niash_core.c b/backend/niash_core.c new file mode 100644 index 0000000..e3ae2b8 --- /dev/null +++ b/backend/niash_core.c @@ -0,0 +1,1362 @@ +/* + Copyright (C) 2001 Bertrik Sikken (bertrik@zonnet.nl) + + 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. + + $Id$ +*/ + +/* + Core NIASH chip functions. +*/ + +#include <stdio.h> /* fopen, fread, fwrite, fclose etc */ +#include <stdarg.h> /* va_list for vfprintf */ +#include <string.h> /* memcpy, memset */ +#include <unistd.h> /* unlink */ +#include <stdlib.h> /* malloc, free */ +#include <math.h> /* exp, pow */ + +#include "niash_xfer.h" +#include "niash_core.h" + + +#ifndef MIN +#define MIN(a,b) (((a) < (b)) ? (a) : (b)) +#endif + +#ifndef MAX +#define MAX(a,b) (((a) > (b)) ? (a) : (b)) +#endif + + +#define XFER_BUF_SIZE 0xF000 + + +/* HP3400 firmware data */ +static unsigned char abData0000[] = { + 0xfe, 0x9f, 0x58, 0x1b, 0x00, 0x03, 0xa4, 0x02, 0x63, 0x02, 0x33, 0x02, + 0x0d, 0x02, 0xf0, 0x01, + 0xd8, 0x01, 0xc5, 0x01, 0xb5, 0x01, 0xa8, 0x01, 0x9d, 0x01, 0x93, 0x01, + 0x8b, 0x01, 0x84, 0x01, + 0x7e, 0x01, 0x79, 0x01, 0x74, 0x01, 0x70, 0x01, 0x6d, 0x01, 0x69, 0x01, + 0x67, 0x01, 0x64, 0x01, + 0x62, 0x01, 0x60, 0x01, 0x5f, 0x01, 0x5d, 0x01, 0x5c, 0x01, 0x5b, 0x01, + 0x5a, 0x01, 0x59, 0x01, + 0x58, 0x01, 0x57, 0x01, 0x57, 0x01, 0x56, 0x01, 0x56, 0x01, 0x55, 0x01, + 0x55, 0x01, 0x54, 0x01, + 0x54, 0x01, 0x54, 0x01, 0x54, 0x01, 0x53, 0x01, 0x53, 0x01, 0x53, 0x01, + 0x53, 0x01, 0x52, 0x81 +}; + +/* 1st word : 0x9ffe = 40958, strip 15th bit: 0x1ffe = 8190 + 2nd word : 0x1b58 = 7000 -> coincidence ? + other words: formula: y = 676 / (2 - exp(0.113 * (1-x)) ), where x = 0 for first entry +*/ + +/* more HP3400 firmware data */ +static unsigned char abData0400[] = { + 0xa4, 0x82, 0x00, 0x80, 0xa4, 0x82, 0xaa, 0x02, 0xc0, 0x02, 0xe8, 0x02, + 0x3e, 0x03, 0xc8, 0x03, + 0x58, 0x1b, 0xfe, 0x9f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 +}; + + + +static void +_ConvertMotorTable (unsigned char *pabOld, unsigned char *pabNew, int iSize, + int iLpi) +{ + int iData, i, iBit15; + + for (i = 0; i < (iSize / 2); i++) + { + iData = pabOld[2 * i + 0] + (pabOld[2 * i + 1] << 8); + iBit15 = (iData & 0x8000); + iData = (iData & 0x7FFF); + if (iData <= 0x400) + { + iData = iData * iLpi / 300; + } + if (iBit15 != 0) + { + iData |= 0x8000; + } + pabNew[2 * i + 0] = iData & 255; + pabNew[2 * i + 1] = (iData >> 8) & 255; + } +} + + +/************************************************************************* + _ProbeRegisters + =============== + Tries to determine certain hardware properties. + + This is done by checking the writeability of some scanner registers. + We cannot rely simply on the scanner model to contain a specific + chip. The HP3300c for example uses one of at least three slightly + different scanner ASICs (NIASH00012, NIASH00013 and NIASH00014). + + OUT pHWParams Hardware parameters, updated fields: + fGamma16 TRUE if 16 bit gamma tables can be used + fReg07 TRUE if reg07 is writeable + iBufferSize Size of scanner's internal buffer + + Returns TRUE if a NIASH chipset was found. +*************************************************************************/ +static SANE_Bool +_ProbeRegisters (THWParams * pHWParams) +{ + unsigned char bData1, bData2; + int iHandle; + + iHandle = pHWParams->iXferHandle; + + DBG (DBG_MSG, "Probing scanner...\n"); + + /* check register 0x04 */ + NiashWriteReg (iHandle, 0x04, 0x55); + NiashReadReg (iHandle, 0x04, &bData1); + NiashWriteReg (iHandle, 0x04, 0xAA); + NiashReadReg (iHandle, 0x04, &bData2); + NiashWriteReg (iHandle, 0x04, 0x07); + if ((bData1 != 0x55) || (bData2 != 0xAA)) + { + DBG (DBG_ERR, " No NIASH chipset found!\n"); + return SANE_FALSE; + } + + /* check writeability of register 3 bit 1 */ + NiashReadReg (iHandle, 0x03, &bData1); + NiashWriteReg (iHandle, 0x03, bData1 | 0x02); + NiashReadReg (iHandle, 0x03, &bData2); + NiashWriteReg (iHandle, 0x03, bData1); + pHWParams->fGamma16 = ((bData2 & 0x02) != 0); + DBG (DBG_MSG, " Gamma table entries are %d bit\n", + pHWParams->fGamma16 ? 16 : 8); + + /* check register 0x07 */ + NiashReadReg (iHandle, 0x07, &bData1); + NiashWriteReg (iHandle, 0x07, 0x1C); + NiashReadReg (iHandle, 0x07, &bData2); + NiashWriteReg (iHandle, 0x07, bData1); + pHWParams->fReg07 = (bData2 == 0x1C); + + if (!pHWParams->fGamma16) + { + /* internal scan buffer size is an educated guess, but seems to correlate + well with the size calculated from several windows driver log files + size = 128kB - 44088 unsigned chars (space required for gamma/calibration table) + */ + pHWParams->iBufferSize = 86984L; + DBG (DBG_MSG, " NIASH version < 00014\n"); + } + else + { + pHWParams->iBufferSize = 0x60000L; + if (!pHWParams->fReg07) + { + DBG (DBG_MSG, " NIASH version = 00014\n"); + } + else + { + DBG (DBG_MSG, " NIASH version > 00014\n"); + } + } + + return SANE_TRUE; +} + + +/* returns 0 on success, < 0 otherwise */ +STATIC int +NiashOpen (THWParams * pHWParams, const char *pszName) +{ + int iXferHandle; + + iXferHandle = NiashXferOpen (pszName, &pHWParams->eModel); + if (iXferHandle < 0) + { + DBG (DBG_ERR, "NiashXferOpen failed for '%s'\n", pszName); + return -1; + } + + pHWParams->iXferHandle = iXferHandle; + + NiashWakeup (pHWParams->iXferHandle); + + /* default HW params */ + pHWParams->iSensorSkew = 8; + pHWParams->iTopLeftX = 0; + pHWParams->iTopLeftY = 3; + pHWParams->fReg07 = SANE_FALSE; + pHWParams->iSkipLines = 0; + pHWParams->iExpTime = 5408; + pHWParams->iReversedHead = SANE_TRUE; + + switch (pHWParams->eModel) + { + + case eHp3300c: + DBG (DBG_MSG, "Setting params for Hp3300\n"); + pHWParams->iTopLeftX = 4; + pHWParams->iTopLeftY = 11; + pHWParams->iSkipLines = 14; + break; + + case eHp3400c: + case eHp4300c: + DBG (DBG_MSG, "Setting params for Hp3400c/Hp4300c\n"); + pHWParams->iTopLeftX = 3; + pHWParams->iTopLeftY = 14; + pHWParams->fReg07 = SANE_TRUE; + break; + + case eAgfaTouch: + DBG (DBG_MSG, "Setting params for AgfaTouch\n"); + pHWParams->iReversedHead = SANE_FALSE; /* head not reversed on Agfa Touch */ + pHWParams->iTopLeftX = 3; + pHWParams->iTopLeftY = 10; + pHWParams->iSkipLines = 7; + break; + + case eUnknownModel: + DBG (DBG_MSG, "Setting params for UnknownModel\n"); + break; + + default: + DBG (DBG_ERR, "ERROR: internal error! (%d)\n", (int) pHWParams->eModel); + return -1; + } + + /* autodetect some hardware properties */ + if (!_ProbeRegisters (pHWParams)) + { + DBG (DBG_ERR, "_ProbeRegisters failed!\n"); + return -1; + } + + return 0; +} + + +STATIC void +NiashClose (THWParams * pHWPar) +{ + NiashXferClose (pHWPar->iXferHandle); + pHWPar->iXferHandle = 0; +} + + +static void +WriteRegWord (int iHandle, unsigned char bReg, SANE_Word wData) +{ + NiashWriteReg (iHandle, bReg, wData & 0xFF); + NiashWriteReg (iHandle, bReg + 1, (wData >> 8) & 0xFF); +} + + +/* calculate a 4096 unsigned char gamma table */ +STATIC void +CalcGamma (unsigned char *pabTable, double Gamma) +{ + int i, iData; + + /* fill gamma table */ + for (i = 0; i < 4096; i++) + { + iData = floor (256.0 * pow (((double) i / 4096.0), 1.0 / Gamma)); + pabTable[i] = iData; + } +} + + +/* + Hp3400WriteFw + ============= + Writes data to scanners with a NIASH00019 chipset, e.g. + gamma, calibration and motor control data. + + IN pabData pointer to firmware data + iLen Size of firmware date (unsigned chars) + iAddr Scanner address to write to +*/ +static void +Hp3400cWriteFW (int iXferHandle, unsigned char *pabData, int iLen, int iAddr) +{ + iAddr--; + NiashWriteReg (iXferHandle, 0x21, iAddr & 0xFF); + NiashWriteReg (iXferHandle, 0x22, (iAddr >> 8) & 0xFF); + NiashWriteReg (iXferHandle, 0x23, (iAddr >> 16) & 0xFF); + NiashWriteBulk (iXferHandle, pabData, iLen); +} + + +/* Writes the gamma and offset/gain tables to the scanner. + In case a calibration file exist, it will be used for offset/gain */ +STATIC void +WriteGammaCalibTable (unsigned char *pabGammaR, unsigned char *pabGammaG, + unsigned char *pabGammaB, unsigned char *pabCalibTable, + int iGain, int iOffset, THWParams * pHWPar) +{ + int i, j, k; + static unsigned char abGamma[60000]; + int iData; + int iHandle; + + iHandle = pHWPar->iXferHandle; + + j = 0; + /* fill gamma table for red component */ + /* pad entries with 0 for 16-bit gamma table */ + for (i = 0; i < 4096; i++) + { + if (pHWPar->fGamma16) + { + abGamma[j++] = 0; + } + abGamma[j++] = pabGammaR[i]; + } + /* fill gamma table for green component */ + for (i = 0; i < 4096; i++) + { + if (pHWPar->fGamma16) + { + abGamma[j++] = 0; + } + abGamma[j++] = pabGammaG[i]; + } + /* fill gamma table for blue component */ + for (i = 0; i < 4096; i++) + { + if (pHWPar->fGamma16) + { + abGamma[j++] = 0; + } + abGamma[j++] = pabGammaB[i]; + } + + if (pabCalibTable == NULL) + { + iData = (iGain << 6) + iOffset; + for (i = 0; i < HW_PIXELS; i++) + { + for (k = 0; k < 3; k++) + { + abGamma[j++] = (iData) & 255; + abGamma[j++] = (iData >> 8) & 255; + } + } + } + else + { + memcpy (&abGamma[j], pabCalibTable, HW_PIXELS * 6); + j += HW_PIXELS * 6; + } + + NiashWriteReg (iHandle, 0x02, 0x80); + NiashWriteReg (iHandle, 0x03, 0x01); + NiashWriteReg (iHandle, 0x03, 0x11); + NiashWriteReg (iHandle, 0x02, 0x84); + + if (pHWPar->fReg07) + { + Hp3400cWriteFW (iHandle, abGamma, j, 0x2000); + } + else + { + NiashWriteBulk (iHandle, abGamma, j); + } + + NiashWriteReg (iHandle, 0x02, 0x80); +} + + +static void +WriteAFEReg (int iHandle, int iReg, int iData) +{ + NiashWriteReg (iHandle, 0x25, iReg); + NiashWriteReg (iHandle, 0x26, iData); +} + + +/* setup the analog front-end -> coarse calibration */ +static void +WriteAFE (int iHandle) +{ + /* see WM8143 datasheet */ + + WriteAFEReg (iHandle, 0x04, 0x00); + WriteAFEReg (iHandle, 0x03, 0x12); + WriteAFEReg (iHandle, 0x02, 0x04); + WriteAFEReg (iHandle, 0x05, 0x10); + WriteAFEReg (iHandle, 0x01, 0x03); + + WriteAFEReg (iHandle, 0x20, 0xc0); /*c8 *//* red offset */ + WriteAFEReg (iHandle, 0x21, 0xc0); /*c8 *//* green offset */ + WriteAFEReg (iHandle, 0x22, 0xc0); /*d0 *//* blue offset */ + + WriteAFEReg (iHandle, 0x28, 0x05); /*5 *//* red gain */ + WriteAFEReg (iHandle, 0x29, 0x03); /*3 *//* green gain */ + WriteAFEReg (iHandle, 0x2A, 0x04); /*4 *//* blue gain */ +} + + +/* wait for the carriage to return */ +static void +WaitReadyBit (int iHandle) +{ + unsigned char bData; + + do + { + NiashReadReg (iHandle, 0x03, &bData); + } + while ((bData & 8) == 0); +} + + +/* + Initialisation specific for NIASH00014 and lower chips +*/ +static void +InitNiash00014 (TScanParams * pParams, THWParams * pHWParams) +{ + int iHandle, iLpiCode; + + iHandle = pHWParams->iXferHandle; + + /* exposure time (in units 24/Fcrystal)? */ + WriteRegWord (iHandle, 0x08, pHWParams->iExpTime - 1); + + /* width in pixels */ + WriteRegWord (iHandle, 0x12, pParams->iWidth - 1); + + /* top */ + WriteRegWord (iHandle, 0x17, pParams->iTop); + WriteRegWord (iHandle, 0x19, pParams->iTop); + + /* time between stepper motor steps (in units of 24/Fcrystal)? */ + iLpiCode = pParams->iLpi * pHWParams->iExpTime / 1200L; + + if (!pHWParams->fGamma16) + { + /* NIASH 00012 / 00013 init */ + + /* LPI specific settings */ + if (pParams->iLpi < 600) + { + /* set halfres bit */ + NiashWriteReg (iHandle, 0x06, 0x01); + /* double lpi code because of halfres bit */ + iLpiCode *= 2; + } + else + { + /* clear halfres bit */ + NiashWriteReg (iHandle, 0x06, 0x00); + /* add exptime to make it scan slower */ + iLpiCode += pHWParams->iExpTime; + } + + /* unknown setting */ + WriteRegWord (iHandle, 0x27, 0x7FD2); + WriteRegWord (iHandle, 0x29, 0x6421); + + } + else + { + /* NIASH 00014 init */ + + /* halfres bit always cleared */ + NiashWriteReg (iHandle, 0x06, 0x00); + + /* LPI specific settings */ + if (pParams->iLpi >= 600) + { + /* add exptime to make it scan slower */ + iLpiCode += pHWParams->iExpTime; + } + + /* unknown setting */ + WriteRegWord (iHandle, 0x27, 0xc862); /*c862 */ + WriteRegWord (iHandle, 0x29, 0xb853); /*b853 */ + } + + /* LPI code */ + WriteRegWord (iHandle, 0x0A, iLpiCode - 1); + + /* backtrack reversing speed */ + NiashWriteReg (iHandle, 0x1E, (iLpiCode - 1) / 32); +} + + +/* + Initialisation specific for NIASH00019 chips +*/ +static void +InitNiash00019 (TScanParams * pParams, THWParams * pHWParams) +{ + int iHandle, iLpiCode; + static unsigned char abMotor[512]; + + + iHandle = pHWParams->iXferHandle; + + /* exposure time (in units 24/Fcrystal)? */ + WriteRegWord (iHandle, 0x08, pHWParams->iExpTime); + + /* width in pixels */ + WriteRegWord (iHandle, 0x12, pParams->iWidth); + + /* ? */ + WriteRegWord (iHandle, 0x27, 0xc862); /*c862 */ + WriteRegWord (iHandle, 0x29, 0xb853); /*b853 */ + + /* specific handling of 150 dpi resolution */ + if (pParams->iLpi == 150) + { + /* use 300 LPI but skip every other line */ + pParams->iLpi = 300; + NiashWriteReg (iHandle, 0x06, 0x01); + } + else + { + NiashWriteReg (iHandle, 0x06, 0x00); + } + + /* DPI and position table */ + NiashWriteReg (iHandle, 0x07, 0x02); + _ConvertMotorTable (abData0000, abMotor, sizeof (abData0000), + pParams->iLpi); + Hp3400cWriteFW (iHandle, abMotor, sizeof (abData0000), 0x000); + _ConvertMotorTable (abData0400, abMotor, sizeof (abData0400), + pParams->iLpi); + Hp3400cWriteFW (iHandle, abMotor, sizeof (abData0400), 0x400); + + /* backtrack reversing speed */ + iLpiCode = pParams->iLpi * pHWParams->iExpTime / 1200L; + NiashWriteReg (iHandle, 0x1E, (iLpiCode - 1) / 32); +} + + +/* + Scanner initialisation common to all NIASH chips +*/ +static void +InitNiashCommon (TScanParams * pParams, THWParams * pHWParams) +{ + int iWidthHW, iHandle, iMaxLevel; + + + iHandle = pHWParams->iXferHandle; + + NiashWriteReg (iHandle, 0x02, 0x80); + NiashWriteReg (iHandle, 0x03, 0x11); + NiashWriteReg (iHandle, 0x01, 0x8B); + NiashWriteReg (iHandle, 0x05, 0x01); + + /* dpi */ + WriteRegWord (iHandle, 0x0C, pParams->iDpi); + + /* calculate width in units of HW resolution */ + iWidthHW = pParams->iWidth * (HW_DPI / pParams->iDpi); + + /* set left and right limits */ + if (pHWParams->iReversedHead) + { + /* head is reversed */ + /* right */ + WriteRegWord (iHandle, 0x0E, + 3 * (HW_PIXELS - (pParams->iLeft + iWidthHW))); + + /* left */ + WriteRegWord (iHandle, 0x10, 3 * (HW_PIXELS - pParams->iLeft) - 1); + } + else + { + /* head is not reversed */ + /*left */ + WriteRegWord (iHandle, 0x0E, 3 * pParams->iLeft); + + /* right */ + WriteRegWord (iHandle, 0x10, 3 * (pParams->iLeft + iWidthHW) - 1); + } + + /* bottom */ + WriteRegWord (iHandle, 0x1B, pParams->iBottom); /* 0x393C); */ + + /* forward jogging speed */ + NiashWriteReg (iHandle, 0x1D, 0x60); + + /* backtrack reversing speed? */ + NiashWriteReg (iHandle, 0x2B, 0x15); + + /* backtrack distance */ + if (pParams->iLpi < 600) + { + NiashWriteReg (iHandle, 0x1F, 0x30); + } + else + { + NiashWriteReg (iHandle, 0x1F, 0x18); + } + + /* max buffer level before backtrace */ + iMaxLevel = MIN (pHWParams->iBufferSize / pParams->iWidth, 250); + NiashWriteReg (iHandle, 0x14, iMaxLevel - 1); + + /* lamp PWM, max = 0x1ff? */ + WriteRegWord (iHandle, 0x2C, 0x01FF); + + /* not needed? */ + NiashWriteReg (iHandle, 0x15, 0x90); /* 90 */ + NiashWriteReg (iHandle, 0x16, 0x70); /* 70 */ + + WriteAFE (iHandle); + + WaitReadyBit (iHandle); + + NiashWriteReg (iHandle, 0x03, 0x05); + + NiashWriteReg (iHandle, 0x02, pParams->fCalib ? 0x88 : 0xA8); +} + + +/* write registers */ +STATIC SANE_Bool +InitScan (TScanParams * pParams, THWParams * pHWParams) +{ + int iHeight; + int iExpTime; + TScanParams Params; + int iHandle; + + iHandle = pHWParams->iXferHandle; + + /* check validity of scanparameters */ + switch (pParams->iDpi) + { + case 150: + case 300: + case 600: + break; + default: + DBG (DBG_ERR, "Invalid dpi (%d)\n", pParams->iDpi); + return SANE_FALSE; + } + + iHeight = (pParams->iBottom - pParams->iTop + 1); + if (iHeight <= 0) + { + DBG (DBG_ERR, "Invalid height (%d)\n", iHeight); + return SANE_FALSE; + } + + if (pParams->iWidth <= 0) + { + DBG (DBG_ERR, "Invalid width (%d)\n", pParams->iWidth); + return SANE_FALSE; + } + + switch (pParams->iLpi) + { + case 150: + case 300: + case 600: + break; + default: + DBG (DBG_ERR, "Invalid lpi (%d)\n", pParams->iLpi); + return SANE_FALSE; + } + + /* exposure time (in units of 24/Fcrystal?), must be divisible by 8 !!! */ + iExpTime = 5408; + if ((iExpTime % 8) != 0) + { + DBG (DBG_ERR, "Invalid exposure time (%d)\n", iExpTime); + return SANE_FALSE; + } + + /* + *** Done checking scan parameters validity *** + */ + + /* + copy the parameters locally and make pParams point to the local copy + */ + memcpy (&Params, pParams, sizeof (Params)); + pParams = &Params; + + if (!pHWParams->fReg07) + { + /* init NIASH00014 and lower */ + InitNiash00014 (pParams, pHWParams); + } + else + { + /* init NIASH00019 */ + InitNiash00019 (pParams, pHWParams); + } + + /* common NIASH init */ + InitNiashCommon (pParams, pHWParams); + + return SANE_TRUE; +} + + +/************************************************************************/ + +static SANE_Bool +XferBufferGetLine (int iHandle, TDataPipe * p, unsigned char *pabLine, + SANE_Bool fReturn) +{ + unsigned char bData, bData2; + SANE_Bool fJustDone = SANE_FALSE; + /* all calculated transfers done ? */ + if (p->iLinesLeft == 0) + return SANE_FALSE; + + /* time for a fresh read? */ + if (p->iCurLine == 0) + { + int iLines; + iLines = p->iLinesPerXferBuf; + /* read only as many lines as needed */ + if (p->iLinesLeft > 0 && p->iLinesLeft <= iLines) + { + iLines = p->iLinesLeft; + DBG (DBG_MSG, "\n"); + DBG (DBG_MSG, "last bulk read\n"); + if (iLines < p->iLinesPerXferBuf) + { + DBG (DBG_MSG, + "reading reduced number of lines: %d instead of %d\n", + iLines, p->iLinesPerXferBuf); + } + fJustDone = SANE_TRUE; + } + /* reading old buffer level */ + NiashReadReg (iHandle, 0x20, &bData); + NiashReadBulk (iHandle, p->pabXferBuf, iLines * p->iBytesPerLine); + /* reding new buffer level */ + NiashReadReg (iHandle, 0x20, &bData2); + if (fJustDone && fReturn) + { + NiashWriteReg (iHandle, 0x02, 0x80); + DBG (DBG_MSG, "returning scanner head\n"); + } + DBG (DBG_MSG, + "buffer level = %3d, <reading %5d unsigned chars>, buffer level = %3d\r", + (int) bData, iLines * p->iBytesPerLine, (int) bData2); + fflush (stdout); + } + /* copy one line */ + if (pabLine != NULL) + { + memcpy (pabLine, &p->pabXferBuf[p->iCurLine * p->iBytesPerLine], + p->iBytesPerLine); + } + /* advance pointer */ + p->iCurLine = (p->iCurLine + 1) % p->iLinesPerXferBuf; + + /* one transfer line less to the XFerBuffer */ + if (p->iLinesLeft > 0) + --(p->iLinesLeft); + return SANE_TRUE; +} + + +static void +XferBufferInit (int iHandle, TDataPipe * p) +{ + int i; + + p->pabXferBuf = (unsigned char *) malloc (XFER_BUF_SIZE); + p->iCurLine = 0; + + /* skip garbage lines */ + for (i = 0; i < p->iSkipLines; i++) + { + XferBufferGetLine (iHandle, p, NULL, SANE_FALSE); + } +} + +/* static procedure that fills the circular buffer in advance to any + circular buffer data retrieval */ +static void +CircBufferFill (int iHandle, TDataPipe * p, SANE_Bool iReversedHead) +{ + int i; + for (i = 0; i < p->iLinesPerCircBuf; i++) + { + if (iReversedHead) + { + XferBufferGetLine (iHandle, p, + &p->pabCircBuf[p->iRedLine * p->iBytesPerLine], + SANE_FALSE); + } + else + { + XferBufferGetLine (iHandle, p, + &p->pabCircBuf[p->iBluLine * p->iBytesPerLine], + SANE_FALSE); + } + /* advance pointers */ + p->iRedLine = (p->iRedLine + 1) % p->iLinesPerCircBuf; + p->iGrnLine = (p->iGrnLine + 1) % p->iLinesPerCircBuf; + p->iBluLine = (p->iBluLine + 1) % p->iLinesPerCircBuf; + } +} + +static void +XferBufferExit (TDataPipe * p) +{ + if (p->pabXferBuf != NULL) + { + free (p->pabXferBuf); + p->pabXferBuf = NULL; + } + else + { + DBG (DBG_ERR, "XferBufExit: Xfer buffer not initialised!\n"); + } +} + + +/* unscrambles a line: + - combining the proper R, G and B lines and converting them to interpixel RGB + - mirroring left to right +*/ +static void +_UnscrambleLine (unsigned char *pabLine, + unsigned char *pabRed, unsigned char *pabGrn, + unsigned char *pabBlu, int iWidth, SANE_Bool iReversedHead, + int iScaleDownDpi, int iBufWeight) +{ + /* never change an approved algorithm ... + so take Bertriks original source for this special case */ + if (iScaleDownDpi == 1 && iBufWeight == 0) + { + int i, j; + if (iReversedHead) + { + /* reversed */ + for (i = 0; i < iWidth; i++) + { + j = (iWidth - i) * 3; + pabLine[j - 3] = pabRed[i]; + pabLine[j - 2] = pabGrn[i + iWidth]; + pabLine[j - 1] = pabBlu[i + iWidth * 2]; + } + } + else + { + /* not reversed */ + for (i = 0; i < iWidth; i++) + { + pabLine[3 * i] = pabRed[i]; + pabLine[3 * i + 1] = pabGrn[i + iWidth]; + pabLine[3 * i + 2] = pabBlu[i + iWidth * 2]; + } + } + } + else + { + int i, j; /* loop variables */ + int c; /* color buffer accumulator for horizontal avarage */ + + /* initialize for incremental color buffer access */ + int iInc = 1; + int iStart = 0; + + /* set for "from the end to the front" of the circular color buffers */ + if (iReversedHead) + { + iStart = iWidth - iScaleDownDpi; + iInc = -1; + } + + /* each pixel is the mean of iScaleDownDpi + so set the skip width accordingly */ + iInc *= iScaleDownDpi; + + for (i = iStart; i >= 0 && i < iWidth; i += iInc) + { + /* collect the red pixels */ + for (c = j = 0; j < iScaleDownDpi; ++j) + c += pabRed[i + j]; + *pabLine = + (*pabLine * iBufWeight + c / iScaleDownDpi) / (iBufWeight + 1); + pabLine++; + + /* collect the green pixels */ + for (c = j = 0; j < iScaleDownDpi; ++j) + c += pabGrn[i + iWidth + j]; + *pabLine = + (*pabLine * iBufWeight + c / iScaleDownDpi) / (iBufWeight + 1); + pabLine++; + + /* collect the blue pixels */ + for (c = j = 0; j < iScaleDownDpi; ++j) + c += pabBlu[i + 2 * iWidth + j]; + *pabLine = + (*pabLine * iBufWeight + c / iScaleDownDpi) / (iBufWeight + 1); + pabLine++; + } + } + +} + + +/* gets an unscrambled line from the circular buffer. the first couple of lines contain garbage, + if fReturn==SANE_TRUE, the head will return automatically on an end of scan */ +STATIC SANE_Bool +CircBufferGetLineEx (int iHandle, TDataPipe * p, unsigned char *pabLine, + SANE_Bool iReversedHead, SANE_Bool fReturn) +{ + int iLineCount; + for (iLineCount = 0; iLineCount < p->iScaleDownLpi; ++iLineCount) + { + if (iReversedHead) + { + if (!XferBufferGetLine (iHandle, p, + &p->pabCircBuf[p->iRedLine * + p->iBytesPerLine], fReturn)) + return SANE_FALSE; + } + else + { + if (!XferBufferGetLine (iHandle, p, + &p->pabCircBuf[p->iBluLine * + p->iBytesPerLine], fReturn)) + return SANE_FALSE; + } + if (pabLine != NULL) + { + _UnscrambleLine (pabLine, + &p->pabCircBuf[p->iRedLine * p->iBytesPerLine], + &p->pabCircBuf[p->iGrnLine * p->iBytesPerLine], + &p->pabCircBuf[p->iBluLine * p->iBytesPerLine], + p->iWidth * p->iScaleDownDpi, iReversedHead, + p->iScaleDownDpi, iLineCount); + } + + /* advance pointers */ + p->iRedLine = (p->iRedLine + 1) % p->iLinesPerCircBuf; + p->iGrnLine = (p->iGrnLine + 1) % p->iLinesPerCircBuf; + p->iBluLine = (p->iBluLine + 1) % p->iLinesPerCircBuf; + } + return SANE_TRUE; +} + + +/* gets an unscrambled line from the circular buffer. the first couple of lines contain garbage */ +STATIC SANE_Bool +CircBufferGetLine (int iHandle, TDataPipe * p, unsigned char *pabLine, + SANE_Bool iReversedHead) +{ + return CircBufferGetLineEx (iHandle, p, pabLine, iReversedHead, SANE_FALSE); +} + + +/* try to keep the number of transfers the same, but make them all + as good as possible the same size to avoid cranking in critical + situations +*/ +static int +_OptimizeXferSize (int iLines, int iLinesPerXfer) +{ + int iXfers; + iXfers = (iLines + iLinesPerXfer - 1) / iLinesPerXfer; + while (--iLinesPerXfer > 0 + && (iLines + iLinesPerXfer - 1) / iLinesPerXfer == iXfers); + return iLinesPerXfer + 1; +} + +STATIC void +CircBufferInit (int iHandle, TDataPipe * p, + int iWidth, int iHeight, + int iMisAlignment, SANE_Bool iReversedHead, + int iScaleDownDpi, int iScaleDownLpi) +{ + + /* relevant for internal read and write functions */ + p->iScaleDownLpi = iScaleDownLpi; + p->iScaleDownDpi = iScaleDownDpi; + p->iWidth = iWidth; + p->iBytesPerLine = iWidth * iScaleDownDpi * BYTES_PER_PIXEL; + p->iSaneBytesPerLine = iWidth * BYTES_PER_PIXEL; + if (iMisAlignment == 0) + { + p->iLinesPerCircBuf = 1; + } + else + { + p->iLinesPerCircBuf = 3 * iMisAlignment; + } + + DBG (DBG_MSG, "_iScaleDown (Dpi,Lpi) = (%d,%d)\n", p->iScaleDownDpi, + p->iScaleDownLpi); + DBG (DBG_MSG, "_iBytesPerLine = %d\n", p->iBytesPerLine); + DBG (DBG_MSG, "_iLinesPerCircBuf = %d\n", p->iLinesPerCircBuf); + p->pabCircBuf = + (unsigned char *) malloc (p->iBytesPerLine * p->iLinesPerCircBuf); + if (p->pabCircBuf == NULL) + { + DBG (DBG_ERR, + "Unable to allocate %d unsigned chars for circular buffer\n", + (int) (p->iBytesPerLine * p->iLinesPerCircBuf)); + return; + } + DBG (DBG_MSG, "Allocated %d unsigned chars for circular buffer\n", + p->iBytesPerLine * p->iLinesPerCircBuf); + + if (iReversedHead) + { + p->iBluLine = 0; + p->iGrnLine = iMisAlignment; + p->iRedLine = iMisAlignment * 2; + } + else + { + p->iRedLine = 0; + p->iGrnLine = iMisAlignment; + p->iBluLine = iMisAlignment * 2; + } + + /* negative height is an indication for "no Check" */ + if (iHeight < 0) + { + p->iLinesLeft = -1; + p->iLinesPerXferBuf = XFER_BUF_SIZE / p->iBytesPerLine; + DBG (DBG_MSG, "using unchecked XFER_BUF_SIZE\n"); + DBG (DBG_MSG, "_iXFerSize = %d\n", + p->iBytesPerLine * p->iLinesPerXferBuf); + } + else + { +#define SAFETY_LINES 0 +#define MAX_LINES_PER_XFERBUF 800 + /* estimate of number of unsigned chars to transfer at all via the USB */ + /* add some lines for securtiy */ + + p->iLinesLeft = + iHeight + p->iSkipLines + p->iLinesPerCircBuf + SAFETY_LINES; + p->iLinesPerXferBuf = XFER_BUF_SIZE / p->iBytesPerLine; + /* with more than 800 lines the timing is spoiled */ + if (p->iLinesPerXferBuf > MAX_LINES_PER_XFERBUF) + { + p->iLinesPerXferBuf = MAX_LINES_PER_XFERBUF; + } + /* final optimization to keep critical scans smooth */ + p->iLinesPerXferBuf = + _OptimizeXferSize (p->iLinesLeft, p->iLinesPerXferBuf); + + DBG (DBG_MSG, "_iXFerSize = %d for %d transfer(s)\n", + (int) p->iLinesPerXferBuf * p->iBytesPerLine, + (p->iLinesLeft + p->iLinesPerXferBuf - 1) / p->iLinesPerXferBuf); + } + DBG (DBG_MSG, "_iLinesPerXferBuf = %d\n", p->iLinesPerXferBuf); + + /* init transfer buffer */ + XferBufferInit (iHandle, p); + + /* fill circular buffer */ + CircBufferFill (iHandle, p, iReversedHead); +} + + +STATIC void +CircBufferExit (TDataPipe * p) +{ + XferBufferExit (p); + if (p->pabCircBuf != NULL) + { + DBG (DBG_MSG, "\n"); + free (p->pabCircBuf); + p->pabCircBuf = NULL; + } + else + { + DBG (DBG_ERR, "CircBufferExit: Circular buffer not initialised!\n"); + } +} + + +/************************************************************************/ + + + +static int +_CalcAvg (unsigned char *pabBuf, int n, int iStep) +{ + int i, j, x; + + for (i = j = x = 0; i < n; i++) + { + x += pabBuf[j]; + j += iStep; + } + return (x / n); +} + + +/* converts white line data and black point data into a calibration table */ +static void +CreateCalibTable (unsigned char *abWhite, unsigned char bBlackR, + unsigned char bBlackG, unsigned char bBlackB, + int iReversedHead, unsigned char *pabCalibTable) +{ + int i, j, iGain, iOffset, iData; + unsigned char *pabPixel; + + j = 0; + for (i = 0; i < HW_PIXELS; i++) + { + if (iReversedHead) + { + pabPixel = &abWhite[(HW_PIXELS - i - 1) * 3]; + } + else + { + pabPixel = &abWhite[i * 3]; + } + /* red */ + if (bBlackR > 16) + bBlackR = 16; + iGain = 65536 / MAX (1, pabPixel[0] - bBlackR); + iOffset = bBlackR * 4; + if (iOffset > 63) + iOffset = 63; + iData = (iGain << 6) + iOffset; + pabCalibTable[j++] = (iData) & 255; + pabCalibTable[j++] = (iData >> 8) & 255; + /* green */ + if (bBlackG > 16) + bBlackG = 16; + iGain = 65536 / MAX (1, pabPixel[1] - bBlackG); + iOffset = bBlackG * 4; + if (iOffset > 63) + iOffset = 63; + iData = (iGain << 6) + iOffset; + pabCalibTable[j++] = (iData) & 255; + pabCalibTable[j++] = (iData >> 8) & 255; + /* blue */ + if (bBlackB > 16) + bBlackB = 16; + iGain = 65536 / MAX (1, pabPixel[2] - bBlackB); + iOffset = bBlackB * 4; + if (iOffset > 63) + iOffset = 63; + iData = (iGain << 6) + iOffset; + pabCalibTable[j++] = (iData) & 255; + pabCalibTable[j++] = (iData >> 8) & 255; + } +} + + +/************************************************************************* + Lamp control functions +*************************************************************************/ +STATIC SANE_Bool +GetLamp (THWParams * pHWParams, SANE_Bool * pfLampIsOn) +{ + unsigned char bData; + + NiashReadReg (pHWParams->iXferHandle, 0x03, &bData); + *pfLampIsOn = ((bData & 0x01) != 0); + return SANE_TRUE; +} + + +STATIC SANE_Bool +SetLamp (THWParams * pHWParams, SANE_Bool fLampOn) +{ + unsigned char bData; + int iHandle; + + iHandle = pHWParams->iXferHandle; + + NiashReadReg (iHandle, 0x03, &bData); + if (fLampOn) + { + NiashWriteReg (iHandle, 0x03, bData | 0x01); + } + else + { + NiashWriteReg (iHandle, 0x03, bData & ~0x01); + } + return SANE_TRUE; +} + + +/************************************************************************* + Experimental simple calibration, but also returning the white levels +*************************************************************************/ +STATIC SANE_Bool +SimpleCalibExt (THWParams * pHWPar, unsigned char *pabCalibTable, + unsigned char *pabCalWhite) +{ + unsigned char bMinR, bMinG, bMinB; + TDataPipe DataPipe; + TScanParams Params; + unsigned char abGamma[4096]; + int i, j; + static unsigned char abBuf[HW_PIXELS * 3 * 71]; /* Carefull : see startWhite and endWhite below */ + static unsigned char abLine[HW_PIXELS * 3]; + static unsigned char abWhite[HW_PIXELS * 3]; + unsigned char *pabWhite; + int iWhiteR, iWhiteG, iWhiteB; + int iHandle; + SANE_Bool iReversedHead; + int startWhiteY, endWhiteY; + int startBlackY, endBlackY; + int startBlackX, endBlackX; + + iHandle = pHWPar->iXferHandle; + iReversedHead = pHWPar->iReversedHead; + + DataPipe.iSkipLines = pHWPar->iSkipLines; + + Params.iDpi = HW_DPI; + Params.iLpi = HW_DPI; + if (iReversedHead) /* hp scanners */ + Params.iTop = 60; + else /* agfa scanners */ + Params.iTop = 30; + Params.iBottom = HP3300C_BOTTOM; + Params.iLeft = 0; + Params.iWidth = HW_PIXELS; + Params.iHeight = 54; + Params.fCalib = SANE_TRUE; + + /* write gamma table with neutral gain / offset */ + CalcGamma (abGamma, 1.0); + WriteGammaCalibTable (abGamma, abGamma, abGamma, NULL, 256, 0, pHWPar); + + if (!InitScan (&Params, pHWPar)) + { + if (pabCalWhite) + pabCalWhite[0] = pabCalWhite[1] = pabCalWhite[2] = 0; + return SANE_FALSE; + } + + /* Definition of white and black areas */ + if (iReversedHead) + { /* hp scanners */ + startWhiteY = 0; + endWhiteY = 15; + startBlackY = 16; + endBlackY = 135; + startBlackX = 0; + endBlackX = HW_PIXELS; + } + else + { /* agfa scanners */ + startWhiteY = 0; + endWhiteY = 70; + startBlackY = 86; + endBlackY = 135; + startBlackX = 1666; + endBlackX = 3374; + } + + CircBufferInit (iHandle, &DataPipe, HW_PIXELS, -1, Params.iLpi / 150, + iReversedHead, 1, 1); + /* white level */ + /* skip some lines */ + for (i = 0; i < startWhiteY; i++) + { + CircBufferGetLine (iHandle, &DataPipe, abLine, iReversedHead); + } + /* Get white lines */ + for (i = 0; i < endWhiteY - startWhiteY + 1; i++) + { + CircBufferGetLine (iHandle, &DataPipe, &abBuf[i * HW_PIXELS * 3], + iReversedHead); + } + /* black level */ + bMinR = 255; + bMinG = 255; + bMinB = 255; + /* Skip some lines */ + for (i = 0; i < startBlackY; i++) + { + CircBufferGetLine (iHandle, &DataPipe, abLine, iReversedHead); + } + for (i = 0; i < endBlackY - startBlackY + 1; i++) + { + CircBufferGetLine (iHandle, &DataPipe, abLine, iReversedHead); + for (j = 0; j < endBlackX; j++) + { + bMinR = MIN (abLine[j * 3 + 0], bMinR); + bMinG = MIN (abLine[j * 3 + 1], bMinG); + bMinB = MIN (abLine[j * 3 + 2], bMinB); + } + } + CircBufferExit (&DataPipe); + FinishScan (pHWPar); + + /* calc average white level */ + pabWhite = abBuf; + for (i = 0; i < HW_PIXELS; i++) + { + abWhite[i * 3 + 0] = + _CalcAvg (&pabWhite[i * 3 + 0], endWhiteY - startWhiteY + 1, + HW_PIXELS * 3); + abWhite[i * 3 + 1] = + _CalcAvg (&pabWhite[i * 3 + 1], endWhiteY - startWhiteY + 1, + HW_PIXELS * 3); + abWhite[i * 3 + 2] = + _CalcAvg (&pabWhite[i * 3 + 2], endWhiteY - startWhiteY + 1, + HW_PIXELS * 3); + } + iWhiteR = _CalcAvg (&abWhite[0], HW_PIXELS, 3); + iWhiteG = _CalcAvg (&abWhite[1], HW_PIXELS, 3); + iWhiteB = _CalcAvg (&abWhite[2], HW_PIXELS, 3); + + DBG (DBG_MSG, "Black level (%d,%d,%d), White level (%d,%d,%d)\n", + (int) bMinR, (int) bMinG, (int) bMinB, iWhiteR, iWhiteG, iWhiteB); + + /* convert the white line and black point into a calibration table */ + CreateCalibTable (abWhite, bMinR, bMinG, bMinB, iReversedHead, + pabCalibTable); + /* assign the White Levels */ + if (pabCalWhite) + { + pabCalWhite[0] = iWhiteR; + pabCalWhite[1] = iWhiteG; + pabCalWhite[2] = iWhiteB; + } + return SANE_TRUE; +} + + +/************************************************************************* + FinishScan + ========== + Finishes the scan. Makes the scanner head move back to the home position. + +*************************************************************************/ +STATIC void +FinishScan (THWParams * pHWParams) +{ + NiashWriteReg (pHWParams->iXferHandle, 0x02, 0x80); +} |