diff --git a/libfprint/drivers/elanspi.c b/libfprint/drivers/elanspi.c new file mode 100644 index 0000000..d03361d --- /dev/null +++ b/libfprint/drivers/elanspi.c @@ -0,0 +1,1696 @@ +/* + * 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) +{ + /* + * 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? */ + 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 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=%ld, ip=%d, is_fp=%d, is_empty=%d", sq_stddev, invalid_percent, is_fp, is_empty); + + if (invalid_percent < ELANSPI_MAX_REAL_INVALID_PERCENT) + is_fp += 1; + if (invalid_percent > ELANSPI_MIN_EMPTY_INVALID_PERCENT) + is_empty += 1; + + if (sq_stddev > ELANSPI_MIN_REAL_STDDEV) + is_fp += 1; + if (sq_stddev < ELANSPI_MAX_EMPTY_STDDEV) + is_empty += 1; + + if (is_fp > is_empty) + return ELANSPI_GUESS_FINGERPRINT; + else if (is_empty > is_fp) + return ELANSPI_GUESS_EMPTY; + else + return ELANSPI_GUESS_UNKNOWN; +} + +/* returns TRUE when the waiting is complete */ +static gboolean +elanspi_check_waitupdown_done (FpiDeviceElanSpi *self, enum elanspi_guess_result target) +{ + enum elanspi_guess_result guess = elanspi_guess_image (self, self->last_image); + + if (guess == ELANSPI_GUESS_UNKNOWN) + return FALSE; + if (guess == target) + { + self->finger_wait_debounce += 1; + return self->finger_wait_debounce == ELANSPI_MIN_FRAMES_DEBOUNCE; + } + else + { + self->finger_wait_debounce = 0; + return FALSE; + } +} + +static int +cmp_u16 (const void *a, const void *b) +{ + return (int) (*(guint16 *) a - *(guint16 *) b); +} + +static void +elanspi_process_frame (FpiDeviceElanSpi *self, const guint16 *data_in, guint8 *data_out) +{ + size_t frame_size = self->frame_width * self->frame_height; + guint16 data_in_sorted[frame_size]; + + for (int i = 0, offset = 0; i < self->frame_height; i += 1) + for (int j = 0; j < self->frame_width; j += 1) + data_in_sorted[offset++] = elanspi_lookup_pixel_with_rotation (self, data_in, i, j); + + qsort (data_in_sorted, frame_size, 2, cmp_u16); + guint16 lvl0 = data_in_sorted[0]; + guint16 lvl1 = data_in_sorted[frame_size * 3 / 10]; + guint16 lvl2 = data_in_sorted[frame_size * 65 / 100]; + guint16 lvl3 = data_in_sorted[frame_size - 1]; + + lvl1 = MAX (lvl1, lvl0 + 1); + lvl2 = MAX (lvl2, lvl1 + 1); + lvl3 = MAX (lvl3, lvl2 + 1); + + for (int i = 0; i < self->frame_height; i += 1) + { + for (int j = 0; j < self->frame_width; j += 1) + { + guint16 px = elanspi_lookup_pixel_with_rotation (self, data_in, i, j); + if (px < lvl0) + { + px = 0; + } + else if (px > lvl3) + { + px = 255; + } + else + { + if (lvl0 <= px && px < lvl1) + px = (px - lvl0) * 99 / (lvl1 - lvl0); + else if (lvl1 <= px && px < lvl2) + px = 99 + ((px - lvl1) * 56 / (lvl2 - lvl1)); + else /* (lvl2 <= px && px <= lvl3) */ + px = 155 + ((px - lvl2) * 100 / (lvl3 - lvl2)); + } + *data_out = px; + data_out += 1; + } + } +} + +static unsigned char +elanspi_fp_assembling_get_pixel (struct fpi_frame_asmbl_ctx *ctx, struct fpi_frame *frame, unsigned int x, unsigned int y) +{ + return frame->data[y * ctx->frame_width + x]; +} + +static void +elanspi_fp_frame_stitch_and_submit (FpiDeviceElanSpi *self) +{ + g_autoptr(FpImage) img = NULL; + g_autoptr(FpImage) scaled = NULL; + struct fpi_frame_asmbl_ctx assembling_ctx = { + .image_width = (self->frame_width * 3) / 2, + + .frame_width = self->frame_width, + .frame_height = self->frame_height, + + .get_pixel = elanspi_fp_assembling_get_pixel, + }; + + /* stitch image */ + GSList *frame_start = g_slist_nth (self->fp_frame_list, ELANSPI_SWIPE_FRAMES_DISCARD); + + fpi_do_movement_estimation (&assembling_ctx, frame_start); + img = fpi_assemble_frames (&assembling_ctx, frame_start); + scaled = fpi_image_resize (img, 2, 2); + + scaled->flags |= FPI_IMAGE_PARTIAL | FPI_IMAGE_COLORS_INVERTED; + + /* submit image */ + fpi_image_device_image_captured (FP_IMAGE_DEVICE (self), g_steal_pointer (&scaled)); + + /* clean out frame data */ + g_slist_free_full (g_steal_pointer (&self->fp_frame_list), g_free); +} + +static gint64 +elanspi_get_frame_diff_stddev_sq (FpiDeviceElanSpi *self, guint16 *frame1, guint16 *frame2) +{ + gint64 mean = 0; + gint64 sq_stddev = 0; + + for (int j = 0; j < (self->sensor_height * self->sensor_width); j += 1) + mean += abs ((int) frame1[j] - (int) frame2[j]); + + g_assert (self->sensor_height && self->sensor_width); /* make clang happy about div0 */ + mean /= (self->sensor_height * self->sensor_width); + + for (int j = 0; j < (self->sensor_height * self->sensor_width); j += 1) + { + gint64 k = abs ((int) frame1[j] - (int) frame2[j]) - mean; + sq_stddev += k * k; + } + + sq_stddev /= (self->sensor_height * self->sensor_width); + + return sq_stddev; +} + +static void +elanspi_fp_frame_handler (FpiSsm *ssm, FpiDeviceElanSpi *self) +{ + g_autofree struct fpi_frame *this_frame = NULL; + + switch (elanspi_guess_image (self, self->last_image)) + { + case ELANSPI_GUESS_UNKNOWN: + fp_dbg (" 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; +} diff --git a/libfprint/drivers/elanspi.h b/libfprint/drivers/elanspi.h new file mode 100644 index 0000000..c9e17b8 --- /dev/null +++ b/libfprint/drivers/elanspi.h @@ -0,0 +1,351 @@ +/* + * 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 + */ + +#pragma once + +#include + +#ifndef HAVE_UDEV +#error "elanspi requires udev" +#endif + +#include +#include + +#define ELANSPI_TP_PID 0x04f3 + +/* Sensor ID information copied from the windows driver */ + +struct elanspi_sensor_entry +{ + unsigned char sensor_id, height, width, ic_version; + gboolean is_otp_model; + const gchar * name; +}; + +static const struct elanspi_sensor_entry elanspi_sensor_table[] = { + {0x0, 0x78, 0x78, 0x0, 0x0, "eFSA120S"}, + {0x1, 0x78, 0x78, 0x1, 0x1, "eFSA120SA"}, + {0x2, 0xA0, 0xA0, 0x0, 0x0, "eFSA160S"}, + {0x3, 0xd0, 0x50, 0x0, 0x0, "eFSA820R"}, + {0x4, 0xC0, 0x38, 0x0, 0x0, "eFSA519R"}, + {0x5, 0x60, 0x60, 0x0, 0x0, "eFSA96S"}, + {0x6, 0x60, 0x60, 0x1, 0x1, "eFSA96SA"}, + {0x7, 0x60, 0x60, 0x2, 0x1, "eFSA96SB"}, + {0x8, 0xa0, 0x50, 0x1, 0x1, "eFSA816RA"}, + {0x9, 0x90, 0x40, 0x1, 0x1, "eFSA614RA"}, + {0xA, 0x90, 0x40, 0x2, 0x1, "eFSA614RB"}, + {0xB, 0x40, 0x58, 0x1, 0x1, "eFSA688RA"}, + {0xC, 0x50, 0x50, 0x1, 0x0, "eFSA80SA"}, + {0xD, 0x47, 0x80, 0x1, 0x1, "eFSA712RA"}, + {0xE, 0x50, 0x50, 0x2, 0x0, "eFSA80SC"}, + {0, 0, 0, 0, 0, NULL} +}; + +struct elanspi_reg_entry +{ + unsigned char addr, value; + /* terminates with 0xFF, 0xFF since register 0x0 is valid */ +}; + +struct elanspi_regtable +{ + const struct elanspi_reg_entry *other; + struct + { + unsigned char sid; + const struct elanspi_reg_entry *table; + } entries[]; +}; + +static const struct elanspi_reg_entry elanspi_calibration_table_default[] = { + {0x05, 0x60}, + {0x06, 0xc0}, + {0x07, 0x80}, + {0x08, 0x04}, + {0x0a, 0x97}, + {0x0b, 0x72}, + {0x0c, 0x69}, + {0x0f, 0x2a}, + {0x11, 0x2a}, + {0x13, 0x27}, + {0x15, 0x67}, + {0x18, 0x04}, + {0x21, 0x20}, + {0x22, 0x36}, + {0x2a, 0x5f}, + {0x2b, 0xc0}, + {0x2e, 0xff}, + + {0xff, 0xff} +}; + +static const struct elanspi_reg_entry elanspi_calibration_table_id567[] = { + {0x2A, 0x07}, + {0x5, 0x60}, + {0x6, 0xC0}, + {0x7, 0x80}, + {0x8, 0x04}, + {0xA, 0x97}, + {0xB, 0x72}, + {0xC, 0x69}, + {0xF, 0x2A}, + {0x11, 0x2A}, + {0x13, 0x27}, + {0x15, 0x67}, + {0x18, 0x04}, + {0x21, 0x20}, + {0x22, 0x36}, + {0x2A, 0x5F}, + {0x2B, 0xC0}, + {0x2E, 0xFF}, + + {0xff, 0xff} +}; + +static const struct elanspi_reg_entry elanspi_calibration_table_id0[] = { + {0x5, 0x60}, + {0x6, 0xC0}, + {0x8, 0x04}, + {0xA, 0x97}, + {0xB, 0x72}, + {0xC, 0x69}, + {0xF, 0x2B}, + {0x11, 0x2B}, + {0x13, 0x28}, + {0x15, 0x28}, + {0x18, 0x04}, + {0x21, 0x20}, + {0x2A, 0x4B}, + + {0xff, 0xff} +}; + +// old style sensor calibration, with only one page of registers +static const struct elanspi_regtable elanspi_calibration_table_old = { + .other = elanspi_calibration_table_default, + .entries = { + { .sid = 0x0, .table = elanspi_calibration_table_id0 }, + { .sid = 0x5, .table = elanspi_calibration_table_id567 }, + { .sid = 0x6, .table = elanspi_calibration_table_id567 }, + { .sid = 0x7, .table = elanspi_calibration_table_id567 }, + { .sid = 0x0, .table = NULL } + } +}; + +// new style sensor calibration, with two pages of registers +static const struct elanspi_reg_entry elanspi_calibration_table_page0_id14[] = { + {0x00, 0x5a}, + {0x01, 0x00}, + {0x02, 0x4f}, + {0x03, 0x00}, + {0x04, 0x4f}, + {0x05, 0xa0}, + {0x06, 0x00}, + {0x07, 0x00}, + {0x08, 0x00}, + {0x09, 0x04}, + {0x0a, 0x74}, + {0x0b, 0x05}, + {0x0c, 0x08}, + {0x0d, 0x00}, + {0x0e, 0x00}, + {0x0f, 0x14}, + {0x10, 0x3c}, + {0x11, 0x41}, + {0x12, 0x0c}, + {0x13, 0x00}, + {0x14, 0x00}, + {0x15, 0x04}, + {0x16, 0x02}, + {0x17, 0x00}, + {0x18, 0x01}, + {0x19, 0xf4}, + {0x1a, 0x00}, + {0x1b, 0x00}, + {0x1c, 0x00}, + {0x1d, 0x00}, + {0x1e, 0x00}, + {0x1f, 0x00}, + {0x20, 0x00}, + {0x21, 0x80}, + {0x22, 0x06}, + {0x23, 0x00}, + {0x24, 0x00}, + {0x25, 0x00}, + {0x26, 0x00}, + {0x27, 0x00}, + {0x28, 0x00}, + {0x29, 0x04}, + {0x2a, 0x5f}, + {0x2b, 0xe2}, + {0x2c, 0xa0}, + {0x2d, 0x00}, + {0x2e, 0xff}, + {0x2f, 0x40}, + {0x30, 0x01}, + {0x31, 0x38}, + {0x32, 0x00}, + {0x33, 0x00}, + {0x34, 0x00}, + {0x35, 0x1f}, + {0x36, 0xff}, + {0x37, 0x00}, + {0x38, 0x00}, + {0x39, 0x00}, + {0x3a, 0x00}, + {0xff, 0xff} +}; + +static const struct elanspi_reg_entry elanspi_calibration_table_page1_id14[] = { + {0x00, 0x7b}, + {0x01, 0x7f}, + {0x02, 0x77}, + {0x03, 0xd4}, + {0x04, 0x7d}, + {0x05, 0x19}, + {0x06, 0x80}, + {0x07, 0x40}, + {0x08, 0x11}, + {0x09, 0x00}, + {0x0a, 0x00}, + {0x0b, 0x14}, + {0x0c, 0x00}, + {0x0d, 0x00}, + {0x0e, 0x32}, + {0x0f, 0x02}, + {0x10, 0x08}, + {0x11, 0x6c}, + {0x12, 0x00}, + {0x13, 0x00}, + {0x14, 0x32}, + {0x15, 0x01}, + {0x16, 0x16}, + {0x17, 0x01}, + {0x18, 0x14}, + {0x19, 0x01}, + {0x1a, 0x16}, + {0x1b, 0x01}, + {0x1c, 0x17}, + {0x1d, 0x01}, + {0x1e, 0x0a}, + {0x1f, 0x01}, + {0x20, 0x0a}, + {0x21, 0x02}, + {0x22, 0x08}, + {0x23, 0x29}, + {0x24, 0x00}, + {0x25, 0x0c}, + {0x26, 0x1a}, + {0x27, 0x30}, + {0x28, 0x1a}, + {0x29, 0x30}, + {0x2a, 0x00}, + {0x2b, 0x00}, + {0x2c, 0x01}, + {0x2d, 0x16}, + {0x2e, 0x01}, + {0x2f, 0x17}, + {0x30, 0x03}, + {0x31, 0x2d}, + {0x32, 0x03}, + {0x33, 0x2d}, + {0x34, 0x14}, + {0x35, 0x00}, + {0x36, 0x00}, + {0x37, 0x00}, + {0x38, 0x00}, + {0x39, 0x03}, + {0x3a, 0xfe}, + {0x3b, 0x00}, + {0x3c, 0x00}, + {0x3d, 0x02}, + {0x3e, 0x00}, + {0x3f, 0x00}, + {0xff, 0xff} +}; + +static const struct elanspi_regtable elanspi_calibration_table_new_page0 = { + .other = NULL, + .entries = { + { .sid = 0xe, .table = elanspi_calibration_table_page0_id14 }, + { .sid = 0x0, .table = NULL } + } +}; + +static const struct elanspi_regtable elanspi_calibration_table_new_page1 = { + .other = NULL, + .entries = { + { .sid = 0xe, .table = elanspi_calibration_table_page1_id14 }, + { .sid = 0x0, .table = NULL } + } +}; + +#define ELANSPI_NO_ROTATE 0 +#define ELANSPI_90LEFT_ROTATE 1 +#define ELANSPI_180_ROTATE 2 +#define ELANSPI_90RIGHT_ROTATE 3 + +#define ELANSPI_HV_FLIPPED 1 + +#define ELANSPI_UDEV_TYPES FPI_DEVICE_UDEV_SUBTYPE_SPIDEV | FPI_DEVICE_UDEV_SUBTYPE_HIDRAW +#define ELANSPI_TP_VID 0x04f3 + +// using checkargs ACPI:HIDPID +static const FpIdEntry elanspi_id_table[] = { + {.udev_types = ELANSPI_UDEV_TYPES, .spi_acpi_id = "ELAN7001", .hid_id = {.vid = ELANSPI_TP_VID, .pid = 0x3057}, .driver_data = ELANSPI_180_ROTATE}, + {.udev_types = ELANSPI_UDEV_TYPES, .spi_acpi_id = "ELAN7001", .hid_id = {.vid = ELANSPI_TP_VID, .pid = 0x3087}, .driver_data = ELANSPI_180_ROTATE}, + {.udev_types = ELANSPI_UDEV_TYPES, .spi_acpi_id = "ELAN7001", .hid_id = {.vid = ELANSPI_TP_VID, .pid = 0x30c6}, .driver_data = ELANSPI_180_ROTATE}, + {.udev_types = ELANSPI_UDEV_TYPES, .spi_acpi_id = "ELAN70A1", .hid_id = {.vid = ELANSPI_TP_VID, .pid = 0x3134}, .driver_data = ELANSPI_90LEFT_ROTATE}, + {.udev_types = ELANSPI_UDEV_TYPES, .spi_acpi_id = "ELAN7001", .hid_id = {.vid = ELANSPI_TP_VID, .pid = 0x3148}, .driver_data = ELANSPI_180_ROTATE}, + {.udev_types = ELANSPI_UDEV_TYPES, .spi_acpi_id = "ELAN7001", .hid_id = {.vid = ELANSPI_TP_VID, .pid = 0x30b2}, .driver_data = ELANSPI_NO_ROTATE}, + {.udev_types = ELANSPI_UDEV_TYPES, .spi_acpi_id = "ELAN70A1", .hid_id = {.vid = ELANSPI_TP_VID, .pid = 0x30b2}, .driver_data = ELANSPI_NO_ROTATE}, + {.udev_types = ELANSPI_UDEV_TYPES, .spi_acpi_id = "ELAN7001", .hid_id = {.vid = ELANSPI_TP_VID, .pid = 0x309f}, .driver_data = ELANSPI_180_ROTATE}, + {.udev_types = 0} +}; + +#define ELANSPI_MAX_OLD_STAGE1_CALIBRATION_MEAN 1000 + +#define ELANSPI_MIN_OLD_STAGE2_CALBIRATION_MEAN 3000 +#define ELANSPI_MAX_OLD_STAGE2_CALBIRATION_MEAN 8000 + +#define ELANSPI_HV_CALIBRATION_TARGET_MEAN 3000 + +#define ELANSPI_MIN_EMPTY_INVALID_PERCENT 6 +#define ELANSPI_MAX_REAL_INVALID_PERCENT 3 + +#define ELANSPI_MIN_REAL_STDDEV (592 * 592) +#define ELANSPI_MAX_EMPTY_STDDEV (350 * 350) + +#define ELANSPI_MIN_FRAMES_DEBOUNCE 2 + +#define ELANSPI_SWIPE_FRAMES_DISCARD 1 +#define ELANSPI_MIN_FRAMES_SWIPE (7 + ELANSPI_SWIPE_FRAMES_DISCARD) +#define ELANSPI_MAX_FRAMES_SWIPE (20 + ELANSPI_SWIPE_FRAMES_DISCARD) + +#define ELANSPI_MAX_FRAME_HEIGHT 43 +#define ELANSPI_MIN_FRAME_TO_FRAME_DIFF (250 * 250) + +#define ELANSPI_HV_SENSOR_FRAME_DELAY 23 + +#define ELANSPI_OTP_TIMEOUT_USEC (12 * 1000) + +#define ELANSPI_OLD_CAPTURE_TIMEOUT_USEC (100 * 1000) +#define ELANSPI_HV_CAPTURE_TIMEOUT_USEC (50 * 1000) diff --git a/libfprint/meson.build b/libfprint/meson.build index f5688b3..29d8111 100644 --- a/libfprint/meson.build +++ b/libfprint/meson.build @@ -157,6 +157,9 @@ foreach driver: drivers if driver == 'elan' drivers_sources += [ 'drivers/elan.c' ] endif + if driver == 'elanspi' + drivers_sources += [ 'drivers/elanspi.c' ] + endif if driver == 'virtual_image' drivers_sources += [ 'drivers/virtual-image.c' ] endif diff --git a/meson.build b/meson.build index 53ad1a8..90ea334 100644 --- a/meson.build +++ b/meson.build @@ -99,6 +99,7 @@ virtual_drivers = [ ] udev_drivers = [ + 'elanspi' ] default_drivers = [ @@ -174,7 +175,7 @@ foreach driver: drivers error('NSS is required for the URU4000/URU4500 driver') endif endif - if driver == 'aes3500' or driver == 'aes4000' + if driver == 'aes3500' or driver == 'aes4000' or driver == 'elanspi' imaging_dep = dependency('pixman-1', required: false) if not imaging_dep.found() error('pixman is required for imaging support')