elan: Change command structs, support more devices

Make each command a separate struct to get finer control over which
commands are called on which devices. Update ssm's accordingly. Add
sensor_reset and fuse_load commands.

Support 0x0903, 0x0c03, 0x0c16, 0x0c1a, 0x0c26
This commit is contained in:
Igor Filatov 2018-01-16 18:04:31 +02:00 committed by Bastien Nocera
parent 9843c2d764
commit bccff4ffef
2 changed files with 368 additions and 213 deletions

View file

@ -2,6 +2,7 @@
* Elan driver for libfprint * Elan driver for libfprint
* *
* Copyright (C) 2017 Igor Filatov <ia.filatov@gmail.com> * Copyright (C) 2017 Igor Filatov <ia.filatov@gmail.com>
* Copyright (C) 2018 Sébastien Béchet <sebastien.bechet@osinix.com >
* *
* This library is free software; you can redistribute it and/or * This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public * modify it under the terms of the GNU Lesser General Public
@ -38,20 +39,29 @@ static struct fpi_frame_asmbl_ctx assembling_ctx = {
}; };
struct elan_dev { struct elan_dev {
gboolean deactivating; /* device config */
unsigned short dev_type;
/* number of pixels to discard on left and right (along raw image height)
* because they have different intensity from the rest of the frame */
unsigned char frame_margin;
/* end device config */
const struct elan_cmd *cmds; /* commands */
size_t cmds_len; const struct elan_cmd *cmd;
int cmd_idx;
int cmd_timeout; int cmd_timeout;
struct libusb_transfer *cur_transfer; struct libusb_transfer *cur_transfer;
/* end commands */
/* state */
gboolean deactivating;
unsigned char calib_atts_left;
unsigned char *last_read; unsigned char *last_read;
unsigned char frame_width; unsigned char frame_width;
unsigned char frame_height; unsigned char frame_height;
unsigned char raw_frame_width; unsigned char raw_frame_width;
int num_frames; int num_frames;
GSList *frames; GSList *frames;
/* end state */
}; };
static void elan_dev_reset(struct elan_dev *elandev) static void elan_dev_reset(struct elan_dev *elandev)
@ -62,8 +72,7 @@ static void elan_dev_reset(struct elan_dev *elandev)
elandev->deactivating = FALSE; elandev->deactivating = FALSE;
elandev->cmds = NULL; elandev->cmd = NULL;
elandev->cmd_idx = 0;
elandev->cmd_timeout = ELAN_CMD_TIMEOUT; elandev->cmd_timeout = ELAN_CMD_TIMEOUT;
g_free(elandev->last_read); g_free(elandev->last_read);
@ -86,13 +95,13 @@ static void elan_save_frame(struct fp_img_dev *dev)
/* Raw images are vertical and perpendicular to swipe direction of a /* Raw images are vertical and perpendicular to swipe direction of a
* normalized image, which means we need to make them horizontal before * normalized image, which means we need to make them horizontal before
* assembling. We also discard stirpes of ELAN_FRAME_MARGIN along raw * assembling. We also discard stripes of 'frame_margin' along raw
* height. */ * height. */
for (int y = 0; y < raw_height; y++) for (int y = 0; y < raw_height; y++)
for (int x = ELAN_FRAME_MARGIN; for (int x = elandev->frame_margin;
x < raw_width - ELAN_FRAME_MARGIN; x++) { x < raw_width - elandev->frame_margin; x++) {
int frame_idx = int frame_idx =
y + (x - ELAN_FRAME_MARGIN) * raw_height; y + (x - elandev->frame_margin) * raw_height;
int raw_idx = x + y * raw_width; int raw_idx = x + y * raw_width;
frame[frame_idx] = frame[frame_idx] =
((unsigned short *)elandev->last_read)[raw_idx]; ((unsigned short *)elandev->last_read)[raw_idx];
@ -161,15 +170,7 @@ static void elan_submit_image(struct fp_img_dev *dev)
static void elan_cmd_done(struct fpi_ssm *ssm) static void elan_cmd_done(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(); G_DEBUG_HERE();
elandev->cmd_idx += 1;
if (elandev->cmd_idx < elandev->cmds_len)
elan_run_next_cmd(ssm);
else
fpi_ssm_next_state(ssm); fpi_ssm_next_state(ssm);
} }
@ -186,19 +187,16 @@ static void elan_cmd_cb(struct libusb_transfer *transfer)
switch (transfer->status) { switch (transfer->status) {
case LIBUSB_TRANSFER_COMPLETED: case LIBUSB_TRANSFER_COMPLETED:
if (transfer->length != transfer->actual_length) { if (transfer->length != transfer->actual_length) {
fp_dbg("unexpected transfer length"); fp_dbg("transfer length error: expected %d, got %d",
transfer->length, transfer->actual_length);
elan_dev_reset(elandev); elan_dev_reset(elandev);
fpi_ssm_mark_aborted(ssm, -EPROTO); fpi_ssm_mark_aborted(ssm, -EPROTO);
} else if (transfer->endpoint & LIBUSB_ENDPOINT_IN) } else if (transfer->endpoint & LIBUSB_ENDPOINT_IN)
/* just finished receiving */ /* just finished receiving */
elan_cmd_done(ssm); elan_cmd_done(ssm);
else {
/* just finished sending */
if (elandev->cmds[elandev->cmd_idx].response_len)
elan_cmd_read(ssm);
else else
elan_cmd_done(ssm); /* just finished sending */
} elan_cmd_read(ssm);
break; break;
case LIBUSB_TRANSFER_CANCELLED: case LIBUSB_TRANSFER_CANCELLED:
fp_dbg("transfer cancelled"); fp_dbg("transfer cancelled");
@ -206,7 +204,6 @@ static void elan_cmd_cb(struct libusb_transfer *transfer)
break; break;
case LIBUSB_TRANSFER_TIMED_OUT: case LIBUSB_TRANSFER_TIMED_OUT:
fp_dbg("transfer timed out"); fp_dbg("transfer timed out");
// elan_dev_reset(elandev);
fpi_ssm_mark_aborted(ssm, -ETIMEDOUT); fpi_ssm_mark_aborted(ssm, -ETIMEDOUT);
break; break;
default: default:
@ -220,11 +217,17 @@ static void elan_cmd_read(struct fpi_ssm *ssm)
{ {
struct fp_img_dev *dev = fpi_ssm_get_user_data(ssm); struct fp_img_dev *dev = fpi_ssm_get_user_data(ssm);
struct elan_dev *elandev = fpi_imgdev_get_user_data(dev); struct elan_dev *elandev = fpi_imgdev_get_user_data(dev);
int response_len = elandev->cmds[elandev->cmd_idx].response_len; int response_len = elandev->cmd->response_len;
G_DEBUG_HERE(); G_DEBUG_HERE();
if (elandev->cmds[elandev->cmd_idx].cmd == read_cmds[0].cmd) if (!(elandev->cmd->response_len)) {
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 */ /* raw data has 2-byte "pixels" and the frame is vertical */
response_len = response_len =
elandev->raw_frame_width * elandev->frame_width * 2; elandev->raw_frame_width * elandev->frame_width * 2;
@ -240,7 +243,7 @@ static void elan_cmd_read(struct fpi_ssm *ssm)
elandev->last_read = g_malloc(response_len); elandev->last_read = g_malloc(response_len);
libusb_fill_bulk_transfer(transfer, fpi_imgdev_get_usb_dev(dev), libusb_fill_bulk_transfer(transfer, fpi_imgdev_get_usb_dev(dev),
elandev->cmds[elandev->cmd_idx].response_in, elandev->cmd->response_in,
elandev->last_read, response_len, elan_cmd_cb, elandev->last_read, response_len, elan_cmd_cb,
ssm, elandev->cmd_timeout); ssm, elandev->cmd_timeout);
transfer->flags = LIBUSB_TRANSFER_FREE_TRANSFER; transfer->flags = LIBUSB_TRANSFER_FREE_TRANSFER;
@ -249,12 +252,21 @@ static void elan_cmd_read(struct fpi_ssm *ssm)
fpi_ssm_mark_aborted(ssm, r); fpi_ssm_mark_aborted(ssm, r);
} }
static void elan_run_next_cmd(struct fpi_ssm *ssm) 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 fp_img_dev *dev = fpi_ssm_get_user_data(ssm);
struct elan_dev *elandev = fpi_imgdev_get_user_data(dev); struct elan_dev *elandev = fpi_imgdev_get_user_data(dev);
G_DEBUG_HERE(); elandev->cmd = cmd;
if (cmd_timeout != -1)
elandev->cmd_timeout = cmd_timeout;
if (!(cmd->devices & elandev->dev_type)) {
fp_dbg("skipping for this device");
elan_cmd_done(ssm);
return;
}
struct libusb_transfer *transfer = libusb_alloc_transfer(0); struct libusb_transfer *transfer = libusb_alloc_transfer(0);
if (!transfer) { if (!transfer) {
@ -264,31 +276,12 @@ static void elan_run_next_cmd(struct fpi_ssm *ssm)
elandev->cur_transfer = transfer; elandev->cur_transfer = transfer;
libusb_fill_bulk_transfer(transfer, fpi_imgdev_get_usb_dev(dev), ELAN_EP_CMD_OUT, libusb_fill_bulk_transfer(transfer, fpi_imgdev_get_usb_dev(dev), ELAN_EP_CMD_OUT,
(unsigned char *)elandev->cmds[elandev-> (char *) cmd->cmd, ELAN_CMD_LEN, elan_cmd_cb, ssm,
cmd_idx].cmd,
ELAN_CMD_LEN, elan_cmd_cb, ssm,
elandev->cmd_timeout); elandev->cmd_timeout);
transfer->flags = LIBUSB_TRANSFER_FREE_TRANSFER; transfer->flags = LIBUSB_TRANSFER_FREE_TRANSFER;
int r = libusb_submit_transfer(transfer); int r = libusb_submit_transfer(transfer);
if (r < 0) if (r < 0)
fpi_ssm_mark_aborted(ssm, r); fpi_ssm_mark_aborted(ssm, r);
}
static void elan_run_cmds(struct fpi_ssm *ssm, const struct elan_cmd *cmds,
size_t cmds_len, int cmd_timeout)
{
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->cmds = cmds;
elandev->cmds_len = cmds_len;
elandev->cmd_idx = 0;
if (cmd_timeout != -1)
elandev->cmd_timeout = cmd_timeout;
elan_run_next_cmd(ssm);
} }
enum deactivate_states { enum deactivate_states {
@ -296,12 +289,13 @@ enum deactivate_states {
DEACTIVATE_NUM_STATES, DEACTIVATE_NUM_STATES,
}; };
static void elan_deactivate_run_state(struct fpi_ssm *ssm) static void deactivate_run_state(struct fpi_ssm *ssm)
{ {
G_DEBUG_HERE();
switch (fpi_ssm_get_cur_state(ssm)) { switch (fpi_ssm_get_cur_state(ssm)) {
case DEACTIVATE: case DEACTIVATE:
elan_run_cmds(ssm, deactivate_cmds, deactivate_cmds_len, elan_run_cmd(ssm, &stop_cmd, ELAN_CMD_TIMEOUT);
ELAN_CMD_TIMEOUT);
break; break;
} }
} }
@ -310,6 +304,8 @@ static void deactivate_complete(struct fpi_ssm *ssm)
{ {
struct fp_img_dev *dev = fpi_ssm_get_user_data(ssm); struct fp_img_dev *dev = fpi_ssm_get_user_data(ssm);
G_DEBUG_HERE();
fpi_imgdev_deactivate_complete(dev); fpi_imgdev_deactivate_complete(dev);
} }
@ -321,50 +317,49 @@ static void elan_deactivate(struct fp_img_dev *dev)
elan_dev_reset(elandev); elan_dev_reset(elandev);
struct fpi_ssm *ssm = fpi_ssm_new(fpi_imgdev_get_dev(dev), elan_deactivate_run_state, struct fpi_ssm *ssm = fpi_ssm_new(fpi_imgdev_get_dev(dev), deactivate_run_state,
DEACTIVATE_NUM_STATES); DEACTIVATE_NUM_STATES);
fpi_ssm_set_user_data(ssm, dev); fpi_ssm_set_user_data(ssm, dev);
fpi_ssm_start(ssm, deactivate_complete); fpi_ssm_start(ssm, deactivate_complete);
} }
enum capture_states { enum capture_states {
CAPTURE_START, CAPTURE_LED_ON,
CAPTURE_WAIT_FINGER, CAPTURE_WAIT_FINGER,
CAPTURE_READ_DATA, CAPTURE_READ_DATA,
CAPTURE_SAVE_FRAME, CAPTURE_CHECK_ENOUGH_FRAMES,
CAPTURE_NUM_STATES, CAPTURE_NUM_STATES,
}; };
static void elan_capture_run_state(struct fpi_ssm *ssm) static void capture_run_state(struct fpi_ssm *ssm)
{ {
struct fp_img_dev *dev = fpi_ssm_get_user_data(ssm); struct fp_img_dev *dev = fpi_ssm_get_user_data(ssm);
struct elan_dev *elandev = fpi_imgdev_get_user_data(dev); struct elan_dev *elandev = fpi_imgdev_get_user_data(dev);
switch (fpi_ssm_get_cur_state(ssm)) { switch (fpi_ssm_get_cur_state(ssm)) {
case CAPTURE_START: case CAPTURE_LED_ON:
elan_run_cmds(ssm, capture_start_cmds, capture_start_cmds_len, elan_run_cmd(ssm, &led_on_cmd, ELAN_CMD_TIMEOUT);
ELAN_CMD_TIMEOUT);
break; break;
case CAPTURE_WAIT_FINGER: case CAPTURE_WAIT_FINGER:
elan_run_cmds(ssm, capture_wait_finger_cmds, elan_run_cmd(ssm, &pre_scan_cmd, -1);
capture_wait_finger_cmds_len, -1);
break; break;
case CAPTURE_READ_DATA: case CAPTURE_READ_DATA:
/* 0x55 - finger present /* 0x55 - finger present
* 0xff - device not calibrated */ * 0xff - device not calibrated (probably) */
if (elandev->last_read && elandev->last_read[0] == 0x55) { if (elandev->last_read && elandev->last_read[0] == 0x55) {
fpi_imgdev_report_finger_status(dev, TRUE); fpi_imgdev_report_finger_status(dev, TRUE);
elan_run_cmds(ssm, read_cmds, read_cmds_len, elan_run_cmd(ssm, &get_image_cmd, ELAN_CMD_TIMEOUT);
ELAN_CMD_TIMEOUT);
} else } else
fpi_ssm_mark_aborted(ssm, FP_VERIFY_RETRY); fpi_ssm_mark_aborted(ssm, FP_VERIFY_RETRY);
break; break;
case CAPTURE_SAVE_FRAME: case CAPTURE_CHECK_ENOUGH_FRAMES:
elan_save_frame(dev); elan_save_frame(dev);
if (elandev->num_frames < ELAN_MAX_FRAMES) { if (elandev->num_frames < ELAN_MAX_FRAMES) {
/* quickly stop if finger is removed */ /* quickly stop if finger is removed */
elandev->cmd_timeout = ELAN_FINGER_TIMEOUT; elandev->cmd_timeout = ELAN_FINGER_TIMEOUT;
fpi_ssm_jump_to_state(ssm, CAPTURE_WAIT_FINGER); fpi_ssm_jump_to_state(ssm, CAPTURE_WAIT_FINGER);
} else {
fpi_ssm_next_state(ssm);
} }
break; break;
} }
@ -392,9 +387,12 @@ static void capture_complete(struct fpi_ssm *ssm)
if (elandev->num_frames >= ELAN_MIN_FRAMES) { if (elandev->num_frames >= ELAN_MIN_FRAMES) {
elan_submit_image(dev); elan_submit_image(dev);
fpi_imgdev_report_finger_status(dev, FALSE); fpi_imgdev_report_finger_status(dev, FALSE);
} else } else {
fp_dbg("swipe too short: want >= %d frames, got %d",
ELAN_MIN_FRAMES, elandev->num_frames);
fpi_imgdev_session_error(dev, fpi_imgdev_session_error(dev,
FP_VERIFY_RETRY_TOO_SHORT); FP_VERIFY_RETRY_TOO_SHORT);
}
/* other error /* other error
* It says "...session_error" but repotring 1 during verification * It says "...session_error" but repotring 1 during verification
@ -421,37 +419,98 @@ static void elan_capture(struct fp_img_dev *dev)
elan_dev_reset(elandev); elan_dev_reset(elandev);
struct fpi_ssm *ssm = struct fpi_ssm *ssm =
fpi_ssm_new(fpi_imgdev_get_dev(dev), elan_capture_run_state, CAPTURE_NUM_STATES); fpi_ssm_new(fpi_imgdev_get_dev(dev), capture_run_state, CAPTURE_NUM_STATES);
fpi_ssm_set_user_data(ssm, dev); fpi_ssm_set_user_data(ssm, dev);
fpi_ssm_start(ssm, capture_complete); 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 last_read to be the calibration mean and at least
* one frame */
static int elan_need_calibration(struct elan_dev *elandev)
{
G_DEBUG_HERE();
if (elandev->dev_type & ELAN_0903) {
fp_dbg("don't know how to calibrate this device");
return 0;
}
unsigned short calib_mean =
elandev->last_read[0] * 0xff + elandev->last_read[1];
unsigned short *bg_data = ((GSList *) elandev->frames)->data;
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 += bg_data[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 { enum calibrate_states {
CALIBRATE_START_1, CALIBRATE_START,
CALIBRATE_READ_DATA_1, CALIBRATE_CHECK_RESULT,
CALIBRATE_END_1, CALIBRATE_REPEAT,
CALIBRATE_START_2, CALIBRATE_GET_BACKGROUND,
CALIBRATE_READ_DATA_2, CALIBRATE_SAVE_BACKGROUND,
CALIBRATE_END_2, CALIBRATE_GET_MEAN,
CALIBRATE_NUM_STATES, CALIBRATE_NUM_STATES,
}; };
static void elan_calibrate_run_state(struct fpi_ssm *ssm) 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)) { switch (fpi_ssm_get_cur_state(ssm)) {
case CALIBRATE_START_1: case CALIBRATE_START:
case CALIBRATE_START_2: elandev->calib_atts_left -= 1;
elan_run_cmds(ssm, calibrate_start_cmds, if (elandev->calib_atts_left)
calibrate_start_cmds_len, ELAN_CMD_TIMEOUT); elan_run_cmd(ssm, &run_calibration_cmd,
break;
case CALIBRATE_READ_DATA_1:
case CALIBRATE_READ_DATA_2:
elan_run_cmds(ssm, read_cmds, read_cmds_len, ELAN_CMD_TIMEOUT);
break;
case CALIBRATE_END_1:
case CALIBRATE_END_2:
elan_run_cmds(ssm, calibrate_end_cmds, calibrate_end_cmds_len,
ELAN_CMD_TIMEOUT); ELAN_CMD_TIMEOUT);
else {
fp_dbg("too many calibration attempts");
fpi_ssm_mark_aborted(ssm, -1);
}
break;
case CALIBRATE_CHECK_RESULT:
/* 0x01 - retry, 0x03 - ok
* but some devices send other responses so in order to avoid needless
* retries we don't check 0x3 but only retry on 0x1 (need to wait 50 ms) */
fp_dbg("calibration status: 0x%02x", elandev->last_read[0]);
if (elandev->last_read[0] == 0x01) {
if (!fpi_timeout_add(50, fpi_ssm_next_state_async, ssm))
fpi_ssm_mark_aborted(ssm, -ETIME);
} else
fpi_ssm_jump_to_state(ssm, CALIBRATE_GET_BACKGROUND);
break;
case CALIBRATE_REPEAT:
fpi_ssm_jump_to_state(ssm, CALIBRATE_START);
break;
case CALIBRATE_GET_BACKGROUND:
elan_run_cmd(ssm, &get_image_cmd, ELAN_CMD_TIMEOUT);
break;
case CALIBRATE_SAVE_BACKGROUND:
elan_save_frame(dev);
fpi_ssm_next_state(ssm);
break;
case CALIBRATE_GET_MEAN:
elan_run_cmd(ssm, &get_calib_mean_cmd, ELAN_CMD_TIMEOUT);
break;
} }
} }
@ -465,11 +524,14 @@ static void calibrate_complete(struct fpi_ssm *ssm)
if (elandev->deactivating) if (elandev->deactivating)
elan_deactivate(dev); elan_deactivate(dev);
else if (fpi_ssm_get_error(ssm)) else if (fpi_ssm_get_error(ssm))
fpi_imgdev_session_error(dev, fpi_ssm_get_error(ssm));
else {
fpi_imgdev_activate_complete(dev, fpi_ssm_get_error(ssm)); fpi_imgdev_activate_complete(dev, fpi_ssm_get_error(ssm));
else if (elan_need_calibration(elandev))
elan_calibrate(dev);
else {
fpi_imgdev_activate_complete(dev, 0);
elan_capture(dev); elan_capture(dev);
} }
fpi_ssm_free(ssm); fpi_ssm_free(ssm);
} }
@ -480,48 +542,67 @@ static void elan_calibrate(struct fp_img_dev *dev)
G_DEBUG_HERE(); G_DEBUG_HERE();
elan_dev_reset(elandev); elan_dev_reset(elandev);
struct fpi_ssm *ssm = fpi_ssm_new(fpi_imgdev_get_dev(dev), elan_calibrate_run_state, struct fpi_ssm *ssm = fpi_ssm_new(fpi_imgdev_get_dev(dev), calibrate_run_state,
CALIBRATE_NUM_STATES); CALIBRATE_NUM_STATES);
fpi_ssm_set_user_data(ssm, dev); fpi_ssm_set_user_data(ssm, dev);
fpi_ssm_start(ssm, calibrate_complete); fpi_ssm_start(ssm, calibrate_complete);
} }
enum activate_states { enum activate_states {
ACTIVATE_GET_FW_VER,
ACTIVATE_PRINT_FW_VER,
ACTIVATE_GET_SENSOR_DIM, ACTIVATE_GET_SENSOR_DIM,
ACTIVATE_SET_SENSOR_DIM, ACTIVATE_SET_SENSOR_DIM,
ACTIVATE_START, ACTIVATE_CMD_1,
ACTIVATE_READ_DATA, ACTIVATE_GET_BACKGROUND,
ACTIVATE_END, ACTIVATE_SAVE_BACKGROUND,
ACTIVATE_GET_MEAN,
ACTIVATE_NUM_STATES, ACTIVATE_NUM_STATES,
}; };
static void elan_activate_run_state(struct fpi_ssm *ssm) static void activate_run_state(struct fpi_ssm *ssm)
{ {
struct fp_img_dev *dev = fpi_ssm_get_user_data(ssm); struct fp_img_dev *dev = fpi_ssm_get_user_data(ssm);
struct elan_dev *elandev = fpi_imgdev_get_user_data(dev); struct elan_dev *elandev = fpi_imgdev_get_user_data(dev);
G_DEBUG_HERE();
switch (fpi_ssm_get_cur_state(ssm)) { 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_PRINT_FW_VER:
fp_dbg("FW ver %d.%d", elandev->last_read[0],
elandev->last_read[1]);
fpi_ssm_next_state(ssm);
break;
case ACTIVATE_GET_SENSOR_DIM: case ACTIVATE_GET_SENSOR_DIM:
elan_run_cmds(ssm, get_sensor_dim_cmds, get_sensor_dim_cmds_len, elan_run_cmd(ssm, &get_sensor_dim_cmd, ELAN_CMD_TIMEOUT);
ELAN_CMD_TIMEOUT);
break; break;
case ACTIVATE_SET_SENSOR_DIM: case ACTIVATE_SET_SENSOR_DIM:
elandev->frame_width = elandev->last_read[2]; elandev->frame_width = elandev->last_read[2];
elandev->raw_frame_width = elandev->last_read[0]; elandev->raw_frame_width = elandev->last_read[0];
elandev->frame_height = elandev->frame_height =
elandev->raw_frame_width - 2 * ELAN_FRAME_MARGIN; elandev->raw_frame_width - 2 * elandev->frame_margin;
/* see elan_save_frame */
fp_dbg("sensor dimensions, WxH: %dx%d", elandev->frame_width,
elandev->raw_frame_width);
fpi_ssm_next_state(ssm); fpi_ssm_next_state(ssm);
break; break;
case ACTIVATE_START: case ACTIVATE_CMD_1:
elan_run_cmds(ssm, init_start_cmds, init_start_cmds_len, /* TODO: find out what this does, if we need it */
ELAN_CMD_TIMEOUT); elan_run_cmd(ssm, &activate_cmd_1, ELAN_CMD_TIMEOUT);
break; break;
case ACTIVATE_READ_DATA: case ACTIVATE_GET_BACKGROUND:
elan_run_cmds(ssm, read_cmds, read_cmds_len, ELAN_CMD_TIMEOUT); elan_run_cmd(ssm, &get_image_cmd, ELAN_CMD_TIMEOUT);
break;
case ACTIVATE_SAVE_BACKGROUND:
elan_save_frame(dev);
fpi_ssm_next_state(ssm);
break;
case ACTIVATE_GET_MEAN:
elan_run_cmd(ssm, &get_calib_mean_cmd, ELAN_CMD_TIMEOUT);
break; break;
case ACTIVATE_END:
elan_run_cmds(ssm, init_end_cmds, init_end_cmds_len,
ELAN_CMD_TIMEOUT);
} }
} }
@ -535,13 +616,17 @@ static void activate_complete(struct fpi_ssm *ssm)
if (elandev->deactivating) if (elandev->deactivating)
elan_deactivate(dev); elan_deactivate(dev);
else if (fpi_ssm_get_error(ssm)) else if (fpi_ssm_get_error(ssm))
fpi_imgdev_session_error(dev, fpi_ssm_get_error(ssm)); fpi_imgdev_activate_complete(dev, fpi_ssm_get_error(ssm));
else else if (elan_need_calibration(elandev))
elan_calibrate(dev); elan_calibrate(dev);
else {
fpi_imgdev_activate_complete(dev, 0);
elan_capture(dev);
}
fpi_ssm_free(ssm); fpi_ssm_free(ssm);
} }
static int dev_activate(struct fp_img_dev *dev, enum fp_imgdev_state state) static void elan_activate(struct fp_img_dev *dev)
{ {
struct elan_dev *elandev = fpi_imgdev_get_user_data(dev); struct elan_dev *elandev = fpi_imgdev_get_user_data(dev);
@ -549,11 +634,9 @@ static int dev_activate(struct fp_img_dev *dev, enum fp_imgdev_state state)
elan_dev_reset(elandev); elan_dev_reset(elandev);
struct fpi_ssm *ssm = struct fpi_ssm *ssm =
fpi_ssm_new(fpi_imgdev_get_dev(dev), elan_activate_run_state, ACTIVATE_NUM_STATES); fpi_ssm_new(fpi_imgdev_get_dev(dev), activate_run_state, ACTIVATE_NUM_STATES);
fpi_ssm_set_user_data(ssm, dev); fpi_ssm_set_user_data(ssm, dev);
fpi_ssm_start(ssm, activate_complete); fpi_ssm_start(ssm, activate_complete);
return 0;
} }
static int dev_init(struct fp_img_dev *dev, unsigned long driver_data) static int dev_init(struct fp_img_dev *dev, unsigned long driver_data)
@ -571,10 +654,80 @@ static int dev_init(struct fp_img_dev *dev, unsigned long driver_data)
elandev = g_malloc0(sizeof(struct elan_dev)); elandev = g_malloc0(sizeof(struct elan_dev));
fpi_imgdev_set_user_data(dev, elandev); fpi_imgdev_set_user_data(dev, elandev);
/* common params */
elandev->dev_type = driver_data;
elandev->calib_atts_left = ELAN_CALIBRATION_ATTEMPTS;
elandev->frame_margin = 0;
switch (driver_data) {
case ELAN_0907:
elandev->frame_margin = 12;
break;
}
fpi_imgdev_open_complete(dev, 0); fpi_imgdev_open_complete(dev, 0);
return 0; return 0;
} }
enum reset_sensor_states {
RESET_SENSOR_DO_RESET,
RESET_SENSOR_WAIT,
RESET_SENSOR_FUSE_LOAD,
RESET_SENSOR_STATUS,
RESET_SENSOR_NUM_STATES,
};
static void reset_sensor_run_state(struct fpi_ssm *ssm)
{
switch (fpi_ssm_get_cur_state(ssm)) {
case RESET_SENSOR_DO_RESET:
elan_run_cmd(ssm, &reset_sensor_cmd, ELAN_CMD_TIMEOUT);
break;
case RESET_SENSOR_WAIT:
/* must wait 5 ms after sensor reset command */
if (!fpi_timeout_add(5, fpi_ssm_next_state_async, ssm))
fpi_ssm_mark_aborted(ssm, -ETIME);
break;
case RESET_SENSOR_FUSE_LOAD:
elan_run_cmd(ssm, &fuse_load_cmd, ELAN_CMD_TIMEOUT);
break;
case RESET_SENSOR_STATUS:
elan_run_cmd(ssm, &read_sensor_status_cmd, ELAN_CMD_TIMEOUT);
break;
}
}
static void reset_sensor_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_activate(dev);
fpi_ssm_free(ssm);
}
static void elan_reset_sensor(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), reset_sensor_run_state,
RESET_SENSOR_NUM_STATES);
fpi_ssm_set_user_data(ssm, dev);
fpi_ssm_start(ssm, reset_sensor_complete);
}
static void dev_deinit(struct fp_img_dev *dev) static void dev_deinit(struct fp_img_dev *dev)
{ {
struct elan_dev *elandev = fpi_imgdev_get_user_data(dev); struct elan_dev *elandev = fpi_imgdev_get_user_data(dev);
@ -587,6 +740,12 @@ static void dev_deinit(struct fp_img_dev *dev)
fpi_imgdev_close_complete(dev); fpi_imgdev_close_complete(dev);
} }
static int dev_activate(struct fp_img_dev *dev, enum fp_imgdev_state state)
{
elan_reset_sensor(dev);
return 0;
}
static void dev_deactivate(struct fp_img_dev *dev) static void dev_deactivate(struct fp_img_dev *dev)
{ {
struct elan_dev *elandev = fpi_imgdev_get_user_data(dev); struct elan_dev *elandev = fpi_imgdev_get_user_data(dev);
@ -602,8 +761,12 @@ static void dev_deactivate(struct fp_img_dev *dev)
} }
static const struct usb_id id_table[] = { static const struct usb_id id_table[] = {
{.vendor = 0x04f3,.product = 0x0907}, {.vendor = ELAN_VENDOR_ID,.product = 0x0903,.driver_data = ELAN_0903},
{.vendor = 0x04f3,.product = 0x0c26}, {.vendor = ELAN_VENDOR_ID,.product = 0x0907,.driver_data = ELAN_0907},
{.vendor = ELAN_VENDOR_ID,.product = 0x0c03,.driver_data = ELAN_0C03},
{.vendor = ELAN_VENDOR_ID,.product = 0x0c16,.driver_data = ELAN_0C16},
{.vendor = ELAN_VENDOR_ID,.product = 0x0c1a,.driver_data = ELAN_0C1A},
{.vendor = ELAN_VENDOR_ID,.product = 0x0c26,.driver_data = ELAN_0C26},
{0, 0, 0,}, {0, 0, 0,},
}; };

