fbf4b45e76
When the device is deactivated while it is still active then the exit SM needs to be executed from the SM that was active at the time. This is signalled by is_active being set to FALSE while the active SM completes. Call m_exit_start in those cases to ensure proper device deactivation.
1471 lines
39 KiB
C
1471 lines
39 KiB
C
/*
|
|
* EgisTec ES603 driver for libfprint
|
|
* Copyright (C) 2012 Patrick Marlier
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library 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
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
/* EgisTec ES603 device information
|
|
* Sensor area: 192 x 4 pixels
|
|
* Sensor gray: 16 gray levels/sensor pixel
|
|
* Sensor resolution: 508 dpi
|
|
* USB Manufacturer ID: 1C7A
|
|
* USB Product ID: 0603
|
|
*
|
|
* Possible compatibility LTT-SS500/SS501
|
|
*
|
|
* Extra features not present in this driver (see https://code.google.com/p/etes603):
|
|
* Tuning of DTVRT for contact detection
|
|
* Contact detection via capacitance
|
|
* Capture mode using assembled frames (usually better quality)
|
|
*
|
|
*/
|
|
|
|
#define FP_COMPONENT "etes603"
|
|
|
|
#include "drivers_api.h"
|
|
#include "driver_ids.h"
|
|
|
|
/* libusb defines */
|
|
#define EP_IN 0x81
|
|
#define EP_OUT 0x02
|
|
/* Note that 1000 ms is usually enough but with CMD_READ_FE could be longer
|
|
* since the sensor is waiting motion. */
|
|
#define BULK_TIMEOUT 1000
|
|
|
|
/* es603 defines */
|
|
#define FRAME_WIDTH 192 /* pixels per row */
|
|
#define FRAME_HEIGHT 4 /* number of rows */
|
|
#define FRAME_SIZE 384 /* size in bytes (4 bits per pixels) */
|
|
#define FE_WIDTH 256 /* pixels per row for Fly-Estimation */
|
|
#define FE_HEIGHT 500 /* number of rows for Fly-Estimation */
|
|
#define FE_SIZE 64000 /* size in bytes (4 bits per pixels) */
|
|
|
|
#define GAIN_SMALL_INIT 0x23 /* Initial small gain */
|
|
#define VRT_MAX 0x3F /* Maximum value for VRT */
|
|
#define VRB_MAX 0x3A /* Maximum value for VRB */
|
|
#define DTVRT_MAX 0x3A /* Maximum value for DTVRT */
|
|
#define DCOFFSET_MIN 0x00 /* Minimum value for DCoffset */
|
|
#define DCOFFSET_MAX 0x35 /* Maximum value for DCoffset */
|
|
|
|
/* es603 commands */
|
|
#define CMD_READ_REG 0x01
|
|
#define CMD_WRITE_REG 0x02
|
|
#define CMD_READ_FRAME 0x03 /* Read the sensor area */
|
|
#define CMD_READ_FE 0x06 /* Read a fingerprint using Fly-Estimation */
|
|
#define CMD_20 0x20 /* ? */
|
|
#define CMD_25 0x25 /* ? */
|
|
#define CMD_60 0x60 /* ? */
|
|
|
|
#define CMD_OK 0x01 /* Command successfully executed */
|
|
|
|
/* es603 registers */
|
|
#define REG_MAX 0x18 /* Maximum number of registers in one message */
|
|
#define REG_MODE_CONTROL 0x02 /* Mode control */
|
|
#define REG_03 0x03 /* Contact register? */
|
|
#define REG_04 0x04 /* ? */
|
|
#define REG_10 0x10 /* MVS FRMBUF control */
|
|
#define REG_1A 0x1A /* ? */
|
|
/* BEGIN init sensor */
|
|
#define REG_20 0x20 /* (def: 0x00) */
|
|
#define REG_21 0x21 /* Small gain (def: 0x23) */
|
|
#define REG_22 0x22 /* Normal gain (def: 0x21) */
|
|
#define REG_23 0x23 /* Large gain (def: 0x20) */
|
|
#define REG_24 0x24 /* (def: 0x14) */
|
|
#define REG_25 0x25 /* (def: 0x6A) */
|
|
#define REG_26 0x26 /* VRB again? (def: 0x00) */
|
|
#define REG_27 0x27 /* VRT again? (def: 0x00) */
|
|
#define REG_28 0x28 /* (def: 0x00) */
|
|
#define REG_29 0x29 /* (def: 0xC0) */
|
|
#define REG_2A 0x2A /* (def: 0x50) */
|
|
#define REG_2B 0x2B /* (def: 0x50) */
|
|
#define REG_2C 0x2C /* (def: 0x4D) */
|
|
#define REG_2D 0x2D /* (def: 0x03) */
|
|
#define REG_2E 0x2E /* (def: 0x06) */
|
|
#define REG_2F 0x2F /* (def: 0x06) */
|
|
#define REG_30 0x30 /* (def: 0x10) */
|
|
#define REG_31 0x31 /* (def: 0x02) */
|
|
#define REG_32 0x32 /* (def: 0x14) */
|
|
#define REG_33 0x33 /* (def: 0x34) */
|
|
#define REG_34 0x34 /* (def: 0x01) */
|
|
#define REG_35 0x35 /* (def: 0x08) */
|
|
#define REG_36 0x36 /* (def: 0x03) */
|
|
#define REG_37 0x37 /* (def: 0x21) */
|
|
/* END init sensor */
|
|
|
|
#define REG_ENC1 0x41 /* Encryption 1 */
|
|
#define REG_ENC2 0x42
|
|
#define REG_ENC3 0x43
|
|
#define REG_ENC4 0x44
|
|
#define REG_ENC5 0x45
|
|
#define REG_ENC6 0x46
|
|
#define REG_ENC7 0x47
|
|
#define REG_ENC8 0x48 /* Encryption 8 */
|
|
|
|
#define REG_50 0x50 /* ? For contact detection */
|
|
#define REG_51 0x51 /* ? */
|
|
#define REG_59 0x59 /* ? */
|
|
#define REG_5A 0x5A /* ? */
|
|
#define REG_5B 0x5B /* ? */
|
|
|
|
#define REG_INFO0 0x70 /* Sensor model byte0 */
|
|
#define REG_INFO1 0x71 /* Sensor model byte1 */
|
|
#define REG_INFO2 0x72 /* Sensor model byte2 */
|
|
#define REG_INFO3 0x73 /* Sensor model byte3 */
|
|
|
|
#define REG_GAIN 0xE0
|
|
#define REG_VRT 0xE1
|
|
#define REG_VRB 0xE2
|
|
#define REG_DTVRT 0xE3 /* used for contact detection */
|
|
#define REG_VCO_CONTROL 0xE5 /* 0x13 (IDLE?), 0x14 (REALTIME) */
|
|
#define REG_DCOFFSET 0xE6
|
|
|
|
#define REG_F0 0xF0 /* ? init:0x00 close:0x01 */
|
|
#define REG_F2 0xF2 /* ? init:0x00 close:0x4E */
|
|
|
|
#define REG_MODE_SLEEP 0x30 /* Sleep mode */
|
|
#define REG_MODE_CONTACT 0x31 /* Contact mode */
|
|
#define REG_MODE_SENSOR 0x33 /* Sensor mode */
|
|
#define REG_MODE_FP 0x34 /* FingerPrint mode (Fly-Estimation®) */
|
|
|
|
#define REG_VCO_IDLE 0x13
|
|
#define REG_VCO_RT 0x14 /* Realtime */
|
|
|
|
/* The size of the message header is 5 plus 1 for the command. */
|
|
#define MSG_HDR_SIZE 6
|
|
|
|
/* This structure must be packed because it is a the raw message sent. */
|
|
struct egis_msg {
|
|
guint8 magic[5]; /* out: 'EGIS' 0x09 / in: 'SIGE' 0x0A */
|
|
guint8 cmd;
|
|
union {
|
|
struct {
|
|
guint8 nb;
|
|
guint8 regs[REG_MAX];
|
|
} egis_readreg;
|
|
struct {
|
|
guint8 regs[REG_MAX];
|
|
} sige_readreg;
|
|
struct {
|
|
guint8 nb;
|
|
struct {
|
|
guint8 reg;
|
|
guint8 val;
|
|
} regs[REG_MAX];
|
|
} egis_writereg;
|
|
struct {
|
|
guint8 length_factor;
|
|
guint8 length;
|
|
guint8 use_gvv;
|
|
guint8 gain;
|
|
guint8 vrt;
|
|
guint8 vrb;
|
|
} egis_readf;
|
|
struct {
|
|
guint8 len[2];
|
|
guint8 val[3];
|
|
} egis_readfp;
|
|
struct {
|
|
guint8 val[5];
|
|
} sige_misc;
|
|
guint8 padding[0x40-6]; /* Ensure size of 0x40 */
|
|
};
|
|
} __attribute__((packed));
|
|
|
|
|
|
/* Structure to keep information between asynchronous functions. */
|
|
struct _FpiDeviceEtes603 {
|
|
FpImageDevice parent;
|
|
|
|
guint8 regs[256];
|
|
struct egis_msg *req;
|
|
size_t req_len;
|
|
struct egis_msg *ans;
|
|
size_t ans_len;
|
|
|
|
guint8 *fp;
|
|
guint16 fp_height;
|
|
|
|
guint8 tunedc_min;
|
|
guint8 tunedc_max;
|
|
|
|
/* Device parameters */
|
|
guint8 gain;
|
|
guint8 dcoffset;
|
|
guint8 vrt;
|
|
guint8 vrb;
|
|
|
|
unsigned int is_active;
|
|
};
|
|
G_DECLARE_FINAL_TYPE(FpiDeviceEtes603, fpi_device_etes603, FPI, DEVICE_ETES603,
|
|
FpImageDevice);
|
|
G_DEFINE_TYPE(FpiDeviceEtes603, fpi_device_etes603, FP_TYPE_IMAGE_DEVICE);
|
|
|
|
static void m_start_fingerdetect(FpImageDevice *idev);
|
|
/*
|
|
* Prepare the header of the message to be sent to the device.
|
|
*/
|
|
static void msg_header_prepare(struct egis_msg *msg)
|
|
{
|
|
msg->magic[0] = 'E';
|
|
msg->magic[1] = 'G';
|
|
msg->magic[2] = 'I';
|
|
msg->magic[3] = 'S';
|
|
msg->magic[4] = 0x09;
|
|
}
|
|
|
|
/*
|
|
* Check that the header of the received message is correct.
|
|
*/
|
|
static int msg_header_check(struct egis_msg *msg)
|
|
{
|
|
if (msg->magic[0] == 'S' && msg->magic[1] == 'I'
|
|
&& msg->magic[2] == 'G' && msg->magic[3] == 'E'
|
|
&& msg->magic[4] == 0x0A)
|
|
return 0;
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* Prepare message to ask for a frame.
|
|
*/
|
|
static void msg_get_frame(FpiDeviceEtes603 *self,
|
|
guint8 use_gvv, guint8 gain, guint8 vrt,
|
|
guint8 vrb)
|
|
{
|
|
struct egis_msg *msg = self->req;
|
|
msg_header_prepare(msg);
|
|
msg->cmd = CMD_READ_FRAME;
|
|
msg->egis_readf.length_factor = 0x01;
|
|
/* length should be 0xC0 */
|
|
msg->egis_readf.length = FRAME_WIDTH;
|
|
msg->egis_readf.use_gvv = use_gvv;
|
|
/* if use_gvv is set, gain/vrt/vrb are used */
|
|
msg->egis_readf.gain = gain;
|
|
msg->egis_readf.vrt = vrt;
|
|
msg->egis_readf.vrb = vrb;
|
|
|
|
self->req_len = MSG_HDR_SIZE + 6;
|
|
self->ans_len = FRAME_SIZE;
|
|
}
|
|
|
|
/*
|
|
* Prepare message to ask for a fingerprint frame.
|
|
*/
|
|
static void msg_get_fp(FpiDeviceEtes603 *self, guint8 len0, guint8 len1,
|
|
guint8 v2, guint8 v3, guint8 v4)
|
|
{
|
|
struct egis_msg *msg = self->req;
|
|
msg_header_prepare(msg);
|
|
msg->cmd = CMD_READ_FE;
|
|
/* Unknown values and always same on captured frames.
|
|
* 1st 2nd bytes is unsigned short for height, but only on value range
|
|
* 0x01 0xF4 (500), 0x02 0x00 (512), 0x02 0xF4 (756) are ok
|
|
*/
|
|
msg->egis_readfp.len[0] = len0;
|
|
msg->egis_readfp.len[1] = len1;
|
|
/* 3rd byte : ?? but changes frame size
|
|
* 4th byte : 0x00 -> can change width
|
|
* 5th byte : motion sensibility?
|
|
*/
|
|
msg->egis_readfp.val[0] = v2;
|
|
msg->egis_readfp.val[1] = v3;
|
|
msg->egis_readfp.val[2] = v4;
|
|
|
|
self->req_len = MSG_HDR_SIZE + 5;
|
|
self->ans_len = FE_SIZE;
|
|
}
|
|
|
|
/*
|
|
* Prepare message to read registers from the sensor.
|
|
* Variadic argument pattern: int reg, ...
|
|
*/
|
|
static void msg_get_regs(FpiDeviceEtes603 *self, int n_args, ... )
|
|
{
|
|
struct egis_msg *msg = self->req;
|
|
va_list ap;
|
|
int i;
|
|
|
|
g_assert(n_args > 0 && n_args <= REG_MAX);
|
|
|
|
msg_header_prepare(msg);
|
|
msg->cmd = CMD_READ_REG;
|
|
msg->egis_readreg.nb = n_args;
|
|
va_start(ap, n_args);
|
|
for (i = 0; i < n_args; i++) {
|
|
msg->egis_readreg.regs[i] = va_arg(ap, int);
|
|
}
|
|
va_end(ap);
|
|
|
|
self->req_len = MSG_HDR_SIZE + 1 + n_args;
|
|
self->ans_len = MSG_HDR_SIZE + 1 + n_args;
|
|
}
|
|
|
|
/*
|
|
* Parse the result of read register command.
|
|
*/
|
|
static int msg_parse_regs(FpiDeviceEtes603 *dev)
|
|
{
|
|
size_t i, n_args;
|
|
struct egis_msg *msg_req = dev->req;
|
|
struct egis_msg *msg_ans = dev->ans;
|
|
n_args = dev->ans_len - MSG_HDR_SIZE;
|
|
|
|
if (msg_header_check(msg_ans)) {
|
|
return -1;
|
|
}
|
|
if (msg_ans->cmd != CMD_OK) {
|
|
return -2;
|
|
}
|
|
|
|
for (i = 0; i < n_args; i++) {
|
|
int reg = msg_req->egis_readreg.regs[i];
|
|
dev->regs[reg] = msg_ans->sige_readreg.regs[i];
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Prepare message to write sensor's registers.
|
|
* Variadic arguments are: int reg, int val, ...
|
|
*/
|
|
static void msg_set_regs(FpiDeviceEtes603 *self, int n_args, ...)
|
|
{
|
|
struct egis_msg *msg = self->req;
|
|
va_list ap;
|
|
int i;
|
|
|
|
g_assert(n_args != 0 && n_args % 2 == 0 && n_args <= REG_MAX * 2);
|
|
|
|
msg_header_prepare(msg);
|
|
msg->cmd = CMD_WRITE_REG;
|
|
msg->egis_writereg.nb = n_args / 2;
|
|
|
|
va_start(ap, n_args);
|
|
for (i = 0; i < n_args / 2; i++) {
|
|
msg->egis_writereg.regs[i].reg = va_arg(ap, int);
|
|
msg->egis_writereg.regs[i].val = va_arg(ap, int);
|
|
}
|
|
va_end(ap);
|
|
|
|
self->req_len = MSG_HDR_SIZE + 1 + n_args;
|
|
self->ans_len = MSG_HDR_SIZE + 1;
|
|
}
|
|
|
|
static int msg_check_ok(FpiDeviceEtes603 *dev)
|
|
{
|
|
struct egis_msg *msg = dev->ans;
|
|
if (msg_header_check(msg)) {
|
|
goto err;
|
|
}
|
|
if (msg->cmd != CMD_OK) {
|
|
goto err;
|
|
}
|
|
return 0;
|
|
err:
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* Check the model of the sensor.
|
|
*/
|
|
static int check_info(FpiDeviceEtes603 *dev)
|
|
{
|
|
if (dev->regs[0x70] == 0x4A && dev->regs[0x71] == 0x44
|
|
&& dev->regs[0x72] == 0x49 && dev->regs[0x73] == 0x31)
|
|
return 0;
|
|
fp_err("unknown device parameters (REG_70:%02X REG_71:%02X "
|
|
"REG_FIRMWARE:%02X REG_VERSION:%02X)",
|
|
dev->regs[0x70], dev->regs[0x71], dev->regs[0x72],
|
|
dev->regs[0x73]);
|
|
return -1;
|
|
}
|
|
|
|
static void msg_get_cmd20(FpiDeviceEtes603 *dev)
|
|
{
|
|
struct egis_msg *msg = dev->req;
|
|
msg_header_prepare(msg);
|
|
msg->cmd = CMD_20;
|
|
dev->req_len = MSG_HDR_SIZE;
|
|
}
|
|
|
|
static int msg_check_cmd20(FpiDeviceEtes603 *dev)
|
|
{
|
|
struct egis_msg *msg = dev->ans;
|
|
if (msg_header_check(msg)) {
|
|
fp_err("msg_header_check failed");
|
|
return -1;
|
|
}
|
|
/* status or flashtype/flashinfo or ? */
|
|
if (msg->cmd != 0x05
|
|
|| msg->sige_misc.val[0] != 0x00
|
|
|| msg->sige_misc.val[1] != 0x00) {
|
|
fp_warn("unexpected answer CMD_20 from device(%02X %02X %02X)",
|
|
msg->cmd, msg->sige_misc.val[0], msg->sige_misc.val[1]);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void msg_get_cmd25(FpiDeviceEtes603 *dev)
|
|
{
|
|
struct egis_msg *msg = dev->req;
|
|
msg_header_prepare(msg);
|
|
msg->cmd = CMD_25;
|
|
dev->req_len = MSG_HDR_SIZE;
|
|
}
|
|
|
|
static int msg_check_cmd25(FpiDeviceEtes603 *dev)
|
|
{
|
|
struct egis_msg *msg = dev->ans;
|
|
if (msg_header_check(msg)) {
|
|
fp_err("msg_header_check failed");
|
|
goto err;
|
|
}
|
|
if (msg->cmd != CMD_OK) {
|
|
fp_err("CMD_OK failed");
|
|
goto err;
|
|
}
|
|
/* flashtype or status or ? */
|
|
if (msg->sige_misc.val[0] != 0x00) {
|
|
fp_warn("unexpected answer for CMD_25 (%02X)",
|
|
msg->sige_misc.val[0]);
|
|
}
|
|
return 0;
|
|
err:
|
|
return -1;
|
|
}
|
|
|
|
static void msg_set_mode_control(FpiDeviceEtes603 *self, guint8 mode)
|
|
{
|
|
msg_set_regs(self, 2, REG_MODE_CONTROL, mode);
|
|
}
|
|
|
|
|
|
/* Processing functions */
|
|
|
|
/*
|
|
* Return the brightness of a 4bpp frame
|
|
*/
|
|
static unsigned int process_get_brightness(guint8 *f, size_t s)
|
|
{
|
|
unsigned int i, sum = 0;
|
|
for (i = 0; i < s; i++) {
|
|
sum += f[i] >> 4;
|
|
sum += f[i] & 0x0F;
|
|
}
|
|
return sum;
|
|
}
|
|
|
|
/*
|
|
* Return the histogram of a 4bpp frame
|
|
*/
|
|
static void process_hist(guint8 *f, size_t s, float stat[5])
|
|
{
|
|
float hist[16];
|
|
float black_mean, white_mean;
|
|
int i;
|
|
/* Clean histogram */
|
|
for (i = 0; i < 16; i++)
|
|
hist[i] = 0.0;
|
|
for (i = 0; i < s; i++) {
|
|
hist[f[i] >> 4]++;
|
|
hist[f[i] & 0x0F]++;
|
|
}
|
|
/* histogram average */
|
|
for (i = 0; i < 16; i++) {
|
|
hist[i] = hist[i] / (s * 2);
|
|
}
|
|
/* Average black/white pixels (full black and full white pixels
|
|
* are excluded). */
|
|
black_mean = white_mean = 0.0;
|
|
for (i = 1; i < 8; i++)
|
|
black_mean += hist[i];
|
|
for (i = 8; i < 15; i++)
|
|
white_mean += hist[i];
|
|
stat[0] = hist[0];
|
|
stat[1] = black_mean;
|
|
stat[2] = black_mean+white_mean;
|
|
stat[3] = white_mean;
|
|
stat[4] = hist[15];
|
|
fp_dbg("fullb=%6f black=%6f grey=%6f white=%6f fullw=%6f",
|
|
hist[0], black_mean, black_mean+white_mean, white_mean,
|
|
hist[15]);
|
|
}
|
|
|
|
/*
|
|
* Return true if the frame is almost empty.
|
|
*/
|
|
static int process_frame_empty(guint8 *frame, size_t size)
|
|
{
|
|
unsigned int sum = process_get_brightness(frame, size);
|
|
/* Allow an average of 'threshold' luminosity per pixel */
|
|
if (sum < size)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
/* Transform 4 bits image to 8 bits image */
|
|
static void process_4to8_bpp(guint8 *input, unsigned int input_size,
|
|
guint8 *output)
|
|
{
|
|
unsigned int i, j = 0;
|
|
for (i = 0; i < input_size; i++, j += 2) {
|
|
/* 16 gray levels transform to 256 levels using << 4 */
|
|
output[j] = input[i] & 0xF0;
|
|
output[j+1] = input[i] << 4;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Remove duplicated lines at the end of a fingerprint.
|
|
*/
|
|
static void process_removefpi_end(FpiDeviceEtes603 *dev)
|
|
{
|
|
unsigned int i;
|
|
/* 2 last lines with Fly-Estimation are the empty pattern. */
|
|
guint8 *pattern = dev->fp + (dev->fp_height - 2) * FE_WIDTH / 2;
|
|
for (i = 2; i < dev->fp_height; i+= 2) {
|
|
if (memcmp(pattern, pattern - (i * FE_WIDTH / 2), FE_WIDTH))
|
|
break;
|
|
}
|
|
dev->fp_height -= i;
|
|
fp_dbg("Removing %d empty lines from image", i - 2);
|
|
}
|
|
|
|
static void reset_param(FpiDeviceEtes603 *dev)
|
|
{
|
|
dev->dcoffset = 0;
|
|
dev->vrt = 0;
|
|
dev->vrb = 0;
|
|
dev->gain = 0;
|
|
}
|
|
|
|
|
|
/* Asynchronous stuff */
|
|
|
|
enum {
|
|
INIT_CHECK_INFO_REQ,
|
|
INIT_CHECK_INFO_ANS,
|
|
INIT_CMD20_REQ,
|
|
INIT_CMD20_ANS,
|
|
INIT_CMD25_REQ,
|
|
INIT_CMD25_ANS,
|
|
INIT_SENSOR_REQ,
|
|
INIT_SENSOR_ANS,
|
|
INIT_ENC_REQ,
|
|
INIT_ENC_ANS,
|
|
INIT_REGS_REQ,
|
|
INIT_REGS_ANS,
|
|
INIT_NUM_STATES
|
|
};
|
|
|
|
enum {
|
|
TUNEDC_INIT,
|
|
TUNEDC_SET_DCOFFSET_REQ,
|
|
TUNEDC_SET_DCOFFSET_ANS,
|
|
TUNEDC_GET_FRAME_REQ,
|
|
TUNEDC_GET_FRAME_ANS,
|
|
TUNEDC_FINAL_SET_REG2122_REQ,
|
|
TUNEDC_FINAL_SET_REG2122_ANS,
|
|
TUNEDC_FINAL_SET_GAIN_REQ,
|
|
TUNEDC_FINAL_SET_GAIN_ANS,
|
|
TUNEDC_FINAL_SET_DCOFFSET_REQ,
|
|
TUNEDC_FINAL_SET_DCOFFSET_ANS,
|
|
TUNEDC_NUM_STATES
|
|
};
|
|
|
|
enum {
|
|
TUNEVRB_INIT,
|
|
TUNEVRB_GET_GAIN_REQ,
|
|
TUNEVRB_GET_GAIN_ANS,
|
|
TUNEVRB_GET_DCOFFSET_REQ,
|
|
TUNEVRB_GET_DCOFFSET_ANS,
|
|
TUNEVRB_SET_DCOFFSET_REQ,
|
|
TUNEVRB_SET_DCOFFSET_ANS,
|
|
TUNEVRB_FRAME_REQ,
|
|
TUNEVRB_FRAME_ANS,
|
|
TUNEVRB_FINAL_SET_DCOFFSET_REQ,
|
|
TUNEVRB_FINAL_SET_DCOFFSET_ANS,
|
|
TUNEVRB_FINAL_SET_REG2627_REQ,
|
|
TUNEVRB_FINAL_SET_REG2627_ANS,
|
|
TUNEVRB_FINAL_SET_GAINVRTVRB_REQ,
|
|
TUNEVRB_FINAL_SET_GAINVRTVRB_ANS,
|
|
TUNEVRB_FINAL_SET_MODE_SLEEP_REQ,
|
|
TUNEVRB_FINAL_SET_MODE_SLEEP_ANS,
|
|
TUNEVRB_NUM_STATES
|
|
};
|
|
|
|
enum {
|
|
FGR_FPA_INIT_SET_MODE_SLEEP_REQ,
|
|
FGR_FPA_INIT_SET_MODE_SLEEP_ANS,
|
|
FGR_FPA_INIT_SET_DCOFFSET_REQ,
|
|
FGR_FPA_INIT_SET_DCOFFSET_ANS,
|
|
FGR_FPA_INIT_SET_GAINVRTVRB_REQ,
|
|
FGR_FPA_INIT_SET_GAINVRTVRB_ANS,
|
|
FGR_FPA_INIT_SET_VCO_CONTROL_RT_REQ,
|
|
FGR_FPA_INIT_SET_VCO_CONTROL_RT_ANS,
|
|
FGR_FPA_INIT_SET_REG04_REQ,
|
|
FGR_FPA_INIT_SET_REG04_ANS,
|
|
FGR_FPA_INIT_SET_MODE_SENSOR_REQ,
|
|
FGR_FPA_INIT_SET_MODE_SENSOR_ANS,
|
|
FGR_FPA_GET_FRAME_REQ,
|
|
FGR_FPA_GET_FRAME_ANS,
|
|
FGR_NUM_STATES
|
|
};
|
|
|
|
enum {
|
|
CAP_FP_INIT_SET_REG10_REQ,
|
|
CAP_FP_INIT_SET_REG10_ANS,
|
|
CAP_FP_INIT_SET_MODE_FP_REQ,
|
|
CAP_FP_INIT_SET_MODE_FP_ANS,
|
|
CAP_FP_GET_FP_REQ,
|
|
CAP_FP_GET_FP_ANS,
|
|
CAP_NUM_STATES
|
|
};
|
|
|
|
enum {
|
|
EXIT_SET_REGS_REQ,
|
|
EXIT_SET_REGS_ANS,
|
|
EXIT_NUM_STATES
|
|
};
|
|
|
|
static void async_tx(FpDevice *dev, unsigned int ep, void *cb,
|
|
FpiSsm *ssm)
|
|
{
|
|
FpiDeviceEtes603 *self = FPI_DEVICE_ETES603(dev);
|
|
FpiUsbTransfer *transfer = fpi_usb_transfer_new(dev);
|
|
unsigned char *buffer = NULL;
|
|
int length;
|
|
|
|
if (ep == EP_OUT) {
|
|
buffer = (unsigned char *) self->req;
|
|
length = self->req_len;
|
|
} else if (ep == EP_IN) {
|
|
buffer = (unsigned char *) self->ans;
|
|
length = self->ans_len;
|
|
} else {
|
|
g_assert_not_reached ();
|
|
}
|
|
transfer->ssm = ssm;
|
|
fpi_usb_transfer_fill_bulk_full(transfer, ep, buffer, length, NULL);
|
|
fpi_usb_transfer_submit(transfer, BULK_TIMEOUT, NULL, cb, NULL);
|
|
fpi_usb_transfer_unref(transfer);
|
|
}
|
|
|
|
|
|
static void async_tx_cb(FpiUsbTransfer *transfer, FpDevice *device,
|
|
gpointer user_data, GError *error)
|
|
{
|
|
FpImageDevice *idev = FP_IMAGE_DEVICE(device);
|
|
FpiDeviceEtes603 *self = FPI_DEVICE_ETES603(idev);
|
|
|
|
if (error) {
|
|
fp_warn("transfer is not completed (result: %s)",
|
|
error->message);
|
|
fpi_ssm_mark_failed(transfer->ssm, error);
|
|
} else {
|
|
unsigned char endpoint = transfer->endpoint;
|
|
int actual_length = transfer->actual_length;
|
|
int length = transfer->length;
|
|
|
|
if (endpoint == EP_OUT) {
|
|
if (length != actual_length)
|
|
fp_warn("length %d != actual_length %d",
|
|
length, actual_length);
|
|
|
|
/* Chained with the answer */
|
|
async_tx(device, EP_IN, async_tx_cb, transfer->ssm);
|
|
} else if (endpoint == EP_IN) {
|
|
self->ans_len = actual_length;
|
|
fpi_ssm_next_state(transfer->ssm);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void m_exit_state(FpiSsm *ssm, FpDevice *dev, void *user_data)
|
|
{
|
|
FpiDeviceEtes603 *self = FPI_DEVICE_ETES603(dev);
|
|
|
|
switch (fpi_ssm_get_cur_state(ssm)) {
|
|
case EXIT_SET_REGS_REQ:
|
|
msg_set_regs(self, 4, REG_VCO_CONTROL, REG_VCO_IDLE,
|
|
REG_MODE_CONTROL, REG_MODE_SLEEP);
|
|
async_tx(dev, EP_OUT, async_tx_cb, ssm);
|
|
break;
|
|
case EXIT_SET_REGS_ANS:
|
|
if (msg_check_ok(self))
|
|
goto err;
|
|
fpi_ssm_mark_completed(ssm);
|
|
break;
|
|
default:
|
|
g_assert_not_reached();
|
|
break;
|
|
}
|
|
|
|
return;
|
|
err:
|
|
fpi_ssm_mark_failed(ssm, fpi_device_error_new (FP_DEVICE_ERROR_PROTO));
|
|
}
|
|
|
|
static void m_exit_complete(FpiSsm *ssm, FpDevice *dev, void *user_data,
|
|
GError *error)
|
|
{
|
|
FpImageDevice *idev = FP_IMAGE_DEVICE (dev);
|
|
|
|
if (error) {
|
|
fp_err("Error switching the device to idle state");
|
|
} else {
|
|
fp_dbg("The device is now in idle state");
|
|
}
|
|
fpi_image_device_deactivate_complete(idev, error);
|
|
fpi_ssm_free(ssm);
|
|
}
|
|
|
|
static void m_exit_start(FpImageDevice *idev)
|
|
{
|
|
FpiDeviceEtes603 *self = FPI_DEVICE_ETES603(idev);
|
|
FpiSsm *ssm = fpi_ssm_new(FP_DEVICE(idev), m_exit_state,
|
|
EXIT_NUM_STATES, idev);
|
|
|
|
self->is_active = FALSE;
|
|
fp_dbg("Switching device to idle mode");
|
|
fpi_ssm_start(ssm, m_exit_complete);
|
|
}
|
|
|
|
static void m_capture_state(FpiSsm *ssm, FpDevice *dev, void *user_data)
|
|
{
|
|
FpImageDevice *idev = FP_IMAGE_DEVICE (dev);
|
|
FpiDeviceEtes603 *self = FPI_DEVICE_ETES603(dev);
|
|
|
|
if (self->is_active == FALSE) {
|
|
fpi_ssm_mark_completed(ssm);
|
|
return;
|
|
}
|
|
|
|
switch (fpi_ssm_get_cur_state(ssm)) {
|
|
case CAP_FP_INIT_SET_REG10_REQ:
|
|
/* Reset fingerprint */
|
|
fp_dbg("Capturing a fingerprint...");
|
|
memset(self->fp, 0, FE_SIZE * 2);
|
|
self->fp_height = 0;
|
|
msg_set_regs(self, 2, REG_10, 0x92);
|
|
async_tx(dev, EP_OUT, async_tx_cb, ssm);
|
|
break;
|
|
case CAP_FP_INIT_SET_REG10_ANS:
|
|
if (msg_check_ok(self))
|
|
goto err;
|
|
fpi_ssm_next_state(ssm);
|
|
break;
|
|
case CAP_FP_INIT_SET_MODE_FP_REQ:
|
|
msg_set_mode_control(self, REG_MODE_FP);
|
|
async_tx(dev, EP_OUT, async_tx_cb, ssm);
|
|
break;
|
|
case CAP_FP_INIT_SET_MODE_FP_ANS:
|
|
if (msg_check_ok(self))
|
|
goto err;
|
|
fp_dbg("Capturing a 1st frame...");
|
|
fpi_ssm_next_state(ssm);
|
|
break;
|
|
case CAP_FP_GET_FP_REQ:
|
|
msg_get_fp(self, 0x01, 0xF4, 0x02, 0x01, 0x64);
|
|
async_tx(dev, EP_OUT, async_tx_cb, ssm);
|
|
break;
|
|
case CAP_FP_GET_FP_ANS:
|
|
memcpy(self->fp + self->fp_height * FE_WIDTH / 2, self->ans,
|
|
FE_SIZE);
|
|
self->fp_height += FE_HEIGHT;
|
|
if (self->fp_height <= FE_HEIGHT) {
|
|
/* 2 lines are at least removed each time */
|
|
self->fp_height -= 2;
|
|
fp_dbg("Capturing a 2nd frame...");
|
|
fpi_ssm_jump_to_state(ssm, CAP_FP_GET_FP_REQ);
|
|
} else {
|
|
FpImage *img;
|
|
unsigned int img_size;
|
|
/* Remove empty parts 2 times for the 2 frames */
|
|
process_removefpi_end(self);
|
|
process_removefpi_end(self);
|
|
img_size = self->fp_height * FE_WIDTH;
|
|
img = fp_image_new(FE_WIDTH, self->fp_height);
|
|
/* Images received are white on black, so invert it. */
|
|
/* TODO detect sweep direction */
|
|
img->flags = FPI_IMAGE_COLORS_INVERTED | FPI_IMAGE_V_FLIPPED;
|
|
img->height = self->fp_height;
|
|
process_4to8_bpp(self->fp, img_size / 2, img->data);
|
|
fp_dbg("Sending the raw fingerprint image (%dx%d)",
|
|
img->width, img->height);
|
|
fpi_image_device_image_captured(idev, img);
|
|
fpi_image_device_report_finger_status(idev, FALSE);
|
|
fpi_ssm_mark_completed(ssm);
|
|
}
|
|
break;
|
|
default:
|
|
g_assert_not_reached();
|
|
break;
|
|
}
|
|
|
|
return;
|
|
err:
|
|
fpi_ssm_mark_failed(ssm, fpi_device_error_new (FP_DEVICE_ERROR_PROTO));
|
|
}
|
|
|
|
static void m_capture_complete(FpiSsm *ssm, FpDevice *dev, void *user_data,
|
|
GError *error)
|
|
{
|
|
FpImageDevice *idev = FP_IMAGE_DEVICE (dev);
|
|
FpiDeviceEtes603 *self = FPI_DEVICE_ETES603(dev);
|
|
|
|
if (error) {
|
|
if (self->is_active) {
|
|
fp_err("Error while capturing fingerprint "
|
|
"(%s)", error->message);
|
|
fpi_image_device_session_error (idev, error);
|
|
} else {
|
|
g_error_free (error);
|
|
}
|
|
}
|
|
fpi_ssm_free(ssm);
|
|
|
|
if (self->is_active == TRUE) {
|
|
fp_dbg("Device is still active, restarting finger detection");
|
|
m_start_fingerdetect(idev);
|
|
} else {
|
|
fp_dbg("And it's over.");
|
|
m_exit_start(idev);
|
|
}
|
|
}
|
|
|
|
static void m_finger_state(FpiSsm *ssm, FpDevice *dev, void *user_data)
|
|
{
|
|
FpiDeviceEtes603 *self = FPI_DEVICE_ETES603(dev);
|
|
|
|
if (self->is_active == FALSE) {
|
|
fpi_ssm_mark_completed(ssm);
|
|
return;
|
|
}
|
|
|
|
switch (fpi_ssm_get_cur_state(ssm)) {
|
|
case FGR_FPA_INIT_SET_MODE_SLEEP_REQ:
|
|
msg_set_mode_control(self, REG_MODE_SLEEP);
|
|
async_tx(dev, EP_OUT, async_tx_cb, ssm);
|
|
break;
|
|
case FGR_FPA_INIT_SET_MODE_SLEEP_ANS:
|
|
if (msg_check_ok(self))
|
|
goto err;
|
|
fpi_ssm_next_state(ssm);
|
|
break;
|
|
case FGR_FPA_INIT_SET_DCOFFSET_REQ:
|
|
msg_set_regs(self, 2, REG_DCOFFSET, self->dcoffset);
|
|
async_tx(dev, EP_OUT, async_tx_cb, ssm);
|
|
break;
|
|
case FGR_FPA_INIT_SET_DCOFFSET_ANS:
|
|
if (msg_check_ok(self))
|
|
goto err;
|
|
fpi_ssm_next_state(ssm);
|
|
break;
|
|
case FGR_FPA_INIT_SET_GAINVRTVRB_REQ:
|
|
msg_set_regs(self, 6, REG_GAIN, self->gain, REG_VRT,
|
|
self->vrt,
|
|
REG_VRB, self->vrb);
|
|
async_tx(dev, EP_OUT, async_tx_cb, ssm);
|
|
break;
|
|
case FGR_FPA_INIT_SET_GAINVRTVRB_ANS:
|
|
if (msg_check_ok(self))
|
|
goto err;
|
|
fpi_ssm_next_state(ssm);
|
|
break;
|
|
case FGR_FPA_INIT_SET_VCO_CONTROL_RT_REQ:
|
|
msg_set_regs(self, 2, REG_VCO_CONTROL, REG_VCO_RT);
|
|
async_tx(dev, EP_OUT, async_tx_cb, ssm);
|
|
break;
|
|
case FGR_FPA_INIT_SET_VCO_CONTROL_RT_ANS:
|
|
if (msg_check_ok(self))
|
|
goto err;
|
|
fpi_ssm_next_state(ssm);
|
|
break;
|
|
case FGR_FPA_INIT_SET_REG04_REQ:
|
|
msg_set_regs(self, 2, REG_04, 0x00);
|
|
async_tx(dev, EP_OUT, async_tx_cb, ssm);
|
|
break;
|
|
case FGR_FPA_INIT_SET_REG04_ANS:
|
|
if (msg_check_ok(self))
|
|
goto err;
|
|
fpi_ssm_next_state(ssm);
|
|
break;
|
|
case FGR_FPA_INIT_SET_MODE_SENSOR_REQ:
|
|
msg_set_mode_control(self, REG_MODE_SENSOR);
|
|
async_tx(dev, EP_OUT, async_tx_cb, ssm);
|
|
break;
|
|
case FGR_FPA_INIT_SET_MODE_SENSOR_ANS:
|
|
if (msg_check_ok(self))
|
|
goto err;
|
|
fpi_ssm_next_state(ssm);
|
|
break;
|
|
case FGR_FPA_GET_FRAME_REQ:
|
|
msg_get_frame(self, 0x00, 0x00, 0x00, 0x00);
|
|
async_tx(dev, EP_OUT, async_tx_cb, ssm);
|
|
break;
|
|
case FGR_FPA_GET_FRAME_ANS:
|
|
if (process_frame_empty((guint8 *) self->ans, FRAME_SIZE)) {
|
|
fpi_ssm_jump_to_state(ssm, FGR_FPA_GET_FRAME_REQ);
|
|
} else {
|
|
fpi_image_device_report_finger_status(FP_IMAGE_DEVICE (dev), TRUE);
|
|
fpi_ssm_mark_completed(ssm);
|
|
}
|
|
break;
|
|
default:
|
|
g_assert_not_reached();
|
|
break;
|
|
}
|
|
|
|
return;
|
|
err:
|
|
fpi_ssm_mark_failed(ssm, fpi_device_error_new (FP_DEVICE_ERROR_PROTO));
|
|
}
|
|
|
|
static void m_finger_complete(FpiSsm *ssm, FpDevice *dev, void *user_data,
|
|
GError *error)
|
|
{
|
|
FpImageDevice *idev = FP_IMAGE_DEVICE (dev);
|
|
FpiDeviceEtes603 *self = FPI_DEVICE_ETES603(dev);
|
|
|
|
if (!error) {
|
|
FpiSsm *ssm_cap;
|
|
ssm_cap = fpi_ssm_new(dev, m_capture_state,
|
|
CAP_NUM_STATES, NULL);
|
|
fpi_ssm_start(ssm_cap, m_capture_complete);
|
|
} else {
|
|
if (self->is_active) {
|
|
fp_err("Error while capturing fingerprint "
|
|
"(%s)", error->message);
|
|
fpi_image_device_session_error(idev, error);
|
|
} else {
|
|
m_exit_start(idev);
|
|
g_error_free (error);
|
|
}
|
|
self->is_active = FALSE;
|
|
}
|
|
|
|
fpi_ssm_free(ssm);
|
|
}
|
|
|
|
static void m_start_fingerdetect(FpImageDevice *idev)
|
|
{
|
|
FpiSsm *ssmf;
|
|
ssmf = fpi_ssm_new(FP_DEVICE(idev), m_finger_state, FGR_NUM_STATES,
|
|
idev);
|
|
fpi_ssm_start(ssmf, m_finger_complete);
|
|
}
|
|
|
|
/*
|
|
* Tune value of VRT and VRB for contrast and brightness.
|
|
*/
|
|
static void m_tunevrb_state(FpiSsm *ssm, FpDevice *dev, void *user_data)
|
|
{
|
|
FpiDeviceEtes603 *self = FPI_DEVICE_ETES603(dev);
|
|
float hist[5];
|
|
|
|
if (self->is_active == FALSE) {
|
|
fpi_ssm_mark_completed(ssm);
|
|
return;
|
|
}
|
|
|
|
switch (fpi_ssm_get_cur_state(ssm)) {
|
|
case TUNEVRB_INIT:
|
|
fp_dbg("Tuning of VRT/VRB");
|
|
g_assert(self->dcoffset);
|
|
/* VRT(reg E1)=0x0A and VRB(reg E2)=0x10 are starting values */
|
|
self->vrt = 0x0A;
|
|
self->vrb = 0x10;
|
|
fpi_ssm_next_state(ssm);
|
|
break;
|
|
case TUNEVRB_GET_GAIN_REQ:
|
|
msg_get_regs(self, 1, REG_GAIN);
|
|
async_tx(dev, EP_OUT, async_tx_cb, ssm);
|
|
break;
|
|
case TUNEVRB_GET_GAIN_ANS:
|
|
if (msg_parse_regs(self))
|
|
goto err;
|
|
fpi_ssm_next_state(ssm);
|
|
break;
|
|
case TUNEVRB_GET_DCOFFSET_REQ:
|
|
msg_get_regs(self, 1, REG_DCOFFSET);
|
|
async_tx(dev, EP_OUT, async_tx_cb, ssm);
|
|
break;
|
|
case TUNEVRB_GET_DCOFFSET_ANS:
|
|
if (msg_parse_regs(self))
|
|
goto err;
|
|
fpi_ssm_next_state(ssm);
|
|
break;
|
|
case TUNEVRB_SET_DCOFFSET_REQ:
|
|
/* Reduce DCoffset by 1 to allow tuning */
|
|
msg_set_regs(self, 2, REG_DCOFFSET, self->dcoffset - 1);
|
|
async_tx(dev, EP_OUT, async_tx_cb, ssm);
|
|
break;
|
|
case TUNEVRB_SET_DCOFFSET_ANS:
|
|
if (msg_check_ok(self))
|
|
goto err;
|
|
fpi_ssm_next_state(ssm);
|
|
break;
|
|
case TUNEVRB_FRAME_REQ:
|
|
fp_dbg("Testing VRT=0x%02X VRB=0x%02X", self->vrt, self->vrb);
|
|
msg_get_frame(self, 0x01, self->gain, self->vrt, self->vrb);
|
|
async_tx(dev, EP_OUT, async_tx_cb, ssm);
|
|
break;
|
|
case TUNEVRB_FRAME_ANS:
|
|
process_hist((guint8 *) self->ans, FRAME_SIZE, hist);
|
|
/* Note that this tuning could probably be improved */
|
|
if (hist[0] + hist[1] > 0.95) {
|
|
if (self->vrt <= 0 || self->vrb <= 0) {
|
|
fp_dbg("Image is too dark, reducing DCOffset");
|
|
self->dcoffset--;
|
|
fpi_ssm_jump_to_state(ssm, TUNEVRB_INIT);
|
|
} else {
|
|
self->vrt--;
|
|
self->vrb--;
|
|
fpi_ssm_jump_to_state(ssm, TUNEVRB_FRAME_REQ);
|
|
}
|
|
break;
|
|
}
|
|
if (hist[4] > 0.95) {
|
|
fp_dbg("Image is too bright, increasing DCOffset");
|
|
self->dcoffset++;
|
|
fpi_ssm_jump_to_state(ssm, TUNEVRB_INIT);
|
|
break;
|
|
}
|
|
if (hist[4] + hist[3] > 0.4) {
|
|
if (self->vrt >= 2 * self->vrb - 0x0a) {
|
|
self->vrt++; self->vrb++;
|
|
} else {
|
|
self->vrt++;
|
|
}
|
|
/* Check maximum for vrt/vrb */
|
|
/* TODO if maximum is reached, leave with an error? */
|
|
if (self->vrt > VRT_MAX)
|
|
self->vrt = VRT_MAX;
|
|
if (self->vrb > VRB_MAX)
|
|
self->vrb = VRB_MAX;
|
|
fpi_ssm_jump_to_state(ssm, TUNEVRB_FRAME_REQ);
|
|
break;
|
|
}
|
|
fpi_ssm_next_state(ssm);
|
|
break;
|
|
case TUNEVRB_FINAL_SET_DCOFFSET_REQ:
|
|
fp_dbg("-> VRT=0x%02X VRB=0x%02X", self->vrt, self->vrb);
|
|
/* Reset the DCOffset */
|
|
msg_set_regs(self, 2, REG_DCOFFSET, self->dcoffset);
|
|
async_tx(dev, EP_OUT, async_tx_cb, ssm);
|
|
break;
|
|
case TUNEVRB_FINAL_SET_DCOFFSET_ANS:
|
|
if (msg_check_ok(self))
|
|
goto err;
|
|
fpi_ssm_next_state(ssm);
|
|
break;
|
|
case TUNEVRB_FINAL_SET_REG2627_REQ:
|
|
/* In traces, REG_26/REG_27 are set. purpose? values? */
|
|
msg_set_regs(self, 4, REG_26, 0x11, REG_27, 0x00);
|
|
async_tx(dev, EP_OUT, async_tx_cb, ssm);
|
|
break;
|
|
case TUNEVRB_FINAL_SET_REG2627_ANS:
|
|
if (msg_check_ok(self))
|
|
goto err;
|
|
fpi_ssm_next_state(ssm);
|
|
break;
|
|
case TUNEVRB_FINAL_SET_GAINVRTVRB_REQ:
|
|
/* Set Gain/VRT/VRB values found */
|
|
msg_set_regs(self, 6, REG_GAIN, self->gain, REG_VRT,
|
|
self->vrt,
|
|
REG_VRB, self->vrb);
|
|
async_tx(dev, EP_OUT, async_tx_cb, ssm);
|
|
break;
|
|
case TUNEVRB_FINAL_SET_GAINVRTVRB_ANS:
|
|
if (msg_check_ok(self))
|
|
goto err;
|
|
/* In traces, Gain/VRT/VRB are read again. */
|
|
fpi_ssm_next_state(ssm);
|
|
break;
|
|
case TUNEVRB_FINAL_SET_MODE_SLEEP_REQ:
|
|
msg_set_mode_control(self, REG_MODE_SLEEP);
|
|
async_tx(dev, EP_OUT, async_tx_cb, ssm);
|
|
break;
|
|
case TUNEVRB_FINAL_SET_MODE_SLEEP_ANS:
|
|
if (msg_check_ok(self))
|
|
goto err;
|
|
fpi_ssm_mark_completed(ssm);
|
|
break;
|
|
default:
|
|
g_assert_not_reached();
|
|
break;
|
|
}
|
|
|
|
return;
|
|
err:
|
|
fpi_ssm_mark_failed(ssm, fpi_device_error_new (FP_DEVICE_ERROR_PROTO));
|
|
}
|
|
|
|
static void m_tunevrb_complete(FpiSsm *ssm, FpDevice *dev, void *user_data,
|
|
GError *error)
|
|
{
|
|
FpiDeviceEtes603 *self = FPI_DEVICE_ETES603(dev);
|
|
FpImageDevice *idev = FP_IMAGE_DEVICE (dev);
|
|
|
|
fpi_image_device_activate_complete(idev, error);
|
|
if (!error) {
|
|
fp_dbg("Tuning is done. Starting finger detection.");
|
|
m_start_fingerdetect(idev);
|
|
}
|
|
|
|
if (!self->is_active)
|
|
m_exit_start(idev);
|
|
|
|
fpi_ssm_free(ssm);
|
|
}
|
|
|
|
/*
|
|
* This function tunes the DCoffset value and adjusts the gain value if
|
|
* required.
|
|
*/
|
|
static void m_tunedc_state(FpiSsm *ssm, FpDevice *dev, void *user_data)
|
|
{
|
|
FpiDeviceEtes603 *self = FPI_DEVICE_ETES603(dev);
|
|
|
|
if (self->is_active == FALSE) {
|
|
fpi_ssm_mark_completed(ssm);
|
|
return;
|
|
}
|
|
|
|
/* TODO To get better results, tuning could be done 3 times as in
|
|
* captured traffic to make sure that the value is correct. */
|
|
/* The default gain should work but it may reach a DCOffset limit so in
|
|
* this case we decrease the gain. */
|
|
switch (fpi_ssm_get_cur_state(ssm)) {
|
|
case TUNEDC_INIT:
|
|
/* reg_e0 = 0x23 is sensor normal/small gain */
|
|
self->gain = GAIN_SMALL_INIT;
|
|
self->tunedc_min = DCOFFSET_MIN;
|
|
self->tunedc_max = DCOFFSET_MAX;
|
|
fp_dbg("Tuning DCoffset");
|
|
fpi_ssm_next_state(ssm);
|
|
break;
|
|
case TUNEDC_SET_DCOFFSET_REQ:
|
|
/* Dichotomic search to find at which value the frame becomes
|
|
* almost black. */
|
|
self->dcoffset = (self->tunedc_max + self->tunedc_min) / 2;
|
|
fp_dbg("Testing DCoffset=0x%02X Gain=0x%02X", self->dcoffset,
|
|
self->gain);
|
|
msg_set_regs(self, 2, REG_DCOFFSET, self->dcoffset);
|
|
async_tx(dev, EP_OUT, async_tx_cb, ssm);
|
|
break;
|
|
case TUNEDC_SET_DCOFFSET_ANS:
|
|
if (msg_check_ok(self))
|
|
goto err;
|
|
fpi_ssm_next_state(ssm);
|
|
break;
|
|
case TUNEDC_GET_FRAME_REQ:
|
|
/* vrt:0x15 vrb:0x10 are constant in all tuning frames. */
|
|
msg_get_frame(self, 0x01, self->gain, 0x15, 0x10);
|
|
async_tx(dev, EP_OUT, async_tx_cb, ssm);
|
|
break;
|
|
case TUNEDC_GET_FRAME_ANS:
|
|
if (process_frame_empty((guint8 *) self->ans, FRAME_WIDTH))
|
|
self->tunedc_max = self->dcoffset;
|
|
else
|
|
self->tunedc_min = self->dcoffset;
|
|
if (self->tunedc_min + 1 < self->tunedc_max) {
|
|
fpi_ssm_jump_to_state(ssm, TUNEDC_SET_DCOFFSET_REQ);
|
|
} else if (self->tunedc_max < DCOFFSET_MAX) {
|
|
self->dcoffset = self->tunedc_max + 1;
|
|
fpi_ssm_next_state(ssm);
|
|
} else {
|
|
self->gain--;
|
|
fpi_ssm_jump_to_state(ssm, TUNEDC_SET_DCOFFSET_REQ);
|
|
}
|
|
break;
|
|
case TUNEDC_FINAL_SET_REG2122_REQ:
|
|
fp_dbg("-> DCoffset=0x%02X Gain=0x%02X", self->dcoffset,
|
|
self->gain);
|
|
/* ??? how reg21 / reg22 are calculated */
|
|
msg_set_regs(self, 4, REG_21, 0x23, REG_22, 0x21);
|
|
async_tx(dev, EP_OUT, async_tx_cb, ssm);
|
|
break;
|
|
case TUNEDC_FINAL_SET_REG2122_ANS:
|
|
if (msg_check_ok(self))
|
|
goto err;
|
|
fpi_ssm_next_state(ssm);
|
|
break;
|
|
case TUNEDC_FINAL_SET_GAIN_REQ:
|
|
msg_set_regs(self, 2, REG_GAIN, self->gain);
|
|
async_tx(dev, EP_OUT, async_tx_cb, ssm);
|
|
break;
|
|
case TUNEDC_FINAL_SET_GAIN_ANS:
|
|
fpi_ssm_next_state(ssm);
|
|
break;
|
|
case TUNEDC_FINAL_SET_DCOFFSET_REQ:
|
|
msg_set_regs(self, 2, REG_DCOFFSET, self->dcoffset);
|
|
async_tx(dev, EP_OUT, async_tx_cb, ssm);
|
|
break;
|
|
case TUNEDC_FINAL_SET_DCOFFSET_ANS:
|
|
/* In captured traffic, read GAIN, VRT, and VRB registers. */
|
|
if (msg_check_ok(self))
|
|
goto err;
|
|
fpi_ssm_mark_completed(ssm);
|
|
break;
|
|
default:
|
|
g_assert_not_reached();
|
|
break;
|
|
}
|
|
|
|
return;
|
|
err:
|
|
fpi_ssm_mark_failed(ssm, fpi_device_error_new (FP_DEVICE_ERROR_PROTO));
|
|
}
|
|
|
|
static void m_tunedc_complete(FpiSsm *ssm, FpDevice *dev, void *user_data,
|
|
GError *error)
|
|
{
|
|
FpiDeviceEtes603 *self = FPI_DEVICE_ETES603(dev);
|
|
FpImageDevice *idev = FP_IMAGE_DEVICE (dev);
|
|
|
|
if (!error) {
|
|
FpiSsm *ssm_tune;
|
|
ssm_tune = fpi_ssm_new(FP_DEVICE(idev), m_tunevrb_state,
|
|
TUNEVRB_NUM_STATES, idev);
|
|
fpi_ssm_start(ssm_tune, m_tunevrb_complete);
|
|
} else {
|
|
fp_err("Error while tuning DCOFFSET");
|
|
reset_param(FPI_DEVICE_ETES603 (dev));
|
|
fpi_image_device_session_error(idev, error);
|
|
}
|
|
|
|
if (!self->is_active)
|
|
m_exit_start(idev);
|
|
|
|
fpi_ssm_free(ssm);
|
|
}
|
|
|
|
static void m_init_state(FpiSsm *ssm, FpDevice *dev, void *user_data)
|
|
{
|
|
FpiDeviceEtes603 *self = FPI_DEVICE_ETES603(dev);
|
|
|
|
if (self->is_active == FALSE) {
|
|
fpi_ssm_mark_completed(ssm);
|
|
return;
|
|
}
|
|
|
|
switch (fpi_ssm_get_cur_state(ssm)) {
|
|
case INIT_CHECK_INFO_REQ:
|
|
msg_get_regs(self, 4, REG_INFO0, REG_INFO1, REG_INFO2,
|
|
REG_INFO3);
|
|
async_tx(dev, EP_OUT, async_tx_cb, ssm);
|
|
break;
|
|
case INIT_CHECK_INFO_ANS:
|
|
if (msg_parse_regs(self))
|
|
goto err;
|
|
if (check_info(self))
|
|
goto err;
|
|
fpi_ssm_next_state(ssm);
|
|
break;
|
|
case INIT_CMD20_REQ:
|
|
msg_get_cmd20(self);
|
|
async_tx(dev, EP_OUT, async_tx_cb, ssm);
|
|
break;
|
|
case INIT_CMD20_ANS:
|
|
if (msg_check_cmd20(self))
|
|
goto err;
|
|
fpi_ssm_next_state(ssm);
|
|
break;
|
|
case INIT_CMD25_REQ:
|
|
msg_get_cmd25(self);
|
|
async_tx(dev, EP_OUT, async_tx_cb, ssm);
|
|
break;
|
|
case INIT_CMD25_ANS:
|
|
if (msg_check_cmd25(self))
|
|
goto err;
|
|
fpi_ssm_next_state(ssm);
|
|
break;
|
|
case INIT_SENSOR_REQ:
|
|
/* In captured traffic, those are split. */
|
|
msg_set_regs(self, 18, REG_MODE_CONTROL, REG_MODE_SLEEP,
|
|
REG_50, 0x0F, REG_GAIN, 0x04, REG_VRT, 0x08,
|
|
REG_VRB, 0x0D, REG_VCO_CONTROL, REG_VCO_RT,
|
|
REG_DCOFFSET, 0x36, REG_F0, 0x00, REG_F2, 0x00);
|
|
async_tx(dev, EP_OUT, async_tx_cb, ssm);
|
|
break;
|
|
case INIT_SENSOR_ANS:
|
|
if (msg_check_ok(self))
|
|
goto err;
|
|
fpi_ssm_next_state(ssm);
|
|
break;
|
|
case INIT_ENC_REQ:
|
|
/* Initialize encryption registers without encryption. */
|
|
/* Set registers from 0x41 to 0x48 (0x8 regs) */
|
|
msg_set_regs(self, 16, REG_ENC1, 0x12, REG_ENC2, 0x34,
|
|
REG_ENC3, 0x56, REG_ENC4, 0x78, REG_ENC5, 0x90,
|
|
REG_ENC6, 0xAB, REG_ENC7, 0xCD, REG_ENC8, 0xEF);
|
|
async_tx(dev, EP_OUT, async_tx_cb, ssm);
|
|
break;
|
|
case INIT_ENC_ANS:
|
|
if (msg_check_ok(self))
|
|
goto err;
|
|
fpi_ssm_next_state(ssm);
|
|
break;
|
|
case INIT_REGS_REQ:
|
|
/* Set register from 0x20 to 0x37 (0x18 regs) */
|
|
msg_set_regs(self, 48,
|
|
REG_20, 0x00, REG_21, 0x23, REG_22, 0x21, REG_23,
|
|
0x20,
|
|
REG_24, 0x14, REG_25, 0x6A, REG_26, 0x00, REG_27,
|
|
0x00,
|
|
REG_28, 0x00, REG_29, 0xC0, REG_2A, 0x50, REG_2B,
|
|
0x50,
|
|
REG_2C, 0x4D, REG_2D, 0x03, REG_2E, 0x06, REG_2F,
|
|
0x06,
|
|
REG_30, 0x10, REG_31, 0x02, REG_32, 0x14, REG_33,
|
|
0x34,
|
|
REG_34, 0x01, REG_35, 0x08, REG_36, 0x03, REG_37,
|
|
0x21);
|
|
async_tx(dev, EP_OUT, async_tx_cb, ssm);
|
|
break;
|
|
case INIT_REGS_ANS:
|
|
if (msg_check_ok(self))
|
|
goto err;
|
|
fpi_ssm_mark_completed(ssm);
|
|
break;
|
|
default:
|
|
g_assert_not_reached();
|
|
break;
|
|
}
|
|
|
|
return;
|
|
err:
|
|
fpi_ssm_mark_failed(ssm, fpi_device_error_new (FP_DEVICE_ERROR_PROTO));
|
|
}
|
|
|
|
static void m_init_complete(FpiSsm *ssm, FpDevice *dev, void *user_data,
|
|
GError *error)
|
|
{
|
|
FpImageDevice *idev = FP_IMAGE_DEVICE (dev);
|
|
if (!error) {
|
|
FpiSsm *ssm_tune;
|
|
ssm_tune = fpi_ssm_new(FP_DEVICE(idev), m_tunedc_state,
|
|
TUNEDC_NUM_STATES, idev);
|
|
fpi_ssm_start(ssm_tune, m_tunedc_complete);
|
|
} else {
|
|
fp_err("Error initializing the device");
|
|
reset_param(FPI_DEVICE_ETES603 (dev));
|
|
fpi_image_device_session_error (idev, error);
|
|
}
|
|
fpi_ssm_free(ssm);
|
|
}
|
|
|
|
static void dev_activate(FpImageDevice *idev)
|
|
{
|
|
FpiDeviceEtes603 *self = FPI_DEVICE_ETES603(idev);
|
|
FpiSsm *ssm;
|
|
|
|
g_assert(self);
|
|
|
|
/* Reset info and data */
|
|
self->is_active = TRUE;
|
|
|
|
if (self->dcoffset == 0) {
|
|
fp_dbg("Tuning device...");
|
|
ssm = fpi_ssm_new(FP_DEVICE(idev), m_init_state,
|
|
INIT_NUM_STATES, idev);
|
|
fpi_ssm_start(ssm, m_init_complete);
|
|
} else {
|
|
fp_dbg("Using previous tuning (DCOFFSET=0x%02X,VRT=0x%02X,"
|
|
"VRB=0x%02X,GAIN=0x%02X).", self->dcoffset, self->vrt,
|
|
self->vrb, self->gain);
|
|
fpi_image_device_activate_complete(idev, NULL);
|
|
ssm = fpi_ssm_new(FP_DEVICE(idev), m_finger_state,
|
|
FGR_NUM_STATES, idev);
|
|
fpi_ssm_start(ssm, m_finger_complete);
|
|
}
|
|
}
|
|
|
|
static void dev_deactivate(FpImageDevice *idev)
|
|
{
|
|
FpiDeviceEtes603 *self = FPI_DEVICE_ETES603(idev);
|
|
|
|
fp_dbg("deactivating");
|
|
|
|
/* this can be called even if still activated. */
|
|
if (self->is_active == TRUE) {
|
|
self->is_active = FALSE;
|
|
} else {
|
|
m_exit_start(idev);
|
|
}
|
|
}
|
|
|
|
static void dev_open(FpImageDevice *idev)
|
|
{
|
|
GError *error = NULL;
|
|
FpiDeviceEtes603 *self = FPI_DEVICE_ETES603(idev);
|
|
|
|
if (!g_usb_device_claim_interface (fpi_device_get_usb_device(FP_DEVICE(idev)), 0, 0, &error)) {
|
|
fpi_image_device_open_complete(idev, error);
|
|
return;
|
|
}
|
|
|
|
self->req = g_malloc(sizeof(struct egis_msg));
|
|
self->ans = g_malloc(FE_SIZE);
|
|
self->fp = g_malloc(FE_SIZE * 4);
|
|
|
|
fpi_image_device_open_complete(idev, NULL);
|
|
}
|
|
|
|
static void dev_close(FpImageDevice *idev)
|
|
{
|
|
GError *error = NULL;
|
|
FpiDeviceEtes603 *self = FPI_DEVICE_ETES603(idev);
|
|
|
|
g_free(self->req);
|
|
g_free(self->ans);
|
|
g_free(self->fp);
|
|
|
|
g_usb_device_release_interface(fpi_device_get_usb_device(FP_DEVICE(idev)),
|
|
0, 0, &error);
|
|
fpi_image_device_close_complete(idev, error);
|
|
}
|
|
|
|
static const FpIdEntry id_table [ ] = {
|
|
/* EgisTec (aka Lightuning) ES603 */
|
|
{ .vid = 0x1c7a, .pid = 0x0603, },
|
|
{ .vid = 0, .pid = 0, .driver_data = 0 },
|
|
};
|
|
|
|
static void fpi_device_etes603_init(FpiDeviceEtes603 *self) {
|
|
}
|
|
static void fpi_device_etes603_class_init(FpiDeviceEtes603Class *klass) {
|
|
FpDeviceClass *dev_class = FP_DEVICE_CLASS(klass);
|
|
FpImageDeviceClass *img_class = FP_IMAGE_DEVICE_CLASS(klass);
|
|
|
|
dev_class->id = "etes603";
|
|
dev_class->full_name = "EgisTec ES603";
|
|
dev_class->type = FP_DEVICE_TYPE_USB;
|
|
dev_class->id_table = id_table;
|
|
dev_class->scan_type = FP_SCAN_TYPE_SWIPE;
|
|
|
|
img_class->img_open = dev_open;
|
|
img_class->img_close = dev_close;
|
|
img_class->activate = dev_activate;
|
|
img_class->deactivate = dev_deactivate;
|
|
|
|
img_class->img_width = 256;
|
|
img_class->img_height = -1;
|
|
}
|
|
|