elan: Stop doing sensor reset, change calibration and frame logic
Sensor reset code has been removed because it is not needed during normal operation. Calibration and frame processing logic has been improved according to recommendations from Elantech.
This commit is contained in:
parent
fba3e682ea
commit
65bbdff3fc
2 changed files with 244 additions and 202 deletions
|
@ -41,9 +41,8 @@ static struct fpi_frame_asmbl_ctx assembling_ctx = {
|
||||||
struct elan_dev {
|
struct elan_dev {
|
||||||
/* device config */
|
/* device config */
|
||||||
unsigned short dev_type;
|
unsigned short dev_type;
|
||||||
/* number of pixels to discard on left and right (along raw image height)
|
unsigned short fw_ver;
|
||||||
* because they have different intensity from the rest of the frame */
|
void (*process_frame) (unsigned short *raw_frame, GSList ** frames);
|
||||||
unsigned char frame_margin;
|
|
||||||
/* end device config */
|
/* end device config */
|
||||||
|
|
||||||
/* commands */
|
/* commands */
|
||||||
|
@ -54,8 +53,10 @@ struct elan_dev {
|
||||||
|
|
||||||
/* state */
|
/* state */
|
||||||
gboolean deactivating;
|
gboolean deactivating;
|
||||||
unsigned char calib_atts_left;
|
|
||||||
unsigned char *last_read;
|
unsigned char *last_read;
|
||||||
|
unsigned char calib_atts_left;
|
||||||
|
unsigned char calib_status;
|
||||||
|
unsigned short *background;
|
||||||
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;
|
||||||
|
@ -64,17 +65,23 @@ struct elan_dev {
|
||||||
/* end state */
|
/* 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)
|
static void elan_dev_reset(struct elan_dev *elandev)
|
||||||
{
|
{
|
||||||
G_DEBUG_HERE();
|
G_DEBUG_HERE();
|
||||||
|
|
||||||
BUG_ON(elandev->cur_transfer);
|
BUG_ON(elandev->cur_transfer);
|
||||||
|
|
||||||
elandev->deactivating = FALSE;
|
|
||||||
|
|
||||||
elandev->cmd = NULL;
|
elandev->cmd = NULL;
|
||||||
elandev->cmd_timeout = ELAN_CMD_TIMEOUT;
|
elandev->cmd_timeout = ELAN_CMD_TIMEOUT;
|
||||||
|
|
||||||
|
elandev->deactivating = FALSE;
|
||||||
|
elandev->calib_status = 0;
|
||||||
|
|
||||||
g_free(elandev->last_read);
|
g_free(elandev->last_read);
|
||||||
elandev->last_read = NULL;
|
elandev->last_read = NULL;
|
||||||
|
|
||||||
|
@ -83,36 +90,93 @@ static void elan_dev_reset(struct elan_dev *elandev)
|
||||||
elandev->num_frames = 0;
|
elandev->num_frames = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void elan_save_frame(struct fp_img_dev *dev)
|
static void elan_save_frame(struct elan_dev *elandev, unsigned short *frame)
|
||||||
{
|
{
|
||||||
struct elan_dev *elandev = fpi_imgdev_get_user_data(dev);
|
G_DEBUG_HERE();
|
||||||
|
|
||||||
unsigned char raw_height = elandev->frame_width;
|
unsigned char raw_height = elandev->frame_width;
|
||||||
unsigned char raw_width = elandev->raw_frame_width;
|
unsigned char raw_width = elandev->raw_frame_width;
|
||||||
unsigned short *frame =
|
|
||||||
g_malloc(elandev->frame_width * elandev->frame_height * 2);
|
|
||||||
|
|
||||||
G_DEBUG_HERE();
|
|
||||||
|
|
||||||
/* 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 stripes of 'frame_margin' along raw
|
* assembling. We also discard stripes of 'frame_margin' along raw
|
||||||
* height. */
|
* height. */
|
||||||
|
unsigned char frame_margin = (raw_width - elandev->frame_height) / 2;
|
||||||
for (int y = 0; y < raw_height; y++)
|
for (int y = 0; y < raw_height; y++)
|
||||||
for (int x = elandev->frame_margin;
|
for (int x = frame_margin; x < raw_width - frame_margin; x++) {
|
||||||
x < raw_width - elandev->frame_margin; x++) {
|
int frame_idx = y + (x - frame_margin) * raw_height;
|
||||||
int frame_idx =
|
|
||||||
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];
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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->frames = g_slist_prepend(elandev->frames, frame);
|
||||||
elandev->num_frames += 1;
|
elandev->num_frames += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Transform raw sensor data to normalized 8-bit grayscale image. */
|
static void elan_process_frame_linear(unsigned short *raw_frame,
|
||||||
static void elan_process_frame(unsigned short *raw_frame, GSList ** frames)
|
GSList ** frames)
|
||||||
{
|
{
|
||||||
unsigned int frame_size =
|
unsigned int frame_size =
|
||||||
assembling_ctx.frame_width * assembling_ctx.frame_height;
|
assembling_ctx.frame_width * assembling_ctx.frame_height;
|
||||||
|
@ -132,12 +196,42 @@ static void elan_process_frame(unsigned short *raw_frame, GSList ** frames)
|
||||||
unsigned short px;
|
unsigned short px;
|
||||||
for (int i = 0; i < frame_size; i++) {
|
for (int i = 0; i < frame_size; i++) {
|
||||||
px = raw_frame[i];
|
px = raw_frame[i];
|
||||||
if (px <= min)
|
px = (px - min) * 0xff / (max - min);
|
||||||
px = 0;
|
frame->data[i] = (unsigned char)px;
|
||||||
else if (px >= max)
|
}
|
||||||
px = 0xff;
|
|
||||||
else
|
*frames = g_slist_prepend(*frames, frame);
|
||||||
px = (px - min) * 0xff / (max - min);
|
}
|
||||||
|
|
||||||
|
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;
|
frame->data[i] = (unsigned char)px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -158,11 +252,13 @@ static void elan_submit_image(struct fp_img_dev *dev)
|
||||||
assembling_ctx.frame_width = elandev->frame_width;
|
assembling_ctx.frame_width = elandev->frame_width;
|
||||||
assembling_ctx.frame_height = elandev->frame_height;
|
assembling_ctx.frame_height = elandev->frame_height;
|
||||||
assembling_ctx.image_width = elandev->frame_width * 3 / 2;
|
assembling_ctx.image_width = elandev->frame_width * 3 / 2;
|
||||||
g_slist_foreach(elandev->frames, (GFunc) elan_process_frame, &frames);
|
g_slist_foreach(elandev->frames, (GFunc) elandev->process_frame,
|
||||||
|
&frames);
|
||||||
fpi_do_movement_estimation(&assembling_ctx, frames,
|
fpi_do_movement_estimation(&assembling_ctx, frames,
|
||||||
elandev->num_frames - ELAN_SKIP_LAST_FRAMES);
|
elandev->num_frames - ELAN_SKIP_LAST_FRAMES);
|
||||||
img = fpi_assemble_frames(&assembling_ctx, frames,
|
img =
|
||||||
elandev->num_frames - ELAN_SKIP_LAST_FRAMES);
|
fpi_assemble_frames(&assembling_ctx, frames,
|
||||||
|
elandev->num_frames - ELAN_SKIP_LAST_FRAMES);
|
||||||
|
|
||||||
img->flags |= FP_IMG_PARTIAL;
|
img->flags |= FP_IMG_PARTIAL;
|
||||||
fpi_imgdev_image_captured(dev, img);
|
fpi_imgdev_image_captured(dev, img);
|
||||||
|
@ -221,7 +317,7 @@ static void elan_cmd_read(struct fpi_ssm *ssm)
|
||||||
|
|
||||||
G_DEBUG_HERE();
|
G_DEBUG_HERE();
|
||||||
|
|
||||||
if (!(elandev->cmd->response_len)) {
|
if (elandev->cmd->response_len == ELAN_CMD_SKIP_READ) {
|
||||||
fp_dbg("skipping read, not expecting anything");
|
fp_dbg("skipping read, not expecting anything");
|
||||||
elan_cmd_done(ssm);
|
elan_cmd_done(ssm);
|
||||||
return;
|
return;
|
||||||
|
@ -258,11 +354,14 @@ static void elan_run_cmd(struct fpi_ssm *ssm, const struct elan_cmd *cmd,
|
||||||
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);
|
||||||
|
|
||||||
|
dbg_buf(cmd->cmd, 2);
|
||||||
|
|
||||||
elandev->cmd = cmd;
|
elandev->cmd = cmd;
|
||||||
if (cmd_timeout != -1)
|
if (cmd_timeout != -1)
|
||||||
elandev->cmd_timeout = cmd_timeout;
|
elandev->cmd_timeout = cmd_timeout;
|
||||||
|
|
||||||
if (!(cmd->devices & elandev->dev_type)) {
|
if (elandev->dev_type && cmd->devices
|
||||||
|
&& !(cmd->devices & elandev->dev_type)) {
|
||||||
fp_dbg("skipping for this device");
|
fp_dbg("skipping for this device");
|
||||||
elan_cmd_done(ssm);
|
elan_cmd_done(ssm);
|
||||||
return;
|
return;
|
||||||
|
@ -353,7 +452,7 @@ static void capture_run_state(struct fpi_ssm *ssm)
|
||||||
fpi_ssm_mark_aborted(ssm, FP_VERIFY_RETRY);
|
fpi_ssm_mark_aborted(ssm, FP_VERIFY_RETRY);
|
||||||
break;
|
break;
|
||||||
case CAPTURE_CHECK_ENOUGH_FRAMES:
|
case CAPTURE_CHECK_ENOUGH_FRAMES:
|
||||||
elan_save_frame(dev);
|
elan_save_img_frame(elandev);
|
||||||
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;
|
||||||
|
@ -429,25 +528,19 @@ static void fpi_ssm_next_state_async(void *data)
|
||||||
fpi_ssm_next_state((struct fpi_ssm *)data);
|
fpi_ssm_next_state((struct fpi_ssm *)data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* this function needs last_read to be the calibration mean and at least
|
/* this function needs to have elandev->background and elandev->last_read to be
|
||||||
* one frame */
|
* the calibration mean */
|
||||||
static int elan_need_calibration(struct elan_dev *elandev)
|
static int elan_need_calibration(struct elan_dev *elandev)
|
||||||
{
|
{
|
||||||
G_DEBUG_HERE();
|
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 =
|
unsigned short calib_mean =
|
||||||
elandev->last_read[0] * 0xff + elandev->last_read[1];
|
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 bg_mean = 0, delta;
|
||||||
unsigned int frame_size = elandev->frame_width * elandev->frame_height;
|
unsigned int frame_size = elandev->frame_width * elandev->frame_height;
|
||||||
|
|
||||||
for (int i = 0; i < frame_size; i++)
|
for (int i = 0; i < frame_size; i++)
|
||||||
bg_mean += bg_data[i];
|
bg_mean += elandev->background[i];
|
||||||
bg_mean /= frame_size;
|
bg_mean /= frame_size;
|
||||||
|
|
||||||
delta =
|
delta =
|
||||||
|
@ -460,12 +553,13 @@ static int elan_need_calibration(struct elan_dev *elandev)
|
||||||
}
|
}
|
||||||
|
|
||||||
enum calibrate_states {
|
enum calibrate_states {
|
||||||
CALIBRATE_START,
|
|
||||||
CALIBRATE_CHECK_RESULT,
|
|
||||||
CALIBRATE_REPEAT,
|
|
||||||
CALIBRATE_GET_BACKGROUND,
|
CALIBRATE_GET_BACKGROUND,
|
||||||
CALIBRATE_SAVE_BACKGROUND,
|
CALIBRATE_SAVE_BACKGROUND,
|
||||||
CALIBRATE_GET_MEAN,
|
CALIBRATE_GET_MEAN,
|
||||||
|
CALIBRATE_CHECK_NEEDED,
|
||||||
|
CALIBRATE_GET_STATUS,
|
||||||
|
CALIBRATE_CHECK_STATUS,
|
||||||
|
CALIBRATE_REPEAT_STATUS,
|
||||||
CALIBRATE_NUM_STATES,
|
CALIBRATE_NUM_STATES,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -477,40 +571,60 @@ static void calibrate_run_state(struct fpi_ssm *ssm)
|
||||||
G_DEBUG_HERE();
|
G_DEBUG_HERE();
|
||||||
|
|
||||||
switch (fpi_ssm_get_cur_state(ssm)) {
|
switch (fpi_ssm_get_cur_state(ssm)) {
|
||||||
case CALIBRATE_START:
|
|
||||||
elandev->calib_atts_left -= 1;
|
|
||||||
if (elandev->calib_atts_left)
|
|
||||||
elan_run_cmd(ssm, &run_calibration_cmd,
|
|
||||||
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:
|
case CALIBRATE_GET_BACKGROUND:
|
||||||
elan_run_cmd(ssm, &get_image_cmd, ELAN_CMD_TIMEOUT);
|
elan_run_cmd(ssm, &get_image_cmd, ELAN_CMD_TIMEOUT);
|
||||||
break;
|
break;
|
||||||
case CALIBRATE_SAVE_BACKGROUND:
|
case CALIBRATE_SAVE_BACKGROUND:
|
||||||
elan_save_frame(dev);
|
elan_save_background(elandev);
|
||||||
fpi_ssm_next_state(ssm);
|
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;
|
break;
|
||||||
case CALIBRATE_GET_MEAN:
|
case CALIBRATE_GET_MEAN:
|
||||||
elan_run_cmd(ssm, &get_calib_mean_cmd, ELAN_CMD_TIMEOUT);
|
elan_run_cmd(ssm, &get_calib_mean_cmd, ELAN_CMD_TIMEOUT);
|
||||||
break;
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -525,8 +639,6 @@ static void calibrate_complete(struct fpi_ssm *ssm)
|
||||||
elan_deactivate(dev);
|
elan_deactivate(dev);
|
||||||
else if (fpi_ssm_get_error(ssm))
|
else if (fpi_ssm_get_error(ssm))
|
||||||
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 {
|
else {
|
||||||
fpi_imgdev_activate_complete(dev, 0);
|
fpi_imgdev_activate_complete(dev, 0);
|
||||||
elan_capture(dev);
|
elan_capture(dev);
|
||||||
|
@ -542,6 +654,8 @@ static void elan_calibrate(struct fp_img_dev *dev)
|
||||||
G_DEBUG_HERE();
|
G_DEBUG_HERE();
|
||||||
|
|
||||||
elan_dev_reset(elandev);
|
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,
|
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);
|
||||||
|
@ -550,13 +664,10 @@ static void elan_calibrate(struct fp_img_dev *dev)
|
||||||
|
|
||||||
enum activate_states {
|
enum activate_states {
|
||||||
ACTIVATE_GET_FW_VER,
|
ACTIVATE_GET_FW_VER,
|
||||||
ACTIVATE_PRINT_FW_VER,
|
ACTIVATE_SET_FW_VER,
|
||||||
ACTIVATE_GET_SENSOR_DIM,
|
ACTIVATE_GET_SENSOR_DIM,
|
||||||
ACTIVATE_SET_SENSOR_DIM,
|
ACTIVATE_SET_SENSOR_DIM,
|
||||||
ACTIVATE_CMD_1,
|
ACTIVATE_CMD_1,
|
||||||
ACTIVATE_GET_BACKGROUND,
|
|
||||||
ACTIVATE_SAVE_BACKGROUND,
|
|
||||||
ACTIVATE_GET_MEAN,
|
|
||||||
ACTIVATE_NUM_STATES,
|
ACTIVATE_NUM_STATES,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -571,9 +682,10 @@ static void activate_run_state(struct fpi_ssm *ssm)
|
||||||
case ACTIVATE_GET_FW_VER:
|
case ACTIVATE_GET_FW_VER:
|
||||||
elan_run_cmd(ssm, &get_fw_ver_cmd, ELAN_CMD_TIMEOUT);
|
elan_run_cmd(ssm, &get_fw_ver_cmd, ELAN_CMD_TIMEOUT);
|
||||||
break;
|
break;
|
||||||
case ACTIVATE_PRINT_FW_VER:
|
case ACTIVATE_SET_FW_VER:
|
||||||
fp_dbg("FW ver %d.%d", elandev->last_read[0],
|
elandev->fw_ver =
|
||||||
elandev->last_read[1]);
|
(elandev->last_read[0] << 8 | elandev->last_read[1]);
|
||||||
|
fp_dbg("FW ver 0x%04hx", elandev->fw_ver);
|
||||||
fpi_ssm_next_state(ssm);
|
fpi_ssm_next_state(ssm);
|
||||||
break;
|
break;
|
||||||
case ACTIVATE_GET_SENSOR_DIM:
|
case ACTIVATE_GET_SENSOR_DIM:
|
||||||
|
@ -582,9 +694,11 @@ static void activate_run_state(struct fpi_ssm *ssm)
|
||||||
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 =
|
if (elandev->raw_frame_width < ELAN_MAX_FRAME_HEIGHT)
|
||||||
elandev->raw_frame_width - 2 * elandev->frame_margin;
|
elandev->frame_height = elandev->raw_frame_width;
|
||||||
/* see elan_save_frame */
|
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,
|
fp_dbg("sensor dimensions, WxH: %dx%d", elandev->frame_width,
|
||||||
elandev->raw_frame_width);
|
elandev->raw_frame_width);
|
||||||
fpi_ssm_next_state(ssm);
|
fpi_ssm_next_state(ssm);
|
||||||
|
@ -593,16 +707,6 @@ static void activate_run_state(struct fpi_ssm *ssm)
|
||||||
/* TODO: find out what this does, if we need it */
|
/* TODO: find out what this does, if we need it */
|
||||||
elan_run_cmd(ssm, &activate_cmd_1, ELAN_CMD_TIMEOUT);
|
elan_run_cmd(ssm, &activate_cmd_1, ELAN_CMD_TIMEOUT);
|
||||||
break;
|
break;
|
||||||
case ACTIVATE_GET_BACKGROUND:
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -617,12 +721,9 @@ static void activate_complete(struct fpi_ssm *ssm)
|
||||||
elan_deactivate(dev);
|
elan_deactivate(dev);
|
||||||
else if (fpi_ssm_get_error(ssm))
|
else if (fpi_ssm_get_error(ssm))
|
||||||
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))
|
else
|
||||||
elan_calibrate(dev);
|
elan_calibrate(dev);
|
||||||
else {
|
|
||||||
fpi_imgdev_activate_complete(dev, 0);
|
|
||||||
elan_capture(dev);
|
|
||||||
}
|
|
||||||
fpi_ssm_free(ssm);
|
fpi_ssm_free(ssm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -631,8 +732,8 @@ 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);
|
||||||
|
|
||||||
G_DEBUG_HERE();
|
G_DEBUG_HERE();
|
||||||
|
|
||||||
elan_dev_reset(elandev);
|
elan_dev_reset(elandev);
|
||||||
|
|
||||||
struct fpi_ssm *ssm =
|
struct fpi_ssm *ssm =
|
||||||
fpi_ssm_new(fpi_imgdev_get_dev(dev), 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);
|
||||||
|
@ -657,12 +758,12 @@ static int dev_init(struct fp_img_dev *dev, unsigned long driver_data)
|
||||||
|
|
||||||
/* common params */
|
/* common params */
|
||||||
elandev->dev_type = driver_data;
|
elandev->dev_type = driver_data;
|
||||||
elandev->calib_atts_left = ELAN_CALIBRATION_ATTEMPTS;
|
elandev->background = NULL;
|
||||||
elandev->frame_margin = 0;
|
elandev->process_frame = elan_process_frame_thirds;
|
||||||
|
|
||||||
switch (driver_data) {
|
switch (driver_data) {
|
||||||
case ELAN_0907:
|
case ELAN_0907:
|
||||||
elandev->frame_margin = 12;
|
elandev->process_frame = elan_process_frame_linear;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -670,64 +771,6 @@ static int dev_init(struct fp_img_dev *dev, unsigned long driver_data)
|
||||||
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);
|
||||||
|
@ -735,6 +778,7 @@ static void dev_deinit(struct fp_img_dev *dev)
|
||||||
G_DEBUG_HERE();
|
G_DEBUG_HERE();
|
||||||
|
|
||||||
elan_dev_reset(elandev);
|
elan_dev_reset(elandev);
|
||||||
|
g_free(elandev->background);
|
||||||
g_free(elandev);
|
g_free(elandev);
|
||||||
libusb_release_interface(fpi_imgdev_get_usb_dev(dev), 0);
|
libusb_release_interface(fpi_imgdev_get_usb_dev(dev), 0);
|
||||||
fpi_imgdev_close_complete(dev);
|
fpi_imgdev_close_complete(dev);
|
||||||
|
@ -742,7 +786,8 @@ static void dev_deinit(struct fp_img_dev *dev)
|
||||||
|
|
||||||
static int dev_activate(struct fp_img_dev *dev, enum fp_imgdev_state state)
|
static int dev_activate(struct fp_img_dev *dev, enum fp_imgdev_state state)
|
||||||
{
|
{
|
||||||
elan_reset_sensor(dev);
|
G_DEBUG_HERE();
|
||||||
|
elan_activate(dev);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -761,12 +806,17 @@ static void dev_deactivate(struct fp_img_dev *dev)
|
||||||
}
|
}
|
||||||
|
|
||||||
static const struct usb_id id_table[] = {
|
static const struct usb_id id_table[] = {
|
||||||
{.vendor = ELAN_VENDOR_ID,.product = 0x0903,.driver_data = ELAN_0903},
|
{.vendor = ELAN_VENDOR_ID,.product = 0x0903,.driver_data =
|
||||||
|
ELAN_ALL_DEVICES},
|
||||||
{.vendor = ELAN_VENDOR_ID,.product = 0x0907,.driver_data = ELAN_0907},
|
{.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 = 0x0c03,.driver_data =
|
||||||
{.vendor = ELAN_VENDOR_ID,.product = 0x0c16,.driver_data = ELAN_0C16},
|
ELAN_ALL_DEVICES},
|
||||||
{.vendor = ELAN_VENDOR_ID,.product = 0x0c1a,.driver_data = ELAN_0C1A},
|
{.vendor = ELAN_VENDOR_ID,.product = 0x0c16,.driver_data =
|
||||||
{.vendor = ELAN_VENDOR_ID,.product = 0x0c26,.driver_data = ELAN_0C26},
|
ELAN_ALL_DEVICES},
|
||||||
|
{.vendor = ELAN_VENDOR_ID,.product = 0x0c1a,.driver_data =
|
||||||
|
ELAN_ALL_DEVICES},
|
||||||
|
{.vendor = ELAN_VENDOR_ID,.product = 0x0c26,.driver_data =
|
||||||
|
ELAN_ALL_DEVICES},
|
||||||
{0, 0, 0,},
|
{0, 0, 0,},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -26,36 +26,42 @@
|
||||||
|
|
||||||
#define ELAN_VENDOR_ID 0x04f3
|
#define ELAN_VENDOR_ID 0x04f3
|
||||||
|
|
||||||
/* supported devices */
|
/* a default device type */
|
||||||
#define ELAN_0903 1
|
#define ELAN_ALL_DEVICES 0
|
||||||
#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)
|
/* devices with quirks */
|
||||||
|
#define ELAN_0907 1
|
||||||
|
|
||||||
|
/* min FW version that supports calibration */
|
||||||
|
#define ELAN_MIN_CALIBRATION_FW 0x0138
|
||||||
|
|
||||||
|
/* 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 reading calibration status during one session
|
||||||
|
* generally prevents calibration from looping indefinitely */
|
||||||
|
#define ELAN_CALIBRATION_ATTEMPTS 10
|
||||||
|
|
||||||
/* min and max frames in a capture */
|
/* min and max frames in a capture */
|
||||||
#define ELAN_MIN_FRAMES 7
|
#define ELAN_MIN_FRAMES 7
|
||||||
#define ELAN_MAX_FRAMES 30
|
#define ELAN_MAX_FRAMES 30
|
||||||
|
|
||||||
|
/* crop frames to this height to improve stitching */
|
||||||
|
#define ELAN_MAX_FRAME_HEIGHT 30
|
||||||
|
|
||||||
/* number of frames to drop at the end of capture because frames captured
|
/* number of frames to drop at the end of capture because frames captured
|
||||||
* 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)
|
||||||
#define ELAN_EP_IMG_IN (0x2 | LIBUSB_ENDPOINT_IN)
|
#define ELAN_EP_IMG_IN (0x2 | LIBUSB_ENDPOINT_IN)
|
||||||
|
|
||||||
|
/* used as response length to tell the driver to skip reading response */
|
||||||
|
#define ELAN_CMD_SKIP_READ 0
|
||||||
|
|
||||||
/* usual command timeout and timeout for when we need to check if the finger is
|
/* usual command timeout and timeout for when we need to check if the finger is
|
||||||
* still on the device */
|
* still on the device */
|
||||||
#define ELAN_CMD_TIMEOUT 10000
|
#define ELAN_CMD_TIMEOUT 10000
|
||||||
|
@ -99,27 +105,6 @@ static const struct elan_cmd get_image_cmd = {
|
||||||
.devices = ELAN_ALL_DEVICES,
|
.devices = ELAN_ALL_DEVICES,
|
||||||
};
|
};
|
||||||
|
|
||||||
static const struct elan_cmd get_calib_mean_cmd = {
|
|
||||||
.cmd = {0x40, 0x24},
|
|
||||||
.response_len = 0x2,
|
|
||||||
.response_in = ELAN_EP_CMD_IN,
|
|
||||||
.devices = ELAN_ALL_DEVICES & ~ELAN_0903,
|
|
||||||
};
|
|
||||||
|
|
||||||
static const struct elan_cmd reset_sensor_cmd = {
|
|
||||||
.cmd = {0x40, 0x11},
|
|
||||||
.response_len = 0x0,
|
|
||||||
.response_in = ELAN_EP_CMD_IN,
|
|
||||||
.devices = ELAN_ALL_DEVICES,
|
|
||||||
};
|
|
||||||
|
|
||||||
static const struct elan_cmd fuse_load_cmd = {
|
|
||||||
.cmd = {0x40, 0x14},
|
|
||||||
.response_len = 0x0,
|
|
||||||
.response_in = ELAN_EP_CMD_IN,
|
|
||||||
.devices = ELAN_ALL_DEVICES,
|
|
||||||
};
|
|
||||||
|
|
||||||
static const struct elan_cmd read_sensor_status_cmd = {
|
static const struct elan_cmd read_sensor_status_cmd = {
|
||||||
.cmd = {0x40, 0x13},
|
.cmd = {0x40, 0x13},
|
||||||
.response_len = 0x1,
|
.response_len = 0x1,
|
||||||
|
@ -127,16 +112,23 @@ static const struct elan_cmd read_sensor_status_cmd = {
|
||||||
.devices = ELAN_ALL_DEVICES,
|
.devices = ELAN_ALL_DEVICES,
|
||||||
};
|
};
|
||||||
|
|
||||||
static const struct elan_cmd run_calibration_cmd = {
|
static const struct elan_cmd get_calib_status_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,
|
.devices = ELAN_ALL_DEVICES,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static const struct elan_cmd get_calib_mean_cmd = {
|
||||||
|
.cmd = {0x40, 0x24},
|
||||||
|
.response_len = 0x2,
|
||||||
|
.response_in = ELAN_EP_CMD_IN,
|
||||||
|
.devices = ELAN_ALL_DEVICES,
|
||||||
|
};
|
||||||
|
|
||||||
static const struct elan_cmd led_on_cmd = {
|
static const struct elan_cmd led_on_cmd = {
|
||||||
.cmd = {0x40, 0x31},
|
.cmd = {0x40, 0x31},
|
||||||
.response_len = 0x0,
|
.response_len = ELAN_CMD_SKIP_READ,
|
||||||
.response_in = ELAN_EP_CMD_IN,
|
.response_in = ELAN_EP_CMD_IN,
|
||||||
.devices = ELAN_0907,
|
.devices = ELAN_0907,
|
||||||
};
|
};
|
||||||
|
@ -153,7 +145,7 @@ static const struct elan_cmd pre_scan_cmd = {
|
||||||
/* led off, stop waiting for finger */
|
/* led off, stop waiting for finger */
|
||||||
static const struct elan_cmd stop_cmd = {
|
static const struct elan_cmd stop_cmd = {
|
||||||
.cmd = {0x00, 0x0b},
|
.cmd = {0x00, 0x0b},
|
||||||
.response_len = 0x0,
|
.response_len = ELAN_CMD_SKIP_READ,
|
||||||
.response_in = ELAN_EP_CMD_IN,
|
.response_in = ELAN_EP_CMD_IN,
|
||||||
.devices = ELAN_ALL_DEVICES,
|
.devices = ELAN_ALL_DEVICES,
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue