Files
libfprint-fpc1020/libfprint/drivers/fpc1020_samsung.c

1199 lines
33 KiB
C

/*
* Samsung USB FPC1020 driver for libfprint
*
* 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 "fpc1020_samsung"
#include "drivers_api.h"
#define FPC1020_EP_OUT (0x01 | FPI_USB_ENDPOINT_OUT)
#define FPC1020_EP_IN (0x02 | FPI_USB_ENDPOINT_IN)
#define FPC1020_IMG_W 160
#define FPC1020_IMG_H 160
#define FPC1020_HALF_SIZE 12800
#define FPC1020_XFER_SIZE 12802 // 2-byte header + 12800 pixels
#define FPC1020_TIMEOUT 2000
#define FPC1020_IMG_TIMEOUT 6000 // longer timout for images
#define FPC1020_NUM_INIT_CMDS 16
#define FPC1020_NUM_INIT_PASSES 2
#define FPC1020_POLL_MAX 100
static const guint8 SPI_CONFIG[16] = {
0xc0, 0xc6, 0x2d, 0x00, 0x08, 0x00, 0xff, 0x01,
0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
// backend device init command
static const guint8 CMD_RESET[] = { 0xf8 };
static const guint8 CMD_IRQ_READ[] = { 0x1c, 0x00 };
static const guint8 CMD_HWID[] = { 0xfc, 0x00, 0x00 };
static const guint8 CMD_CONFIG[] = { 0x9c, 0x55, 0x40, 0x00, 0x2d, 0x24 };
static const guint8 CMD_REG_6C[] = { 0x6c, 0x29 };
static const guint8 CMD_REG_90[] = { 0x90, 0x32 };
static const guint8 CMD_CAL[] = { 0x68, 0x31, 0x31, 0x31, 0x31,
0x3d, 0x3d, 0x3d, 0x3d };
static const guint8 CMD_CRYPTO[] = { 0x98, 0x03, 0xeb, 0x3f, 0xb8,
0x0a, 0x07, 0x11, 0x22, 0x6a, 0x5d };
static const guint8 CMD_REG_8C[] = { 0x8c, 0x12 };
static const guint8 CMD_PXLCTRL[] = { 0x5c, 0x0b };
static const guint8 CMD_FDET_THRES[] = { 0xd8, 0x20 };
static const guint8 CMD_FDET_CNTR[] = { 0xdc, 0x00, 0x0a };
static const guint8 CMD_ADC_GAIN[] = { 0xa8, 0x0f, 0x0a };
static const guint8 CMD_PXL_OFFSET[] = { 0xa0, 0x0a, 0x02 };
static const guint8 CMD_FINALIZE[] = { 0x28 };
struct spi_cmd
{
const guint8 *data;
gsize len;
};
static const struct spi_cmd INIT_CMDS[FPC1020_NUM_INIT_CMDS] = {
{ CMD_RESET, sizeof (CMD_RESET) },
{ CMD_IRQ_READ, sizeof (CMD_IRQ_READ) },
{ CMD_IRQ_READ, sizeof (CMD_IRQ_READ) },
{ CMD_HWID, sizeof (CMD_HWID) },
{ CMD_CONFIG, sizeof (CMD_CONFIG) },
{ CMD_REG_6C, sizeof (CMD_REG_6C) },
{ CMD_REG_90, sizeof (CMD_REG_90) },
{ CMD_CAL, sizeof (CMD_CAL) },
{ CMD_CRYPTO, sizeof (CMD_CRYPTO) },
{ CMD_REG_8C, sizeof (CMD_REG_8C) },
{ CMD_PXLCTRL, sizeof (CMD_PXLCTRL) },
{ CMD_FDET_THRES, sizeof (CMD_FDET_THRES) },
{ CMD_FDET_CNTR, sizeof (CMD_FDET_CNTR) },
{ CMD_ADC_GAIN, sizeof (CMD_ADC_GAIN) },
{ CMD_PXL_OFFSET, sizeof (CMD_PXL_OFFSET) },
{ CMD_FINALIZE, sizeof (CMD_FINALIZE) },
};
// full resolution
static const guint8 CMD_FR_OFFSET[] = { 0xa0, 0x0e, 0x03 };
static const guint8 CMD_FR_SAMPLE[] = { 0x64, 0x1e };
static const guint8 CMD_FR_PXLCTRL[] = { 0x5c, 0x0b };
static const guint8 CMD_FR_WINDOW[] = { 0x54, 0x00, 0xa0, 0x00, 0xa0 };
static const guint8 CMD_ARM[] = { 0x20 };
static const guint8 CMD_CAPTURE[] = { 0xc0 };
static const guint8 CMD_FINGER_DET[] = { 0xd4, 0x00, 0x00, 0x00, 0x00 };
struct _FpDeviceFpc1020Samsung
{
FpImageDevice parent;
const guint8 *current_cmd;
gsize current_cmd_len;
guint8 spi_response[16];
guint8 *top_raw;
guint8 *bot_raw;
gint xfer_complete_count;
FpiSsm *capture_ssm;
gint init_pass;
gint cmd_idx;
gint poll_count;
GSource *finger_poll_source;
gboolean finger_detected;
gboolean needs_reinit;
gboolean deactivating;
gboolean ssm_active;
};
G_DECLARE_FINAL_TYPE (FpDeviceFpc1020Samsung, fpi_device_fpc1020_samsung,
FPI, DEVICE_FPC1020_SAMSUNG, FpImageDevice);
G_DEFINE_TYPE (FpDeviceFpc1020Samsung, fpi_device_fpc1020_samsung,
FP_TYPE_IMAGE_DEVICE);
static void start_finger_poll (FpDeviceFpc1020Samsung *self);
static void start_finger_off_poll (FpDeviceFpc1020Samsung *self);
static void start_capture (FpDeviceFpc1020Samsung *self);
enum spi_cmd_states {
SPI_CMD_BRIDGE_CONFIG,
SPI_CMD_BRIDGE_SET_SIZE,
SPI_CMD_BULK_WRITE,
SPI_CMD_BULK_READ,
SPI_CMD_NUM_STATES,
};
static void
spi_read_cb (FpiUsbTransfer *transfer, FpDevice *dev,
gpointer user_data, GError *error)
{
FpDeviceFpc1020Samsung *self = FPI_DEVICE_FPC1020_SAMSUNG (dev);
if (error)
{
fpi_ssm_mark_failed (transfer->ssm, error);
return;
}
memset (self->spi_response, 0, sizeof (self->spi_response));
memcpy (self->spi_response, transfer->buffer,
MIN ((gsize) transfer->actual_length, sizeof (self->spi_response)));
fpi_ssm_next_state (transfer->ssm);
}
static void
spi_cmd_run_state (FpiSsm *ssm, FpDevice *dev)
{
FpDeviceFpc1020Samsung *self = FPI_DEVICE_FPC1020_SAMSUNG (dev);
FpiUsbTransfer *transfer;
switch (fpi_ssm_get_cur_state (ssm))
{
case SPI_CMD_BRIDGE_CONFIG:
transfer = fpi_usb_transfer_new (dev);
fpi_usb_transfer_fill_control (transfer,
G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE,
G_USB_DEVICE_REQUEST_TYPE_VENDOR,
G_USB_DEVICE_RECIPIENT_DEVICE,
0xC3, 0x0000, 0x0000,
sizeof (SPI_CONFIG));
memcpy (transfer->buffer, SPI_CONFIG, sizeof (SPI_CONFIG));
transfer->ssm = ssm;
fpi_usb_transfer_submit (transfer, FPC1020_TIMEOUT, NULL,
fpi_ssm_usb_transfer_cb, NULL);
break;
case SPI_CMD_BRIDGE_SET_SIZE:
transfer = fpi_usb_transfer_new (dev);
fpi_usb_transfer_fill_control (transfer,
G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE,
G_USB_DEVICE_REQUEST_TYPE_VENDOR,
G_USB_DEVICE_RECIPIENT_DEVICE,
0xCA, 0x0003,
(guint16) self->current_cmd_len, 0);
transfer->ssm = ssm;
fpi_usb_transfer_submit (transfer, FPC1020_TIMEOUT, NULL,
fpi_ssm_usb_transfer_cb, NULL);
break;
case SPI_CMD_BULK_WRITE:
transfer = fpi_usb_transfer_new (dev);
fpi_usb_transfer_fill_bulk_full (transfer, FPC1020_EP_OUT,
g_memdup2 (self->current_cmd,
self->current_cmd_len),
self->current_cmd_len, g_free);
transfer->ssm = ssm;
transfer->short_is_error = TRUE;
fpi_usb_transfer_submit (transfer, FPC1020_TIMEOUT, NULL,
fpi_ssm_usb_transfer_cb, NULL);
break;
case SPI_CMD_BULK_READ:
transfer = fpi_usb_transfer_new (dev);
fpi_usb_transfer_fill_bulk (transfer, FPC1020_EP_IN,
self->current_cmd_len);
transfer->ssm = ssm;
fpi_usb_transfer_submit (transfer, FPC1020_TIMEOUT, NULL,
spi_read_cb, NULL);
break;
default:
g_assert_not_reached ();
}
}
static void
start_spi_cmd_subsm (FpiSsm *parent,
FpDeviceFpc1020Samsung *self,
const guint8 *cmd,
gsize len)
{
FpiSsm *subsm;
self->current_cmd = cmd;
self->current_cmd_len = len;
subsm = fpi_ssm_new (FP_DEVICE (self), spi_cmd_run_state,
SPI_CMD_NUM_STATES);
fpi_ssm_start_subsm (parent, subsm);
}
enum capture_states {
CAP_CONFIRM_ARM,
CAP_BWAIT_1,
CAP_BWAIT_2,
CAP_BWAIT_3,
CAP_BWAIT_4,
CAP_CONFIRM_IRQ,
CAP_CONFIRM_DETAIL,
CAP_FR_OFFSET,
CAP_FR_SAMPLE,
CAP_FR_PXLCTRL,
CAP_FR_WINDOW,
CAP_TRIGGER,
CAP_POLL_IRQ,
CAP_POLL_CHECK,
CAP_POLL_BSTAT,
CAP_TOP_CONFIG,
CAP_TOP_SIZE,
CAP_TOP_XFER,
CAP_BOT_CONFIG,
CAP_BOT_SIZE,
CAP_BOT_XFER,
CAP_TEARDOWN_1,
CAP_TEARDOWN_2,
CAP_TEARDOWN_3,
CAP_TEARDOWN_4,
CAP_TEARDOWN_ARM,
CAP_SUBMIT,
CAP_NUM_STATES,
};
static void
bstat_delay_cb (FpiUsbTransfer *transfer, FpDevice *dev,
gpointer user_data, GError *error)
{
if (error)
{
fpi_ssm_mark_failed (transfer->ssm, error);
return;
}
fpi_ssm_next_state_delayed (transfer->ssm, 10);
}
static void
do_bridge_status (FpiSsm *ssm, FpDevice *dev,
FpiUsbTransferCallback cb)
{
FpiUsbTransfer *transfer = fpi_usb_transfer_new (dev);
fpi_usb_transfer_fill_control (transfer,
G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST,
G_USB_DEVICE_REQUEST_TYPE_VENDOR,
G_USB_DEVICE_RECIPIENT_DEVICE,
0xDA, 0x0007, 0x0000, 2);
transfer->ssm = ssm;
fpi_usb_transfer_submit (transfer, FPC1020_TIMEOUT, NULL, cb, NULL);
}
static void
poll_bstat_cb (FpiUsbTransfer *transfer, FpDevice *dev,
gpointer user_data, GError *error)
{
if (error)
{
fpi_ssm_mark_failed (transfer->ssm, error);
return;
}
fpi_ssm_jump_to_state_delayed (transfer->ssm, CAP_POLL_IRQ, 10);
}
enum open_states {
OPEN_BRIDGE_INIT_1,
OPEN_BRIDGE_INIT_2,
OPEN_INIT_CMD,
OPEN_INIT_NEXT,
OPEN_NUM_STATES,
};
static void
open_run_state (FpiSsm *ssm, FpDevice *dev)
{
FpDeviceFpc1020Samsung *self = FPI_DEVICE_FPC1020_SAMSUNG (dev);
FpiUsbTransfer *transfer;
switch (fpi_ssm_get_cur_state (ssm))
{
case OPEN_BRIDGE_INIT_1:
transfer = fpi_usb_transfer_new (dev);
fpi_usb_transfer_fill_control (transfer,
G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE,
G_USB_DEVICE_REQUEST_TYPE_VENDOR,
G_USB_DEVICE_RECIPIENT_DEVICE,
0xDB, 0x0006, 0x0000, 0);
transfer->ssm = ssm;
fpi_usb_transfer_submit (transfer, FPC1020_TIMEOUT, NULL,
fpi_ssm_usb_transfer_cb, NULL);
break;
case OPEN_BRIDGE_INIT_2:
transfer = fpi_usb_transfer_new (dev);
fpi_usb_transfer_fill_control (transfer,
G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE,
G_USB_DEVICE_REQUEST_TYPE_VENDOR,
G_USB_DEVICE_RECIPIENT_DEVICE,
0xDB, 0x0006, 0x0001, 0);
transfer->ssm = ssm;
fpi_usb_transfer_submit (transfer, FPC1020_TIMEOUT, NULL,
fpi_ssm_usb_transfer_cb, NULL);
break;
case OPEN_INIT_CMD:
{
const struct spi_cmd *cmd = &INIT_CMDS[self->cmd_idx];
start_spi_cmd_subsm (ssm, self, cmd->data, cmd->len);
}
break;
case OPEN_INIT_NEXT:
self->cmd_idx++;
if (self->cmd_idx < FPC1020_NUM_INIT_CMDS)
{
fpi_ssm_jump_to_state (ssm, OPEN_INIT_CMD);
}
else
{
self->init_pass++;
if (self->init_pass < FPC1020_NUM_INIT_PASSES)
{
self->cmd_idx = 0;
fpi_ssm_jump_to_state (ssm, OPEN_INIT_CMD);
}
else
{
fp_dbg ("FPC1020 init complete (%d passes)",
FPC1020_NUM_INIT_PASSES);
fpi_ssm_mark_completed (ssm);
}
}
break;
default:
g_assert_not_reached ();
}
}
static void
open_complete (FpiSsm *ssm, FpDevice *dev, GError *error)
{
FpImageDevice *img_dev = FP_IMAGE_DEVICE (dev);
fpi_image_device_open_complete (img_dev, error);
}
static void
fpc1020_open (FpImageDevice *dev)
{
FpDeviceFpc1020Samsung *self = FPI_DEVICE_FPC1020_SAMSUNG (dev);
FpiSsm *ssm;
GError *error = NULL;
if (!g_usb_device_claim_interface (fpi_device_get_usb_device (FP_DEVICE (dev)),
0, 0, &error))
{
fpi_image_device_open_complete (dev, error);
return;
}
self->top_raw = g_malloc0 (FPC1020_XFER_SIZE);
self->bot_raw = g_malloc0 (FPC1020_XFER_SIZE);
self->init_pass = 0;
self->cmd_idx = 0;
ssm = fpi_ssm_new (FP_DEVICE (dev), open_run_state, OPEN_NUM_STATES);
fpi_ssm_start (ssm, open_complete);
}
static void
fpc1020_close (FpImageDevice *dev)
{
FpDeviceFpc1020Samsung *self = FPI_DEVICE_FPC1020_SAMSUNG (dev);
GError *error = NULL;
g_clear_pointer (&self->top_raw, g_free);
g_clear_pointer (&self->bot_raw, g_free);
g_usb_device_release_interface (fpi_device_get_usb_device (FP_DEVICE (dev)),
0, 0, &error);
fpi_image_device_close_complete (dev, error);
}
static void
fpc1020_activate (FpImageDevice *dev)
{
FpDeviceFpc1020Samsung *self = FPI_DEVICE_FPC1020_SAMSUNG (dev);
self->deactivating = FALSE;
fpi_image_device_activate_complete (dev, NULL);
}
static void
fpc1020_deactivate (FpImageDevice *dev)
{
FpDeviceFpc1020Samsung *self = FPI_DEVICE_FPC1020_SAMSUNG (dev);
self->deactivating = TRUE;
if (self->finger_poll_source)
{
g_source_destroy (self->finger_poll_source);
self->finger_poll_source = NULL;
}
if (!self->ssm_active)
fpi_image_device_deactivate_complete (dev, NULL);
}
enum finger_poll_states {
FPOLL_READ_IRQ,
FPOLL_CHECK_IRQ,
FPOLL_ARM,
FPOLL_BWAIT_1,
FPOLL_BWAIT_2,
FPOLL_BWAIT_3,
FPOLL_BWAIT_4,
FPOLL_REREAD_IRQ,
FPOLL_CHECK_REREAD,
FPOLL_READ_DETAIL,
FPOLL_DONE,
FPOLL_NUM_STATES,
};
static void finger_poll_timer_cb (FpDevice *dev, gpointer user_data);
static void
finger_poll_complete (FpiSsm *ssm, FpDevice *dev, GError *error)
{
FpDeviceFpc1020Samsung *self = FPI_DEVICE_FPC1020_SAMSUNG (dev);
self->ssm_active = FALSE;
if (self->deactivating)
{
fpi_image_device_deactivate_complete (FP_IMAGE_DEVICE (dev), error);
return;
}
if (error)
{
fpi_image_device_session_error (FP_IMAGE_DEVICE (dev), error);
return;
}
if (self->finger_detected)
{
fpi_image_device_report_finger_status (FP_IMAGE_DEVICE (dev), TRUE);
}
else if (self->needs_reinit)
{
self->needs_reinit = FALSE;
start_finger_poll (self);
}
else
{
if (!self->deactivating)
self->finger_poll_source =
fpi_device_add_timeout (FP_DEVICE (self), 100,
finger_poll_timer_cb, NULL, NULL);
}
}
static void
finger_poll_run_state (FpiSsm *ssm, FpDevice *dev)
{
FpDeviceFpc1020Samsung *self = FPI_DEVICE_FPC1020_SAMSUNG (dev);
if (self->deactivating)
{
fpi_ssm_mark_completed (ssm);
return;
}
switch (fpi_ssm_get_cur_state (ssm))
{
case FPOLL_READ_IRQ:
start_spi_cmd_subsm (ssm, self, CMD_IRQ_READ, sizeof (CMD_IRQ_READ));
break;
case FPOLL_CHECK_IRQ:
fp_dbg ("finger poll IRQ: 0x%02x", self->spi_response[1]);
if (self->spi_response[1] == 0x01)
{
fpi_ssm_next_state (ssm);
}
else if (self->spi_response[1] & 0x80)
{
fpi_ssm_jump_to_state (ssm, FPOLL_READ_DETAIL);
}
else
{
fpi_ssm_mark_completed (ssm);
}
break;
case FPOLL_ARM:
start_spi_cmd_subsm (ssm, self, CMD_ARM, sizeof (CMD_ARM));
break;
case FPOLL_BWAIT_1:
do_bridge_status (ssm, dev, bstat_delay_cb);
break;
case FPOLL_BWAIT_2:
do_bridge_status (ssm, dev, bstat_delay_cb);
break;
case FPOLL_BWAIT_3:
do_bridge_status (ssm, dev, bstat_delay_cb);
break;
case FPOLL_BWAIT_4:
do_bridge_status (ssm, dev, bstat_delay_cb);
break;
case FPOLL_REREAD_IRQ:
start_spi_cmd_subsm (ssm, self, CMD_IRQ_READ, sizeof (CMD_IRQ_READ));
break;
case FPOLL_CHECK_REREAD:
fp_dbg ("finger poll re-read IRQ: 0x%02x", self->spi_response[1]);
if (self->spi_response[1] & 0x80)
{
fpi_ssm_next_state (ssm);
}
else
{
fp_dbg ("ARM consumed state, will reinit");
self->needs_reinit = TRUE;
fpi_ssm_mark_completed (ssm);
}
break;
case FPOLL_READ_DETAIL:
start_spi_cmd_subsm (ssm, self, CMD_FINGER_DET,
sizeof (CMD_FINGER_DET));
break;
case FPOLL_DONE:
self->finger_detected = TRUE;
fpi_ssm_mark_completed (ssm);
break;
default:
g_assert_not_reached ();
}
}
static void
finger_poll_timer_cb (FpDevice *dev, gpointer user_data)
{
FpDeviceFpc1020Samsung *self = FPI_DEVICE_FPC1020_SAMSUNG (dev);
FpiSsm *ssm;
self->finger_poll_source = NULL;
if (self->deactivating)
{
fpi_image_device_deactivate_complete (FP_IMAGE_DEVICE (dev), NULL);
return;
}
self->finger_detected = FALSE;
ssm = fpi_ssm_new (dev, finger_poll_run_state, FPOLL_NUM_STATES);
self->ssm_active = TRUE;
fpi_ssm_start (ssm, finger_poll_complete);
}
enum reinit_states {
REINIT_CMD,
REINIT_NEXT,
REINIT_NUM_STATES,
};
static void
reinit_run_state (FpiSsm *ssm, FpDevice *dev)
{
FpDeviceFpc1020Samsung *self = FPI_DEVICE_FPC1020_SAMSUNG (dev);
if (self->deactivating)
{
fpi_ssm_mark_completed (ssm);
return;
}
switch (fpi_ssm_get_cur_state (ssm))
{
case REINIT_CMD:
{
const struct spi_cmd *cmd = &INIT_CMDS[self->cmd_idx];
start_spi_cmd_subsm (ssm, self, cmd->data, cmd->len);
}
break;
case REINIT_NEXT:
self->cmd_idx++;
if (self->cmd_idx < FPC1020_NUM_INIT_CMDS)
{
fpi_ssm_jump_to_state (ssm, REINIT_CMD);
}
else
{
self->init_pass++;
if (self->init_pass < FPC1020_NUM_INIT_PASSES)
{
self->cmd_idx = 0;
fpi_ssm_jump_to_state (ssm, REINIT_CMD);
}
else
{
fp_dbg ("FPC1020 re-init complete");
fpi_ssm_mark_completed (ssm);
}
}
break;
default:
g_assert_not_reached ();
}
}
static void
reinit_complete (FpiSsm *ssm, FpDevice *dev, GError *error)
{
FpDeviceFpc1020Samsung *self = FPI_DEVICE_FPC1020_SAMSUNG (dev);
self->ssm_active = FALSE;
if (self->deactivating)
{
fpi_image_device_deactivate_complete (FP_IMAGE_DEVICE (dev), error);
return;
}
if (error)
{
fpi_image_device_session_error (FP_IMAGE_DEVICE (dev), error);
return;
}
self->finger_poll_source =
fpi_device_add_timeout (FP_DEVICE (self), 100,
finger_poll_timer_cb, NULL, NULL);
}
static void
start_finger_poll (FpDeviceFpc1020Samsung *self)
{
FpiSsm *ssm;
if (self->deactivating)
return;
self->init_pass = 0;
self->cmd_idx = 0;
ssm = fpi_ssm_new (FP_DEVICE (self), reinit_run_state, REINIT_NUM_STATES);
self->ssm_active = TRUE;
fpi_ssm_start (ssm, reinit_complete);
}
static void
img_xfer_check_done (FpDeviceFpc1020Samsung *self)
{
if (g_atomic_int_add (&self->xfer_complete_count, 1) + 1 >= 2)
fpi_ssm_next_state (self->capture_ssm);
}
static void
img_in_cb (FpiUsbTransfer *transfer, FpDevice *dev,
gpointer user_data, GError *error)
{
FpDeviceFpc1020Samsung *self = FPI_DEVICE_FPC1020_SAMSUNG (dev);
guint8 *dest = user_data;
if (error)
{
fpi_ssm_mark_failed (self->capture_ssm, error);
return;
}
memcpy (dest, transfer->buffer,
MIN ((gsize) transfer->actual_length, FPC1020_XFER_SIZE));
fp_dbg ("img IN: %ld bytes", (long) transfer->actual_length);
img_xfer_check_done (self);
}
static void
img_out_cb (FpiUsbTransfer *transfer, FpDevice *dev,
gpointer user_data, GError *error)
{
FpDeviceFpc1020Samsung *self = FPI_DEVICE_FPC1020_SAMSUNG (dev);
if (error)
{
fpi_ssm_mark_failed (self->capture_ssm, error);
return;
}
fp_dbg ("img OUT: %ld bytes", (long) transfer->actual_length);
img_xfer_check_done (self);
}
static void
submit_concurrent_image_xfer (FpiSsm *ssm, FpDevice *dev, guint8 *dest)
{
FpDeviceFpc1020Samsung *self = FPI_DEVICE_FPC1020_SAMSUNG (dev);
FpiUsbTransfer *in_xfer;
FpiUsbTransfer *out_xfer;
guint8 *out_buf;
g_atomic_int_set (&self->xfer_complete_count, 0);
self->capture_ssm = ssm;
/* Submit bulk IN (doesnt work if not ordered? bridge req.) */
in_xfer = fpi_usb_transfer_new (dev);
fpi_usb_transfer_fill_bulk (in_xfer, FPC1020_EP_IN, FPC1020_XFER_SIZE);
fpi_usb_transfer_submit (in_xfer, FPC1020_IMG_TIMEOUT, NULL,
img_in_cb, dest);
/* Submit bulk OUT */
out_buf = g_malloc0 (FPC1020_XFER_SIZE);
out_buf[0] = 0xC4;
out_xfer = fpi_usb_transfer_new (dev);
fpi_usb_transfer_fill_bulk_full (out_xfer, FPC1020_EP_OUT,
out_buf, FPC1020_XFER_SIZE, g_free);
out_xfer->short_is_error = TRUE;
fpi_usb_transfer_submit (out_xfer, FPC1020_IMG_TIMEOUT, NULL,
img_out_cb, NULL);
}
static void
capture_run_state (FpiSsm *ssm, FpDevice *dev)
{
FpDeviceFpc1020Samsung *self = FPI_DEVICE_FPC1020_SAMSUNG (dev);
FpiUsbTransfer *transfer;
if (self->deactivating)
{
fpi_ssm_mark_completed (ssm);
return;
}
switch (fpi_ssm_get_cur_state (ssm))
{
case CAP_CONFIRM_ARM:
start_spi_cmd_subsm (ssm, self, CMD_ARM, sizeof (CMD_ARM));
break;
case CAP_BWAIT_1:
case CAP_BWAIT_2:
case CAP_BWAIT_3:
case CAP_BWAIT_4:
do_bridge_status (ssm, dev, bstat_delay_cb);
break;
case CAP_CONFIRM_IRQ:
start_spi_cmd_subsm (ssm, self, CMD_IRQ_READ, sizeof (CMD_IRQ_READ));
break;
case CAP_CONFIRM_DETAIL:
start_spi_cmd_subsm (ssm, self, CMD_FINGER_DET,
sizeof (CMD_FINGER_DET));
break;
case CAP_FR_OFFSET:
start_spi_cmd_subsm (ssm, self, CMD_FR_OFFSET,
sizeof (CMD_FR_OFFSET));
break;
case CAP_FR_SAMPLE:
start_spi_cmd_subsm (ssm, self, CMD_FR_SAMPLE,
sizeof (CMD_FR_SAMPLE));
break;
case CAP_FR_PXLCTRL:
start_spi_cmd_subsm (ssm, self, CMD_FR_PXLCTRL,
sizeof (CMD_FR_PXLCTRL));
break;
case CAP_FR_WINDOW:
start_spi_cmd_subsm (ssm, self, CMD_FR_WINDOW,
sizeof (CMD_FR_WINDOW));
break;
case CAP_TRIGGER:
self->poll_count = 0;
start_spi_cmd_subsm (ssm, self, CMD_CAPTURE, sizeof (CMD_CAPTURE));
break;
case CAP_POLL_IRQ:
start_spi_cmd_subsm (ssm, self, CMD_IRQ_READ, sizeof (CMD_IRQ_READ));
break;
case CAP_POLL_CHECK:
if (self->spi_response[1] == 0x20)
{
fp_dbg ("Capture ready after %d polls", self->poll_count + 1);
fpi_ssm_jump_to_state (ssm, CAP_TOP_CONFIG);
}
else
{
self->poll_count++;
if (self->poll_count >= FPC1020_POLL_MAX)
{
fpi_ssm_mark_failed (ssm,
fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO,
"Capture not ready after %d polls",
FPC1020_POLL_MAX));
return;
}
fpi_ssm_next_state (ssm);
}
break;
case CAP_POLL_BSTAT:
{
FpiUsbTransfer *xfer = fpi_usb_transfer_new (dev);
fpi_usb_transfer_fill_control (xfer,
G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST,
G_USB_DEVICE_REQUEST_TYPE_VENDOR,
G_USB_DEVICE_RECIPIENT_DEVICE,
0xDA, 0x0007, 0x0000, 2);
xfer->ssm = ssm;
fpi_usb_transfer_submit (xfer, FPC1020_TIMEOUT, NULL,
poll_bstat_cb, NULL);
}
break;
case CAP_TOP_CONFIG:
transfer = fpi_usb_transfer_new (dev);
fpi_usb_transfer_fill_control (transfer,
G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE,
G_USB_DEVICE_REQUEST_TYPE_VENDOR,
G_USB_DEVICE_RECIPIENT_DEVICE,
0xC3, 0x0000, 0x0000,
sizeof (SPI_CONFIG));
memcpy (transfer->buffer, SPI_CONFIG, sizeof (SPI_CONFIG));
transfer->ssm = ssm;
fpi_usb_transfer_submit (transfer, FPC1020_TIMEOUT, NULL,
fpi_ssm_usb_transfer_cb, NULL);
break;
case CAP_TOP_SIZE:
transfer = fpi_usb_transfer_new (dev);
fpi_usb_transfer_fill_control (transfer,
G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE,
G_USB_DEVICE_REQUEST_TYPE_VENDOR,
G_USB_DEVICE_RECIPIENT_DEVICE,
0xCA, 0x0003,
(guint16) FPC1020_XFER_SIZE, 0);
transfer->ssm = ssm;
fpi_usb_transfer_submit (transfer, FPC1020_TIMEOUT, NULL,
fpi_ssm_usb_transfer_cb, NULL);
break;
case CAP_TOP_XFER:
submit_concurrent_image_xfer (ssm, dev, self->top_raw);
break;
case CAP_BOT_CONFIG:
transfer = fpi_usb_transfer_new (dev);
fpi_usb_transfer_fill_control (transfer,
G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE,
G_USB_DEVICE_REQUEST_TYPE_VENDOR,
G_USB_DEVICE_RECIPIENT_DEVICE,
0xC3, 0x0000, 0x0000,
sizeof (SPI_CONFIG));
memcpy (transfer->buffer, SPI_CONFIG, sizeof (SPI_CONFIG));
transfer->ssm = ssm;
fpi_usb_transfer_submit (transfer, FPC1020_TIMEOUT, NULL,
fpi_ssm_usb_transfer_cb, NULL);
break;
case CAP_BOT_SIZE:
transfer = fpi_usb_transfer_new (dev);
fpi_usb_transfer_fill_control (transfer,
G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE,
G_USB_DEVICE_REQUEST_TYPE_VENDOR,
G_USB_DEVICE_RECIPIENT_DEVICE,
0xCA, 0x0003,
(guint16) FPC1020_XFER_SIZE, 0);
transfer->ssm = ssm;
fpi_usb_transfer_submit (transfer, FPC1020_TIMEOUT, NULL,
fpi_ssm_usb_transfer_cb, NULL);
break;
case CAP_BOT_XFER:
submit_concurrent_image_xfer (ssm, dev, self->bot_raw);
break;
/* ── Teardown ── */
case CAP_TEARDOWN_1:
start_spi_cmd_subsm (ssm, self, CMD_ADC_GAIN, sizeof (CMD_ADC_GAIN));
break;
case CAP_TEARDOWN_2:
start_spi_cmd_subsm (ssm, self, CMD_PXL_OFFSET,
sizeof (CMD_PXL_OFFSET));
break;
case CAP_TEARDOWN_3:
start_spi_cmd_subsm (ssm, self, CMD_ADC_GAIN, sizeof (CMD_ADC_GAIN));
break;
case CAP_TEARDOWN_4:
start_spi_cmd_subsm (ssm, self, CMD_PXL_OFFSET,
sizeof (CMD_PXL_OFFSET));
break;
case CAP_TEARDOWN_ARM:
start_spi_cmd_subsm (ssm, self, CMD_ARM, sizeof (CMD_ARM));
break;
case CAP_SUBMIT:
{
FpImage *img = fp_image_new (FPC1020_IMG_W, FPC1020_IMG_H);
memcpy (img->data, self->top_raw + 2, FPC1020_HALF_SIZE);
memcpy (img->data + FPC1020_HALF_SIZE, self->bot_raw + 2,
FPC1020_HALF_SIZE);
/* Needs invert and reverse. */
img->flags = FPI_IMAGE_COLORS_INVERTED | FPI_IMAGE_PARTIAL;
img->ppmm = 20.0;
{
guint8 pmin = 255, pmax = 0;
gulong psum = 0;
int i;
for (i = 0; i < FPC1020_IMG_W * FPC1020_IMG_H; i++)
{
guint8 v = img->data[i];
if (v < pmin) pmin = v;
if (v > pmax) pmax = v;
psum += v;
}
fp_dbg ("Image stats: min=%u max=%u mean=%lu hdr_top=%02x%02x hdr_bot=%02x%02x",
pmin, pmax, psum / (FPC1020_IMG_W * FPC1020_IMG_H),
self->top_raw[0], self->top_raw[1],
self->bot_raw[0], self->bot_raw[1]);
}
fp_dbg ("Image captured (%dx%d)", FPC1020_IMG_W, FPC1020_IMG_H);
fpi_image_device_image_captured (FP_IMAGE_DEVICE (dev), img);
fpi_ssm_mark_completed (ssm);
}
break;
default:
g_assert_not_reached ();
}
}
static void
capture_complete (FpiSsm *ssm, FpDevice *dev, GError *error)
{
FpDeviceFpc1020Samsung *self = FPI_DEVICE_FPC1020_SAMSUNG (dev);
self->ssm_active = FALSE;
self->capture_ssm = NULL;
if (self->deactivating)
{
fpi_image_device_deactivate_complete (FP_IMAGE_DEVICE (dev), error);
return;
}
if (error)
fpi_image_device_session_error (FP_IMAGE_DEVICE (dev), error);
}
static void
start_capture (FpDeviceFpc1020Samsung *self)
{
FpiSsm *ssm;
ssm = fpi_ssm_new (FP_DEVICE (self), capture_run_state, CAP_NUM_STATES);
self->ssm_active = TRUE;
self->capture_ssm = ssm;
fpi_ssm_start (ssm, capture_complete);
}
enum finger_off_states {
FOFF_READ_IRQ,
FOFF_CHECK,
FOFF_NUM_STATES,
};
static void finger_off_poll_complete (FpiSsm *ssm, FpDevice *dev,
GError *error);
static void
finger_off_run_state (FpiSsm *ssm, FpDevice *dev)
{
FpDeviceFpc1020Samsung *self = FPI_DEVICE_FPC1020_SAMSUNG (dev);
if (self->deactivating)
{
fpi_ssm_mark_completed (ssm);
return;
}
switch (fpi_ssm_get_cur_state (ssm))
{
case FOFF_READ_IRQ:
start_spi_cmd_subsm (ssm, self, CMD_IRQ_READ, sizeof (CMD_IRQ_READ));
break;
case FOFF_CHECK:
fp_dbg ("finger off IRQ: 0x%02x", self->spi_response[1]);
if (self->spi_response[1] == 0x00)
{
fpi_ssm_mark_completed (ssm);
}
else
{
fpi_ssm_mark_completed (ssm);
}
break;
default:
g_assert_not_reached ();
}
}
static void finger_off_timer_cb (FpDevice *dev, gpointer user_data);
static void
finger_off_poll_complete (FpiSsm *ssm, FpDevice *dev, GError *error)
{
FpDeviceFpc1020Samsung *self = FPI_DEVICE_FPC1020_SAMSUNG (dev);
self->ssm_active = FALSE;
if (self->deactivating)
{
fpi_image_device_deactivate_complete (FP_IMAGE_DEVICE (dev), error);
return;
}
if (error)
{
fpi_image_device_session_error (FP_IMAGE_DEVICE (dev), error);
return;
}
if (self->spi_response[1] == 0x00)
{
fpi_image_device_report_finger_status (FP_IMAGE_DEVICE (dev), FALSE);
}
else
{
start_finger_off_poll (self);
}
}
static void
finger_off_timer_cb (FpDevice *dev, gpointer user_data)
{
FpDeviceFpc1020Samsung *self = FPI_DEVICE_FPC1020_SAMSUNG (dev);
FpiSsm *ssm;
self->finger_poll_source = NULL;
if (self->deactivating)
{
fpi_image_device_deactivate_complete (FP_IMAGE_DEVICE (dev), NULL);
return;
}
ssm = fpi_ssm_new (dev, finger_off_run_state, FOFF_NUM_STATES);
self->ssm_active = TRUE;
fpi_ssm_start (ssm, finger_off_poll_complete);
}
static void
start_finger_off_poll (FpDeviceFpc1020Samsung *self)
{
if (self->deactivating)
return;
self->finger_poll_source =
fpi_device_add_timeout (FP_DEVICE (self), 100,
finger_off_timer_cb, NULL, NULL);
}
static void
fpc1020_change_state (FpImageDevice *dev, FpiImageDeviceState state)
{
FpDeviceFpc1020Samsung *self = FPI_DEVICE_FPC1020_SAMSUNG (dev);
switch (state)
{
case FPI_IMAGE_DEVICE_STATE_AWAIT_FINGER_ON:
start_finger_poll (self);
break;
case FPI_IMAGE_DEVICE_STATE_CAPTURE:
start_capture (self);
break;
case FPI_IMAGE_DEVICE_STATE_AWAIT_FINGER_OFF:
start_finger_off_poll (self);
break;
case FPI_IMAGE_DEVICE_STATE_INACTIVE:
case FPI_IMAGE_DEVICE_STATE_ACTIVATING:
case FPI_IMAGE_DEVICE_STATE_DEACTIVATING:
case FPI_IMAGE_DEVICE_STATE_IDLE:
break;
}
}
static const FpIdEntry id_table[] = {
{ .vid = 0x04e8, .pid = 0x7301 },
{ .vid = 0, .pid = 0 },
};
static void
fpi_device_fpc1020_samsung_init (FpDeviceFpc1020Samsung *self)
{
}
static void
fpi_device_fpc1020_samsung_class_init (FpDeviceFpc1020SamsungClass *klass)
{
FpDeviceClass *dev_class = FP_DEVICE_CLASS (klass);
FpImageDeviceClass *img_class = FP_IMAGE_DEVICE_CLASS (klass);
dev_class->id = FP_COMPONENT;
dev_class->full_name = "FPC1020 (Samsung USB-SPI Bridge)";
dev_class->type = FP_DEVICE_TYPE_USB;
dev_class->id_table = id_table;
dev_class->scan_type = FP_SCAN_TYPE_PRESS;
img_class->img_width = FPC1020_IMG_W;
img_class->img_height = FPC1020_IMG_H;
img_class->bz3_threshold = 12;
img_class->img_open = fpc1020_open;
img_class->img_close = fpc1020_close;
img_class->activate = fpc1020_activate;
img_class->deactivate = fpc1020_deactivate;
img_class->change_state = fpc1020_change_state;
}