libfprint/libfprint/drivers/elanspi.c
Matthew Mirvish 8be666bb05 elanspi: Permit running in emulated environment
This removes the HID reset, which we cannot emulate currently and also
disabes the line timeout to as simulation might run too slowly at times.
2021-06-23 20:42:52 +00:00

1701 lines
53 KiB
C

/*
* Elan SPI driver for libfprint
*
* Copyright (C) 2021 Matthew Mirvish <matthew@mm12.xyz>
*
* 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
*/
#define FP_COMPONENT "elanspi"
#include "drivers_api.h"
#include "elanspi.h"
#include <linux/hidraw.h>
#include <sys/ioctl.h>
#include <sys/fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <linux/types.h>
#include <errno.h>
struct _FpiDeviceElanSpi
{
FpImageDevice parent;
/* sensor info */
guint8 sensor_width, sensor_height, sensor_ic_version, sensor_id;
gboolean sensor_otp;
guint8 sensor_vcm_mode;
/* processed frame info */
guint8 frame_width, frame_height;
/* init info */
guint8 sensor_raw_version, sensor_reg_17;
guint8 sensor_reg_vref1, sensor_reg_28, sensor_reg_27, sensor_reg_dac2;
/* calibration info */
union
{
struct
{
guint8 dac_value;
guint8 line_ptr;
guint8 dacfine_retry;
gint64 otp_timeout;
} old_data;
struct
{
guint16 gdac_value;
guint16 gdac_step;
guint16 best_gdac;
guint16 best_meandiff;
} hv_data;
};
/* generic temp info for async reading */
guint8 sensor_status;
gint64 capture_timeout;
/* background / calibration parameters */
guint16 *bg_image;
guint16 *last_image;
guint16 *prev_frame_image;
gint fp_empty_counter;
GSList *fp_frame_list;
/* wait ctx */
gint finger_wait_debounce;
gboolean deactivating, capturing;
/* active SPI status info */
int spi_fd;
};
G_DECLARE_FINAL_TYPE (FpiDeviceElanSpi, fpi_device_elanspi, FPI, DEVICE_ELANSPI, FpImageDevice);
G_DEFINE_TYPE (FpiDeviceElanSpi, fpi_device_elanspi, FP_TYPE_IMAGE_DEVICE);
static void
elanspi_do_hwreset (FpiDeviceElanSpi *self, GError **err)
{
/* Skip in emulation mode, since we don't mock hid devices */
if (g_strcmp0 (g_getenv ("FP_DEVICE_EMULATION"), "1") == 0)
return;
/*
* TODO: Make this also work with the non-HID cases
*/
int fd = open ((char *) fpi_device_get_udev_data (FP_DEVICE (self), FPI_DEVICE_UDEV_SUBTYPE_HIDRAW), O_RDWR);
if (fd < 0)
{
g_set_error (err, G_IO_ERROR, g_io_error_from_errno (errno), "unable to open hid");
return;
}
guint8 buf[5] = {
0xe, 0, 0, 0, 0
};
if (ioctl (fd, HIDIOCSFEATURE (5), &buf) != 5)
{
g_set_error (err, G_IO_ERROR, g_io_error_from_errno (errno), "unable to reset via hid");
goto out;
}
out:
close (fd);
}
/*
* Three main processes involved in driving these sensors:
* - initialization (device type detection)
* - calibration
* - image capture (single)
* - image capture (stitched)
*/
enum elanspi_init_state {
ELANSPI_INIT_READ_STATUS1,
ELANSPI_INIT_HWSWRESET, /* fused b.c. hw reset is currently sync */
ELANSPI_INIT_SWRESETDELAY1,
ELANSPI_INIT_READ_HEIGHT,
ELANSPI_INIT_READ_WIDTH,
ELANSPI_INIT_READ_REG17, /* both of these states finish setting up sensor settings */
ELANSPI_INIT_READ_VERSION, /* can jump straight to calibrate */
ELANSPI_INIT_SWRESET2,
ELANSPI_INIT_SWRESETDELAY2,
ELANSPI_INIT_OTP_READ_VREF1,
ELANSPI_INIT_OTP_WRITE_VREF1,
ELANSPI_INIT_OTP_WRITE_0x28,
ELANSPI_INIT_OTP_LOOP_READ_0x28, /* may loop */
ELANSPI_INIT_OTP_LOOP_READ_0x27,
ELANSPI_INIT_OTP_LOOP_UPDATEDAC_READ_DAC2,
ELANSPI_INIT_OTP_LOOP_UPDATEDAC_WRITE_DAC2,
ELANSPI_INIT_OTP_LOOP_UPDATEDAC_WRITE_10,
/* exit loop */
ELANSPI_INIT_OTP_WRITE_0xb,
ELANSPI_INIT_OTP_WRITE_0xc,
/* do calibration (mutexc) */
ELANSPI_INIT_CALIBRATE,
ELANSPI_INIT_BG_CAPTURE,
ELANSPI_INIT_BG_SAVE,
ELANSPI_INIT_NSTATES
};
enum elanspi_calibrate_old_state {
ELANSPI_CALIBOLD_UNPROTECT,
ELANSPI_CALIBOLD_WRITE_STARTCALIB,
ELANSPI_CALIBOLD_STARTCALIBDELAY,
ELANSPI_CALIBOLD_SEND_REGTABLE,
/* calibrate dac base value */
ELANSPI_CALIBOLD_DACBASE_CAPTURE,
ELANSPI_CALIBOLD_DACBASE_WRITE_DAC1,
/* check for finger */
ELANSPI_CALIBOLD_CHECKFIN_CAPTURE,
/* increase gain */
ELANSPI_CALIBOLD_WRITE_GAIN,
/* calibrate dac stage2 */
ELANSPI_CALIBOLD_DACFINE_CAPTURE,
ELANSPI_CALIBOLD_DACFINE_WRITE_DAC1,
ELANSPI_CALIBOLD_DACFINE_LOOP,
/* exit ok (cleanup by protecting) */
ELANSPI_CALIBOLD_PROTECT,
ELANSPI_CALIBOLD_NSTATES
};
enum elanspi_capture_old_state {
ELANSPI_CAPTOLD_WRITE_CAPTURE,
ELANSPI_CAPTOLD_CHECK_LINEREADY,
ELANSPI_CAPTOLD_RECV_LINE,
ELANSPI_CAPTOLD_NSTATES
};
enum elanspi_calibrate_hv_state {
ELANSPI_CALIBHV_SELECT_PAGE0_0,
ELANSPI_CALIBHV_WRITE_STARTCALIB,
ELANSPI_CALIBHV_UNPROTECT,
ELANSPI_CALIBHV_SEND_REGTABLE0,
ELANSPI_CALIBHV_SELECT_PAGE1,
ELANSPI_CALIBHV_SEND_REGTABLE1,
ELANSPI_CALIBHV_SELECT_PAGE0_1,
ELANSPI_CALIBHV_WRITE_GDAC_H,
ELANSPI_CALIBHV_WRITE_GDAC_L,
ELANSPI_CALIBHV_CAPTURE,
ELANSPI_CALIBHV_PROCESS,
ELANSPI_CALIBHV_WRITE_BEST_GDAC_H,
ELANSPI_CALIBHV_WRITE_BEST_GDAC_L,
/* cleanup by protecting */
ELANSPI_CALIBHV_PROTECT,
ELANSPI_CALIBHV_NSTATES
};
enum elanspi_capture_hv_state {
ELANSPI_CAPTHV_WRITE_CAPTURE,
ELANSPI_CAPTHV_CHECK_READY,
ELANSPI_CAPTHV_RECV_IMAGE,
ELANSPI_CAPTHV_NSTATES
};
enum elanspi_write_regtable_state {
ELANSPI_WRTABLE_WRITE,
ELANSPI_WRTABLE_ITERATE,
ELANSPI_WRTABLE_NSTATES
};
enum elanspi_fp_capture_state {
ELANSPI_FPCAPT_INIT,
/* wait for finger */
ELANSPI_FPCAPT_WAITDOWN_CAPTURE,
ELANSPI_FPCAPT_WAITDOWN_PROCESS,
/* capture full image */
ELANSPI_FPCAPT_FP_CAPTURE,
ELANSPI_FPCAPT_FP_PROCESS,
/* wait for no finger */
ELANSPI_FPCAPT_WAITUP_CAPTURE,
ELANSPI_FPCAPT_WAITUP_PROCESS,
ELANSPI_FPCAPT_NSTATES
};
/* helpers */
static FpiSpiTransfer *
elanspi_do_swreset (FpiDeviceElanSpi *self)
{
FpiSpiTransfer * xfer = fpi_spi_transfer_new (FP_DEVICE (self), self->spi_fd);
fpi_spi_transfer_write (xfer, 1);
xfer->buffer_wr[0] = 0x31;
return xfer;
}
static FpiSpiTransfer *
elanspi_do_startcalib (FpiDeviceElanSpi *self)
{
FpiSpiTransfer * xfer = fpi_spi_transfer_new (FP_DEVICE (self), self->spi_fd);
fpi_spi_transfer_write (xfer, 1);
xfer->buffer_wr[0] = 0x4;
return xfer;
}
static FpiSpiTransfer *
elanspi_do_capture (FpiDeviceElanSpi *self)
{
FpiSpiTransfer * xfer = fpi_spi_transfer_new (FP_DEVICE (self), self->spi_fd);
fpi_spi_transfer_write (xfer, 1);
xfer->buffer_wr[0] = 0x1;
return xfer;
}
static FpiSpiTransfer *
elanspi_do_selectpage (FpiDeviceElanSpi *self, guint8 page)
{
FpiSpiTransfer * xfer = fpi_spi_transfer_new (FP_DEVICE (self), self->spi_fd);
fpi_spi_transfer_write (xfer, 2);
xfer->buffer_wr[0] = 0x7;
xfer->buffer_wr[1] = page;
return xfer;
}
static FpiSpiTransfer *
elanspi_single_read_cmd (FpiDeviceElanSpi *self, guint8 cmd_id, guint8 *data_out)
{
FpiSpiTransfer * xfer = fpi_spi_transfer_new (FP_DEVICE (self), self->spi_fd);
fpi_spi_transfer_write (xfer, 2);
xfer->buffer_wr[0] = cmd_id;
xfer->buffer_wr[1] = 0xff;
fpi_spi_transfer_read_full (xfer, data_out, 1, NULL);
return xfer;
}
static FpiSpiTransfer *
elanspi_read_status (FpiDeviceElanSpi *self, guint8 *data_out)
{
return elanspi_single_read_cmd (self, 0x3, data_out);
}
static FpiSpiTransfer *
elanspi_read_width (FpiDeviceElanSpi *self, guint8 *data_out)
{
return elanspi_single_read_cmd (self, 0x9, data_out);
}
static FpiSpiTransfer *
elanspi_read_height (FpiDeviceElanSpi *self, guint8 *data_out)
{
return elanspi_single_read_cmd (self, 0x8, data_out);
}
static FpiSpiTransfer *
elanspi_read_version (FpiDeviceElanSpi *self, guint8 *data_out)
{
return elanspi_single_read_cmd (self, 0xa, data_out);
}
static FpiSpiTransfer *
elanspi_read_register (FpiDeviceElanSpi *self, guint8 reg_id, guint8 *data_out)
{
FpiSpiTransfer * xfer = fpi_spi_transfer_new (FP_DEVICE (self), self->spi_fd);
fpi_spi_transfer_write (xfer, 1);
xfer->buffer_wr[0] = reg_id | 0x40;
fpi_spi_transfer_read_full (xfer, data_out, 1, NULL);
return xfer;
}
static FpiSpiTransfer *
elanspi_write_register (FpiDeviceElanSpi *self, guint8 reg_id, guint8 data_in)
{
FpiSpiTransfer * xfer = fpi_spi_transfer_new (FP_DEVICE (self), self->spi_fd);
fpi_spi_transfer_write (xfer, 2);
xfer->buffer_wr[0] = reg_id | 0x80;
xfer->buffer_wr[1] = data_in;
return xfer;
}
static void
elanspi_determine_sensor (FpiDeviceElanSpi *self, GError **err)
{
guint8 raw_height = self->sensor_height;
guint8 raw_width = self->sensor_width;
if (((raw_height == 0xa1) && (raw_width == 0xa1)) ||
((raw_height == 0xd1) && (raw_width == 0x51)) ||
((raw_height == 0xc1) && (raw_width == 0x39)))
{
self->sensor_ic_version = 0; /* Version 0 */
self->sensor_width = raw_width - 1;
self->sensor_height = raw_height - 1;
}
else
{
/* If the sensor is exactly 96x96 (0x60 x 0x60), the version is the high bit of register 17 */
if (raw_width == 0x60 && raw_height == 0x60)
{
self->sensor_ic_version = (self->sensor_reg_17 & 0x80) ? 1 : 0;
}
else
{
if (((raw_height != 0xa0) || (raw_width != 0x50)) &&
((raw_height != 0x90) || (raw_width != 0x40)) &&
((raw_height != 0x78) || (raw_width != 0x78)))
{
if (((raw_height != 0x40) || (raw_width != 0x58)) &&
((raw_height != 0x50) || (raw_width != 0x50)))
{
/* Old sensor hack?? */
self->sensor_width = 0x78;
self->sensor_height = 0x78;
self->sensor_ic_version = 0;
}
else
{
/* Otherwise, read the version 'normally' */
self->sensor_ic_version = (self->sensor_raw_version & 0x70) >> 4;
}
}
else
{
self->sensor_ic_version = 1;
}
}
}
fp_dbg ("<init/detect> after hardcoded lookup; %dx%d, version %d", self->sensor_width, self->sensor_height, self->sensor_ic_version);
for (const struct elanspi_sensor_entry *entry = elanspi_sensor_table; entry->name; entry += 1)
{
if (entry->ic_version == self->sensor_ic_version && entry->width == self->sensor_width && entry->height == self->sensor_height)
{
self->sensor_id = entry->sensor_id;
self->sensor_otp = entry->is_otp_model;
fp_dbg ("<init/detect> found sensor ID %d => [%s] (%d x %d)", self->sensor_id, entry->name, self->sensor_width, self->sensor_height);
break;
}
}
if (self->sensor_id == 0xff)
{
*err = fpi_device_error_new_msg (FP_DEVICE_ERROR_NOT_SUPPORTED, "unknown sensor (%dx%d, v%d)", self->sensor_width, self->sensor_height, self->sensor_ic_version);
return;
}
/* setup frame size */
if (fpi_device_get_driver_data (FP_DEVICE (self)) & ELANSPI_HV_FLIPPED)
{
self->frame_width = self->sensor_height;
self->frame_height = self->sensor_width > ELANSPI_MAX_FRAME_HEIGHT ? ELANSPI_MAX_FRAME_HEIGHT : self->sensor_width;
}
else
{
self->frame_width = self->sensor_width;
self->frame_height = self->sensor_height > ELANSPI_MAX_FRAME_HEIGHT ? ELANSPI_MAX_FRAME_HEIGHT : self->sensor_height;
}
}
static void
elanspi_capture_old_line_handler (FpiSpiTransfer *transfer, FpDevice *dev, gpointer unused_data, GError *error)
{
FpiDeviceElanSpi *self = FPI_DEVICE_ELANSPI (dev);
if (error)
{
fpi_ssm_mark_failed (transfer->ssm, error);
return;
}
/* copy buffer from line into last_image */
for (int col = 0; col < self->sensor_width; col += 1)
{
guint8 low = transfer->buffer_rd[col * 2 + 1];
guint8 high = transfer->buffer_rd[col * 2];
self->last_image[self->sensor_width * self->old_data.line_ptr + col] = low + high * 0x100;
}
/* increment line ptr */
self->old_data.line_ptr += 1;
/* if there is still data, continue from check lineready */
if (self->old_data.line_ptr < self->sensor_height)
{
fpi_ssm_jump_to_state (transfer->ssm, ELANSPI_CAPTOLD_CHECK_LINEREADY);
}
else
{
/* check for cancellation */
if (fpi_device_action_is_cancelled (dev))
{
g_cancellable_set_error_if_cancelled (fpi_device_get_cancellable (dev), &error);
fpi_ssm_mark_failed (transfer->ssm, error);
return;
}
/* otherwise finish succesfully */
fpi_ssm_mark_completed (transfer->ssm);
}
}
static void
elanspi_capture_old_handler (FpiSsm *ssm, FpDevice *dev)
{
FpiDeviceElanSpi *self = FPI_DEVICE_ELANSPI (dev);
FpiSpiTransfer *xfer = NULL;
switch (fpi_ssm_get_cur_state (ssm))
{
case ELANSPI_CAPTOLD_WRITE_CAPTURE:
/* reset capture state */
self->old_data.line_ptr = 0;
self->capture_timeout = g_get_monotonic_time () + ELANSPI_OLD_CAPTURE_TIMEOUT_USEC;
xfer = elanspi_do_capture (self);
xfer->ssm = ssm;
fpi_spi_transfer_submit (xfer, NULL, fpi_ssm_spi_transfer_cb, NULL);
return;
case ELANSPI_CAPTOLD_CHECK_LINEREADY:
xfer = elanspi_read_status (self, &self->sensor_status);
xfer->ssm = ssm;
fpi_spi_transfer_submit (xfer, NULL, fpi_ssm_spi_transfer_cb, NULL);
return;
case ELANSPI_CAPTOLD_RECV_LINE:
/* is the sensor ready? */
if (!(self->sensor_status & 4))
{
/* has the timeout expired? -- disabled in testing since valgrind is very slow */
if (g_get_monotonic_time () > self->capture_timeout && g_strcmp0 (g_getenv ("FP_DEVICE_EMULATION"), "1") != 0)
{
/* end with a timeout */
fpi_ssm_mark_failed (ssm, g_error_new (G_IO_ERROR, G_IO_ERROR_TIMED_OUT, "timed out waiting for new line"));
return;
}
/* check again */
fpi_ssm_jump_to_state (ssm, ELANSPI_CAPTOLD_CHECK_LINEREADY);
return;
}
/* otherwise, perform a read */
xfer = fpi_spi_transfer_new (dev, self->spi_fd);
xfer->ssm = ssm;
fpi_spi_transfer_write (xfer, 2);
xfer->buffer_wr[0] = 0x10; /* receieve line */
fpi_spi_transfer_read (xfer, self->sensor_width * 2);
fpi_spi_transfer_submit (xfer, NULL, elanspi_capture_old_line_handler, NULL);
return;
}
}
static void
elanspi_send_regtable_handler (FpiSsm *ssm, FpDevice *dev)
{
FpiDeviceElanSpi *self = FPI_DEVICE_ELANSPI (dev);
FpiSpiTransfer *xfer = NULL;
const struct elanspi_reg_entry *entry = fpi_ssm_get_data (ssm);
switch (fpi_ssm_get_cur_state (ssm))
{
case ELANSPI_WRTABLE_WRITE:
xfer = elanspi_write_register (self, entry->addr, entry->value);
xfer->ssm = ssm;
fpi_spi_transfer_submit (xfer, fpi_device_get_cancellable (dev), fpi_ssm_spi_transfer_cb, NULL);
return;
case ELANSPI_WRTABLE_ITERATE:
entry += 1;
if (entry->addr != 0xff)
{
fpi_ssm_set_data (ssm, (gpointer) entry, NULL);
fpi_ssm_jump_to_state (ssm, ELANSPI_WRTABLE_WRITE);
return;
}
fpi_ssm_mark_completed (ssm);
return;
}
}
static FpiSsm *
elanspi_write_regtable (FpiDeviceElanSpi *self, const struct elanspi_regtable * table)
{
/* find regtable pointer */
const struct elanspi_reg_entry * starting_entry = table->other;
for (int i = 0; table->entries[i].table; i += 1)
{
if (table->entries[i].sid == self->sensor_id)
{
starting_entry = table->entries[i].table;
break;
}
}
if (starting_entry == NULL)
{
fp_err ("<regtable> unknown regtable for sensor %d", self->sensor_id);
return NULL;
}
FpiSsm * ssm = fpi_ssm_new (FP_DEVICE (self), elanspi_send_regtable_handler, ELANSPI_WRTABLE_NSTATES);
fpi_ssm_set_data (ssm, (gpointer) starting_entry, NULL);
return ssm;
}
static int
elanspi_mean_image (FpiDeviceElanSpi *self, const guint16 *img)
{
int total = 0;
for (int i = 0; i < self->sensor_width * self->sensor_height; i += 1)
total += img[i];
return total / (self->sensor_width * self->sensor_height);
}
static void
elanspi_calibrate_old_handler (FpiSsm *ssm, FpDevice *dev)
{
FpiDeviceElanSpi *self = FPI_DEVICE_ELANSPI (dev);
FpiSpiTransfer *xfer = NULL;
GError *err = NULL;
FpiSsm *chld = NULL;
int mean_value = 0;
switch (fpi_ssm_get_cur_state (ssm))
{
case ELANSPI_CALIBOLD_UNPROTECT:
xfer = elanspi_write_register (self, 0x00, 0x5a);
xfer->ssm = ssm;
fpi_spi_transfer_submit (xfer, fpi_device_get_cancellable (dev), fpi_ssm_spi_transfer_cb, NULL);
return;
case ELANSPI_CALIBOLD_WRITE_STARTCALIB:
xfer = elanspi_do_startcalib (self);
xfer->ssm = ssm;
fpi_spi_transfer_submit (xfer, fpi_device_get_cancellable (dev), fpi_ssm_spi_transfer_cb, NULL);
return;
case ELANSPI_CALIBOLD_STARTCALIBDELAY:
fpi_ssm_next_state_delayed (ssm, 1);
return;
case ELANSPI_CALIBOLD_SEND_REGTABLE:
chld = elanspi_write_regtable (self, &elanspi_calibration_table_old);
if (chld == NULL)
{
err = fpi_device_error_new_msg (FP_DEVICE_ERROR_NOT_SUPPORTED, "unknown calibration table for sensor");
fpi_ssm_mark_failed (ssm, err);
return;
}
fpi_ssm_start_subsm (ssm, chld);
return;
case ELANSPI_CALIBOLD_DACBASE_CAPTURE:
case ELANSPI_CALIBOLD_CHECKFIN_CAPTURE:
case ELANSPI_CALIBOLD_DACFINE_CAPTURE:
chld = fpi_ssm_new (dev, elanspi_capture_old_handler, ELANSPI_CAPTOLD_NSTATES);
fpi_ssm_start_subsm (ssm, chld);
return;
case ELANSPI_CALIBOLD_DACBASE_WRITE_DAC1:
/* compute dac */
self->old_data.dac_value = ((elanspi_mean_image (self, self->last_image) & 0xffff) + 0x80) >> 8;
if (0x3f < self->old_data.dac_value)
self->old_data.dac_value = 0x3f;
fp_dbg ("<calibold> dac init is 0x%02x", self->old_data.dac_value);
/* write it */
xfer = elanspi_write_register (self, 0x6, self->old_data.dac_value - 0x40);
xfer->ssm = ssm;
fpi_spi_transfer_submit (xfer, fpi_device_get_cancellable (dev), fpi_ssm_spi_transfer_cb, NULL);
return;
case ELANSPI_CALIBOLD_WRITE_GAIN:
/* check if finger was present */
if (elanspi_mean_image (self, self->last_image) >= ELANSPI_MAX_OLD_STAGE1_CALIBRATION_MEAN)
{
err = fpi_device_retry_new_msg (FP_DEVICE_RETRY_REMOVE_FINGER, "finger on sensor during calibration");
fpi_ssm_mark_failed (ssm, err);
return;
}
/* if ok, increase gain */
xfer = elanspi_write_register (self, 0x5, 0x6f);
xfer->ssm = ssm;
fpi_spi_transfer_submit (xfer, fpi_device_get_cancellable (dev), fpi_ssm_spi_transfer_cb, NULL);
/* initialize retry counter */
self->old_data.dacfine_retry = 0;
return;
case ELANSPI_CALIBOLD_DACFINE_WRITE_DAC1:
mean_value = elanspi_mean_image (self, self->last_image);
if (mean_value >= ELANSPI_MIN_OLD_STAGE2_CALBIRATION_MEAN && mean_value <= ELANSPI_MAX_OLD_STAGE2_CALBIRATION_MEAN)
{
/* finished calibration, goto bg */
fpi_ssm_jump_to_state (ssm, ELANSPI_CALIBOLD_PROTECT);
return;
}
if (mean_value < (ELANSPI_MIN_OLD_STAGE2_CALBIRATION_MEAN + (ELANSPI_MAX_OLD_STAGE2_CALBIRATION_MEAN - ELANSPI_MIN_OLD_STAGE2_CALBIRATION_MEAN) / 2))
self->old_data.dac_value -= 1;
else
self->old_data.dac_value += 1;
/* write it */
xfer = elanspi_write_register (self, 0x6, self->old_data.dac_value - 0x40);
xfer->ssm = ssm;
fpi_spi_transfer_submit (xfer, fpi_device_get_cancellable (dev), fpi_ssm_spi_transfer_cb, NULL);
return;
case ELANSPI_CALIBOLD_DACFINE_LOOP:
/* check the retry counter */
self->old_data.dacfine_retry += 1;
if (self->old_data.dacfine_retry >= 2)
{
/* bail with calibration error */
err = fpi_device_retry_new_msg (FP_DEVICE_RETRY_REMOVE_FINGER, "finger on sensor during calibration");
fpi_ssm_mark_failed (ssm, err);
return;
}
fp_dbg ("<calibold> repeating calibration for the %dth time", self->old_data.dacfine_retry);
/* otherwise, take another image */
fpi_ssm_jump_to_state (ssm, ELANSPI_CALIBOLD_DACFINE_CAPTURE);
return;
case ELANSPI_CALIBOLD_PROTECT:
fp_dbg ("<calibold> calibration ok, saving bg image");
xfer = elanspi_write_register (self, 0x00, 0x00);
xfer->ssm = ssm;
fpi_spi_transfer_submit (xfer, fpi_device_get_cancellable (dev), fpi_ssm_spi_transfer_cb, NULL);
return;
}
}
static void
elanspi_capture_hv_image_handler (FpiSpiTransfer *transfer, FpDevice *dev, gpointer unused_data, GError *error)
{
FpiDeviceElanSpi *self = FPI_DEVICE_ELANSPI (dev);
if (error)
{
fpi_ssm_mark_failed (transfer->ssm, error);
return;
}
int i, outptr;
guint16 value = 0;
for (i = 0, outptr = 0; i < transfer->length_rd && outptr < (self->sensor_height * self->sensor_width * 2); i += 1)
{
if (transfer->buffer_rd[i] != 0xff)
{
if (outptr % 2)
{
value <<= 8;
value |= transfer->buffer_rd[i];
self->last_image[outptr / 2] = value;
}
else
{
value = transfer->buffer_rd[i];
}
outptr += 1;
}
}
if (outptr != (self->sensor_height * self->sensor_width * 2))
{
fp_warn ("<capture/hv> did not receive full image");
/* mark ssm failed */
error = fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, "hv image receieve did not fill buffer");
fpi_ssm_mark_failed (transfer->ssm, error);
return;
}
fpi_ssm_mark_completed (transfer->ssm);
}
static void
elanspi_capture_hv_handler (FpiSsm *ssm, FpDevice *dev)
{
FpiDeviceElanSpi *self = FPI_DEVICE_ELANSPI (dev);
FpiSpiTransfer *xfer = NULL;
switch (fpi_ssm_get_cur_state (ssm))
{
case ELANSPI_CAPTHV_WRITE_CAPTURE:
/* reset capture state */
self->old_data.line_ptr = 0;
self->capture_timeout = g_get_monotonic_time () + ELANSPI_HV_CAPTURE_TIMEOUT_USEC;
xfer = elanspi_do_capture (self);
xfer->ssm = ssm;
/* these are specifically cancellable because they don't leave the device at some aribtrary line offset, since
* these devices only send entire images */
fpi_spi_transfer_submit (xfer, fpi_device_get_cancellable (dev), fpi_ssm_spi_transfer_cb, NULL);
return;
case ELANSPI_CAPTHV_CHECK_READY:
xfer = elanspi_read_status (self, &self->sensor_status);
xfer->ssm = ssm;
fpi_spi_transfer_submit (xfer, fpi_device_get_cancellable (dev), fpi_ssm_spi_transfer_cb, NULL);
return;
case ELANSPI_CAPTHV_RECV_IMAGE:
/* is the sensor ready? */
if (!(self->sensor_status & 4))
{
/* has the timeout expired? */
if (g_get_monotonic_time () > self->capture_timeout)
{
/* end with a timeout */
fpi_ssm_mark_failed (ssm, g_error_new (G_IO_ERROR, G_IO_ERROR_TIMED_OUT, "timed out waiting for image"));
return;
}
/* check again */
fpi_ssm_jump_to_state (ssm, ELANSPI_CAPTHV_CHECK_READY);
return;
}
/* otherwise, read the image
* the hv sensors seem to use 128 bytes of padding(?) this is only tested on the 0xe sensors */
xfer = fpi_spi_transfer_new (dev, self->spi_fd);
xfer->ssm = ssm;
fpi_spi_transfer_write (xfer, 2);
xfer->buffer_wr[0] = 0x10; /* receieve line */
fpi_spi_transfer_read (xfer, self->sensor_height * (self->sensor_width * 2 + 48));
fpi_spi_transfer_submit (xfer, fpi_device_get_cancellable (dev), elanspi_capture_hv_image_handler, NULL);
return;
}
}
static void
elanspi_calibrate_hv_handler (FpiSsm *ssm, FpDevice *dev)
{
FpiDeviceElanSpi *self = FPI_DEVICE_ELANSPI (dev);
FpiSpiTransfer *xfer = NULL;
GError *err = NULL;
FpiSsm *chld = NULL;
int mean_diff = 0;
switch (fpi_ssm_get_cur_state (ssm))
{
case ELANSPI_CALIBHV_SELECT_PAGE0_0:
/* initialize gdac */
self->hv_data.gdac_value = 0x100;
self->hv_data.gdac_step = 0x100;
self->hv_data.best_gdac = 0x0;
self->hv_data.best_meandiff = 0xffff;
case ELANSPI_CALIBHV_SELECT_PAGE0_1:
xfer = elanspi_do_selectpage (self, 0);
xfer->ssm = ssm;
fpi_spi_transfer_submit (xfer, fpi_device_get_cancellable (dev), fpi_ssm_spi_transfer_cb, NULL);
return;
case ELANSPI_CALIBHV_WRITE_STARTCALIB:
xfer = elanspi_do_startcalib (self);
xfer->ssm = ssm;
fpi_spi_transfer_submit (xfer, fpi_device_get_cancellable (dev), fpi_ssm_spi_transfer_cb, NULL);
return;
case ELANSPI_CALIBHV_UNPROTECT:
xfer = elanspi_write_register (self, 0x00, 0x5a);
xfer->ssm = ssm;
fpi_spi_transfer_submit (xfer, fpi_device_get_cancellable (dev), fpi_ssm_spi_transfer_cb, NULL);
return;
case ELANSPI_CALIBHV_SEND_REGTABLE0:
chld = elanspi_write_regtable (self, &elanspi_calibration_table_new_page0);
if (chld == NULL)
{
err = fpi_device_error_new_msg (FP_DEVICE_ERROR_NOT_SUPPORTED, "unknown calibration table for sensor");
fpi_ssm_mark_failed (ssm, err);
return;
}
fpi_ssm_start_subsm (ssm, chld);
return;
case ELANSPI_CALIBHV_SELECT_PAGE1:
xfer = elanspi_do_selectpage (self, 1);
xfer->ssm = ssm;
fpi_spi_transfer_submit (xfer, fpi_device_get_cancellable (dev), fpi_ssm_spi_transfer_cb, NULL);
return;
case ELANSPI_CALIBHV_SEND_REGTABLE1:
chld = elanspi_write_regtable (self, &elanspi_calibration_table_new_page1);
if (chld == NULL)
{
err = fpi_device_error_new_msg (FP_DEVICE_ERROR_NOT_SUPPORTED, "unknown calibration table for sensor");
fpi_ssm_mark_failed (ssm, err);
return;
}
fpi_ssm_start_subsm (ssm, chld);
return;
case ELANSPI_CALIBHV_WRITE_GDAC_H:
case ELANSPI_CALIBHV_WRITE_BEST_GDAC_H:
if (fpi_ssm_get_cur_state (ssm) == ELANSPI_CALIBHV_WRITE_BEST_GDAC_H)
self->hv_data.gdac_value = self->hv_data.best_gdac;
xfer = elanspi_write_register (self, 0x06, (self->hv_data.gdac_value >> 2) & 0xff);
xfer->ssm = ssm;
fpi_spi_transfer_submit (xfer, fpi_device_get_cancellable (dev), fpi_ssm_spi_transfer_cb, NULL);
return;
case ELANSPI_CALIBHV_WRITE_GDAC_L:
case ELANSPI_CALIBHV_WRITE_BEST_GDAC_L:
xfer = elanspi_write_register (self, 0x07, self->hv_data.gdac_value & 3);
xfer->ssm = ssm;
fpi_spi_transfer_submit (xfer, fpi_device_get_cancellable (dev), fpi_ssm_spi_transfer_cb, NULL);
return;
case ELANSPI_CALIBHV_CAPTURE:
chld = fpi_ssm_new (dev, elanspi_capture_hv_handler, ELANSPI_CAPTHV_NSTATES);
fpi_ssm_start_subsm (ssm, chld);
return;
case ELANSPI_CALIBHV_PROCESS:
/* compute mean */
mean_diff = abs (elanspi_mean_image (self, self->last_image) - ELANSPI_HV_CALIBRATION_TARGET_MEAN);
if (mean_diff < 100)
{
fp_dbg ("<calibhv> calibration ok (mdiff < 100 w/ gdac=%04x)", self->hv_data.gdac_value);
/* exit early, jump right to protect */
fpi_ssm_jump_to_state (ssm, ELANSPI_CALIBHV_PROTECT);
return;
}
if (mean_diff < self->hv_data.best_meandiff)
{
self->hv_data.best_meandiff = mean_diff;
self->hv_data.best_gdac = self->hv_data.gdac_value;
}
/* shrink step */
self->hv_data.gdac_step /= 2;
if (self->hv_data.gdac_step == 0)
{
fp_dbg ("<calibhv> calibration ok (step = 0 w/ best_gdac=%04x)", self->hv_data.best_gdac);
/* exit, using best value */
fpi_ssm_jump_to_state (ssm, ELANSPI_CALIBHV_WRITE_BEST_GDAC_H);
return;
}
/* update gdac */
if (elanspi_mean_image (self, self->last_image) < ELANSPI_HV_CALIBRATION_TARGET_MEAN)
self->hv_data.gdac_value -= self->hv_data.gdac_step;
else
self->hv_data.gdac_value += self->hv_data.gdac_step;
/* advance back to capture */
fpi_ssm_jump_to_state (ssm, ELANSPI_CALIBHV_WRITE_GDAC_H);
return;
case ELANSPI_CALIBHV_PROTECT:
fp_dbg ("<calibhv> calibration ok, saving bg image");
xfer = elanspi_write_register (self, 0x00, 0x00);
xfer->ssm = ssm;
fpi_spi_transfer_submit (xfer, fpi_device_get_cancellable (dev), fpi_ssm_spi_transfer_cb, NULL);
return;
}
}
static void
elanspi_init_ssm_handler (FpiSsm *ssm, FpDevice *dev)
{
FpiDeviceElanSpi *self = FPI_DEVICE_ELANSPI (dev);
FpiSpiTransfer *xfer = NULL;
GError *err = NULL;
FpiSsm *chld = NULL;
switch (fpi_ssm_get_cur_state (ssm))
{
case ELANSPI_INIT_READ_STATUS1:
xfer = elanspi_read_status (self, &self->sensor_status);
xfer->ssm = ssm;
fpi_spi_transfer_submit (xfer, fpi_device_get_cancellable (dev), fpi_ssm_spi_transfer_cb, NULL);
return;
case ELANSPI_INIT_HWSWRESET:
fp_dbg ("<init> got status %02x", self->sensor_status);
elanspi_do_hwreset (self, &err);
fp_dbg ("<init> sync hw reset");
if (err)
{
fp_err ("<init> sync hw reset failed");
fpi_ssm_mark_failed (ssm, err);
return;
}
do_sw_reset:
xfer = elanspi_do_swreset (self);
xfer->ssm = ssm;
fpi_spi_transfer_submit (xfer, fpi_device_get_cancellable (dev), fpi_ssm_spi_transfer_cb, NULL);
return;
case ELANSPI_INIT_SWRESETDELAY1:
case ELANSPI_INIT_SWRESETDELAY2:
fpi_ssm_next_state_delayed (ssm, 4);
return;
case ELANSPI_INIT_READ_HEIGHT:
fp_dbg ("<init> sw reset ok");
xfer = elanspi_read_height (self, &self->sensor_height);
xfer->ssm = ssm;
fpi_spi_transfer_submit (xfer, fpi_device_get_cancellable (dev), fpi_ssm_spi_transfer_cb, NULL);
return;
case ELANSPI_INIT_READ_WIDTH:
self->sensor_height += 1;
fp_dbg ("<init> raw height = %d", self->sensor_height);
xfer = elanspi_read_width (self, &self->sensor_width);
xfer->ssm = ssm;
fpi_spi_transfer_submit (xfer, fpi_device_get_cancellable (dev), fpi_ssm_spi_transfer_cb, NULL);
return;
case ELANSPI_INIT_READ_REG17:
self->sensor_width += 1;
fp_dbg ("<init> raw width = %d", self->sensor_width);
xfer = elanspi_read_register (self, 0x17, &self->sensor_reg_17);
xfer->ssm = ssm;
fpi_spi_transfer_submit (xfer, fpi_device_get_cancellable (dev), fpi_ssm_spi_transfer_cb, NULL);
return;
case ELANSPI_INIT_READ_VERSION:
fp_dbg ("<init> raw reg17 = %d", self->sensor_reg_17);
xfer = elanspi_read_version (self, &self->sensor_raw_version);
xfer->ssm = ssm;
fpi_spi_transfer_submit (xfer, fpi_device_get_cancellable (dev), fpi_ssm_spi_transfer_cb, NULL);
return;
case ELANSPI_INIT_SWRESET2:
fp_dbg ("<init> raw version = %02x", self->sensor_raw_version);
elanspi_determine_sensor (self, &err);
if (err)
{
fp_err ("<init> sensor detection error");
fpi_ssm_mark_failed (ssm, err);
return;
}
/* allocate memory */
g_clear_pointer (&self->bg_image, g_free);
g_clear_pointer (&self->last_image, g_free);
g_clear_pointer (&self->prev_frame_image, g_free);
self->last_image = g_malloc0 (self->sensor_width * self->sensor_height * 2);
self->bg_image = g_malloc0 (self->sensor_width * self->sensor_height * 2);
self->prev_frame_image = g_malloc0 (self->sensor_width * self->sensor_height * 2);
/* reset again */
goto do_sw_reset;
case ELANSPI_INIT_OTP_READ_VREF1:
/* is this sensor otp? */
if (!self->sensor_otp)
{
/* go to calibration */
fpi_ssm_jump_to_state (ssm, ELANSPI_INIT_CALIBRATE);
return;
}
/* otherwise, begin otp */
self->old_data.otp_timeout = g_get_monotonic_time () + ELANSPI_OTP_TIMEOUT_USEC;
xfer = elanspi_read_register (self, 0x3d, &self->sensor_reg_vref1);
xfer->ssm = ssm;
fpi_spi_transfer_submit (xfer, fpi_device_get_cancellable (dev), fpi_ssm_spi_transfer_cb, NULL);
return;
case ELANSPI_INIT_OTP_WRITE_VREF1:
/* mask out low bits */
self->sensor_reg_vref1 &= 0x3f;
xfer = elanspi_write_register (self, 0x3d, self->sensor_reg_vref1);
xfer->ssm = ssm;
fpi_spi_transfer_submit (xfer, fpi_device_get_cancellable (dev), fpi_ssm_spi_transfer_cb, NULL);
return;
case ELANSPI_INIT_OTP_WRITE_0x28:
xfer = elanspi_write_register (self, 0x28, 0x78);
xfer->ssm = ssm;
fpi_spi_transfer_submit (xfer, fpi_device_get_cancellable (dev), fpi_ssm_spi_transfer_cb, NULL);
return;
/* begin loop */
case ELANSPI_INIT_OTP_LOOP_READ_0x28:
/* begin read of 0x28 */
xfer = elanspi_read_register (self, 0x28, &self->sensor_reg_28);
xfer->ssm = ssm;
fpi_spi_transfer_submit (xfer, fpi_device_get_cancellable (dev), fpi_ssm_spi_transfer_cb, NULL);
return;
case ELANSPI_INIT_OTP_LOOP_READ_0x27:
if (self->sensor_reg_28 & 0x40)
{
/* try again */
fp_dbg ("<init/otp> looping");
fpi_ssm_jump_to_state (ssm, ELANSPI_INIT_OTP_LOOP_READ_0x28);
return;
}
/* otherwise, read reg 27 */
xfer = elanspi_read_register (self, 0x27, &self->sensor_reg_27);
xfer->ssm = ssm;
fpi_spi_transfer_submit (xfer, fpi_device_get_cancellable (dev), fpi_ssm_spi_transfer_cb, NULL);
return;
case ELANSPI_INIT_OTP_LOOP_UPDATEDAC_READ_DAC2:
/* if high bit set, exit with mode 2 */
if (self->sensor_reg_27 & 0x80)
{
self->sensor_vcm_mode = 2;
fpi_ssm_jump_to_state (ssm, ELANSPI_INIT_OTP_WRITE_0xb);
return;
}
/* if low two bits are not set, loop */
if ((self->sensor_reg_27 & 6) != 6)
{
/* have we hit the timeout */
if (g_get_monotonic_time () > self->old_data.otp_timeout)
{
fp_warn ("<init/otp> timed out waiting for vcom detection");
self->sensor_vcm_mode = 2;
fpi_ssm_jump_to_state (ssm, ELANSPI_INIT_OTP_WRITE_0xb);
return;
}
/* try again */
fp_dbg ("<init/otp> looping");
fpi_ssm_jump_to_state (ssm, ELANSPI_INIT_OTP_LOOP_READ_0x28);
return;
}
/* otherwise, set vcm mode from low bit and read dac2 */
self->sensor_vcm_mode = (self->sensor_reg_27 & 1) + 1;
xfer = elanspi_read_register (self, 0x7, &self->sensor_reg_dac2);
xfer->ssm = ssm;
fpi_spi_transfer_submit (xfer, fpi_device_get_cancellable (dev), fpi_ssm_spi_transfer_cb, NULL);
return;
case ELANSPI_INIT_OTP_LOOP_UPDATEDAC_WRITE_DAC2:
/* set high bit and rewrite */
self->sensor_reg_dac2 |= 0x80;
xfer = elanspi_write_register (self, 0x7, self->sensor_reg_dac2);
xfer->ssm = ssm;
fpi_spi_transfer_submit (xfer, fpi_device_get_cancellable (dev), fpi_ssm_spi_transfer_cb, NULL);
return;
case ELANSPI_INIT_OTP_LOOP_UPDATEDAC_WRITE_10:
xfer = elanspi_write_register (self, 0xa, 0x97);
xfer->ssm = ssm;
fpi_spi_transfer_submit (xfer, fpi_device_get_cancellable (dev), fpi_ssm_spi_transfer_cb, NULL);
return;
/* end loop, joins to here on early exits */
case ELANSPI_INIT_OTP_WRITE_0xb:
fp_dbg ("<init/otp> got vcm mode = %d", self->sensor_vcm_mode);
/* if mode is 0, skip to calibration */
if (self->sensor_vcm_mode == 0)
{
fpi_ssm_jump_to_state (ssm, ELANSPI_INIT_CALIBRATE);
return;
}
xfer = elanspi_write_register (self, 0xb, self->sensor_vcm_mode == 2 ? 0x72 : 0x71);
xfer->ssm = ssm;
fpi_spi_transfer_submit (xfer, fpi_device_get_cancellable (dev), fpi_ssm_spi_transfer_cb, NULL);
return;
case ELANSPI_INIT_OTP_WRITE_0xc:
xfer = elanspi_write_register (self, 0xc, self->sensor_vcm_mode == 2 ? 0x62 : 0x49);
xfer->ssm = ssm;
fpi_spi_transfer_submit (xfer, fpi_device_get_cancellable (dev), fpi_ssm_spi_transfer_cb, NULL);
return;
case ELANSPI_INIT_CALIBRATE:
fp_dbg ("<init/calibrate> starting calibrate");
/* if sensor is hv */
if (self->sensor_id == 0xe)
chld = fpi_ssm_new_full (dev, elanspi_calibrate_hv_handler, ELANSPI_CALIBHV_NSTATES, ELANSPI_CALIBHV_PROTECT, "HV calibrate");
else
chld = fpi_ssm_new_full (dev, elanspi_calibrate_old_handler, ELANSPI_CALIBOLD_NSTATES, ELANSPI_CALIBOLD_PROTECT, "old calibrate");
fpi_ssm_start_subsm (ssm, chld);
return;
case ELANSPI_INIT_BG_CAPTURE:
if (self->sensor_id == 0xe)
chld = fpi_ssm_new (dev, elanspi_capture_hv_handler, ELANSPI_CAPTHV_NSTATES);
else
chld = fpi_ssm_new (dev, elanspi_capture_old_handler, ELANSPI_CAPTOLD_NSTATES);
fpi_ssm_start_subsm (ssm, chld);
return;
case ELANSPI_INIT_BG_SAVE:
memcpy (self->bg_image, self->last_image, self->sensor_height * self->sensor_width * 2);
fpi_ssm_mark_completed (ssm);
return;
}
}
enum elanspi_guess_result {
ELANSPI_GUESS_FINGERPRINT,
ELANSPI_GUESS_EMPTY,
ELANSPI_GUESS_UNKNOWN
};
/* in place correct image, returning number of invalid pixels */
static gint
elanspi_correct_with_bg (FpiDeviceElanSpi *self, guint16 *raw_image)
{
gint count = 0;
for (int i = 0; i < self->sensor_width * self->sensor_height; i += 1)
{
if (raw_image[i] < self->bg_image[i])
{
count += 1;
raw_image[i] = 0;
}
else
{
raw_image[i] -= self->bg_image[i];
}
}
return count;
}
static guint16
elanspi_lookup_pixel_with_rotation (FpiDeviceElanSpi *self, const guint16 *data_in, int y, int x)
{
int rotation = fpi_device_get_driver_data (FP_DEVICE (self)) & 3;
gint x1 = x, y1 = y;
if (rotation == ELANSPI_180_ROTATE)
{
x1 = (self->sensor_width - x - 1);
y1 = (self->sensor_height - y - 1);
}
else if (rotation == ELANSPI_90LEFT_ROTATE)
{
x1 = y;
y1 = (self->sensor_width - x - 1);
}
else if (rotation == ELANSPI_90RIGHT_ROTATE)
{
x1 = (self->sensor_height - y - 1);
y1 = x;
}
return data_in[y1 * self->sensor_width + x1];
}
static enum elanspi_guess_result
elanspi_guess_image (FpiDeviceElanSpi *self, guint16 *raw_image)
{
g_autofree guint16 * image_copy = g_malloc0 (self->sensor_height * self->sensor_width * 2);
guint8 frame_width, frame_height;
/* make clang happy about div0 */
frame_width = self->frame_width;
frame_height = self->frame_height;
g_assert (frame_width && frame_height);
memcpy (image_copy, raw_image, self->sensor_height * self->sensor_width * 2);
gint invalid_percent = (100 * elanspi_correct_with_bg (self, image_copy)) / (self->sensor_height * self->sensor_width);
gint is_fp = 0, is_empty = 0;
gint64 mean = 0;
gint64 sq_stddev = 0;
for (int j = 0; j < frame_height; j += 1)
for (int i = 0; i < frame_width; i += 1)
mean += (gint64) elanspi_lookup_pixel_with_rotation (self, image_copy, j, i);
mean /= (frame_width * frame_height);
for (int j = 0; j < frame_height; j += 1)
for (int i = 0; i < frame_width; i += 1)
{
gint64 k = (gint64) elanspi_lookup_pixel_with_rotation (self, image_copy, j, i) - mean;
sq_stddev += k * k;
}
sq_stddev /= (frame_width * frame_height);
fp_dbg ("<guess> stddev=%ld, ip=%d, is_fp=%d, is_empty=%d", sq_stddev, invalid_percent, is_fp, is_empty);
if (invalid_percent < ELANSPI_MAX_REAL_INVALID_PERCENT)
is_fp += 1;
if (invalid_percent > ELANSPI_MIN_EMPTY_INVALID_PERCENT)
is_empty += 1;
if (sq_stddev > ELANSPI_MIN_REAL_STDDEV)
is_fp += 1;
if (sq_stddev < ELANSPI_MAX_EMPTY_STDDEV)
is_empty += 1;
if (is_fp > is_empty)
return ELANSPI_GUESS_FINGERPRINT;
else if (is_empty > is_fp)
return ELANSPI_GUESS_EMPTY;
else
return ELANSPI_GUESS_UNKNOWN;
}
/* returns TRUE when the waiting is complete */
static gboolean
elanspi_check_waitupdown_done (FpiDeviceElanSpi *self, enum elanspi_guess_result target)
{
enum elanspi_guess_result guess = elanspi_guess_image (self, self->last_image);
if (guess == ELANSPI_GUESS_UNKNOWN)
return FALSE;
if (guess == target)
{
self->finger_wait_debounce += 1;
return self->finger_wait_debounce == ELANSPI_MIN_FRAMES_DEBOUNCE;
}
else
{
self->finger_wait_debounce = 0;
return FALSE;
}
}
static int
cmp_u16 (const void *a, const void *b)
{
return (int) (*(guint16 *) a - *(guint16 *) b);
}
static void
elanspi_process_frame (FpiDeviceElanSpi *self, const guint16 *data_in, guint8 *data_out)
{
size_t frame_size = self->frame_width * self->frame_height;
guint16 data_in_sorted[frame_size];
for (int i = 0, offset = 0; i < self->frame_height; i += 1)
for (int j = 0; j < self->frame_width; j += 1)
data_in_sorted[offset++] = elanspi_lookup_pixel_with_rotation (self, data_in, i, j);
qsort (data_in_sorted, frame_size, 2, cmp_u16);
guint16 lvl0 = data_in_sorted[0];
guint16 lvl1 = data_in_sorted[frame_size * 3 / 10];
guint16 lvl2 = data_in_sorted[frame_size * 65 / 100];
guint16 lvl3 = data_in_sorted[frame_size - 1];
lvl1 = MAX (lvl1, lvl0 + 1);
lvl2 = MAX (lvl2, lvl1 + 1);
lvl3 = MAX (lvl3, lvl2 + 1);
for (int i = 0; i < self->frame_height; i += 1)
{
for (int j = 0; j < self->frame_width; j += 1)
{
guint16 px = elanspi_lookup_pixel_with_rotation (self, data_in, i, j);
if (px < lvl0)
{
px = 0;
}
else if (px > lvl3)
{
px = 255;
}
else
{
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));
}
*data_out = px;
data_out += 1;
}
}
}
static unsigned char
elanspi_fp_assembling_get_pixel (struct fpi_frame_asmbl_ctx *ctx, struct fpi_frame *frame, unsigned int x, unsigned int y)
{
return frame->data[y * ctx->frame_width + x];
}
static void
elanspi_fp_frame_stitch_and_submit (FpiDeviceElanSpi *self)
{
g_autoptr(FpImage) img = NULL;
g_autoptr(FpImage) scaled = NULL;
struct fpi_frame_asmbl_ctx assembling_ctx = {
.image_width = (self->frame_width * 3) / 2,
.frame_width = self->frame_width,
.frame_height = self->frame_height,
.get_pixel = elanspi_fp_assembling_get_pixel,
};
/* stitch image */
GSList *frame_start = g_slist_nth (self->fp_frame_list, ELANSPI_SWIPE_FRAMES_DISCARD);
fpi_do_movement_estimation (&assembling_ctx, frame_start);
img = fpi_assemble_frames (&assembling_ctx, frame_start);
scaled = fpi_image_resize (img, 2, 2);
scaled->flags |= FPI_IMAGE_PARTIAL | FPI_IMAGE_COLORS_INVERTED;
/* submit image */
fpi_image_device_image_captured (FP_IMAGE_DEVICE (self), g_steal_pointer (&scaled));
/* clean out frame data */
g_slist_free_full (g_steal_pointer (&self->fp_frame_list), g_free);
}
static gint64
elanspi_get_frame_diff_stddev_sq (FpiDeviceElanSpi *self, guint16 *frame1, guint16 *frame2)
{
gint64 mean = 0;
gint64 sq_stddev = 0;
for (int j = 0; j < (self->sensor_height * self->sensor_width); j += 1)
mean += abs ((int) frame1[j] - (int) frame2[j]);
g_assert (self->sensor_height && self->sensor_width); /* make clang happy about div0 */
mean /= (self->sensor_height * self->sensor_width);
for (int j = 0; j < (self->sensor_height * self->sensor_width); j += 1)
{
gint64 k = abs ((int) frame1[j] - (int) frame2[j]) - mean;
sq_stddev += k * k;
}
sq_stddev /= (self->sensor_height * self->sensor_width);
return sq_stddev;
}
static void
elanspi_fp_frame_handler (FpiSsm *ssm, FpiDeviceElanSpi *self)
{
g_autofree struct fpi_frame *this_frame = NULL;
switch (elanspi_guess_image (self, self->last_image))
{
case ELANSPI_GUESS_UNKNOWN:
fp_dbg ("<fp_frame> unknown, ignore...");
break;
case ELANSPI_GUESS_EMPTY:
self->fp_empty_counter += 1;
fp_dbg ("<fp_frame> got empty");
if (self->fp_empty_counter > 1)
{
fp_dbg ("<fp_frame> have enough debounce");
if (g_slist_length (self->fp_frame_list) >= ELANSPI_MIN_FRAMES_SWIPE)
{
fp_dbg ("<fp_frame> have enough frames, submitting");
elanspi_fp_frame_stitch_and_submit (self);
}
else
{
fp_dbg ("<fp_frame> not enough frames, reporting short swipe");
fpi_image_device_retry_scan (FP_IMAGE_DEVICE (self), FP_DEVICE_RETRY_TOO_SHORT);
}
goto finish_capture;
}
break;
case ELANSPI_GUESS_FINGERPRINT:
if (self->fp_empty_counter && self->fp_frame_list)
{
if (self->fp_empty_counter < 1)
{
fp_dbg ("<fp_frame> possible bounced fp");
break;
}
else
{
fp_dbg ("<fp_frame> too many empties, clearing list");
g_slist_free_full (g_steal_pointer (&self->fp_frame_list), g_free);
self->fp_empty_counter = 0;
}
}
if (g_slist_length (self->fp_frame_list) > ELANSPI_MAX_FRAMES_SWIPE)
{
fp_dbg ("<fp_frame> have enough frames, exiting now");
elanspi_fp_frame_stitch_and_submit (self);
goto finish_capture;
}
/* append image */
this_frame = g_malloc0 (self->sensor_height * self->sensor_width + sizeof (struct fpi_frame));
elanspi_correct_with_bg (self, self->last_image);
elanspi_process_frame (self, self->last_image, this_frame->data);
if (self->fp_frame_list)
{
gint difference = elanspi_get_frame_diff_stddev_sq (self, self->last_image, self->prev_frame_image);
fp_dbg ("<fp_frame> diff = %d", difference);
if (difference < ELANSPI_MIN_FRAME_TO_FRAME_DIFF)
{
fp_dbg ("<fp_frame> ignoring b.c. difference is too small");
break;
}
}
self->fp_frame_list = g_slist_prepend (self->fp_frame_list, g_steal_pointer (&this_frame));
memcpy (self->prev_frame_image, self->last_image, self->sensor_height * self->sensor_width * 2);
break;
}
if (self->sensor_id == 0xe)
fpi_ssm_jump_to_state_delayed (ssm, ELANSPI_FPCAPT_FP_CAPTURE, ELANSPI_HV_SENSOR_FRAME_DELAY);
else
fpi_ssm_jump_to_state (ssm, ELANSPI_FPCAPT_FP_CAPTURE);
return;
finish_capture:
/* prepare for wait up */
self->finger_wait_debounce = 0;
fpi_ssm_jump_to_state (ssm, ELANSPI_FPCAPT_WAITUP_CAPTURE);
return;
}
static void
elanspi_fp_capture_ssm_handler (FpiSsm *ssm, FpDevice *dev)
{
FpiDeviceElanSpi *self = FPI_DEVICE_ELANSPI (dev);
FpiSsm *chld = NULL;
switch (fpi_ssm_get_cur_state (ssm))
{
case ELANSPI_FPCAPT_INIT:
self->finger_wait_debounce = 0;
fpi_ssm_next_state (ssm);
return;
case ELANSPI_FPCAPT_WAITDOWN_CAPTURE:
case ELANSPI_FPCAPT_WAITUP_CAPTURE:
case ELANSPI_FPCAPT_FP_CAPTURE:
/* check if we are deactivating */
if (self->deactivating)
{
fp_dbg ("<capture> got deactivate; exiting");
fpi_ssm_mark_completed (ssm);
/* mark deactivate done */
fpi_image_device_deactivate_complete (FP_IMAGE_DEVICE (dev), NULL);
self->deactivating = FALSE;
return;
}
/* if sensor is hv */
if (self->sensor_id == 0xe)
chld = fpi_ssm_new (dev, elanspi_capture_hv_handler, ELANSPI_CAPTHV_NSTATES);
else
chld = fpi_ssm_new (dev, elanspi_capture_old_handler, ELANSPI_CAPTOLD_NSTATES);
fpi_ssm_start_subsm (ssm, chld);
return;
case ELANSPI_FPCAPT_WAITDOWN_PROCESS:
if (!elanspi_check_waitupdown_done (self, ELANSPI_GUESS_FINGERPRINT))
{
/* take another image */
fpi_ssm_jump_to_state (ssm, ELANSPI_FPCAPT_WAITDOWN_CAPTURE);
return;
}
/* prepare to take actual image */
self->finger_wait_debounce = 0;
g_slist_free_full (g_steal_pointer (&self->fp_frame_list), g_free);
self->fp_empty_counter = 0;
/* report finger status */
fpi_image_device_report_finger_status (FP_IMAGE_DEVICE (self), TRUE);
/* jump */
fpi_ssm_jump_to_state (ssm, ELANSPI_FPCAPT_FP_CAPTURE);
return;
case ELANSPI_FPCAPT_FP_PROCESS:
elanspi_fp_frame_handler (ssm, self);
return;
case ELANSPI_FPCAPT_WAITUP_PROCESS:
if (!elanspi_check_waitupdown_done (self, ELANSPI_GUESS_EMPTY))
{
/* take another image */
fpi_ssm_jump_to_state (ssm, ELANSPI_FPCAPT_WAITUP_CAPTURE);
return;
}
/* Immediately set capturing to FALSE so that when report_finger_status tries to re-start
* capturing in enroll we don't hit the assert since the old SSM is about to stop. */
self->capturing = FALSE;
fpi_image_device_report_finger_status (FP_IMAGE_DEVICE (self), FALSE);
/* finish */
fpi_ssm_mark_completed (ssm);
return;
}
}
static void
elanspi_open (FpImageDevice *dev)
{
FpiDeviceElanSpi *self = FPI_DEVICE_ELANSPI (dev);
GError *err = NULL;
G_DEBUG_HERE ();
int spi_fd = open (fpi_device_get_udev_data (FP_DEVICE (dev), FPI_DEVICE_UDEV_SUBTYPE_SPIDEV), O_RDWR);
if (spi_fd < 0)
{
g_set_error (&err, G_IO_ERROR, g_io_error_from_errno (errno), "unable to open spi");
fpi_image_device_open_complete (dev, err);
return;
}
self->spi_fd = spi_fd;
fpi_image_device_open_complete (dev, NULL);
}
static void
elanspi_close (FpImageDevice *dev)
{
FpiDeviceElanSpi *self = FPI_DEVICE_ELANSPI (dev);
if (self->spi_fd >= 0)
{
close (self->spi_fd);
self->spi_fd = -1;
}
fpi_image_device_close_complete (dev, NULL);
}
static void
elanspi_init_finish (FpiSsm *ssm, FpDevice *dev, GError *error)
{
FpImageDevice *idev = FP_IMAGE_DEVICE (dev);
G_DEBUG_HERE ();
fpi_image_device_activate_complete (idev, error);
}
static void
elanspi_activate (FpImageDevice *dev)
{
FpiSsm *ssm = fpi_ssm_new (FP_DEVICE (dev), elanspi_init_ssm_handler, ELANSPI_INIT_NSTATES);
fpi_ssm_start (ssm, elanspi_init_finish);
}
static void
elanspi_deactivate (FpImageDevice *dev)
{
FpiDeviceElanSpi *self = FPI_DEVICE_ELANSPI (dev);
if (self->capturing)
{
self->deactivating = TRUE;
fp_dbg ("<deactivate> waiting capture to stop");
}
else
{
fpi_image_device_deactivate_complete (dev, NULL);
}
}
static void
elanspi_fp_capture_finish (FpiSsm *ssm, FpDevice *dev, GError *error)
{
FpImageDevice *idev = FP_IMAGE_DEVICE (dev);
FpiDeviceElanSpi *self = FPI_DEVICE_ELANSPI (dev);
self->capturing = FALSE;
if (self->deactivating)
{
/* finish deactivate */
if (error)
g_error_free (error);
self->deactivating = FALSE;
fpi_image_device_deactivate_complete (idev, NULL);
return;
}
/* if there was an error, report it */
if (error)
fpi_image_device_session_error (idev, error);
}
static void
elanspi_change_state (FpImageDevice *dev, FpiImageDeviceState state)
{
FpiDeviceElanSpi *self = FPI_DEVICE_ELANSPI (dev);
if (state == FPI_IMAGE_DEVICE_STATE_AWAIT_FINGER_ON)
{
g_assert (self->capturing == FALSE);
/* start capturer */
self->capturing = TRUE;
fpi_ssm_start (fpi_ssm_new (FP_DEVICE (dev),
elanspi_fp_capture_ssm_handler,
ELANSPI_FPCAPT_NSTATES),
elanspi_fp_capture_finish);
fp_dbg ("<change_state> started capturer");
}
else
{
/* todo: other states? */
}
}
static void
fpi_device_elanspi_init (FpiDeviceElanSpi *self)
{
self->spi_fd = -1;
self->sensor_id = 0xff;
}
static void
fpi_device_elanspi_finalize (GObject *this)
{
FpiDeviceElanSpi *self = FPI_DEVICE_ELANSPI (this);
g_clear_pointer (&self->bg_image, g_free);
g_clear_pointer (&self->last_image, g_free);
g_clear_pointer (&self->prev_frame_image, g_free);
g_slist_free_full (g_steal_pointer (&self->fp_frame_list), g_free);
G_OBJECT_CLASS (fpi_device_elanspi_parent_class)->finalize (this);
}
static void
fpi_device_elanspi_class_init (FpiDeviceElanSpiClass *klass)
{
FpDeviceClass *dev_class = FP_DEVICE_CLASS (klass);
FpImageDeviceClass *img_class = FP_IMAGE_DEVICE_CLASS (klass);
dev_class->id = "elanspi";
dev_class->full_name = "ElanTech Embedded Fingerprint Sensor";
dev_class->type = FP_DEVICE_TYPE_UDEV;
dev_class->id_table = elanspi_id_table;
dev_class->scan_type = FP_SCAN_TYPE_SWIPE;
dev_class->nr_enroll_stages = 7; /* these sensors are very hit or miss, may as well record a few extras */
img_class->bz3_threshold = 24;
img_class->img_open = elanspi_open;
img_class->activate = elanspi_activate;
img_class->deactivate = elanspi_deactivate;
img_class->change_state = elanspi_change_state;
img_class->img_close = elanspi_close;
G_OBJECT_CLASS (klass)->finalize = fpi_device_elanspi_finalize;
}