/* * Elan driver for libfprint * * Copyright (C) 2017 Igor Filatov * Copyright (C) 2018 Sébastien Béchet * * 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 "elan" #include "drivers_api.h" #include "elan.h" unsigned char elan_get_pixel(struct fpi_frame_asmbl_ctx *ctx, struct fpi_frame *frame, unsigned int x, unsigned int y) { return frame->data[x + y * ctx->frame_width]; } static struct fpi_frame_asmbl_ctx assembling_ctx = { .frame_width = 0, .frame_height = 0, .image_width = 0, .get_pixel = elan_get_pixel, }; struct elan_dev { /* device config */ unsigned short dev_type; unsigned short fw_ver; void (*process_frame) (unsigned short *raw_frame, GSList ** frames); /* end device config */ /* commands */ const struct elan_cmd *cmd; int cmd_timeout; struct libusb_transfer *cur_transfer; /* end commands */ /* state */ gboolean deactivating; unsigned char *last_read; unsigned char calib_atts_left; unsigned char calib_status; unsigned short *background; unsigned char frame_width; unsigned char frame_height; unsigned char raw_frame_width; int num_frames; GSList *frames; /* end state */ }; int cmp_short(const void *a, const void *b) { return (int)(*(short *)a - *(short *)b); } static void elan_dev_reset(struct elan_dev *elandev) { G_DEBUG_HERE(); BUG_ON(elandev->cur_transfer); elandev->cmd = NULL; elandev->cmd_timeout = ELAN_CMD_TIMEOUT; elandev->deactivating = FALSE; elandev->calib_status = 0; g_free(elandev->last_read); elandev->last_read = NULL; g_slist_free_full(elandev->frames, g_free); elandev->frames = NULL; elandev->num_frames = 0; } static void elan_save_frame(struct elan_dev *elandev, unsigned short *frame) { G_DEBUG_HERE(); unsigned char raw_height = elandev->frame_width; unsigned char raw_width = elandev->raw_frame_width; /* Raw images are vertical and perpendicular to swipe direction of a * normalized image, which means we need to make them horizontal before * assembling. We also discard stripes of 'frame_margin' along raw * height. */ unsigned char frame_margin = (raw_width - elandev->frame_height) / 2; for (int y = 0; y < raw_height; y++) for (int x = frame_margin; x < raw_width - frame_margin; x++) { int frame_idx = y + (x - frame_margin) * raw_height; int raw_idx = x + y * raw_width; frame[frame_idx] = ((unsigned short *)elandev->last_read)[raw_idx]; } } static void elan_save_background(struct elan_dev *elandev) { G_DEBUG_HERE(); g_free(elandev->background); elandev->background = g_malloc(elandev->frame_width * elandev->frame_height * sizeof(short)); elan_save_frame(elandev, elandev->background); } /* save a frame as part of the fingerprint image * background needs to have been captured for this routine to work * Elantech recommends 2-step non-linear normalization in order to reduce * 2^14 ADC resolution to 2^8 image: * * 1. background is subtracted (done here) * * 2. pixels are grouped in 3 groups by intensity and each group is mapped * separately onto the normalized frame (done in elan_process_frame_*) * ==== 16383 ____> ======== 255 * / * ----- lvl3 __/ * 35% pixels * * ----- lvl2 --------> ======== 156 * * 30% pixels * ----- lvl1 --------> ======== 99 * * 35% pixels * ----- lvl0 __ * \ * ======== 0 \____> ======== 0 * * For some devices we don't do 2. but instead do a simple linear mapping * because it seems to produce better results (or at least as good): * ==== 16383 ___> ======== 255 * / * ------ max __/ * * * ------ min __ * \ * ======== 0 \___> ======== 0 */ static void elan_save_img_frame(struct elan_dev *elandev) { G_DEBUG_HERE(); unsigned int frame_size = elandev->frame_width * elandev->frame_height; unsigned short *frame = g_malloc(frame_size * sizeof(short)); elan_save_frame(elandev, frame); for (int i = 0; i < frame_size; i++) if (elandev->background[i] > frame[i]) frame[i] = 0; else frame[i] -= elandev->background[i]; elandev->frames = g_slist_prepend(elandev->frames, frame); elandev->num_frames += 1; } static void elan_process_frame_linear(unsigned short *raw_frame, GSList ** frames) { unsigned int frame_size = assembling_ctx.frame_width * assembling_ctx.frame_height; struct fpi_frame *frame = g_malloc(frame_size + sizeof(struct fpi_frame)); G_DEBUG_HERE(); unsigned short min = 0xffff, max = 0; for (int i = 0; i < frame_size; i++) { if (raw_frame[i] < min) min = raw_frame[i]; if (raw_frame[i] > max) max = raw_frame[i]; } unsigned short px; for (int i = 0; i < frame_size; i++) { px = raw_frame[i]; px = (px - min) * 0xff / (max - min); frame->data[i] = (unsigned char)px; } *frames = g_slist_prepend(*frames, frame); } static void elan_process_frame_thirds(unsigned short *raw_frame, GSList ** frames) { G_DEBUG_HERE(); unsigned int frame_size = assembling_ctx.frame_width * assembling_ctx.frame_height; struct fpi_frame *frame = g_malloc(frame_size + sizeof(struct fpi_frame)); unsigned short lvl0, lvl1, lvl2, lvl3; unsigned short *sorted = g_malloc(frame_size * sizeof(short)); memcpy(sorted, raw_frame, frame_size * sizeof(short)); qsort(sorted, frame_size, sizeof(short), cmp_short); lvl0 = sorted[0]; lvl1 = sorted[frame_size * 3 / 10]; lvl2 = sorted[frame_size * 65 / 100]; lvl3 = sorted[frame_size - 1]; g_free(sorted); unsigned short px; for (int i = 0; i < frame_size; i++) { px = raw_frame[i]; if (lvl0 <= px && px < lvl1) px = (px - lvl0) * 99 / (lvl1 - lvl0); else if (lvl1 <= px && px < lvl2) px = 99 + ((px - lvl1) * 56 / (lvl2 - lvl1)); else // (lvl2 <= px && px <= lvl3) px = 155 + ((px - lvl2) * 100 / (lvl3 - lvl2)); frame->data[i] = (unsigned char)px; } *frames = g_slist_prepend(*frames, frame); } static void elan_submit_image(struct fp_img_dev *dev) { struct elan_dev *elandev = fpi_imgdev_get_user_data(dev); GSList *frames = NULL; struct fp_img *img; G_DEBUG_HERE(); for (int i = 0; i < ELAN_SKIP_LAST_FRAMES; i++) elandev->frames = g_slist_next(elandev->frames); assembling_ctx.frame_width = elandev->frame_width; assembling_ctx.frame_height = elandev->frame_height; assembling_ctx.image_width = elandev->frame_width * 3 / 2; g_slist_foreach(elandev->frames, (GFunc) elandev->process_frame, &frames); fpi_do_movement_estimation(&assembling_ctx, frames, elandev->num_frames - ELAN_SKIP_LAST_FRAMES); img = fpi_assemble_frames(&assembling_ctx, frames, elandev->num_frames - ELAN_SKIP_LAST_FRAMES); img->flags |= FP_IMG_PARTIAL; fpi_imgdev_image_captured(dev, img); } static void elan_cmd_done(struct fpi_ssm *ssm) { G_DEBUG_HERE(); fpi_ssm_next_state(ssm); } static void elan_cmd_cb(struct libusb_transfer *transfer) { struct fpi_ssm *ssm = transfer->user_data; struct fp_img_dev *dev = fpi_ssm_get_user_data(ssm); struct elan_dev *elandev = fpi_imgdev_get_user_data(dev); G_DEBUG_HERE(); elandev->cur_transfer = NULL; switch (transfer->status) { case LIBUSB_TRANSFER_COMPLETED: if (transfer->length != transfer->actual_length) { fp_dbg("transfer length error: expected %d, got %d", transfer->length, transfer->actual_length); elan_dev_reset(elandev); fpi_ssm_mark_aborted(ssm, -EPROTO); } else if (transfer->endpoint & LIBUSB_ENDPOINT_IN) /* just finished receiving */ elan_cmd_done(ssm); else /* just finished sending */ elan_cmd_read(ssm); break; case LIBUSB_TRANSFER_CANCELLED: fp_dbg("transfer cancelled"); fpi_ssm_mark_aborted(ssm, -ECANCELED); break; case LIBUSB_TRANSFER_TIMED_OUT: fp_dbg("transfer timed out"); fpi_ssm_mark_aborted(ssm, -ETIMEDOUT); break; default: fp_dbg("transfer failed: %d", transfer->status); elan_dev_reset(elandev); fpi_ssm_mark_aborted(ssm, -EIO); } } static void elan_cmd_read(struct fpi_ssm *ssm) { struct fp_img_dev *dev = fpi_ssm_get_user_data(ssm); struct elan_dev *elandev = fpi_imgdev_get_user_data(dev); int response_len = elandev->cmd->response_len; G_DEBUG_HERE(); if (elandev->cmd->response_len == ELAN_CMD_SKIP_READ) { fp_dbg("skipping read, not expecting anything"); elan_cmd_done(ssm); return; } if (elandev->cmd->cmd == get_image_cmd.cmd) /* raw data has 2-byte "pixels" and the frame is vertical */ response_len = elandev->raw_frame_width * elandev->frame_width * 2; struct libusb_transfer *transfer = libusb_alloc_transfer(0); if (!transfer) { fpi_ssm_mark_aborted(ssm, -ENOMEM); return; } elandev->cur_transfer = transfer; g_free(elandev->last_read); elandev->last_read = g_malloc(response_len); libusb_fill_bulk_transfer(transfer, fpi_imgdev_get_usb_dev(dev), elandev->cmd->response_in, elandev->last_read, response_len, elan_cmd_cb, ssm, elandev->cmd_timeout); transfer->flags = LIBUSB_TRANSFER_FREE_TRANSFER; int r = libusb_submit_transfer(transfer); if (r < 0) fpi_ssm_mark_aborted(ssm, r); } static void elan_run_cmd(struct fpi_ssm *ssm, const struct elan_cmd *cmd, int cmd_timeout) { struct fp_img_dev *dev = fpi_ssm_get_user_data(ssm); struct elan_dev *elandev = fpi_imgdev_get_user_data(dev); dbg_buf(cmd->cmd, 2); elandev->cmd = cmd; if (cmd_timeout != -1) elandev->cmd_timeout = cmd_timeout; if (elandev->dev_type && cmd->devices && !(cmd->devices & elandev->dev_type)) { fp_dbg("skipping for this device"); elan_cmd_done(ssm); return; } struct libusb_transfer *transfer = libusb_alloc_transfer(0); if (!transfer) { fpi_ssm_mark_aborted(ssm, -ENOMEM); return; } elandev->cur_transfer = transfer; libusb_fill_bulk_transfer(transfer, fpi_imgdev_get_usb_dev(dev), ELAN_EP_CMD_OUT, (char *) cmd->cmd, ELAN_CMD_LEN, elan_cmd_cb, ssm, elandev->cmd_timeout); transfer->flags = LIBUSB_TRANSFER_FREE_TRANSFER; int r = libusb_submit_transfer(transfer); if (r < 0) fpi_ssm_mark_aborted(ssm, r); } enum deactivate_states { DEACTIVATE, DEACTIVATE_NUM_STATES, }; static void deactivate_run_state(struct fpi_ssm *ssm) { G_DEBUG_HERE(); switch (fpi_ssm_get_cur_state(ssm)) { case DEACTIVATE: elan_run_cmd(ssm, &stop_cmd, ELAN_CMD_TIMEOUT); break; } } static void deactivate_complete(struct fpi_ssm *ssm) { struct fp_img_dev *dev = fpi_ssm_get_user_data(ssm); G_DEBUG_HERE(); fpi_imgdev_deactivate_complete(dev); } static void elan_deactivate(struct fp_img_dev *dev) { struct elan_dev *elandev = fpi_imgdev_get_user_data(dev); G_DEBUG_HERE(); elan_dev_reset(elandev); struct fpi_ssm *ssm = fpi_ssm_new(fpi_imgdev_get_dev(dev), deactivate_run_state, DEACTIVATE_NUM_STATES); fpi_ssm_set_user_data(ssm, dev); fpi_ssm_start(ssm, deactivate_complete); } enum capture_states { CAPTURE_LED_ON, CAPTURE_WAIT_FINGER, CAPTURE_READ_DATA, CAPTURE_CHECK_ENOUGH_FRAMES, CAPTURE_NUM_STATES, }; static void capture_run_state(struct fpi_ssm *ssm) { struct fp_img_dev *dev = fpi_ssm_get_user_data(ssm); struct elan_dev *elandev = fpi_imgdev_get_user_data(dev); switch (fpi_ssm_get_cur_state(ssm)) { case CAPTURE_LED_ON: elan_run_cmd(ssm, &led_on_cmd, ELAN_CMD_TIMEOUT); break; case CAPTURE_WAIT_FINGER: elan_run_cmd(ssm, &pre_scan_cmd, -1); break; case CAPTURE_READ_DATA: /* 0x55 - finger present * 0xff - device not calibrated (probably) */ if (elandev->last_read && elandev->last_read[0] == 0x55) { fpi_imgdev_report_finger_status(dev, TRUE); elan_run_cmd(ssm, &get_image_cmd, ELAN_CMD_TIMEOUT); } else fpi_ssm_mark_aborted(ssm, FP_VERIFY_RETRY); break; case CAPTURE_CHECK_ENOUGH_FRAMES: elan_save_img_frame(elandev); if (elandev->num_frames < ELAN_MAX_FRAMES) { /* quickly stop if finger is removed */ elandev->cmd_timeout = ELAN_FINGER_TIMEOUT; fpi_ssm_jump_to_state(ssm, CAPTURE_WAIT_FINGER); } else { fpi_ssm_next_state(ssm); } break; } } static void elan_capture_async(void *data) { elan_capture((struct fp_img_dev *)data); } static void capture_complete(struct fpi_ssm *ssm) { struct fp_img_dev *dev = fpi_ssm_get_user_data(ssm); struct elan_dev *elandev = fpi_imgdev_get_user_data(dev); G_DEBUG_HERE(); if (elandev->deactivating) elan_deactivate(dev); /* either max frames captured or timed out waiting for the next frame */ else if (!fpi_ssm_get_error(ssm) || (fpi_ssm_get_error(ssm) == -ETIMEDOUT && fpi_ssm_get_cur_state(ssm) == CAPTURE_WAIT_FINGER)) if (elandev->num_frames >= ELAN_MIN_FRAMES) { elan_submit_image(dev); fpi_imgdev_report_finger_status(dev, FALSE); } else { fp_dbg("swipe too short: want >= %d frames, got %d", ELAN_MIN_FRAMES, elandev->num_frames); fpi_imgdev_session_error(dev, FP_VERIFY_RETRY_TOO_SHORT); } /* other error * It says "...session_error" but repotring 1 during verification * makes it successful! */ else fpi_imgdev_session_error(dev, FP_VERIFY_NO_MATCH); /* When enrolling the lib won't restart the capture after a stage has * completed, so we need to keep feeding it images till it's had enough. * But after that it can't finalize enrollemnt until this callback exits. * That's why we schedule elan_capture instead of running it directly. */ if (fpi_dev_get_dev_state(fpi_imgdev_get_dev(dev)) == DEV_STATE_ENROLLING && !fpi_timeout_add(10, elan_capture_async, dev)) fpi_imgdev_session_error(dev, -ETIME); fpi_ssm_free(ssm); } static void elan_capture(struct fp_img_dev *dev) { struct elan_dev *elandev = fpi_imgdev_get_user_data(dev); G_DEBUG_HERE(); elan_dev_reset(elandev); struct fpi_ssm *ssm = fpi_ssm_new(fpi_imgdev_get_dev(dev), capture_run_state, CAPTURE_NUM_STATES); fpi_ssm_set_user_data(ssm, dev); fpi_ssm_start(ssm, capture_complete); } static void fpi_ssm_next_state_async(void *data) { fpi_ssm_next_state((struct fpi_ssm *)data); } /* this function needs to have elandev->background and elandev->last_read to be * the calibration mean */ static int elan_need_calibration(struct elan_dev *elandev) { G_DEBUG_HERE(); unsigned short calib_mean = elandev->last_read[0] * 0xff + elandev->last_read[1]; unsigned int bg_mean = 0, delta; unsigned int frame_size = elandev->frame_width * elandev->frame_height; for (int i = 0; i < frame_size; i++) bg_mean += elandev->background[i]; bg_mean /= frame_size; delta = bg_mean > calib_mean ? bg_mean - calib_mean : calib_mean - bg_mean; fp_dbg("calibration mean: %d, bg mean: %d, delta: %d", calib_mean, bg_mean, delta); return delta > ELAN_CALIBRATION_MAX_DELTA ? 1 : 0; } enum calibrate_states { CALIBRATE_GET_BACKGROUND, CALIBRATE_SAVE_BACKGROUND, CALIBRATE_GET_MEAN, CALIBRATE_CHECK_NEEDED, CALIBRATE_GET_STATUS, CALIBRATE_CHECK_STATUS, CALIBRATE_REPEAT_STATUS, CALIBRATE_NUM_STATES, }; static void calibrate_run_state(struct fpi_ssm *ssm) { struct fp_img_dev *dev = fpi_ssm_get_user_data(ssm); struct elan_dev *elandev = fpi_imgdev_get_user_data(dev); G_DEBUG_HERE(); switch (fpi_ssm_get_cur_state(ssm)) { case CALIBRATE_GET_BACKGROUND: elan_run_cmd(ssm, &get_image_cmd, ELAN_CMD_TIMEOUT); break; case CALIBRATE_SAVE_BACKGROUND: elan_save_background(elandev); if (elandev->fw_ver < ELAN_MIN_CALIBRATION_FW) { fp_dbg("FW does not support calibration"); fpi_ssm_mark_completed(ssm); } else fpi_ssm_next_state(ssm); break; case CALIBRATE_GET_MEAN: elan_run_cmd(ssm, &get_calib_mean_cmd, ELAN_CMD_TIMEOUT); break; case CALIBRATE_CHECK_NEEDED: if (elan_need_calibration(elandev)) { elandev->calib_status = 0; fpi_ssm_next_state(ssm); } else fpi_ssm_mark_completed(ssm); break; case CALIBRATE_GET_STATUS: elandev->calib_atts_left -= 1; if (elandev->calib_atts_left) elan_run_cmd(ssm, &get_calib_status_cmd, ELAN_CMD_TIMEOUT); else { fp_dbg("calibration failed"); fpi_ssm_mark_aborted(ssm, -1); } break; case CALIBRATE_CHECK_STATUS: /* 0x01 - retry, 0x03 - ok * It appears that when reading the response soon after 0x4023 the device * can return 0x03, and only after some time (up to 100 ms) the response * changes to 0x01. It stays that way for some time and then changes back * to 0x03. Because of this we don't just expect 0x03, we want to see 0x01 * first. This is to make sure that a full calibration loop has completed */ fp_dbg("calibration status: 0x%02x", elandev->last_read[0]); if (elandev->calib_status == 0x01 && elandev->last_read[0] == 0x03) { elandev->calib_status = 0x03; fpi_ssm_jump_to_state(ssm, CALIBRATE_GET_BACKGROUND); } else { if (elandev->calib_status == 0x00 && elandev->last_read[0] == 0x01) elandev->calib_status = 0x01; if (!fpi_timeout_add(50, fpi_ssm_next_state_async, ssm)) fpi_ssm_mark_aborted(ssm, -ETIME); } break; case CALIBRATE_REPEAT_STATUS: fpi_ssm_jump_to_state(ssm, CALIBRATE_GET_STATUS); break; } } static void calibrate_complete(struct fpi_ssm *ssm) { struct fp_img_dev *dev = fpi_ssm_get_user_data(ssm); struct elan_dev *elandev = fpi_imgdev_get_user_data(dev); G_DEBUG_HERE(); if (elandev->deactivating) elan_deactivate(dev); else if (fpi_ssm_get_error(ssm)) fpi_imgdev_activate_complete(dev, fpi_ssm_get_error(ssm)); else { fpi_imgdev_activate_complete(dev, 0); elan_capture(dev); } fpi_ssm_free(ssm); } static void elan_calibrate(struct fp_img_dev *dev) { struct elan_dev *elandev = fpi_imgdev_get_user_data(dev); G_DEBUG_HERE(); elan_dev_reset(elandev); elandev->calib_atts_left = ELAN_CALIBRATION_ATTEMPTS; struct fpi_ssm *ssm = fpi_ssm_new(fpi_imgdev_get_dev(dev), calibrate_run_state, CALIBRATE_NUM_STATES); fpi_ssm_set_user_data(ssm, dev); fpi_ssm_start(ssm, calibrate_complete); } enum activate_states { ACTIVATE_GET_FW_VER, ACTIVATE_SET_FW_VER, ACTIVATE_GET_SENSOR_DIM, ACTIVATE_SET_SENSOR_DIM, ACTIVATE_CMD_1, ACTIVATE_NUM_STATES, }; static void activate_run_state(struct fpi_ssm *ssm) { struct fp_img_dev *dev = fpi_ssm_get_user_data(ssm); struct elan_dev *elandev = fpi_imgdev_get_user_data(dev); G_DEBUG_HERE(); switch (fpi_ssm_get_cur_state(ssm)) { case ACTIVATE_GET_FW_VER: elan_run_cmd(ssm, &get_fw_ver_cmd, ELAN_CMD_TIMEOUT); break; case ACTIVATE_SET_FW_VER: elandev->fw_ver = (elandev->last_read[0] << 8 | elandev->last_read[1]); fp_dbg("FW ver 0x%04hx", elandev->fw_ver); fpi_ssm_next_state(ssm); break; case ACTIVATE_GET_SENSOR_DIM: elan_run_cmd(ssm, &get_sensor_dim_cmd, ELAN_CMD_TIMEOUT); break; case ACTIVATE_SET_SENSOR_DIM: elandev->frame_width = elandev->last_read[2]; elandev->raw_frame_width = elandev->last_read[0]; if (elandev->raw_frame_width < ELAN_MAX_FRAME_HEIGHT) elandev->frame_height = elandev->raw_frame_width; else elandev->frame_height = ELAN_MAX_FRAME_HEIGHT; /* see elan_save_frame for why it's width x raw_width */ fp_dbg("sensor dimensions, WxH: %dx%d", elandev->frame_width, elandev->raw_frame_width); fpi_ssm_next_state(ssm); break; case ACTIVATE_CMD_1: /* TODO: find out what this does, if we need it */ elan_run_cmd(ssm, &activate_cmd_1, ELAN_CMD_TIMEOUT); break; } } static void activate_complete(struct fpi_ssm *ssm) { struct fp_img_dev *dev = fpi_ssm_get_user_data(ssm); struct elan_dev *elandev = fpi_imgdev_get_user_data(dev); G_DEBUG_HERE(); if (elandev->deactivating) elan_deactivate(dev); else if (fpi_ssm_get_error(ssm)) fpi_imgdev_activate_complete(dev, fpi_ssm_get_error(ssm)); else elan_calibrate(dev); fpi_ssm_free(ssm); } static void elan_activate(struct fp_img_dev *dev) { struct elan_dev *elandev = fpi_imgdev_get_user_data(dev); G_DEBUG_HERE(); elan_dev_reset(elandev); struct fpi_ssm *ssm = fpi_ssm_new(fpi_imgdev_get_dev(dev), activate_run_state, ACTIVATE_NUM_STATES); fpi_ssm_set_user_data(ssm, dev); fpi_ssm_start(ssm, activate_complete); } static int dev_init(struct fp_img_dev *dev, unsigned long driver_data) { struct elan_dev *elandev; int r; G_DEBUG_HERE(); r = libusb_claim_interface(fpi_imgdev_get_usb_dev(dev), 0); if (r < 0) { fp_err("could not claim interface 0: %s", libusb_error_name(r)); return r; } elandev = g_malloc0(sizeof(struct elan_dev)); fpi_imgdev_set_user_data(dev, elandev); /* common params */ elandev->dev_type = driver_data; elandev->background = NULL; elandev->process_frame = elan_process_frame_thirds; switch (driver_data) { case ELAN_0907: elandev->process_frame = elan_process_frame_linear; break; } fpi_imgdev_open_complete(dev, 0); return 0; } static void dev_deinit(struct fp_img_dev *dev) { struct elan_dev *elandev = fpi_imgdev_get_user_data(dev); G_DEBUG_HERE(); elan_dev_reset(elandev); g_free(elandev->background); g_free(elandev); libusb_release_interface(fpi_imgdev_get_usb_dev(dev), 0); fpi_imgdev_close_complete(dev); } static int dev_activate(struct fp_img_dev *dev, enum fp_imgdev_state state) { G_DEBUG_HERE(); elan_activate(dev); return 0; } static void dev_deactivate(struct fp_img_dev *dev) { struct elan_dev *elandev = fpi_imgdev_get_user_data(dev); G_DEBUG_HERE(); elandev->deactivating = TRUE; if (elandev->cur_transfer) libusb_cancel_transfer(elandev->cur_transfer); else elan_deactivate(dev); } struct fp_img_driver elan_driver = { .driver = { .id = ELAN_ID, .name = FP_COMPONENT, .full_name = "ElanTech Fingerprint Sensor", .id_table = elan_id_table, .scan_type = FP_SCAN_TYPE_SWIPE, }, .flags = 0, .bz3_threshold = 22, .open = dev_init, .close = dev_deinit, .activate = dev_activate, .deactivate = dev_deactivate, };