View file

@ -24,9 +24,17 @@
#include <string.h> #include <string.h>
#include <libusb.h> #include <libusb.h>
/* number of pixels to discard on left and right (along raw image height) #define ELAN_VENDOR_ID 0x04f3
* because they have different intensity from the rest of the frame */
#define ELAN_FRAME_MARGIN 12 /* supported devices */
#define ELAN_0903 1
#define ELAN_0907 (1 << 1)
#define ELAN_0C03 (1 << 2)
#define ELAN_0C16 (1 << 3)
#define ELAN_0C1A (1 << 4)
#define ELAN_0C26 (1 << 5)
#define ELAN_ALL_DEVICES (ELAN_0903|ELAN_0907|ELAN_0C03|ELAN_0C16|ELAN_0C1A|ELAN_0C26)
/* min and max frames in a capture */ /* min and max frames in a capture */
#define ELAN_MIN_FRAMES 7 #define ELAN_MIN_FRAMES 7
@ -36,6 +44,13 @@
* while the finger is being lifted can be bad */ * while the finger is being lifted can be bad */
#define ELAN_SKIP_LAST_FRAMES 1 #define ELAN_SKIP_LAST_FRAMES 1
/* max difference between background image mean and calibration mean
* (the response value of get_calib_mean_cmd)*/
#define ELAN_CALIBRATION_MAX_DELTA 500
/* times to retry calibration */
#define ELAN_CALIBRATION_ATTEMPTS 10
#define ELAN_CMD_LEN 0x2 #define ELAN_CMD_LEN 0x2
#define ELAN_EP_CMD_OUT (0x1 | LIBUSB_ENDPOINT_OUT) #define ELAN_EP_CMD_OUT (0x1 | LIBUSB_ENDPOINT_OUT)
#define ELAN_EP_CMD_IN (0x3 | LIBUSB_ENDPOINT_IN) #define ELAN_EP_CMD_IN (0x3 | LIBUSB_ENDPOINT_IN)
@ -50,127 +65,104 @@ struct elan_cmd {
unsigned char cmd[ELAN_CMD_LEN]; unsigned char cmd[ELAN_CMD_LEN];
int response_len; int response_len;
int response_in; int response_in;
unsigned short devices;
}; };
static const struct elan_cmd get_sensor_dim_cmds[] = { static const struct elan_cmd get_sensor_dim_cmd = {
{
.cmd = {0x00, 0x0c}, .cmd = {0x00, 0x0c},
.response_len = 0x4, .response_len = 0x4,
.response_in = ELAN_EP_CMD_IN, .response_in = ELAN_EP_CMD_IN,
}, .devices = ELAN_ALL_DEVICES,
}; };
static const size_t get_sensor_dim_cmds_len = static const struct elan_cmd get_fw_ver_cmd = {
G_N_ELEMENTS(get_sensor_dim_cmds);
static const struct elan_cmd init_start_cmds[] = {
{
.cmd = {0x40, 0x19}, .cmd = {0x40, 0x19},
.response_len = 0x2, .response_len = 0x2,
.response_in = ELAN_EP_CMD_IN, .response_in = ELAN_EP_CMD_IN,
}, .devices = ELAN_ALL_DEVICES,
{ };
/* unknown, returns 0x0 0x1 on 0907 */
static const struct elan_cmd activate_cmd_1 = {
.cmd = {0x40, 0x2a}, .cmd = {0x40, 0x2a},
.response_len = 0x2, .response_len = 0x2,
.response_in = ELAN_EP_CMD_IN, .response_in = ELAN_EP_CMD_IN,
}, .devices = ELAN_0907,
}; };
static const size_t init_start_cmds_len = G_N_ELEMENTS(init_start_cmds); static const struct elan_cmd get_image_cmd = {
static const struct elan_cmd read_cmds[] = {
/* raw frame sizes are calculated from image dimesions reported by the
* device */
{
.cmd = {0x00, 0x09}, .cmd = {0x00, 0x09},
/* raw frame sizes are calculated from image dimensions reported by the
* device */
.response_len = -1, .response_len = -1,
.response_in = ELAN_EP_IMG_IN, .response_in = ELAN_EP_IMG_IN,
}, .devices = ELAN_ALL_DEVICES,
}; };
const size_t read_cmds_len = G_N_ELEMENTS(read_cmds); static const struct elan_cmd get_calib_mean_cmd = {
/* issued after data reads during init and calibration */
static const struct elan_cmd init_end_cmds[] = {
{
.cmd = {0x40, 0x24}, .cmd = {0x40, 0x24},
.response_len = 0x2, .response_len = 0x2,
.response_in = ELAN_EP_CMD_IN, .response_in = ELAN_EP_CMD_IN,
}, .devices = ELAN_ALL_DEVICES & ~ELAN_0903,
}; };
static const size_t init_end_cmds_len = G_N_ELEMENTS(init_end_cmds); static const struct elan_cmd reset_sensor_cmd = {
.cmd = {0x40, 0x11},
.response_len = 0x0,
.response_in = ELAN_EP_CMD_IN,
.devices = ELAN_ALL_DEVICES,
};
/* same command 2 times static const struct elan_cmd fuse_load_cmd = {
* original driver may observe return value to determine how many times it .cmd = {0x40, 0x14},
* should be repeated */ .response_len = 0x0,
static const struct elan_cmd calibrate_start_cmds[] = { .response_in = ELAN_EP_CMD_IN,
{ .devices = ELAN_ALL_DEVICES,
};
static const struct elan_cmd read_sensor_status_cmd = {
.cmd = {0x40, 0x13},
.response_len = 0x1,
.response_in = ELAN_EP_CMD_IN,
.devices = ELAN_ALL_DEVICES,
};
static const struct elan_cmd run_calibration_cmd = {
.cmd = {0x40, 0x23}, .cmd = {0x40, 0x23},
.response_len = 0x1, .response_len = 0x1,
.response_in = ELAN_EP_CMD_IN, .response_in = ELAN_EP_CMD_IN,
}, .devices = ELAN_ALL_DEVICES,
{
.cmd = {0x40, 0x23},
.response_len = 0x1,
.response_in = ELAN_EP_CMD_IN,
},
}; };
static const size_t calibrate_start_cmds_len = static const struct elan_cmd led_on_cmd = {
G_N_ELEMENTS(calibrate_start_cmds);
/* issued after data reads during init and calibration */
static const struct elan_cmd calibrate_end_cmds[] = {
{
.cmd = {0x40, 0x24},
.response_len = 0x2,
.response_in = ELAN_EP_CMD_IN,
},
};
static const size_t calibrate_end_cmds_len =
G_N_ELEMENTS(calibrate_end_cmds);
static const struct elan_cmd capture_start_cmds[] = {
/* led on */
{
.cmd = {0x40, 0x31}, .cmd = {0x40, 0x31},
.response_len = 0x0, .response_len = 0x0,
.response_in = ELAN_EP_CMD_IN, .response_in = ELAN_EP_CMD_IN,
}, .devices = ELAN_0907,
}; };
static size_t capture_start_cmds_len = G_N_ELEMENTS(capture_start_cmds);
static const struct elan_cmd capture_wait_finger_cmds[] = {
/* wait for finger /* wait for finger
* subsequent read will not complete until finger is placed on the reader */ * subsequent read will not complete until finger is placed on the reader */
{ static const struct elan_cmd pre_scan_cmd = {
.cmd = {0x40, 0x3f}, .cmd = {0x40, 0x3f},
.response_len = 0x1, .response_len = 0x1,
.response_in = ELAN_EP_CMD_IN, .response_in = ELAN_EP_CMD_IN,
}, .devices = ELAN_ALL_DEVICES,
}; };
static size_t capture_wait_finger_cmds_len = /* led off, stop waiting for finger */
G_N_ELEMENTS(capture_wait_finger_cmds); static const struct elan_cmd stop_cmd = {
static const struct elan_cmd deactivate_cmds[] = {
/* led off */
{
.cmd = {0x00, 0x0b}, .cmd = {0x00, 0x0b},
.response_len = 0x0, .response_len = 0x0,
.response_in = ELAN_EP_CMD_IN, .response_in = ELAN_EP_CMD_IN,
}, .devices = ELAN_ALL_DEVICES,
}; };
static const size_t deactivate_cmds_len = G_N_ELEMENTS(deactivate_cmds); static void elan_cmd_done(struct fpi_ssm *ssm);
static void elan_cmd_cb(struct libusb_transfer *transfer);
static void elan_cmd_read(struct fpi_ssm *ssm); static void elan_cmd_read(struct fpi_ssm *ssm);
static void elan_run_next_cmd(struct fpi_ssm *ssm);
static void elan_calibrate(struct fp_img_dev *dev);
static void elan_capture(struct fp_img_dev *dev); static void elan_capture(struct fp_img_dev *dev);
static void elan_deactivate(struct fp_img_dev *dev);
#endif #endif