summaryrefslogtreecommitdiff
path: root/backend/niash_core.c
diff options
context:
space:
mode:
authorJörg Frings-Fürst <debian@jff-webhosting.net>2014-10-06 14:00:40 +0200
committerJörg Frings-Fürst <debian@jff-webhosting.net>2014-10-06 14:00:40 +0200
commit6e9c41a892ed0e0da326e0278b3221ce3f5713b8 (patch)
tree2e301d871bbeeb44aa57ff9cc070fcf3be484487 /backend/niash_core.c
Initial import of sane-backends version 1.0.24-1.2
Diffstat (limited to 'backend/niash_core.c')
-rw-r--r--backend/niash_core.c1362
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);
+}