1700 lines
53 KiB
C
1700 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=%" G_GUINT64_FORMAT "d, 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;
|
|
}
|