Commit 15d32a73 authored by Igor Filatov's avatar Igor Filatov

Update Elan driver

parent 21504c06
Pipeline #1767 passed with stage
in 2 minutes and 35 seconds
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
* Elan driver for libfprint * Elan driver for libfprint
* *
* Copyright (C) 2017 Igor Filatov <ia.filatov@gmail.com> * Copyright (C) 2017 Igor Filatov <ia.filatov@gmail.com>
* Copyright (C) 2018 Sébastien Béchet <sebastien.bechet@osinix.com >
* *
* This library is free software; you can redistribute it and/or * This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public * modify it under the terms of the GNU Lesser General Public
...@@ -18,11 +19,36 @@ ...@@ -18,11 +19,36 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/ */
/*
* The algorithm which libfprint uses to match fingerprints doesn't like small
* images like the ones these drivers produce. There's just not enough minutiae
* (recognizable print-specific points) on them for a reliable match. This means
* that unless another matching algo is found/implemented, these readers will
* not work as good with libfprint as they do with vendor drivers.
*
* To get bigger images the driver expects you to swipe the finger over the
* reader. This works quite well for readers with a rectangular 144x64 sensor.
* Worse than real swipe readers but good enough for day-to-day use. It needs
* a steady and relatively slow swipe. There are also square 96x96 sensors and
* I don't know whether they are in fact usable or not because I don't have one.
* I imagine they'd be less reliable because the resulting image is even
* smaller. If they can't be made usable with libfprint, I might end up dropping
* them because it's better than saying they work when they don't.
*/
#define FP_COMPONENT "elan" #define FP_COMPONENT "elan"
#include "drivers_api.h" #include "drivers_api.h"
#include "elan.h" #include "elan.h"
#define dbg_buf(buf, len) \
if (len == 1) \
fp_dbg("%02hx", buf[0]); \
else if (len == 2) \
fp_dbg("%04hx", buf[0] << 8 | buf[1]); \
else if (len > 2) \
fp_dbg("%04hx... (%d bytes)", buf[0] << 8 | buf[1], len)
unsigned char elan_get_pixel(struct fpi_frame_asmbl_ctx *ctx, unsigned char elan_get_pixel(struct fpi_frame_asmbl_ctx *ctx,
struct fpi_frame *frame, unsigned int x, struct fpi_frame *frame, unsigned int x,
unsigned int y) unsigned int y)
...@@ -38,34 +64,49 @@ static struct fpi_frame_asmbl_ctx assembling_ctx = { ...@@ -38,34 +64,49 @@ static struct fpi_frame_asmbl_ctx assembling_ctx = {
}; };
struct elan_dev { struct elan_dev {
gboolean deactivating; /* device config */
unsigned short dev_type;
const struct elan_cmd *cmds; unsigned short fw_ver;
size_t cmds_len; void (*process_frame) (unsigned short *raw_frame, GSList ** frames);
int cmd_idx; /* end device config */
/* commands */
const struct elan_cmd *cmd;
int cmd_timeout; int cmd_timeout;
struct libusb_transfer *cur_transfer; struct libusb_transfer *cur_transfer;
/* end commands */
/* state */
enum fp_imgdev_state dev_state;
enum fp_imgdev_state dev_state_next;
unsigned char *last_read; unsigned char *last_read;
unsigned char calib_atts_left;
unsigned char calib_status;
unsigned short *background;
unsigned char frame_width; unsigned char frame_width;
unsigned char frame_height; unsigned char frame_height;
unsigned char raw_frame_width; unsigned char raw_frame_height;
int num_frames; int num_frames;
GSList *frames; GSList *frames;
/* end state */
}; };
int cmp_short(const void *a, const void *b)
{
return (int)(*(short *)a - *(short *)b);
}
static void elan_dev_reset(struct elan_dev *elandev) static void elan_dev_reset(struct elan_dev *elandev)
{ {
G_DEBUG_HERE(); G_DEBUG_HERE();
BUG_ON(elandev->cur_transfer); BUG_ON(elandev->cur_transfer);
elandev->deactivating = FALSE; elandev->cmd = NULL;
elandev->cmds = NULL;
elandev->cmd_idx = 0;
elandev->cmd_timeout = ELAN_CMD_TIMEOUT; elandev->cmd_timeout = ELAN_CMD_TIMEOUT;
elandev->calib_status = 0;
g_free(elandev->last_read); g_free(elandev->last_read);
elandev->last_read = NULL; elandev->last_read = NULL;
...@@ -74,44 +115,126 @@ static void elan_dev_reset(struct elan_dev *elandev) ...@@ -74,44 +115,126 @@ static void elan_dev_reset(struct elan_dev *elandev)
elandev->num_frames = 0; elandev->num_frames = 0;
} }
static void elan_save_frame(struct fp_img_dev *dev) static void elan_save_frame(struct elan_dev *elandev, unsigned short *frame)
{ {
struct elan_dev *elandev = fpi_imgdev_get_user_data(dev);
unsigned char raw_height = elandev->frame_width;
unsigned char raw_width = elandev->raw_frame_width;
unsigned short *frame =
g_malloc(elandev->frame_width * elandev->frame_height * 2);
G_DEBUG_HERE(); G_DEBUG_HERE();
/* Raw images are vertical and perpendicular to swipe direction of a /* so far 3 types of readers by sensor dimensions and orientation have been
* normalized image, which means we need to make them horizontal before * seen in the wild:
* assembling. We also discard stirpes of ELAN_FRAME_MARGIN along raw * 1. 144x64. Raw images are in portrait orientation while readers themselves
* height. */ * are placed (e.g. built into a touchpad) in landscape orientation. These
for (int y = 0; y < raw_height; y++) * need to be rotated before assembling.
for (int x = ELAN_FRAME_MARGIN; * 2. 96x96 rotated. Like the first type but square. Likewise, need to be
x < raw_width - ELAN_FRAME_MARGIN; x++) { * rotated before assembling.
int frame_idx = * 3. 96x96 normal. Square and need NOT be rotated. So far there's only been
y + (x - ELAN_FRAME_MARGIN) * raw_height; * 1 report of a 0c03 of this type. Hopefully this type can be identified
int raw_idx = x + y * raw_width; * by device id (and manufacturers don't just install the readers as they
* please).
* we also discard stripes of 'frame_margin' from bottom and top because
* assembling works bad for tall frames */
unsigned char frame_width = elandev->frame_width;
unsigned char frame_height = elandev->frame_height;
unsigned char raw_height = elandev->raw_frame_height;
unsigned char frame_margin = (raw_height - elandev->frame_height) / 2;
int frame_idx, raw_idx;
for (int y = 0; y < frame_height; y++)
for (int x = 0; x < frame_width; x++) {
if (elandev->dev_type & ELAN_NOT_ROTATED)
raw_idx = x + (y + frame_margin) * frame_width;
else
raw_idx = frame_margin + y + x * raw_height;
frame_idx = x + y * frame_width;
frame[frame_idx] = frame[frame_idx] =
((unsigned short *)elandev->last_read)[raw_idx]; ((unsigned short *)elandev->last_read)[raw_idx];
} }
}
static void elan_save_background(struct elan_dev *elandev)
{
G_DEBUG_HERE();
g_free(elandev->background);
elandev->background =
g_malloc(elandev->frame_width * elandev->frame_height *
sizeof(short));
elan_save_frame(elandev, elandev->background);
}
/* save a frame as part of the fingerprint image
* background needs to have been captured for this routine to work
* Elantech recommends 2-step non-linear normalization in order to reduce
* 2^14 ADC resolution to 2^8 image:
*
* 1. background is subtracted (done here)
*
* 2. pixels are grouped in 3 groups by intensity and each group is mapped
* separately onto the normalized frame (done in elan_process_frame_*)
* ==== 16383 ____> ======== 255
* /
* ----- lvl3 __/
* 35% pixels
*
* ----- lvl2 --------> ======== 156
*
* 30% pixels
* ----- lvl1 --------> ======== 99
*
* 35% pixels
* ----- lvl0 __
* \
* ======== 0 \____> ======== 0
*
* For some devices we don't do 2. but instead do a simple linear mapping
* because it seems to produce better results (or at least as good):
* ==== 16383 ___> ======== 255
* /
* ------ max __/
*
*
* ------ min __
* \
* ======== 0 \___> ======== 0
*/
static int elan_save_img_frame(struct elan_dev *elandev)
{
G_DEBUG_HERE();
unsigned int frame_size = elandev->frame_width * elandev->frame_height;
unsigned short *frame = g_malloc(frame_size * sizeof(short));
elan_save_frame(elandev, frame);
unsigned int sum = 0;
for (int i = 0; i < frame_size; i++) {
if (elandev->background[i] > frame[i])
frame[i] = 0;
else
frame[i] -= elandev->background[i];
sum += frame[i];
}
if (sum == 0) {
fp_dbg
("frame darker that background; finger present during calibration?");
return -1;
}
elandev->frames = g_slist_prepend(elandev->frames, frame); elandev->frames = g_slist_prepend(elandev->frames, frame);
elandev->num_frames += 1; elandev->num_frames += 1;
return 0;
} }
/* Transform raw sensor data to normalized 8-bit grayscale image. */ static void elan_process_frame_linear(unsigned short *raw_frame,
static void elan_process_frame(unsigned short *raw_frame, GSList ** frames) GSList ** frames)
{ {
G_DEBUG_HERE();
unsigned int frame_size = unsigned int frame_size =
assembling_ctx.frame_width * assembling_ctx.frame_height; assembling_ctx.frame_width * assembling_ctx.frame_height;
struct fpi_frame *frame = struct fpi_frame *frame =
g_malloc(frame_size + sizeof(struct fpi_frame)); g_malloc(frame_size + sizeof(struct fpi_frame));
G_DEBUG_HERE();
unsigned short min = 0xffff, max = 0; unsigned short min = 0xffff, max = 0;
for (int i = 0; i < frame_size; i++) { for (int i = 0; i < frame_size; i++) {
if (raw_frame[i] < min) if (raw_frame[i] < min)
...@@ -123,12 +246,42 @@ static void elan_process_frame(unsigned short *raw_frame, GSList ** frames) ...@@ -123,12 +246,42 @@ static void elan_process_frame(unsigned short *raw_frame, GSList ** frames)
unsigned short px; unsigned short px;
for (int i = 0; i < frame_size; i++) { for (int i = 0; i < frame_size; i++) {
px = raw_frame[i]; px = raw_frame[i];
if (px <= min) px = (px - min) * 0xff / (max - min);
px = 0; frame->data[i] = (unsigned char)px;
else if (px >= max) }
px = 0xff;
else *frames = g_slist_prepend(*frames, frame);
px = (px - min) * 0xff / (max - min); }
static void elan_process_frame_thirds(unsigned short *raw_frame,
GSList ** frames)
{
G_DEBUG_HERE();
unsigned int frame_size =
assembling_ctx.frame_width * assembling_ctx.frame_height;
struct fpi_frame *frame =
g_malloc(frame_size + sizeof(struct fpi_frame));
unsigned short lvl0, lvl1, lvl2, lvl3;
unsigned short *sorted = g_malloc(frame_size * sizeof(short));
memcpy(sorted, raw_frame, frame_size * sizeof(short));
qsort(sorted, frame_size, sizeof(short), cmp_short);
lvl0 = sorted[0];
lvl1 = sorted[frame_size * 3 / 10];
lvl2 = sorted[frame_size * 65 / 100];
lvl3 = sorted[frame_size - 1];
g_free(sorted);
unsigned short px;
for (int i = 0; i < frame_size; i++) {
px = raw_frame[i];
if (lvl0 <= px && px < lvl1)
px = (px - lvl0) * 99 / (lvl1 - lvl0);
else if (lvl1 <= px && px < lvl2)
px = 99 + ((px - lvl1) * 56 / (lvl2 - lvl1));
else // (lvl2 <= px && px <= lvl3)
px = 155 + ((px - lvl2) * 100 / (lvl3 - lvl2));
frame->data[i] = (unsigned char)px; frame->data[i] = (unsigned char)px;
} }
...@@ -145,15 +298,16 @@ static void elan_submit_image(struct fp_img_dev *dev) ...@@ -145,15 +298,16 @@ static void elan_submit_image(struct fp_img_dev *dev)
for (int i = 0; i < ELAN_SKIP_LAST_FRAMES; i++) for (int i = 0; i < ELAN_SKIP_LAST_FRAMES; i++)
elandev->frames = g_slist_next(elandev->frames); elandev->frames = g_slist_next(elandev->frames);
elandev->num_frames -= ELAN_SKIP_LAST_FRAMES;
assembling_ctx.frame_width = elandev->frame_width; assembling_ctx.frame_width = elandev->frame_width;
assembling_ctx.frame_height = elandev->frame_height; assembling_ctx.frame_height = elandev->frame_height;
assembling_ctx.image_width = elandev->frame_width * 3 / 2; assembling_ctx.image_width = elandev->frame_width * 3 / 2;
g_slist_foreach(elandev->frames, (GFunc) elan_process_frame, &frames); g_slist_foreach(elandev->frames, (GFunc) elandev->process_frame,
&frames);
fpi_do_movement_estimation(&assembling_ctx, frames, fpi_do_movement_estimation(&assembling_ctx, frames,
elandev->num_frames - ELAN_SKIP_LAST_FRAMES); elandev->num_frames);
img = fpi_assemble_frames(&assembling_ctx, frames, img = fpi_assemble_frames(&assembling_ctx, frames, elandev->num_frames);
elandev->num_frames - ELAN_SKIP_LAST_FRAMES);
img->flags |= FP_IMG_PARTIAL; img->flags |= FP_IMG_PARTIAL;
fpi_imgdev_image_captured(dev, img); fpi_imgdev_image_captured(dev, img);
...@@ -161,16 +315,8 @@ static void elan_submit_image(struct fp_img_dev *dev) ...@@ -161,16 +315,8 @@ static void elan_submit_image(struct fp_img_dev *dev)
static void elan_cmd_done(struct fpi_ssm *ssm) static void elan_cmd_done(struct fpi_ssm *ssm)
{ {
struct fp_img_dev *dev = fpi_ssm_get_user_data(ssm);
struct elan_dev *elandev = fpi_imgdev_get_user_data(dev);
G_DEBUG_HERE(); G_DEBUG_HERE();
fpi_ssm_next_state(ssm);
elandev->cmd_idx += 1;
if (elandev->cmd_idx < elandev->cmds_len)
elan_run_next_cmd(ssm);
else
fpi_ssm_next_state(ssm);
} }
static void elan_cmd_cb(struct libusb_transfer *transfer) static void elan_cmd_cb(struct libusb_transfer *transfer)
...@@ -179,34 +325,32 @@ static void elan_cmd_cb(struct libusb_transfer *transfer) ...@@ -179,34 +325,32 @@ static void elan_cmd_cb(struct libusb_transfer *transfer)
struct fp_img_dev *dev = fpi_ssm_get_user_data(ssm); struct fp_img_dev *dev = fpi_ssm_get_user_data(ssm);
struct elan_dev *elandev = fpi_imgdev_get_user_data(dev); struct elan_dev *elandev = fpi_imgdev_get_user_data(dev);
G_DEBUG_HERE();
elandev->cur_transfer = NULL; elandev->cur_transfer = NULL;
switch (transfer->status) { switch (transfer->status) {
case LIBUSB_TRANSFER_COMPLETED: case LIBUSB_TRANSFER_COMPLETED:
if (transfer->length != transfer->actual_length) { if (transfer->length != transfer->actual_length) {
fp_dbg("unexpected transfer length"); fp_dbg("transfer length error: expected %d, got %d",
transfer->length, transfer->actual_length);
elan_dev_reset(elandev); elan_dev_reset(elandev);
fpi_ssm_mark_aborted(ssm, -EPROTO); fpi_ssm_mark_aborted(ssm, -EPROTO);
} else if (transfer->endpoint & LIBUSB_ENDPOINT_IN) } else if (transfer->endpoint & LIBUSB_ENDPOINT_IN) {
/* just finished receiving */ /* just finished receiving */
dbg_buf(elandev->last_read, transfer->actual_length);
elan_cmd_done(ssm); elan_cmd_done(ssm);
else { } else {
/* just finished sending */ /* just finished sending */
if (elandev->cmds[elandev->cmd_idx].response_len) G_DEBUG_HERE();
elan_cmd_read(ssm); elan_cmd_read(ssm);
else
elan_cmd_done(ssm);
} }
break; break;
case LIBUSB_TRANSFER_CANCELLED: case LIBUSB_TRANSFER_CANCELLED:
fp_dbg("transfer cancelled"); fp_dbg("transfer cancelled");
fpi_ssm_mark_aborted(ssm, -ECANCELED); fpi_ssm_mark_aborted(ssm, -ECANCELED);
elan_deactivate(dev);
break; break;
case LIBUSB_TRANSFER_TIMED_OUT: case LIBUSB_TRANSFER_TIMED_OUT:
fp_dbg("transfer timed out"); fp_dbg("transfer timed out");
// elan_dev_reset(elandev);
fpi_ssm_mark_aborted(ssm, -ETIMEDOUT); fpi_ssm_mark_aborted(ssm, -ETIMEDOUT);
break; break;
default: default:
...@@ -218,16 +362,22 @@ static void elan_cmd_cb(struct libusb_transfer *transfer) ...@@ -218,16 +362,22 @@ static void elan_cmd_cb(struct libusb_transfer *transfer)
static void elan_cmd_read(struct fpi_ssm *ssm) static void elan_cmd_read(struct fpi_ssm *ssm)
{ {
G_DEBUG_HERE();
struct fp_img_dev *dev = fpi_ssm_get_user_data(ssm); struct fp_img_dev *dev = fpi_ssm_get_user_data(ssm);
struct elan_dev *elandev = fpi_imgdev_get_user_data(dev); struct elan_dev *elandev = fpi_imgdev_get_user_data(dev);
int response_len = elandev->cmds[elandev->cmd_idx].response_len; int response_len = elandev->cmd->response_len;
G_DEBUG_HERE(); if (elandev->cmd->response_len == ELAN_CMD_SKIP_READ) {
fp_dbg("skipping read, not expecting anything");
elan_cmd_done(ssm);
return;
}
if (elandev->cmds[elandev->cmd_idx].cmd == read_cmds[0].cmd) if (elandev->cmd->cmd == get_image_cmd.cmd)
/* raw data has 2-byte "pixels" and the frame is vertical */ /* raw data has 2-byte "pixels" and the frame is vertical */
response_len = response_len =
elandev->raw_frame_width * elandev->frame_width * 2; elandev->raw_frame_height * elandev->frame_width * 2;
struct libusb_transfer *transfer = libusb_alloc_transfer(0); struct libusb_transfer *transfer = libusb_alloc_transfer(0);
if (!transfer) { if (!transfer) {
...@@ -240,21 +390,32 @@ static void elan_cmd_read(struct fpi_ssm *ssm) ...@@ -240,21 +390,32 @@ static void elan_cmd_read(struct fpi_ssm *ssm)
elandev->last_read = g_malloc(response_len); elandev->last_read = g_malloc(response_len);
libusb_fill_bulk_transfer(transfer, fpi_imgdev_get_usb_dev(dev), libusb_fill_bulk_transfer(transfer, fpi_imgdev_get_usb_dev(dev),
elandev->cmds[elandev->cmd_idx].response_in, elandev->cmd->response_in, elandev->last_read,
elandev->last_read, response_len, elan_cmd_cb, response_len, elan_cmd_cb, ssm,
ssm, elandev->cmd_timeout); elandev->cmd_timeout);
transfer->flags = LIBUSB_TRANSFER_FREE_TRANSFER; transfer->flags = LIBUSB_TRANSFER_FREE_TRANSFER;
int r = libusb_submit_transfer(transfer); int r = libusb_submit_transfer(transfer);
if (r < 0) if (r < 0)
fpi_ssm_mark_aborted(ssm, r); fpi_ssm_mark_aborted(ssm, r);
} }
static void elan_run_next_cmd(struct fpi_ssm *ssm) static void elan_run_cmd(struct fpi_ssm *ssm, const struct elan_cmd *cmd,
int cmd_timeout)
{ {
dbg_buf(cmd->cmd, 2);
struct fp_img_dev *dev = fpi_ssm_get_user_data(ssm); struct fp_img_dev *dev = fpi_ssm_get_user_data(ssm);
struct elan_dev *elandev = fpi_imgdev_get_user_data(dev); struct elan_dev *elandev = fpi_imgdev_get_user_data(dev);
G_DEBUG_HERE(); elandev->cmd = cmd;
if (cmd_timeout != -1)
elandev->cmd_timeout = cmd_timeout;
if (cmd->devices != ELAN_ALL_DEV && !(cmd->devices & elandev->dev_type)) {
fp_dbg("skipping for this device");
elan_cmd_done(ssm);
return;
}
struct libusb_transfer *transfer = libusb_alloc_transfer(0); struct libusb_transfer *transfer = libusb_alloc_transfer(0);
if (!transfer) { if (!transfer) {
...@@ -263,32 +424,13 @@ static void elan_run_next_cmd(struct fpi_ssm *ssm) ...@@ -263,32 +424,13 @@ static void elan_run_next_cmd(struct fpi_ssm *ssm)
} }
elandev->cur_transfer = transfer; elandev->cur_transfer = transfer;
libusb_fill_bulk_transfer(transfer, fpi_imgdev_get_usb_dev(dev), ELAN_EP_CMD_OUT, libusb_fill_bulk_transfer(transfer, fpi_imgdev_get_usb_dev(dev),
(unsigned char *)elandev->cmds[elandev-> ELAN_EP_CMD_OUT, cmd->cmd, ELAN_CMD_LEN,
cmd_idx].cmd, elan_cmd_cb, ssm, elandev->cmd_timeout);
ELAN_CMD_LEN, elan_cmd_cb, ssm,
elandev->cmd_timeout);
transfer->flags = LIBUSB_TRANSFER_FREE_TRANSFER; transfer->flags = LIBUSB_TRANSFER_FREE_TRANSFER;
int r = libusb_submit_transfer(transfer); int r = libusb_submit_transfer(transfer);
if (r < 0) if (r < 0)
fpi_ssm_mark_aborted(ssm, r); fpi_ssm_mark_aborted(ssm, r);
}
static void elan_run_cmds(struct fpi_ssm *ssm, const struct elan_cmd *cmds,
size_t cmds_len, int cmd_timeout)
{
struct fp_img_dev *dev = fpi_ssm_get_user_data(ssm);
struct elan_dev *elandev = fpi_imgdev_get_user_data(dev);
G_DEBUG_HERE();
elandev->cmds = cmds;
elandev->cmds_len = cmds_len;
elandev->cmd_idx = 0;
if (cmd_timeout != -1)
elandev->cmd_timeout = cmd_timeout;
elan_run_next_cmd(ssm);
} }
enum deactivate_states { enum deactivate_states {
...@@ -296,18 +438,21 @@ enum deactivate_states { ...@@ -296,18 +438,21 @@ enum deactivate_states {
DEACTIVATE_NUM_STATES, DEACTIVATE_NUM_STATES,
}; };
static void elan_deactivate_run_state(struct fpi_ssm *ssm) static void deactivate_run_state(struct fpi_ssm *ssm)
{ {
G_DEBUG_HERE();
switch (fpi_ssm_get_cur_state(ssm)) { switch (fpi_ssm_get_cur_state(ssm)) {
case DEACTIVATE: case DEACTIVATE:
elan_run_cmds(ssm, deactivate_cmds, deactivate_cmds_len, elan_run_cmd(ssm, &stop_cmd, ELAN_CMD_TIMEOUT);
ELAN_CMD_TIMEOUT);
break; break;
} }
} }
static void deactivate_complete(struct fpi_ssm *ssm) static void deactivate_complete(struct fpi_ssm *ssm)
{ {
G_DEBUG_HERE();
struct fp_img_dev *dev = fpi_ssm_get_user_data(ssm); struct fp_img_dev *dev = fpi_ssm_get_user_data(ssm);
fpi_imgdev_deactivate_complete(dev); fpi_imgdev_deactivate_complete(dev);
...@@ -315,255 +460,346 @@ static void deactivate_complete(struct fpi_ssm *ssm) ...@@ -315,255 +460,346 @@ static void deactivate_complete(struct fpi_ssm *ssm)
static void elan_deactivate(struct fp_img_dev *dev) static void elan_deactivate(struct fp_img_dev *dev)
{ {
struct elan_dev *elandev = fpi_imgdev_get_user_data(dev);
G_DEBUG_HERE(); G_DEBUG_HERE();
struct elan_dev *elandev = fpi_imgdev_get_user_data(dev);
elan_dev_reset(elandev); elan_dev_reset(elandev);
struct fpi_ssm *ssm = fpi_ssm_new(fpi_imgdev_get_dev(dev), elan_deactivate_run_state, struct fpi_ssm *ssm =
DEACTIVATE_NUM_STATES); fpi_ssm_new(fpi_imgdev_get_dev(dev), deactivate_run_state,
DEACTIVATE_NUM_STATES);
fpi_ssm_set_user_data(ssm, dev); fpi_ssm_set_user_data(ssm, dev);
fpi_ssm_start(ssm, deactivate_complete); fpi_ssm_start(ssm, deactivate_complete);
} }
enum capture_states { enum capture_states {
CAPTURE_START, CAPTURE_LED_ON,
CAPTURE_WAIT_FINGER, CAPTURE_WAIT_FINGER,
CAPTURE_READ_DATA, CAPTURE_READ_DATA,
CAPTURE_SAVE_FRAME, CAPTURE_CHECK_ENOUGH_FRAMES,
CAPTURE_NUM_STATES, CAPTURE_NUM_STATES,
}; };
static void elan_capture_run_state(struct fpi_ssm *ssm) static void capture_run_state(struct fpi_ssm *ssm)
{ {
struct fp_img_dev *dev = fpi_ssm_get_user_data(ssm); struct fp_img_dev *dev = fpi_ssm_get_user_data(ssm);
struct elan_dev *elandev = fpi_imgdev_get_user_data(dev); struct elan_dev *elandev = fpi_imgdev_get_user_data(dev);
int r;
switch (fpi_ssm_get_cur_state(ssm)) { switch (fpi_ssm_get_cur_state(ssm)) {
case CAPTURE_START: case CAPTURE_LED_ON:
elan_run_cmds(ssm, capture_start_cmds, capture_start_cmds_len, elan_run_cmd(ssm, &led_on_cmd, ELAN_CMD_TIMEOUT);
ELAN_CMD_TIMEOUT);
break; break;
case CAPTURE_WAIT_FINGER: case CAPTURE_WAIT_FINGER:
elan_run_cmds(ssm, capture_wait_finger_cmds, elan_run_cmd(ssm, &pre_scan_cmd, -1);
capture_wait_finger_cmds_len, -1);
break; break;
case CAPTURE_READ_DATA: case CAPTURE_READ_DATA:
/* 0x55 - finger present /* 0x55 - finger present
* 0xff - device not calibrated */ * 0xff - device not calibrated (probably) */
if (elandev->last_read && elandev->last_read[0] == 0x55) { if (elandev->last_read && elandev->last_read[0] == 0x55) {
fpi_imgdev_report_finger_status(dev, TRUE); if (elandev->dev_state == IMGDEV_STATE_AWAIT_FINGER_ON)
elan_run_cmds(ssm, read_cmds, read_cmds_len, fpi_imgdev_report_finger_status(dev, TRUE);
ELAN_CMD_TIMEOUT); elan_run_cmd(ssm, &get_image_cmd, ELAN_CMD_TIMEOUT);
} else } else
fpi_ssm_mark_aborted(ssm, FP_VERIFY_RETRY); fpi_ssm_mark_aborted(ssm, -EBADMSG);
break; break;
case CAPTURE_SAVE_FRAME: case CAPTURE_CHECK_ENOUGH_FRAMES:
elan_save_frame(dev); r = elan_save_img_frame(elandev);
if (elandev->num_frames < ELAN_MAX_FRAMES) { if (r < 0)
fpi_ssm_mark_aborted(ssm, r);
else if (elandev->num_frames < ELAN_MAX_FRAMES) {
/* quickly stop if finger is removed */ /* quickly stop if finger is removed */
elandev->cmd_timeout = ELAN_FINGER_TIMEOUT; elandev->cmd_timeout = ELAN_FINGER_TIMEOUT;
fpi_ssm_jump_to_state(ssm, CAPTURE_WAIT_FINGER); fpi_ssm_jump_to_state(ssm, CAPTURE_WAIT_FINGER);
} else {
fpi_ssm_next_state(ssm);
} }
break; break;
} }
} }
static void elan_capture_async(void *data)
{
elan_capture((struct fp_img_dev *)data);
}
static void capture_complete(struct fpi_ssm *ssm) static void capture_complete(struct fpi_ssm *ssm)