/* * Elan SPI driver for libfprint * * Copyright (C) 2021 Matthew Mirvish * * 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 #include #include #include #include #include #include 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 (" 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 (" 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 (" 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 (" 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 (" 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 (" 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 (" 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 (" 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 (" 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 (" 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 (" got status %02x", self->sensor_status); elanspi_do_hwreset (self, &err); fp_dbg (" sync hw reset"); if (err) { fp_err (" 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 (" 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 (" 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 (" 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 (" 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 (" raw version = %02x", self->sensor_raw_version); elanspi_determine_sensor (self, &err); if (err) { fp_err (" 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 (" 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 (" 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 (" 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 (" 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 (" 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 (" 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 (" unknown, ignore..."); break; case ELANSPI_GUESS_EMPTY: self->fp_empty_counter += 1; fp_dbg (" got empty"); if (self->fp_empty_counter > 1) { fp_dbg (" have enough debounce"); if (g_slist_length (self->fp_frame_list) >= ELANSPI_MIN_FRAMES_SWIPE) { fp_dbg (" have enough frames, submitting"); elanspi_fp_frame_stitch_and_submit (self); } else { fp_dbg (" 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 (" possible bounced fp"); break; } else { fp_dbg (" 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 (" 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 (" diff = %d", difference); if (difference < ELANSPI_MIN_FRAME_TO_FRAME_DIFF) { fp_dbg (" 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 (" 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 (" 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 (" 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; }